@zintrust/trace 1.6.5 → 1.6.6

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 (50) hide show
  1. package/dist/watchers/HttpWatcher.js +23 -8
  2. package/package.json +3 -2
  3. package/src/TraceConnection.ts +182 -0
  4. package/src/cli-register.ts +63 -0
  5. package/src/config.ts +383 -0
  6. package/src/context.ts +101 -0
  7. package/src/dashboard/handlers.ts +353 -0
  8. package/src/dashboard/routes.ts +114 -0
  9. package/src/dashboard/ui.ts +1262 -0
  10. package/src/dashboard/zintrust-debuger.svg +30 -0
  11. package/src/index.ts +102 -0
  12. package/src/ingest/TraceIngestGateway.ts +414 -0
  13. package/src/plugin.ts +9 -0
  14. package/src/register.ts +702 -0
  15. package/src/storage/ProxyTraceStorage.ts +190 -0
  16. package/src/storage/TraceContentBudget.ts +493 -0
  17. package/src/storage/TraceContentRedaction.ts +44 -0
  18. package/src/storage/TraceEntryFiltering.ts +50 -0
  19. package/src/storage/TraceServiceTag.ts +56 -0
  20. package/src/storage/TraceStorage.ts +543 -0
  21. package/src/storage/TraceWriteDiagnostics.ts +289 -0
  22. package/src/storage/index.ts +4 -0
  23. package/src/types.ts +430 -0
  24. package/src/ui.ts +9 -0
  25. package/src/utils/authTag.ts +20 -0
  26. package/src/utils/entryFilter.ts +131 -0
  27. package/src/utils/familyHash.ts +8 -0
  28. package/src/utils/redact.ts +112 -0
  29. package/src/utils/requestFilter.ts +79 -0
  30. package/src/utils/stackFrame.ts +44 -0
  31. package/src/watchers/AuthWatcher.ts +53 -0
  32. package/src/watchers/BatchWatcher.ts +55 -0
  33. package/src/watchers/CacheWatcher.ts +72 -0
  34. package/src/watchers/CommandWatcher.ts +58 -0
  35. package/src/watchers/DumpWatcher.ts +45 -0
  36. package/src/watchers/EventWatcher.ts +46 -0
  37. package/src/watchers/ExceptionWatcher.ts +130 -0
  38. package/src/watchers/GateWatcher.ts +53 -0
  39. package/src/watchers/HttpClientWatcher.ts +219 -0
  40. package/src/watchers/HttpWatcher.ts +249 -0
  41. package/src/watchers/JobWatcher.ts +124 -0
  42. package/src/watchers/LogWatcher.ts +120 -0
  43. package/src/watchers/MailWatcher.ts +65 -0
  44. package/src/watchers/MiddlewareWatcher.ts +54 -0
  45. package/src/watchers/ModelWatcher.ts +60 -0
  46. package/src/watchers/NotificationWatcher.ts +60 -0
  47. package/src/watchers/QueryWatcher.ts +105 -0
  48. package/src/watchers/RedisWatcher.ts +42 -0
  49. package/src/watchers/ScheduleWatcher.ts +57 -0
  50. package/src/watchers/ViewWatcher.ts +40 -0
