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

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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ibrahima Touré
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,44 @@
1
- # Temporary Holding Version
1
+ # @fluixi/di
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Dependency injection for Fluixi: typed tokens, hierarchical injectors on the reactive owner tree,
4
+ one root injector per request on the server, multi providers, resolution options, lifecycles and
5
+ test overrides.
6
+
7
+ ```sh
8
+ pnpm add @fluixi/di
9
+ ```
10
+
11
+ ```ts
12
+ import { injectionToken, provideRoot, inject } from '@fluixi/di';
13
+
14
+ const Api = injectionToken<ApiClient>('Api');
15
+ provideRoot([{ provide: Api, useFactory: () => new ApiClient(env.API_URL) }]);
16
+
17
+ function Tickets() {
18
+ const api = inject(Api);
19
+ }
20
+ ```
21
+
22
+ Packages import `@fluixi/di` and declare it as a peer dependency. Apps built with
23
+ `@fluixi/start` may import `@fluixi/start/di`, which re-exports this package.
24
+
25
+ | Export | Purpose |
26
+ |---|---|
27
+ | `injectionToken` | a typed token, optionally self-providing (`factory`, `scope`) |
28
+ | `inject` | resolve from the current context, with `optional`, `self`, `skipSelf`, `host` |
29
+ | `provide` | services for a component and its descendants |
30
+ | `provideRoot` | services for the app: per request on the server, per page in the browser |
31
+ | `providePlatform` | services for the whole process |
32
+ | `provideAppInitializer`, `runAppInitializers` | work that finishes before the first render |
33
+ | `DestroyRef`, `INJECTOR` | an injector's lifecycle, and the injector itself |
34
+ | `runInInjectionContext`, `isInInjectionContext`, `assertInInjectionContext` | the injection context |
35
+ | `createInjector`, `Injector` | injectors of your own |
36
+ | `InjectionError` | thrown with the full resolution path |
37
+
38
+ Tests import `createTestInjector` and `resetInjection` from `@fluixi/di/testing`.
39
+
40
+ Documentation: [Dependency injection](https://docs.fluixi.com/v1/en/dependency-injection).
41
+
42
+ ## License
43
+
44
+ MIT
@@ -0,0 +1,36 @@
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/errors.ts
21
+ var errors_exports = {};
22
+ __export(errors_exports, {
23
+ InjectionError: () => InjectionError
24
+ });
25
+ module.exports = __toCommonJS(errors_exports);
26
+ var InjectionError = class extends Error {
27
+ /**
28
+ * @param message - What went wrong, with the path rendered into it.
29
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
30
+ */
31
+ constructor(message, path) {
32
+ super(message);
33
+ this.path = path;
34
+ this.name = "InjectionError";
35
+ }
36
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The error a resolution throws.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Thrown when a token cannot be resolved: nothing provides it (and it has no factory), its
8
+ * providers depend on each other in a cycle, or it is asked of a disposed injector.
9
+ *
10
+ * The message names the whole resolution path, outermost first, so the provider that is
11
+ * missing can be told apart from the service that needed it: `Checkout → Payments → ApiKey`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * try {
16
+ * inject(Checkout);
17
+ * } catch (error) {
18
+ * if (error instanceof InjectionError) console.error(error.path); // ['Checkout', 'Payments', 'ApiKey']
19
+ * throw error;
20
+ * }
21
+ * ```
22
+ */
23
+ export declare class InjectionError extends Error {
24
+ readonly path: readonly string[];
25
+ /**
26
+ * @param message - What went wrong, with the path rendered into it.
27
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
28
+ */
29
+ constructor(message: string, path: readonly string[]);
30
+ }
31
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,cAAe,SAAQ,KAAK;IAOrC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE;IANlC;;;OAGG;gBAED,OAAO,EAAE,MAAM,EACN,IAAI,EAAE,SAAS,MAAM,EAAE;CAKnC"}
package/dist/errors.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The error a resolution throws.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Thrown when a token cannot be resolved: nothing provides it (and it has no factory), its
8
+ * providers depend on each other in a cycle, or it is asked of a disposed injector.
9
+ *
10
+ * The message names the whole resolution path, outermost first, so the provider that is
11
+ * missing can be told apart from the service that needed it: `Checkout → Payments → ApiKey`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * try {
16
+ * inject(Checkout);
17
+ * } catch (error) {
18
+ * if (error instanceof InjectionError) console.error(error.path); // ['Checkout', 'Payments', 'ApiKey']
19
+ * throw error;
20
+ * }
21
+ * ```
22
+ */
23
+ export class InjectionError extends Error {
24
+ path;
25
+ /**
26
+ * @param message - What went wrong, with the path rendered into it.
27
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
28
+ */
29
+ constructor(message, path) {
30
+ super(message);
31
+ this.path = path;
32
+ this.name = 'InjectionError';
33
+ }
34
+ }
@@ -0,0 +1,15 @@
1
+ // src/errors.ts
2
+ var InjectionError = class extends Error {
3
+ /**
4
+ * @param message - What went wrong, with the path rendered into it.
5
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
6
+ */
7
+ constructor(message, path) {
8
+ super(message);
9
+ this.path = path;
10
+ this.name = "InjectionError";
11
+ }
12
+ };
13
+ export {
14
+ InjectionError
15
+ };
@@ -0,0 +1,291 @@
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/hierarchy.ts
29
+ var hierarchy_exports = {};
30
+ __export(hierarchy_exports, {
31
+ currentInjector: () => currentInjector,
32
+ nearestOwnerInjector: () => nearestOwnerInjector,
33
+ platformInjector: () => platformInjector,
34
+ rootInjector: () => rootInjector
35
+ });
36
+ module.exports = __toCommonJS(hierarchy_exports);
37
+ var import_dom = require("@fluixi/dom");
38
+ var import_signal2 = require("@fluixi/reactive/signal");
39
+
40
+ // src/injector.ts
41
+ var import_signal = require("@fluixi/reactive/signal");
42
+
43
+ // src/errors.ts
44
+ var InjectionError = class extends Error {
45
+ /**
46
+ * @param message - What went wrong, with the path rendered into it.
47
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
48
+ */
49
+ constructor(message, path) {
50
+ super(message);
51
+ this.path = path;
52
+ this.name = "InjectionError";
53
+ }
54
+ };
55
+
56
+ // src/state.ts
57
+ function freshState() {
58
+ return {
59
+ platformProviders: [],
60
+ rootProviders: [],
61
+ platform: null,
62
+ clientRoot: null,
63
+ requestRoots: /* @__PURE__ */ new WeakMap(),
64
+ ownerInjectors: /* @__PURE__ */ new WeakMap(),
65
+ moduleScopes: /* @__PURE__ */ new WeakMap(),
66
+ overrides: /* @__PURE__ */ new Map(),
67
+ explicit: null,
68
+ path: []
69
+ };
70
+ }
71
+ var state = globalThis[/* @__PURE__ */ Symbol.for("fluixi.di")] ??= freshState();
72
+
73
+ // src/token.ts
74
+ function injectionToken(name, options) {
75
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
76
+ }
77
+
78
+ // src/tokens.ts
79
+ var DestroyRef = injectionToken("DestroyRef");
80
+ var INJECTOR = injectionToken("Injector");
81
+
82
+ // src/injector.ts
83
+ var _records, _created, _hooks, _destroyed, _Injector_instances, home_fn, fromProvider_fn, build_fn, make_fn;
84
+ var Injector = class {
85
+ /**
86
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
87
+ * top of a chain.
88
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
89
+ */
90
+ constructor(parent = null, kind = "standalone") {
91
+ this.parent = parent;
92
+ this.kind = kind;
93
+ __privateAdd(this, _Injector_instances);
94
+ __privateAdd(this, _records, /* @__PURE__ */ new Map());
95
+ __privateAdd(this, _created, []);
96
+ __privateAdd(this, _hooks, []);
97
+ __privateAdd(this, _destroyed, false);
98
+ const self = this;
99
+ this.destroyRef = {
100
+ onDestroy(fn) {
101
+ __privateGet(self, _hooks).push(fn);
102
+ return () => {
103
+ const i = __privateGet(self, _hooks).indexOf(fn);
104
+ if (i >= 0) __privateGet(self, _hooks).splice(i, 1);
105
+ };
106
+ },
107
+ get destroyed() {
108
+ return __privateGet(self, _destroyed);
109
+ }
110
+ };
111
+ }
112
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
113
+ get destroyed() {
114
+ return __privateGet(this, _destroyed);
115
+ }
116
+ /**
117
+ * Add providers to this injector. A provider for a token already held replaces it, except
118
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
119
+ * is replaced by the override.
120
+ *
121
+ * @param providers - The providers to add, in order.
122
+ * @returns This injector, for chaining.
123
+ */
124
+ register(providers) {
125
+ for (const given of providers) {
126
+ const p = state.overrides.get(given.provide.id) ?? given;
127
+ if (p.multi) {
128
+ const rec = __privateGet(this, _records).get(p.provide.id);
129
+ if (rec?.multi) {
130
+ rec.providers.push(p);
131
+ rec.built = false;
132
+ } else __privateGet(this, _records).set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
133
+ } else {
134
+ __privateGet(this, _records).set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: void 0 });
135
+ }
136
+ }
137
+ return this;
138
+ }
139
+ get(token, options = {}) {
140
+ var _a, _b, _c;
141
+ if (__privateGet(this, _destroyed)) throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
142
+ if (token.id === INJECTOR.id) return this;
143
+ if (token.id === DestroyRef.id) return this.destroyRef;
144
+ let inj = options.skipSelf ? this.parent : this;
145
+ while (inj) {
146
+ const rec = __privateGet(inj, _records).get(token.id);
147
+ if (rec) return __privateMethod(_a = inj, _Injector_instances, build_fn).call(_a, token, rec);
148
+ if (options.self || options.host && inj.kind === "owner") break;
149
+ inj = inj.parent;
150
+ }
151
+ const override = state.overrides.get(token.id);
152
+ 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);
153
+ if (token.factory && !options.self && !options.host) {
154
+ 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 });
155
+ }
156
+ if (options.optional) return null;
157
+ const path = [...state.path, token.name];
158
+ throw new InjectionError(
159
+ `[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 }.`,
160
+ path
161
+ );
162
+ }
163
+ /**
164
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
165
+ * `onDestroy()` method of every service it built, latest built first, then forget its
166
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
167
+ * again does nothing.
168
+ *
169
+ * Component injectors are disposed when the component unmounts, request roots when the request
170
+ * ends; call it yourself only for injectors you created.
171
+ */
172
+ dispose() {
173
+ if (__privateGet(this, _destroyed)) return;
174
+ __privateSet(this, _destroyed, true);
175
+ const fail = (error) => console.error("[fluixi/di] a destroy hook threw:", error);
176
+ for (const hook of __privateGet(this, _hooks).splice(0).reverse()) {
177
+ try {
178
+ hook();
179
+ } catch (error) {
180
+ fail(error);
181
+ }
182
+ }
183
+ for (const value of __privateGet(this, _created).splice(0).reverse()) {
184
+ const service = value;
185
+ if (service && typeof service.onDestroy === "function") {
186
+ try {
187
+ service.onDestroy();
188
+ } catch (error) {
189
+ fail(error);
190
+ }
191
+ }
192
+ }
193
+ __privateGet(this, _records).clear();
194
+ }
195
+ };
196
+ _records = new WeakMap();
197
+ _created = new WeakMap();
198
+ _hooks = new WeakMap();
199
+ _destroyed = new WeakMap();
200
+ _Injector_instances = new WeakSet();
201
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
202
+ home_fn = function(scope) {
203
+ let inj = this;
204
+ if (scope === "platform") {
205
+ while (inj.parent) inj = inj.parent;
206
+ return inj.kind === "platform" ? inj : platformInjector();
207
+ }
208
+ let probe = this;
209
+ while (probe) {
210
+ if (probe.kind === "root") return probe;
211
+ probe = probe.parent;
212
+ }
213
+ while (inj.parent && inj.parent.kind !== "platform") inj = inj.parent;
214
+ return inj;
215
+ };
216
+ fromProvider_fn = function(token, provider) {
217
+ let rec = __privateGet(this, _records).get(token.id);
218
+ if (!rec) {
219
+ rec = { multi: false, provider, built: false, building: false, value: void 0 };
220
+ __privateGet(this, _records).set(token.id, rec);
221
+ }
222
+ return __privateMethod(this, _Injector_instances, build_fn).call(this, token, rec);
223
+ };
224
+ build_fn = function(token, rec) {
225
+ if (rec.built) return rec.value;
226
+ if (rec.building) {
227
+ const path = [...state.path, token.name];
228
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(" → ")}.`, path);
229
+ }
230
+ rec.building = true;
231
+ state.path.push(token.name);
232
+ const previous = state.explicit;
233
+ state.explicit = this;
234
+ try {
235
+ if (rec.multi) {
236
+ rec.value = rec.providers.map((p) => __privateMethod(this, _Injector_instances, make_fn).call(this, token, p));
237
+ } else {
238
+ const module2 = state.moduleScopes.get(rec.provider);
239
+ rec.value = module2 ? (0, import_signal.withReactiveScope)(module2, () => __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider)) : __privateMethod(this, _Injector_instances, make_fn).call(this, token, rec.provider);
240
+ }
241
+ rec.built = true;
242
+ return rec.value;
243
+ } finally {
244
+ state.explicit = previous;
245
+ state.path.pop();
246
+ rec.building = false;
247
+ }
248
+ };
249
+ make_fn = function(token, p) {
250
+ if ("useValue" in p) return p.useValue;
251
+ if ("useExisting" in p) return this.get(p.useExisting);
252
+ const deps = (p.deps ?? []).map((d) => "token" in d ? this.get(d.token, d) : this.get(d));
253
+ const scope = (0, import_signal.openReactiveScope)({ kind: "service", name: token.name, detail: "useClass" in p ? "useClass" : "useFactory" });
254
+ const value = (0, import_signal.withReactiveScope)(scope, () => "useClass" in p ? new p.useClass(...deps) : p.useFactory(...deps));
255
+ __privateGet(this, _created).push(value);
256
+ return value;
257
+ };
258
+
259
+ // src/hierarchy.ts
260
+ function platformInjector() {
261
+ return state.platform ??= new Injector(null, "platform").register(state.platformProviders);
262
+ }
263
+ function rootInjector() {
264
+ const ctx = (0, import_dom.getRequestContext)();
265
+ if (ctx) {
266
+ let inj = state.requestRoots.get(ctx);
267
+ if (!inj) {
268
+ const root = new Injector(platformInjector(), "root").register(state.rootProviders);
269
+ state.requestRoots.set(ctx, root);
270
+ (0, import_dom.onRequestEnd)(() => {
271
+ state.requestRoots.delete(ctx);
272
+ root.dispose();
273
+ });
274
+ inj = root;
275
+ }
276
+ return inj;
277
+ }
278
+ return state.clientRoot ??= new Injector(platformInjector(), "root").register(state.rootProviders);
279
+ }
280
+ function nearestOwnerInjector(owner) {
281
+ let o = owner;
282
+ while (o) {
283
+ const inj = state.ownerInjectors.get(o);
284
+ if (inj) return inj;
285
+ o = o.parent;
286
+ }
287
+ return null;
288
+ }
289
+ function currentInjector() {
290
+ return state.explicit ?? nearestOwnerInjector((0, import_signal2.getOwner)()) ?? rootInjector();
291
+ }
@@ -0,0 +1,11 @@
1
+ import { Injector } from './injector.js';
2
+ /** The process-wide injector: `providePlatform` services and `scope: 'platform'` tokens. */
3
+ export declare function platformInjector(): Injector;
4
+ /**
5
+ * The root injector: one per request during a server render (disposed when the request ends),
6
+ * one per app in the browser.
7
+ */
8
+ export declare function rootInjector(): Injector;
9
+ export declare function nearestOwnerInjector(owner: any): Injector | null;
10
+ export declare function currentInjector(): Injector;
11
+ //# sourceMappingURL=hierarchy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../src/hierarchy.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,4FAA4F;AAC5F,wBAAgB,gBAAgB,IAAI,QAAQ,CAE3C;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAgBvC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,GAAG,QAAQ,GAAG,IAAI,CAQhE;AAED,wBAAgB,eAAe,IAAI,QAAQ,CAE1C"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The hierarchy of injectors: the platform (one per process), the root (one per request on the
3
+ * server, disposed when the request ends; one per app in the browser), and the component
4
+ * injectors `provide()` attaches to the reactive owner tree.
5
+ */
6
+ import { getRequestContext, onRequestEnd } from '@fluixi/dom';
7
+ import { getOwner } from '@fluixi/reactive/signal';
8
+ import { Injector } from './injector.js';
9
+ import { state } from './state.js';
10
+ /** The process-wide injector: `providePlatform` services and `scope: 'platform'` tokens. */
11
+ export function platformInjector() {
12
+ return (state.platform ??= new Injector(null, 'platform').register(state.platformProviders));
13
+ }
14
+ /**
15
+ * The root injector: one per request during a server render (disposed when the request ends),
16
+ * one per app in the browser.
17
+ */
18
+ export function rootInjector() {
19
+ const ctx = getRequestContext();
20
+ if (ctx) {
21
+ let inj = state.requestRoots.get(ctx);
22
+ if (!inj) {
23
+ const root = new Injector(platformInjector(), 'root').register(state.rootProviders);
24
+ state.requestRoots.set(ctx, root);
25
+ onRequestEnd(() => {
26
+ state.requestRoots.delete(ctx);
27
+ root.dispose();
28
+ });
29
+ inj = root;
30
+ }
31
+ return inj;
32
+ }
33
+ return (state.clientRoot ??= new Injector(platformInjector(), 'root').register(state.rootProviders));
34
+ }
35
+ export function nearestOwnerInjector(owner) {
36
+ let o = owner;
37
+ while (o) {
38
+ const inj = state.ownerInjectors.get(o);
39
+ if (inj)
40
+ return inj;
41
+ o = o.parent;
42
+ }
43
+ return null;
44
+ }
45
+ export function currentInjector() {
46
+ return state.explicit ?? nearestOwnerInjector(getOwner()) ?? rootInjector();
47
+ }