@fougere/container 0.9.2-alpha.0 → 0.10.0-alpha.0

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 (44) hide show
  1. package/dist/{container.d.ts → Container.d.ts} +3 -19
  2. package/dist/Container.d.ts.map +1 -0
  3. package/dist/Container.js +2 -0
  4. package/dist/Container.js.map +1 -0
  5. package/dist/Disposable.d.ts +7 -0
  6. package/dist/Disposable.d.ts.map +1 -0
  7. package/dist/Disposable.js +8 -0
  8. package/dist/Disposable.js.map +1 -0
  9. package/dist/ScopeContainer.d.ts +27 -0
  10. package/dist/ScopeContainer.d.ts.map +1 -0
  11. package/dist/ScopeContainer.js +142 -0
  12. package/dist/ScopeContainer.js.map +1 -0
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/registration/Constructor.d.ts +2 -0
  18. package/dist/registration/Constructor.d.ts.map +1 -0
  19. package/dist/registration/Constructor.js +2 -0
  20. package/dist/registration/Constructor.js.map +1 -0
  21. package/dist/registration/Lifetime.d.ts +6 -0
  22. package/dist/registration/Lifetime.d.ts.map +1 -0
  23. package/dist/registration/Lifetime.js +2 -0
  24. package/dist/registration/Lifetime.js.map +1 -0
  25. package/dist/registration/RegisterOptions.d.ts +11 -0
  26. package/dist/registration/RegisterOptions.d.ts.map +1 -0
  27. package/dist/registration/RegisterOptions.js +2 -0
  28. package/dist/registration/RegisterOptions.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/{container.ts → Container.ts} +2 -22
  31. package/src/Disposable.ts +13 -0
  32. package/src/ScopeContainer.ts +169 -0
  33. package/src/index.ts +8 -5
  34. package/src/registration/Constructor.ts +1 -0
  35. package/src/registration/Lifetime.ts +5 -0
  36. package/src/registration/RegisterOptions.ts +12 -0
  37. package/dist/container.d.ts.map +0 -1
  38. package/dist/container.js +0 -2
  39. package/dist/container.js.map +0 -1
  40. package/dist/create.d.ts +0 -3
  41. package/dist/create.d.ts.map +0 -1
  42. package/dist/create.js +0 -136
  43. package/dist/create.js.map +0 -1
  44. package/src/create.ts +0 -168
@@ -1,21 +1,5 @@
1
- export type Constructor<T = unknown> = new (...args: any[]) => T;
2
- export interface RegisterOptions {
3
- /**
4
- * `'singleton'` builds once per scope and the container disposes it; `'transient'`
5
- * builds per `resolve` and the caller closes it. Absent means `'transient'`.
6
- */
7
- lifetime?: 'singleton' | 'transient';
8
- /**
9
- * The TYPE names of the constructor's parameters, resolved in this scope in order —
10
- * `constructor(private users: UserRepository, private log: Logger)` is
11
- * `deps: ['UserRepository', 'Logger']`.
12
- */
13
- deps?: string[];
14
- }
15
- /** Anything holding a resource can say so, and disposing the container says it back. */
16
- export interface Disposable {
17
- dispose(): void | Promise<void>;
18
- }
1
+ import type { Constructor } from './registration/Constructor.js';
2
+ import type { RegisterOptions } from './registration/RegisterOptions.js';
19
3
  /** DI container interface — the only thing application code sees. */
