@runtime-digital-twin/sdk 1.0.0 → 1.0.3
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/LICENSE +22 -0
- package/README.md +26 -0
- package/dist/fastify-plugin.d.ts +4 -0
- package/dist/fastify-plugin.d.ts.map +1 -1
- package/dist/fastify-plugin.js +85 -16
- package/dist/http-wrapper.d.ts.map +1 -1
- package/dist/http-wrapper.js +47 -4
- package/dist/redaction.d.ts +10 -0
- package/dist/redaction.d.ts.map +1 -1
- package/dist/redaction.js +131 -23
- package/dist/trace-bundle-writer.d.ts +4 -6
- package/dist/trace-bundle-writer.d.ts.map +1 -1
- package/dist/trace-bundle-writer.js +23 -22
- package/dist/trace-uploader.d.ts +2 -1
- package/dist/trace-uploader.d.ts.map +1 -1
- package/dist/trace-uploader.js +85 -22
- package/package.json +18 -20
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Wraith On-Call Engineer Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -91,6 +91,8 @@ await fastify.register(fastifyTracing, {
|
|
|
91
91
|
headerAllowlist: [], // Optional: Headers to capture
|
|
92
92
|
maxBodySize: 10 * 1024 * 1024, // Optional: Max body size to capture (default: 10MB)
|
|
93
93
|
enabled: true, // Optional: Enable/disable tracing (default: true)
|
|
94
|
+
traceSampleRate: 1, // Optional: 0–1; 1 = always, 0.1 = 10% of requests (default: 1)
|
|
95
|
+
alwaysTraceErrors: true, // Optional: always capture trace for requests that error (default: true)
|
|
94
96
|
// Upload options (optional)
|
|
95
97
|
uploadUrl: process.env.WRAITH_API_URL + '/api/traces/ingest',
|
|
96
98
|
apiKey: process.env.WRAITH_API_KEY,
|
|
@@ -136,6 +138,15 @@ WRAITH_API_KEY=your-api-key-here
|
|
|
136
138
|
|
|
137
139
|
Traces are uploaded asynchronously after each request completes, so they don't slow down your application.
|
|
138
140
|
|
|
141
|
+
### Sampling and errors
|
|
142
|
+
|
|
143
|
+
- **traceSampleRate** (0–1): When set below 1, only that fraction of requests are traced (e.g. 0.1 = 10%). Reduces volume and cost while keeping a representative sample.
|
|
144
|
+
- **alwaysTraceErrors**: When true (default), any request that ends in an error (Fastify `onError`) is traced and uploaded even if it was not sampled. Ensures failures are never missed.
|
|
145
|
+
|
|
146
|
+
### Rate limits and 429
|
|
147
|
+
|
|
148
|
+
If the ingest endpoint returns **429 Too Many Requests**, the uploader retries with exponential backoff (same as for 5xx). Use the **Retry-After** response header when present; the SDK respects backoff between attempts. To avoid hitting limits, reduce volume with **traceSampleRate** or increase **TRACE_INGEST_RATE_LIMIT_REQUESTS_PER_MIN** on the server.
|
|
149
|
+
|
|
139
150
|
## Modes
|
|
140
151
|
|
|
141
152
|
### Record Mode
|
|
@@ -209,6 +220,21 @@ import {
|
|
|
209
220
|
|
|
210
221
|
See the [Quickstart Guide](../../QUICKSTART.md) for complete examples.
|
|
211
222
|
|
|
223
|
+
## Changelog
|
|
224
|
+
|
|
225
|
+
### v1.1.0
|
|
226
|
+
- Enhanced trace upload with automatic retry
|
|
227
|
+
- Improved CORS support for browser-based services
|
|
228
|
+
- Better error handling and logging
|
|
229
|
+
- Support for demo mode autofix integration
|
|
230
|
+
|
|
231
|
+
### v1.0.0
|
|
232
|
+
- Initial release
|
|
233
|
+
- Fastify tracing plugin
|
|
234
|
+
- Database query capture
|
|
235
|
+
- HTTP client wrapping
|
|
236
|
+
- Trace bundle writing
|
|
237
|
+
|
|
212
238
|
## License
|
|
213
239
|
|
|
214
240
|
MIT
|
package/dist/fastify-plugin.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export interface FastifyTracingOptions {
|
|
|
8
8
|
headerAllowlist?: string[];
|
|
9
9
|
maxBodySize?: number;
|
|
10
10
|
enabled?: boolean;
|
|
11
|
+
/** Sample rate 0–1. 1 = always trace, 0 = never, 0.1 = 10% of requests. Default 1. */
|
|
12
|
+
traceSampleRate?: number;
|
|
13
|
+
/** When true, always capture a trace for requests that end in error (onError), even if not sampled. Default true. */
|
|
14
|
+
alwaysTraceErrors?: boolean;
|
|
11
15
|
uploadUrl?: string;
|
|
12
16
|
apiKey?: string;
|
|
13
17
|
uploadOnComplete?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify-plugin.d.ts","sourceRoot":"","sources":["../src/fastify-plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fastify-plugin.d.ts","sourceRoot":"","sources":["../src/fastify-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAE3E,OAAO,EAKL,WAAW,EACZ,MAAM,uBAAuB,CAAC;AAY/B,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qHAAqH;IACrH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,WAAW,CAAC,EAAE,WAAW,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACtC;CACF;AAID;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,kBAAkB,CAAC,qBAAqB,CAyVpE,CAAC;;AAGF,wBAGG"}
|
package/dist/fastify-plugin.js
CHANGED
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.fastifyTracing = void 0;
|
|
7
|
+
const promises_1 = require("fs/promises");
|
|
8
|
+
const path_1 = require("path");
|
|
7
9
|
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
10
|
const trace_bundle_writer_1 = require("./trace-bundle-writer");
|
|
9
11
|
const http_wrapper_1 = require("./http-wrapper");
|
|
@@ -15,7 +17,7 @@ const DEFAULT_MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
|
|
15
17
|
* Fastify plugin for capturing HTTP request/response traces
|
|
16
18
|
*/
|
|
17
19
|
const fastifyTracing = async (fastify, options) => {
|
|
18
|
-
const { serviceName, serviceVersion, environment, traceDir, headerAllowlist, maxBodySize = DEFAULT_MAX_BODY_SIZE, enabled = true, uploadUrl, apiKey, uploadOnComplete = true, } = options;
|
|
20
|
+
const { serviceName, serviceVersion, environment, traceDir, headerAllowlist, maxBodySize = DEFAULT_MAX_BODY_SIZE, enabled = true, traceSampleRate = 1, alwaysTraceErrors = true, uploadUrl, apiKey, uploadOnComplete = true, } = options;
|
|
19
21
|
if (!enabled) {
|
|
20
22
|
fastify.log.info("[SDK] Tracing is disabled");
|
|
21
23
|
return;
|
|
@@ -27,30 +29,36 @@ const fastifyTracing = async (fastify, options) => {
|
|
|
27
29
|
// Hook: onRequest - initialize trace but don't write request event yet (body not available)
|
|
28
30
|
fastify.addHook("onRequest", async (request, reply) => {
|
|
29
31
|
try {
|
|
32
|
+
const sampledIn = traceSampleRate >= 1 || (traceSampleRate > 0 && Math.random() < traceSampleRate);
|
|
33
|
+
request.traceSampledIn = sampledIn;
|
|
34
|
+
if (!sampledIn) {
|
|
35
|
+
request.traceBundle = null;
|
|
36
|
+
request.traceSpanId = null;
|
|
37
|
+
request.traceStartTime = null;
|
|
38
|
+
request.traceRequestBodyHash = null;
|
|
39
|
+
request.traceIncomingParentSpanId = null;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
30
42
|
// Check for trace context propagation headers
|
|
31
43
|
const incomingTraceId = request.headers['x-wraith-trace-id'];
|
|
32
44
|
const incomingParentSpanId = request.headers['x-wraith-parent-span-id'];
|
|
33
45
|
// Create trace bundle for this request
|
|
34
|
-
// Use incoming traceId if present (cross-service propagation), otherwise generate new one
|
|
35
46
|
const traceBundle = await (0, trace_bundle_writer_1.createTrace)({
|
|
36
47
|
serviceName,
|
|
37
48
|
serviceVersion,
|
|
38
49
|
environment,
|
|
39
50
|
traceDir,
|
|
40
|
-
traceId: incomingTraceId,
|
|
51
|
+
traceId: incomingTraceId,
|
|
41
52
|
});
|
|
42
53
|
const spanId = (0, trace_bundle_writer_1.generateSpanId)();
|
|
43
54
|
const startTime = Date.now();
|
|
44
|
-
// Store on request for later use
|
|
45
55
|
request.traceBundle = traceBundle;
|
|
46
56
|
request.traceSpanId = spanId;
|
|
47
57
|
request.traceStartTime = startTime;
|
|
48
|
-
request.traceRequestBodyHash = null;
|
|
49
|
-
// Store incoming parent span ID for use in preHandler
|
|
58
|
+
request.traceRequestBodyHash = null;
|
|
50
59
|
request.traceIncomingParentSpanId = incomingParentSpanId || null;
|
|
51
60
|
}
|
|
52
61
|
catch (error) {
|
|
53
|
-
// Fail-open: log error but don't crash
|
|
54
62
|
fastify.log.error(`[SDK] Failed to initialize trace: ${error}`);
|
|
55
63
|
}
|
|
56
64
|
});
|
|
@@ -179,8 +187,8 @@ const fastifyTracing = async (fastify, options) => {
|
|
|
179
187
|
});
|
|
180
188
|
// Auto-upload trace if configured
|
|
181
189
|
if (uploadUrl && apiKey && uploadOnComplete !== false) {
|
|
182
|
-
// Upload asynchronously (don't block response)
|
|
183
190
|
const actualTraceDir = traceDir || './traces';
|
|
191
|
+
const tracePath = (0, path_1.join)(actualTraceDir, traceBundle.traceId);
|
|
184
192
|
(0, trace_uploader_1.uploadTrace)({
|
|
185
193
|
uploadUrl,
|
|
186
194
|
apiKey,
|
|
@@ -189,15 +197,22 @@ const fastifyTracing = async (fastify, options) => {
|
|
|
189
197
|
serviceName,
|
|
190
198
|
serviceVersion,
|
|
191
199
|
environment,
|
|
192
|
-
}).then(result => {
|
|
200
|
+
}).then(async (result) => {
|
|
193
201
|
if (result.success) {
|
|
194
|
-
fastify.log.info(
|
|
202
|
+
fastify.log.info({ traceId: result.traceId }, '[SDK] Trace uploaded');
|
|
203
|
+
// Delete local trace dir after successful upload to avoid re-upload and unbounded disk growth
|
|
204
|
+
try {
|
|
205
|
+
await (0, promises_1.rm)(tracePath, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
fastify.log.debug({ traceId: traceBundle.traceId, err: err?.message }, '[SDK] Could not delete trace dir after upload');
|
|
209
|
+
}
|
|
195
210
|
}
|
|
196
211
|
else {
|
|
197
|
-
fastify.log.warn(
|
|
212
|
+
fastify.log.warn({ traceId: traceBundle.traceId, error: result.error }, '[SDK] Trace upload failed');
|
|
198
213
|
}
|
|
199
214
|
}).catch(error => {
|
|
200
|
-
fastify.log.error(
|
|
215
|
+
fastify.log.error({ traceId: traceBundle.traceId, error: error?.message }, '[SDK] Trace upload error');
|
|
201
216
|
});
|
|
202
217
|
}
|
|
203
218
|
// Clear trace context
|
|
@@ -210,13 +225,67 @@ const fastifyTracing = async (fastify, options) => {
|
|
|
210
225
|
(0, http_wrapper_1.setTraceContext)(null, null);
|
|
211
226
|
}
|
|
212
227
|
});
|
|
213
|
-
// Hook: onError - capture errors
|
|
228
|
+
// Hook: onError - capture errors (always capture if alwaysTraceErrors and no bundle yet)
|
|
214
229
|
fastify.addHook("onError", async (request, reply, error) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
let traceBundle = request.traceBundle;
|
|
231
|
+
let spanId = request.traceSpanId;
|
|
232
|
+
const wasSampledOut = !request.traceSampledIn;
|
|
233
|
+
if (wasSampledOut && alwaysTraceErrors) {
|
|
234
|
+
try {
|
|
235
|
+
const bundle = await (0, trace_bundle_writer_1.createTrace)({
|
|
236
|
+
serviceName,
|
|
237
|
+
serviceVersion,
|
|
238
|
+
environment,
|
|
239
|
+
traceDir,
|
|
240
|
+
});
|
|
241
|
+
const sid = (0, trace_bundle_writer_1.generateSpanId)();
|
|
242
|
+
traceBundle = bundle;
|
|
243
|
+
spanId = sid;
|
|
244
|
+
await bundle.writeEvent({
|
|
245
|
+
type: "error",
|
|
246
|
+
timestamp: Date.now(),
|
|
247
|
+
spanId: sid,
|
|
248
|
+
parentSpanId: null,
|
|
249
|
+
error: {
|
|
250
|
+
name: error.name || "Error",
|
|
251
|
+
message: error.message || String(error),
|
|
252
|
+
stack: error.stack || undefined,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
await bundle.complete({
|
|
256
|
+
method: 'ERROR',
|
|
257
|
+
path: '/error',
|
|
258
|
+
headers: {},
|
|
259
|
+
});
|
|
260
|
+
if (uploadUrl && apiKey) {
|
|
261
|
+
const actualTraceDir = traceDir || './traces';
|
|
262
|
+
const tracePath = (0, path_1.join)(actualTraceDir, bundle.traceId);
|
|
263
|
+
(0, trace_uploader_1.uploadTrace)({
|
|
264
|
+
uploadUrl,
|
|
265
|
+
apiKey,
|
|
266
|
+
traceDir: actualTraceDir,
|
|
267
|
+
traceId: bundle.traceId,
|
|
268
|
+
serviceName,
|
|
269
|
+
serviceVersion,
|
|
270
|
+
environment,
|
|
271
|
+
}).then(async (result) => {
|
|
272
|
+
if (result.success) {
|
|
273
|
+
try {
|
|
274
|
+
// Delete local trace dir after successful upload to prevent re-upload and unbounded disk growth
|
|
275
|
+
await (0, promises_1.rm)(tracePath, { recursive: true, force: true });
|
|
276
|
+
}
|
|
277
|
+
catch (_) { }
|
|
278
|
+
}
|
|
279
|
+
}).catch(() => { });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
fastify.log.error(`[SDK] Failed to capture error trace: ${err}`);
|
|
284
|
+
}
|
|
218
285
|
return;
|
|
219
286
|
}
|
|
287
|
+
if (!traceBundle || !spanId)
|
|
288
|
+
return;
|
|
220
289
|
try {
|
|
221
290
|
await traceBundle.writeEvent({
|
|
222
291
|
type: "error",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-wrapper.d.ts","sourceRoot":"","sources":["../src/http-wrapper.ts"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,EACvI,MAAM,EAAE,MAAM,GAAG,IAAI,QAQtB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI;IACjC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACxI,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAMA;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,aAAa,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"http-wrapper.d.ts","sourceRoot":"","sources":["../src/http-wrapper.ts"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,EACvI,MAAM,EAAE,MAAM,GAAG,IAAI,QAQtB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI;IACjC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACxI,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAMA;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,aAAa,EAAE,OAAO,KAAK,GAAG,OAAO,KAAK,CA0gBnE"}
|
package/dist/http-wrapper.js
CHANGED
|
@@ -64,10 +64,53 @@ function wrapFetch(originalFetch) {
|
|
|
64
64
|
// - If peerService is null → route to mock server (stub)
|
|
65
65
|
if (peerService) {
|
|
66
66
|
if (subgraphServices.includes(peerService)) {
|
|
67
|
-
// Call is to a service in subgraph -
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
// Call is to a service in subgraph - route to actual service
|
|
68
|
+
// Check for service URL mapping via environment variables
|
|
69
|
+
const serviceUrlEnv = process.env[`${peerService.toUpperCase().replace(/-/g, '_')}_URL`];
|
|
70
|
+
const serviceUrls = process.env.SERVICE_URLS || '';
|
|
71
|
+
// Parse SERVICE_URLS: "service-a:http://localhost:3001,service-b:http://localhost:3002"
|
|
72
|
+
// Note: Split on first ':' only to handle URLs with colons
|
|
73
|
+
const urlMap = new Map();
|
|
74
|
+
if (serviceUrls) {
|
|
75
|
+
for (const mapping of serviceUrls.split(',')) {
|
|
76
|
+
const colonIndex = mapping.indexOf(':');
|
|
77
|
+
if (colonIndex > 0) {
|
|
78
|
+
const service = mapping.substring(0, colonIndex).trim();
|
|
79
|
+
const url = mapping.substring(colonIndex + 1).trim();
|
|
80
|
+
if (service && url) {
|
|
81
|
+
urlMap.set(service, url);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const serviceUrl = serviceUrlEnv || urlMap.get(peerService);
|
|
87
|
+
if (serviceUrl) {
|
|
88
|
+
// Route to actual service
|
|
89
|
+
try {
|
|
90
|
+
const path = urlObj.pathname + urlObj.search;
|
|
91
|
+
const fullUrl = new URL(path, serviceUrl).toString();
|
|
92
|
+
// Get trace context for propagation
|
|
93
|
+
const { bundle, spanId } = getTraceContext();
|
|
94
|
+
// Make request to actual service with trace headers
|
|
95
|
+
const enhancedInit = {
|
|
96
|
+
...init,
|
|
97
|
+
headers: {
|
|
98
|
+
...(init?.headers || {}),
|
|
99
|
+
...(bundle?.traceId ? { 'x-trace-id': bundle.traceId } : {}),
|
|
100
|
+
...(spanId ? { 'x-span-id': spanId } : {}),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
return await originalFetch(fullUrl, enhancedInit);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.warn(`[SDK] Failed to route to ${peerService}: ${error}`);
|
|
107
|
+
shouldRouteToMock = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// No service URL configured, route to mock
|
|
112
|
+
shouldRouteToMock = true;
|
|
113
|
+
}
|
|
71
114
|
}
|
|
72
115
|
else if (stubServices.includes(peerService)) {
|
|
73
116
|
// Call is to a stubbed service - route to mock server
|
package/dist/redaction.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare function createRedactedValue(originalValue: string, includeHash?:
|
|
|
55
55
|
export declare function loadRedactionConfig(configPath?: string): RedactionConfig;
|
|
56
56
|
/**
|
|
57
57
|
* Set global redaction config
|
|
58
|
+
* SECURITY: Redaction cannot be disabled - any attempt to set enabled: false will throw an error
|
|
58
59
|
*/
|
|
59
60
|
export declare function setRedactionConfig(config: Partial<RedactionConfig>): void;
|
|
60
61
|
/**
|
|
@@ -63,38 +64,47 @@ export declare function setRedactionConfig(config: Partial<RedactionConfig>): vo
|
|
|
63
64
|
export declare function getRedactionConfig(): RedactionConfig;
|
|
64
65
|
/**
|
|
65
66
|
* Reset redaction config to defaults
|
|
67
|
+
* SECURITY: Always ensures enabled = true
|
|
66
68
|
*/
|
|
67
69
|
export declare function resetRedactionConfig(): void;
|
|
68
70
|
/**
|
|
69
71
|
* Check if a header name should be redacted
|
|
72
|
+
* SECURITY: Redaction is always enabled - enabled check removed for enforcement
|
|
70
73
|
*/
|
|
71
74
|
export declare function shouldRedactHeader(headerName: string): boolean;
|
|
72
75
|
/**
|
|
73
76
|
* Check if a query param should be redacted
|
|
77
|
+
* SECURITY: Redaction is always enabled - enabled check removed for enforcement
|
|
74
78
|
*/
|
|
75
79
|
export declare function shouldRedactQueryParam(paramName: string): boolean;
|
|
76
80
|
/**
|
|
77
81
|
* Redact headers in a headers object
|
|
82
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
78
83
|
*/
|
|
79
84
|
export declare function redactHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string | string[] | undefined>;
|
|
80
85
|
/**
|
|
81
86
|
* Redact query parameters
|
|
87
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
82
88
|
*/
|
|
83
89
|
export declare function redactQueryParams(query: Record<string, any>): Record<string, any>;
|
|
84
90
|
/**
|
|
85
91
|
* Redact sensitive fields from a body object (recursive)
|
|
92
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
86
93
|
*/
|
|
87
94
|
export declare function redactBodyObject(body: any): any;
|
|
88
95
|
/**
|
|
89
96
|
* Redact patterns from a body string
|
|
97
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
90
98
|
*/
|
|
91
99
|
export declare function redactBodyPatterns(body: string): string;
|
|
92
100
|
/**
|
|
93
101
|
* Redact a body (handles both string and object)
|
|
102
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
94
103
|
*/
|
|
95
104
|
export declare function redactBody(body: string | object | null | undefined): string | object | null | undefined;
|
|
96
105
|
/**
|
|
97
106
|
* Redact a URL (query params)
|
|
107
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
98
108
|
*/
|
|
99
109
|
export declare function redactUrl(url: string): string;
|
|
100
110
|
/**
|
package/dist/redaction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,aAAa,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,sCAAsC;IACtC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,aAAa,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,sCAAsC;IACtC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAoOD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,MAAoC,GACzC,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,OAAc,EAC3B,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,eAAe,CAiDxE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAiBzE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAEpD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAkB9D;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAkBjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAyB/C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAuBrB;AAsBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAmC/C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBvD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CA8BpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8B7C;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYhE;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,UAA4B,CAAC;AAC3D,eAAO,MAAM,sBAAsB,UAAiC,CAAC;AACrE,eAAO,MAAM,qBAAqB,UAAgC,CAAC"}
|
package/dist/redaction.js
CHANGED
|
@@ -66,8 +66,10 @@ const DEFAULT_SENSITIVE_QUERY_PARAMS = [
|
|
|
66
66
|
];
|
|
67
67
|
/**
|
|
68
68
|
* Default sensitive body field names (case-insensitive)
|
|
69
|
+
* Includes PII (Personally Identifiable Information) fields for GDPR/compliance
|
|
69
70
|
*/
|
|
70
71
|
const DEFAULT_SENSITIVE_BODY_FIELDS = [
|
|
72
|
+
// Authentication & Secrets
|
|
71
73
|
"password",
|
|
72
74
|
"passwd",
|
|
73
75
|
"pwd",
|
|
@@ -84,20 +86,86 @@ const DEFAULT_SENSITIVE_BODY_FIELDS = [
|
|
|
84
86
|
"secretKey",
|
|
85
87
|
"client_secret",
|
|
86
88
|
"clientSecret",
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
+
"pin",
|
|
90
|
+
// Financial Information
|
|
89
91
|
"credit_card",
|
|
90
92
|
"creditCard",
|
|
91
93
|
"card_number",
|
|
92
94
|
"cardNumber",
|
|
93
95
|
"cvv",
|
|
94
96
|
"cvc",
|
|
95
|
-
"
|
|
97
|
+
"bank_account",
|
|
98
|
+
"bankAccount",
|
|
99
|
+
"routing_number",
|
|
100
|
+
"routingNumber",
|
|
101
|
+
// PII - Personal Identifiers
|
|
102
|
+
"ssn",
|
|
103
|
+
"social_security",
|
|
104
|
+
"social_security_number",
|
|
105
|
+
"tax_id",
|
|
106
|
+
"taxId",
|
|
107
|
+
"driver_license",
|
|
108
|
+
"driverLicense",
|
|
109
|
+
"passport",
|
|
110
|
+
"national_id",
|
|
111
|
+
"nationalId",
|
|
112
|
+
// PII - Contact Information
|
|
113
|
+
"email",
|
|
114
|
+
"email_address",
|
|
115
|
+
"emailAddress",
|
|
116
|
+
"phone",
|
|
117
|
+
"phone_number",
|
|
118
|
+
"phoneNumber",
|
|
119
|
+
"mobile",
|
|
120
|
+
"mobile_number",
|
|
121
|
+
"mobileNumber",
|
|
122
|
+
"telephone",
|
|
123
|
+
"address",
|
|
124
|
+
"street_address",
|
|
125
|
+
"streetAddress",
|
|
126
|
+
"home_address",
|
|
127
|
+
"homeAddress",
|
|
128
|
+
"billing_address",
|
|
129
|
+
"billingAddress",
|
|
130
|
+
"shipping_address",
|
|
131
|
+
"shippingAddress",
|
|
132
|
+
// PII - Personal Details
|
|
133
|
+
"first_name",
|
|
134
|
+
"firstName",
|
|
135
|
+
"last_name",
|
|
136
|
+
"lastName",
|
|
137
|
+
"full_name",
|
|
138
|
+
"fullName",
|
|
139
|
+
"name",
|
|
140
|
+
"date_of_birth",
|
|
141
|
+
"dateOfBirth",
|
|
142
|
+
"dob",
|
|
143
|
+
"birth_date",
|
|
144
|
+
"birthDate",
|
|
145
|
+
"age",
|
|
146
|
+
"gender",
|
|
147
|
+
"race",
|
|
148
|
+
"ethnicity",
|
|
149
|
+
// PII - Location
|
|
150
|
+
"city",
|
|
151
|
+
"state",
|
|
152
|
+
"province",
|
|
153
|
+
"zip",
|
|
154
|
+
"zip_code",
|
|
155
|
+
"zipCode",
|
|
156
|
+
"postal_code",
|
|
157
|
+
"postalCode",
|
|
158
|
+
"country",
|
|
159
|
+
"latitude",
|
|
160
|
+
"longitude",
|
|
161
|
+
"coordinates",
|
|
96
162
|
];
|
|
97
163
|
/**
|
|
98
164
|
* Default body patterns to redact (regex)
|
|
165
|
+
* Includes PII pattern detection for comprehensive data protection
|
|
99
166
|
*/
|
|
100
167
|
const DEFAULT_SENSITIVE_BODY_PATTERNS = [
|
|
168
|
+
// Authentication tokens
|
|
101
169
|
// Bearer tokens
|
|
102
170
|
/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*/g,
|
|
103
171
|
// JWT tokens
|
|
@@ -106,8 +174,21 @@ const DEFAULT_SENSITIVE_BODY_PATTERNS = [
|
|
|
106
174
|
/(?:api[_-]?key|apikey)[=:]["']?[A-Za-z0-9\-_]{20,}["']?/gi,
|
|
107
175
|
// AWS access keys
|
|
108
176
|
/AKIA[0-9A-Z]{16}/g,
|
|
177
|
+
// Financial Information
|
|
109
178
|
// Credit card numbers (basic pattern)
|
|
110
179
|
/\b(?:\d{4}[- ]?){3}\d{4}\b/g,
|
|
180
|
+
// PII - Email addresses (pattern-based detection)
|
|
181
|
+
/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g,
|
|
182
|
+
// PII - Phone numbers (US and international formats)
|
|
183
|
+
/\b\+?[\d\s\-()]{10,}\b/g,
|
|
184
|
+
// PII - Social Security Numbers (US format)
|
|
185
|
+
/\b\d{3}-\d{2}-\d{4}\b/g,
|
|
186
|
+
/\b\d{9}\b/g, // 9 consecutive digits (potential SSN)
|
|
187
|
+
// PII - Dates that might be DOB (MM/DD/YYYY, YYYY-MM-DD, etc.)
|
|
188
|
+
/\b(0[1-9]|1[0-2])[\/\-](0[1-9]|[12]\d|3[01])[\/\-]\d{4}\b/g,
|
|
189
|
+
/\b\d{4}[\/\-](0[1-9]|1[0-2])[\/\-](0[1-9]|[12]\d|3[01])\b/g,
|
|
190
|
+
// PII - IP addresses (may contain sensitive location data)
|
|
191
|
+
/\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
111
192
|
];
|
|
112
193
|
/**
|
|
113
194
|
* Build default redaction rules
|
|
@@ -150,11 +231,12 @@ function buildDefaultRules() {
|
|
|
150
231
|
}
|
|
151
232
|
/**
|
|
152
233
|
* Default redaction configuration
|
|
234
|
+
* SECURITY: Redaction is ALWAYS enabled and cannot be disabled for compliance
|
|
153
235
|
*/
|
|
154
236
|
const DEFAULT_CONFIG = {
|
|
155
|
-
enabled: true,
|
|
237
|
+
enabled: true, // Always true - cannot be changed
|
|
156
238
|
rules: buildDefaultRules(),
|
|
157
|
-
hashSalt: "rdt-redaction-salt-v1",
|
|
239
|
+
hashSalt: process.env.REDACTION_SALT || process.env.REDACTION_HASH_SALT || "rdt-redaction-salt-v1",
|
|
158
240
|
};
|
|
159
241
|
/**
|
|
160
242
|
* Global redaction config (can be overridden)
|
|
@@ -201,14 +283,20 @@ function loadRedactionConfig(configPath) {
|
|
|
201
283
|
if (config.redaction) {
|
|
202
284
|
const userConfig = config.redaction;
|
|
203
285
|
// Merge with defaults
|
|
286
|
+
// SECURITY: Always enforce enabled = true, ignore user attempts to disable
|
|
204
287
|
const mergedConfig = {
|
|
205
|
-
enabled:
|
|
288
|
+
enabled: true, // Always true - cannot be disabled
|
|
206
289
|
rules: userConfig.overrideDefaults
|
|
207
290
|
? userConfig.rules || []
|
|
208
291
|
: [...buildDefaultRules(), ...(userConfig.rules || [])],
|
|
209
292
|
overrideDefaults: userConfig.overrideDefaults,
|
|
210
293
|
hashSalt: userConfig.hashSalt || DEFAULT_CONFIG.hashSalt,
|
|
211
294
|
};
|
|
295
|
+
// Warn if user tried to disable redaction
|
|
296
|
+
if (userConfig.enabled === false) {
|
|
297
|
+
console.warn('[Redaction] Attempted to disable redaction in config file. ' +
|
|
298
|
+
'Redaction is always enabled for security compliance. Ignoring enabled: false.');
|
|
299
|
+
}
|
|
212
300
|
return mergedConfig;
|
|
213
301
|
}
|
|
214
302
|
}
|
|
@@ -221,11 +309,18 @@ function loadRedactionConfig(configPath) {
|
|
|
221
309
|
}
|
|
222
310
|
/**
|
|
223
311
|
* Set global redaction config
|
|
312
|
+
* SECURITY: Redaction cannot be disabled - any attempt to set enabled: false will throw an error
|
|
224
313
|
*/
|
|
225
314
|
function setRedactionConfig(config) {
|
|
315
|
+
// SECURITY: Prevent disabling redaction for compliance
|
|
316
|
+
if (config.enabled === false) {
|
|
317
|
+
throw new Error('Redaction cannot be disabled for security and compliance requirements. ' +
|
|
318
|
+
'All sensitive data must be redacted before storage.');
|
|
319
|
+
}
|
|
226
320
|
globalConfig = {
|
|
227
321
|
...DEFAULT_CONFIG,
|
|
228
322
|
...config,
|
|
323
|
+
enabled: true, // Always enforce enabled = true
|
|
229
324
|
rules: config.overrideDefaults
|
|
230
325
|
? config.rules || []
|
|
231
326
|
: [...buildDefaultRules(), ...(config.rules || [])],
|
|
@@ -239,16 +334,22 @@ function getRedactionConfig() {
|
|
|
239
334
|
}
|
|
240
335
|
/**
|
|
241
336
|
* Reset redaction config to defaults
|
|
337
|
+
* SECURITY: Always ensures enabled = true
|
|
242
338
|
*/
|
|
243
339
|
function resetRedactionConfig() {
|
|
244
|
-
globalConfig = {
|
|
340
|
+
globalConfig = {
|
|
341
|
+
...DEFAULT_CONFIG,
|
|
342
|
+
enabled: true, // Always enforce enabled
|
|
343
|
+
rules: buildDefaultRules()
|
|
344
|
+
};
|
|
245
345
|
}
|
|
246
346
|
/**
|
|
247
347
|
* Check if a header name should be redacted
|
|
348
|
+
* SECURITY: Redaction is always enabled - enabled check removed for enforcement
|
|
248
349
|
*/
|
|
249
350
|
function shouldRedactHeader(headerName) {
|
|
250
|
-
|
|
251
|
-
|
|
351
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
352
|
+
// if (!globalConfig.enabled) return false; // REMOVED - cannot bypass
|
|
252
353
|
const lowerName = headerName.toLowerCase();
|
|
253
354
|
for (const rule of globalConfig.rules) {
|
|
254
355
|
if (rule.type !== "header")
|
|
@@ -267,10 +368,11 @@ function shouldRedactHeader(headerName) {
|
|
|
267
368
|
}
|
|
268
369
|
/**
|
|
269
370
|
* Check if a query param should be redacted
|
|
371
|
+
* SECURITY: Redaction is always enabled - enabled check removed for enforcement
|
|
270
372
|
*/
|
|
271
373
|
function shouldRedactQueryParam(paramName) {
|
|
272
|
-
|
|
273
|
-
|
|
374
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
375
|
+
// if (!globalConfig.enabled) return false; // REMOVED - cannot bypass
|
|
274
376
|
const lowerName = paramName.toLowerCase();
|
|
275
377
|
for (const rule of globalConfig.rules) {
|
|
276
378
|
if (rule.type !== "query_param")
|
|
@@ -289,10 +391,11 @@ function shouldRedactQueryParam(paramName) {
|
|
|
289
391
|
}
|
|
290
392
|
/**
|
|
291
393
|
* Redact headers in a headers object
|
|
394
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
292
395
|
*/
|
|
293
396
|
function redactHeaders(headers) {
|
|
294
|
-
|
|
295
|
-
|
|
397
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
398
|
+
// if (!globalConfig.enabled) return headers; // REMOVED - cannot bypass
|
|
296
399
|
const redacted = {};
|
|
297
400
|
for (const [key, value] of Object.entries(headers)) {
|
|
298
401
|
if (value === undefined) {
|
|
@@ -311,10 +414,11 @@ function redactHeaders(headers) {
|
|
|
311
414
|
}
|
|
312
415
|
/**
|
|
313
416
|
* Redact query parameters
|
|
417
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
314
418
|
*/
|
|
315
419
|
function redactQueryParams(query) {
|
|
316
|
-
|
|
317
|
-
|
|
420
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
421
|
+
// if (!globalConfig.enabled) return query; // REMOVED - cannot bypass
|
|
318
422
|
const redacted = {};
|
|
319
423
|
for (const [key, value] of Object.entries(query)) {
|
|
320
424
|
if (shouldRedactQueryParam(key)) {
|
|
@@ -352,10 +456,11 @@ function shouldRedactBodyField(fieldName) {
|
|
|
352
456
|
}
|
|
353
457
|
/**
|
|
354
458
|
* Redact sensitive fields from a body object (recursive)
|
|
459
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
355
460
|
*/
|
|
356
461
|
function redactBodyObject(body) {
|
|
357
|
-
|
|
358
|
-
|
|
462
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
463
|
+
// if (!globalConfig.enabled) return body; // REMOVED - cannot bypass
|
|
359
464
|
if (body === null || body === undefined) {
|
|
360
465
|
return body;
|
|
361
466
|
}
|
|
@@ -382,10 +487,11 @@ function redactBodyObject(body) {
|
|
|
382
487
|
}
|
|
383
488
|
/**
|
|
384
489
|
* Redact patterns from a body string
|
|
490
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
385
491
|
*/
|
|
386
492
|
function redactBodyPatterns(body) {
|
|
387
|
-
|
|
388
|
-
|
|
493
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
494
|
+
// if (!globalConfig.enabled) return body; // REMOVED - cannot bypass
|
|
389
495
|
let result = body;
|
|
390
496
|
for (const rule of globalConfig.rules) {
|
|
391
497
|
if (rule.type !== "body_pattern" || !rule.pattern)
|
|
@@ -405,10 +511,11 @@ function redactBodyPatterns(body) {
|
|
|
405
511
|
}
|
|
406
512
|
/**
|
|
407
513
|
* Redact a body (handles both string and object)
|
|
514
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
408
515
|
*/
|
|
409
516
|
function redactBody(body) {
|
|
410
|
-
|
|
411
|
-
|
|
517
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
518
|
+
// if (!globalConfig.enabled) return body; // REMOVED - cannot bypass
|
|
412
519
|
if (body === null || body === undefined) {
|
|
413
520
|
return body;
|
|
414
521
|
}
|
|
@@ -436,10 +543,11 @@ function redactBody(body) {
|
|
|
436
543
|
}
|
|
437
544
|
/**
|
|
438
545
|
* Redact a URL (query params)
|
|
546
|
+
* SECURITY: Redaction is always enforced - enabled check removed
|
|
439
547
|
*/
|
|
440
548
|
function redactUrl(url) {
|
|
441
|
-
|
|
442
|
-
|
|
549
|
+
// SECURITY: Redaction is always enabled - removed bypass check
|
|
550
|
+
// if (!globalConfig.enabled) return url; // REMOVED - cannot bypass
|
|
443
551
|
try {
|
|
444
552
|
const urlObj = new URL(url, "http://placeholder");
|
|
445
553
|
const redactedParams = new URLSearchParams();
|
|
@@ -32,19 +32,17 @@ export declare function createTrace(options: CreateTraceOptions): Promise<TraceB
|
|
|
32
32
|
* Helper to process body content and store as blob
|
|
33
33
|
* Applies redaction to sensitive fields before storing
|
|
34
34
|
* NOTE: Always stores bodies as blobs so they can be retrieved during replay
|
|
35
|
+
* SECURITY: Redaction is ALWAYS enforced - skipRedaction option removed
|
|
35
36
|
*/
|
|
36
|
-
export declare function processBody(body: string | Buffer | object | null | undefined, writeBlobFn: (content: string | object) => Promise<string
|
|
37
|
-
skipRedaction?: boolean;
|
|
38
|
-
}): Promise<{
|
|
37
|
+
export declare function processBody(body: string | Buffer | object | null | undefined, writeBlobFn: (content: string | object) => Promise<string>): Promise<{
|
|
39
38
|
bodyHash: string | null;
|
|
40
39
|
bodyBlob: string | null;
|
|
41
40
|
}>;
|
|
42
41
|
/**
|
|
43
42
|
* Filter headers based on allowlist and apply redaction
|
|
43
|
+
* SECURITY: Redaction is ALWAYS enforced - skipRedaction option removed
|
|
44
44
|
*/
|
|
45
|
-
export declare function filterHeaders(headers: Record<string, string | string[] | undefined>, allowlist?: string[],
|
|
46
|
-
skipRedaction?: boolean;
|
|
47
|
-
}): Record<string, string>;
|
|
45
|
+
export declare function filterHeaders(headers: Record<string, string | string[] | undefined>, allowlist?: string[]): Record<string, string>;
|
|
48
46
|
/**
|
|
49
47
|
* Generate span ID (exported for use in middleware)
|
|
50
48
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace-bundle-writer.d.ts","sourceRoot":"","sources":["../src/trace-bundle-writer.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAED,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAM3D,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAWxC;;GAEG;AACH,iBAAS,cAAc,IAAI,MAAM,CAEhC;AAgBD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAkLtB;AAED
|
|
1
|
+
{"version":3,"file":"trace-bundle-writer.d.ts","sourceRoot":"","sources":["../src/trace-bundle-writer.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAED,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAM3D,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAWxC;;GAEG;AACH,iBAAS,cAAc,IAAI,MAAM,CAEhC;AAgBD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAkLtB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACjD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAEzD,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA+B7D;AAEH;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,SAAS,CAAC,EAAE,MAAM,EAAE,GAEnB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqCxB;AAED;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -190,8 +190,11 @@ async function createTrace(options) {
|
|
|
190
190
|
* Helper to process body content and store as blob
|
|
191
191
|
* Applies redaction to sensitive fields before storing
|
|
192
192
|
* NOTE: Always stores bodies as blobs so they can be retrieved during replay
|
|
193
|
+
* SECURITY: Redaction is ALWAYS enforced - skipRedaction option removed
|
|
193
194
|
*/
|
|
194
|
-
function processBody(body, writeBlobFn
|
|
195
|
+
function processBody(body, writeBlobFn
|
|
196
|
+
// SECURITY: Removed skipRedaction option - redaction is always enforced
|
|
197
|
+
) {
|
|
195
198
|
if (!body) {
|
|
196
199
|
return Promise.resolve({ bodyHash: null, bodyBlob: null });
|
|
197
200
|
}
|
|
@@ -205,15 +208,13 @@ function processBody(body, writeBlobFn, options) {
|
|
|
205
208
|
else {
|
|
206
209
|
bodyStr = String(body);
|
|
207
210
|
}
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
bodyStr = JSON.stringify(redacted);
|
|
216
|
-
}
|
|
211
|
+
// SECURITY: Redaction is ALWAYS applied - no bypass option
|
|
212
|
+
const redacted = (0, redaction_1.redactBody)(bodyStr);
|
|
213
|
+
if (typeof redacted === "string") {
|
|
214
|
+
bodyStr = redacted;
|
|
215
|
+
}
|
|
216
|
+
else if (redacted !== null && redacted !== undefined) {
|
|
217
|
+
bodyStr = JSON.stringify(redacted);
|
|
217
218
|
}
|
|
218
219
|
const hash = computeHash(bodyStr);
|
|
219
220
|
const hashFormatted = formatHash(hash);
|
|
@@ -226,8 +227,11 @@ function processBody(body, writeBlobFn, options) {
|
|
|
226
227
|
}
|
|
227
228
|
/**
|
|
228
229
|
* Filter headers based on allowlist and apply redaction
|
|
230
|
+
* SECURITY: Redaction is ALWAYS enforced - skipRedaction option removed
|
|
229
231
|
*/
|
|
230
|
-
function filterHeaders(headers, allowlist
|
|
232
|
+
function filterHeaders(headers, allowlist
|
|
233
|
+
// SECURITY: Removed skipRedaction option - redaction is always enforced
|
|
234
|
+
) {
|
|
231
235
|
if (!allowlist || allowlist.length === 0) {
|
|
232
236
|
// Default allowlist: common headers that are safe to capture
|
|
233
237
|
const defaultAllowlist = [
|
|
@@ -241,7 +245,7 @@ function filterHeaders(headers, allowlist, options) {
|
|
|
241
245
|
"x-request-id",
|
|
242
246
|
"x-correlation-id",
|
|
243
247
|
];
|
|
244
|
-
return filterHeaders(headers, defaultAllowlist
|
|
248
|
+
return filterHeaders(headers, defaultAllowlist);
|
|
245
249
|
}
|
|
246
250
|
const filtered = {};
|
|
247
251
|
const lowerAllowlist = allowlist.map((h) => h.toLowerCase());
|
|
@@ -252,16 +256,13 @@ function filterHeaders(headers, allowlist, options) {
|
|
|
252
256
|
filtered[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
253
257
|
}
|
|
254
258
|
}
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
result[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
262
|
-
}
|
|
259
|
+
// SECURITY: Redaction is ALWAYS applied - no bypass option
|
|
260
|
+
const redacted = (0, redaction_1.redactHeaders)(filtered);
|
|
261
|
+
const result = {};
|
|
262
|
+
for (const [key, value] of Object.entries(redacted)) {
|
|
263
|
+
if (value !== undefined) {
|
|
264
|
+
result[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
263
265
|
}
|
|
264
|
-
return result;
|
|
265
266
|
}
|
|
266
|
-
return
|
|
267
|
+
return result;
|
|
267
268
|
}
|
package/dist/trace-uploader.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export interface TraceUploadResult {
|
|
|
19
19
|
error?: string;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* Upload trace to ingestion endpoint
|
|
22
|
+
* Upload trace to ingestion endpoint with retries (5xx, 429, network errors).
|
|
23
|
+
* Exponential backoff: 1s, 2s, 4s (capped at 8s).
|
|
23
24
|
*/
|
|
24
25
|
export declare function uploadTrace(options: TraceUploadOptions): Promise<TraceUploadResult>;
|
|
25
26
|
//# sourceMappingURL=trace-uploader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace-uploader.d.ts","sourceRoot":"","sources":["../src/trace-uploader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"trace-uploader.d.ts","sourceRoot":"","sources":["../src/trace-uploader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAwI5B"}
|
package/dist/trace-uploader.js
CHANGED
|
@@ -53,8 +53,26 @@ async function readTraceMeta(metaPath) {
|
|
|
53
53
|
throw new Error(`Failed to read meta.json: ${error.message}`);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
const MAX_UPLOAD_ATTEMPTS = 3;
|
|
57
|
+
const INITIAL_BACKOFF_MS = 1000;
|
|
58
|
+
const MAX_BACKOFF_MS = 8000;
|
|
59
|
+
function isRetryableStatus(status) {
|
|
60
|
+
return status >= 500 || status === 429;
|
|
61
|
+
}
|
|
62
|
+
function sleep(ms) {
|
|
63
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
64
|
+
}
|
|
65
|
+
function logUpload(event) {
|
|
66
|
+
try {
|
|
67
|
+
console.warn('[SDK]', JSON.stringify({ event: 'trace_upload', ...event }));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
console.warn('[SDK] trace_upload', event);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
56
73
|
/**
|
|
57
|
-
* Upload trace to ingestion endpoint
|
|
74
|
+
* Upload trace to ingestion endpoint with retries (5xx, 429, network errors).
|
|
75
|
+
* Exponential backoff: 1s, 2s, 4s (capped at 8s).
|
|
58
76
|
*/
|
|
59
77
|
async function uploadTrace(options) {
|
|
60
78
|
const { uploadUrl, apiKey, traceDir, traceId, serviceName, serviceVersion, environment } = options;
|
|
@@ -89,7 +107,6 @@ async function uploadTrace(options) {
|
|
|
89
107
|
correlationIds: meta.correlationIds || [],
|
|
90
108
|
},
|
|
91
109
|
events: events.map(event => {
|
|
92
|
-
// Ensure all events have required base envelope fields
|
|
93
110
|
return {
|
|
94
111
|
...event,
|
|
95
112
|
traceId: event.traceId || traceId,
|
|
@@ -98,31 +115,77 @@ async function uploadTrace(options) {
|
|
|
98
115
|
};
|
|
99
116
|
}),
|
|
100
117
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
let lastError;
|
|
119
|
+
let lastStatus;
|
|
120
|
+
for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt++) {
|
|
121
|
+
logUpload({ traceId, attempt });
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch(uploadUrl, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify(batch),
|
|
130
|
+
});
|
|
131
|
+
lastStatus = response.status;
|
|
132
|
+
logUpload({ traceId, attempt, statusCode: response.status });
|
|
133
|
+
if (response.ok) {
|
|
134
|
+
const result = await response.json();
|
|
135
|
+
logUpload({ traceId, attempt, statusCode: response.status, success: true });
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
traceId: result.traceId || traceId,
|
|
139
|
+
blobRoot: result.blobRoot,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const errorText = await response.text();
|
|
143
|
+
lastError = `${response.status} ${response.statusText} - ${errorText.substring(0, 200)}`;
|
|
144
|
+
if (!isRetryableStatus(response.status) || attempt === MAX_UPLOAD_ATTEMPTS) {
|
|
145
|
+
logUpload({ traceId, attempt, statusCode: response.status, success: false, error: lastError?.substring(0, 200) });
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
traceId,
|
|
149
|
+
error: lastError,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
let backoffMs = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1), MAX_BACKOFF_MS);
|
|
153
|
+
if (response.status === 429) {
|
|
154
|
+
const retryAfter = response.headers.get('Retry-After');
|
|
155
|
+
if (retryAfter != null) {
|
|
156
|
+
const retryAfterMs = /^\d+$/.test(retryAfter.trim())
|
|
157
|
+
? parseInt(retryAfter.trim(), 10) * 1000
|
|
158
|
+
: 0;
|
|
159
|
+
if (retryAfterMs > 0) {
|
|
160
|
+
backoffMs = Math.min(backoffMs, retryAfterMs);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
await sleep(backoffMs);
|
|
165
|
+
}
|
|
166
|
+
catch (networkError) {
|
|
167
|
+
lastError = networkError.message || String(networkError);
|
|
168
|
+
logUpload({ traceId, attempt, success: false, error: lastError?.substring(0, 200) });
|
|
169
|
+
if (attempt === MAX_UPLOAD_ATTEMPTS) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
traceId,
|
|
173
|
+
error: lastError,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const backoffMs = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1), MAX_BACKOFF_MS);
|
|
177
|
+
await sleep(backoffMs);
|
|
178
|
+
}
|
|
117
179
|
}
|
|
118
|
-
|
|
180
|
+
logUpload({ traceId, attempt: MAX_UPLOAD_ATTEMPTS, statusCode: lastStatus, success: false, error: lastError?.substring(0, 200) });
|
|
119
181
|
return {
|
|
120
|
-
success:
|
|
121
|
-
traceId
|
|
122
|
-
|
|
182
|
+
success: false,
|
|
183
|
+
traceId,
|
|
184
|
+
error: lastError || 'Upload failed after retries',
|
|
123
185
|
};
|
|
124
186
|
}
|
|
125
187
|
catch (error) {
|
|
188
|
+
logUpload({ traceId, success: false, error: error.message?.substring(0, 200) });
|
|
126
189
|
return {
|
|
127
190
|
success: false,
|
|
128
191
|
traceId,
|
package/package.json
CHANGED
|
@@ -1,35 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtime-digital-twin/sdk",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "SDK for capturing runtime behavior - automatic incident response and debugging",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "SDK for capturing runtime behavior - automatic incident response and debugging with enhanced autofix support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
|
-
"default": "./
|
|
10
|
+
"default": "./src/index.ts"
|
|
11
11
|
},
|
|
12
12
|
"./src/*": "./src/*",
|
|
13
13
|
"./dist/*": "./dist/*"
|
|
14
14
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
17
|
-
"dev": "tsc --watch",
|
|
18
|
-
"test": "jest",
|
|
19
|
-
"test:watch": "jest --watch",
|
|
20
|
-
"prepublishOnly": "pnpm build"
|
|
21
|
-
},
|
|
22
15
|
"dependencies": {
|
|
23
|
-
"@runtime-digital-twin/core": "^1.0.0",
|
|
24
16
|
"fastify": "^4.24.0",
|
|
25
|
-
"fastify-plugin": "^4.5.0"
|
|
17
|
+
"fastify-plugin": "^4.5.0",
|
|
18
|
+
"@runtime-digital-twin/core": "1.0.1"
|
|
26
19
|
},
|
|
27
20
|
"devDependencies": {
|
|
28
|
-
"@jest/globals": "^
|
|
29
|
-
"@types/jest": "^
|
|
21
|
+
"@jest/globals": "^30.2.0",
|
|
22
|
+
"@types/jest": "^30.0.0",
|
|
30
23
|
"@types/node": "^20.0.0",
|
|
31
|
-
"jest": "^
|
|
32
|
-
"ts-jest": "^29.
|
|
24
|
+
"jest": "^30.2.0",
|
|
25
|
+
"ts-jest": "^29.4.6",
|
|
33
26
|
"typescript": "^5.0.0"
|
|
34
27
|
},
|
|
35
28
|
"license": "MIT",
|
|
@@ -38,7 +31,7 @@
|
|
|
38
31
|
},
|
|
39
32
|
"repository": {
|
|
40
33
|
"type": "git",
|
|
41
|
-
"url": "git+https://github.com/
|
|
34
|
+
"url": "git+https://github.com/usewraith/wraith.git",
|
|
42
35
|
"directory": "packages/sdk"
|
|
43
36
|
},
|
|
44
37
|
"keywords": [
|
|
@@ -58,6 +51,11 @@
|
|
|
58
51
|
"dist",
|
|
59
52
|
"README.md",
|
|
60
53
|
"LICENSE"
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc",
|
|
57
|
+
"dev": "tsc --watch",
|
|
58
|
+
"test": "jest",
|
|
59
|
+
"test:watch": "jest --watch"
|
|
60
|
+
}
|
|
61
|
+
}
|