composed-di 0.2.9 → 0.3.0

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 (62) hide show
  1. package/README.md +182 -141
  2. package/dist/errors.d.ts +17 -0
  3. package/dist/errors.d.ts.map +1 -0
  4. package/dist/errors.js +26 -0
  5. package/dist/errors.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/serviceFactory.d.ts +3 -2
  11. package/dist/serviceFactory.d.ts.map +1 -1
  12. package/dist/serviceFactory.js +22 -7
  13. package/dist/serviceFactory.js.map +1 -1
  14. package/dist/serviceFactoryWrapper.d.ts +2 -0
  15. package/dist/serviceFactoryWrapper.d.ts.map +1 -0
  16. package/dist/serviceFactoryWrapper.js +16 -0
  17. package/dist/serviceFactoryWrapper.js.map +1 -0
  18. package/dist/serviceKey.d.ts +84 -0
  19. package/dist/serviceKey.d.ts.map +1 -1
  20. package/dist/serviceKey.js +83 -2
  21. package/dist/serviceKey.js.map +1 -1
  22. package/dist/serviceModule.d.ts +25 -4
  23. package/dist/serviceModule.d.ts.map +1 -1
  24. package/dist/serviceModule.js +106 -15
  25. package/dist/serviceModule.js.map +1 -1
  26. package/dist/serviceSelector.d.ts +64 -0
  27. package/dist/serviceSelector.d.ts.map +1 -0
  28. package/dist/serviceSelector.js +69 -0
  29. package/dist/serviceSelector.js.map +1 -0
  30. package/dist/test-service-selector.d.ts +2 -0
  31. package/dist/test-service-selector.d.ts.map +1 -0
  32. package/dist/test-service-selector.js +110 -0
  33. package/dist/test-service-selector.js.map +1 -0
  34. package/dist/utils.d.ts +33 -6
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +100 -6
  37. package/dist/utils.js.map +1 -1
  38. package/package.json +45 -41
  39. package/src/errors.ts +23 -0
  40. package/src/index.ts +2 -0
  41. package/src/serviceFactory.ts +104 -83
  42. package/src/serviceKey.ts +95 -8
  43. package/src/serviceModule.ts +223 -123
  44. package/src/serviceScope.ts +7 -7
  45. package/src/serviceSelector.ts +68 -0
  46. package/src/utils.ts +131 -6
  47. package/dist/main.d.ts +0 -2
  48. package/dist/main.d.ts.map +0 -1
  49. package/dist/main.js +0 -15
  50. package/dist/main.js.map +0 -1
  51. package/dist/plugin/index.d.ts +0 -66
  52. package/dist/plugin/index.d.ts.map +0 -1
  53. package/dist/plugin/index.js +0 -397
  54. package/dist/plugin/index.js.map +0 -1
  55. package/dist/serviceProvider.d.ts +0 -5
  56. package/dist/serviceProvider.d.ts.map +0 -1
  57. package/dist/serviceProvider.js +0 -3
  58. package/dist/serviceProvider.js.map +0 -1
  59. package/dist/test-from.d.ts +0 -2
  60. package/dist/test-from.d.ts.map +0 -1
  61. package/dist/test-from.js +0 -68
  62. package/dist/test-from.js.map +0 -1
