@zintrust/trace 0.9.3 → 0.9.4

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/README.md CHANGED
@@ -32,6 +32,13 @@ You can still import the package migrations manually if you prefer to keep them
32
32
  TRACE_ENABLED=true
33
33
  TRACE_DB_CONNECTION=d1 # optional — omit to inherit DB_CONNECTION
34
34
  TRACE_QUERY_CONNECTION=main # optional — app DB to observe for SQL traces
35
+ TRACE_SERVICE_TAG= # optional — global trace tag, falls back to APP_NAME when empty
36
+ TRACE_PROXY=false # optional — send writes to a remote trace server instead of local DB
37
+ TRACE_PROXY_URL= # required when TRACE_PROXY=true
38
+ TRACE_PROXY_PATH=/zin/trace/write
39
+ TRACE_PROXY_KEY_ID= # optional — falls back to APP_NAME
40
+ TRACE_PROXY_SECRET= # optional — falls back to APP_KEY
41
+ TRACE_PROXY_TIMEOUT_MS=30000
35
42
  TRACE_PRUNE_HOURS=24 # how long entries are kept (default: 24)
36
43
  TRACE_SLOW_QUERY_MS=100 # slow-query threshold in ms (default: 100)
37
44
  TRACE_LOG_LEVEL=info # minimum log level captured (default: info)
@@ -52,6 +59,8 @@ TRACE_REDACT_QUERY=
52
59
 
53
60
  When `TRACE_CONTENT_QUEUE_DRIVER` is set, trace writes enqueue through that registered queue driver and an internal trace worker drains them outside the live request path. When it is unset, oversized content is replaced with `Trace content exceeded budget and was replaced.` before persistence instead of running the heavy compaction loop inline.
54
61
 
62
+ When `TRACE_PROXY=true`, the local runtime keeps collecting the same trace payload it would normally send to storage, but it sends the write/update/stale-family operations to `TRACE_PROXY_URL + TRACE_PROXY_PATH` instead of writing directly to the local trace database. The receiver can then persist those entries with the standard `TraceStorage` flow.
63
+
55
64
  This currently works with any queue driver already registered in ZinTrust. First-class Cloudflare Queue support still requires a dedicated queue driver and queue-runtime registration for that transport.
56
65
 
57
66
  ### 2. Enable the plugin in `zintrust.plugins.*`
@@ -154,6 +163,28 @@ registerTraceDashboard(router, {
154
163
  });
