@zintrust/trace 0.4.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +288 -0
  2. package/dist/build-manifest.json +365 -0
  3. package/dist/cli-register.d.ts +9 -0
  4. package/dist/cli-register.js +32 -0
  5. package/dist/config.d.ts +9 -0
  6. package/dist/config.js +38 -0
  7. package/dist/context.d.ts +18 -0
  8. package/dist/context.js +86 -0
  9. package/dist/dashboard/handlers.d.ts +15 -0
  10. package/dist/dashboard/handlers.js +179 -0
  11. package/dist/dashboard/routes.d.ts +19 -0
  12. package/dist/dashboard/routes.js +50 -0
  13. package/dist/dashboard/ui.d.ts +2 -0
  14. package/dist/dashboard/ui.js +870 -0
  15. package/dist/index.d.ts +35 -0
  16. package/dist/index.js +50 -0
  17. package/dist/migrations/20260331000001_create_zin_debugger_entries_table.d.ts +10 -0
  18. package/dist/migrations/20260331000001_create_zin_debugger_entries_table.js +28 -0
  19. package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.d.ts +10 -0
  20. package/dist/migrations/20260331000002_create_zin_debugger_entries_tags_table.js +21 -0
  21. package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.d.ts +10 -0
  22. package/dist/migrations/20260331000003_create_zin_debugger_monitoring_table.js +17 -0
  23. package/dist/migrations/index.d.ts +6 -0
  24. package/dist/migrations/index.js +4 -0
  25. package/dist/plugin.d.ts +1 -0
  26. package/dist/plugin.js +3 -0
  27. package/dist/register.d.ts +1 -0
  28. package/dist/register.js +140 -0
  29. package/dist/storage/DebuggerStorage.d.ts +13 -0
  30. package/dist/storage/DebuggerStorage.js +195 -0
  31. package/dist/storage/TraceStorage.d.ts +13 -0
  32. package/dist/storage/TraceStorage.js +195 -0
  33. package/dist/storage/index.d.ts +2 -0
  34. package/dist/storage/index.js +1 -0
  35. package/dist/types.d.ts +270 -0
  36. package/dist/types.js +25 -0
  37. package/dist/ui.d.ts +8 -0
  38. package/dist/ui.js +7 -0
  39. package/dist/utils/authTag.d.ts +5 -0
  40. package/dist/utils/authTag.js +18 -0
  41. package/dist/utils/familyHash.d.ts +1 -0
  42. package/dist/utils/familyHash.js +8 -0
  43. package/dist/utils/redact.d.ts +6 -0
  44. package/dist/utils/redact.js +49 -0
  45. package/dist/utils/requestFilter.d.ts +4 -0
  46. package/dist/utils/requestFilter.js +26 -0
  47. package/dist/utils/stackFrame.d.ts +6 -0
  48. package/dist/utils/stackFrame.js +38 -0
  49. package/dist/watchers/AuthWatcher.d.ts +6 -0
  50. package/dist/watchers/AuthWatcher.js +49 -0
  51. package/dist/watchers/BatchWatcher.d.ts +6 -0
  52. package/dist/watchers/BatchWatcher.js +46 -0
  53. package/dist/watchers/CacheWatcher.d.ts +6 -0
  54. package/dist/watchers/CacheWatcher.js +51 -0
  55. package/dist/watchers/CommandWatcher.d.ts +6 -0
  56. package/dist/watchers/CommandWatcher.js +49 -0
  57. package/dist/watchers/DumpWatcher.d.ts +7 -0
  58. package/dist/watchers/DumpWatcher.js +41 -0
  59. package/dist/watchers/EventWatcher.d.ts +6 -0
  60. package/dist/watchers/EventWatcher.js +42 -0
  61. package/dist/watchers/ExceptionWatcher.d.ts +4 -0
  62. package/dist/watchers/ExceptionWatcher.js +103 -0
  63. package/dist/watchers/GateWatcher.d.ts +6 -0
  64. package/dist/watchers/GateWatcher.js +45 -0
  65. package/dist/watchers/HttpClientWatcher.d.ts +6 -0
  66. package/dist/watchers/HttpClientWatcher.js +50 -0
  67. package/dist/watchers/HttpWatcher.d.ts +2 -0
  68. package/dist/watchers/HttpWatcher.js +71 -0
  69. package/dist/watchers/JobWatcher.d.ts +10 -0
  70. package/dist/watchers/JobWatcher.js +108 -0
  71. package/dist/watchers/LogWatcher.d.ts +2 -0
  72. package/dist/watchers/LogWatcher.js +50 -0
  73. package/dist/watchers/MailWatcher.d.ts +6 -0
  74. package/dist/watchers/MailWatcher.js +45 -0
  75. package/dist/watchers/MiddlewareWatcher.d.ts +6 -0
  76. package/dist/watchers/MiddlewareWatcher.js +41 -0
  77. package/dist/watchers/ModelWatcher.d.ts +6 -0
  78. package/dist/watchers/ModelWatcher.js +42 -0
  79. package/dist/watchers/NotificationWatcher.d.ts +6 -0
  80. package/dist/watchers/NotificationWatcher.js +42 -0
  81. package/dist/watchers/QueryWatcher.d.ts +2 -0
  82. package/dist/watchers/QueryWatcher.js +72 -0
  83. package/dist/watchers/RedisWatcher.d.ts +7 -0
  84. package/dist/watchers/RedisWatcher.js +38 -0
  85. package/dist/watchers/ScheduleWatcher.d.ts +6 -0
  86. package/dist/watchers/ScheduleWatcher.js +46 -0
  87. package/dist/watchers/ViewWatcher.d.ts +6 -0
  88. package/dist/watchers/ViewWatcher.js +36 -0
  89. package/package.json +59 -0
  90. package/src/cli-register.ts +63 -0
  91. package/src/config.ts +46 -0
  92. package/src/context.ts +101 -0
  93. package/src/dashboard/handlers.ts +197 -0
  94. package/src/dashboard/routes.ts +101 -0
  95. package/src/dashboard/ui.ts +879 -0
  96. package/src/dashboard/zintrust-debuger.svg +30 -0
  97. package/src/index.ts +88 -0
  98. package/src/plugin.ts +9 -0
  99. package/src/register.ts +219 -0
  100. package/src/storage/TraceStorage.ts +306 -0
  101. package/src/storage/index.ts +2 -0
  102. package/src/types.ts +317 -0
  103. package/src/ui.ts +9 -0
  104. package/src/utils/authTag.ts +20 -0
  105. package/src/utils/familyHash.ts +8 -0
  106. package/src/utils/redact.ts +64 -0
  107. package/src/utils/requestFilter.ts +33 -0
  108. package/src/utils/stackFrame.ts +44 -0
  109. package/src/watchers/AuthWatcher.ts +50 -0
  110. package/src/watchers/BatchWatcher.ts +52 -0
  111. package/src/watchers/CacheWatcher.ts +58 -0
  112. package/src/watchers/CommandWatcher.ts +55 -0
  113. package/src/watchers/DumpWatcher.ts +42 -0
  114. package/src/watchers/EventWatcher.ts +43 -0
  115. package/src/watchers/ExceptionWatcher.ts +114 -0
  116. package/src/watchers/GateWatcher.ts +50 -0
  117. package/src/watchers/HttpClientWatcher.ts +56 -0
  118. package/src/watchers/HttpWatcher.ts +94 -0
  119. package/src/watchers/JobWatcher.ts +121 -0
  120. package/src/watchers/LogWatcher.ts +61 -0
  121. package/src/watchers/MailWatcher.ts +47 -0
  122. package/src/watchers/MiddlewareWatcher.ts +42 -0
  123. package/src/watchers/ModelWatcher.ts +48 -0
  124. package/src/watchers/NotificationWatcher.ts +43 -0
  125. package/src/watchers/QueryWatcher.ts +85 -0
  126. package/src/watchers/RedisWatcher.ts +39 -0
  127. package/src/watchers/ScheduleWatcher.ts +54 -0
  128. package/src/watchers/ViewWatcher.ts +37 -0
