@mmstack/di 21.0.3 → 21.0.5

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.
package/README.md CHANGED
@@ -16,6 +16,8 @@ npm install @mmstack/di
16
16
  This library provides the following utilities:
17
17
 
18
18
  - `injectable` - Creates a typed InjectionToken with inject and provide helper functions for type-safe dependency injection.
19
+ - `injectLazy` - Defers the resolution and instantiation of a token until it is actually accessed.
20
+ - `createRunInInjectionContext` - Captures an injection context securely and returns a runner function, useful for `inject()` inside async callbacks.
19
21
  - `rootInjectable` - Creates a lazily-initialized root-level injectable that maintains a singleton instance.
20
22
  - `createScope` - Creates a dependency injection scope that caches singletons based on the factory function.
21
23
 
@@ -237,6 +239,97 @@ export class FormActionsComponent {
237
239
 
238
240
  ---
239
241
 
242
+ ## injectLazy
243
+
244
+ Defers the resolution and instantiation of an injection token until the returned getter function is actually called.
245
+
246
+ Angular's native `inject()` resolves and instantiates dependencies immediately during the construction phase. If a service is heavily resource-intensive but only needed conditionally (like an export service or a complex editor), `injectLazy` allows you to capture the injection context immediately while delaying instantiation. The resolved value is cached, acting as a standard scoped singleton on subsequent calls.
247
+
248
+ ### Basic Usage
249
+
250
+ ```typescript
251
+ import { Component, HostListener } from '@angular/core';
252
+ import { injectLazy } from '@mmstack/di';
253
+
254
+ @Component({
255
+ selector: 'app-export-button',
256
+ template: `<button>Export Data</button>`,
257
+ })
258
+ export class ExportButtonComponent {
259
+ // Captures the Injector but does NOT instantiate HeavyExportService yet
260
+ private getExportService = injectLazy(HeavyExportService);
261
+
262
+ @HostListener('click')
263
+ export() {
264
+ // HeavyExportService is instantiated on the first click, then cached
265
+ const service = this.getExportService();
266
+ service.doExport();
267
+ }
268
+ }
269
+ ```
270
+
271
+ ### With Options
272
+
273
+ It fully supports Angular's `InjectOptions` and guarantees correct return types (e.g., returning `T | null` when `optional: true`):
274
+
275
+ ```typescript
276
+ const getOptionalDep = injectLazy(MyToken, { optional: true });
277
+
278
+ // Later...
279
+ const dep = getOptionalDep(); // MyToken | null
280
+ ```
281
+
282
+ ---
283
+
284
+ ## createRunInInjectionContext
285
+
286
+ Captures an injection context securely and returns a runner function.
287
+
288
+ This utility allows you to execute callbacks inside the captured context at a later time. It solves the common pain point of needing to use `inject()` inside asynchronous callbacks, RxJS streams, or external event listeners where the framework's implicit injection context has been lost.
289
+
290
+ ### Basic Usage
291
+
292
+ ```typescript
293
+ import { Component, OnInit, inject } from '@angular/core';
294
+ import { createRunInInjectionContext } from '@mmstack/di';
295
+
296
+ @Component({
297
+ selector: 'app-dialog-trigger',
298
+ template: `<button>Open Dialog</button>`,
299
+ })
300
+ export class DialogTriggerComponent implements OnInit {
301
+ // Grabs the current injector during construction
302
+ private runInContext = createRunInInjectionContext();
303
+
304
+ ngOnInit() {
305
+ // someExternalLibrary is out of Angular's zone/context
306
+ someExternalLibrary.on('openEvent', () => {
307
+ this.runInContext(() => {
308
+ // We can safely use `inject()` here even though we are inside an async callback!
309
+ const dialog = inject(DialogService);
310
+ dialog.open();
311
+ });
312
+ });
313
+ }
314
+ }
315
+ ```
316
+
317
+ ### With Explicit Injector
318
+
319
+ You can also completely bypass the ambient capture and provide an `Injector` explicitly:
320
+
321
+ ```typescript
322
+ // Outside of normal injection context
323
+ const runner = createRunInInjectionContext(appRef.injector);
324
+
325
+ runner(() => {
326
+ const router = inject(Router);
327
+ router.navigate(['/home']);
328
+ });
329
+ ```
330
+
331
+ ---
332
+
240
333
  ## rootInjectable
241
334
 
242
335
  Creates a lazily-initialized root-level injectable that maintains a singleton instance across your entire application. The factory function runs in the root injection context on first access, allowing you to inject other dependencies.
@@ -1,16 +1,41 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, Injector, runInInjectionContext, Injectable, InjectionToken } from '@angular/core';
3
3
 