package/README.md CHANGED
@@ -1,141 +1,182 @@
1
- # lazy-di
2
-
3
- A tiny, type-friendly dependency injection helper for composing services via keys and factories.
4
-
5
- It provides:
6
- - ServiceKey<T>: a typed token used to identify a service
7
- - ServiceFactory<T>: a contract to create a service (with static methods `singleton` and `oneShot`)
8
- - ServiceModule: a resolver that wires factories, validates dependencies, and provides services
9
-
10
- ServiceModule will:
11
- - detect recursive dependencies (a factory that depends on its own key)
12
- - detect missing dependencies (a factory that depends on keys that have no factory)
13
-
14
- ## Install
15
-
16
- Install from npm:
17
-
18
- - npm: `npm install composed-di`
19
- - pnpm: `pnpm add composed-di`
20
- - yarn: `yarn add composed-di`
21
-
22
- If you're working on this repo locally, build artifacts are generated under `dist/` by running:
23
-
24
- ```
25
- npm run build
26
- ```
27
-
28
- ## Usage
29
-
30
- Below is a minimal example using the public API.
31
-
32
- ```ts
33
- import {
34
- ServiceKey,
35
- ServiceModule,
36
- ServiceFactory,
37
- } from 'composed-di'; // when developing this repo locally, import from './src'
38
-
39
- // 1) Define service types
40
- interface Config {
41
- baseUrl: string;
42
- }
43
-
44
- interface Logger {
45
- info: (msg: string) => void;
46
- }
47
-
48
- class App {
49
- constructor(private config: Config, private logger: Logger) {}
50
- start() {
51
- this.logger.info(`Starting with baseUrl=${this.config.baseUrl}`);
52
- }
53
- }
54
-
55
- // 2) Create keys
56
- const ConfigKey = new ServiceKey<Config>('Config');
57
- const LoggerKey = new ServiceKey<Logger>('Logger');
58
- const AppKey = new ServiceKey<App>('App');
59
-
60
- // 3) Create factories (singleton or one-shot)
61
- const configFactory = ServiceFactory.singleton({
62
- provides: ConfigKey,
63
- dependsOn: [],
64
- initialize: () => {
65
- return { baseUrl: 'https://api.example.com' } satisfies Config;
66
- },
67
- });
68
-
69
- const loggerFactory = ServiceFactory.singleton({
70
- provides: LoggerKey,
71
- dependsOn: [],
72
- initialize: () => {
73
- return console as unknown as Logger;
74
- },
75
- });
76
-
77
- const appFactory = ServiceFactory.oneShot({
78
- provides: AppKey,
79
- dependsOn: [ConfigKey, LoggerKey],
80
- initialize: (config, logger) => {
81
- return new App(config, logger);
82
- },
83
- });
84
-
85
- // 4) Compose a module (you can pass factories and/or other ServiceModule instances)
86
- const module = ServiceModule.from([configFactory, loggerFactory, appFactory]);
87
-
88
- // 5) Resolve and use
89
- (async () => {
90
- const app = await module.get(AppKey);
91
- app.start();
92
- })();
93
- ```
94
-
95
- Notes:
96
- - `ServiceModule.get` resolves dependencies recursively, so factories can depend on other services.
97
- - If a dependency is missing or recursive, `ServiceModule` throws with a helpful error message.
98
-
99
- ## Visualizing Dependencies
100
-
101
- The library includes utilities to generate a DOT graph representation of your service dependencies, which can be visualized using Graphviz tools.
102
-
103
- ```ts
104
- import { ServiceModule, printDotGraph, createDotGraph } from 'composed-di';
105
-
106
- // After creating your ServiceModule
107
- const module = ServiceModule.from([configFactory, loggerFactory, appFactory]);
108
-
109
- // Option 1: Print the DOT graph to console with instructions
110
- printDotGraph(module);
111
-
112
- // Option 2: Generate DOT graph with custom options
113
- const dotGraph = createDotGraph(module, {
114
- direction: 'LR', // 'TB' (top-bottom), 'LR' (left-right), 'BT', 'RL'
115
- title: 'My Service Dependencies',
116
- highlightLeaves: true, // Highlight services with no dependencies (green)
117
- highlightRoots: true, // Highlight services with no dependents (orange)
118
- });
119
- console.log(dotGraph);
120
- ```
121
-
122
- The generated DOT notation can be visualized using:
123
- - [GraphvizOnline](https://dreampuf.github.io/GraphvizOnline/)
124
- - [Edotor](https://edotor.net/)
125
-
126
- ## API
127
-
128
- - `class ServiceKey<T>(name: string)` - Creates a typed token to identify a service
129
- - `abstract class ServiceFactory<T, D extends readonly ServiceKey<unknown>[]>` with:
130
- - `provides: ServiceKey<T>` - The service key this factory provides
131
- - `dependsOn: D` - Array of service keys this factory depends on
132
- - `initialize(...dependencies)` - Creates the service instance
133
- - `dispose(instance)` - Cleans up the service instance
134
- - `static singleton({ provides, dependsOn?, initialize, dispose? })` - Creates a singleton factory
135
- - `static oneShot({ provides, dependsOn, initialize, dispose? })` - Creates a one-shot factory
136
- - `class ServiceModule` with:
137
- - `static from(factoriesOrModules)` - Creates a module from factories and/or other modules
138
- - `async get(key)` - Resolves and returns a service by its key
139
- - `createDotGraph(module, options?)` - Generates DOT notation graph from a ServiceModule
140
- - `printDotGraph(module)` - Prints DOT graph to console with visualization instructions
141
-
1
+ # lazy-di
2
+
3
+ A lightweight, lazy, and typesafe dependency injection library for TypeScript.
4
+
5
+ ## Features
6
+
7
+ - **Lazy Initialization**: Services are only created when they are actually needed.
8
+ - **Type-Safe**: Full TypeScript support with typed keys and dependency resolution.
9
+ - **Circular Dependency Detection**: Validates your dependency graph at module creation.
10
+ - **Flexible Scoping**: Support for singletons, transient (one-shot) services, and custom scopes.
11
+ - **Runtime Selection**: Dynamically choose between multiple implementations of the same interface.
12
+ - **Async Support**: Native support for asynchronous service initialization.
13
+ - **Visualization**: Built-in support for generating Mermaid and DOT diagrams of your dependency graph.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install lazy-di
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Define your Service Keys
24
+
25
+ Service keys are typed tokens that identify your services. They ensure type safety when injecting and retrieving services.
26
+
27
+ ```typescript
28
+ import { ServiceKey } from 'lazy-di';
29
+
30
+ interface Database {
31
+ query: (sql: string) => Promise<any>;
32
+ }
33
+
34
+ export const DatabaseKey = new ServiceKey<Database>('Database');
35
+
36
+ interface UserService {
37
+ getUser: (id: string) => Promise<any>;
38
+ }
39
+
40
+ export const UserServiceKey = new ServiceKey<UserService>('UserService');
41
+ ```
42
+
43
+ ### 2. Create Service Factories
44
+
45
+ Factories define how services are created and what they depend on. `lazy-di` supports both singleton (created once) and one-shot (created every time) services.
46
+
47
+ ```typescript
48
+ import { ServiceFactory } from 'lazy-di';
49
+ import { DatabaseKey, UserServiceKey } from './keys';
50
+
51
+ const databaseFactory = ServiceFactory.singleton({
52
+ provides: DatabaseKey,
53
+ initialize: () => {
54
+ console.log('Initializing Database...');
55
+ return {
56
+ query: async (sql) => ({ id: '1', name: 'John Doe' }),
57
+ };
58
+ },
59
+ });
60
+
61
+ const userServiceFactory = ServiceFactory.singleton({
62
+ provides: UserServiceKey,
63
+ dependsOn: [DatabaseKey],
64
+ initialize: (db) => {
65
+ // db is automatically typed as Database
66
+ return {
67
+ getUser: (id) => db.query(`SELECT * FROM users WHERE id = ${id}`),
68
+ };
69
+ },
70
+ });
71
+ ```
72
+
73
+ ### 3. Create a Service Module and Get Services
74
+
75
+ A `ServiceModule` aggregates factories and manages their lifecycle.
76
+
77
+ ```typescript
78
+ import { ServiceModule } from 'lazy-di';
79
+
80
+ const module = ServiceModule.from([
81
+ databaseFactory,
82
+ userServiceFactory
83
+ ]);
84
+
85
+ // At this point, no services have been initialized.
86
+
87
+ // This will initialize Database and then UserService lazily.
88
+ const userService = await module.get(UserServiceKey);
89
+ const user = await userService.getUser('1');
90
+ ```
91
+
92
+ ## Public API
93
+
94
+ ### `ServiceKey<T>`
95
+
96
+ A unique identifier for a service of type `T`.
97
+
98
+ ```typescript
99
+ const MyKey = new ServiceKey<MyInterface>('MyService');
100
+ ```
101
+
102
+ ### `ServiceFactory`
103
+
104
+ #### `ServiceFactory.singleton(options)`
105
+ Creates a factory for a service that is instantiated only once.
106
+
107
+ - `provides`: The `ServiceKey` this factory satisfies.
108
+ - `dependsOn`: (Optional) An array of `ServiceKey`s this service depends on.
109
+ - `initialize`: A function that creates the service instance. It receives the resolved dependencies as arguments. Can return a Promise.
110
+ - `dispose`: (Optional) A function called when the service is disposed.
111
+ - `scope`: (Optional) A `ServiceScope` for grouping services.
112
+
113
+ #### `ServiceFactory.oneShot(options)`
114
+ Creates a factory for a service that is instantiated every time it is requested.
115
+
116
+ ### `ServiceModule`
117
+
118
+ #### `ServiceModule.from(entries)`
119
+ Creates a module from an array of factories or other `ServiceModule` instances. It automatically detects circular dependencies and missing dependencies.
120
+
121
+ #### `module.get(key)`
122
+ Retrieves a service instance. Returns a `Promise<T>`.
123
+
124
+ #### `module.dispose(scope?)`
125
+ Disposes of services. If a `scope` is provided, only services in that scope are disposed.
126
+
127
+ ### `ServiceSelectorKey<T>` and `ServiceSelector<T>`
128
+
129
+ Useful for choosing between multiple implementations of the same interface at runtime.
130
+
131
+ ```typescript
132
+ const LoggerSelectorKey = new ServiceSelectorKey<Logger>([
133
+ ConsoleLoggerKey,
134
+ FileLoggerKey,
135
+ ]);
136
+
137
+ const AppFactory = ServiceFactory.singleton({
138
+ provides: AppKey,
139
+ dependsOn: [LoggerSelectorKey],
140
+ initialize: (loggerSelector) => {
141
+ return {
142
+ run: async (useFile: boolean) => {
143
+ const logger = await loggerSelector.get(
144
+ useFile ? FileLoggerKey : ConsoleLoggerKey
145
+ );
146
+ logger.log('Running...');
147
+ }
148
+ };
149
+ }
150
+ });
151
+ ```
152
+
153
+ ### `ServiceScope`
154
+
155
+ Used to group services for collective disposal.
156
+
157
+ ```typescript
158
+ const MyScope = new ServiceScope('MyScope');
159
+ ```
160
+
161
+ ## Visualization
162
+
163
+ Visualize your dependency graph using Mermaid or DOT format.
164
+
165
+ ```typescript
166
+ import { printMermaidGraph, printDotGraph } from 'lazy-di';
167
+
168
+ // Outputs a Mermaid diagram string
169
+ printMermaidGraph(module);
170
+
171
+ // Outputs a DOT diagram string
172
+ printDotGraph(module);
173
+ ```
174
+
175
+ ## Error Handling
176
+
177
+ - `ServiceModuleInitError`: Thrown during `ServiceModule.from()` if the dependency graph is invalid (circular or missing dependencies).
178
+ - `ServiceFactoryNotFoundError`: Thrown by `module.get()` if a requested key is not registered.
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Error thrown when there is an issue during the initialization or configuration of a ServiceModule.
3
+ * This can include circular dependencies or missing dependencies that are detected during module creation.
4
+ */
5
+ export declare class ServiceModuleInitError extends Error {
6
+ name: string;
7
+ constructor(message: string);
8
+ }
9
+ /**
10
+ * Error thrown when a requested service cannot be found within the ServiceModule.
11
+ * This typically occurs when no factory has been registered for the given ServiceKey.
12
+ */
13
+ export declare class ServiceFactoryNotFoundError extends Error {
14
+ name: string;
15
+ constructor(message: string);
16
+ }
17
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,IAAI,SAA4B;gBAEpB,OAAO,EAAE,MAAM;CAG5B;AAED;;;GAGG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,IAAI,SAAiC;gBAEzB,OAAO,EAAE,MAAM;CAG5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServiceFactoryNotFoundError = exports.ServiceModuleInitError = void 0;
4
+ /**
5
+ * Error thrown when there is an issue during the initialization or configuration of a ServiceModule.
6
+ * This can include circular dependencies or missing dependencies that are detected during module creation.
7
+ */
8
+ class ServiceModuleInitError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'ServiceModuleInitError';
12
+ }
13
+ }
14
+ exports.ServiceModuleInitError = ServiceModuleInitError;
15
+ /**
16
+ * Error thrown when a requested service cannot be found within the ServiceModule.
17
+ * This typically occurs when no factory has been registered for the given ServiceKey.
18
+ */
19
+ class ServiceFactoryNotFoundError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'ServiceFactoryNotFoundError';
23
+ }
24
+ }
25
+ exports.ServiceFactoryNotFoundError = ServiceFactoryNotFoundError;
26
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,sBAAuB,SAAQ,KAAK;IAG/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHjB,SAAI,GAAG,wBAAwB,CAAC;IAIhC,CAAC;CACF;AAND,wDAMC;AAED;;;GAGG;AACH,MAAa,2BAA4B,SAAQ,KAAK;IAGpD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAHjB,SAAI,GAAG,6BAA6B,CAAC;IAIrC,CAAC;CACF;AAND,kEAMC"}
package/dist/index.d.ts CHANGED
@@ -2,5 +2,7 @@ export * from './serviceKey';
2
2
  export * from './serviceModule';
