Quick Start
Basic redaction
import { createRedactor } from "sensored";
const redactor = createRedactor({ presets: ["pii"], rules: {} });
const text = "Contact me at john@example.com or call 555-123-4567.";
const redacted = redactor.redact(text);
// "Contact me at [EMAIL_1] or call [PHONE_1]."The pii preset enables 96 detectors covering email, phone, payment cards, national IDs, and more. All matches are replaced with numbered type labels like [EMAIL_1], [PHONE_1].
Inspecting detections
Use inspect() to see what was found without transforming the text:
const redactor = createRedactor({ presets: ["pii"], rules: {} });
const inspection = redactor.inspect("Email: john@example.com");
// {
// text: "Email: john@example.com",
// groups: [
// {
// start: 7,
// end: 23,
// replacement: "[EMAIL_1]",
// matches: [
// {
// start: 7,
// end: 23,
// ruleId: "email",
// entityType: "email",
// reasons: ["email.regex"],
// value: "john@example.com"
// }
// ]
// }
// ]
// }Restoration
Enable restore: true to get a restoration map alongside the redacted text:
const redactor = createRedactor({ presets: ["pii"], rules: {}, restore: true });
const { text, map } = redactor.redact("Email: john@example.com");
// text: "Email: [EMAIL_1]"
// map: { "[EMAIL_1]": "john@example.com" }
const restored = redactor.restore(text, map);
// "Email: john@example.com"Streaming
Process continuous text streams without buffering the entire input:
const redactor = createRedactor({ presets: ["pii"], rules: {} });
async function* asyncChunks() {
yield "Contact me at john@";
yield "example.com for details.";
}
const stream = redactor.stream(asyncChunks());
for await (const event of stream) {
if (event.type === "text") {
process.stdout.write(event.text);
}
// event.type === "detection" gives you InspectionGroup with offsets
// event.type === "complete" gives you the optional restoration map
}
// Output: "Contact me at [EMAIL_1] for details."Custom detectors
Add your own detectors alongside the built-in ones:
import { createRedactor, type DetectorDefinition } from "sensored";
const employeeId: DetectorDefinition = {
id: "employee_id",
entityType: "employee_id",
pattern: /\bEMP-\d{6}\b/g,
replacement: "[EMPLOYEE_ID]",
};
const redactor = createRedactor({
presets: ["pii"],
rules: { employee_id: { action: "redact" } },
detectors: [employeeId],
});
const result = redactor.redact("Contact: EMP-123456");
// "Contact: [EMPLOYEE_ID]"Using transformations
Beyond simple redaction, you can mask, remove, format-preserve, or token-replace:
const redactor = createRedactor({
rules: {
email: { action: "mask", preserve: { first: 2 } },
phone: { action: "remove" },
payment_card: { action: "format-preserve" },
us_ssn: { action: "token-replace" },
},
});
redactor.redact("Email: john@example.com, Phone: 555-123-4567");
// "Email: jo************, "See the Transformations guide for details on each action.
AI confirmation
Opt into AI-powered verification to eliminate false positives. Jev confirms each detected candidate before redacting:
const redactor = createRedactor({
rules: { person_name_lite: { action: "redact" } },
semantic: {
provider: "jev",
apiKey: process.env.TYPESAFE_API_KEY!,
},
});
const result = await redactor.redactAsync("Contact John Smith today");
// result.text: "Contact [PERSON_NAME] today"
// result.detections[0].semanticConfirmed: trueSync redact() and stream() are unaffected. Fails open if the AI provider is unavailable. See AI Confirmation for full details.