@gobing-ai/ts-infra 0.3.4 → 0.3.6

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.
Files changed (65) hide show
  1. package/README.md +196 -48
  2. package/dist/application/index.d.ts +55 -0
  3. package/dist/application/index.d.ts.map +1 -0
  4. package/dist/application/index.js +173 -0
  5. package/dist/application/plugins/builtins.d.ts +64 -0
  6. package/dist/application/plugins/builtins.d.ts.map +1 -0
  7. package/dist/application/plugins/builtins.js +146 -0
  8. package/dist/application/plugins/host.d.ts +61 -0
  9. package/dist/application/plugins/host.d.ts.map +1 -0
  10. package/dist/application/plugins/host.js +131 -0
  11. package/dist/application/plugins/index.d.ts +3 -0
  12. package/dist/application/plugins/index.d.ts.map +1 -0
  13. package/dist/application/plugins/index.js +2 -0
  14. package/dist/application/plugins/types.d.ts +69 -0
  15. package/dist/application/plugins/types.d.ts.map +1 -0
  16. package/dist/application/plugins/types.js +10 -0
  17. package/dist/application/types.d.ts +195 -0
  18. package/dist/application/types.d.ts.map +1 -0
  19. package/dist/application/types.js +9 -0
  20. package/dist/application-node.d.ts +68 -0
  21. package/dist/application-node.d.ts.map +1 -0
  22. package/dist/application-node.js +228 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/scheduler/cloudflare.d.ts.map +1 -1
  27. package/dist/scheduler/cloudflare.js +9 -2
  28. package/dist/scheduler/factory.d.ts +4 -8
  29. package/dist/scheduler/factory.d.ts.map +1 -1
  30. package/dist/scheduler/factory.js +12 -22
  31. package/dist/scheduler/index.d.ts +1 -1
  32. package/dist/scheduler/index.d.ts.map +1 -1
  33. package/dist/scheduler/index.js +1 -1
  34. package/dist/scheduler/wrap-handler.d.ts +8 -4
  35. package/dist/scheduler/wrap-handler.d.ts.map +1 -1
  36. package/dist/scheduler/wrap-handler.js +8 -4
  37. package/dist/telemetry/index.d.ts +1 -2
  38. package/dist/telemetry/index.d.ts.map +1 -1
  39. package/dist/telemetry/index.js +1 -2
  40. package/dist/telemetry/metrics.d.ts +9 -1
  41. package/dist/telemetry/metrics.d.ts.map +1 -1
  42. package/dist/telemetry/metrics.js +22 -1
  43. package/dist/telemetry/sdk.d.ts +33 -1
  44. package/dist/telemetry/sdk.d.ts.map +1 -1
  45. package/dist/telemetry/sdk.js +14 -1
  46. package/package.json +16 -3
  47. package/src/application/index.ts +248 -0
  48. package/src/application/plugins/builtins.ts +178 -0
  49. package/src/application/plugins/host.ts +143 -0
  50. package/src/application/plugins/index.ts +3 -0
  51. package/src/application/plugins/types.ts +86 -0
  52. package/src/application/types.ts +210 -0
  53. package/src/application-node.ts +311 -0
  54. package/src/index.ts +0 -2
  55. package/src/scheduler/cloudflare.ts +16 -5
  56. package/src/scheduler/factory.ts +15 -26
  57. package/src/scheduler/index.ts +1 -1
  58. package/src/scheduler/wrap-handler.ts +8 -4
  59. package/src/telemetry/index.ts +9 -2
  60. package/src/telemetry/metrics.ts +22 -1
  61. package/src/telemetry/sdk.ts +51 -2
  62. package/dist/telemetry/config.d.ts +0 -41
  63. package/dist/telemetry/config.d.ts.map +0 -1
  64. package/dist/telemetry/config.js +0 -21
  65. package/src/telemetry/config.ts +0 -59
