@logtape/redaction 2.1.0-dev.600 → 2.1.0-dev.613

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.
@@ -0,0 +1,30 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ Object.defineProperty(exports, '__toESM', {
26
+ enumerable: true,
27
+ get: function () {
28
+ return __toESM;
29
+ }
30
+ });
package/dist/field.cjs CHANGED
@@ -1,5 +1,9 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const __logtape_logtape = require_rolldown_runtime.__toESM(require("@logtape/logtape"));
1
3
 
2
4
  //#region src/field.ts
5
+ const metaLogger = (0, __logtape_logtape.getLogger)(["logtape", "meta"]);
6
+ let reportingRedactionFailure = false;
3
7
  /**
4
8
  * Default field patterns for redaction. These patterns will match
5
9
  * common sensitive fields such as passwords, tokens, and personal
@@ -71,6 +75,133 @@ function redactByField(sink, options = DEFAULT_REDACT_FIELDS) {
71
75
  return wrapped;
72
76
  }
73
77
  /**
78
+ * Redacts properties and message values in a {@link LogRecord} based on the
79
+ * provided field patterns and asynchronous action.
80
+ *
81
+ * The returned sink preserves record ordering by processing redaction work in
82
+ * sequence and implements {@link AsyncDisposable} so callers can wait for all
83
+ * pending redaction work before shutdown.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { getConsoleSink } from "@logtape/logtape";
88
+ * import {
89
+ * createHmacPseudonymizer,
90
+ * redactByFieldAsync,
91
+ * } from "@logtape/redaction";
92
+ *
93
+ * const pseudonymize = await createHmacPseudonymizer({ key: "secret" });
94
+ * const sink = redactByFieldAsync(getConsoleSink(), {
95
+ * fieldPatterns: [/userId/i, /email/i],
96
+ * action: pseudonymize,
97
+ * });
98
+ * ```
99
+ *
100
+ * @param sink The sink to wrap.
101
+ * @param options The async redaction options.
102
+ * @returns The wrapped sink.
103
+ * @since 2.1.0
104
+ */
105
+ function redactByFieldAsync(sink, options) {
106
+ let lastPromise = Promise.resolve();
107
+ let closed = false;
108
+ const sinkErrors = [];
109
+ const wrapped = (record) => {
110
+ if (closed) return;
111
+ const work = redactLogRecordAsync(record, options).catch((error) => {
112
+ reportRedactionFailure(error);
113
+ return null;
114
+ });
115
+ lastPromise = lastPromise.then(async () => {
116
+ const result = await work;
117
+ if (result == null) return;
118
+ try {
119
+ await sink({
120
+ ...record,
121
+ message: result.message,
122
+ properties: result.properties
123
+ });
124
+ } catch (error) {
125
+ sinkErrors.push(error);
126
+ }
127
+ });
128
+ };
129
+ wrapped[Symbol.asyncDispose] = async () => {
130
+ closed = true;
131
+ await lastPromise;
132
+ let disposeError;
133
+ try {
134
+ if (Symbol.asyncDispose in sink) await sink[Symbol.asyncDispose]();
135
+ else if (Symbol.dispose in sink) sink[Symbol.dispose]();
136
+ } catch (error) {
137
+ disposeError = error;
138
+ }
139
+ if (sinkErrors.length > 0) {
140
+ const errors = disposeError == null ? sinkErrors : [...sinkErrors, disposeError];
141
+ if (errors.length === 1) throw errors[0];
142
+ throw new AggregateError(errors, "One or more errors occurred while emitting redacted log records.");
143
+ }
144
+ if (disposeError != null) throw disposeError;
145
+ };
146
+ return wrapped;
147
+ }
148
+ async function redactLogRecordAsync(record, options) {
149
+ const redactedProperties = await redactPropertiesAsync(record.properties, options);
150
+ if (typeof record.rawMessage === "string") {
151
+ const placeholders = extractPlaceholderNames(record.rawMessage);
152
+ const { redactedIndices, wildcardIndices } = getRedactedPlaceholderIndices(placeholders, options.fieldPatterns);
153
+ return {
154
+ message: await redactMessageArrayAsync(record.message, placeholders, redactedIndices, wildcardIndices, redactedProperties, options.action),
155
+ properties: redactedProperties
156
+ };
157
+ }
158
+ let redactedMessage = record.message;
159
+ const redactedValues = getRedactedValues(record.properties, redactedProperties);
160
+ if (redactedValues.size > 0) redactedMessage = redactMessageByValues(record.message, redactedValues);
161
+ return {
162
+ message: redactedMessage,
163
+ properties: redactedProperties
164
+ };
165
+ }
166
+ function reportRedactionFailure(error) {
167
+ if (reportingRedactionFailure || typeof metaLogger.warn !== "function") return;
168
+ try {
169
+ reportingRedactionFailure = true;
170
+ metaLogger.warn("Failed to redact a log record; dropping the record to avoid leaking sensitive data: {error}", { error });
171
+ } catch {} finally {
172
+ reportingRedactionFailure = false;
173
+ }
174
+ }
175
+ /**
176
+ * Creates an asynchronous pseudonymizer based on HMAC using the Web Crypto API.
177
+ *
178
+ * The returned function converts each value with `String(value)`, encodes it
179
+ * as UTF-8, and returns a stable pseudonym. Because HMAC is keyed, this is
180
+ * safer than a plain salted hash for values with small input spaces, such as
181
+ * email addresses or numeric user IDs.
182
+ *
183
+ * @param options The HMAC pseudonymizer options.
184
+ * @returns An async redaction action.
185
+ * @since 2.1.0
186
+ */
187
+ async function createHmacPseudonymizer(options) {
188
+ const subtle = globalThis.crypto?.subtle;
189
+ if (subtle == null) throw new TypeError("The Web Crypto API is not available.");
190
+ const hash = getHmacHash(options.key, options.hash);
191
+ const encoding = options.encoding ?? "base64url";
192
+ const prefix = options.prefix ?? `hmac-${hash.toLowerCase().replaceAll("-", "")}:`;
193
+ const key = isCryptoKey(options.key) ? options.key : await subtle.importKey("raw", keyToBytes(options.key), {
194
+ name: "HMAC",
195
+ hash
196
+ }, false, ["sign"]);
197
+ const encoder = new TextEncoder();
198
+ return async (value) => {
199
+ const data = encoder.encode(String(value));
200
+ const signature = new Uint8Array(await subtle.sign("HMAC", key, data));
201
+ return prefix + encodeBytes(signature, encoding);
202
+ };
203
+ }
204
+ /**
74
205
  * Redacts properties from an object based on specified field patterns.
75
206
  *
76
207
  * This function creates a shallow copy of the input object and applies
@@ -90,7 +221,7 @@ function redactProperties(properties, options, visited = /* @__PURE__ */ new Map
90
221
  if (visited.has(properties)) return visited.get(properties);
