@vercube/di 1.1.7 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -72,6 +72,17 @@ declare class Container {
72
72
  * @returns {Array} array of service keys
73
73
  */
74
74
  get servicesKeys(): IOC.ServiceKey[];
75
+ /**
76
+ * Returns a read-only view of all service definitions without instantiating them.
77
+ * @returns {ReadonlyMap} map of service key to its definition
78
+ */
79
+ get services(): ReadonlyMap<IOC.ServiceKey, Readonly<IOC.ServiceDef>>;
80
+ /**
81
+ * Checks whether a singleton instance for a given key has already been constructed.
82
+ * @param key service key to check
83
+ * @returns {boolean} true when a singleton instance already exists
84
+ */
85
+ hasInstance(key: IOC.ServiceKey): boolean;
75
86
  /**
76
87
  * Returns events handler.
77
88
  */
@@ -189,6 +200,13 @@ declare class Container {
189
200
  * @returns class instance
190
201
  */
191
202
  protected internalResolve(serviceDef: IOC.ServiceDef): IOC.Instance;
203
+ /**
204
+ * Constructs a new instance for a service def and injects its dependencies.
205
+ * @param serviceDef service def to construct
206
+ * @param singleton whether resulting instance should be cached as a singleton
207
+ * @returns newly created class instance
208
+ */
209
+ protected internalConstruct(serviceDef: IOC.ServiceDef, singleton: boolean): IOC.Instance;
192
210
  /**
193
211
  * Internally inject deps for particular class..
194
212
  * @param instance instance to inject
@@ -366,6 +384,97 @@ declare function Destroy(): Function;
366
384
  */
367
385
  declare function Injectable(): ClassDecorator;
368
386
  //#endregion
387
+ //#region src/Domain/DevtoolsHook.d.ts
388
+ /**
389
+ * Global property name for the IOC devtools hook.
390
+ */
391
+ declare const IOC_DEVTOOLS_HOOK_KEY = "__VERCUBE_DEVTOOLS_HOOK__";
392
+ /**
393
+ * A single service resolution record emitted when the container constructs an instance.
394
+ */
395
+ interface IOCResolveRecord {
396
+ /** Service key that was resolved. */
397
+ key: IOC.ServiceKey;
398
+ /** Human readable name of the service key. */
399
+ name: string;
400
+ /** Factory type used to build the instance. */
401
+ type: IOC.ServiceFactoryType;
402
+ /** Context of the container that performed the resolution. */
403
+ context: string | undefined;
404
+ /** Timestamp (ms) before construction. */
405
+ start: number;
406
+ /** Timestamp (ms) after construction and injection. */
407
+ end: number;
408
+ }
409
+ /**
410
+ * Optional hook for observing the IOC container.
411
+ */
412
+ interface IOCDevtoolsHook {
413
+ /** Called once for every {@link Container} instance after construction. */
414
+ onContainerCreated?: (container: Container) => void;
415
+ /** Called whenever the container constructs a new service instance. */
416
+ onResolved?: (record: IOCResolveRecord) => void;
417
+ }
418
+ /**
419
+ * Installs or replaces the devtools hook.
420
+ * @param hook hook implementation, or `undefined` to uninstall
421
+ */
422
+ declare function setIOCDevtoolsHook(hook: IOCDevtoolsHook | undefined): void;
423
+ /**
424
+ * Returns the currently installed devtools hook, if any.
425
+ */
426
+ declare function getIOCDevtoolsHook(): IOCDevtoolsHook | undefined;
427
+ //#endregion
428
+ //#region src/Domain/Engine.d.ts
429
+ /**
430
+ * This is a struct that holds information about class dependency - what dependency, which field to inject,etc.
431
+ */
432
+ interface IClassDep {
433
+ propertyName: string;
434
+ dependency: IOC.ServiceKey;
435
+ type: IOC.DependencyType;
436
+ }
437
+ /**
438
+ * This holds single "metadata" entry for particular class. It holds all @Inject stuff for class.
439
+ */
440
+ interface IClassMapEntry {
441
+ deps: IClassDep[];
442
+ }
443
+ /**
444
+ * This method registers @Inject() in particular class.
445
+ * @param prototype class prototype for which we register @Inject()
446
+ * @param propertyName name of property that is inejcted
447
+ * @param dependency what we should inject there
448
+ * @param type type of dependency (standard or optional dependency)
449
+ */
450
+ declare function registerInject(prototype: IOC.Prototype, propertyName: string, dependency: IOC.ServiceKey, type: IOC.DependencyType): void;
451
+ /**
452
+ * Returns classmap entry for particular class. It holds information about @Injects for this particular class.
453
+ * @param classType type of class to check
454
+ * @returns class map entry or null if cannot be found
455
+ */
456
+ declare function getEntryForClass(classType: IOC.Newable<unknown>): IClassMapEntry | null;
457
+ /**
458
+ * Returns array of dependencies for particular class instance.
459
+ * @param instance class instance
460
+ * @returns array of @Inject dependencies defined for this class
461
+ */
462
+ declare function getDeps(instance: IOC.Instance): IClassDep[];
463
+ /**
464
+ * This method injects stuff into class, using container passed as first argument. We need container as
465
+ * inject execution is always context based.
466
+ * @param container container instance that is providing dependencies
467
+ * @param instance class instance to inject
468
+ * @param method inject method, "lazy" queries dep during property access while "static" injects during class creation
469
+ */
470
+ declare function injectDeps(container: Container, instance: IOC.Instance, method: IOC.InjectMethod): void;
471
+ declare const IOCEngine: {
472
+ registerInject: typeof registerInject;
473
+ getEntryForClass: typeof getEntryForClass;
474
+ injectDeps: typeof injectDeps;
475
+ getDeps: typeof getDeps;
476
+ };
477
+ //#endregion
369
478
  //#region src/Utils/Utils.d.ts
370
479
  /**
371
480
  * This function generates new service key for particular service. Providing
@@ -441,4 +550,4 @@ declare function initializeContainer(container: Container): void;
441
550
  */
442
551
  declare function destroyContainer(container: Container): void;
443
552
  //#endregion
444
- export { BaseDecorator, Container, Destroy, IClassType, IDecoratedInstance, IDecoratedPrototype, IDecoratorEntry, IOC, Identity, Init, Inject, InjectOptional, Injectable, createDecorator, destroyContainer, destroyDecorators, initializeContainer, initializeDecorators };
553
+ export { BaseDecorator, Container, ContainerEvents, Destroy, type IClassDep, type IClassMapEntry, IClassType, IDecoratedInstance, IDecoratedPrototype, IDecoratorEntry, IOC, IOCDevtoolsHook, IOCEngine, IOCResolveRecord, IOC_DEVTOOLS_HOOK_KEY, Identity, Init, Inject, InjectOptional, Injectable, OnExpandedEvent, createDecorator, destroyContainer, destroyDecorators, getIOCDevtoolsHook, initializeContainer, initializeDecorators, setIOCDevtoolsHook };
package/dist/index.mjs CHANGED
@@ -152,6 +152,27 @@ var ContainerEvents = class {
152
152
  }
153
153
  };
154
154
  //#endregion
155
+ //#region src/Domain/DevtoolsHook.ts
156
+ /**
157
+ * Global property name for the IOC devtools hook.
158
+ */
159
+ const IOC_DEVTOOLS_HOOK_KEY = "__VERCUBE_DEVTOOLS_HOOK__";
160
+ let devtoolsHook = globalThis[IOC_DEVTOOLS_HOOK_KEY];
161
+ /**
162
+ * Installs or replaces the devtools hook.
163
+ * @param hook hook implementation, or `undefined` to uninstall
164
+ */
165
+ function setIOCDevtoolsHook(hook) {
166
+ devtoolsHook = hook;
167
+ globalThis[IOC_DEVTOOLS_HOOK_KEY] = hook;
168
+ }
169
+ /**
170
+ * Returns the currently installed devtools hook, if any.
171
+ */
172
+ function getIOCDevtoolsHook() {
173
+ return devtoolsHook;
174
+ }
175
+ //#endregion
155
176
  //#region src/Domain/Container.ts
156
177
  /**
157
178
  * This is new implementation of IOC Container. It mimics Inversify.js container a little bit but its
@@ -180,6 +201,7 @@ var Container = class Container {
180
201
  this.fDefaultParams = Object.assign(this.fDefaultParams, params);
181
202
  this.fInjectMethod = params?.injectMethod ?? IOC.InjectMethod.STATIC;
182
203
  this.bindInstance(Container, this);
204
+ getIOCDevtoolsHook()?.onContainerCreated?.(this);
183
205
  }
184
206
  /**
185
207
  * Returns array of all service keys. This basically returns keys from all .bindXXX calls.
@@ -189,6 +211,21 @@ var Container = class Container {
189
211
  return [...this.fServices.keys()];
190
212
  }
191
213
  /**
214
+ * Returns a read-only view of all service definitions without instantiating them.
215
+ * @returns {ReadonlyMap} map of service key to its definition
216
+ */
217
+ get services() {
218
+ return this.fServices;
219
+ }
220
+ /**
221
+ * Checks whether a singleton instance for a given key has already been constructed.
222
+ * @param key service key to check
223
+ * @returns {boolean} true when a singleton instance already exists
224
+ */
225
+ hasInstance(key) {
226
+ return this.fSingletonInstances.has(key);
227
+ }
228
+ /**
192
229
  * Returns events handler.
193
230
  */
194
231
  get events() {
@@ -377,7 +414,10 @@ var Container = class Container {
377
414
  */
378
415
  internalGet(key, parent) {
379
416
  const serviceDef = this.fServices.get(key);
380
- if (!serviceDef) throw new Error(`Unresolved dependency for [${this.getKeyDescription(key)}]`);
417
+ if (!serviceDef) {
418
+ const owner = parent?.constructor?.name;
419
+ throw new Error(`Unresolved dependency for [${this.getKeyDescription(key)}]${owner ? ` required by [${owner}]` : ""}`);
420
+ }
381
421
  return this.internalResolve(serviceDef);
382
422
  }
383
423
  /**
@@ -400,24 +440,41 @@ var Container = class Container {
400
440
  switch (serviceDef.type) {
401
441
  case IOC.ServiceFactoryType.INSTANCE: return serviceDef.serviceValue;
402
442
  case IOC.ServiceFactoryType.CLASS_SINGLETON:
403
- if (!this.fSingletonInstances.has(serviceDef.serviceKey)) {
404
- const constructor = serviceDef.serviceValue;
405
- const instance = new constructor();
406
- this.fSingletonInstances.set(serviceDef.serviceKey, instance);
407
- this.internalProcessInjects(instance, this.fInjectMethod);
408
- return instance;
409
- }
443
+ if (!this.fSingletonInstances.has(serviceDef.serviceKey)) return this.internalConstruct(serviceDef, true);
410
444
  return this.fSingletonInstances.get(serviceDef.serviceKey);
411
- case IOC.ServiceFactoryType.CLASS: {
412
- const constructor = serviceDef.serviceValue;
413
- const instance = new constructor();
414
- this.internalProcessInjects(instance, this.fInjectMethod);
415
- return instance;
416
- }
445
+ case IOC.ServiceFactoryType.CLASS: return this.internalConstruct(serviceDef, false);
417
446
  default: throw new Error(`Container - invalid factory type: ${serviceDef.type}`);
418
447
  }
419
448
  }
420
449
  /**
450
+ * Constructs a new instance for a service def and injects its dependencies.
451
+ * @param serviceDef service def to construct
452
+ * @param singleton whether resulting instance should be cached as a singleton
453
+ * @returns newly created class instance
454
+ */
455
+ internalConstruct(serviceDef, singleton) {
456
+ const onResolved = getIOCDevtoolsHook()?.onResolved;
457
+ const start = onResolved ? performance.now() : 0;
458
+ const constructor = serviceDef.serviceValue;
459
+ const instance = new constructor();
460
+ if (singleton) this.fSingletonInstances.set(serviceDef.serviceKey, instance);
461
+ try {
462
+ this.internalProcessInjects(instance, this.fInjectMethod);
463
+ } catch (error) {
464
+ if (singleton) this.fSingletonInstances.delete(serviceDef.serviceKey);
465
+ throw error;
466
+ }
467
+ if (onResolved) onResolved({
468
+ key: serviceDef.serviceKey,
469
+ name: this.getKeyDescription(serviceDef.serviceKey),
470
+ type: serviceDef.type,
471
+ context: this.fContext,
472
+ start,
473
+ end: performance.now()
474
+ });
475
+ return instance;
476
+ }
477
+ /**
421
478
  * Internally inject deps for particular class..
422
479
  * @param instance instance to inject
423
480
  * @param method method for injecting dependencies, either lazy or static
@@ -437,7 +494,7 @@ var Container = class Container {
437
494
  const deps = IOCEngine.getDeps(element);
438
495
  for (const inj of deps) if (!elementSet.has(inj.dependency)) {
439
496
  const isOptional = inj.type === IOC.DependencyType.OPTIONAL;
440
- const childInstance = isOptional ? this.internalGetOptional(inj.dependency) : this.internalGet(inj.dependency, instance);
497
+ const childInstance = isOptional ? this.internalGetOptional(inj.dependency) : this.internalGet(inj.dependency, element);
441
498
  elementSet.add(inj.dependency);
442
499
  if (!isOptional) {
443
500
  processQueue.push(childInstance);
@@ -474,10 +531,11 @@ var Container = class Container {
474
531
  * @returns string representation of service key
475
532
  */
476
533
  getKeyDescription(key) {
477
- if (typeof key === "symbol") return key.description;
478
- else if (typeof key === "function") return key.name;
479
- else if (typeof key === "object") return key.constructor?.name ?? "Unknown object";
480
- return "Unknown";
534
+ if (typeof key === "symbol") return key.description ?? "Symbol()";
535
+ else if (typeof key === "string") return key;
536
+ else if (typeof key === "function") return key.name || "AnonymousClass";
537
+ else if (typeof key === "object" && key !== null) return key.constructor?.name ?? "Unknown object";
538
+ return `Unknown (${typeof key})`;
481
539
  }
482
540
  };
483
541
  //#endregion
@@ -712,4 +770,4 @@ function Injectable() {
712
770
  return () => {};
713
771
  }
714
772
  //#endregion
715
- export { BaseDecorator, Container, Destroy, IOC, Identity, Init, Inject, InjectOptional, Injectable, createDecorator, destroyContainer, destroyDecorators, initializeContainer, initializeDecorators };
773
+ export { BaseDecorator, Container, ContainerEvents, Destroy, IOC, IOCEngine, IOC_DEVTOOLS_HOOK_KEY, Identity, Init, Inject, InjectOptional, Injectable, createDecorator, destroyContainer, destroyDecorators, getIOCDevtoolsHook, initializeContainer, initializeDecorators, setIOCDevtoolsHook };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercube/di",
3
- "version": "1.1.7",
3
+ "version": "1.2.1",
4
4
  "description": "Dependency Injection module for Vercube framework",
5
5
  "repository": {
6
6
  "type": "git",