@@ -8,7 +8,39 @@
8
8
  * keeps the main barrel free of any SDK runtime dependency.
9
9
  */
10
10
  import { type Tracer } from '@opentelemetry/api';
11
- import type { TelemetryConfig } from './config';
11
+ /**
12
+ * Full telemetry configuration: master enable switch, service name,
13
+ * environment, and debug-level DB statement capture.
14
+ */
15
+ export interface TelemetryConfig {
16
+ /** Master switch — when false, all tracing degrades to no-ops. */
17
+ enabled: boolean;
18
+ /** Logical service name emitted on every span. */
19
+ serviceName: string;
20
+ /** Deployment environment (development, staging, production). */
21
+ environment: string;
22
+ /**
23
+ * Debug-only DB statement capture.
24
+ *
25
+ * When true, DB spans may include sanitized SQL text in a `db.statement`
26
+ * attribute. SQL text is redacted — parameter values, literals, and
27
+ * identifiers are stripped before capture.
28
+ *
29
+ * Default: `false`. Controlled by `OTEL_DB_STATEMENT_DEBUG` env var.
30
+ */
31
+ dbStatementDebug: boolean;
32
+ }
33
+ /** Partial telemetry config from the centralized config system. */
34
+ export interface TelemetryConfigPartial {
35
+ enabled?: boolean | undefined;
36
+ serviceName?: string | undefined;
37
+ environment?: string | undefined;
38
+ dbStatementDebug?: boolean | undefined;
39
+ /** Deployment environment fallback (from app.env). */
40
+ appEnv?: string | undefined;
41
+ }
42
+ /** Resolve the full telemetry config by merging a partial override with defaults. */
43
+ export declare function getTelemetryConfig(configPartial?: TelemetryConfigPartial): TelemetryConfig;
12
44
  /** Get the resolved telemetry configuration (defaults + overrides). */
13
45
  export declare function getResolvedConfig(): TelemetryConfig;
14
46
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/telemetry/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAShD,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,eAAe,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAIrE;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/telemetry/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,oBAAoB,CAAC;AAIxD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,gBAAgB,EAAE,OAAO,CAAC;CAC7B;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAQD,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,aAAa,GAAE,sBAA2B,GAAG,eAAe,CAO9F;AAQD,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,eAAe,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAIrE;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC"}
@@ -8,7 +8,20 @@
8
8
  * keeps the main barrel free of any SDK runtime dependency.
9
9
  */
10
10
  import { trace } from '@opentelemetry/api';
11
- import { getTelemetryConfig } from './config.js';
11
+ const CONFIG_DEFAULTS = {
12
+ enabled: true,
13
+ serviceName: 'ts-libs',
14
+ environment: 'development',
15
+ };
16
+ /** Resolve the full telemetry config by merging a partial override with defaults. */
17
+ export function getTelemetryConfig(configPartial = {}) {
18
+ return {
19
+ enabled: configPartial.enabled ?? CONFIG_DEFAULTS.enabled,
20
+ serviceName: configPartial.serviceName ?? CONFIG_DEFAULTS.serviceName,
21
+ environment: configPartial.environment ?? configPartial.appEnv ?? CONFIG_DEFAULTS.environment,
22
+ dbStatementDebug: configPartial.dbStatementDebug ?? false,
23
+ };
24
+ }
12
25
  const TRACER_NAME = '@gobing-ai/ts-infra';
13
26
  const TRACER_VERSION = '0.1.0';
14
27
  let telemetryInitialized = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "@gobing-ai/ts-infra — Infrastructure backbone: event bus, job queue, scheduler, telemetry, API client, and logging.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,6 +47,14 @@
47
47
  "./scheduler-node": {
48
48
  "types": "./dist/scheduler-node.d.ts",
49
49
  "import": "./dist/scheduler-node.js"
50
+ },
51
+ "./application": {
52
+ "types": "./dist/application/index.d.ts",
53
+ "import": "./dist/application/index.js"
54
+ },
55
+ "./application-node": {
56
+ "types": "./dist/application-node.d.ts",
57
+ "import": "./dist/application-node.js"
50
58
  }
