@happyvertical/smrt-core 0.47.2 → 0.49.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 (110) hide show
  1. package/agents/generators.md +12 -0
  2. package/agents/object-runtime.md +7 -0
  3. package/agents/schema-paths.md +62 -0
  4. package/agents/system-diagnostics.md +6 -1
  5. package/dist/cascade.d.ts.map +1 -1
  6. package/dist/cascade.js +9 -6
  7. package/dist/cascade.js.map +1 -1
  8. package/dist/collection.d.ts +0 -1
  9. package/dist/collection.d.ts.map +1 -1
  10. package/dist/collection.js +35 -45
  11. package/dist/collection.js.map +1 -1
  12. package/dist/decorators/compatibility.d.ts +1 -1
  13. package/dist/decorators/compatibility.d.ts.map +1 -1
  14. package/dist/decorators/compatibility.js +2 -2
  15. package/dist/decorators/compatibility.js.map +1 -1
  16. package/dist/decorators/index.d.ts.map +1 -1
  17. package/dist/decorators/index.js +33 -24
  18. package/dist/decorators/index.js.map +1 -1
  19. package/dist/interceptors.d.ts +3 -1
  20. package/dist/interceptors.d.ts.map +1 -1
  21. package/dist/interceptors.js +3 -2
  22. package/dist/interceptors.js.map +1 -1
  23. package/dist/manifest/static-manifest.js +1 -1
  24. package/dist/manifest/static-manifest.js.map +1 -1
  25. package/dist/manifest/store.js +1 -1
  26. package/dist/manifest.json +1 -1
  27. package/dist/migrations/differ.d.ts +10 -4
  28. package/dist/migrations/differ.d.ts.map +1 -1
  29. package/dist/migrations/differ.js +173 -5
  30. package/dist/migrations/differ.js.map +1 -1
  31. package/dist/object.d.ts +1 -5
  32. package/dist/object.d.ts.map +1 -1
  33. package/dist/object.js +28 -37
  34. package/dist/object.js.map +1 -1
  35. package/dist/registry/class-registration.d.ts +2 -1
  36. package/dist/registry/class-registration.d.ts.map +1 -1
  37. package/dist/registry/class-registration.js +47 -16
  38. package/dist/registry/class-registration.js.map +1 -1
  39. package/dist/registry/name-resolver.d.ts.map +1 -1
  40. package/dist/registry/name-resolver.js +3 -3
  41. package/dist/registry/name-resolver.js.map +1 -1
  42. package/dist/registry/relationship-graph.d.ts +2 -0
  43. package/dist/registry/relationship-graph.d.ts.map +1 -1
  44. package/dist/registry/relationship-graph.js +60 -5
  45. package/dist/registry/relationship-graph.js.map +1 -1
  46. package/dist/registry/schema-builder.d.ts.map +1 -1
  47. package/dist/registry/schema-builder.js +4 -1
  48. package/dist/registry/schema-builder.js.map +1 -1
  49. package/dist/registry/shared-state.d.ts +18 -0
  50. package/dist/registry/shared-state.d.ts.map +1 -1
  51. package/dist/registry/shared-state.js +25 -1
  52. package/dist/registry/shared-state.js.map +1 -1
  53. package/dist/registry/types.d.ts +8 -0
  54. package/dist/registry/types.d.ts.map +1 -1
  55. package/dist/registry.d.ts +32 -5
  56. package/dist/registry.d.ts.map +1 -1
  57. package/dist/registry.js +120 -12
  58. package/dist/registry.js.map +1 -1
  59. package/dist/relationship-loader.d.ts +6 -0
  60. package/dist/relationship-loader.d.ts.map +1 -0
  61. package/dist/relationship-loader.js +35 -0
  62. package/dist/relationship-loader.js.map +1 -0
  63. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  64. package/dist/scanner/manifest-generator.js +6 -3
  65. package/dist/scanner/manifest-generator.js.map +1 -1
  66. package/dist/schema/foreign-key-ddl.d.ts +8 -0
  67. package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
  68. package/dist/schema/foreign-key-ddl.js +12 -1
  69. package/dist/schema/foreign-key-ddl.js.map +1 -1
  70. package/dist/schema/foreign-key-orphan-report.d.ts.map +1 -1
  71. package/dist/schema/foreign-key-orphan-report.js +18 -1
  72. package/dist/schema/foreign-key-orphan-report.js.map +1 -1
  73. package/dist/schema/generator.d.ts +2 -0
  74. package/dist/schema/generator.d.ts.map +1 -1
  75. package/dist/schema/generator.js +16 -9
  76. package/dist/schema/generator.js.map +1 -1
  77. package/dist/schema/index.d.ts +1 -1
  78. package/dist/schema/index.d.ts.map +1 -1
  79. package/dist/schema/index.js +2 -2
  80. package/dist/schema/live-parity.d.ts.map +1 -1
  81. package/dist/schema/live-parity.js +74 -0
  82. package/dist/schema/live-parity.js.map +1 -1
  83. package/dist/schema/text-cast-probe.d.ts +66 -0
  84. package/dist/schema/text-cast-probe.d.ts.map +1 -0
  85. package/dist/schema/text-cast-probe.js +142 -0
  86. package/dist/schema/text-cast-probe.js.map +1 -0
  87. package/dist/schema/types.d.ts +28 -0
  88. package/dist/schema/types.d.ts.map +1 -1
  89. package/dist/schema/utils.d.ts.map +1 -1
  90. package/dist/schema/utils.js +2 -1
  91. package/dist/schema/utils.js.map +1 -1
  92. package/dist/schema.js +2 -2
  93. package/dist/smrt-knowledge.json +11 -11
  94. package/dist/system/diagnostics.d.ts +22 -0
  95. package/dist/system/diagnostics.d.ts.map +1 -1
  96. package/dist/system/diagnostics.js +88 -31
  97. package/dist/system/diagnostics.js.map +1 -1
  98. package/dist/test-utils.d.ts.map +1 -1
  99. package/dist/utils.d.ts.map +1 -1
  100. package/dist/utils.js +2 -1
  101. package/dist/utils.js.map +1 -1
  102. package/dist/vite-plugin/dev-plane-route.d.ts +5 -0
  103. package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -0
  104. package/dist/vite-plugin/dev-plane-route.js +91 -0
  105. package/dist/vite-plugin/dev-plane-route.js.map +1 -0
  106. package/dist/vite-plugin/sveltekit-generator.d.ts +8 -0
  107. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  108. package/dist/vite-plugin/sveltekit-generator.js +2 -0
  109. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  110. package/package.json +4 -4