155
164
  ```
156
165
 
166
+ ### 3b. Optional remote trace ingest gateway
167
+
168
+ If you want one project to send trace writes to another project, mount the signed ingest gateway on the trace server:
169
+
170
+ ```ts
171
+ // routes/api.ts
172
+ import { registerTraceIngestGateway } from '@zintrust/trace';
173
+
174
+ registerTraceIngestGateway(router, {
175
+ basePath: '/zin/trace/write',
176
+ middleware: ['admin'], // optional
177
+ });
178
+ ```
179
+
180
+ The sender runtime uses `TRACE_PROXY_URL`, `TRACE_PROXY_PATH`, `TRACE_PROXY_KEY_ID`, and `TRACE_PROXY_SECRET` to sign write requests. On the receiver side, `TRACE_PROXY_KEY_ID` and `TRACE_PROXY_SECRET` must match the sender pair or fall back to the same `APP_NAME` and `APP_KEY` values.
181
+
182
+ The ingest gateway exposes exactly these signed POST endpoints:
183
+
184
+ - `TRACE_PROXY_PATH` for new trace entries
185
+ - `TRACE_PROXY_PATH + /update` for trace content updates
186
+ - `TRACE_PROXY_PATH + /mark-family-stale` for latest-entry rotation
187
+
157
188
  The dashboard SPA will be available at `GET /trace` (or your chosen `basePath`).
158
189
 
159
190
  If you need custom storage wiring, keep using the low-level route registrar:
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.9.3",
4
- "buildDate": "2026-04-22T15:48:24.454Z",
3
+ "version": "0.9.4",
4
+ "buildDate": "2026-04-23T11:29:28.904Z",
5
5
  "buildEnvironment": {
6
- "node": "v20.20.2",
7
- "platform": "linux",
8
- "arch": "x64"
6
+ "node": "v22.22.1",
7
+ "platform": "darwin",
8
+ "arch": "arm64"
9
9
  },
10
10
  "git": {
11
- "commit": "552dfdf4",
12
- "branch": "master"
11
+ "commit": "eebcc3ad",
12
+ "branch": "release"
13
13
  },
14
14
  "package": {
15
15
  "engines": {
@@ -29,6 +29,10 @@
29
29
  "size": 4640,
30
30
  "sha256": "c51cc312046b6b2bbe1673f1ff9508425cc7140a1d2341907f67aa36069c09f9"
31
31
  },
32
+ "build-manifest.json": {
33
+ "size": 14744,
34
+ "sha256": "92fc9e1e76ba84696dbdaf02aa2ed7588c6e1234d586e24bf8bab4f54104ad61"
35
+ },
32
36
  "cli-register.d.ts": {
33
37
  "size": 255,
34
38
  "sha256": "da8d689fe5ef32e97e755f28017e4d3cb1aa63489073a71907ea41ad5761ede9"
@@ -42,8 +46,8 @@
42
46
  "sha256": "b034cbef0c71fb868071363624ef7a9f8d7acc20f8be8c895dd5db5a75e81f37"
43
47
  },
44
48
  "config.js": {
45
- "size": 10064,
46
- "sha256": "cef8c30f280d02b765c3930c1f5d9bac083596f9b944197fe4ad008be6627475"
49
+ "size": 10503,
50
+ "sha256": "4514e95067194c7afacb1202a8f1caffc64a400dd995cf809301b1a51a67da38"
47
51
  },
48
52
  "context.d.ts": {
49
53
  "size": 596,
@@ -78,12 +82,20 @@
78
82
  "sha256": "b260cd17012cd5bf5f24f24eb8958d0cf79bed1d976412def3706f960b3aefa6"
79
83
  },
80
84
  "index.d.ts": {
81
- "size": 2599,
82
- "sha256": "5ee9fa0c65942078a1ac7f38afcdc6b86595842191c766190974733c6f8d86c6"
85
+ "size": 2693,
86
+ "sha256": "901aaefe88fb2cc1e7771cd541f41ebcfe6443390bd66905eacbda51f1a6f73d"
83
87
  },
84
88
  "index.js": {
85
- "size": 3324,
86
- "sha256": "93a911d7f3813199936568bf8a63d598ec8f32a0006810453f0487a73e042ab3"
89
+ "size": 3421,
90
+ "sha256": "478b80c6aa6c2eea2d109aa2e6fe090d77469d186b3e6a19ababb7d1f6d0be7e"
91
+ },
92
+ "ingest/TraceIngestGateway.d.ts": {
93
+ "size": 786,
94
+ "sha256": "3a0a46fa5bbf5367047214533ec0ee92a171ee35a60d25c197eea1eed1ff0f65"
95
+ },
96
+ "ingest/TraceIngestGateway.js": {
97
+ "size": 8456,
98
+ "sha256": "56df56cecda7110d1096c3beef1ef62c386f4a0e85120ebfb4cdfc5ab3b758d7"
87
99
  },
88
100
  "migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
89
101
  "size": 304,
@@ -138,16 +150,32 @@
138
150
  "sha256": "71d366165dd36f1675aa253a76262b226fb6c62e5ab632746b8aea61c0c625fc"
139
151
  },
140
152
  "register.js": {
141
- "size": 16585,
142
- "sha256": "1e940ce8c216991572c633f34bc66ca6ef980c11e84042b02e957d99e142dbe5"
153
+ "size": 19822,
154
+ "sha256": "c553356d90c812f7de430d5679b1d44468131433e034ae2ea89e2876b1254444"
155
+ },
156
+ "storage/DebuggerStorage.d.ts": {
157
+ "size": 517,
158
+ "sha256": "c9c215aaa414f7b0c1fec6c82b054fc52bdf97af58f96f35c7f96672fb859c31"
159
+ },
160
+ "storage/DebuggerStorage.js": {
161
+ "size": 7442,
162
+ "sha256": "5ecce0fcfcf695df587a7b90a7a5c7efd2e64ad13c9f2d104b392f89f34f0dc4"
163
+ },
164
+ "storage/ProxyTraceStorage.d.ts": {
165
+ "size": 339,
166
+ "sha256": "9c724ff342dfe82da12e7cce95593f6623faa80c40f97593719b8e78e95f266d"
167
+ },
168
+ "storage/ProxyTraceStorage.js": {
169
+ "size": 4158,
170
+ "sha256": "097d82e94c6ef38661fb57a20d378ba890dbacd03a22666aaf4aba4dfdedf735"
143
171
  },
144
172
  "storage/TraceContentBudget.d.ts": {
145
173
  "size": 1306,
146
174
  "sha256": "606a37af0a4aef4866c22cc727f67f485c43181b40eb831e1920b8b90fdaf503"
147
175
  },
148
176
  "storage/TraceContentBudget.js": {
149
- "size": 11412,
150
- "sha256": "e5a72a6bb0c8bb432ea4aa7e8c5b43a8bf9e83d1b2935e102b4d1697da5fd18e"
177
+ "size": 11434,
178
+ "sha256": "f53229e7233cf13b095780451b68c912a95dc3f44e17c49289446219231e7e06"
151
179
  },
152
180
  "storage/TraceContentRedaction.d.ts": {
153
181
  "size": 207,
@@ -165,6 +193,14 @@
165
193
  "size": 2465,
166
194
  "sha256": "ace7e492373d2dccdbefe20040d30fffd7ad9f8e71113b7a044bddf92dcdf6fb"
167
195
  },
196
+ "storage/TraceServiceTag.d.ts": {
197
+ "size": 224,
198
+ "sha256": "35d93c82d6db80071946adaa8e40d012408b469949f1002f85ae3fd20b0a70c8"
199
+ },
200
+ "storage/TraceServiceTag.js": {
201
+ "size": 1916,
202
+ "sha256": "1ddad82563c98ddbb4033e9b8fb31901d635cffa7024d499a896f0d7a6d00900"
203
+ },
168
204
  "storage/TraceStorage.d.ts": {
169
205
  "size": 517,
170
206
  "sha256": "c9c215aaa414f7b0c1fec6c82b054fc52bdf97af58f96f35c7f96672fb859c31"
@@ -182,16 +218,16 @@
182
218
  "sha256": "6fc34e6e52a9b463db0967dba4aec4c8c706b6978c496f568637ffc15327279a"
183
219
  },
184
220
  "storage/index.d.ts": {
185
- "size": 100,
186
- "sha256": "1f2c1529bd5fa9c7a026695e4a8b184973b80d149afc778a8d86bcd03cbe093c"
221
+ "size": 210,
222
+ "sha256": "bb4c3a0c73eb3e2629dd1a1dbc29eecedd1910a37b221357cc7981ced320bdeb"
187
223
  },
188
224
  "storage/index.js": {
189
- "size": 50,
190
- "sha256": "d916e8e3abb1b1087f6b184851b0e6265e53380d7857b008e745d566aad15d44"
225
+ "size": 166,
226
+ "sha256": "ed5e83e108cd15f9a3976be71e966bdf0c44d8e0266d1d2dbad189f0885ad501"
191
227
  },
192
228
  "types.d.ts": {
193
- "size": 9825,
194
- "sha256": "53a1b2a572b9b5a73261f8c9b85903a3955bf71ec582f96c5b5249951aeb9b88"
229
+ "size": 10037,
230
+ "sha256": "68a2de953af2fce1a12447078177ff886f10e853c760378e13478b2558648868"
195
231
  },
196
232
  "types.js": {
197
233
  "size": 696,
package/dist/config.js CHANGED
@@ -163,10 +163,27 @@ const mergeContentDispatch = (base, override) => {
163
163
  },
164
164
  };
165
165
  };
166
+ const mergeProxyConfig = (base, override) => {
167
+ if (override === undefined)
168
+ return base;
169
+ return {
170
+ ...base,
171
+ ...override,
172
+ };
173
+ };
166
174
  const DEFAULTS = Object.freeze({
167
175
  enabled: false,
168
176
  connection: undefined,
169
177
  observeConnection: undefined,
178
+ serviceTag: undefined,
179
+ proxy: {
180
+ enabled: false,
181
+ url: undefined,
182
+ path: '/zin/trace/write',
183
+ keyId: undefined,
184
+ secret: undefined,
185
+ timeoutMs: 30000,
186
+ },
170
187
  pruneAfterHours: 24,
171
188
  ignoreRoutes: ['/trace', '/health', '/ping'],
172
189
  ignorePaths: [],
@@ -247,6 +264,7 @@ export const TraceConfig = Object.freeze({
247
264
  return Object.freeze({
248
265
  ...DEFAULTS,
249
266
  ...overrides,
267
+ proxy: mergeProxyConfig(DEFAULTS.proxy, overrides.proxy),
250
268
  contentDispatch: mergeContentDispatch(DEFAULTS.contentDispatch, overrides.contentDispatch),
251
269
  watchers: mergeWatchers(DEFAULTS.watchers, overrides.watchers),
252
270
  redaction: {
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { TraceContentRedaction } from './storage/TraceContentRedaction';
13
13
  export { TraceContext } from './context';
14
14
  export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes';
15
15
  export type { TraceDashboardOptions, TraceDashboardRegistrationOptions } from './dashboard/routes';
16
+ export { registerTraceIngestGateway, TraceIngestGateway } from './ingest/TraceIngestGateway';
16
17
  export { AuthWatcher } from './watchers/AuthWatcher';
17
18
  export { BatchWatcher } from './watchers/BatchWatcher';
18
19
  export { CacheWatcher } from './watchers/CacheWatcher';
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ export { TraceContext } from './context.js';
24
24
  // Dashboard
25
25
  // ---------------------------------------------------------------------------
26
26
  export { registerTraceDashboard, registerTraceRoutes } from './dashboard/routes.js';
27
+ export { registerTraceIngestGateway, TraceIngestGateway } from './ingest/TraceIngestGateway.js';
27
28
  // ---------------------------------------------------------------------------
28
29
  // Watchers (named re-exports for use with custom wiring)
29
30
  // ---------------------------------------------------------------------------
@@ -0,0 +1,22 @@
1
+ import { type IRouter } from '@zintrust/core';
2
+ import type { ITraceStorage } from '../types';
3
+ type TraceIngestGatewaySettings = {
4
+ basePath: string;
5
+ keyId: string;
6
+ secret: string;
7
+ signingWindowMs: number;
8
+ nonceTtlMs: number;
9
+ middleware: ReadonlyArray<string>;
10
+ storage: ITraceStorage;
11
+ };
12
+ type TraceIngestGatewayOverrides = Partial<Omit<TraceIngestGatewaySettings, 'storage'> & {
13
+ storage: ITraceStorage;
14
+ connectionName: string;
15
+ }>;
16
+ export declare const TraceIngestGateway: Readonly<{
17
+ create(overrides?: TraceIngestGatewayOverrides): {
18
+ registerRoutes: (router: IRouter) => void;
19
+ };
20
+ }>;
21
+ export declare const registerTraceIngestGateway: (router: IRouter, overrides?: TraceIngestGatewayOverrides) => void;
22
+ export default TraceIngestGateway;
@@ -0,0 +1,215 @@
1
+ import { Env, ErrorFactory, Router, SignedRequest, useDatabase, } from '@zintrust/core';
2
+ import { TraceConfig } from '../config.js';
3
+ import { TraceStorage } from '../storage/index.js';
4
+ const nonces = new Map();
5
+ const nowMs = () => Date.now();
6
+ const normalizePath = (value) => {
7
+ const trimmed = value.trim();
8
+ if (trimmed === '')
9
+ return '/zin/trace/write';
10
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
11
+ };
12
+ const parseMiddleware = (value) => value
13
+ .split(',')
14
+ .map((entry) => entry.trim())
15
+ .filter((entry) => entry.length > 0);
16
+ const appendSuffix = (path, suffix) => {
17
+ const base = normalizePath(path).replace(/\/+$/, '');
18
+ const tail = suffix.startsWith('/') ? suffix : `/${suffix}`;
19
+ return `${base}${tail}`;
20
+ };
21
+ const cleanupExpiredNonces = () => {
22
+ const current = nowMs();
23
+ for (const [nonceKey, expiresAt] of nonces.entries()) {
24
+ if (expiresAt <= current) {
25
+ nonces.delete(nonceKey);
26
+ }
27
+ }
28
+ };
29
+ const storeNonce = async (keyId, nonce, ttlMs) => {
30
+ cleanupExpiredNonces();
31
+ const nonceKey = `${keyId}:${nonce}`;
32
+ if (nonces.has(nonceKey))
33
+ return false;
34
+ nonces.set(nonceKey, nowMs() + Math.max(ttlMs, 1));
35
+ return true;
36
+ };
37
+ const getBodyRecord = (req) => {
38
+ const body = req.getBody?.() ?? req.body;
39
+ if (typeof body === 'object' && body !== null && !Array.isArray(body)) {
40
+ return body;
41
+ }
42
+ return {};
43
+ };
44
+ const getRawBody = (req) => {
45
+ const rawText = req.context['rawBodyText'];
46
+ if (typeof rawText === 'string')
47
+ return rawText;
48
+ return JSON.stringify(getBodyRecord(req));
49
+ };
50
+ const toIncomingHeaders = (req) => {
51
+ const headers = req.getHeaders();
52
+ const normalize = (value) => {
53
+ if (Array.isArray(value))
54
+ return value.join(',');
55
+ return value;
56
+ };
57
+ return {
58
+ 'x-zt-key-id': normalize(headers['x-zt-key-id']),
59
+ 'x-zt-timestamp': normalize(headers['x-zt-timestamp']),
60
+ 'x-zt-nonce': normalize(headers['x-zt-nonce']),
61
+ 'x-zt-body-sha256': normalize(headers['x-zt-body-sha256']),
62
+ 'x-zt-signature': normalize(headers['x-zt-signature']),
63
+ };
64
+ };
65
+ const sendFailure = (res, status, code, message, details) => {
66
+ const payload = {
67
+ ok: false,
68
+ error: { code, message, details },
69
+ };
70
+ res.status(status).json(payload);
71
+ };
72
+ const sendSuccess = (res) => {
73
+ const payload = { ok: true };
74
+ res.status(200).json(payload);
75
+ };
76
+ const verifyRequest = async (req, bodyText, settings, path) => {
77
+ if (settings.keyId.trim() === '' || settings.secret.trim() === '') {
78
+ return {
79
+ ok: false,
80
+ code: 'CONFIG_ERROR',
81
+ status: 500,
82
+ message: 'Trace ingest signing credentials are not configured',
83
+ };
84
+ }
85
+ const verifyResult = await SignedRequest.verify({
86
+ method: req.getMethod(),
87
+ url: new URL(path, 'http://localhost'),
88
+ body: bodyText,
89
+ headers: toIncomingHeaders(req),
90
+ nowMs: nowMs(),
91
+ windowMs: settings.signingWindowMs,
92
+ verifyNonce: async (keyId, nonce) => storeNonce(keyId, nonce, settings.nonceTtlMs),
93
+ getSecretForKeyId: async (keyId) => {
94
+ if (keyId === settings.keyId)
95
+ return settings.secret;
96
+ return undefined;
97
+ },
98
+ });
99
+ if (verifyResult.ok === true)
100
+ return { ok: true };
101
+ return {
102
+ ok: false,
103
+ code: verifyResult.code,
104
+ status: verifyResult.code === 'EXPIRED' || verifyResult.code === 'REPLAYED' ? 401 : 403,
105
+ message: verifyResult.message,
106
+ };
107
+ };
108
+ const createWriteHandler = (settings, path) => {
109
+ return async (req, res) => {
110
+ const body = getBodyRecord(req);
111
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
112
+ if (auth.ok === false) {
113
+ sendFailure(res, auth.status, auth.code, auth.message);
114
+ return;
115
+ }
116
+ const entry = body['entry'];
117
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
118
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'entry must be an object');
119
+ return;
120
+ }
121
+ await settings.storage.writeEntry(entry);
122
+ sendSuccess(res);
123
+ };
124
+ };
125
+ const createUpdateHandler = (settings, path) => {
126
+ return async (req, res) => {
127
+ const body = getBodyRecord(req);
128
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
129
+ if (auth.ok === false) {
130
+ sendFailure(res, auth.status, auth.code, auth.message);
131
+ return;
132
+ }
133
+ const uuid = body['uuid'];
134
+ const patch = body['patch'];
135
+ if (typeof uuid !== 'string' || uuid.trim() === '') {
136
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'uuid is required');
137
+ return;
138
+ }
139
+ if (typeof patch !== 'object' || patch === null || Array.isArray(patch)) {
140
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'patch must be an object');
141
+ return;
142
+ }
143
+ await settings.storage.updateEntry(uuid, patch);
144
+ sendSuccess(res);
145
+ };
146
+ };
147
+ const createMarkFamilyStaleHandler = (settings, path) => {
148
+ return async (req, res) => {
149
+ const body = getBodyRecord(req);
150
+ const auth = await verifyRequest(req, getRawBody(req), settings, path);
151
+ if (auth.ok === false) {
152
+ sendFailure(res, auth.status, auth.code, auth.message);
153
+ return;
154
+ }
155
+ const familyHash = body['familyHash'];
156
+ const exceptUuid = body['exceptUuid'];
157
+ if (typeof familyHash !== 'string' || familyHash.trim() === '') {
158
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'familyHash is required');
159
+ return;
160
+ }
161
+ if (typeof exceptUuid !== 'string' || exceptUuid.trim() === '') {
162
+ sendFailure(res, 400, 'VALIDATION_ERROR', 'exceptUuid is required');
163
+ return;
164
+ }
165
+ await settings.storage.markFamilyStale(familyHash, exceptUuid);
166
+ sendSuccess(res);
167
+ };
168
+ };
169
+ const resolveStorage = (overrides) => {
170
+ if (overrides?.storage !== undefined)
171
+ return overrides.storage;
172
+ const connectionName = overrides?.connectionName ?? TraceConfig.merge().connection;
173
+ const db = useDatabase(undefined, connectionName);
174
+ if (db === undefined) {
175
+ throw ErrorFactory.createConfigError('Trace ingest connection is not configured.', {
176
+ connectionName,
177
+ envKey: 'TRACE_DB_CONNECTION',
178
+ });
179
+ }
180
+ return TraceStorage.resolveStorage(db);
181
+ };
182
+ 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
+ return {
186
+ 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', '')),
192
+ storage: resolveStorage(overrides),
193
+ };
194
+ };
195
+ export const TraceIngestGateway = Object.freeze({
196
+ create(overrides) {
197
+ 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
+ return {
204
+ 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);
208
+ },
209
+ };
210
+ },
211
+ });
212
+ export const registerTraceIngestGateway = (router, overrides) => {
213
+ TraceIngestGateway.create(overrides).registerRoutes(router);
214
+ };
215
+ export default TraceIngestGateway;
package/dist/register.js CHANGED
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import { TraceConfig } from './config.js';
23
23
  import { TraceContext } from './context.js';
24
- import { TraceStorage } from './storage/index.js';
24
+ import { ProxyTraceStorage, TraceServiceTag, TraceStorage } from './storage/index.js';
25
25
  import { TraceContentBudget } from './storage/TraceContentBudget.js';
26
26
  import { TraceContentRedaction } from './storage/TraceContentRedaction.js';
27
27
  import { TraceEntryFiltering } from './storage/TraceEntryFiltering.js';
@@ -142,6 +142,15 @@ if (!traceAlreadyInitialized && Env) {
142
142
  const pruneAfterHoursRaw = Env.get('TRACE_PRUNE_HOURS', '').trim();
143
143
  const slowQueryThresholdRaw = Env.get('TRACE_SLOW_QUERY_MS', '').trim();
144
144
  const logMinLevelRaw = Env.get('TRACE_LOG_LEVEL', '').trim();
145
+ const traceProxyRaw = Env.get('TRACE_PROXY', '').trim();
146
+ const traceProxyUrlRaw = Env.get('TRACE_PROXY_URL', '').trim();
147
+ const traceProxyPathRaw = Env.get('TRACE_PROXY_PATH', '').trim();
148
+ const traceProxyKeyIdRaw = Env.get('TRACE_PROXY_KEY_ID', '').trim();
149
+ const traceProxySecretRaw = Env.get('TRACE_PROXY_SECRET', '').trim();
150
+ const traceProxyTimeoutRaw = Env.get('TRACE_PROXY_TIMEOUT_MS', '').trim();
151
+ const traceServiceTagRaw = Env.get('TRACE_SERVICE_TAG', '').trim();
152
+ const appNameRaw = Env.get('APP_NAME', '').trim();
153
+ const appKeyRaw = Env.get('APP_KEY', '').trim();
145
154
  const captureCachePayloadsRaw = Env.get('TRACE_CACHE_PAYLOADS', '').trim();
146
155
  const captureQueryBindingsRaw = Env.get('TRACE_QUERY_BINDINGS', '').trim();
147
156
  const contentDispatchDriverRaw = Env.get('TRACE_CONTENT_QUEUE_DRIVER', '').trim();
@@ -166,6 +175,21 @@ if (!traceAlreadyInitialized && Env) {
166
175
  const logMinLevel = (logMinLevelRaw === '' ? startupOverrides?.logMinLevel : logMinLevelRaw);
167
176
  const captureCachePayloads = parseEnvBool(captureCachePayloadsRaw) ?? startupOverrides?.captureCachePayloads;
168
177
  const captureQueryBindings = parseEnvBool(captureQueryBindingsRaw) ?? startupOverrides?.captureQueryBindings;
178
+ const traceProxyEnabled = parseEnvBool(traceProxyRaw) ?? startupOverrides?.proxy?.enabled;
179
+ const traceProxyUrl = traceProxyUrlRaw === '' ? startupOverrides?.proxy?.url : traceProxyUrlRaw;
180
+ const traceProxyPath = traceProxyPathRaw === '' ? startupOverrides?.proxy?.path : traceProxyPathRaw;
181
+ const traceProxyKeyId = traceProxyKeyIdRaw === ''
182
+ ? (startupOverrides?.proxy?.keyId ?? appNameRaw)
183
+ : traceProxyKeyIdRaw;
184
+ const traceProxySecret = traceProxySecretRaw === ''
185
+ ? (startupOverrides?.proxy?.secret ?? appKeyRaw)
186
+ : traceProxySecretRaw;
187
+ const traceProxyTimeout = traceProxyTimeoutRaw === ''
188
+ ? startupOverrides?.proxy?.timeoutMs
189
+ : Number.parseInt(traceProxyTimeoutRaw, 10);
190
+ const traceServiceTag = traceServiceTagRaw === ''
191
+ ? (startupOverrides?.serviceTag ?? appNameRaw).trim() || undefined
192
+ : traceServiceTagRaw;
169
193
  const contentDispatchDriver = contentDispatchDriverRaw === ''
170
194
  ? startupOverrides?.contentDispatch?.driver
171
195
  : contentDispatchDriverRaw;
@@ -201,6 +225,29 @@ if (!traceAlreadyInitialized && Env) {
201
225
  enabled,
202
226
  connection,
203
227
  observeConnection,
228
+ ...(typeof traceServiceTag === 'string' && traceServiceTag !== ''
229
+ ? { serviceTag: traceServiceTag }
230
+ : {}),
231
+ proxy: {
232
+ ...TraceConfig.defaults().proxy,
233
+ ...startupOverrides?.proxy,
234
+ ...(typeof traceProxyEnabled === 'boolean' ? { enabled: traceProxyEnabled } : {}),
235
+ ...(typeof traceProxyUrl === 'string' && traceProxyUrl !== ''
236
+ ? { url: traceProxyUrl }
237
+ : {}),
238
+ ...(typeof traceProxyPath === 'string' && traceProxyPath !== ''
239
+ ? { path: traceProxyPath }
240
+ : {}),
241
+ ...(typeof traceProxyKeyId === 'string' && traceProxyKeyId !== ''
242
+ ? { keyId: traceProxyKeyId }
243
+ : {}),
244
+ ...(typeof traceProxySecret === 'string' && traceProxySecret !== ''
245
+ ? { secret: traceProxySecret }
246
+ : {}),
247
+ ...(typeof traceProxyTimeout === 'number' && Number.isFinite(traceProxyTimeout)
248
+ ? { timeoutMs: traceProxyTimeout }
249
+ : {}),
250
+ },
204
251
  ...(typeof pruneAfterHours === 'number' && Number.isFinite(pruneAfterHours)
205
252
  ? { pruneAfterHours }
206
253
  : {}),
@@ -264,7 +311,16 @@ if (!traceAlreadyInitialized && Env) {
264
311
  envKey: 'TRACE_QUERY_CONNECTION',
265
312
  });
266
313
  await assertTraceStorageReady(core, storageDb, resolvedConnectionName);
267
- const storage = TraceWriteDiagnostics.wrapStorage(TraceContentBudget.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction), config), {
314
+ const resolvedStorage = config.proxy.enabled
315
+ ? ProxyTraceStorage.create({
316
+ baseUrl: config.proxy.url ?? '',
317
+ path: config.proxy.path,
318
+ keyId: config.proxy.keyId ?? '',
319
+ secret: config.proxy.secret ?? '',
320
+ timeoutMs: config.proxy.timeoutMs,
321
+ })
322
+ : TraceStorage.resolveStorage(storageDb);
323
+ const storage = TraceWriteDiagnostics.wrapStorage(TraceContentBudget.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceServiceTag.wrapStorage(resolvedStorage, config), config), config.redaction), config), {
268
324
  connectionName: resolvedConnectionName,
269
325
  logger: core.Logger,
270
326
  });
@@ -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';