@di-framework/di-framework 2.0.4 → 3.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/README.md CHANGED
@@ -6,12 +6,12 @@
6
6
 
7
7
  A lightweight, type-safe Dependency Injection framework for TypeScript using decorators. This framework automatically manages service instantiation, dependency resolution, and lifecycle management.
8
8
 
9
-
10
9
  ## Installation
11
10
 
12
11
  No external dependencies required! The framework works with SWC and TypeScript's native decorator support.
13
12
 
14
13
  Just ensure you have:
14
+
15
15
  - TypeScript 5.0+
16
16
  - SWC or TypeScript compiler with `experimentalDecorators` and `emitDecoratorMetadata` enabled
17
17
 
@@ -22,7 +22,7 @@ The decorators are fully integrated with SWC's native support - no need for `ref
22
22
  ### 1. Basic Service
23
23
 
24
24
  ```typescript
25
- import { Container } from 'di-framework/decorators';
25
+ import { Container } from '@di-framework/di-framework/decorators';
26
26
 
27
27
  @Container()
28
28
  export class DatabaseService {
@@ -35,7 +35,7 @@ export class DatabaseService {
35
35
  ### 2. Service with Dependencies
36
36
 
37
37
  ```typescript
38
- import { Container, Component } from 'di-framework/decorators';
38
+ import { Container, Component } from '@di-framework/di-framework/decorators';
39
39
  import { DatabaseService } from './services/DatabaseService';
40
40
 
41
41
  @Container()
@@ -56,7 +56,7 @@ Note: Property injection is used for all dependencies. This works seamlessly wit
56
56
  ### 3. Resolve Services
57
57
 
58
58
  ```typescript
59
- import { useContainer } from 'di-framework/container';
59
+ import { useContainer } from '@di-framework/di-framework/container';
60
60
  import { UserService } from './services/UserService';
61
61
 
62
62
  const container = useContainer();
@@ -73,11 +73,13 @@ userService.getUser('123');
73
73
  Marks a class as injectable and automatically registers it with the DI container.
74
74
 
75
75
  **Options:**
76
+
76
77
  - `singleton?: boolean` (default: `true`) - Create a new instance each time or reuse the same instance
77
78
  - `container?: DIContainer` - Specify a custom container (defaults to global container)
78
- - Note: Import as `import { Container as DIContainer } from 'di-framework/container'` to avoid name collision with the `@Container` decorator.
79
+ - Note: Import as `import { Container as DIContainer } from '@di-framework/di-framework/container'` to avoid name collision with the `@Container` decorator.
79
80
 
80
81
  **Example:**
82
+
81
83
  ```typescript
82
84
  @Container({ singleton: false })