91
222
  const copy = {};
92
223
  visited.set(properties, copy);
93
- for (const field in properties) {
224
+ for (const field of Object.keys(properties)) {
94
225
  if (shouldFieldRedacted(field, options.fieldPatterns)) {
95
226
  if (typeof options.action === "function") setProperty(copy, field, options.action(properties[field]));
96
227
  continue;
@@ -103,6 +234,36 @@ function redactProperties(properties, options, visited = /* @__PURE__ */ new Map
103
234
  }
104
235
  return copy;
105
236
  }
237
+ /**
238
+ * Redacts properties from an object using an asynchronous action.
239
+ * @param properties The properties to redact.
240
+ * @param options The async redaction options.
241
+ * @param visited Map of visited objects to prevent circular reference issues.
242
+ * @returns The redacted properties.
243
+ * @since 2.1.0
244
+ */
245
+ async function redactPropertiesAsync(properties, options, visited = /* @__PURE__ */ new Map()) {
246
+ if (visited.has(properties)) return visited.get(properties);
247
+ const copy = {};
248
+ visited.set(properties, copy);
249
+ const fields = [];
250
+ const values = [];
251
+ for (const field of Object.keys(properties)) {
252
+ fields.push(field);
253
+ if (shouldFieldRedacted(field, options.fieldPatterns)) {
254
+ values.push(Promise.resolve(options.action(properties[field])));
255
+ continue;
256
+ }
257
+ const value = properties[field];
258
+ if (Array.isArray(value)) values.push(redactArrayAsync(value, options, visited));
259
+ else if (typeof value === "object" && value !== null) if (isBuiltInObject(value)) values.push(Promise.resolve(value));
260
+ else values.push(redactPropertiesAsync(value, options, visited));
261
+ else values.push(Promise.resolve(value));
262
+ }
263
+ const redactedValues = await Promise.all(values);
264
+ for (let i = 0; i < fields.length; i++) setProperty(copy, fields[i], redactedValues[i]);
265
+ return copy;
266
+ }
106
267
  function setProperty(object, field, value) {
107
268
  if (field === "__proto__") Object.defineProperty(object, field, {
108
269
  value,
@@ -120,14 +281,40 @@ function setProperty(object, field, value) {
120
281
  * @returns A new array with redacted values.
121
282
  */
122
283
  function redactArray(array, options, visited) {
123
- return array.map((item) => {
124
- if (Array.isArray(item)) return redactArray(item, options, visited);
125
- if (typeof item === "object" && item !== null) {
126
- if (isBuiltInObject(item)) return item;
127
- return redactProperties(item, options, visited);
284
+ if (visited.has(array)) return visited.get(array);
285
+ const copy = [];
286
+ copy.length = array.length;
287
+ visited.set(array, copy);
288
+ for (let i = 0; i < array.length; i++) {
289
+ if (!(i in array)) continue;
290
+ const item = array[i];
291
+ if (Array.isArray(item)) copy[i] = redactArray(item, options, visited);
292
+ else if (typeof item === "object" && item !== null) if (isBuiltInObject(item)) copy[i] = item;
293
+ else copy[i] = redactProperties(item, options, visited);
294
+ else copy[i] = item;
295
+ }
296
+ return copy;
297
+ }
298
+ async function redactArrayAsync(array, options, visited) {
299
+ if (visited.has(array)) return visited.get(array);
300
+ const copy = [];
301
+ copy.length = array.length;
302
+ visited.set(array, copy);
303
+ const itemPromises = [];
304
+ for (let i = 0; i < array.length; i++) {
305
+ if (!(i in array)) {
306
+ itemPromises.push(Promise.resolve(void 0));
307
+ continue;
128
308
  }
129
- return item;
130
- });
309
+ const item = array[i];
310
+ if (Array.isArray(item)) itemPromises.push(redactArrayAsync(item, options, visited));
311
+ else if (typeof item === "object" && item !== null) if (isBuiltInObject(item)) itemPromises.push(Promise.resolve(item));
312
+ else itemPromises.push(redactPropertiesAsync(item, options, visited));
313
+ else itemPromises.push(Promise.resolve(item));
314
+ }
315
+ const redactedItems = await Promise.all(itemPromises);
316
+ for (let i = 0; i < redactedItems.length; i++) if (i in array) copy[i] = redactedItems[i];
317
+ return copy;
131
318
  }
132
319
  /**
133
320
  * Checks if a value is a built-in object that should not be recursively
@@ -188,11 +375,51 @@ function extractPlaceholderNames(template) {
188
375
  function parsePathSegments(path) {
189
376
  const segments = [];
190
377
  let current = "";
191
- for (const char of path) if (char === "." || char === "[") {
192
- if (current) segments.push(current);
378
+ let inBracket = false;
379
+ let quotedBracketSegment = false;
380
+ let quote;
381
+ let escaped = false;
382
+ const pushCurrent = (trim = false) => {
383
+ const segment = trim ? current.trimEnd() : current;
384
+ if (segment) segments.push(segment);
193
385
  current = "";
194
- } else if (char === "]" || char === "?") {} else current += char;
195
- if (current) segments.push(current);
386
+ };
387
+ for (const char of path) {
388
+ if (quote != null) {
389
+ if (escaped) {
390
+ current += char;
391
+ escaped = false;
392
+ } else if (char === "\\") escaped = true;
393
+ else if (char === quote) quote = void 0;
394
+ else current += char;
395
+ continue;
396
+ }
397
+ if (inBracket && current === "" && /\s/.test(char)) continue;
398
+ if (inBracket && (char === "\"" || char === "'") && current === "") {
399
+ quote = char;
400
+ quotedBracketSegment = true;
401
+ continue;
402
+ }
403
+ if (inBracket && quotedBracketSegment && /\s/.test(char)) continue;
404
+ if (char === "." && !inBracket) {
405
+ pushCurrent();
406
+ continue;
407
+ }
408
+ if (char === "[") {
409
+ pushCurrent();
410
+ inBracket = true;
411
+ continue;
412
+ }
413
+ if (char === "]") {
414
+ pushCurrent(!quotedBracketSegment);
415
+ inBracket = false;
416
+ quotedBracketSegment = false;
417
+ continue;
418
+ }
419
+ if (char === "?") continue;
420
+ current += char;
421
+ }
422
+ pushCurrent();
196
423
  return segments;
197
424
  }
198
425
  /**
@@ -247,14 +474,51 @@ function redactMessageArray(message, placeholders, redactedIndices, wildcardIndi
247
474
  else result.push(action(message[i]));
248
475
  else {
249
476
  const placeholderName = placeholders[placeholderIndex];
250
- const rootKey = parsePathSegments(placeholderName)[0];
251
- if (rootKey in redactedProperties) result.push(redactedProperties[rootKey]);
477
+ const redactedValue = getPathValue(redactedProperties, placeholderName);
478
+ if (redactedValue.found) result.push(redactedValue.value);
479
+ else result.push(message[i]);
480
+ }
481
+ placeholderIndex++;
482
+ }
483
+ return result;
484
+ }
485
+ async function redactMessageArrayAsync(message, placeholders, redactedIndices, wildcardIndices, redactedProperties, action) {
486
+ const result = [];
487
+ const tasks = [];
488
+ let placeholderIndex = 0;
489
+ for (let i = 0; i < message.length; i++) if (i % 2 === 0) result.push(message[i]);
490
+ else {
491
+ if (wildcardIndices.has(placeholderIndex)) result.push(redactedProperties);
492
+ else if (redactedIndices.has(placeholderIndex)) {
493
+ const index = result.length;
494
+ result.push(void 0);
495
+ tasks.push(Promise.resolve(action(message[i])).then((redacted) => {
496
+ result[index] = redacted;
497
+ }));
498
+ } else {
499
+ const placeholderName = placeholders[placeholderIndex];
500
+ const redactedValue = getPathValue(redactedProperties, placeholderName);
501
+ if (redactedValue.found) result.push(redactedValue.value);
252
502
  else result.push(message[i]);
253
503
  }
254
504
  placeholderIndex++;
255
505
  }
506
+ await Promise.all(tasks);
256
507
  return result;
257
508
  }
509
+ function getPathValue(properties, path) {
510
+ const segments = parsePathSegments(path);
511
+ if (segments.length < 1) return { found: false };
512
+ let value = properties;
513
+ for (const segment of segments) {
514
+ if (typeof value !== "object" && typeof value !== "function" || value == null || !Object.hasOwn(value, segment)) return { found: false };
515
+ value = value[segment];
516
+ }
517
+ return {
518
+ found: true,
519
+ value
520
+ };
521
+ }
258
522
  /**
259
523
  * Collects redacted value mappings from original to redacted properties.
260
524
  * @param original The original properties.
@@ -262,7 +526,7 @@ function redactMessageArray(message, placeholders, redactedIndices, wildcardIndi
262
526
  * @param map The map to populate with original -> redacted value pairs.
263
527
  */
264
528
  function collectRedactedValues(original, redacted, map) {
265
- for (const key in original) {
529
+ for (const key of Object.keys(original)) {
266
530
  const origVal = original[key];
267
531
  const redVal = redacted[key];
268
532
  if (origVal !== redVal) map.set(origVal, redVal);
@@ -298,7 +562,53 @@ function redactMessageByValues(message, redactedValues) {
298
562
  }
299
563
  return result;
300
564
  }
565
+ function keyToBytes(key) {
566
+ if (typeof key === "string") return new TextEncoder().encode(key);
567
+ if (key instanceof ArrayBuffer) return key;
568
+ return key;
569
+ }
570
+ function isCryptoKey(key) {
571
+ return typeof key === "object" && key !== null && Object.prototype.toString.call(key) === "[object CryptoKey]";
572
+ }
573
+ function getHmacHash(key, hash) {
574
+ if (!isCryptoKey(key)) return hash ?? "SHA-256";
575
+ if (!key.usages.includes("sign")) throw new TypeError("The HMAC CryptoKey must include the \"sign\" usage.");
576
+ const keyHash = getCryptoKeyHmacHash(key);
577
+ if (hash != null && hash !== keyHash) throw new TypeError(`The HMAC CryptoKey uses ${keyHash}, but the "hash" option is ${hash}.`);
578
+ return keyHash;
579
+ }
580
+ function getCryptoKeyHmacHash(key) {
581
+ const algorithm = key.algorithm;
582
+ if (algorithm.name !== "HMAC" || !("hash" in algorithm)) throw new TypeError("The CryptoKey must be an HMAC key.");
583
+ const hashAlgorithm = algorithm.hash;
584
+ if (typeof hashAlgorithm !== "object" || hashAlgorithm == null || !("name" in hashAlgorithm) || typeof hashAlgorithm.name !== "string") throw new TypeError("The CryptoKey must specify an HMAC hash algorithm.");
585
+ const hash = hashAlgorithm.name;
586
+ switch (hash) {
587
+ case "SHA-256":
588
+ case "SHA-384":
589
+ case "SHA-512": return hash;
590
+ default: throw new TypeError(`Unsupported HMAC hash algorithm: ${hash}.`);
591
+ }
592
+ }
593
+ function encodeBytes(bytes, encoding) {
594
+ switch (encoding) {
595
+ case "base64url": return encodeBase64Url(bytes);
596
+ case "hex": return encodeHex(bytes);
597
+ }
598
+ }
599
+ function encodeBase64Url(bytes) {
600
+ let binary = "";
601
+ for (const byte of bytes) binary += String.fromCharCode(byte);
602
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
603
+ }
604
+ function encodeHex(bytes) {
605
+ let result = "";
606
+ for (const byte of bytes) result += byte.toString(16).padStart(2, "0");
607
+ return result;
608
+ }
301
609
 
302
610
  //#endregion
303
611
  exports.DEFAULT_REDACT_FIELDS = DEFAULT_REDACT_FIELDS;
304
- exports.redactByField = redactByField;
612
+ exports.createHmacPseudonymizer = createHmacPseudonymizer;
613
+ exports.redactByField = redactByField;
614
+ exports.redactByFieldAsync = redactByFieldAsync;
package/dist/field.d.cts CHANGED
@@ -14,6 +14,21 @@ type FieldPattern = string | RegExp;
14
14
  * @since 0.10.0
15
15
  */
16
16
  type FieldPatterns = FieldPattern[];
17
+ /**
18
+ * The synchronous action to perform on a redacted field value.
19
+ * @since 0.10.0
20
+ */
21
+ type FieldRedactionAction = "delete" | ((value: unknown) => unknown);
22
+ /**
23
+ * The asynchronous action to perform on a redacted field value.
24
+ * @since 2.1.0
25
+ */
26
+ type AsyncFieldRedactionAction = (value: unknown) => PromiseLike<unknown>;
27
+ /**
28
+ * A pseudonymizer created by {@link createHmacPseudonymizer}.
29
+ * @since 2.1.0
30
+ */
31
+ type HmacPseudonymizer = (value: unknown) => Promise<string>;
17
32
  /**
18
33
  * Default field patterns for redaction. These patterns will match
19
34
  * common sensitive fields such as passwords, tokens, and personal
@@ -44,7 +59,50 @@ interface FieldRedactionOptions {
44
59
  * properties.
45
60
  * @default `"delete"`
46
61
  */
47
- readonly action?: "delete" | ((value: unknown) => unknown);
62
+ readonly action?: FieldRedactionAction;
63
+ }
64
+ /**
65
+ * Options for asynchronously redacting fields in a {@link LogRecord}. Used by
66
+ * the {@link redactByFieldAsync} function.
67
+ * @since 2.1.0
68
+ */
69
+ interface AsyncFieldRedactionOptions {
70
+ /**
71
+ * The field patterns to match against. This can be an array of
72
+ * strings or regular expressions. If a field matches any of the
73
+ * patterns, it will be redacted.
74
+ */
75
+ readonly fieldPatterns: FieldPatterns;
76
+ /**
77
+ * The asynchronous action to perform on the matched fields.
78
+ */
79
+ readonly action: AsyncFieldRedactionAction;
80
+ }
81
+ /**
82
+ * Options for the {@link createHmacPseudonymizer} function.
83
+ * @since 2.1.0
84
+ */
85
+ interface HmacPseudonymizerOptions {
86
+ /**
87
+ * The secret key to use for HMAC. Strings are encoded as UTF-8.
88
+ */
89
+ readonly key: string | Uint8Array | ArrayBuffer | CryptoKey;
90
+ /**
91
+ * The HMAC hash algorithm.
92
+ * @default `"SHA-256"`
93
+ */
94
+ readonly hash?: "SHA-256" | "SHA-384" | "SHA-512";
95
+ /**
96
+ * The digest encoding.
97
+ * @default `"base64url"`
98
+ */
99
+ readonly encoding?: "base64url" | "hex";
100
+ /**
101
+ * The string prefix to prepend to each pseudonym. If omitted, a prefix based
102
+ * on the hash algorithm is used, such as `"hmac-sha256:"`. Set this to an
103
+ * empty string to disable the prefix.
104
+ */
105
+ readonly prefix?: string;
48
106
  }
49
107
  /**
50
108
  * Redacts properties and message values in a {@link LogRecord} based on the
@@ -73,6 +131,48 @@ interface FieldRedactionOptions {
73
131
  * @since 0.10.0
74
132
  */
75
133
  declare function redactByField(sink: Sink | Sink & Disposable | Sink & AsyncDisposable, options?: FieldRedactionOptions | FieldPatterns): Sink | Sink & Disposable | Sink & AsyncDisposable;
134
+ /**
135
+ * Redacts properties and message values in a {@link LogRecord} based on the
136
+ * provided field patterns and asynchronous action.
137
+ *
138
+ * The returned sink preserves record ordering by processing redaction work in
139
+ * sequence and implements {@link AsyncDisposable} so callers can wait for all
140
+ * pending redaction work before shutdown.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * import { getConsoleSink } from "@logtape/logtape";
145
+ * import {
146
+ * createHmacPseudonymizer,
147
+ * redactByFieldAsync,
148
+ * } from "@logtape/redaction";
149
+ *
150
+ * const pseudonymize = await createHmacPseudonymizer({ key: "secret" });
151
+ * const sink = redactByFieldAsync(getConsoleSink(), {
152
+ * fieldPatterns: [/userId/i, /email/i],
153
+ * action: pseudonymize,
154
+ * });
155
+ * ```
156
+ *
157
+ * @param sink The sink to wrap.
158
+ * @param options The async redaction options.
159
+ * @returns The wrapped sink.
160
+ * @since 2.1.0
161
+ */
162
+ declare function redactByFieldAsync(sink: Sink | Sink & Disposable | Sink & AsyncDisposable, options: AsyncFieldRedactionOptions): Sink & AsyncDisposable;
163
+ /**
164
+ * Creates an asynchronous pseudonymizer based on HMAC using the Web Crypto API.
165
+ *
166
+ * The returned function converts each value with `String(value)`, encodes it
167
+ * as UTF-8, and returns a stable pseudonym. Because HMAC is keyed, this is
168
+ * safer than a plain salted hash for values with small input spaces, such as
169
+ * email addresses or numeric user IDs.
170
+ *
171
+ * @param options The HMAC pseudonymizer options.
172
+ * @returns An async redaction action.
173
+ * @since 2.1.0
174
+ */
175
+ declare function createHmacPseudonymizer(options: HmacPseudonymizerOptions): Promise<HmacPseudonymizer>;
76
176
  /**
77
177
  * Redacts properties from an object based on specified field patterns.
78
178
  *
@@ -90,5 +190,5 @@ declare function redactByField(sink: Sink | Sink & Disposable | Sink & AsyncDisp
90
190
  * @since 0.10.0
91
191
  */
92
192
  //#endregion
93
- export { DEFAULT_REDACT_FIELDS, FieldPattern, FieldPatterns, FieldRedactionOptions, redactByField };
193
+ export { AsyncFieldRedactionAction, AsyncFieldRedactionOptions, DEFAULT_REDACT_FIELDS, FieldPattern, FieldPatterns, FieldRedactionAction, FieldRedactionOptions, HmacPseudonymizer, HmacPseudonymizerOptions, createHmacPseudonymizer, redactByField, redactByFieldAsync };
94
194
  //# sourceMappingURL=field.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.cts","names":[],"sources":["../src/field.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;AAOA;AAQA;AAqBiB,KApCL,YAAA,GAoCK,MAAqB,GApCF,MA2CV;AAyC1B;;;;;AACmC,KA9EvB,aAAA,GAAgB,YA8EO,EAAA;;;;;;;AAEL,cAxEjB,qBAwEiB,EAxEM,aAwEN;;AAAsB;;;;UAnDnC,qBAAA;;;;;;;0BAOS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCV,aAAA,OACR,OAAO,OAAO,aAAa,OAAO,2BAC/B,wBAAwB,gBAChC,OAAO,OAAO,aAAa,OAAO"}
1
+ {"version":3,"file":"field.d.cts","names":[],"sources":["../src/field.ts"],"sourcesContent":[],"mappings":";;;;;;AAOA;AAOA;AAMA;AAMY,KAnBA,YAAA,GAmBA,MAAyB,GAnBD,MAmBC;AAQrC;AAWA;AAqBA;;;AAmBoB,KAvER,aAAA,GAAgB,YAuER,EAAA;AAAoB;AAQxC;;;AAWmB,KApFP,oBAAA,GAoFO,QAAA,GAAA,CAAA,CAAA,KAAA,EAAA,OAAA,EAAA,GAAA,OAAA,CAAA;AAAyB;AAO5C;;;AAIsC,KAzF1B,yBAAA,GAyF0B,CAAA,KAAA,EAAA,OAAA,EAAA,GAvFjC,WAuFiC,CAAA,OAAA,CAAA;;AAAuB;AAgD7D;;AACQ,KAlII,iBAAA,GAkIJ,CAAA,KAAA,EAAA,OAAA,EAAA,GAlI4C,OAkI5C,CAAA,MAAA,CAAA;;;;;;;AAEL,cAzHU,qBAyHV,EAzHiC,aAyHjC;;;;;AAAiD;AA0EpC,UA9KC,qBAAA,CA8KiB;EAAA;;;;;;EACuB,SAC9C,aAAA,EAzKe,aAyKf;EAA0B;;AACZ;AAkIzB;;;;;AAEU;;oBAlSU;;;;;;;UAQH,0BAAA;;;;;;0BAMS;;;;mBAKP;;;;;;UAOF,wBAAA;;;;yBAIQ,aAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDpC,aAAA,OACR,OAAO,OAAO,aAAa,OAAO,2BAC/B,wBAAwB,gBAChC,OAAO,OAAO,aAAa,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0ErB,kBAAA,OACR,OAAO,OAAO,aAAa,OAAO,0BAC/B,6BACR,OAAO;;;;;;;;;;;;;iBAkIY,uBAAA,UACX,2BACR,QAAQ"}