20
4
  export interface Container {
21
5
  /** The container builds it: `register('UserService', UserService, { deps: ['UserRepository'] })`. */
@@ -33,4 +17,4 @@ export interface Container {
33
17
  /** Children first, then what this scope built, in reverse — refusals travel as one `AggregateError`. */
34
18
  dispose(): Promise<void>;
35
19
  }
36
- //# sourceMappingURL=container.d.ts.map
20
+ //# sourceMappingURL=Container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Container.d.ts","sourceRoot":"","sources":["../src/Container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAEjF,+GAA+G;IAC/G,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAE/C,yEAAyE;IACzE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAE5B,kEAAkE;IAClE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE3B,yEAAyE;IACzE,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IAEvD,yEAAyE;IACzE,WAAW,IAAI,SAAS,CAAC;IAEzB,wGAAwG;IACxG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Container.js","sourceRoot":"","sources":["../src/Container.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ export interface Disposable {
2
+ dispose(): void | Promise<void>;
3
+ }
4
+ export declare class Disposables {
5
+ static is(value: unknown): value is Disposable;
6
+ }
7
+ //# sourceMappingURL=Disposable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Disposable.d.ts","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,qBAAa,WAAW;IACtB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAM7C;CACF"}
@@ -0,0 +1,8 @@
1
+ export class Disposables {
2
+ static is(value) {
3
+ return (typeof value === 'object' &&
4
+ value !== null &&
5
+ typeof value.dispose === 'function');
6
+ }
7
+ }
8
+ //# sourceMappingURL=Disposable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Disposable.js","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,WAAW;IACtB,MAAM,CAAC,EAAE,CAAC,KAAc;QACtB,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,OAAQ,KAAoB,CAAC,OAAO,KAAK,UAAU,CACpD,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import type { Container } from './Container.js';
2
+ import type { Constructor } from './registration/Constructor.js';
3
+ import type { RegisterOptions } from './registration/RegisterOptions.js';
4
+ /** A scope reaches its parent and its children through members only a scope can read. */
5
+ export declare class ScopeContainer implements Container {
6
+ private readonly parent?;
7
+ private readonly registry;
8
+ private readonly built;
9
+ private readonly children;
10
+ private fallback;
11
+ private readonly resolving;
12
+ constructor(parent?: ScopeContainer | undefined);
13
+ register<T>(name: string, ctor: Constructor<T>, options?: RegisterOptions): void;
14
+ registerValue<T>(name: string, value: T): void;
15
+ resolve<T>(name: string): T;
16
+ has(name: string): boolean;
17
+ createScope(): Container;
18
+ dispose(): Promise<void>;
19
+ setFallback(resolve: (name: string) => unknown): void;
20
+ private entryOf;
21
+ /** Set on the root, honoured from any scope — a scope inherits it by asking upward. */
22
+ private fallbackOf;
23
+ private forget;
24
+ private through;
25
+ private remember;
26
+ }
27
+ //# sourceMappingURL=ScopeContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeContainer.d.ts","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAQzE,yFAAyF;AACzF,qBAAa,cAAe,YAAW,SAAS;IAalC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAZpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IAGvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAA0C;IAI1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IAErC,YAA6B,MAAM,CAAC,EAAE,cAAc,YAAA,EAEnD;IAED,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAO/E;IAED,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAO7C;IAED,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAgD1B;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzB;IAED,WAAW,IAAI,SAAS,CAKvB;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B7B;IAED,WAAW,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAEpD;IAED,OAAO,CAAC,OAAO;IAIf,uFAAuF;IACvF,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,OAAO;IAMf,OAAO,CAAC,QAAQ;CAKjB"}
@@ -0,0 +1,142 @@
1
+ import { Disposables } from './Disposable.js';
2
+ /** A scope reaches its parent and its children through members only a scope can read. */
3
+ export class ScopeContainer {
4
+ parent;
5
+ registry = new Map();
6
+ built = [];
7
+ // Closed by this container, and before `built` — a child may hold what this scope built,
8
+ // never the other way round.
9
+ children = [];
10
+ fallback;
11
+ // The names being built right now, shared by the whole tree: a miss here is answered by the
12
+ // parent and the descent continues there, so only one stack can name the path whole —
13
+ // `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
14
+ resolving;
15
+ constructor(parent) {
16
+ this.parent = parent;
17
+ this.resolving = parent?.resolving ?? [];
18
+ }
19
+ register(name, ctor, options) {
20
+ const lifetime = options?.lifetime ?? 'transient';
21
+ const deps = options?.deps ?? [];
22
+ this.registry.set(name, {
23
+ factory: (c) => new ctor(...deps.map((d) => c.resolve(d))),
24
+ lifetime,
25
+ });
26
+ }
27
+ registerValue(name, value) {
28
+ // A value the container did not build is not the container's to dispose.
29
+ this.registry.set(name, {
30
+ factory: () => value,
31
+ lifetime: 'singleton',
32
+ instance: value,
33
+ });
34
+ }
35
+ resolve(name) {
36
+ const entry = this.registry.get(name);
37
+ // Not found locally — the parent holds it, and holds its instance too.
38
+ if (!entry && this.parent?.entryOf(name)) {
39
+ return this.parent.resolve(name);
40
+ }
41
+ // Nobody holds it. A frond declared in `remotes` registers nothing here, so its
42
+ // façade is fabricated by the fallback rather than found.
43
+ if (!entry) {
44
+ const made = this.fallbackOf()?.(name);
45
+ if (made !== undefined) {
46
+ this.registry.set(name, {
47
+ factory: () => made,
48
+ lifetime: 'singleton',
49
+ instance: made,
50
+ });
51
+ return made;
52
+ }
53
+ throw new Error(`[container] '${name}' is not registered${this.through(name)}`);
54
+ }
55
+ if (entry.instance !== undefined)
56
+ return entry.instance;
57
+ if (this.resolving.includes(name)) {
58
+ throw new Error(`[container] dependency cycle: ${[...this.resolving, name].join(' → ')}`);
59
+ }
60
+ // Popped in a finally, because a constructor that throws leaves the name on the stack
61
+ // otherwise and the next resolution of it reports a cycle that is not there.
62
+ this.resolving.push(name);
63
+ try {
64
+ const value = entry.factory(this);
65
+ // The container disposes what it KEEPS: a transient is handed over and forgotten,
66
+ // and its caller is the one who knows when it is done.
67
+ if (entry.lifetime === 'singleton') {
68
+ entry.instance = value;
69
+ this.remember(value);
70
+ }
71
+ return value;
72
+ }
73
+ finally {
74
+ this.resolving.pop();
75
+ }
76
+ }
77
+ has(name) {
78
+ return this.registry.has(name) || (this.parent?.has(name) ?? false);
79
+ }
80
+ createScope() {
81
+ const child = new ScopeContainer(this);
82
+ this.children.push(child);
83
+ return child;
84
+ }
85
+ async dispose() {
86
+ // Everything is told before anything throws, then the failures travel together.
87
+ // Dropped from the parent first: a scope that closes itself leaves a reference the
88
+ // parent would hold for the life of the process.
89
+ this.parent?.forget(this);
90
+ const failures = [];
91
+ // A copy: closing a child splices it out of `children`, so walking the array
92
+ // itself stepped over every second sibling.
93
+ for (const child of [...this.children].reverse()) {
94
+ try {
95
+ await child.dispose();
96
+ }
97
+ catch (error) {
98
+ failures.push(error);
99
+ }
100
+ }
101
+ this.children.length = 0;
102
+ for (const value of this.built.reverse()) {
103
+ try {
104
+ await value.dispose();
105
+ }
106
+ catch (error) {
107
+ failures.push(error);
108
+ }
109
+ }
110
+ this.built.length = 0;
111
+ this.registry.clear();
112
+ if (failures.length > 0) {
113
+ throw new AggregateError(failures, '[container] one or more disposals failed');
114
+ }
115
+ }
116
+ setFallback(resolve) {
117
+ this.fallback = resolve;
118
+ }
119
+ entryOf(name) {
120
+ return this.registry.get(name) ?? this.parent?.entryOf(name);
121
+ }
122
+ /** Set on the root, honoured from any scope — a scope inherits it by asking upward. */
123
+ fallbackOf() {
124
+ return this.fallback ?? this.parent?.fallbackOf();
125
+ }
126
+ forget(child) {
127
+ const at = this.children.indexOf(child);
128
+ if (at !== -1)
129
+ this.children.splice(at, 1);
130
+ }
131
+ through(name) {
132
+ return this.resolving.length > 0
133
+ ? ` (resolving: ${[...this.resolving, name].join(' → ')})`
134
+ : '';
135
+ }
136
+ remember(value) {
137
+ if (Disposables.is(value))
138
+ this.built.push(value);
139
+ return value;
140
+ }
141
+ }
142
+ //# sourceMappingURL=ScopeContainer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScopeContainer.js","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export { type Container, type Constructor, } from './container.js';
2
- export { createContainer } from './create.js';
1
+ import type { Container } from './Container.js';
2
+ import type { Constructor } from './registration/Constructor.js';
3
+ export type { Container, Constructor };
4
+ export declare function createContainer(): Container;
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,GACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAEvC,wBAAgB,eAAe,IAAI,SAAS,CAE3C"}
package/dist/index.js CHANGED
@@ -1,2 +1,5 @@
1
- export { createContainer } from './create.js';
1
+ import { ScopeContainer } from './ScopeContainer.js';
2
+ export function createContainer() {
3
+ return new ScopeContainer();
4
+ }
2
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMrD,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,cAAc,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export type Constructor<T = unknown> = new (...args: any[]) => T;
2
+ //# sourceMappingURL=Constructor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Constructor.d.ts","sourceRoot":"","sources":["../../src/registration/Constructor.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Constructor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Constructor.js","sourceRoot":"","sources":["../../src/registration/Constructor.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `'singleton'` builds once per scope and the container disposes it; `'transient'`
3
+ * builds per `resolve` and the caller closes it. Absent means `'transient'`.
4
+ */
5
+ export type Lifetime = 'singleton' | 'transient';
6
+ //# sourceMappingURL=Lifetime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lifetime.d.ts","sourceRoot":"","sources":["../../src/registration/Lifetime.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Lifetime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lifetime.js","sourceRoot":"","sources":["../../src/registration/Lifetime.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import type { Lifetime } from './Lifetime.js';
2
+ export interface RegisterOptions {
3
+ lifetime?: Lifetime;
4
+ /**
5
+ * The TYPE names of the constructor's parameters, resolved in this scope in order —
6
+ * `constructor(private users: UserRepository, private log: Logger)` is
7
+ * `deps: ['UserRepository', 'Logger']`.
8
+ */
9
+ deps?: string[];
10
+ }
11
+ //# sourceMappingURL=RegisterOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegisterOptions.d.ts","sourceRoot":"","sources":["../../src/registration/RegisterOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RegisterOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegisterOptions.js","sourceRoot":"","sources":["../../src/registration/RegisterOptions.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/container",
3
- "version": "0.9.2-alpha.0",
3
+ "version": "0.10.0-alpha.0",
4
4
  "description": "Fougere's DI container: resolution by type, wired from the AST scan. Zero dependencies.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -1,25 +1,5 @@
