@zintrust/trace 0.9.4 → 1.2.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.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.9.4",
4
- "buildDate": "2026-04-23T11:29:28.904Z",
3
+ "version": "1.2.0",
4
+ "buildDate": "2026-04-24T08:48:22.033Z",
5
5
  "buildEnvironment": {
6
- "node": "v22.22.1",
7
- "platform": "darwin",
8
- "arch": "arm64"
6
+ "node": "v20.20.2",
7
+ "platform": "linux",
8
+ "arch": "x64"
9
9
  },
10
10
  "git": {
11
- "commit": "eebcc3ad",
12
- "branch": "release"
11
+ "commit": "a179ffe4",
12
+ "branch": "master"
13
13
  },
14
14
  "package": {
15
15
  "engines": {
@@ -29,10 +29,6 @@
29
29
  "size": 4640,
30
30
  "sha256": "c51cc312046b6b2bbe1673f1ff9508425cc7140a1d2341907f67aa36069c09f9"
31
31
  },
32
- "build-manifest.json": {
33
- "size": 14744,
34
- "sha256": "92fc9e1e76ba84696dbdaf02aa2ed7588c6e1234d586e24bf8bab4f54104ad61"
35
- },
36
32
  "cli-register.d.ts": {
37
33
  "size": 255,
38
34
  "sha256": "da8d689fe5ef32e97e755f28017e4d3cb1aa63489073a71907ea41ad5761ede9"
@@ -87,15 +83,15 @@
87
83
  },
88
84
  "index.js": {
89
85
  "size": 3421,
90
- "sha256": "478b80c6aa6c2eea2d109aa2e6fe090d77469d186b3e6a19ababb7d1f6d0be7e"
86
+ "sha256": "def9be169e22b131447ecfd522a255f5800a7407e0ab58aad9efa1150c5e2a0e"
91
87
  },
92
88
  "ingest/TraceIngestGateway.d.ts": {
93
89
  "size": 786,
94
90
  "sha256": "3a0a46fa5bbf5367047214533ec0ee92a171ee35a60d25c197eea1eed1ff0f65"
95
91
  },
96
92
  "ingest/TraceIngestGateway.js": {
97
- "size": 8456,
98
- "sha256": "56df56cecda7110d1096c3beef1ef62c386f4a0e85120ebfb4cdfc5ab3b758d7"
93
+ "size": 9413,
94
+ "sha256": "84d0d2d0bd648d9734b2c19881d953f6ebd9d55e4d96a3d3e5d500ffd332c5ac"
99
95
  },
100
96
  "migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
101
97
  "size": 304,
@@ -153,21 +149,13 @@
153
149
  "size": 19822,
154
150
  "sha256": "c553356d90c812f7de430d5679b1d44468131433e034ae2ea89e2876b1254444"
155
151
  },
156
- "storage/DebuggerStorage.d.ts": {
157
- "size": 517,
158
- "sha256": "c9c215aaa414f7b0c1fec6c82b054fc52bdf97af58f96f35c7f96672fb859c31"
159
- },
160
- "storage/DebuggerStorage.js": {
161
- "size": 7442,
162
- "sha256": "5ecce0fcfcf695df587a7b90a7a5c7efd2e64ad13c9f2d104b392f89f34f0dc4"
163
- },
164
152
  "storage/ProxyTraceStorage.d.ts": {
165
153
  "size": 339,
166
154
  "sha256": "9c724ff342dfe82da12e7cce95593f6623faa80c40f97593719b8e78e95f266d"
167
155
  },
168
156
  "storage/ProxyTraceStorage.js": {
169
- "size": 4158,
170
- "sha256": "097d82e94c6ef38661fb57a20d378ba890dbacd03a22666aaf4aba4dfdedf735"
157
+ "size": 4330,
158
+ "sha256": "672c95ed022504b2e5be62f3c2b31bac168ac06158d793dd020f7e38907e8f64"
171
159
  },
