@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.
- package/agents/generators.md +12 -0
- package/agents/object-runtime.md +7 -0
- package/agents/schema-paths.md +62 -0
- package/agents/system-diagnostics.md +6 -1
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +9 -6
- package/dist/cascade.js.map +1 -1
- package/dist/collection.d.ts +0 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +35 -45
- package/dist/collection.js.map +1 -1
- package/dist/decorators/compatibility.d.ts +1 -1
- package/dist/decorators/compatibility.d.ts.map +1 -1
- package/dist/decorators/compatibility.js +2 -2
- package/dist/decorators/compatibility.js.map +1 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +33 -24
- package/dist/decorators/index.js.map +1 -1
- package/dist/interceptors.d.ts +3 -1
- package/dist/interceptors.d.ts.map +1 -1
- package/dist/interceptors.js +3 -2
- package/dist/interceptors.js.map +1 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/differ.d.ts +10 -4
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +173 -5
- package/dist/migrations/differ.js.map +1 -1
- package/dist/object.d.ts +1 -5
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +28 -37
- package/dist/object.js.map +1 -1
- package/dist/registry/class-registration.d.ts +2 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +47 -16
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/name-resolver.d.ts.map +1 -1
- package/dist/registry/name-resolver.js +3 -3
- package/dist/registry/name-resolver.js.map +1 -1
- package/dist/registry/relationship-graph.d.ts +2 -0
- package/dist/registry/relationship-graph.d.ts.map +1 -1
- package/dist/registry/relationship-graph.js +60 -5
- package/dist/registry/relationship-graph.js.map +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +4 -1
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/shared-state.d.ts +18 -0
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +25 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry/types.d.ts +8 -0
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +32 -5
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +120 -12
- package/dist/registry.js.map +1 -1
- package/dist/relationship-loader.d.ts +6 -0
- package/dist/relationship-loader.d.ts.map +1 -0
- package/dist/relationship-loader.js +35 -0
- package/dist/relationship-loader.js.map +1 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +6 -3
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/schema/foreign-key-ddl.d.ts +8 -0
- package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
- package/dist/schema/foreign-key-ddl.js +12 -1
- package/dist/schema/foreign-key-ddl.js.map +1 -1
- package/dist/schema/foreign-key-orphan-report.d.ts.map +1 -1
- package/dist/schema/foreign-key-orphan-report.js +18 -1
- package/dist/schema/foreign-key-orphan-report.js.map +1 -1
- package/dist/schema/generator.d.ts +2 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +16 -9
- package/dist/schema/generator.js.map +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/schema/live-parity.d.ts.map +1 -1
- package/dist/schema/live-parity.js +74 -0
- package/dist/schema/live-parity.js.map +1 -1
- package/dist/schema/text-cast-probe.d.ts +66 -0
- package/dist/schema/text-cast-probe.d.ts.map +1 -0
- package/dist/schema/text-cast-probe.js +142 -0
- package/dist/schema/text-cast-probe.js.map +1 -0
- package/dist/schema/types.d.ts +28 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/utils.d.ts.map +1 -1
- package/dist/schema/utils.js +2 -1
- package/dist/schema/utils.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/smrt-knowledge.json +11 -11
- package/dist/system/diagnostics.d.ts +22 -0
- package/dist/system/diagnostics.d.ts.map +1 -1
- package/dist/system/diagnostics.js +88 -31
- package/dist/system/diagnostics.js.map +1 -1
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite-plugin/dev-plane-route.d.ts +5 -0
- package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -0
- package/dist/vite-plugin/dev-plane-route.js +91 -0
- package/dist/vite-plugin/dev-plane-route.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts +8 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +2 -0
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- 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 ${
|
|
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 ${
|
|
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
|
|
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
|
-
]
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
-
]
|
|
179
|
-
var
|
|
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
|
-
]
|
|
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 ${
|
|
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
|
|
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
|
-
]
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
-
]
|
|
375
|
-
|
|
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
|
-
]
|
|
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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"}
|
package/dist/test-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,IAAI,EAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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 = {};
|