3
3
  export * from './serviceFactory';
4
4
  export * from './serviceScope';
5
+ export * from './serviceSelector';
6
+ export * from './errors';
5
7
  export * from './utils';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -18,5 +18,7 @@ __exportStar(require("./serviceKey"), exports);
18
18
  __exportStar(require("./serviceModule"), exports);
19
19
  __exportStar(require("./serviceFactory"), exports);
20
20
  __exportStar(require("./serviceScope"), exports);
21
+ __exportStar(require("./serviceSelector"), exports);
22
+ __exportStar(require("./errors"), exports);
21
23
  __exportStar(require("./utils"), exports);
22
24
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,kDAAgC;AAChC,mDAAiC;AACjC,iDAA+B;AAC/B,0CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,kDAAgC;AAChC,mDAAiC;AACjC,iDAA+B;AAC/B,oDAAkC;AAClC,2CAAyB;AACzB,0CAAwB"}
@@ -1,6 +1,7 @@
1
- import { ServiceKey } from './serviceKey';
1
+ import { ServiceKey, ServiceSelectorKey } from './serviceKey';
2
2
  import { ServiceScope } from './serviceScope';
3
- type ServiceType<T> = T extends ServiceKey<infer U> ? U : never;
3
+ import { ServiceSelector } from './serviceSelector';
4
+ type ServiceType<T> = T extends ServiceSelectorKey<infer U> ? ServiceSelector<U> : T extends ServiceKey<infer U> ? U : never;
4
5
  type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
