@di-framework/di-framework 0.0.0-prerelease-0 → 0.0.0-prerelease.100
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/container.d.ts +9 -3
- package/dist/container.js +88 -1
- package/dist/decorators.d.ts +35 -0
- package/dist/decorators.js +46 -1
- package/package.json +1 -1
package/dist/container.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ type ContainerEventPayloads = {
|
|
|
40
40
|
type Listener<T> = (payload: T) => void;
|
|
41
41
|
export declare const TELEMETRY_METADATA_KEY = "di:telemetry";
|
|
42
42
|
export declare const TELEMETRY_LISTENER_METADATA_KEY = "di:telemetry-listener";
|
|
43
|
+
export declare const PUBLISHER_METADATA_KEY = "di:publisher";
|
|
44
|
+
export declare const SUBSCRIBER_METADATA_KEY = "di:subscriber";
|
|
43
45
|
declare function defineMetadata(key: string | symbol, value: any, target: any): void;
|
|
44
46
|
declare function getMetadata(key: string | symbol, target: any): any;
|
|
45
47
|
declare function hasMetadata(key: string | symbol, target: any): boolean;
|
|
@@ -93,12 +95,16 @@ export declare class Container {
|
|
|
93
95
|
* Subscribe to container lifecycle events (observer pattern).
|
|
94
96
|
* Returns an unsubscribe function.
|
|
95
97
|
*/
|
|
96
|
-
on<K extends keyof ContainerEventPayloads>(event: K, listener: Listener<ContainerEventPayloads[K]>): () => void;
|
|
98
|
+
on<K extends keyof ContainerEventPayloads | (string & {})>(event: K, listener: Listener<K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any>): () => void;
|
|
97
99
|
/**
|
|
98
100
|
* Remove a previously registered listener
|
|
99
101
|
*/
|
|
100
|
-
off<K extends keyof ContainerEventPayloads>(event: K, listener: Listener<ContainerEventPayloads[K]>): void;
|
|
101
|
-
|
|
102
|
+
off<K extends keyof ContainerEventPayloads | (string & {})>(event: K, listener: Listener<K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any>): void;
|
|
103
|
+
emit<K extends keyof ContainerEventPayloads | (string & {})>(event: K, payload: K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any): void;
|
|
104
|
+
/**
|
|
105
|
+
* Apply event publishers and subscribers defined via decorators
|
|
106
|
+
*/
|
|
107
|
+
private applyEvents;
|
|
102
108
|
/**
|
|
103
109
|
* Private method to instantiate a service
|
|
104
110
|
*/
|
package/dist/container.js
CHANGED
|
@@ -10,6 +10,8 @@ const INJECT_METADATA_KEY = "di:inject";
|
|
|
10
10
|
const DESIGN_PARAM_TYPES_KEY = "design:paramtypes";
|
|
11
11
|
export const TELEMETRY_METADATA_KEY = "di:telemetry";
|
|
12
12
|
export const TELEMETRY_LISTENER_METADATA_KEY = "di:telemetry-listener";
|
|
13
|
+
export const PUBLISHER_METADATA_KEY = "di:publisher";
|
|
14
|
+
export const SUBSCRIBER_METADATA_KEY = "di:subscriber";
|
|
13
15
|
/**
|
|
14
16
|
* Simple metadata storage that doesn't require reflect-metadata
|
|
15
17
|
* Works with SWC's native decorator support
|
|
@@ -200,7 +202,90 @@ export class Container {
|
|
|
200
202
|
listener(payload);
|
|
201
203
|
}
|
|
202
204
|
catch (err) {
|
|
203
|
-
console.error(`[Container] listener for '${event}' threw`, err);
|
|
205
|
+
console.error(`[Container] listener for '${String(event)}' threw`, err);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Apply event publishers and subscribers defined via decorators
|
|
211
|
+
*/
|
|
212
|
+
applyEvents(instance, constructor) {
|
|
213
|
+
const className = constructor.name;
|
|
214
|
+
// Handle @Subscriber(event)
|
|
215
|
+
const subscriberMap = getMetadata(SUBSCRIBER_METADATA_KEY, constructor.prototype) || {};
|
|
216
|
+
Object.entries(subscriberMap).forEach(([event, methods]) => {
|
|
217
|
+
methods.forEach((methodName) => {
|
|
218
|
+
const method = instance[methodName];
|
|
219
|
+
if (typeof method === "function") {
|
|
220
|
+
this.on(event, (payload) => {
|
|
221
|
+
try {
|
|
222
|
+
method.call(instance, payload);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
console.error(`[Container] Subscriber '${className}.${methodName}' for event '${event}' threw`, err);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
// Handle @Publisher(options)
|
|
232
|
+
const publisherMethods = getMetadata(PUBLISHER_METADATA_KEY, constructor.prototype) || {};
|
|
233
|
+
Object.entries(publisherMethods).forEach(([methodName, options]) => {
|
|
234
|
+
const originalMethod = instance[methodName];
|
|
235
|
+
if (typeof originalMethod === "function") {
|
|
236
|
+
const self = this;
|
|
237
|
+
const phase = options.phase ?? "after";
|
|
238
|
+
instance[methodName] = function (...args) {
|
|
239
|
+
const startTime = Date.now();
|
|
240
|
+
const emit = (result, error) => {
|
|
241
|
+
const payload = {
|
|
242
|
+
className,
|
|
243
|
+
methodName,
|
|
244
|
+
args,
|
|
245
|
+
startTime,
|
|
246
|
+
endTime: Date.now(),
|
|
247
|
+
result,
|
|
248
|
+
error,
|
|
249
|
+
};
|
|
250
|
+
if (options.logging) {
|
|
251
|
+
const duration = payload.endTime - payload.startTime;
|
|
252
|
+
const status = error
|
|
253
|
+
? `ERROR: ${error && error.message ? error.message : String(error)}`
|
|
254
|
+
: "SUCCESS";
|
|
255
|
+
console.log(`[Publisher] ${className}.${methodName} -> '${options.event}' - ${status} (${duration}ms)`);
|
|
256
|
+
}
|
|
257
|
+
self.emit(options.event, payload);
|
|
258
|
+
};
|
|
259
|
+
try {
|
|
260
|
+
if (phase === "before" || phase === "both") {
|
|
261
|
+
// Emit before invocation (no result yet)
|
|
262
|
+
emit(undefined, undefined);
|
|
263
|
+
}
|
|
264
|
+
const result = originalMethod.apply(this, args);
|
|
265
|
+
if (result instanceof Promise) {
|
|
266
|
+
return result
|
|
267
|
+
.then((val) => {
|
|
268
|
+
if (phase === "after" || phase === "both") {
|
|
269
|
+
emit(val, undefined);
|
|
270
|
+
}
|
|
271
|
+
return val;
|
|
272
|
+
})
|
|
273
|
+
.catch((err) => {
|
|
274
|
+
// Always emit on error to allow subscribers to react
|
|
275
|
+
emit(undefined, err);
|
|
276
|
+
throw err;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (phase === "after" || phase === "both") {
|
|
280
|
+
emit(result, undefined);
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
emit(undefined, err);
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
204
289
|
}
|
|
205
290
|
});
|
|
206
291
|
}
|
|
@@ -255,6 +340,8 @@ export class Container {
|
|
|
255
340
|
const instance = new type(...dependencies);
|
|
256
341
|
// Apply Telemetry and TelemetryListener
|
|
257
342
|
this.applyTelemetry(instance, type);
|
|
343
|
+
// Apply custom event publishers and subscribers
|
|
344
|
+
this.applyEvents(instance, type);
|
|
258
345
|
// Call @Component() decorators on properties
|
|
259
346
|
// Check both the instance and the constructor prototype for metadata
|
|
260
347
|
const injectProperties = getMetadata(INJECT_METADATA_KEY, type) || {};
|
package/dist/decorators.d.ts
CHANGED
|
@@ -31,6 +31,41 @@ export declare function Telemetry(options?: TelemetryOptions): (target: any, pro
|
|
|
31
31
|
* The method will be automatically registered to the container's 'telemetry' event.
|
|
32
32
|
*/
|
|
33
33
|
export declare function TelemetryListener(): (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Options for the @Publisher decorator
|
|
36
|
+
*/
|
|
37
|
+
export interface PublisherOptions {
|
|
38
|
+
/** The custom event name to emit on the container */
|
|
39
|
+
event: string;
|
|
40
|
+
/** When to emit relative to the method invocation. Defaults to 'after'. */
|
|
41
|
+
phase?: "before" | "after" | "both";
|
|
42
|
+
/** Optional console logging for debug purposes. Defaults to false. */
|
|
43
|
+
logging?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Marks a method to publish a custom event on invocation.
|
|
47
|
+
* Useful for cross-platform event-driven architectures.
|
|
48
|
+
*
|
|
49
|
+
* Example:
|
|
50
|
+
* @Container()
|
|
51
|
+
* class UserService {
|
|
52
|
+
* @Publisher('user.created')
|
|
53
|
+
* createUser(dto: CreateUserDto) { ... }
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
export declare function Publisher(optionsOrEvent: string | PublisherOptions): (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Marks a method to subscribe to a custom event emitted on the container.
|
|
59
|
+
* The decorated method will receive the published payload.
|
|
60
|
+
*
|
|
61
|
+
* Example:
|
|
62
|
+
* @Container()
|
|
63
|
+
* class AuditService {
|
|
64
|
+
* @Subscriber('user.created')
|
|
65
|
+
* onUserCreated(payload: any) { ... }
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
export declare function Subscriber(event: string): (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
|
|
34
69
|
/**
|
|
35
70
|
* Marks a class as injectable and registers it with the DI container
|
|
36
71
|
*
|
package/dist/decorators.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Works with SWC and TypeScript's native decorator support.
|
|
8
8
|
* No external dependencies required (no reflect-metadata needed).
|
|
9
9
|
*/
|
|
10
|
-
import { useContainer, Container as DIContainer, defineMetadata, getOwnMetadata, getMetadata, TELEMETRY_METADATA_KEY, TELEMETRY_LISTENER_METADATA_KEY, } from "./container";
|
|
10
|
+
import { useContainer, Container as DIContainer, defineMetadata, getOwnMetadata, getMetadata, TELEMETRY_METADATA_KEY, TELEMETRY_LISTENER_METADATA_KEY, PUBLISHER_METADATA_KEY, SUBSCRIBER_METADATA_KEY, } from "./container";
|
|
11
11
|
const INJECTABLE_METADATA_KEY = "di:injectable";
|
|
12
12
|
const INJECT_METADATA_KEY = "di:inject";
|
|
13
13
|
/**
|
|
@@ -35,6 +35,51 @@ export function TelemetryListener() {
|
|
|
35
35
|
defineMetadata(TELEMETRY_LISTENER_METADATA_KEY, listeners, target);
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Marks a method to publish a custom event on invocation.
|
|
40
|
+
* Useful for cross-platform event-driven architectures.
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
* @Container()
|
|
44
|
+
* class UserService {
|
|
45
|
+
* @Publisher('user.created')
|
|
46
|
+
* createUser(dto: CreateUserDto) { ... }
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
export function Publisher(optionsOrEvent) {
|
|
50
|
+
return function (target, propertyKey, descriptor) {
|
|
51
|
+
const options = typeof optionsOrEvent === "string"
|
|
52
|
+
? { event: optionsOrEvent }
|
|
53
|
+
: optionsOrEvent;
|
|
54
|
+
const methods = getOwnMetadata(PUBLISHER_METADATA_KEY, target) || {};
|
|
55
|
+
methods[propertyKey] = {
|
|
56
|
+
event: options.event,
|
|
57
|
+
phase: options.phase ?? "after",
|
|
58
|
+
logging: options.logging ?? false,
|
|
59
|
+
};
|
|
60
|
+
defineMetadata(PUBLISHER_METADATA_KEY, methods, target);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Marks a method to subscribe to a custom event emitted on the container.
|
|
65
|
+
* The decorated method will receive the published payload.
|
|
66
|
+
*
|
|
67
|
+
* Example:
|
|
68
|
+
* @Container()
|
|
69
|
+
* class AuditService {
|
|
70
|
+
* @Subscriber('user.created')
|
|
71
|
+
* onUserCreated(payload: any) { ... }
|
|
72
|
+
* }
|
|
73
|
+
*/
|
|
74
|
+
export function Subscriber(event) {
|
|
75
|
+
return function (target, propertyKey, descriptor) {
|
|
76
|
+
const map = getOwnMetadata(SUBSCRIBER_METADATA_KEY, target) || {};
|
|
77
|
+
if (!map[event])
|
|
78
|
+
map[event] = [];
|
|
79
|
+
map[event].push(propertyKey);
|
|
80
|
+
defineMetadata(SUBSCRIBER_METADATA_KEY, map, target);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
38
83
|
/**
|
|
39
84
|
* Marks a class as injectable and registers it with the DI container
|
|
40
85
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@di-framework/di-framework",
|
|
3
|
-
"version": "0.0.0-prerelease
|
|
3
|
+
"version": "0.0.0-prerelease.100",
|
|
4
4
|
"description": "Lightweight, zero-dependency TypeScript Dependency Injection framework using decorators. Works seamlessly with SWC and TypeScript's native decorator support.",
|
|
5
5
|
"main": "./dist/container.js",
|
|
6
6
|
"types": "./dist/container.d.ts",
|