@gobing-ai/ts-infra 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +389 -0
  2. package/dist/api-client.d.ts +31 -0
  3. package/dist/api-client.d.ts.map +1 -0
  4. package/dist/api-client.js +112 -0
  5. package/dist/event-bus/event-bus.d.ts +33 -0
  6. package/dist/event-bus/event-bus.d.ts.map +1 -0
  7. package/dist/event-bus/event-bus.js +211 -0
  8. package/dist/event-bus/index.d.ts +3 -0
  9. package/dist/event-bus/index.d.ts.map +1 -0
  10. package/dist/event-bus/index.js +1 -0
  11. package/dist/event-bus/types.d.ts +35 -0
  12. package/dist/event-bus/types.d.ts.map +1 -0
  13. package/dist/event-bus/types.js +3 -0
  14. package/dist/events/app-events.d.ts +7 -0
  15. package/dist/events/app-events.d.ts.map +1 -0
  16. package/dist/events/app-events.js +4 -0
  17. package/dist/events/create-system-bus.d.ts +6 -0
  18. package/dist/events/create-system-bus.d.ts.map +1 -0
  19. package/dist/events/create-system-bus.js +7 -0
  20. package/dist/events/index.d.ts +3 -0
  21. package/dist/events/index.d.ts.map +1 -0
  22. package/dist/events/index.js +1 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +11 -0
  26. package/dist/index.js.map +9 -0
  27. package/dist/job-queue/index.d.ts +2 -0
  28. package/dist/job-queue/index.d.ts.map +1 -0
  29. package/dist/job-queue/index.js +0 -0
  30. package/dist/job-queue/types.d.ts +57 -0
  31. package/dist/job-queue/types.d.ts.map +1 -0
  32. package/dist/job-queue/types.js +8 -0
  33. package/dist/logger.d.ts +28 -0
  34. package/dist/logger.d.ts.map +1 -0
  35. package/dist/logger.js +100 -0
  36. package/dist/scheduler/action.d.ts +5 -0
  37. package/dist/scheduler/action.d.ts.map +1 -0
  38. package/dist/scheduler/action.js +0 -0
  39. package/dist/scheduler/cloudflare.d.ts +27 -0
  40. package/dist/scheduler/cloudflare.d.ts.map +1 -0
  41. package/dist/scheduler/cloudflare.js +24 -0
  42. package/dist/scheduler/factory.d.ts +19 -0
  43. package/dist/scheduler/factory.d.ts.map +1 -0
  44. package/dist/scheduler/factory.js +45 -0
  45. package/dist/scheduler/index.d.ts +6 -0
  46. package/dist/scheduler/index.d.ts.map +1 -0
  47. package/dist/scheduler/index.js +4 -0
  48. package/dist/scheduler/node.d.ts +16 -0
  49. package/dist/scheduler/node.d.ts.map +1 -0
  50. package/dist/scheduler/node.js +63 -0
  51. package/dist/scheduler/noop.d.ts +11 -0
  52. package/dist/scheduler/noop.d.ts.map +1 -0
  53. package/dist/scheduler/noop.js +12 -0
  54. package/dist/scheduler/types.d.ts +12 -0
  55. package/dist/scheduler/types.d.ts.map +1 -0
  56. package/dist/scheduler/types.js +3 -0
  57. package/dist/telemetry/config.d.ts +42 -0
  58. package/dist/telemetry/config.d.ts.map +1 -0
  59. package/dist/telemetry/config.js +24 -0
  60. package/dist/telemetry/db-sanitize.d.ts +15 -0
  61. package/dist/telemetry/db-sanitize.d.ts.map +1 -0
  62. package/dist/telemetry/db-sanitize.js +72 -0
  63. package/dist/telemetry/index.d.ts +7 -0
  64. package/dist/telemetry/index.d.ts.map +1 -0
  65. package/dist/telemetry/index.js +5 -0
  66. package/dist/telemetry/metrics.d.ts +32 -0
  67. package/dist/telemetry/metrics.d.ts.map +1 -0
  68. package/dist/telemetry/metrics.js +109 -0
  69. package/dist/telemetry/sdk.d.ts +13 -0
  70. package/dist/telemetry/sdk.d.ts.map +1 -0
  71. package/dist/telemetry/sdk.js +54 -0
  72. package/dist/telemetry/tracing.d.ts +13 -0
  73. package/dist/telemetry/tracing.d.ts.map +1 -0
  74. package/dist/telemetry/tracing.js +54 -0
  75. package/package.json +50 -0
  76. package/src/api-client.ts +162 -0
  77. package/src/event-bus/event-bus.ts +236 -0
  78. package/src/event-bus/index.ts +9 -0
  79. package/src/event-bus/types.ts +40 -0
  80. package/src/events/app-events.ts +8 -0
  81. package/src/events/create-system-bus.ts +8 -0
  82. package/src/events/index.ts +2 -0
  83. package/src/index.ts +74 -0
  84. package/src/job-queue/index.ts +9 -0
  85. package/src/job-queue/types.ts +60 -0
  86. package/src/logger.ts +123 -0
  87. package/src/scheduler/action.ts +4 -0
  88. package/src/scheduler/cloudflare.ts +45 -0
  89. package/src/scheduler/factory.ts +57 -0
  90. package/src/scheduler/index.ts +5 -0
  91. package/src/scheduler/node.ts +83 -0
  92. package/src/scheduler/noop.ts +20 -0
  93. package/src/scheduler/types.ts +13 -0
  94. package/src/telemetry/config.ts +63 -0
  95. package/src/telemetry/db-sanitize.ts +79 -0
  96. package/src/telemetry/index.ts +29 -0
  97. package/src/telemetry/metrics.ts +150 -0
  98. package/src/telemetry/sdk.ts +65 -0
  99. package/src/telemetry/tracing.ts +64 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Job queue types for async work processing with retry.
