conduithub 0.1.0 → 1.0.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.
- package/dist/core/conduit-hub/index.cjs +188 -0
- package/dist/core/conduit-hub/index.d.cts +44 -0
- package/dist/core/conduit-hub/index.d.mts +44 -0
- package/dist/core/conduit-hub/index.d.ts +44 -0
- package/dist/core/conduit-hub/index.mjs +186 -0
- package/dist/core/config-manager/index.cjs +216 -0
- package/dist/core/config-manager/index.d.cts +7 -0
- package/dist/core/config-manager/index.d.mts +7 -0
- package/dist/core/config-manager/index.d.ts +7 -0
- package/dist/core/config-manager/index.mjs +214 -0
- package/dist/core/event-bus/index.cjs +154 -53
- package/dist/core/event-bus/index.d.cts +2 -42
- package/dist/core/event-bus/index.d.mts +2 -42
- package/dist/core/event-bus/index.d.ts +2 -42
- package/dist/core/event-bus/index.mjs +153 -52
- package/dist/core/hook/index.cjs +185 -148
- package/dist/core/hook/index.d.cts +28 -16
- package/dist/core/hook/index.d.mts +28 -16
- package/dist/core/hook/index.d.ts +28 -16
- package/dist/core/hook/index.mjs +183 -146
- package/dist/core/index.cjs +17 -1
- package/dist/core/index.d.cts +184 -3
- package/dist/core/index.d.mts +184 -3
- package/dist/core/index.d.ts +184 -3
- package/dist/core/index.mjs +12 -1
- package/dist/core/plugin/index.cjs +271 -0
- package/dist/core/plugin/index.d.cts +7 -0
- package/dist/core/plugin/index.d.mts +7 -0
- package/dist/core/plugin/index.d.ts +7 -0
- package/dist/core/plugin/index.mjs +268 -0
- package/dist/core/service-container/index.cjs +306 -0
- package/dist/core/service-container/index.d.cts +51 -0
- package/dist/core/service-container/index.d.mts +51 -0
- package/dist/core/service-container/index.d.ts +51 -0
- package/dist/core/service-container/index.mjs +304 -0
- package/dist/core/state-manager/index.cjs +195 -0
- package/dist/core/state-manager/index.d.cts +39 -0
- package/dist/core/state-manager/index.d.mts +39 -0
- package/dist/core/state-manager/index.d.ts +39 -0
- package/dist/core/state-manager/index.mjs +193 -0
- package/dist/index.cjs +34 -5
- package/dist/index.d.cts +54 -2
- package/dist/index.d.mts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.mjs +13 -1
- package/dist/shared/conduithub.BDwZXllF.mjs +63 -0
- package/dist/shared/{conduithub.CvMLTa-R.cjs → conduithub.BNQsddJO.cjs} +2 -9
- package/dist/shared/{conduithub.74V0wiLi.mjs → conduithub.BNefRQsK.mjs} +3 -9
- package/dist/shared/conduithub.BZQmkQy7.d.cts +52 -0
- package/dist/shared/conduithub.BqUYv04j.cjs +68 -0
- package/dist/shared/conduithub.Bq_7Xj0J.cjs +18 -0
- package/dist/shared/conduithub.BzLwccre.d.mts +52 -0
- package/dist/shared/conduithub.CkOQG3cD.mjs +14 -0
- package/dist/shared/conduithub.CmZo_Vuc.cjs +38 -0
- package/dist/shared/conduithub.DQO1dRnn.cjs +33 -0
- package/dist/shared/conduithub.DQOWQ-Bx.d.ts +52 -0
- package/dist/shared/conduithub.DsOOeNwU.cjs +269 -0
- package/dist/shared/conduithub.DyQQrHW9.mjs +267 -0
- package/dist/shared/conduithub.G7ICpZIy.mjs +36 -0
- package/dist/shared/conduithub.alPiaJax.mjs +29 -0
- package/dist/shared/conduithub.bsiNMTVD.mjs +59 -0
- package/dist/shared/conduithub.gF2DFc43.cjs +76 -0
- package/dist/utils/index.cjs +18 -5
- package/dist/utils/index.d.cts +33 -2
- package/dist/utils/index.d.mts +33 -2
- package/dist/utils/index.d.ts +33 -2
- package/dist/utils/index.mjs +7 -1
- package/package.json +55 -7
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { E as ERROR_CODE } from '../../shared/conduithub.G7ICpZIy.mjs';
|
|
2
|
+
export { P as PluginManager } from '../../shared/conduithub.DyQQrHW9.mjs';
|
|
3
|
+
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null;
|
|
6
|
+
}
|
|
7
|
+
class BasePlugin {
|
|
8
|
+
config;
|
|
9
|
+
context;
|
|
10
|
+
metadata;
|
|
11
|
+
isInitialized = false;
|
|
12
|
+
isRunning = false;
|
|
13
|
+
managedEventHandlers = [];
|
|
14
|
+
managedHookHandlers = [];
|
|
15
|
+
lock = Promise.resolve();
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.metadata = this.getMetadata();
|
|
19
|
+
}
|
|
20
|
+
async initialize(context) {
|
|
21
|
+
await this.withLock(async () => {
|
|
22
|
+
if (this.isInitialized) return;
|
|
23
|
+
this.context = context;
|
|
24
|
+
try {
|
|
25
|
+
await this.executeLifecycleHook("beforeInit");
|
|
26
|
+
await this.onInitialize();
|
|
27
|
+
this.isInitialized = true;
|
|
28
|
+
await this.executeLifecycleHook("afterInit");
|
|
29
|
+
this.context.logger.log?.(
|
|
30
|
+
"info",
|
|
31
|
+
`Plugin ${this.config.name} initialized successfully`
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
this.context.logger.log?.(
|
|
35
|
+
"error",
|
|
36
|
+
`Failed to initialize plugin ${this.config.name}:`,
|
|
37
|
+
error
|
|
38
|
+
);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
await this.withLock(async () => {
|
|
45
|
+
if (!this.isInitialized)
|
|
46
|
+
throw new Error(ERROR_CODE.PLUGIN_NOT_INITIALIZED);
|
|
47
|
+
if (this.isRunning) return;
|
|
48
|
+
try {
|
|
49
|
+
await this.executeLifecycleHook("beforeStart");
|
|
50
|
+
await this.onStart();
|
|
51
|
+
this.isRunning = true;
|
|
52
|
+
await this.executeLifecycleHook("afterStart");
|
|
53
|
+
this.context.logger.log?.(
|
|
54
|
+
"info",
|
|
55
|
+
`Plugin ${this.config.name} started successfully`
|
|
56
|
+
);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
this.context.logger.log?.(
|
|
59
|
+
"error",
|
|
60
|
+
`Failed to start plugin ${this.config.name}:`,
|
|
61
|
+
error
|
|
62
|
+
);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async stop() {
|
|
68
|
+
await this.withLock(async () => {
|
|
69
|
+
if (!this.isRunning) return;
|
|
70
|
+
try {
|
|
71
|
+
await this.executeLifecycleHook("beforeStop");
|
|
72
|
+
await this.onStop();
|
|
73
|
+
this.isRunning = false;
|
|
74
|
+
await this.executeLifecycleHook("afterStop");
|
|
75
|
+
this.context.logger.log?.(
|
|
76
|
+
"info",
|
|
77
|
+
`Plugin ${this.config.name} stopped successfully`
|
|
78
|
+
);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.context.logger.log?.(
|
|
81
|
+
"error",
|
|
82
|
+
`Failed to stop plugin ${this.config.name}:`,
|
|
83
|
+
error
|
|
84
|
+
);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async destroy() {
|
|
90
|
+
await this.withLock(async () => {
|
|
91
|
+
if (!this.isInitialized)
|
|
92
|
+
throw new Error(ERROR_CODE.PLUGIN_NOT_INITIALIZED);
|
|
93
|
+
try {
|
|
94
|
+
await this.executeLifecycleHook("beforeDestroy");
|
|
95
|
+
await this.onDestroy();
|
|
96
|
+
await this.cleanupManagedHandlers();
|
|
97
|
+
await this.cleanupManagedHooks();
|
|
98
|
+
this.isInitialized = false;
|
|
99
|
+
await this.executeLifecycleHook("afterDestroy");
|
|
100
|
+
this.context.logger.log?.(
|
|
101
|
+
"info",
|
|
102
|
+
`Plugin ${this.config.name} destroyed successfully`
|
|
103
|
+
);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.context.logger.log?.(
|
|
106
|
+
"error",
|
|
107
|
+
`Failed to destroy plugin ${this.config.name}:`,
|
|
108
|
+
error
|
|
109
|
+
);
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async executeLifecycleHook(hook) {
|
|
115
|
+
try {
|
|
116
|
+
await this.context.hookSystem.executeHook(
|
|
117
|
+
`plugin:${this.config.name}:${hook}`,
|
|
118
|
+
{
|
|
119
|
+
plugin: this,
|
|
120
|
+
config: this.config,
|
|
121
|
+
context: this.context
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.context.logger.log?.(
|
|
126
|
+
"warn",
|
|
127
|
+
`Lifecycle hook ${hook} for plugin ${this.config.name} failed:`,
|
|
128
|
+
error
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
getConfig() {
|
|
133
|
+
return { ...this.config };
|
|
134
|
+
}
|
|
135
|
+
getContext() {
|
|
136
|
+
return this.context;
|
|
137
|
+
}
|
|
138
|
+
isPluginInitialized() {
|
|
139
|
+
return this.isInitialized;
|
|
140
|
+
}
|
|
141
|
+
isPluginRunning() {
|
|
142
|
+
return this.isRunning;
|
|
143
|
+
}
|
|
144
|
+
async emit(event, data) {
|
|
145
|
+
await this.context.eventBus.emit(event, data);
|
|
146
|
+
}
|
|
147
|
+
on(event, listener) {
|
|
148
|
+
return this.context.eventBus.on(
|
|
149
|
+
event,
|
|
150
|
+
(eventData) => listener(eventData.payload, eventData)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
once(event, listener) {
|
|
154
|
+
return this.context.eventBus.once(
|
|
155
|
+
event,
|
|
156
|
+
(eventData) => listener(eventData.payload, eventData)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
onManaged(event, listener) {
|
|
160
|
+
const id = this.on(event, listener);
|
|
161
|
+
this.managedEventHandlers.push({ event, id });
|
|
162
|
+
return id;
|
|
163
|
+
}
|
|
164
|
+
onceManaged(event, listener) {
|
|
165
|
+
const id = this.once(event, listener);
|
|
166
|
+
this.managedEventHandlers.push({ event, id });
|
|
167
|
+
return id;
|
|
168
|
+
}
|
|
169
|
+
off(event, handlerId) {
|
|
170
|
+
this.context.eventBus.off(event, handlerId);
|
|
171
|
+
}
|
|
172
|
+
async executeHook(hook, input) {
|
|
173
|
+
return await this.context.hookSystem.executeHook(hook, input);
|
|
174
|
+
}
|
|
175
|
+
async executeHookSeries(hook, input) {
|
|
176
|
+
return await this.context.hookSystem.executeHookSeries(hook, input);
|
|
177
|
+
}
|
|
178
|
+
async executeHookSeriesWithContext(hook, input, context) {
|
|
179
|
+
return await this.context.hookSystem.executeHookSeriesWithContext(
|
|
180
|
+
hook,
|
|
181
|
+
input,
|
|
182
|
+
context
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
async registerHook(hook, handler, priority = 0, customId) {
|
|
186
|
+
return await this.context.hookSystem.registerHook(
|
|
187
|
+
hook,
|
|
188
|
+
handler,
|
|
189
|
+
priority,
|
|
190
|
+
customId
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
async registerHookManaged(hook, handler, priority = 0, customId) {
|
|
194
|
+
const id = await this.registerHook(hook, handler, priority, customId);
|
|
195
|
+
this.managedHookHandlers.push({ hook, id });
|
|
196
|
+
return id;
|
|
197
|
+
}
|
|
198
|
+
async unregisterHook(hook, handlerId) {
|
|
199
|
+
return await this.context.hookSystem.unregisterHook(hook, handlerId);
|
|
200
|
+
}
|
|
201
|
+
getStatus() {
|
|
202
|
+
return { initialized: this.isInitialized, running: this.isRunning };
|
|
203
|
+
}
|
|
204
|
+
getName() {
|
|
205
|
+
return this.config.name;
|
|
206
|
+
}
|
|
207
|
+
getVersion() {
|
|
208
|
+
return this.config.version;
|
|
209
|
+
}
|
|
210
|
+
isEnabled() {
|
|
211
|
+
return this.config.enabled;
|
|
212
|
+
}
|
|
213
|
+
getState(key) {
|
|
214
|
+
return this.context.stateManager.get(key);
|
|
215
|
+
}
|
|
216
|
+
setState(key, value) {
|
|
217
|
+
this.context.stateManager.set(key, value);
|
|
218
|
+
}
|
|
219
|
+
deleteState(key) {
|
|
220
|
+
this.context.stateManager.delete(key);
|
|
221
|
+
}
|
|
222
|
+
get logger() {
|
|
223
|
+
return this.context.logger;
|
|
224
|
+
}
|
|
225
|
+
updateConfig(update) {
|
|
226
|
+
const next = {
|
|
227
|
+
...this.config,
|
|
228
|
+
...update
|
|
229
|
+
};
|
|
230
|
+
if (isRecord(update) && "config" in update && isRecord(update.config) && isRecord(next.config)) {
|
|
231
|
+
next.config = { ...next.config, ...update.config };
|
|
232
|
+
}
|
|
233
|
+
this.config = next;
|
|
234
|
+
}
|
|
235
|
+
async cleanupManagedHandlers() {
|
|
236
|
+
for (const { event, id } of this.managedEventHandlers) {
|
|
237
|
+
try {
|
|
238
|
+
this.off(event, id);
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this.managedEventHandlers = [];
|
|
243
|
+
}
|
|
244
|
+
async cleanupManagedHooks() {
|
|
245
|
+
for (const { hook, id } of this.managedHookHandlers) {
|
|
246
|
+
try {
|
|
247
|
+
await this.unregisterHook(hook, id);
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.managedHookHandlers = [];
|
|
252
|
+
}
|
|
253
|
+
async withLock(operation) {
|
|
254
|
+
const currentLock = this.lock;
|
|
255
|
+
let resolveLock;
|
|
256
|
+
this.lock = new Promise((resolve) => {
|
|
257
|
+
resolveLock = resolve;
|
|
258
|
+
});
|
|
259
|
+
await currentLock;
|
|
260
|
+
try {
|
|
261
|
+
return await operation();
|
|
262
|
+
} finally {
|
|
263
|
+
resolveLock();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export { BasePlugin };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const index = require('../../shared/conduithub.BqUYv04j.cjs');
|
|
4
|
+
const logger = require('../../shared/conduithub.BNQsddJO.cjs');
|
|
5
|
+
require('process');
|
|
6
|
+
require('readline');
|
|
7
|
+
require('uuid');
|
|
8
|
+
const code = require('../../shared/conduithub.CmZo_Vuc.cjs');
|
|
9
|
+
|
|
10
|
+
class ServiceContainer {
|
|
11
|
+
services = /* @__PURE__ */ new Map();
|
|
12
|
+
instances = /* @__PURE__ */ new Map();
|
|
13
|
+
resolutionStack = /* @__PURE__ */ new Set();
|
|
14
|
+
pendingResolutions = /* @__PURE__ */ new Map();
|
|
15
|
+
isInitialized = false;
|
|
16
|
+
async initialize() {
|
|
17
|
+
if (this.isInitialized) return;
|
|
18
|
+
this.isInitialized = true;
|
|
19
|
+
logger.logger.info("Service Container initialized successfully");
|
|
20
|
+
}
|
|
21
|
+
async shutdown() {
|
|
22
|
+
if (!this.isInitialized) return;
|
|
23
|
+
logger.logger.info("Shutting down Service Container...");
|
|
24
|
+
this.services.clear();
|
|
25
|
+
this.instances.clear();
|
|
26
|
+
this.resolutionStack.clear();
|
|
27
|
+
this.pendingResolutions.clear();
|
|
28
|
+
this.isInitialized = false;
|
|
29
|
+
logger.logger.success("Service Container shutdown completed");
|
|
30
|
+
}
|
|
31
|
+
ensureInitialized() {
|
|
32
|
+
if (!this.isInitialized) {
|
|
33
|
+
throw new index.ConduithubError(code.ERROR_CODE.SERVICE_CONTAINER_NOT_INITIALIZED);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
register(name, factory, singleton = true, dependencies, metadata) {
|
|
37
|
+
this.ensureInitialized();
|
|
38
|
+
const descriptor = {
|
|
39
|
+
name,
|
|
40
|
+
factory,
|
|
41
|
+
singleton,
|
|
42
|
+
dependencies,
|
|
43
|
+
metadata
|
|
44
|
+
};
|
|
45
|
+
this.services.set(name, descriptor);
|
|
46
|
+
logger.logger.info(`Service registered: ${name} (singleton: ${singleton})`);
|
|
47
|
+
}
|
|
48
|
+
async resolve(name) {
|
|
49
|
+
this.ensureInitialized();
|
|
50
|
+
const service = this.services.get(name);
|
|
51
|
+
if (!service) {
|
|
52
|
+
throw new index.ConduithubError(`${code.ERROR_CODE.SERVICE_NOT_FOUND}: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
if (service.singleton && this.instances.has(name)) {
|
|
55
|
+
return this.instances.get(name);
|
|
56
|
+
}
|
|
57
|
+
if (service.singleton && this.pendingResolutions.has(name)) {
|
|
58
|
+
return this.pendingResolutions.get(name);
|
|
59
|
+
}
|
|
60
|
+
if (this.resolutionStack.has(name)) {
|
|
61
|
+
throw new index.ConduithubError(
|
|
62
|
+
`${code.ERROR_CODE.CIRCULAR_DEPENDENCY_DETECTED}: ${name}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
this.resolutionStack.add(name);
|
|
66
|
+
const resolutionPromise = (async () => {
|
|
67
|
+
try {
|
|
68
|
+
await this.resolveDependencies(service.dependencies);
|
|
69
|
+
const instance = await Promise.resolve(service.factory());
|
|
70
|
+
if (service.singleton) {
|
|
71
|
+
this.instances.set(name, instance);
|
|
72
|
+
}
|
|
73
|
+
return instance;
|
|
74
|
+
} finally {
|
|
75
|
+
this.resolutionStack.delete(name);
|
|
76
|
+
if (service.singleton) {
|
|
77
|
+
this.pendingResolutions.delete(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
if (service.singleton) {
|
|
82
|
+
this.pendingResolutions.set(name, resolutionPromise);
|
|
83
|
+
}
|
|
84
|
+
return resolutionPromise;
|
|
85
|
+
}
|
|
86
|
+
async resolveDependencies(dependencies) {
|
|
87
|
+
if (!dependencies?.length) return;
|
|
88
|
+
await Promise.all(dependencies.map((dep) => this.resolve(dep)));
|
|
89
|
+
}
|
|
90
|
+
has(name) {
|
|
91
|
+
return this.services.has(name);
|
|
92
|
+
}
|
|
93
|
+
remove(name) {
|
|
94
|
+
this.ensureInitialized();
|
|
95
|
+
if (!this.services.has(name)) {
|
|
96
|
+
throw new index.ConduithubError(`${code.ERROR_CODE.SERVICE_NOT_FOUND}: ${name}`);
|
|
97
|
+
}
|
|
98
|
+
this.instances.delete(name);
|
|
99
|
+
this.services.delete(name);
|
|
100
|
+
logger.logger.info(`Service removed: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
clear() {
|
|
103
|
+
this.ensureInitialized();
|
|
104
|
+
this.instances.clear();
|
|
105
|
+
this.services.clear();
|
|
106
|
+
logger.logger.info("All services cleared from the Service Container");
|
|
107
|
+
}
|
|
108
|
+
list() {
|
|
109
|
+
this.ensureInitialized();
|
|
110
|
+
return Array.from(this.services.values());
|
|
111
|
+
}
|
|
112
|
+
isSingleton(name) {
|
|
113
|
+
const descriptor = this.services.get(name);
|
|
114
|
+
return descriptor ? descriptor.singleton : false;
|
|
115
|
+
}
|
|
116
|
+
getDescriptor(name) {
|
|
117
|
+
return this.services.get(name);
|
|
118
|
+
}
|
|
119
|
+
getAllDescriptors() {
|
|
120
|
+
return Array.from(this.services.values());
|
|
121
|
+
}
|
|
122
|
+
async createInstance(name) {
|
|
123
|
+
this.ensureInitialized();
|
|
124
|
+
const descriptor = this.services.get(name);
|
|
125
|
+
if (!descriptor) {
|
|
126
|
+
throw new index.ConduithubError(`${code.ERROR_CODE.SERVICE_NOT_FOUND}: ${name}`);
|
|
127
|
+
}
|
|
128
|
+
if (descriptor.singleton) {
|
|
129
|
+
throw new index.ConduithubError(`${code.ERROR_CODE.SERVICE_IS_SINGLETON}: ${name}`);
|
|
130
|
+
}
|
|
131
|
+
if (this.instances.has(name)) {
|
|
132
|
+
return this.instances.get(name);
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await this.resolveDependencies(descriptor.dependencies);
|
|
136
|
+
const instance = await Promise.resolve(descriptor.factory());
|
|
137
|
+
this.instances.set(name, instance);
|
|
138
|
+
logger.logger.info(`Instance created for service: ${name}`);
|
|
139
|
+
return instance;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.logger.error(`Failed to create instance for service: ${name}`, error);
|
|
142
|
+
throw new index.ConduithubError(
|
|
143
|
+
`${code.ERROR_CODE.SERVICE_CREATION_FAILED}: ${name}`,
|
|
144
|
+
{
|
|
145
|
+
code: "SERVICE_CREATION_FAILED",
|
|
146
|
+
details: {
|
|
147
|
+
serviceName: name,
|
|
148
|
+
error: error instanceof Error ? error.message : String(error)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
getStats() {
|
|
155
|
+
const descriptors = this.list();
|
|
156
|
+
let singletonCount = 0;
|
|
157
|
+
let transientCount = 0;
|
|
158
|
+
descriptors.forEach((descriptor) => {
|
|
159
|
+
if (descriptor.singleton) {
|
|
160
|
+
singletonCount++;
|
|
161
|
+
} else {
|
|
162
|
+
transientCount++;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
totalServices: descriptors.length,
|
|
167
|
+
singletonCount,
|
|
168
|
+
transientCount,
|
|
169
|
+
instanceCount: this.instances.size,
|
|
170
|
+
serviceNames: descriptors.map((d) => d.name)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
validateDependencies() {
|
|
174
|
+
const errors = [];
|
|
175
|
+
for (const [name, descriptor] of this.services) {
|
|
176
|
+
if (descriptor.dependencies) {
|
|
177
|
+
for (const dependency of descriptor.dependencies) {
|
|
178
|
+
if (!this.services.has(dependency)) {
|
|
179
|
+
errors.push(
|
|
180
|
+
`Service '${name}' depends on '${dependency}', which is not registered.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
valid: errors.length === 0,
|
|
188
|
+
errors
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
getDependencyGraph() {
|
|
192
|
+
const graph = {};
|
|
193
|
+
for (const [name, descriptor] of this.services) {
|
|
194
|
+
graph[name] = descriptor.dependencies ? [...descriptor.dependencies] : [];
|
|
195
|
+
}
|
|
196
|
+
return graph;
|
|
197
|
+
}
|
|
198
|
+
getServiceNames() {
|
|
199
|
+
return Array.from(this.services.keys());
|
|
200
|
+
}
|
|
201
|
+
detectCircularDependencies() {
|
|
202
|
+
const graph = this.getDependencyGraph();
|
|
203
|
+
const visited = /* @__PURE__ */ new Set();
|
|
204
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
205
|
+
const cycles = [];
|
|
206
|
+
const dfs = (node, path) => {
|
|
207
|
+
if (recursionStack.has(node)) {
|
|
208
|
+
const cycleStart = path.indexOf(node);
|
|
209
|
+
const cycle = path.slice(cycleStart).concat([node]);
|
|
210
|
+
cycles.push(cycle);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (visited.has(node)) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
visited.add(node);
|
|
217
|
+
recursionStack.add(node);
|
|
218
|
+
path.push(node);
|
|
219
|
+
for (const dependency of graph[node] || []) {
|
|
220
|
+
dfs(dependency, [...path]);
|
|
221
|
+
}
|
|
222
|
+
recursionStack.delete(node);
|
|
223
|
+
path.pop();
|
|
224
|
+
};
|
|
225
|
+
for (const node of Object.keys(graph)) {
|
|
226
|
+
if (!visited.has(node)) {
|
|
227
|
+
dfs(node, []);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return cycles;
|
|
231
|
+
}
|
|
232
|
+
async resolveAll(names) {
|
|
233
|
+
this.ensureInitialized();
|
|
234
|
+
return Promise.all(names.map((name) => this.resolve(name)));
|
|
235
|
+
}
|
|
236
|
+
findServicesByMetadata(key, value) {
|
|
237
|
+
this.ensureInitialized();
|
|
238
|
+
return this.list().filter((descriptor) => {
|
|
239
|
+
if (!descriptor.metadata || !(key in descriptor.metadata)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return value === void 0 || descriptor.metadata[key] === value;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
getTopologicalOrder() {
|
|
246
|
+
const graph = this.getDependencyGraph();
|
|
247
|
+
const visited = /* @__PURE__ */ new Set();
|
|
248
|
+
const result = [];
|
|
249
|
+
const dfs = (node) => {
|
|
250
|
+
if (visited.has(node)) return;
|
|
251
|
+
visited.add(node);
|
|
252
|
+
for (const dependency of graph[node] || []) {
|
|
253
|
+
dfs(dependency);
|
|
254
|
+
}
|
|
255
|
+
result.push(node);
|
|
256
|
+
};
|
|
257
|
+
for (const node of Object.keys(graph)) {
|
|
258
|
+
dfs(node);
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
async warmup(serviceNames) {
|
|
263
|
+
this.ensureInitialized();
|
|
264
|
+
const namesToWarmup = serviceNames || this.getServiceNames();
|
|
265
|
+
const singletonServices = namesToWarmup.filter(
|
|
266
|
+
(name) => this.isSingleton(name)
|
|
267
|
+
);
|
|
268
|
+
await Promise.all(
|
|
269
|
+
singletonServices.map(async (name) => {
|
|
270
|
+
try {
|
|
271
|
+
await this.resolve(name);
|
|
272
|
+
logger.logger.info(`Warmed up service: ${name}`);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
logger.logger.error(`Failed to warm up service: ${name}`, error);
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
clone() {
|
|
280
|
+
const newContainer = new ServiceContainer();
|
|
281
|
+
for (const [name, descriptor] of this.services) {
|
|
282
|
+
newContainer.services.set(name, { ...descriptor });
|
|
283
|
+
}
|
|
284
|
+
return newContainer;
|
|
285
|
+
}
|
|
286
|
+
dispose(name) {
|
|
287
|
+
this.ensureInitialized();
|
|
288
|
+
if (!this.services.has(name)) {
|
|
289
|
+
throw new index.ConduithubError(`${code.ERROR_CODE.SERVICE_NOT_FOUND}: ${name}`);
|
|
290
|
+
}
|
|
291
|
+
const instance = this.instances.get(name);
|
|
292
|
+
if (instance && typeof instance === "object" && "dispose" in instance) {
|
|
293
|
+
try {
|
|
294
|
+
if (typeof instance.dispose === "function") {
|
|
295
|
+
instance.dispose();
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.logger.warn(`Error disposing service ${name}:`, error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
this.instances.delete(name);
|
|
302
|
+
logger.logger.info(`Service disposed: ${name}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
exports.ServiceContainer = ServiceContainer;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
interface ServiceDescriptor<T = unknown> {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly factory: () => Promise<T> | T;
|
|
4
|
+
readonly singleton: boolean;
|
|
5
|
+
readonly dependencies?: readonly string[];
|
|
6
|
+
readonly metadata?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
declare class ServiceContainer {
|
|
9
|
+
private readonly services;
|
|
10
|
+
private readonly instances;
|
|
11
|
+
private readonly resolutionStack;
|
|
12
|
+
private readonly pendingResolutions;
|
|
13
|
+
private isInitialized;
|
|
14
|
+
initialize(): Promise<void>;
|
|
15
|
+
shutdown(): Promise<void>;
|
|
16
|
+
private ensureInitialized;
|
|
17
|
+
register<T>(name: string, factory: () => T | Promise<T>, singleton?: boolean, dependencies?: readonly string[], metadata?: Record<string, unknown>): void;
|
|
18
|
+
resolve<T = unknown>(name: string): Promise<T>;
|
|
19
|
+
private resolveDependencies;
|
|
20
|
+
has(name: string): boolean;
|
|
21
|
+
remove(name: string): void;
|
|
22
|
+
clear(): void;
|
|
23
|
+
list(): readonly ServiceDescriptor[];
|
|
24
|
+
isSingleton(name: string): boolean;
|
|
25
|
+
getDescriptor(name: string): ServiceDescriptor | undefined;
|
|
26
|
+
getAllDescriptors(): readonly ServiceDescriptor[];
|
|
27
|
+
createInstance(name: string): Promise<unknown>;
|
|
28
|
+
getStats(): {
|
|
29
|
+
totalServices: number;
|
|
30
|
+
singletonCount: number;
|
|
31
|
+
transientCount: number;
|
|
32
|
+
instanceCount: number;
|
|
33
|
+
serviceNames: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
validateDependencies(): {
|
|
36
|
+
valid: boolean;
|
|
37
|
+
errors: string[];
|
|
38
|
+
};
|
|
39
|
+
getDependencyGraph(): Record<string, string[]>;
|
|
40
|
+
getServiceNames(): readonly string[];
|
|
41
|
+
detectCircularDependencies(): string[][];
|
|
42
|
+
resolveAll(names: readonly string[]): Promise<unknown[]>;
|
|
43
|
+
findServicesByMetadata(key: string, value?: unknown): ServiceDescriptor[];
|
|
44
|
+
getTopologicalOrder(): string[];
|
|
45
|
+
warmup(serviceNames?: readonly string[]): Promise<void>;
|
|
46
|
+
clone(): ServiceContainer;
|
|
47
|
+
dispose(name: string): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { ServiceContainer };
|
|
51
|
+
export type { ServiceDescriptor };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
interface ServiceDescriptor<T = unknown> {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly factory: () => Promise<T> | T;
|
|
4
|
+
readonly singleton: boolean;
|
|
5
|
+
readonly dependencies?: readonly string[];
|
|
6
|
+
readonly metadata?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
declare class ServiceContainer {
|
|
9
|
+
private readonly services;
|
|
10
|
+
private readonly instances;
|
|
11
|
+
private readonly resolutionStack;
|
|
12
|
+
private readonly pendingResolutions;
|
|
13
|
+
private isInitialized;
|
|
14
|
+
initialize(): Promise<void>;
|
|
15
|
+
shutdown(): Promise<void>;
|
|
16
|
+
private ensureInitialized;
|
|
17
|
+
register<T>(name: string, factory: () => T | Promise<T>, singleton?: boolean, dependencies?: readonly string[], metadata?: Record<string, unknown>): void;
|
|
18
|
+
resolve<T = unknown>(name: string): Promise<T>;
|
|
19
|
+
private resolveDependencies;
|
|
20
|
+
has(name: string): boolean;
|
|
21
|
+
remove(name: string): void;
|
|
22
|
+
clear(): void;
|
|
23
|
+
list(): readonly ServiceDescriptor[];
|
|
24
|
+
isSingleton(name: string): boolean;
|
|
25
|
+
getDescriptor(name: string): ServiceDescriptor | undefined;
|
|
26
|
+
getAllDescriptors(): readonly ServiceDescriptor[];
|
|
27
|
+
createInstance(name: string): Promise<unknown>;
|
|
28
|
+
getStats(): {
|
|
29
|
+
totalServices: number;
|
|
30
|
+
singletonCount: number;
|
|
31
|
+
transientCount: number;
|
|
32
|
+
instanceCount: number;
|
|
33
|
+
serviceNames: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
validateDependencies(): {
|
|
36
|
+
valid: boolean;
|
|
37
|
+
errors: string[];
|
|
38
|
+
};
|
|
39
|
+
getDependencyGraph(): Record<string, string[]>;
|
|
40
|
+
getServiceNames(): readonly string[];
|
|
41
|
+
detectCircularDependencies(): string[][];
|
|
42
|
+
resolveAll(names: readonly string[]): Promise<unknown[]>;
|
|
43
|
+
findServicesByMetadata(key: string, value?: unknown): ServiceDescriptor[];
|
|
44
|
+
getTopologicalOrder(): string[];
|
|
45
|
+
warmup(serviceNames?: readonly string[]): Promise<void>;
|
|
46
|
+
clone(): ServiceContainer;
|
|
47
|
+
dispose(name: string): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { ServiceContainer };
|
|
51
|
+
export type { ServiceDescriptor };
|