@reproapp/node-sdk 0.0.2 → 0.0.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.
Files changed (45) hide show
  1. package/README.md +36 -2
  2. package/dist/index.d.ts +140 -15
  3. package/dist/index.js +4927 -927
  4. package/dist/ingest/client.d.ts +10 -0
  5. package/dist/ingest/client.js +158 -0
  6. package/dist/ingest/mapper.d.ts +2 -0
  7. package/dist/ingest/mapper.js +92 -0
  8. package/dist/ingest/types.d.ts +40 -0
  9. package/dist/ingest/types.js +2 -0
  10. package/dist/ingest/worker.js +19 -0
  11. package/dist/integrations/sendgrid.d.ts +2 -4
  12. package/dist/integrations/sendgrid.js +4 -14
  13. package/dist/privacy-fallback.d.ts +1 -0
  14. package/dist/privacy-fallback.js +27 -0
  15. package/dist/privacy-redaction.d.ts +3 -0
  16. package/dist/privacy-redaction.js +38 -0
  17. package/dist/privacy.d.ts +108 -0
  18. package/dist/privacy.js +2868 -0
  19. package/dist/trace-materializer-worker.d.ts +1 -0
  20. package/dist/trace-materializer-worker.js +33 -0
  21. package/docs/tracing.md +1 -0
  22. package/package.json +9 -3
  23. package/src/index.ts +5583 -954
  24. package/src/ingest/client.ts +194 -0
  25. package/src/ingest/mapper.ts +104 -0
  26. package/src/ingest/types.ts +42 -0
  27. package/src/integrations/sendgrid.ts +6 -19
  28. package/src/privacy-fallback.ts +25 -0
  29. package/src/privacy-redaction.ts +37 -0
  30. package/src/privacy.ts +3593 -0
  31. package/src/trace-materializer-worker.ts +39 -0
  32. package/test/circular-capture.test.js +111 -0
  33. package/test/disable-subtree.test.js +154 -0
  34. package/test/integration-unawaited.js +183 -0
  35. package/test/kafka-runtime-privacy-policy.test.js +285 -0
  36. package/test/privacy-runtime-policy.test.js +2043 -0
  37. package/test/promise-map.test.js +72 -0
  38. package/test/unawaited.test.js +163 -0
  39. package/test/wrap-plugin-arrow-args.test.js +80 -0
  40. package/tracer/cjs-hook.js +0 -1
  41. package/tracer/wrap-plugin.js +96 -10
  42. package/dist/redaction.d.ts +0 -44
  43. package/dist/redaction.js +0 -167
  44. package/dist/server.js +0 -26
  45. /package/dist/{server.d.ts → ingest/worker.d.ts} +0 -0
