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
bfd03193
Commit
bfd03193
authored
Feb 08, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add AppContext
parent
c584a517
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
245 additions
and
19 deletions
+245
-19
src/app-context/app-context.ts
src/app-context/app-context.ts
+228
-12
src/app-context/index.ts
src/app-context/index.ts
+1
-1
src/app-context/types.ts
src/app-context/types.ts
+3
-3
src/dual-object.ts
src/dual-object.ts
+13
-3
No files found.
src/app-context/app-context.ts
View file @
bfd03193
import
{
Empty
,
Prettif
y
}
from
'
../types
'
;
import
{
AnyClass
,
Empt
y
}
from
'
../types
'
;
import
{
import
{
AppContext
,
AppContext
,
AppServiceClass
,
AppServiceClass
,
...
@@ -7,49 +7,254 @@ import {
...
@@ -7,49 +7,254 @@ import {
AppProvideOptions
,
AppProvideOptions
,
AppContextUsed
,
AppContextUsed
,
}
from
'
./types
'
;
}
from
'
./types
'
;
import
{
createAsyncMethod
,
isPromiseLike
,
trackPromise
,
wrapMaybePromise
,
}
from
'
./promise-utils
'
;
const
ServiceClassPrefix
=
'
class:
'
;
const
ProvidePrefix
=
'
provide:
'
;
const
ProvidePrefix
=
'
provide:
'
;
const
getMethodDescriptor
=
(
cls
:
AnyClass
,
key
:
PropertyKey
)
=>
{
let
proto
=
cls
.
prototype
;
while
(
proto
&&
proto
!==
Object
.
prototype
)
{
const
desc
=
Object
.
getOwnPropertyDescriptor
(
proto
,
key
as
any
);
if
(
desc
)
return
desc
;
proto
=
Object
.
getPrototypeOf
(
proto
);
}
return
undefined
;
};
type
LoadEntry
=
{
classRef
:
AnyClass
;
inst
:
any
;
methodKeys
:
Set
<
PropertyKey
>
;
pendingSets
:
Array
<
{
key
:
PropertyKey
;
value
:
any
}
>
;
};
type
ObjectStep
=
(
ctx
:
AppContextCore
<
any
,
any
>
)
=>
void
;
const
flushPendingSets
=
(
entry
:
LoadEntry
)
=>
{
if
(
isPromiseLike
(
entry
.
inst
))
return
;
if
(
!
entry
.
pendingSets
.
length
)
return
;
for
(
const
item
of
entry
.
pendingSets
)
{
(
entry
.
inst
as
any
)[
item
.
key
]
=
item
.
value
;
}
entry
.
pendingSets
.
length
=
0
;
};
const
resolveEntryIfNeeded
=
async
(
entry
:
LoadEntry
)
=>
{
if
(
isPromiseLike
(
entry
.
inst
))
{
entry
.
inst
=
await
entry
.
inst
;
}
flushPendingSets
(
entry
);
return
entry
.
inst
;
};
export
class
AppContextCore
<
Cur
=
Empty
,
Req
=
Empty
>
{
export
class
AppContextCore
<
Cur
=
Empty
,
Req
=
Empty
>
{
private
__current
:
Cur
;
private
__current
:
Cur
;
private
__required
:
Req
;
private
__required
:
Req
;
registry
=
new
Map
<
string
,
any
>
();
registry
=
new
Map
<
string
|
AnyClass
,
LoadEntry
>
();
loadSeq
:
any
[]
=
[];
loadSeq
:
LoadEntry
[]
=
[];
objectSteps
:
ObjectStep
[]
=
[];
provide
<
provide
<
C
extends
AppServiceClass
<
Cur
>
,
C
extends
AppServiceClass
<
Cur
,
Req
>
,
const
P
extends
string
=
''
,
const
P
extends
string
=
''
,
const
M
extends
(
keyof
InstanceType
<
C
>
)[]
=
[],
const
M
extends
(
keyof
InstanceType
<
C
>
)[]
=
[],
>
(
>
(
cls
:
C
,
cls
:
C
,
...
args
:
AppProvideArgs
<
Cur
,
Req
,
C
,
P
,
M
>
...
args
:
AppProvideArgs
<
Cur
,
Req
,
C
,
P
,
M
>
):
AppProvidedMerged
<
Cur
,
Req
,
C
,
P
,
M
>
{
):
AppProvidedMerged
<
Cur
,
Req
,
C
,
P
,
M
>
{
const
options
=
args
[
args
.
length
-
1
]
as
const
last
=
args
[
args
.
length
-
1
]
as
any
;
const
hasOptions
=
!!
last
&&
typeof
last
===
'
object
'
&&
(
'
provide
'
in
last
||
'
merge
'
in
last
||
'
useValue
'
in
last
||
'
useFactory
'
in
last
||
'
useClass
'
in
last
);
const
options
=
(
hasOptions
?
last
:
undefined
)
as
|
AppProvideOptions
<
Cur
,
Req
,
C
,
P
,
M
>
|
AppProvideOptions
<
Cur
,
Req
,
C
,
P
,
M
>
|
undefined
;
|
undefined
;
const
_args
:
ConstructorParameters
<
C
>
=
args
;
const
_args
=
(
hasOptions
?
args
.
slice
(
0
,
-
1
)
:
args
)
as
ConstructorParameters
<
C
>
;
const
inst
=
const
inst
=
options
?.
useValue
??
options
?.
useValue
??
(
options
?.
useFactory
(
options
?.
useFactory
?
options
.
useFactory
(
this
as
any
,
...
_args
)
?
options
.
useFactory
(
this
as
any
,
...
_args
)
:
new
(
options
?.
useClass
??
cls
)(
this
as
any
,
_args
));
:
new
(
options
?.
useClass
??
cls
)(
this
as
any
,
...
_args
));
this
.
registry
.
set
(
ServiceClassPrefix
+
cls
.
name
,
inst
);
const
classRef
=
cls
as
unknown
as
AnyClass
;
const
provideKey
=
options
?.
provide
?
ProvidePrefix
+
String
(
options
.
provide
)
:
undefined
;
const
methodKeys
=
new
Set
<
PropertyKey
>
();
for
(
const
name
of
Object
.
getOwnPropertyNames
(
cls
.
prototype
))
{
if
(
name
===
'
constructor
'
)
continue
;
const
desc
=
Object
.
getOwnPropertyDescriptor
(
cls
.
prototype
,
name
);
if
(
desc
&&
typeof
desc
.
value
===
'
function
'
)
{
methodKeys
.
add
(
name
);
}
}
const
entry
:
LoadEntry
=
{
classRef
,
inst
,
methodKeys
,
pendingSets
:
[],
};
this
.
registry
.
set
(
classRef
,
entry
);
if
(
provideKey
)
this
.
registry
.
set
(
provideKey
,
entry
);
this
.
loadSeq
.
push
(
entry
);
if
(
options
?.
provide
)
{
const
prop
=
options
.
provide
;
const
step
:
ObjectStep
=
(
ctx
)
=>
{
Object
.
defineProperty
(
ctx
,
prop
,
{
enumerable
:
true
,
configurable
:
true
,
get
:
()
=>
{
const
currentEntry
=
ctx
.
registry
.
get
(
classRef
);
return
wrapMaybePromise
(
currentEntry
?.
inst
,
{
methodKeys
});
},
});
};
step
(
this
);
this
.
objectSteps
.
push
(
step
);
}
if
(
options
?.
merge
?.
length
)
{
for
(
const
key
of
options
.
merge
)
{
const
desc
=
getMethodDescriptor
(
cls
,
key
);
const
isMethod
=
!!
desc
&&
typeof
desc
.
value
===
'
function
'
;
const
step
:
ObjectStep
=
(
ctx
)
=>
{
if
(
isMethod
)
{
Object
.
defineProperty
(
ctx
,
key
,
{
enumerable
:
true
,
configurable
:
true
,
get
:
()
=>
{
const
currentEntry
=
ctx
.
registry
.
get
(
classRef
);
const
currentInst
=
currentEntry
?.
inst
;
if
(
isPromiseLike
(
currentInst
))
return
createAsyncMethod
(
currentInst
,
key
);
const
fn
=
(
currentInst
as
any
)?.[
key
];
return
typeof
fn
===
'
function
'
?
fn
.
bind
(
currentInst
)
:
fn
;
},
});
return
;
}
Object
.
defineProperty
(
ctx
,
key
,
{
enumerable
:
true
,
configurable
:
true
,
get
:
()
=>
{
const
currentEntry
=
ctx
.
registry
.
get
(
classRef
);
const
target
=
wrapMaybePromise
(
currentEntry
?.
inst
);
return
(
target
as
any
)?.[
key
];
},
set
:
(
value
:
any
)
=>
{
const
currentEntry
=
ctx
.
registry
.
get
(
classRef
);
if
(
!
currentEntry
)
return
;
if
(
!
isPromiseLike
(
currentEntry
.
inst
))
{
(
currentEntry
.
inst
as
any
)[
key
]
=
value
;
return
;
}
const
state
=
trackPromise
(
currentEntry
.
inst
);
if
(
state
.
status
===
'
fulfilled
'
)
{
currentEntry
.
inst
=
state
.
value
;
flushPendingSets
(
currentEntry
);
(
currentEntry
.
inst
as
any
)[
key
]
=
value
;
return
;
}
if
(
state
.
status
===
'
rejected
'
)
throw
state
.
error
;
currentEntry
.
pendingSets
.
push
({
key
,
value
});
},
});
};
step
(
this
);
this
.
objectSteps
.
push
(
step
);
}
}
return
this
as
any
;
return
this
as
any
;
}
}
get
<
R
>
(
cls
:
AppServiceClass
<
Cur
,
Req
,
any
,
R
>
):
R
{}
get
<
R
>
(
cls
:
AppServiceClass
<
Cur
,
Req
,
any
,
R
>
):
R
{
const
key
=
cls
as
unknown
as
AnyClass
;
if
(
!
this
.
registry
.
has
(
key
))
{
throw
new
Error
(
`Service not provided:
${
cls
.
name
}
`
);
}
const
entry
=
this
.
registry
.
get
(
key
)
!
;
const
inst
=
entry
.
inst
;
if
(
isPromiseLike
(
inst
))
{
const
state
=
trackPromise
(
inst
);
if
(
state
.
status
===
'
fulfilled
'
)
{
entry
.
inst
=
state
.
value
;
flushPendingSets
(
entry
);
return
state
.
value
as
any
;
}
if
(
state
.
status
===
'
rejected
'
)
throw
state
.
error
;
return
wrapMaybePromise
(
inst
,
{
methodKeys
:
entry
.
methodKeys
,
})
as
any
;
}
flushPendingSets
(
entry
);
return
inst
as
any
;
}
getAsync
<
R
>
(
cls
:
AppServiceClass
<
Cur
,
Req
,
any
,
R
>
):
Promise
<
R
>
{}
async
getAsync
<
R
>
(
cls
:
AppServiceClass
<
Cur
,
Req
,
any
,
R
>
):
Promise
<
R
>
{
const
key
=
cls
as
unknown
as
AnyClass
;
if
(
!
this
.
registry
.
has
(
key
))
{
throw
new
Error
(
`Service not provided:
${
cls
.
name
}
`
);
}
const
entry
=
this
.
registry
.
get
(
key
)
!
;
const
resolved
=
await
resolveEntryIfNeeded
(
entry
);
return
resolved
as
R
;
}
use
<
const
Ctxes
extends
AppContext
<
any
,
any
>
[]
>
(
use
<
const
Ctxes
extends
AppContext
<
any
,
any
>
[]
>
(
...
ctxes
:
Ctxes
...
ctxes
:
Ctxes
):
AppContextUsed
<
Cur
,
Req
,
Ctxes
>
{
):
AppContextUsed
<
Cur
,
Req
,
Ctxes
>
{
for
(
const
ctx
of
ctxes
)
{
const
other
=
ctx
as
any
as
AppContextCore
<
any
,
any
>
;
const
entryMap
=
new
Map
<
LoadEntry
,
LoadEntry
>
();
if
(
Array
.
isArray
(
other
?.
loadSeq
))
{
const
copiedSeq
=
other
.
loadSeq
.
map
((
item
)
=>
({
...
item
,
methodKeys
:
new
Set
(
item
.
methodKeys
??
[]),
pendingSets
:
[...(
item
.
pendingSets
??
[])],
}));
other
.
loadSeq
.
forEach
((
item
,
index
)
=>
{
entryMap
.
set
(
item
,
copiedSeq
[
index
]);
});
this
.
loadSeq
.
push
(...
copiedSeq
);
}
if
(
other
?.
registry
instanceof
Map
)
{
for
(
const
[
key
,
value
]
of
other
.
registry
.
entries
())
{
this
.
registry
.
set
(
key
,
entryMap
.
get
(
value
)
??
value
);
}
}
if
(
Array
.
isArray
(
other
?.
objectSteps
))
{
this
.
objectSteps
.
push
(...
other
.
objectSteps
);
for
(
const
step
of
other
.
objectSteps
)
{
step
(
this
);
}
}
}
return
this
as
any
;
return
this
as
any
;
}
}
...
@@ -58,6 +263,17 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
...
@@ -58,6 +263,17 @@ export class AppContextCore<Cur = Empty, Req = Empty> {
}
}
async
start
():
Promise
<
Empty
extends
Req
?
AppContext
<
Cur
,
Req
>
:
never
>
{
async
start
():
Promise
<
Empty
extends
Req
?
AppContext
<
Cur
,
Req
>
:
never
>
{
for
(
const
entry
of
this
.
loadSeq
)
{
await
resolveEntryIfNeeded
(
entry
);
}
for
(
const
entry
of
this
.
loadSeq
)
{
const
inst
=
entry
.
inst
;
if
(
inst
&&
typeof
inst
.
init
===
'
function
'
)
{
await
inst
.
init
();
}
}
return
this
as
any
;
return
this
as
any
;
}
}
}
}
...
...
src/app-context/index.ts
View file @
bfd03193
export
*
from
'
./types
'
;
export
type
*
from
'
./types
'
;
export
*
from
'
./app-context
'
;
export
*
from
'
./app-context
'
;
export
*
from
'
./app-service-base
'
;
export
*
from
'
./app-service-base
'
;
src/app-context/types.ts
View file @
bfd03193
...
@@ -17,7 +17,7 @@ export type AppServiceClass<
...
@@ -17,7 +17,7 @@ export type AppServiceClass<
Req
=
Empty
,
Req
=
Empty
,
A
extends
any
[]
=
any
[],
A
extends
any
[]
=
any
[],
R
=
any
,
R
=
any
,
>
=
new
(
ctx
:
AppContext
<
Cur
,
Req
>
,
...
args
:
A
)
=>
R
;
>
=
new
(
ctx
:
AppContext
<
Cur
AndReq
<
Cur
,
Req
>
>
,
...
args
:
A
)
=>
R
;
export
type
AppServiceConfig
<
C
extends
AppServiceClass
>
=
C
extends
new
(
export
type
AppServiceConfig
<
C
extends
AppServiceClass
>
=
C
extends
new
(
first
:
any
,
first
:
any
,
...
@@ -29,7 +29,7 @@ export type AppServiceConfig<C extends AppServiceClass> = C extends new (
...
@@ -29,7 +29,7 @@ export type AppServiceConfig<C extends AppServiceClass> = C extends new (
export
type
AppProvideOptions
<
export
type
AppProvideOptions
<
Cur
,
Cur
,
Req
,
Req
,
C
extends
AppServiceClass
<
Cur
>
,
C
extends
AppServiceClass
<
Cur
,
Req
>
,
P
extends
string
,
P
extends
string
,
M
extends
(
keyof
InstanceType
<
C
>
)[],
M
extends
(
keyof
InstanceType
<
C
>
)[],
>
=
{
>
=
{
...
@@ -46,7 +46,7 @@ export type AppProvideOptions<
...
@@ -46,7 +46,7 @@ export type AppProvideOptions<
export
type
AppProvideArgs
<
export
type
AppProvideArgs
<
Cur
,
Cur
,
Req
,
Req
,
C
extends
AppServiceClass
<
Cur
>
,
C
extends
AppServiceClass
<
Cur
,
Req
>
,
P
extends
string
,
P
extends
string
,
M
extends
(
keyof
InstanceType
<
C
>
)[],
M
extends
(
keyof
InstanceType
<
C
>
)[],
>
=
[
>
=
[
...
...
src/dual-object.ts
View file @
bfd03193
// dual-unified.ts
// dual-unified.ts
export
type
Dual
<
T
>
=
T
&
PromiseLike
<
T
>
;
export
type
Dual
<
T
>
=
T
&
PromiseLike
<
T
>
;
export
const
DUAL_PENDING
=
Symbol
(
'
dual.pending
'
);
export
const
throwDualPending
=
():
never
=>
{
throw
DUAL_PENDING
;
};
type
ThenKey
=
'
then
'
|
'
catch
'
|
'
finally
'
;
type
ThenKey
=
'
then
'
|
'
catch
'
|
'
finally
'
;
const
isThenKey
=
(
k
:
PropertyKey
):
k
is
ThenKey
=>
const
isThenKey
=
(
k
:
PropertyKey
):
k
is
ThenKey
=>
...
@@ -7,14 +11,13 @@ const isThenKey = (k: PropertyKey): k is ThenKey =>
...
@@ -7,14 +11,13 @@ const isThenKey = (k: PropertyKey): k is ThenKey =>
type
State
=
'
undecided
'
|
'
pending
'
|
'
fulfilled
'
|
'
rejected
'
;
type
State
=
'
undecided
'
|
'
pending
'
|
'
fulfilled
'
|
'
rejected
'
;
/** 仅允许填入 “返回 Promise 的方法名” */
export
type
AsyncMethodKeys
<
T
>
=
{
export
type
AsyncMethodKeys
<
T
>
=
{
[
K
in
keyof
T
]
-
?:
T
[
K
]
extends
(...
args
:
any
[])
=>
Promise
<
any
>
?
K
:
never
;
[
K
in
keyof
T
]
-
?:
T
[
K
]
extends
(...
args
:
any
[])
=>
Promise
<
any
>
?
K
:
never
;
}[
keyof
T
];
}[
keyof
T
];
export
interface
DualizeOptions
<
T
>
{
export
interface
DualizeOptions
<
T
>
{
/** 这些方法在 undecided/pending 时会返回
一个
延迟执行函数,等待对象 Promise 完成后再调用 */
/** 这些方法在 undecided/pending 时会返回延迟执行函数,等待对象 Promise 完成后再调用 */
asyncMethods
?:
readonly
AsyncMethodKeys
<
T
>
[];
asyncMethods
?:
readonly
(
keyof
T
)
[];
}
}
export
function
dualizeAny
<
T
>
(
export
function
dualizeAny
<
T
>
(
...
@@ -59,6 +62,10 @@ export function dualizeAny<T>(
...
@@ -59,6 +62,10 @@ export function dualizeAny<T>(
value
=
sync
();
value
=
sync
();
state
=
'
fulfilled
'
;
state
=
'
fulfilled
'
;
}
catch
(
e
)
{
}
catch
(
e
)
{
if
(
e
===
DUAL_PENDING
)
{
startAsync
();
return
;
}
reason
=
e
;
reason
=
e
;
state
=
'
rejected
'
;
state
=
'
rejected
'
;
}
}
...
@@ -139,6 +146,9 @@ export function dualizeAny<T>(
...
@@ -139,6 +146,9 @@ export function dualizeAny<T>(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// @ts-expect-error
if
(
state
===
'
fulfilled
'
)
return
getFrom
(
value
,
prop
);
if
(
state
===
'
fulfilled
'
)
return
getFrom
(
value
,
prop
);
if
((
state
as
State
)
===
'
pending
'
)
{
throw
new
TypeError
(
'
Value is not ready yet. Please await it first.
'
);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// @ts-expect-error
if
(
state
===
'
rejected
'
)
throw
reason
;
if
(
state
===
'
rejected
'
)
throw
reason
;
...
...
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