@heroku/js-blanket 0.0.0 → 0.0.1
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 +4 -1
- package/dist/.tsbuildinfo +1 -0
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/adapters/logging/generic.js +23 -0
- package/dist/cjs/adapters/logging/generic.js.map +1 -0
- package/dist/cjs/adapters/logging/generic.test.js +432 -0
- package/dist/cjs/adapters/logging/generic.test.js.map +1 -0
- package/dist/cjs/core/patterns.js +17 -0
- package/dist/cjs/core/patterns.js.map +1 -0
- package/dist/cjs/core/presets.js +116 -0
- package/dist/cjs/core/presets.js.map +1 -0
- package/dist/cjs/core/scrubber.js +260 -0
- package/dist/cjs/core/scrubber.js.map +1 -0
- package/dist/cjs/core/scrubber.test.js +392 -0
- package/dist/cjs/core/scrubber.test.js.map +1 -0
- package/dist/cjs/core/types.js +3 -0
- package/dist/cjs/core/types.js.map +1 -0
- package/dist/cjs/core/types.test.js +326 -0
- package/dist/cjs/core/types.test.js.map +1 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/index.test.js +31 -0
- package/dist/cjs/index.test.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/{src/adapters/logging/generic.ts → dist/esm/adapters/logging/generic.d.ts} +1 -4
- package/dist/esm/adapters/logging/generic.js +20 -0
- package/dist/esm/adapters/logging/generic.js.map +1 -0
- package/dist/esm/adapters/logging/generic.test.d.ts +7 -0
- package/dist/esm/adapters/logging/generic.test.js +430 -0
- package/dist/esm/adapters/logging/generic.test.js.map +1 -0
- package/dist/esm/core/patterns.d.ts +4 -0
- package/dist/esm/core/patterns.js +14 -0
- package/dist/esm/core/patterns.js.map +1 -0
- package/dist/esm/core/presets.d.ts +64 -0
- package/{src/core/presets.ts → dist/esm/core/presets.js} +46 -55
- package/dist/esm/core/presets.js.map +1 -0
- package/dist/esm/core/scrubber.d.ts +131 -0
- package/dist/esm/core/scrubber.js +256 -0
- package/dist/esm/core/scrubber.js.map +1 -0
- package/dist/esm/core/scrubber.test.d.ts +1 -0
- package/dist/esm/core/scrubber.test.js +390 -0
- package/dist/esm/core/scrubber.test.js.map +1 -0
- package/dist/esm/core/types.d.ts +169 -0
- package/dist/esm/core/types.js +2 -0
- package/dist/esm/core/types.js.map +1 -0
- package/dist/esm/core/types.test.d.ts +9 -0
- package/dist/esm/core/types.test.js +324 -0
- package/dist/esm/core/types.test.js.map +1 -0
- package/{src/index.ts → dist/esm/index.d.ts} +0 -3
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.test.d.ts +1 -0
- package/dist/esm/index.test.js +29 -0
- package/dist/esm/index.test.js.map +1 -0
- package/package.json +45 -47
- package/.c8rc.json +0 -11
- package/.editorconfig +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -41
- package/.github/copilot-instructions.md +0 -117
- package/.github/workflows/ci.yml +0 -25
- package/.husky/pre-commit +0 -1
- package/.lintstagedrc.json +0 -4
- package/.tool-versions +0 -1
- package/CODEOWNERS +0 -8
- package/CODE_OF_CONDUCT.md +0 -111
- package/CONTRIBUTING.md +0 -123
- package/SECURITY.md +0 -8
- package/docs/examples/logging-integration.md +0 -736
- package/eslint.config.mjs +0 -108
- package/prettier.config.mjs +0 -10
- package/scripts/test-setup.mjs +0 -24
- package/src/adapters/logging/generic.test.ts +0 -531
- package/src/core/patterns.ts +0 -22
- package/src/core/scrubber.test.ts +0 -465
- package/src/core/scrubber.ts +0 -284
- package/src/core/types.test.ts +0 -516
- package/src/core/types.ts +0 -176
- package/src/index.test.ts +0 -41
- package/tsconfig.cjs.json +0 -12
- package/tsconfig.esm.json +0 -12
- package/tsconfig.json +0 -32
- package/tsconfig.test.json +0 -9
package/src/core/types.test.ts
DELETED
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type Safety Tests for Core Scrubber
|
|
3
|
-
*
|
|
4
|
-
* These tests validate that TypeScript types are preserved correctly through scrubbing operations.
|
|
5
|
-
* They use compile-time type assertions to ensure type safety without runtime overhead.
|
|
6
|
-
*
|
|
7
|
-
* Run with: pnpm test (type-checked during pretest)
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { expect } from 'chai';
|
|
11
|
-
import { Scrubber } from './scrubber.js';
|
|
12
|
-
import type { ScrubConfig, ScrubResult } from './types.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Compile-time type assertion helper
|
|
16
|
-
* If T is not assignable to Expected, TypeScript will error at compile time
|
|
17
|
-
*/
|
|
18
|
-
type AssertType<T, Expected> = T extends Expected
|
|
19
|
-
? Expected extends T
|
|
20
|
-
? true
|
|
21
|
-
: never
|
|
22
|
-
: never;
|
|
23
|
-
|
|
24
|
-
describe('Type Safety', () => {
|
|
25
|
-
describe('Generic Type Preservation', () => {
|
|
26
|
-
it('preserves simple object types', () => {
|
|
27
|
-
interface User {
|
|
28
|
-
name: string;
|
|
29
|
-
email: string;
|
|
30
|
-
password: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
34
|
-
const user: User = {
|
|
35
|
-
name: 'John',
|
|
36
|
-
email: 'john@example.com',
|
|
37
|
-
password: 'secret',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const result = scrubber.scrub(user);
|
|
41
|
-
|
|
42
|
-
// Compile-time assertion: result.data should be User type
|
|
43
|
-
const typeCheck: AssertType<typeof result.data, User> = true;
|
|
44
|
-
expect(typeCheck).to.be.true;
|
|
45
|
-
|
|
46
|
-
// Runtime validation
|
|
47
|
-
expect(result.data).to.have.property('name');
|
|
48
|
-
expect(result.data).to.have.property('email');
|
|
49
|
-
expect(result.data).to.have.property('password');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('preserves nested object types', () => {
|
|
53
|
-
interface Address {
|
|
54
|
-
street: string;
|
|
55
|
-
city: string;
|
|
56
|
-
zip: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface UserProfile {
|
|
60
|
-
user: {
|
|
61
|
-
name: string;
|
|
62
|
-
email: string;
|
|
63
|
-
};
|
|
64
|
-
address: Address;
|
|
65
|
-
metadata: {
|
|
66
|
-
lastLogin: string;
|
|
67
|
-
loginCount: number;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const scrubber = new Scrubber({ fields: ['email'] });
|
|
72
|
-
const profile: UserProfile = {
|
|
73
|
-
user: { name: 'John', email: 'john@example.com' },
|
|
74
|
-
address: { street: '123 Main St', city: 'Springfield', zip: '12345' },
|
|
75
|
-
metadata: { lastLogin: '2024-01-01', loginCount: 42 },
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const _result = scrubber.scrub(profile);
|
|
79
|
-
|
|
80
|
-
// Compile-time assertion
|
|
81
|
-
const typeCheck: AssertType<typeof _result.data, UserProfile> = true;
|
|
82
|
-
expect(typeCheck).to.be.true;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('preserves array types', () => {
|
|
86
|
-
interface Item {
|
|
87
|
-
id: number;
|
|
88
|
-
name: string;
|
|
89
|
-
secret: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const scrubber = new Scrubber({ fields: ['secret'] });
|
|
93
|
-
const items: Item[] = [
|
|
94
|
-
{ id: 1, name: 'Item 1', secret: 'secret1' },
|
|
95
|
-
{ id: 2, name: 'Item 2', secret: 'secret2' },
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
const result = scrubber.scrub(items);
|
|
99
|
-
|
|
100
|
-
// Compile-time assertion
|
|
101
|
-
const typeCheck: AssertType<typeof result.data, Item[]> = true;
|
|
102
|
-
expect(typeCheck).to.be.true;
|
|
103
|
-
|
|
104
|
-
// Runtime validation
|
|
105
|
-
expect(result.data).to.be.an('array');
|
|
106
|
-
expect(result.data).to.have.lengthOf(2);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('preserves union types', () => {
|
|
110
|
-
type ComplexUnion =
|
|
111
|
-
| { type: 'user'; name: string; email: string }
|
|
112
|
-
| { type: 'admin'; name: string; permissions: string[] };
|
|
113
|
-
|
|
114
|
-
const scrubber = new Scrubber({ fields: ['email'] });
|
|
115
|
-
const data: ComplexUnion = {
|
|
116
|
-
type: 'user',
|
|
117
|
-
name: 'John',
|
|
118
|
-
email: 'john@example.com',
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const _result = scrubber.scrub(data);
|
|
122
|
-
|
|
123
|
-
// Compile-time assertion
|
|
124
|
-
const typeCheck: AssertType<typeof _result.data, ComplexUnion> = true;
|
|
125
|
-
expect(typeCheck).to.be.true;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('preserves readonly types', () => {
|
|
129
|
-
interface ReadonlyUser {
|
|
130
|
-
readonly id: number;
|
|
131
|
-
readonly name: string;
|
|
132
|
-
password: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
136
|
-
const user: ReadonlyUser = {
|
|
137
|
-
id: 1,
|
|
138
|
-
name: 'John',
|
|
139
|
-
password: 'secret',
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const result = scrubber.scrub(user);
|
|
143
|
-
|
|
144
|
-
// Compile-time assertion
|
|
145
|
-
const typeCheck: AssertType<typeof result.data, ReadonlyUser> = true;
|
|
146
|
-
expect(typeCheck).to.be.true;
|
|
147
|
-
|
|
148
|
-
// Immutability: original should not be modified
|
|
149
|
-
expect(user.password).to.equal('secret');
|
|
150
|
-
expect(result.data.password).to.equal('[SCRUBBED]');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('preserves optional property types', () => {
|
|
154
|
-
interface PartialUser {
|
|
155
|
-
name: string;
|
|
156
|
-
email?: string;
|
|
157
|
-
phone?: string;
|
|
158
|
-
password: string;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
162
|
-
const user: PartialUser = {
|
|
163
|
-
name: 'John',
|
|
164
|
-
email: 'john@example.com',
|
|
165
|
-
// phone is omitted
|
|
166
|
-
password: 'secret',
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const _result = scrubber.scrub(user);
|
|
170
|
-
|
|
171
|
-
// Compile-time assertion
|
|
172
|
-
const typeCheck: AssertType<typeof _result.data, PartialUser> = true;
|
|
173
|
-
expect(typeCheck).to.be.true;
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe('ScrubResult Type', () => {
|
|
178
|
-
it('has correct result structure', () => {
|
|
179
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
180
|
-
const data = { user: 'john', password: 'secret' };
|
|
181
|
-
const result = scrubber.scrub(data);
|
|
182
|
-
|
|
183
|
-
// Type assertion: result should be ScrubResult
|
|
184
|
-
const typeCheck: AssertType<
|
|
185
|
-
typeof result,
|
|
186
|
-
ScrubResult<typeof data>
|
|
187
|
-
> = true;
|
|
188
|
-
expect(typeCheck).to.be.true;
|
|
189
|
-
|
|
190
|
-
// Runtime validation
|
|
191
|
-
expect(result).to.have.property('data');
|
|
192
|
-
expect(result).to.have.property('scrubbed');
|
|
193
|
-
expect(result).to.have.property('scrubbedPaths');
|
|
194
|
-
expect(result.scrubbed).to.be.a('boolean');
|
|
195
|
-
expect(result.scrubbedPaths).to.be.an('array');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('preserves input type in result.data', () => {
|
|
199
|
-
interface Input {
|
|
200
|
-
a: string;
|
|
201
|
-
b: number;
|
|
202
|
-
c: boolean;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const scrubber = new Scrubber({ fields: ['a'] });
|
|
206
|
-
const input: Input = { a: 'test', b: 42, c: true };
|
|
207
|
-
const _result: ScrubResult<Input> = scrubber.scrub(input);
|
|
208
|
-
|
|
209
|
-
// Compile-time assertion
|
|
210
|
-
const typeCheck: AssertType<typeof _result.data, Input> = true;
|
|
211
|
-
expect(typeCheck).to.be.true;
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('ScrubConfig Type', () => {
|
|
216
|
-
it('accepts valid configurations', () => {
|
|
217
|
-
const config1: ScrubConfig = {
|
|
218
|
-
fields: ['password', 'apiToken'],
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const config2: ScrubConfig = {
|
|
222
|
-
fields: ['password', /api[-_]?key/i],
|
|
223
|
-
paths: ['user.email'],
|
|
224
|
-
patterns: [/\d{3}-\d{2}-\d{4}/g],
|
|
225
|
-
replacement: '[REDACTED]',
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
const config3: ScrubConfig = {
|
|
229
|
-
fields: [],
|
|
230
|
-
paths: [],
|
|
231
|
-
patterns: [],
|
|
232
|
-
recursive: false,
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// All should be valid ScrubConfig types
|
|
236
|
-
expect(config1).to.be.an('object');
|
|
237
|
-
expect(config2).to.be.an('object');
|
|
238
|
-
expect(config3).to.be.an('object');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('allows partial configurations', () => {
|
|
242
|
-
const partial1: ScrubConfig = {};
|
|
243
|
-
const partial2: ScrubConfig = { fields: ['password'] };
|
|
244
|
-
const partial3: ScrubConfig = { replacement: '[X]' };
|
|
245
|
-
|
|
246
|
-
expect(partial1).to.be.an('object');
|
|
247
|
-
expect(partial2).to.be.an('object');
|
|
248
|
-
expect(partial3).to.be.an('object');
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe('Complex Type Scenarios', () => {
|
|
253
|
-
it('handles deeply nested generic types', () => {
|
|
254
|
-
interface DeepNested<T> {
|
|
255
|
-
level1: {
|
|
256
|
-
level2: {
|
|
257
|
-
level3: {
|
|
258
|
-
level4: {
|
|
259
|
-
value: T;
|
|
260
|
-
secret: string;
|
|
261
|
-
};
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const scrubber = new Scrubber({ fields: ['secret'] });
|
|
268
|
-
const data: DeepNested<number> = {
|
|
269
|
-
level1: {
|
|
270
|
-
level2: {
|
|
271
|
-
level3: {
|
|
272
|
-
level4: {
|
|
273
|
-
value: 42,
|
|
274
|
-
secret: 'hidden',
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const _result = scrubber.scrub(data);
|
|
282
|
-
|
|
283
|
-
// Compile-time assertion
|
|
284
|
-
const typeCheck: AssertType<
|
|
285
|
-
typeof _result.data,
|
|
286
|
-
DeepNested<number>
|
|
287
|
-
> = true;
|
|
288
|
-
expect(typeCheck).to.be.true;
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('handles arrays of complex types', () => {
|
|
292
|
-
interface Event {
|
|
293
|
-
id: string;
|
|
294
|
-
timestamp: Date;
|
|
295
|
-
user: {
|
|
296
|
-
id: string;
|
|
297
|
-
email: string;
|
|
298
|
-
};
|
|
299
|
-
metadata: Record<string, unknown>;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const scrubber = new Scrubber({ fields: ['email'] });
|
|
303
|
-
const events: Event[] = [
|
|
304
|
-
{
|
|
305
|
-
id: '1',
|
|
306
|
-
timestamp: new Date(),
|
|
307
|
-
user: { id: 'u1', email: 'user1@example.com' },
|
|
308
|
-
metadata: { key: 'value' },
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
id: '2',
|
|
312
|
-
timestamp: new Date(),
|
|
313
|
-
user: { id: 'u2', email: 'user2@example.com' },
|
|
314
|
-
metadata: { key: 'value' },
|
|
315
|
-
},
|
|
316
|
-
];
|
|
317
|
-
|
|
318
|
-
const _result = scrubber.scrub(events);
|
|
319
|
-
|
|
320
|
-
// Compile-time assertion
|
|
321
|
-
const typeCheck: AssertType<typeof _result.data, Event[]> = true;
|
|
322
|
-
expect(typeCheck).to.be.true;
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('handles Record types', () => {
|
|
326
|
-
type UserMap = Record<string, { name: string; password: string }>;
|
|
327
|
-
|
|
328
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
329
|
-
const users: UserMap = {
|
|
330
|
-
user1: { name: 'John', password: 'secret1' },
|
|
331
|
-
user2: { name: 'Jane', password: 'secret2' },
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
const _result = scrubber.scrub(users);
|
|
335
|
-
|
|
336
|
-
// Compile-time assertion
|
|
337
|
-
const typeCheck: AssertType<typeof _result.data, UserMap> = true;
|
|
338
|
-
expect(typeCheck).to.be.true;
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('handles mixed primitive and object types', () => {
|
|
342
|
-
interface MixedData {
|
|
343
|
-
string: string;
|
|
344
|
-
number: number;
|
|
345
|
-
boolean: boolean;
|
|
346
|
-
null: null;
|
|
347
|
-
undefined: undefined;
|
|
348
|
-
date: Date;
|
|
349
|
-
regex: RegExp;
|
|
350
|
-
object: { key: string };
|
|
351
|
-
array: number[];
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const scrubber = new Scrubber({ fields: ['key'] });
|
|
355
|
-
const data: MixedData = {
|
|
356
|
-
string: 'text',
|
|
357
|
-
number: 42,
|
|
358
|
-
boolean: true,
|
|
359
|
-
null: null,
|
|
360
|
-
undefined: undefined,
|
|
361
|
-
date: new Date(),
|
|
362
|
-
regex: /test/,
|
|
363
|
-
object: { key: 'value' },
|
|
364
|
-
array: [1, 2, 3],
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const _result = scrubber.scrub(data);
|
|
368
|
-
|
|
369
|
-
// Compile-time assertion
|
|
370
|
-
const typeCheck: AssertType<typeof _result.data, MixedData> = true;
|
|
371
|
-
expect(typeCheck).to.be.true;
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('handles tuple types', () => {
|
|
375
|
-
type UserTuple = [string, number, boolean, { password: string }];
|
|
376
|
-
|
|
377
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
378
|
-
const tuple: UserTuple = ['John', 30, true, { password: 'secret' }];
|
|
379
|
-
|
|
380
|
-
const result = scrubber.scrub(tuple);
|
|
381
|
-
|
|
382
|
-
// Note: TypeScript treats tuples as arrays at runtime, so the type is preserved
|
|
383
|
-
// but the specific tuple structure is maintained
|
|
384
|
-
expect(result.data).to.be.an('array');
|
|
385
|
-
expect(result.data).to.have.lengthOf(4);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('preserves type safety with unknown types', () => {
|
|
389
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
390
|
-
const data: unknown = { user: 'john', password: 'secret' };
|
|
391
|
-
|
|
392
|
-
const _result = scrubber.scrub(data);
|
|
393
|
-
|
|
394
|
-
// Compile-time assertion: unknown in, unknown out
|
|
395
|
-
const typeCheck: AssertType<typeof _result.data, unknown> = true;
|
|
396
|
-
expect(typeCheck).to.be.true;
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
describe('Type Inference', () => {
|
|
401
|
-
it('infers types from literal objects', () => {
|
|
402
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
403
|
-
|
|
404
|
-
// Type should be inferred from the literal
|
|
405
|
-
const result = scrubber.scrub({
|
|
406
|
-
name: 'John',
|
|
407
|
-
email: 'john@example.com',
|
|
408
|
-
password: 'secret',
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// TypeScript infers the exact shape
|
|
412
|
-
expect(result.data).to.have.property('name');
|
|
413
|
-
expect(result.data).to.have.property('email');
|
|
414
|
-
expect(result.data).to.have.property('password');
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('works with const assertions', () => {
|
|
418
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
419
|
-
|
|
420
|
-
const data = {
|
|
421
|
-
name: 'John',
|
|
422
|
-
role: 'admin',
|
|
423
|
-
password: 'secret',
|
|
424
|
-
} as const;
|
|
425
|
-
|
|
426
|
-
// Type should preserve readonly properties from const assertion
|
|
427
|
-
const result = scrubber.scrub(data);
|
|
428
|
-
|
|
429
|
-
expect(result.data.name).to.equal('John');
|
|
430
|
-
expect(result.data.role).to.equal('admin');
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
describe('Edge Case Types', () => {
|
|
435
|
-
it('handles empty objects', () => {
|
|
436
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
437
|
-
const empty = {};
|
|
438
|
-
|
|
439
|
-
const result = scrubber.scrub(empty);
|
|
440
|
-
|
|
441
|
-
const typeCheck: AssertType<typeof result.data, typeof empty> = true;
|
|
442
|
-
expect(typeCheck).to.be.true;
|
|
443
|
-
expect(result.data).to.deep.equal({});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
it('handles primitives directly', () => {
|
|
447
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
448
|
-
|
|
449
|
-
const string = 'test';
|
|
450
|
-
const number = 42;
|
|
451
|
-
const boolean = true;
|
|
452
|
-
const nullVal = null;
|
|
453
|
-
|
|
454
|
-
expect(scrubber.scrub(string).data).to.equal(string);
|
|
455
|
-
expect(scrubber.scrub(number).data).to.equal(number);
|
|
456
|
-
expect(scrubber.scrub(boolean).data).to.equal(boolean);
|
|
457
|
-
expect(scrubber.scrub(nullVal).data).to.equal(nullVal);
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('handles circular references with type preservation', () => {
|
|
461
|
-
interface Circular {
|
|
462
|
-
name: string;
|
|
463
|
-
password: string;
|
|
464
|
-
self?: Circular;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
468
|
-
const obj: Circular = {
|
|
469
|
-
name: 'test',
|
|
470
|
-
password: 'secret',
|
|
471
|
-
};
|
|
472
|
-
obj.self = obj; // Circular reference
|
|
473
|
-
|
|
474
|
-
const result = scrubber.scrub(obj);
|
|
475
|
-
|
|
476
|
-
// Type is preserved even with circular reference
|
|
477
|
-
const typeCheck: AssertType<typeof result.data, Circular> = true;
|
|
478
|
-
expect(typeCheck).to.be.true;
|
|
479
|
-
expect(result.data.name).to.equal('test');
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
describe('Strict TypeScript Compliance', () => {
|
|
484
|
-
it('respects noUncheckedIndexedAccess', () => {
|
|
485
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
486
|
-
const data: Record<string, string> = {
|
|
487
|
-
user: 'john',
|
|
488
|
-
password: 'secret',
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const result = scrubber.scrub(data);
|
|
492
|
-
|
|
493
|
-
// With noUncheckedIndexedAccess, indexed access returns string | undefined
|
|
494
|
-
const value = result.data['nonexistent'];
|
|
495
|
-
expect(value).to.be.undefined;
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('respects strictNullChecks', () => {
|
|
499
|
-
interface NullableData {
|
|
500
|
-
value: string | null;
|
|
501
|
-
optional?: string;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const scrubber = new Scrubber({ fields: ['value'] });
|
|
505
|
-
const data: NullableData = {
|
|
506
|
-
value: null,
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
const _result = scrubber.scrub(data);
|
|
510
|
-
|
|
511
|
-
// Compile-time assertion
|
|
512
|
-
const typeCheck: AssertType<typeof _result.data, NullableData> = true;
|
|
513
|
-
expect(typeCheck).to.be.true;
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
});
|
package/src/core/types.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration for the Scrubber
|
|
3
|
-
*
|
|
4
|
-
* Defines how the scrubber should identify and replace sensitive data.
|
|
5
|
-
* Supports three complementary scrubbing strategies:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Field-based scrubbing** (`fields`): Matches field names at any depth in the object tree
|
|
8
|
-
* 2. **Path-based scrubbing** (`paths`): Matches specific dot-notation paths
|
|
9
|
-
* 3. **Pattern-based scrubbing** (`patterns`): Matches regex patterns in string content
|
|
10
|
-
*
|
|
11
|
-
* All three strategies can be used together for comprehensive data scrubbing.
|
|
12
|
-
*
|
|
13
|
-
* @example Field-based configuration
|
|
14
|
-
* ```typescript
|
|
15
|
-
* const config: ScrubConfig = {
|
|
16
|
-
* fields: ['password', 'apiToken', /api[-_]?key/i], // Strings and regex patterns
|
|
17
|
-
* replacement: '[REDACTED]'
|
|
18
|
-
* };
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* @example Path-based configuration
|
|
22
|
-
* ```typescript
|
|
23
|
-
* const config: ScrubConfig = {
|
|
24
|
-
* paths: [
|
|
25
|
-
* 'user.email',
|
|
26
|
-
* 'request.headers.authorization',
|
|
27
|
-
* 'items[0].password' // Array index notation
|
|
28
|
-
* ]
|
|
29
|
-
* };
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example Pattern-based configuration
|
|
33
|
-
* ```typescript
|
|
34
|
-
* const config: ScrubConfig = {
|
|
35
|
-
* patterns: [
|
|
36
|
-
* /\b\d{3}-\d{2}-\d{4}\b/g, // SSN
|
|
37
|
-
* /\d{4}-\d{4}-\d{4}-\d{4}/g, // Credit card
|
|
38
|
-
* /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}/g // Email
|
|
39
|
-
* ]
|
|
40
|
-
* };
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export interface ScrubConfig {
|
|
44
|
-
/**
|
|
45
|
-
* Field-based scrubbing: matches field names at any depth
|
|
46
|
-
*
|
|
47
|
-
* Supports both exact string matches and regular expressions for flexible matching.
|
|
48
|
-
* String matches are case-insensitive and use substring matching.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* fields: [
|
|
53
|
-
* 'password', // Matches 'password', 'Password', 'old_password', etc.
|
|
54
|
-
* 'apiToken', // Matches 'apiToken', 'api_token', etc.
|
|
55
|
-
* /api[-_]?key/i, // Regex: matches 'api_key', 'api-key', 'apikey' (case insensitive)
|
|
56
|
-
* /^secret$/ // Exact match: only 'secret', not 'my_secret'
|
|
57
|
-
* ]
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
fields?: (string | RegExp)[];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Path-based scrubbing: matches specific dot-notation paths
|
|
64
|
-
*
|
|
65
|
-
* Use dot notation to target specific fields in nested objects.
|
|
66
|
-
* Supports array index notation (e.g., `items[0].password`).
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* paths: [
|
|
71
|
-
* 'user.email', // Scrubs obj.user.email
|
|
72
|
-
* 'request.headers.authorization', // Nested path
|
|
73
|
-
* 'items[0].secret', // Array index notation
|
|
74
|
-
* 'users[0]' // Scrubs entire array element
|
|
75
|
-
* ]
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
paths?: string[];
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Pattern-based scrubbing: regex patterns for content scrubbing
|
|
82
|
-
*
|
|
83
|
-
* Scans string values and replaces content matching the patterns.
|
|
84
|
-
* Use the global flag (`/pattern/g`) to replace all matches in a string.
|
|
85
|
-
*
|
|
86
|
-
* **Note**: Patterns are applied to string values only, not to field names or paths.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```typescript
|
|
90
|
-
* patterns: [
|
|
91
|
-
* /\b\d{3}-\d{2}-\d{4}\b/g, // Social Security Number
|
|
92
|
-
* /\d{4}-\d{4}-\d{4}-\d{4}/g, // Credit Card
|
|
93
|
-
* /Bearer\s+[A-Za-z0-9._-]+/g // Bearer tokens
|
|
94
|
-
* ]
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
patterns?: RegExp[];
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Replacement string for scrubbed values
|
|
101
|
-
*
|
|
102
|
-
* @default '[SCRUBBED]'
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* replacement: '[REDACTED]' // Custom replacement text
|
|
107
|
-
* replacement: '***' // Simple masking
|
|
108
|
-
* replacement: '' // Empty string (removes content)
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
replacement?: string;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Whether to recursively scrub nested objects
|
|
115
|
-
*
|
|
116
|
-
* When `true`, the scrubber traverses the entire object tree.
|
|
117
|
-
* When `false`, only top-level fields are scrubbed.
|
|
118
|
-
*
|
|
119
|
-
* @default true
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* ```typescript
|
|
123
|
-
* recursive: false // Only scrub top-level fields
|
|
124
|
-
* ```
|
|
125
|
-
*/
|
|
126
|
-
recursive?: boolean;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Result of a scrub operation
|
|
131
|
-
*
|
|
132
|
-
* Contains the scrubbed data along with metadata about what was scrubbed.
|
|
133
|
-
*
|
|
134
|
-
* @template T - The type of the scrubbed data (same as input type)
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* const scrubber = new Scrubber({ fields: ['password'] });
|
|
139
|
-
* const result = scrubber.scrub({ user: 'john', password: 'secret' });
|
|
140
|
-
*
|
|
141
|
-
* console.log(result.data); // { user: 'john', password: '[SCRUBBED]' }
|
|
142
|
-
* console.log(result.scrubbed); // true
|
|
143
|
-
* console.log(result.scrubbedPaths); // ['password']
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
export interface ScrubResult<T> {
|
|
147
|
-
/**
|
|
148
|
-
* The scrubbed data with sensitive values replaced
|
|
149
|
-
*
|
|
150
|
-
* This is a deep clone of the input with scrubbed values replaced.
|
|
151
|
-
* The original input is never mutated.
|
|
152
|
-
*/
|
|
153
|
-
data: T;
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Whether any scrubbing occurred
|
|
157
|
-
*
|
|
158
|
-
* `true` if at least one value was scrubbed, `false` if no sensitive data was found.
|
|
159
|
-
*
|
|
160
|
-
* Useful for logging or metrics to track scrubbing activity.
|
|
161
|
-
*/
|
|
162
|
-
scrubbed: boolean;
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Array of paths that were scrubbed
|
|
166
|
-
*
|
|
167
|
-
* Contains dot-notation paths for all fields that were scrubbed.
|
|
168
|
-
* Useful for debugging, auditing, or understanding what data was redacted.
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```typescript
|
|
172
|
-
* ['password', 'user.email', 'request.headers.authorization']
|
|
173
|
-
* ```
|
|
174
|
-
*/
|
|
175
|
-
scrubbedPaths: string[];
|
|
176
|
-
}
|