51
59
  },
52
60
  "files": [
@@ -69,7 +77,8 @@
69
77
  "@logtape/logtape": "^2.0.0"
70
78
  },
71
79
  "peerDependencies": {
72
- "@gobing-ai/ts-db": "^0.3.4",
80
+ "@gobing-ai/ts-db": "^0.3.6",
81
+ "@gobing-ai/ts-runtime": "^0.3.6",
73
82
  "@opentelemetry/api": "^1.9.0",
74
83
  "@opentelemetry/sdk-trace-node": "^2.0.0",
75
84
  "@opentelemetry/sdk-metrics": "^2.0.0",
@@ -82,6 +91,9 @@
82
91
  "@gobing-ai/ts-db": {
83
92
  "optional": true
84
93
  },
94
+ "@gobing-ai/ts-runtime": {
95
+ "optional": true
96
+ },
85
97
  "@opentelemetry/sdk-trace-node": {
86
98
  "optional": true
87
99
  },
@@ -99,7 +111,8 @@
99
111
  }
100
112
  },
101
113
  "devDependencies": {
102
- "@gobing-ai/ts-db": "^0.3.4",
114
+ "@gobing-ai/ts-db": "^0.3.6",
115
+ "@gobing-ai/ts-runtime": "^0.3.6",
103
116
  "@types/bun": "1.3.14",
104
117
  "@opentelemetry/api": "^1.9.0",
105
118
  "@opentelemetry/sdk-trace-node": "^2.0.0",
@@ -0,0 +1,248 @@
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
+
13
+ import { attachDefaultObservers, createLifecycleBus } from '../event-bus/default-observers';
14
+ import { EventBus } from '../event-bus/event-bus';
15
+ import type { BusLifecycleEvents, EventMap } from '../event-bus/types';
16
+ import type { InfraEvents } from '../events';
17
+ import { getLogger, type Logger } from '../logger';
18
+ import { initScheduler } from '../scheduler/factory';
19
+ import type { SchedulerAdapter } from '../scheduler/types';
20
+ import { loggerPlugin, schedulerPlugin, telemetryPlugin, userCallbackPlugin } from './plugins/builtins';
21
+ import { PluginHost } from './plugins/host';
22
+ import type {
23
+ ApplicationBootstrapConfig,
24
+ ApplicationBootstrapOptions,
25
+ ApplicationRuntime,
26
+ ApplicationStopReason,
27
+ DbAdapterLike,
28
+ } from './types';
29
+
30
+ // ── Internal runtime state ────────────────────────────────────────────────
31
+
32
+ interface RuntimeState<TAppConfig, TEvents extends EventMap> {
33
+ app: ApplicationRuntime<TAppConfig, TEvents> | undefined;
34
+ pluginHost: PluginHost;
35
+ stopped: boolean;
36
+ }
37
+
38
+ // ── Shutdown (deterministic reverse order per R5) ─────────────────────────
39
+
40
+ async function performShutdown<TAppConfig, TEvents extends EventMap>(
41
+ state: RuntimeState<TAppConfig, TEvents>,
42
+ reason: ApplicationStopReason,
43
+ ): Promise<void> {
44
+ if (state.stopped) return;
45
+ state.stopped = true;
46
+
47
+ const app = state.app;
48
+ if (!app) return;
49
+
50
+ // 1. Stop + unload plugins in reverse registration order (fail-soft).
51
+ // Scheduler stop and telemetry shutdown run here via their onStop hooks.
52
+ await state.pluginHost.stopAll(reason);
53
+ await state.pluginHost.unloadAll(reason);
54
+ // Shutdown is complete — the host's stopAll/unloadAll calls every plugin's
55
+ // onStop/onUnload in reverse registration order, including user callback,
56
+ // scheduler, and service teardown. No inline steps.
57
+ }
58
+
59
+ // ── Public API ────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Portable application bootstrap.
63
+ *
64
+ * Orchestrates logger, telemetry, events, optional DB, and optional scheduler.
65
+ * Accepts injected dependencies; never opens files, reads config from disk,
66
+ * or wires runtime-specific exporters.
67
+ *
68
+ * Startup is plugin-driven. Built-in service plugins are registered in dependency
69
+ * order, then `loadAll()` + `startAll()` run them forward:
70
+ * 1. Resolve bootstrap config; build EventBus + PluginHost
71
+ * 2. Register built-ins in order: logger → telemetry → [caller plugins] →
72
+ * user-callback → scheduler (scheduler last so autoStart runs after user start)
73
+ * 3. `loadAll()` then `startAll()` — `failFast` plugins abort boot on failure
74
+ *
75
+ * Shutdown is the reverse fan-out: `stopAll(reason)` → `unloadAll(reason)` calls
76
+ * every plugin's `onStop`/`onUnload` in reverse registration order (scheduler stop,
77
+ * user `stop(app, reason)`, telemetry shutdown, owned-DB close). Caller-injected
78
+ * `services.db` is caller-owned and never closed here.
79
+ *
80
+ * If startup fails, the host's reverse-order `stopAll('error')`/`unloadAll('error')`
81
+ * tears down whatever started before rethrowing. `stop()` is idempotent.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * import { runApplication } from '@gobing-ai/ts-infra/application';
86
+ *
87
+ * const app = await runApplication({
88
+ * config: { logging: { level: 'debug' } },
89
+ * async start(app) {
90
+ * app.logger.info('started');
91
+ * },
92
+ * });
93
+ * ```
94
+ */
95
+ export async function runApplication<TAppConfig = unknown, TEvents extends EventMap = InfraEvents>(
96
+ options: ApplicationBootstrapOptions<TAppConfig, TEvents>,
97
+ ): Promise<ApplicationRuntime<TAppConfig, TEvents>> {
98
+ // ── Resolve config ─────────────────────────────────────────────────
99
+
100
+ const logOpts = options.config?.logging;
101
+ const loggingConfig: ApplicationBootstrapConfig['logging'] = {
102
+ enabled: logOpts?.enabled ?? true,
103
+ level: logOpts?.level ?? 'info',
104
+ console: logOpts?.console ?? true,
105
+ json: logOpts?.json ?? true,
106
+ ...(logOpts?.fileSink ? { fileSink: logOpts.fileSink } : {}),
107
+ };
108
+
109
+ const telOpts = options.config?.telemetry;
110
+ const telemetryConfig: ApplicationBootstrapConfig['telemetry'] = {
111
+ enabled: telOpts?.enabled ?? true,
112
+ serviceName: telOpts?.serviceName ?? 'ts-libs',
113
+ environment: telOpts?.environment ?? 'development',
114
+ dbStatementDebug: telOpts?.dbStatementDebug ?? false,
115
+ };
116
+
117
+ const schedOpts = options.config?.scheduler;
118
+ const schedulerConfig: ApplicationBootstrapConfig['scheduler'] = {
119
+ enabled: schedOpts?.enabled ?? false,
120
+ autoStart: schedOpts?.autoStart ?? true,
121
+ };
122
+
123
+ const eventsEnabled = options.config?.events?.enabled ?? true;
124
+ const eventsLifecycle = options.config?.events?.lifecycle ?? true;
125
+ const eventsDefaultObservers = options.config?.events?.defaultObservers ?? true;
126
+
127
+ const state: RuntimeState<TAppConfig, TEvents> = {
128
+ app: undefined,
129
+ stopped: false,
130
+ pluginHost: undefined as unknown as PluginHost,
131
+ };
132
+
133
+ try {
134
+ // ── 1. Resolve logger (init deferred to loggerPlugin) ───────────
135
+ const logger: Logger = options.services?.logger ?? getLogger('bootstrap');
136
+ const loggerInjected = !!options.services?.logger;
137
+
138
+ // ── 3. Create lifecycle bus + EventBus ─────────────────────────
139
+ const lifecycleBus =
140
+ eventsEnabled && eventsLifecycle ? (options.services?.lifecycleBus ?? createLifecycleBus()) : undefined;
141
+
142
+ if (lifecycleBus && eventsDefaultObservers) {
143
+ attachDefaultObservers(lifecycleBus);
144
+ }
145
+
146
+ const events = options.services?.events
147
+ ? options.services.events
148
+ : new EventBus<TEvents>({ lifecycleBus: lifecycleBus as EventBus<BusLifecycleEvents> | undefined });
149
+
150
+ // ── 4. Database (injected only) ────────────────────────────────
151
+ const db: DbAdapterLike | undefined = options.services?.db;
152
+
153
+ // ── 5. Scheduler ───────────────────────────────────────────────
154
+ let scheduler: SchedulerAdapter | undefined;
155
+ if (schedulerConfig.enabled) {
156
+ const adapter = options.services?.scheduler ?? schedOpts?.adapter;
157
+ scheduler = initScheduler(adapter, schedOpts?.entries);
158
+ }
159
+
160
+ // ── 5.5 Plugin host + built-in service plugins ─────────────────
161
+ const pluginHost: PluginHost =
162
+ options.services?.pluginHost ?? new PluginHost(events as unknown as EventBus<EventMap>);
163
+ state.pluginHost = pluginHost;
164
+
165
+ // Register built-in service plugins in dependency order: logger -> telemetry.
166
+ pluginHost.register(loggerPlugin(loggingConfig, loggerInjected));
167
+ pluginHost.register(telemetryPlugin(telemetryConfig));
168
+ // Caller-injected `services.db` is NOT closed by the portable layer — it is
169
+ // caller-owned (task 0028). Only adapters the bootstrap CREATES (the Node
170
+ // subpath) are wrapped in a `dbPlugin` whose onStop closes them.
171
+
172
+ // ── Build runtime handle (before startAll so plugins can capture it) ─
173
+ const resolvedConfig: ApplicationBootstrapConfig = {
174
+ logging: loggingConfig,
175
+ events: { enabled: eventsEnabled, lifecycle: eventsLifecycle, defaultObservers: eventsDefaultObservers },
176
+ telemetry: telemetryConfig,
177
+ scheduler: schedulerConfig,
178
+ };
179
+
180
+ const app: ApplicationRuntime<TAppConfig, TEvents> = {
181
+ config: resolvedConfig,
182
+ appConfig: options.appConfig as TAppConfig,
183
+ logger,
184
+ events,
185
+ lifecycleBus,
186
+ db,
187
+ scheduler,
188
+ pluginHost,
189
+ stop: (reason?: ApplicationStopReason) => performShutdown(state, reason ?? 'manual'),
190
+ };
191
+ state.app = app;
192
+
193
+ // ── Register caller-provided plugins (before user callback) ─────────
194
+ if (options.plugins) {
195
+ for (const p of options.plugins) {
196
+ pluginHost.register(p);
197
+ }
198
+ }
199
+
200
+ // ── Register user-callback plugin (after services, before scheduler) ─
201
+ pluginHost.register(
202
+ userCallbackPlugin(
203
+ options.start,
204
+ options.stop as ((app: ApplicationRuntime<TAppConfig, TEvents>, reason: string) => void) | undefined,
205
+ app,
206
+ ),
207
+ );
208
+
209
+ // ── Register scheduler plugin (LAST — autoStart after user callback) ─
210
+ if (schedulerConfig.enabled && scheduler) {
211
+ pluginHost.register(schedulerPlugin(scheduler, schedulerConfig.autoStart));
212
+ }
213
+
214
+ // ── Load + start (built-in failFast=rethrow on critical failure) ─
215
+ await pluginHost.loadAll();
216
+ await pluginHost.startAll();
217
+ return app;
218
+ } catch (error) {
219
+ // Startup failed: tear down whatever started, in reverse registration
220
+ // order, via the host. Each plugin's onStop/onUnload is best-effort, so a
221
+ // partially-started ring still releases its resources (telemetry, scheduler,
222
+ // owned DB). Caller-injected services.db is caller-owned and not touched.
223
+ await state.pluginHost.stopAll('error');
224
+ await state.pluginHost.unloadAll('error');
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ export type { BusLifecycleEvents, EventMap } from '../event-bus/types';
230
+ export type { InfraEvents } from '../events';
231
+ export type { PluginHost } from './plugins/host';
232
+ export type { Plugin, PluginSummary } from './plugins/types';
233
+ // Re-export types
234
+ export type {
235
+ ApplicationBootstrapConfig,
236
+ ApplicationBootstrapOptions,
237
+ ApplicationConfigLoader,
238
+ ApplicationConfigValidator,
239
+ ApplicationRuntime,
240
+ ApplicationServices,
241
+ ApplicationStopReason,
242
+ ConfigValidationResult,
243
+ DbAdapterLike,
244
+ EventsOptions,
245
+ LoggingOptions,
246
+ SchedulerOptions,
247
+ TelemetryOptions,
248
+ } from './types';
@@ -0,0 +1,178 @@
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
+
11
+ import type { LogLevel } from '../../logger';
12
+ import { initializeLogger } from '../../logger';
13
+ import { initMetrics, shutdownMetrics } from '../../telemetry/metrics';
14
+ import { initTelemetry, shutdownTelemetry } from '../../telemetry/sdk';
15
+ import type { ApplicationBootstrapConfig, DbAdapterLike } from '../types';
16
+ import type { Plugin } from './types';
17
+
18
+ // ── Telemetry ──────────────────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Built-in plugin for telemetry + metrics.
22
+ *
23
+ * `onStart`: `initTelemetry` + `initMetrics` (pre-warm instruments).
24
+ * `onStop`: `shutdownMetrics` + `shutdownTelemetry` (reverse of init).
25
+ * `failFast: true` — a failing telemetry init aborts the bootstrap.
26
+ */
27
+ export function telemetryPlugin(config: ApplicationBootstrapConfig['telemetry']): Plugin {
28
+ return {
29
+ name: 'builtin:telemetry',
30
+ version: '0.0.0',
31
+ failFast: true,
32
+ onLoad: async () => {},
33
+ onStart: async () => {
34
+ if (config.enabled) {
35
+ initTelemetry({
36
+ enabled: config.enabled,
37
+ serviceName: config.serviceName,
38
+ environment: config.environment,
39
+ dbStatementDebug: config.dbStatementDebug,
40
+ });
41
+ initMetrics();
42
+ }
43
+ },
44
+ onStop: async () => {
45
+ if (config.enabled) {
46
+ shutdownMetrics();
47
+ await shutdownTelemetry();
48
+ }
49
+ },
50
+ };
51
+ }
52
+
53
+ // ── Logger ─────────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Built-in plugin for the structured logger.
57
+ *
58
+ * `onStart`: `initializeLogger` (skipped when an injected logger is present).
59
+ * No `onStop` — the logger has no teardown.
60
+ * `failFast: true` — a failing logger init aborts the bootstrap.
61
+ */
62
+ export function loggerPlugin(
63
+ config: {
64
+ enabled: boolean;
65
+ level: LogLevel;
66
+ console: boolean;
67
+ json: boolean;
68
+ fileSink?: ((line: string) => void) | undefined;
69
+ },
70
+ injected?: boolean,
71
+ ): Plugin {
72
+ return {
73
+ name: 'builtin:logger',
74
+ version: '0.0.0',
75
+ failFast: true,
76
+ onLoad: async () => {},
77
+ onStart: async () => {
78
+ if (config.enabled && !injected) {
79
+ await initializeLogger({
80
+ level: config.level,
81
+ console: config.console,
82
+ json: config.json,
83
+ fileSink: config.fileSink,
84
+ });
85
+ }
86
+ },
87
+ };
88
+ }
89
+
90
+ // ── User callback ──────────────────────────────────────────────────────────
91
+
92
+ import type { EventMap } from '../../event-bus/types';
93
+ import type { ApplicationRuntime } from '../types';
94
+
95
+ /**
96
+ * Built-in plugin that wraps the user's `start`/`stop` callbacks.
97
+ *
98
+ * `onStart`: calls `options.start(app)`. `failFast: true`.
99
+ * `onStop`: calls `options.stop(app, reason)`. Registered after services,
100
+ * before scheduler, so stopAll/reverse-order places it after
101
+ * scheduler.stop and before service teardown.
102
+ */
103
+ export function userCallbackPlugin<TAppConfig, TEvents extends EventMap>(
104
+ start: (app: ApplicationRuntime<TAppConfig, TEvents>) => Promise<void> | void,
105
+ stop: ((app: ApplicationRuntime<TAppConfig, TEvents>, reason: string) => Promise<void> | void) | undefined,
106
+ app?: ApplicationRuntime<TAppConfig, TEvents>,
107
+ ): Plugin {
108
+ return {
109
+ name: 'builtin:user-callback',
110
+ version: '0.0.0',
111
+ failFast: true,
112
+ onLoad: async () => {},
113
+ onStart: async () => {
114
+ if (app) await start(app);
115
+ },
116
+ onStop:
117
+ stop && app
118
+ ? async (_host, reason) => {
119
+ await stop(app, reason ?? 'manual');
120
+ }
121
+ : undefined,
122
+ };
123
+ }
124
+
125
+ // ── Scheduler ──────────────────────────────────────────────────────────────
126
+ import type { SchedulerAdapter } from '../../scheduler/types';
127
+
128
+ /**
129
+ * Built-in plugin for the scheduler.
130
+ *
131
+ * `onStart`: `adapter.start()` when `autoStart` is true.
132
+ * `onStop`: `adapter.stop()` — fail-soft.
133
+ *
134
+ * Registered LAST so autoStart always runs after the user callback.
135
+ */
136
+ export function schedulerPlugin(adapter: SchedulerAdapter, autoStart: boolean): Plugin {
137
+ return {
138
+ name: 'builtin:scheduler',
139
+ version: '0.0.0',
140
+ failFast: true,
141
+ onLoad: async () => {},
142
+ onStart: async () => {
143
+ if (autoStart) {
144
+ await adapter.start();
145
+ }
146
+ },
147
+ onStop: async () => {
148
+ // Fail-soft — the host always catches stop/unload errors
149
+ await adapter.stop();
150
+ },
151
+ };
152
+ }
153
+
154
+ // ── DB (reason-aware, for owned adapters only) ────────────────────────────
155
+
156
+ /**
157
+ * Built-in plugin for a DB adapter the bootstrap OWNS.
158
+ *
159
+ * `onStop(reason)`: `db.close()` — best-effort.
160
+ * Register ONLY when the bootstrap creates the adapter (Node subpath).
161
+ * Do NOT register for caller-injected `services.db` — those are caller-owned.
162
+ */
163
+ export function dbPlugin(db: DbAdapterLike): Plugin {
164
+ return {
165
+ name: 'builtin:db',
166
+ version: '0.0.0',
167
+ failFast: false,
168
+ onLoad: async () => {},
169
+ onStart: async () => {},
170
+ onStop: async () => {
171
+ try {
172
+ db.close();
173
+ } catch {
174
+ /* best-effort */
175
+ }
176
+ },
177
+ };
178
+ }