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
9263e94b
Commit
9263e94b
authored
Feb 13, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix app-context load
parent
618ed3b9
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
96 additions
and
264 deletions
+96
-264
src/app-context/app-context.ts
src/app-context/app-context.ts
+71
-173
src/app-context/promise-utils.ts
src/app-context/promise-utils.ts
+0
-67
tests/app-context.spec.ts
tests/app-context.spec.ts
+25
-24
No files found.
src/app-context/app-context.ts
View file @
9263e94b
This diff is collapsed.
Click to expand it.
src/app-context/promise-utils.ts
deleted
100644 → 0
View file @
618ed3b9
import
{
dualizeAny
,
throwDualPending
}
from
'
../dual-object
'
;
type
PromiseState
<
T
=
any
>
=
|
{
status
:
'
pending
'
;
value
?:
undefined
;
error
?:
undefined
}
|
{
status
:
'
fulfilled
'
;
value
:
T
;
error
?:
undefined
}
|
{
status
:
'
rejected
'
;
value
?:
undefined
;
error
:
any
};
const
promiseStates
=
new
WeakMap
<
Promise
<
any
>
,
PromiseState
>
();
export
const
isPromiseLike
=
(
value
:
any
):
value
is
Promise
<
any
>
=>
!!
value
&&
typeof
value
.
then
===
'
function
'
;
export
const
trackPromise
=
<
T
>
(
promise
:
Promise
<
T
>
):
PromiseState
<
T
>
=>
{
const
existing
=
promiseStates
.
get
(
promise
);
if
(
existing
)
return
existing
as
PromiseState
<
T
>
;
const
state
=
{
status
:
'
pending
'
}
as
PromiseState
<
T
>
;
promiseStates
.
set
(
promise
,
state
);
promise
.
then
(
(
value
)
=>
{
(
state
as
any
).
status
=
'
fulfilled
'
;
(
state
as
any
).
value
=
value
;
},
(
error
)
=>
{
(
state
as
any
).
status
=
'
rejected
'
;
(
state
as
any
).
error
=
error
;
},
);
return
state
;
};
export
const
wrapMaybePromise
=
<
T
>
(
value
:
T
,
options
?:
{
methodKeys
?:
Iterable
<
PropertyKey
>
},
):
T
=>
{
if
(
!
isPromiseLike
(
value
))
return
value
;
const
promise
=
Promise
.
resolve
(
value
);
const
state
=
trackPromise
(
promise
);
if
(
state
.
status
===
'
fulfilled
'
)
return
state
.
value
;
if
(
state
.
status
===
'
rejected
'
)
throw
state
.
error
;
return
dualizeAny
<
T
>
(
()
=>
{
const
current
=
trackPromise
(
promise
);
if
(
current
.
status
===
'
fulfilled
'
)
return
current
.
value
;
if
(
current
.
status
===
'
rejected
'
)
throw
current
.
error
;
throwDualPending
();
},
()
=>
promise
,
{
// Intentionally hide strict method return type here.
asyncMethods
:
Array
.
from
(
options
?.
methodKeys
??
[])
as
any
,
},
);
};
export
const
createAsyncMethod
=
(
inst
:
any
,
key
:
PropertyKey
)
=>
(...
args
:
any
[])
=>
Promise
.
resolve
(
inst
).
then
((
resolved
)
=>
{
const
fn
=
resolved
?.[
key
];
if
(
typeof
fn
!==
'
function
'
)
{
throw
new
TypeError
(
'
Target method is not a function
'
);
}
return
fn
.
apply
(
resolved
,
args
);
});
tests/app-context.spec.ts
View file @
9263e94b
...
@@ -42,18 +42,19 @@ class InitLogService {
...
@@ -42,18 +42,19 @@ class InitLogService {
}
}
describe
(
'
app-context runtime
'
,
()
=>
{
describe
(
'
app-context runtime
'
,
()
=>
{
test
(
'
provide + merge(method) binds this correctly
'
,
()
=>
{
test
(
'
provide + merge(method) binds this correctly
'
,
async
()
=>
{
const
ctx
=
createAppContext
()
const
ctx
=
await
createAppContext
()
.
provide
(
CounterService
,
1
,
{
merge
:
[
'
inc
'
]
})
.
provide
(
CounterService
,
1
,
{
merge
:
[
'
inc
'
]
})
.
define
();
.
define
()
.
start
();
const
inc
=
ctx
.
inc
;
const
inc
=
ctx
.
inc
;
expect
(
inc
()).
toBe
(
2
);
expect
(
inc
()).
toBe
(
2
);
expect
(
inc
()).
toBe
(
3
);
expect
(
inc
()).
toBe
(
3
);
});
});
test
(
'
provide getter on pending service: method call works and normal field throws
'
,
async
()
=>
{
test
(
'
async factory is resolved during start()
'
,
async
()
=>
{
const
ctx
=
createAppContext
()
const
ctx
=
await
createAppContext
()
.
provide
(
CounterService
,
5
,
{
.
provide
(
CounterService
,
5
,
{
provide
:
'
counter
'
,
provide
:
'
counter
'
,
useFactory
:
async
(
self
,
...
args
:
unknown
[])
=>
{
useFactory
:
async
(
self
,
...
args
:
unknown
[])
=>
{
...
@@ -62,17 +63,16 @@ describe('app-context runtime', () => {
...
@@ -62,17 +63,16 @@ describe('app-context runtime', () => {
return
new
CounterService
(
self
,
initial
as
number
);
return
new
CounterService
(
self
,
initial
as
number
);
},
},
})
})
.
define
();
.
define
()
.
start
();
const
p
=
ctx
.
counter
.
ping
(
3
);
// After start(), all async services are resolved
expect
(()
=>
ctx
.
counter
.
value
).
toThrow
(
expect
(
ctx
.
counter
.
value
).
toBe
(
5
);
new
TypeError
(
'
Value is not ready yet. Please await it first.
'
),
await
expect
(
ctx
.
counter
.
ping
(
3
)).
resolves
.
toBe
(
8
);
);
await
expect
(
p
).
resolves
.
toBe
(
8
);
});
});
test
(
'
merge(property)
set is queued before resolve and flushed after getAsync
'
,
async
()
=>
{
test
(
'
merge(property)
can be set after start()
'
,
async
()
=>
{
const
ctx
=
createAppContext
()
const
ctx
=
await
createAppContext
()
.
provide
(
AsyncMutableService
,
{
.
provide
(
AsyncMutableService
,
{
merge
:
[
'
count
'
],
merge
:
[
'
count
'
],
useFactory
:
async
(
self
)
=>
{
useFactory
:
async
(
self
)
=>
{
...
@@ -80,12 +80,12 @@ describe('app-context runtime', () => {
...
@@ -80,12 +80,12 @@ describe('app-context runtime', () => {
return
new
AsyncMutableService
(
self
);
return
new
AsyncMutableService
(
self
);
},
},
})
})
.
define
();
.
define
()
.
start
();
// After start(), properties can be set directly
ctx
.
count
=
42
;
ctx
.
count
=
42
;
await
expect
(
ctx
.
getAsync
(
AsyncMutableService
)).
resolves
.
toMatchObject
({
expect
(
ctx
.
get
(
AsyncMutableService
).
count
).
toBe
(
42
);
count
:
42
,
});
expect
(
ctx
.
count
).
toBe
(
42
);
expect
(
ctx
.
count
).
toBe
(
42
);
});
});
...
@@ -93,10 +93,10 @@ describe('app-context runtime', () => {
...
@@ -93,10 +93,10 @@ describe('app-context runtime', () => {
const
ctx1
=
createAppContext
()
const
ctx1
=
createAppContext
()
.
provide
(
CounterService
,
7
,
{
provide
:
'
counter
'
,
merge
:
[
'
inc
'
]
})
.
provide
(
CounterService
,
7
,
{
provide
:
'
counter
'
,
merge
:
[
'
inc
'
]
})
.
define
();
.
define
();
const
root
=
createAppContext
().
use
(
ctx1
).
define
();
const
root
=
await
createAppContext
().
use
(
ctx1
).
define
().
start
();
expect
(
root
.
counter
.
inc
()).
toBe
(
8
);
expect
(
root
.
counter
.
inc
()).
toBe
(
8
);
await
expect
(
root
.
getAsync
(
CounterService
)).
resolves
.
toMatchObject
({
expect
(
root
.
get
(
CounterService
))
.
toMatchObject
({
value
:
8
,
value
:
8
,
});
});
});
});
...
@@ -136,10 +136,11 @@ describe('app-context runtime', () => {
...
@@ -136,10 +136,11 @@ describe('app-context runtime', () => {
});
});
describe
(
'
app-context type checks
'
,
()
=>
{
describe
(
'
app-context type checks
'
,
()
=>
{
test
(
'
compile-time type assertions
'
,
()
=>
{
test
(
'
compile-time type assertions
'
,
async
()
=>
{
const
ctx
=
createAppContext
()
const
ctx
=
await
createAppContext
()
.
provide
(
CounterService
,
1
,
{
provide
:
'
counter
'
,
merge
:
[
'
inc
'
]
})
.
provide
(
CounterService
,
1
,
{
provide
:
'
counter
'
,
merge
:
[
'
inc
'
]
})
.
define
();
.
define
()
.
start
();
const
n
:
number
=
ctx
.
counter
.
inc
();
const
n
:
number
=
ctx
.
counter
.
inc
();
expect
(
n
).
toBe
(
2
);
expect
(
n
).
toBe
(
2
);
...
@@ -158,11 +159,11 @@ describe('app-context type checks', () => {
...
@@ -158,11 +159,11 @@ describe('app-context type checks', () => {
expect
(
ok
).
toBe
(
true
);
expect
(
ok
).
toBe
(
true
);
});
});
test
(
'
use merges context types
'
,
()
=>
{
test
(
'
use merges context types
'
,
async
()
=>
{
const
a
=
createAppContext
()
const
a
=
createAppContext
()
.
provide
(
CounterService
,
1
,
{
provide
:
'
counter
'
})
.
provide
(
CounterService
,
1
,
{
provide
:
'
counter
'
})
.
define
();
.
define
();
const
b
=
createAppContext
().
use
(
a
).
define
();
const
b
=
await
createAppContext
().
use
(
a
).
define
().
start
();
const
v
:
number
=
b
.
counter
.
value
;
const
v
:
number
=
b
.
counter
.
value
;
expect
(
v
).
toBe
(
1
);
expect
(
v
).
toBe
(
1
);
});
});
...
...
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