@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,151 @@
1
+ import { Injector } from './injector.js';
2
+ import type { InjectOptions, Provider } from './provider.js';
3
+ import type { Token } from './token.js';
4
+ /**
5
+ * Resolve `token` from the current injection context: the injector set by
6
+ * {@link runInInjectionContext} or building the current service, else the nearest component
7
+ * injector up the reactive owner tree, else the root (the request's on the server, the app's in
8
+ * the browser).
9
+ *
10
+ * Call it during a component's setup, inside a provider's constructor or factory, or inside
11
+ * {@link runInInjectionContext}. In an event handler or after an `await` there is no component
12
+ * owner any more: keep the value, or the injector (`inject(INJECTOR)`), from setup instead.
13
+ *
14
+ * @param token - What to resolve.
15
+ * @param options - Where to look and what to do when nothing is found; see {@link InjectOptions}.
16
+ * @returns The value, or `null` when `optional` is set and nothing provides the token.
17
+ * @throws {@link InjectionError} When nothing provides the token, or on a dependency cycle.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * function Profile() {
22
+ * const users = inject(Users);
23
+ * const analytics = inject(Analytics, { optional: true }); // null where not provided
24
+ * ...
25
+ * }
26
+ * ```
27
+ */
28
+ export declare function inject<T>(token: Token<T>, options: InjectOptions & {
29
+ optional: true;
30
+ }): T | null;
31
+ export declare function inject<T>(token: Token<T>, options?: InjectOptions): T;
32
+ /**
33
+ * Provide services to the current component and its descendants. The first call in a component
34
+ * creates its injector, below the nearest ancestor component's injector (or the root); later
35
+ * calls add to it. The injector is disposed when the component unmounts: hooks registered on its
36
+ * `DestroyRef` and each service's `onDestroy()` run then.
37
+ *
38
+ * @param providers - The providers to add to the component's injector.
39
+ * @returns The component's injector.
40
+ * @throws Error When called outside a component, where there is no owner to attach to; use
41
+ * {@link provideRoot} for services of the whole app.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * function Checkout() {
46
+ * provide([{ provide: Cart, useClass: CartStore }]); // one cart per checkout, released with it
47
+ * return <CheckoutSteps />;
48
+ * }
49
+ * ```
50
+ */
51
+ export declare function provide(providers: ReadonlyArray<Provider>): Injector;
52
+ /**
53
+ * Provide services to the whole app, in the root injector: one instance per request on the
54
+ * server (released when the request ends, so per-user state never leaks between requests), one
55
+ * per app in the browser.
56
+ *
57
+ * Call it at module load, before the first render: in an app module's providers, or in the app's
58
+ * entry. Providers added later reach the browser's root and the current request's root, not
59
+ * requests already finished.
60
+ *
61
+ * @param providers - The providers every root injector starts with.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * provideRoot([
66
+ * { provide: ApiUrl, useValue: import.meta.env.API_URL },
67
+ * { provide: Http, useClass: FetchHttp, deps: [ApiUrl] },
68
+ * ]);
69
+ * ```
70
+ */
71
+ export declare function provideRoot(providers: ReadonlyArray<Provider>): void;
72
+ /**
73
+ * Provide services once per process, in the platform injector above every root: shared by every
74
+ * request on the server. For what is costly to create and safe to share (a connection pool,
75
+ * parsed settings); never for per-user state, which belongs in {@link provideRoot}.
76
+ *
77
+ * @param providers - The providers the platform injector holds.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * providePlatform([{ provide: Database, useFactory: () => createPool(process.env.DATABASE_URL) }]);
82
+ * ```
83
+ */
84
+ export declare function providePlatform(providers: ReadonlyArray<Provider>): void;
85
+ /**
86
+ * Run `fn` with `injector` as the injection context, so `inject()` inside it resolves from
87
+ * `injector`. The context lasts for the synchronous part of `fn`: after an `await`, resolve what
88
+ * is needed before it, or call this again.
89
+ *
90
+ * @param injector - Where `inject()` resolves from while `fn` runs.
91
+ * @param fn - The work to run.
92
+ * @returns What `fn` returns.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const injector = inject(INJECTOR);
97
+ * button.addEventListener('click', () => runInInjectionContext(injector, () => inject(Cart).add(item)));
98
+ * ```
99
+ */
100
+ export declare function runInInjectionContext<R>(injector: Injector, fn: () => R): R;
101
+ /**
102
+ * Whether `inject()` can resolve here: inside {@link runInInjectionContext} or a provider being
103
+ * built, during a component's setup, or during a server request.
104
+ *
105
+ * @returns `true` when an injection context is active.
106
+ */
107
+ export declare function isInInjectionContext(): boolean;
108
+ /**
109
+ * Throw unless an injection context is active. For functions that call `inject()` themselves and
110
+ * want a clear error for their own callers instead of an unexpected resolution from the root.
111
+ *
112
+ * @param name - The calling function's name, used in the message.
113
+ * @throws Error When no injection context is active.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * export function useCart(): Cart {
118
+ * assertInInjectionContext('useCart');
119
+ * return inject(Cart);
120
+ * }
121
+ * ```
122
+ */
123
+ export declare function assertInInjectionContext(name: string): void;
124
+ /**
125
+ * The injector of the current context: to keep, and resolve from or hand to
126
+ * {@link runInInjectionContext} later. Same as `inject(INJECTOR)`.
127
+ *
128
+ * @returns The injector `inject()` would resolve from here.
129
+ */
130
+ export declare function getInjector(): Injector;
131
+ /**
132
+ * Create a standalone injector holding `providers`, below `parent`. For a scope of your own (a
133
+ * job, a dialog, a test): what it builds is released when you call `dispose()` on it.
134
+ *
135
+ * @param providers - The providers the new injector holds.
136
+ * @param parent - Where unresolved tokens are looked for next. Defaults to the current root;
137
+ * `null` makes it the top of its own chain.
138
+ * @returns The new injector.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const job = createInjector([{ provide: JobId, useValue: id }]);
143
+ * try {
144
+ * await runInInjectionContext(job, () => inject(Importer)).run();
145
+ * } finally {
146
+ * job.dispose();
147
+ * }
148
+ * ```
149
+ */
150
+ export declare function createInjector(providers: ReadonlyArray<Provider>, parent?: Injector | null): Injector;
151
+ //# sourceMappingURL=inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../src/inject.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG,CAAC,GAAG,IAAI,CAAC;AAClG,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,CAAC,CAAC;AAKvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAapE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,IAAI,CAKpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,IAAI,CAGxE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAQ3E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,CAErG"}
package/dist/inject.js ADDED
@@ -0,0 +1,174 @@
1
+ /**
2
+ * What code calls: resolve from the current context, provide to a component, the whole app or
3
+ * the process, and work with the injection context.
4
+ *
5
+ * @module
6
+ */
7
+ import { getRequestContext } from '@fluixi/dom';
8
+ import { getOwner, onCleanup } from '@fluixi/reactive/signal';
9
+ import { currentInjector, nearestOwnerInjector, rootInjector } from './hierarchy.js';
10
+ import { Injector } from './injector.js';
11
+ import { state } from './state.js';
12
+ export function inject(token, options) {
13
+ return currentInjector().get(token, options ?? {});
14
+ }
15
+ /**
16
+ * Provide services to the current component and its descendants. The first call in a component
17
+ * creates its injector, below the nearest ancestor component's injector (or the root); later
18
+ * calls add to it. The injector is disposed when the component unmounts: hooks registered on its
19
+ * `DestroyRef` and each service's `onDestroy()` run then.
20
+ *
21
+ * @param providers - The providers to add to the component's injector.
22
+ * @returns The component's injector.
23
+ * @throws Error When called outside a component, where there is no owner to attach to; use
24
+ * {@link provideRoot} for services of the whole app.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * function Checkout() {
29
+ * provide([{ provide: Cart, useClass: CartStore }]); // one cart per checkout, released with it
30
+ * return <CheckoutSteps />;
31
+ * }
32
+ * ```
33
+ */
34
+ export function provide(providers) {
35
+ const owner = getOwner();
36
+ if (!owner) {
37
+ throw new Error('[fluixi/di] provide() runs inside a component. Use provideRoot() for services of the whole app.');
38
+ }
39
+ let inj = state.ownerInjectors.get(owner);
40
+ if (!inj) {
41
+ const created = new Injector(nearestOwnerInjector(owner.parent) ?? rootInjector(), 'owner');
42
+ state.ownerInjectors.set(owner, created);
43
+ onCleanup(() => created.dispose());
44
+ inj = created;
45
+ }
46
+ return inj.register(providers);
47
+ }
48
+ /**
49
+ * Provide services to the whole app, in the root injector: one instance per request on the
50
+ * server (released when the request ends, so per-user state never leaks between requests), one
51
+ * per app in the browser.
52
+ *
53
+ * Call it at module load, before the first render: in an app module's providers, or in the app's
54
+ * entry. Providers added later reach the browser's root and the current request's root, not
55
+ * requests already finished.
56
+ *
57
+ * @param providers - The providers every root injector starts with.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * provideRoot([
62
+ * { provide: ApiUrl, useValue: import.meta.env.API_URL },
63
+ * { provide: Http, useClass: FetchHttp, deps: [ApiUrl] },
64
+ * ]);
65
+ * ```
66
+ */
67
+ export function provideRoot(providers) {
68
+ state.rootProviders.push(...providers);
69
+ state.clientRoot?.register(providers);
70
+ const ctx = getRequestContext();
71
+ if (ctx)
72
+ state.requestRoots.get(ctx)?.register(providers);
73
+ }
74
+ /**
75
+ * Provide services once per process, in the platform injector above every root: shared by every
76
+ * request on the server. For what is costly to create and safe to share (a connection pool,
77
+ * parsed settings); never for per-user state, which belongs in {@link provideRoot}.
78
+ *
79
+ * @param providers - The providers the platform injector holds.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * providePlatform([{ provide: Database, useFactory: () => createPool(process.env.DATABASE_URL) }]);
84
+ * ```
85
+ */
86
+ export function providePlatform(providers) {
87
+ state.platformProviders.push(...providers);
88
+ state.platform?.register(providers);
89
+ }
90
+ /**
91
+ * Run `fn` with `injector` as the injection context, so `inject()` inside it resolves from
92
+ * `injector`. The context lasts for the synchronous part of `fn`: after an `await`, resolve what
93
+ * is needed before it, or call this again.
94
+ *
95
+ * @param injector - Where `inject()` resolves from while `fn` runs.
96
+ * @param fn - The work to run.
97
+ * @returns What `fn` returns.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const injector = inject(INJECTOR);
102
+ * button.addEventListener('click', () => runInInjectionContext(injector, () => inject(Cart).add(item)));
103
+ * ```
104
+ */
105
+ export function runInInjectionContext(injector, fn) {
106
+ const previous = state.explicit;
107
+ state.explicit = injector;
108
+ try {
109
+ return fn();
110
+ }
111
+ finally {
112
+ state.explicit = previous;
113
+ }
114
+ }
115
+ /**
116
+ * Whether `inject()` can resolve here: inside {@link runInInjectionContext} or a provider being
117
+ * built, during a component's setup, or during a server request.
118
+ *
119
+ * @returns `true` when an injection context is active.
120
+ */
121
+ export function isInInjectionContext() {
122
+ return state.explicit !== null || getOwner() !== null || getRequestContext() !== undefined;
123
+ }
124
+ /**
125
+ * Throw unless an injection context is active. For functions that call `inject()` themselves and
126
+ * want a clear error for their own callers instead of an unexpected resolution from the root.
127
+ *
128
+ * @param name - The calling function's name, used in the message.
129
+ * @throws Error When no injection context is active.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * export function useCart(): Cart {
134
+ * assertInInjectionContext('useCart');
135
+ * return inject(Cart);
136
+ * }
137
+ * ```
138
+ */
139
+ export function assertInInjectionContext(name) {
140
+ if (!isInInjectionContext()) {
141
+ throw new Error(`[fluixi/di] ${name}() runs inside a component, a request or runInInjectionContext().`);
142
+ }
143
+ }
144
+ /**
145
+ * The injector of the current context: to keep, and resolve from or hand to
146
+ * {@link runInInjectionContext} later. Same as `inject(INJECTOR)`.
147
+ *
148
+ * @returns The injector `inject()` would resolve from here.
149
+ */
150
+ export function getInjector() {
151
+ return currentInjector();
152
+ }
153
+ /**
154
+ * Create a standalone injector holding `providers`, below `parent`. For a scope of your own (a
155
+ * job, a dialog, a test): what it builds is released when you call `dispose()` on it.
156
+ *
157
+ * @param providers - The providers the new injector holds.
158
+ * @param parent - Where unresolved tokens are looked for next. Defaults to the current root;
159
+ * `null` makes it the top of its own chain.
160
+ * @returns The new injector.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const job = createInjector([{ provide: JobId, useValue: id }]);
165
+ * try {
166
+ * await runInInjectionContext(job, () => inject(Importer)).run();
167
+ * } finally {
168
+ * job.dispose();
169
+ * }
170
+ * ```
171
+ */
172
+ export function createInjector(providers, parent) {
173
+ return new Injector(parent === undefined ? rootInjector() : parent, 'standalone').register(providers);
174
+ }
@@ -0,0 +1,332 @@
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
+ 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);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
+
10
+ // src/inject.ts
11
+ import { getRequestContext as getRequestContext2 } from "@fluixi/dom";
12
+ import { getOwner as getOwner2, onCleanup } from "@fluixi/reactive/signal";
13
+
14
+ // src/hierarchy.ts
15
+ import { getRequestContext, onRequestEnd } from "@fluixi/dom";
16
+ import { getOwner } from "@fluixi/reactive/signal";
17
+
18
+ // src/injector.ts
19
+ import { openReactiveScope, withReactiveScope } from "@fluixi/reactive/signal";
20
+
21
+ // src/errors.ts
22
+ var InjectionError = class extends Error {
23
+ /**
24
+ * @param message - What went wrong, with the path rendered into it.
25
+ * @param path - The tokens being resolved, outermost first; the last one is the one that failed.
26
+ */
27
+ constructor(message, path) {
28
+ super(message);
29
+ this.path = path;
30
+ this.name = "InjectionError";
31
+ }
32
+ };
33
+
34
+ // src/state.ts
35
+ function freshState() {
36
+ return {
37
+ platformProviders: [],
38
+ rootProviders: [],
39
+ platform: null,
40
+ clientRoot: null,
41
+ requestRoots: /* @__PURE__ */ new WeakMap(),
42
+ ownerInjectors: /* @__PURE__ */ new WeakMap(),
43
+ moduleScopes: /* @__PURE__ */ new WeakMap(),
44
+ overrides: /* @__PURE__ */ new Map(),
45
+ explicit: null,
46
+ path: []
47
+ };
48
+ }
49
+ var state = globalThis[/* @__PURE__ */ Symbol.for("fluixi.di")] ??= freshState();
50
+
51
+ // src/token.ts
52
+ function injectionToken(name, options) {
53
+ return { id: Symbol(name), name, factory: options?.factory, scope: options?.scope ?? "root" };
54
+ }
55
+
56
+ // src/tokens.ts
57
+ var DestroyRef = injectionToken("DestroyRef");
58
+ var INJECTOR = injectionToken("Injector");
59
+
60
+ // src/injector.ts
61
+ var _records, _created, _hooks, _destroyed, _Injector_instances, home_fn, fromProvider_fn, build_fn, make_fn;
62
+ var Injector = class {
63
+ /**
64
+ * @param parent - Where a token this injector does not hold is looked for next; `null` for the
65
+ * top of a chain.
66
+ * @param kind - Where the injector sits in the hierarchy; see {@link InjectorKind}.
67
+ */
68
+ constructor(parent = null, kind = "standalone") {
69
+ this.parent = parent;
70
+ this.kind = kind;
71
+ __privateAdd(this, _Injector_instances);
72
+ __privateAdd(this, _records, /* @__PURE__ */ new Map());
73
+ __privateAdd(this, _created, []);
74
+ __privateAdd(this, _hooks, []);
75
+ __privateAdd(this, _destroyed, false);
76
+ const self = this;
77
+ this.destroyRef = {
78
+ onDestroy(fn) {
79
+ __privateGet(self, _hooks).push(fn);
80
+ return () => {
81
+ const i = __privateGet(self, _hooks).indexOf(fn);
82
+ if (i >= 0) __privateGet(self, _hooks).splice(i, 1);
83
+ };
84
+ },
85
+ get destroyed() {
86
+ return __privateGet(self, _destroyed);
87
+ }
88
+ };
89
+ }
90
+ /** Whether {@link Injector.dispose} has run. A disposed injector refuses every `get`. */
91
+ get destroyed() {
92
+ return __privateGet(this, _destroyed);
93
+ }
94
+ /**
95
+ * Add providers to this injector. A provider for a token already held replaces it, except
96
+ * `multi` providers, which extend the token's list. A provider whose token has a test override
97
+ * is replaced by the override.
98
+ *
99
+ * @param providers - The providers to add, in order.
100
+ * @returns This injector, for chaining.
101
+ */
102
+ register(providers) {
103
+ for (const given of providers) {
104
+ const p = state.overrides.get(given.provide.id) ?? given;
105
+ if (p.multi) {
106
+ const rec = __privateGet(this, _records).get(p.provide.id);
107
+ if (rec?.multi) {
108
+ rec.providers.push(p);
109
+ rec.built = false;
110
+ } else __privateGet(this, _records).set(p.provide.id, { multi: true, providers: [p], built: false, building: false, value: [] });
111
+ } else {
112
+ __privateGet(this, _records).set(p.provide.id, { multi: false, provider: p, built: false, building: false, value: void 0 });
113
+ }
114
+ }
115
+ return this;
116
+ }
117
+ get(token, options = {}) {
118
+ var _a, _b, _c;
119
+ if (__privateGet(this, _destroyed)) throw new InjectionError(`[fluixi/di] "${token.name}" requested from a disposed injector.`, [...state.path, token.name]);
120
+ if (token.id === INJECTOR.id) return this;
121
+ if (token.id === DestroyRef.id) return this.destroyRef;
122
+ let inj = options.skipSelf ? this.parent : this;
123
+ while (inj) {
124
+ const rec = __privateGet(inj, _records).get(token.id);
125
+ if (rec) return __privateMethod(_a = inj, _Injector_instances, build_fn).call(_a, token, rec);
126
+ if (options.self || options.host && inj.kind === "owner") break;
127
+ inj = inj.parent;
128
+ }
129
+ const override = state.overrides.get(token.id);
130
+ 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);
131
+ if (token.factory && !options.self && !options.host) {
132
+ 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 });
133
+ }
134
+ if (options.optional) return null;
135
+ const path = [...state.path, token.name];
136
+ throw new InjectionError(
137
+ `[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 }.`,
138
+ path
139
+ );
140
+ }
141
+ /**
142
+ * Release this injector: run the hooks registered on its `DestroyRef`, latest first, then the
143
+ * `onDestroy()` method of every service it built, latest built first, then forget its
144
+ * providers. A hook or `onDestroy()` that throws is logged and the rest still run. Running it
145
+ * again does nothing.
146
+ *
147
+ * Component injectors are disposed when the component unmounts, request roots when the request
148
+ * ends; call it yourself only for injectors you created.
149
+ */
150
+ dispose() {
151
+ if (__privateGet(this, _destroyed)) return;
152
+ __privateSet(this, _destroyed, true);
153
+ const fail = (error) => console.error("[fluixi/di] a destroy hook threw:", error);
154
+ for (const hook of __privateGet(this, _hooks).splice(0).reverse()) {
155
+ try {
156
+ hook();
157
+ } catch (error) {
158
+ fail(error);
159
+ }
160
+ }
161
+ for (const value of __privateGet(this, _created).splice(0).reverse()) {
162
+ const service = value;
163
+ if (service && typeof service.onDestroy === "function") {
164
+ try {
165
+ service.onDestroy();
166
+ } catch (error) {
167
+ fail(error);
168
+ }
169
+ }
170
+ }
171
+ __privateGet(this, _records).clear();
172
+ }
173
+ };
174
+ _records = new WeakMap();
175
+ _created = new WeakMap();
176
+ _hooks = new WeakMap();
177
+ _destroyed = new WeakMap();
178
+ _Injector_instances = new WeakSet();
179
+ /** The injector a self-providing token builds in: the nearest root, or the platform. */
180
+ home_fn = function(scope) {
181
+ let inj = this;
182
+ if (scope === "platform") {
183
+ while (inj.parent) inj = inj.parent;
184
+ return inj.kind === "platform" ? inj : platformInjector();
185
+ }
186
+ let probe = this;
187
+ while (probe) {
188
+ if (probe.kind === "root") return probe;
189
+ probe = probe.parent;
190
+ }
191
+ while (inj.parent && inj.parent.kind !== "platform") inj = inj.parent;
192
+ return inj;
193
+ };
194
+ fromProvider_fn = function(token, provider) {
195
+ let rec = __privateGet(this, _records).get(token.id);
196
+ if (!rec) {
197
+ rec = { multi: false, provider, built: false, building: false, value: void 0 };
198
+ __privateGet(this, _records).set(token.id, rec);
199
+ }
200
+ return __privateMethod(this, _Injector_instances, build_fn).call(this, token, rec);
201
+ };
202
+ build_fn = function(token, rec) {
203
+ if (rec.built) return rec.value;
204
+ if (rec.building) {
205
+ const path = [...state.path, token.name];
206
+ throw new InjectionError(`[fluixi/di] Circular dependency: ${path.join(" → ")}.`, path);
207
+ }
208
+ rec.building = true;
209
+ state.path.push(token.name);
210
+ const previous = state.explicit;
211
+ state.explicit = this;
212
+ try {
213
+ if (rec.multi) {
214
+ rec.value = rec.providers.map((p) => __privateMethod(this, _Injector_instances, make_fn).call(this, token, p));
215
+ } else {
216
+ const module = state.moduleScopes.get(rec.provider);
217
+ 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);
218
+ }
219
+ rec.built = true;
220
+ return rec.value;
221
+ } finally {
222
+ state.explicit = previous;
223
+ state.path.pop();
224
+ rec.building = false;
225
+ }
226
+ };
227
+ make_fn = function(token, p) {
228
+ if ("useValue" in p) return p.useValue;
229
+ if ("useExisting" in p) return this.get(p.useExisting);
230
+ const deps = (p.deps ?? []).map((d) => "token" in d ? this.get(d.token, d) : this.get(d));
231
+ const scope = openReactiveScope({ kind: "service", name: token.name, detail: "useClass" in p ? "useClass" : "useFactory" });
232
+ const value = withReactiveScope(scope, () => "useClass" in p ? new p.useClass(...deps) : p.useFactory(...deps));
233
+ __privateGet(this, _created).push(value);
234
+ return value;
235
+ };
236
+
237
+ // src/hierarchy.ts
238
+ function platformInjector() {
239
+ return state.platform ??= new Injector(null, "platform").register(state.platformProviders);
240
+ }
241
+ function rootInjector() {
242
+ const ctx = getRequestContext();
243
+ if (ctx) {
244
+ let inj = state.requestRoots.get(ctx);
245
+ if (!inj) {
246
+ const root = new Injector(platformInjector(), "root").register(state.rootProviders);
247
+ state.requestRoots.set(ctx, root);
248
+ onRequestEnd(() => {
249
+ state.requestRoots.delete(ctx);
250
+ root.dispose();
251
+ });
252
+ inj = root;
253
+ }
254
+ return inj;
255
+ }
256
+ return state.clientRoot ??= new Injector(platformInjector(), "root").register(state.rootProviders);
257
+ }
258
+ function nearestOwnerInjector(owner) {
259
+ let o = owner;
260
+ while (o) {
261
+ const inj = state.ownerInjectors.get(o);
262
+ if (inj) return inj;
263
+ o = o.parent;
264
+ }
265
+ return null;
266
+ }
267
+ function currentInjector() {
268
+ return state.explicit ?? nearestOwnerInjector(getOwner()) ?? rootInjector();
269
+ }
270
+
271
+ // src/inject.ts
272
+ function inject(token, options) {
273
+ return currentInjector().get(token, options ?? {});
274
+ }
275
+ function provide(providers) {
276
+ const owner = getOwner2();
277
+ if (!owner) {
278
+ throw new Error("[fluixi/di] provide() runs inside a component. Use provideRoot() for services of the whole app.");
279
+ }
280
+ let inj = state.ownerInjectors.get(owner);
281
+ if (!inj) {
282
+ const created = new Injector(nearestOwnerInjector(owner.parent) ?? rootInjector(), "owner");
283
+ state.ownerInjectors.set(owner, created);
284
+ onCleanup(() => created.dispose());
285
+ inj = created;
286
+ }
287
+ return inj.register(providers);
288
+ }
289
+ function provideRoot(providers) {
290
+ state.rootProviders.push(...providers);
291
+ state.clientRoot?.register(providers);
292
+ const ctx = getRequestContext2();
293
+ if (ctx) state.requestRoots.get(ctx)?.register(providers);
294
+ }
295
+ function providePlatform(providers) {
296
+ state.platformProviders.push(...providers);
297
+ state.platform?.register(providers);
298
+ }
299
+ function runInInjectionContext(injector, fn) {
300
+ const previous = state.explicit;
301
+ state.explicit = injector;
302
+ try {
303
+ return fn();
304
+ } finally {
305
+ state.explicit = previous;
306
+ }
307
+ }
308
+ function isInInjectionContext() {
309
+ return state.explicit !== null || getOwner2() !== null || getRequestContext2() !== void 0;
310
+ }
311
+ function assertInInjectionContext(name) {
312
+ if (!isInInjectionContext()) {
313
+ throw new Error(`[fluixi/di] ${name}() runs inside a component, a request or runInInjectionContext().`);
314
+ }
315
+ }
316
+ function getInjector() {
317
+ return currentInjector();
318
+ }
319
+ function createInjector(providers, parent) {
320
+ return new Injector(parent === void 0 ? rootInjector() : parent, "standalone").register(providers);
321
+ }
322
+ export {
323
+ assertInInjectionContext,
324
+ createInjector,
325
+ getInjector,
326
+ inject,
327
+ isInInjectionContext,
328
+ provide,
329
+ providePlatform,
330
+ provideRoot,
331
+ runInInjectionContext
332
+ };