@gobing-ai/ts-infra 0.3.3 → 0.3.5

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
@@ -419,6 +419,119 @@ bun add @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics \
419
419
  @opentelemetry/exporter-metrics-otlp-http
420
420
  ```
421
421
 
422
+
423
+ ### Application Bootstrap
424
+
425
+ The application bootstrap API provides a deterministic lifecycle for wiring
426
+ infrastructure services. Two layers:
427
+
428
+ 1. **Portable DI bootstrap** (`@gobing-ai/ts-infra/application`) — orchestrates
429
+ injected dependencies. Never opens files, creates DB connections, or wires
430
+ runtime-specific exporters.
431
+ 2. **Node/Bun convenience** (`@gobing-ai/ts-infra/application-node`) — composes
432
+ the portable bootstrap with runtime-specific adapters: YAML config loading,
433
+ file log sink, Bun SQLite DB creation, Node OTel export, Node scheduler.
434
+
435
+ #### Portable DI bootstrap
436
+
437
+ ```ts
438
+ import { runApplication } from '@gobing-ai/ts-infra/application';
439
+
440
+ const app = await runApplication({
441
+ config: {
442
+ logging: { level: 'info', console: true },
443
+ telemetry: { enabled: true, serviceName: 'my-api' },
444
+ },
445
+ appConfig: { port: 3000 },
446
+ async start(app) {
447
+ app.logger.info('started', { port: app.appConfig.port });
448
+ },
449
+ });
450
+
451
+ // Graceful shutdown — idempotent
452
+ await app.stop();
453
+ ```
454
+
455
+ The portable `runApplication` accepts pre-built services via `services` and
456
+ never reads files. All services are created with defaults; feature flags
457
+ control what gets initialized. Startup order is deterministic:
458
+
459
+ 1. Resolve bootstrap config + app config
460
+ 2. Initialize logger
461
+ 3. Initialize telemetry
462
+ 4. Create lifecycle bus + EventBus
463
+ 5. Register injected DB adapter
464
+ 6. Initialize scheduler + register entries
465
+ 7. Call user `start(app)` callback
466
+ 8. Start scheduler if `autoStart`
467
+
468
+ Shutdown runs in reverse order. `stop()` is idempotent — safe to call from
469
+ multiple signal handlers.
470
+
471
+ #### Node/Bun convenience bootstrap
472
+
473
+ ```ts
474
+ import { runNodeApplication } from '@gobing-ai/ts-infra/application-node';
475
+
476
+ await runNodeApplication({
477
+ configLoader: {
478
+ configFile: 'config/app.yaml',
479
+ bootstrapSection: 'bootstrap',
480
+ appSection: 'billing',
481
+ appConfig: {
482
+ // Structural safeParse adapter — works with Zod or any validator
483
+ safeParse(raw) {
484
+ return billingSchema.safeParse(raw);
485
+ },
486
+ },
487
+ },
488
+ async start(app) {
489
+ app.logger.info('billing app started', {
490
+ settlementWindowMinutes: app.appConfig.settlementWindowMinutes,
491
+ });
492
+ },
493
+ });
494
+ ```
495
+
496
+ Example YAML config file:
497
+
498
+ ```yaml
499
+ app:
500
+ name: billing-api
501
+ env: production
502
+
503
+ bootstrap:
504
+ logging:
505
+ level: info
506
+ console: true
507
+ filePath: ./logs/app.jsonl
508
+ telemetry:
509
+ enabled: true
510
+ serviceName: billing-api
511
+ endpoint: http://otel-collector:4318
512
+ database:
513
+ enabled: true
514
+ driver: bun-sqlite
515
+ url: ./data/app.db
516
+
517
+ billing:
518
+ settlementWindowMinutes: 15
519
+ riskLimit: 100000
520
+ ```
521
+
522
+ Config validators accept four shapes:
523
+ - `{ safeParse(raw) → { success, data?, errors? } }` — Zod-compatible
524
+ - `(raw) => TAppConfig` — bare function
525
+ - `{ validate(raw) => TAppConfig }` — method form
526
+ - `{ parse(raw) => TAppConfig }` — method form
527
+
528
+ Validation errors include the config file path and section name for diagnostics.
529
+
530
+ **What is intentionally NOT in the main barrel:** `runApplication` and
531
+ `runNodeApplication` live behind explicit subpaths so the main
532
+ `@gobing-ai/ts-infra` import stays portable and adapter-light. A future ADR
533
+ may decide whether type-only re-exports are acceptable.
534
+
422
535
  ## Usage
423
536
 
424
537
  ### Install
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Portable `runApplication` — DI bootstrap over existing ts-infra primitives.
3
+ *
4
+ * Orchestrates logger, telemetry, event bus, (optional) DB adapter, and
5
+ * (optional) scheduler into a deterministic startup/shutdown lifecycle.
6
+ * The portable subpath never opens files, creates DB connections, or wires
7
+ * runtime-specific exporters — those are injected or handled by the
8
+ * Node/Bun convenience subpath.
9
+ *
10
+ * @module application
11
+ */
12
+ import type { EventMap } from '../event-bus/types';
13
+ import type { InfraEvents } from '../events';
14
+ import type { ApplicationBootstrapOptions, ApplicationRuntime } from './types';
15
+ /**
16
+ * Portable application bootstrap.
17
+ *
18
+ * Orchestrates logger, telemetry, events, optional DB, and optional scheduler.
19
+ * Accepts injected dependencies; never opens files, reads config from disk,
20
+ * or wires runtime-specific exporters.
21
+ *
22
+ * Startup order (deterministic, per R5):
23
+ * 1. Resolve bootstrap config + app config
24
+ * 2. Initialize logger
25
+ * 3. Initialize telemetry
26
+ * 4. Create lifecycle bus + application EventBus
27
+ * 5. Register DB adapter (injected only)
28
+ * 6. Initialize scheduler + register entries
29
+ * 7. Call user `start(app)` callback
30
+ * 8. Start scheduler if `autoStart`
31
+ *
32
+ * Shutdown order (reverse, per R5):
33
+ * 1. User `stop(app, reason)` callback
34
+ * 2. Stop scheduler
35
+ * 3. Close DB adapter
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.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { runApplication } from '@gobing-ai/ts-infra/application';
44
+ *
45
+ * const app = await runApplication({
46
+ * config: { logging: { level: 'debug' } },
47
+ * async start(app) {
48
+ * app.logger.info('started');
49
+ * },
50
+ * });
51
+ * ```
52
+ */
53
+ export declare function runApplication<TAppConfig = unknown, TEvents extends EventMap = InfraEvents>(options: ApplicationBootstrapOptions<TAppConfig, TEvents>): Promise<ApplicationRuntime<TAppConfig, TEvents>>;
54
+ export type { BusLifecycleEvents, EventMap } from '../event-bus/types';
55
+ export type { InfraEvents } from '../events';
56
+ export type { ApplicationBootstrapConfig, ApplicationBootstrapOptions, ApplicationConfigLoader, ApplicationConfigValidator, ApplicationRuntime, ApplicationServices, ApplicationStopReason, ConfigValidationResult, DbAdapterLike, EventsOptions, LoggingOptions, SchedulerOptions, TelemetryOptions, } from './types';
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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;AAK7C,OAAO,KAAK,EAER,2BAA2B,EAC3B,kBAAkB,EAGrB,MAAM,SAAS,CAAC;AAuDjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;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,CAyIlD;AAED,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,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"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Portable `runApplication` — DI bootstrap over existing ts-infra primitives.
3
+ *
4
+ * Orchestrates logger, telemetry, event bus, (optional) DB adapter, and
5
+ * (optional) scheduler into a deterministic startup/shutdown lifecycle.
6
+ * The portable subpath never opens files, creates DB connections, or wires
7
+ * runtime-specific exporters — those are injected or handled by the
8
+ * Node/Bun convenience subpath.
9
+ *
10
+ * @module application
11
+ */
12
+ import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers.js';
13
+ import { EventBus } from '../event-bus/event-bus.js';
14
+ import { getLogger, initializeLogger } from '../logger.js';
15
+ import { initScheduler, setSchedulerAdapter } from '../scheduler/factory.js';
16
+ import { initTelemetry, shutdownTelemetry } from '../telemetry/sdk.js';
17
+ // ── Shutdown (deterministic reverse order per R5) ─────────────────────────
18
+ async function performShutdown(state, reason) {
19
+ if (state.stopped)
20
+ return;
21
+ state.stopped = true;
22
+ const app = state.app;
23
+ if (!app)
24
+ return;
25
+ // 1. User stop callback
26
+ if (state.userStop) {
27
+ await state.userStop(app, reason);
28
+ }
29
+ // 2. Stop scheduler
30
+ if (state.schedulerStarted && state.schedulerAdapter) {
31
+ await state.schedulerAdapter.stop().catch(() => { });
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
+ }
48
+ }
49
+ // ── Public API ────────────────────────────────────────────────────────────
50
+ /**
51
+ * Portable application bootstrap.
52
+ *
53
+ * Orchestrates logger, telemetry, events, optional DB, and optional scheduler.
54
+ * Accepts injected dependencies; never opens files, reads config from disk,
55
+ * or wires runtime-specific exporters.
56
+ *
57
+ * Startup order (deterministic, per R5):
58
+ * 1. Resolve bootstrap config + app config
59
+ * 2. Initialize logger
60
+ * 3. Initialize telemetry
61
+ * 4. Create lifecycle bus + application EventBus
62
+ * 5. Register DB adapter (injected only)
63
+ * 6. Initialize scheduler + register entries
64
+ * 7. Call user `start(app)` callback
65
+ * 8. Start scheduler if `autoStart`
66
+ *
67
+ * Shutdown order (reverse, per R5):
68
+ * 1. User `stop(app, reason)` callback
69
+ * 2. Stop scheduler
70
+ * 3. Close DB adapter
71
+ * 4. Shut down telemetry
72
+ *
73
+ * If any startup step fails, already-started services are cleaned up in reverse
74
+ * order before rethrowing. `stop()` is idempotent.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * import { runApplication } from '@gobing-ai/ts-infra/application';
79
+ *
80
+ * const app = await runApplication({
81
+ * config: { logging: { level: 'debug' } },
82
+ * async start(app) {
83
+ * app.logger.info('started');
84
+ * },
85
+ * });
86
+ * ```
87
+ */
88
+ export async function runApplication(options) {
89
+ // ── Resolve config ─────────────────────────────────────────────────
90
+ const logOpts = options.config?.logging;
91
+ const loggingConfig = {
92
+ enabled: logOpts?.enabled ?? true,
93
+ level: logOpts?.level ?? 'info',
94
+ console: logOpts?.console ?? true,
95
+ json: logOpts?.json ?? true,
96
+ ...(logOpts?.fileSink ? { fileSink: logOpts.fileSink } : {}),
97
+ };
98
+ const telOpts = options.config?.telemetry;
99
+ const telemetryConfig = {
100
+ enabled: telOpts?.enabled ?? true,
101
+ serviceName: telOpts?.serviceName ?? 'ts-libs',
102
+ environment: telOpts?.environment ?? 'development',
103
+ dbStatementDebug: telOpts?.dbStatementDebug ?? false,
104
+ };
105
+ const schedOpts = options.config?.scheduler;
106
+ const schedulerConfig = {
107
+ enabled: schedOpts?.enabled ?? false,
108
+ autoStart: schedOpts?.autoStart ?? true,
109
+ };
110
+ const eventsEnabled = options.config?.events?.enabled ?? true;
111
+ const eventsLifecycle = options.config?.events?.lifecycle ?? true;
112
+ const eventsDefaultObservers = options.config?.events?.defaultObservers ?? true;
113
+ const state = {
114
+ app: undefined,
115
+ userStop: options.stop,
116
+ schedulerAdapter: undefined,
117
+ schedulerStarted: false,
118
+ loggerInitialized: false,
119
+ telemetryInitialized: false,
120
+ stopped: false,
121
+ };
122
+ try {
123
+ // ── 1. Initialize logger ────────────────────────────────────────
124
+ let logger;
125
+ if (options.services?.logger) {
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
+ }
150
+ // ── 3. Create lifecycle bus + EventBus ─────────────────────────
151
+ const lifecycleBus = eventsEnabled && eventsLifecycle ? (options.services?.lifecycleBus ?? createLifecycleBus()) : undefined;
152
+ if (lifecycleBus && eventsDefaultObservers) {
153
+ attachDefaultObservers(lifecycleBus);
154
+ }
155
+ const events = options.services?.events
156
+ ? options.services.events
157
+ : new EventBus({ lifecycleBus: lifecycleBus });
158
+ // ── 4. Database (injected only) ────────────────────────────────
159
+ const db = options.services?.db;
160
+ // ── 5. Scheduler ───────────────────────────────────────────────
161
+ let scheduler;
162
+ if (schedulerConfig.enabled) {
163
+ const adapter = options.services?.scheduler ?? schedOpts?.adapter;
164
+ if (adapter) {
165
+ setSchedulerAdapter(adapter);
166
+ }
167
+ scheduler = initScheduler(schedOpts?.entries);
168
+ state.schedulerAdapter = scheduler;
169
+ }
170
+ // ── Build resolved config ──────────────────────────────────────
171
+ const resolvedConfig = {
172
+ logging: loggingConfig,
173
+ events: { enabled: eventsEnabled, lifecycle: eventsLifecycle, defaultObservers: eventsDefaultObservers },
174
+ telemetry: telemetryConfig,
175
+ scheduler: schedulerConfig,
176
+ };
177
+ // ── Build runtime handle ───────────────────────────────────────
178
+ const app = {
179
+ config: resolvedConfig,
180
+ appConfig: options.appConfig,
181
+ logger,
182
+ events,
183
+ lifecycleBus,
184
+ db,
185
+ scheduler,
186
+ stop: (reason) => performShutdown(state, reason ?? 'manual'),
187
+ };
188
+ state.app = app;
189
+ // ── 6. User start callback ─────────────────────────────────────
190
+ await options.start(app);
191
+ // ── 7. Start scheduler ─────────────────────────────────────────
192
+ if (schedulerConfig.enabled && schedulerConfig.autoStart && scheduler) {
193
+ await scheduler.start();
194
+ state.schedulerStarted = true;
195
+ }
196
+ return app;
197
+ }
198
+ catch (error) {
199
+ // Reverse-order cleanup of services this bootstrap owns.
200
+ // Scheduler: start() is the last async op before return; if it throws,
201
+ // schedulerStarted is still false, so there is nothing started to stop.
202
+ // DB: injected by the caller (portable bootstrap never creates one), so
203
+ // its lifecycle is caller-owned and not closed here.
204
+ // Telemetry: owned by the bootstrap — shut it down if it was initialized.
205
+ if (state.telemetryInitialized) {
206
+ await shutdownTelemetry();
207
+ }
208
+ throw error;
209
+ }
210
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Portable application bootstrap types.
3
+ *
4
+ * These types define the DI contract for `runApplication` — a thin orchestration
5
+ * layer over existing ts-infra primitives. The portable subpath does not import
6
+ * any runtime-specific adapters; everything injectable comes through the options.
7
+ *
8
+ * @module application/types
9
+ */
10
+ import type { EventBus } from '../event-bus/event-bus';
11
+ import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
12
+ import type { InfraEvents } from '../events';
13
+ import type { Logger, LogLevel } from '../logger';
14
+ import type { SchedulerAdapter } from '../scheduler/types';
15
+ /** Logging feature flags. */
16
+ export interface LoggingOptions {
17
+ /** Enable logging. Default `true`. */
18
+ enabled?: boolean;
19
+ /** Minimum log level. Default `'info'`. */
20
+ level?: LogLevel;
21
+ /** Enable console output. Default `true`. */
22
+ console?: boolean;
23
+ /**
24
+ * File sink writer. The portable bootstrap never opens files — the caller
25
+ * (or Node convenience subpath) provides a writer.
26
+ */
27
+ fileSink?: (line: string) => void;
28
+ /** JSON Lines format. Default `true`. */
29
+ json?: boolean;
30
+ }
31
+ /** Event bus feature flags. */
32
+ export interface EventsOptions<TEvents extends EventMap = InfraEvents> {
33
+ /** Enable event bus. Default `true`. */
34
+ enabled?: boolean;
35
+ /** Create and attach a lifecycle bus. Default `true`. */
36
+ lifecycle?: boolean;
37
+ /** Attach default observers (log + telemetry). Default `true`. */
38
+ defaultObservers?: boolean;
39
+ /** Pre-built event bus (skips creation when provided). */
40
+ bus?: EventBus<TEvents>;
41
+ }
42
+ /** Telemetry feature flags. */
43
+ export interface TelemetryOptions {
44
+ /** Enable telemetry instrumentation. Default `true`. */
45
+ enabled?: boolean;
46
+ /** Service name for spans. Default `'ts-libs'`. */
47
+ serviceName?: string;
48
+ /** Deployment environment. Default `'development'`. */
49
+ environment?: string;
50
+ /** Capture sanitized SQL in DB spans. Default `false`. */
51
+ dbStatementDebug?: boolean;
52
+ }
53
+ /** Scheduler feature flags. */
54
+ export interface SchedulerOptions {
55
+ /** Enable scheduler. Default `false`. */
56
+ enabled?: boolean;
57
+ /** Injected adapter (skips noop default when provided). */
58
+ adapter?: SchedulerAdapter;
59
+ /** Cron entries to register: `[cron, action][]`. */
60
+ entries?: Array<[string, () => Promise<void>]>;
61
+ /** Start scheduler immediately after registration. Default `true` when enabled. */
62
+ autoStart?: boolean;
63
+ }
64
+ /**
65
+ * Fully-resolved bootstrap config (all optionals filled with defaults).
66
+ * Constructed internally by `resolveBootstrapConfig`.
67
+ */
68
+ export interface ApplicationBootstrapConfig {
69
+ readonly logging: Readonly<Required<Pick<LoggingOptions, 'enabled' | 'level' | 'console' | 'json'>> & {
70
+ fileSink?: (line: string) => void;
71
+ }>;
72
+ readonly events: {
73
+ enabled: boolean;
74
+ lifecycle: boolean;
75
+ defaultObservers: boolean;
76
+ };
77
+ readonly telemetry: {
78
+ enabled: boolean;
79
+ serviceName: string;
80
+ environment: string;
81
+ dbStatementDebug: boolean;
82
+ };
83
+ readonly scheduler: {
84
+ enabled: boolean;
85
+ autoStart: boolean;
86
+ };
87
+ }
88
+ /** Services that may be pre-injected instead of created by the bootstrap. */
89
+ export interface ApplicationServices<TEvents extends EventMap = InfraEvents> {
90
+ logger?: Logger;
91
+ events?: EventBus<TEvents>;
92
+ lifecycleBus?: EventBus<BusLifecycleEvents>;
93
+ db?: DbAdapterLike;
94
+ scheduler?: SchedulerAdapter;
95
+ }
96
+ /**
97
+ * Minimal DB adapter shape the bootstrap cares about: `close()` for lifecycle.
98
+ * This avoids importing `ts-db` (an optional peer) from the portable subpath.
99
+ */
100
+ export interface DbAdapterLike {
101
+ close(): void;
102
+ }
103
+ /** Why the application is stopping. */
104
+ export type ApplicationStopReason = 'manual' | 'signal' | 'error' | 'shutdown';
105
+ /**
106
+ * Runtime handle returned by `runApplication`.
107
+ *
108
+ * Exposes all resolved services and a `stop()` method for graceful shutdown.
109
+ */
110
+ export interface ApplicationRuntime<TAppConfig = unknown, TEvents extends EventMap = InfraEvents> {
111
+ /** Fully-resolved bootstrap config (feature flags + defaults). */
112
+ readonly config: ApplicationBootstrapConfig;
113
+ /** Caller-provided application config, or `undefined`. */
114
+ readonly appConfig: TAppConfig;
115
+ /** Structured logger. */
116
+ readonly logger: Logger;
117
+ /** Application event bus. */
118
+ readonly events: EventBus<TEvents>;
119
+ /** Lifecycle bus (when enabled). */
120
+ readonly lifecycleBus?: EventBus<BusLifecycleEvents>;
121
+ /** DB adapter (when enabled and injected). */
122
+ readonly db?: DbAdapterLike;
123
+ /** Scheduler adapter (when enabled). */
124
+ readonly scheduler?: SchedulerAdapter;
125
+ /** Graceful shutdown. Idempotent — safe to call multiple times. */
126
+ stop(reason?: ApplicationStopReason): Promise<void>;
127
+ }
128
+ /**
129
+ * Options for the portable `runApplication`.
130
+ */
131
+ export interface ApplicationBootstrapOptions<TAppConfig = unknown, TEvents extends EventMap = InfraEvents> {
132
+ /** Bootstrap feature flags (partial — defaults applied internally). */
133
+ readonly config?: {
134
+ logging?: LoggingOptions;
135
+ events?: EventsOptions<TEvents>;
136
+ telemetry?: TelemetryOptions;
137
+ scheduler?: SchedulerOptions;
138
+ };
139
+ /** Already-resolved application config. Portable — no file reads. */
140
+ readonly appConfig?: TAppConfig;
141
+ /** Pre-built services to inject instead of creating defaults. */
142
+ readonly services?: Partial<ApplicationServices<TEvents>>;
143
+ /** User callback: application logic. Called after all services are ready. */
144
+ readonly start: (app: ApplicationRuntime<TAppConfig, TEvents>) => Promise<void> | void;
145
+ /** User callback: cleanup before services shut down. */
146
+ readonly stop?: (app: ApplicationRuntime<TAppConfig, TEvents>, reason: ApplicationStopReason) => Promise<void> | void;
147
+ }
148
+ /**
149
+ * Validation result matching the structural `safeParse` pattern.
150
+ * Avoids hard-coupling to a specific validation library.
151
+ */
152
+ export interface ConfigValidationResult<T> {
153
+ readonly success: boolean;
154
+ readonly data?: T;
155
+ readonly errors?: ReadonlyArray<{
156
+ path: string;
157
+ message: string;
158
+ }>;
159
+ }
160
+ /**
161
+ * Config validator: structural `safeParse`-compatible adapter.
162
+ * Accepts any validation library that produces `{ success, data?, errors? }`.
163
+ */
164
+ export type ApplicationConfigValidator<TAppConfig> = {
165
+ safeParse(raw: unknown): ConfigValidationResult<TAppConfig>;
166
+ } | ((raw: unknown) => TAppConfig) | {
167
+ validate(raw: unknown): TAppConfig;
168
+ } | {
169
+ parse(raw: unknown): TAppConfig;
170
+ };
171
+ /**
172
+ * Config loader options for the Node/Bun convenience subpath.
173
+ */
174
+ export interface ApplicationConfigLoader<TAppConfig = unknown> {
175
+ /** Path to the YAML config file. */
176
+ readonly configFile?: string;
177
+ /** YAML section name for bootstrap config. Default `'bootstrap'`. */
178
+ readonly bootstrapSection?: string;
179
+ /** YAML section name for app-specific config. Default: remaining object. */
180
+ readonly appSection?: string;
181
+ /** Caller-provided validator for the app config section. */
182
+ readonly appConfig?: ApplicationConfigValidator<TAppConfig>;
183
+ /** Override values merged after loading. */
184
+ readonly overrides?: Record<string, unknown>;
185
+ }
186
+ export type { BusLifecycleEvents, EventMap } from '../event-bus/types';
187
+ export type { InfraEvents } from '../events';
188
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/application/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC3B,sCAAsC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,yCAAyC;IACzC,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,+BAA+B;AAC/B,MAAM,WAAW,aAAa,CAAC,OAAO,SAAS,QAAQ,GAAG,WAAW;IACjE,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0DAA0D;IAC1D,GAAG,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;CAC3B;AAED,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC7B,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC7B,yCAAyC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,oDAAoD;IACpD,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/C,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAID;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACvC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CACtB,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CACnH,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,gBAAgB,EAAE,OAAO,CAAA;KAAE,CAAC;IACrF,QAAQ,CAAC,SAAS,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9G,QAAQ,CAAC,SAAS,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAChE;AAID,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB,CAAC,OAAO,SAAS,QAAQ,GAAG,WAAW;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC5C,EAAE,CAAC,EAAE,aAAa,CAAC;IACnB,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,KAAK,IAAI,IAAI,CAAC;CACjB;AAID,uCAAuC;AACvC,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAI/E;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAC5F,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnC,oCAAoC;IACpC,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACrD,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IACtC,mEAAmE;IACnE,IAAI,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAID;;GAEG;AACH,MAAM,WAAW,2BAA2B,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IACrG,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,MAAM,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAChC,SAAS,CAAC,EAAE,gBAAgB,CAAC;QAC7B,SAAS,CAAC,EAAE,gBAAgB,CAAC;KAChC,CAAC;IACF,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC;IAChC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;GAGG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAED;;;GAGG;AACH,MAAM,MAAM,0BAA0B,CAAC,UAAU,IAC3C;IAAE,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAA;CAAE,GAC/D,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,UAAU,CAAC,GAC9B;IAAE,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,GACtC;IAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,uBAAuB,CAAC,UAAU,GAAG,OAAO;IACzD,oCAAoC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC,UAAU,CAAC,CAAC;IAC5D,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChD;AAGD,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACvE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Portable application bootstrap types.
3
+ *
4
+ * These types define the DI contract for `runApplication` — a thin orchestration
5
+ * layer over existing ts-infra primitives. The portable subpath does not import
6
+ * any runtime-specific adapters; everything injectable comes through the options.
7
+ *
8
+ * @module application/types
9
+ */
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Node/Bun convenience bootstrap for `@gobing-ai/ts-infra`.
3
+ *
4
+ * Composes the portable `runApplication` with runtime-specific wiring:
5
+ * - YAML config loading via `@gobing-ai/ts-runtime`
6
+ * - Application-specific config validation with caller-provided validator
7
+ * - File log sink via `node:fs`
8
+ * - Bun SQLite DB adapter creation from config
9
+ * - Optional Node OTel exporter initialization
10
+ * - Optional Node scheduler adapter
11
+ *
12
+ * This subpath may import runtime-specific adapters. The portable bootstrap
13
+ * subpath (`@gobing-ai/ts-infra/application`) must not.
14
+ *
15
+ * @module application-node
16
+ */
17
+ import type { ApplicationBootstrapOptions, ApplicationConfigLoader, ApplicationRuntime, ApplicationStopReason, EventMap, InfraEvents } from './application/types';
18
+ /** Error thrown when application config validation fails. Includes file path and section name. */
19
+ export declare class ConfigValidationError extends Error {
20
+ constructor(message: string);
21
+ }
22
+ /** Options for the Node/Bun convenience {@link runNodeApplication}. */
23
+ export interface NodeApplicationOptions<TAppConfig = unknown, TEvents extends EventMap = InfraEvents> {
24
+ /** YAML config loading options. When omitted, uses defaults. */
25
+ readonly configLoader?: ApplicationConfigLoader<TAppConfig>;
26
+ /** Inline bootstrap config (overrides YAML-loaded config). */
27
+ readonly config?: ApplicationBootstrapOptions<TAppConfig, TEvents>['config'];
28
+ /** Pre-built services to inject. */
29
+ readonly services?: ApplicationBootstrapOptions<TAppConfig, TEvents>['services'];
30
+ /** User callback: application logic. */
31
+ readonly start: (app: ApplicationRuntime<TAppConfig, TEvents>) => Promise<void> | void;
32
+ /** User callback: cleanup before services shut down. */
33
+ readonly stop?: (app: ApplicationRuntime<TAppConfig, TEvents>, reason: ApplicationStopReason) => Promise<void> | void;
34
+ }
35
+ /**
36
+ * Node/Bun convenience application bootstrap.
37
+ *
38
+ * Extends the portable `runApplication` with:
39
+ * - YAML config file loading with section splitting
40
+ * - Application-specific config validation
41
+ * - File log sink creation from `logging.filePath`
42
+ * - Bun SQLite DB adapter creation when `database.enabled` + `database.driver`
43
+ * - Optional Node OTel telemetry exporter
44
+ * - Optional Node scheduler adapter
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { runNodeApplication } from '@gobing-ai/ts-infra/application-node';
49
+ *
50
+ * await runNodeApplication({
51
+ * configLoader: {
52
+ * configFile: 'config/app.yaml',
53
+ * bootstrapSection: 'bootstrap',
54
+ * appSection: 'billing',
55
+ * appConfig: {
56
+ * safeParse(raw) {
57
+ * return billingSchema.safeParse(raw);
58
+ * },
59
+ * },
60
+ * },
61
+ * async start(app) {
62
+ * app.logger.info('started');
63
+ * },
64
+ * });
65
+ * ```
66
+ */
67
+ export declare function runNodeApplication<TAppConfig = unknown, TEvents extends EventMap = InfraEvents>(options: NodeApplicationOptions<TAppConfig, TEvents>): Promise<ApplicationRuntime<TAppConfig, TEvents>>;
68
+ //# sourceMappingURL=application-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-node.d.ts","sourceRoot":"","sources":["../src/application-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,KAAK,EACR,2BAA2B,EAC3B,uBAAuB,EAEvB,kBAAkB,EAClB,qBAAqB,EAGrB,QAAQ,EACR,WAAW,EAId,MAAM,qBAAqB,CAAC;AAM7B,kGAAkG;AAClG,qBAAa,qBAAsB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI9B;AAgHD,uEAAuE;AACvE,MAAM,WAAW,sBAAsB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW;IAChG,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7E,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC;IACjF,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvF,wDAAwD;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,CACZ,GAAG,EAAE,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,EAC5C,MAAM,EAAE,qBAAqB,KAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,GAAG,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAG,WAAW,EACjG,OAAO,EAAE,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,kBAAkB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAkHlD"}