1
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
- export type Constructor<T = unknown> = new (...args: any[]) => T;
3
-
4
- export interface RegisterOptions {
5
- /**
6
- * `'singleton'` builds once per scope and the container disposes it; `'transient'`
7
- * builds per `resolve` and the caller closes it. Absent means `'transient'`.
8
- */
9
- lifetime?: 'singleton' | 'transient';
10
-
11
- /**
12
- * The TYPE names of the constructor's parameters, resolved in this scope in order —
13
- * `constructor(private users: UserRepository, private log: Logger)` is
14
- * `deps: ['UserRepository', 'Logger']`.
15
- */
16
- deps?: string[];
17
- }
18
-
19
- /** Anything holding a resource can say so, and disposing the container says it back. */
20
- export interface Disposable {
21
- dispose(): void | Promise<void>;
22
- }
1
+ import type { Constructor } from './registration/Constructor.js';
2
+ import type { RegisterOptions } from './registration/RegisterOptions.js';
23
3
 
24
4
  /** DI container interface — the only thing application code sees. */
25
5
  export interface Container {
@@ -0,0 +1,13 @@
1
+ export interface Disposable {
2
+ dispose(): void | Promise<void>;
3
+ }
4
+
5
+ export class Disposables {
6
+ static is(value: unknown): value is Disposable {
7
+ return (
8
+ typeof value === 'object' &&
9
+ value !== null &&
10
+ typeof (value as Disposable).dispose === 'function'
11
+ );
12
+ }
13
+ }
@@ -0,0 +1,169 @@
1
+ import type { Container } from './Container.js';
2
+ import { Disposables, type Disposable } from './Disposable.js';
3
+ import type { Constructor } from './registration/Constructor.js';
4
+ import type { Lifetime } from './registration/Lifetime.js';
5
+ import type { RegisterOptions } from './registration/RegisterOptions.js';
6
+
7
+ interface Entry {
8
+ factory: (container: Container) => unknown;
9
+ lifetime: Lifetime;
10
+ instance?: unknown;
11
+ }
12
+
13
+ /** A scope reaches its parent and its children through members only a scope can read. */
14
+ export class ScopeContainer implements Container {
15
+ private readonly registry = new Map<string, Entry>();
16
+
17
+ private readonly built: unknown[] = [];
18
+ // Closed by this container, and before `built` — a child may hold what this scope built,
19
+ // never the other way round.
20
+ private readonly children: ScopeContainer[] = [];
21
+ private fallback: ((name: string) => unknown) | undefined;
22
+ // The names being built right now, shared by the whole tree: a miss here is answered by the
23
+ // parent and the descent continues there, so only one stack can name the path whole —
24
+ // `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
25
+ private readonly resolving: string[];
26
+
27
+ constructor(private readonly parent?: ScopeContainer) {
28
+ this.resolving = parent?.resolving ?? [];
29
+ }
30
+
31
+ register<T>(name: string, ctor: Constructor<T>, options?: RegisterOptions): void {
32
+ const lifetime = options?.lifetime ?? 'transient';
33
+ const deps = options?.deps ?? [];
34
+ this.registry.set(name, {
35
+ factory: (c) => new ctor(...deps.map((d) => c.resolve(d))),
36
+ lifetime,
37
+ });
38
+ }
39
+
40
+ registerValue<T>(name: string, value: T): void {
41
+ // A value the container did not build is not the container's to dispose.
42
+ this.registry.set(name, {
43
+ factory: () => value,
44
+ lifetime: 'singleton',
45
+ instance: value,
46
+ });
47
+ }
48
+
49
+ resolve<T>(name: string): T {
50
+ const entry = this.registry.get(name);
51
+
52
+ // Not found locally — the parent holds it, and holds its instance too.
53
+ if (!entry && this.parent?.entryOf(name)) {
54
+ return this.parent.resolve<T>(name);
55
+ }
56
+
57
+ // Nobody holds it. A frond declared in `remotes` registers nothing here, so its
58
+ // façade is fabricated by the fallback rather than found.
59
+ if (!entry) {
60
+ const made = this.fallbackOf()?.(name);
61
+ if (made !== undefined) {
62
+ this.registry.set(name, {
63
+ factory: () => made,
64
+ lifetime: 'singleton',
65
+ instance: made,
66
+ });
67
+
68
+ return made as T;
69
+ }
70
+ throw new Error(`[container] '${name}' is not registered${this.through(name)}`);
71
+ }
72
+
73
+ if (entry.instance !== undefined) return entry.instance as T;
74
+
75
+ if (this.resolving.includes(name)) {
76
+ throw new Error(
77
+ `[container] dependency cycle: ${[...this.resolving, name].join(' → ')}`,
78
+ );
79
+ }
80
+
81
+ // Popped in a finally, because a constructor that throws leaves the name on the stack
82
+ // otherwise and the next resolution of it reports a cycle that is not there.
83
+ this.resolving.push(name);
84
+ try {
85
+ const value = entry.factory(this) as T;
86
+ // The container disposes what it KEEPS: a transient is handed over and forgotten,
87
+ // and its caller is the one who knows when it is done.
88
+ if (entry.lifetime === 'singleton') {
89
+ entry.instance = value;
90
+ this.remember(value);
91
+ }
92
+
93
+ return value;
94
+ } finally {
95
+ this.resolving.pop();
96
+ }
97
+ }
98
+
99
+ has(name: string): boolean {
100
+ return this.registry.has(name) || (this.parent?.has(name) ?? false);
101
+ }
102
+
103
+ createScope(): Container {
104
+ const child = new ScopeContainer(this);
105
+ this.children.push(child);
106
+
107
+ return child;
108
+ }
109
+
110
+ async dispose(): Promise<void> {
111
+ // Everything is told before anything throws, then the failures travel together.
112
+ // Dropped from the parent first: a scope that closes itself leaves a reference the
113
+ // parent would hold for the life of the process.
114
+ this.parent?.forget(this);
115
+ const failures: unknown[] = [];
116
+ // A copy: closing a child splices it out of `children`, so walking the array
117
+ // itself stepped over every second sibling.
118
+ for (const child of [...this.children].reverse()) {
119
+ try {
120
+ await child.dispose();
121
+ } catch (error) {
122
+ failures.push(error);
123
+ }
124
+ }
125
+ this.children.length = 0;
126
+ for (const value of this.built.reverse()) {
127
+ try {
128
+ await (value as Disposable).dispose();
129
+ } catch (error) {
130
+ failures.push(error);
131
+ }
132
+ }
133
+ this.built.length = 0;
134
+ this.registry.clear();
135
+ if (failures.length > 0) {
136
+ throw new AggregateError(failures, '[container] one or more disposals failed');
137
+ }
138
+ }
139
+
140
+ setFallback(resolve: (name: string) => unknown): void {
141
+ this.fallback = resolve;
142
+ }
143
+
144
+ private entryOf(name: string): Entry | undefined {
145
+ return this.registry.get(name) ?? this.parent?.entryOf(name);
146
+ }
147
+
148
+ /** Set on the root, honoured from any scope — a scope inherits it by asking upward. */
149
+ private fallbackOf(): ((name: string) => unknown) | undefined {
150
+ return this.fallback ?? this.parent?.fallbackOf();
151
+ }
152
+
153
+ private forget(child: ScopeContainer): void {
154
+ const at = this.children.indexOf(child);
155
+ if (at !== -1) this.children.splice(at, 1);
156
+ }
157
+
158
+ private through(name: string): string {
159
+ return this.resolving.length > 0
160
+ ? ` (resolving: ${[...this.resolving, name].join(' → ')})`
161
+ : '';
162
+ }
163
+
164
+ private remember<T>(value: T): T {
165
+ if (Disposables.is(value)) this.built.push(value);
166
+
167
+ return value;
168
+ }
169
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
- export {
2
- type Container,
3
- type Constructor,
4
- } from './container.js';
1
+ import { ScopeContainer } from './ScopeContainer.js';
2
+ import type { Container } from './Container.js';
3
+ import type { Constructor } from './registration/Constructor.js';
5
4
 
6
- export { createContainer } from './create.js';
5
+ export type { Container, Constructor };
6
+
7
+ export function createContainer(): Container {
8
+ return new ScopeContainer();
9
+ }
@@ -0,0 +1 @@
1
+ export type Constructor<T = unknown> = new (...args: any[]) => T;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `'singleton'` builds once per scope and the container disposes it; `'transient'`
3
+ * builds per `resolve` and the caller closes it. Absent means `'transient'`.
4
+ */
5
+ export type Lifetime = 'singleton' | 'transient';
@@ -0,0 +1,12 @@
1
+ import type { Lifetime } from './Lifetime.js';
2
+
3
+ export interface RegisterOptions {
4
+ lifetime?: Lifetime;
5
+
6
+ /**
7
+ * The TYPE names of the constructor's parameters, resolved in this scope in order —
8
+ * `constructor(private users: UserRepository, private log: Logger)` is
9
+ * `deps: ['UserRepository', 'Logger']`.
10
+ */
11
+ deps?: string[];
12
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,CAAC;IAErC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,qGAAqG;IACrG,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAEjF,+GAA+G;IAC/G,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAE/C,yEAAyE;IACzE,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAE5B,kEAAkE;IAClE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAE3B,yEAAyE;IACzE,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IAEvD,yEAAyE;IACzE,WAAW,IAAI,SAAS,CAAC;IAEzB,wGAAwG;IACxG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
package/dist/container.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=container.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":""}
package/dist/create.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import type { Container } from './container.js';
2
- export declare function createContainer(): Container;
3
- //# sourceMappingURL=create.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../src/create.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,gBAAgB,CAAC;AAqK1F,wBAAgB,eAAe,IAAI,SAAS,CAE3C"}
package/dist/create.js DELETED
@@ -1,136 +0,0 @@
1
- const isDisposable = (value) => typeof value === 'object' && value !== null &&
2
- typeof value.dispose === 'function';
3
- function createScope(parent) {
4
- const registry = new Map();
5
- // In construction order: a thing built later may hold one built earlier, so disposal
6
- // walks this backwards.
7
- const built = [];
8
- // Closed by this container, and before `built` — a child may hold what this scope built,
9
- // never the other way round.
10
- const children = [];
11
- let fallback;
12
- // The names being built right now, shared by the whole tree: a miss here is answered by the
13
- // parent and the descent continues there, so only one stack can name the path whole —
14
- // `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
15
- const resolving = parent?._resolving() ?? [];
16
- const through = (name) => resolving.length > 0 ? ` (resolving: ${[...resolving, name].join(' → ')})` : '';
17
- const remember = (value) => {
18
- if (isDisposable(value))
19
- built.push(value);
20
- return value;
21
- };
22
- const container = {
23
- register(name, ctor, options) {
24
- const lifetime = options?.lifetime ?? 'transient';
25
- const deps = options?.deps ?? [];
26
- registry.set(name, {
27
- factory: (c) => new ctor(...deps.map((d) => c.resolve(d))),
28
- lifetime,
29
- });
30
- },
31
- registerValue(name, value) {
32
- // A value the container did not build is not the container's to dispose.
33
- registry.set(name, { factory: () => value, lifetime: 'singleton', instance: value });
34
- },
35
- resolve(name) {
36
- let entry = registry.get(name);
37
- // Not found locally — the parent holds it, and holds its instance too.
38
- if (!entry && parent && parent._getEntry(name)) {
39
- return parent.resolve(name);
40
- }
41
- // Nobody holds it. A frond declared in `remotes` registers nothing here, so its
42
- // façade is fabricated by the fallback rather than found.
43
- if (!entry) {
44
- const made = container._getFallback()?.(name);
45
- if (made !== undefined) {
46
- registry.set(name, { factory: () => made, lifetime: 'singleton', instance: made });
47
- return made;
48
- }
49
- throw new Error(`[container] '${name}' is not registered${through(name)}`);
50
- }
51
- if (entry.instance !== undefined)
52
- return entry.instance;
53
- if (resolving.includes(name)) {
54
- throw new Error(`[container] dependency cycle: ${[...resolving, name].join(' → ')}`);
55
- }
56
- // Popped in a finally, because a constructor that throws leaves the name on the stack
57
- // otherwise and the next resolution of it reports a cycle that is not there.
58
- resolving.push(name);
59
- try {
60
- const value = entry.factory(container);
61
- // The container disposes what it KEEPS: a transient is handed over and forgotten,
62
- // and its caller is the one who knows when it is done.
63
- if (entry.lifetime === 'singleton') {
64
- entry.instance = value;
65
- remember(value);
66
- }
67
- return value;
68
- }
69
- finally {
70
- resolving.pop();
71
- }
72
- },
73
- has(name) {
74
- return registry.has(name) || (parent?.has(name) ?? false);
75
- },
76
- createScope() {
77
- const child = createScope(container);
78
- children.push(child);
79
- return child;
80
- },
81
- async dispose() {
82
- // Everything is told before anything throws, then the failures travel together.
83
- // Dropped from the parent first: a scope that closes itself leaves a reference the
84
- // parent would hold for the life of the process.
85
- parent?._forget(container);
86
- const failures = [];
87
- // A copy: closing a child splices it out of `children`, so walking the array
88
- // itself stepped over every second sibling.
89
- for (const child of [...children].reverse()) {
90
- try {
91
- await child.dispose();
92
- }
93
- catch (error) {
94
- failures.push(error);
95
- }
96
- }
97
- children.length = 0;
98
- for (const value of built.reverse()) {
99
- try {
100
- await value.dispose();
101
- }
102
- catch (error) {
103
- failures.push(error);
104
- }
105
- }
106
- built.length = 0;
107
- registry.clear();
108
- if (failures.length > 0) {
109
- throw new AggregateError(failures, '[container] one or more disposals failed');
110
- }
111
- },
112
- setFallback(resolve) {
113
- fallback = resolve;
114
- },
115
- _getEntry(name) {
116
- return registry.get(name) ?? parent?._getEntry(name);
117
- },
118
- _resolving() {
119
- return resolving;
120
- },
121
- _forget(child) {
122
- const at = children.indexOf(child);
123
- if (at !== -1)
124
- children.splice(at, 1);
125
- },
126
- /** Set on the root, honoured from any scope — a scope inherits it by asking upward. */
127
- _getFallback() {
128
- return fallback ?? parent?._getFallback();
129
- },
130
- };
131
- return container;
132
- }
133
- export function createContainer() {
134
- return createScope();
135
- }
136
- //# sourceMappingURL=create.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create.js","sourceRoot":"","sources":["../src/create.ts"],"names":[],"mappings":"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"}
package/src/create.ts DELETED
@@ -1,168 +0,0 @@
1
- import type { Container, RegisterOptions, Constructor, Disposable } from './container.js';
2
-
3
- interface Entry {
4
- factory: (container: Container) => unknown;
5
- lifetime: 'singleton' | 'transient';
6
- instance?: unknown;
7
- }
8
-
9
- interface ScopeContainer extends Container {
10
- _getEntry(name: string): Entry | undefined;
11
- _getFallback(): ((name: string) => unknown) | undefined;
12
- _forget(child: ScopeContainer): void;
13
- _resolving(): string[];
14
- }
15
-
16
- const isDisposable = (value: unknown): value is Disposable =>
17
- typeof value === 'object' && value !== null &&
18
- typeof (value as Disposable).dispose === 'function';
19
-
20
- function createScope(parent?: ScopeContainer): ScopeContainer {
21
- const registry = new Map<string, Entry>();
22
- // In construction order: a thing built later may hold one built earlier, so disposal
23
- // walks this backwards.
24
- const built: unknown[] = [];
25
- // Closed by this container, and before `built` — a child may hold what this scope built,
26
- // never the other way round.
27
- const children: ScopeContainer[] = [];
28
- let fallback: ((name: string) => unknown) | undefined;
29
- // The names being built right now, shared by the whole tree: a miss here is answered by the
30
- // parent and the descent continues there, so only one stack can name the path whole —
31
- // `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
32
- const resolving: string[] = parent?._resolving() ?? [];
33
-
34
- const through = (name: string) =>
35
- resolving.length > 0 ? ` (resolving: ${[...resolving, name].join(' → ')})` : '';
36
-
37
- const remember = <T>(value: T): T => {
38
- if (isDisposable(value)) built.push(value);
39
- return value;
40
- };
41
-
42
- const container: ScopeContainer = {
43
- register<T>(name: string, ctor: Constructor<T>, options?: RegisterOptions) {
44
- const lifetime = options?.lifetime ?? 'transient';
45
- const deps = options?.deps ?? [];
46
- registry.set(name, {
47
- factory: (c) => new ctor(...deps.map((d) => c.resolve(d))),
48
- lifetime,
49
- });
50
- },
51
-
52
- registerValue<T>(name: string, value: T) {
53
- // A value the container did not build is not the container's to dispose.
54
- registry.set(name, { factory: () => value, lifetime: 'singleton', instance: value });
55
- },
56
-
57
- resolve<T>(name: string): T {
58
- let entry = registry.get(name);
59
-
60
- // Not found locally — the parent holds it, and holds its instance too.
61
- if (!entry && parent && parent._getEntry(name)) {
62
- return parent.resolve<T>(name);
63
- }
64
-
65
- // Nobody holds it. A frond declared in `remotes` registers nothing here, so its
66
- // façade is fabricated by the fallback rather than found.
67
- if (!entry) {
68
- const made = container._getFallback()?.(name);
69
- if (made !== undefined) {
70
- registry.set(name, { factory: () => made, lifetime: 'singleton', instance: made });
71
- return made as T;
72
- }
73
- throw new Error(`[container] '${name}' is not registered${through(name)}`);
74
- }
75
-
76
- if (entry.instance !== undefined) return entry.instance as T;
77
-
78
- if (resolving.includes(name)) {
79
- throw new Error(`[container] dependency cycle: ${[...resolving, name].join(' → ')}`);
80
- }
81
-
82
- // Popped in a finally, because a constructor that throws leaves the name on the stack
83
- // otherwise and the next resolution of it reports a cycle that is not there.
84
- resolving.push(name);
85
- try {
86
- const value = entry.factory(container) as T;
87
- // The container disposes what it KEEPS: a transient is handed over and forgotten,
88
- // and its caller is the one who knows when it is done.
89
- if (entry.lifetime === 'singleton') {
90
- entry.instance = value;
91
- remember(value);
92
- }
93
-
94
- return value;
95
- } finally {
96
- resolving.pop();
97
- }
98
- },
99
-
100
- has(name: string): boolean {
101
- return registry.has(name) || (parent?.has(name) ?? false);
102
- },
103
-
104
- createScope(): Container {
105
- const child = createScope(container);
106
- children.push(child);
107
- return child;
108
- },
109
-
110
- async dispose(): Promise<void> {
111
- // Everything is told before anything throws, then the failures travel together.
112
- // Dropped from the parent first: a scope that closes itself leaves a reference the
113
- // parent would hold for the life of the process.
114
- parent?._forget(container);
115
- const failures: unknown[] = [];
116
- // A copy: closing a child splices it out of `children`, so walking the array
117
- // itself stepped over every second sibling.
118
- for (const child of [...children].reverse()) {
119
- try {
120
- await child.dispose();
121
- } catch (error) {
122
- failures.push(error);
123
- }
124
- }
125
- children.length = 0;
126
- for (const value of built.reverse()) {
127
- try {
128
- await (value as Disposable).dispose();
129
- } catch (error) {
130
- failures.push(error);
131
- }
132
- }
133
- built.length = 0;
134
- registry.clear();
135
- if (failures.length > 0) {
136
- throw new AggregateError(failures, '[container] one or more disposals failed');
137
- }
138
- },
139
-
140
- setFallback(resolve: (name: string) => unknown) {
141
- fallback = resolve;
142
- },
143
-
144
- _getEntry(name: string): Entry | undefined {
145
- return registry.get(name) ?? parent?._getEntry(name);
146
- },
147
-
148
- _resolving() {
149
- return resolving;
150
- },
151
-
152
- _forget(child: ScopeContainer) {
153
- const at = children.indexOf(child);
154
- if (at !== -1) children.splice(at, 1);
155
- },
156
-
157
- /** Set on the root, honoured from any scope — a scope inherits it by asking upward. */
158
- _getFallback() {
159
- return fallback ?? parent?._getFallback();
160
- },
161
- };
162
-
163
- return container;
164
- }
165
-
166
- export function createContainer(): Container {
167
- return createScope();
168
- }