@fluixi/di 0.0.0-stage → 1.0.0-alpha.99

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -2
  3. package/dist/errors.cjs +36 -0
  4. package/dist/errors.d.ts +31 -0
  5. package/dist/errors.d.ts.map +1 -0
  6. package/dist/errors.js +34 -0
  7. package/dist/errors.mjs +15 -0
  8. package/dist/hierarchy.cjs +291 -0
  9. package/dist/hierarchy.d.ts +11 -0
  10. package/dist/hierarchy.d.ts.map +1 -0
  11. package/dist/hierarchy.js +47 -0
  12. package/dist/hierarchy.mjs +271 -0
  13. package/dist/index.cjs +374 -0
  14. package/dist/index.d.ts +17 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +15 -0
  17. package/dist/index.mjs +352 -0
  18. package/dist/initializers.cjs +289 -0
  19. package/dist/initializers.d.ts +36 -0
  20. package/dist/initializers.d.ts.map +1 -0
  21. package/dist/initializers.js +45 -0
  22. package/dist/initializers.mjs +267 -0
  23. package/dist/inject.cjs +352 -0
  24. package/dist/inject.d.ts +151 -0
  25. package/dist/inject.d.ts.map +1 -0
  26. package/dist/inject.js +174 -0
  27. package/dist/inject.mjs +332 -0
  28. package/dist/injector.cjs +259 -0
  29. package/dist/injector.d.ts +73 -0
  30. package/dist/injector.d.ts.map +1 -0
  31. package/dist/injector.js +213 -0
  32. package/dist/injector.mjs +239 -0
  33. package/dist/provider.cjs +18 -0
  34. package/dist/provider.d.ts +97 -0
  35. package/dist/provider.d.ts.map +1 -0
  36. package/dist/provider.js +1 -0
  37. package/dist/provider.mjs +0 -0
  38. package/dist/state.cjs +45 -0
  39. package/dist/state.d.ts +35 -0
  40. package/dist/state.d.ts.map +1 -0
  41. package/dist/state.js +26 -0
  42. package/dist/state.mjs +24 -0
  43. package/dist/testing.cjs +275 -0
  44. package/dist/testing.d.ts +50 -0
  45. package/dist/testing.d.ts.map +1 -0
  46. package/dist/testing.js +48 -0
  47. package/dist/testing.mjs +253 -0
  48. package/dist/token.cjs +28 -0
  49. package/dist/token.d.ts +67 -0
  50. package/dist/token.d.ts.map +1 -0
  51. package/dist/token.js +33 -0
  52. package/dist/token.mjs +7 -0
  53. package/dist/tokens.cjs +35 -0
  54. package/dist/tokens.d.ts +53 -0
  55. package/dist/tokens.d.ts.map +1 -0
  56. package/dist/tokens.js +26 -0
  57. package/dist/tokens.mjs +12 -0
  58. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  59. package/package.json +55 -3
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
23
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
26
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
+
28
+ // src/injector.ts
29
+ var injector_exports = {};
30
+ __export(injector_exports, {
31
+ Injector: () => Injector
32
+ });
33
+ module.exports = __toCommonJS(injector_exports);
34
+ var import_signal2 = require("@fluixi/reactive/signal");
35
+
36
+ // src/errors.ts
37
+ var InjectionError = class extends Error {
38
+ /**
39
+ * @param message - What went wrong, with the path rendered into it.
40
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
41
+ */
42
+ constructor(message, path) {
43
+ super(message);
44
+ this.path = path;
45
+ this.name = "InjectionError";
46
+ }
47
+ };
48
+
49
+ // src/hierarchy.ts
50
+ var import_dom = require("@fluixi/dom");
51
+ var import_signal = require("@fluixi/reactive/signal");
52
+
53
+ // src/state.ts
54
+ function freshState() {
55
+ return {
56
+ platformProviders: [],
57
+ rootProviders: [],
58
+ platform: null,
59
+ clientRoot: null,
60
+ requestRoots: /* @__PURE__ */ new WeakMap(),
61
+ ownerInjectors: /* @__PURE__ */ new WeakMap(),
62
+ moduleScopes: /* @__PURE__ */ new WeakMap(),
63
+ overrides: /* @__PURE__ */ new Map(),
64
+ explicit: null,
65
+ path: []
66
+ };
67
+ }
68
+ var state = globalThis[/* @__PURE__ */ Symbol.for("fluixi.di")] ??= freshState();
69
+
70
+ // src/hierarchy.ts
71
+ function platformInjector() {
72
+ return state.platform ??= new Injector(null, "platform").register(state.platformProviders);
73
+ }
74
+
75
+ // src/token.ts
76
+ function injectionToken(name, options) {
77
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
78
+ }
79
+
80
+ // src/tokens.ts
81
+ var DestroyRef = injectionToken("DestroyRef");
82
+ var INJECTOR = injectionToken("Injector");
83
+
84
+ // src/injector.ts
85
+ var _records, _created, _hooks, _destroyed, _Injector_instances, home_fn, fromProvider_fn, build_fn, make_fn;
86
+ var Injector = class {
87
+ /**
88
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
89
+ * top of a chain.
90
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
91
+ */
92
+ constructor(parent = null, kind = "standalone") {
93
+ this.parent = parent;
94
+ this.kind = kind;
95
+ __privateAdd(this, _Injector_instances);
96
+ __privateAdd(this, _records, /* @__PURE__ */ new Map());
97
+ __privateAdd(this, _created, []);
98
+ __privateAdd(this, _hooks, []);
99
+ __privateAdd(this, _destroyed, false);
100
+ const self = this;
101
+ this.destroyRef = {
102
+ onDestroy(fn) {
103
+ __privateGet(self, _hooks).push(fn);
104
+ return () => {
105
+ const i = __privateGet(self, _hooks).indexOf(fn);
106
+ if (i >= 0) __privateGet(self, _hooks).splice(i, 1);
107
+ };
108
+ },
109
+ get destroyed() {
110
+ return __privateGet(self, _destroyed);
111
+ }
112
+ };
113
+ }
114
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
115
+ get destroyed() {
116
+ return __privateGet(this, _destroyed);
117
+ }
118
+ /**
119
+ * Add providers to this injector. A provider for a token already held replaces it, except
120
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
121
+ * is replaced by the override.
122
+ *
123
+ * @param providers - The providers to add, in order.
124
+ * @returns This injector, for chaining.
125
+ */
126
+ register(providers) {
127
+ for (const given of providers) {
128
+ const p = state.overrides.get(given.provide.id) ?? given;
129
+ if (p.multi) {
130
+ const rec = __privateGet(this, _records).get(p.provide.id);
131
+ if (rec?.multi) {
132
+ rec.providers.push(p);
133
+ rec.built = false;
134
+ } else __privateGet(this, _records).set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
135
+ } else {
136
+ __privateGet(this, _records).set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: void 0 });
137
+ }
138
+ }
139
+ return this;
140
+ }
141
+ get(token, options = {}) {
142
+ var _a, _b, _c;
143
+ if (__privateGet(this, _destroyed)) throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
144
+ if (token.id === INJECTOR.id) return this;
145
+ if (token.id === DestroyRef.id) return this.destroyRef;
146
+ let inj = options.skipSelf ? this.parent : this;
147
+ while (inj) {
148
+ const rec = __privateGet(inj, _records).get(token.id);
149
+ if (rec) return __privateMethod(_a = inj, _Injector_instances, build_fn).call(_a, token, rec);
150
+ if (options.self || options.host && inj.kind === "owner") break;
151
+ inj = inj.parent;
152
+ }
153
+ const override = state.overrides.get(token.id);
154
+ if (override && !options.self) return __privateMethod(_b = __privateMethod(this, _Injector_instances, home_fn).call(this, token.scope), _Injector_instances, fromProvider_fn).call(_b, token, override);
155
+ if (token.factory && !options.self && !options.host) {
156
+ return __privateMethod(_c = __privateMethod(this, _Injector_instances, home_fn).call(this, token.scope), _Injector_instances, fromProvider_fn).call(_c, token, { provide: token, useFactory: token.factory });
157
+ }
158
+ if (options.optional) return null;
159
+ const path = [...state.path, token.name];
160
+ throw new InjectionError(
161
+ `[fluixi/di] No provider for "${token.name}" (${path.join(" → ")}). Provide it in an app module, with provide() or provideRoot(), or give the token a { factory }.`,
162
+ path
163
+ );
164
+ }
165
+ /**
166
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
167
+ * `onDestroy()` method of every service it built, latest built first, then forget its
168
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
169
+ * again does nothing.
170
+ *
171
+ * Component injectors are disposed when the component unmounts, request roots when the request
172
+ * ends; call it yourself only for injectors you created.
173
+ */
174
+ dispose() {
175
+ if (__privateGet(this, _destroyed)) return;
176
+ __privateSet(this, _destroyed, true);
177
+ const fail = (error) => console.error("[fluixi/di] a destroy hook threw:", error);
178
+ for (const hook of __privateGet(this, _hooks).splice(0).reverse()) {
179
+ try {
180
+ hook();
181
+ } catch (error) {
182
+ fail(error);
183
+ }
184
+ }
185
+ for (const value of __privateGet(this, _created).splice(0).reverse()) {
186
+ const service = value;
187
+ if (service && typeof service.onDestroy === "function") {
188
+ try {
189
+ service.onDestroy();
190
+ } catch (error) {
191
+ fail(error);
192
+ }
193
+ }
194
+ }
195
+ __privateGet(this, _records).clear();
196
+ }
197
+ };
198
+ _records = new WeakMap();
199
+ _created = new WeakMap();
200
+ _hooks = new WeakMap();
201
+ _destroyed = new WeakMap();
202
+ _Injector_instances = new WeakSet();
203
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
204
+ home_fn = function(scope) {
205
+ let inj = this;
206
+ if (scope === "platform") {
207
+ while (inj.parent) inj = inj.parent;
208
+ return inj.kind === "platform" ? inj : platformInjector();
209
+ }
210
+ let probe = this;
211
+ while (probe) {
212
+ if (probe.kind === "root") return probe;
213
+ probe = probe.parent;
214
+ }
215
+ while (inj.parent && inj.parent.kind !== "platform") inj = inj.parent;
216
+ return inj;
217
+ };
218
+ fromProvider_fn = function(token, provider) {
219
+ let rec = __privateGet(this, _records).get(token.id);
220
+ if (!rec) {
221
+ rec = { multi: false, provider, built: false, building: false, value: void 0 };
222
+ __privateGet(this, _records).set(token.id, rec);
223
+ }
224
+ return __privateMethod(this, _Injector_instances, build_fn).call(this, token, rec);
225
+ };
226
+ build_fn = function(token, rec) {
227
+ if (rec.built) return rec.value;
228
+ if (rec.building) {
229
+ const path = [...state.path, token.name];
230
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(" → ")}.`, path);
231
+ }
232
+ rec.building = true;
233
+ state.path.push(token.name);
234
+ const previous = state.explicit;
235
+ state.explicit = this;
236
+ try {
237
+ if (rec.multi) {
238
+ rec.value = rec.providers.map((p) => __privateMethod(this, _Injector_instances, make_fn).call(this, token, p));
239
+ } else {
240
+ const module2 = state.moduleScopes.get(rec.provider);
241
+ rec.value = module2 ? (0, import_signal2.withReactiveScope)(module2, () => __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider)) : __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider);
242
+ }
243
+ rec.built = true;
244
+ return rec.value;
245
+ } finally {
246
+ state.explicit = previous;
247
+ state.path.pop();
248
+ rec.building = false;
249
+ }
250
+ };
251
+ make_fn = function(token, p) {
252
+ if ("useValue" in p) return p.useValue;
253
+ if ("useExisting" in p) return this.get(p.useExisting);
254
+ const deps = (p.deps ?? []).map((d) => "token" in d ? this.get(d.token, d) : this.get(d));
255
+ const scope = (0, import_signal2.openReactiveScope)({ kind: "service", name: token.name, detail: "useClass" in p ? "useClass" : "useFactory" });
256
+ const value = (0, import_signal2.withReactiveScope)(scope, () => "useClass" in p ? new p.useClass(...deps) : p.useFactory(...deps));
257
+ __privateGet(this, _created).push(value);
258
+ return value;
259
+ };
@@ -0,0 +1,73 @@
1
+ import type { InjectOptions, Provider } from './provider.js';
2
+ import type { Token } from './token.js';
3
+ import { DestroyRef } from './tokens.js';
4
+ /**
5
+ * Where an injector sits in the hierarchy.
6
+ *
7
+ * - `'platform'`: one per process, the top of every chain.
8
+ * - `'root'`: one per request on the server, one per app in the browser, below the platform.
9
+ * - `'owner'`: created by `provide()` for a component, below its nearest ancestor's injector or
10
+ * the root; the boundary `{ host: true }` stops at.
11
+ * - `'standalone'`: created by `createInjector()` or for a test.
12
+ */
13
+ export type InjectorKind = 'platform' | 'root' | 'owner' | 'standalone';
14
+ /**
15
+ * Holds providers and resolves tokens: from itself, then up its parents, then from the token's
16
+ * own factory. Each value is built once per injector, on first request, and released when the
17
+ * injector is disposed.
18
+ *
19
+ * Code rarely creates injectors: `provide()`, `provideRoot()` and `providePlatform()` place
20
+ * providers in the hierarchy, and `inject()` resolves from the current context. An `Injector` is
21
+ * handled directly to resolve outside a context (`get`), to scope work to it
22
+ * (`runInInjectionContext`), or in tests.
23
+ */
24
+ export declare class Injector {
25
+ #private;
26
+ readonly parent: Injector | null;
27
+ readonly kind: InjectorKind;
28
+ /** The lifecycle of this injector, also answered by `inject(DestroyRef)` in its context. */
29
+ readonly destroyRef: DestroyRef;
30
+ /**
31
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
32
+ * top of a chain.
33
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
34
+ */
35
+ constructor(parent?: Injector | null, kind?: InjectorKind);
36
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
37
+ get destroyed(): boolean;
38
+ /**
39
+ * Add providers to this injector. A provider for a token already held replaces it, except
40
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
41
+ * is replaced by the override.
42
+ *
43
+ * @param providers - The providers to add, in order.
44
+ * @returns This injector, for chaining.
45
+ */
46
+ register(providers: ReadonlyArray<Provider>): this;
47
+ /**
48
+ * Resolve `token`: from this injector, then up its parents, then from the token's own factory
49
+ * (built in its scope). The value is built on first request and reused after. `INJECTOR` and
50
+ * `DestroyRef` resolve to this injector and its lifecycle.
51
+ *
52
+ * @param token - What to resolve.
53
+ * @param options - Where to look and what to do when nothing is found; see {@link InjectOptions}.
54
+ * @returns The value, or `null` when `optional` is set and nothing provides the token.
55
+ * @throws {@link InjectionError} When nothing provides the token, on a dependency cycle, or when
56
+ * this injector is disposed.
57
+ */
58
+ get<T>(token: Token<T>, options: InjectOptions & {
59
+ optional: true;
60
+ }): T | null;
61
+ get<T>(token: Token<T>, options?: InjectOptions): T;
62
+ /**
63
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
64
+ * `onDestroy()` method of every service it built, latest built first, then forget its
65
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
66
+ * again does nothing.
67
+ *
68
+ * Component injectors are disposed when the component unmounts, request roots when the request
69
+ * ends; call it yourself only for injectors you created.
70
+ */
71
+ dispose(): void;
72
+ }
73
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,KAAK,EAAS,KAAK,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAY,MAAM,aAAa,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,CAAC;AAoBxE;;;;;;;;;GASG;AACH,qBAAa,QAAQ;;IAejB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,IAAI;IAChC,QAAQ,CAAC,IAAI,EAAE,YAAY;IAV7B,4FAA4F;IAC5F,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC;;;;OAIG;gBAEQ,MAAM,GAAE,QAAQ,GAAG,IAAW,EAC9B,IAAI,GAAE,YAA2B;IAiB5C,yFAAyF;IACzF,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,IAAI;IAgBlD;;;;;;;;;;OAUG;IACH,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC,GAAG,IAAI;IAC9E,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,CAAC;IA0FnD;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;CAuBhB"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * An injector: the providers it holds, how it resolves a token (up its parents, then the token's
3
+ * own factory), how it builds a service, and how it releases what it built.
4
+ *
5
+ * @module
6
+ */
7
+ import { openReactiveScope, withReactiveScope } from '@fluixi/reactive/signal';
8
+ import { InjectionError } from './errors.js';
9
+ import { platformInjector } from './hierarchy.js';
10
+ import { state } from './state.js';
11
+ import { DestroyRef, INJECTOR } from './tokens.js';
12
+ /**
13
+ * Holds providers and resolves tokens: from itself, then up its parents, then from the token's
14
+ * own factory. Each value is built once per injector, on first request, and released when the
15
+ * injector is disposed.
16
+ *
17
+ * Code rarely creates injectors: `provide()`, `provideRoot()` and `providePlatform()` place
18
+ * providers in the hierarchy, and `inject()` resolves from the current context. An `Injector` is
19
+ * handled directly to resolve outside a context (`get`), to scope work to it
20
+ * (`runInInjectionContext`), or in tests.
21
+ */
22
+ export class Injector {
23
+ parent;
24
+ kind;
25
+ #records = new Map();
26
+ #created = [];
27
+ #hooks = [];
28
+ #destroyed = false;
29
+ /** The lifecycle of this injector, also answered by `inject(DestroyRef)` in its context. */
30
+ destroyRef;
31
+ /**
32
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
33
+ * top of a chain.
34
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
35
+ */
36
+ constructor(parent = null, kind = 'standalone') {
37
+ this.parent = parent;
38
+ this.kind = kind;
39
+ const self = this;
40
+ this.destroyRef = {
41
+ onDestroy(fn) {
42
+ self.#hooks.push(fn);
43
+ return () => {
44
+ const i = self.#hooks.indexOf(fn);
45
+ if (i >= 0)
46
+ self.#hooks.splice(i, 1);
47
+ };
48
+ },
49
+ get destroyed() {
50
+ return self.#destroyed;
51
+ },
52
+ };
53
+ }
54
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
55
+ get destroyed() {
56
+ return this.#destroyed;
57
+ }
58
+ /**
59
+ * Add providers to this injector. A provider for a token already held replaces it, except
60
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
61
+ * is replaced by the override.
62
+ *
63
+ * @param providers - The providers to add, in order.
64
+ * @returns This injector, for chaining.
65
+ */
66
+ register(providers) {
67
+ for (const given of providers) {
68
+ const p = state.overrides.get(given.provide.id) ?? given;
69
+ if (p.multi) {
70
+ const rec = this.#records.get(p.provide.id);
71
+ if (rec?.multi) {
72
+ rec.providers.push(p);
73
+ rec.built = false;
74
+ }
75
+ else
76
+ this.#records.set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
77
+ }
78
+ else {
79
+ this.#records.set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: undefined });
80
+ }
81
+ }
82
+ return this;
83
+ }
84
+ get(token, options = {}) {
85
+ if (this.#destroyed)
86
+ throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
87
+ if (token.id === INJECTOR.id)
88
+ return this;
89
+ if (token.id === DestroyRef.id)
90
+ return this.destroyRef;
91
+ let inj = options.skipSelf ? this.parent : this;
92
+ while (inj) {
93
+ const rec = inj.#records.get(token.id);
94
+ if (rec)
95
+ return inj.#build(token, rec);
96
+ if (options.self || (options.host && inj.kind === 'owner'))
97
+ break;
98
+ inj = inj.parent;
99
+ }
100
+ const override = state.overrides.get(token.id);
101
+ if (override && !options.self)
102
+ return this.#home(token.scope).#fromProvider(token, override);
103
+ if (token.factory && !options.self && !options.host) {
104
+ return this.#home(token.scope).#fromProvider(token, { provide: token, useFactory: token.factory });
105
+ }
106
+ if (options.optional)
107
+ return null;
108
+ const path = [...state.path, token.name];
109
+ throw new InjectionError(`[fluixi/di] No provider for "${token.name}" (${path.join(' → ')}). Provide it in an app module, ` +
110
+ `with provide() or provideRoot(), or give the token a { factory }.`, path);
111
+ }
112
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
113
+ #home(scope) {
114
+ let inj = this;
115
+ if (scope === 'platform') {
116
+ while (inj.parent)
117
+ inj = inj.parent;
118
+ return inj.kind === 'platform' ? inj : platformInjector();
119
+ }
120
+ let probe = this;
121
+ while (probe) {
122
+ if (probe.kind === 'root')
123
+ return probe;
124
+ probe = probe.parent;
125
+ }
126
+ while (inj.parent && inj.parent.kind !== 'platform')
127
+ inj = inj.parent;
128
+ return inj;
129
+ }
130
+ #fromProvider(token, provider) {
131
+ let rec = this.#records.get(token.id);
132
+ if (!rec) {
133
+ rec = { multi: false, provider, built: false, building: false, value: undefined };
134
+ this.#records.set(token.id, rec);
135
+ }
136
+ return this.#build(token, rec);
137
+ }
138
+ #build(token, rec) {
139
+ if (rec.built)
140
+ return rec.value;
141
+ if (rec.building) {
142
+ const path = [...state.path, token.name];
143
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(' → ')}.`, path);
144
+ }
145
+ rec.building = true;
146
+ state.path.push(token.name);
147
+ const previous = state.explicit;
148
+ // What the service injects resolves from where it is provided, as in Angular.
149
+ state.explicit = this;
150
+ try {
151
+ if (rec.multi) {
152
+ rec.value = rec.providers.map((p) => this.#make(token, p));
153
+ }
154
+ else {
155
+ const module = state.moduleScopes.get(rec.provider);
156
+ rec.value = module ? withReactiveScope(module, () => this.#make(token, rec.provider)) : this.#make(token, rec.provider);
157
+ }
158
+ rec.built = true;
159
+ return rec.value;
160
+ }
161
+ finally {
162
+ state.explicit = previous;
163
+ state.path.pop();
164
+ rec.building = false;
165
+ }
166
+ }
167
+ #make(token, p) {
168
+ if ('useValue' in p)
169
+ return p.useValue;
170
+ if ('useExisting' in p)
171
+ return this.get(p.useExisting);
172
+ const deps = (p.deps ?? []).map((d) => ('token' in d ? this.get(d.token, d) : this.get(d)));
173
+ const scope = openReactiveScope({ kind: 'service', name: token.name, detail: 'useClass' in p ? 'useClass' : 'useFactory' });
174
+ const value = withReactiveScope(scope, () => ('useClass' in p ? new p.useClass(...deps) : p.useFactory(...deps)));
175
+ this.#created.push(value);
176
+ return value;
177
+ }
178
+ /**
179
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
180
+ * `onDestroy()` method of every service it built, latest built first, then forget its
181
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
182
+ * again does nothing.
183
+ *
184
+ * Component injectors are disposed when the component unmounts, request roots when the request
185
+ * ends; call it yourself only for injectors you created.
186
+ */
187
+ dispose() {
188
+ if (this.#destroyed)
189
+ return;
190
+ this.#destroyed = true;
191
+ const fail = (error) => console.error('[fluixi/di] a destroy hook threw:', error);
192
+ for (const hook of this.#hooks.splice(0).reverse()) {
193
+ try {
194
+ hook();
195
+ }
196
+ catch (error) {
197
+ fail(error);
198
+ }
199
+ }
200
+ for (const value of this.#created.splice(0).reverse()) {
201
+ const service = value;
202
+ if (service && typeof service.onDestroy === 'function') {
203
+ try {
204
+ service.onDestroy();
205
+ }
206
+ catch (error) {
207
+ fail(error);
208
+ }
209
+ }
210
+ }
211
+ this.#records.clear();
212
+ }
213
+ }