@codefast/di 0.3.14-canary.0 → 0.3.14-canary.2

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -29
  3. package/dist/binding-scope.d.mts +11 -0
  4. package/dist/binding-scope.mjs +19 -0
  5. package/dist/binding-select.d.mts +8 -26
  6. package/dist/binding-select.mjs +56 -49
  7. package/dist/binding.d.mts +107 -327
  8. package/dist/binding.mjs +19 -324
  9. package/dist/constraints.d.mts +11 -29
  10. package/dist/constraints.mjs +32 -36
  11. package/dist/constructor-type.d.mts +17 -0
  12. package/dist/constructor-type.mjs +1 -0
  13. package/dist/container.d.mts +44 -128
  14. package/dist/container.mjs +664 -433
  15. package/dist/decorators/inject.d.mts +19 -50
  16. package/dist/decorators/inject.mjs +131 -93
  17. package/dist/decorators/injectable.d.mts +16 -37
  18. package/dist/decorators/injectable.mjs +47 -66
  19. package/dist/decorators/lifecycle-decorators.d.mts +2 -22
  20. package/dist/decorators/lifecycle-decorators.mjs +81 -37
  21. package/dist/dependency-graph.d.mts +24 -54
  22. package/dist/dependency-graph.mjs +51 -153
  23. package/dist/environment.d.mts +38 -12
  24. package/dist/environment.mjs +82 -16
  25. package/dist/errors.d.mts +69 -188
  26. package/dist/errors.mjs +92 -219
  27. package/dist/graph-adapters/cytoscape.d.mts +24 -0
  28. package/dist/graph-adapters/cytoscape.mjs +23 -0
  29. package/dist/graph-adapters/dot.d.mts +6 -0
  30. package/dist/graph-adapters/dot.mjs +17 -0
  31. package/dist/graph-adapters/reactflow.d.mts +29 -0
  32. package/dist/graph-adapters/reactflow.mjs +29 -0
  33. package/dist/graph-adapters/types.d.mts +2 -0
  34. package/dist/graph-adapters/types.mjs +1 -0
  35. package/dist/index.d.mts +16 -9
  36. package/dist/index.mjs +8 -5
  37. package/dist/inspector.d.mts +34 -95
  38. package/dist/inspector.mjs +57 -256
  39. package/dist/lifecycle.d.mts +20 -53
  40. package/dist/lifecycle.mjs +129 -99
  41. package/dist/metadata/metadata-keys.d.mts +9 -26
  42. package/dist/metadata/metadata-keys.mjs +7 -28
  43. package/dist/metadata/metadata-reader-token.d.mts +7 -0
  44. package/dist/metadata/metadata-reader-token.mjs +5 -0
  45. package/dist/metadata/metadata-types.d.mts +22 -71
  46. package/dist/metadata/symbol-metadata-reader.d.mts +10 -26
  47. package/dist/metadata/symbol-metadata-reader.mjs +32 -45
  48. package/dist/module.d.mts +30 -84
  49. package/dist/module.mjs +26 -72
  50. package/dist/registry.d.mts +32 -63
  51. package/dist/registry.mjs +131 -82
  52. package/dist/resolve-options.d.mts +18 -0
  53. package/dist/resolve-options.mjs +22 -0
  54. package/dist/resolver.d.mts +67 -190
  55. package/dist/resolver.mjs +715 -424
  56. package/dist/scope.d.mts +19 -102
  57. package/dist/scope.mjs +37 -192
  58. package/dist/token.d.mts +8 -22
  59. package/dist/token.mjs +9 -11
  60. package/dist/types.d.mts +48 -0
  61. package/dist/types.mjs +1 -0
  62. package/package.json +52 -14
  63. package/dist/metadata/param-registry.d.mts +0 -16
  64. package/dist/metadata/param-registry.mjs +0 -31
  65. package/dist/scope-validation.d.mts +0 -21
  66. package/dist/scope-validation.mjs +0 -35
@@ -1,55 +1,24 @@
1
+ import { Constructor } from "../constructor-type.mjs";
1
2
  import { Token } from "../token.mjs";