4
+ /**
5
+ * Captures an injection context and returns a runner function.
6
+ *
7
+ * This runner function allows you to execute callbacks inside the captured context at a later time.
8
+ * It's really just a slight DX improvement over calling runInInjectionContext over and over
9
+ *
10
+ * @param passedInjector An optional injector. If not provided, the current context's injector is pulled natively via `inject(Injector)`.
11
+ * @returns A runner function that executes any provided callback within the captured context.
12
+ */
13
+ function createRunInInjectionContext(injector) {
14
+ if (!injector)
15
+ injector = inject(Injector);
16
+ return (fn) => runInInjectionContext(injector, fn);
17
+ }
18
+
4
19
  class ScopeRegistry {
5
20
  injector = inject(Injector);
6
21
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
7
22
  registry = new Map();
8
- getOrCreate(factory) {
23
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
24
+ resolving = new Set();
25
+ getOrCreate(factory, scopeName, factoryName) {
9
26
  if (this.registry.has(factory))
10
27
  return this.registry.get(factory);
11
- const val = runInInjectionContext(this.injector, factory);
12
- this.registry.set(factory, val);
13
- return val;
28
+ if (this.resolving.has(factory))
29
+ throw new Error(`[mmstack/di]: Circular dependency detected in scope "${scopeName ?? 'unknown'}"${factoryName ? ` while resolving "${factoryName}"` : ''}`);
30
+ this.resolving.add(factory);
31
+ try {
32
+ const val = runInInjectionContext(this.injector, factory);
33
+ this.registry.set(factory, val);
34
+ return val;
35
+ }
36
+ finally {
37
+ this.resolving.delete(factory);
38
+ }
14
39
  }
15
40
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ScopeRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
16
41
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ScopeRegistry });
@@ -61,17 +86,32 @@ function createScope(name) {
61
86
  provide: token,
62
87
  useClass: ScopeRegistry,
63
88
  });
64
- const registerFn = (factory) => {
89
+ const registerFn = (factory, factoryName) => {
65
90
  return () => {
66
91
  const registry = inject(token, { optional: true });
67
92
  if (!registry)
68
93
  throw new Error(`[mmstack/di]: Scope ${name ?? 'unknown'} not found. Please make sure you provide it`);
69
- return registry.getOrCreate(factory);
94
+ return registry.getOrCreate(factory, name, factoryName);
70
95
  };
71
96
  };
72
97
  return [registerFn, provideFn];
73
98
  }
74
99
 
