@composed-di/core 0.5.0-alpha
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/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/serviceEventListener.d.ts +120 -0
- package/dist/serviceEventListener.d.ts.map +1 -0
- package/dist/serviceEventListener.js +3 -0
- package/dist/serviceEventListener.js.map +1 -0
- package/dist/serviceFactory.d.ts +36 -0
- package/dist/serviceFactory.d.ts.map +1 -0
- package/dist/serviceFactory.js +68 -0
- package/dist/serviceFactory.js.map +1 -0
- package/dist/serviceKey.d.ts +90 -0
- package/dist/serviceKey.d.ts.map +1 -0
- package/dist/serviceKey.js +93 -0
- package/dist/serviceKey.js.map +1 -0
- package/dist/serviceModule.d.ts +71 -0
- package/dist/serviceModule.d.ts.map +1 -0
- package/dist/serviceModule.js +329 -0
- package/dist/serviceModule.js.map +1 -0
- package/dist/serviceScope.d.ts +6 -0
- package/dist/serviceScope.d.ts.map +1 -0
- package/dist/serviceScope.js +11 -0
- package/dist/serviceScope.js.map +1 -0
- package/dist/serviceSelector.d.ts +64 -0
- package/dist/serviceSelector.d.ts.map +1 -0
- package/dist/serviceSelector.js +69 -0
- package/dist/serviceSelector.js.map +1 -0
- package/dist/utils.d.ts +61 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +209 -0
- package/dist/utils.js.map +1 -0
- package/package.json +39 -0
- package/src/errors.ts +23 -0
- package/src/index.ts +8 -0
- package/src/serviceEventListener.ts +130 -0
- package/src/serviceFactory.ts +104 -0
- package/src/serviceKey.ts +95 -0
- package/src/serviceModule.ts +372 -0
- package/src/serviceScope.ts +7 -0
- package/src/serviceSelector.ts +68 -0
- package/src/utils.ts +277 -0
package/dist/utils.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDotGraph = createDotGraph;
|
|
4
|
+
exports.printDotGraph = printDotGraph;
|
|
5
|
+
exports.createMermaidGraph = createMermaidGraph;
|
|
6
|
+
exports.printMermaidGraph = printMermaidGraph;
|
|
7
|
+
/**
|
|
8
|
+
* Escapes special characters in strings for DOT notation
|
|
9
|
+
*/
|
|
10
|
+
function escapeDotString(str) {
|
|
11
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Escapes special characters in strings for Mermaid notation
|
|
15
|
+
*/
|
|
16
|
+
function escapeMermaidString(str) {
|
|
17
|
+
return str.replace(/"/g, '#quot;');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generates a DOT notation graph from a ServiceModule.
|
|
21
|
+
* The output can be visualized using Graphviz tools or online viewers like:
|
|
22
|
+
* - https://dreampuf.github.io/GraphvizOnline/
|
|
23
|
+
* - https://edotor.net/
|
|
24
|
+
*
|
|
25
|
+
* Arrows point from dependencies to dependents (from what is needed to what needs it).
|
|
26
|
+
*
|
|
27
|
+
* @param module - The ServiceModule to convert to DOT notation
|
|
28
|
+
* @param options - Optional configuration for the graph appearance
|
|
29
|
+
* @returns A string containing the DOT notation graph
|
|
30
|
+
*/
|
|
31
|
+
function createDotGraph(module, { direction, title, highlightLeaves, highlightRoots } = {
|
|
32
|
+
direction: 'TB',
|
|
33
|
+
title: 'Service Dependency Graph',
|
|
34
|
+
highlightLeaves: true,
|
|
35
|
+
highlightRoots: true,
|
|
36
|
+
}) {
|
|
37
|
+
const factories = module.factories;
|
|
38
|
+
const lines = [];
|
|
39
|
+
// Start the digraph
|
|
40
|
+
lines.push('digraph ServiceDependencies {');
|
|
41
|
+
lines.push(` label="${title}";`);
|
|
42
|
+
lines.push(' labelloc="t";');
|
|
43
|
+
lines.push(' fontsize=16;');
|
|
44
|
+
lines.push(` rankdir=${direction};`);
|
|
45
|
+
lines.push('');
|
|
46
|
+
// Default node styling
|
|
47
|
+
lines.push(' node [');
|
|
48
|
+
lines.push(' shape=box,');
|
|
49
|
+
lines.push(' style="rounded,filled",');
|
|
50
|
+
lines.push(' fillcolor="#e1f5ff",');
|
|
51
|
+
lines.push(' color="#0288d1",');
|
|
52
|
+
lines.push(' fontname="Arial",');
|
|
53
|
+
lines.push(' fontsize=12');
|
|
54
|
+
lines.push(' ];');
|
|
55
|
+
lines.push('');
|
|
56
|
+
// Default edge styling
|
|
57
|
+
lines.push(' edge [');
|
|
58
|
+
lines.push(' color="#666666",');
|
|
59
|
+
lines.push(' arrowsize=0.8');
|
|
60
|
+
lines.push(' ];');
|
|
61
|
+
lines.push('');
|
|
62
|
+
// Build dependency maps to identify leaves and roots
|
|
63
|
+
const hasDependencies = new Set();
|
|
64
|
+
const hasDependents = new Set();
|
|
65
|
+
factories.forEach((factory) => {
|
|
66
|
+
const serviceName = factory.provides.name;
|
|
67
|
+
if (factory.dependsOn.length > 0) {
|
|
68
|
+
hasDependencies.add(serviceName);
|
|
69
|
+
}
|
|
70
|
+
factory.dependsOn.forEach((dependency) => {
|
|
71
|
+
hasDependents.add(dependency.name);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
// Define nodes with special styling for leaves and roots
|
|
75
|
+
const nodeIds = new Map();
|
|
76
|
+
let nodeCounter = 0;
|
|
77
|
+
factories.forEach((factory) => {
|
|
78
|
+
const serviceName = factory.provides.name;
|
|
79
|
+
const nodeId = `node${nodeCounter++}`;
|
|
80
|
+
nodeIds.set(serviceName, nodeId);
|
|
81
|
+
const isLeaf = !hasDependencies.has(serviceName);
|
|
82
|
+
const isRoot = !hasDependents.has(serviceName);
|
|
83
|
+
let nodeStyle = '';
|
|
84
|
+
if (highlightLeaves && isLeaf) {
|
|
85
|
+
nodeStyle = ' [fillcolor="#c8e6c9", color="#388e3c"]';
|
|
86
|
+
}
|
|
87
|
+
else if (highlightRoots && isRoot) {
|
|
88
|
+
nodeStyle = ' [fillcolor="#ffccbc", color="#d84315"]';
|
|
89
|
+
}
|
|
90
|
+
lines.push(` ${nodeId} [label="${escapeDotString(serviceName)}"]${nodeStyle};`);
|
|
91
|
+
});
|
|
92
|
+
lines.push('');
|
|
93
|
+
// Define edges (dependencies)
|
|
94
|
+
factories.forEach((factory) => {
|
|
95
|
+
const serviceName = factory.provides.name;
|
|
96
|
+
const serviceNodeId = nodeIds.get(serviceName);
|
|
97
|
+
factory.dependsOn.forEach((dependency) => {
|
|
98
|
+
const depName = dependency.name;
|
|
99
|
+
const depNodeId = nodeIds.get(depName);
|
|
100
|
+
if (depNodeId) {
|
|
101
|
+
// Arrow points from dependent to dependency (what needs it -> what provides it)
|
|
102
|
+
lines.push(` ${serviceNodeId} -> ${depNodeId};`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
// Close the digraph
|
|
107
|
+
lines.push('}');
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Prints a DOT representation of a service module graph to the console.
|
|
112
|
+
* The output can be used to visualize the graph using online graph visualization tools.
|
|
113
|
+
*
|
|
114
|
+
* @param module - The service module representing the graph to be converted into DOT format.
|
|
115
|
+
* @param options - Optional configurations to customize the output of the DOT graph.
|
|
116
|
+
*/
|
|
117
|
+
function printDotGraph(module, options) {
|
|
118
|
+
console.log(createDotGraph(module, options));
|
|
119
|
+
console.log('\n\nCopy the DOT output above and paste it into:');
|
|
120
|
+
console.log('https://dreampuf.github.io/GraphvizOnline/');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generates a Mermaid flowchart from a ServiceModule.
|
|
124
|
+
* The output can be visualized using Mermaid-compatible tools or online viewers like:
|
|
125
|
+
* - https://mermaid.live/
|
|
126
|
+
*
|
|
127
|
+
* Arrows point from dependents to dependencies (what needs it -> what provides it).
|
|
128
|
+
*
|
|
129
|
+
* @param module - The ServiceModule to convert to Mermaid notation
|
|
130
|
+
* @param options - Optional configuration for the graph appearance
|
|
131
|
+
* @returns A string containing the Mermaid flowchart
|
|
132
|
+
*/
|
|
133
|
+
function createMermaidGraph(module, { direction, highlightLeaves, highlightRoots } = {
|
|
134
|
+
direction: 'TB',
|
|
135
|
+
highlightLeaves: true,
|
|
136
|
+
highlightRoots: true,
|
|
137
|
+
}) {
|
|
138
|
+
const factories = module.factories;
|
|
139
|
+
const lines = [];
|
|
140
|
+
// Start the flowchart
|
|
141
|
+
lines.push(`flowchart ${direction}`);
|
|
142
|
+
// Build dependency maps to identify leaves and roots
|
|
143
|
+
const hasDependencies = new Set();
|
|
144
|
+
const hasDependents = new Set();
|
|
145
|
+
factories.forEach((factory) => {
|
|
146
|
+
const serviceName = factory.provides.name;
|
|
147
|
+
if (factory.dependsOn.length > 0) {
|
|
148
|
+
hasDependencies.add(serviceName);
|
|
149
|
+
}
|
|
150
|
+
factory.dependsOn.forEach((dependency) => {
|
|
151
|
+
hasDependents.add(dependency.name);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// Define nodes with special styling for leaves and roots
|
|
155
|
+
const nodeIds = new Map();
|
|
156
|
+
let nodeCounter = 0;
|
|
157
|
+
factories.forEach((factory) => {
|
|
158
|
+
const serviceName = factory.provides.name;
|
|
159
|
+
const nodeId = `node${nodeCounter++}`;
|
|
160
|
+
nodeIds.set(serviceName, nodeId);
|
|
161
|
+
lines.push(` ${nodeId}["${escapeMermaidString(serviceName)}"]`);
|
|
162
|
+
});
|
|
163
|
+
lines.push('');
|
|
164
|
+
// Define edges (dependencies)
|
|
165
|
+
factories.forEach((factory) => {
|
|
166
|
+
const serviceName = factory.provides.name;
|
|
167
|
+
const serviceNodeId = nodeIds.get(serviceName);
|
|
168
|
+
factory.dependsOn.forEach((dependency) => {
|
|
169
|
+
const depName = dependency.name;
|
|
170
|
+
const depNodeId = nodeIds.get(depName);
|
|
171
|
+
if (depNodeId) {
|
|
172
|
+
// Arrow points from dependent to dependency (what needs it -> what provides it)
|
|
173
|
+
lines.push(` ${serviceNodeId} --> ${depNodeId}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
lines.push('');
|
|
178
|
+
// Apply styling
|
|
179
|
+
factories.forEach((factory) => {
|
|
180
|
+
const serviceName = factory.provides.name;
|
|
181
|
+
const serviceNodeId = nodeIds.get(serviceName);
|
|
182
|
+
const isLeaf = !hasDependencies.has(serviceName);
|
|
183
|
+
const isRoot = !hasDependents.has(serviceName);
|
|
184
|
+
if (highlightLeaves && isLeaf) {
|
|
185
|
+
lines.push(` style ${serviceNodeId} fill:#c8e6c9,stroke:#388e3c`);
|
|
186
|
+
}
|
|
187
|
+
else if (highlightRoots && isRoot) {
|
|
188
|
+
lines.push(` style ${serviceNodeId} fill:#ffccbc,stroke:#d84315`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Default style
|
|
192
|
+
lines.push(` style ${serviceNodeId} fill:#e1f5ff,stroke:#0288d1`);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return lines.join('\n');
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Prints a Mermaid representation of a service module graph to the console.
|
|
199
|
+
* The output can be used to visualize the graph using online Mermaid tools.
|
|
200
|
+
*
|
|
201
|
+
* @param module - The service module representing the graph to be converted into Mermaid format.
|
|
202
|
+
* @param options - Optional configurations to customize the output of the Mermaid graph.
|
|
203
|
+
*/
|
|
204
|
+
function printMermaidGraph(module, options) {
|
|
205
|
+
console.log(createMermaidGraph(module, options));
|
|
206
|
+
console.log('\n\nCopy the Mermaid output above and paste it into:');
|
|
207
|
+
console.log('https://mermaid.live/');
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@composed-di/core",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.5.0-alpha",
|
|
5
|
+
"author": "Juan Herrera juanhr454@gmail.com",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"keywords": [
|
|
22
|
+
"dependency injection",
|
|
23
|
+
"di",
|
|
24
|
+
"lazy dependency injection"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/imherrera/composed-di.git",
|
|
30
|
+
"directory": "packages/core"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc --build",
|
|
37
|
+
"clean": "tsc --build --clean"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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 class ServiceModuleInitError extends Error {
|
|
6
|
+
name = 'ServiceModuleInitError';
|
|
7
|
+
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when a requested service cannot be found within the ServiceModule.
|
|
15
|
+
* This typically occurs when no factory has been registered for the given ServiceKey.
|
|
16
|
+
*/
|
|
17
|
+
export class ServiceFactoryNotFoundError extends Error {
|
|
18
|
+
name = 'ServiceFactoryNotFoundError';
|
|
19
|
+
|
|
20
|
+
constructor(message: string) {
|
|
21
|
+
super(message);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './serviceKey';
|
|
2
|
+
export * from './serviceModule';
|
|
3
|
+
export * from './serviceFactory';
|
|
4
|
+
export * from './serviceScope';
|
|
5
|
+
export * from './serviceSelector';
|
|
6
|
+
export * from './serviceEventListener';
|
|
7
|
+
export * from './errors';
|
|
8
|
+
export * from './utils';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { ServiceKey } from './serviceKey';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A handle representing a single in-flight operation (initialization,
|
|
5
|
+
* disposal, or method call), returned by a ServiceEventListener when the
|
|
6
|
+
* operation starts.
|
|
7
|
+
*
|
|
8
|
+
* Exactly one of `end` or `error` is invoked when the operation finishes,
|
|
9
|
+
* so implementations can close over per-call state (start time, an
|
|
10
|
+
* OpenTelemetry span, a correlation id) without any bookkeeping to pair
|
|
11
|
+
* concurrent start/finish events.
|
|
12
|
+
*/
|
|
13
|
+
export interface EventSpan {
|
|
14
|
+
/**
|
|
15
|
+
* Invoked when the operation completes successfully. For methods that
|
|
16
|
+
* return a promise, this fires when the promise resolves, not when the
|
|
17
|
+
* method returns.
|
|
18
|
+
*
|
|
19
|
+
* Whether to retain or log the outcome is the implementation's choice;
|
|
20
|
+
* the value is passed by reference, so implementations must not mutate it.
|
|
21
|
+
*
|
|
22
|
+
* @param outcome - The outcome of the operation. Present for method call
|
|
23
|
+
* spans (the return or resolved value) and initialize spans (the service
|
|
24
|
+
* instance), absent for dispose spans.
|
|
25
|
+
* @return void
|
|
26
|
+
*/
|
|
27
|
+
end?(outcome?: EventOutcome): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Invoked when the operation throws or rejects. Terminal like `end`;
|
|
31
|
+
* the two are mutually exclusive. The error is rethrown to the caller
|
|
32
|
+
* after this is invoked.
|
|
33
|
+
*
|
|
34
|
+
* @param error - The error object or value that was thrown or rejected.
|
|
35
|
+
* @return void
|
|
36
|
+
*/
|
|
37
|
+
error?(error: unknown): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The successful outcome of an operation, delivered to EventSpan.end.
|
|
42
|
+
* Future fields (e.g. a correlation id) are added here rather than as
|
|
43
|
+
* extra parameters.
|
|
44
|
+
*/
|
|
45
|
+
export interface EventOutcome {
|
|
46
|
+
/**
|
|
47
|
+
* The value produced by the operation: the return value for synchronous
|
|
48
|
+
* methods, or the resolved value for methods returning a promise.
|
|
49
|
+
*/
|
|
50
|
+
result: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Context of a service initialization, delivered to onInitialize.
|
|
55
|
+
* Future fields are added here rather than as extra parameters.
|
|
56
|
+
*/
|
|
57
|
+
export interface InitializeContext {
|
|
58
|
+
/**
|
|
59
|
+
* The unique identifier of the service that is being initialized.
|
|
60
|
+
*/
|
|
61
|
+
key: ServiceKey<unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Context of a service disposal, delivered to onDispose.
|
|
66
|
+
* Future fields are added here rather than as extra parameters.
|
|
67
|
+
*/
|
|
68
|
+
export interface DisposeContext {
|
|
69
|
+
/**
|
|
70
|
+
* The unique identifier of the service that is being disposed.
|
|
71
|
+
*/
|
|
72
|
+
key: ServiceKey<unknown>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Context of a method invocation, delivered to onMethodCall.
|
|
77
|
+
* Future fields are added here rather than as extra parameters.
|
|
78
|
+
*/
|
|
79
|
+
export interface MethodCallContext {
|
|
80
|
+
/**
|
|
81
|
+
* The unique identifier of the service the method belongs to.
|
|
82
|
+
*/
|
|
83
|
+
key: ServiceKey<unknown>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The name of the method that is being called.
|
|
87
|
+
*/
|
|
88
|
+
methodName: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The arguments the method was invoked with, passed by reference;
|
|
92
|
+
* implementations must not mutate them.
|
|
93
|
+
*/
|
|
94
|
+
args: readonly unknown[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Interface for listening to service events. Implement this interface to
|
|
99
|
+
* observe lifecycle events and method calls of services in a module.
|
|
100
|
+
*
|
|
101
|
+
* Each method is invoked when the corresponding operation starts and may
|
|
102
|
+
* return an EventSpan that is notified when that operation finishes.
|
|
103
|
+
* Returning nothing opts out of completion tracking for that call.
|
|
104
|
+
*/
|
|
105
|
+
export interface ServiceEventListener {
|
|
106
|
+
/**
|
|
107
|
+
* Invoked at the start of the initialization process for a specific service.
|
|
108
|
+
*
|
|
109
|
+
* @param context - Context of the initialization, including the service key.
|
|
110
|
+
* @return An EventSpan notified when initialization finishes, or void.
|
|
111
|
+
*/
|
|
112
|
+
onInitialize?(context: InitializeContext): EventSpan | void;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Invoked when the disposal process for a service starts.
|
|
116
|
+
*
|
|
117
|
+
* @param context - Context of the disposal, including the service key.
|
|
118
|
+
* @return An EventSpan notified when disposal finishes, or void.
|
|
119
|
+
*/
|
|
120
|
+
onDispose?(context: DisposeContext): EventSpan | void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Invoked when a method call starts on a service instance.
|
|
124
|
+
*
|
|
125
|
+
* @param context - Context of the invocation, including the service key,
|
|
126
|
+
* the method name, and its arguments.
|
|
127
|
+
* @return An EventSpan notified when the call finishes, or void.
|
|
128
|
+
*/
|
|
129
|
+
onMethodCall?(context: MethodCallContext): EventSpan | void;
|
|
130
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ServiceKey, ServiceSelectorKey } from './serviceKey';
|
|
2
|
+
import { ServiceScope } from './serviceScope';
|
|
3
|
+
import { ServiceSelector } from './serviceSelector';
|
|
4
|
+
|
|
5
|
+
// Helper types to extract the type from ServiceKey or ServiceSelectorKey
|
|
6
|
+
type ServiceType<T> =
|
|
7
|
+
T extends ServiceSelectorKey<infer U>
|
|
8
|
+
? ServiceSelector<U>
|
|
9
|
+
: T extends ServiceKey<infer U>
|
|
10
|
+
? U
|
|
11
|
+
: never;
|
|
12
|
+
|
|
13
|
+
// Helper types to convert an array/tuple of ServiceKey to tuple of their types
|
|
14
|
+
type DependencyTypes<T extends readonly ServiceKey<unknown>[]> = {
|
|
15
|
+
[K in keyof T]: ServiceType<T[K]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export abstract class ServiceFactory<
|
|
19
|
+
const T,
|
|
20
|
+
const D extends readonly ServiceKey<unknown>[] = [],
|
|
21
|
+
> {
|
|
22
|
+
abstract provides: ServiceKey<T>;
|
|
23
|
+
abstract dependsOn: D;
|
|
24
|
+
abstract scope?: ServiceScope;
|
|
25
|
+
abstract initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
|
|
26
|
+
abstract dispose?: () => void;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a singleton service factory that ensures a single instance of the provided service is initialized
|
|
30
|
+
* and used throughout the scope lifecycle.
|
|
31
|
+
*/
|
|
32
|
+
static singleton<
|
|
33
|
+
const T,
|
|
34
|
+
const D extends readonly ServiceKey<unknown>[] = [],
|
|
35
|
+
>({
|
|
36
|
+
scope,
|
|
37
|
+
provides,
|
|
38
|
+
dependsOn = [] as unknown as D,
|
|
39
|
+
initialize,
|
|
40
|
+
dispose = () => {},
|
|
41
|
+
}: {
|
|
42
|
+
scope?: ServiceScope;
|
|
43
|
+
provides: ServiceKey<T>;
|
|
44
|
+
dependsOn?: D;
|
|
45
|
+
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
|
|
46
|
+
dispose?: (instance: T) => void;
|
|
47
|
+
}): ServiceFactory<T, D> {
|
|
48
|
+
let promisedInstance: Promise<T> | undefined;
|
|
49
|
+
let resolvedInstance: T | undefined;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
scope,
|
|
53
|
+
provides,
|
|
54
|
+
dependsOn,
|
|
55
|
+
async initialize(...dependencies: DependencyTypes<D>): Promise<T> {
|
|
56
|
+
if (resolvedInstance !== undefined) {
|
|
57
|
+
return resolvedInstance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (promisedInstance !== undefined) {
|
|
61
|
+
return promisedInstance;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Store the reference to the promise so that concurrent requests can wait for it
|
|
65
|
+
promisedInstance = (async () => {
|
|
66
|
+
try {
|
|
67
|
+
resolvedInstance = await initialize(...dependencies);
|
|
68
|
+
return resolvedInstance;
|
|
69
|
+
} finally {
|
|
70
|
+
promisedInstance = undefined;
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
return promisedInstance;
|
|
74
|
+
},
|
|
75
|
+
dispose(): void {
|
|
76
|
+
if (resolvedInstance !== undefined) {
|
|
77
|
+
dispose(resolvedInstance);
|
|
78
|
+
resolvedInstance = undefined;
|
|
79
|
+
}
|
|
80
|
+
promisedInstance = undefined;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a one-shot service factory that initializes a new instance of the provided service
|
|
87
|
+
* every time it is requested.
|
|
88
|
+
*/
|
|
89
|
+
static oneShot<const T, const D extends readonly ServiceKey<unknown>[] = []>({
|
|
90
|
+
provides,
|
|
91
|
+
dependsOn,
|
|
92
|
+
initialize,
|
|
93
|
+
}: {
|
|
94
|
+
provides: ServiceKey<T>;
|
|
95
|
+
dependsOn: D;
|
|
96
|
+
initialize: (...dependencies: DependencyTypes<D>) => T | Promise<T>;
|
|
97
|
+
}): ServiceFactory<T, D> {
|
|
98
|
+
return {
|
|
99
|
+
provides,
|
|
100
|
+
dependsOn,
|
|
101
|
+
initialize,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { ServiceSelector } from './serviceSelector';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A typed token used to identify and retrieve a service from a ServiceModule.
|
|
6
|
+
*
|
|
7
|
+
* ServiceKey acts as a unique identifier for a service type, allowing type-safe
|
|
8
|
+
* dependency injection. Each key has a unique symbol to ensure identity comparison
|
|
9
|
+
* works correctly even if two keys have the same name.
|
|
10
|
+
*
|
|
11
|
+
* @template T The type of service this key identifies.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* interface Logger {
|
|
16
|
+
* log: (msg: string) => void;
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* const LoggerKey = new ServiceKey<Logger>('Logger');
|
|
20
|
+
*
|
|
21
|
+
* // Use with ServiceFactory and ServiceModule
|
|
22
|
+
* const loggerFactory = ServiceFactory.singleton({
|
|
23
|
+
* provides: LoggerKey,
|
|
24
|
+
* dependsOn: [],
|
|
25
|
+
* initialize: () => console,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* const module = ServiceModule.from([loggerFactory]);
|
|
29
|
+
* const logger = await module.get(LoggerKey);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class ServiceKey<T> {
|
|
33
|
+
/**
|
|
34
|
+
* A unique symbol that identifies this service key.
|
|
35
|
+
* Used internally for identity comparison between keys.
|
|
36
|
+
*/
|
|
37
|
+
public readonly symbol: symbol;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a new ServiceKey with the given name.
|
|
41
|
+
*
|
|
42
|
+
* @param name A human-readable name for the service, used in error messages and debugging.
|
|
43
|
+
*/
|
|
44
|
+
constructor(public readonly name: string) {
|
|
45
|
+
this.symbol = Symbol(name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A specialized ServiceKey that groups multiple ServiceKeys of the same type,
|
|
51
|
+
* allowing a service to depend on a selector that can retrieve any of the grouped services.
|
|
52
|
+
*
|
|
53
|
+
* When used in a factory's `dependsOn` array, the factory's `initialize` callback
|
|
54
|
+
* receives a `ServiceSelector<T>` instance instead of a direct service instance.
|
|
55
|
+
* This enables runtime selection between multiple implementations of the same interface.
|
|
56
|
+
*
|
|
57
|
+
* @template T The common type shared by all service keys in this selector.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* interface Logger {
|
|
62
|
+
* log: (msg: string) => void;
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* const ConsoleLoggerKey = new ServiceKey<Logger>('ConsoleLogger');
|
|
66
|
+
* const FileLoggerKey = new ServiceKey<Logger>('FileLogger');
|
|
67
|
+
*
|
|
68
|
+
* // Group multiple logger implementations under one selector
|
|
69
|
+
* const LoggerSelectorKey = new ServiceSelectorKey<Logger>([
|
|
70
|
+
* ConsoleLoggerKey,
|
|
71
|
+
* FileLoggerKey,
|
|
72
|
+
* ]);
|
|
73
|
+
*
|
|
74
|
+
* // Use in a factory's dependsOn array
|
|
75
|
+
* const appFactory = ServiceFactory.singleton({
|
|
76
|
+
* provides: AppKey,
|
|
77
|
+
* dependsOn: [LoggerSelectorKey] as const,
|
|
78
|
+
* initialize: (loggerSelector: ServiceSelector<Logger>) => {
|
|
79
|
+
* // loggerSelector.get(ConsoleLoggerKey) or loggerSelector.get(FileLoggerKey)
|
|
80
|
+
* return new App(loggerSelector);
|
|
81
|
+
* },
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export class ServiceSelectorKey<T> extends ServiceKey<ServiceSelector<T>> {
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new ServiceSelectorKey that groups the provided service keys.
|
|
88
|
+
*
|
|
89
|
+
* @param values An array of ServiceKeys that this selector can provide access to.
|
|
90
|
+
* All keys must be registered in the ServiceModule for dependency validation to pass.
|
|
91
|
+
*/
|
|
92
|
+
constructor(readonly values: ServiceKey<T>[]) {
|
|
93
|
+
super(`ServiceSelector[${values}]`);
|
|
94
|
+
}
|
|
95
|
+
}
|