@@ -0,0 +1,30 @@
1
+ <svg width="120" height="120" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="zt-g1e" x1="15" y1="15" x2="85" y2="85" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#38bdf8" />
5
+ <stop offset="1" stop-color="#22c55e" />
6
+ </linearGradient>
7
+ </defs>
8
+ <path
9
+ d="M50 8L18 22V46C18 66.2 32 84.1 50 92C68 84.1 82 66.2 82 46V22L50 8Z"
10
+ stroke="url(#zt-g1e)"
11
+ stroke-width="6"
12
+ stroke-linejoin="round"
13
+ />
14
+ <path
15
+ d="M34 54H42L46 44L52 62L58 50H66"
16
+ stroke="white"
17
+ stroke-width="8"
18
+ stroke-linecap="round"
19
+ stroke-linejoin="round"
20
+ />
21
+ <circle cx="34" cy="54" r="2.8" fill="white" fill-opacity="0.7" />
22
+ <circle cx="66" cy="50" r="2.8" fill="white" fill-opacity="0.7" />
23
+ <path
24
+ d="M30 28H70"
25
+ stroke="white"
26
+ stroke-opacity="0.12"
27
+ stroke-width="3"
28
+ stroke-linecap="round"
29
+ />
30
+ </svg>
package/src/index.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @zintrust/trace — public API surface.
3
+ *
4
+ * Zero side-effects. Import watchers, storage, dashboard, and config
5
+ * individually. For full auto-initialisation, use:
6
+ * import '@zintrust/trace/register';
7
+ */
8
+
9
+ import { ExceptionWatcher as ExceptionWatcherApi } from './watchers/ExceptionWatcher';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Config
13
+ // ---------------------------------------------------------------------------
14
+ export { TraceConfig } from './config';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Storage
18
+ // ---------------------------------------------------------------------------
19
+ export { TraceStorage } from './storage';
20
+ export type { ITraceStorage } from './storage';
21
+ export { TraceContentBudget } from './storage/TraceContentBudget';
22
+ export { TraceContentRedaction } from './storage/TraceContentRedaction';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Context
26
+ // ---------------------------------------------------------------------------
27
+ export { TraceContext } from './context';
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Dashboard
31
+ // ---------------------------------------------------------------------------
32
+ export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes';
33
+ export type { TraceDashboardOptions, TraceDashboardRegistrationOptions } from './dashboard/routes';
34
+ export { registerTraceIngestGateway, TraceIngestGateway } from './ingest/TraceIngestGateway';
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Watchers (named re-exports for use with custom wiring)
38
+ // ---------------------------------------------------------------------------
39
+ export { AuthWatcher } from './watchers/AuthWatcher';
40
+ export { BatchWatcher } from './watchers/BatchWatcher';
41
+ export { CacheWatcher } from './watchers/CacheWatcher';
42
+ export { CommandWatcher } from './watchers/CommandWatcher';
43
+ export { DumpWatcher } from './watchers/DumpWatcher';
44
+ export { EventWatcher } from './watchers/EventWatcher';
45
+ export { ExceptionWatcher } from './watchers/ExceptionWatcher';
46
+ export { GateWatcher } from './watchers/GateWatcher';
47
+ export { HttpClientWatcher } from './watchers/HttpClientWatcher';
48
+ export { HttpWatcher } from './watchers/HttpWatcher';
49
+ export { JobWatcher } from './watchers/JobWatcher';
50
+ export { LogWatcher } from './watchers/LogWatcher';
51
+ export { MailWatcher } from './watchers/MailWatcher';
52
+ export { MiddlewareWatcher } from './watchers/MiddlewareWatcher';
53
+ export { ModelWatcher } from './watchers/ModelWatcher';
54
+ export { NotificationWatcher } from './watchers/NotificationWatcher';
55
+ export { QueryWatcher } from './watchers/QueryWatcher';
56
+ export { RedisWatcher } from './watchers/RedisWatcher';
57
+ export { ScheduleWatcher } from './watchers/ScheduleWatcher';
58
+ export { ViewWatcher } from './watchers/ViewWatcher';
59
+
60
+ export const captureTraceException = (
61
+ error: unknown,
62
+ context?: { batchId?: string; hostname?: string; path?: string; userId?: string }
63
+ ): void => {
64
+ ExceptionWatcherApi.capture(error, context);
65
+ };
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Types
69
+ // ---------------------------------------------------------------------------
70
+ export { EntryType } from './types';
71
+ export type {
72
+ AuthContent,
73
+ BatchContent,
74
+ CacheContent,
75
+ ClientRequestContent,
76
+ CommandContent,
77
+ DumpContent,
78
+ EntryTypeValue,
79
+ EventContent,
80
+ ExceptionContent,
81
+ GateContent,
82
+ ITraceConfig,
83
+ ITraceEntry,
84
+ ITraceWatcher,
85
+ ITraceWatcherConfig,
86
+ JobContent,
87
+ LogContent,
88
+ MailContent,
89
+ MiddlewareContent,
90
+ ModelContent,
91
+ NotificationContent,
92
+ QueryContent,
93
+ RedactionConfig,
94
+ RedisContent,
95
+ RequestContent,
96
+ ScheduleContent,
97
+ TraceConfigOverrides,
98
+ TraceContentDispatchConfig,
99
+ TraceContentDispatchWorkerConfig,
100
+ ViewContent,
101
+ WatcherToggles,
102
+ } from './types';
@@ -0,0 +1,414 @@
1
+ import {
2
+ Env,
3
+ ErrorFactory,
4
+ Router,
5
+ SignedRequest,
6
+ useDatabase,
7
+ type IRequest,
8
+ type IResponse,
9
+ type IRouter,
10
+ type RouteOptions,
11
+ } from '@zintrust/core';
12
+ import { TraceConfig } from '../config';
13
+ import { TraceStorage } from '../storage';
14
+ import type { ITraceEntry, ITraceStorage } from '../types';
15
+
16
+ type TraceIngestGatewaySettings = {
17
+ basePath: string;
18
+ keyId: string;
19
+ secret: string;
20
+ signingWindowMs: number;
21
+ nonceTtlMs: number;
22
+ middleware: ReadonlyArray<string>;
23
+ storage: ITraceStorage;
24
+ };
25
+
26
+ type TraceIngestGatewayOverrides = Partial<
27
+ Omit<TraceIngestGatewaySettings, 'storage'> & { storage: ITraceStorage; connectionName: string }
28
+ >;
29
+
30
+ type TraceGatewayFailure = {
31
+ ok: false;
32
+ error: {
33
+ code: string;
34
+ message: string;
35
+ details?: unknown;
36
+ };
37
+ };
38
+
39
+ type TraceGatewaySuccess = {
40
+ ok: true;
41
+ };
42
+
43
+ const nonces = new Map<string, number>();
44
+
45
+ const nowMs = (): number => Date.now();
46
+
47
+ const normalizePath = (value: string): string => {
48
+ const trimmed = value.trim();
49
+ if (trimmed === '') return '/zin/trace/write';
50
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
51
+ };
52
+
53
+ const parseMiddleware = (value: string): ReadonlyArray<string> =>
54
+ value
55
+ .split(',')
56
+ .map((entry) => entry.trim())
57
+ .filter((entry) => entry.length > 0);
58
+
59
+ const isParameterizedRateLimitMiddleware = (value: string): boolean => {
60
+ return /^rateLimit:\d+:\d+(?:\.\d+)?$/.test(value.trim());
61
+ };
62
+
63
+ const trimTrailingSlashes = (value: string): string => {
64
+ let trimmed = value;
65
+ while (trimmed.endsWith('/')) {
66
+ trimmed = trimmed.slice(0, -1);
67
+ }
68
+ return trimmed;
69
+ };
70
+
71
+ const appendSuffix = (path: string, suffix: string): string => {
72
+ const base = trimTrailingSlashes(normalizePath(path));
73
+ const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
74
+ return `${base}${tail}`;
75
+ };
76
+
77
+ const cleanupExpiredNonces = (): void => {
78
+ const current = nowMs();
79
+ for (const [nonceKey, expiresAt] of nonces.entries()) {
80
+ if (expiresAt <= current) {
81
+ nonces.delete(nonceKey);
82
+ }
83
+ }
84
+ };
85
+
86
+ const storeNonce = async (keyId: string, nonce: string, ttlMs: number): Promise<boolean> => {
87
+ cleanupExpiredNonces();
88
+ const nonceKey = `${keyId}:${nonce}`;
89
+ if (nonces.has(nonceKey)) return false;
90
+ nonces.set(nonceKey, nowMs() + Math.max(ttlMs, 1));
91
+ return true;
92
+ };
93
+
94
+ const getBodyRecord = (req: IRequest): Record<string, unknown> => {
95
+ const body = req.getBody?.() ?? req.body;
96
+ if (typeof body === 'object' && body !== null && !Array.isArray(body)) {
97
+ return body as Record<string, unknown>;
98
+ }
99
+ return {};
100
+ };
101
+
102
+ const getRawBody = (req: IRequest): string => {
103
+ const rawText = req.context['rawBodyText'];
104
+ if (typeof rawText === 'string') return rawText;
105
+ return JSON.stringify(getBodyRecord(req));
106
+ };
107
+
108
+ const toIncomingHeaders = (req: IRequest): Record<string, string | undefined> => {
109
+ const headers = req.getHeaders();
110
+ const normalize = (value: string | string[] | undefined): string | undefined => {
111
+ if (Array.isArray(value)) return value.join(',');
112
+ return value;
113
+ };
114
+
115
+ return {
116
+ 'x-zt-key-id': normalize(headers['x-zt-key-id']),
117
+ 'x-zt-timestamp': normalize(headers['x-zt-timestamp']),
118
+ 'x-zt-nonce': normalize(headers['x-zt-nonce']),
119
+ 'x-zt-body-sha256': normalize(headers['x-zt-body-sha256']),
120
+ 'x-zt-signature': normalize(headers['x-zt-signature']),
121
+ };
122
+ };
123
+
124
+ const sendFailure = (
125
+ res: IResponse,
126
+ status: number,
127
+ code: string,
128
+ message: string,
129
+ details?: unknown
130
+ ): void => {
131
+ const payload: TraceGatewayFailure = {
132
+ ok: false,
133
+ error: { code, message, details },
134
+ };
135
+ res.status(status).json(payload);
136
+ };
137
+
138
+ const sendSuccess = (res: IResponse): void => {
139
+ const payload: TraceGatewaySuccess = { ok: true };
140
+ res.status(200).json(payload);
141
+ };
142
+
143
+ const verifyRequest = async (
144
+ req: IRequest,
145
+ bodyText: string,
146
+ settings: TraceIngestGatewaySettings,
147
+ path: string
148
+ ): Promise<{ ok: true } | { ok: false; code: string; status: number; message: string }> => {
149
+ if (settings.keyId.trim() === '' || settings.secret.trim() === '') {
150
+ return {
151
+ ok: false,
152
+ code: 'CONFIG_ERROR',
153
+ status: 500,
154
+ message: 'Trace ingest signing credentials are not configured',
155
+ };
156
+ }
157
+
158
+ const verifyResult = await SignedRequest.verify({
159
+ method: req.getMethod(),
160
+ url: new URL(path, 'http://localhost'),
161
+ body: bodyText,
162
+ headers: toIncomingHeaders(req),
163
+ nowMs: nowMs(),
164
+ windowMs: settings.signingWindowMs,
165
+ verifyNonce: async (keyId: string, nonce: string) =>
166
+ storeNonce(keyId, nonce, settings.nonceTtlMs),
167
+ getSecretForKeyId: async (keyId: string) => {
168
+ if (keyId === settings.keyId) return settings.secret;
169
+ return undefined;
170
+ },
171
+ });
172
+
173
+ if (verifyResult.ok === true) return { ok: true };
174
+
175
+ return {
176
+ ok: false,
177
+ code: verifyResult.code,
178
+ status: verifyResult.code === 'EXPIRED' || verifyResult.code === 'REPLAYED' ? 401 : 403,
179
+ message: verifyResult.message,
180
+ };
181
+ };
182
+
183
+ const createWriteHandler = (settings: TraceIngestGatewaySettings, path: string) => {
184
+ return async (req: IRequest, res: IResponse): Promise<void> => {
185
+ const body = getBodyRecord(req);
186
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
187
+ if (auth.ok === false) {
188
+ sendFailure(res, auth.status, auth.code, auth.message);
189
+ return;
190
+ }
191
+
192
+ const entry = body['entry'];
193
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
194
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'entry must be an object');
195
+ return;
196
+ }
197
+
198
+ await settings.storage.writeEntry(entry as ITraceEntry);
199
+ sendSuccess(res);
200
+ };
201
+ };
202
+
203
+ const createUpdateHandler = (settings: TraceIngestGatewaySettings, path: string) => {
204
+ return async (req: IRequest, res: IResponse): Promise<void> => {
205
+ const body = getBodyRecord(req);
206
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
207
+ if (auth.ok === false) {
208
+ sendFailure(res, auth.status, auth.code, auth.message);
209
+ return;
210
+ }
211
+
212
+ const uuid = body['uuid'];
213
+ const patch = body['patch'];
214
+ if (typeof uuid !== 'string' || uuid.trim() === '') {
215
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'uuid is required');
216
+ return;
217
+ }
218
+
219
+ if (typeof patch !== 'object' || patch === null || Array.isArray(patch)) {
220
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'patch must be an object');
221
+ return;
222
+ }
223
+
224
+ await settings.storage.updateEntry(
225
+ uuid,
226
+ patch as Partial<Pick<ITraceEntry, 'content' | 'isLatest'>>
227
+ );
228
+ sendSuccess(res);
229
+ };
230
+ };
231
+
232
+ const createMarkFamilyStaleHandler = (settings: TraceIngestGatewaySettings, path: string) => {
233
+ return async (req: IRequest, res: IResponse): Promise<void> => {
234
+ const body = getBodyRecord(req);
235
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
236
+ if (auth.ok === false) {
237
+ sendFailure(res, auth.status, auth.code, auth.message);
238
+ return;
239
+ }
240
+
241
+ const familyHash = body['familyHash'];
242
+ const exceptUuid = body['exceptUuid'];
243
+
244
+ if (typeof familyHash !== 'string' || familyHash.trim() === '') {
245
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'familyHash is required');
246
+ return;
247
+ }
248
+
249
+ if (typeof exceptUuid !== 'string' || exceptUuid.trim() === '') {
250
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'exceptUuid is required');
251
+ return;
252
+ }
253
+
254
+ await settings.storage.markFamilyStale(familyHash, exceptUuid);
255
+ sendSuccess(res);
256
+ };
257
+ };
258
+
259
+ const resolveStorage = (overrides?: TraceIngestGatewayOverrides): ITraceStorage => {
260
+ if (overrides?.storage !== undefined) return overrides.storage;
261
+
262
+ const connectionName = overrides?.connectionName ?? TraceConfig.merge().connection;
263
+ const db = useDatabase(undefined, connectionName);
264
+ if (db === undefined) {
265
+ throw ErrorFactory.createConfigError('Trace ingest connection is not configured.', {
266
+ connectionName,
267
+ envKey: 'TRACE_DB_CONNECTION',
268
+ });
269
+ }
270
+
271
+ return TraceStorage.resolveStorage(db);
272
+ };
273
+
274
+ const readConfiguredKeyId = (overrides?: TraceIngestGatewayOverrides): string => {
275
+ return (overrides?.keyId ?? Env.get('TRACE_PROXY_KEY_ID', '')).trim();
276
+ };
277
+
278
+ const readConfiguredSecret = (overrides?: TraceIngestGatewayOverrides): string => {
279
+ return (overrides?.secret ?? Env.get('TRACE_PROXY_SECRET', '')).trim();
280
+ };
281
+
282
+ const resolveKeyId = (overrides?: TraceIngestGatewayOverrides): string => {
283
+ const configuredKeyId = readConfiguredKeyId(overrides);
284
+ if (configuredKeyId !== '') return configuredKeyId;
285
+ return (Env.APP_NAME || 'zintrust').trim();
286
+ };
287
+
288
+ const resolveSecret = (overrides?: TraceIngestGatewayOverrides): string => {
289
+ const configuredSecret = readConfiguredSecret(overrides);
290
+ if (configuredSecret !== '') return configuredSecret;
291
+ return Env.APP_KEY;
292
+ };
293
+
294
+ const resolveSigningWindowMs = (overrides?: TraceIngestGatewayOverrides): number => {
295
+ return overrides?.signingWindowMs ?? Env.getInt('TRACE_PROXY_SIGNING_WINDOW_MS', 60000);
296
+ };
297
+
298
+ const resolveNonceTtlMs = (overrides?: TraceIngestGatewayOverrides): number => {
299
+ return overrides?.nonceTtlMs ?? Env.getInt('TRACE_PROXY_NONCE_TTL_MS', 120000);
300
+ };
301
+
302
+ const resolveRateLimitMax = (): number => {
303
+ return Env.getInt('TRACE_PROXY_RATE_LIMIT_MAX', 0);
304
+ };
305
+
306
+ const resolveRateLimitWindowMinutes = (): number => {
307
+ const windowMinutes = Env.getFloat('TRACE_PROXY_RATE_LIMIT_WINDOW_MINUTES', 0);
308
+ return Math.max(windowMinutes, 0);
309
+ };
310
+
311
+ const createRateLimitMiddleware = (): string | undefined => {
312
+ const max = resolveRateLimitMax();
313
+ const windowMinutes = resolveRateLimitWindowMinutes();
314
+
315
+ if (!Number.isFinite(max) || !Number.isInteger(max)) {
316
+ throw ErrorFactory.createConfigError('TRACE_PROXY_RATE_LIMIT_MAX must be a valid integer', {
317
+ value: max,
318
+ envKey: 'TRACE_PROXY_RATE_LIMIT_MAX',
319
+ });
320
+ }
321
+
322
+ if (!Number.isFinite(windowMinutes)) {
323
+ throw ErrorFactory.createConfigError(
324
+ 'TRACE_PROXY_RATE_LIMIT_WINDOW_MINUTES must be a valid number',
325
+ {
326
+ value: windowMinutes,
327
+ envKey: 'TRACE_PROXY_RATE_LIMIT_WINDOW_MINUTES',
328
+ }
329
+ );
330
+ }
331
+
332
+ if (max <= 0 || windowMinutes <= 0) {
333
+ return undefined;
334
+ }
335
+
336
+ return `rateLimit:${max}:${windowMinutes}`;
337
+ };
338
+
339
+ const resolveMiddleware = (overrides?: TraceIngestGatewayOverrides): ReadonlyArray<string> => {
340
+ if (overrides?.middleware !== undefined) {
341
+ return overrides.middleware;
342
+ }
343
+
344
+ const configured = parseMiddleware(Env.get('TRACE_PROXY_MIDDLEWARE', ''));
345
+ const rateLimitMiddleware = createRateLimitMiddleware();
346
+ if (rateLimitMiddleware === undefined) {
347
+ return configured;
348
+ }
349
+
350
+ return [
351
+ ...configured.filter((entry) => !isParameterizedRateLimitMiddleware(entry)),
352
+ rateLimitMiddleware,
353
+ ];
354
+ };
355
+
356
+ const readSettings = (overrides?: TraceIngestGatewayOverrides): TraceIngestGatewaySettings => {
357
+ return {
358
+ basePath: normalizePath(overrides?.basePath ?? Env.get('TRACE_PROXY_PATH', '/zin/trace/write')),
359
+ keyId: resolveKeyId(overrides),
360
+ secret: resolveSecret(overrides),
361
+ signingWindowMs: resolveSigningWindowMs(overrides),
362
+ nonceTtlMs: resolveNonceTtlMs(overrides),
363
+ middleware: resolveMiddleware(overrides),
364
+ storage: resolveStorage(overrides),
365
+ };
366
+ };
367
+
368
+ const getRouteOptions = (settings: TraceIngestGatewaySettings): RouteOptions | undefined => {
369
+ if (settings.middleware.length === 0) return undefined;
370
+ return { middleware: settings.middleware } as RouteOptions;
371
+ };
372
+
373
+ const registerRoutes = (router: IRouter, settings: TraceIngestGatewaySettings): void => {
374
+ const routeOptions = getRouteOptions(settings);
375
+ const updatePath = appendSuffix(settings.basePath, '/update');
376
+ const markFamilyStalePath = appendSuffix(settings.basePath, '/mark-family-stale');
377
+
378
+ Router.post(
379
+ router,
380
+ settings.basePath,
381
+ createWriteHandler(settings, settings.basePath),
382
+ routeOptions
383
+ );
384
+ Router.post(router, updatePath, createUpdateHandler(settings, updatePath), routeOptions);
385
+ Router.post(
386
+ router,
387
+ markFamilyStalePath,
388
+ createMarkFamilyStaleHandler(settings, markFamilyStalePath),
389
+ routeOptions
390
+ );
391
+ };
392
+
393
+ export const TraceIngestGateway = Object.freeze({
394
+ create(overrides?: TraceIngestGatewayOverrides): {
395
+ registerRoutes: (router: IRouter) => void;
396
+ } {
397
+ const settings = readSettings(overrides);
398
+
399
+ return {
400
+ registerRoutes(router: IRouter): void {
401
+ registerRoutes(router, settings);
402
+ },
403
+ };
404
+ },
405
+ });
406
+
407
+ export const registerTraceIngestGateway = (
408
+ router: IRouter,
409
+ overrides?: TraceIngestGatewayOverrides
410
+ ): void => {
411
+ TraceIngestGateway.create(overrides).registerRoutes(router);
412
+ };
413
+
414
+ export default TraceIngestGateway;
package/src/plugin.ts ADDED
@@ -0,0 +1,9 @@
1
+ export type {};
2
+
3
+ type GlobalTracePluginState = {
4
+ __zintrust_system_trace_plugin_requested__?: boolean;
5
+ };
6
+
7
+ const globalTracePluginState = globalThis as unknown as GlobalTracePluginState;
8
+
9
+ globalTracePluginState.__zintrust_system_trace_plugin_requested__ = true;