100
+ const UNINITIALIZED_SYMBOL = Symbol('@mmstack/di/inject-lazy/uninitialized');
101
+ function injectLazy(token, options) {
102
+ const injector = inject(Injector);
103
+ let instance = UNINITIALIZED_SYMBOL;
104
+ return () => {
105
+ if (instance === UNINITIALIZED_SYMBOL)
106
+ instance = runInInjectionContext(injector, () => {
107
+ return options
108
+ ? inject(token, options)
109
+ : inject(token);
110
+ });
111
+ return instance;
112
+ };
113
+ }
114
+
75
115
  function injectable(tokenOrFn, optOrString) {
76
116
  const token = typeof tokenOrFn === 'string'
77
117
  ? tokenOrFn
@@ -117,7 +157,7 @@ function injectable(tokenOrFn, optOrString) {
117
157
  }
118
158
 
119
159
  /**
120
- * Creates a tree-shakable root singleton without a class.
160
+ * Creates a root-level singleton hooked into the global injector.
121
161
  * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));
122
162
  */
123
163
  function rootInjectable(factory, // Keeping the injector just in case
@@ -133,5 +173,5 @@ name) {
133
173
  * Generated bundle index. Do not edit.
134
174
  */
135
175
 
136
- export { createScope, injectable, rootInjectable };
176
+ export { createRunInInjectionContext, createScope, injectLazy, injectable, rootInjectable };
137
177
  //# sourceMappingURL=mmstack-di.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mmstack-di.mjs","sources":["../../../../packages/di/src/lib/create-scope.ts","../../../../packages/di/src/lib/injectable.ts","../../../../packages/di/src/lib/root-injectable.ts","../../../../packages/di/src/mmstack-di.ts"],"sourcesContent":["import {\n inject,\n Injectable,\n InjectionToken,\n Injector,\n runInInjectionContext,\n type Provider,\n} from '@angular/core';\n\n@Injectable()\nexport class ScopeRegistry {\n private readonly injector = inject(Injector);\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n private readonly registry = new Map<Function, any>();\n\n getOrCreate<T>(factory: () => T): T {\n if (this.registry.has(factory)) return this.registry.get(factory);\n\n const val = runInInjectionContext(this.injector, factory);\n this.registry.set(factory, val);\n return val;\n }\n}\n\n/**\n * Creates a specialized dependency injection scope.\n *\n * This utility allows you to create a localized dependency injection scope where you can\n * register and provide shared state, services, or primitives that are bound to a specific\n * component tree instead of the global root injector.\n *\n * @param name Optional name for the scope, primarily used for debugging and error messages.\n * @returns A tuple containing `[registerFn, provideFn]`:\n * - `registerFn`: A function to register a factory within the scope. Returns an injection function to retrieve the value.\n * - `provideFn`: An Angular provider function that must be added to the `providers` array where the scope begins.\n *\n * @example\n * ```ts\n * const [registerInUserScope, provideUserScope] = createScope('UserScope');\n *\n * // Define a state/service bound to this scope\n * const injectUserState = registerInUserScope(() => signal({ name: 'John Doe' }));\n * const injectLogger = registerInUserScope(() => {\n * const globalLogger = inject(GlobalLogger);\n * const user = injectUserState();\n * return {\n * log: (msg: string) => globalLogger.log(`[USER MODULE (${user().name})]: ${msg}`),\n * }\n * })\n * @Component({\n * providers: [provideUserScope()] // provides a new instance of every dependency registered to the scope\n * })\n * class ParentComponent {}\n *\n * @Component({})\n * class ChildComponent {\n * readonly userState = injectUserState();\n * readonly logger = injectLogger();\n * }\n * ```\n */\nexport function createScope(name?: string) {\n const token = new InjectionToken<ScopeRegistry>(name ?? '@mmstack/di/scope');\n\n const provideFn = (): Provider => ({\n provide: token,\n useClass: ScopeRegistry,\n });\n\n const registerFn = <T>(factory: () => T) => {\n return () => {\n const registry = inject(token, { optional: true });\n if (!registry)\n throw new Error(\n `[mmstack/di]: Scope ${name ?? 'unknown'} not found. Please make sure you provide it`,\n );\n\n return registry.getOrCreate(factory);\n };\n };\n\n return [registerFn, provideFn] as const;\n}\n","import {\n inject,\n InjectionToken,\n type AbstractType,\n type InjectOptions,\n type Provider,\n type Type,\n} from '@angular/core';\n\ntype ServiceType<T> =\n T extends Type<infer U>\n ? U\n : T extends AbstractType<infer U>\n ? U\n : T extends InjectionToken<infer U>\n ? U\n : never;\n\ntype MapDeps<T extends readonly any[]> = {\n [K in keyof T]: ServiceType<T[K]>;\n};\n\ntype ProviderFn<T> = {\n (value: T): Provider;\n <const TDeps extends any[]>(\n fn: (...deps: MapDeps<TDeps>) => T,\n deps: TDeps,\n ): Provider;\n};\n\ntype InjectFns<T> = [\n (opt?: Omit<InjectOptions, 'optional'>) => T,\n ProviderFn<T>,\n];\n\ntype FallbackInjectableOptions<T> = {\n /** Default value returned when the injectable is not provided */\n fallback: T;\n};\n\ntype LazyFallbackInjectableOptions<T> = {\n /** Function that returns a default value when the injectable is not provided. Useful for expensive defaults. */\n lazyFallback: () => T;\n};\n\ntype ErrorMessageInjectableOptions = {\n /** Error message thrown when the injectable is not provided */\n errorMessage: string;\n};\n\ntype InjectableOptions<T> =\n | FallbackInjectableOptions<T>\n | LazyFallbackInjectableOptions<T>\n | ErrorMessageInjectableOptions;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n *\n * @param token - Unique token identifier\n * @param opt - Optional configuration for fallback value or error message\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(token: string): InjectFns<T | null>;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Returns a fallback value when the injectable is not provided.\n *\n * @param token - Unique token identifier\n * @param opt - Configuration with fallback value\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(\n token: string,\n opt: FallbackInjectableOptions<T>,\n): InjectFns<T>;\n\n/**\n *\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Returns a lazily evaluated fallback value when the injectable is not provided.\n *\n * @param token\n * @param opt\n */\nexport function injectable<T>(\n token: string,\n opt: LazyFallbackInjectableOptions<T>,\n): InjectFns<T>;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Throws an error with a custom message when the injectable is not provided.\n *\n * @param token - Unique token identifier\n * @param opt - Configuration with error message\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(\n token: string,\n opt: ErrorMessageInjectableOptions,\n): InjectFns<T>;\n\n/**\n * Creates an injectable with a baked-in factory.\n * @example const [injectUser, provideUser] = injectable(() => inject(HttpClient).get('/user'), 'UserToken');\n */\nexport function injectable<T>(fn: () => T, name?: string): InjectFns<T>;\n\nexport function injectable<T>(\n tokenOrFn: string | (() => T),\n optOrString?: InjectableOptions<T> | string,\n): InjectFns<T> {\n const token =\n typeof tokenOrFn === 'string'\n ? tokenOrFn\n : typeof optOrString === 'string'\n ? optOrString\n : '@mmstack/di/injectable';\n\n const opt =\n typeof tokenOrFn === 'function'\n ? { lazyFallback: tokenOrFn }\n : typeof optOrString === 'string'\n ? undefined\n : optOrString;\n\n const injectionToken = new InjectionToken<T>(token);\n\n const options = opt as\n | Partial<\n FallbackInjectableOptions<T> &\n LazyFallbackInjectableOptions<T> &\n ErrorMessageInjectableOptions\n >\n | undefined;\n\n let fallback: T | undefined | null = options?.fallback;\n\n const initFallback =\n options?.lazyFallback ?? (() => options?.fallback ?? null);\n\n const fallbackFn = () => {\n if (fallback === undefined) fallback = initFallback();\n return fallback;\n };\n\n const injectFn = (iOpt?: Omit<InjectOptions, 'optional'>) => {\n const injected =\n inject(injectionToken, {\n ...iOpt,\n optional: true,\n }) ?? fallbackFn();\n\n if (injected === null && options?.errorMessage)\n throw new Error(options.errorMessage);\n\n return injected as T;\n };\n\n const provideFn = (\n fnOrValue: T | ((...deps: any[]) => T),\n deps?: any[],\n ): Provider => {\n if (deps !== undefined)\n return {\n provide: injectionToken,\n useFactory: fnOrValue as (...args: any[]) => T,\n deps,\n };\n\n return {\n provide: injectionToken,\n useValue: fnOrValue,\n };\n };\n\n return [injectFn, provideFn];\n}\n","import { inject, InjectionToken, Injector } from '@angular/core';\n\n/**\n * Creates a tree-shakable root singleton without a class.\n * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));\n */\nexport function rootInjectable<T>(\n factory: (injector: Injector) => T, // Keeping the injector just in case\n name?: string,\n): () => T {\n const token = new InjectionToken<T>(name ?? '@mmstack/di/root-injectable', {\n providedIn: 'root',\n factory: () => factory(inject(Injector)),\n });\n\n return () => inject(token);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mmstack-di.mjs","sources":["../../../../packages/di/src/lib/create-run-in-injection-context.ts","../../../../packages/di/src/lib/create-scope.ts","../../../../packages/di/src/lib/inject-lazy.ts","../../../../packages/di/src/lib/injectable.ts","../../../../packages/di/src/lib/root-injectable.ts","../../../../packages/di/src/mmstack-di.ts"],"sourcesContent":["import { inject, Injector, runInInjectionContext } from '@angular/core';\n\n/**\n * Captures an injection context and returns a runner function.\n *\n * This runner function allows you to execute callbacks inside the captured context at a later time.\n * It's really just a slight DX improvement over calling runInInjectionContext over and over\n *\n * @param passedInjector An optional injector. If not provided, the current context's injector is pulled natively via `inject(Injector)`.\n * @returns A runner function that executes any provided callback within the captured context.\n */\n\nexport function createRunInInjectionContext(injector?: Injector) {\n if (!injector) injector = inject(Injector);\n\n return <T>(fn: () => T) => runInInjectionContext(injector, fn);\n}\n","import {\n inject,\n Injectable,\n InjectionToken,\n Injector,\n runInInjectionContext,\n type Provider,\n} from '@angular/core';\n\n@Injectable()\nclass ScopeRegistry {\n private readonly injector = inject(Injector);\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n private readonly registry = new Map<Function, any>();\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n private readonly resolving = new Set<Function>();\n\n getOrCreate<T>(factory: () => T, scopeName?: string, factoryName?: string): T {\n if (this.registry.has(factory)) return this.registry.get(factory);\n if (this.resolving.has(factory))\n throw new Error(\n `[mmstack/di]: Circular dependency detected in scope \"${scopeName ?? 'unknown'}\"${factoryName ? ` while resolving \"${factoryName}\"` : ''}`,\n );\n\n this.resolving.add(factory);\n try {\n const val = runInInjectionContext(this.injector, factory);\n this.registry.set(factory, val);\n return val;\n } finally {\n this.resolving.delete(factory);\n }\n }\n}\n\n/**\n * Creates a specialized dependency injection scope.\n *\n * This utility allows you to create a localized dependency injection scope where you can\n * register and provide shared state, services, or primitives that are bound to a specific\n * component tree instead of the global root injector.\n *\n * @param name Optional name for the scope, primarily used for debugging and error messages.\n * @returns A tuple containing `[registerFn, provideFn]`:\n * - `registerFn`: A function to register a factory within the scope. Returns an injection function to retrieve the value.\n * - `provideFn`: An Angular provider function that must be added to the `providers` array where the scope begins.\n *\n * @example\n * ```ts\n * const [registerInUserScope, provideUserScope] = createScope('UserScope');\n *\n * // Define a state/service bound to this scope\n * const injectUserState = registerInUserScope(() => signal({ name: 'John Doe' }));\n * const injectLogger = registerInUserScope(() => {\n * const globalLogger = inject(GlobalLogger);\n * const user = injectUserState();\n * return {\n * log: (msg: string) => globalLogger.log(`[USER MODULE (${user().name})]: ${msg}`),\n * }\n * })\n * @Component({\n * providers: [provideUserScope()] // provides a new instance of every dependency registered to the scope\n * })\n * class ParentComponent {}\n *\n * @Component({})\n * class ChildComponent {\n * readonly userState = injectUserState();\n * readonly logger = injectLogger();\n * }\n * ```\n */\nexport function createScope(name?: string) {\n const token = new InjectionToken<ScopeRegistry>(name ?? '@mmstack/di/scope');\n\n const provideFn = (): Provider => ({\n provide: token,\n useClass: ScopeRegistry,\n });\n\n const registerFn = <T>(factory: () => T, factoryName?: string) => {\n return () => {\n const registry = inject(token, { optional: true });\n if (!registry)\n throw new Error(\n `[mmstack/di]: Scope ${name ?? 'unknown'} not found. Please make sure you provide it`,\n );\n\n return registry.getOrCreate(factory, name, factoryName);\n };\n };\n\n return [registerFn, provideFn] as const;\n}\n","import {\n inject,\n Injector,\n runInInjectionContext,\n type HostAttributeToken,\n type InjectOptions,\n type ProviderToken,\n} from '@angular/core';\n\nconst UNINITIALIZED_SYMBOL = Symbol('@mmstack/di/inject-lazy/uninitialized');\n\ntype OptionalInjectOptions = Omit<InjectOptions, 'optional'> & {\n optional: true;\n};\n\ntype NonOptionalInjectOptions = Omit<InjectOptions, 'optional'> & {\n optional?: false;\n};\n\n/**\n * Defers the resolution and instantiation of a token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The dependency token to inject.\n * @returns A getter function that returns the lazily resolved dependency.\n */\nexport function injectLazy<T>(token: ProviderToken<T>): () => T;\n\n/**\n * Defers the resolution and instantiation of a token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The dependency token to inject.\n * @param options Injection options specifying optional resolution.\n * @returns A getter function that returns the lazily resolved dependency or null.\n */\nexport function injectLazy<T>(\n token: ProviderToken<T>,\n options: OptionalInjectOptions,\n): () => T | null;\n\n/**\n * Defers the resolution and instantiation of a token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The dependency token to inject.\n * @param options Injection options specifying non-optional resolution.\n * @returns A getter function that returns the lazily resolved dependency.\n */\nexport function injectLazy<T>(\n token: ProviderToken<T>,\n options: NonOptionalInjectOptions,\n): () => T;\n\n/**\n * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The host attribute token to inject.\n * @returns A getter function that returns the lazily resolved attribute string.\n */\nexport function injectLazy(token: HostAttributeToken): () => string;\n\n/**\n * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The host attribute token to inject.\n * @param options Injection options specifying non-optional resolution.\n * @returns A getter function that returns the lazily resolved attribute string.\n */\nexport function injectLazy(\n token: HostAttributeToken,\n options?: {\n optional?: false;\n },\n): () => string;\n\n/**\n * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.\n * The resolved value is cached for subsequent calls.\n *\n * @param token The host attribute token to inject.\n * @param options Injection options specifying optional resolution.\n * @returns A getter function that returns the lazily resolved attribute string or null.\n */\nexport function injectLazy(\n token: HostAttributeToken,\n options: {\n optional: true;\n },\n): () => string | null;\n\nexport function injectLazy<T>(\n token: ProviderToken<T> | HostAttributeToken,\n options?: Partial<OptionalInjectOptions | NonOptionalInjectOptions>,\n): () => T | string | null {\n const injector = inject(Injector);\n let instance: T | string | null | typeof UNINITIALIZED_SYMBOL =\n UNINITIALIZED_SYMBOL;\n\n return () => {\n if (instance === UNINITIALIZED_SYMBOL)\n instance = runInInjectionContext(injector, () => {\n return options\n ? inject(token as any, options as any)\n : inject(token as any);\n });\n\n return instance as T | string | null;\n };\n}\n","import {\n inject,\n InjectionToken,\n type AbstractType,\n type InjectOptions,\n type Provider,\n type Type,\n} from '@angular/core';\n\ntype ServiceType<T> =\n T extends Type<infer U>\n ? U\n : T extends AbstractType<infer U>\n ? U\n : T extends InjectionToken<infer U>\n ? U\n : never;\n\ntype MapDeps<T extends readonly any[]> = {\n [K in keyof T]: ServiceType<T[K]>;\n};\n\ntype ProviderFn<T> = {\n (value: T): Provider;\n <const TDeps extends any[]>(\n fn: (...deps: MapDeps<TDeps>) => T,\n deps: TDeps,\n ): Provider;\n};\n\ntype InjectFns<T> = [\n (opt?: Omit<InjectOptions, 'optional'>) => T,\n ProviderFn<T>,\n];\n\ntype FallbackInjectableOptions<T> = {\n /** Default value returned when the injectable is not provided */\n fallback: T;\n};\n\ntype LazyFallbackInjectableOptions<T> = {\n /** Function that returns a default value when the injectable is not provided. Useful for expensive defaults. */\n lazyFallback: () => T;\n};\n\ntype ErrorMessageInjectableOptions = {\n /** Error message thrown when the injectable is not provided */\n errorMessage: string;\n};\n\ntype InjectableOptions<T> =\n | FallbackInjectableOptions<T>\n | LazyFallbackInjectableOptions<T>\n | ErrorMessageInjectableOptions;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n *\n * @param token - Unique token identifier\n * @param opt - Optional configuration for fallback value or error message\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(token: string): InjectFns<T | null>;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Returns a fallback value when the injectable is not provided.\n *\n * @param token - Unique token identifier\n * @param opt - Configuration with fallback value\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(\n token: string,\n opt: FallbackInjectableOptions<T>,\n): InjectFns<T>;\n\n/**\n *\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Returns a lazily evaluated fallback value when the injectable is not provided.\n *\n * @param token\n * @param opt\n */\nexport function injectable<T>(\n token: string,\n opt: LazyFallbackInjectableOptions<T>,\n): InjectFns<T>;\n\n/**\n * Creates a typed InjectionToken with inject and provide helper functions.\n * Throws an error with a custom message when the injectable is not provided.\n *\n * @param token - Unique token identifier\n * @param opt - Configuration with error message\n * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection\n */\nexport function injectable<T>(\n token: string,\n opt: ErrorMessageInjectableOptions,\n): InjectFns<T>;\n\n/**\n * Creates an injectable with a baked-in factory.\n * @example const [injectUser, provideUser] = injectable(() => inject(HttpClient).get('/user'), 'UserToken');\n */\nexport function injectable<T>(fn: () => T, name?: string): InjectFns<T>;\n\nexport function injectable<T>(\n tokenOrFn: string | (() => T),\n optOrString?: InjectableOptions<T> | string,\n): InjectFns<T> {\n const token =\n typeof tokenOrFn === 'string'\n ? tokenOrFn\n : typeof optOrString === 'string'\n ? optOrString\n : '@mmstack/di/injectable';\n\n const opt =\n typeof tokenOrFn === 'function'\n ? { lazyFallback: tokenOrFn }\n : typeof optOrString === 'string'\n ? undefined\n : optOrString;\n\n const injectionToken = new InjectionToken<T>(token);\n\n const options = opt as\n | Partial<\n FallbackInjectableOptions<T> &\n LazyFallbackInjectableOptions<T> &\n ErrorMessageInjectableOptions\n >\n | undefined;\n\n let fallback: T | undefined | null = options?.fallback;\n\n const initFallback =\n options?.lazyFallback ?? (() => options?.fallback ?? null);\n\n const fallbackFn = () => {\n if (fallback === undefined) fallback = initFallback();\n return fallback;\n };\n\n const injectFn = (iOpt?: Omit<InjectOptions, 'optional'>) => {\n const injected =\n inject(injectionToken, {\n ...iOpt,\n optional: true,\n }) ?? fallbackFn();\n\n if (injected === null && options?.errorMessage)\n throw new Error(options.errorMessage);\n\n return injected as T;\n };\n\n const provideFn = (\n fnOrValue: T | ((...deps: any[]) => T),\n deps?: any[],\n ): Provider => {\n if (deps !== undefined)\n return {\n provide: injectionToken,\n useFactory: fnOrValue as (...args: any[]) => T,\n deps,\n };\n\n return {\n provide: injectionToken,\n useValue: fnOrValue,\n };\n };\n\n return [injectFn, provideFn];\n}\n","import { inject, InjectionToken, Injector } from '@angular/core';\n\n/**\n * Creates a root-level singleton hooked into the global injector.\n * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));\n */\nexport function rootInjectable<T>(\n factory: (injector: Injector) => T, // Keeping the injector just in case\n name?: string,\n): () => T {\n const token = new InjectionToken<T>(name ?? '@mmstack/di/root-injectable', {\n providedIn: 'root',\n factory: () => factory(inject(Injector)),\n });\n\n return () => inject(token);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/di",
3
- "version": "21.0.3",
3
+ "version": "21.0.5",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "dependency injection",
@@ -1,4 +1,15 @@
1
- import { Provider, InjectOptions, Type, AbstractType, InjectionToken, Injector } from '@angular/core';
1
+ import { Injector, Provider, ProviderToken, InjectOptions, HostAttributeToken, Type, AbstractType, InjectionToken } from '@angular/core';
2
+
3
+ /**
4
+ * Captures an injection context and returns a runner function.
5
+ *
6
+ * This runner function allows you to execute callbacks inside the captured context at a later time.
7
+ * It's really just a slight DX improvement over calling runInInjectionContext over and over
8
+ *
9
+ * @param passedInjector An optional injector. If not provided, the current context's injector is pulled natively via `inject(Injector)`.
10
+ * @returns A runner function that executes any provided callback within the captured context.
11
+ */
12
+ declare function createRunInInjectionContext(injector?: Injector): <T>(fn: () => T) => T;
2
13
 
3
14
  /**
4
15
  * Creates a specialized dependency injection scope.
@@ -37,7 +48,70 @@ import { Provider, InjectOptions, Type, AbstractType, InjectionToken, Injector }
37
48
  * }
38
49
  * ```
39
50
  */
40
- declare function createScope(name?: string): readonly [<T>(factory: () => T) => () => T, () => Provider];
51
+ declare function createScope(name?: string): readonly [<T>(factory: () => T, factoryName?: string) => () => T, () => Provider];
52
+
53
+ type OptionalInjectOptions = Omit<InjectOptions, 'optional'> & {
54
+ optional: true;
55
+ };
56
+ type NonOptionalInjectOptions = Omit<InjectOptions, 'optional'> & {
57
+ optional?: false;
58
+ };
59
+ /**
60
+ * Defers the resolution and instantiation of a token until the returned getter function is called.
61
+ * The resolved value is cached for subsequent calls.
62
+ *
63
+ * @param token The dependency token to inject.
64
+ * @returns A getter function that returns the lazily resolved dependency.
65
+ */
66
+ declare function injectLazy<T>(token: ProviderToken<T>): () => T;
67
+ /**
68
+ * Defers the resolution and instantiation of a token until the returned getter function is called.
69
+ * The resolved value is cached for subsequent calls.
70
+ *
71
+ * @param token The dependency token to inject.
72
+ * @param options Injection options specifying optional resolution.
73
+ * @returns A getter function that returns the lazily resolved dependency or null.
74
+ */
75
+ declare function injectLazy<T>(token: ProviderToken<T>, options: OptionalInjectOptions): () => T | null;
76
+ /**
77
+ * Defers the resolution and instantiation of a token until the returned getter function is called.
78
+ * The resolved value is cached for subsequent calls.
79
+ *
80
+ * @param token The dependency token to inject.
81
+ * @param options Injection options specifying non-optional resolution.
82
+ * @returns A getter function that returns the lazily resolved dependency.
83
+ */
84
+ declare function injectLazy<T>(token: ProviderToken<T>, options: NonOptionalInjectOptions): () => T;
85
+ /**
86
+ * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.
87
+ * The resolved value is cached for subsequent calls.
88
+ *
89
+ * @param token The host attribute token to inject.
90
+ * @returns A getter function that returns the lazily resolved attribute string.
91
+ */
92
+ declare function injectLazy(token: HostAttributeToken): () => string;
93
+ /**
94
+ * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.
95
+ * The resolved value is cached for subsequent calls.
96
+ *
97
+ * @param token The host attribute token to inject.
98
+ * @param options Injection options specifying non-optional resolution.
99
+ * @returns A getter function that returns the lazily resolved attribute string.
100
+ */
101
+ declare function injectLazy(token: HostAttributeToken, options?: {
102
+ optional?: false;
103
+ }): () => string;
104
+ /**
105
+ * Defers the resolution and instantiation of a host attribute token until the returned getter function is called.
106
+ * The resolved value is cached for subsequent calls.
107
+ *
108
+ * @param token The host attribute token to inject.
109
+ * @param options Injection options specifying optional resolution.
110
+ * @returns A getter function that returns the lazily resolved attribute string or null.
111
+ */
112
+ declare function injectLazy(token: HostAttributeToken, options: {
113
+ optional: true;
114
+ }): () => string | null;
41
115
 
42
116
  type ServiceType<T> = T extends Type<infer U> ? U : T extends AbstractType<infer U> ? U : T extends InjectionToken<infer U> ? U : never;
43
117
  type MapDeps<T extends readonly any[]> = {
@@ -105,10 +179,10 @@ declare function injectable<T>(token: string, opt: ErrorMessageInjectableOptions
105
179
  declare function injectable<T>(fn: () => T, name?: string): InjectFns<T>;
106
180
 
107
181
  /**
108
- * Creates a tree-shakable root singleton without a class.
182
+ * Creates a root-level singleton hooked into the global injector.
109
183
  * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));
110
184
  */
111
185
  declare function rootInjectable<T>(factory: (injector: Injector) => T, // Keeping the injector just in case
112
186
  name?: string): () => T;
113
187
 
114
- export { createScope, injectable, rootInjectable };
188
+ export { createRunInInjectionContext, createScope, injectLazy, injectable, rootInjectable };