@@ -0,0 +1,195 @@
1
+ import { familyHash } from '../utils/familyHash';
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 });
@@ -0,0 +1,2 @@
1
+ export { TraceStorage } from './TraceStorage';
2
+ export type { ITraceStorage } from './TraceStorage';
@@ -0,0 +1 @@
1
+ export { TraceStorage } from './TraceStorage';
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Types for @zintrust/trace
3
+ * Sealed type definitions — no side effects.
4
+ */
5
+ import type { IDatabase } from '@zintrust/core';
6
+ export declare const EntryType: Readonly<{
7
+ readonly REQUEST: "request";
8
+ readonly QUERY: "query";
9
+ readonly EXCEPTION: "exception";
10
+ readonly LOG: "log";
11
+ readonly JOB: "job";
12
+ readonly CACHE: "cache";
13
+ readonly SCHEDULE: "schedule";
14
+ readonly MAIL: "mail";
15
+ readonly AUTH: "auth";
16
+ readonly EVENT: "event";
17
+ readonly MODEL: "model";
18
+ readonly NOTIFICATION: "notification";
19
+ readonly REDIS: "redis";
20
+ readonly GATE: "gate";
21
+ readonly MIDDLEWARE: "middleware";
22
+ readonly COMMAND: "command";
23
+ readonly BATCH: "batch";
24
+ readonly DUMP: "dump";
25
+ readonly VIEW: "view";
26
+ readonly CLIENT_REQUEST: "client_request";
27
+ }>;
28
+ export type EntryTypeValue = (typeof EntryType)[keyof typeof EntryType];
29
+ export interface RequestContent {
30
+ method: string;
31
+ uri: string;
32
+ headers: Record<string, string>;
33
+ payload: Record<string, unknown>;
34
+ responseStatus: number;
35
+ responseHeaders: Record<string, string>;
36
+ duration: number;
37
+ memory: number | null;
38
+ middleware: string[];
39
+ hostname: string;
40
+ userId?: string;
41
+ }
42
+ export interface QueryContent {
43
+ connection: string;
44
+ sql: string;
45
+ time: number;
46
+ duration: number;
47
+ slow: boolean;
48
+ hash: string;
49
+ hostname: string;
50
+ }
51
+ export interface ExceptionContent {
52
+ class: string;
53
+ file: string;
54
+ line: number;
55
+ message: string;
56
+ trace: Array<{
57
+ file: string;
58
+ line: number;
59
+ function?: string;
60
+ }>;
61
+ linePreview: Record<string, string>;
62
+ occurrences: number;
63
+ hostname: string;
64
+ userId?: string;
65
+ }
66
+ export interface LogContent {
67
+ level: string;
68
+ message: string;
69
+ context?: Record<string, unknown>;
70
+ hostname: string;
71
+ }
72
+ export interface JobContent {
73
+ status: 'pending' | 'processed' | 'failed';
74
+ connection: string;
75
+ queue: string;
76
+ name: string;
77
+ tries?: number;
78
+ timeout?: number;
79
+ data?: unknown;
80
+ exception?: {
81
+ message: string;
82
+ trace: Array<{
83
+ file: string;
84
+ line: number;
85
+ }>;
86
+ };
87
+ hostname: string;
88
+ }
89
+ export interface CacheContent {
90
+ operation: 'get' | 'set' | 'delete' | 'clear' | 'has';
91
+ key: string;
92
+ hit?: boolean;
93
+ duration: number;
94
+ hostname: string;
95
+ }
96
+ export interface ScheduleContent {
97
+ name: string;
98
+ expression: string;
99
+ status: 'ran' | 'failed' | 'skipped';
100
+ duration: number;
101
+ output?: string;
102
+ hostname: string;
103
+ }
104
+ export interface MailContent {
105
+ to: string;
106
+ subject: string;
107
+ template?: string;
108
+ hostname: string;
109
+ }
110
+ export interface AuthContent {
111
+ event: 'login' | 'logout' | 'failed';
112
+ userId?: string;
113
+ hostname: string;
114
+ }
115
+ export interface EventContent {
116
+ name: string;
117
+ payload?: unknown;
118
+ listenerCount: number;
119
+ hostname: string;
120
+ }
121
+ export interface ModelContent {
122
+ action: 'create' | 'update' | 'delete';
123
+ model: string;
124
+ id?: string | number;
125
+ changes?: Record<string, unknown>;
126
+ hostname: string;
127
+ }
128
+ export interface NotificationContent {
129
+ channels: string[];
130
+ notifiable?: string;
131
+ notification: string;
132
+ hostname: string;
133
+ }
134
+ export interface RedisContent {
135
+ command: string;
136
+ duration: number;
137
+ hostname: string;
138
+ }
139
+ export interface GateContent {
140
+ ability: string;
141
+ result: 'allowed' | 'denied';
142
+ userId?: string;
143
+ subject?: string;
144
+ hostname: string;
145
+ }
146
+ export interface MiddlewareContent {
147
+ name: string;
148
+ event: 'before' | 'after';
149
+ duration?: number;
150
+ hostname: string;
151
+ }
152
+ export interface CommandContent {
153
+ name: string;
154
+ arguments: Record<string, unknown>;
155
+ exitCode: number;
156
+ duration: number;
157
+ output?: string;
158
+ hostname: string;
159
+ }
160
+ export interface BatchContent {
161
+ name: string;
162
+ total: number;
163
+ processed: number;
164
+ failed: number;
165
+ status: 'pending' | 'processing' | 'finished' | 'failed';
166
+ hostname: string;
167
+ }
168
+ export interface DumpContent {
169
+ value: unknown;
170
+ file?: string;
171
+ line?: number;
172
+ hostname: string;
173
+ }
174
+ export interface ViewContent {
175
+ template: string;
176
+ duration: number;
177
+ hostname: string;
178
+ }
179
+ export interface ClientRequestContent {
180
+ method: string;
181
+ url: string;
182
+ requestHeaders: Record<string, string>;
183
+ responseStatus: number;
184
+ duration: number;
185
+ hostname: string;
186
+ }
187
+ export interface ITraceEntry<T = unknown> {
188
+ uuid: string;
189
+ batchId: string;
190
+ familyHash?: string;
191
+ type: EntryTypeValue;
192
+ content: T;
193
+ tags: string[];
194
+ isLatest: boolean;
195
+ createdAt: number;
196
+ }
197
+ export interface QueryEntriesOptions {
198
+ type?: EntryTypeValue;
199
+ tag?: string;
200
+ batchId?: string;
201
+ from?: number;
202
+ to?: number;
203
+ page?: number;
204
+ perPage?: number;
205
+ }
206
+ export interface ITraceStorage {
207
+ writeEntry(entry: ITraceEntry): Promise<void>;
208
+ updateEntry(uuid: string, patch: Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>): Promise<void>;
209
+ markFamilyStale(familyHash: string, exceptUuid: string): Promise<void>;
210
+ queryEntries(opts: QueryEntriesOptions): Promise<{
211
+ data: ITraceEntry[];
212
+ total: number;
213
+ }>;
214
+ getEntry(uuid: string): Promise<ITraceEntry | null>;
215
+ getBatch(batchId: string): Promise<ITraceEntry[]>;
216
+ prune(olderThanMs: number, keepExceptions?: boolean): Promise<number>;
217
+ clear(): Promise<void>;
218
+ getMonitoring(): Promise<string[]>;
219
+ addMonitoring(tag: string): Promise<void>;
220
+ removeMonitoring(tag: string): Promise<void>;
221
+ stats(): Promise<Record<EntryTypeValue, number>>;
222
+ }
223
+ export interface ITraceWatcherConfig {
224
+ storage: ITraceStorage;
225
+ config: ITraceConfig;
226
+ db?: IDatabase;
227
+ /** Optional: provide to allow HttpWatcher to register as global middleware. */
228
+ registerMiddleware?: (fn: (req: unknown, res: unknown, next: () => Promise<void>) => Promise<void>) => void;
229
+ }
230
+ export interface ITraceWatcher {
231
+ register(opts: ITraceWatcherConfig): () => void;
232
+ }
233
+ export type RedactionConfig = {
234
+ headers: string[];
235
+ body: string[];
236
+ query: string[];
237
+ };
238
+ export type WatcherToggles = {
239
+ request?: boolean;
240
+ query?: boolean;
241
+ exception?: boolean;
242
+ log?: boolean;
243
+ job?: boolean;
244
+ cache?: boolean;
245
+ schedule?: boolean;
246
+ mail?: boolean;
247
+ auth?: boolean;
248
+ event?: boolean;
249
+ model?: boolean;
250
+ notification?: boolean;
251
+ redis?: boolean;
252
+ gate?: boolean;
253
+ middleware?: boolean;
254
+ command?: boolean;
255
+ batch?: boolean;
256
+ dump?: boolean;
257
+ view?: boolean;
258
+ clientRequest?: boolean;
259
+ };
260
+ export interface ITraceConfig {
261
+ enabled: boolean;
262
+ connection?: string;
263
+ pruneAfterHours: number;
264
+ ignoreRoutes: string[];
265
+ slowQueryThreshold: number;
266
+ logMinLevel: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
267
+ watchers: WatcherToggles;
268
+ redaction: RedactionConfig;
269
+ }
270
+ export type TraceConfigOverrides = Partial<ITraceConfig>;
package/dist/types.js ADDED
@@ -0,0 +1,25 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Entry types used by the trace event stream.
3
+ // ---------------------------------------------------------------------------
4
+ export const EntryType = Object.freeze({
5
+ REQUEST: 'request',
6
+ QUERY: 'query',
7
+ EXCEPTION: 'exception',
8
+ LOG: 'log',
9
+ JOB: 'job',
10
+ CACHE: 'cache',
11
+ SCHEDULE: 'schedule',
12
+ MAIL: 'mail',
13
+ AUTH: 'auth',
14
+ EVENT: 'event',
15
+ MODEL: 'model',
16
+ NOTIFICATION: 'notification',
17
+ REDIS: 'redis',
18
+ GATE: 'gate',
19
+ MIDDLEWARE: 'middleware',
20
+ COMMAND: 'command',
21
+ BATCH: 'batch',
22
+ DUMP: 'dump',
23
+ VIEW: 'view',
24
+ CLIENT_REQUEST: 'client_request',
25
+ });
package/dist/ui.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Lightweight UI/dashboard entrypoint for @zintrust/trace.
3
+ *
4
+ * Import this subpath when you only need trace dashboard registration
5
+ * without pulling in the package root re-export surface.
6
+ */
7
+ export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes';
8
+ export type { TraceDashboardOptions, TraceDashboardRegistrationOptions } from './dashboard/routes';
package/dist/ui.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Lightweight UI/dashboard entrypoint for @zintrust/trace.
3
+ *
4
+ * Import this subpath when you only need trace dashboard registration
5
+ * without pulling in the package root re-export surface.
6
+ */
7
+ export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes';
@@ -0,0 +1,5 @@
1
+ export declare const AuthTag: Readonly<{
2
+ append: (tags: string[]) => string[];
3
+ resolve: () => string | undefined;
4
+ }>;
5
+ export default AuthTag;
@@ -0,0 +1,18 @@
1
+ import { TraceContext } from '../context';
2
+ const resolveAuthTag = () => {
3
+ const userId = TraceContext.getUserId();
4
+ if (userId === undefined || userId === '')
5
+ return undefined;
6
+ return `Auth:${userId}`;
7
+ };
8
+ const appendAuthTag = (tags) => {
9
+ const authTag = resolveAuthTag();
10
+ if (authTag === undefined || tags.includes(authTag))
11
+ return tags;
12
+ return [...tags, authTag];
13
+ };
14
+ export const AuthTag = Object.freeze({
15
+ append: appendAuthTag,
16
+ resolve: resolveAuthTag,
17
+ });
18
+ export default AuthTag;
@@ -0,0 +1 @@
1
+ export declare const familyHash: (input: string) => string;
@@ -0,0 +1,8 @@
1
+ export const familyHash = (input) => {
2
+ let hash = 2166136261;
3
+ for (let index = 0; index < input.length; index++) {
4
+ hash ^= input.codePointAt(index) ?? 0;
5
+ hash = (hash * 16777619) >>> 0;
6
+ }
7
+ return hash.toString(16).padStart(8, '0');
8
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Redaction helpers for @zintrust/trace watchers.
3
+ */
4
+ export declare const redactHeaders: (headers: Record<string, string>, fields: string[]) => Record<string, string>;
5
+ export declare const redactObject: (obj: Record<string, unknown>, fields: string[]) => Record<string, unknown>;
6
+ export declare const redactString: (value: string, fields: string[]) => string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Redaction helpers for @zintrust/trace watchers.
3
+ */
4
+ const REDACTED = '[REDACTED]';
5
+ const redactQuerySegment = (segment, fields) => {
6
+ const separatorIndex = segment.indexOf('=');
7
+ if (separatorIndex <= 0)
8
+ return segment;
9
+ const key = segment.slice(0, separatorIndex);
10
+ const value = segment.slice(separatorIndex + 1);
11
+ if (!fields.has(key.toLowerCase()))
12
+ return `${key}=${value}`;
13
+ return `${key}=${REDACTED}`;
14
+ };
15
+ export const redactHeaders = (headers, fields) => {
16
+ const lower = new Set(fields.map((f) => f.toLowerCase()));
17
+ const out = {};
18
+ for (const [k, v] of Object.entries(headers)) {
19
+ out[k] = lower.has(k.toLowerCase()) ? REDACTED : v;
20
+ }
21
+ return out;
22
+ };
23
+ export const redactObject = (obj, fields) => {
24
+ const lower = new Set(fields.map((f) => f.toLowerCase()));
25
+ const out = {};
26
+ for (const [k, v] of Object.entries(obj)) {
27
+ out[k] = lower.has(k.toLowerCase()) ? REDACTED : v;
28
+ }
29
+ return out;
30
+ };
31
+ export const redactString = (value, fields) => {
32
+ const lower = new Set(fields.map((f) => f.toLowerCase()));
33
+ if (value === '')
34
+ return value;
35
+ let output = '';
36
+ let segmentStart = 0;
37
+ for (let index = 0; index <= value.length; index += 1) {
38
+ const isBoundary = index === value.length || value[index] === '&';
39
+ if (!isBoundary)
40
+ continue;
41
+ const segment = value.slice(segmentStart, index);
42
+ output += redactQuerySegment(segment, lower);
43
+ if (index < value.length) {
44
+ output += '&';
45
+ }
46
+ segmentStart = index + 1;
47
+ }
48
+ return output;
49
+ };
@@ -0,0 +1,4 @@
1
+ export declare const RequestFilter: Readonly<{
2
+ matchesIgnoredPath: (path: string, ignoreRoutes: string[]) => boolean;
3
+ shouldIgnoreCurrentRequest: (ignoreRoutes: string[]) => boolean;
4
+ }>;
@@ -0,0 +1,26 @@
1
+ import { TraceContext } from '../context';
2
+ const normalizePath = (input) => {
3
+ const trimmed = input.trim();
4
+ const [pathOnly] = trimmed.split('?');
5
+ if (!pathOnly || pathOnly === '')
6
+ return '/';
7
+ return pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
8
+ };
9
+ const matchesIgnoredPath = (path, ignoreRoutes) => {
10
+ const normalizedPath = normalizePath(path);
11
+ return ignoreRoutes.some((route) => {
12
+ const normalizedRoute = normalizePath(route);
13
+ return (normalizedPath === normalizedRoute ||
14
+ normalizedPath.startsWith(normalizedRoute.endsWith('/') ? normalizedRoute : `${normalizedRoute}/`));
15
+ });
16
+ };
17
+ const shouldIgnoreCurrentRequest = (ignoreRoutes) => {
18
+ const currentPath = TraceContext.getRequestPath();
19
+ if (typeof currentPath !== 'string' || currentPath === '')
20
+ return false;
21
+ return matchesIgnoredPath(currentPath, ignoreRoutes);
22
+ };
23
+ export const RequestFilter = Object.freeze({
24
+ matchesIgnoredPath,
25
+ shouldIgnoreCurrentRequest,
26
+ });
@@ -0,0 +1,6 @@
1
+ type StackFrame = {
2
+ file: string;
3
+ line: number;
4
+ };
5
+ export declare const parseStackFrameLine: (line: string) => StackFrame | null;
6
+ export {};
@@ -0,0 +1,38 @@
1
+ const FRAME_PREFIX = 'at ';
2
+ const parsePositiveInt = (value) => {
3
+ if (value === '')
4
+ return null;
5
+ for (const char of value) {
6
+ if (char < '0' || char > '9')
7
+ return null;
8
+ }
9
+ const parsed = Number.parseInt(value, 10);
10
+ return Number.isNaN(parsed) ? null : parsed;
11
+ };
12
+ const parseFrameLocation = (value) => {
13
+ const columnSeparatorIndex = value.lastIndexOf(':');
14
+ if (columnSeparatorIndex <= 0)
15
+ return null;
16
+ const lineSeparatorIndex = value.lastIndexOf(':', columnSeparatorIndex - 1);
17
+ if (lineSeparatorIndex <= 0)
18
+ return null;
19
+ const file = value.slice(0, lineSeparatorIndex).trim();
20
+ const line = parsePositiveInt(value.slice(lineSeparatorIndex + 1, columnSeparatorIndex));
21
+ const column = parsePositiveInt(value.slice(columnSeparatorIndex + 1));
22
+ if (file === '' || line === null || column === null)
23
+ return null;
24
+ return { file, line };
25
+ };
26
+ export const parseStackFrameLine = (line) => {
27
+ const trimmed = line.trim();
28
+ if (!trimmed.startsWith(FRAME_PREFIX))
29
+ return null;
30
+ const body = trimmed.slice(FRAME_PREFIX.length).trim();
31
+ if (body === '')
32
+ return null;
33
+ const wrappedStartIndex = body.lastIndexOf(' (');
34
+ if (wrappedStartIndex !== -1 && body.endsWith(')')) {
35
+ return parseFrameLocation(body.slice(wrappedStartIndex + 2, -1));
36
+ }
37
+ return parseFrameLocation(body);
38
+ };