First commit
This commit is contained in:
350
node_modules/@pinojs/redact/README.md
generated
vendored
Normal file
350
node_modules/@pinojs/redact/README.md
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
# @pinojs/redact
|
||||
|
||||
> Smart object redaction for JavaScript applications - safe AND fast!
|
||||
|
||||
Redact 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.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @pinojs/redact
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const slowRedact = require('@pinojs/redact')
|
||||
|
||||
const redact = slowRedact({
|
||||
paths: ['headers.cookie', 'headers.authorization', 'user.password']
|
||||
})
|
||||
|
||||
const obj = {
|
||||
headers: {
|
||||
cookie: 'secret-session-token',
|
||||
authorization: 'Bearer abc123',
|
||||
'x-forwarded-for': '192.168.1.1'
|
||||
},
|
||||
user: {
|
||||
name: 'john',
|
||||
password: 'secret123'
|
||||
}
|
||||
}
|
||||
|
||||
console.log(redact(obj))
|
||||
// Output: {"headers":{"cookie":"[REDACTED]","authorization":"[REDACTED]","x-forwarded-for":"192.168.1.1"},"user":{"name":"john","password":"[REDACTED]"}}
|
||||
|
||||
// Original object is completely unchanged:
|
||||
console.log(obj.headers.cookie) // 'secret-session-token'
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### slowRedact(options) → Function
|
||||
|
||||
Creates a redaction function with the specified options.
|
||||
|
||||
#### Options
|
||||
|
||||
- **paths** `string[]` (required): An array of strings describing the nested location of a key in an object
|
||||
- **censor** `any` (optional, default: `'[REDACTED]'`): The value to replace sensitive data with. Can be a static value or function.
|
||||
- **serialize** `Function|boolean` (optional, default: `JSON.stringify`): Serialization function. Set to `false` to return the redacted object.
|
||||
- **remove** `boolean` (optional, default: `false`): Remove redacted keys from serialized output
|
||||
- **strict** `boolean` (optional, default: `true`): Throw on non-object values or pass through primitives
|
||||
|
||||
#### Path Syntax
|
||||
|
||||
Supports the same path syntax as fast-redact:
|
||||
|
||||
- **Dot notation**: `'user.name'`, `'headers.cookie'`
|
||||
- **Bracket notation**: `'user["password"]'`, `'headers["X-Forwarded-For"]'`
|
||||
- **Array indices**: `'users[0].password'`, `'items[1].secret'`
|
||||
- **Wildcards**:
|
||||
- Terminal: `'users.*.password'` (redacts password for all users)
|
||||
- Intermediate: `'*.password'` (redacts password at any level)
|
||||
- Array wildcard: `'items.*'` (redacts all array elements)
|
||||
|
||||
#### Examples
|
||||
|
||||
**Custom censor value:**
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['password'],
|
||||
censor: '***HIDDEN***'
|
||||
})
|
||||
```
|
||||
|
||||
**Dynamic censor function:**
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['password'],
|
||||
censor: (value, path) => `REDACTED:${path}`
|
||||
})
|
||||
```
|
||||
|
||||
**Return object instead of JSON string:**
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['secret'],
|
||||
serialize: false
|
||||
})
|
||||
|
||||
const result = redact({ secret: 'hidden', public: 'data' })
|
||||
console.log(result.secret) // '[REDACTED]'
|
||||
console.log(result.public) // 'data'
|
||||
|
||||
// Restore original values
|
||||
const restored = result.restore()
|
||||
console.log(restored.secret) // 'hidden'
|
||||
```
|
||||
|
||||
**Custom serialization:**
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['password'],
|
||||
serialize: obj => JSON.stringify(obj, null, 2)
|
||||
})
|
||||
```
|
||||
|
||||
**Remove keys instead of redacting:**
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['password', 'user.secret'],
|
||||
remove: true
|
||||
})
|
||||
|
||||
const obj = { username: 'john', password: 'secret123', user: { name: 'Jane', secret: 'hidden' } }
|
||||
console.log(redact(obj))
|
||||
// Output: {"username":"john","user":{"name":"Jane"}}
|
||||
// Note: 'password' and 'user.secret' are completely absent, not redacted
|
||||
```
|
||||
|
||||
**Wildcard patterns:**
|
||||
```js
|
||||
// Redact all properties in secrets object
|
||||
const redact1 = slowRedact({ paths: ['secrets.*'] })
|
||||
|
||||
// Redact password for any user
|
||||
const redact2 = slowRedact({ paths: ['users.*.password'] })
|
||||
|
||||
// Redact all items in an array
|
||||
const redact3 = slowRedact({ paths: ['items.*'] })
|
||||
|
||||
// Remove all secrets instead of redacting them
|
||||
const redact4 = slowRedact({ paths: ['secrets.*'], remove: true })
|
||||
```
|
||||
|
||||
## Key Differences from fast-redact
|
||||
|
||||
### Safety First
|
||||
- **No mutation**: Original objects are never modified
|
||||
- **Selective cloning**: Only clones paths that need redaction, shares references for everything else
|
||||
- **Restore capability**: Can restore original values when `serialize: false`
|
||||
|
||||
### Feature Compatibility
|
||||
- **Remove option**: Full compatibility with fast-redact's `remove: true` option to completely omit keys from output
|
||||
- **All path patterns**: Supports same syntax including wildcards, bracket notation, and array indices
|
||||
- **Censor functions**: Dynamic censoring with path information passed as arrays
|
||||
- **Serialization**: Custom serializers and `serialize: false` mode
|
||||
|
||||
### Smart Performance Approach
|
||||
- **Selective cloning**: Analyzes redaction paths and only clones necessary object branches
|
||||
- **Reference sharing**: Non-redacted properties maintain original object references
|
||||
- **Memory efficiency**: Dramatically reduced memory usage for large objects with minimal redaction
|
||||
- **Setup-time optimization**: Path analysis happens once during setup, not per redaction
|
||||
|
||||
### When to Use @pinojs/redact
|
||||
- When immutability is critical
|
||||
- When you need to preserve original objects
|
||||
- When objects are shared across multiple contexts
|
||||
- In functional programming environments
|
||||
- When debugging and you need to compare before/after
|
||||
- **Large objects with selective redaction** (now performance-competitive!)
|
||||
- When memory efficiency with reference sharing is important
|
||||
|
||||
### When to Use fast-redact
|
||||
- When absolute maximum performance is critical
|
||||
- In extremely high-throughput scenarios (>100,000 ops/sec)
|
||||
- When you control the object lifecycle and mutation is acceptable
|
||||
- Very small objects where setup overhead matters
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
@pinojs/redact uses **selective cloning** that provides good performance while maintaining immutability guarantees:
|
||||
|
||||
### Performance Results
|
||||
|
||||
| Operation Type | @pinojs/redact | fast-redact | Performance Ratio |
|
||||
|---------------|-------------|-------------|-------------------|
|
||||
| **Small objects** | ~690ns | ~200ns | ~3.5x slower |
|
||||
| **Large objects (minimal redaction)** | **~18μs** | ~17μs | **~same performance** |
|
||||
| **Large objects (wildcards)** | **~48μs** | ~37μs | **~1.3x slower** |
|
||||
| **No redaction (large objects)** | **~18μs** | ~17μs | **~same performance** |
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@pinojs/redact is performance-competitive with fast-redact for large objects.
|
||||
|
||||
1. **Selective cloning approach**: Only clones object paths that need redaction
|
||||
2. **Reference sharing**: Non-redacted properties share original object references
|
||||
3. **Setup-time optimization**: Path analysis happens once, not per redaction
|
||||
4. **Memory efficiency**: Dramatically reduced memory usage for typical use cases
|
||||
|
||||
### Benchmark Details
|
||||
|
||||
**Small Objects (~180 bytes)**:
|
||||
- @pinojs/redact: **690ns** per operation
|
||||
- fast-redact: **200ns** per operation
|
||||
- **Slight setup overhead for small objects**
|
||||
|
||||
**Large Objects (~18KB, minimal redaction)**:
|
||||
- @pinojs/redact: **18μs** per operation
|
||||
- fast-redact: **17μs** per operation
|
||||
- Near-identical performance
|
||||
|
||||
**Large Objects (~18KB, wildcard patterns)**:
|
||||
- @pinojs/redact: **48μs** per operation
|
||||
- fast-redact: **37μs** per operation
|
||||
- Competitive performance for complex patterns
|
||||
|
||||
**Memory Considerations**:
|
||||
- @pinojs/redact: **Selective reference sharing** (much lower memory usage than before)
|
||||
- fast-redact: Mutates in-place (lowest memory usage)
|
||||
- Large objects with few redacted paths now share most references
|
||||
|
||||
### When Performance Matters
|
||||
|
||||
Choose **fast-redact** when:
|
||||
- Absolute maximum performance is critical (>100,000 ops/sec)
|
||||
- Working with very small objects frequently
|
||||
- Mutation is acceptable and controlled
|
||||
- Every microsecond counts
|
||||
|
||||
Choose **@pinojs/redact** when:
|
||||
- Immutability is required (with competitive performance)
|
||||
- Objects are shared across contexts
|
||||
- Large objects with selective redaction
|
||||
- Memory efficiency through reference sharing is important
|
||||
- Safety and functionality are priorities
|
||||
- Most production applications (performance gap is minimal)
|
||||
|
||||
Run benchmarks yourself:
|
||||
```bash
|
||||
npm run bench
|
||||
```
|
||||
|
||||
## How Selective Cloning Works
|
||||
|
||||
@pinojs/redact uses an innovative **selective cloning** approach that provides immutability guarantees while dramatically improving performance:
|
||||
|
||||
### Traditional Approach (before optimization)
|
||||
```js
|
||||
// Old approach: Deep clone entire object, then redact
|
||||
const fullClone = deepClone(originalObject) // Clone everything
|
||||
redact(fullClone, paths) // Then redact specific paths
|
||||
```
|
||||
|
||||
### Selective Cloning Approach (current)
|
||||
```js
|
||||
// New approach: Analyze paths, clone only what's needed
|
||||
const pathStructure = buildPathStructure(paths) // One-time setup
|
||||
const selectiveClone = cloneOnlyNeededPaths(obj, pathStructure) // Smart cloning
|
||||
redact(selectiveClone, paths) // Redact pre-identified paths
|
||||
```
|
||||
|
||||
### Key Innovations
|
||||
|
||||
1. **Path Analysis**: Pre-processes redaction paths into an efficient tree structure
|
||||
2. **Selective Cloning**: Only creates new objects for branches that contain redaction targets
|
||||
3. **Reference Sharing**: Non-redacted properties maintain exact same object references
|
||||
4. **Setup Optimization**: Path parsing happens once during redactor creation, not per redaction
|
||||
|
||||
### Example: Reference Sharing in Action
|
||||
|
||||
```js
|
||||
const largeConfig = {
|
||||
database: { /* large config object */ },
|
||||
api: { /* another large config */ },
|
||||
secrets: { password: 'hidden', apiKey: 'secret' }
|
||||
}
|
||||
|
||||
const redact = slowRedact({ paths: ['secrets.password'] })
|
||||
const result = redact(largeConfig)
|
||||
|
||||
// Only secrets object is cloned, database and api share original references
|
||||
console.log(result.database === largeConfig.database) // true - shared reference!
|
||||
console.log(result.api === largeConfig.api) // true - shared reference!
|
||||
console.log(result.secrets === largeConfig.secrets) // false - cloned for redaction
|
||||
```
|
||||
|
||||
This approach provides **immutability where it matters** while **sharing references where it's safe**.
|
||||
|
||||
## Remove Option
|
||||
|
||||
The `remove: true` option provides full compatibility with fast-redact's key removal functionality:
|
||||
|
||||
```js
|
||||
const redact = slowRedact({
|
||||
paths: ['password', 'secrets.*', 'users.*.credentials'],
|
||||
remove: true
|
||||
})
|
||||
|
||||
const data = {
|
||||
username: 'john',
|
||||
password: 'secret123',
|
||||
secrets: { apiKey: 'abc', token: 'xyz' },
|
||||
users: [
|
||||
{ name: 'Alice', credentials: { password: 'pass1' } },
|
||||
{ name: 'Bob', credentials: { password: 'pass2' } }
|
||||
]
|
||||
}
|
||||
|
||||
console.log(redact(data))
|
||||
// Output: {"username":"john","secrets":{},"users":[{"name":"Alice"},{"name":"Bob"}]}
|
||||
```
|
||||
|
||||
### Remove vs Redact Behavior
|
||||
|
||||
| Option | Behavior | Output Example |
|
||||
|--------|----------|----------------|
|
||||
| Default (redact) | Replaces values with censor | `{"password":"[REDACTED]"}` |
|
||||
| `remove: true` | Completely omits keys | `{}` |
|
||||
|
||||
### Compatibility Notes
|
||||
|
||||
- **Same output as fast-redact**: Identical JSON output when using `remove: true`
|
||||
- **Wildcard support**: Works with all wildcard patterns (`*`, `users.*`, `items.*.secret`)
|
||||
- **Array handling**: Array items are set to `undefined` (omitted in JSON output)
|
||||
- **Nested paths**: Supports deep removal (`users.*.credentials.password`)
|
||||
- **Serialize compatibility**: Only works with `JSON.stringify` serializer (like fast-redact)
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
npm test
|
||||
|
||||
# Run integration tests comparing with fast-redact
|
||||
npm run test:integration
|
||||
|
||||
# Run all tests (unit + integration)
|
||||
npm run test:all
|
||||
|
||||
# Run benchmarks
|
||||
npm run bench
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- **16 unit tests**: Core functionality and edge cases
|
||||
- **16 integration tests**: Output compatibility with fast-redact
|
||||
- **All major features**: Paths, wildcards, serialization, custom censors
|
||||
- **Performance benchmarks**: Direct comparison with fast-redact
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests welcome! Please ensure all tests pass and add tests for new features.
|
||||
Reference in New Issue
Block a user