83
85
  export class RequestScopedService {
@@ -90,9 +92,11 @@ export class RequestScopedService {
90
92
  Marks a constructor parameter or property for dependency injection.
91
93
 
92
94
  **Parameters:**
95
+
93
96
  - `target` - The class to inject or a string identifier for factory-registered services
94
97
 
95
98
  **Example - Constructor Parameter:**
99
+
96
100
  ```typescript
97
101
  @Container()
98
102
  export class OrderService {
@@ -101,6 +105,7 @@ export class OrderService {
101
105
  ```
102
106
 
103
107
  **Example - Property Injection:**
108
+
104
109
  ```typescript
105
110
  @Container()
106
111
  export class ReportService {
@@ -114,9 +119,11 @@ export class ReportService {
114
119
  Marks a method for telemetry tracking. When called, it emits a `telemetry` event on the container. Works with both synchronous and asynchronous methods.
115
120
 
116
121
  **Options:**
122
+
117
123
  - `logging?: boolean` (default: `false`) - If true, logs the method execution details (status and duration) to the console.
118
124
 
119
125
  **Example:**
126
+
120
127
  ```typescript
121
128
  @Container()
122
129
  export class ApiService {
@@ -132,12 +139,15 @@ export class ApiService {
132
139
  Marks a method as a listener for telemetry events. The method will be automatically registered to the container's `telemetry` event when the service is instantiated.
133
140
 
134
141
  **Example:**
142
+
135
143
  ```typescript
136
144
  @Container()
137
145
  export class MonitoringService {
138
146
  @TelemetryListener()
139
147
  onTelemetry(event: any) {
140
- console.log(`Method ${event.className}.${event.methodName} took ${event.endTime - event.startTime}ms`);
148
+ console.log(
149
+ `Method ${event.className}.${event.methodName} took ${event.endTime - event.startTime}ms`,
150
+ );
141
151
  }
142
152
  }
143
153
  ```
@@ -147,7 +157,7 @@ export class MonitoringService {
147
157
  Returns the global DI container instance.
148
158
 
149
159
  ```typescript
150
- import { useContainer } from 'di-framework/container';
160
+ import { useContainer } from '@di-framework/di-framework/container';
151
161
 
152
162
  const container = useContainer();
153
163
  ```
@@ -165,10 +175,14 @@ container.register(UserService, { singleton: true });
165
175
  Register a service using a factory function.
166
176
 
167
177
  ```typescript
168
- container.registerFactory('config', () => ({
169
- apiKey: process.env.API_KEY,
170
- dbUrl: process.env.DATABASE_URL
171
- }), { singleton: true });
178
+ container.registerFactory(
179
+ 'config',
180
+ () => ({
181
+ apiKey: process.env.API_KEY,
182
+ dbUrl: process.env.DATABASE_URL,
183
+ }),
184
+ { singleton: true },
185
+ );
172
186
  ```
173
187
 
174
188
  ### `container.resolve(serviceClass)`
@@ -205,12 +219,14 @@ console.log(names); // ['DatabaseService', 'UserService', ...]
205
219
  Subscribe to DI container lifecycle events (observer pattern).
206
220
 
207
221
  **Events:**
222
+
208
223
  - `registered` - fired when a class or factory is registered
209
224
  - `resolved` - fired whenever a service is resolved (cached or fresh)
210
225
  - `constructed` - fired when `construct()` creates a new instance
211
226
  - `cleared` - fired when the container is cleared
212
227
 
213
228
  **Example:**
229
+
214
230
  ```typescript
215
231
  const unsubscribe = container.on('resolved', ({ key, fromCache }) => {
216
232
  console.log(`Resolved ${typeof key === 'string' ? key : key.name} (fromCache=${fromCache})`);
@@ -224,11 +240,14 @@ unsubscribe(); // stop listening
224
240
  Create a fresh instance without registering it, while still honoring dependency injection. Useful for constructor-pattern scenarios where you need to supply specific primitives/config values.
225
241
 
226
242
  ```typescript
227
- import { Component } from 'di-framework/decorators';
228
- import { LoggerService } from 'di-framework/services/LoggerService';
243
+ import { Component } from '@di-framework/di-framework/decorators';
244
+ import { LoggerService } from '@di-framework/di-framework/services/LoggerService';
229
245
 
230
246
  class Greeter {
231
- constructor(@Component(LoggerService) private logger: LoggerService, private greeting: string) {}
247
+ constructor(
248
+ @Component(LoggerService) private logger: LoggerService,
249
+ private greeting: string,
250
+ ) {}
232
251
  }
233
252
 
234
253
  const greeter = container.construct(Greeter, { 1: 'hello world' });
@@ -252,7 +271,7 @@ export class ApplicationContext {
252
271
  constructor(
253
272
  @Component(DatabaseService) private db: DatabaseService,
254
273
  @Component(LoggerService) private logger: LoggerService,
255
- @Component(AuthService) private auth: AuthService
274
+ @Component(AuthService) private auth: AuthService,
256
275
  ) {}
257
276
 
258
277
  async initialize() {
@@ -314,12 +333,16 @@ db.connect();
314
333
  ### Factory Functions
315
334
 
316
335
  ```typescript
317
- container.registerFactory('apiClient', () => {
318
- return new HttpClient({
319
- baseUrl: process.env.API_URL,
320
- timeout: 5000
321
- });
322
- }, { singleton: true });
336
+ container.registerFactory(
337
+ 'apiClient',
338
+ () => {
339
+ return new HttpClient({
340
+ baseUrl: process.env.API_URL,
341
+ timeout: 5000,
342
+ });
343
+ },
344
+ { singleton: true },
345
+ );
323
346
 
324
347
  // Use in services
325
348
  @Container()
@@ -343,9 +366,10 @@ export class UserService {
343
366
  ## Comparison with SAMPLE.ts
344
367
 
345
368
  ### Before (Manual - SAMPLE.ts)
369
+
346
370
  ```typescript
347
371
  const createServerContext = (env, ctx) => {
348
- if(!instanceState.member) {
372
+ if (!instanceState.member) {
349
373
  const contextInstance = Context.create({
350
374
  contactService: ContactService.create({}),
351
375
  assetService: AssetService.create({}),
@@ -363,20 +387,22 @@ const createServerContext = (env, ctx) => {
363
387
  instanceState.member.setCtx(ctx);
364
388
  // ... manual dependency wiring
365
389
  instanceState.member.knowledgeService.setAttachmentService(
366
- instanceState.member.attachmentService
390
+ instanceState.member.attachmentService,
367
391
  );
368
392
  return instanceState.member;
369
393
  };
370
394
  ```
371
395
 
372
396
  ### After (DI Framework)
397
+
373
398
  ```typescript
374
399
  @Container()
375
400
  export class ApplicationContext {
376
401
  constructor(
377
402
  @Component(ContactService) private contactService: ContactService,
378
403
  @Component(AssetService) private assetService: AssetService,
379
- @Component(TransactionService) private transactionService: TransactionService,
404
+ @Component(TransactionService)
405
+ private transactionService: TransactionService,
380
406
  // ... all services automatically injected
381
407
  @Component(ChatService) private chatService: ChatService,
382
408
  ) {}
@@ -399,6 +425,7 @@ appContext.setCtx(ctx);
399
425
  ```
400
426
 
401
427
  **Benefits:**
428
+
402
429
  - No manual service instantiation
403
430
  - No manual dependency wiring
404
431
  - Automatic singleton management
@@ -409,6 +436,7 @@ appContext.setCtx(ctx);
409
436
  ## Error Handling
410
437
 
411
438
  ### Circular Dependencies
439
+
412
440
  ```typescript
413
441
  // This will be detected and throw an error:
414
442
  @Container()
@@ -425,6 +453,7 @@ class ServiceB {
425
453
  ```
426
454
 
427
455
  ### Unregistered Services
456
+
428
457
  ```typescript
429
458
  @Container()
430
459
  class MyService {
@@ -448,13 +477,15 @@ class MyService {
448
477
 
449
478
  ```typescript
450
479
  // Create a test container
451
- import { Container as DIContainer } from 'di-framework/container';
480
+ import { Container as DIContainer } from '@di-framework/di-framework/container';
452
481
 
453
482
  const testContainer = new DIContainer();
454
483
 
455
484
  // Register mock implementations
456
485
  class MockDatabaseService {
457
- query() { return { mock: true }; }
486
+ query() {
487
+ return { mock: true };
488
+ }
458
489
  }
459
490
 
460
491
  testContainer.register(MockDatabaseService);
@@ -40,6 +40,9 @@ 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";
45
+ export declare const CRON_METADATA_KEY = "di:cron";
43
46
  declare function defineMetadata(key: string | symbol, value: any, target: any): void;
44
47
  declare function getMetadata(key: string | symbol, target: any): any;
45
48
  declare function hasMetadata(key: string | symbol, target: any): boolean;
@@ -48,6 +51,7 @@ export declare class Container {
48
51
  private services;
49
52
  private resolutionStack;
50
53
  private listeners;
54
+ private cronJobs;
51
55
  /**
52
56
  * Register a service class as injectable
53
57
  */
@@ -78,6 +82,10 @@ export declare class Container {
78
82
  * Clear all registered services
79
83
  */
80
84
  clear(): void;
85
+ /**
86
+ * Stop all active cron jobs
87
+ */
88
+ stopCronJobs(): void;
81
89
  /**
82
90
  * Get all registered service names
83
91
  */
@@ -93,12 +101,20 @@ export declare class Container {
93
101
  * Subscribe to container lifecycle events (observer pattern).
94
102
  * Returns an unsubscribe function.
95
103
  */
96
- on<K extends keyof ContainerEventPayloads>(event: K, listener: Listener<ContainerEventPayloads[K]>): () => void;
104
+ on<K extends keyof ContainerEventPayloads | (string & {})>(event: K, listener: Listener<K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any>): () => void;
97
105
  /**
98
106
  * Remove a previously registered listener
99
107
  */
100
- off<K extends keyof ContainerEventPayloads>(event: K, listener: Listener<ContainerEventPayloads[K]>): void;
101
- private emit;
108
+ off<K extends keyof ContainerEventPayloads | (string & {})>(event: K, listener: Listener<K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any>): void;
109
+ emit<K extends keyof ContainerEventPayloads | (string & {})>(event: K, payload: K extends keyof ContainerEventPayloads ? ContainerEventPayloads[K] : any): void;
110
+ /**
111
+ * Apply event publishers and subscribers defined via decorators
112
+ */
113
+ private applyEvents;
114
+ /**
115
+ * Apply cron schedules defined via @Cron decorator
116
+ */
117
+ private applyCron;
102
118
  /**
103
119
  * Private method to instantiate a service
104
120
  */
package/dist/container.js CHANGED
@@ -10,6 +10,9 @@ 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';
15
+ export const CRON_METADATA_KEY = 'di:cron';
13
16
  /**
14
17
  * Simple metadata storage that doesn't require reflect-metadata
15
18
  * Works with SWC's native decorator support
@@ -30,10 +33,70 @@ function hasMetadata(key, target) {
30
33
  function getOwnMetadata(key, target) {
31
34
  return getMetadata(key, target);
32
35
  }
36
+ // Parse a single cron field into an array of matching values.
37
+ // Supports: * (any), star/N (step), N (exact), N,M (list), N-M (range)
38
+ function parseCronField(field, min, max) {
39
+ if (field === '*') {
40
+ const out = [];
41
+ for (let i = min; i <= max; i++)
42
+ out.push(i);
43
+ return out;
44
+ }
45
+ if (field.startsWith('*/')) {
46
+ const step = parseInt(field.slice(2), 10);
47
+ const out = [];
48
+ for (let i = min; i <= max; i++) {
49
+ if (i % step === 0)
50
+ out.push(i);
51
+ }
52
+ return out;
53
+ }
54
+ if (field.includes(',')) {
55
+ return field.split(',').map((s) => parseInt(s.trim(), 10));
56
+ }
57
+ if (field.includes('-')) {
58
+ const [lo = 0, hi = 0] = field.split('-').map((s) => parseInt(s.trim(), 10));
59
+ const out = [];
60
+ for (let i = lo; i <= hi; i++)
61
+ out.push(i);
62
+ return out;
63
+ }
64
+ return [parseInt(field, 10)];
65
+ }
66
+ function parseCronExpression(expr) {
67
+ const parts = expr.trim().split(/\s+/);
68
+ if (parts.length !== 5)
69
+ throw new Error(`Invalid cron expression "${expr}": expected 5 fields (minute hour dayOfMonth month dayOfWeek)`);
70
+ return {
71
+ minute: parseCronField(parts[0], 0, 59),
72
+ hour: parseCronField(parts[1], 0, 23),
73
+ dayOfMonth: parseCronField(parts[2], 1, 31),
74
+ month: parseCronField(parts[3], 1, 12),
75
+ dayOfWeek: parseCronField(parts[4], 0, 6),
76
+ };
77
+ }
78
+ function getNextCronTime(fields, from) {
79
+ const next = new Date(from);
80
+ next.setSeconds(0, 0);
81
+ next.setMinutes(next.getMinutes() + 1);
82
+ // Search forward up to ~2 years of minutes
83
+ for (let i = 0; i < 1_051_920; i++) {
84
+ if (fields.minute.includes(next.getMinutes()) &&
85
+ fields.hour.includes(next.getHours()) &&
86
+ fields.dayOfMonth.includes(next.getDate()) &&
87
+ fields.month.includes(next.getMonth() + 1) &&
88
+ fields.dayOfWeek.includes(next.getDay())) {
89
+ return next;
90
+ }
91
+ next.setMinutes(next.getMinutes() + 1);
92
+ }
93
+ throw new Error(`No matching cron time found for expression within 2 years`);
94
+ }
33
95
  export class Container {
34
96
  services = new Map();
35
97
  resolutionStack = new Set();
36
98
  listeners = new Map();
99
+ cronJobs = [];
37
100
  /**
38
101
  * Register a service class as injectable
39
102
  */
@@ -145,9 +208,19 @@ export class Container {
145
208
  */
146
209
  clear() {
147
210
  const count = this.services.size;
211
+ this.stopCronJobs();
148
212
  this.services.clear();
149
213
  this.emit('cleared', { count });
150
214
  }
215
+ /**
216
+ * Stop all active cron jobs
217
+ */
218
+ stopCronJobs() {
219
+ for (const job of this.cronJobs) {
220
+ job.stop();
221
+ }
222
+ this.cronJobs = [];
223
+ }
151
224
  /**
152
225
  * Get all registered service names
153
226
  */
@@ -200,7 +273,148 @@ export class Container {
200
273
  listener(payload);
201
274
  }
202
275
  catch (err) {
203
- console.error(`[Container] listener for '${event}' threw`, err);
276
+ console.error(`[Container] listener for '${String(event)}' threw`, err);
277
+ }
278
+ });
279
+ }
280
+ /**
281
+ * Apply event publishers and subscribers defined via decorators
282
+ */
283
+ applyEvents(instance, constructor) {
284
+ const className = constructor.name;
285
+ // Handle @Subscriber(event)
286
+ const subscriberMap = getMetadata(SUBSCRIBER_METADATA_KEY, constructor.prototype) || {};
287
+ Object.entries(subscriberMap).forEach(([event, methods]) => {
288
+ methods.forEach((methodName) => {
289
+ const method = instance[methodName];
290
+ if (typeof method === 'function') {
291
+ this.on(event, (payload) => {
292
+ try {
293
+ method.call(instance, payload);
294
+ }
295
+ catch (err) {
296
+ console.error(`[Container] Subscriber '${className}.${methodName}' for event '${event}' threw`, err);
297
+ }
298
+ });
299
+ }
300
+ });
301
+ });
302
+ // Handle @Publisher(options)
303
+ const publisherMethods = getMetadata(PUBLISHER_METADATA_KEY, constructor.prototype) || {};
304
+ Object.entries(publisherMethods).forEach(([methodName, options]) => {
305
+ const originalMethod = instance[methodName];
306
+ if (typeof originalMethod === 'function') {
307
+ const self = this;
308
+ const phase = options.phase ?? 'after';
309
+ instance[methodName] = function (...args) {
310
+ const startTime = Date.now();
311
+ const emit = (result, error) => {
312
+ const payload = {
313
+ className,
314
+ methodName,
315
+ args,
316
+ startTime,
317
+ endTime: Date.now(),
318
+ result,
319
+ error,
320
+ };
321
+ if (options.logging) {
322
+ const duration = payload.endTime - payload.startTime;
323
+ const status = error
324
+ ? `ERROR: ${error && error.message ? error.message : String(error)}`
325
+ : 'SUCCESS';
326
+ console.log(`[Publisher] ${className}.${methodName} -> '${options.event}' - ${status} (${duration}ms)`);
327
+ }
328
+ self.emit(options.event, payload);
329
+ };
330
+ try {
331
+ if (phase === 'before' || phase === 'both') {
332
+ // Emit before invocation (no result yet)
333
+ emit(undefined, undefined);
334
+ }
335
+ const result = originalMethod.apply(this, args);
336
+ if (result instanceof Promise) {
337
+ return result
338
+ .then((val) => {
339
+ if (phase === 'after' || phase === 'both') {
340
+ emit(val, undefined);
341
+ }
342
+ return val;
343
+ })
344
+ .catch((err) => {
345
+ // Always emit on error to allow subscribers to react
346
+ emit(undefined, err);
347
+ throw err;
348
+ });
349
+ }
350
+ if (phase === 'after' || phase === 'both') {
351
+ emit(result, undefined);
352
+ }
353
+ return result;
354
+ }
355
+ catch (err) {
356
+ emit(undefined, err);
357
+ throw err;
358
+ }
359
+ };
360
+ }
361
+ });
362
+ }
363
+ /**
364
+ * Apply cron schedules defined via @Cron decorator
365
+ */
366
+ applyCron(instance, constructor) {
367
+ const cronMethods = getMetadata(CRON_METADATA_KEY, constructor.prototype) || {};
368
+ Object.entries(cronMethods).forEach(([methodName, schedule]) => {
369
+ const method = instance[methodName];
370
+ if (typeof method !== 'function')
371
+ return;
372
+ if (typeof schedule === 'number') {
373
+ // Simple interval in ms
374
+ const timer = setInterval(() => {
375
+ try {
376
+ method.call(instance);
377
+ }
378
+ catch (err) {
379
+ console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
380
+ }
381
+ }, schedule);
382
+ this.cronJobs.push({ stop: () => clearInterval(timer) });
383
+ }
384
+ else {
385
+ // Cron expression
386
+ const fields = parseCronExpression(schedule);
387
+ let stopped = false;
388
+ const scheduleNext = () => {
389
+ if (stopped)
390
+ return;
391
+ const now = new Date();
392
+ const next = getNextCronTime(fields, now);
393
+ const delay = next.getTime() - now.getTime();
394
+ const timer = setTimeout(() => {
395
+ if (stopped)
396
+ return;
397
+ try {
398
+ method.call(instance);
399
+ }
400
+ catch (err) {
401
+ console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
402
+ }
403
+ scheduleNext();
404
+ }, delay);
405
+ // Update the stop function to clear the latest timer
406
+ job.stop = () => {
407
+ stopped = true;
408
+ clearTimeout(timer);
409
+ };
410
+ };
411
+ const job = {
412
+ stop: () => {
413
+ stopped = true;
414
+ },
415
+ };
416
+ this.cronJobs.push(job);
417
+ scheduleNext();
204
418
  }
205
419
  });
206
420
  }
@@ -255,11 +469,18 @@ export class Container {
255
469
  const instance = new type(...dependencies);
256
470
  // Apply Telemetry and TelemetryListener
257
471
  this.applyTelemetry(instance, type);
472
+ // Apply custom event publishers and subscribers
473
+ this.applyEvents(instance, type);
474
+ // Apply cron schedules
475
+ this.applyCron(instance, type);
258
476
  // Call @Component() decorators on properties
259
477
  // Check both the instance and the constructor prototype for metadata
260
478
  const injectProperties = getMetadata(INJECT_METADATA_KEY, type) || {};
261
479
  const protoInjectProperties = getMetadata(INJECT_METADATA_KEY, type.prototype) || {};
262
- const allInjectProperties = { ...injectProperties, ...protoInjectProperties };
480
+ const allInjectProperties = {
481
+ ...injectProperties,
482
+ ...protoInjectProperties,
483
+ };
263
484
  Object.entries(allInjectProperties).forEach(([propName, targetType]) => {
264
485
  if (!propName.startsWith('param_') && targetType) {
265
486
  try {
@@ -345,9 +566,7 @@ export class Container {
345
566
  * Check if a function is a class constructor
346
567
  */
347
568
  isClass(func) {
348
- return (typeof func === 'function' &&
349
- func.prototype &&
350
- func.prototype.constructor === func);
569
+ return typeof func === 'function' && func.prototype && func.prototype.constructor === func;
351
570
  }
352
571
  /**
353
572
  * Extract parameter names from constructor
@@ -31,6 +31,54 @@ 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;
69
+ /**
70
+ * Marks a method to run on a cron schedule.
71
+ * The schedule starts automatically when the service is resolved.
72
+ * Jobs are stopped when container.clear() is called.
73
+ *
74
+ * @param schedule A cron expression (5 fields: minute hour dayOfMonth month dayOfWeek)
75
+ * or an interval in milliseconds.
76
+ *
77
+ * @example
78
+ * Cron('0 * * * *') // every hour
79
+ * Cron(30000) // every 30 seconds
80
+ */
81
+ export declare function Cron(schedule: string | number): (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void;
34
82
  /**
35
83
  * Marks a class as injectable and registers it with the DI container
36
84
  *
@@ -54,6 +102,18 @@ export declare function Container(options?: {
54
102
  }): <T extends {
55
103
  new (...args: any[]): {};
56
104
  }>(constructor: T) => T;
105
+ /**
106
+ * Eagerly resolves a class once at definition time.
107
+ *
108
+ * Useful for startup-only classes (e.g. HTTP controllers) whose constructors
109
+ * register routes or side effects and should run before handling requests.
110
+ */
111
+ export declare function Bootstrap(options?: {
112
+ singleton?: boolean;
113
+ container?: DIContainer;
114
+ }): <T extends {
115
+ new (...args: any[]): {};
116
+ }>(constructor: T) => T;
57
117
  /**
58
118
  * Marks a constructor parameter or property for dependency injection
59
119
  *
@@ -82,7 +142,7 @@ export declare function Container(options?: {
82
142
  * constructor(@Component('apiKey') apiKey: string) {}
83
143
  * }
84
144
  */
85
- export declare function Component(target: any): (targetClass: Object | any, propertyKey?: string | symbol, parameterIndex?: number) => void;
145
+ export declare function Component(target: any): (targetClass: object | any, propertyKey?: string | symbol, parameterIndex?: number) => void;
86
146
  /**
87
147
  * Check if a class is marked as injectable
88
148
  */
@@ -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, CRON_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,68 @@ 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' ? { event: optionsOrEvent } : optionsOrEvent;
52
+ const methods = getOwnMetadata(PUBLISHER_METADATA_KEY, target) || {};
53
+ methods[propertyKey] = {
54
+ event: options.event,
55
+ phase: options.phase ?? 'after',
56
+ logging: options.logging ?? false,
57
+ };
58
+ defineMetadata(PUBLISHER_METADATA_KEY, methods, target);
59
+ };
60
+ }
61
+ /**
62
+ * Marks a method to subscribe to a custom event emitted on the container.
63
+ * The decorated method will receive the published payload.
64
+ *
65
+ * Example:
66
+ * @Container()
67
+ * class AuditService {
68
+ * @Subscriber('user.created')
69
+ * onUserCreated(payload: any) { ... }
70
+ * }
71
+ */
72
+ export function Subscriber(event) {
73
+ return function (target, propertyKey, descriptor) {
74
+ const map = getOwnMetadata(SUBSCRIBER_METADATA_KEY, target) || {};
75
+ if (!map[event])
76
+ map[event] = [];
77
+ map[event].push(propertyKey);
78
+ defineMetadata(SUBSCRIBER_METADATA_KEY, map, target);
79
+ };
80
+ }
81
+ /**
82
+ * Marks a method to run on a cron schedule.
83
+ * The schedule starts automatically when the service is resolved.
84
+ * Jobs are stopped when container.clear() is called.
85
+ *
86
+ * @param schedule A cron expression (5 fields: minute hour dayOfMonth month dayOfWeek)
87
+ * or an interval in milliseconds.
88
+ *
89
+ * @example
90
+ * Cron('0 * * * *') // every hour
91
+ * Cron(30000) // every 30 seconds
92
+ */
93
+ export function Cron(schedule) {
94
+ return function (target, propertyKey, descriptor) {
95
+ const methods = getOwnMetadata(CRON_METADATA_KEY, target) || {};
96
+ methods[propertyKey] = schedule;
97
+ defineMetadata(CRON_METADATA_KEY, methods, target);
98
+ };
99
+ }
38
100
  /**
39
101
  * Marks a class as injectable and registers it with the DI container
40
102
  *
@@ -63,6 +125,25 @@ export function Container(options = {}) {
63
125
  return constructor;
64
126
  };
65
127
  }
128
+ /**
129
+ * Eagerly resolves a class once at definition time.
130
+ *
131
+ * Useful for startup-only classes (e.g. HTTP controllers) whose constructors
132
+ * register routes or side effects and should run before handling requests.
133
+ */
134
+ export function Bootstrap(options = {}) {
135
+ return function (constructor) {
136
+ const container = options.container ?? useContainer();
137
+ // Allow bootstrap to be used with or without @Container().
138
+ if (!container.has(constructor)) {
139
+ const singleton = options.singleton ?? true;
140
+ defineMetadata(INJECTABLE_METADATA_KEY, true, constructor);
141
+ container.register(constructor, { singleton });
142
+ }
143
+ container.resolve(constructor);
144
+ return constructor;
145
+ };
146
+ }
66
147
  /**
67
148
  * Marks a constructor parameter or property for dependency injection
68
149
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@di-framework/di-framework",
3
- "version": "2.0.4",
3
+ "version": "3.0.0",
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",
@@ -19,12 +19,25 @@
19
19
  ],
20
20
  "exports": {
21
21
  ".": {
22
+ "bun": "./container.ts",
22
23
  "import": "./dist/container.js",
23
24
  "types": "./dist/container.d.ts"
24
25
  },
25
- "./container": "./dist/container.js",
26
- "./decorators": "./dist/decorators.js",
27
- "./types": "./dist/types.js"
26
+ "./container": {
27
+ "bun": "./container.ts",
28
+ "import": "./dist/container.js",
29
+ "types": "./dist/container.d.ts"
30
+ },
31
+ "./decorators": {
32
+ "bun": "./decorators.ts",
33
+ "import": "./dist/decorators.js",
34
+ "types": "./dist/decorators.d.ts"
35
+ },
36
+ "./types": {
37
+ "bun": "./types.ts",
38
+ "import": "./dist/types.js",
39
+ "types": "./dist/types.d.ts"
40
+ }
28
41
  },
29
42
  "license": "MIT",
30
43
  "repository": {