{"_id":"@pinojs/redact","_rev":"2-5bf648ce0a7cbbda5538278c0168a959","name":"@pinojs/redact","dist-tags":{"latest":"0.4.0"},"versions":{"0.3.2":{"name":"@pinojs/redact","version":"0.3.2","keywords":["redact"],"author":{"name":"Matteo Collina","email":"hello@matteocollina.com"},"license":"MIT","_id":"@pinojs/redact@0.3.2","maintainers":[{"name":"matteo.collina","email":"hello@matteocollina.com"},{"name":"jsumners","email":"james.sumners@gmail.com"},{"name":"watson","email":"w@tson.dk"}],"homepage":"https://github.com/pinojs/redact#readme","bugs":{"url":"https://github.com/pinojs/redact/issues"},"dist":{"shasum":"81ee801e591e2d5e7f5a10bf67c89c952ff33982","tarball":"https://registry.npmjs.org/@pinojs/redact/-/redact-0.3.2.tgz","fileCount":20,"integrity":"sha512-aEc+x6TTxzja1Ofm117q7ujEdYPZUgnVG4xtPV8Y9F5RiBavnEMKOQv51DGxzymXVSUsvZ/o/S6vAReEUdUphw==","signatures":[{"sig":"MEQCIB9VNrasuxpBu+7qIW3VUPgRXgo0PX3wGdkJw2Kld3Q4AiA9vtsfLYJJCupfcVuwfU4AoQW3NDp/v/Ic6mz+9jgl0w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":100783},"main":"index.js","types":"index.d.ts","gitHead":"aef4bb449a25467329b2a99d2bf17bb58fd623c6","scripts":{"lint":"eslint .","test":"node --test && npm run test:types","bench":"node benchmarks/basic.js","lint:fix":"eslint . --fix","test:all":"node --test test/*.test.js","test:types":"tsd","test:integration":"node --test test/integration.test.js"},"_npmUser":{"name":"matteo.collina","email":"hello@matteocollina.com"},"repository":{"url":"git+https://github.com/pinojs/redact.git","type":"git"},"_npmVersion":"10.9.3","description":"Redact JS objects","directories":{},"_nodeVersion":"22.19.0","_hasShrinkwrap":false,"devDependencies":{"tsd":"^0.33.0","eslint":"^9.36.0","mitata":"^1.0.34","typescript":"^5.9.2","fast-redact":"^3.5.0","neostandard":"^0.12.2"},"_npmOperationalInternal":{"tmp":"tmp/redact_0.3.2_1760448177483_0.1618811444348749","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@pinojs/redact","version":"0.4.0","description":"Redact JS objects","main":"index.js","types":"index.d.ts","scripts":{"test":"node --test && npm run test:types","test:integration":"node --test test/integration.test.js","test:types":"tsd","test:all":"node --test test/*.test.js","lint":"eslint .","lint:fix":"eslint . --fix","bench":"node benchmarks/basic.js"},"repository":{"type":"git","url":"git+https://github.com/pinojs/redact.git"},"keywords":["redact"],"author":{"name":"Matteo Collina","email":"hello@matteocollina.com"},"license":"MIT","bugs":{"url":"https://github.com/pinojs/redact/issues"},"homepage":"https://github.com/pinojs/redact#readme","devDependencies":{"eslint":"^9.36.0","fast-redact":"^3.5.0","mitata":"^1.0.34","neostandard":"^0.12.2","tsd":"^0.33.0","typescript":"^5.9.2"},"gitHead":"c91398e51d0676ae8744eb4c1c7c5dbf8e18d4ed","_id":"@pinojs/redact@0.4.0","_nodeVersion":"22.20.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==","shasum":"c3de060dd12640dcc838516aa2a6803cc7b2e9d6","tarball":"https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz","fileCount":19,"unpackedSize":97351,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@pinojs%2fredact@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF+VLpQVlsHFiRVioa4u8BV+OQgrOnKsU23m86khx0lOAiEAqkMTe/EFBS5f5ORSsr5P7l+Bi6OqclUOiLxCWeRxY5c="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:a41fdc01-7d2f-4803-bb89-59bc5b8739d5"}},"directories":{},"maintainers":[{"name":"matteo.collina","email":"hello@matteocollina.com"},{"name":"jsumners","email":"james.sumners@gmail.com"},{"name":"watson","email":"w@tson.dk"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/redact_0.4.0_1760448296202_0.6456606094319262"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-14T13:22:57.423Z","modified":"2025-10-14T13:24:56.844Z","0.3.2":"2025-10-14T13:22:57.668Z","0.4.0":"2025-10-14T13:24:56.394Z"},"bugs":{"url":"https://github.com/pinojs/redact/issues"},"author":{"name":"Matteo Collina","email":"hello@matteocollina.com"},"license":"MIT","homepage":"https://github.com/pinojs/redact#readme","keywords":["redact"],"repository":{"type":"git","url":"git+https://github.com/pinojs/redact.git"},"description":"Redact JS objects","maintainers":[{"name":"matteo.collina","email":"hello@matteocollina.com"},{"name":"jsumners","email":"james.sumners@gmail.com"},{"name":"watson","email":"w@tson.dk"}],"readme":"# @pinojs/redact\n\n> Smart object redaction for JavaScript applications - safe AND fast!\n\nRedact JS objects with the same API as [fast-redact](https://github.com/davidmarkclements/fast-redact), but uses innovative **selective cloning** instead of mutating the original. This provides immutability guarantees with **performance competitive** to fast-redact for real-world usage patterns.\n\n## Install\n\n```bash\nnpm install @pinojs/redact\n```\n\n## Usage\n\n```js\nconst slowRedact = require('@pinojs/redact')\n\nconst redact = slowRedact({\n paths: ['headers.cookie', 'headers.authorization', 'user.password']\n})\n\nconst obj = {\n headers: {\n cookie: 'secret-session-token',\n authorization: 'Bearer abc123',\n 'x-forwarded-for': '192.168.1.1'\n },\n user: {\n name: 'john',\n password: 'secret123'\n }\n}\n\nconsole.log(redact(obj))\n// Output: {\"headers\":{\"cookie\":\"[REDACTED]\",\"authorization\":\"[REDACTED]\",\"x-forwarded-for\":\"192.168.1.1\"},\"user\":{\"name\":\"john\",\"password\":\"[REDACTED]\"}}\n\n// Original object is completely unchanged:\nconsole.log(obj.headers.cookie) // 'secret-session-token'\n```\n\n## API\n\n### slowRedact(options) → Function\n\nCreates a redaction function with the specified options.\n\n#### Options\n\n- **paths** `string[]` (required): An array of strings describing the nested location of a key in an object\n- **censor** `any` (optional, default: `'[REDACTED]'`): The value to replace sensitive data with. Can be a static value or function.\n- **serialize** `Function|boolean` (optional, default: `JSON.stringify`): Serialization function. Set to `false` to return the redacted object.\n- **remove** `boolean` (optional, default: `false`): Remove redacted keys from serialized output\n- **strict** `boolean` (optional, default: `true`): Throw on non-object values or pass through primitives\n\n#### Path Syntax\n\nSupports the same path syntax as fast-redact:\n\n- **Dot notation**: `'user.name'`, `'headers.cookie'`\n- **Bracket notation**: `'user[\"password\"]'`, `'headers[\"X-Forwarded-For\"]'`\n- **Array indices**: `'users[0].password'`, `'items[1].secret'`\n- **Wildcards**:\n - Terminal: `'users.*.password'` (redacts password for all users)\n - Intermediate: `'*.password'` (redacts password at any level)\n - Array wildcard: `'items.*'` (redacts all array elements)\n\n#### Examples\n\n**Custom censor value:**\n```js\nconst redact = slowRedact({\n paths: ['password'],\n censor: '***HIDDEN***'\n})\n```\n\n**Dynamic censor function:**\n```js\nconst redact = slowRedact({\n paths: ['password'],\n censor: (value, path) => `REDACTED:${path}`\n})\n```\n\n**Return object instead of JSON string:**\n```js\nconst redact = slowRedact({\n paths: ['secret'],\n serialize: false\n})\n\nconst result = redact({ secret: 'hidden', public: 'data' })\nconsole.log(result.secret) // '[REDACTED]'\nconsole.log(result.public) // 'data'\n\n// Restore original values\nconst restored = result.restore()\nconsole.log(restored.secret) // 'hidden'\n```\n\n**Custom serialization:**\n```js\nconst redact = slowRedact({\n paths: ['password'],\n serialize: obj => JSON.stringify(obj, null, 2)\n})\n```\n\n**Remove keys instead of redacting:**\n```js\nconst redact = slowRedact({\n paths: ['password', 'user.secret'],\n remove: true\n})\n\nconst obj = { username: 'john', password: 'secret123', user: { name: 'Jane', secret: 'hidden' } }\nconsole.log(redact(obj))\n// Output: {\"username\":\"john\",\"user\":{\"name\":\"Jane\"}}\n// Note: 'password' and 'user.secret' are completely absent, not redacted\n```\n\n**Wildcard patterns:**\n```js\n// Redact all properties in secrets object\nconst redact1 = slowRedact({ paths: ['secrets.*'] })\n\n// Redact password for any user\nconst redact2 = slowRedact({ paths: ['users.*.password'] })\n\n// Redact all items in an array\nconst redact3 = slowRedact({ paths: ['items.*'] })\n\n// Remove all secrets instead of redacting them\nconst redact4 = slowRedact({ paths: ['secrets.*'], remove: true })\n```\n\n## Key Differences from fast-redact\n\n### Safety First\n- **No mutation**: Original objects are never modified\n- **Selective cloning**: Only clones paths that need redaction, shares references for everything else\n- **Restore capability**: Can restore original values when `serialize: false`\n\n### Feature Compatibility\n- **Remove option**: Full compatibility with fast-redact's `remove: true` option to completely omit keys from output\n- **All path patterns**: Supports same syntax including wildcards, bracket notation, and array indices\n- **Censor functions**: Dynamic censoring with path information passed as arrays\n- **Serialization**: Custom serializers and `serialize: false` mode\n\n### Smart Performance Approach\n- **Selective cloning**: Analyzes redaction paths and only clones necessary object branches\n- **Reference sharing**: Non-redacted properties maintain original object references\n- **Memory efficiency**: Dramatically reduced memory usage for large objects with minimal redaction\n- **Setup-time optimization**: Path analysis happens once during setup, not per redaction\n\n### When to Use @pinojs/redact\n- When immutability is critical\n- When you need to preserve original objects\n- When objects are shared across multiple contexts\n- In functional programming environments\n- When debugging and you need to compare before/after\n- **Large objects with selective redaction** (now performance-competitive!)\n- When memory efficiency with reference sharing is important\n\n### When to Use fast-redact\n- When absolute maximum performance is critical\n- In extremely high-throughput scenarios (>100,000 ops/sec)\n- When you control the object lifecycle and mutation is acceptable\n- Very small objects where setup overhead matters\n\n## Performance Benchmarks\n\n@pinojs/redact uses **selective cloning** that provides good performance while maintaining immutability guarantees:\n\n### Performance Results\n\n| Operation Type | @pinojs/redact | fast-redact | Performance Ratio |\n|---------------|-------------|-------------|-------------------|\n| **Small objects** | ~690ns | ~200ns | ~3.5x slower |\n| **Large objects (minimal redaction)** | **~18μs** | ~17μs | **~same performance** |\n| **Large objects (wildcards)** | **~48μs** | ~37μs | **~1.3x slower** |\n| **No redaction (large objects)** | **~18μs** | ~17μs | **~same performance** |\n\n### Performance Improvements\n\n@pinojs/redact is performance-competitive with fast-redact for large objects.\n\n1. **Selective cloning approach**: Only clones object paths that need redaction\n2. **Reference sharing**: Non-redacted properties share original object references\n3. **Setup-time optimization**: Path analysis happens once, not per redaction\n4. **Memory efficiency**: Dramatically reduced memory usage for typical use cases\n\n### Benchmark Details\n\n**Small Objects (~180 bytes)**:\n- @pinojs/redact: **690ns** per operation\n- fast-redact: **200ns** per operation\n- **Slight setup overhead for small objects**\n\n**Large Objects (~18KB, minimal redaction)**:\n- @pinojs/redact: **18μs** per operation\n- fast-redact: **17μs** per operation\n- Near-identical performance\n\n**Large Objects (~18KB, wildcard patterns)**:\n- @pinojs/redact: **48μs** per operation\n- fast-redact: **37μs** per operation\n- Competitive performance for complex patterns\n\n**Memory Considerations**:\n- @pinojs/redact: **Selective reference sharing** (much lower memory usage than before)\n- fast-redact: Mutates in-place (lowest memory usage)\n- Large objects with few redacted paths now share most references\n\n### When Performance Matters\n\nChoose **fast-redact** when:\n- Absolute maximum performance is critical (>100,000 ops/sec)\n- Working with very small objects frequently\n- Mutation is acceptable and controlled\n- Every microsecond counts\n\nChoose **@pinojs/redact** when:\n- Immutability is required (with competitive performance)\n- Objects are shared across contexts\n- Large objects with selective redaction\n- Memory efficiency through reference sharing is important\n- Safety and functionality are priorities\n- Most production applications (performance gap is minimal)\n\nRun benchmarks yourself:\n```bash\nnpm run bench\n```\n\n## How Selective Cloning Works\n\n@pinojs/redact uses an innovative **selective cloning** approach that provides immutability guarantees while dramatically improving performance:\n\n### Traditional Approach (before optimization)\n```js\n// Old approach: Deep clone entire object, then redact\nconst fullClone = deepClone(originalObject) // Clone everything\nredact(fullClone, paths) // Then redact specific paths\n```\n\n### Selective Cloning Approach (current)\n```js\n// New approach: Analyze paths, clone only what's needed\nconst pathStructure = buildPathStructure(paths) // One-time setup\nconst selectiveClone = cloneOnlyNeededPaths(obj, pathStructure) // Smart cloning\nredact(selectiveClone, paths) // Redact pre-identified paths\n```\n\n### Key Innovations\n\n1. **Path Analysis**: Pre-processes redaction paths into an efficient tree structure\n2. **Selective Cloning**: Only creates new objects for branches that contain redaction targets\n3. **Reference Sharing**: Non-redacted properties maintain exact same object references\n4. **Setup Optimization**: Path parsing happens once during redactor creation, not per redaction\n\n### Example: Reference Sharing in Action\n\n```js\nconst largeConfig = {\n database: { /* large config object */ },\n api: { /* another large config */ },\n secrets: { password: 'hidden', apiKey: 'secret' }\n}\n\nconst redact = slowRedact({ paths: ['secrets.password'] })\nconst result = redact(largeConfig)\n\n// Only secrets object is cloned, database and api share original references\nconsole.log(result.database === largeConfig.database) // true - shared reference!\nconsole.log(result.api === largeConfig.api) // true - shared reference!\nconsole.log(result.secrets === largeConfig.secrets) // false - cloned for redaction\n```\n\nThis approach provides **immutability where it matters** while **sharing references where it's safe**.\n\n## Remove Option\n\nThe `remove: true` option provides full compatibility with fast-redact's key removal functionality:\n\n```js\nconst redact = slowRedact({\n paths: ['password', 'secrets.*', 'users.*.credentials'],\n remove: true\n})\n\nconst data = {\n username: 'john',\n password: 'secret123',\n secrets: { apiKey: 'abc', token: 'xyz' },\n users: [\n { name: 'Alice', credentials: { password: 'pass1' } },\n { name: 'Bob', credentials: { password: 'pass2' } }\n ]\n}\n\nconsole.log(redact(data))\n// Output: {\"username\":\"john\",\"secrets\":{},\"users\":[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]}\n```\n\n### Remove vs Redact Behavior\n\n| Option | Behavior | Output Example |\n|--------|----------|----------------|\n| Default (redact) | Replaces values with censor | `{\"password\":\"[REDACTED]\"}` |\n| `remove: true` | Completely omits keys | `{}` |\n\n### Compatibility Notes\n\n- **Same output as fast-redact**: Identical JSON output when using `remove: true`\n- **Wildcard support**: Works with all wildcard patterns (`*`, `users.*`, `items.*.secret`)\n- **Array handling**: Array items are set to `undefined` (omitted in JSON output)\n- **Nested paths**: Supports deep removal (`users.*.credentials.password`)\n- **Serialize compatibility**: Only works with `JSON.stringify` serializer (like fast-redact)\n\n## Testing\n\n```bash\n# Run unit tests\nnpm test\n\n# Run integration tests comparing with fast-redact\nnpm run test:integration\n\n# Run all tests (unit + integration)\nnpm run test:all\n\n# Run benchmarks\nnpm run bench\n```\n\n### Test Coverage\n\n- **16 unit tests**: Core functionality and edge cases\n- **16 integration tests**: Output compatibility with fast-redact\n- **All major features**: Paths, wildcards, serialization, custom censors\n- **Performance benchmarks**: Direct comparison with fast-redact\n\n## License\n\nMIT\n\n## Contributing\n\nPull requests welcome! Please ensure all tests pass and add tests for new features.","readmeFilename":"README.md"}