@zintrust/trace 1.8.0 → 1.8.1
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/dist/build-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"buildDate": "2026-
|
|
3
|
+
"version": "1.6.4",
|
|
4
|
+
"buildDate": "2026-04-30T15:30:37.643Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
|
-
"node": "
|
|
7
|
-
"platform": "
|
|
8
|
-
"arch": "
|
|
6
|
+
"node": "v25.6.1",
|
|
7
|
+
"platform": "darwin",
|
|
8
|
+
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
12
|
-
"branch": "
|
|
11
|
+
"commit": "f235df8b",
|
|
12
|
+
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
15
15
|
"engines": {
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"size": 4640,
|
|
30
30
|
"sha256": "c51cc312046b6b2bbe1673f1ff9508425cc7140a1d2341907f67aa36069c09f9"
|
|
31
31
|
},
|
|
32
|
+
"build-manifest.json": {
|
|
33
|
+
"size": 15912,
|
|
34
|
+
"sha256": "6732e16044ffbcb77bf9873cf30a671007b5acc4aa2f3edf5071da667d4df534"
|
|
35
|
+
},
|
|
32
36
|
"cli-register.d.ts": {
|
|
33
37
|
"size": 255,
|
|
34
38
|
"sha256": "da8d689fe5ef32e97e755f28017e4d3cb1aa63489073a71907ea41ad5761ede9"
|
|
@@ -83,7 +87,7 @@
|
|
|
83
87
|
},
|
|
84
88
|
"index.js": {
|
|
85
89
|
"size": 3421,
|
|
86
|
-
"sha256": "
|
|
90
|
+
"sha256": "ee5aca20d1e6d980fa0b2c02e4832c35e8a18fff9fe63a7bf6a26ae8f734d377"
|
|
87
91
|
},
|
|
88
92
|
"ingest/TraceIngestGateway.d.ts": {
|
|
89
93
|
"size": 786,
|
|
@@ -149,6 +153,14 @@
|
|
|
149
153
|
"size": 19680,
|
|
150
154
|
"sha256": "042a7585e68a8a676e25f33c9a176c6f263bccb4a63090b4d2cea52b9bcdd193"
|
|
151
155
|
},
|
|
156
|
+
"storage/DebuggerStorage.d.ts": {
|
|
157
|
+
"size": 517,
|
|
158
|
+
"sha256": "c9c215aaa414f7b0c1fec6c82b054fc52bdf97af58f96f35c7f96672fb859c31"
|
|
159
|
+
},
|
|
160
|
+
"storage/DebuggerStorage.js": {
|
|
161
|
+
"size": 7442,
|
|
162
|
+
"sha256": "5ecce0fcfcf695df587a7b90a7a5c7efd2e64ad13c9f2d104b392f89f34f0dc4"
|
|
163
|
+
},
|
|
152
164
|
"storage/ProxyTraceStorage.d.ts": {
|
|
153
165
|
"size": 339,
|
|
154
166
|
"sha256": "9c724ff342dfe82da12e7cce95593f6623faa80c40f97593719b8e78e95f266d"
|
|
@@ -354,8 +366,8 @@
|
|
|
354
366
|
"sha256": "ce9a95a670f755193fd74ce721dbfa4b30f20c879a6566ebb35229b3b2435429"
|
|
355
367
|
},
|
|
356
368
|
"watchers/HttpWatcher.js": {
|
|
357
|
-
"size":
|
|
358
|
-
"sha256": "
|
|
369
|
+
"size": 6152,
|
|
370
|
+
"sha256": "b5af31b46a9a8754f6032fde78fc46e9ad7221977a0bfbbaa26e13214951ea0f"
|
|
359
371
|
},
|
|
360
372
|
"watchers/JobWatcher.d.ts": {
|
|
361
373
|
"size": 441,
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -221,6 +221,11 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
221
221
|
.replace(/'/g, ''');
|
|
222
222
|
|
|
223
223
|
const looksLikeHtml = (value) => new RegExp('</?(?:html|body|div|table)\\b|<!doctype\\b', 'i').test(String(value || ''));
|
|
224
|
+
const looksLikeFlattenedHtml = (value) => {
|
|
225
|
+
const source = String(value || '');
|
|
226
|
+
if (source.trim() === '' || looksLikeHtml(source)) return false;
|
|
227
|
+
return /(\n|^)\s*(?:body|table|td|div)\s*\{|(\n|^)\s*\.[a-z][\w-]*\s*\{|(\n|^)\s*@media\b/i.test(source);
|
|
228
|
+
};
|
|
224
229
|
|
|
225
230
|
const api = async (path, opts) => {
|
|
226
231
|
const response = await fetch(API + path, opts);
|
|
@@ -503,7 +508,9 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
503
508
|
if (typeof value === 'string') {
|
|
504
509
|
return looksLikeHtml(value)
|
|
505
510
|
? renderHtmlPreview(label, value, { collapseSource: true })
|
|
506
|
-
:
|
|
511
|
+
: ((/html/i.test(label) && looksLikeFlattenedHtml(value))
|
|
512
|
+
? '<p class="trace-note">HTML preview unavailable. The captured payload is plain text, so markup was stripped before trace capture.</p>'
|
|
513
|
+
: '') + renderTextCard(label, value);
|
|
507
514
|
}
|
|
508
515
|
return detailJson(value, label);
|
|
509
516
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceStorage — sealed namespace wrapping the D1/SQLite driver.
|
|
3
|
+
* Resolves the correct IDatabase from the app config, then delegates all
|
|
4
|
+
* read/write operations to the trace storage facade.
|
|
5
|
+
*/
|
|
6
|
+
import type { IDatabase } from '@zintrust/core';
|
|
7
|
+
import type { ITraceStorage } from '../types';
|
|
8
|
+
export declare const TraceStorage: Readonly<{
|
|
9
|
+
resolveStorage: (db: IDatabase) => ITraceStorage;
|
|
10
|
+
reset: () => void;
|
|
11
|
+
familyHash: (input: string) => string;
|
|
12
|
+
}>;
|
|
13
|
+
export { type ITraceStorage } from '../types';
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { familyHash } from '../utils/familyHash.js';
|
|
2
|
+
const TABLE_ENTRIES = 'zin_trace_entries';
|
|
3
|
+
const TABLE_TAGS = 'zin_trace_entries_tags';
|
|
4
|
+
const TABLE_MONITORING = 'zin_trace_monitoring';
|
|
5
|
+
const generateUuid = () => crypto.randomUUID();
|
|
6
|
+
const rowToEntry = (row, tags) => ({
|
|
7
|
+
uuid: row.uuid,
|
|
8
|
+
batchId: row.batch_id,
|
|
9
|
+
familyHash: row.family_hash ?? undefined,
|
|
10
|
+
type: row.type,
|
|
11
|
+
content: JSON.parse(row.content),
|
|
12
|
+
tags,
|
|
13
|
+
isLatest: Boolean(row.is_latest),
|
|
14
|
+
createdAt: row.created_at,
|
|
15
|
+
});
|
|
16
|
+
const insertTags = async (db, uuid, tags) => {
|
|
17
|
+
if (tags.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
await Promise.all(tags.map(async (tag) => {
|
|
20
|
+
await db.execute(`INSERT OR IGNORE INTO ${TABLE_TAGS} (entry_uuid, tag) VALUES (?, ?)`, [
|
|
21
|
+
uuid,
|
|
22
|
+
tag,
|
|
23
|
+
]);
|
|
24
|
+
}));
|
|
25
|
+
};
|
|
26
|
+
const buildEntryFilters = (opts) => {
|
|
27
|
+
const conditions = [];
|
|
28
|
+
const params = [];
|
|
29
|
+
if (opts.type) {
|
|
30
|
+
conditions.push('e.type = ?');
|
|
31
|
+
params.push(opts.type);
|
|
32
|
+
}
|
|
33
|
+
if (opts.batchId) {
|
|
34
|
+
conditions.push('e.batch_id = ?');
|
|
35
|
+
params.push(opts.batchId);
|
|
36
|
+
}
|
|
37
|
+
if (opts.from) {
|
|
38
|
+
conditions.push('e.created_at >= ?');
|
|
39
|
+
params.push(opts.from);
|
|
40
|
+
}
|
|
41
|
+
if (opts.to) {
|
|
42
|
+
conditions.push('e.created_at <= ?');
|
|
43
|
+
params.push(opts.to);
|
|
44
|
+
}
|
|
45
|
+
let joinClause = '';
|
|
46
|
+
if (opts.tag) {
|
|
47
|
+
joinClause = `INNER JOIN ${TABLE_TAGS} t ON t.entry_uuid = e.uuid AND t.tag = ?`;
|
|
48
|
+
params.unshift(opts.tag);
|
|
49
|
+
}
|
|
50
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
51
|
+
const countParams = opts.tag ? [opts.tag, ...params.slice(1)] : [...params];
|
|
52
|
+
return { joinClause, whereClause, params, countParams };
|
|
53
|
+
};
|
|
54
|
+
const loadTagsByUuid = async (db, uuids) => {
|
|
55
|
+
const tagsByUuid = new Map();
|
|
56
|
+
if (uuids.length === 0)
|
|
57
|
+
return tagsByUuid;
|
|
58
|
+
const tagRows = (await db.query(`SELECT entry_uuid, tag FROM ${TABLE_TAGS} WHERE entry_uuid IN (${uuids.map(() => '?').join(',')})`, uuids));
|
|
59
|
+
for (const tagRow of tagRows) {
|
|
60
|
+
const tags = tagsByUuid.get(tagRow.entry_uuid) ?? [];
|
|
61
|
+
tags.push(tagRow.tag);
|
|
62
|
+
tagsByUuid.set(tagRow.entry_uuid, tags);
|
|
63
|
+
}
|
|
64
|
+
return tagsByUuid;
|
|
65
|
+
};
|
|
66
|
+
// The storage facade intentionally groups related DB operations in one factory.
|
|
67
|
+
// eslint-disable-next-line max-lines-per-function
|
|
68
|
+
const createStorage = (db) => {
|
|
69
|
+
const writeEntry = async (entry) => {
|
|
70
|
+
const uuid = entry.uuid || generateUuid();
|
|
71
|
+
await db.execute(`INSERT INTO ${TABLE_ENTRIES} (uuid, batch_id, family_hash, type, content, is_latest, created_at)
|
|
72
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
73
|
+
uuid,
|
|
74
|
+
entry.batchId,
|
|
75
|
+
entry.familyHash ?? null,
|
|
76
|
+
entry.type,
|
|
77
|
+
JSON.stringify(entry.content),
|
|
78
|
+
entry.isLatest ? 1 : 0,
|
|
79
|
+
entry.createdAt,
|
|
80
|
+
]);
|
|
81
|
+
await insertTags(db, uuid, entry.tags);
|
|
82
|
+
};
|
|
83
|
+
const updateEntry = async (uuid, patch) => {
|
|
84
|
+
const sets = [];
|
|
85
|
+
const params = [];
|
|
86
|
+
if (patch.content !== undefined) {
|
|
87
|
+
sets.push('content = ?');
|
|
88
|
+
params.push(JSON.stringify(patch.content));
|
|
89
|
+
}
|
|
90
|
+
if (patch.isLatest !== undefined) {
|
|
91
|
+
sets.push('is_latest = ?');
|
|
92
|
+
params.push(patch.isLatest ? 1 : 0);
|
|
93
|
+
}
|
|
94
|
+
if (sets.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
params.push(uuid);
|
|
97
|
+
await db.execute(`UPDATE ${TABLE_ENTRIES} SET ${sets.join(', ')} WHERE uuid = ?`, params);
|
|
98
|
+
};
|
|
99
|
+
const markFamilyStale = async (hash, exceptUuid) => {
|
|
100
|
+
await db.execute(`UPDATE ${TABLE_ENTRIES} SET is_latest = 0
|
|
101
|
+
WHERE family_hash = ? AND uuid != ? AND is_latest = 1`, [hash, exceptUuid]);
|
|
102
|
+
};
|
|
103
|
+
const queryEntries = async (opts) => {
|
|
104
|
+
const page = opts.page ?? 1;
|
|
105
|
+
const perPage = opts.perPage ?? 50;
|
|
106
|
+
const offset = (page - 1) * perPage;
|
|
107
|
+
const { joinClause, whereClause, params, countParams } = buildEntryFilters(opts);
|
|
108
|
+
const countResult = (await db.queryOne(`SELECT COUNT(*) as cnt FROM ${TABLE_ENTRIES} e ${joinClause} ${whereClause}`, countParams));
|
|
109
|
+
const total = countResult?.cnt ?? 0;
|
|
110
|
+
const rows = (await db.query(`SELECT e.id, e.uuid, e.batch_id, e.family_hash, e.type, e.content, e.is_latest, e.created_at
|
|
111
|
+
FROM ${TABLE_ENTRIES} e ${joinClause} ${whereClause}
|
|
112
|
+
ORDER BY e.created_at DESC, e.id DESC
|
|
113
|
+
LIMIT ? OFFSET ?`, [...params, perPage, offset]));
|
|
114
|
+
const tagsByUuid = await loadTagsByUuid(db, rows.map((row) => row.uuid));
|
|
115
|
+
return {
|
|
116
|
+
data: rows.map((row) => rowToEntry(row, tagsByUuid.get(row.uuid) ?? [])),
|
|
117
|
+
total,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const getEntry = async (uuid) => {
|
|
121
|
+
const row = (await db.queryOne(`SELECT id, uuid, batch_id, family_hash, type, content, is_latest, created_at
|
|
122
|
+
FROM ${TABLE_ENTRIES}
|
|
123
|
+
WHERE uuid = ?`, [uuid]));
|
|
124
|
+
if (!row)
|
|
125
|
+
return null;
|
|
126
|
+
const tags = (await db.query(`SELECT tag FROM ${TABLE_TAGS} WHERE entry_uuid = ?`, [
|
|
127
|
+
uuid,
|
|
128
|
+
]));
|
|
129
|
+
return rowToEntry(row, tags.map((tag) => tag.tag));
|
|
130
|
+
};
|
|
131
|
+
const getBatch = async (batchId) => {
|
|
132
|
+
const rows = (await db.query(`SELECT id, uuid, batch_id, family_hash, type, content, is_latest, created_at
|
|
133
|
+
FROM ${TABLE_ENTRIES}
|
|
134
|
+
WHERE batch_id = ?
|
|
135
|
+
ORDER BY created_at ASC, id ASC`, [batchId]));
|
|
136
|
+
if (rows.length === 0)
|
|
137
|
+
return [];
|
|
138
|
+
const tagsByUuid = await loadTagsByUuid(db, rows.map((row) => row.uuid));
|
|
139
|
+
return rows.map((row) => rowToEntry(row, tagsByUuid.get(row.uuid) ?? []));
|
|
140
|
+
};
|
|
141
|
+
const prune = async (olderThanMs, keepExceptions = false) => {
|
|
142
|
+
const countResult = (await db.queryOne(`SELECT COUNT(*) as cnt FROM ${TABLE_ENTRIES}
|
|
143
|
+
WHERE created_at < ?
|
|
144
|
+
${keepExceptions ? "AND type != 'exception'" : ''}`, [olderThanMs]));
|
|
145
|
+
const deleted = countResult?.cnt ?? 0;
|
|
146
|
+
if (deleted === 0)
|
|
147
|
+
return 0;
|
|
148
|
+
await db.execute(`DELETE FROM ${TABLE_ENTRIES}
|
|
149
|
+
WHERE created_at < ?
|
|
150
|
+
${keepExceptions ? "AND type != 'exception'" : ''}`, [olderThanMs]);
|
|
151
|
+
return deleted;
|
|
152
|
+
};
|
|
153
|
+
const clear = async () => {
|
|
154
|
+
await db.execute(`DELETE FROM ${TABLE_ENTRIES}`, []);
|
|
155
|
+
};
|
|
156
|
+
const getMonitoring = async () => {
|
|
157
|
+
const rows = (await db.query(`SELECT tag FROM ${TABLE_MONITORING}`, []));
|
|
158
|
+
return rows.map((row) => row.tag);
|
|
159
|
+
};
|
|
160
|
+
const addMonitoring = async (tag) => {
|
|
161
|
+
await db.execute(`INSERT OR IGNORE INTO ${TABLE_MONITORING} (tag) VALUES (?)`, [tag]);
|
|
162
|
+
};
|
|
163
|
+
const removeMonitoring = async (tag) => {
|
|
164
|
+
await db.execute(`DELETE FROM ${TABLE_MONITORING} WHERE tag = ?`, [tag]);
|
|
165
|
+
};
|
|
166
|
+
const stats = async () => {
|
|
167
|
+
const rows = (await db.query(`SELECT type, COUNT(*) as cnt FROM ${TABLE_ENTRIES} GROUP BY type`, []));
|
|
168
|
+
const output = {};
|
|
169
|
+
for (const row of rows) {
|
|
170
|
+
output[row.type] = row.cnt;
|
|
171
|
+
}
|
|
172
|
+
return output;
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
writeEntry,
|
|
176
|
+
updateEntry,
|
|
177
|
+
markFamilyStale,
|
|
178
|
+
queryEntries,
|
|
179
|
+
getEntry,
|
|
180
|
+
getBatch,
|
|
181
|
+
prune,
|
|
182
|
+
clear,
|
|
183
|
+
getMonitoring,
|
|
184
|
+
addMonitoring,
|
|
185
|
+
removeMonitoring,
|
|
186
|
+
stats,
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const resolveStorage = (db) => {
|
|
190
|
+
return createStorage(db);
|
|
191
|
+
};
|
|
192
|
+
const reset = () => {
|
|
193
|
+
return;
|
|
194
|
+
};
|
|
195
|
+
export const TraceStorage = Object.freeze({ resolveStorage, reset, familyHash });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Trace assistant for ZinTrust: logs requests, queries, exceptions, jobs, and more.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.migrations.json && node ../../scripts/fix-dist-esm-imports.mjs dist",
|
|
56
56
|
"prepublishOnly": "npm run build"
|
|
57
57
|
}
|
|
58
|
-
}
|
|
58
|
+
}
|