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
@@ -1,123 +1,223 @@
1
- import { ServiceKey } from './serviceKey';
2
- import { ServiceFactory } from './serviceFactory';
3
- import { ServiceScope } from './serviceScope';
4
-
5
- type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>;
6
- type GenericKey = ServiceKey<any>;
7
-
8
- export class ServiceModule {
9
- private constructor(readonly factories: GenericFactory[]) {
10
- factories.forEach((factory) => {
11
- checkRecursiveDependencies(factory);
12
- checkMissingDependencies(factory, this.factories);
13
- });
14
- }
15
-
16
- public async get<T>(key: ServiceKey<T>): Promise<T> {
17
- const factory = this.factories.find((factory: GenericFactory) => {
18
- return isSuitable(key, factory);
19
- });
20
-
21
- // Check if a factory to supply the requested key was not found
22
- if (!factory) {
23
- throw new Error(`Could not find a suitable factory for ${key.name}`);
24
- }
25
-
26
- // Resolve all dependencies first
27
- const dependencies = await Promise.all(
28
- factory.dependsOn.map((dependencyKey: ServiceKey<unknown>) => {
29
- return this.get(dependencyKey);
30
- }),
31
- );
32
-
33
- // Call the factory to retrieve the dependency
34
- return factory.initialize(...dependencies);
35
- }
36
-
37
- /**
38
- * Disposes of service factories within the specified scope or all factories if no scope is provided.
39
- *
40
- * This method is useful for cleaning up resources and instances held by service factories,
41
- * such as singleton factories, as they may hold database connections or other resources that need to be released.
42
- *
43
- * @param {ServiceScope} [scope] The scope to filter the factories to be disposed.
44
- * If not provided, all factories are disposed of.
45
- * @return {void} No return value.
46
- */
47
- public dispose(scope?: ServiceScope) {
48
- const factories = scope
49
- ? this.factories.filter((f) => f.scope === scope)
50
- : this.factories;
51
-
52
- factories.forEach((factory) => factory.dispose?.());
53
- }
54
-
55
- /**
56
- * Creates a new ServiceModule instance by aggregating and deduplicating a list of
57
- * ServiceModule or GenericFactory instances.
58
- * If multiple factories provide the same
59
- * ServiceKey, the last one in the list takes precedence.
60
- *
61
- * @param {Array<ServiceModule | GenericFactory>} entries - An array of ServiceModule or GenericFactory
62
- * instances to be processed into a single ServiceModule.
63
- * @return {ServiceModule} A new ServiceModule containing the deduplicated factories.
64
- */
65
- static from(entries: (ServiceModule | GenericFactory)[]): ServiceModule {
66
- // Flatten entries and keep only the last factory for each ServiceKey
67
- const flattened = entries.flatMap((e) =>
68
- e instanceof ServiceModule ? e.factories : [e],
69
- );
70
-
71
- const byKey = new Map<symbol, GenericFactory>();
72
- // Later factories overwrite earlier ones (last-wins)
73
- for (const f of flattened) {
74
- byKey.set(f.provides.symbol, f);
75
- }
76
-
77
- return new ServiceModule(Array.from(byKey.values()));
78
- }
79
- }
80
-
81
- function checkRecursiveDependencies(factory: GenericFactory) {
82
- const recursive = factory.dependsOn.some((dependencyKey) => {
83
- return dependencyKey === factory.provides;
84
- });
85
-
86
- if (recursive) {
87
- throw new Error(
88
- 'Recursive dependency detected on: ' + factory.provides.name,
89
- );
90
- }
91
- }
92
-
93
- function checkMissingDependencies(
94
- factory: GenericFactory,
95
- factories: GenericFactory[],
96
- ) {
97
- const missingDependencies = factory.dependsOn.filter(
98
- (dependencyKey: GenericKey) => {
99
- return !isRegistered(dependencyKey, factories);
100
- },
101
- );
102
- if (missingDependencies.length === 0) {
103
- return;
104
- }
105
-
106
- const dependencyList = missingDependencies
107
- .map((dependencyKey) => ` -> ${dependencyKey.name}`)
108
- .join('\n');
109
- throw new Error(
110
- `${factory.provides.name} will fail because it depends on:\n ${dependencyList}`,
111
- );
112
- }
113
-
114
- function isRegistered(key: GenericKey, factories: GenericFactory[]) {
115
- return factories.some((factory) => factory.provides?.symbol === key?.symbol);
116
- }
117
-
118
- function isSuitable<T, D extends readonly ServiceKey<any>[]>(
119
- key: ServiceKey<T>,
120
- factory: ServiceFactory<any, D>,
121
- ): factory is ServiceFactory<T, D> {
122
- return factory?.provides?.symbol === key?.symbol;
123
- }
1
+ import { ServiceKey, ServiceSelectorKey } from './serviceKey';
2
+ import { ServiceFactory } from './serviceFactory';
3
+ import { ServiceScope } from './serviceScope';
4
+ import { ServiceSelector } from './serviceSelector';
5
+ import { ServiceFactoryNotFoundError, ServiceModuleInitError } from './errors';
6
+
7
+ type GenericFactory = ServiceFactory<unknown, readonly ServiceKey<any>[]>;
8
+ type GenericKey = ServiceKey<any>;
9
+
10
+ /**
11
+ * ServiceModule is a container for service factories and manages dependency resolution.
12
+ *
13
+ * It provides a way to retrieve service instances based on their ServiceKey,
14
+ * ensuring that all dependencies are resolved and initialized in the correct order.
15
+ * It also handles circular dependency detection and missing dependency validation
16
+ * at the time of module creation.
17
+ */
18
+ export class ServiceModule {
19
+ /**
20
+ * Private constructor to enforce module creation through the `static from` method.
21
+ *
22
+ * @param factories An array of service factories that this module will manage.
23
+ */
24
+ private constructor(readonly factories: GenericFactory[]) {
25
+ checkCircularDependencies(this.factories);
26
+ factories.forEach((factory) => {
27
+ checkMissingDependencies(factory, this.factories);
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Retrieves an instance for the given ServiceKey.
33
+ *
34
+ * @param key - The key of the service to retrieve.
35
+ * @return A promise that resolves to the service instance.
36
+ * @throws {ServiceFactoryNotFoundError} If no suitable factory is found for the given key.
37
+ */
38
+ public async get<T>(key: ServiceKey<T>): Promise<T> {
39
+ const factory = this.factories.find((factory: GenericFactory) => {
40
+ return isSuitable(key, factory);
41
+ });
42
+
43
+ // Check if a factory to supply the requested key was not found
44
+ if (!factory) {
45
+ throw new ServiceFactoryNotFoundError(
46
+ `Could not find a suitable factory for ${key.name}`,
47
+ );
48
+ }
49
+
50
+ // Resolve all dependencies first
51
+ const dependencies = await Promise.all(
52
+ factory.dependsOn.map((dependencyKey: ServiceKey<unknown>) => {
53
+ // If the dependency is a ServiceSelectorKey, create a ServiceSelector instance
54
+ if (dependencyKey instanceof ServiceSelectorKey) {
55
+ return new ServiceSelector(this, dependencyKey);
56
+ }
57
+ return this.get(dependencyKey);
58
+ }),
59
+ );
60
+
61
+ // Call the factory to retrieve the dependency
62
+ return factory.initialize(...dependencies);
63
+ }
64
+
65
+ /**
66
+ * Disposes of service factories within the specified scope or all factories if no scope is provided.
67
+ *
68
+ * This method is useful for cleaning up resources and instances held by service factories,
69
+ * such as singleton factories, as they may hold database connections or other resources that need to be released.
70
+ *
71
+ * @param scope The scope to filter the factories to be disposed.
72
+ * If not provided, all factories are disposed of.
73
+ * @return No return value.
74
+ */
75
+ public dispose(scope?: ServiceScope) {
76
+ const factories = scope
77
+ ? this.factories.filter((f) => f.scope === scope)
78
+ : this.factories;
79
+
80
+ factories.forEach((factory) => factory.dispose?.());
81
+ }
82
+
83
+ /**
84
+ * Creates a new ServiceModule instance by aggregating and deduplicating a list of
85
+ * ServiceModule or GenericFactory instances.
86
+ * If multiple factories provide the same
87
+ * ServiceKey, the last one in the list takes precedence.
88
+ *
89
+ * @param entries - An array of ServiceModule or GenericFactory
90
+ * instances to be processed into a single ServiceModule.
91
+ * @return A new ServiceModule containing the deduplicated factories.
92
+ * @throws {ServiceModuleInitError} If circular or missing dependencies are detected during module creation.
93
+ */
94
+ static from(entries: (ServiceModule | GenericFactory)[]): ServiceModule {
95
+ // Flatten entries and keep only the last factory for each ServiceKey
96
+ const flattened = entries.flatMap((e) =>
97
+ e instanceof ServiceModule ? e.factories : [e],
98
+ );
99
+
100
+ const byKey = new Map<symbol, GenericFactory>();
101
+ // Later factories overwrite earlier ones (last-wins)
102
+ for (const f of flattened) {
103
+ byKey.set(f.provides.symbol, f);
104
+ }
105
+
106
+ return new ServiceModule(Array.from(byKey.values()));
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Validates that there are no circular dependencies among the provided factories.
112
+ *
113
+ * @param factories The list of factories to check for cycles.
114
+ * @throws {ServiceModuleInitError} If a circular dependency is detected.
115
+ */
116
+ function checkCircularDependencies(factories: GenericFactory[]) {
117
+ const factoryMap = new Map<symbol, GenericFactory>();
118
+ for (const f of factories) {
119
+ factoryMap.set(f.provides.symbol, f);
120
+ }
121
+
122
+ const visited = new Set<symbol>();
123
+ const stack = new Set<symbol>();
124
+
125
+ function walk(factory: GenericFactory, path: string[]) {
126
+ const symbol = factory.provides.symbol;
127
+
128
+ if (stack.has(symbol)) {
129
+ const cyclePath = [...path, factory.provides.name].join(' -> ');
130
+ throw new ServiceModuleInitError(
131
+ `Circular dependency detected: ${cyclePath}`,
132
+ );
133
+ }
134
+
135
+ if (visited.has(symbol)) {
136
+ return;
137
+ }
138
+
139
+ visited.add(symbol);
140
+ stack.add(symbol);
141
+
142
+ for (const depKey of factory.dependsOn) {
143
+ const keysToCheck =
144
+ depKey instanceof ServiceSelectorKey ? depKey.values : [depKey];
145
+
146
+ for (const key of keysToCheck) {
147
+ const depFactory = factoryMap.get(key.symbol);
148
+ if (depFactory) {
149
+ walk(depFactory, [...path, factory.provides.name]);
150
+ }
151
+ }
152
+ }
153
+
154
+ stack.delete(symbol);
155
+ }
156
+
157
+ for (const factory of factories) {
158
+ walk(factory, []);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Validates that all dependencies of a given factory are present in the list of factories.
164
+ *
165
+ * @param factory The factory whose dependencies are to be checked.
166
+ * @param factories The list of available factories in the module.
167
+ * @throws {ServiceModuleInitError} If any dependency is missing.
168
+ */
169
+ function checkMissingDependencies(
170
+ factory: GenericFactory,
171
+ factories: GenericFactory[],
172
+ ) {
173
+ const missingDependencies: GenericKey[] = [];
174
+
175
+ factory.dependsOn.forEach((dependencyKey: GenericKey) => {
176
+ // For ServiceSelectorKey, check all contained keys are registered
177
+ if (dependencyKey instanceof ServiceSelectorKey) {
178
+ dependencyKey.values.forEach((key) => {
179
+ if (!isRegistered(key, factories)) {
180
+ missingDependencies.push(key);
181
+ }
182
+ });
183
+ } else if (!isRegistered(dependencyKey, factories)) {
184
+ missingDependencies.push(dependencyKey);
185
+ }
186
+ });
187
+
188
+ if (missingDependencies.length === 0) {
189
+ return;
190
+ }
191
+
192
+ const dependencyList = missingDependencies
193
+ .map((dependencyKey) => ` -> ${dependencyKey.name}`)
194
+ .join('\n');
195
+ throw new ServiceModuleInitError(
196
+ `${factory.provides.name} will fail because it depends on:\n ${dependencyList}`,
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Checks if a ServiceKey is registered among the provided factories.
202
+ *
203
+ * @param key The ServiceKey to look for.
204
+ * @param factories The list of factories to search in.
205
+ * @returns True if a factory provides the given key, false otherwise.
206
+ */
207
+ function isRegistered(key: GenericKey, factories: GenericFactory[]) {
208
+ return factories.some((factory) => factory.provides?.symbol === key?.symbol);
209
+ }
210
+
211
+ /**
212
+ * Determines if a factory is suitable for providing a specific ServiceKey.
213
+ *
214
+ * @param key The ServiceKey being requested.
215
+ * @param factory The factory to check.
216
+ * @returns True if the factory provides the key, false otherwise.
217
+ */
218
+ function isSuitable<T, D extends readonly ServiceKey<any>[]>(
219
+ key: ServiceKey<T>,
220
+ factory: ServiceFactory<any, D>,
221
+ ): factory is ServiceFactory<T, D> {
222
+ return factory?.provides?.symbol === key?.symbol;
223
+ }
@@ -1,7 +1,7 @@
1
- export class ServiceScope {
2
- readonly symbol: symbol;
3
-
4
- constructor(readonly name: string) {
5
- this.symbol = Symbol(name);
6
- }
7
- }
1
+ export class ServiceScope {
2
+ readonly symbol: symbol;
3
+
4
+ constructor(readonly name: string) {
5
+ this.symbol = Symbol(name);
6
+ }
7
+ }
@@ -0,0 +1,68 @@
1
+ import { ServiceKey, ServiceSelectorKey } from './serviceKey';
2
+ import { ServiceModule } from './serviceModule';
3
+
4
+ /**
5
+ * A runtime selector that provides access to multiple service implementations of the same type.
6
+ *
7
+ * ServiceSelector is automatically created and injected when a factory depends on a
8
+ * `ServiceSelectorKey<T>`. It allows the dependent service to dynamically choose which
9
+ * implementation to use at runtime, rather than being bound to a single implementation
10
+ * at configuration time.
11
+ *
12
+ * @template T The common type shared by all services accessible through this selector.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // In a factory that depends on ServiceSelectorKey
17
+ * const appFactory = ServiceFactory.singleton({
18
+ * provides: AppKey,
19
+ * dependsOn: [LoggerSelectorKey] as const,
20
+ * initialize: (loggerSelector: ServiceSelector<Logger>) => {
21
+ * return {
22
+ * logWithConsole: async () => {
23
+ * const logger = await loggerSelector.get(ConsoleLoggerKey);
24
+ * logger.log('Using console logger');
25
+ * },
26
+ * logWithFile: async () => {
27
+ * const logger = await loggerSelector.get(FileLoggerKey);
28
+ * logger.log('Using file logger');
29
+ * },
30
+ * };
31
+ * },
32
+ * });
33
+ * ```
34
+ */
35
+ export class ServiceSelector<T> {
36
+ /**
37
+ * Creates a new ServiceSelector instance.
38
+ *
39
+ * Note: ServiceSelector instances are created automatically by ServiceModule
40
+ * when resolving dependencies. You typically don't need to create them manually.
41
+ *
42
+ * @param serviceModule The ServiceModule used to resolve the selected service.
43
+ * @param selectorKey The ServiceSelectorKey that defines which services can be selected.
44
+ */
45
+ constructor(
46
+ readonly serviceModule: ServiceModule,
47
+ readonly selectorKey: ServiceSelectorKey<T>,
48
+ ) {}
49
+
50
+ /**
51
+ * Retrieves a service instance by its key from the available services in this selector.
52
+ *
53
+ * The key must be one of the keys that were included in the `ServiceSelectorKey`
54
+ * used to create this selector.
55
+ *
56
+ * @param key The ServiceKey identifying which service implementation to retrieve.
57
+ * @returns A Promise that resolves to the requested service instance.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const logger = await loggerSelector.get(ConsoleLoggerKey);
62
+ * logger.log('Hello!');
63
+ * ```
64
+ */
65
+ get(key: ServiceKey<T>): Promise<T> {
66
+ return this.serviceModule.get(key);
67
+ }
68
+ }
package/src/utils.ts CHANGED
@@ -12,6 +12,15 @@ export interface DotGraphOptions {
12
12
  highlightRoots?: boolean;
13
13
  }
14
14
 
15
+ export interface MermaidGraphOptions {
16
+ /** Graph direction: 'TB' (top-bottom), 'LR' (left-right), 'BT' (bottom-top), 'RL' (right-left) */
17
+ direction?: 'TB' | 'LR' | 'BT' | 'RL';
18
+ /** Show nodes with no dependencies in a different color */
19
+ highlightLeaves?: boolean;
20
+ /** Show nodes with no dependents in a different color */
21
+ highlightRoots?: boolean;
22
+ }
23
+
15
24
  /**
16
25
  * Escapes special characters in strings for DOT notation
17
26
  */
@@ -19,6 +28,13 @@ function escapeDotString(str: string): string {
19
28
  return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
20
29
  }
21
30
 
31
+ /**
32
+ * Escapes special characters in strings for Mermaid notation
33
+ */
34
+ function escapeMermaidString(str: string): string {
35
+ return str.replace(/"/g, '#quot;');
36
+ }
37
+
22
38
  /**
23
39
  * Generates a DOT notation graph from a ServiceModule.
24
40
  * The output can be visualized using Graphviz tools or online viewers like:
@@ -27,9 +43,9 @@ function escapeDotString(str: string): string {
27
43
  *
28
44
  * Arrows point from dependencies to dependents (from what is needed to what needs it).
29
45
  *
30
- * @param {ServiceModule} module - The ServiceModule to convert to DOT notation
31
- * @param {DotGraphOptions} options - Optional configuration for the graph appearance
32
- * @returns {string} A string containing the DOT notation graph
46
+ * @param module - The ServiceModule to convert to DOT notation
47
+ * @param options - Optional configuration for the graph appearance
48
+ * @returns A string containing the DOT notation graph
33
49
  */
34
50
  export function createDotGraph(
35
51
  module: ServiceModule,
@@ -138,9 +154,8 @@ export function createDotGraph(
138
154
  * Prints a DOT representation of a service module graph to the console.
139
155
  * The output can be used to visualize the graph using online graph visualization tools.
140
156
  *
141
- * @param {ServiceModule} module - The service module representing the graph to be converted into DOT format.
142
- * @param {DotGraphOptions} [options] - Optional configurations to customize the output of the DOT graph.
143
- * @return {void} - This function does not return a value.
157
+ * @param module - The service module representing the graph to be converted into DOT format.
158
+ * @param options - Optional configurations to customize the output of the DOT graph.
144
159
  */
145
160
  export function printDotGraph(
146
161
  module: ServiceModule,
@@ -150,3 +165,113 @@ export function printDotGraph(
150
165
  console.log('\n\nCopy the DOT output above and paste it into:');
151
166
  console.log('https://dreampuf.github.io/GraphvizOnline/');
152
167
  }
168
+
169
+ /**
170
+ * Generates a Mermaid flowchart from a ServiceModule.
171
+ * The output can be visualized using Mermaid-compatible tools or online viewers like:
172
+ * - https://mermaid.live/
173
+ *
174
+ * Arrows point from dependents to dependencies (what needs it -> what provides it).
175
+ *
176
+ * @param module - The ServiceModule to convert to Mermaid notation
177
+ * @param options - Optional configuration for the graph appearance
178
+ * @returns A string containing the Mermaid flowchart
179
+ */
180
+ export function createMermaidGraph(
181
+ module: ServiceModule,
182
+ { direction, highlightLeaves, highlightRoots }: MermaidGraphOptions = {
183
+ direction: 'TB',
184
+ highlightLeaves: true,
185
+ highlightRoots: true,
186
+ },
187
+ ): string {
188
+ const factories = module.factories;
189
+ const lines: string[] = [];
190
+
191
+ // Start the flowchart
192
+ lines.push(`flowchart ${direction}`);
193
+
194
+ // Build dependency maps to identify leaves and roots
195
+ const hasDependencies = new Set<string>();
196
+ const hasDependents = new Set<string>();
197
+
198
+ factories.forEach((factory) => {
199
+ const serviceName = factory.provides.name;
200
+
201
+ if (factory.dependsOn.length > 0) {
202
+ hasDependencies.add(serviceName);
203
+ }
204
+
205
+ factory.dependsOn.forEach((dependency) => {
206
+ hasDependents.add(dependency.name);
207
+ });
208
+ });
209
+
210
+ // Define nodes with special styling for leaves and roots
211
+ const nodeIds = new Map<string, string>();
212
+ let nodeCounter = 0;
213
+
214
+ factories.forEach((factory) => {
215
+ const serviceName = factory.provides.name;
216
+ const nodeId = `node${nodeCounter++}`;
217
+ nodeIds.set(serviceName, nodeId);
218
+
219
+ lines.push(` ${nodeId}["${escapeMermaidString(serviceName)}"]`);
220
+ });
221
+
222
+ lines.push('');
223
+
224
+ // Define edges (dependencies)
225
+ factories.forEach((factory) => {
226
+ const serviceName = factory.provides.name;
227
+ const serviceNodeId = nodeIds.get(serviceName)!;
228
+
229
+ factory.dependsOn.forEach((dependency) => {
230
+ const depName = dependency.name;
231
+ const depNodeId = nodeIds.get(depName);
232
+
233
+ if (depNodeId) {
234
+ // Arrow points from dependent to dependency (what needs it -> what provides it)
235
+ lines.push(` ${serviceNodeId} --> ${depNodeId}`);
236
+ }
237
+ });
238
+ });
239
+
240
+ lines.push('');
241
+
242
+ // Apply styling
243
+ factories.forEach((factory) => {
244
+ const serviceName = factory.provides.name;
245
+ const serviceNodeId = nodeIds.get(serviceName)!;
246
+
247
+ const isLeaf = !hasDependencies.has(serviceName);
248
+ const isRoot = !hasDependents.has(serviceName);
249
+
250
+ if (highlightLeaves && isLeaf) {
251
+ lines.push(` style ${serviceNodeId} fill:#c8e6c9,stroke:#388e3c`);
252
+ } else if (highlightRoots && isRoot) {
253
+ lines.push(` style ${serviceNodeId} fill:#ffccbc,stroke:#d84315`);
254
+ } else {
255
+ // Default style
256
+ lines.push(` style ${serviceNodeId} fill:#e1f5ff,stroke:#0288d1`);
257
+ }
258
+ });
259
+
260
+ return lines.join('\n');
261
+ }
262
+
263
+ /**
264
+ * Prints a Mermaid representation of a service module graph to the console.
265
+ * The output can be used to visualize the graph using online Mermaid tools.
266
+ *
267
+ * @param module - The service module representing the graph to be converted into Mermaid format.
268
+ * @param options - Optional configurations to customize the output of the Mermaid graph.
269
+ */
270
+ export function printMermaidGraph(
271
+ module: ServiceModule,
272
+ options?: MermaidGraphOptions,
273
+ ): void {
274
+ console.log(createMermaidGraph(module, options));
275
+ console.log('\n\nCopy the Mermaid output above and paste it into:');
276
+ console.log('https://mermaid.live/');
277
+ }
package/dist/main.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=main.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js DELETED
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const serviceKey_1 = require("./serviceKey");
4
- const serviceFactory_1 = require("./serviceFactory");
5
- const TEST_SERVICE_KEY = new serviceKey_1.ServiceKey('testService');
6
- const FOO_KEY = new serviceKey_1.ServiceKey('foo');
7
- const BAR_KEY = new serviceKey_1.ServiceKey('bar');
8
- const TestServiceFactory = (0, serviceFactory_1.singletonFactory)({
9
- provides: TEST_SERVICE_KEY,
10
- dependsOn: [FOO_KEY, BAR_KEY],
11
- initialize: (foo, bar) => {
12
- return { foo, bar };
13
- },
14
- });
15
- //# sourceMappingURL=main.js.map
package/dist/main.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,6CAA0C;AAC1C,qDAAoD;AAEpD,MAAM,gBAAgB,GAAG,IAAI,uBAAU,CAAc,aAAa,CAAC,CAAC;AACpE,MAAM,OAAO,GAAG,IAAI,uBAAU,CAAS,KAAK,CAAC,CAAC;AAC9C,MAAM,OAAO,GAAG,IAAI,uBAAU,CAAS,KAAK,CAAC,CAAC;AAO9C,MAAM,kBAAkB,GAAG,IAAA,iCAAgB,EAAC;IAC1C,QAAQ,EAAE,gBAAgB;IAC1B,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,CAAU;IACtC,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACvB,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACtB,CAAC;CACF,CAAC,CAAC"}