172
160
  "storage/TraceContentBudget.d.ts": {
173
161
  "size": 1306,
@@ -13,8 +13,15 @@ const parseMiddleware = (value) => value
13
13
  .split(',')
14
14
  .map((entry) => entry.trim())
15
15
  .filter((entry) => entry.length > 0);
16
+ const trimTrailingSlashes = (value) => {
17
+ let trimmed = value;
18
+ while (trimmed.endsWith('/')) {
19
+ trimmed = trimmed.slice(0, -1);
20
+ }
21
+ return trimmed;
22
+ };
16
23
  const appendSuffix = (path, suffix) => {
17
- const base = normalizePath(path).replace(/\/+$/, '');
24
+ const base = trimTrailingSlashes(normalizePath(path));
18
25
  const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
19
26
  return `${base}${tail}`;
20
27
  };
@@ -179,32 +186,63 @@ const resolveStorage = (overrides) => {
179
186
  }
180
187
  return TraceStorage.resolveStorage(db);
181
188
  };
189
+ const readConfiguredKeyId = (overrides) => {
190
+ return (overrides?.keyId ?? Env.get('TRACE_PROXY_KEY_ID', '')).trim();
191
+ };
192
+ const readConfiguredSecret = (overrides) => {
193
+ return (overrides?.secret ?? Env.get('TRACE_PROXY_SECRET', '')).trim();
194
+ };
195
+ const resolveKeyId = (overrides) => {
196
+ const configuredKeyId = readConfiguredKeyId(overrides);
197
+ if (configuredKeyId !== '')
198
+ return configuredKeyId;
199
+ return (Env.APP_NAME || 'zintrust').trim();
200
+ };
201
+ const resolveSecret = (overrides) => {
202
+ const configuredSecret = readConfiguredSecret(overrides);
203
+ if (configuredSecret !== '')
204
+ return configuredSecret;
205
+ return Env.APP_KEY;
206
+ };
207
+ const resolveSigningWindowMs = (overrides) => {
208
+ return overrides?.signingWindowMs ?? Env.getInt('TRACE_PROXY_SIGNING_WINDOW_MS', 60000);
209
+ };
210
+ const resolveNonceTtlMs = (overrides) => {
211
+ return overrides?.nonceTtlMs ?? Env.getInt('TRACE_PROXY_NONCE_TTL_MS', 120000);
212
+ };
213
+ const resolveMiddleware = (overrides) => {
214
+ return overrides?.middleware ?? parseMiddleware(Env.get('TRACE_PROXY_MIDDLEWARE', ''));
215
+ };
182
216
  const readSettings = (overrides) => {
183
- const configuredSecret = (overrides?.secret ?? Env.get('TRACE_PROXY_SECRET', '')).trim();
184
- const configuredKeyId = (overrides?.keyId ?? Env.get('TRACE_PROXY_KEY_ID', '')).trim();
185
217
  return {
186
218
  basePath: normalizePath(overrides?.basePath ?? Env.get('TRACE_PROXY_PATH', '/zin/trace/write')),
187
- keyId: configuredKeyId === '' ? (Env.APP_NAME || 'zintrust').trim() : configuredKeyId,
188
- secret: configuredSecret === '' ? Env.APP_KEY : configuredSecret,
189
- signingWindowMs: overrides?.signingWindowMs ?? Env.getInt('TRACE_PROXY_SIGNING_WINDOW_MS', 60000),
190
- nonceTtlMs: overrides?.nonceTtlMs ?? Env.getInt('TRACE_PROXY_NONCE_TTL_MS', 120000),
191
- middleware: overrides?.middleware ?? parseMiddleware(Env.get('TRACE_PROXY_MIDDLEWARE', '')),
219
+ keyId: resolveKeyId(overrides),
220
+ secret: resolveSecret(overrides),
221
+ signingWindowMs: resolveSigningWindowMs(overrides),
222
+ nonceTtlMs: resolveNonceTtlMs(overrides),
223
+ middleware: resolveMiddleware(overrides),
192
224
  storage: resolveStorage(overrides),
193
225
  };
194
226
  };