@@ -0,0 +1,72 @@
1
+ process.env.TRACE_RELOAD_CACHE = '0';
2
+ require('../tracer/register');
3
+ const assert = require('assert');
4
+ const { trace } = require('../tracer/runtime');
5
+
6
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
7
+
8
+ const handlers = [
9
+ {
10
+ getRecipients: async () => { await wait(10); return ['a']; },
11
+ getTemplate: () => 'tmpl1',
12
+ },
13
+ {
14
+ getRecipients: async () => { await wait(1); return ['b']; },
15
+ getTemplate: () => 'tmpl2',
16
+ },
17
+ ];
18
+
19
+ async function handlerWorker(handler) {
20
+ const recipients = await handler.getRecipients();
21
+ const template = await handler.getTemplate();
22
+ return { recipients, template };
23
+ }
24
+
25
+ async function runScenario() {
26
+ const results = handlers.map(handler => handlerWorker(handler));
27
+ return Promise.all(results);
28
+ }
29
+
30
+ async function main() {
31
+ const events = [];
32
+ const off = trace.on(ev => { if (ev && ev.traceId === 'map-loop') events.push(ev); });
33
+
34
+ await trace.withTrace('map-loop', async () => {
35
+ await runScenario();
36
+ });
37
+
38
+ // Give any pending promise callbacks a moment to emit their exits.
39
+ await wait(10);
40
+ off();
41
+
42
+ const enters = (name) => events.filter(ev => ev && ev.type === 'enter' && ev.fn === name);
43
+
44
+ const workerEnters = enters('handlerWorker');
45
+ const recipientsEnters = enters('handler.getRecipients');
46
+ const templateEnters = enters('handler.getTemplate');
47
+
48
+ const uniq = (arr) => Array.from(new Set(arr));
49
+
50
+ const workerSpanIds = uniq(workerEnters.map(ev => ev.spanId).filter(Boolean));
51
+ const recipientSpanIds = new Set(recipientsEnters.map(ev => ev.spanId).filter(Boolean));
52
+ const templateParentIds = uniq(templateEnters.map(ev => ev.parentSpanId).filter(Boolean));
53
+
54
+ assert(workerSpanIds.length >= 2, 'expected at least two handlerWorker spans');
55
+ assert(recipientsEnters.length >= 2, 'expected two getRecipients entries');
56
+ assert(templateEnters.length >= 2, 'expected two getTemplate entries');
57
+ assert.strictEqual(templateParentIds.length, 2, 'expected templates to attach to two distinct handler spans');
58
+
59
+ templateEnters.forEach(ev => {
60
+ assert(workerSpanIds.includes(ev.parentSpanId),
61
+ 'getTemplate should be parented to its map callback (handlerWorker)');
62
+ assert(!recipientSpanIds.has(ev.parentSpanId),
63
+ 'getTemplate should not be nested under getRecipients span');
64
+ });
65
+
66
+ console.log('promise map + Promise.all trace parenting OK');
67
+ }
68
+
69
+ main().catch(err => {
70
+ console.error(err);
71
+ process.exitCode = 1;
72
+ });
@@ -0,0 +1,163 @@
1
+ // Integration-style sanity check for unawaited async call ordering.
2
+ const assert = require('assert');
3
+ const { trace, SYM_IS_APP } = require('../tracer/runtime');
4
+
5
+ function wrapFunction(name, fn, opts = {}) {
6
+ const { bodyTraced = false } = opts;
7
+ const wrapped = function wrappedFn(...args) {
8
+ trace.enter(name, { file: 'app', line: 1 });
9
+ let result;
10
+ let err = null;
11
+ let threw = false;
12
+ try {
13
+ result = fn.apply(this, args);
14
+ return result;
15
+ } catch (e) {
16
+ threw = true;
17
+ err = e;
18
+ throw e;
19
+ } finally {
20
+ trace.exit({ fn: name, file: 'app', line: 1 }, { returnValue: result, error: err, threw });
21
+ }
22
+ };
23
+ try {
24
+ wrapped[SYM_IS_APP] = true;
25
+ wrapped.__repro_instrumented = true;
26
+ if (bodyTraced) wrapped.__repro_body_traced = true;
27
+ } catch {}
28
+ return wrapped;
29
+ }
30
+
31
+ async function testUnawaitedOrdering() {
32
+ const events = [];
33
+ const unsubscribe = trace.on(ev => { if (ev && ev.traceId) events.push(ev); });
34
+
35
+ // Mock inner async functions.
36
+ const findNotificationModule = wrapFunction('findNotificationModule', async () => 'config');
37
+ const loadStudyModuleConfig = wrapFunction('loadStudyModuleConfig', async () => 'studyConfig');
38
+ const loadStudyConfigUserModule = wrapFunction('loadStudyConfigUserModule', async () => 'userConfig');
39
+
40
+ const notifyAboutShipmentDispatch = wrapFunction('notifyAboutShipmentDispatch', async () => {
41
+ await findNotificationModule();
42
+ await loadStudyModuleConfig();
43
+ await loadStudyConfigUserModule();
44
+ return 'done';
45
+ }, { bodyTraced: true });
46
+
47
+ await trace.withTrace('t1', async () => {
48
+ const p = global.__repro_call(
49
+ notifyAboutShipmentDispatch,
50
+ null,
51
+ [],
52
+ 'app',
53
+ 1,
54
+ 'notifyAboutShipmentDispatch',
55
+ true // mark unawaited
56
+ );
57
+ // Simulate unawaited usage, but wait so events flush.
58
+ await p.catch(() => {});
59
+ });
60
+
61
+ unsubscribe();
62
+
63
+ const ordered = events.map(ev => `${ev.type}:${ev.fn}`);
64
+ const expected = [
65
+ 'enter:notifyAboutShipmentDispatch',
66
+ 'enter:findNotificationModule',
67
+ 'exit:findNotificationModule',
68
+ 'enter:loadStudyModuleConfig',
69
+ 'exit:loadStudyModuleConfig',
70
+ 'enter:loadStudyConfigUserModule',
71
+ 'exit:loadStudyConfigUserModule',
72
+ 'exit:notifyAboutShipmentDispatch',
73
+ ];
74
+
75
+ assert.deepStrictEqual(ordered, expected, `Trace order mismatch.\nGot: ${ordered.join(' | ')}\nExpected: ${expected.join(' | ')}`);
76
+ console.log('unawaited async trace order OK');
77
+ }
78
+
79
+ async function testUnawaitedSiblingParenting() {
80
+ const events = [];
81
+ const off = trace.on(ev => { if (ev && ev.traceId === 't2') events.push(ev); });
82
+
83
+ const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
84
+
85
+ const unawaitedChild = wrapFunction('unawaitedChild', async () => {
86
+ await wait(5);
87
+ return 'child';
88
+ });
89
+ const siblingWork = wrapFunction('siblingWork', async () => {
90
+ await wait(1);
91
+ return 'sibling';
92
+ });
93
+ const parent = wrapFunction('parent', async () => {
94
+ const childPromise = global.__repro_call(
95
+ unawaitedChild,
96
+ null,
97
+ [],
98
+ 'app',
99
+ 21,
100
+ 'unawaitedChild',
101
+ true
102
+ );
103
+ await global.__repro_call(
104
+ siblingWork,
105
+ null,
106
+ [],
107
+ 'app',
108
+ 22,
109
+ 'siblingWork',
110
+ false
111
+ );
112
+ return { childPromise };
113
+ });
114
+
115
+ const { childPromise } = await trace.withTrace('t2', async () => parent());
116
+ await childPromise.catch(() => {});
117
+ await wait(5);
118
+ off();
119
+
120
+ const enterByFn = {};
121
+ const exitByFn = {};
122
+ events.forEach(ev => {
123
+ if (!ev || !ev.fn) return;
124
+ if (ev.type === 'enter' && !enterByFn[ev.fn]) enterByFn[ev.fn] = ev;
125
+ if (ev.type === 'exit' && !exitByFn[ev.fn]) exitByFn[ev.fn] = ev;
126
+ });
127
+
128
+ const parentEnter = enterByFn.parent;
129
+ const childEnter = enterByFn.unawaitedChild;
130
+ const siblingEnter = enterByFn.siblingWork;
131
+ assert(parentEnter && childEnter && siblingEnter, 'Missing enter events for parent/child/sibling');
132
+
133
+ assert.strictEqual(
134
+ childEnter.parentSpanId,
135
+ parentEnter.spanId,
136
+ 'Unawaited child should be parented to the caller span'
137
+ );
138
+ assert.strictEqual(
139
+ siblingEnter.parentSpanId,
140
+ parentEnter.spanId,
141
+ 'Sibling after unawaited call should not inherit unawaited child as parent'
142
+ );
143
+
144
+ const parentExit = exitByFn.parent;
145
+ assert(parentExit, 'Missing exit event for parent');
146
+ assert.strictEqual(
147
+ parentExit.spanId,
148
+ parentEnter.spanId,
149
+ 'Parent exit should retain its original span id'
150
+ );
151
+
152
+ console.log('unawaited sibling parenting OK');
153
+ }
154
+
155
+ async function main() {
156
+ await testUnawaitedOrdering();
157
+ await testUnawaitedSiblingParenting();
158
+ }
159
+
160
+ main().catch(err => {
161
+ console.error(err);
162
+ process.exitCode = 1;
163
+ });
@@ -0,0 +1,80 @@
1
+ const assert = require('assert');
2
+ const vm = require('vm');
3
+ const babel = require('@babel/core');
4
+ const makeWrapPlugin = require('../tracer/wrap-plugin');
5
+ const { trace } = require('../tracer/runtime');
6
+
7
+ function loadInstrumentedModule(source, filename) {
8
+ const transformed = babel.transformSync(source, {
9
+ filename,
10
+ plugins: [makeWrapPlugin(filename, {})],
11
+ parserOpts: { sourceType: 'script', plugins: ['typescript'] },
12
+ generatorOpts: { retainLines: true },
13
+ });
14
+
15
+ const module = { exports: {} };
16
+ const context = {
17
+ module,
18
+ exports: module.exports,
19
+ require,
20
+ console,
21
+ __trace: global.__trace,
22
+ __repro_call: global.__repro_call,
23
+ Promise,
24
+ Buffer,
25
+ setTimeout,
26
+ clearTimeout,
27
+ setImmediate,
28
+ clearImmediate,
29
+ };
30
+
31
+ vm.runInNewContext(transformed.code, context, { filename });
32
+ return module.exports;
33
+ }
34
+
35
+ function toPlainJson(value) {
36
+ return JSON.parse(JSON.stringify(value));
37
+ }
38
+
39
+ async function main() {
40
+ const filename = '/tmp/wrap-plugin-arrow-args.fixture.js';
41
+ const mod = loadInstrumentedModule(`
42
+ module.exports = {
43
+ consumerConfig: {
44
+ eachMessage: async ({ message }) => {
45
+ return message.value;
46
+ },
47
+ },
48
+ };
49
+ `, filename);
50
+
51
+ const events = [];
52
+ const off = trace.on(ev => {
53
+ if (ev && ev.traceId === 'arrow-args' && ev.type === 'enter' && ev.functionType === 'arrow') {
54
+ events.push(ev);
55
+ }
56
+ });
57
+
58
+ const payload = {
59
+ topic: 'payments.events',
60
+ partition: 1,
61
+ message: { value: 'ok' },
62
+ };
63
+
64
+ await trace.withTrace('arrow-args', async () => {
65
+ const value = await mod.consumerConfig.eachMessage(payload);
66
+ assert.strictEqual(value, 'ok');
67
+ });
68
+
69
+ off();
70
+
71
+ assert.strictEqual(events.length, 1, `expected one arrow enter event, got ${events.length}`);
72
+ assert.deepStrictEqual(toPlainJson(events[0].args), [payload]);
73
+
74
+ console.log('wrap-plugin arrow destructured args OK');
75
+ }
76
+
77
+ main().catch(err => {
78
+ console.error(err);
79
+ process.exitCode = 1;
80
+ });
@@ -67,7 +67,6 @@ function tagExports(value, filename, seen = new WeakSet(), depth = 0, instrument
67
67
 
68
68
  if (typeof d.get === 'function') {
69
69
  tagExports(d.get, filename, seen, depth + 1, instrumented);
70
- try { tagExports(value[k], filename, seen, depth + 1, instrumented); } catch {}
71
70
  }
72
71
  if (typeof d.set === 'function') {
73
72
  tagExports(d.set, filename, seen, depth + 1, instrumented);
@@ -155,6 +155,97 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
155
155
  return argIndex === call.arguments.length - 1;
156
156
  }
157
157
 
158
+ function normalizeArrowParam(param, path) {
159
+ if (t.isIdentifier(param)) {
160
+ return {
161
+ param: t.cloneNode(param, true),
162
+ argExpr: t.cloneNode(param, true),
163
+ prelude: null,
164
+ };
165
+ }
166
+
167
+ if (t.isRestElement(param)) {
168
+ if (t.isIdentifier(param.argument)) {
169
+ return {
170
+ param: t.cloneNode(param, true),
171
+ argExpr: t.cloneNode(param.argument, true),
172
+ prelude: null,
173
+ };
174
+ }
175
+
176
+ const tempId = path.scope.generateUidIdentifier('arg');
177
+ return {
178
+ param: t.restElement(t.cloneNode(tempId, true)),
179
+ argExpr: t.cloneNode(tempId, true),
180
+ prelude: t.variableDeclaration('const', [
181
+ t.variableDeclarator(
182
+ t.cloneNode(param.argument, true),
183
+ t.cloneNode(tempId, true)
184
+ )
185
+ ]),
186
+ };
187
+ }
188
+
189
+ if (t.isAssignmentPattern(param)) {
190
+ if (t.isIdentifier(param.left)) {
191
+ return {
192
+ param: t.cloneNode(param, true),
193
+ argExpr: t.cloneNode(param.left, true),
194
+ prelude: null,
195
+ };
196
+ }
197
+
198
+ const tempId = path.scope.generateUidIdentifier('arg');
199
+ return {
200
+ param: t.assignmentPattern(
201
+ t.cloneNode(tempId, true),
202
+ t.cloneNode(param.right, true)
203
+ ),
204
+ argExpr: t.cloneNode(tempId, true),
205
+ prelude: t.variableDeclaration('const', [
206
+ t.variableDeclarator(
207
+ t.cloneNode(param.left, true),
208
+ t.cloneNode(tempId, true)
209
+ )
210
+ ]),
211
+ };
212
+ }
213
+
214
+ if (t.isObjectPattern(param) || t.isArrayPattern(param)) {
215
+ const tempId = path.scope.generateUidIdentifier('arg');
216
+ return {
217
+ param: t.cloneNode(tempId, true),
218
+ argExpr: t.cloneNode(tempId, true),
219
+ prelude: t.variableDeclaration('const', [
220
+ t.variableDeclarator(
221
+ t.cloneNode(param, true),
222
+ t.cloneNode(tempId, true)
223
+ )
224
+ ]),
225
+ };
226
+ }
227
+
228
+ return {
229
+ param: t.cloneNode(param, true),
230
+ argExpr: t.identifier('undefined'),
231
+ prelude: null,
232
+ };
233
+ }
234
+
235
+ function normalizeArrowParams(path) {
236
+ if (!path.isArrowFunctionExpression()) {
237
+ return { argExprs: [], preludes: [] };
238
+ }
239
+
240
+ const normalized = path.node.params.map(param => normalizeArrowParam(param, path));
241
+ path.node.params = normalized.map(item => item.param);
242
+
243
+ return {
244
+ argExprs: normalized.map(item => item.argExpr),
245
+ preludes: normalized.map(item => item.prelude).filter(Boolean),
246
+ };
247
+ }
248
+
158
249
  function wrap(path){
159
250
  const n = path.node;
160
251
  if (n.__repro_internal) return;
@@ -190,6 +281,10 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
190
281
  const bodyPath = path.get('body');
191
282
  if (!bodyPath.isBlockStatement()) return;
192
283
  const body = bodyPath.node;
284
+ const arrowParams = normalizeArrowParams(path);
285
+ if (arrowParams.preludes.length) {
286
+ body.body.unshift(...arrowParams.preludes);
287
+ }
193
288
 
194
289
  const argsId = path.scope.generateUidIdentifier('args');
195
290
  const resultId = path.scope.generateUidIdentifier('result');
@@ -209,16 +304,7 @@ module.exports = function makeWrapPlugin(filenameForMeta, opts = {}) {
209
304
  argsArray
210
305
  )
211
306
  : t.arrayExpression(
212
- n.params.map(param => {
213
- if (t.isIdentifier(param)) return t.cloneNode(param, true);
214
- if (t.isRestElement(param) && t.isIdentifier(param.argument)) {
215
- return t.cloneNode(param.argument, true);
216
- }
217
- if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
218
- return t.cloneNode(param.left, true);
219
- }
220
- return t.identifier('undefined');
221
- })
307
+ arrowParams.argExprs.map(argExpr => t.cloneNode(argExpr, true))
222
308
  );
223
309
 
224
310
  const argsDecl = t.variableDeclaration('const', [
@@ -1,44 +0,0 @@
1
- type RedactionStrategy = 'mask' | 'remove';
2
- export type RedactionRuleInput = string | RegExp | {
3
- match?: string | RegExp;
4
- field?: string | RegExp;
5
- exact?: boolean;
6
- strategy?: RedactionStrategy;
7
- maskWith?: string;
8
- };
9
- type RedactionFieldMapKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'response' | 'responseBody' | 'responseHeaders';
10
- export type TransportRedactionFields = Partial<Record<RedactionFieldMapKey, RedactionRuleInput[]>>;
11
- export interface TransportRedactionOptions {
12
- /**
13
- * Custom mask string for masked values. Default: "*****".
14
- */
15
- maskWith?: string;
16
- /**
17
- * Additional rules that apply to specific request/response sections.
18
- * Provide field names (case-insensitive), dot-delimited paths, or
19
- * regular expressions. Rules declared under `any` apply everywhere.
20
- */
21
- fields?: TransportRedactionFields;
22
- }
23
- type SectionKey = 'any' | 'headers' | 'body' | 'query' | 'params' | 'respBody' | 'responseHeaders';
24
- type NormalizedRule = {
25
- kind: 'string';
26
- needle: string;
27
- exact: boolean;
28
- action: RedactionStrategy;
29
- maskWith?: string;
30
- } | {
31
- kind: 'regex';
32
- regex: RegExp;
33
- action: RedactionStrategy;
34
- maskWith?: string;
35
- };
36
- export type TransportRedactionState = {
37
- maskWith: string;
38
- rules: Record<SectionKey, NormalizedRule[]>;
39
- };
40
- export declare function configureTransportRedaction(options?: TransportRedactionOptions | null): TransportRedactionState;
41
- export declare function getTransportRedactionState(): TransportRedactionState;
42
- export declare function redactPayload<T>(payload: T, stateOverride?: TransportRedactionState | null): T;
43
- export declare function redactSection<T>(value: T, section: Exclude<SectionKey, 'any'>, stateOverride?: TransportRedactionState | null): T;
44
- export {};
package/dist/redaction.js DELETED
@@ -1,167 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.redactSection = exports.redactPayload = exports.getTransportRedactionState = exports.configureTransportRedaction = void 0;
4
- const SECTION_KEYS = ['any', 'headers', 'body', 'query', 'params', 'respBody', 'responseHeaders'];
5
- const DEFAULT_MASK = '*****';
6
- const DEFAULT_RULES = [
7
- { match: /pass(word|phrase)?/i },
8
- { match: /pwd/i },
9
- { match: /secret/i },
10
- { match: /token/i },
11
- { match: /authorization/i },
12
- { match: /api[_-]?key/i },
13
- ];
14
- let CURRENT_STATE = buildState();
15
- function configureTransportRedaction(options) {
16
- CURRENT_STATE = buildState(options);
17
- return CURRENT_STATE;
18
- }
19
- exports.configureTransportRedaction = configureTransportRedaction;
20
- function getTransportRedactionState() {
21
- return CURRENT_STATE;
22
- }
23
- exports.getTransportRedactionState = getTransportRedactionState;
24
- function redactPayload(payload, stateOverride) {
25
- const state = stateOverride ?? CURRENT_STATE;
26
- applyRules(payload, gatherRules('any', state), state.maskWith);
27
- return payload;
28
- }
29
- exports.redactPayload = redactPayload;
30
- function redactSection(value, section, stateOverride) {
31
- const state = stateOverride ?? CURRENT_STATE;
32
- applyRules(value, gatherRules(section, state), state.maskWith);
33
- return value;
34
- }
35
- exports.redactSection = redactSection;
36
- function buildState(options) {
37
- const state = {
38
- maskWith: typeof options?.maskWith === 'string' && options.maskWith ? options.maskWith : DEFAULT_MASK,
39
- rules: SECTION_KEYS.reduce((acc, key) => {
40
- acc[key] = [];
41
- return acc;
42
- }, {}),
43
- };
44
- appendRules(state.rules.any, DEFAULT_RULES);
45
- if (!options?.fields) {
46
- return state;
47
- }
48
- appendRules(state.rules.any, options.fields.any);
49
- appendRules(state.rules.headers, options.fields.headers);
50
- appendRules(state.rules.body, options.fields.body);
51
- appendRules(state.rules.query, options.fields.query);
52
- appendRules(state.rules.params, options.fields.params);
53
- appendRules(state.rules.respBody, options.fields.respBody);
54
- appendRules(state.rules.respBody, options.fields.response);
55
- appendRules(state.rules.respBody, options.fields.responseBody);
56
- appendRules(state.rules.responseHeaders, options.fields.responseHeaders);
57
- return state;
58
- }
59
- function appendRules(bucket, inputs) {
60
- if (!inputs || !Array.isArray(inputs))
61
- return;
62
- for (const entry of inputs) {
63
- const normalized = normalizeRule(entry);
64
- if (normalized)
65
- bucket.push(normalized);
66
- }
67
- }
68
- function normalizeRule(input) {
69
- if (!input)
70
- return null;
71
- if (typeof input === 'string') {
72
- const needle = input.trim().toLowerCase();
73
- if (!needle)
74
- return null;
75
- return { kind: 'string', needle, exact: false, action: 'mask' };
76
- }
77
- if (input instanceof RegExp) {
78
- return { kind: 'regex', regex: cloneRegex(input), action: 'mask' };
79
- }
80
- if (typeof input === 'object') {
81
- const pattern = input.match ?? input.field;
82
- const exact = !!input.exact;
83
- const action = input.strategy === 'remove' ? 'remove' : 'mask';
84
- const maskWith = typeof input.maskWith === 'string' && input.maskWith ? input.maskWith : undefined;
85
- if (pattern instanceof RegExp) {
86
- return { kind: 'regex', regex: cloneRegex(pattern), action, maskWith };
87
- }
88
- if (typeof pattern === 'string') {
89
- const needle = pattern.trim().toLowerCase();
90
- if (!needle)
91
- return null;
92
- return { kind: 'string', needle, exact, action, maskWith };
93
- }
94
- }
95
- return null;
96
- }
97
- function cloneRegex(rx) {
98
- const flags = rx.flags.includes('g') ? rx.flags.replace(/g/g, '') : rx.flags;
99
- return new RegExp(rx.source, flags);
100
- }
101
- function gatherRules(section, state) {
102
- if (section === 'any')
103
- return state.rules.any;
104
- return state.rules.any.concat(state.rules[section] ?? []);
105
- }
106
- function applyRules(value, rules, defaultMask) {
107
- if (!rules.length)
108
- return;
109
- if (Array.isArray(value)) {
110
- for (let i = 0; i < value.length; i++) {
111
- const item = value[i];
112
- if (item && typeof item === 'object') {
113
- applyRules(item, rules, defaultMask);
114
- }
115
- }
116
- return;
117
- }
118
- if (!isRedactableObject(value))
119
- return;
120
- for (const key of Object.keys(value)) {
121
- const decision = pickDecision(key, rules, defaultMask);
122
- if (!decision) {
123
- const child = value[key];
124
- if (child && typeof child === 'object') {
125
- applyRules(child, rules, defaultMask);
126
- }
127
- continue;
128
- }
129
- if (decision === 'remove') {
130
- delete value[key];
131
- }
132
- else {
133
- value[key] = decision;
134
- }
135
- }
136
- }
137
- function isRedactableObject(value) {
138
- if (!value || typeof value !== 'object')
139
- return false;
140
- if (Buffer.isBuffer(value))
141
- return false;
142
- if (ArrayBuffer.isView(value))
143
- return false;
144
- if (value instanceof Date || value instanceof RegExp)
145
- return false;
146
- if (value instanceof Map || value instanceof Set)
147
- return false;
148
- return true;
149
- }
150
- function pickDecision(key, rules, defaultMask) {
151
- const lower = key.toLowerCase();
152
- for (const rule of rules) {
153
- if (rule.kind === 'string') {
154
- const match = rule.exact ? lower === rule.needle : lower.includes(rule.needle);
155
- if (!match)
156
- continue;
157
- }
158
- else {
159
- if (!rule.regex.test(key))
160
- continue;
161
- }
162
- if (rule.action === 'remove')
163
- return 'remove';
164
- return rule.maskWith ?? defaultMask;
165
- }
166
- return null;
167
- }
package/dist/server.js DELETED
@@ -1,26 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const index_1 = require("./index");
4
- const express = require('express');
5
- const mongoose = require('mongoose');
6
- (0, index_1.initReproTracing)();
7
- const app = express();
8
- (async () => {
9
- await mongoose.connect('mongodb://127.0.0.1:27018', { dbName: 'trace_demo' });
10
- const userSchema = new mongoose.Schema({
11
- name: String, email: String, age: Number
12
- }, { timestamps: true });
13
- const User = mongoose.model('User', userSchema);
14
- // seed some data
15
- // @ts-ignore
16
- app.get('/seed', async (_req, res) => {
17
- await User.deleteMany({});
18
- await User.insertMany([
19
- { name: 'Ada', email: 'ada@example.com', age: 27 },
20
- { name: 'Lin', email: 'lin@example.com', age: 35 },
21
- { name: 'Sam', email: 'sam@example.com', age: 17 },
22
- ]);
23
- res.json({ ok: true, count: await User.countDocuments() });
24
- });
25
- app.listen(3000, () => process.stdout.write('listening on http://localhost:3000\n'));
26
- })();
File without changes