@pattern-stack/codegen 0.4.4 → 0.4.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 (34) hide show
  1. package/dist/runtime/subsystems/index.d.ts +7 -0
  2. package/dist/runtime/subsystems/index.js +923 -208
  3. package/dist/runtime/subsystems/index.js.map +1 -1
  4. package/dist/runtime/subsystems/observability/index.d.ts +10 -0
  5. package/dist/runtime/subsystems/observability/index.js +913 -0
  6. package/dist/runtime/subsystems/observability/index.js.map +1 -0
  7. package/dist/runtime/subsystems/observability/observability.drizzle-backend.d.ts +15 -0
  8. package/dist/runtime/subsystems/observability/observability.drizzle-backend.js +465 -0
  9. package/dist/runtime/subsystems/observability/observability.drizzle-backend.js.map +1 -0
  10. package/dist/runtime/subsystems/observability/observability.memory-backend.d.ts +28 -0
  11. package/dist/runtime/subsystems/observability/observability.memory-backend.js +75 -0
  12. package/dist/runtime/subsystems/observability/observability.memory-backend.js.map +1 -0
  13. package/dist/runtime/subsystems/observability/observability.module.d.ts +56 -0
  14. package/dist/runtime/subsystems/observability/observability.module.js +905 -0
  15. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -0
  16. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +155 -0
  17. package/dist/runtime/subsystems/observability/observability.protocol.js +1 -0
  18. package/dist/runtime/subsystems/observability/observability.protocol.js.map +1 -0
  19. package/dist/runtime/subsystems/observability/observability.tokens.d.ts +19 -0
  20. package/dist/runtime/subsystems/observability/observability.tokens.js +8 -0
  21. package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -0
  22. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +87 -0
  23. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js +443 -0
  24. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -0
  25. package/dist/runtime/subsystems/sync/sync-audit.schema.d.ts +4 -4
  26. package/package.json +6 -1
  27. package/runtime/subsystems/index.ts +23 -0
  28. package/runtime/subsystems/observability/index.ts +35 -0
  29. package/runtime/subsystems/observability/observability.drizzle-backend.ts +223 -0
  30. package/runtime/subsystems/observability/observability.memory-backend.ts +111 -0
  31. package/runtime/subsystems/observability/observability.module.ts +115 -0
  32. package/runtime/subsystems/observability/observability.protocol.ts +167 -0
  33. package/runtime/subsystems/observability/observability.tokens.ts +18 -0
  34. package/runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts +242 -0