227
+ const getRouteOptions = (settings) => {
228
+ if (settings.middleware.length === 0)
229
+ return undefined;
230
+ return { middleware: settings.middleware };
231
+ };
232
+ const registerRoutes = (router, settings) => {
233
+ const routeOptions = getRouteOptions(settings);
234
+ const updatePath = appendSuffix(settings.basePath, '/update');
235
+ const markFamilyStalePath = appendSuffix(settings.basePath, '/mark-family-stale');
236
+ Router.post(router, settings.basePath, createWriteHandler(settings, settings.basePath), routeOptions);
237
+ Router.post(router, updatePath, createUpdateHandler(settings, updatePath), routeOptions);
238
+ Router.post(router, markFamilyStalePath, createMarkFamilyStaleHandler(settings, markFamilyStalePath), routeOptions);
239
+ };
195
240
  export const TraceIngestGateway = Object.freeze({
196
241
  create(overrides) {
197
242
  const settings = readSettings(overrides);
198
- const routeOptions = settings.middleware.length > 0
199
- ? { middleware: settings.middleware }
200
- : undefined;
201
- const updatePath = appendSuffix(settings.basePath, '/update');
202
- const markFamilyStalePath = appendSuffix(settings.basePath, '/mark-family-stale');
203
243
  return {
204
244
  registerRoutes(router) {
205
- Router.post(router, settings.basePath, createWriteHandler(settings, settings.basePath), routeOptions);
206
- Router.post(router, updatePath, createUpdateHandler(settings, updatePath), routeOptions);
207
- Router.post(router, markFamilyStalePath, createMarkFamilyStaleHandler(settings, markFamilyStalePath), routeOptions);
245
+ registerRoutes(router, settings);
208
246
  },
209
247
  };
210
248
  },
@@ -13,6 +13,13 @@ const normalizePath = (value) => {
13
13
  return '/zin/trace/write';
14
14
  return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
15
15
  };
16
+ const trimTrailingSlashes = (value) => {
17
+ let trimmed = value;
18
+ while (trimmed.endsWith('/')) {
19
+ trimmed = trimmed.slice(0, -1);
20
+ }
21
+ return trimmed;
22
+ };
16
23
  const createUnsupportedReadError = () => ErrorFactory.createConfigError('Trace proxy sender storage does not expose dashboard/query operations. Use the trace server for reads.');
17
24
  const buildSettings = (settings) => {
18
25
  ensureConfigured(settings);
@@ -37,7 +44,7 @@ const buildSettings = (settings) => {
37
44
  };
38
45
  };
39
46
  const appendSuffix = (path, suffix) => {
40
- const base = normalizePath(path).replace(/\/+$/, '');
47
+ const base = trimTrailingSlashes(normalizePath(path));
41
48
  const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
42
49
  return `${base}${tail}`;
43
50
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.9.4",
3
+ "version": "1.2.0",
4
4
  "description": "Trace assistant for ZinTrust: logs requests, queries, exceptions, jobs, and more.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -40,7 +40,7 @@
40
40
  "node": ">=20.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@zintrust/core": "^0.9.4"
43
+ "@zintrust/core": "^0.9.6"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
@@ -56,8 +56,16 @@ const parseMiddleware = (value: string): ReadonlyArray<string> =>
56
56
  .map((entry) => entry.trim())
57
57
  .filter((entry) => entry.length > 0);
58
58
 
59
+ const trimTrailingSlashes = (value: string): string => {
60
+ let trimmed = value;
61
+ while (trimmed.endsWith('/')) {
62
+ trimmed = trimmed.slice(0, -1);
63
+ }
64
+ return trimmed;
65
+ };
66
+
59
67
  const appendSuffix = (path: string, suffix: string): string => {
60
- const base = normalizePath(path).replace(/\/+$/, '');
68
+ const base = trimTrailingSlashes(normalizePath(path));
61
69
  const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
62
70
  return `${base}${tail}`;
63
71
  };
@@ -259,49 +267,84 @@ const resolveStorage = (overrides?: TraceIngestGatewayOverrides): ITraceStorage
259
267
  return TraceStorage.resolveStorage(db);
260
268
  };
261
269
 
262
- const readSettings = (overrides?: TraceIngestGatewayOverrides): TraceIngestGatewaySettings => {
263
- const configuredSecret = (overrides?.secret ?? Env.get('TRACE_PROXY_SECRET', '')).trim();
264
- const configuredKeyId = (overrides?.keyId ?? Env.get('TRACE_PROXY_KEY_ID', '')).trim();
270
+ const readConfiguredKeyId = (overrides?: TraceIngestGatewayOverrides): string => {
271
+ return (overrides?.keyId ?? Env.get('TRACE_PROXY_KEY_ID', '')).trim();
272
+ };
273
+
274
+ const readConfiguredSecret = (overrides?: TraceIngestGatewayOverrides): string => {
275
+ return (overrides?.secret ?? Env.get('TRACE_PROXY_SECRET', '')).trim();
276
+ };
277
+
278
+ const resolveKeyId = (overrides?: TraceIngestGatewayOverrides): string => {
279
+ const configuredKeyId = readConfiguredKeyId(overrides);
280
+ if (configuredKeyId !== '') return configuredKeyId;
281
+ return (Env.APP_NAME || 'zintrust').trim();
282
+ };
283
+
284
+ const resolveSecret = (overrides?: TraceIngestGatewayOverrides): string => {
285
+ const configuredSecret = readConfiguredSecret(overrides);
286
+ if (configuredSecret !== '') return configuredSecret;
287
+ return Env.APP_KEY;
288
+ };
265
289
 
290
+ const resolveSigningWindowMs = (overrides?: TraceIngestGatewayOverrides): number => {
291
+ return overrides?.signingWindowMs ?? Env.getInt('TRACE_PROXY_SIGNING_WINDOW_MS', 60000);
292
+ };
293
+
294
+ const resolveNonceTtlMs = (overrides?: TraceIngestGatewayOverrides): number => {
295
+ return overrides?.nonceTtlMs ?? Env.getInt('TRACE_PROXY_NONCE_TTL_MS', 120000);
296
+ };
297
+
298
+ const resolveMiddleware = (overrides?: TraceIngestGatewayOverrides): ReadonlyArray<string> => {
299
+ return overrides?.middleware ?? parseMiddleware(Env.get('TRACE_PROXY_MIDDLEWARE', ''));
300
+ };
301
+
302
+ const readSettings = (overrides?: TraceIngestGatewayOverrides): TraceIngestGatewaySettings => {
266
303
  return {
267
304
  basePath: normalizePath(overrides?.basePath ?? Env.get('TRACE_PROXY_PATH', '/zin/trace/write')),
268
- keyId: configuredKeyId === '' ? (Env.APP_NAME || 'zintrust').trim() : configuredKeyId,
269
- secret: configuredSecret === '' ? Env.APP_KEY : configuredSecret,
270
- signingWindowMs:
271
- overrides?.signingWindowMs ?? Env.getInt('TRACE_PROXY_SIGNING_WINDOW_MS', 60000),
272
- nonceTtlMs: overrides?.nonceTtlMs ?? Env.getInt('TRACE_PROXY_NONCE_TTL_MS', 120000),
273
- middleware: overrides?.middleware ?? parseMiddleware(Env.get('TRACE_PROXY_MIDDLEWARE', '')),
305
+ keyId: resolveKeyId(overrides),
306
+ secret: resolveSecret(overrides),
307
+ signingWindowMs: resolveSigningWindowMs(overrides),
308
+ nonceTtlMs: resolveNonceTtlMs(overrides),
309
+ middleware: resolveMiddleware(overrides),
274
310
  storage: resolveStorage(overrides),
275
311
  };
276
312
  };
277
313
 
314
+ const getRouteOptions = (settings: TraceIngestGatewaySettings): RouteOptions | undefined => {
315
+ if (settings.middleware.length === 0) return undefined;
316
+ return { middleware: settings.middleware } as RouteOptions;
317
+ };
318
+
319
+ const registerRoutes = (router: IRouter, settings: TraceIngestGatewaySettings): void => {
320
+ const routeOptions = getRouteOptions(settings);
321
+ const updatePath = appendSuffix(settings.basePath, '/update');
322
+ const markFamilyStalePath = appendSuffix(settings.basePath, '/mark-family-stale');
323
+
324
+ Router.post(
325
+ router,
326
+ settings.basePath,
327
+ createWriteHandler(settings, settings.basePath),
328
+ routeOptions
329
+ );
330
+ Router.post(router, updatePath, createUpdateHandler(settings, updatePath), routeOptions);
331
+ Router.post(
332
+ router,
333
+ markFamilyStalePath,
334
+ createMarkFamilyStaleHandler(settings, markFamilyStalePath),
335
+ routeOptions
336
+ );
337
+ };
338
+
278
339
  export const TraceIngestGateway = Object.freeze({
279
340
  create(overrides?: TraceIngestGatewayOverrides): {
280
341
  registerRoutes: (router: IRouter) => void;
281
342
  } {
282
343
  const settings = readSettings(overrides);
283
- const routeOptions: RouteOptions | undefined =
284
- settings.middleware.length > 0
285
- ? ({ middleware: settings.middleware } as RouteOptions)
286
- : undefined;
287
- const updatePath = appendSuffix(settings.basePath, '/update');
288
- const markFamilyStalePath = appendSuffix(settings.basePath, '/mark-family-stale');
289
344
 
290
345
  return {
291
346
  registerRoutes(router: IRouter): void {
292
- Router.post(
293
- router,
294
- settings.basePath,
295
- createWriteHandler(settings, settings.basePath),
296
- routeOptions
297
- );
298
- Router.post(router, updatePath, createUpdateHandler(settings, updatePath), routeOptions);
299
- Router.post(
300
- router,
301
- markFamilyStalePath,
302
- createMarkFamilyStaleHandler(settings, markFamilyStalePath),
303
- routeOptions
304
- );
347
+ registerRoutes(router, settings);
305
348
  },
306
349
  };
307
350
  },
@@ -41,6 +41,14 @@ const normalizePath = (value: string): string => {
41
41
  return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
42
42
  };
43
43
 
44
+ const trimTrailingSlashes = (value: string): string => {
45
+ let trimmed = value;
46
+ while (trimmed.endsWith('/')) {
47
+ trimmed = trimmed.slice(0, -1);
48
+ }
49
+ return trimmed;
50
+ };
51
+
44
52
  const createUnsupportedReadError = (): Error =>
45
53
  ErrorFactory.createConfigError(
46
54
  'Trace proxy sender storage does not expose dashboard/query operations. Use the trace server for reads.'
@@ -90,7 +98,7 @@ const buildSettings = (settings: ProxyTraceStorageSettings): ProxyRequestSetting
90
98
  };
91
99
 
92
100
  const appendSuffix = (path: string, suffix: string): string => {
93
- const base = normalizePath(path).replace(/\/+$/, '');
101
+ const base = trimTrailingSlashes(normalizePath(path));
94
102
  const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
95
103
  return `${base}${tail}`;
96
104
  };
@@ -1,13 +0,0 @@
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';
@@ -1,195 +0,0 @@
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 });