@@ -27,6 +27,7 @@ async function readMigrationStatus(db, options = {}) {
27
27
  const limit = boundedLimit(options.limit);
28
28
  try {
29
29
  const p = placeholders(db);
30
+ const projection = await selectable(db, table, MIGRATION_COLUMNS_LIST);
30
31
  const countRows = queryRows(await db.query(`SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`));
31
32
  const byStatus = {};
32
33
  let total = 0;
@@ -40,16 +41,17 @@ async function readMigrationStatus(db, options = {}) {
40
41
  const running = byStatus.running ?? 0;
41
42
  const failed = byStatus.failed ?? 0;
42
43
  const rolledBack = byStatus.rolled_back ?? 0;
43
- const latest = queryRows(await db.query(`SELECT ${MIGRATION_COLUMNS} FROM ${table}
44
+ const latest = queryRows(await db.query(`SELECT ${projection.columns} FROM ${table}
44
45
  WHERE status = 'completed'
45
46
  ORDER BY applied_at DESC, id ASC
46
47
  LIMIT ${p(1)}`, limit)).map(toMigrationRow);
47
- const failedRows = queryRows(await db.query(`SELECT ${MIGRATION_COLUMNS} FROM ${table}
48
+ const failedRows = queryRows(await db.query(`SELECT ${projection.columns} FROM ${table}
48
49
  WHERE status = 'failed'
49
50
  ORDER BY applied_at DESC, id ASC
50
51
  LIMIT ${p(1)}`, limit)).map(toMigrationRow);
51
52
  return {
52
53
  available: true,
54
+ schemaBehind: projection.behind ? [projection.behind] : [],
53
55
  summary: {
54
56
  total,
55
57
  byStatus,
@@ -61,11 +63,11 @@ async function readMigrationStatus(db, options = {}) {
61
63
  latest,
62
64
  failed: failedRows
63
65
  };
64
- } catch {
65
- return unavailable(table, "read-error");
66
+ } catch (error) {
67
+ return unavailable(table, "read-error", void 0, readError(error));
66
68
  }
67
69
  }
68
- var MIGRATION_COLUMNS = [
70
+ var MIGRATION_COLUMNS_LIST = [
69
71
  "id",
70
72
  "name",
71
73
  "version",
@@ -80,7 +82,8 @@ var MIGRATION_COLUMNS = [
80
82
  "rolled_back_at",
81
83
  "applied_by",
82
84
  "batch"
83
- ].join(", ");
85
+ ];
86
+ MIGRATION_COLUMNS_LIST.join(", ");
84
87
  function toMigrationRow(row) {
85
88
  return {
86
89
  id: String(row.id ?? ""),
@@ -121,7 +124,14 @@ async function readJobHealth(db, options = {}) {
121
124
  const failed = byStatus.failed ?? 0;
122
125
  const workersTable = SYSTEM_DIAGNOSTICS_TABLES.workers;
123
126
  const workersAvailable = await tableExists(db, workersTable);
124
- const stuck = toCount(queryRows(workersAvailable ? await db.query(`SELECT COUNT(*) AS total FROM ${table} AS j
127
+ const jobProjection = await selectable(db, table, JOB_COLUMNS_LIST);
128
+ const workerProjection = workersAvailable ? await selectable(db, workersTable, WORKER_COLUMNS_LIST) : {
129
+ columns: WORKER_COLUMNS,
130
+ behind: null
131
+ };
132
+ const jobsHaveHeartbeat = !jobProjection.behind?.missingColumns.includes("worker_heartbeat");
133
+ const canJoinLease = workersAvailable && !jobProjection.behind?.missingColumns.includes("worker_id") && !workerProjection.behind?.missingColumns.includes("lease_expires_at");
134
+ const stuck = toCount((!jobsHaveHeartbeat ? [{ total: 0 }] : queryRows(canJoinLease ? await db.query(`SELECT COUNT(*) AS total FROM ${table} AS j
125
135
  WHERE j.status = 'running'
126
136
  AND (j.worker_heartbeat IS NULL OR j.worker_heartbeat < ${p(1)})
127
137
  AND NOT EXISTS (
@@ -130,12 +140,12 @@ async function readJobHealth(db, options = {}) {
130
140
  AND w.lease_expires_at > ${p(2)}
131
141
  )`, staleCutoff.toISOString(), now.toISOString()) : await db.query(`SELECT COUNT(*) AS total FROM ${table}
132
142
  WHERE status = 'running'
133
- AND (worker_heartbeat IS NULL OR worker_heartbeat < ${p(1)})`, staleCutoff.toISOString()))[0]?.total);
143
+ AND (worker_heartbeat IS NULL OR worker_heartbeat < ${p(1)})`, staleCutoff.toISOString())))[0]?.total);
134
144
  const jobs = queryRows(await db.query(`SELECT ${JOB_COLUMNS} FROM ${table}
135
145
  ORDER BY run_at DESC, id ASC
136
146
  LIMIT ${p(1)}`, limit)).map(toJobHealthRow);
137
147
  let workers = [];
138
- if (workersAvailable) workers = queryRows(await db.query(`SELECT ${WORKER_COLUMNS} FROM ${workersTable}
148
+ if (workersAvailable) workers = queryRows(await db.query(`SELECT ${workerProjection.columns} FROM ${workersTable}
139
149
  ORDER BY heartbeat_at DESC, id ASC
140
150
  LIMIT ${p(1)}`, limit)).map(toWorkerHealthRow);
141
151
  let eventCount = null;
@@ -143,6 +153,7 @@ async function readJobHealth(db, options = {}) {
143
153
  if (await tableExists(db, eventsTable)) eventCount = toNumber(queryRows(await db.query(`SELECT COUNT(*) AS total FROM ${eventsTable}`))[0]?.total);
144
154
  return {
145
155
  available: true,
156
+ schemaBehind: [jobProjection.behind, workerProjection.behind].filter((behind) => behind !== null),
146
157
  summary: {
147
158
  total,
148
159
  byStatus,
@@ -153,11 +164,11 @@ async function readJobHealth(db, options = {}) {
153
164
  jobs,
154
165
  workers
155
166
  };
156
- } catch {
157
- return unavailable(table, "read-error");
167
+ } catch (error) {
168
+ return unavailable(table, "read-error", void 0, readError(error));
158
169
  }
159
170
  }
160
- var JOB_COLUMNS = [
171
+ var JOB_COLUMNS_LIST = [
161
172
  "id",
162
173
  "tenant_id",
163
174
  "queue",
@@ -175,8 +186,9 @@ var JOB_COLUMNS = [
175
186
  "task_owner_id",
176
187
  "worker_id",
177
188
  "worker_heartbeat"
178
- ].join(", ");
179
- var WORKER_COLUMNS = [
189
+ ];
190
+ var JOB_COLUMNS = JOB_COLUMNS_LIST.join(", ");
191
+ var WORKER_COLUMNS_LIST = [
180
192
  "id",
181
193
  "worker_id",
182
194
  "pid",
@@ -185,7 +197,8 @@ var WORKER_COLUMNS = [
185
197
  "heartbeat_at",
186
198
  "lease_expires_at",
187
199
  "status"
188
- ].join(", ");
200
+ ];
201
+ var WORKER_COLUMNS = WORKER_COLUMNS_LIST.join(", ");
189
202
  function toJobHealthRow(row) {
190
203
  return {
191
204
  id: String(row.id ?? ""),
@@ -227,6 +240,7 @@ async function readScheduleHealth(db, options = {}) {
227
240
  const now = options.now ?? /* @__PURE__ */ new Date();
228
241
  try {
229
242
  const p = placeholders(db);
243
+ const projection = await selectable(db, table, SCHEDULE_COLUMNS_LIST);
230
244
  const enabledRows = queryRows(await db.query(`SELECT enabled, status, COUNT(*) AS total FROM ${table} GROUP BY enabled, status`));
231
245
  let total = 0;
232
246
  let enabled = 0;
@@ -243,11 +257,12 @@ async function readScheduleHealth(db, options = {}) {
243
257
  const failed = toCount(queryRows(await db.query(`SELECT COUNT(*) AS total FROM ${table} WHERE last_status = 'failed'`))[0]?.total);
244
258
  const overdue = toCount(queryRows(await db.query(`SELECT COUNT(*) AS total FROM ${table}
245
259
  WHERE enabled = true AND status = 'active' AND next_run IS NOT NULL AND next_run <= ${p(1)}`, now.toISOString()))[0]?.total);
246
- const schedules = queryRows(await db.query(`SELECT ${SCHEDULE_COLUMNS} FROM ${table}
260
+ const schedules = queryRows(await db.query(`SELECT ${projection.columns} FROM ${table}
247
261
  ORDER BY next_run ASC, id ASC
248
262
  LIMIT ${p(1)}`, limit)).map(toScheduleHealthRow);
249
263
  return {
250
264
  available: true,
265
+ schemaBehind: projection.behind ? [projection.behind] : [],
251
266
  summary: {
252
267
  total,
253
268
  enabled,
@@ -258,11 +273,11 @@ async function readScheduleHealth(db, options = {}) {
258
273
  },
259
274
  schedules
260
275
  };
261
- } catch {
262
- return unavailable(table, "read-error");
276
+ } catch (error) {
277
+ return unavailable(table, "read-error", void 0, readError(error));
263
278
  }
264
279
  }
265
- var SCHEDULE_COLUMNS = [
280
+ var SCHEDULE_COLUMNS_LIST = [
266
281
  "id",
267
282
  "tenant_id",
268
283
  "agent_type",
@@ -282,7 +297,8 @@ var SCHEDULE_COLUMNS = [
282
297
  "running_count",
283
298
  "timeout",
284
299
  "method"
285
- ].join(", ");
300
+ ];
301
+ SCHEDULE_COLUMNS_LIST.join(", ");
286
302
  function toScheduleHealthRow(row) {
287
303
  return {
288
304
  id: String(row.id ?? ""),
@@ -313,6 +329,7 @@ async function readDispatchHealth(db, options = {}) {
313
329
  const limit = boundedLimit(options.limit);
314
330
  try {
315
331
  const p = placeholders(db);
332
+ const projection = await selectable(db, table, DISPATCH_COLUMNS_LIST);
316
333
  const statusRows = queryRows(await db.query(`SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`));
317
334
  const byStatus = {};
318
335
  let total = 0;
@@ -326,20 +343,23 @@ async function readDispatchHealth(db, options = {}) {
326
343
  const processing = byStatus.processing ?? 0;
327
344
  const failed = byStatus.failed ?? 0;
328
345
  const completed = byStatus.completed ?? 0;
329
- const dispatches = queryRows(await db.query(`SELECT ${DISPATCH_COLUMNS} FROM ${table}
346
+ const dispatches = queryRows(await db.query(`SELECT ${projection.columns} FROM ${table}
330
347
  ORDER BY created_at DESC, id ASC
331
348
  LIMIT ${p(1)}`, limit)).map(toDispatchHealthRow);
332
349
  let subscriptions = [];
333
350
  let subscriptionCount = 0;
334
351
  const subsTable = SYSTEM_DIAGNOSTICS_TABLES.dispatchSubscriptions;
352
+ let subsProjection = null;
335
353
  if (await tableExists(db, subsTable)) {
354
+ subsProjection = await selectable(db, subsTable, SUBSCRIPTION_COLUMNS_LIST);
336
355
  subscriptionCount = toCount(queryRows(await db.query(`SELECT COUNT(*) AS total FROM ${subsTable}`))[0]?.total);
337
- subscriptions = queryRows(await db.query(`SELECT ${SUBSCRIPTION_COLUMNS} FROM ${subsTable}
356
+ subscriptions = queryRows(await db.query(`SELECT ${subsProjection.columns} FROM ${subsTable}
338
357
  ORDER BY created_at DESC, id ASC
339
358
  LIMIT ${p(1)}`, limit)).map(toSubscriptionHealthRow);
340
359
  }
341
360
  return {
342
361
  available: true,
362
+ schemaBehind: [projection.behind, subsProjection?.behind ?? null].filter((behind) => behind !== null),
343
363
  summary: {
344
364
  total,
345
365
  byStatus,
@@ -352,11 +372,11 @@ async function readDispatchHealth(db, options = {}) {
352
372
  dispatches,
353
373
  subscriptions
354
374
  };
355
- } catch {
356
- return unavailable(table, "read-error");
375
+ } catch (error) {
376
+ return unavailable(table, "read-error", void 0, readError(error));
357
377
  }
358
378
  }
359
- var DISPATCH_COLUMNS = [
379
+ var DISPATCH_COLUMNS_LIST = [
360
380
  "id",
361
381
  "type",
362
382
  "source",
@@ -371,8 +391,9 @@ var DISPATCH_COLUMNS = [
371
391
  "tenant_id",
372
392
  "created_at",
373
393
  "updated_at"
374
- ].join(", ");
375
- var SUBSCRIPTION_COLUMNS = [
394
+ ];
395
+ DISPATCH_COLUMNS_LIST.join(", ");
396
+ var SUBSCRIPTION_COLUMNS_LIST = [
376
397
  "id",
377
398
  "signal_type",
378
399
  "subscriber",
@@ -382,7 +403,8 @@ var SUBSCRIPTION_COLUMNS = [
382
403
  "tenant_id",
383
404
  "created_at",
384
405
  "updated_at"
385
- ].join(", ");
406
+ ];
407
+ SUBSCRIPTION_COLUMNS_LIST.join(", ");
386
408
  function toDispatchHealthRow(row) {
387
409
  return {
388
410
  id: String(row.id ?? ""),
@@ -442,8 +464,8 @@ async function readRecentChanges(db, options = {}) {
442
464
  } : {},
443
465
  count: page.changes.length
444
466
  };
445
- } catch {
446
- return unavailable(table, "read-error");
467
+ } catch (error) {
468
+ return unavailable(table, "read-error", void 0, readError(error));
447
469
  }
448
470
  }
449
471
  async function readRegistryDrift(db) {
@@ -476,14 +498,49 @@ async function readSystemDiagnostics(db, options = {}) {
476
498
  registry
477
499
  };
478
500
  }
479
- function unavailable(tableName, reason, message) {
501
+ function unavailable(tableName, reason, message, detail) {
480
502
  return {
481
503
  available: false,
482
504
  reason,
505
+ ...detail ? { detail } : {},
483
506
  message: message ?? (reason === "retired" ? `${tableName} is retired and no longer read.` : reason === "table-missing" ? `The ${tableName} system table is not present on this database.` : `Reading ${tableName} failed; the live diagnostic is unavailable.`),
484
507
  tableName
485
508
  };
486
509
  }
510
+ /** Column names of a table, lower-cased; empty when the probe fails. */
511
+ async function tableColumns(db, tableName) {
512
+ try {
513
+ const rows = queryRows(getDatabaseEngine(db) === "postgres" ? await db.query("SELECT column_name AS name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = $1", tableName) : await db.query(`PRAGMA table_info(${tableName})`));
514
+ return new Set(rows.map((row) => String(row.name ?? "").toLowerCase()).filter((name) => name.length > 0));
515
+ } catch {
516
+ return /* @__PURE__ */ new Set();
517
+ }
518
+ }
519
+ /**
520
+ * Intersect a reader's wanted projection with the live table. Returns the
521
+ * SELECT list to use plus a `SchemaBehind` record when anything is missing.
522
+ * If the probe itself fails (empty set) the full projection is used so the
523
+ * read error, not a false schema-behind, surfaces.
524
+ */
525
+ async function selectable(db, tableName, wanted) {
526
+ const present = await tableColumns(db, tableName);
527
+ if (present.size === 0) return {
528
+ columns: wanted.join(", "),
529
+ behind: null
530
+ };
531
+ const have = wanted.filter((column) => present.has(column));
532
+ const missing = wanted.filter((column) => !present.has(column));
533
+ return {
534
+ columns: (have.length > 0 ? have : wanted).join(", "),
535
+ behind: missing.length > 0 ? {
536
+ tableName,
537
+ missingColumns: missing
538
+ } : null
539
+ };
540
+ }
541
+ function readError(error) {
542
+ return error instanceof Error ? error.message : String(error ?? "unknown");
543
+ }
487
544
  function boundedLimit(value, fallback = 50) {
488
545
  if (!Number.isFinite(value) || value === void 0) return fallback;
489
546
  return Math.min(Math.max(Math.floor(value), 1), 500);
@@ -1 +1 @@
1
- {"version":3,"file":"diagnostics.js","names":[],"sources":["../../src/system/diagnostics.ts"],"sourcesContent":["/**\n * SMRT system-diagnostics reader.\n *\n * A typed, SELECT-only view over the framework-owned `_smrt_*` system tables\n * for development tooling (smrt-dev-mcp runtime diagnostics). Every read is a\n * bounded SELECT against explicitly projected safe columns; sensitive columns\n * are never selected and no statement here writes.\n *\n * The reader takes a `DatabaseInterface` (connection resolution lives with the\n * caller) and degrades per category to a `CategoryUnavailable` result when its\n * table is missing or a read fails — it never throws for missing tables and\n * never fabricates data. `_smrt_registry` is a retired system table\n * ({@link RETIRED_SYSTEM_TABLES}) and is reported as retired rather than\n * queried.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { type ChangeFeedEntry, getChangesSince } from '../change-feed.js';\nimport { getDatabaseEngine, tableExists } from './compatibility.js';\n\n/** Physical `_smrt_*` tables the diagnostics reader knows about. */\nexport const SYSTEM_DIAGNOSTICS_TABLES = {\n migrations: '_smrt_schema_migrations',\n dispatch: '_smrt_dispatch',\n dispatchSubscriptions: '_smrt_dispatch_subscriptions',\n changes: '_smrt_changes',\n jobs: '_smrt_jobs',\n jobEvents: '_smrt_job_events',\n workers: '_smrt_workers',\n schedules: '_smrt_agent_schedules',\n registry: '_smrt_registry',\n} as const;\n\n/** Default page size for row-listing categories. */\nexport const DIAGNOSTICS_DEFAULT_LIMIT = 50;\n\n/**\n * A category whose table is missing (or whose read failed) on the live\n * database. Callers must surface this as \"category unavailable\" — never as an\n * empty-but-healthy category and never as a fabricated value.\n */\nexport interface CategoryUnavailable {\n available: false;\n reason: 'table-missing' | 'retired' | 'read-error';\n message: string;\n tableName?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Migration status\n// ---------------------------------------------------------------------------\n\n/** Safe projection of one `_smrt_schema_migrations` row. */\nexport interface MigrationStatusRow {\n id: string;\n name: string;\n version: string;\n status: string;\n appliedAt: string | null;\n executionTimeMs: number | null;\n attempts: number | null;\n errorMessage: string | null;\n packageName: string | null;\n sourceFile: string | null;\n isReversible: boolean | null;\n rolledBackAt: string | null;\n appliedBy: string | null;\n batch: number | null;\n}\n\n/**\n * Counts use `MigrationTracker`'s real status vocabulary: `completed`\n * (successfully applied), `running` (in flight), `failed`, and `rolled_back`.\n */\nexport interface MigrationStatusSummary {\n total: number;\n byStatus: Record<string, number>;\n completed: number;\n running: number;\n failed: number;\n rolledBack: number;\n}\n\nexport type MigrationStatusResult =\n | {\n available: true;\n summary: MigrationStatusSummary;\n /** Most recently applied migrations, newest first. */\n latest: MigrationStatusRow[];\n /** Failed migrations, newest first. */\n failed: MigrationStatusRow[];\n }\n | CategoryUnavailable;\n\nexport interface MigrationStatusOptions {\n /** Row budget for `latest`/`failed` lists (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n}\n\n/**\n * Bounded, SELECT-only migration history read.\n *\n * Deliberately does not use `MigrationTracker` — its history helpers run\n * DDL-initializing bootstrap — and reads `_smrt_schema_migrations` directly.\n */\nexport async function readMigrationStatus(\n db: DatabaseInterface,\n options: MigrationStatusOptions = {},\n): Promise<MigrationStatusResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.migrations;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n\n try {\n const p = placeholders(db);\n const countRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of countRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const completed = byStatus.completed ?? 0;\n const running = byStatus.running ?? 0;\n const failed = byStatus.failed ?? 0;\n const rolledBack = byStatus.rolled_back ?? 0;\n\n const latest = queryRows(\n await db.query(\n `SELECT ${MIGRATION_COLUMNS} FROM ${table}\n WHERE status = 'completed'\n ORDER BY applied_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toMigrationRow);\n\n const failedRows = queryRows(\n await db.query(\n `SELECT ${MIGRATION_COLUMNS} FROM ${table}\n WHERE status = 'failed'\n ORDER BY applied_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toMigrationRow);\n\n return {\n available: true,\n summary: { total, byStatus, completed, running, failed, rolledBack },\n latest,\n failed: failedRows,\n };\n } catch {\n return unavailable(table, 'read-error');\n }\n}\n\nconst MIGRATION_COLUMNS = [\n 'id',\n 'name',\n 'version',\n 'status',\n 'applied_at',\n 'execution_time_ms',\n 'attempts',\n 'error_message',\n 'package_name',\n 'source_file',\n 'is_reversible',\n 'rolled_back_at',\n 'applied_by',\n 'batch',\n].join(', ');\n\nfunction toMigrationRow(row: Record<string, unknown>): MigrationStatusRow {\n return {\n id: String(row.id ?? ''),\n name: String(row.name ?? ''),\n version: String(row.version ?? ''),\n status: String(row.status ?? ''),\n appliedAt: toIso(row.applied_at),\n executionTimeMs: toNumber(row.execution_time_ms),\n attempts: toNumber(row.attempts),\n errorMessage: toNullableString(row.error_message),\n packageName: toNullableString(row.package_name),\n sourceFile: toNullableString(row.source_file),\n isReversible: toBoolean(row.is_reversible),\n rolledBackAt: toIso(row.rolled_back_at),\n appliedBy: toNullableString(row.applied_by),\n batch: toNumber(row.batch),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Job health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_jobs` row. Job payload/result columns\n * (`args`, `task_result`, `task_input_requests`, `task_input_responses`,\n * `result_pointer`) are deliberately never selected.\n */\nexport interface JobHealthRow {\n id: string;\n tenantId: string | null;\n queue: string;\n objectType: string;\n objectId: string | null;\n method: string;\n status: string;\n attempts: number | null;\n maxAttempts: number | null;\n runAt: string | null;\n startedAt: string | null;\n completedAt: string | null;\n lastError: string | null;\n taskId: string | null;\n taskOwnerId: string | null;\n workerId: string | null;\n workerHeartbeat: string | null;\n}\n\n/** Safe projection of one `_smrt_workers` row (no sensitive columns). */\nexport interface WorkerHealthRow {\n id: string;\n workerId: string;\n pid: number | null;\n hostname: string | null;\n startedAt: string | null;\n heartbeatAt: string | null;\n leaseExpiresAt: string | null;\n status: string;\n}\n\nexport interface JobHealthSummary {\n total: number;\n byStatus: Record<string, number>;\n failed: number;\n /** `running` jobs whose heartbeat is older than the stale threshold. */\n stuck: number;\n /** Total rows in `_smrt_job_events`, or null when that table is missing. */\n eventCount: number | null;\n}\n\nexport type JobHealthResult =\n | {\n available: true;\n summary: JobHealthSummary;\n jobs: JobHealthRow[];\n workers: WorkerHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface JobHealthOptions {\n /** Row budget for `jobs` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n /**\n * Heartbeat older than this (ms) marks a `running` job as stuck — unless\n * its owning worker still holds a fresh `_smrt_workers` lease.\n */\n staleAfterMs?: number;\n /** Clock for staleness computations (defaults to `new Date()`). */\n now?: Date;\n}\n\n/** Read `_smrt_jobs`, `_smrt_workers`, and a `_smrt_job_events` count. */\nexport async function readJobHealth(\n db: DatabaseInterface,\n options: JobHealthOptions = {},\n): Promise<JobHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.jobs;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n const staleAfterMs = options.staleAfterMs ?? 5 * 60_000;\n const now = options.now ?? new Date();\n const staleCutoff = new Date(now.getTime() - staleAfterMs);\n\n try {\n const p = placeholders(db);\n const statusRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of statusRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const failed = byStatus.failed ?? 0;\n\n // Mirror the runner's recovery rule (#1474): a stale per-job heartbeat is\n // only telemetry. A running job whose worker still holds a fresh liveness\n // lease in `_smrt_workers` (renewed off the handler event loop) is alive\n // even while its handler blocks, so it is not stuck. Without the workers\n // table liveness is unknowable and the heartbeat is the only signal.\n const workersTable = SYSTEM_DIAGNOSTICS_TABLES.workers;\n const workersAvailable = await tableExists(db, workersTable);\n const stuckRows = queryRows(\n workersAvailable\n ? await db.query(\n `SELECT COUNT(*) AS total FROM ${table} AS j\n WHERE j.status = 'running'\n AND (j.worker_heartbeat IS NULL OR j.worker_heartbeat < ${p(1)})\n AND NOT EXISTS (\n SELECT 1 FROM ${workersTable} AS w\n WHERE w.worker_id = j.worker_id\n AND w.lease_expires_at > ${p(2)}\n )`,\n staleCutoff.toISOString(),\n now.toISOString(),\n )\n : await db.query(\n `SELECT COUNT(*) AS total FROM ${table}\n WHERE status = 'running'\n AND (worker_heartbeat IS NULL OR worker_heartbeat < ${p(1)})`,\n staleCutoff.toISOString(),\n ),\n );\n const stuck = toCount(stuckRows[0]?.total);\n\n const jobs = queryRows(\n await db.query(\n `SELECT ${JOB_COLUMNS} FROM ${table}\n ORDER BY run_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toJobHealthRow);\n\n let workers: WorkerHealthRow[] = [];\n if (workersAvailable) {\n workers = queryRows(\n await db.query(\n `SELECT ${WORKER_COLUMNS} FROM ${workersTable}\n ORDER BY heartbeat_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toWorkerHealthRow);\n }\n\n let eventCount: number | null = null;\n const eventsTable = SYSTEM_DIAGNOSTICS_TABLES.jobEvents;\n if (await tableExists(db, eventsTable)) {\n const eventRows = queryRows(\n await db.query(`SELECT COUNT(*) AS total FROM ${eventsTable}`),\n );\n eventCount = toNumber(eventRows[0]?.total);\n }\n\n return {\n available: true,\n summary: { total, byStatus, failed, stuck, eventCount },\n jobs,\n workers,\n };\n } catch {\n return unavailable(table, 'read-error');\n }\n}\n\nconst JOB_COLUMNS = [\n 'id',\n 'tenant_id',\n 'queue',\n 'object_type',\n 'object_id',\n 'method',\n 'status',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'started_at',\n 'completed_at',\n 'last_error',\n 'task_id',\n 'task_owner_id',\n 'worker_id',\n 'worker_heartbeat',\n].join(', ');\n\nconst WORKER_COLUMNS = [\n 'id',\n 'worker_id',\n 'pid',\n 'hostname',\n 'started_at',\n 'heartbeat_at',\n 'lease_expires_at',\n 'status',\n].join(', ');\n\nfunction toJobHealthRow(row: Record<string, unknown>): JobHealthRow {\n return {\n id: String(row.id ?? ''),\n tenantId: toNullableString(row.tenant_id),\n queue: String(row.queue ?? ''),\n objectType: String(row.object_type ?? ''),\n objectId: toNullableString(row.object_id),\n method: String(row.method ?? ''),\n status: String(row.status ?? ''),\n attempts: toNumber(row.attempts),\n maxAttempts: toNumber(row.max_attempts),\n runAt: toIso(row.run_at),\n startedAt: toIso(row.started_at),\n completedAt: toIso(row.completed_at),\n lastError: toNullableString(row.last_error),\n taskId: toNullableString(row.task_id),\n taskOwnerId: toNullableString(row.task_owner_id),\n workerId: toNullableString(row.worker_id),\n workerHeartbeat: toIso(row.worker_heartbeat),\n };\n}\n\nfunction toWorkerHealthRow(row: Record<string, unknown>): WorkerHealthRow {\n return {\n id: String(row.id ?? ''),\n workerId: String(row.worker_id ?? ''),\n pid: toNumber(row.pid),\n hostname: toNullableString(row.hostname),\n startedAt: toIso(row.started_at),\n heartbeatAt: toIso(row.heartbeat_at),\n leaseExpiresAt: toIso(row.lease_expires_at),\n status: String(row.status ?? ''),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Schedule health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_agent_schedules` row. The sensitive\n * `agent_config` and `method_args` columns are never selected.\n */\nexport interface ScheduleHealthRow {\n id: string;\n tenantId: string | null;\n agentType: string;\n agentId: string | null;\n cron: string;\n timezone: string;\n enabled: boolean | null;\n status: string;\n lastRun: string | null;\n nextRun: string | null;\n lastStatus: string | null;\n lastError: string | null;\n runCount: number | null;\n successCount: number | null;\n failureCount: number | null;\n maxConcurrent: number | null;\n runningCount: number | null;\n timeout: number | null;\n method: string;\n}\n\nexport interface ScheduleHealthSummary {\n total: number;\n enabled: number;\n active: number;\n errored: number;\n /** Schedules whose last run failed (`last_status = 'failed'`). */\n failed: number;\n /** Enabled, active schedules whose `next_run` is due (<= now). */\n overdue: number;\n}\n\nexport type ScheduleHealthResult =\n | {\n available: true;\n summary: ScheduleHealthSummary;\n schedules: ScheduleHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface ScheduleHealthOptions {\n /** Row budget for `schedules` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n /** Clock for overdue computation (defaults to `new Date()`). */\n now?: Date;\n}\n\n/** Read `_smrt_agent_schedules` (owned by `@happyvertical/smrt-agents`). */\nexport async function readScheduleHealth(\n db: DatabaseInterface,\n options: ScheduleHealthOptions = {},\n): Promise<ScheduleHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.schedules;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n const now = options.now ?? new Date();\n\n try {\n const p = placeholders(db);\n const enabledRows = queryRows(\n await db.query(\n `SELECT enabled, status, COUNT(*) AS total FROM ${table} GROUP BY enabled, status`,\n ),\n );\n let total = 0;\n let enabled = 0;\n let active = 0;\n let errored = 0;\n for (const row of enabledRows) {\n const count = toCount(row.total);\n total += count;\n if (toBoolean(row.enabled)) enabled += count;\n const status = String(row.status ?? '');\n if (status === 'active') active += count;\n if (status === 'error') errored += count;\n }\n\n const failedRows = queryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table} WHERE last_status = 'failed'`,\n ),\n );\n const failed = toCount(failedRows[0]?.total);\n\n const overdueRows = queryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table}\n WHERE enabled = true AND status = 'active' AND next_run IS NOT NULL AND next_run <= ${p(1)}`,\n now.toISOString(),\n ),\n );\n const overdue = toCount(overdueRows[0]?.total);\n\n const schedules = queryRows(\n await db.query(\n `SELECT ${SCHEDULE_COLUMNS} FROM ${table}\n ORDER BY next_run ASC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toScheduleHealthRow);\n\n return {\n available: true,\n summary: { total, enabled, active, errored, failed, overdue },\n schedules,\n };\n } catch {\n return unavailable(table, 'read-error');\n }\n}\n\nconst SCHEDULE_COLUMNS = [\n 'id',\n 'tenant_id',\n 'agent_type',\n 'agent_id',\n 'cron',\n 'timezone',\n 'enabled',\n 'status',\n 'last_run',\n 'next_run',\n 'last_status',\n 'last_error',\n 'run_count',\n 'success_count',\n 'failure_count',\n 'max_concurrent',\n 'running_count',\n 'timeout',\n 'method',\n].join(', ');\n\nfunction toScheduleHealthRow(row: Record<string, unknown>): ScheduleHealthRow {\n return {\n id: String(row.id ?? ''),\n tenantId: toNullableString(row.tenant_id),\n agentType: String(row.agent_type ?? ''),\n agentId: toNullableString(row.agent_id),\n cron: String(row.cron ?? ''),\n timezone: String(row.timezone ?? ''),\n enabled: toBoolean(row.enabled),\n status: String(row.status ?? ''),\n lastRun: toIso(row.last_run),\n nextRun: toIso(row.next_run),\n lastStatus: toNullableString(row.last_status),\n lastError: toNullableString(row.last_error),\n runCount: toNumber(row.run_count),\n successCount: toNumber(row.success_count),\n failureCount: toNumber(row.failure_count),\n maxConcurrent: toNumber(row.max_concurrent),\n runningCount: toNumber(row.running_count),\n timeout: toNumber(row.timeout),\n method: String(row.method ?? ''),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_dispatch` row. The `payload` and `metadata`\n * columns are never selected.\n */\nexport interface DispatchHealthRow {\n id: string;\n type: string;\n source: string;\n sourceId: string | null;\n status: string;\n attempts: number | null;\n lastError: string | null;\n processedAt: string | null;\n processedBy: string | null;\n targetSubscriber: string | null;\n correlationId: string | null;\n tenantId: string | null;\n createdAt: string | null;\n updatedAt: string | null;\n}\n\n/** Safe projection of one `_smrt_dispatch_subscriptions` row. */\nexport interface DispatchSubscriptionHealthRow {\n id: string;\n signalType: string;\n subscriber: string;\n handler: string;\n delivery: string;\n enabled: boolean | null;\n tenantId: string | null;\n createdAt: string | null;\n updatedAt: string | null;\n}\n\nexport interface DispatchHealthSummary {\n total: number;\n byStatus: Record<string, number>;\n pending: number;\n processing: number;\n failed: number;\n completed: number;\n subscriptions: number;\n}\n\nexport type DispatchHealthResult =\n | {\n available: true;\n summary: DispatchHealthSummary;\n dispatches: DispatchHealthRow[];\n subscriptions: DispatchSubscriptionHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface DispatchHealthOptions {\n /** Row budget for `dispatches`/`subscriptions` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n}\n\n/** Read `_smrt_dispatch` and `_smrt_dispatch_subscriptions`. */\nexport async function readDispatchHealth(\n db: DatabaseInterface,\n options: DispatchHealthOptions = {},\n): Promise<DispatchHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.dispatch;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n\n try {\n const p = placeholders(db);\n const statusRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of statusRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const pending = byStatus.pending ?? 0;\n const processing = byStatus.processing ?? 0;\n const failed = byStatus.failed ?? 0;\n const completed = byStatus.completed ?? 0;\n\n const dispatches = queryRows(\n await db.query(\n `SELECT ${DISPATCH_COLUMNS} FROM ${table}\n ORDER BY created_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toDispatchHealthRow);\n\n let subscriptions: DispatchSubscriptionHealthRow[] = [];\n let subscriptionCount = 0;\n const subsTable = SYSTEM_DIAGNOSTICS_TABLES.dispatchSubscriptions;\n if (await tableExists(db, subsTable)) {\n // The summary count is a COUNT(*) aggregate, not the listed-page length:\n // `subscriptions` honors the row budget like every other list, so the\n // list length would silently under-report topology above the limit.\n const countRows = queryRows(\n await db.query(`SELECT COUNT(*) AS total FROM ${subsTable}`),\n );\n subscriptionCount = toCount(countRows[0]?.total);\n const subsRows = queryRows(\n await db.query(\n `SELECT ${SUBSCRIPTION_COLUMNS} FROM ${subsTable}\n ORDER BY created_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n );\n subscriptions = subsRows.map(toSubscriptionHealthRow);\n }\n\n return {\n available: true,\n summary: {\n total,\n byStatus,\n pending,\n processing,\n failed,\n completed,\n subscriptions: subscriptionCount,\n },\n dispatches,\n subscriptions,\n };\n } catch {\n return unavailable(table, 'read-error');\n }\n}\n\nconst DISPATCH_COLUMNS = [\n 'id',\n 'type',\n 'source',\n 'source_id',\n 'status',\n 'attempts',\n 'last_error',\n 'processed_at',\n 'processed_by',\n 'target_subscriber',\n 'correlation_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n].join(', ');\n\nconst SUBSCRIPTION_COLUMNS = [\n 'id',\n 'signal_type',\n 'subscriber',\n 'handler',\n 'delivery',\n 'enabled',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n].join(', ');\n\nfunction toDispatchHealthRow(row: Record<string, unknown>): DispatchHealthRow {\n return {\n id: String(row.id ?? ''),\n type: String(row.type ?? ''),\n source: String(row.source ?? ''),\n sourceId: toNullableString(row.source_id),\n status: String(row.status ?? ''),\n attempts: toNumber(row.attempts),\n lastError: toNullableString(row.last_error),\n processedAt: toIso(row.processed_at),\n processedBy: toNullableString(row.processed_by),\n targetSubscriber: toNullableString(row.target_subscriber),\n correlationId: toNullableString(row.correlation_id),\n tenantId: toNullableString(row.tenant_id),\n createdAt: toIso(row.created_at),\n updatedAt: toIso(row.updated_at),\n };\n}\n\nfunction toSubscriptionHealthRow(\n row: Record<string, unknown>,\n): DispatchSubscriptionHealthRow {\n return {\n id: String(row.id ?? ''),\n signalType: String(row.signal_type ?? ''),\n subscriber: String(row.subscriber ?? ''),\n handler: String(row.handler ?? ''),\n delivery: String(row.delivery ?? ''),\n enabled: toBoolean(row.enabled),\n tenantId: toNullableString(row.tenant_id),\n createdAt: toIso(row.created_at),\n updatedAt: toIso(row.updated_at),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Recent changes\n// ---------------------------------------------------------------------------\n\nexport type RecentChangesResult =\n | {\n available: true;\n changes: ChangeFeedEntry[];\n cursor: number;\n resyncRequired?: boolean;\n resyncCursor?: number;\n count: number;\n }\n | CategoryUnavailable;\n\nexport interface RecentChangesOptions {\n /** Cursor to read after (default 0). */\n since?: number;\n /** Restrict to these physical table names. */\n tables?: string[];\n /** Tenant narrowing filter (see the change-feed reader's tenant semantics). */\n tenantId?: string | null;\n /** Page size (default 200, capped like the change feed's own limit). */\n limit?: number;\n}\n\n/**\n * Read the recent change feed via the canonical {@link getChangesSince}\n * reader with `drain: false`, so the PostgreSQL staged-entry drain (a write\n * through `_smrt_drain_changes()`) never runs from a diagnostic. Entries still\n * staged by an open caller transaction stay invisible until a writer drains\n * them. A missing `_smrt_changes` table degrades to category-unavailable.\n */\nexport async function readRecentChanges(\n db: DatabaseInterface,\n options: RecentChangesOptions = {},\n): Promise<RecentChangesResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.changes;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n try {\n const page = await getChangesSince(db, {\n since: options.since ?? 0,\n drain: false,\n tables: options.tables,\n tenantId: options.tenantId,\n limit: boundedLimit(options.limit, 200),\n });\n return {\n available: true,\n changes: page.changes,\n cursor: page.cursor,\n ...(page.resyncRequired !== undefined\n ? {\n resyncRequired: page.resyncRequired,\n resyncCursor: page.resyncCursor,\n }\n : {}),\n count: page.changes.length,\n };\n } catch {\n return unavailable(table, 'read-error');\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registry (retired)\n// ---------------------------------------------------------------------------\n\n/**\n * `_smrt_registry` is retired: never written since system schema 1.10.1, absent\n * on new databases, and deliberately not dropped by the framework. The\n * diagnostics reader reports the retirement — it never reads registry rows and\n * never fabricates drift; `stillPresent` is a table-existence probe only, so a\n * probe failure reads as absence, which the message keeps non-committal about.\n */\nexport interface RegistryDriftResult {\n available: false;\n reason: 'retired';\n message: string;\n tableName: string;\n /** True when a legacy `_smrt_registry` table still exists on this database. */\n stillPresent: boolean;\n}\n\nexport async function readRegistryDrift(\n db: DatabaseInterface,\n): Promise<RegistryDriftResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.registry;\n const stillPresent = await tableExists(db, table);\n return {\n available: false,\n reason: 'retired',\n message: stillPresent\n ? `The ${table} table is retired (SMRT system schema ${'1.10.1'}) and is no longer written or read; a legacy empty table remains on this database and can be dropped. No registry drift is reported.`\n : `The ${table} table is retired (SMRT system schema ${'1.10.1'}) and is no longer written or read; no such table was found on this database. No registry drift is reported.`,\n tableName: table,\n stillPresent,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Facade\n// ---------------------------------------------------------------------------\n\nexport interface SystemDiagnosticsOptions {\n migrations?: MigrationStatusOptions;\n jobs?: JobHealthOptions;\n schedules?: ScheduleHealthOptions;\n dispatch?: DispatchHealthOptions;\n changes?: RecentChangesOptions;\n}\n\nexport interface SystemDiagnosticsResult {\n migrations: MigrationStatusResult;\n jobs: JobHealthResult;\n schedules: ScheduleHealthResult;\n dispatch: DispatchHealthResult;\n changes: RecentChangesResult;\n registry: RegistryDriftResult;\n}\n\n/** Read every diagnostics category in one pass (SELECT-only). */\nexport async function readSystemDiagnostics(\n db: DatabaseInterface,\n options: SystemDiagnosticsOptions = {},\n): Promise<SystemDiagnosticsResult> {\n const [migrations, jobs, schedules, dispatch, changes, registry] =\n await Promise.all([\n readMigrationStatus(db, options.migrations),\n readJobHealth(db, options.jobs),\n readScheduleHealth(db, options.schedules),\n readDispatchHealth(db, options.dispatch),\n readRecentChanges(db, options.changes),\n readRegistryDrift(db),\n ]);\n return { migrations, jobs, schedules, dispatch, changes, registry };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction unavailable(\n tableName: string,\n reason: CategoryUnavailable['reason'],\n message?: string,\n): CategoryUnavailable {\n return {\n available: false,\n reason,\n message:\n message ??\n (reason === 'retired'\n ? `${tableName} is retired and no longer read.`\n : reason === 'table-missing'\n ? `The ${tableName} system table is not present on this database.`\n : `Reading ${tableName} failed; the live diagnostic is unavailable.`),\n tableName,\n };\n}\n\nfunction boundedLimit(\n value: number | undefined,\n fallback = DIAGNOSTICS_DEFAULT_LIMIT,\n): number {\n if (!Number.isFinite(value) || value === undefined) return fallback;\n return Math.min(Math.max(Math.floor(value), 1), 500);\n}\n\nfunction placeholders(db: DatabaseInterface): (index: number) => string {\n // Engine detection follows the repo convention (`getDatabaseEngine` prefers\n // an explicit `type`/config hint, then the connection URL's scheme), so a\n // postgres URL opened without a matching `type` field still binds `$n`.\n const engine = getDatabaseEngine(db);\n return engine === 'postgres' ? (index) => `$${index}` : () => '?';\n}\n\nfunction queryRows(result: unknown): Record<string, unknown>[] {\n if (Array.isArray(result)) {\n return result as Record<string, unknown>[];\n }\n if (result && typeof result === 'object' && 'rows' in result) {\n const rows = (result as { rows?: unknown }).rows;\n if (Array.isArray(rows)) {\n return rows as Record<string, unknown>[];\n }\n }\n return [];\n}\n\nfunction toNullableString(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n return String(value);\n}\n\nfunction toNumber(value: unknown): number | null {\n if (value === null || value === undefined) return null;\n const numeric = Number(value);\n return Number.isFinite(numeric) ? numeric : null;\n}\n\n/** Count aggregation: `COUNT(*)` always yields a number (0 when absent). */\nfunction toCount(value: unknown): number {\n return toNumber(value) ?? 0;\n}\n\nfunction toBoolean(value: unknown): boolean | null {\n if (value === null || value === undefined) return null;\n if (\n value === true ||\n value === 'true' ||\n value === 't' ||\n value === '1' ||\n value === 1\n ) {\n return true;\n }\n if (\n value === false ||\n value === 'false' ||\n value === 'f' ||\n value === '0' ||\n value === 0\n ) {\n return false;\n }\n return Boolean(value);\n}\n\nfunction toIso(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n const text = String(value);\n return text.length > 0 ? text : null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"diagnostics.js","names":[],"sources":["../../src/system/diagnostics.ts"],"sourcesContent":["/**\n * SMRT system-diagnostics reader.\n *\n * A typed, SELECT-only view over the framework-owned `_smrt_*` system tables\n * for development tooling (smrt-dev-mcp runtime diagnostics). Every read is a\n * bounded SELECT against explicitly projected safe columns; sensitive columns\n * are never selected and no statement here writes.\n *\n * The reader takes a `DatabaseInterface` (connection resolution lives with the\n * caller) and degrades per category to a `CategoryUnavailable` result when its\n * table is missing or a read fails — it never throws for missing tables and\n * never fabricates data. `_smrt_registry` is a retired system table\n * ({@link RETIRED_SYSTEM_TABLES}) and is reported as retired rather than\n * queried.\n */\n\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { type ChangeFeedEntry, getChangesSince } from '../change-feed.js';\nimport { getDatabaseEngine, tableExists } from './compatibility.js';\n\n/** Physical `_smrt_*` tables the diagnostics reader knows about. */\nexport const SYSTEM_DIAGNOSTICS_TABLES = {\n migrations: '_smrt_schema_migrations',\n dispatch: '_smrt_dispatch',\n dispatchSubscriptions: '_smrt_dispatch_subscriptions',\n changes: '_smrt_changes',\n jobs: '_smrt_jobs',\n jobEvents: '_smrt_job_events',\n workers: '_smrt_workers',\n schedules: '_smrt_agent_schedules',\n registry: '_smrt_registry',\n} as const;\n\n/** Default page size for row-listing categories. */\nexport const DIAGNOSTICS_DEFAULT_LIMIT = 50;\n\n/**\n * Columns the reader wanted but the live table lacks (an older system schema).\n * Readers select only the columns that exist and report the rest here, so a\n * dev database that predates a column still answers instead of failing.\n */\nexport interface SchemaBehind {\n tableName: string;\n missingColumns: string[];\n}\n\n/**\n * A category whose table is missing (or whose read failed) on the live\n * database. Callers must surface this as \"category unavailable\" — never as an\n * empty-but-healthy category and never as a fabricated value.\n */\nexport interface CategoryUnavailable {\n available: false;\n reason: 'table-missing' | 'retired' | 'read-error';\n message: string;\n tableName?: string;\n /**\n * Driver error text for `read-error` (never a query with values). Callers\n * that surface it to a client must redact it first.\n */\n detail?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Migration status\n// ---------------------------------------------------------------------------\n\n/** Safe projection of one `_smrt_schema_migrations` row. */\nexport interface MigrationStatusRow {\n id: string;\n name: string;\n version: string;\n status: string;\n appliedAt: string | null;\n executionTimeMs: number | null;\n attempts: number | null;\n errorMessage: string | null;\n packageName: string | null;\n sourceFile: string | null;\n isReversible: boolean | null;\n rolledBackAt: string | null;\n appliedBy: string | null;\n batch: number | null;\n}\n\n/**\n * Counts use `MigrationTracker`'s real status vocabulary: `completed`\n * (successfully applied), `running` (in flight), `failed`, and `rolled_back`.\n */\nexport interface MigrationStatusSummary {\n total: number;\n byStatus: Record<string, number>;\n completed: number;\n running: number;\n failed: number;\n rolledBack: number;\n}\n\nexport type MigrationStatusResult =\n | {\n available: true;\n /** Wanted columns absent on this database (older system schema). */\n schemaBehind: SchemaBehind[];\n summary: MigrationStatusSummary;\n /** Most recently applied migrations, newest first. */\n latest: MigrationStatusRow[];\n /** Failed migrations, newest first. */\n failed: MigrationStatusRow[];\n }\n | CategoryUnavailable;\n\nexport interface MigrationStatusOptions {\n /** Row budget for `latest`/`failed` lists (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n}\n\n/**\n * Bounded, SELECT-only migration history read.\n *\n * Deliberately does not use `MigrationTracker` — its history helpers run\n * DDL-initializing bootstrap — and reads `_smrt_schema_migrations` directly.\n */\nexport async function readMigrationStatus(\n db: DatabaseInterface,\n options: MigrationStatusOptions = {},\n): Promise<MigrationStatusResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.migrations;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n\n try {\n const p = placeholders(db);\n const projection = await selectable(db, table, MIGRATION_COLUMNS_LIST);\n const countRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of countRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const completed = byStatus.completed ?? 0;\n const running = byStatus.running ?? 0;\n const failed = byStatus.failed ?? 0;\n const rolledBack = byStatus.rolled_back ?? 0;\n\n const latest = queryRows(\n await db.query(\n `SELECT ${projection.columns} FROM ${table}\n WHERE status = 'completed'\n ORDER BY applied_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toMigrationRow);\n\n const failedRows = queryRows(\n await db.query(\n `SELECT ${projection.columns} FROM ${table}\n WHERE status = 'failed'\n ORDER BY applied_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toMigrationRow);\n\n return {\n available: true,\n schemaBehind: projection.behind ? [projection.behind] : [],\n summary: { total, byStatus, completed, running, failed, rolledBack },\n latest,\n failed: failedRows,\n };\n } catch (error) {\n return unavailable(table, 'read-error', undefined, readError(error));\n }\n}\n\nconst MIGRATION_COLUMNS_LIST = [\n 'id',\n 'name',\n 'version',\n 'status',\n 'applied_at',\n 'execution_time_ms',\n 'attempts',\n 'error_message',\n 'package_name',\n 'source_file',\n 'is_reversible',\n 'rolled_back_at',\n 'applied_by',\n 'batch',\n] as const;\nconst MIGRATION_COLUMNS = MIGRATION_COLUMNS_LIST.join(', ');\n\nfunction toMigrationRow(row: Record<string, unknown>): MigrationStatusRow {\n return {\n id: String(row.id ?? ''),\n name: String(row.name ?? ''),\n version: String(row.version ?? ''),\n status: String(row.status ?? ''),\n appliedAt: toIso(row.applied_at),\n executionTimeMs: toNumber(row.execution_time_ms),\n attempts: toNumber(row.attempts),\n errorMessage: toNullableString(row.error_message),\n packageName: toNullableString(row.package_name),\n sourceFile: toNullableString(row.source_file),\n isReversible: toBoolean(row.is_reversible),\n rolledBackAt: toIso(row.rolled_back_at),\n appliedBy: toNullableString(row.applied_by),\n batch: toNumber(row.batch),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Job health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_jobs` row. Job payload/result columns\n * (`args`, `task_result`, `task_input_requests`, `task_input_responses`,\n * `result_pointer`) are deliberately never selected.\n */\nexport interface JobHealthRow {\n id: string;\n tenantId: string | null;\n queue: string;\n objectType: string;\n objectId: string | null;\n method: string;\n status: string;\n attempts: number | null;\n maxAttempts: number | null;\n runAt: string | null;\n startedAt: string | null;\n completedAt: string | null;\n lastError: string | null;\n taskId: string | null;\n taskOwnerId: string | null;\n workerId: string | null;\n workerHeartbeat: string | null;\n}\n\n/** Safe projection of one `_smrt_workers` row (no sensitive columns). */\nexport interface WorkerHealthRow {\n id: string;\n workerId: string;\n pid: number | null;\n hostname: string | null;\n startedAt: string | null;\n heartbeatAt: string | null;\n leaseExpiresAt: string | null;\n status: string;\n}\n\nexport interface JobHealthSummary {\n total: number;\n byStatus: Record<string, number>;\n failed: number;\n /** `running` jobs whose heartbeat is older than the stale threshold. */\n stuck: number;\n /** Total rows in `_smrt_job_events`, or null when that table is missing. */\n eventCount: number | null;\n}\n\nexport type JobHealthResult =\n | {\n available: true;\n /** Wanted columns absent on this database (older system schema). */\n schemaBehind: SchemaBehind[];\n summary: JobHealthSummary;\n jobs: JobHealthRow[];\n workers: WorkerHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface JobHealthOptions {\n /** Row budget for `jobs` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n /**\n * Heartbeat older than this (ms) marks a `running` job as stuck — unless\n * its owning worker still holds a fresh `_smrt_workers` lease.\n */\n staleAfterMs?: number;\n /** Clock for staleness computations (defaults to `new Date()`). */\n now?: Date;\n}\n\n/** Read `_smrt_jobs`, `_smrt_workers`, and a `_smrt_job_events` count. */\nexport async function readJobHealth(\n db: DatabaseInterface,\n options: JobHealthOptions = {},\n): Promise<JobHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.jobs;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n const staleAfterMs = options.staleAfterMs ?? 5 * 60_000;\n const now = options.now ?? new Date();\n const staleCutoff = new Date(now.getTime() - staleAfterMs);\n\n try {\n const p = placeholders(db);\n const statusRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of statusRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const failed = byStatus.failed ?? 0;\n\n // Mirror the runner's recovery rule (#1474): a stale per-job heartbeat is\n // only telemetry. A running job whose worker still holds a fresh liveness\n // lease in `_smrt_workers` (renewed off the handler event loop) is alive\n // even while its handler blocks, so it is not stuck. Without the workers\n // table liveness is unknowable and the heartbeat is the only signal.\n const workersTable = SYSTEM_DIAGNOSTICS_TABLES.workers;\n const workersAvailable = await tableExists(db, workersTable);\n const jobProjection = await selectable(db, table, JOB_COLUMNS_LIST);\n const workerProjection = workersAvailable\n ? await selectable(db, workersTable, WORKER_COLUMNS_LIST)\n : { columns: WORKER_COLUMNS, behind: null };\n const jobsHaveHeartbeat =\n !jobProjection.behind?.missingColumns.includes('worker_heartbeat');\n const canJoinLease =\n workersAvailable &&\n !jobProjection.behind?.missingColumns.includes('worker_id') &&\n !workerProjection.behind?.missingColumns.includes('lease_expires_at');\n const stuckRows = !jobsHaveHeartbeat\n ? [{ total: 0 }]\n : queryRows(\n canJoinLease\n ? await db.query(\n `SELECT COUNT(*) AS total FROM ${table} AS j\n WHERE j.status = 'running'\n AND (j.worker_heartbeat IS NULL OR j.worker_heartbeat < ${p(1)})\n AND NOT EXISTS (\n SELECT 1 FROM ${workersTable} AS w\n WHERE w.worker_id = j.worker_id\n AND w.lease_expires_at > ${p(2)}\n )`,\n staleCutoff.toISOString(),\n now.toISOString(),\n )\n : await db.query(\n `SELECT COUNT(*) AS total FROM ${table}\n WHERE status = 'running'\n AND (worker_heartbeat IS NULL OR worker_heartbeat < ${p(1)})`,\n staleCutoff.toISOString(),\n ),\n );\n const stuck = toCount(stuckRows[0]?.total);\n\n const jobs = queryRows(\n await db.query(\n `SELECT ${JOB_COLUMNS} FROM ${table}\n ORDER BY run_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toJobHealthRow);\n\n let workers: WorkerHealthRow[] = [];\n if (workersAvailable) {\n workers = queryRows(\n await db.query(\n `SELECT ${workerProjection.columns} FROM ${workersTable}\n ORDER BY heartbeat_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toWorkerHealthRow);\n }\n\n let eventCount: number | null = null;\n const eventsTable = SYSTEM_DIAGNOSTICS_TABLES.jobEvents;\n if (await tableExists(db, eventsTable)) {\n const eventRows = queryRows(\n await db.query(`SELECT COUNT(*) AS total FROM ${eventsTable}`),\n );\n eventCount = toNumber(eventRows[0]?.total);\n }\n\n return {\n available: true,\n schemaBehind: [jobProjection.behind, workerProjection.behind].filter(\n (behind): behind is SchemaBehind => behind !== null,\n ),\n summary: { total, byStatus, failed, stuck, eventCount },\n jobs,\n workers,\n };\n } catch (error) {\n return unavailable(table, 'read-error', undefined, readError(error));\n }\n}\n\nconst JOB_COLUMNS_LIST = [\n 'id',\n 'tenant_id',\n 'queue',\n 'object_type',\n 'object_id',\n 'method',\n 'status',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'started_at',\n 'completed_at',\n 'last_error',\n 'task_id',\n 'task_owner_id',\n 'worker_id',\n 'worker_heartbeat',\n] as const;\nconst JOB_COLUMNS = JOB_COLUMNS_LIST.join(', ');\n\nconst WORKER_COLUMNS_LIST = [\n 'id',\n 'worker_id',\n 'pid',\n 'hostname',\n 'started_at',\n 'heartbeat_at',\n 'lease_expires_at',\n 'status',\n] as const;\nconst WORKER_COLUMNS = WORKER_COLUMNS_LIST.join(', ');\n\nfunction toJobHealthRow(row: Record<string, unknown>): JobHealthRow {\n return {\n id: String(row.id ?? ''),\n tenantId: toNullableString(row.tenant_id),\n queue: String(row.queue ?? ''),\n objectType: String(row.object_type ?? ''),\n objectId: toNullableString(row.object_id),\n method: String(row.method ?? ''),\n status: String(row.status ?? ''),\n attempts: toNumber(row.attempts),\n maxAttempts: toNumber(row.max_attempts),\n runAt: toIso(row.run_at),\n startedAt: toIso(row.started_at),\n completedAt: toIso(row.completed_at),\n lastError: toNullableString(row.last_error),\n taskId: toNullableString(row.task_id),\n taskOwnerId: toNullableString(row.task_owner_id),\n workerId: toNullableString(row.worker_id),\n workerHeartbeat: toIso(row.worker_heartbeat),\n };\n}\n\nfunction toWorkerHealthRow(row: Record<string, unknown>): WorkerHealthRow {\n return {\n id: String(row.id ?? ''),\n workerId: String(row.worker_id ?? ''),\n pid: toNumber(row.pid),\n hostname: toNullableString(row.hostname),\n startedAt: toIso(row.started_at),\n heartbeatAt: toIso(row.heartbeat_at),\n leaseExpiresAt: toIso(row.lease_expires_at),\n status: String(row.status ?? ''),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Schedule health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_agent_schedules` row. The sensitive\n * `agent_config` and `method_args` columns are never selected.\n */\nexport interface ScheduleHealthRow {\n id: string;\n tenantId: string | null;\n agentType: string;\n agentId: string | null;\n cron: string;\n timezone: string;\n enabled: boolean | null;\n status: string;\n lastRun: string | null;\n nextRun: string | null;\n lastStatus: string | null;\n lastError: string | null;\n runCount: number | null;\n successCount: number | null;\n failureCount: number | null;\n maxConcurrent: number | null;\n runningCount: number | null;\n timeout: number | null;\n method: string;\n}\n\nexport interface ScheduleHealthSummary {\n total: number;\n enabled: number;\n active: number;\n errored: number;\n /** Schedules whose last run failed (`last_status = 'failed'`). */\n failed: number;\n /** Enabled, active schedules whose `next_run` is due (<= now). */\n overdue: number;\n}\n\nexport type ScheduleHealthResult =\n | {\n available: true;\n /** Wanted columns absent on this database (older system schema). */\n schemaBehind: SchemaBehind[];\n summary: ScheduleHealthSummary;\n schedules: ScheduleHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface ScheduleHealthOptions {\n /** Row budget for `schedules` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n /** Clock for overdue computation (defaults to `new Date()`). */\n now?: Date;\n}\n\n/** Read `_smrt_agent_schedules` (owned by `@happyvertical/smrt-agents`). */\nexport async function readScheduleHealth(\n db: DatabaseInterface,\n options: ScheduleHealthOptions = {},\n): Promise<ScheduleHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.schedules;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n const now = options.now ?? new Date();\n\n try {\n const p = placeholders(db);\n const projection = await selectable(db, table, SCHEDULE_COLUMNS_LIST);\n const enabledRows = queryRows(\n await db.query(\n `SELECT enabled, status, COUNT(*) AS total FROM ${table} GROUP BY enabled, status`,\n ),\n );\n let total = 0;\n let enabled = 0;\n let active = 0;\n let errored = 0;\n for (const row of enabledRows) {\n const count = toCount(row.total);\n total += count;\n if (toBoolean(row.enabled)) enabled += count;\n const status = String(row.status ?? '');\n if (status === 'active') active += count;\n if (status === 'error') errored += count;\n }\n\n const failedRows = queryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table} WHERE last_status = 'failed'`,\n ),\n );\n const failed = toCount(failedRows[0]?.total);\n\n const overdueRows = queryRows(\n await db.query(\n `SELECT COUNT(*) AS total FROM ${table}\n WHERE enabled = true AND status = 'active' AND next_run IS NOT NULL AND next_run <= ${p(1)}`,\n now.toISOString(),\n ),\n );\n const overdue = toCount(overdueRows[0]?.total);\n\n const schedules = queryRows(\n await db.query(\n `SELECT ${projection.columns} FROM ${table}\n ORDER BY next_run ASC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toScheduleHealthRow);\n\n return {\n available: true,\n schemaBehind: projection.behind ? [projection.behind] : [],\n summary: { total, enabled, active, errored, failed, overdue },\n schedules,\n };\n } catch (error) {\n return unavailable(table, 'read-error', undefined, readError(error));\n }\n}\n\nconst SCHEDULE_COLUMNS_LIST = [\n 'id',\n 'tenant_id',\n 'agent_type',\n 'agent_id',\n 'cron',\n 'timezone',\n 'enabled',\n 'status',\n 'last_run',\n 'next_run',\n 'last_status',\n 'last_error',\n 'run_count',\n 'success_count',\n 'failure_count',\n 'max_concurrent',\n 'running_count',\n 'timeout',\n 'method',\n] as const;\nconst SCHEDULE_COLUMNS = SCHEDULE_COLUMNS_LIST.join(', ');\n\nfunction toScheduleHealthRow(row: Record<string, unknown>): ScheduleHealthRow {\n return {\n id: String(row.id ?? ''),\n tenantId: toNullableString(row.tenant_id),\n agentType: String(row.agent_type ?? ''),\n agentId: toNullableString(row.agent_id),\n cron: String(row.cron ?? ''),\n timezone: String(row.timezone ?? ''),\n enabled: toBoolean(row.enabled),\n status: String(row.status ?? ''),\n lastRun: toIso(row.last_run),\n nextRun: toIso(row.next_run),\n lastStatus: toNullableString(row.last_status),\n lastError: toNullableString(row.last_error),\n runCount: toNumber(row.run_count),\n successCount: toNumber(row.success_count),\n failureCount: toNumber(row.failure_count),\n maxConcurrent: toNumber(row.max_concurrent),\n runningCount: toNumber(row.running_count),\n timeout: toNumber(row.timeout),\n method: String(row.method ?? ''),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Dispatch health\n// ---------------------------------------------------------------------------\n\n/**\n * Safe projection of one `_smrt_dispatch` row. The `payload` and `metadata`\n * columns are never selected.\n */\nexport interface DispatchHealthRow {\n id: string;\n type: string;\n source: string;\n sourceId: string | null;\n status: string;\n attempts: number | null;\n lastError: string | null;\n processedAt: string | null;\n processedBy: string | null;\n targetSubscriber: string | null;\n correlationId: string | null;\n tenantId: string | null;\n createdAt: string | null;\n updatedAt: string | null;\n}\n\n/** Safe projection of one `_smrt_dispatch_subscriptions` row. */\nexport interface DispatchSubscriptionHealthRow {\n id: string;\n signalType: string;\n subscriber: string;\n handler: string;\n delivery: string;\n enabled: boolean | null;\n tenantId: string | null;\n createdAt: string | null;\n updatedAt: string | null;\n}\n\nexport interface DispatchHealthSummary {\n total: number;\n byStatus: Record<string, number>;\n pending: number;\n processing: number;\n failed: number;\n completed: number;\n subscriptions: number;\n}\n\nexport type DispatchHealthResult =\n | {\n available: true;\n /** Wanted columns absent on this database (older system schema). */\n schemaBehind: SchemaBehind[];\n summary: DispatchHealthSummary;\n dispatches: DispatchHealthRow[];\n subscriptions: DispatchSubscriptionHealthRow[];\n }\n | CategoryUnavailable;\n\nexport interface DispatchHealthOptions {\n /** Row budget for `dispatches`/`subscriptions` (default {@link DIAGNOSTICS_DEFAULT_LIMIT}). */\n limit?: number;\n}\n\n/** Read `_smrt_dispatch` and `_smrt_dispatch_subscriptions`. */\nexport async function readDispatchHealth(\n db: DatabaseInterface,\n options: DispatchHealthOptions = {},\n): Promise<DispatchHealthResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.dispatch;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n const limit = boundedLimit(options.limit);\n\n try {\n const p = placeholders(db);\n const projection = await selectable(db, table, DISPATCH_COLUMNS_LIST);\n const statusRows = queryRows(\n await db.query(\n `SELECT status, COUNT(*) AS total FROM ${table} GROUP BY status`,\n ),\n );\n const byStatus: Record<string, number> = {};\n let total = 0;\n for (const row of statusRows) {\n const status = String(row.status ?? 'unknown');\n const count = toCount(row.total);\n byStatus[status] = count;\n total += count;\n }\n const pending = byStatus.pending ?? 0;\n const processing = byStatus.processing ?? 0;\n const failed = byStatus.failed ?? 0;\n const completed = byStatus.completed ?? 0;\n\n const dispatches = queryRows(\n await db.query(\n `SELECT ${projection.columns} FROM ${table}\n ORDER BY created_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n ).map(toDispatchHealthRow);\n\n let subscriptions: DispatchSubscriptionHealthRow[] = [];\n let subscriptionCount = 0;\n const subsTable = SYSTEM_DIAGNOSTICS_TABLES.dispatchSubscriptions;\n let subsProjection: Awaited<ReturnType<typeof selectable>> | null = null;\n if (await tableExists(db, subsTable)) {\n subsProjection = await selectable(\n db,\n subsTable,\n SUBSCRIPTION_COLUMNS_LIST,\n );\n // The summary count is a COUNT(*) aggregate, not the listed-page length:\n // `subscriptions` honors the row budget like every other list, so the\n // list length would silently under-report topology above the limit.\n const countRows = queryRows(\n await db.query(`SELECT COUNT(*) AS total FROM ${subsTable}`),\n );\n subscriptionCount = toCount(countRows[0]?.total);\n const subsRows = queryRows(\n await db.query(\n `SELECT ${subsProjection.columns} FROM ${subsTable}\n ORDER BY created_at DESC, id ASC\n LIMIT ${p(1)}`,\n limit,\n ),\n );\n subscriptions = subsRows.map(toSubscriptionHealthRow);\n }\n\n return {\n available: true,\n schemaBehind: [projection.behind, subsProjection?.behind ?? null].filter(\n (behind): behind is SchemaBehind => behind !== null,\n ),\n summary: {\n total,\n byStatus,\n pending,\n processing,\n failed,\n completed,\n subscriptions: subscriptionCount,\n },\n dispatches,\n subscriptions,\n };\n } catch (error) {\n return unavailable(table, 'read-error', undefined, readError(error));\n }\n}\n\nconst DISPATCH_COLUMNS_LIST = [\n 'id',\n 'type',\n 'source',\n 'source_id',\n 'status',\n 'attempts',\n 'last_error',\n 'processed_at',\n 'processed_by',\n 'target_subscriber',\n 'correlation_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n] as const;\nconst DISPATCH_COLUMNS = DISPATCH_COLUMNS_LIST.join(', ');\n\nconst SUBSCRIPTION_COLUMNS_LIST = [\n 'id',\n 'signal_type',\n 'subscriber',\n 'handler',\n 'delivery',\n 'enabled',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n] as const;\nconst SUBSCRIPTION_COLUMNS = SUBSCRIPTION_COLUMNS_LIST.join(', ');\n\nfunction toDispatchHealthRow(row: Record<string, unknown>): DispatchHealthRow {\n return {\n id: String(row.id ?? ''),\n type: String(row.type ?? ''),\n source: String(row.source ?? ''),\n sourceId: toNullableString(row.source_id),\n status: String(row.status ?? ''),\n attempts: toNumber(row.attempts),\n lastError: toNullableString(row.last_error),\n processedAt: toIso(row.processed_at),\n processedBy: toNullableString(row.processed_by),\n targetSubscriber: toNullableString(row.target_subscriber),\n correlationId: toNullableString(row.correlation_id),\n tenantId: toNullableString(row.tenant_id),\n createdAt: toIso(row.created_at),\n updatedAt: toIso(row.updated_at),\n };\n}\n\nfunction toSubscriptionHealthRow(\n row: Record<string, unknown>,\n): DispatchSubscriptionHealthRow {\n return {\n id: String(row.id ?? ''),\n signalType: String(row.signal_type ?? ''),\n subscriber: String(row.subscriber ?? ''),\n handler: String(row.handler ?? ''),\n delivery: String(row.delivery ?? ''),\n enabled: toBoolean(row.enabled),\n tenantId: toNullableString(row.tenant_id),\n createdAt: toIso(row.created_at),\n updatedAt: toIso(row.updated_at),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Recent changes\n// ---------------------------------------------------------------------------\n\nexport type RecentChangesResult =\n | {\n available: true;\n changes: ChangeFeedEntry[];\n cursor: number;\n resyncRequired?: boolean;\n resyncCursor?: number;\n count: number;\n }\n | CategoryUnavailable;\n\nexport interface RecentChangesOptions {\n /** Cursor to read after (default 0). */\n since?: number;\n /** Restrict to these physical table names. */\n tables?: string[];\n /** Tenant narrowing filter (see the change-feed reader's tenant semantics). */\n tenantId?: string | null;\n /** Page size (default 200, capped like the change feed's own limit). */\n limit?: number;\n}\n\n/**\n * Read the recent change feed via the canonical {@link getChangesSince}\n * reader with `drain: false`, so the PostgreSQL staged-entry drain (a write\n * through `_smrt_drain_changes()`) never runs from a diagnostic. Entries still\n * staged by an open caller transaction stay invisible until a writer drains\n * them. A missing `_smrt_changes` table degrades to category-unavailable.\n */\nexport async function readRecentChanges(\n db: DatabaseInterface,\n options: RecentChangesOptions = {},\n): Promise<RecentChangesResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.changes;\n if (!(await tableExists(db, table))) {\n return unavailable(table, 'table-missing');\n }\n try {\n const page = await getChangesSince(db, {\n since: options.since ?? 0,\n drain: false,\n tables: options.tables,\n tenantId: options.tenantId,\n limit: boundedLimit(options.limit, 200),\n });\n return {\n available: true,\n changes: page.changes,\n cursor: page.cursor,\n ...(page.resyncRequired !== undefined\n ? {\n resyncRequired: page.resyncRequired,\n resyncCursor: page.resyncCursor,\n }\n : {}),\n count: page.changes.length,\n };\n } catch (error) {\n return unavailable(table, 'read-error', undefined, readError(error));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registry (retired)\n// ---------------------------------------------------------------------------\n\n/**\n * `_smrt_registry` is retired: never written since system schema 1.10.1, absent\n * on new databases, and deliberately not dropped by the framework. The\n * diagnostics reader reports the retirement — it never reads registry rows and\n * never fabricates drift; `stillPresent` is a table-existence probe only, so a\n * probe failure reads as absence, which the message keeps non-committal about.\n */\nexport interface RegistryDriftResult {\n available: false;\n reason: 'retired';\n message: string;\n tableName: string;\n /** True when a legacy `_smrt_registry` table still exists on this database. */\n stillPresent: boolean;\n}\n\nexport async function readRegistryDrift(\n db: DatabaseInterface,\n): Promise<RegistryDriftResult> {\n const table = SYSTEM_DIAGNOSTICS_TABLES.registry;\n const stillPresent = await tableExists(db, table);\n return {\n available: false,\n reason: 'retired',\n message: stillPresent\n ? `The ${table} table is retired (SMRT system schema ${'1.10.1'}) and is no longer written or read; a legacy empty table remains on this database and can be dropped. No registry drift is reported.`\n : `The ${table} table is retired (SMRT system schema ${'1.10.1'}) and is no longer written or read; no such table was found on this database. No registry drift is reported.`,\n tableName: table,\n stillPresent,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Facade\n// ---------------------------------------------------------------------------\n\nexport interface SystemDiagnosticsOptions {\n migrations?: MigrationStatusOptions;\n jobs?: JobHealthOptions;\n schedules?: ScheduleHealthOptions;\n dispatch?: DispatchHealthOptions;\n changes?: RecentChangesOptions;\n}\n\nexport interface SystemDiagnosticsResult {\n migrations: MigrationStatusResult;\n jobs: JobHealthResult;\n schedules: ScheduleHealthResult;\n dispatch: DispatchHealthResult;\n changes: RecentChangesResult;\n registry: RegistryDriftResult;\n}\n\n/** Read every diagnostics category in one pass (SELECT-only). */\nexport async function readSystemDiagnostics(\n db: DatabaseInterface,\n options: SystemDiagnosticsOptions = {},\n): Promise<SystemDiagnosticsResult> {\n const [migrations, jobs, schedules, dispatch, changes, registry] =\n await Promise.all([\n readMigrationStatus(db, options.migrations),\n readJobHealth(db, options.jobs),\n readScheduleHealth(db, options.schedules),\n readDispatchHealth(db, options.dispatch),\n readRecentChanges(db, options.changes),\n readRegistryDrift(db),\n ]);\n return { migrations, jobs, schedules, dispatch, changes, registry };\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction unavailable(\n tableName: string,\n reason: CategoryUnavailable['reason'],\n message?: string,\n detail?: string,\n): CategoryUnavailable {\n return {\n available: false,\n reason,\n ...(detail ? { detail } : {}),\n message:\n message ??\n (reason === 'retired'\n ? `${tableName} is retired and no longer read.`\n : reason === 'table-missing'\n ? `The ${tableName} system table is not present on this database.`\n : `Reading ${tableName} failed; the live diagnostic is unavailable.`),\n tableName,\n };\n}\n\n/** Column names of a table, lower-cased; empty when the probe fails. */\nasync function tableColumns(\n db: DatabaseInterface,\n tableName: string,\n): Promise<Set<string>> {\n try {\n const engine = getDatabaseEngine(db);\n const rows = queryRows(\n engine === 'postgres'\n ? await db.query(\n 'SELECT column_name AS name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = $1',\n tableName,\n )\n : await db.query(`PRAGMA table_info(${tableName})`),\n );\n return new Set(\n rows\n .map((row) => String(row.name ?? '').toLowerCase())\n .filter((name) => name.length > 0),\n );\n } catch {\n return new Set();\n }\n}\n\n/**\n * Intersect a reader's wanted projection with the live table. Returns the\n * SELECT list to use plus a `SchemaBehind` record when anything is missing.\n * If the probe itself fails (empty set) the full projection is used so the\n * read error, not a false schema-behind, surfaces.\n */\nasync function selectable(\n db: DatabaseInterface,\n tableName: string,\n wanted: readonly string[],\n): Promise<{ columns: string; behind: SchemaBehind | null }> {\n const present = await tableColumns(db, tableName);\n if (present.size === 0) {\n return { columns: wanted.join(', '), behind: null };\n }\n const have = wanted.filter((column) => present.has(column));\n const missing = wanted.filter((column) => !present.has(column));\n return {\n columns: (have.length > 0 ? have : wanted).join(', '),\n behind: missing.length > 0 ? { tableName, missingColumns: missing } : null,\n };\n}\n\nfunction readError(error: unknown): string {\n return error instanceof Error ? error.message : String(error ?? 'unknown');\n}\n\nfunction boundedLimit(\n value: number | undefined,\n fallback = DIAGNOSTICS_DEFAULT_LIMIT,\n): number {\n if (!Number.isFinite(value) || value === undefined) return fallback;\n return Math.min(Math.max(Math.floor(value), 1), 500);\n}\n\nfunction placeholders(db: DatabaseInterface): (index: number) => string {\n // Engine detection follows the repo convention (`getDatabaseEngine` prefers\n // an explicit `type`/config hint, then the connection URL's scheme), so a\n // postgres URL opened without a matching `type` field still binds `$n`.\n const engine = getDatabaseEngine(db);\n return engine === 'postgres' ? (index) => `$${index}` : () => '?';\n}\n\nfunction queryRows(result: unknown): Record<string, unknown>[] {\n if (Array.isArray(result)) {\n return result as Record<string, unknown>[];\n }\n if (result && typeof result === 'object' && 'rows' in result) {\n const rows = (result as { rows?: unknown }).rows;\n if (Array.isArray(rows)) {\n return rows as Record<string, unknown>[];\n }\n }\n return [];\n}\n\nfunction toNullableString(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n return String(value);\n}\n\nfunction toNumber(value: unknown): number | null {\n if (value === null || value === undefined) return null;\n const numeric = Number(value);\n return Number.isFinite(numeric) ? numeric : null;\n}\n\n/** Count aggregation: `COUNT(*)` always yields a number (0 when absent). */\nfunction toCount(value: unknown): number {\n return toNumber(value) ?? 0;\n}\n\nfunction toBoolean(value: unknown): boolean | null {\n if (value === null || value === undefined) return null;\n if (\n value === true ||\n value === 'true' ||\n value === 't' ||\n value === '1' ||\n value === 1\n ) {\n return true;\n }\n if (\n value === false ||\n value === 'false' ||\n value === 'f' ||\n value === '0' ||\n value === 0\n ) {\n return false;\n }\n return Boolean(value);\n}\n\nfunction toIso(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value.toISOString();\n }\n const text = String(value);\n return text.length > 0 ? text : null;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAQ9E,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,WAItC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,QAK/C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,UAK/C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,EAC/C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,oCAmCjC;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAa5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAEhC,SAAS,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,CAAC,UAiB7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,CAAC,CAqIxD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,2BAc1D"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG3C,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAQ9E,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,WAItC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,QAK/C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,UAK/C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,EAC/C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,oCA6CjC;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAa5E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAEhC,SAAS,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,UAAU,CAAC,UAiB7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,CAAC,CAqIxD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,2BAc1D"}
package/dist/utils.js CHANGED
@@ -104,7 +104,8 @@ function dateAsObject(date) {
104
104
  * ```
105
105
  */
106
106
  async function fieldsFromClass(ClassType, values) {
107
- const className = ObjectRegistry.getClassByConstructor(ClassType)?.name || ClassType.name;
107
+ const registered = ObjectRegistry.getClassByConstructor(ClassType);
108
+ const className = registered?.qualifiedName && registered.name && ObjectRegistry.findClassesByName(registered.name).length > 1 ? registered.qualifiedName : registered?.name ?? ClassType.name;
108
109
  const cachedFields = await ObjectRegistry.getAllFields(className);
109
110
  if (cachedFields.size === 0) return {};
110
111
  const fields = {};