2
- import { Constructor, ResolveHint } from "../binding.mjs";
3
- import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
4
3
 
5
4
  //#region src/decorators/inject.d.ts
6
- /**
7
- * Name/tag hint forwarded to the container when resolving an injected dependency.
8
- * Alias for {@link ResolveHint}; used as the second parameter of {@link inject} and {@link optional}.
9
- */
10
- type InjectOptions = ResolveHint;
11
- /**
12
- * Dual-purpose injection helper:
13
- *
14
- * **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
15
- * optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
16
- *
17
- * ```ts
18
- * @injectable([inject(Logger, { name: 'file' })])
19
- * class UserService { constructor(log: Logger) {} }
20
- * ```
21
- *
22
- * **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
23
- * `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
24
- * injection after construction.
25
- *
26
- * ```ts
27
- * @inject(Logger) accessor logger!: LoggerService;
28
- * ```
29
- *
30
- * @param token - The injection key (token or constructor) to resolve.
31
- * @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
32
- * `ClassAccessorDecoratorContext` automatically supplied by the runtime.
33
- */
34
- declare function inject<Value>(token: Token<Value> | Constructor<Value>, optionsOrContext?: InjectOptions | ClassAccessorDecoratorContext): InjectionDescriptor<Value>;
35
- /**
36
- * Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
37
- * During resolution, an unbound token resolves to `undefined` instead of throwing
38
- * {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
39
- */
40
- declare function optional<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
41
- /**
42
- * Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
43
- * (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
44
- * Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
45
- *
46
- * Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
47
- * callers omit options and register disambiguators on each binding instead).
48
- */
49
- declare function injectAll<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
50
- /**
51
- * Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
52
- */
5
+ interface InjectOptions {
6
+ name?: string;
7
+ tags?: ReadonlyArray<readonly [tag: string, value: unknown]>;
8
+ }
9
+ interface InjectionDescriptor<Value = unknown> {
10
+ readonly token: Token<Value> | Constructor<Value>;
11
+ readonly optional: boolean;
12
+ readonly multi: boolean;
13
+ readonly name?: string;
14
+ readonly tags?: ReadonlyArray<readonly [string, unknown]>;
15
+ }
16
+ type InjectableDependency<Value = unknown> = Token<Value> | Constructor<Value> | InjectionDescriptor<Value>;
53
17
  declare function isInjectionDescriptor(value: unknown): value is InjectionDescriptor;
