@nmtjs/core 0.11.2 → 0.11.4
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/dist/container.js +111 -80
- package/dist/container.js.map +1 -1
- package/dist/hooks.js.map +1 -1
- package/dist/injectables.js +66 -25
- package/dist/injectables.js.map +1 -1
- package/package.json +5 -5
- package/src/container.ts +172 -104
- package/src/hooks.ts +1 -0
- package/src/injectables.ts +103 -35
package/dist/container.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { tryCaptureStackTrace } from "@nmtjs/common";
|
|
1
3
|
import { Scope } from "./enums.js";
|
|
2
|
-
import { CoreInjectables, compareScope, createValueInjectable, getDepedencencyInjectable, isClassInjectable, isFactoryInjectable, isInjectable, isLazyInjectable, isOptionalInjectable, isValueInjectable } from "./injectables.js";
|
|
4
|
+
import { CoreInjectables, compareScope, createExtendableClassInjectable, createValueInjectable, getDepedencencyInjectable, isClassInjectable, isFactoryInjectable, isInjectable, isLazyInjectable, isOptionalInjectable, isValueInjectable } from "./injectables.js";
|
|
3
5
|
export class Container {
|
|
4
6
|
instances = new Map();
|
|
5
7
|
resolvers = new Map();
|
|
@@ -13,7 +15,9 @@ export class Container {
|
|
|
13
15
|
if (scope === Scope.Transient) {
|
|
14
16
|
throw new Error("Invalid scope");
|
|
15
17
|
}
|
|
16
|
-
this.provide(CoreInjectables.inject, createInjectFunction(
|
|
18
|
+
this.provide(CoreInjectables.inject, this.createInjectFunction());
|
|
19
|
+
this.provide(CoreInjectables.dispose, this.createDisposeFunction());
|
|
20
|
+
this.provide(CoreInjectables.registry, application.registry);
|
|
17
21
|
}
|
|
18
22
|
async load() {
|
|
19
23
|
const traverse = (dependencies) => {
|
|
@@ -38,7 +42,9 @@ export class Container {
|
|
|
38
42
|
this.disposing = true;
|
|
39
43
|
const disposalOrder = this.getDisposalOrder();
|
|
40
44
|
for (const injectable of disposalOrder) {
|
|
41
|
-
|
|
45
|
+
if (this.instances.has(injectable)) {
|
|
46
|
+
await this.disposeInjectableInstances(injectable);
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
this.instances.clear();
|
|
44
50
|
this.injectables.clear();
|
|
@@ -53,8 +59,11 @@ export class Container {
|
|
|
53
59
|
return this.containsWithinSelf(injectable) || (this.parent?.contains(injectable) ?? false);
|
|
54
60
|
}
|
|
55
61
|
get(injectable) {
|
|
62
|
+
if (injectable.scope === Scope.Transient) {
|
|
63
|
+
throw new Error("Cannot get transient injectable directly");
|
|
64
|
+
}
|
|
56
65
|
if (this.instances.has(injectable)) {
|
|
57
|
-
return this.instances.get(injectable).
|
|
66
|
+
return this.instances.get(injectable).at(0).public;
|
|
58
67
|
}
|
|
59
68
|
if (this.parent?.contains(injectable)) {
|
|
60
69
|
return this.parent.get(injectable);
|
|
@@ -84,11 +93,11 @@ export class Container {
|
|
|
84
93
|
if (compareScope(injectable.scope, ">", this.scope)) {
|
|
85
94
|
throw new Error("Invalid scope");
|
|
86
95
|
}
|
|
87
|
-
this.instances.set(injectable, {
|
|
88
|
-
instance,
|
|
89
|
-
|
|
96
|
+
this.instances.set(injectable, [{
|
|
97
|
+
private: instance,
|
|
98
|
+
public: instance,
|
|
90
99
|
context: undefined
|
|
91
|
-
});
|
|
100
|
+
}]);
|
|
92
101
|
}
|
|
93
102
|
satisfies(injectable) {
|
|
94
103
|
return compareScope(injectable.scope, "<=", this.scope);
|
|
@@ -112,7 +121,7 @@ export class Container {
|
|
|
112
121
|
} else if (this.parent?.contains(injectable) || this.parent?.satisfies(injectable) && compareScope(this.parent.scope, "<", this.scope)) {
|
|
113
122
|
return this.parent.resolveInjectable(injectable, dependant);
|
|
114
123
|
} else {
|
|
115
|
-
const {
|
|
124
|
+
const { stack, label } = injectable;
|
|
116
125
|
if (dependant) {
|
|
117
126
|
let dependants = this.dependants.get(injectable);
|
|
118
127
|
if (!dependants) {
|
|
@@ -120,9 +129,10 @@ export class Container {
|
|
|
120
129
|
}
|
|
121
130
|
dependants.add(dependant);
|
|
122
131
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
const isTransient = injectable.scope === Scope.Transient;
|
|
133
|
+
if (!isTransient && this.instances.has(injectable)) {
|
|
134
|
+
return Promise.resolve(this.instances.get(injectable).at(0).public);
|
|
135
|
+
} else if (!isTransient && this.resolvers.has(injectable)) {
|
|
126
136
|
return this.resolvers.get(injectable);
|
|
127
137
|
} else {
|
|
128
138
|
const isLazy = isLazyInjectable(injectable);
|
|
@@ -131,8 +141,10 @@ export class Container {
|
|
|
131
141
|
if (isOptional) return Promise.resolve(undefined);
|
|
132
142
|
return Promise.reject(new Error(`No instance provided for ${label || "an"} injectable:\n${stack}`));
|
|
133
143
|
} else {
|
|
134
|
-
const resolution = this.createResolution(injectable
|
|
135
|
-
|
|
144
|
+
const resolution = this.createResolution(injectable).finally(() => {
|
|
145
|
+
this.resolvers.delete(injectable);
|
|
146
|
+
});
|
|
147
|
+
if (injectable.scope !== Scope.Transient) {
|
|
136
148
|
this.resolvers.set(injectable, resolution);
|
|
137
149
|
}
|
|
138
150
|
return resolution;
|
|
@@ -140,48 +152,84 @@ export class Container {
|
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
|
-
async createResolution(injectable
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
155
|
+
async createResolution(injectable) {
|
|
156
|
+
const { dependencies } = injectable;
|
|
157
|
+
const context = await this.createInjectableContext(dependencies, injectable);
|
|
158
|
+
const wrapper = {
|
|
159
|
+
private: null,
|
|
160
|
+
public: null,
|
|
161
|
+
context
|
|
162
|
+
};
|
|
163
|
+
if (isFactoryInjectable(injectable)) {
|
|
164
|
+
wrapper.private = await Promise.resolve(injectable.factory(wrapper.context));
|
|
165
|
+
wrapper.public = injectable.pick(wrapper.private);
|
|
166
|
+
} else if (isClassInjectable(injectable)) {
|
|
167
|
+
wrapper.private = new injectable(context);
|
|
168
|
+
wrapper.public = wrapper.private;
|
|
169
|
+
await wrapper.private.$onCreate();
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error("Invalid injectable type");
|
|
172
|
+
}
|
|
173
|
+
let instances = this.instances.get(injectable);
|
|
174
|
+
if (!instances) {
|
|
175
|
+
instances = [];
|
|
176
|
+
this.instances.set(injectable, instances);
|
|
177
|
+
}
|
|
178
|
+
instances.push(wrapper);
|
|
179
|
+
return wrapper.public;
|
|
180
|
+
}
|
|
181
|
+
createInjectFunction() {
|
|
182
|
+
const inject = (injectable, context) => {
|
|
183
|
+
const dependencies = { ...injectable.dependencies };
|
|
184
|
+
for (const key in context) {
|
|
185
|
+
const dep = context[key];
|
|
186
|
+
if (isInjectable(dep) || isOptionalInjectable(dep)) {
|
|
187
|
+
dependencies[key] = dep;
|
|
188
|
+
} else {
|
|
189
|
+
dependencies[key] = createValueInjectable(dep);
|
|
170
190
|
}
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
}
|
|
192
|
+
const newInjectable = isClassInjectable(injectable) ? createExtendableClassInjectable(injectable, dependencies, Scope.Transient, 1) : {
|
|
193
|
+
...injectable,
|
|
194
|
+
dependencies,
|
|
195
|
+
scope: Scope.Transient,
|
|
196
|
+
stack: tryCaptureStackTrace(1)
|
|
197
|
+
};
|
|
198
|
+
return this.resolve(newInjectable);
|
|
199
|
+
};
|
|
200
|
+
const explicit = async (injectable, context) => {
|
|
201
|
+
if ("asyncDispose" in Symbol === false) {
|
|
202
|
+
throw new Error("Symbol.asyncDispose is not supported in this environment");
|
|
203
|
+
}
|
|
204
|
+
const instance = await inject(injectable, context);
|
|
205
|
+
const dispose = this.createDisposeFunction();
|
|
206
|
+
return Object.assign(instance, { [Symbol.asyncDispose]: async () => {
|
|
207
|
+
await dispose(injectable, instance);
|
|
208
|
+
} });
|
|
209
|
+
};
|
|
210
|
+
return Object.assign(inject, { explicit });
|
|
211
|
+
}
|
|
212
|
+
createDisposeFunction() {
|
|
213
|
+
return async (injectable, instance) => {
|
|
214
|
+
if (injectable.scope === Scope.Transient) {
|
|
215
|
+
assert(instance, "Instance is required for transient injectable disposal");
|
|
216
|
+
const wrappers = this.instances.get(injectable);
|
|
217
|
+
if (wrappers) {
|
|
218
|
+
for (const wrapper of wrappers) {
|
|
219
|
+
if (wrapper.public === instance) {
|
|
220
|
+
await this.disposeInjectableInstance(injectable, wrapper.private, wrapper.context);
|
|
221
|
+
const index = wrappers.indexOf(wrapper);
|
|
222
|
+
wrappers.splice(index, 1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (wrappers.length === 0) {
|
|
226
|
+
this.instances.delete(injectable);
|
|
227
|
+
}
|
|
173
228
|
}
|
|
174
|
-
return instance;
|
|
175
229
|
} else {
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (scope !== Scope.Transient) {
|
|
180
|
-
this.resolvers.delete(injectable);
|
|
230
|
+
await this.disposeInjectableInstances(injectable);
|
|
181
231
|
}
|
|
182
|
-
|
|
183
|
-
throw error;
|
|
184
|
-
}
|
|
232
|
+
};
|
|
185
233
|
}
|
|
186
234
|
getDisposalOrder() {
|
|
187
235
|
const visited = new Set();
|
|
@@ -206,17 +254,11 @@ export class Container {
|
|
|
206
254
|
}
|
|
207
255
|
return result;
|
|
208
256
|
}
|
|
209
|
-
async
|
|
257
|
+
async disposeInjectableInstances(injectable) {
|
|
210
258
|
try {
|
|
211
|
-
if (
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
const { instance, context } = this.instances.get(injectable);
|
|
215
|
-
await dispose(instance, context);
|
|
216
|
-
}
|
|
217
|
-
} else if (isClassInjectable(injectable)) {
|
|
218
|
-
const { instance } = this.instances.get(injectable);
|
|
219
|
-
await instance.$onDispose();
|
|
259
|
+
if (this.instances.has(injectable)) {
|
|
260
|
+
const wrappers = this.instances.get(injectable);
|
|
261
|
+
await Promise.all(wrappers.map((wrapper) => this.disposeInjectableInstance(injectable, wrapper.private, wrapper.context)));
|
|
220
262
|
}
|
|
221
263
|
} catch (cause) {
|
|
222
264
|
const error = new Error("Injectable disposal error. Potential memory leak", { cause });
|
|
@@ -225,25 +267,14 @@ export class Container {
|
|
|
225
267
|
this.instances.delete(injectable);
|
|
226
268
|
}
|
|
227
269
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (isInjectable(dep) || isOptionalInjectable(dep)) {
|
|
235
|
-
dependencies[key] = dep;
|
|
236
|
-
} else {
|
|
237
|
-
dependencies[key] = createValueInjectable(dep);
|
|
238
|
-
}
|
|
270
|
+
async disposeInjectableInstance(injectable, instance, context) {
|
|
271
|
+
if (isFactoryInjectable(injectable)) {
|
|
272
|
+
const { dispose } = injectable;
|
|
273
|
+
if (dispose) await dispose(instance, context);
|
|
274
|
+
} else if (isClassInjectable(injectable)) {
|
|
275
|
+
await instance.$onDispose();
|
|
239
276
|
}
|
|
240
|
-
|
|
241
|
-
...injectable,
|
|
242
|
-
dependencies,
|
|
243
|
-
scope: Scope.Transient
|
|
244
|
-
};
|
|
245
|
-
return container.resolve(newInjectable);
|
|
246
|
-
};
|
|
277
|
+
}
|
|
247
278
|
}
|
|
248
279
|
|
|
249
280
|
//# sourceMappingURL=container.js.map
|
package/dist/container.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","names":["application: {\n registry: Registry\n logger: Logger\n }","scope: Exclude<Scope, Scope.Transient>","parent?: Container","dependencies: Dependencies","injectable: AnyInjectable","injectable: T","dependencies: T","dependant?: AnyInjectable","injections: Record<string, any>","resolvers: Promise<any>[]","instance: ResolveInjectableType<T>","scope: Scope","result: AnyInjectable[]","container: Container","context: InlineInjectionDependencies<T>"],"sources":["../src/container.ts"],"sourcesContent":["import { Scope } from './enums.ts'\nimport {\n type AnyInjectable,\n CoreInjectables,\n compareScope,\n createValueInjectable,\n type Dependencies,\n type DependencyContext,\n getDepedencencyInjectable,\n isClassInjectable,\n isFactoryInjectable,\n isInjectable,\n isLazyInjectable,\n isOptionalInjectable,\n isValueInjectable,\n type ResolveInjectableType,\n} from './injectables.ts'\nimport type { Logger } from './logger.ts'\nimport type { Registry } from './registry.ts'\n\nexport class Container {\n readonly instances = new Map<\n AnyInjectable,\n { instance: any; picked?: any; context?: any }\n >()\n private readonly resolvers = new Map<AnyInjectable, Promise<any>>()\n private readonly injectables = new Set<AnyInjectable>()\n private readonly dependants = new Map<AnyInjectable, Set<AnyInjectable>>()\n private disposing = false\n\n constructor(\n private readonly application: {\n registry: Registry\n logger: Logger\n },\n public readonly scope: Exclude<Scope, Scope.Transient> = Scope.Global,\n private readonly parent?: Container,\n ) {\n if ((scope as any) === Scope.Transient) {\n throw new Error('Invalid scope')\n }\n this.provide(CoreInjectables.inject, createInjectFunction(this))\n }\n\n async load() {\n const traverse = (dependencies: Dependencies) => {\n for (const key in dependencies) {\n const dependency = dependencies[key]\n const injectable = getDepedencencyInjectable(dependency)\n this.injectables.add(injectable)\n traverse(injectable.dependencies)\n }\n }\n\n for (const dependant of this.application.registry.getDependants()) {\n traverse(dependant.dependencies)\n }\n\n const injectables = Array.from(this.findCurrentScopeInjectables())\n await Promise.all(injectables.map((injectable) => this.resolve(injectable)))\n }\n\n fork(scope: Exclude<Scope, Scope.Transient>) {\n return new Container(this.application, scope, this)\n }\n\n async dispose() {\n this.application.logger.trace('Disposing [%s] scope context...', this.scope)\n\n // Prevent new resolutions during disposal\n this.disposing = true\n\n // Get proper disposal order using topological sort\n const disposalOrder = this.getDisposalOrder()\n\n // Dispose in the correct order\n for (const injectable of disposalOrder) {\n await this.disposeInjectable(injectable)\n }\n\n this.instances.clear()\n this.injectables.clear()\n this.resolvers.clear()\n this.dependants.clear()\n\n this.disposing = false\n }\n\n containsWithinSelf(injectable: AnyInjectable) {\n return this.instances.has(injectable) || this.resolvers.has(injectable)\n }\n\n contains(injectable: AnyInjectable): boolean {\n return (\n this.containsWithinSelf(injectable) ||\n (this.parent?.contains(injectable) ?? false)\n )\n }\n\n get<T extends AnyInjectable>(injectable: T): ResolveInjectableType<T> {\n if (this.instances.has(injectable)) {\n return this.instances.get(injectable)!.instance\n }\n\n if (this.parent?.contains(injectable)) {\n return this.parent.get(injectable)\n }\n\n throw new Error('No instance found')\n }\n\n resolve<T extends AnyInjectable>(injectable: T) {\n return this.resolveInjectable(injectable)\n }\n\n async createContext<T extends Dependencies>(dependencies: T) {\n return this.createInjectableContext(dependencies)\n }\n\n private async createInjectableContext<T extends Dependencies>(\n dependencies: T,\n dependant?: AnyInjectable,\n ) {\n const injections: Record<string, any> = {}\n const deps = Object.entries(dependencies)\n const resolvers: Promise<any>[] = Array(deps.length)\n for (let i = 0; i < deps.length; i++) {\n const [key, dependency] = deps[i]\n const injectable = getDepedencencyInjectable(dependency)\n const resolver = this.resolveInjectable(injectable, dependant)\n resolvers[i] = resolver.then((value) => (injections[key] = value))\n }\n await Promise.all(resolvers)\n return Object.freeze(injections) as DependencyContext<T>\n }\n\n async provide<T extends AnyInjectable>(\n injectable: T,\n instance: ResolveInjectableType<T>,\n ) {\n if (compareScope(injectable.scope, '>', this.scope)) {\n throw new Error('Invalid scope') // TODO: more informative error\n }\n this.instances.set(injectable, {\n instance,\n picked: instance,\n context: undefined,\n })\n }\n\n satisfies(injectable: AnyInjectable) {\n return compareScope(injectable.scope, '<=', this.scope)\n }\n\n private *findCurrentScopeInjectables() {\n for (const injectable of this.injectables) {\n if (injectable.scope === this.scope) {\n yield injectable\n }\n }\n }\n\n private resolveInjectable<T extends AnyInjectable>(\n injectable: T,\n dependant?: AnyInjectable,\n ): Promise<ResolveInjectableType<T>> {\n if (this.disposing) {\n throw new Error('Cannot resolve during disposal')\n }\n\n if (dependant && compareScope(dependant.scope, '<', injectable.scope)) {\n // TODO: more informative error\n throw new Error('Invalid scope: dependant is looser than injectable')\n }\n\n if (isValueInjectable(injectable)) {\n return Promise.resolve(injectable.value)\n } else if (\n this.parent?.contains(injectable) ||\n (this.parent?.satisfies(injectable) &&\n compareScope(this.parent.scope, '<', this.scope))\n ) {\n return this.parent.resolveInjectable(injectable, dependant)\n } else {\n const { scope, dependencies, stack, label } = injectable\n\n if (dependant) {\n let dependants = this.dependants.get(injectable)\n if (!dependants) {\n this.dependants.set(injectable, (dependants = new Set()))\n }\n dependants.add(dependant)\n }\n\n if (this.instances.has(injectable)) {\n return Promise.resolve(this.instances.get(injectable)!.picked)\n } else if (this.resolvers.has(injectable)) {\n return this.resolvers.get(injectable)!\n } else {\n const isLazy = isLazyInjectable(injectable)\n\n if (isLazy) {\n const isOptional = isOptionalInjectable(injectable)\n if (isOptional) return Promise.resolve(undefined as any)\n return Promise.reject(\n new Error(\n `No instance provided for ${label || 'an'} injectable:\\n${stack}`,\n ),\n )\n } else {\n const resolution = this.createResolution(\n injectable,\n dependencies,\n scope,\n )\n if (scope !== Scope.Transient) {\n this.resolvers.set(injectable, resolution)\n }\n return resolution\n }\n }\n }\n }\n\n private async createResolution<T extends AnyInjectable>(\n injectable: T,\n dependencies: Dependencies,\n scope: Scope,\n ): Promise<ResolveInjectableType<T>> {\n try {\n if (isFactoryInjectable(injectable)) {\n const context = await this.createInjectableContext(\n dependencies,\n injectable,\n )\n const instance = await Promise.resolve(injectable.factory(context))\n const picked = injectable.pick(instance)\n\n if (compareScope(this.scope, '>=', scope)) {\n this.instances.set(injectable, { instance, picked, context })\n }\n\n if (scope !== Scope.Transient) {\n this.resolvers.delete(injectable)\n }\n\n return picked\n } else if (isClassInjectable(injectable)) {\n const context = await this.createInjectableContext(\n dependencies,\n injectable,\n )\n const instance = new injectable(context)\n await instance.$onCreate()\n\n if (compareScope(this.scope, '>=', scope)) {\n this.instances.set(injectable, {\n instance,\n picked: instance,\n context: undefined,\n })\n }\n\n if (scope !== Scope.Transient) {\n this.resolvers.delete(injectable)\n }\n\n return instance\n } else {\n throw new Error('Invalid injectable type')\n }\n } catch (error) {\n // Clean up on failure to prevent memory leaks\n if (scope !== Scope.Transient) {\n this.resolvers.delete(injectable)\n }\n this.instances.delete(injectable)\n throw error\n }\n }\n\n private getDisposalOrder(): AnyInjectable[] {\n const visited = new Set<AnyInjectable>()\n const result: AnyInjectable[] = []\n\n const visit = (injectable: AnyInjectable) => {\n if (visited.has(injectable)) return\n visited.add(injectable)\n\n const dependants = this.dependants.get(injectable)\n if (dependants) {\n for (const dependant of dependants) {\n if (this.instances.has(dependant)) {\n visit(dependant)\n }\n }\n }\n\n // Only add to result if this container owns the instance\n if (this.instances.has(injectable)) {\n result.push(injectable)\n }\n }\n\n for (const injectable of this.instances.keys()) {\n visit(injectable)\n }\n\n return result\n }\n\n private async disposeInjectable(injectable: AnyInjectable) {\n try {\n if (isFactoryInjectable(injectable)) {\n const { dispose } = injectable\n if (dispose) {\n const { instance, context } = this.instances.get(injectable)!\n await dispose(instance, context)\n }\n } else if (isClassInjectable(injectable)) {\n const { instance } = this.instances.get(injectable)!\n await instance.$onDispose()\n }\n } catch (cause) {\n const error = new Error(\n 'Injectable disposal error. Potential memory leak',\n { cause },\n )\n this.application.logger.error(error)\n } finally {\n this.instances.delete(injectable)\n }\n }\n}\n\nexport function createInjectFunction(container: Container) {\n return <T extends AnyInjectable>(\n injectable: T,\n context: InlineInjectionDependencies<T>,\n ) => {\n const dependencies: Dependencies = {\n ...injectable.dependencies,\n }\n\n for (const key in context) {\n const dep = context[key]\n if (isInjectable(dep) || isOptionalInjectable(dep)) {\n dependencies[key] = dep\n } else {\n dependencies[key] = createValueInjectable(dep)\n }\n }\n\n const newInjectable = {\n ...injectable,\n dependencies,\n scope: Scope.Transient,\n }\n\n return container.resolve(newInjectable)\n }\n}\n\ntype InlineInjectionDependencies<T extends AnyInjectable> = {\n [K in keyof T['dependencies']]?:\n | ResolveInjectableType<T['dependencies'][K]>\n | AnyInjectable<ResolveInjectableType<T['dependencies'][K]>>\n}\n\nexport type InjectFn = ReturnType<typeof createInjectFunction>\n"],"version":3,"file":"container.js"}
|
|
1
|
+
{"mappings":"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","names":["application: ContainerOptions","scope: Exclude<Scope, Scope.Transient>","parent?: Container","dependencies: Dependencies","injectable: AnyInjectable","injectable: T","dependencies: T","dependant?: AnyInjectable","injections: Record<string, any>","resolvers: Promise<any>[]","instance: ResolveInjectableType<T>","context: InlineInjectionDependencies<T>","instance?: any","result: AnyInjectable[]","instance: any","context: any"],"sources":["../src/container.ts"],"sourcesContent":["import assert from 'node:assert'\nimport { tryCaptureStackTrace } from '@nmtjs/common'\nimport { Scope } from './enums.ts'\nimport {\n type AnyInjectable,\n CoreInjectables,\n compareScope,\n createExtendableClassInjectable,\n createValueInjectable,\n type Dependencies,\n type DependencyContext,\n getDepedencencyInjectable,\n isClassInjectable,\n isFactoryInjectable,\n isInjectable,\n isLazyInjectable,\n isOptionalInjectable,\n isValueInjectable,\n type ResolveInjectableType,\n} from './injectables.ts'\nimport type { Logger } from './logger.ts'\nimport type { Registry } from './registry.ts'\n\ntype InstanceWrapper = { private: any; public: any; context: any }\n\ntype ContainerOptions = {\n registry: Registry\n logger: Logger\n}\n\nexport class Container {\n readonly instances = new Map<AnyInjectable, InstanceWrapper[]>()\n private readonly resolvers = new Map<AnyInjectable, Promise<any>>()\n private readonly injectables = new Set<AnyInjectable>()\n private readonly dependants = new Map<AnyInjectable, Set<AnyInjectable>>()\n // private readonly transients = new Map<any, any>()\n private disposing = false\n\n constructor(\n private readonly application: ContainerOptions,\n public readonly scope: Exclude<Scope, Scope.Transient> = Scope.Global,\n private readonly parent?: Container,\n ) {\n if ((scope as any) === Scope.Transient) {\n throw new Error('Invalid scope')\n }\n this.provide(CoreInjectables.inject, this.createInjectFunction())\n this.provide(CoreInjectables.dispose, this.createDisposeFunction())\n this.provide(CoreInjectables.registry, application.registry)\n }\n\n async load() {\n const traverse = (dependencies: Dependencies) => {\n for (const key in dependencies) {\n const dependency = dependencies[key]\n const injectable = getDepedencencyInjectable(dependency)\n this.injectables.add(injectable)\n traverse(injectable.dependencies)\n }\n }\n\n for (const dependant of this.application.registry.getDependants()) {\n traverse(dependant.dependencies)\n }\n\n const injectables = Array.from(this.findCurrentScopeInjectables())\n await Promise.all(injectables.map((injectable) => this.resolve(injectable)))\n }\n\n fork(scope: Exclude<Scope, Scope.Transient>) {\n return new Container(this.application, scope, this)\n }\n\n async dispose() {\n this.application.logger.trace('Disposing [%s] scope context...', this.scope)\n\n // Prevent new resolutions during disposal\n this.disposing = true\n\n // Get proper disposal order using topological sort\n const disposalOrder = this.getDisposalOrder()\n\n // Dispose in the correct order\n for (const injectable of disposalOrder) {\n if (this.instances.has(injectable)) {\n await this.disposeInjectableInstances(injectable)\n }\n }\n\n this.instances.clear()\n this.injectables.clear()\n this.resolvers.clear()\n this.dependants.clear()\n\n this.disposing = false\n }\n\n containsWithinSelf(injectable: AnyInjectable) {\n return this.instances.has(injectable) || this.resolvers.has(injectable)\n }\n\n contains(injectable: AnyInjectable): boolean {\n return (\n this.containsWithinSelf(injectable) ||\n (this.parent?.contains(injectable) ?? false)\n )\n }\n\n get<T extends AnyInjectable>(injectable: T): ResolveInjectableType<T> {\n if (injectable.scope === Scope.Transient) {\n throw new Error('Cannot get transient injectable directly')\n }\n\n if (this.instances.has(injectable)) {\n return this.instances.get(injectable)!.at(0)!.public\n }\n\n if (this.parent?.contains(injectable)) {\n return this.parent.get(injectable)\n }\n\n throw new Error('No instance found')\n }\n\n resolve<T extends AnyInjectable>(injectable: T) {\n return this.resolveInjectable(injectable)\n }\n\n async createContext<T extends Dependencies>(dependencies: T) {\n return this.createInjectableContext(dependencies)\n }\n\n private async createInjectableContext<T extends Dependencies>(\n dependencies: T,\n dependant?: AnyInjectable,\n ) {\n const injections: Record<string, any> = {}\n const deps = Object.entries(dependencies)\n const resolvers: Promise<any>[] = Array(deps.length)\n for (let i = 0; i < deps.length; i++) {\n const [key, dependency] = deps[i]\n const injectable = getDepedencencyInjectable(dependency)\n const resolver = this.resolveInjectable(injectable, dependant)\n resolvers[i] = resolver.then((value) => (injections[key] = value))\n }\n await Promise.all(resolvers)\n return Object.freeze(injections) as DependencyContext<T>\n }\n\n async provide<T extends AnyInjectable>(\n injectable: T,\n instance: ResolveInjectableType<T>,\n ) {\n if (compareScope(injectable.scope, '>', this.scope)) {\n throw new Error('Invalid scope') // TODO: more informative error\n }\n\n this.instances.set(injectable, [\n {\n private: instance,\n public: instance,\n context: undefined,\n },\n ])\n }\n\n satisfies(injectable: AnyInjectable) {\n return compareScope(injectable.scope, '<=', this.scope)\n }\n\n private *findCurrentScopeInjectables() {\n for (const injectable of this.injectables) {\n if (injectable.scope === this.scope) {\n yield injectable\n }\n }\n }\n\n private resolveInjectable<T extends AnyInjectable>(\n injectable: T,\n dependant?: AnyInjectable,\n ): Promise<ResolveInjectableType<T>> {\n if (this.disposing) {\n throw new Error('Cannot resolve during disposal')\n }\n\n if (dependant && compareScope(dependant.scope, '<', injectable.scope)) {\n // TODO: more informative error\n throw new Error('Invalid scope: dependant is looser than injectable')\n }\n\n if (isValueInjectable(injectable)) {\n return Promise.resolve(injectable.value)\n } else if (\n this.parent?.contains(injectable) ||\n (this.parent?.satisfies(injectable) &&\n compareScope(this.parent.scope, '<', this.scope))\n ) {\n return this.parent.resolveInjectable(injectable, dependant)\n } else {\n const { stack, label } = injectable\n\n if (dependant) {\n let dependants = this.dependants.get(injectable)\n if (!dependants) {\n this.dependants.set(injectable, (dependants = new Set()))\n }\n dependants.add(dependant)\n }\n\n const isTransient = injectable.scope === Scope.Transient\n\n if (!isTransient && this.instances.has(injectable)) {\n return Promise.resolve(this.instances.get(injectable)!.at(0)!.public)\n } else if (!isTransient && this.resolvers.has(injectable)) {\n return this.resolvers.get(injectable)!\n } else {\n const isLazy = isLazyInjectable(injectable)\n\n if (isLazy) {\n const isOptional = isOptionalInjectable(injectable)\n if (isOptional) return Promise.resolve(undefined as any)\n return Promise.reject(\n new Error(\n `No instance provided for ${label || 'an'} injectable:\\n${stack}`,\n ),\n )\n } else {\n const resolution = this.createResolution(injectable).finally(() => {\n this.resolvers.delete(injectable)\n })\n if (injectable.scope !== Scope.Transient) {\n this.resolvers.set(injectable, resolution)\n }\n return resolution\n }\n }\n }\n }\n\n private async createResolution<T extends AnyInjectable>(\n injectable: T,\n ): Promise<ResolveInjectableType<T>> {\n const { dependencies } = injectable\n const context = await this.createInjectableContext(dependencies, injectable)\n const wrapper = {\n private: null as any,\n public: null as ResolveInjectableType<T>,\n context,\n }\n if (isFactoryInjectable(injectable)) {\n wrapper.private = await Promise.resolve(\n injectable.factory(wrapper.context),\n )\n wrapper.public = injectable.pick(wrapper.private)\n } else if (isClassInjectable(injectable)) {\n wrapper.private = new injectable(context)\n wrapper.public = wrapper.private\n await wrapper.private.$onCreate()\n } else {\n throw new Error('Invalid injectable type')\n }\n\n let instances = this.instances.get(injectable)\n\n if (!instances) {\n instances = []\n this.instances.set(injectable, instances)\n }\n instances.push(wrapper)\n\n return wrapper.public\n }\n\n private createInjectFunction() {\n const inject = <T extends AnyInjectable>(\n injectable: T,\n context: InlineInjectionDependencies<T>,\n ) => {\n const dependencies: Dependencies = {\n ...injectable.dependencies,\n }\n\n for (const key in context) {\n const dep = context[key]\n if (isInjectable(dep) || isOptionalInjectable(dep)) {\n dependencies[key] = dep\n } else {\n dependencies[key] = createValueInjectable(dep)\n }\n }\n\n const newInjectable = isClassInjectable(injectable)\n ? createExtendableClassInjectable(\n injectable,\n dependencies,\n Scope.Transient,\n 1,\n )\n : {\n ...injectable,\n dependencies,\n scope: Scope.Transient,\n stack: tryCaptureStackTrace(1),\n }\n\n return this.resolve(newInjectable) as Promise<ResolveInjectableType<T>>\n }\n\n const explicit = async <T extends AnyInjectable>(\n injectable: T,\n context: InlineInjectionDependencies<T>,\n ) => {\n if ('asyncDispose' in Symbol === false) {\n throw new Error(\n 'Symbol.asyncDispose is not supported in this environment',\n )\n }\n const instance = await inject(injectable, context)\n const dispose = this.createDisposeFunction()\n return Object.assign(instance, {\n [Symbol.asyncDispose]: async () => {\n await dispose(injectable, instance)\n },\n })\n }\n\n return Object.assign(inject, { explicit })\n }\n\n private createDisposeFunction() {\n return async <T extends AnyInjectable>(injectable: T, instance?: any) => {\n if (injectable.scope === Scope.Transient) {\n assert(\n instance,\n 'Instance is required for transient injectable disposal',\n )\n const wrappers = this.instances.get(injectable)\n if (wrappers) {\n for (const wrapper of wrappers) {\n if (wrapper.public === instance) {\n await this.disposeInjectableInstance(\n injectable,\n wrapper.private,\n wrapper.context,\n )\n const index = wrappers.indexOf(wrapper)\n wrappers.splice(index, 1)\n }\n }\n\n if (wrappers.length === 0) {\n this.instances.delete(injectable)\n }\n }\n } else {\n await this.disposeInjectableInstances(injectable)\n }\n }\n }\n\n private getDisposalOrder(): AnyInjectable[] {\n const visited = new Set<AnyInjectable>()\n const result: AnyInjectable[] = []\n\n const visit = (injectable: AnyInjectable) => {\n if (visited.has(injectable)) return\n visited.add(injectable)\n\n const dependants = this.dependants.get(injectable)\n if (dependants) {\n for (const dependant of dependants) {\n if (this.instances.has(dependant)) {\n visit(dependant)\n }\n }\n }\n\n // Only add to result if this container owns the instance\n if (this.instances.has(injectable)) {\n result.push(injectable)\n }\n }\n\n for (const injectable of this.instances.keys()) {\n visit(injectable)\n }\n\n return result\n }\n\n private async disposeInjectableInstances(injectable: AnyInjectable) {\n try {\n if (this.instances.has(injectable)) {\n const wrappers = this.instances.get(injectable)!\n await Promise.all(\n wrappers.map((wrapper) =>\n this.disposeInjectableInstance(\n injectable,\n wrapper.private,\n wrapper.context,\n ),\n ),\n )\n }\n } catch (cause) {\n const error = new Error(\n 'Injectable disposal error. Potential memory leak',\n { cause },\n )\n this.application.logger.error(error)\n } finally {\n this.instances.delete(injectable)\n }\n }\n\n private async disposeInjectableInstance(\n injectable: AnyInjectable,\n instance: any,\n context: any,\n ) {\n if (isFactoryInjectable(injectable)) {\n const { dispose } = injectable\n if (dispose) await dispose(instance, context)\n } else if (isClassInjectable(injectable)) {\n await instance.$onDispose()\n }\n }\n}\n\ntype InlineInjectionDependencies<T extends AnyInjectable> = {\n [K in keyof T['dependencies']]?:\n | ResolveInjectableType<T['dependencies'][K]>\n | AnyInjectable<ResolveInjectableType<T['dependencies'][K]>>\n}\n\nexport type InjectFn = ReturnType<Container['createInjectFunction']>\nexport type DisposeFn = ReturnType<Container['createDisposeFunction']>\n"],"version":3,"file":"container.js"}
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"AACA,SAAS,uBAAuB,gBAAgB;
|
|
1
|
+
{"mappings":"AACA,SAAS,uBAAuB,gBAAgB;AAuBhD,OAAO,MAAM,MAAM;CACjB,OAAO,MAAMA,MAAaC,IAAW;AACnC,OAAK,MAAM,CAAC,MAAM,UAAU,IAAI,KAAK,kBAAkB;AACrD,QAAK,MAAM,YAAY,WAAW;AAChC,OAAG,IAAI,MAAM,SAAS;GACvB;EACF;CACF;CAED,CAAC,mBAAmB,IAAI;CAExB,IAAIC,MAAcC,UAAoB;EACpC,IAAI,QAAQ,KAAK,iBAAiB,IAAI,KAAK;AAC3C,OAAK,MAAO,MAAK,iBAAiB,IAAI,MAAO,QAAQ,IAAI,MAAO;AAChE,QAAM,IAAI,SAAS;AACnB,SAAO,MAAM,KAAK,OAAO,MAAM,SAAS;CACzC;CAED,OAAOD,MAAcC,UAAoB;EACvC,MAAM,QAAQ,KAAK,iBAAiB,IAAI,KAAK;AAC7C,MAAI,MAAO,OAAM,OAAO,SAAS;CAClC;CAED,MAAM,KACJC,MACAC,SACA,GAAG,MACH;EACA,MAAM,EAAE,aAAa,MAAM,UAAU,OAAO,GAAG,WAAW,CAAE;EAC5D,MAAM,QAAQ,KAAK,iBAAiB,IAAI,KAAK;AAC7C,OAAK,MAAO;EACZ,MAAM,WAAW,MAAM,KAAK,MAAM;AAClC,MAAI,YAAY;AACd,SAAM,QAAQ,IAAI,SAAS,IAAI,CAAC,SAAS,KAAK,GAAG,KAAK,CAAC,CAAC;EACzD,OAAM;AACL,OAAI,QAAS,UAAS,SAAS;AAC/B,QAAK,MAAM,QAAQ,SAAU,OAAM,KAAK,GAAG,KAAK;EACjD;CACF;CAED,QAAQ;AACN,OAAK,iBAAiB,OAAO;CAC9B;AACF;AAED,OAAO,MAAM,mBAAmB,CAACC,UAC9B,gBAAgB,KAAK","names":["from: Hooks","to: Hooks","name: string","callback: Callback","name: T","options: { concurrent?: boolean; reverse?: boolean } | undefined","hook: Hook | (object & string)"],"sources":["../src/hooks.ts"],"sourcesContent":["import type { Callback } from '../../common/src/index.ts'\nimport { kHookCollection } from './constants.ts'\nimport type { Hook } from './enums.ts'\n\n// import type { HookType } from './types.ts'\n\nexport interface HookType {\n [key: string]: (...args: any[]) => any\n // [Hook.AfterInitialize]: () => any\n // [Hook.BeforeStart]: () => any\n // [Hook.AfterStart]: () => any\n // [Hook.BeforeStop]: () => any\n // [Hook.AfterStop]: () => any\n // [Hook.BeforeTerminate]: () => any\n // [Hook.AfterTerminate]: () => any\n // [Hook.OnConnect]: (...args: any[]) => any\n // [Hook.OnDisconnect]: (...args: any[]) => any\n}\n\nexport type CallHook<T extends string> = (\n hook: T,\n ...args: T extends keyof HookType ? Parameters<HookType[T]> : any[]\n) => Promise<void>\n\nexport class Hooks {\n static merge(from: Hooks, to: Hooks) {\n for (const [name, callbacks] of from[kHookCollection]) {\n for (const callback of callbacks) {\n to.add(name, callback)\n }\n }\n }\n\n [kHookCollection] = new Map<string, Set<Callback>>()\n\n add(name: string, callback: Callback) {\n let hooks = this[kHookCollection].get(name)\n if (!hooks) this[kHookCollection].set(name, (hooks = new Set()))\n hooks.add(callback)\n return () => this.remove(name, callback)\n }\n\n remove(name: string, callback: Callback) {\n const hooks = this[kHookCollection].get(name)\n if (hooks) hooks.delete(callback)\n }\n\n async call<T extends string | Hook>(\n name: T,\n options: { concurrent?: boolean; reverse?: boolean } | undefined,\n ...args: T extends Hook ? Parameters<HookType[T]> : any[]\n ) {\n const { concurrent = true, reverse = false } = options ?? {}\n const hooks = this[kHookCollection].get(name)\n if (!hooks) return\n const hooksArr = Array.from(hooks)\n if (concurrent) {\n await Promise.all(hooksArr.map((hook) => hook(...args)))\n } else {\n if (reverse) hooksArr.reverse()\n for (const hook of hooksArr) await hook(...args)\n }\n }\n\n clear() {\n this[kHookCollection].clear()\n }\n}\n\nexport const createErrForHook = (hook: Hook | (object & string)) =>\n `Error during [${hook}] hook`\n"],"version":3,"file":"hooks.js"}
|
package/dist/injectables.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { tryCaptureStackTrace } from "@nmtjs/common";
|
|
2
|
-
import { kClassInjectable, kFactoryInjectable, kInjectable, kLazyInjectable, kOptionalDependency, kValueInjectable } from "./constants.js";
|
|
2
|
+
import { kClassInjectable, kFactoryInjectable, kHookCollection, kInjectable, kLazyInjectable, kOptionalDependency, kValueInjectable } from "./constants.js";
|
|
3
3
|
import { Scope } from "./enums.js";
|
|
4
|
+
import { Hooks } from "./hooks.js";
|
|
4
5
|
const ScopeStrictness = {
|
|
5
|
-
[Scope.
|
|
6
|
-
[Scope.
|
|
7
|
-
[Scope.
|
|
8
|
-
[Scope.
|
|
6
|
+
[Scope.Transient]: Number.NaN,
|
|
7
|
+
[Scope.Global]: 1,
|
|
8
|
+
[Scope.Connection]: 2,
|
|
9
|
+
[Scope.Call]: 3
|
|
9
10
|
};
|
|
10
11
|
export const isLazyInjectable = (injectable) => kLazyInjectable in injectable;
|
|
11
12
|
export const isFactoryInjectable = (injectable) => kFactoryInjectable in injectable;
|
|
@@ -15,11 +16,12 @@ export const isInjectable = (injectable) => kInjectable in injectable;
|
|
|
15
16
|
export const isOptionalInjectable = (injectable) => kOptionalDependency in injectable;
|
|
16
17
|
export function getInjectableScope(injectable) {
|
|
17
18
|
let scope = injectable.scope;
|
|
18
|
-
const deps =
|
|
19
|
-
for (const
|
|
19
|
+
const deps = injectable.dependencies;
|
|
20
|
+
for (const key in deps) {
|
|
21
|
+
const dependency = deps[key];
|
|
20
22
|
const injectable = getDepedencencyInjectable(dependency);
|
|
21
23
|
const dependencyScope = getInjectableScope(injectable);
|
|
22
|
-
if (compareScope(dependencyScope, ">", scope)) {
|
|
24
|
+
if (dependencyScope !== Scope.Transient && scope !== Scope.Transient && compareScope(dependencyScope, ">", scope)) {
|
|
23
25
|
scope = dependencyScope;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -32,10 +34,10 @@ export function getDepedencencyInjectable(dependency) {
|
|
|
32
34
|
return dependency;
|
|
33
35
|
}
|
|
34
36
|
export function createOptionalInjectable(injectable) {
|
|
35
|
-
return {
|
|
37
|
+
return Object.freeze({
|
|
36
38
|
[kOptionalDependency]: true,
|
|
37
39
|
injectable
|
|
38
|
-
};
|
|
40
|
+
});
|
|
39
41
|
}
|
|
40
42
|
export function createLazyInjectable(scope = Scope.Global, label, stackTraceDepth = 0) {
|
|
41
43
|
return Object.freeze({
|
|
@@ -72,15 +74,13 @@ export function createFactoryInjectable(paramsOrFactory, label, stackTraceDepth
|
|
|
72
74
|
[kInjectable]: true,
|
|
73
75
|
[kFactoryInjectable]: true
|
|
74
76
|
};
|
|
75
|
-
|
|
76
|
-
if (!isFactory && params.scope && ScopeStrictness[actualScope] > ScopeStrictness[params.scope]) throw new Error(`Invalid scope ${params.scope} for factory injectable: dependencies have stricter scope - ${actualScope}`);
|
|
77
|
-
injectable.scope = actualScope;
|
|
77
|
+
injectable.scope = resolveInjectableScope(typeof params.scope === "undefined", injectable);
|
|
78
78
|
return Object.freeze(injectable);
|
|
79
79
|
}
|
|
80
|
-
export const createClassInjectable = (dependencies = {}, scope
|
|
81
|
-
const
|
|
80
|
+
export const createClassInjectable = (dependencies = {}, scope, stackTraceDepth = 0) => {
|
|
81
|
+
const InjectableClass = class {
|
|
82
82
|
static dependencies = dependencies;
|
|
83
|
-
static scope = scope;
|
|
83
|
+
static scope = scope ?? Scope.Global;
|
|
84
84
|
static stack = tryCaptureStackTrace(stackTraceDepth + 2);
|
|
85
85
|
static [kInjectable] = true;
|
|
86
86
|
static [kClassInjectable] = true;
|
|
@@ -93,18 +93,21 @@ export const createClassInjectable = (dependencies = {}, scope = Scope.Global, s
|
|
|
93
93
|
async $onCreate() {}
|
|
94
94
|
async $onDispose() {}
|
|
95
95
|
};
|
|
96
|
-
|
|
96
|
+
InjectableClass.scope = resolveInjectableScope(typeof scope === "undefined", InjectableClass);
|
|
97
|
+
return InjectableClass;
|
|
97
98
|
};
|
|
98
|
-
export function createExtendableClassInjectable(baseClass, dependencies = {}, scope
|
|
99
|
+
export function createExtendableClassInjectable(baseClass, dependencies = {}, scope, stackTraceDepth = 0) {
|
|
99
100
|
if (isClassInjectable(baseClass)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if (scope && compareScope(baseClass.scope, ">", scope)) {
|
|
102
|
+
throw new Error(`Invalid scope ${scope} for an extendable class injectable: base class have stricter scope - ${baseClass.scope}`);
|
|
103
|
+
} else {
|
|
104
|
+
scope = scope ?? baseClass.scope;
|
|
103
105
|
}
|
|
106
|
+
dependencies = Object.assign({}, baseClass.dependencies, dependencies);
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
const InjectableClass = class extends baseClass {
|
|
106
109
|
static dependencies = dependencies;
|
|
107
|
-
static scope = scope;
|
|
110
|
+
static scope = scope ?? Scope.Global;
|
|
108
111
|
static stack = tryCaptureStackTrace(stackTraceDepth);
|
|
109
112
|
static [kInjectable] = true;
|
|
110
113
|
static [kClassInjectable] = true;
|
|
@@ -128,6 +131,8 @@ export function createExtendableClassInjectable(baseClass, dependencies = {}, sc
|
|
|
128
131
|
await super.$onDispose?.();
|
|
129
132
|
}
|
|
130
133
|
};
|
|
134
|
+
InjectableClass.scope = resolveInjectableScope(typeof scope === "undefined", InjectableClass);
|
|
135
|
+
return InjectableClass;
|
|
131
136
|
}
|
|
132
137
|
export function substitute(injectable, substitution, stackTraceDepth = 0) {
|
|
133
138
|
const dependencies = { ...injectable.dependencies };
|
|
@@ -166,9 +171,45 @@ export function compareScope(left, operator, right) {
|
|
|
166
171
|
default: throw new Error("Invalid operator");
|
|
167
172
|
}
|
|
168
173
|
}
|
|
174
|
+
const logger = createLazyInjectable(Scope.Global, "Logger");
|
|
175
|
+
const registry = createLazyInjectable(Scope.Global, "Registry");
|
|
176
|
+
const inject = createLazyInjectable(Scope.Global, "Inject function");
|
|
177
|
+
const dispose = createLazyInjectable(Scope.Global, "Dispose function");
|
|
178
|
+
const hook = createFactoryInjectable({
|
|
179
|
+
scope: Scope.Transient,
|
|
180
|
+
dependencies: { registry },
|
|
181
|
+
factory: ({ registry }) => {
|
|
182
|
+
const hooks = new Hooks();
|
|
183
|
+
const on = (name, callback) => {
|
|
184
|
+
hooks.add(name, callback);
|
|
185
|
+
return registry.hooks.add(name, callback);
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
hooks,
|
|
189
|
+
on
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
pick: ({ on }) => on,
|
|
193
|
+
dispose: ({ hooks }, { registry }) => {
|
|
194
|
+
for (const [hook, callbacks] of hooks[kHookCollection].entries()) {
|
|
195
|
+
for (const callback of callbacks) {
|
|
196
|
+
registry.hooks.remove(hook, callback);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
hooks.clear();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
function resolveInjectableScope(isDefaultScope, injectable) {
|
|
203
|
+
const actualScope = getInjectableScope(injectable);
|
|
204
|
+
if (!isDefaultScope && compareScope(actualScope, ">", injectable.scope)) throw new Error(`Invalid scope ${injectable.scope} for an injectable: dependencies have stricter scope - ${actualScope}`);
|
|
205
|
+
return actualScope;
|
|
206
|
+
}
|
|
169
207
|
export const CoreInjectables = {
|
|
170
|
-
logger
|
|
171
|
-
|
|
208
|
+
logger,
|
|
209
|
+
registry,
|
|
210
|
+
inject,
|
|
211
|
+
dispose,
|
|
212
|
+
hook
|
|
172
213
|
};
|
|
173
214
|
|
|
174
215
|
//# sourceMappingURL=injectables.js.map
|
package/dist/injectables.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":"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","names":["injectable: any","injectable: AnyInjectable","dependency: Depedency","injectable: T","label?: string","value: T","paramsOrFactory:\n | {\n dependencies?: D\n scope?: S\n pick?: InjectablePickType<P, T>\n factory: InjectableFactoryType<P, D>\n dispose?: InjectableDisposeType<P, D>\n }\n | InjectableFactoryType<P, D>","instance: P","dependencies: D","scope: S","$context: DependencyContext<D>","baseClass: B","substitution: DependenciesSubstitution<T['dependencies']>","left: Scope","operator: '>' | '<' | '>=' | '<=' | '=' | '!='","right: Scope"],"sources":["../src/injectables.ts"],"sourcesContent":["import {\n type Async,\n type ClassConstructor,\n type ClassConstructorArgs,\n type ClassInstance,\n tryCaptureStackTrace,\n} from '@nmtjs/common'\nimport {\n kClassInjectable,\n kFactoryInjectable,\n kInjectable,\n kLazyInjectable,\n kOptionalDependency,\n kValueInjectable,\n} from './constants.ts'\nimport type { InjectFn } from './container.ts'\nimport { Scope } from './enums.ts'\nimport type { Logger } from './logger.ts'\n\nconst ScopeStrictness = {\n [Scope.Global]: 0,\n [Scope.Connection]: 1,\n [Scope.Call]: 2,\n [Scope.Transient]: 3,\n}\n\nexport type DependencyOptional<T extends AnyInjectable = AnyInjectable> = {\n [kOptionalDependency]: any\n injectable: T\n}\n\nexport type Depedency = DependencyOptional | AnyInjectable\n\nexport type Dependencies = Record<string, Depedency>\n\nexport type ResolveInjectableType<T extends AnyInjectable> =\n T extends Injectable<infer Type, any, any> ? Type : never\n\nexport interface Dependant<Deps extends Dependencies = Dependencies> {\n dependencies: Deps\n label?: string\n stack?: string\n}\n\nexport type DependencyInjectable<T extends Depedency> = T extends AnyInjectable\n ? T\n : T extends DependencyOptional\n ? T['injectable']\n : never\n\nexport type DependencyContext<Deps extends Dependencies> = {\n readonly [K in keyof Deps as Deps[K] extends AnyInjectable\n ? K\n : never]: Deps[K] extends AnyInjectable\n ? ResolveInjectableType<Deps[K]>\n : never\n} & {\n readonly [K in keyof Deps as Deps[K] extends DependencyOptional\n ? K\n : never]?: Deps[K] extends DependencyOptional\n ? ResolveInjectableType<Deps[K]['injectable']>\n : never\n}\n\nexport type InjectableFactoryType<\n InjectableType,\n InjectableDeps extends Dependencies,\n> = (context: DependencyContext<InjectableDeps>) => Async<InjectableType>\n\nexport type InjectablePickType<Input, Output> = (injectable: Input) => Output\n\nexport type InjectableDisposeType<\n InjectableType,\n InjectableDeps extends Dependencies,\n> = (\n instance: InjectableType,\n context: DependencyContext<InjectableDeps>,\n) => any\n\nexport interface LazyInjectable<T, S extends Scope = Scope.Global>\n extends Dependant<{}> {\n scope: S\n [kInjectable]: any\n [kLazyInjectable]: T\n}\n\nexport interface ValueInjectable<T> extends Dependant<{}> {\n scope: Scope.Global\n value: T\n [kInjectable]: any\n [kValueInjectable]: any\n}\n\nexport interface FactoryInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n P = T,\n> extends Dependant<D> {\n scope: S\n factory: InjectableFactoryType<P, D>\n pick: InjectablePickType<P, T>\n dispose?: InjectableDisposeType<P, D>\n [kInjectable]: any\n [kFactoryInjectable]: any\n}\n\nexport interface BaseClassInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n> extends Dependant<D> {\n new (...args: any[]): T\n scope: S\n [kInjectable]: any\n [kClassInjectable]: any\n}\n\nexport interface ClassInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n A extends any[] = [],\n> extends Dependant<D> {\n new (\n $context: DependencyContext<D>,\n ...args: A\n ): T & {\n $context: DependencyContext<D>\n }\n scope: S\n [kInjectable]: any\n [kClassInjectable]: any\n}\n\nexport type Injectable<\n V = any,\n D extends Dependencies = {},\n S extends Scope = Scope,\n> =\n | LazyInjectable<V, S>\n | ValueInjectable<V>\n | FactoryInjectable<V, D, S, any>\n | BaseClassInjectable<V, D, S>\n\nexport type AnyInjectable<T = any, S extends Scope = Scope> = Injectable<\n T,\n any,\n S\n>\n\nexport const isLazyInjectable = (\n injectable: any,\n): injectable is LazyInjectable<any> => kLazyInjectable in injectable\nexport const isFactoryInjectable = (\n injectable: any,\n): injectable is FactoryInjectable<any> => kFactoryInjectable in injectable\nexport const isClassInjectable = (\n injectable: any,\n): injectable is ClassInjectable<any> => kClassInjectable in injectable\nexport const isValueInjectable = (\n injectable: any,\n): injectable is ValueInjectable<any> => kValueInjectable in injectable\nexport const isInjectable = (\n injectable: any,\n): injectable is AnyInjectable<any> => kInjectable in injectable\nexport const isOptionalInjectable = (\n injectable: any,\n): injectable is DependencyOptional<any> => kOptionalDependency in injectable\n\nexport function getInjectableScope(injectable: AnyInjectable) {\n let scope = injectable.scope\n const deps = Object.values(injectable.dependencies as Dependencies)\n for (const dependency of deps) {\n const injectable = getDepedencencyInjectable(dependency)\n const dependencyScope = getInjectableScope(injectable)\n if (compareScope(dependencyScope, '>', scope)) {\n scope = dependencyScope\n }\n }\n return scope\n}\n\nexport function getDepedencencyInjectable(\n dependency: Depedency,\n): AnyInjectable {\n if (kOptionalDependency in dependency) {\n return dependency.injectable\n }\n return dependency\n}\n\nexport function createOptionalInjectable<T extends AnyInjectable>(\n injectable: T,\n) {\n return {\n [kOptionalDependency]: true,\n injectable,\n } as DependencyOptional<T>\n}\n\nexport function createLazyInjectable<T, S extends Scope = Scope.Global>(\n scope = Scope.Global as S,\n label?: string,\n stackTraceDepth = 0,\n): LazyInjectable<T, S> {\n return Object.freeze({\n scope,\n dependencies: {},\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kLazyInjectable]: true as unknown as T,\n })\n}\n\nexport function createValueInjectable<T>(\n value: T,\n label?: string,\n stackTraceDepth = 0,\n): ValueInjectable<T> {\n return Object.freeze({\n value,\n scope: Scope.Global,\n dependencies: {},\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kValueInjectable]: true,\n })\n}\n\nexport function createFactoryInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n P = T,\n>(\n paramsOrFactory:\n | {\n dependencies?: D\n scope?: S\n pick?: InjectablePickType<P, T>\n factory: InjectableFactoryType<P, D>\n dispose?: InjectableDisposeType<P, D>\n }\n | InjectableFactoryType<P, D>,\n label?: string,\n stackTraceDepth = 0,\n): FactoryInjectable<null extends T ? P : T, D, S, P> {\n const isFactory = typeof paramsOrFactory === 'function'\n const params = isFactory ? { factory: paramsOrFactory } : paramsOrFactory\n const injectable = {\n dependencies: (params.dependencies ?? {}) as D,\n scope: (params.scope ?? Scope.Global) as S,\n factory: params.factory,\n dispose: params.dispose,\n pick: params.pick ?? ((instance: P) => instance as unknown as T),\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kFactoryInjectable]: true,\n }\n const actualScope = getInjectableScope(injectable)\n if (\n !isFactory &&\n params.scope &&\n ScopeStrictness[actualScope] > ScopeStrictness[params.scope]\n )\n throw new Error(\n `Invalid scope ${params.scope} for factory injectable: dependencies have stricter scope - ${actualScope}`,\n )\n injectable.scope = actualScope as unknown as S\n return Object.freeze(injectable) as any\n}\n\nexport const createClassInjectable = <\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n>(\n dependencies: D = {} as D,\n scope: S = Scope.Global as S,\n stackTraceDepth = 0,\n): ClassInjectable<ClassInstance<typeof klass>, D, S> => {\n const klass = class {\n static dependencies = dependencies\n static scope = scope\n static stack = tryCaptureStackTrace(stackTraceDepth + 2)\n static [kInjectable] = true\n static [kClassInjectable] = true\n\n static get label() {\n // biome-ignore lint/complexity/noThisInStatic: ok\n return this.name\n }\n\n constructor(public $context: DependencyContext<D>) {}\n\n protected async $onCreate() {}\n protected async $onDispose() {}\n }\n return klass\n}\n\nexport function createExtendableClassInjectable<\n B extends ClassConstructor<any>,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n>(\n baseClass: B,\n dependencies: D = {} as D,\n scope: S = Scope.Global as S,\n stackTraceDepth = 0,\n): B extends ClassInjectable<any>\n ? ClassInjectable<ClassInstance<B>, D, S>\n : ClassInjectable<ClassInstance<B>, D, S, ClassConstructorArgs<B, []>> {\n if (isClassInjectable(baseClass)) {\n dependencies = Object.assign({}, baseClass.dependencies, dependencies)\n if (compareScope(scope, '<', baseClass.scope)) {\n throw new Error(\n 'Invalid scope for injectable: base class has stricter scope',\n )\n }\n }\n // @ts-expect-error\n return class extends baseClass {\n static dependencies = dependencies\n static scope = scope\n static stack = tryCaptureStackTrace(stackTraceDepth)\n static [kInjectable] = true\n static [kClassInjectable] = true\n\n static get label() {\n // biome-ignore lint/complexity/noThisInStatic: ok\n return this.name\n }\n\n $context!: DependencyContext<D>\n\n constructor(...args: any[]) {\n const [$context, ...baseClassArgs] = args\n if (isClassInjectable(baseClass)) {\n super($context)\n } else {\n super(...baseClassArgs)\n }\n this.$context = $context\n }\n\n protected async $onCreate() {\n await super.$onCreate?.()\n }\n\n protected async $onDispose() {\n await super.$onDispose?.()\n }\n }\n}\n\nexport type DependenciesSubstitution<T extends Dependencies> = {\n [K in keyof T]?: T[K] extends AnyInjectable<infer Type>\n ? AnyInjectable<Type> | DependenciesSubstitution<T[K]['dependencies']>\n : never\n}\n\nexport function substitute<\n T extends\n | FactoryInjectable<any, any, Scope>\n | BaseClassInjectable<any, any, Scope>,\n>(\n injectable: T,\n substitution: DependenciesSubstitution<T['dependencies']>,\n stackTraceDepth = 0,\n): T {\n const dependencies = { ...injectable.dependencies }\n const depth = stackTraceDepth + 1\n for (const key in substitution) {\n const value = substitution[key]!\n if (key in dependencies) {\n const original = dependencies[key]\n if (isInjectable(value)) {\n dependencies[key] = value\n } else if (isClassInjectable(original) || isFactoryInjectable(original)) {\n dependencies[key] = substitute(original, value, depth)\n }\n }\n }\n\n if (isClassInjectable(injectable)) {\n // @ts-expect-error\n return createExtendableClassInjectable(\n injectable,\n dependencies,\n injectable.scope,\n depth,\n )\n } else if (isFactoryInjectable(injectable)) {\n // @ts-expect-error\n return createFactoryInjectable(\n {\n ...injectable,\n dependencies,\n },\n injectable.label,\n depth,\n )\n }\n\n throw new Error('Invalid injectable type')\n}\n\nexport function compareScope(\n left: Scope,\n operator: '>' | '<' | '>=' | '<=' | '=' | '!=',\n right: Scope,\n) {\n const leftScope = ScopeStrictness[left]\n const rightScope = ScopeStrictness[right]\n switch (operator) {\n case '=':\n return leftScope === rightScope\n case '!=':\n return leftScope !== rightScope\n case '>':\n return leftScope > rightScope\n case '<':\n return leftScope < rightScope\n case '>=':\n return leftScope >= rightScope\n case '<=':\n return leftScope <= rightScope\n default:\n throw new Error('Invalid operator')\n }\n}\n\nexport const CoreInjectables = {\n logger: createLazyInjectable<Logger>(Scope.Global, 'Logger'),\n inject: createLazyInjectable<InjectFn>(Scope.Global, 'Inject function'),\n}\n"],"version":3,"file":"injectables.js"}
|
|
1
|
+
{"mappings":"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","names":["injectable: any","injectable: AnyInjectable","dependency: Depedency","injectable: T","label?: string","value: T","paramsOrFactory:\n | {\n dependencies?: D\n scope?: S\n pick?: InjectablePickType<P, T>\n factory: InjectableFactoryType<P, D>\n dispose?: InjectableDisposeType<P, D>\n }\n | InjectableFactoryType<P, D>","instance: P","dependencies: D","scope?: S","$context: DependencyContext<D>","baseClass: B","substitution: DependenciesSubstitution<T['dependencies']>","left: Scope","operator: '>' | '<' | '>=' | '<=' | '=' | '!='","right: Scope","name: T","callback: HookType[T]","isDefaultScope: boolean"],"sources":["../src/injectables.ts"],"sourcesContent":["import {\n type Async,\n type Callback,\n type ClassConstructor,\n type ClassConstructorArgs,\n type ClassInstance,\n tryCaptureStackTrace,\n} from '@nmtjs/common'\nimport {\n kClassInjectable,\n kFactoryInjectable,\n kHookCollection,\n kInjectable,\n kLazyInjectable,\n kOptionalDependency,\n kValueInjectable,\n} from './constants.ts'\nimport type { DisposeFn, InjectFn } from './container.ts'\nimport { type Hook, Scope } from './enums.ts'\nimport { Hooks, type HookType } from './hooks.ts'\nimport type { Logger } from './logger.ts'\nimport type { Registry } from './registry.ts'\n\nconst ScopeStrictness = {\n [Scope.Transient]: Number.NaN, // this should make it always fail to compare with other scopes\n [Scope.Global]: 1,\n [Scope.Connection]: 2,\n [Scope.Call]: 3,\n}\n\nexport type DependencyOptional<T extends AnyInjectable = AnyInjectable> = {\n [kOptionalDependency]: any\n injectable: T\n}\n\nexport type Depedency = DependencyOptional | AnyInjectable\n\nexport type Dependencies = Record<string, Depedency>\n\nexport type ResolveInjectableType<T extends AnyInjectable> =\n T extends Injectable<infer Type, any, any> ? Type : never\n\nexport interface Dependant<Deps extends Dependencies = Dependencies> {\n dependencies: Deps\n label?: string\n stack?: string\n}\n\nexport type DependencyInjectable<T extends Depedency> = T extends AnyInjectable\n ? T\n : T extends DependencyOptional\n ? T['injectable']\n : never\n\nexport type DependencyContext<Deps extends Dependencies> = {\n readonly [K in keyof Deps as Deps[K] extends AnyInjectable\n ? K\n : never]: Deps[K] extends AnyInjectable\n ? ResolveInjectableType<Deps[K]>\n : never\n} & {\n readonly [K in keyof Deps as Deps[K] extends DependencyOptional\n ? K\n : never]?: Deps[K] extends DependencyOptional\n ? ResolveInjectableType<Deps[K]['injectable']>\n : never\n}\n\nexport type InjectableFactoryType<\n InjectableType,\n InjectableDeps extends Dependencies,\n> = (context: DependencyContext<InjectableDeps>) => Async<InjectableType>\n\nexport type InjectablePickType<Input, Output> = (injectable: Input) => Output\n\nexport type InjectableDisposeType<\n InjectableType,\n InjectableDeps extends Dependencies,\n> = (\n instance: InjectableType,\n context: DependencyContext<InjectableDeps>,\n) => any\n\nexport interface LazyInjectable<T, S extends Scope = Scope.Global>\n extends Dependant<{}> {\n scope: S\n [kInjectable]: any\n [kLazyInjectable]: T\n}\n\nexport interface ValueInjectable<T> extends Dependant<{}> {\n scope: Scope.Global\n value: T\n [kInjectable]: any\n [kValueInjectable]: any\n}\n\nexport interface FactoryInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n P = T,\n> extends Dependant<D> {\n scope: S\n factory: InjectableFactoryType<P, D>\n pick: InjectablePickType<P, T>\n dispose?: InjectableDisposeType<P, D>\n [kInjectable]: any\n [kFactoryInjectable]: any\n}\n\nexport interface BaseClassInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n> extends Dependant<D> {\n new (...args: any[]): T\n scope: S\n [kInjectable]: any\n [kClassInjectable]: any\n}\n\nexport interface ClassInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n A extends any[] = [],\n> extends Dependant<D> {\n new (\n $context: DependencyContext<D>,\n ...args: A\n ): T & {\n $context: DependencyContext<D>\n }\n scope: S\n [kInjectable]: any\n [kClassInjectable]: any\n}\n\nexport type Injectable<\n V = any,\n D extends Dependencies = {},\n S extends Scope = Scope,\n> =\n | LazyInjectable<V, S>\n | ValueInjectable<V>\n | FactoryInjectable<V, D, S, any>\n | BaseClassInjectable<V, D, S>\n\nexport type AnyInjectable<T = any, S extends Scope = Scope> = Injectable<\n T,\n any,\n S\n>\n\nexport const isLazyInjectable = (\n injectable: any,\n): injectable is LazyInjectable<any> => kLazyInjectable in injectable\n\nexport const isFactoryInjectable = (\n injectable: any,\n): injectable is FactoryInjectable<any> => kFactoryInjectable in injectable\n\nexport const isClassInjectable = (\n injectable: any,\n): injectable is ClassInjectable<any> => kClassInjectable in injectable\n\nexport const isValueInjectable = (\n injectable: any,\n): injectable is ValueInjectable<any> => kValueInjectable in injectable\n\nexport const isInjectable = (\n injectable: any,\n): injectable is AnyInjectable<any> => kInjectable in injectable\n\nexport const isOptionalInjectable = (\n injectable: any,\n): injectable is DependencyOptional<any> => kOptionalDependency in injectable\n\nexport function getInjectableScope(injectable: AnyInjectable) {\n let scope = injectable.scope\n const deps = injectable.dependencies as Dependencies\n for (const key in deps) {\n const dependency = deps[key]\n const injectable = getDepedencencyInjectable(dependency)\n const dependencyScope = getInjectableScope(injectable)\n if (\n dependencyScope !== Scope.Transient &&\n scope !== Scope.Transient &&\n compareScope(dependencyScope, '>', scope)\n ) {\n scope = dependencyScope\n }\n }\n return scope\n}\n\nexport function getDepedencencyInjectable(\n dependency: Depedency,\n): AnyInjectable {\n if (kOptionalDependency in dependency) {\n return dependency.injectable\n }\n return dependency\n}\n\nexport function createOptionalInjectable<T extends AnyInjectable>(\n injectable: T,\n) {\n return Object.freeze({\n [kOptionalDependency]: true,\n injectable,\n }) as DependencyOptional<T>\n}\n\nexport function createLazyInjectable<T, S extends Scope = Scope.Global>(\n scope = Scope.Global as S,\n label?: string,\n stackTraceDepth = 0,\n): LazyInjectable<T, S> {\n return Object.freeze({\n scope,\n dependencies: {},\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kLazyInjectable]: true as unknown as T,\n })\n}\n\nexport function createValueInjectable<T>(\n value: T,\n label?: string,\n stackTraceDepth = 0,\n): ValueInjectable<T> {\n return Object.freeze({\n value,\n scope: Scope.Global,\n dependencies: {},\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kValueInjectable]: true,\n })\n}\n\nexport function createFactoryInjectable<\n T,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n P = T,\n>(\n paramsOrFactory:\n | {\n dependencies?: D\n scope?: S\n pick?: InjectablePickType<P, T>\n factory: InjectableFactoryType<P, D>\n dispose?: InjectableDisposeType<P, D>\n }\n | InjectableFactoryType<P, D>,\n label?: string,\n stackTraceDepth = 0,\n): FactoryInjectable<null extends T ? P : T, D, S, P> {\n const isFactory = typeof paramsOrFactory === 'function'\n const params = isFactory ? { factory: paramsOrFactory } : paramsOrFactory\n const injectable = {\n dependencies: (params.dependencies ?? {}) as D,\n scope: (params.scope ?? Scope.Global) as S,\n factory: params.factory,\n dispose: params.dispose,\n pick: params.pick ?? ((instance: P) => instance as unknown as T),\n label,\n stack: tryCaptureStackTrace(stackTraceDepth),\n [kInjectable]: true,\n [kFactoryInjectable]: true,\n }\n injectable.scope = resolveInjectableScope(\n typeof params.scope === 'undefined',\n injectable,\n ) as S\n return Object.freeze(injectable) as any\n}\n\nexport const createClassInjectable = <\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n>(\n dependencies: D = {} as D,\n scope?: S,\n stackTraceDepth = 0,\n): ClassInjectable<ClassInstance<typeof InjectableClass>, D, S> => {\n const InjectableClass = class {\n static dependencies = dependencies\n static scope = (scope ?? Scope.Global) as S\n static stack = tryCaptureStackTrace(stackTraceDepth + 2)\n static [kInjectable] = true\n static [kClassInjectable] = true\n\n static get label() {\n // biome-ignore lint/complexity/noThisInStatic: ok\n return this.name\n }\n\n constructor(public $context: DependencyContext<D>) {}\n\n protected async $onCreate() {}\n protected async $onDispose() {}\n }\n\n InjectableClass.scope = resolveInjectableScope(\n typeof scope === 'undefined',\n InjectableClass,\n ) as S\n\n return InjectableClass\n}\n\nexport function createExtendableClassInjectable<\n B extends ClassConstructor<any>,\n D extends Dependencies = {},\n S extends Scope = Scope.Global,\n>(\n baseClass: B,\n dependencies: D = {} as D,\n scope?: S,\n stackTraceDepth = 0,\n): B extends ClassInjectable<any>\n ? ClassInjectable<ClassInstance<B>, D, S>\n : ClassInjectable<ClassInstance<B>, D, S, ClassConstructorArgs<B, []>> {\n if (isClassInjectable(baseClass)) {\n if (scope && compareScope(baseClass.scope, '>', scope)) {\n throw new Error(\n `Invalid scope ${scope} for an extendable class injectable: base class have stricter scope - ${baseClass.scope}`,\n )\n } else {\n scope = scope ?? (baseClass.scope as S)\n }\n dependencies = Object.assign({}, baseClass.dependencies, dependencies)\n }\n\n const InjectableClass = class extends baseClass {\n static dependencies = dependencies\n static scope = (scope ?? Scope.Global) as S\n static stack = tryCaptureStackTrace(stackTraceDepth)\n static [kInjectable] = true\n static [kClassInjectable] = true\n\n static get label() {\n // biome-ignore lint/complexity/noThisInStatic: ok\n return this.name\n }\n\n $context!: DependencyContext<D>\n\n constructor(...args: any[]) {\n const [$context, ...baseClassArgs] = args\n if (isClassInjectable(baseClass)) {\n super($context)\n } else {\n super(...baseClassArgs)\n }\n this.$context = $context\n }\n\n protected async $onCreate() {\n await super.$onCreate?.()\n }\n\n protected async $onDispose() {\n await super.$onDispose?.()\n }\n }\n\n InjectableClass.scope = resolveInjectableScope(\n typeof scope === 'undefined',\n InjectableClass,\n ) as S\n\n // @ts-expect-error\n return InjectableClass\n}\n\nexport type DependenciesSubstitution<T extends Dependencies> = {\n [K in keyof T]?: T[K] extends AnyInjectable<infer Type>\n ? AnyInjectable<Type> | DependenciesSubstitution<T[K]['dependencies']>\n : never\n}\n\nexport function substitute<\n T extends\n | FactoryInjectable<any, any, Scope>\n | BaseClassInjectable<any, any, Scope>,\n>(\n injectable: T,\n substitution: DependenciesSubstitution<T['dependencies']>,\n stackTraceDepth = 0,\n): T {\n const dependencies = { ...injectable.dependencies }\n const depth = stackTraceDepth + 1\n for (const key in substitution) {\n const value = substitution[key]!\n if (key in dependencies) {\n const original = dependencies[key]\n if (isInjectable(value)) {\n dependencies[key] = value\n } else if (isClassInjectable(original) || isFactoryInjectable(original)) {\n dependencies[key] = substitute(original, value, depth)\n }\n }\n }\n\n if (isClassInjectable(injectable)) {\n // @ts-expect-error\n return createExtendableClassInjectable(\n injectable,\n dependencies,\n injectable.scope,\n depth,\n )\n } else if (isFactoryInjectable(injectable)) {\n // @ts-expect-error\n return createFactoryInjectable(\n {\n ...injectable,\n dependencies,\n },\n injectable.label,\n depth,\n )\n }\n\n throw new Error('Invalid injectable type')\n}\n\nexport function compareScope(\n left: Scope,\n operator: '>' | '<' | '>=' | '<=' | '=' | '!=',\n right: Scope,\n) {\n const leftScope = ScopeStrictness[left]\n const rightScope = ScopeStrictness[right]\n switch (operator) {\n case '=':\n return leftScope === rightScope\n case '!=':\n return leftScope !== rightScope\n case '>':\n return leftScope > rightScope\n case '<':\n return leftScope < rightScope\n case '>=':\n return leftScope >= rightScope\n case '<=':\n return leftScope <= rightScope\n default:\n throw new Error('Invalid operator')\n }\n}\n\nconst logger = createLazyInjectable<Logger>(Scope.Global, 'Logger')\nconst registry = createLazyInjectable<Registry>(Scope.Global, 'Registry')\nconst inject = createLazyInjectable<InjectFn>(Scope.Global, 'Inject function')\nconst dispose = createLazyInjectable<DisposeFn>(\n Scope.Global,\n 'Dispose function',\n)\nconst hook = createFactoryInjectable({\n scope: Scope.Transient,\n dependencies: { registry },\n factory: ({ registry }) => {\n const hooks = new Hooks()\n const on = <T extends Hook>(name: T, callback: HookType[T]) => {\n hooks.add(name, callback as Callback)\n return registry.hooks.add(name, callback)\n }\n return { hooks, on }\n },\n pick: ({ on }) => on,\n dispose: ({ hooks }, { registry }) => {\n for (const [hook, callbacks] of hooks[kHookCollection].entries()) {\n for (const callback of callbacks) {\n registry.hooks.remove(hook, callback)\n }\n }\n hooks.clear()\n },\n})\n\nfunction resolveInjectableScope(\n isDefaultScope: boolean,\n injectable: AnyInjectable,\n) {\n const actualScope = getInjectableScope(injectable)\n if (!isDefaultScope && compareScope(actualScope, '>', injectable.scope))\n throw new Error(\n `Invalid scope ${injectable.scope} for an injectable: dependencies have stricter scope - ${actualScope}`,\n )\n return actualScope\n}\n\nexport const CoreInjectables = {\n logger,\n registry,\n inject,\n dispose,\n hook,\n}\n"],"version":3,"file":"injectables.js"}
|
package/package.json
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
"peerDependencies": {
|
|
11
11
|
"pino": "^9.6.0",
|
|
12
12
|
"pino-pretty": "^13.0.0",
|
|
13
|
-
"@nmtjs/common": "0.11.
|
|
14
|
-
"@nmtjs/type": "0.11.
|
|
13
|
+
"@nmtjs/common": "0.11.4",
|
|
14
|
+
"@nmtjs/type": "0.11.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^20",
|
|
18
18
|
"pino": "^9.6.0",
|
|
19
19
|
"pino-pretty": "^13.0.0",
|
|
20
|
-
"@nmtjs/
|
|
21
|
-
"@nmtjs/
|
|
20
|
+
"@nmtjs/type": "0.11.4",
|
|
21
|
+
"@nmtjs/common": "0.11.4"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"LICENSE.md",
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
|
-
"version": "0.11.
|
|
29
|
+
"version": "0.11.4",
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "neemata-build --root=./src './**/*.ts'",
|
|
32
32
|
"type-check": "tsc --noEmit"
|
package/src/container.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { tryCaptureStackTrace } from '@nmtjs/common'
|
|
1
3
|
import { Scope } from './enums.ts'
|
|
2
4
|
import {
|
|
3
5
|
type AnyInjectable,
|
|
4
6
|
CoreInjectables,
|
|
5
7
|
compareScope,
|
|
8
|
+
createExtendableClassInjectable,
|
|
6
9
|
createValueInjectable,
|
|
7
10
|
type Dependencies,
|
|
8
11
|
type DependencyContext,
|
|
@@ -18,28 +21,32 @@ import {
|
|
|
18
21
|
import type { Logger } from './logger.ts'
|
|
19
22
|
import type { Registry } from './registry.ts'
|
|
20
23
|
|
|
24
|
+
type InstanceWrapper = { private: any; public: any; context: any }
|
|
25
|
+
|
|
26
|
+
type ContainerOptions = {
|
|
27
|
+
registry: Registry
|
|
28
|
+
logger: Logger
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
export class Container {
|
|
22
|
-
readonly instances = new Map<
|
|
23
|
-
AnyInjectable,
|
|
24
|
-
{ instance: any; picked?: any; context?: any }
|
|
25
|
-
>()
|
|
32
|
+
readonly instances = new Map<AnyInjectable, InstanceWrapper[]>()
|
|
26
33
|
private readonly resolvers = new Map<AnyInjectable, Promise<any>>()
|
|
27
34
|
private readonly injectables = new Set<AnyInjectable>()
|
|
28
35
|
private readonly dependants = new Map<AnyInjectable, Set<AnyInjectable>>()
|
|
36
|
+
// private readonly transients = new Map<any, any>()
|
|
29
37
|
private disposing = false
|
|
30
38
|
|
|
31
39
|
constructor(
|
|
32
|
-
private readonly application:
|
|
33
|
-
registry: Registry
|
|
34
|
-
logger: Logger
|
|
35
|
-
},
|
|
40
|
+
private readonly application: ContainerOptions,
|
|
36
41
|
public readonly scope: Exclude<Scope, Scope.Transient> = Scope.Global,
|
|
37
42
|
private readonly parent?: Container,
|
|
38
43
|
) {
|
|
39
44
|
if ((scope as any) === Scope.Transient) {
|
|
40
45
|
throw new Error('Invalid scope')
|
|
41
46
|
}
|
|
42
|
-
this.provide(CoreInjectables.inject, createInjectFunction(
|
|
47
|
+
this.provide(CoreInjectables.inject, this.createInjectFunction())
|
|
48
|
+
this.provide(CoreInjectables.dispose, this.createDisposeFunction())
|
|
49
|
+
this.provide(CoreInjectables.registry, application.registry)
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
async load() {
|
|
@@ -75,7 +82,9 @@ export class Container {
|
|
|
75
82
|
|
|
76
83
|
// Dispose in the correct order
|
|
77
84
|
for (const injectable of disposalOrder) {
|
|
78
|
-
|
|
85
|
+
if (this.instances.has(injectable)) {
|
|
86
|
+
await this.disposeInjectableInstances(injectable)
|
|
87
|
+
}
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
this.instances.clear()
|
|
@@ -98,8 +107,12 @@ export class Container {
|
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
get<T extends AnyInjectable>(injectable: T): ResolveInjectableType<T> {
|
|
110
|
+
if (injectable.scope === Scope.Transient) {
|
|
111
|
+
throw new Error('Cannot get transient injectable directly')
|
|
112
|
+
}
|
|
113
|
+
|
|
101
114
|
if (this.instances.has(injectable)) {
|
|
102
|
-
return this.instances.get(injectable)!.
|
|
115
|
+
return this.instances.get(injectable)!.at(0)!.public
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
if (this.parent?.contains(injectable)) {
|
|
@@ -141,11 +154,14 @@ export class Container {
|
|
|
141
154
|
if (compareScope(injectable.scope, '>', this.scope)) {
|
|
142
155
|
throw new Error('Invalid scope') // TODO: more informative error
|
|
143
156
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
|
|
158
|
+
this.instances.set(injectable, [
|
|
159
|
+
{
|
|
160
|
+
private: instance,
|
|
161
|
+
public: instance,
|
|
162
|
+
context: undefined,
|
|
163
|
+
},
|
|
164
|
+
])
|
|
149
165
|
}
|
|
150
166
|
|
|
151
167
|
satisfies(injectable: AnyInjectable) {
|
|
@@ -182,7 +198,7 @@ export class Container {
|
|
|
182
198
|
) {
|
|
183
199
|
return this.parent.resolveInjectable(injectable, dependant)
|
|
184
200
|
} else {
|
|
185
|
-
const {
|
|
201
|
+
const { stack, label } = injectable
|
|
186
202
|
|
|
187
203
|
if (dependant) {
|
|
188
204
|
let dependants = this.dependants.get(injectable)
|
|
@@ -192,9 +208,11 @@ export class Container {
|
|
|
192
208
|
dependants.add(dependant)
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
const isTransient = injectable.scope === Scope.Transient
|
|
212
|
+
|
|
213
|
+
if (!isTransient && this.instances.has(injectable)) {
|
|
214
|
+
return Promise.resolve(this.instances.get(injectable)!.at(0)!.public)
|
|
215
|
+
} else if (!isTransient && this.resolvers.has(injectable)) {
|
|
198
216
|
return this.resolvers.get(injectable)!
|
|
199
217
|
} else {
|
|
200
218
|
const isLazy = isLazyInjectable(injectable)
|
|
@@ -208,12 +226,10 @@ export class Container {
|
|
|
208
226
|
),
|
|
209
227
|
)
|
|
210
228
|
} else {
|
|
211
|
-
const resolution = this.createResolution(
|
|
212
|
-
injectable
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
if (scope !== Scope.Transient) {
|
|
229
|
+
const resolution = this.createResolution(injectable).finally(() => {
|
|
230
|
+
this.resolvers.delete(injectable)
|
|
231
|
+
})
|
|
232
|
+
if (injectable.scope !== Scope.Transient) {
|
|
217
233
|
this.resolvers.set(injectable, resolution)
|
|
218
234
|
}
|
|
219
235
|
return resolution
|
|
@@ -224,58 +240,122 @@ export class Container {
|
|
|
224
240
|
|
|
225
241
|
private async createResolution<T extends AnyInjectable>(
|
|
226
242
|
injectable: T,
|
|
227
|
-
dependencies: Dependencies,
|
|
228
|
-
scope: Scope,
|
|
229
243
|
): Promise<ResolveInjectableType<T>> {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
244
|
+
const { dependencies } = injectable
|
|
245
|
+
const context = await this.createInjectableContext(dependencies, injectable)
|
|
246
|
+
const wrapper = {
|
|
247
|
+
private: null as any,
|
|
248
|
+
public: null as ResolveInjectableType<T>,
|
|
249
|
+
context,
|
|
250
|
+
}
|
|
251
|
+
if (isFactoryInjectable(injectable)) {
|
|
252
|
+
wrapper.private = await Promise.resolve(
|
|
253
|
+
injectable.factory(wrapper.context),
|
|
254
|
+
)
|
|
255
|
+
wrapper.public = injectable.pick(wrapper.private)
|
|
256
|
+
} else if (isClassInjectable(injectable)) {
|
|
257
|
+
wrapper.private = new injectable(context)
|
|
258
|
+
wrapper.public = wrapper.private
|
|
259
|
+
await wrapper.private.$onCreate()
|
|
260
|
+
} else {
|
|
261
|
+
throw new Error('Invalid injectable type')
|
|
262
|
+
}
|
|
238
263
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
264
|
+
let instances = this.instances.get(injectable)
|
|
265
|
+
|
|
266
|
+
if (!instances) {
|
|
267
|
+
instances = []
|
|
268
|
+
this.instances.set(injectable, instances)
|
|
269
|
+
}
|
|
270
|
+
instances.push(wrapper)
|
|
271
|
+
|
|
272
|
+
return wrapper.public
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private createInjectFunction() {
|
|
276
|
+
const inject = <T extends AnyInjectable>(
|
|
277
|
+
injectable: T,
|
|
278
|
+
context: InlineInjectionDependencies<T>,
|
|
279
|
+
) => {
|
|
280
|
+
const dependencies: Dependencies = {
|
|
281
|
+
...injectable.dependencies,
|
|
282
|
+
}
|
|
242
283
|
|
|
243
|
-
|
|
244
|
-
|
|
284
|
+
for (const key in context) {
|
|
285
|
+
const dep = context[key]
|
|
286
|
+
if (isInjectable(dep) || isOptionalInjectable(dep)) {
|
|
287
|
+
dependencies[key] = dep
|
|
288
|
+
} else {
|
|
289
|
+
dependencies[key] = createValueInjectable(dep)
|
|
245
290
|
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const newInjectable = isClassInjectable(injectable)
|
|
294
|
+
? createExtendableClassInjectable(
|
|
295
|
+
injectable,
|
|
296
|
+
dependencies,
|
|
297
|
+
Scope.Transient,
|
|
298
|
+
1,
|
|
299
|
+
)
|
|
300
|
+
: {
|
|
301
|
+
...injectable,
|
|
302
|
+
dependencies,
|
|
303
|
+
scope: Scope.Transient,
|
|
304
|
+
stack: tryCaptureStackTrace(1),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return this.resolve(newInjectable) as Promise<ResolveInjectableType<T>>
|
|
308
|
+
}
|
|
246
309
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
310
|
+
const explicit = async <T extends AnyInjectable>(
|
|
311
|
+
injectable: T,
|
|
312
|
+
context: InlineInjectionDependencies<T>,
|
|
313
|
+
) => {
|
|
314
|
+
if ('asyncDispose' in Symbol === false) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
'Symbol.asyncDispose is not supported in this environment',
|
|
252
317
|
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
318
|
+
}
|
|
319
|
+
const instance = await inject(injectable, context)
|
|
320
|
+
const dispose = this.createDisposeFunction()
|
|
321
|
+
return Object.assign(instance, {
|
|
322
|
+
[Symbol.asyncDispose]: async () => {
|
|
323
|
+
await dispose(injectable, instance)
|
|
324
|
+
},
|
|
325
|
+
})
|
|
326
|
+
}
|
|
263
327
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
328
|
+
return Object.assign(inject, { explicit })
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private createDisposeFunction() {
|
|
332
|
+
return async <T extends AnyInjectable>(injectable: T, instance?: any) => {
|
|
333
|
+
if (injectable.scope === Scope.Transient) {
|
|
334
|
+
assert(
|
|
335
|
+
instance,
|
|
336
|
+
'Instance is required for transient injectable disposal',
|
|
337
|
+
)
|
|
338
|
+
const wrappers = this.instances.get(injectable)
|
|
339
|
+
if (wrappers) {
|
|
340
|
+
for (const wrapper of wrappers) {
|
|
341
|
+
if (wrapper.public === instance) {
|
|
342
|
+
await this.disposeInjectableInstance(
|
|
343
|
+
injectable,
|
|
344
|
+
wrapper.private,
|
|
345
|
+
wrapper.context,
|
|
346
|
+
)
|
|
347
|
+
const index = wrappers.indexOf(wrapper)
|
|
348
|
+
wrappers.splice(index, 1)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
267
351
|
|
|
268
|
-
|
|
352
|
+
if (wrappers.length === 0) {
|
|
353
|
+
this.instances.delete(injectable)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
269
356
|
} else {
|
|
270
|
-
|
|
357
|
+
await this.disposeInjectableInstances(injectable)
|
|
271
358
|
}
|
|
272
|
-
} catch (error) {
|
|
273
|
-
// Clean up on failure to prevent memory leaks
|
|
274
|
-
if (scope !== Scope.Transient) {
|
|
275
|
-
this.resolvers.delete(injectable)
|
|
276
|
-
}
|
|
277
|
-
this.instances.delete(injectable)
|
|
278
|
-
throw error
|
|
279
359
|
}
|
|
280
360
|
}
|
|
281
361
|
|
|
@@ -309,17 +389,19 @@ export class Container {
|
|
|
309
389
|
return result
|
|
310
390
|
}
|
|
311
391
|
|
|
312
|
-
private async
|
|
392
|
+
private async disposeInjectableInstances(injectable: AnyInjectable) {
|
|
313
393
|
try {
|
|
314
|
-
if (
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
394
|
+
if (this.instances.has(injectable)) {
|
|
395
|
+
const wrappers = this.instances.get(injectable)!
|
|
396
|
+
await Promise.all(
|
|
397
|
+
wrappers.map((wrapper) =>
|
|
398
|
+
this.disposeInjectableInstance(
|
|
399
|
+
injectable,
|
|
400
|
+
wrapper.private,
|
|
401
|
+
wrapper.context,
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
)
|
|
323
405
|
}
|
|
324
406
|
} catch (cause) {
|
|
325
407
|
const error = new Error(
|
|
@@ -331,33 +413,18 @@ export class Container {
|
|
|
331
413
|
this.instances.delete(injectable)
|
|
332
414
|
}
|
|
333
415
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function createInjectFunction(container: Container) {
|
|
337
|
-
return <T extends AnyInjectable>(
|
|
338
|
-
injectable: T,
|
|
339
|
-
context: InlineInjectionDependencies<T>,
|
|
340
|
-
) => {
|
|
341
|
-
const dependencies: Dependencies = {
|
|
342
|
-
...injectable.dependencies,
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
for (const key in context) {
|
|
346
|
-
const dep = context[key]
|
|
347
|
-
if (isInjectable(dep) || isOptionalInjectable(dep)) {
|
|
348
|
-
dependencies[key] = dep
|
|
349
|
-
} else {
|
|
350
|
-
dependencies[key] = createValueInjectable(dep)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
416
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
417
|
+
private async disposeInjectableInstance(
|
|
418
|
+
injectable: AnyInjectable,
|
|
419
|
+
instance: any,
|
|
420
|
+
context: any,
|
|
421
|
+
) {
|
|
422
|
+
if (isFactoryInjectable(injectable)) {
|
|
423
|
+
const { dispose } = injectable
|
|
424
|
+
if (dispose) await dispose(instance, context)
|
|
425
|
+
} else if (isClassInjectable(injectable)) {
|
|
426
|
+
await instance.$onDispose()
|
|
358
427
|
}
|
|
359
|
-
|
|
360
|
-
return container.resolve(newInjectable)
|
|
361
428
|
}
|
|
362
429
|
}
|
|
363
430
|
|
|
@@ -367,4 +434,5 @@ type InlineInjectionDependencies<T extends AnyInjectable> = {
|
|
|
367
434
|
| AnyInjectable<ResolveInjectableType<T['dependencies'][K]>>
|
|
368
435
|
}
|
|
369
436
|
|
|
370
|
-
export type InjectFn = ReturnType<
|
|
437
|
+
export type InjectFn = ReturnType<Container['createInjectFunction']>
|
|
438
|
+
export type DisposeFn = ReturnType<Container['createDisposeFunction']>
|
package/src/hooks.ts
CHANGED
package/src/injectables.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Async,
|
|
3
|
+
type Callback,
|
|
3
4
|
type ClassConstructor,
|
|
4
5
|
type ClassConstructorArgs,
|
|
5
6
|
type ClassInstance,
|
|
@@ -8,20 +9,23 @@ import {
|
|
|
8
9
|
import {
|
|
9
10
|
kClassInjectable,
|
|
10
11
|
kFactoryInjectable,
|
|
12
|
+
kHookCollection,
|
|
11
13
|
kInjectable,
|
|
12
14
|
kLazyInjectable,
|
|
13
15
|
kOptionalDependency,
|
|
14
16
|
kValueInjectable,
|
|
15
17
|
} from './constants.ts'
|
|
16
|
-
import type { InjectFn } from './container.ts'
|
|
17
|
-
import { Scope } from './enums.ts'
|
|
18
|
+
import type { DisposeFn, InjectFn } from './container.ts'
|
|
19
|
+
import { type Hook, Scope } from './enums.ts'
|
|
20
|
+
import { Hooks, type HookType } from './hooks.ts'
|
|
18
21
|
import type { Logger } from './logger.ts'
|
|
22
|
+
import type { Registry } from './registry.ts'
|
|
19
23
|
|
|
20
24
|
const ScopeStrictness = {
|
|
21
|
-
[Scope.
|
|
22
|
-
[Scope.
|
|
23
|
-
[Scope.
|
|
24
|
-
[Scope.
|
|
25
|
+
[Scope.Transient]: Number.NaN, // this should make it always fail to compare with other scopes
|
|
26
|
+
[Scope.Global]: 1,
|
|
27
|
+
[Scope.Connection]: 2,
|
|
28
|
+
[Scope.Call]: 3,
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export type DependencyOptional<T extends AnyInjectable = AnyInjectable> = {
|
|
@@ -152,29 +156,39 @@ export type AnyInjectable<T = any, S extends Scope = Scope> = Injectable<
|
|
|
152
156
|
export const isLazyInjectable = (
|
|
153
157
|
injectable: any,
|
|
154
158
|
): injectable is LazyInjectable<any> => kLazyInjectable in injectable
|
|
159
|
+
|
|
155
160
|
export const isFactoryInjectable = (
|
|
156
161
|
injectable: any,
|
|
157
162
|
): injectable is FactoryInjectable<any> => kFactoryInjectable in injectable
|
|
163
|
+
|
|
158
164
|
export const isClassInjectable = (
|
|
159
165
|
injectable: any,
|
|
160
166
|
): injectable is ClassInjectable<any> => kClassInjectable in injectable
|
|
167
|
+
|
|
161
168
|
export const isValueInjectable = (
|
|
162
169
|
injectable: any,
|
|
163
170
|
): injectable is ValueInjectable<any> => kValueInjectable in injectable
|
|
171
|
+
|
|
164
172
|
export const isInjectable = (
|
|
165
173
|
injectable: any,
|
|
166
174
|
): injectable is AnyInjectable<any> => kInjectable in injectable
|
|
175
|
+
|
|
167
176
|
export const isOptionalInjectable = (
|
|
168
177
|
injectable: any,
|
|
169
178
|
): injectable is DependencyOptional<any> => kOptionalDependency in injectable
|
|
170
179
|
|
|
171
180
|
export function getInjectableScope(injectable: AnyInjectable) {
|
|
172
181
|
let scope = injectable.scope
|
|
173
|
-
const deps =
|
|
174
|
-
for (const
|
|
182
|
+
const deps = injectable.dependencies as Dependencies
|
|
183
|
+
for (const key in deps) {
|
|
184
|
+
const dependency = deps[key]
|
|
175
185
|
const injectable = getDepedencencyInjectable(dependency)
|
|
176
186
|
const dependencyScope = getInjectableScope(injectable)
|
|
177
|
-
if (
|
|
187
|
+
if (
|
|
188
|
+
dependencyScope !== Scope.Transient &&
|
|
189
|
+
scope !== Scope.Transient &&
|
|
190
|
+
compareScope(dependencyScope, '>', scope)
|
|
191
|
+
) {
|
|
178
192
|
scope = dependencyScope
|
|
179
193
|
}
|
|
180
194
|
}
|
|
@@ -193,10 +207,10 @@ export function getDepedencencyInjectable(
|
|
|
193
207
|
export function createOptionalInjectable<T extends AnyInjectable>(
|
|
194
208
|
injectable: T,
|
|
195
209
|
) {
|
|
196
|
-
return {
|
|
210
|
+
return Object.freeze({
|
|
197
211
|
[kOptionalDependency]: true,
|
|
198
212
|
injectable,
|
|
199
|
-
} as DependencyOptional<T>
|
|
213
|
+
}) as DependencyOptional<T>
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
export function createLazyInjectable<T, S extends Scope = Scope.Global>(
|
|
@@ -261,16 +275,10 @@ export function createFactoryInjectable<
|
|
|
261
275
|
[kInjectable]: true,
|
|
262
276
|
[kFactoryInjectable]: true,
|
|
263
277
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
ScopeStrictness[actualScope] > ScopeStrictness[params.scope]
|
|
269
|
-
)
|
|
270
|
-
throw new Error(
|
|
271
|
-
`Invalid scope ${params.scope} for factory injectable: dependencies have stricter scope - ${actualScope}`,
|
|
272
|
-
)
|
|
273
|
-
injectable.scope = actualScope as unknown as S
|
|
278
|
+
injectable.scope = resolveInjectableScope(
|
|
279
|
+
typeof params.scope === 'undefined',
|
|
280
|
+
injectable,
|
|
281
|
+
) as S
|
|
274
282
|
return Object.freeze(injectable) as any
|
|
275
283
|
}
|
|
276
284
|
|
|
@@ -279,12 +287,12 @@ export const createClassInjectable = <
|
|
|
279
287
|
S extends Scope = Scope.Global,
|
|
280
288
|
>(
|
|
281
289
|
dependencies: D = {} as D,
|
|
282
|
-
scope
|
|
290
|
+
scope?: S,
|
|
283
291
|
stackTraceDepth = 0,
|
|
284
|
-
): ClassInjectable<ClassInstance<typeof
|
|
285
|
-
const
|
|
292
|
+
): ClassInjectable<ClassInstance<typeof InjectableClass>, D, S> => {
|
|
293
|
+
const InjectableClass = class {
|
|
286
294
|
static dependencies = dependencies
|
|
287
|
-
static scope = scope
|
|
295
|
+
static scope = (scope ?? Scope.Global) as S
|
|
288
296
|
static stack = tryCaptureStackTrace(stackTraceDepth + 2)
|
|
289
297
|
static [kInjectable] = true
|
|
290
298
|
static [kClassInjectable] = true
|
|
@@ -299,7 +307,13 @@ export const createClassInjectable = <
|
|
|
299
307
|
protected async $onCreate() {}
|
|
300
308
|
protected async $onDispose() {}
|
|
301
309
|
}
|
|
302
|
-
|
|
310
|
+
|
|
311
|
+
InjectableClass.scope = resolveInjectableScope(
|
|
312
|
+
typeof scope === 'undefined',
|
|
313
|
+
InjectableClass,
|
|
314
|
+
) as S
|
|
315
|
+
|
|
316
|
+
return InjectableClass
|
|
303
317
|
}
|
|
304
318
|
|
|
305
319
|
export function createExtendableClassInjectable<
|
|
@@ -309,23 +323,25 @@ export function createExtendableClassInjectable<
|
|
|
309
323
|
>(
|
|
310
324
|
baseClass: B,
|
|
311
325
|
dependencies: D = {} as D,
|
|
312
|
-
scope
|
|
326
|
+
scope?: S,
|
|
313
327
|
stackTraceDepth = 0,
|
|
314
328
|
): B extends ClassInjectable<any>
|
|
315
329
|
? ClassInjectable<ClassInstance<B>, D, S>
|
|
316
330
|
: ClassInjectable<ClassInstance<B>, D, S, ClassConstructorArgs<B, []>> {
|
|
317
331
|
if (isClassInjectable(baseClass)) {
|
|
318
|
-
|
|
319
|
-
if (compareScope(scope, '<', baseClass.scope)) {
|
|
332
|
+
if (scope && compareScope(baseClass.scope, '>', scope)) {
|
|
320
333
|
throw new Error(
|
|
321
|
-
|
|
334
|
+
`Invalid scope ${scope} for an extendable class injectable: base class have stricter scope - ${baseClass.scope}`,
|
|
322
335
|
)
|
|
336
|
+
} else {
|
|
337
|
+
scope = scope ?? (baseClass.scope as S)
|
|
323
338
|
}
|
|
339
|
+
dependencies = Object.assign({}, baseClass.dependencies, dependencies)
|
|
324
340
|
}
|
|
325
|
-
|
|
326
|
-
|
|
341
|
+
|
|
342
|
+
const InjectableClass = class extends baseClass {
|
|
327
343
|
static dependencies = dependencies
|
|
328
|
-
static scope = scope
|
|
344
|
+
static scope = (scope ?? Scope.Global) as S
|
|
329
345
|
static stack = tryCaptureStackTrace(stackTraceDepth)
|
|
330
346
|
static [kInjectable] = true
|
|
331
347
|
static [kClassInjectable] = true
|
|
@@ -355,6 +371,14 @@ export function createExtendableClassInjectable<
|
|
|
355
371
|
await super.$onDispose?.()
|
|
356
372
|
}
|
|
357
373
|
}
|
|
374
|
+
|
|
375
|
+
InjectableClass.scope = resolveInjectableScope(
|
|
376
|
+
typeof scope === 'undefined',
|
|
377
|
+
InjectableClass,
|
|
378
|
+
) as S
|
|
379
|
+
|
|
380
|
+
// @ts-expect-error
|
|
381
|
+
return InjectableClass
|
|
358
382
|
}
|
|
359
383
|
|
|
360
384
|
export type DependenciesSubstitution<T extends Dependencies> = {
|
|
@@ -434,7 +458,51 @@ export function compareScope(
|
|
|
434
458
|
}
|
|
435
459
|
}
|
|
436
460
|
|
|
461
|
+
const logger = createLazyInjectable<Logger>(Scope.Global, 'Logger')
|
|
462
|
+
const registry = createLazyInjectable<Registry>(Scope.Global, 'Registry')
|
|
463
|
+
const inject = createLazyInjectable<InjectFn>(Scope.Global, 'Inject function')
|
|
464
|
+
const dispose = createLazyInjectable<DisposeFn>(
|
|
465
|
+
Scope.Global,
|
|
466
|
+
'Dispose function',
|
|
467
|
+
)
|
|
468
|
+
const hook = createFactoryInjectable({
|
|
469
|
+
scope: Scope.Transient,
|
|
470
|
+
dependencies: { registry },
|
|
471
|
+
factory: ({ registry }) => {
|
|
472
|
+
const hooks = new Hooks()
|
|
473
|
+
const on = <T extends Hook>(name: T, callback: HookType[T]) => {
|
|
474
|
+
hooks.add(name, callback as Callback)
|
|
475
|
+
return registry.hooks.add(name, callback)
|
|
476
|
+
}
|
|
477
|
+
return { hooks, on }
|
|
478
|
+
},
|
|
479
|
+
pick: ({ on }) => on,
|
|
480
|
+
dispose: ({ hooks }, { registry }) => {
|
|
481
|
+
for (const [hook, callbacks] of hooks[kHookCollection].entries()) {
|
|
482
|
+
for (const callback of callbacks) {
|
|
483
|
+
registry.hooks.remove(hook, callback)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
hooks.clear()
|
|
487
|
+
},
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
function resolveInjectableScope(
|
|
491
|
+
isDefaultScope: boolean,
|
|
492
|
+
injectable: AnyInjectable,
|
|
493
|
+
) {
|
|
494
|
+
const actualScope = getInjectableScope(injectable)
|
|
495
|
+
if (!isDefaultScope && compareScope(actualScope, '>', injectable.scope))
|
|
496
|
+
throw new Error(
|
|
497
|
+
`Invalid scope ${injectable.scope} for an injectable: dependencies have stricter scope - ${actualScope}`,
|
|
498
|
+
)
|
|
499
|
+
return actualScope
|
|
500
|
+
}
|
|
501
|
+
|
|
437
502
|
export const CoreInjectables = {
|
|
438
|
-
logger
|
|
439
|
-
|
|
503
|
+
logger,
|
|
504
|
+
registry,
|
|
505
|
+
inject,
|
|
506
|
+
dispose,
|
|
507
|
+
hook,
|
|
440
508
|
}
|