@mmstack/di 21.0.1 → 21.0.2

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
@@ -17,6 +17,7 @@ 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
19
  - `rootInjectable` - Creates a lazily-initialized root-level injectable that maintains a singleton instance.
20
+ - `createScope` - Creates a dependency injection scope that caches singletons based on the factory function.
20
21
 
21
22
  ---
22
23
 
@@ -381,6 +382,49 @@ export class NavbarComponent {
381
382
 
382
383
  ---
383
384
 
385
+ ## createScope
386
+
387
+ Creates a dependency injection scope using a dynamic `InjectionToken` representing a caching registry. Factories executed within the scope run in the Angular injection context and their results are cached, effectively creating scoped singletons, that are destroyed when the scoped provider is. It returns a tuple of `[injectable, provider]`.
388
+
389
+ ### Basic Usage
390
+
391
+ ```typescript
392
+ import { Component, inject } from '@angular/core';
393
+ import { createScope } from '@mmstack/di';
394
+
395
+ // Create the scope
396
+ const [injectableFeatureItem, provideFeatureScope] = createScope('FeatureScope');
397
+
398
+ // Provide the scope at a specific component level boundary
399
+ @Component({
400
+ selector: 'app-feature',
401
+ providers: [provideFeatureScope()],
402
+ template: `<app-child></app-child>`,
403
+ })
404
+ export class FeatureComponent {}
405
+
406
+ // Use the scope to register an item factory
407
+ // The factory will run in the injection context so you can use inject()
408
+ const useFeatureItem = injectableFeatureItem(() => {
409
+ const someDep = inject(SomeDependency);
410
+ return {
411
+ id: Math.random(),
412
+ doWork: () => someDep.work(),
413
+ };
414
+ });
415
+
416
+ @Component({
417
+ selector: 'app-child',
418
+ template: `<div>Child Item ID: {{ item.id }}</div>`,
419
+ })
420
+ export class ChildComponent {
421
+ // Always returns the exact same instance for this specific scope provider boundary
422
+ item = useFeatureItem();
423
+ }
424
+ ```
425
+
426
+ ---
427
+
384
428
  ## License
385
429
 
386
430
  MIT © [Miha Mulec](https://github.com/mihajm)
@@ -1,7 +1,51 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injector, runInInjectionContext, Injectable } from '@angular/core';
2
+ import { inject, Injector, runInInjectionContext, Injectable, InjectionToken } from '@angular/core';
3
3
 
4
- function injectable(token, opt) {
4
+ class ScopeRegistry {
5
+ injector = inject(Injector);
6
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
7
+ registry = new Map();
8
+ getOrCreate(factory) {
9
+ if (this.registry.has(factory))
10
+ return this.registry.get(factory);
11
+ const val = runInInjectionContext(this.injector, factory);
12
+ this.registry.set(factory, val);
13
+ return val;
14
+ }
15
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ScopeRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
16
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ScopeRegistry });
17
+ }
18
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ScopeRegistry, decorators: [{
19
+ type: Injectable
20
+ }] });
21
+ function createScope(name) {
22
+ const token = new InjectionToken(name ?? '@mmstack/di/scope');
23
+ const provideFn = () => ({
24
+ provide: token,
25
+ useClass: ScopeRegistry,
26
+ });
27
+ const registerFn = (factory) => {
28
+ return () => {
29
+ const registry = inject(token, { optional: true });
30
+ if (!registry)
31
+ throw new Error(`[mmstack/di]: Scope ${name ?? 'unknown'} not found. Please make sure you provide it`);
32
+ return registry.getOrCreate(factory);
33
+ };
34
+ };
35
+ return [registerFn, provideFn];
36
+ }
37
+
38
+ function injectable(tokenOrFn, optOrString) {
39
+ const token = typeof tokenOrFn === 'string'
40
+ ? tokenOrFn
41
+ : typeof optOrString === 'string'
42
+ ? optOrString
43
+ : '@mmstack/di/injectable';
44
+ const opt = typeof tokenOrFn === 'function'
45
+ ? { lazyFallback: tokenOrFn }
46
+ : typeof optOrString === 'string'
47
+ ? undefined
48
+ : optOrString;
5
49
  const injectionToken = new InjectionToken(token);
6
50
  const options = opt;
7
51
  let fallback = options?.fallback;
@@ -35,59 +79,22 @@ function injectable(token, opt) {
35
79
  return [injectFn, provideFn];
36
80
  }
37
81
 
38
- function generateID() {
39
- if (globalThis.crypto?.randomUUID) {
40
- return globalThis.crypto.randomUUID();
41
- }
42
- return Math.random().toString(36).substring(2);
43
- }
44
- class RootInjectables {
45
- injector = inject(Injector);
46
- registry = {};
47
- register(register) {
48
- let key = generateID();
49
- while (this.registry[key]) {
50
- key = generateID();
51
- }
52
- const injector = this.injector;
53
- const value = runInInjectionContext(this.injector, () => register(injector));
54
- this.registry[key] = value;
55
- return key;
56
- }
57
- get(key) {
58
- return this.registry[key];
59
- }
60
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootInjectables, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
61
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootInjectables, providedIn: 'root' });
62
- }
63
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: RootInjectables, decorators: [{
64
- type: Injectable,
65
- args: [{
66
- providedIn: 'root',
67
- }]
68
- }] });
69
82
  /**
70
- * Creates a lazily-initialized root-level injectable that maintains a singleton instance.
71
- * The factory function runs in the root injection context on first access.
72
- * This should only be used for pure singletons, if you need scoped instances use regular @Injectable services.
73
- *
74
- * @param register - Factory function that creates the injectable instance using the root injector
75
- * @returns An inject function that returns the singleton instance
83
+ * Creates a tree-shakable root singleton without a class.
84
+ * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));
76
85
  */
