@noxfly/noxus 2.5.0 → 3.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +405 -340
- package/dist/app-injector-Bz3Upc0y.d.mts +125 -0
- package/dist/app-injector-Bz3Upc0y.d.ts +125 -0
- package/dist/child.d.mts +157 -23
- package/dist/child.d.ts +157 -23
- package/dist/child.js +1111 -1341
- package/dist/child.mjs +1086 -1294
- package/dist/main.d.mts +720 -284
- package/dist/main.d.ts +720 -284
- package/dist/main.js +1471 -1650
- package/dist/main.mjs +1409 -1559
- package/dist/preload.d.mts +28 -0
- package/dist/preload.d.ts +28 -0
- package/dist/preload.js +95 -0
- package/dist/preload.mjs +70 -0
- package/dist/renderer.d.mts +159 -22
- package/dist/renderer.d.ts +159 -22
- package/dist/renderer.js +104 -177
- package/dist/renderer.mjs +100 -172
- package/dist/request-BlTtiHbi.d.ts +112 -0
- package/dist/request-qJ9EiDZc.d.mts +112 -0
- package/package.json +24 -19
- package/src/DI/app-injector.ts +95 -106
- package/src/DI/injector-explorer.ts +93 -119
- package/src/DI/token.ts +53 -0
- package/src/decorators/controller.decorator.ts +38 -27
- package/src/decorators/guards.decorator.ts +5 -64
- package/src/decorators/injectable.decorator.ts +68 -15
- package/src/decorators/method.decorator.ts +40 -81
- package/src/decorators/middleware.decorator.ts +5 -72
- package/src/index.ts +4 -5
- package/src/internal/app.ts +217 -0
- package/src/internal/bootstrap.ts +108 -0
- package/src/{preload-bridge.ts → internal/preload-bridge.ts} +1 -1
- package/src/{renderer-client.ts → internal/renderer-client.ts} +2 -2
- package/src/{renderer-events.ts → internal/renderer-events.ts} +1 -1
- package/src/{request.ts → internal/request.ts} +3 -3
- package/src/internal/router.ts +353 -0
- package/src/internal/routes.ts +78 -0
- package/src/{socket.ts → internal/socket.ts} +4 -4
- package/src/main.ts +10 -14
- package/src/non-electron-process.ts +1 -2
- package/src/preload.ts +10 -0
- package/src/renderer.ts +13 -0
- package/src/window/window-manager.ts +255 -0
- package/tsconfig.json +5 -10
- package/tsup.config.ts +29 -13
- package/dist/app-injector-B3MvgV3k.d.mts +0 -95
- package/dist/app-injector-B3MvgV3k.d.ts +0 -95
- package/dist/request-CdpZ9qZL.d.ts +0 -167
- package/dist/request-Dx_5Prte.d.mts +0 -167
- package/src/app.ts +0 -244
- package/src/bootstrap.ts +0 -84
- package/src/decorators/inject.decorator.ts +0 -24
- package/src/decorators/injectable.metadata.ts +0 -15
- package/src/decorators/module.decorator.ts +0 -75
- package/src/router.ts +0 -594
- /package/src/{exceptions.ts → internal/exceptions.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noxfly/noxus",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-dev.1",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"module": "dist/main.mjs",
|
|
6
6
|
"types": "dist/main.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"browser": {
|
|
10
|
-
"types":
|
|
11
|
-
"import":
|
|
10
|
+
"types": "./dist/renderer.d.ts",
|
|
11
|
+
"import": "./dist/renderer.mjs",
|
|
12
12
|
"require": "./dist/renderer.js"
|
|
13
13
|
},
|
|
14
14
|
"default": {
|
|
15
|
-
"types":
|
|
16
|
-
"import":
|
|
15
|
+
"types": "./dist/child.d.ts",
|
|
16
|
+
"import": "./dist/child.mjs",
|
|
17
17
|
"require": "./dist/child.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"./main": {
|
|
21
|
+
"types": "./dist/main.d.ts",
|
|
22
|
+
"import": "./dist/main.mjs",
|
|
23
|
+
"require": "./dist/main.js"
|
|
24
|
+
},
|
|
20
25
|
"./renderer": {
|
|
21
|
-
"types":
|
|
22
|
-
"import":
|
|
26
|
+
"types": "./dist/renderer.d.ts",
|
|
27
|
+
"import": "./dist/renderer.mjs",
|
|
23
28
|
"require": "./dist/renderer.js"
|
|
24
29
|
},
|
|
25
|
-
"./
|
|
26
|
-
"types":
|
|
27
|
-
"import":
|
|
28
|
-
"require": "./dist/
|
|
30
|
+
"./preload": {
|
|
31
|
+
"types": "./dist/preload.d.ts",
|
|
32
|
+
"import": "./dist/preload.mjs",
|
|
33
|
+
"require": "./dist/preload.js"
|
|
29
34
|
},
|
|
30
35
|
"./child": {
|
|
31
|
-
"types":
|
|
32
|
-
"import":
|
|
36
|
+
"types": "./dist/child.d.ts",
|
|
37
|
+
"import": "./dist/child.mjs",
|
|
33
38
|
"require": "./dist/child.js"
|
|
34
39
|
}
|
|
35
40
|
},
|
|
@@ -45,12 +50,15 @@
|
|
|
45
50
|
"nodejs",
|
|
46
51
|
"typescript",
|
|
47
52
|
"framework",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
53
|
+
"electron",
|
|
54
|
+
"ipc",
|
|
55
|
+
"dependency-injection",
|
|
56
|
+
"standalone",
|
|
57
|
+
"lazy-loading"
|
|
50
58
|
],
|
|
51
59
|
"author": "NoxFly",
|
|
52
60
|
"license": "MIT",
|
|
53
|
-
"description": "
|
|
61
|
+
"description": "Lightweight HTTP-like IPC framework for Electron with standalone controllers, explicit DI, and lazy route loading. No reflect-metadata required.",
|
|
54
62
|
"homepage": "https://github.com/NoxFly/noxus",
|
|
55
63
|
"repository": {
|
|
56
64
|
"type": "git",
|
|
@@ -73,8 +81,5 @@
|
|
|
73
81
|
"tsup": "^8.5.0",
|
|
74
82
|
"typescript": "^5.8.3",
|
|
75
83
|
"typescript-eslint": "^8.36.0"
|
|
76
|
-
},
|
|
77
|
-
"peerDependencies": {
|
|
78
|
-
"reflect-metadata": "^0.2.2"
|
|
79
84
|
}
|
|
80
85
|
}
|
package/src/DI/app-injector.ts
CHANGED
|
@@ -4,159 +4,148 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import '
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { ForwardReference } from 'src/utils/forward-ref';
|
|
11
|
-
import { Type } from 'src/utils/types';
|
|
7
|
+
import { ForwardReference } from '../utils/forward-ref';
|
|
8
|
+
import { Type } from '../utils/types';
|
|
9
|
+
import { Token, TokenKey } from './token';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* - 'transient': A new instance is created every time it is requested.
|
|
12
|
+
* Lifetime of a binding in the DI container.
|
|
13
|
+
* - singleton: created once, shared for the lifetime of the app.
|
|
14
|
+
* - scope: created once per request scope.
|
|
15
|
+
* - transient: new instance every time it is resolved.
|
|
19
16
|
*/
|
|
20
17
|
export type Lifetime = 'singleton' | 'scope' | 'transient';
|
|
21
18
|
|
|
22
19
|
/**
|
|
23
|
-
*
|
|
24
|
-
* It contains the lifetime of the binding, the implementation type, and optionally an instance.
|
|
20
|
+
* Internal representation of a registered binding.
|
|
25
21
|
*/
|
|
26
|
-
export interface IBinding {
|
|
22
|
+
export interface IBinding<T = unknown> {
|
|
27
23
|
lifetime: Lifetime;
|
|
28
|
-
implementation: Type<
|
|
29
|
-
|
|
24
|
+
implementation: Type<T>;
|
|
25
|
+
/** Explicit constructor dependencies, declared by the class itself. */
|
|
26
|
+
deps: ReadonlyArray<TokenKey>;
|
|
27
|
+
instance?: T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function keyOf<T>(k: TokenKey<T>): Type<T> | Token<T> {
|
|
31
|
+
return k;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* AppInjector is the
|
|
34
|
-
* It
|
|
35
|
-
*
|
|
36
|
-
* This should not be manually instantiated, outside of the framework.
|
|
37
|
-
* Use the `RootInjector` instance instead.
|
|
35
|
+
* AppInjector is the core DI container.
|
|
36
|
+
* It no longer uses reflect-metadata — all dependency information
|
|
37
|
+
* comes from explicitly declared `deps` arrays on each binding.
|
|
38
38
|
*/
|
|
39
39
|
export class AppInjector {
|
|
40
|
-
public bindings = new Map<Type<unknown>, IBinding
|
|
41
|
-
public singletons = new Map<Type<unknown
|
|
42
|
-
public scoped = new Map<Type<unknown
|
|
40
|
+
public readonly bindings = new Map<Type<unknown> | Token<unknown>, IBinding<unknown>>();
|
|
41
|
+
public readonly singletons = new Map<Type<unknown> | Token<unknown>, unknown>();
|
|
42
|
+
public readonly scoped = new Map<Type<unknown> | Token<unknown>, unknown>();
|
|
43
43
|
|
|
44
|
-
constructor(
|
|
45
|
-
public readonly name: string | null = null,
|
|
46
|
-
) {}
|
|
44
|
+
constructor(public readonly name: string | null = null) {}
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
|
-
*
|
|
50
|
-
* at the "scope" level (i.e., per-request lifetime).
|
|
51
|
-
*
|
|
52
|
-
* SHOULD NOT BE USED by anything else than the framework itself.
|
|
47
|
+
* Creates a child scope for per-request lifetime resolution.
|
|
53
48
|
*/
|
|
54
49
|
public createScope(): AppInjector {
|
|
55
50
|
const scope = new AppInjector();
|
|
56
|
-
scope.bindings = this.bindings;
|
|
57
|
-
scope.singletons = this.singletons;
|
|
58
|
-
// do not keep parent's scoped instances
|
|
51
|
+
(scope as any).bindings = this.bindings;
|
|
52
|
+
(scope as any).singletons = this.singletons;
|
|
59
53
|
return scope;
|
|
60
54
|
}
|
|
61
55
|
|
|
62
56
|
/**
|
|
63
|
-
*
|
|
64
|
-
* i.e., retrieving the instance of a given class.
|
|
57
|
+
* Registers a binding explicitly.
|
|
65
58
|
*/
|
|
66
|
-
public
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
set: (obj, prop, value, receiver) => {
|
|
77
|
-
const realType = target.forwardRefFn();
|
|
78
|
-
const instance = this.resolve(realType) as any;
|
|
79
|
-
return Reflect.set(instance, prop, value, receiver);
|
|
80
|
-
},
|
|
81
|
-
getPrototypeOf: () => {
|
|
82
|
-
const realType = target.forwardRefFn();
|
|
83
|
-
return (realType as any).prototype;
|
|
84
|
-
}
|
|
85
|
-
}) as T;
|
|
59
|
+
public register<T>(
|
|
60
|
+
key: TokenKey<T>,
|
|
61
|
+
implementation: Type<T>,
|
|
62
|
+
lifetime: Lifetime,
|
|
63
|
+
deps: ReadonlyArray<TokenKey> = [],
|
|
64
|
+
): void {
|
|
65
|
+
const k = keyOf(key) as TokenKey<unknown>;
|
|
66
|
+
if (!this.bindings.has(k)) {
|
|
67
|
+
this.bindings.set(k, { lifetime, implementation: implementation as Type<unknown>, deps });
|
|
86
68
|
}
|
|
69
|
+
}
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
);
|
|
96
|
-
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves a dependency by token or class reference.
|
|
73
|
+
*/
|
|
74
|
+
public resolve<T>(target: TokenKey<T> | ForwardReference<T>): T {
|
|
75
|
+
if (target instanceof ForwardReference) {
|
|
76
|
+
return this._resolveForwardRef(target);
|
|
77
|
+
}
|
|
97
78
|
|
|
98
|
-
|
|
79
|
+
const k = keyOf(target) as TokenKey<unknown>;
|
|
80
|
+
const binding = this.bindings.get(k);
|
|
99
81
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
82
|
+
if (!binding) {
|
|
83
|
+
const name = target instanceof Token ? target.description : (target as Type<unknown>).name ?? 'unknown';
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[Noxus DI] No binding found for "${name}".\n`
|
|
86
|
+
+ `Did you forget to declare it in @Injectable({ deps }) or in bootstrapApplication({ singletons })?`,
|
|
103
87
|
);
|
|
104
88
|
}
|
|
105
89
|
|
|
106
|
-
switch(binding.lifetime) {
|
|
90
|
+
switch (binding.lifetime) {
|
|
107
91
|
case 'transient':
|
|
108
|
-
return this.
|
|
92
|
+
return this._instantiate(binding) as T;
|
|
109
93
|
|
|
110
94
|
case 'scope': {
|
|
111
|
-
if(this.scoped.has(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const instance = this.instantiate(binding.implementation);
|
|
116
|
-
this.scoped.set(target, instance);
|
|
117
|
-
|
|
118
|
-
return instance as T;
|
|
95
|
+
if (this.scoped.has(k)) return this.scoped.get(k) as T;
|
|
96
|
+
const inst = this._instantiate(binding);
|
|
97
|
+
this.scoped.set(k, inst);
|
|
98
|
+
return inst as T;
|
|
119
99
|
}
|
|
120
100
|
|
|
121
101
|
case 'singleton': {
|
|
122
|
-
if(
|
|
123
|
-
|
|
124
|
-
|
|
102
|
+
if (this.singletons.has(k)) return this.singletons.get(k) as T;
|
|
103
|
+
const inst = this._instantiate(binding);
|
|
104
|
+
this.singletons.set(k, inst);
|
|
105
|
+
if (binding.instance === undefined) {
|
|
106
|
+
(binding as IBinding<unknown>).instance = inst as unknown;
|
|
125
107
|
}
|
|
126
|
-
|
|
127
|
-
return binding.instance as T;
|
|
108
|
+
return inst as T;
|
|
128
109
|
}
|
|
129
110
|
}
|
|
130
111
|
}
|
|
131
112
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
113
|
+
// -------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
private _resolveForwardRef<T>(ref: ForwardReference<T>): T {
|
|
116
|
+
return new Proxy({} as object, {
|
|
117
|
+
get: (_obj, prop, receiver) => {
|
|
118
|
+
const realType = ref.forwardRefFn();
|
|
119
|
+
const instance = this.resolve(realType) as Record<string | symbol, unknown>;
|
|
120
|
+
const value = Reflect.get(instance, prop, receiver);
|
|
121
|
+
return typeof value === 'function' ? (value as Function).bind(instance) : value;
|
|
122
|
+
},
|
|
123
|
+
set: (_obj, prop, value, receiver) => {
|
|
124
|
+
const realType = ref.forwardRefFn();
|
|
125
|
+
const instance = this.resolve(realType) as object;
|
|
126
|
+
return Reflect.set(instance, prop, value, receiver);
|
|
127
|
+
},
|
|
128
|
+
getPrototypeOf: () => {
|
|
129
|
+
const realType = ref.forwardRefFn();
|
|
130
|
+
return (realType as unknown as { prototype: object }).prototype;
|
|
131
|
+
},
|
|
132
|
+
}) as T;
|
|
133
|
+
}
|
|
145
134
|
|
|
146
|
-
|
|
135
|
+
private _instantiate<T>(binding: IBinding<T>): T {
|
|
136
|
+
const resolvedDeps = binding.deps.map((dep) => this.resolve(dep));
|
|
137
|
+
return new binding.implementation(...resolvedDeps) as T;
|
|
147
138
|
}
|
|
148
139
|
}
|
|
149
140
|
|
|
150
141
|
/**
|
|
151
|
-
*
|
|
152
|
-
* This function is used to retrieve an instance of a type that has been registered in the dependency injection system.
|
|
153
|
-
* It is typically used in the constructor of a class to inject dependencies.
|
|
154
|
-
* @param t - The type to inject.
|
|
155
|
-
* @returns An instance of the type.
|
|
156
|
-
* @throws If the type is not registered in the dependency injection system.
|
|
142
|
+
* The global root injector. All singletons live here.
|
|
157
143
|
*/
|
|
158
|
-
export
|
|
144
|
+
export const RootInjector = new AppInjector('root');
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convenience function: resolve a token from the root injector.
|
|
148
|
+
*/
|
|
149
|
+
export function inject<T>(t: TokenKey<T> | ForwardReference<T>): T {
|
|
159
150
|
return RootInjector.resolve(t);
|
|
160
151
|
}
|
|
161
|
-
|
|
162
|
-
export const RootInjector = new AppInjector('root');
|
|
@@ -4,166 +4,140 @@
|
|
|
4
4
|
* @author NoxFly
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
interface PendingRegistration {
|
|
17
|
-
target: Type<unknown>;
|
|
7
|
+
import { Lifetime, RootInjector } from './app-injector';
|
|
8
|
+
import { TokenKey } from './token';
|
|
9
|
+
import { Type } from '../utils/types';
|
|
10
|
+
import { Logger } from '../utils/logger';
|
|
11
|
+
import { Guard, Middleware } from "src/main";
|
|
12
|
+
|
|
13
|
+
export interface PendingRegistration {
|
|
14
|
+
key: TokenKey;
|
|
15
|
+
implementation: Type<unknown>;
|
|
18
16
|
lifetime: Lifetime;
|
|
17
|
+
deps: ReadonlyArray<TokenKey>;
|
|
18
|
+
isController: boolean;
|
|
19
|
+
pathPrefix?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* InjectorExplorer
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* InjectorExplorer accumulates registrations emitted by decorators
|
|
24
|
+
* at import time, then flushes them in two phases (bind → resolve)
|
|
25
|
+
* once bootstrapApplication triggers processing.
|
|
26
|
+
*
|
|
27
|
+
* Because deps are now explicit arrays (no reflect-metadata), this class
|
|
28
|
+
* no longer needs to introspect constructor parameter types.
|
|
25
29
|
*/
|
|
26
30
|
export class InjectorExplorer {
|
|
27
31
|
private static readonly pending: PendingRegistration[] = [];
|
|
28
32
|
private static processed = false;
|
|
29
33
|
private static accumulating = false;
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* work correctly.
|
|
39
|
-
*
|
|
40
|
-
* When accumulation mode is active (between {@link beginAccumulate} and
|
|
41
|
-
* {@link flushAccumulated}), classes are queued instead — preserving the
|
|
42
|
-
* two-phase binding/resolution guarantee for lazy-loaded modules.
|
|
43
|
-
*/
|
|
44
|
-
public static enqueue(target: Type<unknown>, lifetime: Lifetime): void {
|
|
45
|
-
if(InjectorExplorer.processed && !InjectorExplorer.accumulating) {
|
|
46
|
-
InjectorExplorer.registerImmediate(target, lifetime);
|
|
35
|
+
// -------------------------------------------------------------------------
|
|
36
|
+
// Public API
|
|
37
|
+
// -------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
public static enqueue(reg: PendingRegistration): void {
|
|
40
|
+
if (InjectorExplorer.processed && !InjectorExplorer.accumulating) {
|
|
41
|
+
InjectorExplorer._registerImmediate(reg);
|
|
47
42
|
return;
|
|
48
43
|
}
|
|
49
|
-
|
|
50
|
-
InjectorExplorer.pending.push({ target, lifetime });
|
|
44
|
+
InjectorExplorer.pending.push(reg);
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* immediately. Call {@link flushAccumulated} to process them with the
|
|
57
|
-
* full two-phase (bind-then-resolve) guarantee.
|
|
48
|
+
* Two-phase flush of all pending registrations collected at startup.
|
|
49
|
+
* Called by bootstrapApplication after app.whenReady().
|
|
58
50
|
*/
|
|
51
|
+
public static processPending(singletonOverrides?: Map<TokenKey, unknown>): void {
|
|
52
|
+
const queue = [...InjectorExplorer.pending];
|
|
53
|
+
InjectorExplorer.pending.length = 0;
|
|
54
|
+
|
|
55
|
+
InjectorExplorer._phaseOne(queue);
|
|
56
|
+
InjectorExplorer._phaseTwo(queue, singletonOverrides);
|
|
57
|
+
|
|
58
|
+
InjectorExplorer.processed = true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Enters accumulation mode for lazy-loaded batches. */
|
|
59
62
|
public static beginAccumulate(): void {
|
|
60
63
|
InjectorExplorer.accumulating = true;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
/**
|
|
64
|
-
* Exits accumulation mode and
|
|
65
|
-
*
|
|
66
|
-
* as {@link processPending} (register all bindings first, then resolve
|
|
67
|
-
* singletons / controllers) so import ordering within a lazy batch
|
|
68
|
-
* does not cause resolution failures.
|
|
67
|
+
* Exits accumulation mode and flushes queued registrations
|
|
68
|
+
* with the same two-phase guarantee as processPending.
|
|
69
69
|
*/
|
|
70
|
-
public static flushAccumulated(
|
|
70
|
+
public static flushAccumulated(
|
|
71
|
+
routeGuards: Guard[] = [],
|
|
72
|
+
routeMiddlewares: Middleware[] = [],
|
|
73
|
+
pathPrefix = '',
|
|
74
|
+
): void {
|
|
71
75
|
InjectorExplorer.accumulating = false;
|
|
72
|
-
|
|
73
76
|
const queue = [...InjectorExplorer.pending];
|
|
74
77
|
InjectorExplorer.pending.length = 0;
|
|
78
|
+
InjectorExplorer._phaseOne(queue);
|
|
75
79
|
|
|
76
|
-
//
|
|
77
|
-
for(const
|
|
78
|
-
if(
|
|
79
|
-
RootInjector.bindings.set(target, {
|
|
80
|
-
implementation: target,
|
|
81
|
-
lifetime
|
|
82
|
-
});
|
|
83
|
-
}
|
|
80
|
+
// Stamp the path prefix on controller registrations
|
|
81
|
+
for (const reg of queue) {
|
|
82
|
+
if (reg.isController) reg.pathPrefix = pathPrefix;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
for(const { target, lifetime } of queue) {
|
|
88
|
-
InjectorExplorer.processRegistration(target, lifetime);
|
|
89
|
-
}
|
|
85
|
+
InjectorExplorer._phaseTwo(queue, undefined, routeGuards, routeMiddlewares);
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* 2. Resolve singletons, register controllers and log module readiness.
|
|
96
|
-
*
|
|
97
|
-
* This two-phase approach makes the system resilient to import ordering:
|
|
98
|
-
* all bindings exist before any singleton is instantiated.
|
|
99
|
-
*/
|
|
100
|
-
public static processPending(): void {
|
|
101
|
-
const queue = InjectorExplorer.pending;
|
|
102
|
-
|
|
103
|
-
// Phase 1: register all bindings without instantiation
|
|
104
|
-
for(const { target, lifetime } of queue) {
|
|
105
|
-
if(!RootInjector.bindings.has(target)) {
|
|
106
|
-
RootInjector.bindings.set(target, {
|
|
107
|
-
implementation: target,
|
|
108
|
-
lifetime
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Private helpers
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
112
91
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
92
|
+
/** Phase 1: register all bindings without instantiating anything. */
|
|
93
|
+
private static _phaseOne(queue: PendingRegistration[]): void {
|
|
94
|
+
for (const reg of queue) {
|
|
95
|
+
RootInjector.register(reg.key, reg.implementation, reg.lifetime, reg.deps);
|
|
116
96
|
}
|
|
117
|
-
|
|
118
|
-
queue.length = 0;
|
|
119
|
-
InjectorExplorer.processed = true;
|
|
120
97
|
}
|
|
121
98
|
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
99
|
+
/** Phase 2: resolve singletons and register controllers in the router. */
|
|
100
|
+
private static _phaseTwo(
|
|
101
|
+
queue: PendingRegistration[],
|
|
102
|
+
overrides?: Map<TokenKey, unknown>,
|
|
103
|
+
routeGuards: Guard[] = [],
|
|
104
|
+
routeMiddlewares: Middleware[] = [],
|
|
105
|
+
): void {
|
|
106
|
+
for (const reg of queue) {
|
|
107
|
+
// Apply value overrides (e.g. singleton instances provided via bootstrapApplication config)
|
|
108
|
+
if (overrides?.has(reg.key)) {
|
|
109
|
+
const override = overrides.get(reg.key);
|
|
110
|
+
RootInjector.singletons.set(reg.key as any, override);
|
|
111
|
+
Logger.log(`Registered ${reg.implementation.name} as singleton (overridden)`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
138
114
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*/
|
|
143
|
-
private static processRegistration(target: Type<unknown>, lifetime: Lifetime): void {
|
|
144
|
-
if(lifetime === 'singleton') {
|
|
145
|
-
RootInjector.resolve(target);
|
|
146
|
-
}
|
|
115
|
+
if (reg.lifetime === 'singleton') {
|
|
116
|
+
RootInjector.resolve(reg.key);
|
|
117
|
+
}
|
|
147
118
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
119
|
+
if (reg.isController) {
|
|
120
|
+
// Lazily import Router to avoid circular dependency at module load time
|
|
121
|
+
const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
|
|
122
|
+
const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>, pathPrefix: string, routeGuards: Guard[], routeMiddlewares: Middleware[]): void };
|
|
123
|
+
router.registerController(reg.implementation, reg.pathPrefix ?? '', routeGuards, routeMiddlewares);
|
|
124
|
+
} else if (reg.lifetime !== 'singleton') {
|
|
125
|
+
Logger.log(`Registered ${reg.implementation.name} as ${reg.lifetime}`);
|
|
126
|
+
}
|
|
151
127
|
}
|
|
128
|
+
}
|
|
152
129
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if(controllerMeta) {
|
|
156
|
-
const router = RootInjector.resolve(Router);
|
|
157
|
-
router?.registerController(target);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
130
|
+
private static _registerImmediate(reg: PendingRegistration): void {
|
|
131
|
+
RootInjector.register(reg.key, reg.implementation, reg.lifetime, reg.deps);
|
|
160
132
|
|
|
161
|
-
if(
|
|
162
|
-
|
|
133
|
+
if (reg.lifetime === 'singleton') {
|
|
134
|
+
RootInjector.resolve(reg.key);
|
|
163
135
|
}
|
|
164
136
|
|
|
165
|
-
if(
|
|
166
|
-
|
|
137
|
+
if (reg.isController) {
|
|
138
|
+
const { Router } = require('../internal/router') as { Router: { prototype: { registerController(t: Type<unknown>): void } } };
|
|
139
|
+
const router = RootInjector.resolve(Router as any) as { registerController(t: Type<unknown>): void };
|
|
140
|
+
router.registerController(reg.implementation);
|
|
167
141
|
}
|
|
168
142
|
}
|
|
169
143
|
}
|
package/src/DI/token.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2025 NoxFly
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @author NoxFly
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Type } from '../utils/types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A DI token uniquely identifies a dependency.
|
|
11
|
+
* It can wrap a class (Type<T>) or be a named symbol token.
|
|
12
|
+
*
|
|
13
|
+
* Using tokens instead of reflect-metadata means dependencies are
|
|
14
|
+
* declared explicitly — no magic type inference, no emitDecoratorMetadata.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Class token (most common)
|
|
18
|
+
* const MY_SERVICE = token(MyService);
|
|
19
|
+
*
|
|
20
|
+
* // Named symbol token (for interfaces or non-class values)
|
|
21
|
+
* const DB_URL = token<string>('DB_URL');
|
|
22
|
+
*/
|
|
23
|
+
export class Token<T> {
|
|
24
|
+
public readonly description: string;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
public readonly target: Type<T> | string,
|
|
28
|
+
) {
|
|
29
|
+
this.description = typeof target === 'string' ? target : target.name;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public toString(): string {
|
|
33
|
+
return `Token(${this.description})`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a DI token for a class type or a named value.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* export const MY_SERVICE = token(MyService);
|
|
42
|
+
* export const DB_URL = token<string>('DB_URL');
|
|
43
|
+
*/
|
|
44
|
+
export function token<T>(target: Type<T> | string): Token<T> {
|
|
45
|
+
return new Token<T>(target);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The key used to look up a class token in the registry.
|
|
50
|
+
* For class tokens, the key is the class constructor itself.
|
|
51
|
+
* For named tokens, the key is the Token instance.
|
|
52
|
+
*/
|
|
53
|
+
export type TokenKey<T = unknown> = Type<T> | Token<T>;
|