( texts: string[], entityTypes: string[], language = 'en' )
| 220 | * can apply their own fail-safe (scrub). |
| 221 | */ |
| 222 | export async function maskPIIBatch( |
| 223 | texts: string[], |
| 224 | entityTypes: string[], |
| 225 | language = 'en' |
| 226 | ): Promise<string[]> { |
| 227 | if (texts.length === 0) return [] |
| 228 | |
| 229 | const result = new Array<string>(texts.length) |
| 230 | |
| 231 | await mapWithConcurrency(chunkIndicesByBudget(texts), CHUNK_CONCURRENCY, async (indices) => { |
| 232 | const chunkTexts = indices.map((i) => texts[i]) |
| 233 | const spansPerText = await analyzeBatch(chunkTexts, entityTypes, language) |
| 234 | |
| 235 | // A short/misaligned batch response would silently leave the unmatched |
| 236 | // strings unmasked (fail-open). Throw so the caller applies its fail-safe |
| 237 | // (scrub for logs, abort for in-flight stages) instead of leaking PII. |
| 238 | if (spansPerText.length !== chunkTexts.length) { |
| 239 | throw new Error( |
| 240 | `Presidio analyze_batch returned ${spansPerText.length} result(s) for ${chunkTexts.length} input(s)` |
| 241 | ) |
| 242 | } |
| 243 | |
| 244 | const toAnonymize: AnonymizeBatchItem[] = [] |
| 245 | const anonymizePositions: number[] = [] |
| 246 | indices.forEach((originalIndex, pos) => { |
| 247 | const spans = spansPerText[pos] ?? [] |
| 248 | if (spans.length === 0) { |
| 249 | result[originalIndex] = chunkTexts[pos] |
| 250 | return |
| 251 | } |
| 252 | toAnonymize.push({ text: chunkTexts[pos], analyzer_results: spans }) |
| 253 | anonymizePositions.push(pos) |
| 254 | }) |
| 255 | |
| 256 | const masked = await anonymizeBatch(toAnonymize) |
| 257 | if (masked.length !== toAnonymize.length) { |
| 258 | throw new Error( |
| 259 | `Presidio anonymize_batch returned ${masked.length} result(s) for ${toAnonymize.length} input(s)` |
| 260 | ) |
| 261 | } |
| 262 | anonymizePositions.forEach((pos, k) => { |
| 263 | result[indices[pos]] = masked[k] |
| 264 | }) |
| 265 | }) |
| 266 | |
| 267 | return result |
| 268 | } |
| 269 | |
| 270 | export { type PIIEntityType, SUPPORTED_PII_ENTITIES } from '@/lib/guardrails/pii-entities' |
no test coverage detected