77
- function rootInjectable(register) {
78
- let key = null;
79
- return () => {
80
- const registry = inject(RootInjectables);
81
- if (!key) {
82
- key = registry.register(register);
83
- }
84
- return registry.get(key);
85
- };
86
+ function rootInjectable(factory, // Keeping the injector just in case
87
+ name) {
88
+ const token = new InjectionToken(name ?? '@mmstack/di/root-injectable', {
89
+ providedIn: 'root',
90
+ factory: () => factory(inject(Injector)),
91
+ });
92
+ return () => inject(token);
86
93
  }
87
94
 
88
95
  /**
89
96
  * Generated bundle index. Do not edit.
90
97
  */
91
98
 
92
- export { injectable, rootInjectable };
99
+ export { createScope, injectable, rootInjectable };
93
100
  //# sourceMappingURL=mmstack-di.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mmstack-di.mjs","sources":["../../../../packages/di/src/lib/injectable.ts","../../../../packages/di/src/lib/root-injectable.ts","../../../../packages/di/src/mmstack-di.ts"],"sourcesContent":["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\nexport function injectable<T>(\n token: string,\n opt?: InjectableOptions<T>,\n): InjectFns<T> {\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 {\n inject,\n Injectable,\n Injector,\n runInInjectionContext,\n} from '@angular/core';\n\nfunction generateID() {\n if (globalThis.crypto?.randomUUID) {\n return globalThis.crypto.randomUUID();\n }\n return Math.random().toString(36).substring(2);\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class RootInjectables {\n private readonly injector = inject(Injector);\n private readonly registry: Record<string, any> = {};\n\n register<T>(register: (injector: Injector) => T): string {\n let key = generateID();\n\n while (this.registry[key]) {\n key = generateID();\n }\n\n const injector = this.injector;\n\n const value = runInInjectionContext(this.injector, () =>\n register(injector),\n );\n this.registry[key] = value;\n\n return key;\n }\n\n get<T>(key: string): T {\n return this.registry[key];\n }\n}\n\n/**\n * Creates a lazily-initialized root-level injectable that maintains a singleton instance.\n * The factory function runs in the root injection context on first access.\n * This should only be used for pure singletons, if you need scoped instances use regular @Injectable services.\n *\n * @param register - Factory function that creates the injectable instance using the root injector\n * @returns An inject function that returns the singleton instance\n */\nexport function rootInjectable<T>(\n register: (injector: Injector) => T,\n): () => T {\n let key: string | null = null;\n return () => {\n const registry = inject(RootInjectables);\n if (!key) {\n key = registry.register(register);\n }\n return registry.get<T>(key);\n };\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-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\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/di",
3
- "version": "21.0.1",
3
+ "version": "21.0.2",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "dependency injection",
@@ -12,7 +12,8 @@
12
12
  "license": "MIT",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "https://github.com/mihajm/mmstack"
15
+ "url": "git+https://github.com/mihajm/mmstack.git",
16
+ "directory": "packages/di"
16
17
  },
17
18
  "homepage": "https://github.com/mihajm/mmstack/blob/master/packages/di",
18
19
  "peerDependencies": {
@@ -1,4 +1,6 @@
1
- import { InjectOptions, Provider, Type, AbstractType, InjectionToken, Injector } from '@angular/core';
1
+ import { Provider, InjectOptions, Type, AbstractType, InjectionToken, Injector } from '@angular/core';
2
+
3
+ declare function createScope(name?: string): readonly [<T>(factory: () => T) => () => T, () => Provider];
2
4
 
3
5
  type ServiceType<T> = T extends Type<infer U> ? U : T extends AbstractType<infer U> ? U : T extends InjectionToken<infer U> ? U : never;
4
6
  type MapDeps<T extends readonly any[]> = {
@@ -59,15 +61,17 @@ declare function injectable<T>(token: string, opt: LazyFallbackInjectableOptions
59
61
  * @returns A tuple of [injectFn, provideFn] for type-safe dependency injection
60
62
  */
61
63
  declare function injectable<T>(token: string, opt: ErrorMessageInjectableOptions): InjectFns<T>;
64
+ /**
65
+ * Creates an injectable with a baked-in factory.
66
+ * @example const [injectUser, provideUser] = injectable(() => inject(HttpClient).get('/user'), 'UserToken');
67
+ */
68
+ declare function injectable<T>(fn: () => T, name?: string): InjectFns<T>;
62
69
 
63
70
  /**
64
- * Creates a lazily-initialized root-level injectable that maintains a singleton instance.
65
- * The factory function runs in the root injection context on first access.
66
- * This should only be used for pure singletons, if you need scoped instances use regular @Injectable services.
67
- *
68
- * @param register - Factory function that creates the injectable instance using the root injector
69
- * @returns An inject function that returns the singleton instance
71
+ * Creates a tree-shakable root singleton without a class.
72
+ * @example const injectUser = rootInjectable(() => ({ name: signal('John') }));
70
73
  */
71
- declare function rootInjectable<T>(register: (injector: Injector) => T): () => T;
74
+ declare function rootInjectable<T>(factory: (injector: Injector) => T, // Keeping the injector just in case
75
+ name?: string): () => T;
72
76
 
73
- export { injectable, rootInjectable };
77
+ export { createScope, injectable, rootInjectable };