@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,275 @@
1
+ /*! @fluixi/di v1.0.0-alpha.99 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __typeError = (msg) => {
8
+ throw TypeError(msg);
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
24
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
25
+ 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);
26
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
27
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
28
+
29
+ // src/testing.ts
30
+ var testing_exports = {};
31
+ __export(testing_exports, {
32
+ createTestInjector: () => createTestInjector,
33
+ resetInjection: () => resetInjection
34
+ });
35
+ module.exports = __toCommonJS(testing_exports);
36
+
37
+ // src/injector.ts
38
+ var import_signal2 = require("@fluixi/reactive/signal");
39
+
40
+ // src/errors.ts
41
+ var InjectionError = class extends Error {
42
+ /**
43
+ * @param message - What went wrong, with the path rendered into it.
44
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
45
+ */
46
+ constructor(message, path) {
47
+ super(message);
48
+ this.path = path;
49
+ this.name = "InjectionError";
50
+ }
51
+ };
52
+
53
+ // src/hierarchy.ts
54
+ var import_dom = require("@fluixi/dom");
55
+ var import_signal = require("@fluixi/reactive/signal");
56
+
57
+ // src/state.ts
58
+ function freshState() {
59
+ return {
60
+ platformProviders: [],
61
+ rootProviders: [],
62
+ platform: null,
63
+ clientRoot: null,
64
+ requestRoots: /* @__PURE__ */ new WeakMap(),
65
+ ownerInjectors: /* @__PURE__ */ new WeakMap(),
66
+ moduleScopes: /* @__PURE__ */ new WeakMap(),
67
+ overrides: /* @__PURE__ */ new Map(),
68
+ explicit: null,
69
+ path: []
70
+ };
71
+ }
72
+ var state = globalThis[/* @__PURE__ */ Symbol.for("fluixi.di")] ??= freshState();
73
+
74
+ // src/hierarchy.ts
75
+ function platformInjector() {
76
+ return state.platform ??= new Injector(null, "platform").register(state.platformProviders);
77
+ }
78
+
79
+ // src/token.ts
80
+ function injectionToken(name, options) {
81
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
82
+ }
83
+
84
+ // src/tokens.ts
85
+ var DestroyRef = injectionToken("DestroyRef");
86
+ var INJECTOR = injectionToken("Injector");
87
+
88
+ // src/injector.ts
89
+ var _records, _created, _hooks, _destroyed, _Injector_instances, home_fn, fromProvider_fn, build_fn, make_fn;
90
+ var Injector = class {
91
+ /**
92
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
93
+ * top of a chain.
94
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
95
+ */
96
+ constructor(parent = null, kind = "standalone") {
97
+ this.parent = parent;
98
+ this.kind = kind;
99
+ __privateAdd(this, _Injector_instances);
100
+ __privateAdd(this, _records, /* @__PURE__ */ new Map());
101
+ __privateAdd(this, _created, []);
102
+ __privateAdd(this, _hooks, []);
103
+ __privateAdd(this, _destroyed, false);
104
+ const self = this;
105
+ this.destroyRef = {
106
+ onDestroy(fn) {
107
+ __privateGet(self, _hooks).push(fn);
108
+ return () => {
109
+ const i = __privateGet(self, _hooks).indexOf(fn);
110
+ if (i >= 0) __privateGet(self, _hooks).splice(i, 1);
111
+ };
112
+ },
113
+ get destroyed() {
114
+ return __privateGet(self, _destroyed);
115
+ }
116
+ };
117
+ }
118
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
119
+ get destroyed() {
120
+ return __privateGet(this, _destroyed);
121
+ }
122
+ /**
123
+ * Add providers to this injector. A provider for a token already held replaces it, except
124
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
125
+ * is replaced by the override.
126
+ *
127
+ * @param providers - The providers to add, in order.
128
+ * @returns This injector, for chaining.
129
+ */
130
+ register(providers) {
131
+ for (const given of providers) {
132
+ const p = state.overrides.get(given.provide.id) ?? given;
133
+ if (p.multi) {
134
+ const rec = __privateGet(this, _records).get(p.provide.id);
135
+ if (rec?.multi) {
136
+ rec.providers.push(p);
137
+ rec.built = false;
138
+ } else __privateGet(this, _records).set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
139
+ } else {
140
+ __privateGet(this, _records).set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: void 0 });
141
+ }
142
+ }
143
+ return this;
144
+ }
145
+ get(token, options = {}) {
146
+ var _a, _b, _c;
147
+ if (__privateGet(this, _destroyed)) throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
148
+ if (token.id === INJECTOR.id) return this;
149
+ if (token.id === DestroyRef.id) return this.destroyRef;
150
+ let inj = options.skipSelf ? this.parent : this;
151
+ while (inj) {
152
+ const rec = __privateGet(inj, _records).get(token.id);
153
+ if (rec) return __privateMethod(_a = inj, _Injector_instances, build_fn).call(_a, token, rec);
154
+ if (options.self || options.host && inj.kind === "owner") break;
155
+ inj = inj.parent;
156
+ }
157
+ const override = state.overrides.get(token.id);
158
+ 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);
159
+ if (token.factory && !options.self && !options.host) {
160
+ 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 });
161
+ }
162
+ if (options.optional) return null;
163
+ const path = [...state.path, token.name];
164
+ throw new InjectionError(
165
+ `[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 }.`,
166
+ path
167
+ );
168
+ }
169
+ /**
170
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
171
+ * `onDestroy()` method of every service it built, latest built first, then forget its
172
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
173
+ * again does nothing.
174
+ *
175
+ * Component injectors are disposed when the component unmounts, request roots when the request
176
+ * ends; call it yourself only for injectors you created.
177
+ */
178
+ dispose() {
179
+ if (__privateGet(this, _destroyed)) return;
180
+ __privateSet(this, _destroyed, true);
181
+ const fail = (error) => console.error("[fluixi/di] a destroy hook threw:", error);
182
+ for (const hook of __privateGet(this, _hooks).splice(0).reverse()) {
183
+ try {
184
+ hook();
185
+ } catch (error) {
186
+ fail(error);
187
+ }
188
+ }
189
+ for (const value of __privateGet(this, _created).splice(0).reverse()) {
190
+ const service = value;
191
+ if (service && typeof service.onDestroy === "function") {
192
+ try {
193
+ service.onDestroy();
194
+ } catch (error) {
195
+ fail(error);
196
+ }
197
+ }
198
+ }
199
+ __privateGet(this, _records).clear();
200
+ }
201
+ };
202
+ _records = new WeakMap();
203
+ _created = new WeakMap();
204
+ _hooks = new WeakMap();
205
+ _destroyed = new WeakMap();
206
+ _Injector_instances = new WeakSet();
207
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
208
+ home_fn = function(scope) {
209
+ let inj = this;
210
+ if (scope === "platform") {
211
+ while (inj.parent) inj = inj.parent;
212
+ return inj.kind === "platform" ? inj : platformInjector();
213
+ }
214
+ let probe = this;
215
+ while (probe) {
216
+ if (probe.kind === "root") return probe;
217
+ probe = probe.parent;
218
+ }
219
+ while (inj.parent && inj.parent.kind !== "platform") inj = inj.parent;
220
+ return inj;
221
+ };
222
+ fromProvider_fn = function(token, provider) {
223
+ let rec = __privateGet(this, _records).get(token.id);
224
+ if (!rec) {
225
+ rec = { multi: false, provider, built: false, building: false, value: void 0 };
226
+ __privateGet(this, _records).set(token.id, rec);
227
+ }
228
+ return __privateMethod(this, _Injector_instances, build_fn).call(this, token, rec);
229
+ };
230
+ build_fn = function(token, rec) {
231
+ if (rec.built) return rec.value;
232
+ if (rec.building) {
233
+ const path = [...state.path, token.name];
234
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(" → ")}.`, path);
235
+ }
236
+ rec.building = true;
237
+ state.path.push(token.name);
238
+ const previous = state.explicit;
239
+ state.explicit = this;
240
+ try {
241
+ if (rec.multi) {
242
+ rec.value = rec.providers.map((p) => __privateMethod(this, _Injector_instances, make_fn).call(this, token, p));
243
+ } else {
244
+ const module2 = state.moduleScopes.get(rec.provider);
245
+ 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);
246
+ }
247
+ rec.built = true;
248
+ return rec.value;
249
+ } finally {
250
+ state.explicit = previous;
251
+ state.path.pop();
252
+ rec.building = false;
253
+ }
254
+ };
255
+ make_fn = function(token, p) {
256
+ if ("useValue" in p) return p.useValue;
257
+ if ("useExisting" in p) return this.get(p.useExisting);
258
+ const deps = (p.deps ?? []).map((d) => "token" in d ? this.get(d.token, d) : this.get(d));
259
+ const scope = (0, import_signal2.openReactiveScope)({ kind: "service", name: token.name, detail: "useClass" in p ? "useClass" : "useFactory" });
260
+ const value = (0, import_signal2.withReactiveScope)(scope, () => "useClass" in p ? new p.useClass(...deps) : p.useFactory(...deps));
261
+ __privateGet(this, _created).push(value);
262
+ return value;
263
+ };
264
+
265
+ // src/testing.ts
266
+ function createTestInjector(options = {}) {
267
+ for (const p of options.overrides ?? []) state.overrides.set(p.provide.id, p);
268
+ const platform = new Injector(null, "platform");
269
+ return new Injector(platform, "root").register(options.providers ?? []);
270
+ }
271
+ function resetInjection() {
272
+ state.platform?.dispose();
273
+ state.clientRoot?.dispose();
274
+ Object.assign(state, freshState());
275
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `@fluixi/di/testing`: injectors for tests. A separate entry point, so no app ships it.
3
+ *
4
+ * @module
5
+ */
6
+ import { Injector } from './injector.js';
7
+ import type { Provider } from './provider.js';
8
+ /** What {@link createTestInjector} is built with. */
9
+ export interface TestInjectorOptions {
10
+ /** The providers the test's root injector holds. */
11
+ providers?: ReadonlyArray<Provider>;
12
+ /**
13
+ * Replacements for tokens, wherever they would be provided: in any injector, and in place of a
14
+ * token's own factory. In effect until {@link resetInjection}.
15
+ */
16
+ overrides?: ReadonlyArray<Provider>;
17
+ }
18
+ /**
19
+ * Create an injector for a test: a root of its own, holding only `providers` (none of the app's
20
+ * root providers), under a fresh platform. Services that inject each other resolve as they
21
+ * would in the app; the ones listed in `overrides` resolve to the replacements.
22
+ *
23
+ * Pair it with {@link resetInjection} after each test.
24
+ *
25
+ * @param options - The providers and overrides; see {@link TestInjectorOptions}.
26
+ * @returns The test's root injector.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * afterEach(() => resetInjection());
31
+ *
32
+ * it('charges the cart total', async () => {
33
+ * const payments = { charge: vi.fn() };
34
+ * const injector = createTestInjector({
35
+ * providers: [{ provide: Checkout, useClass: CheckoutService }],
36
+ * overrides: [{ provide: Payments, useValue: payments }],
37
+ * });
38
+ * await injector.get(Checkout).pay();
39
+ * expect(payments.charge).toHaveBeenCalled();
40
+ * });
41
+ * ```
42
+ */
43
+ export declare function createTestInjector(options?: TestInjectorOptions): Injector;
44
+ /**
45
+ * Forget every provider, override and built service, disposing the platform and browser roots
46
+ * first so their services release what they hold. Leaves the state as a fresh process would have
47
+ * it: call it after each test.
48
+ */
49
+ export declare function resetInjection(): void;
50
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,QAAQ,CAI9E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAIrC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `@fluixi/di/testing`: injectors for tests. A separate entry point, so no app ships it.
3
+ *
4
+ * @module
5
+ */
6
+ import { Injector } from './injector.js';
7
+ import { freshState, state } from './state.js';
8
+ /**
9
+ * Create an injector for a test: a root of its own, holding only `providers` (none of the app's
10
+ * root providers), under a fresh platform. Services that inject each other resolve as they
11
+ * would in the app; the ones listed in `overrides` resolve to the replacements.
12
+ *
13
+ * Pair it with {@link resetInjection} after each test.
14
+ *
15
+ * @param options - The providers and overrides; see {@link TestInjectorOptions}.
16
+ * @returns The test's root injector.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * afterEach(() => resetInjection());
21
+ *
22
+ * it('charges the cart total', async () => {
23
+ * const payments = { charge: vi.fn() };
24
+ * const injector = createTestInjector({
25
+ * providers: [{ provide: Checkout, useClass: CheckoutService }],
26
+ * overrides: [{ provide: Payments, useValue: payments }],
27
+ * });
28
+ * await injector.get(Checkout).pay();
29
+ * expect(payments.charge).toHaveBeenCalled();
30
+ * });
31
+ * ```
32
+ */
33
+ export function createTestInjector(options = {}) {
34
+ for (const p of options.overrides ?? [])
35
+ state.overrides.set(p.provide.id, p);
36
+ const platform = new Injector(null, 'platform');
37
+ return new Injector(platform, 'root').register(options.providers ?? []);
38
+ }
39
+ /**
40
+ * Forget every provider, override and built service, disposing the platform and browser roots
41
+ * first so their services release what they hold. Leaves the state as a fresh process would have
42
+ * it: call it after each test.
43
+ */
44
+ export function resetInjection() {
45
+ state.platform?.dispose();
46
+ state.clientRoot?.dispose();
47
+ Object.assign(state, freshState());
48
+ }
@@ -0,0 +1,253 @@
1
+ /*! @fluixi/di v1.0.0-alpha.99 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
6
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
7
+ 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);
8
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
9
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
10
+
11
+ // src/injector.ts
12
+ import { openReactiveScope, withReactiveScope } from "@fluixi/reactive/signal";
13
+
14
+ // src/errors.ts
15
+ var InjectionError = class extends Error {
16
+ /**
17
+ * @param message - What went wrong, with the path rendered into it.
18
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
19
+ */
20
+ constructor(message, path) {
21
+ super(message);
22
+ this.path = path;
23
+ this.name = "InjectionError";
24
+ }
25
+ };
26
+
27
+ // src/hierarchy.ts
28
+ import { getRequestContext, onRequestEnd } from "@fluixi/dom";
29
+ import { getOwner } from "@fluixi/reactive/signal";
30
+
31
+ // src/state.ts
32
+ function freshState() {
33
+ return {
34
+ platformProviders: [],
35
+ rootProviders: [],
36
+ platform: null,
37
+ clientRoot: null,
38
+ requestRoots: /* @__PURE__ */ new WeakMap(),
39
+ ownerInjectors: /* @__PURE__ */ new WeakMap(),
40
+ moduleScopes: /* @__PURE__ */ new WeakMap(),
41
+ overrides: /* @__PURE__ */ new Map(),
42
+ explicit: null,
43
+ path: []
44
+ };
45
+ }
46
+ var state = globalThis[/* @__PURE__ */ Symbol.for("fluixi.di")] ??= freshState();
47
+
48
+ // src/hierarchy.ts
49
+ function platformInjector() {
50
+ return state.platform ??= new Injector(null, "platform").register(state.platformProviders);
51
+ }
52
+
53
+ // src/token.ts
54
+ function injectionToken(name, options) {
55
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
56
+ }
57
+
58
+ // src/tokens.ts
59
+ var DestroyRef = injectionToken("DestroyRef");
60
+ var INJECTOR = injectionToken("Injector");
61
+
62
+ // src/injector.ts
63
+ var _records, _created, _hooks, _destroyed, _Injector_instances, home_fn, fromProvider_fn, build_fn, make_fn;
64
+ var Injector = class {
65
+ /**
66
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
67
+ * top of a chain.
68
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
69
+ */
70
+ constructor(parent = null, kind = "standalone") {
71
+ this.parent = parent;
72
+ this.kind = kind;
73
+ __privateAdd(this, _Injector_instances);
74
+ __privateAdd(this, _records, /* @__PURE__ */ new Map());
75
+ __privateAdd(this, _created, []);
76
+ __privateAdd(this, _hooks, []);
77
+ __privateAdd(this, _destroyed, false);
78
+ const self = this;
79
+ this.destroyRef = {
80
+ onDestroy(fn) {
81
+ __privateGet(self, _hooks).push(fn);
82
+ return () => {
83
+ const i = __privateGet(self, _hooks).indexOf(fn);
84
+ if (i >= 0) __privateGet(self, _hooks).splice(i, 1);
85
+ };
86
+ },
87
+ get destroyed() {
88
+ return __privateGet(self, _destroyed);
89
+ }
90
+ };
91
+ }
92
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
93
+ get destroyed() {
94
+ return __privateGet(this, _destroyed);
95
+ }
96
+ /**
97
+ * Add providers to this injector. A provider for a token already held replaces it, except
98
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
99
+ * is replaced by the override.
100
+ *
101
+ * @param providers - The providers to add, in order.
102
+ * @returns This injector, for chaining.
103
+ */
104
+ register(providers) {
105
+ for (const given of providers) {
106
+ const p = state.overrides.get(given.provide.id) ?? given;
107
+ if (p.multi) {
108
+ const rec = __privateGet(this, _records).get(p.provide.id);
109
+ if (rec?.multi) {
110
+ rec.providers.push(p);
111
+ rec.built = false;
112
+ } else __privateGet(this, _records).set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
113
+ } else {
114
+ __privateGet(this, _records).set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: void 0 });
115
+ }
116
+ }
117
+ return this;
118
+ }
119
+ get(token, options = {}) {
120
+ var _a, _b, _c;
121
+ if (__privateGet(this, _destroyed)) throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
122
+ if (token.id === INJECTOR.id) return this;
123
+ if (token.id === DestroyRef.id) return this.destroyRef;
124
+ let inj = options.skipSelf ? this.parent : this;
125
+ while (inj) {
126
+ const rec = __privateGet(inj, _records).get(token.id);
127
+ if (rec) return __privateMethod(_a = inj, _Injector_instances, build_fn).call(_a, token, rec);
128
+ if (options.self || options.host && inj.kind === "owner") break;
129
+ inj = inj.parent;
130
+ }
131
+ const override = state.overrides.get(token.id);
132
+ 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);
133
+ if (token.factory && !options.self && !options.host) {
134
+ 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 });
135
+ }
136
+ if (options.optional) return null;
137
+ const path = [...state.path, token.name];
138
+ throw new InjectionError(
139
+ `[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 }.`,
140
+ path
141
+ );
142
+ }
143
+ /**
144
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
145
+ * `onDestroy()` method of every service it built, latest built first, then forget its
146
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
147
+ * again does nothing.
148
+ *
149
+ * Component injectors are disposed when the component unmounts, request roots when the request
150
+ * ends; call it yourself only for injectors you created.
151
+ */
152
+ dispose() {
153
+ if (__privateGet(this, _destroyed)) return;
154
+ __privateSet(this, _destroyed, true);
155
+ const fail = (error) => console.error("[fluixi/di] a destroy hook threw:", error);
156
+ for (const hook of __privateGet(this, _hooks).splice(0).reverse()) {
157
+ try {
158
+ hook();
159
+ } catch (error) {
160
+ fail(error);
161
+ }
162
+ }
163
+ for (const value of __privateGet(this, _created).splice(0).reverse()) {
164
+ const service = value;
165
+ if (service && typeof service.onDestroy === "function") {
166
+ try {
167
+ service.onDestroy();
168
+ } catch (error) {
169
+ fail(error);
170
+ }
171
+ }
172
+ }
173
+ __privateGet(this, _records).clear();
174
+ }
175
+ };
176
+ _records = new WeakMap();
177
+ _created = new WeakMap();
178
+ _hooks = new WeakMap();
179
+ _destroyed = new WeakMap();
180
+ _Injector_instances = new WeakSet();
181
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
182
+ home_fn = function(scope) {
183
+ let inj = this;
184
+ if (scope === "platform") {
185
+ while (inj.parent) inj = inj.parent;
186
+ return inj.kind === "platform" ? inj : platformInjector();
187
+ }
188
+ let probe = this;
189
+ while (probe) {
190
+ if (probe.kind === "root") return probe;
191
+ probe = probe.parent;
192
+ }
193
+ while (inj.parent && inj.parent.kind !== "platform") inj = inj.parent;
194
+ return inj;
195
+ };
196
+ fromProvider_fn = function(token, provider) {
197
+ let rec = __privateGet(this, _records).get(token.id);
198
+ if (!rec) {
199
+ rec = { multi: false, provider, built: false, building: false, value: void 0 };
200
+ __privateGet(this, _records).set(token.id, rec);
201
+ }
202
+ return __privateMethod(this, _Injector_instances, build_fn).call(this, token, rec);
203
+ };
204
+ build_fn = function(token, rec) {
205
+ if (rec.built) return rec.value;
206
+ if (rec.building) {
207
+ const path = [...state.path, token.name];
208
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(" → ")}.`, path);
209
+ }
210
+ rec.building = true;
211
+ state.path.push(token.name);
212
+ const previous = state.explicit;
213
+ state.explicit = this;
214
+ try {
215
+ if (rec.multi) {
216
+ rec.value = rec.providers.map((p) => __privateMethod(this, _Injector_instances, make_fn).call(this, token, p));
217
+ } else {
218
+ const module = state.moduleScopes.get(rec.provider);
219
+ rec.value = module ? withReactiveScope(module, () => __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider)) : __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider);
220
+ }
221
+ rec.built = true;
222
+ return rec.value;
223
+ } finally {
224
+ state.explicit = previous;
225
+ state.path.pop();
226
+ rec.building = false;
227
+ }
228
+ };
229
+ make_fn = function(token, p) {
230
+ if ("useValue" in p) return p.useValue;
231
+ if ("useExisting" in p) return this.get(p.useExisting);
232
+ const deps = (p.deps ?? []).map((d) => "token" in d ? this.get(d.token, d) : this.get(d));
233
+ const scope = openReactiveScope({ kind: "service", name: token.name, detail: "useClass" in p ? "useClass" : "useFactory" });
234
+ const value = withReactiveScope(scope, () => "useClass" in p ? new p.useClass(...deps) : p.useFactory(...deps));
235
+ __privateGet(this, _created).push(value);
236
+ return value;
237
+ };
238
+
239
+ // src/testing.ts
240
+ function createTestInjector(options = {}) {
241
+ for (const p of options.overrides ?? []) state.overrides.set(p.provide.id, p);
242
+ const platform = new Injector(null, "platform");
243
+ return new Injector(platform, "root").register(options.providers ?? []);
244
+ }
245
+ function resetInjection() {
246
+ state.platform?.dispose();
247
+ state.clientRoot?.dispose();
248
+ Object.assign(state, freshState());
249
+ }
250
+ export {
251
+ createTestInjector,
252
+ resetInjection
253
+ };
package/dist/token.cjs ADDED
@@ -0,0 +1,28 @@
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 __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/token.ts
21
+ var token_exports = {};
22
+ __export(token_exports, {
23
+ injectionToken: () => injectionToken
24
+ });
25
+ module.exports = __toCommonJS(token_exports);
26
+ function injectionToken(name, options) {
27
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
28
+ }