5
6
  [K in keyof T]: ServiceType<T[K]>;
6
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"serviceFactory.d.ts","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAGhE,KAAK,eAAe,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF,8BAAsB,cAAc,CAClC,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE;IAEnD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B;;;OAGG;IACH,MAAM,CAAC,SAAS,CACd,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EACnD,EACA,KAAK,EACL,QAAQ,EACR,SAA8B,EAC9B,UAAU,EACV,OAAkB,GACnB,EAAE;QACD,KAAK,CAAC,EAAE,YAAY,CAAC;QACrB,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;KACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAuBxB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,EAAE,CAAC,CAAC;QACb,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KACrE,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;CAOzB"}
1
+ {"version":3,"file":"serviceFactory.d.ts","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,KAAK,WAAW,CAAC,CAAC,IAChB,CAAC,SAAS,kBAAkB,CAAC,MAAM,CAAC,CAAC,GACjC,eAAe,CAAC,CAAC,CAAC,GAClB,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAC3B,CAAC,GACD,KAAK,CAAC;AAGd,KAAK,eAAe,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI;KAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF,8BAAsB,cAAc,CAClC,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE;IAEnD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B;;;OAGG;IACH,MAAM,CAAC,SAAS,CACd,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EACnD,EACA,KAAK,EACL,QAAQ,EACR,SAA8B,EAC9B,UAAU,EACV,OAAkB,GACnB,EAAE;QACD,KAAK,CAAC,EAAE,YAAY,CAAC;QACrB,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,IAAI,CAAC;KACjC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAsCxB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,SAAS,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,SAAS,EAAE,CAAC,CAAC;QACb,UAAU,EAAE,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KACrE,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;CAOzB"}
@@ -16,24 +16,39 @@ class ServiceFactory {
16
16
  * and used throughout the scope lifecycle.
17
17
  */
18
18
  static singleton({ scope, provides, dependsOn = [], initialize, dispose = () => { }, }) {
19
- let instance;
19
+ let promisedInstance;
20
+ let resolvedInstance;
20
21
  return {
21
22
  scope,
22
23
  provides,
23
24
  dependsOn,
24
25
  initialize(...dependencies) {
25
26
  return __awaiter(this, void 0, void 0, function* () {
26
- if (instance === undefined) {
27
- instance = yield initialize(...dependencies);
27
+ if (resolvedInstance !== undefined) {
28
+ return resolvedInstance;
28
29
  }
29
- return instance;
30
+ if (promisedInstance !== undefined) {
31
+ return promisedInstance;
32
+ }
33
+ // Store the reference to the promise so that concurrent requests can wait for it
34
+ promisedInstance = (() => __awaiter(this, void 0, void 0, function* () {
35
+ try {
36
+ resolvedInstance = yield initialize(...dependencies);
37
+ return resolvedInstance;
38
+ }
39
+ finally {
40
+ promisedInstance = undefined;
41
+ }
42
+ }))();
43
+ return promisedInstance;
30
44
  });
31
45
  },
32
46
  dispose() {
33
- if (instance !== undefined) {
34
- dispose(instance);
35
- instance = undefined;
47
+ if (resolvedInstance !== undefined) {
48
+ dispose(resolvedInstance);
49
+ resolvedInstance = undefined;
36
50
  }
51
+ promisedInstance = undefined;
37
52
  },
38
53
  };
39
54
  }
@@ -1 +1 @@
1
- {"version":3,"file":"serviceFactory.js","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":";;;;;;;;;;;;AAWA,MAAsB,cAAc;IAUlC;;;OAGG;IACH,MAAM,CAAC,SAAS,CAGd,EACA,KAAK,EACL,QAAQ,EACR,SAAS,GAAG,EAAkB,EAC9B,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAOnB;QACC,IAAI,QAAuB,CAAC;QAE5B,OAAO;YACL,KAAK;YACL,QAAQ;YACR,SAAS;YACH,UAAU,CAAC,GAAG,YAAgC;;oBAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;wBAC3B,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;oBAC/C,CAAC;oBAED,OAAO,QAAQ,CAAC;gBAClB,CAAC;aAAA;YACD,OAAO;gBACL,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAClB,QAAQ,GAAG,SAAS,CAAC;gBACvB,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,CAA+D,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,GAKX;QACC,OAAO;YACL,QAAQ;YACR,SAAS;YACT,UAAU;SACX,CAAC;IACJ,CAAC;CACF;AAvED,wCAuEC"}
1
+ {"version":3,"file":"serviceFactory.js","sourceRoot":"","sources":["../src/serviceFactory.ts"],"names":[],"mappings":";;;;;;;;;;;;AAiBA,MAAsB,cAAc;IAUlC;;;OAGG;IACH,MAAM,CAAC,SAAS,CAGd,EACA,KAAK,EACL,QAAQ,EACR,SAAS,GAAG,EAAkB,EAC9B,UAAU,EACV,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,GAOnB;QACC,IAAI,gBAAwC,CAAC;QAC7C,IAAI,gBAA+B,CAAC;QAEpC,OAAO;YACL,KAAK;YACL,QAAQ;YACR,SAAS;YACH,UAAU,CAAC,GAAG,YAAgC;;oBAClD,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;wBACnC,OAAO,gBAAgB,CAAC;oBAC1B,CAAC;oBAED,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;wBACnC,OAAO,gBAAgB,CAAC;oBAC1B,CAAC;oBAED,iFAAiF;oBACjF,gBAAgB,GAAG,CAAC,GAAS,EAAE;wBAC7B,IAAI,CAAC;4BACH,gBAAgB,GAAG,MAAM,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;4BACrD,OAAO,gBAAgB,CAAC;wBAC1B,CAAC;gCAAS,CAAC;4BACT,gBAAgB,GAAG,SAAS,CAAC;wBAC/B,CAAC;oBACH,CAAC,CAAA,CAAC,EAAE,CAAC;oBACL,OAAO,gBAAgB,CAAC;gBAC1B,CAAC;aAAA;YACD,OAAO;gBACL,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;oBACnC,OAAO,CAAC,gBAAgB,CAAC,CAAC;oBAC1B,gBAAgB,GAAG,SAAS,CAAC;gBAC/B,CAAC;gBACD,gBAAgB,GAAG,SAAS,CAAC;YAC/B,CAAC;SACF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,CAA+D,EAC3E,QAAQ,EACR,SAAS,EACT,UAAU,GAKX;QACC,OAAO;YACL,QAAQ;YACR,SAAS;YACT,UAAU;SACX,CAAC;IACJ,CAAC;CACF;AAtFD,wCAsFC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serviceFactoryWrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceFactoryWrapper.d.ts","sourceRoot":"","sources":["../src/serviceFactoryWrapper.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const serviceKey_1 = require("./serviceKey");
4
+ class ServiceResolver {
5
+ constructor(serviceModule) {
6
+ this.serviceModule = serviceModule;
7
+ }
8
+ get(key) {
9
+ // TODO: Implement service factory wrapper logic
10
+ return this.serviceModule.get(key);
11
+ }
12
+ static from(keys) {
13
+ return new serviceKey_1.ServiceKey(`ServiceResolver[${keys}]`);
14
+ }
15
+ }
16
+ //# sourceMappingURL=serviceFactoryWrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceFactoryWrapper.js","sourceRoot":"","sources":["../src/serviceFactoryWrapper.ts"],"names":[],"mappings":";;AAAA,6CAA0C;AAG1C,MAAM,eAAe;IACnB,YAAqB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAErD,GAAG,CAAC,GAAkB;QACpB,gDAAgD;QAChD,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,IAAI,CAAI,IAAqB;QAClC,OAAO,IAAI,uBAAU,CAAC,mBAAmB,IAAI,GAAG,CAAC,CAAA;IACnD,CAAC;CACF"}
@@ -1,6 +1,90 @@
1
+ import { ServiceSelector } from './serviceSelector';
2
+ /**
3
+ * A typed token used to identify and retrieve a service from a ServiceModule.
4
+ *
5
+ * ServiceKey acts as a unique identifier for a service type, allowing type-safe
6
+ * dependency injection. Each key has a unique symbol to ensure identity comparison
7
+ * works correctly even if two keys have the same name.
8
+ *
9
+ * @template T The type of service this key identifies.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * interface Logger {
14
+ * log: (msg: string) => void;
15
+ * }
16
+ *
17
+ * const LoggerKey = new ServiceKey<Logger>('Logger');
18
+ *
19
+ * // Use with ServiceFactory and ServiceModule
20
+ * const loggerFactory = ServiceFactory.singleton({
21
+ * provides: LoggerKey,
22
+ * dependsOn: [],
23
+ * initialize: () => console,
24
+ * });
25
+ *
26
+ * const module = ServiceModule.from([loggerFactory]);
27
+ * const logger = await module.get(LoggerKey);
28
+ * ```
29
+ */
1
30
  export declare class ServiceKey<T> {
2
31
  readonly name: string;
32
+ /**
33
+ * A unique symbol that identifies this service key.
34
+ * Used internally for identity comparison between keys.
35
+ */
3
36
  readonly symbol: symbol;
37
+ /**
38
+ * Creates a new ServiceKey with the given name.
39
+ *
40
+ * @param name A human-readable name for the service, used in error messages and debugging.
41
+ */
4
42
  constructor(name: string);
5
43
  }
44
+ /**
45
+ * A specialized ServiceKey that groups multiple ServiceKeys of the same type,
46
+ * allowing a service to depend on a selector that can retrieve any of the grouped services.
47
+ *
48
+ * When used in a factory's `dependsOn` array, the factory's `initialize` callback
49
+ * receives a `ServiceSelector<T>` instance instead of a direct service instance.
50
+ * This enables runtime selection between multiple implementations of the same interface.
51
+ *
52
+ * @template T The common type shared by all service keys in this selector.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * interface Logger {
57
+ * log: (msg: string) => void;
58
+ * }
59
+ *
60
+ * const ConsoleLoggerKey = new ServiceKey<Logger>('ConsoleLogger');
61
+ * const FileLoggerKey = new ServiceKey<Logger>('FileLogger');
62
+ *
63
+ * // Group multiple logger implementations under one selector
64
+ * const LoggerSelectorKey = new ServiceSelectorKey<Logger>([
65
+ * ConsoleLoggerKey,
66
+ * FileLoggerKey,
67
+ * ]);
68
+ *
69
+ * // Use in a factory's dependsOn array
70
+ * const appFactory = ServiceFactory.singleton({
71
+ * provides: AppKey,
72
+ * dependsOn: [LoggerSelectorKey] as const,
73
+ * initialize: (loggerSelector: ServiceSelector<Logger>) => {
74
+ * // loggerSelector.get(ConsoleLoggerKey) or loggerSelector.get(FileLoggerKey)
75
+ * return new App(loggerSelector);
76
+ * },
77
+ * });
78
+ * ```
79
+ */
80
+ export declare class ServiceSelectorKey<T> extends ServiceKey<ServiceSelector<T>> {
81
+ readonly values: ServiceKey<T>[];
82
+ /**
83
+ * Creates a new ServiceSelectorKey that groups the provided service keys.
84
+ *
85
+ * @param values An array of ServiceKeys that this selector can provide access to.
86
+ * All keys must be registered in the ServiceModule for dependency validation to pass.
87
+ */
88
+ constructor(values: ServiceKey<T>[]);
89
+ }
6
90
  //# sourceMappingURL=serviceKey.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serviceKey.d.ts","sourceRoot":"","sources":["../src/serviceKey.ts"],"names":[],"mappings":"AACA,qBAAa,UAAU,CAAC,CAAC;aAGK,IAAI,EAAE,MAAM;IAFxC,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAEH,IAAI,EAAE,MAAM;CAGzC"}
1
+ {"version":3,"file":"serviceKey.d.ts","sourceRoot":"","sources":["../src/serviceKey.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAU,CAAC,CAAC;aAYK,IAAI,EAAE,MAAM;IAXxC;;;OAGG;IACH,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;gBACyB,IAAI,EAAE,MAAM;CAGzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,kBAAkB,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAO3D,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE;IAN5C;;;;;OAKG;gBACkB,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE;CAG7C"}