@onebun/core 0.1.17 → 0.1.19
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/package.json +2 -2
- package/src/application/application.test.ts +234 -4
- package/src/application/application.ts +109 -12
- package/src/application/multi-service-application.ts +4 -23
- package/src/docs-examples.test.ts +357 -0
- package/src/index.ts +17 -0
- package/src/module/index.ts +1 -0
- package/src/module/lifecycle.ts +197 -0
- package/src/module/module.ts +225 -24
- package/src/module/service.ts +0 -10
- package/src/types.ts +48 -3
package/src/module/module.ts
CHANGED
|
@@ -29,6 +29,13 @@ import {
|
|
|
29
29
|
type IConfig,
|
|
30
30
|
type OneBunAppConfig,
|
|
31
31
|
} from './config.interface';
|
|
32
|
+
import {
|
|
33
|
+
hasOnModuleInit,
|
|
34
|
+
hasOnApplicationInit,
|
|
35
|
+
hasOnModuleDestroy,
|
|
36
|
+
hasBeforeApplicationDestroy,
|
|
37
|
+
hasOnApplicationDestroy,
|
|
38
|
+
} from './lifecycle';
|
|
32
39
|
import { getServiceMetadata, getServiceTag } from './service';
|
|
33
40
|
|
|
34
41
|
/**
|
|
@@ -68,7 +75,7 @@ export class OneBunModule implements ModuleInstance {
|
|
|
68
75
|
private controllers: Function[] = [];
|
|
69
76
|
private controllerInstances: Map<Function, Controller> = new Map();
|
|
70
77
|
private serviceInstances: Map<Context.Tag<unknown, unknown>, unknown> = new Map();
|
|
71
|
-
private
|
|
78
|
+
private pendingServiceInits: Array<{ name: string; instance: unknown }> = [];
|
|
72
79
|
private logger: SyncLogger;
|
|
73
80
|
private config: IConfig<OneBunAppConfig>;
|
|
74
81
|
|
|
@@ -303,16 +310,11 @@ export class OneBunModule implements ModuleInstance {
|
|
|
303
310
|
.initializeService(this.logger, this.config);
|
|
304
311
|
}
|
|
305
312
|
|
|
306
|
-
// Track services that need
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
typeof serviceInstance === 'object' &&
|
|
310
|
-
'onAsyncInit' in serviceInstance &&
|
|
311
|
-
typeof (serviceInstance as { onAsyncInit: unknown }).onAsyncInit === 'function'
|
|
312
|
-
) {
|
|
313
|
-
this.pendingAsyncInits.push({
|
|
313
|
+
// Track services that need lifecycle hooks (onModuleInit)
|
|
314
|
+
if (hasOnModuleInit(serviceInstance)) {
|
|
315
|
+
this.pendingServiceInits.push({
|
|
314
316
|
name: provider.name,
|
|
315
|
-
|
|
317
|
+
instance: serviceInstance,
|
|
316
318
|
});
|
|
317
319
|
}
|
|
318
320
|
|
|
@@ -558,35 +560,45 @@ export class OneBunModule implements ModuleInstance {
|
|
|
558
560
|
* Setup the module and its dependencies
|
|
559
561
|
*/
|
|
560
562
|
setup(): Effect.Effect<unknown, never, void> {
|
|
561
|
-
return this.
|
|
562
|
-
// Also run
|
|
563
|
+
return this.callServicesOnModuleInit().pipe(
|
|
564
|
+
// Also run onModuleInit for child modules' services
|
|
563
565
|
Effect.flatMap(() =>
|
|
564
|
-
Effect.forEach(this.childModules, (childModule) => childModule.
|
|
566
|
+
Effect.forEach(this.childModules, (childModule) => childModule.callServicesOnModuleInit(), {
|
|
565
567
|
discard: true,
|
|
566
568
|
}),
|
|
567
569
|
),
|
|
568
570
|
// Then create controller instances
|
|
569
571
|
Effect.flatMap(() => this.createControllerInstances()),
|
|
572
|
+
// Then call onModuleInit for controllers
|
|
573
|
+
Effect.flatMap(() => this.callControllersOnModuleInit()),
|
|
574
|
+
// Also run onModuleInit for child modules' controllers
|
|
575
|
+
Effect.flatMap(() =>
|
|
576
|
+
Effect.forEach(this.childModules, (childModule) => childModule.callControllersOnModuleInit(), {
|
|
577
|
+
discard: true,
|
|
578
|
+
}),
|
|
579
|
+
),
|
|
570
580
|
);
|
|
571
581
|
}
|
|
572
582
|
|
|
573
583
|
/**
|
|
574
|
-
*
|
|
584
|
+
* Call onModuleInit lifecycle hook for all services that implement it
|
|
575
585
|
*/
|
|
576
|
-
|
|
577
|
-
if (this.
|
|
586
|
+
callServicesOnModuleInit(): Effect.Effect<unknown, never, void> {
|
|
587
|
+
if (this.pendingServiceInits.length === 0) {
|
|
578
588
|
return Effect.void;
|
|
579
589
|
}
|
|
580
590
|
|
|
581
|
-
this.logger.debug(`
|
|
591
|
+
this.logger.debug(`Calling onModuleInit for ${this.pendingServiceInits.length} service(s)`);
|
|
582
592
|
|
|
583
|
-
// Run all
|
|
584
|
-
const initPromises = this.
|
|
593
|
+
// Run all service onModuleInit hooks sequentially
|
|
594
|
+
const initPromises = this.pendingServiceInits.map(async ({ name, instance }) => {
|
|
585
595
|
try {
|
|
586
|
-
|
|
587
|
-
|
|
596
|
+
if (hasOnModuleInit(instance)) {
|
|
597
|
+
await instance.onModuleInit();
|
|
598
|
+
}
|
|
599
|
+
this.logger.debug(`Service ${name} onModuleInit completed`);
|
|
588
600
|
} catch (error) {
|
|
589
|
-
this.logger.error(`Service ${name}
|
|
601
|
+
this.logger.error(`Service ${name} onModuleInit failed: ${error}`);
|
|
590
602
|
throw error;
|
|
591
603
|
}
|
|
592
604
|
});
|
|
@@ -594,11 +606,179 @@ export class OneBunModule implements ModuleInstance {
|
|
|
594
606
|
return Effect.promise(() => Promise.all(initPromises)).pipe(
|
|
595
607
|
Effect.map(() => {
|
|
596
608
|
// Clear the list after initialization
|
|
597
|
-
this.
|
|
609
|
+
this.pendingServiceInits = [];
|
|
598
610
|
}),
|
|
599
611
|
);
|
|
600
612
|
}
|
|
601
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Call onModuleInit lifecycle hook for all controllers that implement it
|
|
616
|
+
*/
|
|
617
|
+
callControllersOnModuleInit(): Effect.Effect<unknown, never, void> {
|
|
618
|
+
const controllers = Array.from(this.controllerInstances.values());
|
|
619
|
+
const controllersWithInit = controllers.filter((c): c is Controller & { onModuleInit(): Promise<void> | void } =>
|
|
620
|
+
hasOnModuleInit(c),
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
if (controllersWithInit.length === 0) {
|
|
624
|
+
return Effect.void;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
this.logger.debug(`Calling onModuleInit for ${controllersWithInit.length} controller(s)`);
|
|
628
|
+
|
|
629
|
+
const initPromises = controllersWithInit.map(async (controller) => {
|
|
630
|
+
try {
|
|
631
|
+
await controller.onModuleInit();
|
|
632
|
+
this.logger.debug(`Controller ${controller.constructor.name} onModuleInit completed`);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
this.logger.error(`Controller ${controller.constructor.name} onModuleInit failed: ${error}`);
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
return Effect.promise(() => Promise.all(initPromises));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Call onApplicationInit lifecycle hook for all services and controllers
|
|
644
|
+
*/
|
|
645
|
+
async callOnApplicationInit(): Promise<void> {
|
|
646
|
+
// Call for services
|
|
647
|
+
for (const [, instance] of this.serviceInstances) {
|
|
648
|
+
if (hasOnApplicationInit(instance)) {
|
|
649
|
+
try {
|
|
650
|
+
await instance.onApplicationInit();
|
|
651
|
+
this.logger.debug(`Service ${(instance as object).constructor.name} onApplicationInit completed`);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
this.logger.error(`Service ${(instance as object).constructor.name} onApplicationInit failed: ${error}`);
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Call for controllers
|
|
660
|
+
for (const [, controller] of this.controllerInstances) {
|
|
661
|
+
if (hasOnApplicationInit(controller)) {
|
|
662
|
+
try {
|
|
663
|
+
await controller.onApplicationInit();
|
|
664
|
+
this.logger.debug(`Controller ${controller.constructor.name} onApplicationInit completed`);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
this.logger.error(`Controller ${controller.constructor.name} onApplicationInit failed: ${error}`);
|
|
667
|
+
throw error;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Call for child modules
|
|
673
|
+
for (const childModule of this.childModules) {
|
|
674
|
+
await childModule.callOnApplicationInit();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Call beforeApplicationDestroy lifecycle hook for all services and controllers
|
|
680
|
+
*/
|
|
681
|
+
async callBeforeApplicationDestroy(signal?: string): Promise<void> {
|
|
682
|
+
// Call for services
|
|
683
|
+
for (const [, instance] of this.serviceInstances) {
|
|
684
|
+
if (hasBeforeApplicationDestroy(instance)) {
|
|
685
|
+
try {
|
|
686
|
+
await instance.beforeApplicationDestroy(signal);
|
|
687
|
+
this.logger.debug(`Service ${(instance as object).constructor.name} beforeApplicationDestroy completed`);
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.logger.error(`Service ${(instance as object).constructor.name} beforeApplicationDestroy failed: ${error}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Call for controllers
|
|
695
|
+
for (const [, controller] of this.controllerInstances) {
|
|
696
|
+
if (hasBeforeApplicationDestroy(controller)) {
|
|
697
|
+
try {
|
|
698
|
+
await controller.beforeApplicationDestroy(signal);
|
|
699
|
+
this.logger.debug(`Controller ${controller.constructor.name} beforeApplicationDestroy completed`);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
this.logger.error(`Controller ${controller.constructor.name} beforeApplicationDestroy failed: ${error}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Call for child modules
|
|
707
|
+
for (const childModule of this.childModules) {
|
|
708
|
+
await childModule.callBeforeApplicationDestroy(signal);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Call onModuleDestroy lifecycle hook for controllers first, then services
|
|
714
|
+
*/
|
|
715
|
+
async callOnModuleDestroy(): Promise<void> {
|
|
716
|
+
// Call for controllers first (reverse order of creation)
|
|
717
|
+
const controllers = Array.from(this.controllerInstances.values()).reverse();
|
|
718
|
+
for (const controller of controllers) {
|
|
719
|
+
if (hasOnModuleDestroy(controller)) {
|
|
720
|
+
try {
|
|
721
|
+
await controller.onModuleDestroy();
|
|
722
|
+
this.logger.debug(`Controller ${controller.constructor.name} onModuleDestroy completed`);
|
|
723
|
+
} catch (error) {
|
|
724
|
+
this.logger.error(`Controller ${controller.constructor.name} onModuleDestroy failed: ${error}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Call for services (reverse order of creation)
|
|
730
|
+
const services = Array.from(this.serviceInstances.values()).reverse();
|
|
731
|
+
for (const instance of services) {
|
|
732
|
+
if (hasOnModuleDestroy(instance)) {
|
|
733
|
+
try {
|
|
734
|
+
await instance.onModuleDestroy();
|
|
735
|
+
this.logger.debug(`Service ${(instance as object).constructor.name} onModuleDestroy completed`);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
this.logger.error(`Service ${(instance as object).constructor.name} onModuleDestroy failed: ${error}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Call for child modules
|
|
743
|
+
for (const childModule of this.childModules) {
|
|
744
|
+
await childModule.callOnModuleDestroy();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Call onApplicationDestroy lifecycle hook for all services and controllers
|
|
750
|
+
*/
|
|
751
|
+
async callOnApplicationDestroy(signal?: string): Promise<void> {
|
|
752
|
+
// Call for services
|
|
753
|
+
for (const [, instance] of this.serviceInstances) {
|
|
754
|
+
if (hasOnApplicationDestroy(instance)) {
|
|
755
|
+
try {
|
|
756
|
+
await instance.onApplicationDestroy(signal);
|
|
757
|
+
this.logger.debug(`Service ${(instance as object).constructor.name} onApplicationDestroy completed`);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
this.logger.error(`Service ${(instance as object).constructor.name} onApplicationDestroy failed: ${error}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Call for controllers
|
|
765
|
+
for (const [, controller] of this.controllerInstances) {
|
|
766
|
+
if (hasOnApplicationDestroy(controller)) {
|
|
767
|
+
try {
|
|
768
|
+
await controller.onApplicationDestroy(signal);
|
|
769
|
+
this.logger.debug(`Controller ${controller.constructor.name} onApplicationDestroy completed`);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
this.logger.error(`Controller ${controller.constructor.name} onApplicationDestroy failed: ${error}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Call for child modules
|
|
777
|
+
for (const childModule of this.childModules) {
|
|
778
|
+
await childModule.callOnApplicationDestroy(signal);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
602
782
|
/**
|
|
603
783
|
* Get all controllers from this module
|
|
604
784
|
*/
|
|
@@ -627,12 +807,33 @@ export class OneBunModule implements ModuleInstance {
|
|
|
627
807
|
}
|
|
628
808
|
|
|
629
809
|
/**
|
|
630
|
-
* Get service instance
|
|
810
|
+
* Get service instance by tag
|
|
631
811
|
*/
|
|
632
812
|
getServiceInstance<T>(tag: Context.Tag<T, T>): T | undefined {
|
|
633
813
|
return this.serviceInstances.get(tag as Context.Tag<unknown, unknown>) as T | undefined;
|
|
634
814
|
}
|
|
635
815
|
|
|
816
|
+
/**
|
|
817
|
+
* Get service instance by class
|
|
818
|
+
*/
|
|
819
|
+
getServiceByClass<T>(serviceClass: new (...args: unknown[]) => T): T | undefined {
|
|
820
|
+
try {
|
|
821
|
+
const tag = getServiceTag(serviceClass);
|
|
822
|
+
|
|
823
|
+
return this.getServiceInstance(tag);
|
|
824
|
+
} catch {
|
|
825
|
+
// Service doesn't have @Service decorator or not found
|
|
826
|
+
return undefined;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Get all service instances
|
|
832
|
+
*/
|
|
833
|
+
getAllServiceInstances(): Map<Context.Tag<unknown, unknown>, unknown> {
|
|
834
|
+
return new Map(this.serviceInstances);
|
|
835
|
+
}
|
|
836
|
+
|
|
636
837
|
/**
|
|
637
838
|
* Get the Layer for this module
|
|
638
839
|
*/
|
package/src/module/service.ts
CHANGED
|
@@ -99,16 +99,6 @@ export class BaseService {
|
|
|
99
99
|
this.logger.debug(`Service ${className} initialized`);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Async initialization hook - called by the framework after initializeService()
|
|
104
|
-
* Override in subclasses that need async initialization (e.g., database connections)
|
|
105
|
-
* The framework will await this method before making the service available
|
|
106
|
-
* @internal
|
|
107
|
-
*/
|
|
108
|
-
async onAsyncInit(): Promise<void> {
|
|
109
|
-
// Default: no async init needed
|
|
110
|
-
}
|
|
111
|
-
|
|
112
102
|
/**
|
|
113
103
|
* Check if service is initialized
|
|
114
104
|
* @internal
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Type } from 'arktype';
|
|
2
2
|
import type { Effect, Layer } from 'effect';
|
|
3
3
|
|
|
4
|
-
import type { Logger } from '@onebun/logger';
|
|
4
|
+
import type { Logger, LoggerOptions } from '@onebun/logger';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Base interface for all OneBun services
|
|
@@ -58,6 +58,31 @@ export interface ModuleInstance {
|
|
|
58
58
|
* Get controller instance
|
|
59
59
|
*/
|
|
60
60
|
getControllerInstance?(controllerClass: Function): unknown;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Call onApplicationInit lifecycle hook for all services and controllers
|
|
64
|
+
*/
|
|
65
|
+
callOnApplicationInit?(): Promise<void>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Call beforeApplicationDestroy lifecycle hook for all services and controllers
|
|
69
|
+
*/
|
|
70
|
+
callBeforeApplicationDestroy?(signal?: string): Promise<void>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Call onModuleDestroy lifecycle hook for controllers first, then services
|
|
74
|
+
*/
|
|
75
|
+
callOnModuleDestroy?(): Promise<void>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Call onApplicationDestroy lifecycle hook for all services and controllers
|
|
79
|
+
*/
|
|
80
|
+
callOnApplicationDestroy?(signal?: string): Promise<void>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get service instance by class
|
|
84
|
+
*/
|
|
85
|
+
getServiceByClass?<T>(serviceClass: new (...args: unknown[]) => T): T | undefined;
|
|
61
86
|
}
|
|
62
87
|
|
|
63
88
|
/**
|
|
@@ -107,8 +132,28 @@ export interface ApplicationOptions {
|
|
|
107
132
|
development?: boolean;
|
|
108
133
|
|
|
109
134
|
/**
|
|
110
|
-
* Logger
|
|
111
|
-
*
|
|
135
|
+
* Logger configuration options.
|
|
136
|
+
* Provides a declarative way to configure logging.
|
|
137
|
+
*
|
|
138
|
+
* Priority: loggerLayer > loggerOptions > LOG_LEVEL/LOG_FORMAT env > NODE_ENV defaults
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const app = new OneBunApplication(AppModule, {
|
|
143
|
+
* loggerOptions: {
|
|
144
|
+
* minLevel: 'info',
|
|
145
|
+
* format: 'json',
|
|
146
|
+
* defaultContext: { service: 'user-service' },
|
|
147
|
+
* },
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
loggerOptions?: LoggerOptions;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Logger layer to use (advanced).
|
|
155
|
+
* If provided, takes precedence over loggerOptions.
|
|
156
|
+
* Use loggerOptions for simpler configuration.
|
|
112
157
|
*/
|
|
113
158
|
loggerLayer?: Layer.Layer<Logger>;
|
|
114
159
|
|