@gobing-ai/ts-infra 0.3.5 → 0.3.7
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 +95 -60
- package/dist/application/index.d.ts +16 -18
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +59 -96
- package/dist/application/plugins/builtins.d.ts +64 -0
- package/dist/application/plugins/builtins.d.ts.map +1 -0
- package/dist/application/plugins/builtins.js +146 -0
- package/dist/application/plugins/host.d.ts +61 -0
- package/dist/application/plugins/host.d.ts.map +1 -0
- package/dist/application/plugins/host.js +131 -0
- package/dist/application/plugins/index.d.ts +3 -0
- package/dist/application/plugins/index.d.ts.map +1 -0
- package/dist/application/plugins/index.js +2 -0
- package/dist/application/plugins/types.d.ts +69 -0
- package/dist/application/plugins/types.d.ts.map +1 -0
- package/dist/application/plugins/types.js +10 -0
- package/dist/application/types.d.ts +7 -0
- package/dist/application/types.d.ts.map +1 -1
- package/dist/application-node.d.ts.map +1 -1
- package/dist/application-node.js +40 -57
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/scheduler/cloudflare.d.ts.map +1 -1
- package/dist/scheduler/cloudflare.js +9 -2
- package/dist/scheduler/factory.d.ts +4 -8
- package/dist/scheduler/factory.d.ts.map +1 -1
- package/dist/scheduler/factory.js +12 -22
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/scheduler/wrap-handler.d.ts +8 -4
- package/dist/scheduler/wrap-handler.d.ts.map +1 -1
- package/dist/scheduler/wrap-handler.js +8 -4
- package/dist/telemetry/index.d.ts +1 -2
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +1 -2
- package/dist/telemetry/metrics.d.ts +9 -1
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +22 -1
- package/dist/telemetry/sdk.d.ts +33 -1
- package/dist/telemetry/sdk.d.ts.map +1 -1
- package/dist/telemetry/sdk.js +14 -1
- package/package.json +5 -5
- package/src/application/index.ts +72 -102
- package/src/application/plugins/builtins.ts +178 -0
- package/src/application/plugins/host.ts +143 -0
- package/src/application/plugins/index.ts +3 -0
- package/src/application/plugins/types.ts +86 -0
- package/src/application/types.ts +7 -0
- package/src/application-node.ts +43 -61
- package/src/index.ts +0 -2
- package/src/scheduler/cloudflare.ts +16 -5
- package/src/scheduler/factory.ts +15 -26
- package/src/scheduler/index.ts +1 -1
- package/src/scheduler/wrap-handler.ts +8 -4
- package/src/telemetry/index.ts +9 -2
- package/src/telemetry/metrics.ts +22 -1
- package/src/telemetry/sdk.ts +51 -2
- package/dist/telemetry/config.d.ts +0 -41
- package/dist/telemetry/config.d.ts.map +0 -1
- package/dist/telemetry/config.js +0 -21
- package/src/telemetry/config.ts +0 -59
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Infrastructure backbone — typed event bus, queue/scheduler contracts, adapter
|
|
|
14
14
|
| **Telemetry** | `telemetry/` | OTel instrumentation — tracing (`traceAsync`), metrics (12 instruments), SQL sanitizer; opt-in OTLP export via the `/otel-node` subpath |
|
|
15
15
|
| **API Client** | `api-client.ts` | Typed HTTP client with OTel tracing, timeout, and error handling |
|
|
16
16
|
| **Logger** | `logger.ts` | LogTape-backed structured logger with levels, child loggers, injectable sinks, and mute toggle |
|
|
17
|
+
| **Application Bootstrap** | `application/` | Plugin-driven lifecycle: deterministic startup → shutdown, DI orchestration, services as plugins |
|
|
18
|
+
| **Plugin Host** | `application/plugins/` | Insertion-ordered plugin registry, fail-fast/fail-soft lifecycle fan-out, reason-carrying teardown |
|
|
17
19
|
|
|
18
20
|
## Architecture
|
|
19
21
|
|
|
@@ -453,20 +455,26 @@ await app.stop();
|
|
|
453
455
|
```
|
|
454
456
|
|
|
455
457
|
The portable `runApplication` accepts pre-built services via `services` and
|
|
456
|
-
never reads files.
|
|
457
|
-
|
|
458
|
+
never reads files. The lifecycle is **plugin-driven**: infra services (logger,
|
|
459
|
+
telemetry, scheduler) and the user `start`/`stop` callbacks are registered as
|
|
460
|
+
built-in plugins on a `PluginHost`, in dependency order:
|
|
458
461
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
462
|
+
```
|
|
463
|
+
logger → telemetry → [your plugins] → user-callback → scheduler
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
`loadAll()` then `startAll()` run them forward (A→Z). Plugins marked `failFast`
|
|
467
|
+
(the built-in services) abort the bootstrap if their `onStart` throws; other
|
|
468
|
+
plugins are fail-soft. Shutdown is the reverse fan-out — `stop(reason)` calls
|
|
469
|
+
each plugin's `onStop(host, reason)` in reverse registration order (scheduler
|
|
470
|
+
stop, your `stop(app, reason)`, telemetry shutdown). `stop()` is idempotent —
|
|
471
|
+
safe to call from multiple signal handlers.
|
|
467
472
|
|
|
468
|
-
|
|
469
|
-
|
|
473
|
+
**DB ownership.** The portable layer **never closes a caller-injected
|
|
474
|
+
`services.db`** — you own its lifecycle and must close it yourself. Only an
|
|
475
|
+
adapter the bootstrap *creates* (e.g. the Node subpath's Bun SQLite adapter) is
|
|
476
|
+
closed automatically, via a built-in DB plugin. (Changed in 0.x — previously the
|
|
477
|
+
portable layer closed injected adapters unconditionally.)
|
|
470
478
|
|
|
471
479
|
#### Node/Bun convenience bootstrap
|
|
472
480
|
|
|
@@ -549,68 +557,95 @@ bun add @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics \
|
|
|
549
557
|
@opentelemetry/exporter-metrics-otlp-http
|
|
550
558
|
```
|
|
551
559
|
|
|
552
|
-
### Full bootstrap example
|
|
560
|
+
### Full bootstrap example (recommended: `runApplication` or `runNodeApplication`)
|
|
561
|
+
|
|
562
|
+
Prefer the plugin-driven bootstrap over manual wiring — the host lifecycle handles
|
|
563
|
+
startup ordering, reverse-order teardown, fail-fast/fail-soft policies, and idempotent
|
|
564
|
+
stop. Two entry points:
|
|
553
565
|
|
|
554
566
|
```ts
|
|
555
|
-
|
|
556
|
-
import {
|
|
557
|
-
import {
|
|
558
|
-
EventBus,
|
|
559
|
-
setSchedulerAdapter,
|
|
560
|
-
initTelemetry,
|
|
561
|
-
APIClient,
|
|
562
|
-
getLogger,
|
|
563
|
-
initializeLogger,
|
|
564
|
-
} from '@gobing-ai/ts-infra';
|
|
565
|
-
import { NodeSchedulerAdapter } from '@gobing-ai/ts-infra/scheduler-node';
|
|
567
|
+
// Portable — inject everything yourself:
|
|
568
|
+
import { runApplication } from '@gobing-ai/ts-infra/application';
|
|
566
569
|
|
|
567
|
-
|
|
568
|
-
|
|
570
|
+
const app = await runApplication({
|
|
571
|
+
config: {
|
|
572
|
+
logging: { level: 'info', console: true },
|
|
573
|
+
telemetry: { enabled: true, serviceName: 'my-app' },
|
|
574
|
+
},
|
|
575
|
+
services: { db }, // caller-owned — you close it
|
|
576
|
+
async start(app) {
|
|
577
|
+
app.logger.info('started');
|
|
578
|
+
},
|
|
579
|
+
async stop(app, reason) {
|
|
580
|
+
app.logger.info('shutting down', { reason });
|
|
581
|
+
},
|
|
582
|
+
});
|
|
569
583
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
await applyMigrations(db);
|
|
573
|
-
ctx.register('db', db);
|
|
584
|
+
await app.stop('signal');
|
|
585
|
+
```
|
|
574
586
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
587
|
+
```ts
|
|
588
|
+
// Node / Bun — YAML config, file logs, OTel export, Bun SQLite, Node scheduler:
|
|
589
|
+
import { runNodeApplication } from '@gobing-ai/ts-infra/application-node';
|
|
578
590
|
|
|
579
|
-
|
|
580
|
-
|
|
591
|
+
const app = await runNodeApplication({
|
|
592
|
+
configLoader: {
|
|
593
|
+
configFile: 'config/app.yaml',
|
|
594
|
+
bootstrapSection: 'bootstrap',
|
|
595
|
+
appSection: 'billing',
|
|
596
|
+
appConfig: { safeParse: (raw) => mySchema.safeParse(raw) },
|
|
597
|
+
},
|
|
598
|
+
async start(app) {
|
|
599
|
+
app.logger.info('started', { port: app.appConfig.port });
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
```
|
|
581
603
|
|
|
582
|
-
|
|
583
|
-
|
|
604
|
+
If you prefer manual wiring (e.g. you already have an init sequence), the individual
|
|
605
|
+
subsystems still work standalone — `EventBus`, `APIClient`, `getLogger`, etc. are
|
|
606
|
+
all importable directly from the main barrel and do not require the bootstrap.
|
|
584
607
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
log.info('Hourly cleanup running');
|
|
590
|
-
});
|
|
591
|
-
await scheduler.start();
|
|
608
|
+
#### Custom plugins
|
|
609
|
+
|
|
610
|
+
Plugins run in registration order forward (`loadAll → startAll`) and reverse order
|
|
611
|
+
on teardown (`stopAll → unloadAll`):
|
|
592
612
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
613
|
+
```ts
|
|
614
|
+
import { runApplication } from '@gobing-ai/ts-infra/application';
|
|
615
|
+
import type { Plugin } from '@gobing-ai/ts-infra';
|
|
616
|
+
|
|
617
|
+
const auditPlugin: Plugin = {
|
|
618
|
+
name: 'audit-logger',
|
|
619
|
+
version: '1.0.0',
|
|
620
|
+
failFast: false, // fail-soft: a throwing onStart is logged, boot continues
|
|
621
|
+
onLoad: async (host) => { /* validate preconditions — throws abort boot */ },
|
|
622
|
+
onStart: async (host) => { host.events.on('order.placed', auditHandler); },
|
|
623
|
+
onStop: async (host, reason) => { host.logger.info('persisting audit buffer', { reason }); },
|
|
624
|
+
onUnload: async () => { /* release resources */ },
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const app = await runApplication({
|
|
628
|
+
plugins: [auditPlugin],
|
|
629
|
+
config: { logging: { console: true } },
|
|
597
630
|
});
|
|
631
|
+
```
|
|
598
632
|
|
|
599
|
-
|
|
633
|
+
The host forwards the optional teardown `reason` (a plain `string` on the core contract,
|
|
634
|
+
`ApplicationStopReason` — `'manual' | 'signal' | 'error' | 'shutdown'` — at the app level)
|
|
635
|
+
to every plugin's `onStop`/`onUnload`. Built-in service plugins use this for log context;
|
|
636
|
+
the user-callback plugin delivers it to your `stop(app, reason)`.
|
|
600
637
|
```
|
|
601
638
|
|
|
602
639
|
### Graceful shutdown
|
|
603
640
|
|
|
604
|
-
|
|
605
|
-
async function shutdown() {
|
|
606
|
-
log.info('Shutting down...');
|
|
607
|
-
await scheduler.stop();
|
|
608
|
-
db.close();
|
|
609
|
-
await shutdownTelemetry();
|
|
610
|
-
await ctx.dispose();
|
|
611
|
-
process.exit(0);
|
|
612
|
-
}
|
|
641
|
+
With `runApplication` or `runNodeApplication`, shutdown is a single idempotent call:
|
|
613
642
|
|
|
614
|
-
|
|
615
|
-
process.on('
|
|
643
|
+
```ts
|
|
644
|
+
process.on('SIGTERM', () => app.stop('signal'));
|
|
645
|
+
process.on('SIGINT', () => app.stop('signal'));
|
|
616
646
|
```
|
|
647
|
+
|
|
648
|
+
The host runs `stopAll(reason) → unloadAll(reason)` in reverse registration order:
|
|
649
|
+
scheduler stops, your `stop` callback fires, telemetry flushes, and the optional
|
|
650
|
+
DB adapter closes (if the bootstrap created it — caller-injected adapters are
|
|
651
|
+
your responsibility). Idempotent: safe from overlapping signal handlers.
|
|
@@ -19,24 +19,20 @@ import type { ApplicationBootstrapOptions, ApplicationRuntime } from './types';
|
|
|
19
19
|
* Accepts injected dependencies; never opens files, reads config from disk,
|
|
20
20
|
* or wires runtime-specific exporters.
|
|
21
21
|
*
|
|
22
|
-
* Startup
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* 4. Shut down telemetry
|
|
37
|
-
*
|
|
38
|
-
* If any startup step fails, already-started services are cleaned up in reverse
|
|
39
|
-
* order before rethrowing. `stop()` is idempotent.
|
|
22
|
+
* Startup is plugin-driven. Built-in service plugins are registered in dependency
|
|
23
|
+
* order, then `loadAll()` + `startAll()` run them forward:
|
|
24
|
+
* 1. Resolve bootstrap config; build EventBus + PluginHost
|
|
25
|
+
* 2. Register built-ins in order: logger → telemetry → [caller plugins] →
|
|
26
|
+
* user-callback → scheduler (scheduler last so autoStart runs after user start)
|
|
27
|
+
* 3. `loadAll()` then `startAll()` — `failFast` plugins abort boot on failure
|
|
28
|
+
*
|
|
29
|
+
* Shutdown is the reverse fan-out: `stopAll(reason)` → `unloadAll(reason)` calls
|
|
30
|
+
* every plugin's `onStop`/`onUnload` in reverse registration order (scheduler stop,
|
|
31
|
+
* user `stop(app, reason)`, telemetry shutdown, owned-DB close). Caller-injected
|
|
32
|
+
* `services.db` is caller-owned and never closed here.
|
|
33
|
+
*
|
|
34
|
+
* If startup fails, the host's reverse-order `stopAll('error')`/`unloadAll('error')`
|
|
35
|
+
* tears down whatever started before rethrowing. `stop()` is idempotent.
|
|
40
36
|
*
|
|
41
37
|
* @example
|
|
42
38
|
* ```ts
|
|
@@ -53,5 +49,7 @@ import type { ApplicationBootstrapOptions, ApplicationRuntime } from './types';
|
|
|
53
49
|
export declare function runApplication<TAppConfig = unknown, TEvents extends EventMap = InfraEvents>(options: ApplicationBootstrapOptions<TAppConfig, TEvents>): Promise<ApplicationRuntime<TAppConfig, TEvents>>;
|
|
54
50
|
export type { BusLifecycleEvents, EventMap } from '../event-bus/types';
|
|
55
51
|
export type { InfraEvents } from '../events';
|
|
52
|
+
export type { PluginHost } from './plugins/host';
|
|
53
|
+
export type { Plugin, PluginSummary } from './plugins/types';
|
|
56
54
|
export type { ApplicationBootstrapConfig, ApplicationBootstrapOptions, ApplicationConfigLoader, ApplicationConfigValidator, ApplicationRuntime, ApplicationServices, ApplicationStopReason, ConfigValidationResult, DbAdapterLike, EventsOptions, LoggingOptions, SchedulerOptions, TelemetryOptions, } from './types';
|
|
57
55
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAM7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAiCjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,cAAc,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EAC7F,OAAO,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1D,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAkIlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,YAAY,EACR,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACnB,MAAM,SAAS,CAAC"}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers.js';
|
|
13
13
|
import { EventBus } from '../event-bus/event-bus.js';
|
|
14
|
-
import { getLogger
|
|
15
|
-
import { initScheduler
|
|
16
|
-
import {
|
|
14
|
+
import { getLogger } from '../logger.js';
|
|
15
|
+
import { initScheduler } from '../scheduler/factory.js';
|
|
16
|
+
import { loggerPlugin, schedulerPlugin, telemetryPlugin, userCallbackPlugin } from './plugins/builtins.js';
|
|
17
|
+
import { PluginHost } from './plugins/host.js';
|
|
17
18
|
// ── Shutdown (deterministic reverse order per R5) ─────────────────────────
|
|
18
19
|
async function performShutdown(state, reason) {
|
|
19
20
|
if (state.stopped)
|
|
@@ -22,29 +23,13 @@ async function performShutdown(state, reason) {
|
|
|
22
23
|
const app = state.app;
|
|
23
24
|
if (!app)
|
|
24
25
|
return;
|
|
25
|
-
// 1.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
state.schedulerStarted = false;
|
|
33
|
-
}
|
|
34
|
-
// 3. Close DB adapter
|
|
35
|
-
if (app.db) {
|
|
36
|
-
try {
|
|
37
|
-
app.db.close();
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
/* best-effort */
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
// 4. Shutdown telemetry
|
|
44
|
-
if (state.telemetryInitialized) {
|
|
45
|
-
await shutdownTelemetry();
|
|
46
|
-
state.telemetryInitialized = false;
|
|
47
|
-
}
|
|
26
|
+
// 1. Stop + unload plugins in reverse registration order (fail-soft).
|
|
27
|
+
// Scheduler stop and telemetry shutdown run here via their onStop hooks.
|
|
28
|
+
await state.pluginHost.stopAll(reason);
|
|
29
|
+
await state.pluginHost.unloadAll(reason);
|
|
30
|
+
// Shutdown is complete — the host's stopAll/unloadAll calls every plugin's
|
|
31
|
+
// onStop/onUnload in reverse registration order, including user callback,
|
|
32
|
+
// scheduler, and service teardown. No inline steps.
|
|
48
33
|
}
|
|
49
34
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
50
35
|
/**
|
|
@@ -54,24 +39,20 @@ async function performShutdown(state, reason) {
|
|
|
54
39
|
* Accepts injected dependencies; never opens files, reads config from disk,
|
|
55
40
|
* or wires runtime-specific exporters.
|
|
56
41
|
*
|
|
57
|
-
* Startup
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* 6. Initialize scheduler + register entries
|
|
64
|
-
* 7. Call user `start(app)` callback
|
|
65
|
-
* 8. Start scheduler if `autoStart`
|
|
42
|
+
* Startup is plugin-driven. Built-in service plugins are registered in dependency
|
|
43
|
+
* order, then `loadAll()` + `startAll()` run them forward:
|
|
44
|
+
* 1. Resolve bootstrap config; build EventBus + PluginHost
|
|
45
|
+
* 2. Register built-ins in order: logger → telemetry → [caller plugins] →
|
|
46
|
+
* user-callback → scheduler (scheduler last so autoStart runs after user start)
|
|
47
|
+
* 3. `loadAll()` then `startAll()` — `failFast` plugins abort boot on failure
|
|
66
48
|
*
|
|
67
|
-
* Shutdown
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* 4. Shut down telemetry
|
|
49
|
+
* Shutdown is the reverse fan-out: `stopAll(reason)` → `unloadAll(reason)` calls
|
|
50
|
+
* every plugin's `onStop`/`onUnload` in reverse registration order (scheduler stop,
|
|
51
|
+
* user `stop(app, reason)`, telemetry shutdown, owned-DB close). Caller-injected
|
|
52
|
+
* `services.db` is caller-owned and never closed here.
|
|
72
53
|
*
|
|
73
|
-
* If
|
|
74
|
-
*
|
|
54
|
+
* If startup fails, the host's reverse-order `stopAll('error')`/`unloadAll('error')`
|
|
55
|
+
* tears down whatever started before rethrowing. `stop()` is idempotent.
|
|
75
56
|
*
|
|
76
57
|
* @example
|
|
77
58
|
* ```ts
|
|
@@ -112,41 +93,13 @@ export async function runApplication(options) {
|
|
|
112
93
|
const eventsDefaultObservers = options.config?.events?.defaultObservers ?? true;
|
|
113
94
|
const state = {
|
|
114
95
|
app: undefined,
|
|
115
|
-
userStop: options.stop,
|
|
116
|
-
schedulerAdapter: undefined,
|
|
117
|
-
schedulerStarted: false,
|
|
118
|
-
loggerInitialized: false,
|
|
119
|
-
telemetryInitialized: false,
|
|
120
96
|
stopped: false,
|
|
97
|
+
pluginHost: undefined,
|
|
121
98
|
};
|
|
122
99
|
try {
|
|
123
|
-
// ── 1.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
logger = options.services.logger;
|
|
127
|
-
}
|
|
128
|
-
else if (loggingConfig.enabled) {
|
|
129
|
-
await initializeLogger({
|
|
130
|
-
level: loggingConfig.level,
|
|
131
|
-
console: loggingConfig.console,
|
|
132
|
-
fileSink: loggingConfig.fileSink,
|
|
133
|
-
json: loggingConfig.json,
|
|
134
|
-
});
|
|
135
|
-
logger = getLogger('bootstrap');
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
logger = getLogger('bootstrap');
|
|
139
|
-
}
|
|
140
|
-
// ── 2. Initialize telemetry ────────────────────────────────────
|
|
141
|
-
if (telemetryConfig.enabled) {
|
|
142
|
-
initTelemetry({
|
|
143
|
-
enabled: telemetryConfig.enabled,
|
|
144
|
-
serviceName: telemetryConfig.serviceName,
|
|
145
|
-
environment: telemetryConfig.environment,
|
|
146
|
-
dbStatementDebug: telemetryConfig.dbStatementDebug,
|
|
147
|
-
});
|
|
148
|
-
state.telemetryInitialized = true;
|
|
149
|
-
}
|
|
100
|
+
// ── 1. Resolve logger (init deferred to loggerPlugin) ───────────
|
|
101
|
+
const logger = options.services?.logger ?? getLogger('bootstrap');
|
|
102
|
+
const loggerInjected = !!options.services?.logger;
|
|
150
103
|
// ── 3. Create lifecycle bus + EventBus ─────────────────────────
|
|
151
104
|
const lifecycleBus = eventsEnabled && eventsLifecycle ? (options.services?.lifecycleBus ?? createLifecycleBus()) : undefined;
|
|
152
105
|
if (lifecycleBus && eventsDefaultObservers) {
|
|
@@ -161,20 +114,24 @@ export async function runApplication(options) {
|
|
|
161
114
|
let scheduler;
|
|
162
115
|
if (schedulerConfig.enabled) {
|
|
163
116
|
const adapter = options.services?.scheduler ?? schedOpts?.adapter;
|
|
164
|
-
|
|
165
|
-
setSchedulerAdapter(adapter);
|
|
166
|
-
}
|
|
167
|
-
scheduler = initScheduler(schedOpts?.entries);
|
|
168
|
-
state.schedulerAdapter = scheduler;
|
|
117
|
+
scheduler = initScheduler(adapter, schedOpts?.entries);
|
|
169
118
|
}
|
|
170
|
-
// ──
|
|
119
|
+
// ── 5.5 Plugin host + built-in service plugins ─────────────────
|
|
120
|
+
const pluginHost = options.services?.pluginHost ?? new PluginHost(events);
|
|
121
|
+
state.pluginHost = pluginHost;
|
|
122
|
+
// Register built-in service plugins in dependency order: logger -> telemetry.
|
|
123
|
+
pluginHost.register(loggerPlugin(loggingConfig, loggerInjected));
|
|
124
|
+
pluginHost.register(telemetryPlugin(telemetryConfig));
|
|
125
|
+
// Caller-injected `services.db` is NOT closed by the portable layer — it is
|
|
126
|
+
// caller-owned (task 0028). Only adapters the bootstrap CREATES (the Node
|
|
127
|
+
// subpath) are wrapped in a `dbPlugin` whose onStop closes them.
|
|
128
|
+
// ── Build runtime handle (before startAll so plugins can capture it) ─
|
|
171
129
|
const resolvedConfig = {
|
|
172
130
|
logging: loggingConfig,
|
|
173
131
|
events: { enabled: eventsEnabled, lifecycle: eventsLifecycle, defaultObservers: eventsDefaultObservers },
|
|
174
132
|
telemetry: telemetryConfig,
|
|
175
133
|
scheduler: schedulerConfig,
|
|
176
134
|
};
|
|
177
|
-
// ── Build runtime handle ───────────────────────────────────────
|
|
178
135
|
const app = {
|
|
179
136
|
config: resolvedConfig,
|
|
180
137
|
appConfig: options.appConfig,
|
|
@@ -183,28 +140,34 @@ export async function runApplication(options) {
|
|
|
183
140
|
lifecycleBus,
|
|
184
141
|
db,
|
|
185
142
|
scheduler,
|
|
143
|
+
pluginHost,
|
|
186
144
|
stop: (reason) => performShutdown(state, reason ?? 'manual'),
|
|
187
145
|
};
|
|
188
146
|
state.app = app;
|
|
189
|
-
// ──
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
147
|
+
// ── Register caller-provided plugins (before user callback) ─────────
|
|
148
|
+
if (options.plugins) {
|
|
149
|
+
for (const p of options.plugins) {
|
|
150
|
+
pluginHost.register(p);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// ── Register user-callback plugin (after services, before scheduler) ─
|
|
154
|
+
pluginHost.register(userCallbackPlugin(options.start, options.stop, app));
|
|
155
|
+
// ── Register scheduler plugin (LAST — autoStart after user callback) ─
|
|
156
|
+
if (schedulerConfig.enabled && scheduler) {
|
|
157
|
+
pluginHost.register(schedulerPlugin(scheduler, schedulerConfig.autoStart));
|
|
195
158
|
}
|
|
159
|
+
// ── Load + start (built-in failFast=rethrow on critical failure) ─
|
|
160
|
+
await pluginHost.loadAll();
|
|
161
|
+
await pluginHost.startAll();
|
|
196
162
|
return app;
|
|
197
163
|
}
|
|
198
164
|
catch (error) {
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// DB
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (state.telemetryInitialized) {
|
|
206
|
-
await shutdownTelemetry();
|
|
207
|
-
}
|
|
165
|
+
// Startup failed: tear down whatever started, in reverse registration
|
|
166
|
+
// order, via the host. Each plugin's onStop/onUnload is best-effort, so a
|
|
167
|
+
// partially-started ring still releases its resources (telemetry, scheduler,
|
|
168
|
+
// owned DB). Caller-injected services.db is caller-owned and not touched.
|
|
169
|
+
await state.pluginHost.stopAll('error');
|
|
170
|
+
await state.pluginHost.unloadAll('error');
|
|
208
171
|
throw error;
|
|
209
172
|
}
|
|
210
173
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in service plugins for the application bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Each factory returns a `Plugin` that maps an existing init/shutdown pair onto
|
|
5
|
+
* the PluginHost lifecycle: `onStart` = init, `onStop` = teardown. Absent
|
|
6
|
+
* hooks are omitted (no-op by absence, not by stub).
|
|
7
|
+
*
|
|
8
|
+
* @module application/plugins
|
|
9
|
+
*/
|
|
10
|
+
import type { LogLevel } from '../../logger';
|
|
11
|
+
import type { ApplicationBootstrapConfig, DbAdapterLike } from '../types';
|
|
12
|
+
import type { Plugin } from './types';
|
|
13
|
+
/**
|
|
14
|
+
* Built-in plugin for telemetry + metrics.
|
|
15
|
+
*
|
|
16
|
+
* `onStart`: `initTelemetry` + `initMetrics` (pre-warm instruments).
|
|
17
|
+
* `onStop`: `shutdownMetrics` + `shutdownTelemetry` (reverse of init).
|
|
18
|
+
* `failFast: true` — a failing telemetry init aborts the bootstrap.
|
|
19
|
+
*/
|
|
20
|
+
export declare function telemetryPlugin(config: ApplicationBootstrapConfig['telemetry']): Plugin;
|
|
21
|
+
/**
|
|
22
|
+
* Built-in plugin for the structured logger.
|
|
23
|
+
*
|
|
24
|
+
* `onStart`: `initializeLogger` (skipped when an injected logger is present).
|
|
25
|
+
* No `onStop` — the logger has no teardown.
|
|
26
|
+
* `failFast: true` — a failing logger init aborts the bootstrap.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loggerPlugin(config: {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
level: LogLevel;
|
|
31
|
+
console: boolean;
|
|
32
|
+
json: boolean;
|
|
33
|
+
fileSink?: ((line: string) => void) | undefined;
|
|
34
|
+
}, injected?: boolean): Plugin;
|
|
35
|
+
import type { EventMap } from '../../event-bus/types';
|
|
36
|
+
import type { ApplicationRuntime } from '../types';
|
|
37
|
+
/**
|
|
38
|
+
* Built-in plugin that wraps the user's `start`/`stop` callbacks.
|
|
39
|
+
*
|
|
40
|
+
* `onStart`: calls `options.start(app)`. `failFast: true`.
|
|
41
|
+
* `onStop`: calls `options.stop(app, reason)`. Registered after services,
|
|
42
|
+
* before scheduler, so stopAll/reverse-order places it after
|
|
43
|
+
* scheduler.stop and before service teardown.
|
|
44
|
+
*/
|
|
45
|
+
export declare function userCallbackPlugin<TAppConfig, TEvents extends EventMap>(start: (app: ApplicationRuntime<TAppConfig, TEvents>) => Promise<void> | void, stop: ((app: ApplicationRuntime<TAppConfig, TEvents>, reason: string) => Promise<void> | void) | undefined, app?: ApplicationRuntime<TAppConfig, TEvents>): Plugin;
|
|
46
|
+
import type { SchedulerAdapter } from '../../scheduler/types';
|
|
47
|
+
/**
|
|
48
|
+
* Built-in plugin for the scheduler.
|
|
49
|
+
*
|
|
50
|
+
* `onStart`: `adapter.start()` when `autoStart` is true.
|
|
51
|
+
* `onStop`: `adapter.stop()` — fail-soft.
|
|
52
|
+
*
|
|
53
|
+
* Registered LAST so autoStart always runs after the user callback.
|
|
54
|
+
*/
|
|
55
|
+
export declare function schedulerPlugin(adapter: SchedulerAdapter, autoStart: boolean): Plugin;
|
|
56
|
+
/**
|
|
57
|
+
* Built-in plugin for a DB adapter the bootstrap OWNS.
|
|
58
|
+
*
|
|
59
|
+
* `onStop(reason)`: `db.close()` — best-effort.
|
|
60
|
+
* Register ONLY when the bootstrap creates the adapter (Node subpath).
|
|
61
|
+
* Do NOT register for caller-injected `services.db` — those are caller-owned.
|
|
62
|
+
*/
|
|
63
|
+
export declare function dbPlugin(db: DbAdapterLike): Plugin;
|
|
64
|
+
//# sourceMappingURL=builtins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../../src/application/plugins/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAI7C,OAAO,KAAK,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,0BAA0B,CAAC,WAAW,CAAC,GAAG,MAAM,CAwBvF;AAID;;;;;;GAMG;AACH,wBAAgB,YAAY,CACxB,MAAM,EAAE;IACJ,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACnD,EACD,QAAQ,CAAC,EAAE,OAAO,GACnB,MAAM,CAiBR;AAID,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,OAAO,SAAS,QAAQ,EACnE,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAC7E,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,EAC1G,GAAG,CAAC,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,GAC9C,MAAM,CAgBR;AAGD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAgBrF;AAID;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,CAelD"}
|