Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
N
nfkit
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
nfkit
Commits
baa9e715
Commit
baa9e715
authored
Feb 13, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
8b1eceb6
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
86 additions
and
17 deletions
+86
-17
src/app-context/app-context.ts
src/app-context/app-context.ts
+27
-17
tests/app-context.spec.ts
tests/app-context.spec.ts
+59
-0
No files found.
src/app-context/app-context.ts
View file @
baa9e715
...
...
@@ -23,7 +23,7 @@ const getMethodDescriptor = (cls: AnyClass, key: PropertyKey) => {
type
ProvideRecord
=
{
classRef
:
AnyClass
;
factory
:
()
=>
any
;
factory
:
(
ctx
:
AppContextCore
<
any
,
any
>
)
=>
any
;
};
type
LoadEntry
=
{
...
...
@@ -39,9 +39,8 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
private
provideRecords
:
ProvideRecord
[]
=
[];
private
registry
=
new
Map
<
string
|
AnyClass
,
LoadEntry
>
();
private
loadSeq
:
LoadEntry
[]
=
[];
private
objectSteps
:
ObjectStep
[]
=
[];
private
started
=
false
;
started
=
false
;
provide
<
C
extends
AppServiceClass
<
Cur
,
Req
>
,
...
...
@@ -71,12 +70,12 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
const
classRef
=
cls
as
unknown
as
AnyClass
;
// Create factory function that will be called during start()
const
factory
=
()
=>
// Create factory function that will be called during start()
with the target ctx.
const
factory
=
(
ctx
:
AppContextCore
<
any
,
any
>
)
=>
options
?.
useValue
??
(
options
?.
useFactory
?
options
.
useFactory
(
this
as
any
,
...
_args
)
:
new
(
options
?.
useClass
??
cls
)(
this
as
any
,
...
_args
));
?
options
.
useFactory
(
ctx
as
any
,
...
_args
)
:
new
(
options
?.
useClass
??
cls
)(
ctx
as
any
,
...
_args
));
// Record the provide configuration
this
.
provideRecords
.
push
({
...
...
@@ -182,6 +181,12 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
for (const ctx of ctxes) {
const other = ctx as any as AppContextCore<any, any>;
if (this.started && !other?.started) {
throw new Error(
'Cannot use an unstarted context into a started context.',
);
}
// Copy provide records
if (Array.isArray(other?.provideRecords)) {
this.provideRecords.push(...other.provideRecords);
...
...
@@ -195,11 +200,9 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
}
}
// If the other context has already started, copy its loaded entries
// If the other context has already started, copy loaded entries only.
// They should remain initialized by the source context and must not be re-initialized.
if (other?.started) {
if (Array.isArray(other?.loadSeq)) {
this.loadSeq.push(...other.loadSeq);
}
if (other?.registry instanceof Map) {
for (const [key, value] of other.registry.entries()) {
this.registry.set(key, value);
...
...
@@ -220,26 +223,33 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
return this as any;
}
const startedEntries: LoadEntry[] = [];
const preloadedKeys = new Set(this.registry.keys());
// Create all instances
for (const record of this.provideRecords) {
const inst = record.factory();
if (preloadedKeys.has(record.classRef)) {
continue;
}
const inst = record.factory(this);
const entry: LoadEntry = {
classRef: record.classRef,
inst,
};
this.registry.set(record.classRef, entry);
this.loadSeq
.push(entry);
startedEntries
.push(entry);
}
// Resolve all promises
for (const entry of
this.loadSeq
) {
// Resolve all promises
created in this start().
for (const entry of
startedEntries
) {
if (entry.inst && typeof entry.inst.then === 'function') {
entry.inst = await entry.inst;
}
}
//
Call init on all instances
for (const entry of
this.loadSeq
) {
//
Init only instances created in this start().
for (const entry of
startedEntries
) {
const inst = entry.inst;
if (inst && typeof inst.init === 'function') {
await inst.init();
...
...
tests/app-context.spec.ts
View file @
baa9e715
...
...
@@ -41,6 +41,28 @@ class InitLogService {
}
}
class
InitCounterService
{
constructor
(
public
ctx
:
AppContext
,
private
counter
:
{
value
:
number
},
)
{}
async
init
()
{
this
.
counter
.
value
+=
1
;
}
}
class
NeedsMergedMethodService
{
value
:
number
;
constructor
(
public
ctx
:
AppContext
)
{
if
(
typeof
(
ctx
as
any
).
inc
!==
'
function
'
)
{
throw
new
Error
(
'
missing merged method: inc
'
);
}
this
.
value
=
(
ctx
as
any
).
inc
();
}
}
describe
(
'
app-context runtime
'
,
()
=>
{
test
(
'
provide + merge(method) binds this correctly
'
,
async
()
=>
{
const
ctx
=
await
createAppContext
()
...
...
@@ -133,6 +155,43 @@ describe('app-context runtime', () => {
expect
(
logs
).
toContain
(
'
init:B
'
);
expect
(
logs
.
indexOf
(
'
init:A
'
)).
toBeLessThan
(
logs
.
indexOf
(
'
init:B
'
));
});
test
(
'
started context cannot use unstarted context
'
,
async
()
=>
{
const
started
=
await
createAppContext
().
define
().
start
();
const
unstarted
=
createAppContext
()
.
provide
(
CounterService
,
1
,
{
provide
:
'
counter
'
})
.
define
();
expect
(()
=>
started
.
use
(
unstarted
)).
toThrow
(
'
Cannot use an unstarted context into a started context.
'
,
);
});
test
(
'
using started context does not re-init imported providers
'
,
async
()
=>
{
const
counter
=
{
value
:
0
};
const
startedChild
=
await
createAppContext
()
.
provide
(
InitCounterService
,
counter
)
.
define
()
.
start
();
expect
(
counter
.
value
).
toBe
(
1
);
const
root
=
await
createAppContext
().
use
(
startedChild
).
define
().
start
();
expect
(
counter
.
value
).
toBe
(
1
);
expect
(
root
.
get
(
InitCounterService
)).
toBe
(
startedChild
.
get
(
InitCounterService
));
});
test
(
'
provider from used context can access merged members on target context
'
,
async
()
=>
{
const
parent
=
createAppContext
()
.
provide
(
CounterService
,
10
,
{
merge
:
[
'
inc
'
]
})
.
define
();
const
child
=
createAppContext
()
.
provide
(
NeedsMergedMethodService
,
{
provide
:
'
needsMerged
'
})
.
define
();
const
root
=
await
createAppContext
().
use
(
parent
).
use
(
child
).
define
().
start
();
expect
(
root
.
needsMerged
.
value
).
toBe
(
11
);
});
});
describe
(
'
app-context type checks
'
,
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment