asherah 4.0.34 → 4.0.35
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/README.md +274 -186
- package/index.d.ts +343 -16
- package/npm/index.js +1 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -14,260 +14,348 @@ npm install asherah
|
|
|
14
14
|
|
|
15
15
|
Requires Node.js >= 18.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Choosing an API style
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Two API styles are exposed; both are fully supported and produce the same
|
|
20
|
+
wire format. New code should prefer the **Factory / Session API**.
|
|
21
|
+
|
|
22
|
+
| Style | When to use |
|
|
23
|
+
|---|---|
|
|
24
|
+
| **Static / module-level** (`asherah.setup`, `asherah.encrypt`, …) | Drop-in compatibility with the canonical `godaddy/asherah-node` package. Simplest call surface. Singleton lifecycle (`setup()` once, `shutdown()` once). |
|
|
25
|
+
| **Factory / Session** (`new SessionFactory(...)`, `factory.getSession(...)`) | Recommended for new code. Explicit lifecycle, no hidden singleton, multi-tenant isolation is obvious in code. |
|
|
26
|
+
|
|
27
|
+
A complete runnable example exercising both styles plus async, log hook, and
|
|
28
|
+
metrics hook is in [`samples/node/index.mjs`](../samples/node/index.mjs).
|
|
29
|
+
|
|
30
|
+
## Quick start (static API)
|
|
20
31
|
|
|
21
32
|
```js
|
|
22
33
|
const asherah = require('asherah');
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
// In production, use kms: 'aws' with a region map.
|
|
26
|
-
process.env.STATIC_MASTER_KEY_HEX = '22'.repeat(32);
|
|
35
|
+
process.env.STATIC_MASTER_KEY_HEX = '22'.repeat(32); // testing only
|
|
27
36
|
|
|
28
37
|
asherah.setup({
|
|
29
38
|
serviceName: 'my-service',
|
|
30
39
|
productId: 'my-product',
|
|
31
|
-
metastore: 'memory', // testing only
|
|
32
|
-
kms: 'static', // testing only
|
|
33
|
-
enableSessionCaching: true,
|
|
40
|
+
metastore: 'memory', // testing only — use 'rdbms' or 'dynamodb' in production
|
|
41
|
+
kms: 'static', // testing only — use 'aws' in production
|
|
34
42
|
});
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const plaintext = asherah.decrypt('my-partition', ciphertext);
|
|
39
|
-
console.log(plaintext.toString()); // 'secret data'
|
|
40
|
-
|
|
41
|
-
// Or use the string convenience methods
|
|
42
|
-
const ct = asherah.encryptString('my-partition', 'hello world');
|
|
43
|
-
const pt = asherah.decryptString('my-partition', ct);
|
|
44
|
-
console.log(pt); // 'hello world'
|
|
44
|
+
const ct = asherah.encryptString('user-42', 'secret');
|
|
45
|
+
const pt = asherah.decryptString('user-42', ct);
|
|
45
46
|
|
|
46
47
|
asherah.shutdown();
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
The `SessionFactory` / `AsherahSession` pattern is preferred for production. It
|
|
52
|
-
avoids the global singleton and gives you explicit control over session
|
|
53
|
-
lifetimes.
|
|
50
|
+
## Quick start (factory / session API)
|
|
54
51
|
|
|
55
52
|
```js
|
|
56
53
|
const { SessionFactory } = require('asherah');
|
|
57
54
|
|
|
58
|
-
process.env.STATIC_MASTER_KEY_HEX = '22'.repeat(32);
|
|
59
|
-
|
|
60
55
|
const factory = new SessionFactory({
|
|
61
56
|
serviceName: 'my-service',
|
|
62
57
|
productId: 'my-product',
|
|
63
|
-
metastore: 'memory',
|
|
64
|
-
kms: 'static',
|
|
58
|
+
metastore: 'memory',
|
|
59
|
+
kms: 'static',
|
|
65
60
|
});
|
|
61
|
+
const session = factory.getSession('user-42');
|
|
62
|
+
try {
|
|
63
|
+
const ct = session.encryptString('secret');
|
|
64
|
+
const pt = session.decryptString(ct);
|
|
65
|
+
} finally {
|
|
66
|
+
session.close();
|
|
67
|
+
factory.close();
|
|
68
|
+
}
|
|
69
|
+
```
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const ct = session.encrypt(Buffer.from('secret'));
|
|
70
|
-
const pt = session.decrypt(ct);
|
|
71
|
-
console.log(pt.toString()); // 'secret'
|
|
71
|
+
## Async API
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const pt2 = session.decryptString(ct2);
|
|
76
|
-
console.log(pt2); // 'hello'
|
|
73
|
+
Every sync function has a `*Async` counterpart that returns a `Promise` and
|
|
74
|
+
runs on the Rust tokio runtime — the Node event loop is not blocked.
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
```js
|
|
77
|
+
await asherah.setupAsync(config);
|
|
78
|
+
const ct = await asherah.encryptStringAsync('user-42', 'secret');
|
|
79
|
+
const pt = await asherah.decryptStringAsync('user-42', ct);
|
|
80
|
+
await asherah.shutdownAsync();
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
| Metastore | Async path | Blocks event loop? |
|
|
84
|
+
|-----------|------------|---------------------|
|
|
85
|
+
| In-memory | tokio worker thread | No |
|
|
86
|
+
| DynamoDB | true async AWS SDK calls on tokio | No |
|
|
87
|
+
| MySQL | `spawn_blocking` (sync driver on tokio thread pool) | No |
|
|
88
|
+
| Postgres | `spawn_blocking` (sync driver on tokio thread pool) | No |
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
Tradeoff: ~12µs async vs ~1µs sync per call (hot cache, 64 B payload). Use
|
|
91
|
+
sync in tight loops where latency matters; async when you need to keep the
|
|
92
|
+
event loop responsive.
|
|
87
93
|
|
|
88
|
-
##
|
|
94
|
+
## Observability hooks
|
|
95
|
+
|
|
96
|
+
### Log hook
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
Receive every log event from the Rust core (encrypt/decrypt path, metastore
|
|
99
|
+
drivers, KMS clients).
|
|
92
100
|
|
|
93
101
|
```js
|
|
94
|
-
|
|
102
|
+
asherah.setLogHook((event) => {
|
|
103
|
+
// event = { level, message, target }
|
|
104
|
+
// level ∈ 'trace' | 'debug' | 'info' | 'warn' | 'error'
|
|
105
|
+
if (event.level === 'warn' || event.level === 'error') {
|
|
106
|
+
console.error(`[asherah ${event.level}] ${event.message}`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
95
109
|
|
|
96
|
-
|
|
110
|
+
// later, to deregister:
|
|
111
|
+
asherah.setLogHook(null);
|
|
112
|
+
```
|
|
97
113
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
The snake_case alias `set_log_hook` also accepts the canonical
|
|
115
|
+
`(level: number, message: string)` signature for backward compatibility with
|
|
116
|
+
the Go-based `asherah` npm package.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
asherah.set_log_hook((level, message) => {
|
|
120
|
+
// level is a number 0..4 (0=trace, 1=debug, 2=info, 3=warn, 4=error)
|
|
121
|
+
console.log(`[level ${level}] ${message}`);
|
|
103
122
|
});
|
|
123
|
+
```
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
console.log(pt); // 'secret'
|
|
125
|
+
Log events are delivered via N-API ThreadsafeFunction — they run on the Node
|
|
126
|
+
main thread, so synchronous code in the callback is safe.
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
### Metrics hook
|
|
129
|
+
|
|
130
|
+
Receive timing events for encrypt/decrypt/store/load and counter events for
|
|
131
|
+
cache hit/miss/stale.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
asherah.setMetricsHook((event) => {
|
|
135
|
+
switch (event.type) {
|
|
136
|
+
case 'encrypt':
|
|
137
|
+
case 'decrypt':
|
|
138
|
+
case 'store':
|
|
139
|
+
case 'load':
|
|
140
|
+
// event = { type, durationNs }
|
|
141
|
+
myHistogram.observe(event.type, event.durationNs / 1e6);
|
|
142
|
+
break;
|
|
143
|
+
case 'cache_hit':
|
|
144
|
+
case 'cache_miss':
|
|
145
|
+
case 'cache_stale':
|
|
146
|
+
// event = { type, name }
|
|
147
|
+
myCounter.inc({ result: event.type, cache: event.name });
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// later:
|
|
153
|
+
asherah.setMetricsHook(null);
|
|
110
154
|
```
|
|
111
155
|
|
|
112
|
-
|
|
156
|
+
Metrics collection is enabled automatically when a hook is installed, and
|
|
157
|
+
disabled when cleared.
|
|
113
158
|
|
|
114
|
-
|
|
115
|
-
loop. The exact execution strategy depends on the metastore:
|
|
159
|
+
## Input contract
|
|
116
160
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
**Partition ID** (`null`, `undefined`, `""`): always rejected as
|
|
162
|
+
programming errors with `TypeError` (sync) or rejected `Promise`
|
|
163
|
+
(async). No row is ever written to the metastore under a degenerate
|
|
164
|
+
partition ID.
|
|
165
|
+
|
|
166
|
+
**Plaintext** to encrypt:
|
|
167
|
+
- `null` / `undefined` → `TypeError` from N-API marshalling (sync) or
|
|
168
|
+
rejected `Promise` (async).
|
|
169
|
+
- Empty `string` (`""`) and `Buffer.alloc(0)` are **valid** plaintexts.
|
|
170
|
+
`encrypt(...)` / `encryptString(...)` produces a real `DataRowRecord`
|
|
171
|
+
envelope; the matching `decrypt(...)` returns exactly `""` or an
|
|
172
|
+
empty `Buffer`.
|
|
123
173
|
|
|
124
|
-
**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
174
|
+
**Ciphertext** to decrypt:
|
|
175
|
+
- `null` / `undefined` → `TypeError`.
|
|
176
|
+
- Empty `string` / empty `Buffer` → `Error` from native layer (not
|
|
177
|
+
valid `DataRowRecord` JSON).
|
|
178
|
+
|
|
179
|
+
**Do not short-circuit empty plaintext encryption in caller code** —
|
|
180
|
+
empty data is real data, encrypting it produces a genuine envelope, and
|
|
181
|
+
skipping encryption leaks the fact that the value was empty. See
|
|
182
|
+
[docs/input-contract.md](../docs/input-contract.md) for the full
|
|
183
|
+
rationale.
|
|
128
184
|
|
|
129
185
|
## Configuration
|
|
130
186
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
187
|
+
All fields can be passed in `camelCase` (native) or `PascalCase` (canonical Go
|
|
188
|
+
SDK) — both are auto-mapped. Pass to `setup()`, `setupAsync()`, or the
|
|
189
|
+
`SessionFactory` constructor.
|
|
134
190
|
|
|
135
191
|
| Field | Type | Default | Description |
|
|
136
192
|
|-------|------|---------|-------------|
|
|
137
|
-
| `serviceName` | `string` | **
|
|
138
|
-
| `productId` | `string` | **
|
|
139
|
-
| `metastore` | `
|
|
140
|
-
| `kms` | `
|
|
141
|
-
| `connectionString` | `string` | | Connection string for
|
|
142
|
-
| `
|
|
143
|
-
| `enableSessionCaching` | `boolean` | `true` | Cache
|
|
144
|
-
| `sessionCacheMaxSize` | `number` | `1000` | Max cached sessions |
|
|
145
|
-
| `sessionCacheDuration` | `number` | | Session cache TTL in
|
|
146
|
-
| `regionMap` | `
|
|
147
|
-
| `preferredRegion` | `string` | | Preferred AWS region
|
|
148
|
-
| `enableRegionSuffix` | `boolean` | | Append region suffix to
|
|
149
|
-
| `expireAfter` | `number` | |
|
|
150
|
-
| `checkInterval` | `number` |
|
|
151
|
-
| `
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
193
|
+
| `serviceName` | `string` | **required** | Service identifier for the key hierarchy. |
|
|
194
|
+
| `productId` | `string` | **required** | Product identifier for the key hierarchy. |
|
|
195
|
+
| `metastore` | `'memory' \| 'rdbms' \| 'dynamodb'` | **required** | `'memory'` is testing-only and does not persist across processes. |
|
|
196
|
+
| `kms` | `'static' \| 'aws'` | `'static'` | `'static'` is testing-only (uses a hard-coded master key). |
|
|
197
|
+
| `connectionString` | `string` | | Connection string for `rdbms` metastore. |
|
|
198
|
+
| `sqlMetastoreDbType` | `'mysql' \| 'postgres'` | | SQL driver. |
|
|
199
|
+
| `enableSessionCaching` | `boolean` | `true` | Cache `Session` objects by partition ID. |
|
|
200
|
+
| `sessionCacheMaxSize` | `number` | `1000` | Max cached sessions. |
|
|
201
|
+
| `sessionCacheDuration` | `number` | | Session cache TTL in seconds. |
|
|
202
|
+
| `regionMap` | `Record<string, string>` | | AWS KMS multi-region key ARN map. |
|
|
203
|
+
| `preferredRegion` | `string` | | Preferred AWS region from `regionMap`. |
|
|
204
|
+
| `enableRegionSuffix` | `boolean` | | Append AWS region suffix to key IDs. |
|
|
205
|
+
| `expireAfter` | `number` | 90 days | Intermediate-key expiration in seconds. |
|
|
206
|
+
| `checkInterval` | `number` | 60 minutes | Revoke-check interval in seconds. |
|
|
207
|
+
| `dynamoDbEndpoint` | `string` | | DynamoDB endpoint URL (for local DynamoDB). |
|
|
208
|
+
| `dynamoDbRegion` | `string` | | AWS region for DynamoDB. |
|
|
209
|
+
| `dynamoDbTableName` | `string` | `'EncryptionKey'` | DynamoDB table name. |
|
|
210
|
+
| `dynamoDbSigningRegion` | `string` | | Region used for SigV4 signing. |
|
|
211
|
+
| `replicaReadConsistency` | `'eventual' \| 'global' \| 'session'` | | DynamoDB read consistency. |
|
|
212
|
+
| `verbose` | `boolean` | `false` | Emit verbose log events (use a log hook to consume). |
|
|
213
|
+
| `enableCanaries` | `boolean` | `false` | Enable in-memory canary buffers around plaintexts. |
|
|
214
|
+
| `disableZeroCopy` | `boolean` | | Compatibility shim — accepted but no effect. |
|
|
215
|
+
| `nullDataCheck` | `boolean` | | Compatibility shim — accepted but no effect. |
|
|
216
|
+
| `poolMaxOpen` | `number` | `0` | Max open DB connections (0 = unlimited). |
|
|
217
|
+
| `poolMaxIdle` | `number` | `2` | Max idle DB connections to retain. |
|
|
218
|
+
| `poolMaxLifetime` | `number` | `0` | Max connection lifetime in seconds (0 = unlimited). |
|
|
219
|
+
| `poolMaxIdleTime` | `number` | `0` | Max idle time in seconds per connection (0 = unlimited). |
|
|
220
|
+
|
|
221
|
+
### Environment variables
|
|
222
|
+
|
|
223
|
+
| Variable | Effect |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `STATIC_MASTER_KEY_HEX` | 64 hex chars (32 bytes) for static KMS. **Testing only.** |
|
|
226
|
+
| `ASHERAH_NODE_DEBUG=1` | Enable native-side debug logging. |
|
|
227
|
+
| `ASHERAH_POOL_MAX_OPEN` | Override `poolMaxOpen`. |
|
|
228
|
+
| `ASHERAH_POOL_MAX_IDLE` | Override `poolMaxIdle`. |
|
|
229
|
+
| `ASHERAH_POOL_MAX_LIFETIME` | Override `poolMaxLifetime`. |
|
|
230
|
+
| `ASHERAH_POOL_MAX_IDLE_TIME` | Override `poolMaxIdleTime`. |
|
|
172
231
|
|
|
173
232
|
## Performance
|
|
174
233
|
|
|
175
|
-
|
|
176
|
-
|
|
234
|
+
Native Rust implementation compiled via napi-rs. Typical latencies on Apple
|
|
235
|
+
M4 Max (in-memory metastore, session caching enabled, 64-byte payload):
|
|
177
236
|
|
|
178
237
|
| Operation | Sync | Async |
|
|
179
238
|
|-----------|------|-------|
|
|
180
|
-
| Encrypt | ~970 ns | ~12
|
|
181
|
-
| Decrypt | ~1
|
|
239
|
+
| Encrypt | ~970 ns | ~12 µs |
|
|
240
|
+
| Decrypt | ~1.2 µs | ~12 µs |
|
|
182
241
|
|
|
183
242
|
See `scripts/benchmark.sh` for head-to-head comparisons with the canonical
|
|
184
243
|
Go-based implementation.
|
|
185
244
|
|
|
186
|
-
## Migration from
|
|
245
|
+
## Migration from the canonical Go-based `asherah` (v3.x)
|
|
187
246
|
|
|
188
|
-
|
|
189
|
-
(v3.x). The JavaScript wrapper provides full backward compatibility:
|
|
247
|
+
Drop-in replacement. The npm wrapper provides full backward compatibility:
|
|
190
248
|
|
|
191
|
-
- **PascalCase config**
|
|
192
|
-
auto-mapped to camelCase
|
|
193
|
-
- **snake_case function aliases**
|
|
194
|
-
`encrypt_string`, `decrypt_string_async`, etc.
|
|
195
|
-
- **Metastore/KMS aliases**
|
|
196
|
-
|
|
197
|
-
- **`set_log_hook`
|
|
198
|
-
`(level: number, message: string)` and the
|
|
199
|
-
`(event: { level, message, target })`
|
|
249
|
+
- **PascalCase config** — `ServiceName`, `ProductID`, `Metastore`, etc. are
|
|
250
|
+
auto-mapped to camelCase.
|
|
251
|
+
- **snake_case function aliases** — `set_log_hook`, `set_metrics_hook`,
|
|
252
|
+
`get_setup_status`, `encrypt_string`, `decrypt_string_async`, etc.
|
|
253
|
+
- **Metastore/KMS aliases** — `'test-debug-memory'`, `'test-debug-static'`
|
|
254
|
+
normalize to the short forms.
|
|
255
|
+
- **`set_log_hook` signature variants** — both the canonical
|
|
256
|
+
`(level: number, message: string)` and the structured
|
|
257
|
+
`(event: { level, message, target })` are supported.
|
|
200
258
|
|
|
201
|
-
To migrate
|
|
259
|
+
To migrate: change your dependency from `asherah@^3` to this package. No code
|
|
260
|
+
changes required.
|
|
202
261
|
|
|
203
|
-
## Supported
|
|
262
|
+
## Supported platforms
|
|
204
263
|
|
|
205
264
|
| Platform | Architecture | Notes |
|
|
206
|
-
|
|
207
|
-
| Linux | x64
|
|
208
|
-
| Linux | x64
|
|
209
|
-
| Linux | ARM64
|
|
210
|
-
| Linux | ARM64
|
|
211
|
-
| macOS | x64
|
|
212
|
-
| macOS | ARM64
|
|
213
|
-
| Windows | x64
|
|
214
|
-
| Windows | ARM64
|
|
265
|
+
|----------|--------------|-------|
|
|
266
|
+
| Linux | x64 | glibc (most distros) |
|
|
267
|
+
| Linux | x64 | musl (Alpine) |
|
|
268
|
+
| Linux | ARM64 | glibc |
|
|
269
|
+
| Linux | ARM64 | musl (Alpine) |
|
|
270
|
+
| macOS | x64 | Intel |
|
|
271
|
+
| macOS | ARM64 | Apple Silicon |
|
|
272
|
+
| Windows | x64 | MSVC |
|
|
273
|
+
| Windows | ARM64 | MSVC |
|
|
215
274
|
|
|
216
275
|
## API Reference
|
|
217
276
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
> Full TSDoc lives in `index.d.ts` and surfaces in your IDE on hover. The
|
|
278
|
+
> tables below summarize each API; the type file is the source of truth.
|
|
279
|
+
|
|
280
|
+
### Static / module-level API (legacy compatibility)
|
|
281
|
+
|
|
282
|
+
#### Lifecycle
|
|
283
|
+
|
|
284
|
+
| Function | Description |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `setup(config)` | Initialize the global instance. Throws if already configured. |
|
|
287
|
+
| `setupAsync(config)` | Async variant. Returns `Promise<void>`. |
|
|
288
|
+
| `shutdown()` | Tear down the global instance and clear cached sessions. Idempotent. |
|
|
289
|
+
| `shutdownAsync()` | Async variant. Returns `Promise<void>`. |
|
|
290
|
+
| `getSetupStatus()` | `boolean` — true if `setup()` has been called and `shutdown()` has not. |
|
|
291
|
+
| `setenv(envJson)` | Apply env vars from a JSON string before `setup()`. Mirrors the canonical SDK. |
|
|
292
|
+
|
|
293
|
+
#### Encrypt / decrypt
|
|
294
|
+
|
|
295
|
+
| Function | Param 1 | Param 2 | Returns |
|
|
296
|
+
|---|---|---|---|
|
|
297
|
+
| `encrypt(partitionId, data)` | `string` (non-empty) | `Buffer` (empty OK) | `string` (DRR JSON) |
|
|
298
|
+
| `encryptAsync(partitionId, data)` | `string` | `Buffer` | `Promise<string>` |
|
|
299
|
+
| `encryptString(partitionId, data)` | `string` | `string` (empty OK) | `string` (DRR JSON) |
|
|
300
|
+
| `encryptStringAsync(partitionId, data)` | `string` | `string` | `Promise<string>` |
|
|
301
|
+
| `decrypt(partitionId, drr)` | `string` | `string` (DRR JSON) | `Buffer` |
|
|
302
|
+
| `decryptAsync(partitionId, drr)` | `string` | `string` | `Promise<Buffer>` |
|
|
303
|
+
| `decryptString(partitionId, drr)` | `string` | `string` | `string` |
|
|
304
|
+
| `decryptStringAsync(partitionId, drr)` | `string` | `string` | `Promise<string>` |
|
|
305
|
+
|
|
306
|
+
All accept the snake_case aliases `encrypt_async`, `encrypt_string`,
|
|
307
|
+
`encrypt_string_async`, `decrypt_async`, `decrypt_string`,
|
|
308
|
+
`decrypt_string_async`, `setup_async`, `shutdown_async`, `get_setup_status`.
|
|
309
|
+
|
|
310
|
+
#### Hooks
|
|
311
|
+
|
|
312
|
+
| Function | Description |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `setLogHook(cb)` / `set_log_hook(cb)` | Register a structured-event log callback. Pass `null` to deregister. The snake_case alias also accepts the canonical `(level, message)` signature. |
|
|
315
|
+
| `setMetricsHook(cb)` / `set_metrics_hook(cb)` | Register a metrics callback. Pass `null` to deregister. |
|
|
316
|
+
|
|
317
|
+
### Factory / Session API (recommended)
|
|
318
|
+
|
|
319
|
+
#### `class SessionFactory`
|
|
320
|
+
|
|
321
|
+
| Member | Description |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `new SessionFactory(config)` | Construct from inline config. |
|
|
324
|
+
| `static SessionFactory.fromEnv()` | Construct from environment variables. |
|
|
325
|
+
| `factory.getSession(partitionId)` | Get a per-partition session. Throws on null/empty partition. |
|
|
326
|
+
| `factory.close()` | Release native resources. After close, `getSession()` throws. |
|
|
327
|
+
|
|
328
|
+
#### `class AsherahSession`
|
|
329
|
+
|
|
330
|
+
| Member | Description |
|
|
331
|
+
|---|---|
|
|
332
|
+
| `session.encrypt(data)` | `Buffer` → DRR JSON `string`. Empty `Buffer` is valid. |
|
|
333
|
+
| `session.encryptString(data)` | `string` → DRR JSON `string`. Empty `string` is valid. |
|
|
334
|
+
| `session.decrypt(drr)` | DRR JSON `string` → `Buffer`. |
|
|
335
|
+
| `session.decryptString(drr)` | DRR JSON `string` → `string`. |
|
|
336
|
+
| `session.close()` | Release native resources. |
|
|
337
|
+
|
|
338
|
+
### Type aliases
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
342
|
+
|
|
343
|
+
type LogEvent = {
|
|
344
|
+
level: LogLevel;
|
|
345
|
+
message: string;
|
|
346
|
+
target: string;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
type MetricsEvent =
|
|
350
|
+
| { type: 'encrypt' | 'decrypt' | 'store' | 'load'; durationNs: number }
|
|
351
|
+
| { type: 'cache_hit' | 'cache_miss' | 'cache_stale'; name: string };
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Compatibility shims
|
|
355
|
+
|
|
356
|
+
`setMaxStackAllocItemSize(n)` and `setSafetyPaddingOverhead(n)` are accepted
|
|
357
|
+
for parity with the canonical Go-based asherah-node package but have no
|
|
358
|
+
effect in this Rust binding.
|
|
271
359
|
|
|
272
360
|
## License
|
|
273
361
|
|
package/index.d.ts
CHANGED
|
@@ -1,40 +1,129 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Asherah for Node.js
|
|
6
|
+
//
|
|
7
|
+
// Application-layer envelope encryption with automatic key rotation and a
|
|
8
|
+
// pluggable KMS / metastore. Drop-in compatible with the canonical
|
|
9
|
+
// `asherah` npm package (PascalCase config + snake_case API aliases) and
|
|
10
|
+
// significantly faster (Rust core via napi-rs).
|
|
11
|
+
//
|
|
12
|
+
// Two API styles are exposed; both are fully supported and produce the
|
|
13
|
+
// same wire format:
|
|
14
|
+
//
|
|
15
|
+
// 1. **Static / module-level API** (legacy): `setup()` once at process
|
|
16
|
+
// startup, then call free `encrypt()` / `decrypt()` functions on the
|
|
17
|
+
// module. This mirrors the canonical `godaddy/asherah-node` API and
|
|
18
|
+
// is the easiest path for existing callers to migrate.
|
|
19
|
+
//
|
|
20
|
+
// 2. **Factory / Session API** (recommended for new code): construct a
|
|
21
|
+
// `SessionFactory`, hold one or more `AsherahSession` instances, and
|
|
22
|
+
// call `encrypt()` / `decrypt()` on the session. This avoids the
|
|
23
|
+
// hidden-singleton lifecycle of the static API and makes session
|
|
24
|
+
// isolation explicit per partition.
|
|
25
|
+
//
|
|
26
|
+
// See `samples/node/index.mjs` for a runnable end-to-end example covering
|
|
27
|
+
// both styles plus async, log hook, metrics hook, and config variants.
|
|
28
|
+
//
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
// ─── Configuration types ────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Asherah configuration object using camelCase field names. This is the
|
|
35
|
+
* native shape; it is passed to `setup()`, `setupAsync()`, and the
|
|
36
|
+
* `SessionFactory` constructor.
|
|
37
|
+
*
|
|
38
|
+
* The PascalCase variant ({@link AsherahConfigCompat}) is also accepted
|
|
39
|
+
* everywhere this type is — fields are auto-mapped for compatibility with
|
|
40
|
+
* the canonical Go-based asherah package.
|
|
41
|
+
*/
|
|
3
42
|
export type AsherahConfig = {
|
|
43
|
+
/** Service name. Forms part of the key hierarchy partition path. Required. */
|
|
4
44
|
serviceName: string;
|
|
45
|
+
|
|
46
|
+
/** Product ID. Forms part of the key hierarchy partition path. Required. */
|
|
5
47
|
productId: string;
|
|
48
|
+
|
|
49
|
+
/** Intermediate-key expiration in seconds. Default: 90 days. */
|
|
6
50
|
expireAfter?: number | null;
|
|
51
|
+
|
|
52
|
+
/** Revoke-check interval in seconds — how often to re-read parent keys
|
|
53
|
+
* to pick up revocation. Default: 60 minutes. */
|
|
7
54
|
checkInterval?: number | null;
|
|
55
|
+
|
|
56
|
+
/** Metastore backend. `'memory'` is testing-only and will not persist
|
|
57
|
+
* across processes. */
|
|
8
58
|
metastore: 'memory' | 'rdbms' | 'dynamodb';
|
|
59
|
+
|
|
60
|
+
/** SQL connection string when `metastore` is `'rdbms'`. Format depends
|
|
61
|
+
* on `sqlMetastoreDbType`. */
|
|
9
62
|
connectionString?: string | null;
|
|
63
|
+
|
|
64
|
+
/** RDBMS replica read consistency: `'eventual'`, `'global'`, or `'session'`. */
|
|
10
65
|
replicaReadConsistency?: string | null;
|
|
66
|
+
|
|
67
|
+
/** DynamoDB endpoint URL (typically only set for local DynamoDB). */
|
|
11
68
|
dynamoDbEndpoint?: string | null;
|
|
69
|
+
/** AWS region for DynamoDB. */
|
|
12
70
|
dynamoDbRegion?: string | null;
|
|
71
|
+
/** DynamoDB table name. Default: `EncryptionKey`. */
|
|
13
72
|
dynamoDbTableName?: string | null;
|
|
14
|
-
/**
|
|
73
|
+
/** AWS region used for SigV4 signing of DynamoDB requests. */
|
|
74
|
+
dynamoDbSigningRegion?: string | null;
|
|
75
|
+
|
|
76
|
+
/** @deprecated Use `dynamoDbEndpoint` (lowercase `b`). */
|
|
15
77
|
dynamoDBEndpoint?: string | null;
|
|
16
|
-
/** @deprecated Use dynamoDbRegion */
|
|
78
|
+
/** @deprecated Use `dynamoDbRegion` (lowercase `b`). */
|
|
17
79
|
dynamoDBRegion?: string | null;
|
|
18
|
-
/** @deprecated Use dynamoDbTableName */
|
|
80
|
+
/** @deprecated Use `dynamoDbTableName` (lowercase `b`). */
|
|
19
81
|
dynamoDBTableName?: string | null;
|
|
82
|
+
|
|
83
|
+
/** Maximum number of cached sessions. */
|
|
20
84
|
sessionCacheMaxSize?: number | null;
|
|
85
|
+
/** Session cache TTL in seconds. */
|
|
21
86
|
sessionCacheDuration?: number | null;
|
|
87
|
+
|
|
88
|
+
/** KMS backend. `'static'` is testing-only (uses a hard-coded master key). */
|
|
22
89
|
kms?: 'aws' | 'static' | null;
|
|
90
|
+
|
|
91
|
+
/** AWS KMS region-to-key-ARN map for multi-region KMS. */
|
|
23
92
|
regionMap?: Record<string, string> | null;
|
|
93
|
+
/** Preferred AWS region when `regionMap` is set. */
|
|
24
94
|
preferredRegion?: string | null;
|
|
95
|
+
/** Append the AWS region as a suffix to the key ID. */
|
|
25
96
|
enableRegionSuffix?: boolean | null;
|
|
97
|
+
|
|
98
|
+
/** Cache `Session` objects by partition ID. Default: `true`. */
|
|
26
99
|
enableSessionCaching?: boolean | null;
|
|
100
|
+
|
|
101
|
+
/** Emit verbose log events at the `info`/`debug` level. Use a log hook
|
|
102
|
+
* ({@link setLogHook}) to consume them. */
|
|
27
103
|
verbose?: boolean | null;
|
|
28
|
-
|
|
104
|
+
|
|
105
|
+
/** SQL driver: `'mysql'` or `'postgres'` (used with `metastore: 'rdbms'`). */
|
|
29
106
|
sqlMetastoreDbType?: string | null;
|
|
30
|
-
/** @deprecated Use sqlMetastoreDbType */
|
|
107
|
+
/** @deprecated Use `sqlMetastoreDbType` (lowercase `b`). */
|
|
31
108
|
sqlMetastoreDBType?: string | null;
|
|
109
|
+
|
|
110
|
+
/** Compatibility shim for the canonical Go-based asherah-node package.
|
|
111
|
+
* Has no effect in this Rust-based binding (which is always zero-copy
|
|
112
|
+
* where possible). */
|
|
32
113
|
disableZeroCopy?: boolean | null;
|
|
114
|
+
/** Compatibility shim — accepted but has no effect (this binding always
|
|
115
|
+
* validates inputs). */
|
|
33
116
|
nullDataCheck?: boolean | null;
|
|
117
|
+
/** Enable in-memory canary buffers around plaintexts. Costs a small
|
|
118
|
+
* amount of allocation overhead per operation. */
|
|
34
119
|
enableCanaries?: boolean | null;
|
|
35
120
|
};
|
|
36
121
|
|
|
37
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Asherah configuration in canonical PascalCase format, matching the
|
|
124
|
+
* canonical `asherah` npm package. Accepted everywhere {@link AsherahConfig}
|
|
125
|
+
* is — fields are auto-mapped on the way in.
|
|
126
|
+
*/
|
|
38
127
|
export type AsherahConfigCompat = {
|
|
39
128
|
readonly ServiceName: string;
|
|
40
129
|
readonly ProductID: string;
|
|
@@ -60,67 +149,305 @@ export type AsherahConfigCompat = {
|
|
|
60
149
|
readonly EnableCanaries?: boolean | null;
|
|
61
150
|
};
|
|
62
151
|
|
|
63
|
-
|
|
64
|
-
export type LogHookCallback = (level: number, message: string) => void;
|
|
152
|
+
// ─── Static / module-level API (legacy) ─────────────────────────────────────
|
|
65
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Initialize the global Asherah instance. Must be called once before any
|
|
156
|
+
* `encrypt()` / `decrypt()` call on the static API.
|
|
157
|
+
*
|
|
158
|
+
* Subsequent calls to `setup()` without an intervening `shutdown()` throw.
|
|
159
|
+
* For new code, prefer the {@link SessionFactory} API, which avoids the
|
|
160
|
+
* hidden-singleton lifecycle.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```js
|
|
164
|
+
* const asherah = require('asherah');
|
|
165
|
+
* asherah.setup({
|
|
166
|
+
* serviceName: 'my-svc',
|
|
167
|
+
* productId: 'my-prod',
|
|
168
|
+
* metastore: 'memory', // production: 'rdbms' or 'dynamodb'
|
|
169
|
+
* kms: 'static', // production: 'aws'
|
|
170
|
+
* });
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @throws if Asherah is already configured (call {@link shutdown} first)
|
|
174
|
+
* or if the config is invalid.
|
|
175
|
+
*/
|
|
66
176
|
export declare function setup(config: AsherahConfig | AsherahConfigCompat): void;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Async variant of {@link setup}. Resolves once the global instance is
|
|
180
|
+
* configured and the metastore/KMS are reachable. Safe to call from an
|
|
181
|
+
* async context — does not block the Node event loop on KMS/SDK setup.
|
|
182
|
+
*/
|
|
67
183
|
export declare function setupAsync(config: AsherahConfig | AsherahConfigCompat): Promise<void>;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Tear down the global Asherah instance. Releases the native factory and
|
|
187
|
+
* clears any cached sessions. Idempotent — calling on an already-shut-down
|
|
188
|
+
* instance is a no-op.
|
|
189
|
+
*/
|
|
68
190
|
export declare function shutdown(): void;
|
|
191
|
+
|
|
192
|
+
/** Async variant of {@link shutdown}. */
|
|
69
193
|
export declare function shutdownAsync(): Promise<void>;
|
|
194
|
+
|
|
195
|
+
/** Returns `true` when {@link setup} has been called and {@link shutdown}
|
|
196
|
+
* has not yet been called. */
|
|
70
197
|
export declare function getSetupStatus(): boolean;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Apply a JSON object of environment variables before {@link setup} is
|
|
201
|
+
* called. Equivalent to `process.env[k] = v` for each entry, but evaluated
|
|
202
|
+
* by the native side so configuration via env-var works identically to
|
|
203
|
+
* the canonical SDK.
|
|
204
|
+
*
|
|
205
|
+
* @param env JSON string. Keys must be strings; values may be strings or
|
|
206
|
+
* `null` (a `null` value unsets the variable).
|
|
207
|
+
*/
|
|
71
208
|
export declare function setenv(env: string): void;
|
|
72
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Encrypt `data` for the given partition. Returns a `DataRowRecord` JSON
|
|
212
|
+
* string suitable for storing in a database column.
|
|
213
|
+
*
|
|
214
|
+
* @param partitionId Tenant / user / record-owner identifier. Must be
|
|
215
|
+
* non-empty — `null`, `undefined`, and `""` are
|
|
216
|
+
* rejected as programming errors.
|
|
217
|
+
* @param data Plaintext bytes. Empty `Buffer` is **valid** and round-trips
|
|
218
|
+
* to an empty `Buffer` on decrypt — do not short-circuit empty
|
|
219
|
+
* inputs in caller code (see docs/input-contract.md).
|
|
220
|
+
* @returns The full `DataRowRecord` JSON envelope (Key, Data,
|
|
221
|
+
* ParentKeyMeta).
|
|
222
|
+
* @throws TypeError if `partitionId` or `data` is null/undefined.
|
|
223
|
+
* @throws Error from the native layer on encryption failure.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```js
|
|
227
|
+
* const drr = asherah.encrypt('user-42', Buffer.from('secret'));
|
|
228
|
+
* // store drr in your database
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
73
231
|
export declare function encrypt(partitionId: string, data: Buffer): string;
|
|
232
|
+
|
|
233
|
+
/** Async variant of {@link encrypt}. The work runs on the Rust tokio
|
|
234
|
+
* runtime; the Node event loop is NOT blocked. */
|
|
74
235
|
export declare function encryptAsync(partitionId: string, data: Buffer): Promise<string>;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Decrypt a `DataRowRecord` JSON string produced by {@link encrypt}.
|
|
239
|
+
*
|
|
240
|
+
* @param partitionId Must match the partition the value was encrypted
|
|
241
|
+
* under. Non-empty.
|
|
242
|
+
* @param dataRowRecordJson The DRR JSON envelope as a string.
|
|
243
|
+
* @returns Plaintext as a `Buffer`. Length 0 if the original plaintext
|
|
244
|
+
* was empty — empty round-trips to empty.
|
|
245
|
+
* @throws TypeError if either argument is null/undefined.
|
|
246
|
+
* @throws Error if the JSON is malformed, the partition doesn't match,
|
|
247
|
+
* the parent key has been revoked, or the AEAD tag fails.
|
|
248
|
+
*/
|
|
75
249
|
export declare function decrypt(partitionId: string, dataRowRecordJson: string): Buffer;
|
|
250
|
+
|
|
251
|
+
/** Async variant of {@link decrypt}. */
|
|
76
252
|
export declare function decryptAsync(partitionId: string, dataRowRecordJson: string): Promise<Buffer>;
|
|
77
253
|
|
|
254
|
+
/** UTF-8 string-typed wrapper around {@link encrypt}. Empty `string`
|
|
255
|
+
* ("") is valid and round-trips. */
|
|
78
256
|
export declare function encryptString(partitionId: string, data: string): string;
|
|
257
|
+
|
|
258
|
+
/** Async variant of {@link encryptString}. */
|
|
79
259
|
export declare function encryptStringAsync(partitionId: string, data: string): Promise<string>;
|
|
260
|
+
|
|
261
|
+
/** UTF-8 string-typed wrapper around {@link decrypt}. */
|
|
80
262
|
export declare function decryptString(partitionId: string, dataRowRecordJson: string): string;
|
|
263
|
+
|
|
264
|
+
/** Async variant of {@link decryptString}. */
|
|
81
265
|
export declare function decryptStringAsync(partitionId: string, dataRowRecordJson: string): Promise<string>;
|
|
82
266
|
|
|
83
|
-
|
|
84
|
-
export declare function setSafetyPaddingOverhead(n: number): void;
|
|
267
|
+
// ─── Factory / Session API (recommended) ────────────────────────────────────
|
|
85
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Factory for creating per-partition `AsherahSession` instances. Holding
|
|
271
|
+
* a long-lived factory is cheaper than calling `setup()`/`shutdown()`
|
|
272
|
+
* repeatedly and makes session isolation explicit.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```js
|
|
276
|
+
* const factory = new asherah.SessionFactory({
|
|
277
|
+
* serviceName: 'my-svc',
|
|
278
|
+
* productId: 'my-prod',
|
|
279
|
+
* metastore: 'memory',
|
|
280
|
+
* kms: 'static',
|
|
281
|
+
* });
|
|
282
|
+
* const session = factory.getSession('user-42');
|
|
283
|
+
* try {
|
|
284
|
+
* const ct = session.encryptString('secret');
|
|
285
|
+
* const pt = session.decryptString(ct);
|
|
286
|
+
* } finally {
|
|
287
|
+
* session.close();
|
|
288
|
+
* factory.close();
|
|
289
|
+
* }
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
86
292
|
export declare class SessionFactory {
|
|
293
|
+
/** Construct a factory from an inline config object. */
|
|
87
294
|
constructor(config: AsherahConfig | AsherahConfigCompat);
|
|
295
|
+
|
|
296
|
+
/** Construct a factory from environment variables (for parity with the
|
|
297
|
+
* canonical Go-based asherah module). */
|
|
88
298
|
static fromEnv(): SessionFactory;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get a session for the given partition. Sessions returned for the same
|
|
302
|
+
* partition share the underlying intermediate key; different partitions
|
|
303
|
+
* are cryptographically isolated.
|
|
304
|
+
*
|
|
305
|
+
* @param partitionId Non-empty tenant / record-owner identifier.
|
|
306
|
+
* @throws TypeError if `partitionId` is null/undefined.
|
|
307
|
+
* @throws Error if `partitionId` is the empty string.
|
|
308
|
+
*/
|
|
89
309
|
getSession(partitionId: string): AsherahSession;
|
|
310
|
+
|
|
311
|
+
/** Release native resources. After `close()`, `getSession()` will throw. */
|
|
90
312
|
close(): void;
|
|
91
313
|
}
|
|
92
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Per-partition encrypt/decrypt session. Created via
|
|
317
|
+
* {@link SessionFactory.getSession}. Always pair with `close()` to release
|
|
318
|
+
* native resources promptly.
|
|
319
|
+
*/
|
|
93
320
|
export declare class AsherahSession {
|
|
321
|
+
/** Encrypt a `Buffer` and return the DRR JSON. Empty `Buffer` is valid. */
|
|
94
322
|
encrypt(data: Buffer): string;
|
|
323
|
+
/** Encrypt a UTF-8 string and return the DRR JSON. Empty string is valid. */
|
|
95
324
|
encryptString(data: string): string;
|
|
325
|
+
/** Decrypt a DRR JSON string and return the plaintext as a `Buffer`. */
|
|
96
326
|
decrypt(dataRowRecordJson: string): Buffer;
|
|
327
|
+
/** Decrypt a DRR JSON string and return the plaintext as a UTF-8 string. */
|
|
97
328
|
decryptString(dataRowRecordJson: string): string;
|
|
329
|
+
/** Release native resources. */
|
|
98
330
|
close(): void;
|
|
99
331
|
}
|
|
100
332
|
|
|
333
|
+
// ─── Observability hooks ────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
/** Log severity strings carried in {@link LogEvent.level}. */
|
|
336
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Structured log event delivered to a log hook. Includes the source
|
|
340
|
+
* `target` (typically the Rust module path that emitted the log) and the
|
|
341
|
+
* formatted `message`.
|
|
342
|
+
*/
|
|
101
343
|
export type LogEvent = {
|
|
102
|
-
|
|
344
|
+
/** Severity. Mirrors the Rust `log::Level`. */
|
|
345
|
+
level: LogLevel;
|
|
346
|
+
/** Formatted log message. */
|
|
103
347
|
message: string;
|
|
348
|
+
/** Source module / target string. Useful for filtering. */
|
|
104
349
|
target: string;
|
|
105
350
|
};
|
|
106
351
|
|
|
352
|
+
/** Numeric-level callback for compatibility with the canonical
|
|
353
|
+
* `godaddy/asherah-node` log hook signature. New code should prefer the
|
|
354
|
+
* structured {@link LogEvent} variant. */
|
|
355
|
+
export type LogHookCallback = (level: number, message: string) => void;
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Metrics event delivered to a metrics hook. Timing events
|
|
359
|
+
* (`encrypt`/`decrypt`/`store`/`load`) carry `durationNs`; cache events
|
|
360
|
+
* (`cache_hit`/`cache_miss`/`cache_stale`) carry the cache `name`.
|
|
361
|
+
*/
|
|
107
362
|
export type MetricsEvent =
|
|
108
363
|
| { type: 'encrypt' | 'decrypt' | 'store' | 'load'; durationNs: number }
|
|
109
|
-
| { type: 'cache_hit' | 'cache_miss'; name: string };
|
|
364
|
+
| { type: 'cache_hit' | 'cache_miss' | 'cache_stale'; name: string };
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Install a callback that fires for every log event emitted by the Rust
|
|
368
|
+
* core (encrypt/decrypt path, metastore drivers, KMS clients, etc.).
|
|
369
|
+
*
|
|
370
|
+
* Pass `null` to deregister.
|
|
371
|
+
*
|
|
372
|
+
* Callbacks may fire from any thread (Rust tokio worker threads, DB
|
|
373
|
+
* driver threads). The N-API ThreadsafeFunction layer marshals each
|
|
374
|
+
* event back to the Node event loop, so the callback runs on the main
|
|
375
|
+
* thread — synchronous code in the callback is safe.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```js
|
|
379
|
+
* asherah.setLogHook((event) => {
|
|
380
|
+
* if (event.level === 'warn' || event.level === 'error') {
|
|
381
|
+
* console.error(`[asherah ${event.level}] ${event.message}`);
|
|
382
|
+
* }
|
|
383
|
+
* });
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
export declare function setLogHook(
|
|
387
|
+
hook: ((event: LogEvent) => void) | LogHookCallback | null,
|
|
388
|
+
): void;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Install a callback that fires for every metrics event (encrypt/decrypt
|
|
392
|
+
* timings, store/load timings, cache hit/miss/stale counters).
|
|
393
|
+
*
|
|
394
|
+
* Pass `null` to deregister. Same threading semantics as
|
|
395
|
+
* {@link setLogHook}.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```js
|
|
399
|
+
* asherah.setMetricsHook((event) => {
|
|
400
|
+
* if (event.type === 'encrypt') {
|
|
401
|
+
* histogram.observe(event.durationNs / 1e6); // ms
|
|
402
|
+
* } else if (event.type === 'cache_miss') {
|
|
403
|
+
* counter.inc({ cache: event.name });
|
|
404
|
+
* }
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
export declare function setMetricsHook(
|
|
409
|
+
hook: ((event: MetricsEvent) => void) | null,
|
|
410
|
+
): void;
|
|
411
|
+
|
|
412
|
+
// ─── Performance tuning ─────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
/** Compatibility shim — accepted but has no effect in the Rust binding
|
|
415
|
+
* (which manages its own buffer allocation strategy). Provided for API
|
|
416
|
+
* parity with the canonical Go-based asherah-node package. */
|
|
417
|
+
export declare function setMaxStackAllocItemSize(n: number): void;
|
|
418
|
+
|
|
419
|
+
/** Compatibility shim — accepted but has no effect. */
|
|
420
|
+
export declare function setSafetyPaddingOverhead(n: number): void;
|
|
110
421
|
|
|
111
|
-
|
|
112
|
-
export declare function setMetricsHook(hook: ((event: MetricsEvent) => void) | null): void;
|
|
422
|
+
// ─── snake_case aliases (canonical asherah-node compatibility) ──────────────
|
|
113
423
|
|
|
114
|
-
|
|
424
|
+
/** Alias for {@link setupAsync}. */
|
|
115
425
|
export declare function setup_async(config: AsherahConfig | AsherahConfigCompat): Promise<void>;
|
|
426
|
+
/** Alias for {@link shutdownAsync}. */
|
|
116
427
|
export declare function shutdown_async(): Promise<void>;
|
|
428
|
+
/** Alias for {@link encryptAsync}. */
|
|
117
429
|
export declare function encrypt_async(partitionId: string, data: Buffer): Promise<string>;
|
|
430
|
+
/** Alias for {@link encryptString}. */
|
|
118
431
|
export declare function encrypt_string(partitionId: string, data: string): string;
|
|
432
|
+
/** Alias for {@link encryptStringAsync}. */
|
|
119
433
|
export declare function encrypt_string_async(partitionId: string, data: string): Promise<string>;
|
|
434
|
+
/** Alias for {@link decryptAsync}. */
|
|
120
435
|
export declare function decrypt_async(partitionId: string, dataRowRecordJson: string): Promise<Buffer>;
|
|
436
|
+
/** Alias for {@link decryptString}. */
|
|
121
437
|
export declare function decrypt_string(partitionId: string, dataRowRecordJson: string): string;
|
|
438
|
+
/** Alias for {@link decryptStringAsync}. */
|
|
122
439
|
export declare function decrypt_string_async(partitionId: string, dataRowRecordJson: string): Promise<string>;
|
|
440
|
+
/** Alias for {@link setMaxStackAllocItemSize}. */
|
|
123
441
|
export declare function set_max_stack_alloc_item_size(n: number): void;
|
|
442
|
+
/** Alias for {@link setSafetyPaddingOverhead}. */
|
|
124
443
|
export declare function set_safety_padding_overhead(n: number): void;
|
|
125
|
-
|
|
444
|
+
/** Alias for {@link setLogHook}. */
|
|
445
|
+
export declare function set_log_hook(
|
|
446
|
+
hook: ((event: LogEvent) => void) | LogHookCallback | null,
|
|
447
|
+
): void;
|
|
448
|
+
/** Alias for {@link setMetricsHook}. */
|
|
449
|
+
export declare function set_metrics_hook(
|
|
450
|
+
hook: ((event: MetricsEvent) => void) | null,
|
|
451
|
+
): void;
|
|
452
|
+
/** Alias for {@link getSetupStatus}. */
|
|
126
453
|
export declare function get_setup_status(): boolean;
|
package/npm/index.js
CHANGED
|
@@ -284,4 +284,5 @@ module.exports.decrypt_string_async = native.decryptStringAsync;
|
|
|
284
284
|
module.exports.set_max_stack_alloc_item_size = native.setMaxStackAllocItemSize;
|
|
285
285
|
module.exports.set_safety_padding_overhead = native.setSafetyPaddingOverhead;
|
|
286
286
|
module.exports.set_log_hook = set_log_hook;
|
|
287
|
+
module.exports.set_metrics_hook = native.setMetricsHook;
|
|
287
288
|
module.exports.get_setup_status = native.getSetupStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asherah",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.35",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Asherah application-layer encryption for Node.js with automatic key rotation, powered by the native Rust implementation.",
|
|
6
6
|
"author": "Jay Gowdy",
|
|
@@ -70,13 +70,13 @@
|
|
|
70
70
|
"node": ">= 18"
|
|
71
71
|
},
|
|
72
72
|
"optionalDependencies": {
|
|
73
|
-
"asherah-darwin-arm64": "4.0.
|
|
74
|
-
"asherah-darwin-x64": "4.0.
|
|
75
|
-
"asherah-linux-x64-gnu": "4.0.
|
|
76
|
-
"asherah-linux-arm64-gnu": "4.0.
|
|
77
|
-
"asherah-linux-x64-musl": "4.0.
|
|
78
|
-
"asherah-linux-arm64-musl": "4.0.
|
|
79
|
-
"asherah-windows-x64": "4.0.
|
|
80
|
-
"asherah-windows-arm64": "4.0.
|
|
73
|
+
"asherah-darwin-arm64": "4.0.35",
|
|
74
|
+
"asherah-darwin-x64": "4.0.35",
|
|
75
|
+
"asherah-linux-x64-gnu": "4.0.35",
|
|
76
|
+
"asherah-linux-arm64-gnu": "4.0.35",
|
|
77
|
+
"asherah-linux-x64-musl": "4.0.35",
|
|
78
|
+
"asherah-linux-arm64-musl": "4.0.35",
|
|
79
|
+
"asherah-windows-x64": "4.0.35",
|
|
80
|
+
"asherah-windows-arm64": "4.0.35"
|
|
81
81
|
}
|
|
82
82
|
}
|