@@ -0,0 +1,905 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc2) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var __decorateClass = (decorators, target, key, kind) => {
28
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
29
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
30
+ if (decorator = decorators[i])
31
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
32
+ if (kind && result) __defProp(target, key, result);
33
+ return result;
34
+ };
35
+ var __decorateParam = (index5, decorator) => (target, key) => decorator(target, key, index5);
36
+
37
+ // runtime/constants/tokens.ts
38
+ var DRIZZLE;
39
+ var init_tokens = __esm({
40
+ "runtime/constants/tokens.ts"() {
41
+ "use strict";
42
+ DRIZZLE = "DRIZZLE";
43
+ }
44
+ });
45
+
46
+ // runtime/subsystems/jobs/job-orchestration.schema.ts
47
+ import {
48
+ pgEnum,
49
+ pgTable,
50
+ uuid,
51
+ text,
52
+ jsonb,
53
+ integer,
54
+ timestamp,
55
+ index,
56
+ uniqueIndex
57
+ } from "drizzle-orm/pg-core";
58
+ import { sql } from "drizzle-orm";
59
+ var jobRunStatusEnum, jobStepKindEnum, jobStepStatusEnum, collisionModeEnum, replayFromEnum, parentClosePolicyEnum, waitKindEnum, triggerSourceEnum, jobs, jobRuns, jobSteps;
60
+ var init_job_orchestration_schema = __esm({
61
+ "runtime/subsystems/jobs/job-orchestration.schema.ts"() {
62
+ "use strict";
63
+ jobRunStatusEnum = pgEnum("job_run_status", [
64
+ "pending",
65
+ "running",
66
+ "waiting",
67
+ "completed",
68
+ "failed",
69
+ "timed_out",
70
+ "canceled"
71
+ ]);
72
+ jobStepKindEnum = pgEnum("job_step_kind", ["task"]);
73
+ jobStepStatusEnum = pgEnum("job_step_status", [
74
+ "pending",
75
+ "running",
76
+ "completed",
77
+ "failed",
78
+ "skipped"
79
+ ]);
80
+ collisionModeEnum = pgEnum("job_collision_mode", [
81
+ "queue",
82
+ "reject",
83
+ "replace"
84
+ ]);
85
+ replayFromEnum = pgEnum("job_replay_from", [
86
+ "scratch",
87
+ "last_step",
88
+ "last_checkpoint"
89
+ ]);
90
+ parentClosePolicyEnum = pgEnum("job_parent_close_policy", [
91
+ "terminate",
92
+ "cancel",
93
+ "abandon"
94
+ ]);
95
+ waitKindEnum = pgEnum("job_wait_kind", ["signal"]);
96
+ triggerSourceEnum = pgEnum("job_trigger_source", [
97
+ "manual",
98
+ "schedule",
99
+ "event",
100
+ "parent"
101
+ ]);
102
+ jobs = pgTable("job", {
103
+ type: text("type").primaryKey(),
104
+ version: integer("version").notNull().default(1),
105
+ pool: text("pool").notNull(),
106
+ scopeEntityType: text("scope_entity_type"),
107
+ retryPolicy: jsonb("retry_policy").notNull().$type(),
108
+ timeoutMs: integer("timeout_ms"),
109
+ concurrencyKeyTemplate: text("concurrency_key_template"),
110
+ collisionMode: collisionModeEnum("collision_mode").notNull().default("queue"),
111
+ dedupeKeyTemplate: text("dedupe_key_template"),
112
+ dedupeWindowMs: integer("dedupe_window_ms"),
113
+ priorityDefault: integer("priority_default").notNull().default(0),
114
+ replayFrom: replayFromEnum("replay_from").notNull().default("last_checkpoint"),
115
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
116
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
117
+ });
118
+ jobRuns = pgTable(
119
+ "job_run",
120
+ {
121
+ id: uuid("id").primaryKey().defaultRandom(),
122
+ jobType: text("job_type").notNull().references(() => jobs.type),
123
+ jobVersion: integer("job_version").notNull(),
124
+ parentRunId: uuid("parent_run_id").references(() => jobRuns.id),
125
+ /**
126
+ * Service generates `id` client-side via randomUUID() and sets
127
+ * root_run_id = id for root runs (single INSERT, no self-FK race).
128
+ */
129
+ rootRunId: uuid("root_run_id").notNull(),
130
+ parentClosePolicy: parentClosePolicyEnum("parent_close_policy").notNull().default("terminate"),
131
+ scopeEntityType: text("scope_entity_type"),
132
+ scopeEntityId: text("scope_entity_id"),
133
+ tenantId: text("tenant_id"),
134
+ tags: jsonb("tags").notNull().default({}).$type(),
135
+ pool: text("pool").notNull(),
136
+ priority: integer("priority").notNull().default(0),
137
+ concurrencyKey: text("concurrency_key"),
138
+ dedupeKey: text("dedupe_key"),
139
+ status: jobRunStatusEnum("status").notNull().default("pending"),
140
+ input: jsonb("input").notNull().$type(),
141
+ output: jsonb("output").$type(),
142
+ error: jsonb("error").$type(),
143
+ triggerSource: triggerSourceEnum("trigger_source").notNull(),
144
+ triggerRef: text("trigger_ref"),
145
+ runAt: timestamp("run_at", { withTimezone: true }).notNull().defaultNow(),
146
+ startedAt: timestamp("started_at", { withTimezone: true }),
147
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
148
+ claimedAt: timestamp("claimed_at", { withTimezone: true }),
149
+ attempts: integer("attempts").notNull().default(0),
150
+ // Phase 3 placeholder — see ADR-025
151
+ waitKind: waitKindEnum("wait_kind"),
152
+ // Phase 3 placeholder — see ADR-025
153
+ resumeToken: text("resume_token"),
154
+ // Phase 3 placeholder — see ADR-025
155
+ waitDeadline: timestamp("wait_deadline", { withTimezone: true }),
156
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
157
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
158
+ },
159
+ (t) => ({
160
+ /** Claim query: ORDER BY priority DESC, run_at ASC. */
161
+ idxJobRunClaim: index("idx_job_run_claim").on(t.status, t.pool, t.runAt),
162
+ /** Tree traversal / cascade cancel. */
163
+ idxJobRunRoot: index("idx_job_run_root").on(t.rootRunId),
164
+ /** listForScope query. */
165
+ idxJobRunScope: index("idx_job_run_scope").on(t.scopeEntityType, t.scopeEntityId),
166
+ /** Idempotency collapse — partial index. */
167
+ idxJobRunDedupe: index("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(sql`${t.dedupeKey} IS NOT NULL`),
168
+ /** Collision check — partial index. */
169
+ idxJobRunConcurrency: index("idx_job_run_concurrency").on(t.concurrencyKey).where(
170
+ sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`
171
+ )
172
+ })
173
+ );
174
+ jobSteps = pgTable(
175
+ "job_step",
176
+ {
177
+ id: uuid("id").primaryKey().defaultRandom(),
178
+ jobRunId: uuid("job_run_id").notNull().references(() => jobRuns.id),
179
+ stepId: text("step_id").notNull(),
180
+ kind: jobStepKindEnum("kind").notNull().default("task"),
181
+ /**
182
+ * Monotonic within run. integer (max ~2B per run) is sufficient —
183
+ * downgraded from ADR-022's bigint; revisit only if a single run
184
+ * ever exceeds 2 billion steps.
185
+ */
186
+ seq: integer("seq").notNull(),
187
+ status: jobStepStatusEnum("status").notNull().default("pending"),
188
+ input: jsonb("input").$type(),
189
+ /** Memoised on success for replay. */
190
+ output: jsonb("output").$type(),
191
+ error: jsonb("error").$type(),
192
+ attempts: integer("attempts").notNull().default(0),
193
+ startedAt: timestamp("started_at", { withTimezone: true }),
194
+ finishedAt: timestamp("finished_at", { withTimezone: true })
195
+ },
196
+ (t) => ({
197
+ /** No duplicate step IDs per run. */
198
+ idxJobStepRunStep: uniqueIndex("idx_job_step_run_step").on(t.jobRunId, t.stepId),
199
+ /** Ordered timeline reads. */
200
+ idxJobStepTimeline: index("idx_job_step_timeline").on(t.jobRunId, t.seq)
201
+ })
202
+ );
203
+ }
204
+ });
205
+
206
+ // runtime/subsystems/events/domain-events.schema.ts
207
+ import {
208
+ index as index3,
209
+ jsonb as jsonb3,
210
+ pgTable as pgTable3,
211
+ text as text3,
212
+ timestamp as timestamp3,
213
+ uuid as uuid3
214
+ } from "drizzle-orm/pg-core";
215
+ var domainEvents;
216
+ var init_domain_events_schema = __esm({
217
+ "runtime/subsystems/events/domain-events.schema.ts"() {
218
+ "use strict";
219
+ domainEvents = pgTable3(
220
+ "domain_events",
221
+ {
222
+ id: uuid3("id").primaryKey(),
223
+ type: text3("type").notNull(),
224
+ aggregateId: text3("aggregate_id").notNull(),
225
+ aggregateType: text3("aggregate_type").notNull(),
226
+ payload: jsonb3("payload").notNull().$type(),
227
+ occurredAt: timestamp3("occurred_at", { withTimezone: true }).notNull(),
228
+ processedAt: timestamp3("processed_at", { withTimezone: true }),
229
+ /** Lifecycle status: pending | processed | failed */
230
+ status: text3("status").notNull().default("pending"),
231
+ /** Error message from the last failed dispatch attempt. */
232
+ error: text3("error"),
233
+ metadata: jsonb3("metadata").$type(),
234
+ /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. */
235
+ pool: text3("pool"),
236
+ /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. */
237
+ direction: text3("direction"),
238
+ // conditional: emitted only when events.multi_tenant: true
239
+ tenantId: text3("tenant_id")
240
+ },
241
+ (t) => ({
242
+ /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */
243
+ idxDomainEventsStatusOccurredAt: index3("idx_domain_events_status_occurred_at").on(
244
+ t.status,
245
+ t.occurredAt
246
+ ),
247
+ /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */
248
+ idxDomainEventsAggregate: index3("idx_domain_events_aggregate").on(
249
+ t.aggregateId,
250
+ t.aggregateType
251
+ ),
252
+ /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */
253
+ idxDomainEventsPoolStatusOccurredAt: index3(
254
+ "idx_domain_events_pool_status_occurred_at"
255
+ ).on(t.pool, t.status, t.occurredAt)
256
+ })
257
+ );
258
+ }
259
+ });
260
+
261
+ // runtime/subsystems/bridge/bridge-delivery.schema.ts
262
+ import {
263
+ index as index4,
264
+ jsonb as jsonb4,
265
+ pgEnum as pgEnum3,
266
+ pgTable as pgTable4,
267
+ text as text4,
268
+ timestamp as timestamp4,
269
+ unique,
270
+ uuid as uuid4
271
+ } from "drizzle-orm/pg-core";
272
+ import { sql as sql3 } from "drizzle-orm";
273
+ var bridgeDeliveryStatusEnum, bridgeDelivery;
274
+ var init_bridge_delivery_schema = __esm({
275
+ "runtime/subsystems/bridge/bridge-delivery.schema.ts"() {
276
+ "use strict";
277
+ init_domain_events_schema();
278
+ init_job_orchestration_schema();
279
+ bridgeDeliveryStatusEnum = pgEnum3("bridge_delivery_status", [
280
+ "pending",
281
+ "delivered",
282
+ "skipped",
283
+ "failed"
284
+ ]);
285
+ bridgeDelivery = pgTable4(
286
+ "bridge_delivery",
287
+ {
288
+ id: uuid4("id").primaryKey().defaultRandom(),
289
+ /** FK to the source event in the outbox. */
290
+ eventId: uuid4("event_id").notNull().references(() => domainEvents.id),
291
+ /**
292
+ * Stable codegen-emitted identifier for the (job, trigger) pair, of the
293
+ * form `<job_type>#<triggerIndex>` (BRIDGE-6). Forms the second half of
294
+ * the UNIQUE idempotency key.
295
+ */
296
+ triggerId: text4("trigger_id").notNull(),
297
+ /**
298
+ * Wrapper `job_run.id` (the framework `@framework/bridge_delivery` run
299
+ * that drove this delivery). Nullable: the facade-eager path
300
+ * (`publishAndStart` Case B) pre-writes a delivered row with no wrapper.
301
+ */
302
+ wrapperRunId: uuid4("wrapper_run_id").references(() => jobRuns.id),
303
+ /**
304
+ * Spawned user `job_run.id`. Null until status is `delivered`; remains
305
+ * null for `skipped` and `failed` deliveries.
306
+ */
307
+ userRunId: uuid4("user_run_id").references(() => jobRuns.id),
308
+ status: bridgeDeliveryStatusEnum("status").notNull().default("pending"),
309
+ /** Populated when status=`skipped` (e.g. `'when_returned_false'`, `'trigger_unregistered'`). */
310
+ skipReason: text4("skip_reason"),
311
+ /** Populated when status=`failed`. Mirrors `job_run.error` shape. */
312
+ error: jsonb4("error").$type(),
313
+ /**
314
+ * Emitted unconditionally and nullable (JOB-8 / SYNC-6 precedent).
315
+ * Enforcement gated on `BRIDGE_MULTI_TENANT` at the service layer
316
+ * (BRIDGE-8); no DB constraint.
317
+ */
318
+ tenantId: text4("tenant_id"),
319
+ attemptedAt: timestamp4("attempted_at", { withTimezone: true }).notNull().defaultNow(),
320
+ deliveredAt: timestamp4("delivered_at", { withTimezone: true })
321
+ },
322
+ (t) => ({
323
+ /**
324
+ * Idempotency ledger. Outbox replays and facade-vs-drain collisions both
325
+ * dedup through this constraint.
326
+ */
327
+ uqBridgeDeliveryEventTrigger: unique("uq_bridge_delivery_event_trigger").on(
328
+ t.eventId,
329
+ t.triggerId
330
+ ),
331
+ /** Lookup all deliveries for an event (fanout report, debugging). */
332
+ idxBridgeDeliveryEvent: index4("idx_bridge_delivery_event").on(t.eventId),
333
+ /**
334
+ * Ops dashboard filter — only the actionable states. Partial index keeps
335
+ * it small at scale (the bulk of rows will be `delivered`).
336
+ */
337
+ idxBridgeDeliveryStatus: index4("idx_bridge_delivery_status").on(t.status).where(sql3`${t.status} IN ('pending','failed')`),
338
+ /**
339
+ * Reverse lookup from a spawned user run back to its delivery row.
340
+ * Partial — most rows in the bridge ledger but only successful
341
+ * deliveries have a `user_run_id`.
342
+ */
343
+ idxBridgeDeliveryUserRun: index4("idx_bridge_delivery_user_run").on(t.userRunId).where(sql3`${t.userRunId} IS NOT NULL`)
344
+ })
345
+ );
346
+ }
347
+ });
348
+
349
+ // runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts
350
+ var bridge_metrics_reporter_exports = {};
351
+ __export(bridge_metrics_reporter_exports, {
352
+ BridgeMetricsReporter: () => BridgeMetricsReporter
353
+ });
354
+ import {
355
+ Inject as Inject2,
356
+ Injectable as Injectable3,
357
+ Logger,
358
+ Optional
359
+ } from "@nestjs/common";
360
+ import { and, eq as eq2, gt, sql as sql4 } from "drizzle-orm";
361
+ var INTERVAL_NAME, DEFAULT_INTERVAL_MS, MIN_INTERVAL_MS, BridgeMetricsReporter;
362
+ var init_bridge_metrics_reporter = __esm({
363
+ "runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts"() {
364
+ "use strict";
365
+ init_tokens();
366
+ init_bridge_delivery_schema();
367
+ init_domain_events_schema();
368
+ INTERVAL_NAME = "bridge-metrics-tick";
369
+ DEFAULT_INTERVAL_MS = 6e4;
370
+ MIN_INTERVAL_MS = 1e3;
371
+ BridgeMetricsReporter = class {
372
+ constructor(db, scheduler = null) {
373
+ this.db = db;
374
+ this.scheduler = scheduler;
375
+ this.intervalMs = this.resolveIntervalMs();
376
+ this.lastTickAt = /* @__PURE__ */ new Date();
377
+ }
378
+ db;
379
+ scheduler;
380
+ logger = new Logger(BridgeMetricsReporter.name);
381
+ intervalMs;
382
+ lastTickAt;
383
+ /**
384
+ * Timer handle retained as a field when `SchedulerRegistry` isn't
385
+ * available (optional dep). `SchedulerRegistry` is the Nest-idiomatic
386
+ * home for interval cleanup, but global-module wiring across consumer
387
+ * topologies doesn't always make it injectable here — the fallback
388
+ * keeps the reporter self-sufficient.
389
+ */
390
+ ownedTimer = null;
391
+ onModuleInit() {
392
+ this.logger.log(
393
+ `BridgeMetricsReporter starting (intervalMs=${this.intervalMs}).`
394
+ );
395
+ const timer = setInterval(() => {
396
+ void this.tick();
397
+ }, this.intervalMs);
398
+ timer.unref?.();
399
+ if (this.scheduler) {
400
+ this.scheduler.addInterval(INTERVAL_NAME, timer);
401
+ } else {
402
+ this.ownedTimer = timer;
403
+ }
404
+ }
405
+ onModuleDestroy() {
406
+ if (this.scheduler && this.scheduler.getIntervals().includes(INTERVAL_NAME)) {
407
+ this.scheduler.deleteInterval(INTERVAL_NAME);
408
+ }
409
+ if (this.ownedTimer !== null) {
410
+ clearInterval(this.ownedTimer);
411
+ this.ownedTimer = null;
412
+ }
413
+ }
414
+ /**
415
+ * Run one sampling tick. Public so tests can drive it deterministically
416
+ * without waiting on the timer.
417
+ */
418
+ async tick() {
419
+ const windowStart = this.lastTickAt;
420
+ const windowEnd = /* @__PURE__ */ new Date();
421
+ this.lastTickAt = windowEnd;
422
+ let rows = [];
423
+ try {
424
+ rows = await this.sample(windowStart, windowEnd);
425
+ } catch (err) {
426
+ this.logger.error(
427
+ `bridge metrics sample failed: ${err.message}`
428
+ );
429
+ return { windowStart, windowEnd, rows: [] };
430
+ }
431
+ this.emit({ windowStart, windowEnd, rows });
432
+ return { windowStart, windowEnd, rows };
433
+ }
434
+ async sample(windowStart, windowEnd) {
435
+ const lastTransition = sql4`COALESCE(${bridgeDelivery.deliveredAt}, ${bridgeDelivery.attemptedAt})`;
436
+ const result = await this.db.select({
437
+ status: bridgeDelivery.status,
438
+ eventType: domainEvents.type,
439
+ skipReason: bridgeDelivery.skipReason,
440
+ count: sql4`COUNT(*)::int`
441
+ }).from(bridgeDelivery).innerJoin(domainEvents, eq2(bridgeDelivery.eventId, domainEvents.id)).where(
442
+ and(
443
+ gt(lastTransition, windowStart),
444
+ sql4`${lastTransition} <= ${windowEnd}`
445
+ )
446
+ ).groupBy(
447
+ bridgeDelivery.status,
448
+ domainEvents.type,
449
+ bridgeDelivery.skipReason
450
+ );
451
+ return result.map((r) => ({
452
+ status: r.status,
453
+ eventType: r.eventType,
454
+ skipReason: r.skipReason,
455
+ count: r.count
456
+ }));
457
+ }
458
+ emit(tick) {
459
+ if (tick.rows.length === 0) {
460
+ this.logger.log(
461
+ `bridge_metrics tick=empty window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}]`
462
+ );
463
+ return;
464
+ }
465
+ const totals = tick.rows.reduce(
466
+ (acc, r) => {
467
+ acc[r.status] = (acc[r.status] ?? 0) + r.count;
468
+ return acc;
469
+ },
470
+ {}
471
+ );
472
+ const detail = tick.rows.map(
473
+ (r) => `${r.eventType}|${r.status}${r.skipReason ? `:${r.skipReason}` : ""}=${r.count}`
474
+ ).join(" ");
475
+ this.logger.log(
476
+ `bridge_metrics tick window=[${tick.windowStart.toISOString()}..${tick.windowEnd.toISOString()}] totals=${JSON.stringify(totals)} detail=[${detail}]`
477
+ );
478
+ }
479
+ resolveIntervalMs() {
480
+ const raw = process.env["BRIDGE_METRICS_INTERVAL_MS"];
481
+ if (!raw) return DEFAULT_INTERVAL_MS;
482
+ const parsed = Number.parseInt(raw, 10);
483
+ if (!Number.isFinite(parsed) || parsed < MIN_INTERVAL_MS) {
484
+ new Logger(BridgeMetricsReporter.name).warn(
485
+ `Ignoring BRIDGE_METRICS_INTERVAL_MS='${raw}' (invalid or < ${MIN_INTERVAL_MS}ms); using default ${DEFAULT_INTERVAL_MS}ms.`
486
+ );
487
+ return DEFAULT_INTERVAL_MS;
488
+ }
489
+ return parsed;
490
+ }
491
+ };
492
+ BridgeMetricsReporter = __decorateClass([
493
+ Injectable3(),
494
+ __decorateParam(0, Inject2(DRIZZLE)),
495
+ __decorateParam(1, Optional())
496
+ ], BridgeMetricsReporter);
497
+ }
498
+ });
499
+
500
+ // runtime/subsystems/observability/observability.module.ts
501
+ import { Module } from "@nestjs/common";
502
+
503
+ // runtime/subsystems/observability/observability.drizzle-backend.ts
504
+ init_tokens();
505
+ init_job_orchestration_schema();
506
+ import { Inject, Injectable } from "@nestjs/common";
507
+ import { desc, eq, sql as sql2 } from "drizzle-orm";
508
+
509
+ // runtime/subsystems/sync/sync-audit.schema.ts
510
+ import {
511
+ pgEnum as pgEnum2,
512
+ pgTable as pgTable2,
513
+ uuid as uuid2,
514
+ text as text2,
515
+ jsonb as jsonb2,
516
+ integer as integer2,
517
+ boolean,
518
+ timestamp as timestamp2,
519
+ index as index2,
520
+ uniqueIndex as uniqueIndex2
521
+ } from "drizzle-orm/pg-core";
522
+ var syncRunDirectionEnum = pgEnum2("sync_run_direction", [
523
+ "inbound",
524
+ "outbound"
525
+ ]);
526
+ var syncRunActionEnum = pgEnum2("sync_run_action", [
527
+ "poll",
528
+ "cdc",
529
+ "webhook",
530
+ "manual",
531
+ "writeback"
532
+ ]);
533
+ var syncRunStatusEnum = pgEnum2("sync_run_status", [
534
+ "running",
535
+ "success",
536
+ "no_changes",
537
+ "failed"
538
+ ]);
539
+ var syncRunItemOperationEnum = pgEnum2("sync_run_item_operation", [
540
+ "created",
541
+ "updated",
542
+ "deleted",
543
+ "noop"
544
+ ]);
545
+ var syncRunItemStatusEnum = pgEnum2("sync_run_item_status", [
546
+ "success",
547
+ "failed",
548
+ "skipped"
549
+ ]);
550
+ var syncSubscriptions = pgTable2(
551
+ "sync_subscriptions",
552
+ {
553
+ id: uuid2("id").primaryKey().defaultRandom(),
554
+ integrationId: text2("integration_id").notNull(),
555
+ adapter: text2("adapter").notNull(),
556
+ domain: text2("domain").notNull(),
557
+ externalRef: text2("external_ref"),
558
+ enabled: boolean("enabled").notNull().default(true),
559
+ /**
560
+ * Per-subscription configuration bag. Strategies type it internally;
561
+ * e.g. polling strategies stash `{ batchSize, highWatermark }` here.
562
+ */
563
+ config: jsonb2("config").notNull().default({}).$type(),
564
+ /**
565
+ * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first
566
+ * successful run advances it.
567
+ */
568
+ cursor: jsonb2("cursor").$type(),
569
+ lastSyncAt: timestamp2("last_sync_at", { withTimezone: true }),
570
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
571
+ tenantId: text2("tenant_id"),
572
+ createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
573
+ updatedAt: timestamp2("updated_at", { withTimezone: true }).notNull().defaultNow()
574
+ },
575
+ (t) => ({
576
+ /**
577
+ * Composite uniqueness per the epic shape. `external_ref` is nullable;
578
+ * Postgres treats NULLs as distinct in a UNIQUE constraint, which means
579
+ * two rows with the same `(integration_id, adapter, domain)` and NULL
580
+ * external_ref are allowed. That's intentional — a subscription with
581
+ * NULL external_ref covers the full domain, and duplicates there would
582
+ * be a consumer-layer modeling issue, not a schema concern.
583
+ */
584
+ uqSyncSubscriptionTuple: uniqueIndex2("uq_sync_subscriptions_tuple").on(
585
+ t.integrationId,
586
+ t.adapter,
587
+ t.domain,
588
+ t.externalRef
589
+ ),
590
+ /** Scheduling query: list enabled subscriptions ordered by staleness. */
591
+ idxSyncSubscriptionsEnabledLastSync: index2(
592
+ "idx_sync_subscriptions_enabled_last_sync"
593
+ ).on(t.enabled, t.lastSyncAt)
594
+ })
595
+ );
596
+ var syncRuns = pgTable2(
597
+ "sync_runs",
598
+ {
599
+ id: uuid2("id").primaryKey().defaultRandom(),
600
+ subscriptionId: uuid2("subscription_id").notNull().references(() => syncSubscriptions.id, { onDelete: "cascade" }),
601
+ direction: syncRunDirectionEnum("direction").notNull(),
602
+ action: syncRunActionEnum("action").notNull(),
603
+ status: syncRunStatusEnum("status").notNull().default("running"),
604
+ recordsFound: integer2("records_found").notNull().default(0),
605
+ recordsProcessed: integer2("records_processed").notNull().default(0),
606
+ cursorBefore: jsonb2("cursor_before").$type(),
607
+ cursorAfter: jsonb2("cursor_after").$type(),
608
+ durationMs: integer2("duration_ms"),
609
+ error: text2("error"),
610
+ startedAt: timestamp2("started_at", { withTimezone: true }).notNull().defaultNow(),
611
+ completedAt: timestamp2("completed_at", { withTimezone: true }),
612
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
613
+ tenantId: text2("tenant_id")
614
+ },
615
+ (t) => ({
616
+ /** Timeline read: "most recent runs for this subscription". */
617
+ idxSyncRunsSubscriptionStartedAt: index2(
618
+ "idx_sync_runs_subscription_started_at"
619
+ ).on(t.subscriptionId, t.startedAt),
620
+ /** Stale-run sweeper: "runs that started > N minutes ago and are still running". */
621
+ idxSyncRunsStatusStartedAt: index2("idx_sync_runs_status_started_at").on(
622
+ t.status,
623
+ t.startedAt
624
+ )
625
+ })
626
+ );
627
+ var syncRunItems = pgTable2(
628
+ "sync_run_items",
629
+ {
630
+ id: uuid2("id").primaryKey().defaultRandom(),
631
+ syncRunId: uuid2("sync_run_id").notNull().references(() => syncRuns.id, { onDelete: "cascade" }),
632
+ entityType: text2("entity_type").notNull(),
633
+ externalId: text2("external_id").notNull(),
634
+ localId: text2("local_id"),
635
+ operation: syncRunItemOperationEnum("operation").notNull(),
636
+ status: syncRunItemStatusEnum("status").notNull(),
637
+ /**
638
+ * Structured per-field diff — ADR-0003 shape enforced by
639
+ * `FieldDiffSchema.parse` at the recorder service layer.
640
+ *
641
+ * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.
642
+ * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`
643
+ * for created items; `{ [field]: { from: <value>, to: null } }` for
644
+ * deleted items.
645
+ */
646
+ changedFields: jsonb2("changed_fields").notNull().default({}).$type(),
647
+ title: text2("title"),
648
+ error: text2("error"),
649
+ createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
650
+ /** Runtime-enforced when `SYNC_MULTI_TENANT` is true; see SYNC-6. */
651
+ tenantId: text2("tenant_id")
652
+ },
653
+ (t) => ({
654
+ /** Ordered timeline within a run. */
655
+ idxSyncRunItemsRunCreatedAt: index2("idx_sync_run_items_run_created_at").on(
656
+ t.syncRunId,
657
+ t.createdAt
658
+ ),
659
+ /** Per-record history: "every sync that touched opportunity/$extId". */
660
+ idxSyncRunItemsEntityExternal: index2(
661
+ "idx_sync_run_items_entity_external"
662
+ ).on(t.entityType, t.externalId)
663
+ })
664
+ );
665
+
666
+ // runtime/subsystems/observability/observability.drizzle-backend.ts
667
+ var DrizzleObservabilityService = class {
668
+ constructor(db) {
669
+ this.db = db;
670
+ }
671
+ db;
672
+ async getPoolDepths() {
673
+ const result = await this.db.execute(sql2`
674
+ SELECT
675
+ pool AS name,
676
+ COUNT(*) FILTER (WHERE status = 'pending')::int AS pending,
677
+ COUNT(*) FILTER (WHERE status = 'running')::int AS running,
678
+ (percentile_cont(0.95) WITHIN GROUP (
679
+ ORDER BY EXTRACT(EPOCH FROM (now() - claimed_at)) * 1000
680
+ ) FILTER (WHERE status = 'running' AND claimed_at IS NOT NULL))::int
681
+ AS claimed_age_p95_ms
682
+ FROM job_run
683
+ WHERE status IN ('pending','running')
684
+ GROUP BY pool
685
+ ORDER BY pool
686
+ `);
687
+ const rows = extractRows(result);
688
+ return rows.map((r) => ({
689
+ name: r.name,
690
+ pending: r.pending,
691
+ running: r.running,
692
+ claimedAgeP95Ms: r.claimed_age_p95_ms
693
+ }));
694
+ }
695
+ async getRecentSyncRuns(limit, integrationId) {
696
+ const base = this.db.select({
697
+ id: syncRuns.id,
698
+ subscriptionId: syncRuns.subscriptionId,
699
+ integrationId: syncSubscriptions.integrationId,
700
+ adapter: syncSubscriptions.adapter,
701
+ domain: syncSubscriptions.domain,
702
+ direction: syncRuns.direction,
703
+ action: syncRuns.action,
704
+ status: syncRuns.status,
705
+ recordsFound: syncRuns.recordsFound,
706
+ recordsProcessed: syncRuns.recordsProcessed,
707
+ durationMs: syncRuns.durationMs,
708
+ error: syncRuns.error,
709
+ startedAt: syncRuns.startedAt,
710
+ completedAt: syncRuns.completedAt
711
+ }).from(syncRuns).innerJoin(
712
+ syncSubscriptions,
713
+ eq(syncRuns.subscriptionId, syncSubscriptions.id)
714
+ );
715
+ const filtered = integrationId !== void 0 ? base.where(eq(syncSubscriptions.integrationId, integrationId)) : base;
716
+ const rows = await filtered.orderBy(desc(syncRuns.startedAt)).limit(limit);
717
+ return rows.map((r) => ({
718
+ id: r.id,
719
+ subscriptionId: r.subscriptionId,
720
+ integrationId: r.integrationId,
721
+ adapter: r.adapter,
722
+ domain: r.domain,
723
+ direction: r.direction,
724
+ action: r.action,
725
+ status: r.status,
726
+ recordsFound: r.recordsFound,
727
+ recordsProcessed: r.recordsProcessed,
728
+ durationMs: r.durationMs,
729
+ error: r.error,
730
+ startedAt: r.startedAt,
731
+ completedAt: r.completedAt
732
+ }));
733
+ }
734
+ async getBridgeDeliveryHistogram(windowHours) {
735
+ const result = await this.db.execute(sql2`
736
+ SELECT status, COUNT(*)::int AS count
737
+ FROM bridge_delivery
738
+ WHERE COALESCE(delivered_at, attempted_at) > now() - make_interval(hours => ${windowHours})
739
+ GROUP BY status
740
+ `);
741
+ const rows = extractRows(result);
742
+ const hist = {};
743
+ for (const r of rows) hist[r.status] = r.count;
744
+ return hist;
745
+ }
746
+ async getRecentFailedJobs(limit) {
747
+ const rows = await this.db.select({
748
+ id: jobRuns.id,
749
+ jobType: jobRuns.jobType,
750
+ pool: jobRuns.pool,
751
+ status: jobRuns.status,
752
+ error: jobRuns.error,
753
+ startedAt: jobRuns.startedAt,
754
+ finishedAt: jobRuns.finishedAt,
755
+ attempts: jobRuns.attempts
756
+ }).from(jobRuns).where(eq(jobRuns.status, "failed")).orderBy(desc(jobRuns.finishedAt)).limit(limit);
757
+ return rows.map((r) => ({
758
+ id: r.id,
759
+ jobType: r.jobType,
760
+ pool: r.pool,
761
+ status: r.status,
762
+ error: r.error,
763
+ startedAt: r.startedAt,
764
+ finishedAt: r.finishedAt,
765
+ attempts: r.attempts
766
+ }));
767
+ }
768
+ async getCursors() {
769
+ const rows = await this.db.select({
770
+ id: syncSubscriptions.id,
771
+ integrationId: syncSubscriptions.integrationId,
772
+ adapter: syncSubscriptions.adapter,
773
+ domain: syncSubscriptions.domain,
774
+ cursor: syncSubscriptions.cursor,
775
+ lastSyncAt: syncSubscriptions.lastSyncAt
776
+ }).from(syncSubscriptions).where(eq(syncSubscriptions.enabled, true)).orderBy(syncSubscriptions.integrationId, syncSubscriptions.domain);
777
+ return rows.map((r) => ({
778
+ subscriptionId: r.id,
779
+ integrationId: r.integrationId,
780
+ adapter: r.adapter,
781
+ domain: r.domain,
782
+ lastCursor: r.cursor,
783
+ lastSyncAt: r.lastSyncAt
784
+ }));
785
+ }
786
+ };
787
+ DrizzleObservabilityService = __decorateClass([
788
+ Injectable(),
789
+ __decorateParam(0, Inject(DRIZZLE))
790
+ ], DrizzleObservabilityService);
791
+ function extractRows(result) {
792
+ const maybe = result;
793
+ if (Array.isArray(maybe.rows)) return maybe.rows;
794
+ if (Array.isArray(result)) return result;
795
+ return [];
796
+ }
797
+
798
+ // runtime/subsystems/observability/observability.memory-backend.ts
799
+ import { Injectable as Injectable2 } from "@nestjs/common";
800
+ var MemoryObservabilityService = class {
801
+ pools = [];
802
+ syncRuns = [];
803
+ bridgeHistogram = {};
804
+ failedJobs = [];
805
+ cursors = [];
806
+ // ─── Core contract ─────────────────────────────────────────────────────
807
+ async getPoolDepths() {
808
+ return [...this.pools];
809
+ }
810
+ async getRecentSyncRuns(limit, integrationId) {
811
+ const filtered = integrationId !== void 0 ? this.syncRuns.filter((r) => r.integrationId === integrationId) : this.syncRuns;
812
+ return filtered.slice().sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime()).slice(0, limit);
813
+ }
814
+ async getBridgeDeliveryHistogram(_windowHours) {
815
+ return { ...this.bridgeHistogram };
816
+ }
817
+ async getRecentFailedJobs(limit) {
818
+ return this.failedJobs.slice().sort(
819
+ (a, b) => (b.finishedAt?.getTime() ?? 0) - (a.finishedAt?.getTime() ?? 0)
820
+ ).slice(0, limit);
821
+ }
822
+ async getCursors() {
823
+ return [...this.cursors];
824
+ }
825
+ // ─── Test seams ────────────────────────────────────────────────────────
826
+ /** Replace the pool-depth slice. */
827
+ seedPools(pools) {
828
+ this.pools = [...pools];
829
+ }
830
+ /** Replace the sync-run slice. */
831
+ seedSyncRuns(runs) {
832
+ this.syncRuns = [...runs];
833
+ }
834
+ /** Replace the bridge-delivery histogram. */
835
+ seedBridgeHistogram(hist) {
836
+ this.bridgeHistogram = { ...hist };
837
+ }
838
+ /** Replace the failed-jobs slice. */
839
+ seedFailedJobs(jobs2) {
840
+ this.failedJobs = [...jobs2];
841
+ }
842
+ /** Replace the cursor slice. */
843
+ seedCursors(cursors) {
844
+ this.cursors = [...cursors];
845
+ }
846
+ /** Reset every slice — for afterEach hooks. */
847
+ reset() {
848
+ this.pools = [];
849
+ this.syncRuns = [];
850
+ this.bridgeHistogram = {};
851
+ this.failedJobs = [];
852
+ this.cursors = [];
853
+ }
854
+ };
855
+ MemoryObservabilityService = __decorateClass([
856
+ Injectable2()
857
+ ], MemoryObservabilityService);
858
+
859
+ // runtime/subsystems/observability/observability.tokens.ts
860
+ var OBSERVABILITY = /* @__PURE__ */ Symbol("OBSERVABILITY");
861
+ var OBSERVABILITY_REPORTERS = /* @__PURE__ */ Symbol("OBSERVABILITY_REPORTERS");
862
+
863
+ // runtime/subsystems/observability/observability.module.ts
864
+ var ObservabilityModule = class {
865
+ static forRoot(options = { backend: "drizzle" }) {
866
+ const ConcreteClass = options.backend === "drizzle" ? DrizzleObservabilityService : MemoryObservabilityService;
867
+ const wantsBridgeMetrics = options.reporters?.bridgeMetrics === true;
868
+ const providers = [
869
+ // Register the concrete class as the canonical instance.
870
+ ConcreteClass,
871
+ // OBSERVABILITY token points at the same instance — no duplicate.
872
+ { provide: OBSERVABILITY, useExisting: ConcreteClass },
873
+ // Expose the resolved reporter config for introspection / tests.
874
+ {
875
+ provide: OBSERVABILITY_REPORTERS,
876
+ useValue: options.reporters ?? {}
877
+ }
878
+ ];
879
+ const exports = [OBSERVABILITY];
880
+ if (wantsBridgeMetrics) {
881
+ const { BridgeMetricsReporter: BridgeMetricsReporter2 } = (init_bridge_metrics_reporter(), __toCommonJS(bridge_metrics_reporter_exports));
882
+ providers.push(BridgeMetricsReporter2);
883
+ exports.push(BridgeMetricsReporter2);
884
+ }
885
+ const imports = [];
886
+ if (wantsBridgeMetrics) {
887
+ const { ScheduleModule } = __require("@nestjs/schedule");
888
+ imports.push(ScheduleModule.forRoot());
889
+ }
890
+ return {
891
+ module: ObservabilityModule,
892
+ global: true,
893
+ imports,
894
+ providers,
895
+ exports
896
+ };
897
+ }
898
+ };
899
+ ObservabilityModule = __decorateClass([
900
+ Module({})
901
+ ], ObservabilityModule);
902
+ export {
903
+ ObservabilityModule
904
+ };
905
+ //# sourceMappingURL=observability.module.js.map