@nmtjs/core 0.11.2 → 0.11.3

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 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(this));
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
- await this.disposeInjectable(injectable);
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).instance;
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
- picked: instance,
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 { scope, dependencies, stack, label } = injectable;
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
- if (this.instances.has(injectable)) {
124
- return Promise.resolve(this.instances.get(injectable).picked);
125
- } else if (this.resolvers.has(injectable)) {
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, dependencies, scope);
135
- if (scope !== Scope.Transient) {
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, dependencies, scope) {
144
- try {
145
- if (isFactoryInjectable(injectable)) {
146
- const context = await this.createInjectableContext(dependencies, injectable);
147
- const instance = await Promise.resolve(injectable.factory(context));
148
- const picked = injectable.pick(instance);
149
- if (compareScope(this.scope, ">=", scope)) {
150
- this.instances.set(injectable, {
151
- instance,
152
- picked,
153
- context
154
- });
155
- }
156
- if (scope !== Scope.Transient) {
157
- this.resolvers.delete(injectable);
158
- }
159
- return picked;
160
- } else if (isClassInjectable(injectable)) {
161
- const context = await this.createInjectableContext(dependencies, injectable);
162
- const instance = new injectable(context);
163
- await instance.$onCreate();
164
- if (compareScope(this.scope, ">=", scope)) {
165
- this.instances.set(injectable, {
166
- instance,
167
- picked: instance,
168
- context: undefined
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
- if (scope !== Scope.Transient) {
172
- this.resolvers.delete(injectable);
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
- throw new Error("Invalid injectable type");
177
- }
178
- } catch (error) {
179
- if (scope !== Scope.Transient) {
180
- this.resolvers.delete(injectable);
230
+ await this.disposeInjectableInstances(injectable);
181
231
  }
182
- this.instances.delete(injectable);
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 disposeInjectable(injectable) {
257
+ async disposeInjectableInstances(injectable) {
210
258
  try {
211
- if (isFactoryInjectable(injectable)) {
212
- const { dispose } = injectable;
213
- if (dispose) {
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
- export function createInjectFunction(container) {
230
- return (injectable, context) => {
231
- const dependencies = { ...injectable.dependencies };
232
- for (const key in context) {
233
- const dep = context[key];
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
- const newInjectable = {
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
@@ -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;AAsBhD,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// 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"}
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"}
@@ -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.Global]: 0,
6
- [Scope.Connection]: 1,
7
- [Scope.Call]: 2,
8
- [Scope.Transient]: 3
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 = Object.values(injectable.dependencies);
19
- for (const dependency of deps) {
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
- const actualScope = getInjectableScope(injectable);
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 = Scope.Global, stackTraceDepth = 0) => {
81
- const klass = class {
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
- return klass;
96
+ InjectableClass.scope = resolveInjectableScope(typeof scope === "undefined", InjectableClass);
97
+ return InjectableClass;
97
98
  };
98
- export function createExtendableClassInjectable(baseClass, dependencies = {}, scope = Scope.Global, stackTraceDepth = 0) {
99
+ export function createExtendableClassInjectable(baseClass, dependencies = {}, scope, stackTraceDepth = 0) {
99
100
  if (isClassInjectable(baseClass)) {
100
- dependencies = Object.assign({}, baseClass.dependencies, dependencies);
101
- if (compareScope(scope, "<", baseClass.scope)) {
102
- throw new Error("Invalid scope for injectable: base class has stricter scope");
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
- return class extends baseClass {
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: createLazyInjectable(Scope.Global, "Logger"),
171
- inject: createLazyInjectable(Scope.Global, "Inject function")
208
+ logger,
209
+ registry,
210
+ inject,
211
+ dispose,
212
+ hook
172
213
  };
173
214
 
174
215
  //# sourceMappingURL=injectables.js.map
@@ -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.2",
14
- "@nmtjs/type": "0.11.2"
13
+ "@nmtjs/common": "0.11.3",
14
+ "@nmtjs/type": "0.11.3"
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/common": "0.11.2",
21
- "@nmtjs/type": "0.11.2"
20
+ "@nmtjs/type": "0.11.3",
21
+ "@nmtjs/common": "0.11.3"
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.2",
29
+ "version": "0.11.3",
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(this))
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
- await this.disposeInjectable(injectable)
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)!.instance
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
- this.instances.set(injectable, {
145
- instance,
146
- picked: instance,
147
- context: undefined,
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 { scope, dependencies, stack, label } = injectable
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
- if (this.instances.has(injectable)) {
196
- return Promise.resolve(this.instances.get(injectable)!.picked)
197
- } else if (this.resolvers.has(injectable)) {
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
- dependencies,
214
- scope,
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
- try {
231
- if (isFactoryInjectable(injectable)) {
232
- const context = await this.createInjectableContext(
233
- dependencies,
234
- injectable,
235
- )
236
- const instance = await Promise.resolve(injectable.factory(context))
237
- const picked = injectable.pick(instance)
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
- if (compareScope(this.scope, '>=', scope)) {
240
- this.instances.set(injectable, { instance, picked, context })
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
- if (scope !== Scope.Transient) {
244
- this.resolvers.delete(injectable)
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
- return picked
248
- } else if (isClassInjectable(injectable)) {
249
- const context = await this.createInjectableContext(
250
- dependencies,
251
- injectable,
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
- const instance = new injectable(context)
254
- await instance.$onCreate()
255
-
256
- if (compareScope(this.scope, '>=', scope)) {
257
- this.instances.set(injectable, {
258
- instance,
259
- picked: instance,
260
- context: undefined,
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
- if (scope !== Scope.Transient) {
265
- this.resolvers.delete(injectable)
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
- return instance
352
+ if (wrappers.length === 0) {
353
+ this.instances.delete(injectable)
354
+ }
355
+ }
269
356
  } else {
270
- throw new Error('Invalid injectable type')
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 disposeInjectable(injectable: AnyInjectable) {
392
+ private async disposeInjectableInstances(injectable: AnyInjectable) {
313
393
  try {
314
- if (isFactoryInjectable(injectable)) {
315
- const { dispose } = injectable
316
- if (dispose) {
317
- const { instance, context } = this.instances.get(injectable)!
318
- await dispose(instance, context)
319
- }
320
- } else if (isClassInjectable(injectable)) {
321
- const { instance } = this.instances.get(injectable)!
322
- await instance.$onDispose()
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
- const newInjectable = {
355
- ...injectable,
356
- dependencies,
357
- scope: Scope.Transient,
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<typeof createInjectFunction>
437
+ export type InjectFn = ReturnType<Container['createInjectFunction']>
438
+ export type DisposeFn = ReturnType<Container['createDisposeFunction']>
package/src/hooks.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Callback } from '../../common/src/index.ts'
2
2
  import { kHookCollection } from './constants.ts'
3
3
  import type { Hook } from './enums.ts'
4
+
4
5
  // import type { HookType } from './types.ts'
5
6
 
6
7
  export interface HookType {
@@ -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.Global]: 0,
22
- [Scope.Connection]: 1,
23
- [Scope.Call]: 2,
24
- [Scope.Transient]: 3,
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 = Object.values(injectable.dependencies as Dependencies)
174
- for (const dependency of deps) {
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 (compareScope(dependencyScope, '>', scope)) {
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
- const actualScope = getInjectableScope(injectable)
265
- if (
266
- !isFactory &&
267
- params.scope &&
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: S = Scope.Global as S,
290
+ scope?: S,
283
291
  stackTraceDepth = 0,
284
- ): ClassInjectable<ClassInstance<typeof klass>, D, S> => {
285
- const klass = class {
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
- return klass
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: S = Scope.Global as S,
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
- dependencies = Object.assign({}, baseClass.dependencies, dependencies)
319
- if (compareScope(scope, '<', baseClass.scope)) {
332
+ if (scope && compareScope(baseClass.scope, '>', scope)) {
320
333
  throw new Error(
321
- 'Invalid scope for injectable: base class has stricter scope',
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
- // @ts-expect-error
326
- return class extends baseClass {
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: createLazyInjectable<Logger>(Scope.Global, 'Logger'),
439
- inject: createLazyInjectable<InjectFn>(Scope.Global, 'Inject function'),
503
+ logger,
504
+ registry,
505
+ inject,
506
+ dispose,
507
+ hook,
440
508
  }