@zintrust/trace 0.4.94 → 0.4.96
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 +40 -28
- package/dist/config.js +83 -6
- package/dist/dashboard/ui.js +40 -13
- package/dist/storage/DebuggerStorage.d.ts +13 -0
- package/dist/storage/DebuggerStorage.js +195 -0
- package/dist/storage/TraceContentBudget.js +67 -15
- package/dist/types.d.ts +13 -1
- package/dist/utils/entryFilter.js +20 -0
- package/dist/watchers/HttpClientWatcher.d.ts +1 -1
- package/dist/watchers/HttpClientWatcher.js +95 -18
- package/dist/watchers/HttpWatcher.js +7 -1
- package/dist/watchers/MiddlewareWatcher.js +5 -0
- package/dist/watchers/ModelWatcher.js +5 -0
- package/package.json +2 -2
- package/src/config.ts +136 -6
- package/src/dashboard/ui.ts +40 -13
- package/src/storage/TraceContentBudget.ts +98 -17
- package/src/types.ts +15 -1
- package/src/utils/entryFilter.ts +23 -0
- package/src/watchers/HttpClientWatcher.ts +125 -19
- package/src/watchers/HttpWatcher.ts +8 -1
- package/src/watchers/MiddlewareWatcher.ts +9 -0
- package/src/watchers/ModelWatcher.ts +9 -0
package/dist/build-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/trace",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"buildDate": "2026-04-
|
|
3
|
+
"version": "0.4.96",
|
|
4
|
+
"buildDate": "2026-04-11T20:49:58.121Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
|
-
"node": "
|
|
7
|
-
"platform": "
|
|
8
|
-
"arch": "
|
|
6
|
+
"node": "v22.22.1",
|
|
7
|
+
"platform": "darwin",
|
|
8
|
+
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
12
|
-
"branch": "
|
|
11
|
+
"commit": "ddf9b233",
|
|
12
|
+
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
15
15
|
"engines": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
]
|
|
22
22
|
},
|
|
23
23
|
"files": {
|
|
24
|
+
"build-manifest.json": {
|
|
25
|
+
"size": 14739,
|
|
26
|
+
"sha256": "b57ad8dd90ba688df31448cae52218fcfc3be48d533c55a06db0247741f30c8d"
|
|
27
|
+
},
|
|
24
28
|
"cli-register.d.ts": {
|
|
25
29
|
"size": 255,
|
|
26
30
|
"sha256": "da8d689fe5ef32e97e755f28017e4d3cb1aa63489073a71907ea41ad5761ede9"
|
|
@@ -34,8 +38,8 @@
|
|
|
34
38
|
"sha256": "b034cbef0c71fb868071363624ef7a9f8d7acc20f8be8c895dd5db5a75e81f37"
|
|
35
39
|
},
|
|
36
40
|
"config.js": {
|
|
37
|
-
"size":
|
|
38
|
-
"sha256": "
|
|
41
|
+
"size": 9270,
|
|
42
|
+
"sha256": "346361028d355391f3068c340db559dce558145e173bf248aa8d1c6328575de3"
|
|
39
43
|
},
|
|
40
44
|
"context.d.ts": {
|
|
41
45
|
"size": 596,
|
|
@@ -66,8 +70,8 @@
|
|
|
66
70
|
"sha256": "4862b41e0477f01afa0dbb446d4553b65c22ed774cd1e2db3489059ced392f94"
|
|
67
71
|
},
|
|
68
72
|
"dashboard/ui.js": {
|
|
69
|
-
"size":
|
|
70
|
-
"sha256": "
|
|
73
|
+
"size": 79698,
|
|
74
|
+
"sha256": "60295315a1fbbfd02018a971399739971252ff16f252ddfea0ea43a562aa305f"
|
|
71
75
|
},
|
|
72
76
|
"index.d.ts": {
|
|
73
77
|
"size": 2537,
|
|
@@ -75,7 +79,7 @@
|
|
|
75
79
|
},
|
|
76
80
|
"index.js": {
|
|
77
81
|
"size": 3325,
|
|
78
|
-
"sha256": "
|
|
82
|
+
"sha256": "fc1e557217d22f31fa29611bebd54d9a0d93393f9a425283f238d91a30b216c9"
|
|
79
83
|
},
|
|
80
84
|
"migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
|
|
81
85
|
"size": 304,
|
|
@@ -133,13 +137,21 @@
|
|
|
133
137
|
"size": 14327,
|
|
134
138
|
"sha256": "efc9bb131b9eef7e81a6f85ea3338fd8ac74257f794aa0fc7c76efed636f99d8"
|
|
135
139
|
},
|
|
140
|
+
"storage/DebuggerStorage.d.ts": {
|
|
141
|
+
"size": 517,
|
|
142
|
+
"sha256": "c9c215aaa414f7b0c1fec6c82b054fc52bdf97af58f96f35c7f96672fb859c31"
|
|
143
|
+
},
|
|
144
|
+
"storage/DebuggerStorage.js": {
|
|
145
|
+
"size": 7442,
|
|
146
|
+
"sha256": "5ecce0fcfcf695df587a7b90a7a5c7efd2e64ad13c9f2d104b392f89f34f0dc4"
|
|
147
|
+
},
|
|
136
148
|
"storage/TraceContentBudget.d.ts": {
|
|
137
149
|
"size": 159,
|
|
138
150
|
"sha256": "d899a615e6cf2a5eea51f6200347cb81fbaae11979b801859f57135083aaf85b"
|
|
139
151
|
},
|
|
140
152
|
"storage/TraceContentBudget.js": {
|
|
141
|
-
"size":
|
|
142
|
-
"sha256": "
|
|
153
|
+
"size": 5868,
|
|
154
|
+
"sha256": "020597ed8a44def06677b8f0ae3947f44cb8c53d03803a2cf214a831858288a7"
|
|
143
155
|
},
|
|
144
156
|
"storage/TraceContentRedaction.d.ts": {
|
|
145
157
|
"size": 207,
|
|
@@ -182,8 +194,8 @@
|
|
|
182
194
|
"sha256": "d916e8e3abb1b1087f6b184851b0e6265e53380d7857b008e745d566aad15d44"
|
|
183
195
|
},
|
|
184
196
|
"types.d.ts": {
|
|
185
|
-
"size":
|
|
186
|
-
"sha256": "
|
|
197
|
+
"size": 8919,
|
|
198
|
+
"sha256": "4ff6c7c067fa9d16535e4356181a73325977c6813a13983ae9edb17c5fa3c466"
|
|
187
199
|
},
|
|
188
200
|
"types.js": {
|
|
189
201
|
"size": 696,
|
|
@@ -210,8 +222,8 @@
|
|
|
210
222
|
"sha256": "a7809e98f76b4e326262c26b0e43e278b1c50ac0b35fee145e3e6006357dc700"
|
|
211
223
|
},
|
|
212
224
|
"utils/entryFilter.js": {
|
|
213
|
-
"size":
|
|
214
|
-
"sha256": "
|
|
225
|
+
"size": 4123,
|
|
226
|
+
"sha256": "de2690c6e5852969083387b469fd2e12f10470c6eee81120154437cd8d8f3555"
|
|
215
227
|
},
|
|
216
228
|
"utils/familyHash.d.ts": {
|
|
217
229
|
"size": 60,
|
|
@@ -310,20 +322,20 @@
|
|
|
310
322
|
"sha256": "f318cdeec954ce0bba97be1dc11a6dff935b081e6b6a417c614be1934fa47f04"
|
|
311
323
|
},
|
|
312
324
|
"watchers/HttpClientWatcher.d.ts": {
|
|
313
|
-
"size":
|
|
314
|
-
"sha256": "
|
|
325
|
+
"size": 341,
|
|
326
|
+
"sha256": "7e20bd9240de2165def5d90ec529e4da4e0a7302bc855bd2fb873f8b71d0182f"
|
|
315
327
|
},
|
|
316
328
|
"watchers/HttpClientWatcher.js": {
|
|
317
|
-
"size":
|
|
318
|
-
"sha256": "
|
|
329
|
+
"size": 5245,
|
|
330
|
+
"sha256": "dcbc10ac6fd583a009bf37c6787738765f5b54bd2a15a1cd6582d0bb2bbb1207"
|
|
319
331
|
},
|
|
320
332
|
"watchers/HttpWatcher.d.ts": {
|
|
321
333
|
"size": 96,
|
|
322
334
|
"sha256": "ce9a95a670f755193fd74ce721dbfa4b30f20c879a6566ebb35229b3b2435429"
|
|
323
335
|
},
|
|
324
336
|
"watchers/HttpWatcher.js": {
|
|
325
|
-
"size":
|
|
326
|
-
"sha256": "
|
|
337
|
+
"size": 6165,
|
|
338
|
+
"sha256": "aa18bc520f5f17a73aa12b71e57d825f5fc75cddb909ebdb222c0ce03291685d"
|
|
327
339
|
},
|
|
328
340
|
"watchers/JobWatcher.d.ts": {
|
|
329
341
|
"size": 441,
|
|
@@ -354,16 +366,16 @@
|
|
|
354
366
|
"sha256": "5a28b472c835bd0f79ec9d3670516544bc5b6da9f1f1bed5159be0cba39304a1"
|
|
355
367
|
},
|
|
356
368
|
"watchers/MiddlewareWatcher.js": {
|
|
357
|
-
"size":
|
|
358
|
-
"sha256": "
|
|
369
|
+
"size": 1399,
|
|
370
|
+
"sha256": "54c7c2c7b1cbeaf3987a7e888b3494c12e8848bfb9d07be8be35934bd6a7c63d"
|
|
359
371
|
},
|
|
360
372
|
"watchers/ModelWatcher.d.ts": {
|
|
361
373
|
"size": 285,
|
|
362
374
|
"sha256": "4c05112af855a92b3f3f97c6b61bf1b07444bcb7d5a17f3e0ae19da4a85faf1c"
|
|
363
375
|
},
|
|
364
376
|
"watchers/ModelWatcher.js": {
|
|
365
|
-
"size":
|
|
366
|
-
"sha256": "
|
|
377
|
+
"size": 1381,
|
|
378
|
+
"sha256": "0ead883fad311762f8310a3748d4859d2913ad92c8fd0db48b7070fe4b5e9d70"
|
|
367
379
|
},
|
|
368
380
|
"watchers/NotificationWatcher.d.ts": {
|
|
369
381
|
"size": 274,
|
package/dist/config.js
CHANGED
|
@@ -12,15 +12,26 @@ const mergeStringLists = (base, override) => {
|
|
|
12
12
|
const isObjectValue = (value) => {
|
|
13
13
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
14
|
};
|
|
15
|
+
const resolveEnabled = (base, override) => {
|
|
16
|
+
return override?.enabled ?? base?.enabled;
|
|
17
|
+
};
|
|
18
|
+
const hasMergedRuleValues = (include, exclude, enabled) => {
|
|
19
|
+
return include.length > 0 || exclude.length > 0 || enabled !== undefined;
|
|
20
|
+
};
|
|
21
|
+
const buildFilterRule = (input) => {
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
...(input.enabled === undefined ? {} : { enabled: input.enabled }),
|
|
24
|
+
...(input.include.length > 0 ? { include: input.include } : {}),
|
|
25
|
+
...(input.exclude.length > 0 ? { exclude: input.exclude } : {}),
|
|
26
|
+
});
|
|
27
|
+
};
|
|
15
28
|
const mergeFilterRule = (base, override) => {
|
|
16
29
|
const include = mergeStringLists(base?.include ?? [], override?.include);
|
|
17
30
|
const exclude = mergeStringLists(base?.exclude ?? [], override?.exclude);
|
|
18
|
-
|
|
31
|
+
const enabled = resolveEnabled(base, override);
|
|
32
|
+
if (!hasMergedRuleValues(include, exclude, enabled))
|
|
19
33
|
return undefined;
|
|
20
|
-
return
|
|
21
|
-
...(include.length > 0 ? { include } : {}),
|
|
22
|
-
...(exclude.length > 0 ? { exclude } : {}),
|
|
23
|
-
});
|
|
34
|
+
return buildFilterRule({ include, exclude, enabled });
|
|
24
35
|
};
|
|
25
36
|
const mergeWatcherToggle = (base, override) => {
|
|
26
37
|
if (override === undefined)
|
|
@@ -30,6 +41,72 @@ const mergeWatcherToggle = (base, override) => {
|
|
|
30
41
|
const baseRule = isObjectValue(base) ? base : undefined;
|
|
31
42
|
return mergeFilterRule(baseRule, override);
|
|
32
43
|
};
|
|
44
|
+
const resolveClientRequestCaptureFlags = (base, override) => {
|
|
45
|
+
return {
|
|
46
|
+
requestHeaders: override?.requestHeaders ?? base?.requestHeaders,
|
|
47
|
+
requestBody: override?.requestBody ?? base?.requestBody,
|
|
48
|
+
responseHeaders: override?.responseHeaders ?? base?.responseHeaders,
|
|
49
|
+
responseBody: override?.responseBody ?? base?.responseBody,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const hasClientRequestCaptureFlags = (flags) => {
|
|
53
|
+
return Object.values(flags).some((value) => value !== undefined);
|
|
54
|
+
};
|
|
55
|
+
const buildClientRequestCaptureRule = (mergedRule, flags) => {
|
|
56
|
+
const baseRule = mergedRule ? { ...mergedRule } : {};
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
...baseRule,
|
|
59
|
+
...(flags.requestHeaders === undefined ? {} : { requestHeaders: flags.requestHeaders }),
|
|
60
|
+
...(flags.requestBody === undefined ? {} : { requestBody: flags.requestBody }),
|
|
61
|
+
...(flags.responseHeaders === undefined ? {} : { responseHeaders: flags.responseHeaders }),
|
|
62
|
+
...(flags.responseBody === undefined ? {} : { responseBody: flags.responseBody }),
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const mergeClientRequestCaptureRule = (base, override) => {
|
|
66
|
+
const mergedRule = mergeFilterRule(base, override);
|
|
67
|
+
const flags = resolveClientRequestCaptureFlags(base, override);
|
|
68
|
+
if (mergedRule === undefined && !hasClientRequestCaptureFlags(flags)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return buildClientRequestCaptureRule(mergedRule, flags);
|
|
72
|
+
};
|
|
73
|
+
const collectClientRequestSourceKeys = (base, override) => {
|
|
74
|
+
const overrideSources = override?.sources ?? {};
|
|
75
|
+
const sourceKeys = new Set([
|
|
76
|
+
...Object.keys(isObjectValue(base) ? base.sources ?? {} : {}),
|
|
77
|
+
...Object.keys(overrideSources),
|
|
78
|
+
]);
|
|
79
|
+
return [...sourceKeys];
|
|
80
|
+
};
|
|
81
|
+
const mergeClientRequestSources = (base, override) => {
|
|
82
|
+
if (override === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const sources = {};
|
|
85
|
+
for (const key of collectClientRequestSourceKeys(base, override)) {
|
|
86
|
+
const baseSources = isObjectValue(base) ? base.sources : undefined;
|
|
87
|
+
const sourceRule = mergeClientRequestCaptureRule(baseSources?.[key], override.sources?.[key]);
|
|
88
|
+
if (sourceRule !== undefined) {
|
|
89
|
+
sources[key] = sourceRule;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return Object.keys(sources).length === 0 ? undefined : sources;
|
|
93
|
+
};
|
|
94
|
+
const mergeClientRequestWatcherToggle = (base, override) => {
|
|
95
|
+
if (override === undefined)
|
|
96
|
+
return base;
|
|
97
|
+
if (override === false || override === true)
|
|
98
|
+
return override;
|
|
99
|
+
const baseConfig = isObjectValue(base) ? base : undefined;
|
|
100
|
+
const merged = mergeClientRequestCaptureRule(baseConfig, override) ?? {};
|
|
101
|
+
const sources = mergeClientRequestSources(base, override);
|
|
102
|
+
if (sources === undefined) {
|
|
103
|
+
return merged;
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
...merged,
|
|
107
|
+
sources,
|
|
108
|
+
});
|
|
109
|
+
};
|
|
33
110
|
const REQUEST_METHOD_KEYS = ['all', 'get', 'post', 'put', 'patch', 'delete'];
|
|
34
111
|
const mergeRequestWatcherToggle = (base, override) => {
|
|
35
112
|
if (override === undefined)
|
|
@@ -70,7 +147,7 @@ const mergeWatchers = (base, override) => {
|
|
|
70
147
|
batch: mergeWatcherToggle(base.batch, override.batch),
|
|
71
148
|
dump: mergeWatcherToggle(base.dump, override.dump),
|
|
72
149
|
view: mergeWatcherToggle(base.view, override.view),
|
|
73
|
-
clientRequest:
|
|
150
|
+
clientRequest: mergeClientRequestWatcherToggle(base.clientRequest, override.clientRequest),
|
|
74
151
|
};
|
|
75
152
|
};
|
|
76
153
|
const DEFAULTS = Object.freeze({
|
package/dist/dashboard/ui.js
CHANGED
|
@@ -35,6 +35,7 @@ const BRAND_SVG = `<svg width="120" height="120" viewBox="0 0 100 100" fill="non
|
|
|
35
35
|
const SUN_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2.2M12 19.8V22M4.93 4.93l1.56 1.56M17.51 17.51l1.56 1.56M2 12h2.2M19.8 12H22M4.93 19.07l1.56-1.56M17.51 6.49l1.56-1.56"></path></svg>`;
|
|
36
36
|
const MOON_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path></svg>`;
|
|
37
37
|
const COPY_ICON = `<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
|
|
38
|
+
const DISCLOSURE_ICON = `<svg viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4l6 6-6 6"></path></svg>`;
|
|
38
39
|
const JSON_HIGHLIGHT_PATTERN = String.raw `("(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*")(?=\s*:)|(\s*:)|("(?:\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*")|\btrue\b|\bfalse\b|\bnull\b|-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?`;
|
|
39
40
|
const SQL_HIGHLIGHT_PATTERN = String.raw `(\/\*[\s\S]*?\*\/|--.*$|'(?:''|[^'])*'|\x60[^\x60]+\x60|\b(?:select|from|where|insert|into|values|update|delete|join|left|right|inner|outer|on|and|or|limit|order|by|group|having|as|distinct|null|is|in|like|set|case|when|then|else|end|returning|union|all)\b|-?\d+(?:\.\d+)?)`;
|
|
40
41
|
const encodeSvgDataUri = (svg) => {
|
|
@@ -131,6 +132,10 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
131
132
|
const SUN_ICON = __TRACE_SUN_ICON__;
|
|
132
133
|
const MOON_ICON = __TRACE_MOON_ICON__;
|
|
133
134
|
const COPY_ICON = __TRACE_COPY_ICON__;
|
|
135
|
+
const DISCLOSURE_ICON = __TRACE_DISCLOSURE_ICON__;
|
|
136
|
+
.panel{border-radius:var(--radius);border:1px solid var(--line);background:var(--surface);box-shadow:var(--shadow);backdrop-filter:blur(16px)}.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px;margin-bottom:18px}.stat-card{padding:20px;position:relative;overflow:hidden}.stat-card::after{content:'';position:absolute;right:-18px;bottom:-26px;width:92px;height:92px;border-radius:28px;background:linear-gradient(135deg,rgba(56,189,248,.16),rgba(34,197,94,.08));transform:rotate(18deg)}.stat-label{font-size:.74rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);font-weight:800;margin-bottom:12px}.stat-value{font-size:2.25rem;font-weight:800;line-height:1}.stat-meta{margin-top:10px;color:var(--muted);font-size:.9rem}.content-grid{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(320px,.95fr);gap:18px}.side-stack{display:grid;gap:18px}
|
|
137
|
+
.section-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:22px 24px 16px}.section-head h3{margin:0;font-size:1.04rem}.section-head p{margin:6px 0 0;color:var(--muted);font-size:.92rem}.toolbar{display:flex;flex-wrap:wrap;gap:10px;padding:0 24px 18px}.control,.toolbar input,.toolbar select{height:44px;border-radius:13px;border:1px solid var(--line);background:var(--surface-strong);color:var(--text);padding:0 14px;min-width:0}.toolbar input,.toolbar select{flex:1 1 180px}.toolbar input::placeholder{color:var(--muted)}.btn{height:44px;border:none;border-radius:13px;padding:0 16px;cursor:pointer;font-weight:800}.btn-primary{background:linear-gradient(135deg,var(--accent-strong),var(--accent));color:#fff}.btn-danger{background:rgba(239,68,68,.12);color:var(--danger);border:1px solid rgba(239,68,68,.18)}.btn-ghost{background:var(--surface-soft);color:var(--text);border:1px solid var(--line)}
|
|
138
|
+
.activity-list{list-style:none;margin:0;padding:0 24px 24px}.activity-item{padding:14px 0;border-top:1px solid var(--line)}.activity-item:first-child{border-top:none}.activity-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.activity-time{color:var(--muted);font-size:.85rem}.activity-summary{margin-top:8px;color:var(--text);line-height:1.48}.back-link{display:inline-flex;align-items:center;gap:8px;margin:0 0 14px;color:var(--accent);font-weight:800;cursor:pointer}.detail-card{padding:24px}.detail-meta{display:flex;flex-wrap:wrap;gap:10px;margin:14px 0 20px;color:var(--muted);font-size:.9rem;overflow-wrap:anywhere}.detail-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}.detail-stack{display:grid;gap:16px;margin-top:18px}.detail-box{padding:16px;border-radius:16px;background:var(--surface-soft);border:1px solid var(--line)}.detail-box h4{margin:0 0 10px;font-size:.92rem}.detail-box dl{margin:0;display:grid;gap:8px}.detail-box dt{font-size:.76rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:800}.detail-box dd{margin:0;color:var(--text);line-height:1.45;overflow-wrap:anywhere}.trace-tabs{display:flex;gap:10px;flex-wrap:wrap;margin:20px 0 16px}.trace-tab{border:none;border-radius:12px;padding:10px 12px;background:transparent;color:var(--muted);cursor:pointer;box-shadow:inset 0 0 0 1px var(--line);font-weight:800}.trace-tab.active{background:rgba(56,189,248,.12);color:var(--text);box-shadow:inset 0 0 0 1px rgba(56,189,248,.28)}.trace-panel{display:grid;gap:14px}.trace-item{padding:18px;border-radius:16px;background:var(--surface-soft);border:1px solid var(--line)}.trace-item-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.trace-item-summary{margin-top:10px;display:grid;gap:10px}.trace-note{color:var(--muted);line-height:1.6}.trace-disclosure{padding:0;overflow:hidden}.trace-disclosure[open]{padding-bottom:18px}.trace-disclosure .trace-item-summary{margin-top:0}.trace-disclosure-body{display:grid;gap:12px;padding:0 18px}.trace-summary{list-style:none;cursor:pointer;padding:18px}.trace-summary::-webkit-details-marker{display:none}.trace-summary-main{display:grid;gap:10px;min-width:0;flex:1}.trace-summary-copy{display:grid;gap:6px;min-width:0}.trace-summary-copy .summary,.trace-summary-copy .summary-sub{display:block;overflow-wrap:anywhere}.trace-disclosure-body .summary-sub{overflow-wrap:anywhere}.trace-summary-icon{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted);flex:none;transition:transform .16s ease,color .16s ease}.trace-summary-icon svg{width:14px;height:14px;display:block}.trace-disclosure[open] .trace-summary-icon{transform:rotate(90deg);color:var(--accent)}
|
|
134
139
|
const JSON_HIGHLIGHT_PATTERN = new RegExp(__TRACE_JSON_REGEX__, 'g');
|
|
135
140
|
const SQL_HIGHLIGHT_PATTERN = new RegExp(__TRACE_SQL_REGEX__, 'gim');
|
|
136
141
|
const ENTRY_TYPES = ['request','query','exception','log','job','cache','schedule','mail','auth','event','model','notification','redis','gate','middleware','command','batch','dump','view','client_request'];
|
|
@@ -215,6 +220,19 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
215
220
|
return ' method-other';
|
|
216
221
|
};
|
|
217
222
|
|
|
223
|
+
const normalizeMethodLabel = (value) => {
|
|
224
|
+
const method = String(value || '').trim().toUpperCase();
|
|
225
|
+
if (method === '') return 'Request';
|
|
226
|
+
return method.charAt(0) + method.slice(1).toLowerCase();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const entryTypeLabel = (entry) => {
|
|
230
|
+
if (entry && entry.type === 'request') {
|
|
231
|
+
return normalizeMethodLabel(entry.content && entry.content.method);
|
|
232
|
+
}
|
|
233
|
+
return String(entry && entry.type || '');
|
|
234
|
+
};
|
|
235
|
+
|
|
218
236
|
const typeClass = (entryOrType, maybeEntry) => {
|
|
219
237
|
const entry = maybeEntry || (typeof entryOrType === 'object' ? entryOrType : null);
|
|
220
238
|
const type = entry && entry.type ? entry.type : entryOrType;
|
|
@@ -427,7 +445,7 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
427
445
|
|
|
428
446
|
const entrySummaryText = (entry) => {
|
|
429
447
|
const content = entry && entry.content ? entry.content : {};
|
|
430
|
-
if (entry.type === 'request') return [content.responseStatus || '', content.
|
|
448
|
+
if (entry.type === 'request') return [content.responseStatus || '', content.uri || ''].filter(Boolean).join(' ');
|
|
431
449
|
if (entry.type === 'query') return String(content.sql || '').slice(0, 160);
|
|
432
450
|
if (entry.type === 'exception') return [content.class || '', content.message || ''].filter(Boolean).join(': ');
|
|
433
451
|
if (entry.type === 'log') return '[' + String(content.level || 'log') + '] ' + String(content.message || '').slice(0, 160);
|
|
@@ -624,16 +642,17 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
624
642
|
const renderTraceItems = (entries, options = {}) => {
|
|
625
643
|
if (entries.length === 0) return '<p class="trace-note">No related entries captured.</p>';
|
|
626
644
|
|
|
627
|
-
const collapsible = options.collapsible
|
|
628
|
-
const isInitiallyOpen = options.collapsed
|
|
645
|
+
const collapsible = options.collapsible !== false;
|
|
646
|
+
const isInitiallyOpen = options.collapsed === false;
|
|
629
647
|
|
|
630
648
|
return '<div class="trace-panel">' + entries.map((entry) => {
|
|
631
649
|
if (collapsible) {
|
|
632
650
|
return [
|
|
633
651
|
'<details class="trace-item trace-disclosure"' + (isInitiallyOpen ? ' open' : '') + '>',
|
|
634
652
|
'<summary class="trace-item-head trace-summary">',
|
|
653
|
+
'<span class="trace-summary-icon">' + DISCLOSURE_ICON + '</span>',
|
|
635
654
|
'<span class="trace-summary-main">',
|
|
636
|
-
'<span><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
655
|
+
'<span><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></span>',
|
|
637
656
|
'<span class="trace-summary-copy">' + entrySummaryInlineHtml(entry) + '</span>',
|
|
638
657
|
'</span>',
|
|
639
658
|
'<span class="activity-head">' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></span>',
|
|
@@ -650,7 +669,7 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
650
669
|
'<section class="trace-item">',
|
|
651
670
|
'<div class="trace-item-head">',
|
|
652
671
|
'<div>',
|
|
653
|
-
'<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
672
|
+
'<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span>',
|
|
654
673
|
'</div>',
|
|
655
674
|
'<div class="activity-head">' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></div>',
|
|
656
675
|
'</div>',
|
|
@@ -673,14 +692,16 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
673
692
|
{ id: 'headers', label: 'Headers' },
|
|
674
693
|
{ id: 'response', label: 'Response' },
|
|
675
694
|
{ id: 'queries', label: 'Queries', count: batchEntriesByType('query').length },
|
|
695
|
+
{ id: 'middleware', label: 'Middleware', count: batchEntriesByType('middleware').length },
|
|
696
|
+
{ id: 'models', label: 'Models', count: batchEntriesByType('model').length },
|
|
676
697
|
{ id: 'logs', label: 'Logs', count: batchEntriesByType('log').length },
|
|
677
698
|
{ id: 'exceptions', label: 'Exceptions', count: batchEntriesByType('exception').length },
|
|
678
699
|
{ id: 'http', label: 'HTTP', count: batchEntriesByType('client_request').length },
|
|
679
700
|
{ id: 'cache', label: 'Cache', count: batchEntriesByType('cache').length },
|
|
680
|
-
{ id: 'other', label: 'Other', count: batchEntries().filter((item) => !['request','query','log','exception','client_request','cache'].includes(item.type)).length }
|
|
701
|
+
{ id: 'other', label: 'Other', count: batchEntries().filter((item) => !['request','query','middleware','model','log','exception','client_request','cache'].includes(item.type)).length }
|
|
681
702
|
];
|
|
682
703
|
const currentTab = traceTabs.some((tab) => tab.id === state.detailTab) ? state.detailTab : 'summary';
|
|
683
|
-
const otherEntries = batchEntries().filter((item) => !['request','query','log','exception','client_request','cache'].includes(item.type));
|
|
704
|
+
const otherEntries = batchEntries().filter((item) => !['request','query','middleware','model','log','exception','client_request','cache'].includes(item.type));
|
|
684
705
|
const panels = {
|
|
685
706
|
summary: [
|
|
686
707
|
'<div class="detail-grid">',
|
|
@@ -699,12 +720,17 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
699
720
|
renderMetricBox('Tags', [
|
|
700
721
|
{ label: 'Values', value: tagsHtml(entry.tags) || '<span class="activity-time">-</span>' }
|
|
701
722
|
]),
|
|
723
|
+
renderMetricBox('Route middleware', [
|
|
724
|
+
{ label: 'Attached', value: escapeHtml(Array.isArray(content.middleware) && content.middleware.length > 0 ? content.middleware.join(', ') : 'None') }
|
|
725
|
+
]),
|
|
702
726
|
'</div>'
|
|
703
727
|
].join(''),
|
|
704
728
|
payload: detailJson(content.payload || {}, 'Payload Json'),
|
|
705
729
|
headers: '<div class="detail-stack">' + detailJson(content.headers || {}, 'Request Header Json') + detailJson(content.responseHeaders || {}, 'Response Header Json') + '</div>',
|
|
706
730
|
response: '<div class="detail-stack"><div class="detail-grid">' + renderMetricBox('Status', [{ label: 'Response status', value: escapeHtml(content.responseStatus || '') }, { label: 'Duration', value: escapeHtml(formatDuration(getEntryDuration(entry))) }]) + '</div>' + (content.responseBody === undefined ? '<p class="trace-note">No response body was captured for this request.</p>' : detailJson(content.responseBody, 'Response Body Json')) + detailJson(content.responseHeaders || {}, 'Response Header Json') + '</div>',
|
|
707
|
-
queries: renderTraceItems(batchEntriesByType('query')
|
|
731
|
+
queries: renderTraceItems(batchEntriesByType('query')),
|
|
732
|
+
middleware: renderTraceItems(batchEntriesByType('middleware')),
|
|
733
|
+
models: renderTraceItems(batchEntriesByType('model')),
|
|
708
734
|
logs: renderTraceItems(batchEntriesByType('log')),
|
|
709
735
|
exceptions: renderTraceItems(batchEntriesByType('exception')),
|
|
710
736
|
http: renderTraceItems(batchEntriesByType('client_request')),
|
|
@@ -716,8 +742,8 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
716
742
|
'<span class="back-link" data-action="close-detail"><- Back to entries</span>',
|
|
717
743
|
'<section class="panel detail-card">',
|
|
718
744
|
'<div>' + (entry.type === 'request'
|
|
719
|
-
? '<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
720
|
-
: '<span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
745
|
+
? '<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span> <span class="' + typeClass(entry) + '">' + escapeHtml(content.responseStatus || '') + '</span> <span class="mono">' + escapeHtml(content.uri || '') + '</span> ' + tagsHtml(entry.tags)
|
|
746
|
+
: '<span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span> ' + tagsHtml(entry.tags)) + '</div>',
|
|
721
747
|
'<div class="detail-meta"><span>UUID <span class="mono">' + escapeHtml(entry.uuid) + '</span></span><span>Batch <span class="mono">' + escapeHtml(entry.batchId || '-') + '</span></span><span>' + durationHtml(entry) + '</span><span>' + escapeHtml(new Date(Number(entry.createdAt)).toISOString()) + '</span></div>',
|
|
722
748
|
'<div class="trace-tabs">',
|
|
723
749
|
traceTabs.map((tab) => '<button type="button" class="trace-tab' + (tab.id === currentTab ? ' active' : '') + '" data-action="detail-tab" data-tab="' + escapeHtml(tab.id) + '">' + escapeHtml(tab.label) + (tab.count !== undefined ? ' (' + escapeHtml(tab.count) + ')' : '') + '</button>').join(''),
|
|
@@ -743,10 +769,10 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
743
769
|
const recentRows = recent.data || [];
|
|
744
770
|
const recentTable = recentRows.length === 0
|
|
745
771
|
? '<div class="empty">No trace entries recorded.</div>'
|
|
746
|
-
: '<div class="table-wrap"><table><thead><tr><th>Type</th><th>Summary</th><th>Tags</th><th>Duration</th><th>Happened</th></tr></thead><tbody>' + recentRows.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
772
|
+
: '<div class="table-wrap"><table><thead><tr><th>Type</th><th>Summary</th><th>Tags</th><th>Duration</th><th>Happened</th></tr></thead><tbody>' + recentRows.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></td><td>' + entrySummaryHtml(entry) + '</td><td>' + tagsHtml(entry.tags) + '</td><td>' + durationHtml(entry) + '</td><td class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</td></tr>').join('') + '</tbody></table></div>';
|
|
747
773
|
const activityList = recentRows.length === 0
|
|
748
774
|
? '<div class="empty">No recent activity.</div>'
|
|
749
|
-
: '<ul class="activity-list">' + recentRows.slice(0, 5).map((entry) => '<li class="activity-item"><div class="activity-head"><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
775
|
+
: '<ul class="activity-list">' + recentRows.slice(0, 5).map((entry) => '<li class="activity-item"><div class="activity-head"><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span>' + durationHtml(entry) + '<span class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</span></div><div class="activity-summary">' + escapeHtml(entrySummaryText(entry)) + '</div></li>').join('') + '</ul>';
|
|
750
776
|
|
|
751
777
|
main.innerHTML = [
|
|
752
778
|
statsCardsHtml(stats),
|
|
@@ -790,7 +816,7 @@ const DASHBOARD_DOCUMENT = String.raw `<!DOCTYPE html>
|
|
|
790
816
|
const total = Number(response.total || 0);
|
|
791
817
|
const perPage = Number(response.perPage || 50);
|
|
792
818
|
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
793
|
-
const rows = data.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entry
|
|
819
|
+
const rows = data.map((entry) => '<tr class="row-button" data-action="show-detail" data-uuid="' + escapeHtml(entry.uuid) + '"><td><span class="' + typeClass(entry) + '">' + escapeHtml(entryTypeLabel(entry)) + '</span></td><td>' + entrySummaryHtml(entry) + '</td><td>' + tagsHtml(entry.tags) + '</td><td>' + durationHtml(entry) + '</td><td class="mono">' + batchSnippet(entry.batchId) + '</td><td class="activity-time">' + escapeHtml(timeSince(entry.createdAt)) + '</td></tr>').join('');
|
|
794
820
|
|
|
795
821
|
main.innerHTML = [
|
|
796
822
|
'<section class="panel">',
|
|
@@ -1055,6 +1081,7 @@ const buildDashboardHtml = (basePath, projectName) => {
|
|
|
1055
1081
|
.replace('__TRACE_SUN_ICON__', JSON.stringify(SUN_ICON))
|
|
1056
1082
|
.replace('__TRACE_MOON_ICON__', JSON.stringify(MOON_ICON))
|
|
1057
1083
|
.replace('__TRACE_COPY_ICON__', JSON.stringify(COPY_ICON))
|
|
1084
|
+
.replace('__TRACE_DISCLOSURE_ICON__', JSON.stringify(DISCLOSURE_ICON))
|
|
1058
1085
|
.replace('__TRACE_JSON_REGEX__', JSON.stringify(JSON_HIGHLIGHT_PATTERN))
|
|
1059
1086
|
.replace('__TRACE_SQL_REGEX__', JSON.stringify(SQL_HIGHLIGHT_PATTERN))
|
|
1060
1087
|
.replace('__TRACE_BASE_PATH_LABEL__', basePath)
|
|
@@ -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 });
|