18
+ declare function normalizeToDescriptor(dep: InjectableDependency): InjectionDescriptor;
19
+ type ClassAccessorDecorator<This, Value> = (target: ClassAccessorDecoratorTarget<This, Value>, context: ClassAccessorDecoratorContext<This, Value>) => ClassAccessorDecoratorResult<This, Value> | void;
20
+ declare function inject<const Value>(t: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value>;
21
+ declare function optional<const Value>(t: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value | undefined>;
22
+ declare function injectAll<const Value>(t: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value[]>;
54
23
  //#endregion
55
- export { InjectOptions, inject, injectAll, isInjectionDescriptor, optional };
24
+ export { InjectOptions, InjectableDependency, InjectionDescriptor, inject, injectAll, isInjectionDescriptor, normalizeToDescriptor, optional };
@@ -1,108 +1,146 @@
1
- import { InternalError } from "../errors.mjs";
2
- import { CODEFAST_DI_ACCESSOR_INJECTIONS } from "../metadata/metadata-keys.mjs";
1
+ import { MissingContainerContextError } from "../errors.mjs";
2
+ import { getActiveContainer } from "../environment.mjs";
3
+ import { injectableSlotToResolveOptions } from "../resolve-options.mjs";
4
+ import { INJECT_ACCESSOR_KEY } from "../metadata/metadata-keys.mjs";
3
5
  //#region src/decorators/inject.ts
4
- /**
5
- * Validates and normalises the `tag` option from {@link InjectOptions}; throws {@link InternalError} on bad input.
6
- */
7
- function normalizeTag(tag) {
8
- if (tag === void 0) return;
9
- if (!Array.isArray(tag) || tag.length !== 2) throw new InternalError(`@inject tag must be a tuple [tagKey, value] with length 2; received ${String(tag)}`);
10
- const [tagName, value] = tag;
11
- if (typeof tagName !== "string") throw new InternalError(`@inject tag key must be a string; received ${typeof tagName}`);
12
- return [tagName, value];
6
+ function isInjectionDescriptor(value) {
7
+ if (value === null || value === void 0) return false;
8
+ const type = typeof value;
9
+ if (type !== "object" && type !== "function") return false;
10
+ return "token" in value && "optional" in value && "multi" in value && typeof value.optional === "boolean" && typeof value.multi === "boolean";
11
+ }
12
+ function normalizeToDescriptor(dep) {
13
+ if (isInjectionDescriptor(dep)) return materializeInjectionDescriptor(dep);
14
+ return {
15
+ token: dep,
16
+ optional: false,
17
+ multi: false
18
+ };
13
19
  }
14
20
  /**
15
- * Builds an {@link InjectionDescriptor} from a token, optional flag, and raw inject options.
21
+ * Dual-role `inject()` values are functions: [[Function]].name must not be treated as a DI slot name.
22
+ * Only enumerable own `name` / `tags` from `Object.defineProperties` are real injection options.
16
23
  */
17
- function toDescriptor(token, optional, options) {
18
- const normalizedTag = normalizeTag(options?.tag);
19
- if (options?.name !== void 0) return {
20
- token,
21
- optional,
22
- name: options.name
24
+ function materializeInjectionDescriptor(dep) {
25
+ if (typeof dep !== "function") return dep;
26
+ const dualRole = dep;
27
+ const base = {
28
+ token: dualRole.token,
29
+ optional: dualRole.optional,
30
+ multi: dualRole.multi
23
31
  };
24
- if (normalizedTag !== void 0) return {
25
- token,
26
- optional,
27
- tag: normalizedTag
32
+ const nameDesc = Object.getOwnPropertyDescriptor(dualRole, "name");
33
+ const tagsDesc = Object.getOwnPropertyDescriptor(dualRole, "tags");
34
+ const explicitName = nameDesc?.enumerable === true && typeof nameDesc.value === "string" ? nameDesc.value : void 0;
35
+ const explicitTags = tagsDesc?.enumerable === true ? tagsDesc.value : void 0;
36
+ if (explicitName !== void 0 && explicitTags !== void 0) return {
37
+ ...base,
38
+ name: explicitName,
39
+ tags: explicitTags
28
40
  };
29
- return {
30
- token,
31
- optional
41
+ if (explicitName !== void 0) return {
42
+ ...base,
43
+ name: explicitName
32
44
  };
45
+ if (explicitTags !== void 0) return {
46
+ ...base,
47
+ tags: explicitTags
48
+ };
49
+ return base;
33
50
  }
34
- /**
35
- * Type guard — returns `true` when `value` is a TC39 `ClassAccessorDecoratorContext` (accessor field).
36
- */
37
- function isAccessorDecoratorContext(value) {
38
- return typeof value === "object" && value !== null && "kind" in value && value.kind === "accessor";
51
+ function buildInjectionDescriptor(t, options) {
52
+ const base = {
53
+ token: t,
54
+ optional: false,
55
+ multi: false
56
+ };
57
+ if (options?.name !== void 0 && options.tags !== void 0) return {
58
+ ...base,
59
+ name: options.name,
60
+ tags: options.tags
61
+ };
62
+ if (options?.name !== void 0) return {
63
+ ...base,
64
+ name: options.name
65
+ };
66
+ if (options?.tags !== void 0) return {
67
+ ...base,
68
+ tags: options.tags
69
+ };
70
+ return base;
39
71
  }
40
- /**
41
- * Dual-purpose injection helper:
42
- *
43
- * **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
44
- * optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
45
- *
46
- * ```ts
47
- * @injectable([inject(Logger, { name: 'file' })])
48
- * class UserService { constructor(log: Logger) {} }
49
- * ```
50
- *
51
- * **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
52
- * `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
53
- * injection after construction.
54
- *
55
- * ```ts
56
- * @inject(Logger) accessor logger!: LoggerService;
57
- * ```
58
- *
59
- * @param token - The injection key (token or constructor) to resolve.
60
- * @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
61
- * `ClassAccessorDecoratorContext` automatically supplied by the runtime.
62
- */
63
- function inject(token, optionsOrContext) {
64
- if (isAccessorDecoratorContext(optionsOrContext)) {
65
- const ctx = optionsOrContext;
66
- const metaRecord = ctx.metadata;
67
- const key = CODEFAST_DI_ACCESSOR_INJECTIONS;
68
- if (!Array.isArray(metaRecord[key])) metaRecord[key] = [];
69
- metaRecord[key].push({
70
- name: String(ctx.name),
71
- token,
72
- optional: false
72
+ function inject(t, options) {
73
+ const descriptor = buildInjectionDescriptor(t, options);
74
+ const decoratorFn = (_target, context) => {
75
+ const meta = context.metadata;
76
+ if (!Array.isArray(meta[INJECT_ACCESSOR_KEY])) meta[INJECT_ACCESSOR_KEY] = [];
77
+ meta[INJECT_ACCESSOR_KEY].push({
78
+ key: context.name,
79
+ descriptor
73
80
  });
74
- return;
75
- }
76
- return toDescriptor(token, false, optionsOrContext);
77
- }
78
- /**
79
- * Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
80
- * During resolution, an unbound token resolves to `undefined` instead of throwing
81
- * {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
82
- */
83
- function optional(token, options) {
84
- return toDescriptor(token, true, options);
81
+ context.addInitializer(function() {
82
+ const container = getActiveContainer();
83
+ if (container === void 0) throw new MissingContainerContextError(String(context.name));
84
+ const hint = options === void 0 ? void 0 : injectableSlotToResolveOptions({
85
+ ...options.name !== void 0 ? { name: options.name } : {},
86
+ ...options.tags !== void 0 ? { tags: options.tags } : {}
87
+ });
88
+ const value = descriptor.optional ? container.resolveOptional(t, hint) : container.resolve(t, hint);
89
+ context.access.set(this, value);
90
+ });
91
+ return {};
92
+ };
93
+ const props = {};
94
+ for (const key of Object.keys(descriptor)) props[key] = {
95
+ value: descriptor[key],
96
+ writable: true,
97
+ enumerable: true,
98
+ configurable: true
99
+ };
100
+ Object.defineProperties(decoratorFn, props);
101
+ return decoratorFn;
85
102
  }
86
- /**
87
- * Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
88
- * (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
89
- * Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
90
- *
91
- * Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
92
- * callers omit options and register disambiguators on each binding instead).
93
- */
94
- function injectAll(token, options) {
95
- return {
96
- ...toDescriptor(token, false, options),
97
- all: true
103
+ function optional(t, options) {
104
+ const base = {
105
+ token: t,
106
+ optional: true,
107
+ multi: false
108
+ };
109
+ if (options?.name !== void 0 && options.tags !== void 0) return {
110
+ ...base,
111
+ name: options.name,
112
+ tags: options.tags
113
+ };
114
+ if (options?.name !== void 0) return {
115
+ ...base,
116
+ name: options.name
98
117
  };
118
+ if (options?.tags !== void 0) return {
119
+ ...base,
120
+ tags: options.tags
121
+ };
122
+ return base;
99
123
  }
100
- /**
101
- * Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
102
- */
103
- function isInjectionDescriptor(value) {
104
- if (typeof value !== "object" || value === null) return false;
105
- return "token" in value && "optional" in value;
124
+ function injectAll(t, options) {
125
+ const base = {
126
+ token: t,
127
+ optional: false,
128
+ multi: true
129
+ };
130
+ if (options?.name !== void 0 && options.tags !== void 0) return {
131
+ ...base,
132
+ name: options.name,
133
+ tags: options.tags
134
+ };
135
+ if (options?.name !== void 0) return {
136
+ ...base,
137
+ name: options.name
138
+ };
139
+ if (options?.tags !== void 0) return {
140
+ ...base,
141
+ tags: options.tags
142
+ };
143
+ return base;
106
144
  }
107
145
  //#endregion
108
- export { inject, injectAll, isInjectionDescriptor, optional };
146
+ export { inject, injectAll, isInjectionDescriptor, normalizeToDescriptor, optional };
@@ -1,41 +1,20 @@
1
- import { Token } from "../token.mjs";
2
- import { BindingScope, Constructor } from "../binding.mjs";
3
- import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
1
+ import { Constructor } from "../constructor-type.mjs";
2
+ import { BindingScope } from "../types.mjs";
3
+ import { InjectableDependency } from "./inject.mjs";
4
4
 
5
5
  //#region src/decorators/injectable.d.ts
6
- /**
7
- * A single entry in the `deps` array passed to `@injectable()`.
8
- * Can be a plain token/constructor (resolved with no hint) or an {@link InjectionDescriptor}
9
- * produced by `inject` / `optional` / `injectAll` when name, tag, optional, or resolve-all
10
- * semantics are needed.
11
- */
12
- type InjectableDependency = Token<unknown> | Constructor<unknown> | InjectionDescriptor<unknown>;
13
- /**
14
- * Returns all classes decorated with `@injectable({ autoRegister: true })`.
15
- * Pass to {@link Container.loadAutoRegistered} or iterate manually to bind them.
16
- */
17
- declare function getAutoRegistered(): ReadonlyArray<{
18
- implementationClass: Constructor<unknown>;
19
- scope: BindingScope;
20
- }>;
21
- /**
22
- * Stage 3 class decorator that writes constructor dependency metadata into `Symbol.metadata`.
23
- *
24
- * @param deps - Ordered list of constructor parameters; length must match the class arity.
25
- * @param autoRegisterOptions - Optional auto-registration flags.
26
- * @param autoRegisterOptions.autoRegister - When `true`, registers the class in {@link getAutoRegistered} at
27
- * class-definition time so {@link Container.loadAutoRegistered} can bind it automatically.
28
- * @param autoRegisterOptions.scope - Scope used when auto-registering; defaults to `"transient"`.
29
- *
30
- * @example
31
- * ```ts
32
- * @injectable([Logger, inject(Config, { name: "app" })])
33
- * class UserService { constructor(log: Logger, cfg: AppConfig) {} }
34
- * ```
35
- */
36
- declare function injectable(deps?: readonly InjectableDependency[], autoRegisterOptions?: {
37
- autoRegister?: boolean;
6
+ interface AutoRegisterRegistry {
7
+ register(target: Constructor, scope: BindingScope): void;
8
+ entries(): ReadonlyArray<{
9
+ target: Constructor;
10
+ scope: BindingScope;
11
+ }>;
12
+ }
13
+ declare function createAutoRegisterRegistry(): AutoRegisterRegistry;
14
+ interface InjectableOptions {
15
+ autoRegister?: AutoRegisterRegistry;
38
16
  scope?: BindingScope;
39
- }): <Class extends abstract new (...args: never[]) => unknown>(implementationClass: Class, context: ClassDecoratorContext<Class>) => void;
17
+ }
18
+ declare function injectable(deps?: readonly InjectableDependency[], options?: InjectableOptions): (target: unknown, context: ClassDecoratorContext) => void;
40
19
  //#endregion
41
- export { InjectableDependency, getAutoRegistered, injectable };
20
+ export { AutoRegisterRegistry, type InjectableDependency, InjectableOptions, createAutoRegisterRegistry, injectable };
@@ -1,74 +1,55 @@
1
- import { InternalError } from "../errors.mjs";
2
- import { CODEFAST_DI_CONSTRUCTOR_METADATA } from "../metadata/metadata-keys.mjs";
3
- import { isInjectionDescriptor } from "./inject.mjs";
1
+ import { INJECTABLE_KEY, constructorMetadataMap } from "../metadata/metadata-keys.mjs";
2
+ import { normalizeToDescriptor } from "./inject.mjs";
4
3
  //#region src/decorators/injectable.ts
5
- /**
6
- * Global mutable registry of classes decorated with `@injectable({ autoRegister: true })`.
7
- * Populated at class-definition time (via `context.addInitializer`), drained by
8
- * {@link Container.loadAutoRegistered}. Entries accumulate for the lifetime of the process.
9
- */
10
- const AUTO_REGISTER_REGISTRY = [];
11
- /**
12
- * Returns all classes decorated with `@injectable({ autoRegister: true })`.
13
- * Pass to {@link Container.loadAutoRegistered} or iterate manually to bind them.
14
- */
15
- function getAutoRegistered() {
16
- return AUTO_REGISTER_REGISTRY;
17
- }
18
- /**
19
- * Normalises a single `@injectable` deps-array entry into the uniform {@link ParamMetadata}
20
- * shape used by the resolver's constructor-instantiation path.
21
- *
22
- * - {@link InjectionDescriptor} entries carry `optional`, `name`, and `tag` fields.
23
- * - Plain token / constructor entries are wrapped with `optional: false` and no hint.
24
- */
25
- function toParamMetadata(dependency, index) {
26
- if (isInjectionDescriptor(dependency)) return {
27
- index,
28
- token: dependency.token,
29
- optional: dependency.optional,
30
- name: dependency.name,
31
- tag: dependency.tag,
32
- all: dependency.all === true ? true : void 0
33
- };
4
+ function createAutoRegisterRegistry() {
5
+ const _entries = [];
34
6
  return {
35
- index,
36
- token: dependency,
37
- optional: false
7
+ register(target, scope) {
8
+ _entries.push({
9
+ target,
10
+ scope
11
+ });
12
+ },
13
+ entries() {
14
+ return _entries;
15
+ }
38
16
  };
39
17
  }
40
- /**
41
- * Stage 3 class decorator that writes constructor dependency metadata into `Symbol.metadata`.
42
- *
43
- * @param deps - Ordered list of constructor parameters; length must match the class arity.
44
- * @param autoRegisterOptions - Optional auto-registration flags.
45
- * @param autoRegisterOptions.autoRegister - When `true`, registers the class in {@link getAutoRegistered} at
46
- * class-definition time so {@link Container.loadAutoRegistered} can bind it automatically.
47
- * @param autoRegisterOptions.scope - Scope used when auto-registering; defaults to `"transient"`.
48
- *
49
- * @example
50
- * ```ts
51
- * @injectable([Logger, inject(Config, { name: "app" })])
52
- * class UserService { constructor(log: Logger, cfg: AppConfig) {} }
53
- * ```
54
- */
55
- function injectable(deps = [], autoRegisterOptions) {
56
- return (implementationClass, context) => {
57
- const declaredArity = implementationClass.length;
58
- if (declaredArity !== deps.length) throw new InternalError(`Class "${String(context.name ?? implementationClass.name)}" declares ${String(declaredArity)} constructor parameters but @injectable(...) received ${String(deps.length)} dependency descriptors.`);
59
- const payload = { params: deps.map((dependency, index) => toParamMetadata(dependency, index)) };
60
- const metadataRecord = context.metadata;
61
- metadataRecord[CODEFAST_DI_CONSTRUCTOR_METADATA] = payload;
62
- if (autoRegisterOptions?.autoRegister === true) {
63
- const scope = autoRegisterOptions.scope ?? "transient";
64
- context.addInitializer(function() {
65
- AUTO_REGISTER_REGISTRY.push({
66
- implementationClass: this,
67
- scope
68
- });
69
- });
18
+ function injectable(deps, options) {
19
+ return function(target, context) {
20
+ const constructorMeta = { params: (deps ?? []).map((dep, index) => {
21
+ const descriptor = normalizeToDescriptor(dep);
22
+ const base = {
23
+ index,
24
+ token: descriptor.token,
25
+ optional: descriptor.optional,
26
+ multi: descriptor.multi
27
+ };
28
+ if (descriptor.name !== void 0 && descriptor.tags !== void 0) return {
29
+ ...base,
30
+ name: descriptor.name,
31
+ tags: descriptor.tags
32
+ };
33
+ if (descriptor.name !== void 0) return {
34
+ ...base,
35
+ name: descriptor.name
36
+ };
37
+ if (descriptor.tags !== void 0) return {
38
+ ...base,
39
+ tags: descriptor.tags
40
+ };
41
+ return base;
42
+ }) };
43
+ constructorMetadataMap.set(target, constructorMeta);
44
+ try {
45
+ const meta = context.metadata;
46
+ if (meta !== null && typeof meta === "object") meta[INJECTABLE_KEY] = constructorMeta;
47
+ } catch {}
48
+ if (options?.autoRegister !== void 0) {
49
+ const scope = options.scope ?? "transient";
50
+ options.autoRegister.register(target, scope);
70
51
  }
71
52
  };
72
53
  }
73
54
  //#endregion
74
- export { getAutoRegistered, injectable };
55
+ export { createAutoRegisterRegistry, injectable };
@@ -1,25 +1,5 @@
1
1
  //#region src/decorators/lifecycle-decorators.d.ts
2
- /**
3
- * Stage 3 method decorator: marks a method to be called after the class is instantiated
4
- * by the container and before the `onActivation` hook runs.
5
- *
6
- * Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
7
- *
8
- * Only one method per class may carry this decorator; a second application throws.
9
- * If the decorated method returns a `Promise` during synchronous resolution,
10
- * {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
11
- */
12
- declare function postConstruct(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
13
- /**
14
- * Stage 3 method decorator: marks a method to be called when the container disposes or
15
- * unloads the owning binding.
16
- *
17
- * Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
18
- *
19
- * Only one method per class may carry this decorator; a second application throws.
20
- * If the decorated method returns a `Promise` during synchronous disposal,
21
- * an error is thrown — use `Container.disposeAsync()` instead.
22
- */
23
- declare function preDestroy(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
2
+ declare function postConstruct(): (target: unknown, context: ClassMethodDecoratorContext) => void;
3
+ declare function preDestroy(): (target: unknown, context: ClassMethodDecoratorContext) => void;
24
4
  //#endregion
25
5
  export { postConstruct, preDestroy };
@@ -1,45 +1,89 @@
1
- import { CODEFAST_DI_LIFECYCLE_METADATA } from "../metadata/metadata-keys.mjs";
1
+ import { LIFECYCLE_KEY, lifecycleByConstructorMetadataMap, lifecycleMetadataMap } from "../metadata/metadata-keys.mjs";
2
2
  //#region src/decorators/lifecycle-decorators.ts
3
- /**
4
- * Stage 3 method decorator: marks a method to be called after the class is instantiated
5
- * by the container and before the `onActivation` hook runs.
6
- *
7
- * Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
8
- *
9
- * Only one method per class may carry this decorator; a second application throws.
10
- * If the decorated method returns a `Promise` during synchronous resolution,
11
- * {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
12
- */
3
+ function appendUniqueMethod(metadata, phase, methodName) {
4
+ if (!metadata[phase].includes(methodName)) metadata[phase].push(methodName);
5
+ }
6
+ function resolveConstructorFromDecoratorTarget(target) {
7
+ if (typeof target === "function") return target;
8
+ if (typeof target === "object" && target !== null) {
9
+ const ctor = target.constructor;
10
+ if (typeof ctor === "function") return ctor;
11
+ }
12
+ }
13
+ function registerByConstructor(target, phase, methodName) {
14
+ const ctor = resolveConstructorFromDecoratorTarget(target);
15
+ if (ctor === void 0) return;
16
+ const ctorExisting = lifecycleByConstructorMetadataMap.get(ctor);
17
+ if (ctorExisting !== void 0) appendUniqueMethod(ctorExisting, phase, methodName);
18
+ else lifecycleByConstructorMetadataMap.set(ctor, {
19
+ postConstruct: phase === "postConstruct" ? [methodName] : [],
20
+ preDestroy: phase === "preDestroy" ? [methodName] : []
21
+ });
22
+ }
13
23
  function postConstruct() {
14
- return (_target, context) => {
15
- const metaRecord = context.metadata;
16
- const existing = metaRecord["codefast/di:lifecycle-metadata:v1"] ?? {};
17
- if (existing.postConstruct !== void 0) throw new Error(`@postConstruct() is already defined as "${existing.postConstruct}" on this class.`);
18
- metaRecord[CODEFAST_DI_LIFECYCLE_METADATA] = {
19
- ...existing,
20
- postConstruct: String(context.name)
21
- };
24
+ return function(target, context) {
25
+ const methodName = String(context.name);
26
+ registerByConstructor(target, "postConstruct", methodName);
27
+ const existing = lifecycleMetadataMap.get(context.metadata);
28
+ if (existing !== void 0) appendUniqueMethod(existing, "postConstruct", methodName);
29
+ else lifecycleMetadataMap.set(context.metadata, {
30
+ postConstruct: [methodName],
31
+ preDestroy: []
32
+ });
33
+ context.addInitializer(function() {
34
+ const targetOrInstance = this;
35
+ const ctor = typeof targetOrInstance === "function" ? targetOrInstance : targetOrInstance.constructor;
36
+ const ctorExisting = lifecycleByConstructorMetadataMap.get(ctor);
37
+ if (ctorExisting !== void 0) appendUniqueMethod(ctorExisting, "postConstruct", methodName);
38
+ else lifecycleByConstructorMetadataMap.set(ctor, {
39
+ postConstruct: [methodName],
40
+ preDestroy: []
41
+ });
42
+ });
43
+ try {
44
+ const meta = context.metadata;
45
+ if (meta !== null && typeof meta === "object") {
46
+ if (!meta[LIFECYCLE_KEY]) meta[LIFECYCLE_KEY] = {
47
+ postConstruct: [],
48
+ preDestroy: []
49
+ };
50
+ const lifecycle = meta[LIFECYCLE_KEY];
51
+ appendUniqueMethod(lifecycle, "postConstruct", methodName);
52
+ }
53
+ } catch {}
22
54
  };
23
55
  }
24
- /**
25
- * Stage 3 method decorator: marks a method to be called when the container disposes or
26
- * unloads the owning binding.
27
- *
28
- * Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
29
- *
30
- * Only one method per class may carry this decorator; a second application throws.
31
- * If the decorated method returns a `Promise` during synchronous disposal,
32
- * an error is thrown — use `Container.disposeAsync()` instead.
33
- */
34
56
  function preDestroy() {
35
- return (_target, context) => {
36
- const metaRecord = context.metadata;
37
- const existing = metaRecord["codefast/di:lifecycle-metadata:v1"] ?? {};
38
- if (existing.preDestroy !== void 0) throw new Error(`@preDestroy() is already defined as "${existing.preDestroy}" on this class.`);
39
- metaRecord[CODEFAST_DI_LIFECYCLE_METADATA] = {
40
- ...existing,
41
- preDestroy: String(context.name)
42
- };
57
+ return function(target, context) {
58
+ const methodName = String(context.name);
59
+ registerByConstructor(target, "preDestroy", methodName);
60
+ const existing = lifecycleMetadataMap.get(context.metadata);
61
+ if (existing !== void 0) appendUniqueMethod(existing, "preDestroy", methodName);
62
+ else lifecycleMetadataMap.set(context.metadata, {
63
+ postConstruct: [],
64
+ preDestroy: [methodName]
65
+ });
66
+ context.addInitializer(function() {
67
+ const targetOrInstance = this;
68
+ const ctor = typeof targetOrInstance === "function" ? targetOrInstance : targetOrInstance.constructor;
69
+ const ctorExisting = lifecycleByConstructorMetadataMap.get(ctor);
70
+ if (ctorExisting !== void 0) appendUniqueMethod(ctorExisting, "preDestroy", methodName);
71
+ else lifecycleByConstructorMetadataMap.set(ctor, {
72
+ postConstruct: [],
73
+ preDestroy: [methodName]
74
+ });
75
+ });
76
+ try {
77
+ const meta = context.metadata;
78
+ if (meta !== null && typeof meta === "object") {
79
+ if (!meta[LIFECYCLE_KEY]) meta[LIFECYCLE_KEY] = {
80
+ postConstruct: [],
81
+ preDestroy: []
82
+ };
83
+ const lifecycle = meta[LIFECYCLE_KEY];
84
+ appendUniqueMethod(lifecycle, "preDestroy", methodName);
85
+ }
86
+ } catch {}
43
87
  };
44
88
  }
45
89
  //#endregion