3
+ *
4
+ * **Types only** — the DB-backed `DbQueue` and `DbConsumer` implementations
5
+ * that drive the job-queue subsystem live in `@spur/core`. These interfaces
6
+ * are provided here so `EventBus` and other infra components can reference
7
+ * the queue contract without a circular dependency on the DB layer.
8
+ */
9
+
10
+ export interface Job<T = unknown> {
11
+ id: string;
12
+ type: string;
13
+ payload: T;
14
+ status: 'pending' | 'processing' | 'completed' | 'failed';
15
+ attempts: number;
16
+ maxRetries: number;
17
+ createdAt: number;
18
+ updatedAt: number;
19
+ nextRetryAt: number | null;
20
+ lastError: string | null;
21
+ processingAt: number | null;
22
+ }
23
+
24
+ export interface EnqueueOptions {
25
+ maxRetries?: number;
26
+ delay?: number;
27
+ ttlMs?: number;
28
+ }
29
+
30
+ export interface JobQueue<T = unknown> {
31
+ enqueue(type: string, payload: T, options?: EnqueueOptions): Promise<string>;
32
+ enqueueBatch(jobs: Array<{ type: string; payload: T } & EnqueueOptions>): Promise<string[]>;
33
+ }
34
+
35
+ export type JobHandler<T = unknown> = (job: Job<T>) => Promise<void>;
36
+
37
+ export interface QueueStats {
38
+ pending: number;
39
+ processing: number;
40
+ completed: number;
41
+ failed: number;
42
+ }
43
+
44
+ export interface QueueConsumerConfig {
45
+ pollInterval?: number;
46
+ batchSize?: number;
47
+ maxConcurrency?: number;
48
+ visibilityTimeout?: number;
49
+ baseDelay?: number;
50
+ maxDelay?: number;
51
+ drainTimeoutMs?: number;
52
+ maxPollBackoff?: number;
53
+ }
54
+
55
+ export interface QueueConsumer<T = unknown> {
56
+ register(type: string, handler: JobHandler<T>): void;
57
+ start(): Promise<void>;
58
+ stop(): Promise<void>;
59
+ stats(): Promise<QueueStats>;
60
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Structured JSON logger with levels (trace/debug/info/warn/error/fatal).
3
+ *
4
+ * No external dependencies — console-based implementation.
5
+ */
6
+
7
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
8
+
9
+ const LEVEL_ORDER: Record<LogLevel, number> = {
10
+ trace: 0,
11
+ debug: 1,
12
+ info: 2,
13
+ warn: 3,
14
+ error: 4,
15
+ fatal: 5,
16
+ };
17
+
18
+ export interface Logger {
19
+ trace(msg: string, data?: Record<string, unknown>): void;
20
+ debug(msg: string, data?: Record<string, unknown>): void;
21
+ info(msg: string, data?: Record<string, unknown>): void;
22
+ warn(msg: string, data?: Record<string, unknown>): void;
23
+ error(msg: string, data?: Record<string, unknown>): void;
24
+ fatal(msg: string, data?: Record<string, unknown>): void;
25
+ child(context: Record<string, unknown>): Logger;
26
+ }
27
+
28
+ class ConsoleLogger implements Logger {
29
+ private readonly context: Record<string, unknown>;
30
+
31
+ constructor(
32
+ private readonly category: string,
33
+ private readonly minLevel: LogLevel = 'info',
34
+ context: Record<string, unknown> = {},
35
+ ) {
36
+ this.context = { category, ...context };
37
+ }
38
+
39
+ private log(level: LogLevel, msg: string, data?: Record<string, unknown>): void {
40
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel]) return;
41
+ if (globalMuted) return;
42
+
43
+ const entry = {
44
+ level,
45
+ message: msg,
46
+ timestamp: new Date().toISOString(),
47
+ ...this.context,
48
+ ...data,
49
+ };
50
+
51
+ const json = JSON.stringify(entry);
52
+ switch (level) {
53
+ case 'error':
54
+ case 'fatal':
55
+ console.error(json);
56
+ break;
57
+ case 'warn':
58
+ console.warn(json);
59
+ break;
60
+ case 'debug':
61
+ case 'trace':
62
+ console.debug(json);
63
+ break;
64
+ default:
65
+ console.log(json);
66
+ }
67
+ }
68
+
69
+ trace(msg: string, data?: Record<string, unknown>): void {
70
+ this.log('trace', msg, data);
71
+ }
72
+ debug(msg: string, data?: Record<string, unknown>): void {
73
+ this.log('debug', msg, data);
74
+ }
75
+ info(msg: string, data?: Record<string, unknown>): void {
76
+ this.log('info', msg, data);
77
+ }
78
+ warn(msg: string, data?: Record<string, unknown>): void {
79
+ this.log('warn', msg, data);
80
+ }
81
+ error(msg: string, data?: Record<string, unknown>): void {
82
+ this.log('error', msg, data);
83
+ }
84
+ fatal(msg: string, data?: Record<string, unknown>): void {
85
+ this.log('fatal', msg, data);
86
+ }
87
+
88
+ child(context: Record<string, unknown>): Logger {
89
+ return new ConsoleLogger(this.category, this.minLevel, { ...this.context, ...context });
90
+ }
91
+ }
92
+
93
+ const loggers = new Map<string, ConsoleLogger>();
94
+
95
+ let globalLevel: LogLevel = 'info';
96
+ let globalMuted = false;
97
+
98
+ /**
99
+ * Mute or unmute all logger console output. Useful for tests.
100
+ */
101
+ export function setLoggerMuted(muted: boolean): void {
102
+ globalMuted = muted;
103
+ }
104
+
105
+ /**
106
+ * Get or create a logger for the given category.
107
+ */
108
+ export function getLogger(category: string): Logger {
109
+ const existing = loggers.get(category);
110
+ if (existing) return existing;
111
+
112
+ const logger = new ConsoleLogger(category, globalLevel);
113
+ loggers.set(category, logger);
114
+ return logger;
115
+ }
116
+
117
+ /**
118
+ * Initialize the logger subsystem with a minimum log level.
119
+ */
120
+ export function initializeLogger(level: LogLevel = 'info'): void {
121
+ globalLevel = level;
122
+ loggers.clear();
123
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Scheduler action type re-export.
3
+ */
4
+ export type { ScheduledAction } from './types';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Cloudflare Workers scheduler adapter using Cron Triggers.
3
+ * Uses minimal local type declarations — no @cloudflare/workers-types dependency.
4
+ */
5
+ import type { ScheduledAction, SchedulerAdapter } from './types';
6
+
7
+ interface CfScheduledEvent {
8
+ cron: string;
9
+ scheduledTime: number;
10
+ waitUntil(promise: Promise<unknown>): void;
11
+ }
12
+
13
+ interface CfEventContext {
14
+ waitUntil(promise: Promise<unknown>): void;
15
+ }
16
+
17
+ export class CloudflareSchedulerAdapter implements SchedulerAdapter {
18
+ private readonly entries = new Map<string, ScheduledAction>();
19
+
20
+ constructor() {}
21
+
22
+ register(cron: string, action: ScheduledAction): void {
23
+ this.entries.set(cron, action);
24
+ }
25
+
26
+ async start(): Promise<void> {
27
+ // Cloudflare Workers use cron triggers defined in wrangler.toml.
28
+ // The `scheduled()` handler should call `handleScheduledEvent()`.
29
+ }
30
+
31
+ async stop(): Promise<void> {
32
+ this.entries.clear();
33
+ }
34
+
35
+ /**
36
+ * Handle a Cloudflare Workers Cron Trigger event.
37
+ * Call this from the Worker's `scheduled()` export.
38
+ */
39
+ handleScheduledEvent(event: CfScheduledEvent, ctx: CfEventContext): void {
40
+ const action = this.entries.get(event.cron);
41
+ if (action) {
42
+ ctx.waitUntil(action());
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Scheduler factory — selects adapter based on runtime.
3
+ */
4
+ import type { ScheduledAction, SchedulerAdapter } from './types';
5
+
6
+ let runtimeAdapter: SchedulerAdapter | undefined;
7
+
8
+ export function setSchedulerAdapter(adapter: SchedulerAdapter): void {
9
+ runtimeAdapter = adapter;
10
+ }
11
+
12
+ /** Reset the scheduler adapter singleton. For testing. */
13
+ export function resetSchedulerAdapter(): void {
14
+ runtimeAdapter = undefined;
15
+ }
16
+
17
+ export function getSchedulerAdapter(): SchedulerAdapter | undefined {
18
+ return runtimeAdapter;
19
+ }
20
+
21
+ /**
22
+ * Initialize the scheduler adapter and register cron entries.
23
+ *
24
+ * MUST be called before `adapter.start()`. If the adapter is already
25
+ * running, newly registered entries will NOT be started until the next
26
+ * `start()` call.
27
+ *
28
+ * Returns the configured adapter (defaults to noop if none set).
29
+ */
30
+ export function initScheduler(cronEntries?: Array<[string, ScheduledAction]>): SchedulerAdapter {
31
+ // Default: create a noop adapter. Apps inject their own via setSchedulerAdapter.
32
+ if (!runtimeAdapter) {
33
+ runtimeAdapter = createNoopScheduler();
34
+ }
35
+
36
+ if (cronEntries) {
37
+ for (const [cron, action] of cronEntries) {
38
+ runtimeAdapter.register(cron, action);
39
+ }
40
+ }
41
+
42
+ return runtimeAdapter;
43
+ }
44
+
45
+ function createNoopScheduler(): SchedulerAdapter {
46
+ return {
47
+ register(): void {
48
+ /* noop */
49
+ },
50
+ start(): Promise<void> {
51
+ return Promise.resolve();
52
+ },
53
+ stop(): Promise<void> {
54
+ return Promise.resolve();
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,5 @@
1
+ export { CloudflareSchedulerAdapter } from './cloudflare';
2
+ export { getSchedulerAdapter, initScheduler, resetSchedulerAdapter, setSchedulerAdapter } from './factory';
3
+ export { NodeSchedulerAdapter } from './node';
4
+ export { NoopSchedulerAdapter } from './noop';
5
+ export type { ScheduledAction, SchedulerAdapter } from './types';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Node.js scheduler adapter using a simple setInterval-based approach.
3
+ * No external cron library dependency — cron expressions are parsed minimally.
4
+ */
5
+ import type { ScheduledAction, SchedulerAdapter } from './types';
6
+
7
+ /** Simple helper to parse cron-like interval strings into milliseconds. */
8
+ function parseInterval(cron: string): number {
9
+ // Support simple patterns: "* * * * *" (every minute), "*/5 * * * *" (every 5 min)
10
+ // Also support direct ms strings like "60000"
11
+ const num = Number(cron);
12
+ if (!Number.isNaN(num)) return num;
13
+
14
+ const parts = cron.trim().split(/\s+/);
15
+ if (parts.length === 5 && parts[0] === '*') {
16
+ return 60_000; // every minute default
17
+ }
18
+
19
+ // */N pattern
20
+ const match = parts[0]?.match(/^\*\/(\d+)$/);
21
+ if (match) {
22
+ return Number(match[1]) * 60_000;
23
+ }
24
+
25
+ return 60_000; // fallback: every minute
26
+ }
27
+
28
+ interface ScheduledEntry {
29
+ cron: string;
30
+ action: ScheduledAction;
31
+ timer?: ReturnType<typeof setInterval>;
32
+ }
33
+
34
+ export class NodeSchedulerAdapter implements SchedulerAdapter {
35
+ private readonly entries: ScheduledEntry[] = [];
36
+ private running = false;
37
+
38
+ constructor() {}
39
+
40
+ register(cron: string, action: ScheduledAction): void {
41
+ this.entries.push({ cron, action });
42
+ if (this.running) {
43
+ const last = this.entries[this.entries.length - 1];
44
+ if (last) {
45
+ this.startEntry(last);
46
+ }
47
+ }
48
+ }
49
+
50
+ async start(): Promise<void> {
51
+ if (this.running) return;
52
+
53
+ this.running = true;
54
+ for (const entry of this.entries) {
55
+ this.startEntry(entry);
56
+ }
57
+ }
58
+
59
+ async stop(): Promise<void> {
60
+ this.running = false;
61
+ for (const entry of this.entries) {
62
+ if (entry.timer) {
63
+ clearInterval(entry.timer);
64
+ entry.timer = undefined;
65
+ }
66
+ }
67
+ }
68
+
69
+ private startEntry(entry: ScheduledEntry): void {
70
+ if (entry.timer) return;
71
+
72
+ const interval = parseInterval(entry.cron);
73
+ entry.timer = setInterval(this._onScheduledTick.bind(this, entry), interval);
74
+ }
75
+
76
+ private async _onScheduledTick(entry: ScheduledEntry): Promise<void> {
77
+ try {
78
+ await entry.action();
79
+ } catch {
80
+ // Swallow — scheduler errors should not crash the process
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * No-op scheduler adapter for testing and environments without scheduling.
3
+ */
4
+ import type { ScheduledAction, SchedulerAdapter } from './types';
5
+
6
+ export class NoopSchedulerAdapter implements SchedulerAdapter {
7
+ constructor() {}
8
+
9
+ register(_cron: string, _action: ScheduledAction): void {
10
+ // noop
11
+ }
12
+
13
+ async start(): Promise<void> {
14
+ // noop
15
+ }
16
+
17
+ async stop(): Promise<void> {
18
+ // noop
19
+ }
20
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Scheduler types and interface.
3
+ */
4
+
5
+ /** Signature for scheduled action handlers. */
6
+ export type ScheduledAction = () => Promise<void>;
7
+
8
+ /** Abstract scheduler interface — implementations for Node (node-cron) and Cloudflare. */
9
+ export interface SchedulerAdapter {
10
+ register(cron: string, action: ScheduledAction): void;
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Telemetry configuration interface.
3
+ */
4
+
5
+ export interface TelemetryConfig {
6
+ /** Master switch — when false, all tracing degrades to no-ops. */
7
+ enabled: boolean;
8
+ /** Logical service name emitted on every span. */
9
+ serviceName: string;
10
+ /** Deployment environment (development, staging, production). */
11
+ environment: string;
12
+ /** OTLP exporter endpoint (e.g. `http://localhost:4318/v1/traces`). */
13
+ exporterEndpoint?: string | undefined;
14
+ /** Export protocol — only `http` is supported in v1. */
15
+ exporterProtocol: 'http';
16
+ /**
17
+ * Debug-only DB statement capture.
18
+ *
19
+ * When true, DB spans may include sanitized SQL text in a `db.statement`
20
+ * attribute. SQL text is redacted — parameter values, literals, and
21
+ * identifiers are stripped before capture.
22
+ *
23
+ * Default: `false`. Controlled by `OTEL_DB_STATEMENT_DEBUG` env var.
24
+ */
25
+ dbStatementDebug: boolean;
26
+ }
27
+
28
+ /**
29
+ * Partial telemetry config from the centralized config system.
30
+ */
31
+ export interface TelemetryConfigPartial {
32
+ enabled?: boolean | undefined;
33
+ serviceName?: string | undefined;
34
+ environment?: string | undefined;
35
+ exporterEndpoint?: string | undefined;
36
+ dbStatementDebug?: boolean | undefined;
37
+ /** Deployment environment fallback (from app.env). */
38
+ appEnv?: string | undefined;
39
+ }
40
+
41
+ const DEFAULTS = {
42
+ enabled: true as const,
43
+ serviceName: 'ts-libs' as const,
44
+ environment: 'development' as const,
45
+ exporterProtocol: 'http' as const,
46
+ };
47
+
48
+ /**
49
+ * Resolve the full telemetry config by merging a partial override with defaults.
50
+ */
51
+ export function getTelemetryConfig(configPartial: TelemetryConfigPartial = {}): TelemetryConfig {
52
+ const enabled = configPartial.enabled ?? DEFAULTS.enabled;
53
+ const serviceName = configPartial.serviceName ?? DEFAULTS.serviceName;
54
+
55
+ return {
56
+ enabled,
57
+ serviceName,
58
+ environment: configPartial.environment ?? configPartial.appEnv ?? DEFAULTS.environment,
59
+ exporterEndpoint: configPartial.exporterEndpoint,
60
+ exporterProtocol: DEFAULTS.exporterProtocol,
61
+ dbStatementDebug: configPartial.dbStatementDebug ?? false,
62
+ };
63
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * SQL sanitization for debug-mode DB statement capture.
3
+ *
4
+ * Strips parameter values, string literals, numeric literals, and
5
+ * identifier-specific data from SQL text before it is attached to spans.
6
+ */
7
+
8
+ /**
9
+ * Redact string literals and numeric literals from SQL text.
10
+ */
11
+ export function sanitizeSql(sql: string): string {
12
+ let result = '';
13
+ let i = 0;
14
+ const len = sql.length;
15
+
16
+ while (i < len) {
17
+ const code = sql.charCodeAt(i);
18
+
19
+ // Quote characters: ' (39) or " (34)
20
+ if (code === 39 || code === 34) {
21
+ const quote = code;
22
+ i++;
23
+ while (i < len) {
24
+ if (sql.charCodeAt(i) === quote) {
25
+ if (sql.charCodeAt(i + 1) === quote) {
26
+ i += 2;
27
+ continue;
28
+ }
29
+ i++;
30
+ break;
31
+ }
32
+ i++;
33
+ }
34
+ result += '?';
35
+ continue;
36
+ }
37
+
38
+ // Numeric literal
39
+ if (code >= 48 && code <= 57) {
40
+ const prev = result.length > 0 ? result.charCodeAt(result.length - 1) : 0;
41
+ const isIdentifierContinuation =
42
+ (prev >= 65 && prev <= 90) || (prev >= 97 && prev <= 122) || prev === 95 || (prev >= 48 && prev <= 57);
43
+
44
+ if (!isIdentifierContinuation) {
45
+ while (i < len) {
46
+ const c = sql.charCodeAt(i);
47
+ if (c < 48 || c > 57) break;
48
+ i++;
49
+ }
50
+ if (i < len && sql.charCodeAt(i) === 46 && i + 1 < len) {
51
+ const next = sql.charCodeAt(i + 1);
52
+ if (next >= 48 && next <= 57) {
53
+ i++;
54
+ while (i < len) {
55
+ const c = sql.charCodeAt(i);
56
+ if (c < 48 || c > 57) break;
57
+ i++;
58
+ }
59
+ }
60
+ }
61
+ result += '?';
62
+ continue;
63
+ }
64
+ }
65
+
66
+ result += sql[i] ?? '';
67
+ i++;
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ /**
74
+ * Extract the SQL operation keyword (SELECT, INSERT, UPDATE, DELETE, etc.)
75
+ */
76
+ export function extractSqlOperation(sql: string): string | undefined {
77
+ const match = sql.trim().match(/^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|PRAGMA)\b/i);
78
+ return match?.[1]?.toUpperCase();
79
+ }
@@ -0,0 +1,29 @@
1
+ export { getTelemetryConfig, type TelemetryConfig, type TelemetryConfigPartial } from './config';
2
+ export { extractSqlOperation, sanitizeSql } from './db-sanitize';
3
+ export {
4
+ type Counter,
5
+ getDbOperationDuration,
6
+ getDbOperationErrors,
7
+ getDbOperationTotal,
8
+ getEventbusEmitsTotal,
9
+ getEventbusErrorsTotal,
10
+ getHttpClientRequestDuration,
11
+ getHttpClientRequestErrors,
12
+ getHttpClientRequestTotal,
13
+ getHttpServerRequestDuration,
14
+ getHttpServerRequestErrors,
15
+ getHttpServerRequestTotal,
16
+ getQueueJobCompletedTotal,
17
+ getQueueJobEnqueuedTotal,
18
+ getQueueJobFailedTotal,
19
+ getQueueJobProcessingDuration,
20
+ getSchedulerJobDuration,
21
+ getSchedulerJobExecutedTotal,
22
+ getSchedulerJobFailedTotal,
23
+ type Histogram,
24
+ initMetrics,
25
+ shutdownMetrics,
26
+ } from './metrics';
27
+ export { getTracer, initTelemetry, isTelemetryEnabled, shutdownTelemetry } from './sdk';
28
+ export type { Span, SpanOptions, Tracer } from './tracing';
29
+ export { addSpanAttributes, addSpanEvent, getActiveSpan, traceAsync, traceSync, withSpan } from './tracing';