@blindfold/sdk 1.0.2

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/dist/index.js ADDED
@@ -0,0 +1,575 @@
1
+ "use strict";
2
+ const require_regex = require('./regex-BEaK0E7Y.js');
3
+ const fs = require_regex.__toESM(require("fs"));
4
+ const path = require_regex.__toESM(require("path"));
5
+
6
+ //#region src/errors.ts
7
+ /**
8
+ * Base error class for Blindfold SDK
9
+ */
10
+ var BlindfoldError = class BlindfoldError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "BlindfoldError";
14
+ Object.setPrototypeOf(this, BlindfoldError.prototype);
15
+ }
16
+ };
17
+ /**
18
+ * Error thrown when authentication fails
19
+ */
20
+ var AuthenticationError = class AuthenticationError extends BlindfoldError {
21
+ constructor(message = "Authentication failed. Please check your API key.") {
22
+ super(message);
23
+ this.name = "AuthenticationError";
24
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
25
+ }
26
+ };
27
+ /**
28
+ * Error thrown when API request fails
29
+ */
30
+ var APIError = class APIError extends BlindfoldError {
31
+ statusCode;
32
+ responseBody;
33
+ constructor(message, statusCode, responseBody) {
34
+ super(message);
35
+ this.name = "APIError";
36
+ this.statusCode = statusCode;
37
+ this.responseBody = responseBody;
38
+ Object.setPrototypeOf(this, APIError.prototype);
39
+ }
40
+ };
41
+ /**
42
+ * Error thrown when network request fails
43
+ */
44
+ var NetworkError = class NetworkError extends BlindfoldError {
45
+ constructor(message = "Network request failed. Please check your connection.") {
46
+ super(message);
47
+ this.name = "NetworkError";
48
+ Object.setPrototypeOf(this, NetworkError.prototype);
49
+ }
50
+ };
51
+
52
+ //#endregion
53
+ //#region src/client.ts
54
+ const DEFAULT_BASE_URL = "https://api.blindfold.dev/api/public/v1";
55
+ const RETRYABLE_STATUS_CODES = new Set([
56
+ 429,
57
+ 500,
58
+ 502,
59
+ 503,
60
+ 504
61
+ ]);
62
+ const REGION_URLS = {
63
+ eu: "https://eu-api.blindfold.dev/api/public/v1",
64
+ us: "https://us-api.blindfold.dev/api/public/v1"
65
+ };
66
+ function sleep(ms) {
67
+ return new Promise((resolve) => setTimeout(resolve, ms));
68
+ }
69
+ const BUNDLED_POLICIES_PATH = path.join(__dirname, "policies.json");
70
+ function loadPolicies(policiesFile) {
71
+ const bundled = JSON.parse(fs.readFileSync(BUNDLED_POLICIES_PATH, "utf-8"));
72
+ if (policiesFile) {
73
+ const user = JSON.parse(fs.readFileSync(policiesFile, "utf-8"));
74
+ return {
75
+ ...bundled,
76
+ ...user
77
+ };
78
+ }
79
+ return bundled;
80
+ }
81
+ /**
82
+ * Blindfold client for tokenization and detokenization.
83
+ *
84
+ * When no `apiKey` is provided, all methods run locally using the
85
+ * built-in regex PII scanner. Set `mode: "local"` to force local
86
+ * mode even when an API key is present.
87
+ */
88
+ var Blindfold = class {
89
+ apiKey;
90
+ baseUrl;
91
+ userId;
92
+ maxRetries;
93
+ retryDelay;
94
+ mode;
95
+ locales;
96
+ policies;
97
+ _scanner;
98
+ /**
99
+ * Create a new Blindfold client
100
+ * @param config - Configuration options. Can be omitted entirely for local-only mode.
101
+ */
102
+ constructor(config = {}) {
103
+ this.apiKey = config.apiKey;
104
+ this.mode = config.mode;
105
+ this.locales = config.locales;
106
+ this.policies = loadPolicies(config.policiesFile);
107
+ if (config.region && !config.baseUrl) {
108
+ const regionUrl = REGION_URLS[config.region];
109
+ if (!regionUrl) throw new Error(`Invalid region '${config.region}'. Must be one of: ${Object.keys(REGION_URLS).join(", ")}`);
110
+ this.baseUrl = regionUrl;
111
+ } else this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
112
+ this.userId = config.userId;
113
+ this.maxRetries = config.maxRetries ?? 2;
114
+ this.retryDelay = config.retryDelay ?? .5;
115
+ if (this.useLocal && config.region) console.warn(`region='${config.region}' has no effect in local mode. Set an apiKey to use region-based routing.`);
116
+ }
117
+ get useLocal() {
118
+ if (this.mode === "local") return true;
119
+ return !this.apiKey;
120
+ }
121
+ getScanner() {
122
+ if (!this._scanner) {
123
+ const { PIIScanner: PIIScanner$1 } = (require_regex.init_regex(), require_regex.__toCommonJS(require_regex.regex_exports));
124
+ this._scanner = new PIIScanner$1(this.locales ? { locales: this.locales } : void 0);
125
+ }
126
+ return this._scanner;
127
+ }
128
+ resolvePolicy(policy, entities, scoreThreshold) {
129
+ if (entities) return {
130
+ entities,
131
+ threshold: scoreThreshold
132
+ };
133
+ if (policy) {
134
+ const policyDef = this.policies[policy];
135
+ if (policyDef) return {
136
+ entities: policyDef.entities,
137
+ threshold: scoreThreshold ?? policyDef.threshold
138
+ };
139
+ else console.warn(`Unknown policy '${policy}' in local mode, detecting all entities`);
140
+ }
141
+ return { threshold: scoreThreshold };
142
+ }
143
+ retryWait(attempt, error) {
144
+ if (error && error.statusCode === 429) {
145
+ const body = error.responseBody;
146
+ if (body && typeof body.retry_after === "number") return body.retry_after * 1e3;
147
+ }
148
+ const delay = this.retryDelay * 2 ** attempt * 1e3;
149
+ const jitter = delay * .1 * Math.random();
150
+ return delay + jitter;
151
+ }
152
+ /**
153
+ * Make an authenticated request to the API
154
+ */
155
+ async request(endpoint, method, body) {
156
+ const url = `${this.baseUrl}${endpoint}`;
157
+ const headers = { "Content-Type": "application/json" };
158
+ if (this.apiKey) headers["X-API-Key"] = this.apiKey;
159
+ if (this.userId) headers["X-Blindfold-User-Id"] = this.userId;
160
+ let lastError = new NetworkError("Request failed");
161
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) try {
162
+ const response = await fetch(url, {
163
+ method,
164
+ headers,
165
+ body: body ? JSON.stringify(body) : void 0
166
+ });
167
+ if (response.status === 401 || response.status === 403) throw new AuthenticationError("Authentication failed. Please check your API key.");
168
+ if (!response.ok) {
169
+ let errorMessage = `API request failed with status ${response.status}`;
170
+ let responseBody;
171
+ try {
172
+ responseBody = await response.json();
173
+ const errorData = responseBody;
174
+ errorMessage = errorData.detail || errorData.message || errorMessage;
175
+ } catch {
176
+ errorMessage = `${errorMessage}: ${response.statusText}`;
177
+ }
178
+ throw new APIError(errorMessage, response.status, responseBody);
179
+ }
180
+ return await response.json();
181
+ } catch (error) {
182
+ if (error instanceof AuthenticationError) throw error;
183
+ if (error instanceof APIError) {
184
+ if (RETRYABLE_STATUS_CODES.has(error.statusCode) && attempt < this.maxRetries) {
185
+ await sleep(this.retryWait(attempt, error));
186
+ continue;
187
+ }
188
+ throw error;
189
+ }
190
+ if (error instanceof NetworkError) {
191
+ lastError = error;
192
+ if (attempt < this.maxRetries) {
193
+ await sleep(this.retryWait(attempt));
194
+ continue;
195
+ }
196
+ throw error;
197
+ }
198
+ if (error instanceof TypeError && error.message.includes("fetch")) {
199
+ lastError = new NetworkError("Network request failed. Please check your connection and the API URL.");
200
+ if (attempt < this.maxRetries) {
201
+ await sleep(this.retryWait(attempt));
202
+ continue;
203
+ }
204
+ throw lastError;
205
+ }
206
+ throw new NetworkError(error instanceof Error ? error.message : "Unknown error occurred");
207
+ }
208
+ throw lastError;
209
+ }
210
+ /**
211
+ * Tokenize text by replacing sensitive information with tokens
212
+ * @param text - Text to tokenize
213
+ * @param config - Optional configuration
214
+ * @returns Promise with tokenized text and mapping
215
+ */
216
+ async tokenize(text, config) {
217
+ if (this.useLocal) return this.tokenizeLocal(text, config);
218
+ return this.request("/tokenize", "POST", {
219
+ text,
220
+ ...config
221
+ });
222
+ }
223
+ tokenizeLocal(text, config) {
224
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
225
+ const scanner = this.getScanner();
226
+ const result = scanner.tokenize(text, resolved);
227
+ const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
228
+ type: m.entityType,
229
+ text: m.text,
230
+ start: m.start,
231
+ end: m.end,
232
+ score: m.score
233
+ }));
234
+ return {
235
+ text: result.text,
236
+ mapping: result.mapping,
237
+ detected_entities: detected,
238
+ entities_count: detected.length
239
+ };
240
+ }
241
+ /**
242
+ * Detect PII in text without modifying it
243
+ *
244
+ * Returns only the detected entities with their types, positions,
245
+ * and confidence scores. The original text is not transformed.
246
+ *
247
+ * When no API key is set (or `mode: "local"`), detection runs locally
248
+ * using the built-in regex scanner.
249
+ *
250
+ * @param text - Text to analyze for PII
251
+ * @param config - Optional configuration (entities, score_threshold, policy)
252
+ * @returns Promise with detected entities
253
+ */
254
+ async detect(text, config) {
255
+ if (this.useLocal) return this.detectLocal(text, config);
256
+ return this.request("/detect", "POST", {
257
+ text,
258
+ ...config
259
+ });
260
+ }
261
+ detectLocal(text, config) {
262
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
263
+ const scanner = this.getScanner();
264
+ const matches = scanner.detect(text, resolved);
265
+ const detected = [];
266
+ for (const m of matches) {
267
+ if (threshold != null && m.score < threshold) continue;
268
+ detected.push({
269
+ type: m.entityType,
270
+ text: m.text,
271
+ start: m.start,
272
+ end: m.end,
273
+ score: m.score
274
+ });
275
+ }
276
+ return {
277
+ detected_entities: detected,
278
+ entities_count: detected.length
279
+ };
280
+ }
281
+ /**
282
+ * Detokenize text by replacing tokens with original values
283
+ *
284
+ * This method performs detokenization CLIENT-SIDE for better performance,
285
+ * security, and to work offline. No API call is made.
286
+ *
287
+ * @param text - Tokenized text
288
+ * @param mapping - Token mapping from tokenize response
289
+ * @returns DetokenizeResponse with original text
290
+ */
291
+ detokenize(text, mapping) {
292
+ let result = text;
293
+ let replacements = 0;
294
+ const sortedTokens = Object.keys(mapping).sort((a, b) => b.length - a.length);
295
+ for (const token of sortedTokens) {
296
+ const originalValue = mapping[token];
297
+ const regex = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
298
+ const matches = result.match(regex);
299
+ if (matches) {
300
+ result = result.replace(regex, originalValue);
301
+ replacements += matches.length;
302
+ }
303
+ }
304
+ return {
305
+ text: result,
306
+ replacements_made: replacements
307
+ };
308
+ }
309
+ /**
310
+ * Redact (permanently remove) sensitive information from text
311
+ *
312
+ * WARNING: Redaction is irreversible - original data cannot be restored!
313
+ *
314
+ * When no API key is set (or `mode: "local"`), redaction runs locally
315
+ * using the built-in regex scanner, replacing PII with `<Entity Name>` placeholders.
316
+ *
317
+ * @param text - Text to redact
318
+ * @param config - Optional configuration (masking_char, entities)
319
+ * @returns Promise with redacted text and detected entities
320
+ */
321
+ async redact(text, config) {
322
+ if (this.useLocal) return this.redactLocal(text, config);
323
+ return this.request("/redact", "POST", {
324
+ text,
325
+ ...config
326
+ });
327
+ }
328
+ redactLocal(text, config) {
329
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
330
+ const scanner = this.getScanner();
331
+ const [redactedText, matches] = scanner.redact(text, resolved);
332
+ const detected = [];
333
+ for (const m of matches) {
334
+ if (threshold != null && m.score < threshold) continue;
335
+ detected.push({
336
+ type: m.entityType,
337
+ text: m.text,
338
+ start: m.start,
339
+ end: m.end,
340
+ score: m.score
341
+ });
342
+ }
343
+ return {
344
+ text: redactedText,
345
+ detected_entities: detected,
346
+ entities_count: detected.length
347
+ };
348
+ }
349
+ /**
350
+ * Mask (partially hide) sensitive information from text
351
+ *
352
+ * @param text - Text to mask
353
+ * @param config - Optional configuration (chars_to_show, from_end, masking_char, entities)
354
+ * @returns Promise with masked text and detected entities
355
+ */
356
+ async mask(text, config) {
357
+ if (this.useLocal) return this.maskLocal(text, config);
358
+ return this.request("/mask", "POST", {
359
+ text,
360
+ ...config
361
+ });
362
+ }
363
+ maskLocal(text, config) {
364
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
365
+ const scanner = this.getScanner();
366
+ const result = scanner.mask(text, config?.chars_to_show ?? 3, config?.from_end ?? false, config?.masking_char ?? "*", resolved);
367
+ const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
368
+ type: m.entityType,
369
+ text: m.text,
370
+ start: m.start,
371
+ end: m.end,
372
+ score: m.score
373
+ }));
374
+ return {
375
+ text: result.text,
376
+ detected_entities: detected,
377
+ entities_count: detected.length
378
+ };
379
+ }
380
+ /**
381
+ * Synthesize (replace real data with synthetic fake data)
382
+ *
383
+ * When no API key is set (or `mode: "local"`), synthesis runs locally
384
+ * using format-preserving random generation.
385
+ *
386
+ * @param text - Text to synthesize
387
+ * @param config - Optional configuration (language, entities)
388
+ * @returns Promise with synthetic text and detected entities
389
+ */
390
+ async synthesize(text, config) {
391
+ if (this.useLocal) return this.synthesizeLocal(text, config);
392
+ return this.request("/synthesize", "POST", {
393
+ text,
394
+ ...config
395
+ });
396
+ }
397
+ synthesizeLocal(text, config) {
398
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
399
+ const scanner = this.getScanner();
400
+ const result = scanner.synthesize(text, void 0, resolved);
401
+ const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
402
+ type: m.entityType,
403
+ text: m.text,
404
+ start: m.start,
405
+ end: m.end,
406
+ score: m.score
407
+ }));
408
+ return {
409
+ text: result.text,
410
+ detected_entities: detected,
411
+ entities_count: detected.length
412
+ };
413
+ }
414
+ /**
415
+ * Hash (replace with deterministic hash values)
416
+ *
417
+ * @param text - Text to hash
418
+ * @param config - Optional configuration (hash_type, hash_prefix, hash_length, entities)
419
+ * @returns Promise with hashed text and detected entities
420
+ */
421
+ async hash(text, config) {
422
+ if (this.useLocal) return this.hashLocal(text, config);
423
+ return this.request("/hash", "POST", {
424
+ text,
425
+ ...config
426
+ });
427
+ }
428
+ hashLocal(text, config) {
429
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
430
+ const scanner = this.getScanner();
431
+ const result = scanner.hash(text, config?.hash_type ?? "sha256", config?.hash_prefix ?? "HASH_", config?.hash_length ?? 16, resolved);
432
+ const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
433
+ type: m.entityType,
434
+ text: m.text,
435
+ start: m.start,
436
+ end: m.end,
437
+ score: m.score
438
+ }));
439
+ return {
440
+ text: result.text,
441
+ detected_entities: detected,
442
+ entities_count: detected.length
443
+ };
444
+ }
445
+ /**
446
+ * Encrypt (reversibly protect) sensitive data in text using AES encryption
447
+ *
448
+ * @param text - Text to encrypt
449
+ * @param config - Optional configuration (encryption_key, entities)
450
+ * @returns Promise with encrypted text and detected entities
451
+ */
452
+ async encrypt(text, config) {
453
+ if (this.useLocal) return this.encryptLocal(text, config);
454
+ return this.request("/encrypt", "POST", {
455
+ text,
456
+ ...config
457
+ });
458
+ }
459
+ encryptLocal(text, config) {
460
+ if (!config?.encryption_key) throw new Error("encryption_key is required for local encryption mode");
461
+ const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold);
462
+ const scanner = this.getScanner();
463
+ const result = scanner.encrypt(text, config.encryption_key, resolved);
464
+ const detected = result.matches.filter((m) => threshold == null || m.score >= threshold).map((m) => ({
465
+ type: m.entityType,
466
+ text: m.text,
467
+ start: m.start,
468
+ end: m.end,
469
+ score: m.score
470
+ }));
471
+ return {
472
+ text: result.text,
473
+ detected_entities: detected,
474
+ entities_count: detected.length
475
+ };
476
+ }
477
+ /**
478
+ * Tokenize multiple texts in a single request
479
+ * @param texts - Array of texts to tokenize (max 100)
480
+ * @param config - Optional configuration
481
+ * @returns Promise with batch results
482
+ */
483
+ async tokenizeBatch(texts, config) {
484
+ return this.request("/tokenize", "POST", {
485
+ texts,
486
+ ...config
487
+ });
488
+ }
489
+ /**
490
+ * Detect PII in multiple texts in a single request
491
+ * @param texts - Array of texts to analyze (max 100)
492
+ * @param config - Optional configuration
493
+ * @returns Promise with batch results
494
+ */
495
+ async detectBatch(texts, config) {
496
+ return this.request("/detect", "POST", {
497
+ texts,
498
+ ...config
499
+ });
500
+ }
501
+ /**
502
+ * Redact PII from multiple texts in a single request
503
+ * @param texts - Array of texts to redact (max 100)
504
+ * @param config - Optional configuration
505
+ * @returns Promise with batch results
506
+ */
507
+ async redactBatch(texts, config) {
508
+ return this.request("/redact", "POST", {
509
+ texts,
510
+ ...config
511
+ });
512
+ }
513
+ /**
514
+ * Mask PII in multiple texts in a single request
515
+ * @param texts - Array of texts to mask (max 100)
516
+ * @param config - Optional configuration
517
+ * @returns Promise with batch results
518
+ */
519
+ async maskBatch(texts, config) {
520
+ return this.request("/mask", "POST", {
521
+ texts,
522
+ ...config
523
+ });
524
+ }
525
+ /**
526
+ * Synthesize multiple texts in a single request
527
+ * @param texts - Array of texts to synthesize (max 100)
528
+ * @param config - Optional configuration
529
+ * @returns Promise with batch results
530
+ */
531
+ async synthesizeBatch(texts, config) {
532
+ return this.request("/synthesize", "POST", {
533
+ texts,
534
+ ...config
535
+ });
536
+ }
537
+ /**
538
+ * Hash PII in multiple texts in a single request
539
+ * @param texts - Array of texts to hash (max 100)
540
+ * @param config - Optional configuration
541
+ * @returns Promise with batch results
542
+ */
543
+ async hashBatch(texts, config) {
544
+ return this.request("/hash", "POST", {
545
+ texts,
546
+ ...config
547
+ });
548
+ }
549
+ /**
550
+ * Encrypt PII in multiple texts in a single request
551
+ * @param texts - Array of texts to encrypt (max 100)
552
+ * @param config - Optional configuration
553
+ * @returns Promise with batch results
554
+ */
555
+ async encryptBatch(texts, config) {
556
+ return this.request("/encrypt", "POST", {
557
+ texts,
558
+ ...config
559
+ });
560
+ }
561
+ };
562
+
563
+ //#endregion
564
+ //#region src/index.ts
565
+ require_regex.init_regex();
566
+
567
+ //#endregion
568
+ exports.APIError = APIError
569
+ exports.AuthenticationError = AuthenticationError
570
+ exports.Blindfold = Blindfold
571
+ exports.BlindfoldError = BlindfoldError
572
+ exports.EntityType = require_regex.EntityType
573
+ exports.NetworkError = NetworkError
574
+ exports.PIIScanner = require_regex.PIIScanner
575
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["message: string","statusCode: number","responseBody?: unknown","REGION_URLS: Record<string, string>","ms: number","policiesFile?: string","bundled: PolicyMap","user: PolicyMap","config: BlindfoldConfig","PIIScanner","policy?: string","entities?: string[]","scoreThreshold?: number","attempt: number","error?: APIError","endpoint: string","method: string","body?: Record<string, unknown>","headers: Record<string, string>","lastError: Error","responseBody: unknown","text: string","config?: TokenizeConfig","detected: DetectedEntity[]","config?: DetectConfig","mapping: Record<string, string>","config?: RedactConfig","config?: MaskConfig","config?: SynthesizeConfig","config?: HashConfig","config?: EncryptConfig","texts: string[]"],"sources":["../src/errors.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["/**\n * Base error class for Blindfold SDK\n */\nexport class BlindfoldError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BlindfoldError'\n Object.setPrototypeOf(this, BlindfoldError.prototype)\n }\n}\n\n/**\n * Error thrown when authentication fails\n */\nexport class AuthenticationError extends BlindfoldError {\n constructor(message: string = 'Authentication failed. Please check your API key.') {\n super(message)\n this.name = 'AuthenticationError'\n Object.setPrototypeOf(this, AuthenticationError.prototype)\n }\n}\n\n/**\n * Error thrown when API request fails\n */\nexport class APIError extends BlindfoldError {\n statusCode: number\n responseBody?: unknown\n\n constructor(message: string, statusCode: number, responseBody?: unknown) {\n super(message)\n this.name = 'APIError'\n this.statusCode = statusCode\n this.responseBody = responseBody\n Object.setPrototypeOf(this, APIError.prototype)\n }\n}\n\n/**\n * Error thrown when network request fails\n */\nexport class NetworkError extends BlindfoldError {\n constructor(message: string = 'Network request failed. Please check your connection.') {\n super(message)\n this.name = 'NetworkError'\n Object.setPrototypeOf(this, NetworkError.prototype)\n }\n}\n","import type {\n BlindfoldConfig,\n DetectConfig,\n DetectResponse,\n DetectedEntity,\n TokenizeConfig,\n TokenizeResponse,\n DetokenizeResponse,\n RedactConfig,\n RedactResponse,\n MaskConfig,\n MaskResponse,\n SynthesizeConfig,\n SynthesizeResponse,\n HashConfig,\n HashResponse,\n EncryptConfig,\n EncryptResponse,\n BatchResponse,\n APIErrorResponse,\n} from './types'\nimport { AuthenticationError, APIError, NetworkError } from './errors'\nimport type { PIIScanner as PIIScannerType } from './regex'\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nconst DEFAULT_BASE_URL = 'https://api.blindfold.dev/api/public/v1'\nconst RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504])\n\nconst REGION_URLS: Record<string, string> = {\n eu: 'https://eu-api.blindfold.dev/api/public/v1',\n us: 'https://us-api.blindfold.dev/api/public/v1',\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\ninterface PolicyDefinition {\n entities: string[]\n threshold?: number\n}\n\ntype PolicyMap = Record<string, PolicyDefinition>\n\nconst BUNDLED_POLICIES_PATH = path.join(__dirname, 'policies.json')\n\nfunction loadPolicies(policiesFile?: string): PolicyMap {\n const bundled: PolicyMap = JSON.parse(fs.readFileSync(BUNDLED_POLICIES_PATH, 'utf-8'))\n if (policiesFile) {\n const user: PolicyMap = JSON.parse(fs.readFileSync(policiesFile, 'utf-8'))\n return { ...bundled, ...user }\n }\n return bundled\n}\n\n/**\n * Blindfold client for tokenization and detokenization.\n *\n * When no `apiKey` is provided, all methods run locally using the\n * built-in regex PII scanner. Set `mode: \"local\"` to force local\n * mode even when an API key is present.\n */\nexport class Blindfold {\n private apiKey?: string\n private baseUrl: string\n private userId?: string\n private maxRetries: number\n private retryDelay: number\n private mode?: string\n private locales?: string[]\n private policies: PolicyMap\n private _scanner?: PIIScannerType\n\n /**\n * Create a new Blindfold client\n * @param config - Configuration options. Can be omitted entirely for local-only mode.\n */\n constructor(config: BlindfoldConfig = {}) {\n this.apiKey = config.apiKey\n this.mode = config.mode\n this.locales = config.locales\n this.policies = loadPolicies(config.policiesFile)\n if (config.region && !config.baseUrl) {\n const regionUrl = REGION_URLS[config.region]\n if (!regionUrl) {\n throw new Error(\n `Invalid region '${config.region}'. Must be one of: ${Object.keys(REGION_URLS).join(', ')}`\n )\n }\n this.baseUrl = regionUrl\n } else {\n this.baseUrl = config.baseUrl || DEFAULT_BASE_URL\n }\n this.userId = config.userId\n this.maxRetries = config.maxRetries ?? 2\n this.retryDelay = config.retryDelay ?? 0.5\n\n if (this.useLocal && config.region) {\n console.warn(\n `region='${config.region}' has no effect in local mode. ` +\n 'Set an apiKey to use region-based routing.'\n )\n }\n }\n\n private get useLocal(): boolean {\n if (this.mode === 'local') return true\n return !this.apiKey\n }\n\n private getScanner(): PIIScannerType {\n if (!this._scanner) {\n const { PIIScanner } = require('./regex') as typeof import('./regex')\n this._scanner = new PIIScanner(this.locales ? { locales: this.locales } : undefined)\n }\n return this._scanner\n }\n\n private resolvePolicy(\n policy?: string,\n entities?: string[],\n scoreThreshold?: number\n ): { entities?: string[]; threshold?: number } {\n if (entities) {\n return { entities, threshold: scoreThreshold }\n }\n if (policy) {\n const policyDef = this.policies[policy]\n if (policyDef) {\n return {\n entities: policyDef.entities,\n threshold: scoreThreshold ?? policyDef.threshold,\n }\n } else {\n console.warn(`Unknown policy '${policy}' in local mode, detecting all entities`)\n }\n }\n return { threshold: scoreThreshold }\n }\n\n private retryWait(attempt: number, error?: APIError): number {\n if (error && error.statusCode === 429) {\n const body = error.responseBody as Record<string, unknown> | undefined\n if (body && typeof body.retry_after === 'number') {\n return body.retry_after * 1000\n }\n }\n const delay = this.retryDelay * 2 ** attempt * 1000\n const jitter = delay * 0.1 * Math.random()\n return delay + jitter\n }\n\n /**\n * Make an authenticated request to the API\n */\n private async request<T>(\n endpoint: string,\n method: string,\n body?: Record<string, unknown>\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.apiKey) {\n headers['X-API-Key'] = this.apiKey\n }\n\n if (this.userId) {\n headers['X-Blindfold-User-Id'] = this.userId\n }\n\n let lastError: Error = new NetworkError('Request failed')\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n })\n\n // Handle authentication errors\n if (response.status === 401 || response.status === 403) {\n throw new AuthenticationError('Authentication failed. Please check your API key.')\n }\n\n // Handle other error responses\n if (!response.ok) {\n let errorMessage = `API request failed with status ${response.status}`\n let responseBody: unknown\n\n try {\n responseBody = await response.json()\n const errorData = responseBody as APIErrorResponse\n errorMessage = errorData.detail || errorData.message || errorMessage\n } catch {\n // If we can't parse the error response, use the status text\n errorMessage = `${errorMessage}: ${response.statusText}`\n }\n\n throw new APIError(errorMessage, response.status, responseBody)\n }\n\n return (await response.json()) as T\n } catch (error) {\n // Never retry auth errors\n if (error instanceof AuthenticationError) {\n throw error\n }\n\n // Retry retryable API errors\n if (error instanceof APIError) {\n if (RETRYABLE_STATUS_CODES.has(error.statusCode) && attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt, error))\n continue\n }\n throw error\n }\n\n // Retry network errors\n if (error instanceof NetworkError) {\n lastError = error\n if (attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt))\n continue\n }\n throw error\n }\n\n // Handle raw fetch errors (network failures)\n if (error instanceof TypeError && error.message.includes('fetch')) {\n lastError = new NetworkError(\n 'Network request failed. Please check your connection and the API URL.'\n )\n if (attempt < this.maxRetries) {\n await sleep(this.retryWait(attempt))\n continue\n }\n throw lastError\n }\n\n // Non-retryable unknown errors\n throw new NetworkError(error instanceof Error ? error.message : 'Unknown error occurred')\n }\n }\n\n throw lastError\n }\n\n /**\n * Tokenize text by replacing sensitive information with tokens\n * @param text - Text to tokenize\n * @param config - Optional configuration\n * @returns Promise with tokenized text and mapping\n */\n async tokenize(text: string, config?: TokenizeConfig): Promise<TokenizeResponse> {\n if (this.useLocal) {\n return this.tokenizeLocal(text, config)\n }\n return this.request<TokenizeResponse>('/tokenize', 'POST', {\n text,\n ...config,\n })\n }\n\n private tokenizeLocal(text: string, config?: TokenizeConfig): TokenizeResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.tokenize(text, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, mapping: result.mapping, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Detect PII in text without modifying it\n *\n * Returns only the detected entities with their types, positions,\n * and confidence scores. The original text is not transformed.\n *\n * When no API key is set (or `mode: \"local\"`), detection runs locally\n * using the built-in regex scanner.\n *\n * @param text - Text to analyze for PII\n * @param config - Optional configuration (entities, score_threshold, policy)\n * @returns Promise with detected entities\n */\n async detect(text: string, config?: DetectConfig): Promise<DetectResponse> {\n if (this.useLocal) {\n return this.detectLocal(text, config)\n }\n return this.request<DetectResponse>('/detect', 'POST', {\n text,\n ...config,\n })\n }\n\n private detectLocal(text: string, config?: DetectConfig): DetectResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const matches = scanner.detect(text, resolved)\n\n const detected: DetectedEntity[] = []\n for (const m of matches) {\n if (threshold != null && m.score < threshold) continue\n detected.push({ type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score })\n }\n\n return { detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Detokenize text by replacing tokens with original values\n *\n * This method performs detokenization CLIENT-SIDE for better performance,\n * security, and to work offline. No API call is made.\n *\n * @param text - Tokenized text\n * @param mapping - Token mapping from tokenize response\n * @returns DetokenizeResponse with original text\n */\n detokenize(text: string, mapping: Record<string, string>): DetokenizeResponse {\n let result = text\n let replacements = 0\n\n // Sort tokens by length (longest first) to avoid partial replacements\n const sortedTokens = Object.keys(mapping).sort((a, b) => b.length - a.length)\n\n for (const token of sortedTokens) {\n const originalValue = mapping[token]\n const regex = new RegExp(token.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g')\n const matches = result.match(regex)\n\n if (matches) {\n result = result.replace(regex, originalValue)\n replacements += matches.length\n }\n }\n\n return {\n text: result,\n replacements_made: replacements,\n }\n }\n\n /**\n * Redact (permanently remove) sensitive information from text\n *\n * WARNING: Redaction is irreversible - original data cannot be restored!\n *\n * When no API key is set (or `mode: \"local\"`), redaction runs locally\n * using the built-in regex scanner, replacing PII with `<Entity Name>` placeholders.\n *\n * @param text - Text to redact\n * @param config - Optional configuration (masking_char, entities)\n * @returns Promise with redacted text and detected entities\n */\n async redact(text: string, config?: RedactConfig): Promise<RedactResponse> {\n if (this.useLocal) {\n return this.redactLocal(text, config)\n }\n return this.request<RedactResponse>('/redact', 'POST', {\n text,\n ...config,\n })\n }\n\n private redactLocal(text: string, config?: RedactConfig): RedactResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const [redactedText, matches] = scanner.redact(text, resolved)\n\n const detected: DetectedEntity[] = []\n for (const m of matches) {\n if (threshold != null && m.score < threshold) continue\n detected.push({ type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score })\n }\n\n return { text: redactedText, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Mask (partially hide) sensitive information from text\n *\n * @param text - Text to mask\n * @param config - Optional configuration (chars_to_show, from_end, masking_char, entities)\n * @returns Promise with masked text and detected entities\n */\n async mask(text: string, config?: MaskConfig): Promise<MaskResponse> {\n if (this.useLocal) {\n return this.maskLocal(text, config)\n }\n return this.request<MaskResponse>('/mask', 'POST', {\n text,\n ...config,\n })\n }\n\n private maskLocal(text: string, config?: MaskConfig): MaskResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.mask(\n text,\n config?.chars_to_show ?? 3,\n config?.from_end ?? false,\n config?.masking_char ?? '*',\n resolved\n )\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Synthesize (replace real data with synthetic fake data)\n *\n * When no API key is set (or `mode: \"local\"`), synthesis runs locally\n * using format-preserving random generation.\n *\n * @param text - Text to synthesize\n * @param config - Optional configuration (language, entities)\n * @returns Promise with synthetic text and detected entities\n */\n async synthesize(text: string, config?: SynthesizeConfig): Promise<SynthesizeResponse> {\n if (this.useLocal) {\n return this.synthesizeLocal(text, config)\n }\n return this.request<SynthesizeResponse>('/synthesize', 'POST', {\n text,\n ...config,\n })\n }\n\n private synthesizeLocal(text: string, config?: SynthesizeConfig): SynthesizeResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.synthesize(text, undefined, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Hash (replace with deterministic hash values)\n *\n * @param text - Text to hash\n * @param config - Optional configuration (hash_type, hash_prefix, hash_length, entities)\n * @returns Promise with hashed text and detected entities\n */\n async hash(text: string, config?: HashConfig): Promise<HashResponse> {\n if (this.useLocal) {\n return this.hashLocal(text, config)\n }\n return this.request<HashResponse>('/hash', 'POST', {\n text,\n ...config,\n })\n }\n\n private hashLocal(text: string, config?: HashConfig): HashResponse {\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.hash(\n text,\n config?.hash_type ?? 'sha256',\n config?.hash_prefix ?? 'HASH_',\n config?.hash_length ?? 16,\n resolved\n )\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n /**\n * Encrypt (reversibly protect) sensitive data in text using AES encryption\n *\n * @param text - Text to encrypt\n * @param config - Optional configuration (encryption_key, entities)\n * @returns Promise with encrypted text and detected entities\n */\n async encrypt(text: string, config?: EncryptConfig): Promise<EncryptResponse> {\n if (this.useLocal) {\n return this.encryptLocal(text, config)\n }\n return this.request<EncryptResponse>('/encrypt', 'POST', {\n text,\n ...config,\n })\n }\n\n private encryptLocal(text: string, config?: EncryptConfig): EncryptResponse {\n if (!config?.encryption_key) {\n throw new Error('encryption_key is required for local encryption mode')\n }\n const { entities: resolved, threshold } = this.resolvePolicy(config?.policy, config?.entities, config?.score_threshold)\n const scanner = this.getScanner()\n const result = scanner.encrypt(text, config.encryption_key, resolved)\n\n const detected: DetectedEntity[] = result.matches\n .filter((m) => threshold == null || m.score >= threshold)\n .map((m) => ({\n type: m.entityType, text: m.text, start: m.start, end: m.end, score: m.score,\n }))\n\n return { text: result.text, detected_entities: detected, entities_count: detected.length }\n }\n\n // ===== Batch methods =====\n\n /**\n * Tokenize multiple texts in a single request\n * @param texts - Array of texts to tokenize (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async tokenizeBatch(texts: string[], config?: TokenizeConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/tokenize', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Detect PII in multiple texts in a single request\n * @param texts - Array of texts to analyze (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async detectBatch(texts: string[], config?: DetectConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/detect', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Redact PII from multiple texts in a single request\n * @param texts - Array of texts to redact (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async redactBatch(texts: string[], config?: RedactConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/redact', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Mask PII in multiple texts in a single request\n * @param texts - Array of texts to mask (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async maskBatch(texts: string[], config?: MaskConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/mask', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Synthesize multiple texts in a single request\n * @param texts - Array of texts to synthesize (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async synthesizeBatch(texts: string[], config?: SynthesizeConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/synthesize', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Hash PII in multiple texts in a single request\n * @param texts - Array of texts to hash (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async hashBatch(texts: string[], config?: HashConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/hash', 'POST', {\n texts,\n ...config,\n })\n }\n\n /**\n * Encrypt PII in multiple texts in a single request\n * @param texts - Array of texts to encrypt (max 100)\n * @param config - Optional configuration\n * @returns Promise with batch results\n */\n async encryptBatch(texts: string[], config?: EncryptConfig): Promise<BatchResponse> {\n return this.request<BatchResponse>('/encrypt', 'POST', {\n texts,\n ...config,\n })\n }\n}\n","export { Blindfold } from './client'\nexport type {\n BlindfoldConfig,\n DetectConfig,\n DetectResponse,\n TokenizeConfig,\n TokenizeResponse,\n DetokenizeResponse,\n DetectedEntity,\n BatchResponse,\n APIErrorResponse,\n} from './types'\nexport { BlindfoldError, AuthenticationError, APIError, NetworkError } from './errors'\nexport { PIIScanner, EntityType } from './regex'\nexport type { PIIMatch } from './regex'\n"],"mappings":"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"}