localspace 0.2.2 β 0.3.1
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 +222 -21
- package/dist/core/plugin-manager.d.ts +48 -0
- package/dist/core/plugin-manager.d.ts.map +1 -0
- package/dist/core/plugin-manager.js +334 -0
- package/dist/drivers/indexeddb.d.ts.map +1 -1
- package/dist/drivers/indexeddb.js +377 -297
- package/dist/drivers/localstorage.js +44 -45
- package/dist/errors.js +19 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/localspace.d.ts +16 -3
- package/dist/localspace.d.ts.map +1 -1
- package/dist/localspace.js +544 -243
- package/dist/plugins/compression.d.ts +16 -0
- package/dist/plugins/compression.d.ts.map +1 -0
- package/dist/plugins/compression.js +66 -0
- package/dist/plugins/encryption.d.ts +26 -0
- package/dist/plugins/encryption.d.ts.map +1 -0
- package/dist/plugins/encryption.js +136 -0
- package/dist/plugins/quota.d.ts +22 -0
- package/dist/plugins/quota.d.ts.map +1 -0
- package/dist/plugins/quota.js +162 -0
- package/dist/plugins/sync.d.ts +16 -0
- package/dist/plugins/sync.d.ts.map +1 -0
- package/dist/plugins/sync.js +182 -0
- package/dist/plugins/ttl.d.ts +14 -0
- package/dist/plugins/ttl.d.ts.map +1 -0
- package/dist/plugins/ttl.js +92 -0
- package/dist/types.d.ts +83 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/utils/helpers.js +3 -3
- package/dist/utils/serializer.d.ts.map +1 -1
- package/dist/utils/serializer.js +33 -34
- package/package.json +10 -1
- package/src/core/plugin-manager.ts +522 -0
- package/src/drivers/indexeddb.ts +243 -113
- package/src/drivers/localstorage.ts +1 -1
- package/src/index.ts +25 -0
- package/src/localspace.ts +387 -5
- package/src/plugins/compression.ts +108 -0
- package/src/plugins/encryption.ts +254 -0
- package/src/plugins/quota.ts +244 -0
- package/src/plugins/sync.ts +267 -0
- package/src/plugins/ttl.ts +142 -0
- package/src/types.ts +136 -1
- package/src/utils/serializer.ts +6 -2
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Starting fresh let us eliminate technical debt while maintaining API compatibili
|
|
|
35
35
|
- [Configure isolated stores for clear data boundaries](#configure-isolated-stores-for-clear-data-boundaries)
|
|
36
36
|
- [Choose drivers with predictable fallbacks](#choose-drivers-with-predictable-fallbacks)
|
|
37
37
|
- [Handle binary data across browsers](#handle-binary-data-across-browsers)
|
|
38
|
+
- [Advanced: Coalesced Writes (IndexedDB only)](#advanced-coalesced-writes-indexeddb-only)
|
|
38
39
|
- [Migration Guide](#migration-guide)
|
|
39
40
|
- [Note differences from localForage before upgrading](#note-differences-from-localforage-before-upgrading)
|
|
40
41
|
- [Enable compatibility mode for legacy callbacks](#enable-compatibility-mode-for-legacy-callbacks)
|
|
@@ -52,25 +53,23 @@ localspace is built on a foundation designed for growth. Here's what's planned:
|
|
|
52
53
|
- [x] Comprehensive test coverage
|
|
53
54
|
- [x] Modern build pipeline (ES modules, CommonJS, UMD)
|
|
54
55
|
- [x] Batch operations (`setItems()`, `getItems()`, `removeItems()`) for higher throughput
|
|
55
|
-
- [x] Automatic write coalescing (3-10x faster rapid writes,
|
|
56
|
+
- [x] Automatic write coalescing (3-10x faster rapid writes, opt-in for IndexedDB)
|
|
56
57
|
- [x] Connection pooling, transaction batching, and warmup
|
|
57
58
|
- [x] **Improved error handling** - Structured error types with detailed context
|
|
58
59
|
|
|
59
60
|
### TODO
|
|
60
|
-
- [
|
|
61
|
-
- [ ] **Cache API driver** - Native browser caching with automatic HTTP semantics
|
|
61
|
+
- [x] **Plugin system** - Middleware architecture for cross-cutting concerns
|
|
62
62
|
- [ ] **OPFS driver** - Origin Private File System for high-performance file storage
|
|
63
|
-
- [ ] **Memory driver** - In-memory storage for testing and SSR
|
|
64
63
|
- [ ] **Custom driver templates** - Documentation and examples for third-party drivers
|
|
65
64
|
- [ ] **Node.js** - File system and SQLite adapters
|
|
66
65
|
- [ ] **React Native** - AsyncStorage and SQLite drivers
|
|
67
66
|
- [ ] **Electron** - Main and renderer process coordination
|
|
68
67
|
- [ ] **Deno** - Native KV store integration
|
|
69
|
-
- [
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
72
|
-
- [
|
|
73
|
-
- [
|
|
68
|
+
- [x] **TTL plugin** - Time-to-live expiration with automatic cleanup
|
|
69
|
+
- [x] **Encryption plugin** - Transparent encryption/decryption with Web Crypto API
|
|
70
|
+
- [x] **Compression plugin** - LZ-string or Brotli compression for large values
|
|
71
|
+
- [x] **Sync plugin** - Multi-tab synchronization with BroadcastChannel
|
|
72
|
+
- [x] **Quota plugin** - Automatic quota management and cleanup strategies
|
|
74
73
|
|
|
75
74
|
### π Community Priorities
|
|
76
75
|
|
|
@@ -124,8 +123,8 @@ localspace.getItem('user', (error, value) => {
|
|
|
124
123
|
});
|
|
125
124
|
```
|
|
126
125
|
|
|
127
|
-
### π
|
|
128
|
-
localspace
|
|
126
|
+
### π Opt into automatic performance optimization (coalesced writes)
|
|
127
|
+
localspace can merge rapid single writes into batched transactions for IndexedDB, giving you **3-10x performance improvement** under write-heavy bursts. This is opt-in so default behavior stays predictable; enable it when you know you have high write pressure.
|
|
129
128
|
|
|
130
129
|
```ts
|
|
131
130
|
// Your existing code - unchanged
|
|
@@ -139,21 +138,18 @@ await Promise.all([
|
|
|
139
138
|
// β
Zero code changes required
|
|
140
139
|
```
|
|
141
140
|
|
|
142
|
-
**How it works**: When using IndexedDB, rapid writes within an 8ms window are
|
|
141
|
+
**How it works**: When using IndexedDB, rapid writes within an 8ms window are merged into a single transaction commit. This is transparent to your application and has no impact on single writes.
|
|
143
142
|
|
|
144
|
-
**
|
|
143
|
+
**Turn it on or tune it**
|
|
145
144
|
```ts
|
|
146
145
|
const instance = localspace.createInstance({
|
|
147
|
-
coalesceWrites: true, //
|
|
146
|
+
coalesceWrites: true, // opt-in (default is false)
|
|
148
147
|
coalesceWindowMs: 8, // 8ms window (default)
|
|
149
148
|
});
|
|
150
|
-
|
|
151
|
-
// Or disable if you need strict per-operation durability
|
|
152
|
-
const strict = localspace.createInstance({
|
|
153
|
-
coalesceWrites: false,
|
|
154
|
-
});
|
|
155
149
|
```
|
|
156
150
|
|
|
151
|
+
For consistency modes, batch limits, and failure semantics, see **Advanced: Coalesced Writes** below.
|
|
152
|
+
|
|
157
153
|
**When is this useful?**
|
|
158
154
|
- Form auto-save that writes multiple fields rapidly
|
|
159
155
|
- Bulk state synchronization loops
|
|
@@ -283,6 +279,208 @@ await localspace.setItem('file', file);
|
|
|
283
279
|
const restored = await localspace.getItem<Blob>('file');
|
|
284
280
|
```
|
|
285
281
|
|
|
282
|
+
## Advanced: Coalesced Writes (IndexedDB only)
|
|
283
|
+
|
|
284
|
+
localspace offers an opt-in, configurable coalesced write path to cut IndexedDB transaction count and improve throughput under heavy write bursts.
|
|
285
|
+
|
|
286
|
+
> `coalesceWrites` defaults to `false` so behavior stays predictable. Turn it on when you expect high-frequency writes.
|
|
287
|
+
|
|
288
|
+
### Why coalesce writes?
|
|
289
|
+
|
|
290
|
+
Each IndexedDB write opens a readwrite transaction. At high frequency, transaction startup overhead becomes a bottleneck. With coalescing enabled, `setItem` and `removeItem` calls that land within a short window (default 8 ms) are merged into fewer transactions:
|
|
291
|
+
- Multiple writes can share one transaction.
|
|
292
|
+
- `coalesceMaxBatchSize` caps how many ops each flush processes.
|
|
293
|
+
- `coalesceReadConsistency` controls when writes resolve and when reads see them.
|
|
294
|
+
|
|
295
|
+
### Configuration
|
|
296
|
+
|
|
297
|
+
Relevant `LocalSpaceConfig` fields:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
interface LocalSpaceConfig {
|
|
301
|
+
/**
|
|
302
|
+
* Enable coalesced writes (IndexedDB only).
|
|
303
|
+
* Default: false
|
|
304
|
+
*/
|
|
305
|
+
coalesceWrites?: boolean;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Time window (ms) for merging writes into the same batch.
|
|
309
|
+
* Default: 8
|
|
310
|
+
*/
|
|
311
|
+
coalesceWindowMs?: number;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Maximum operations per flush batch. Beyond this, flush immediately
|
|
315
|
+
* and split into multiple transactions.
|
|
316
|
+
* Default: undefined (no limit)
|
|
317
|
+
*/
|
|
318
|
+
coalesceMaxBatchSize?: number;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* When coalesceWrites is on:
|
|
322
|
+
* - 'strong' (default): drain pending writes before reads
|
|
323
|
+
* - 'eventual': reads skip draining; writes only guarantee queueing
|
|
324
|
+
*/
|
|
325
|
+
coalesceReadConsistency?: 'strong' | 'eventual';
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Consistency modes
|
|
330
|
+
|
|
331
|
+
#### `coalesceReadConsistency: 'strong'` (default)
|
|
332
|
+
- Writes (`setItem` / `removeItem`): Promises resolve after the data is persisted; flush errors reject.
|
|
333
|
+
- Reads (`getItem`, `iterate`, batch reads): call `drainCoalescedWrites` first so you read what you just wrote.
|
|
334
|
+
|
|
335
|
+
Use this for user settings, drafts, and any flow where you need read-your-writes.
|
|
336
|
+
|
|
337
|
+
#### `coalesceReadConsistency: 'eventual'`
|
|
338
|
+
- Writes: queued and resolve immediately once enqueued; flush happens in the background. Errors log `console.warn('[localspace] coalesced write failed (eventual mode)', error)` but do not reject the earlier Promise.
|
|
339
|
+
- Reads: do not flush pending writes, so you may briefly see stale values.
|
|
340
|
+
- Destructive operations still force a flush to avoid dropping queued writes: `removeItems`, `clear`, `dropInstance`.
|
|
341
|
+
|
|
342
|
+
Use this for logs/analytics or workloads that can tolerate short windows of staleness in exchange for the lightest write path.
|
|
343
|
+
|
|
344
|
+
### Bounding batch size
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
const store = localspace.createInstance({
|
|
348
|
+
name: 'logs',
|
|
349
|
+
storeName: 'events',
|
|
350
|
+
coalesceWrites: true,
|
|
351
|
+
coalesceWindowMs: 8,
|
|
352
|
+
coalesceMaxBatchSize: 64,
|
|
353
|
+
coalesceReadConsistency: 'eventual',
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
- When the queue reaches `coalesceMaxBatchSize`, it flushes immediately.
|
|
358
|
+
- Flush splits work into batches of up to 64 ops, each in its own transaction.
|
|
359
|
+
- `getPerformanceStats()` reports `totalWrites`, `coalescedWrites`, and `transactionsSaved` so you can see the gains.
|
|
360
|
+
|
|
361
|
+
### Recommended recipes
|
|
362
|
+
|
|
363
|
+
1) Default: coalescing off
|
|
364
|
+
```ts
|
|
365
|
+
const store = localspace.createInstance({
|
|
366
|
+
name: 'app',
|
|
367
|
+
storeName: 'keyvaluepairs',
|
|
368
|
+
// coalesceWrites is false by default
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
2) High-frequency writes with eventual consistency
|
|
373
|
+
```ts
|
|
374
|
+
const logStore = localspace.createInstance({
|
|
375
|
+
name: 'analytics',
|
|
376
|
+
storeName: 'events',
|
|
377
|
+
coalesceWrites: true,
|
|
378
|
+
coalesceWindowMs: 8,
|
|
379
|
+
coalesceMaxBatchSize: 64,
|
|
380
|
+
coalesceReadConsistency: 'eventual',
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
- `setItem` resolves almost immediately.
|
|
384
|
+
- Short windows of stale reads are acceptable.
|
|
385
|
+
- `clear` and `dropInstance` force-flush so queued writes are not lost.
|
|
386
|
+
|
|
387
|
+
3) Strong consistency with bounded batches
|
|
388
|
+
```ts
|
|
389
|
+
const userStore = localspace.createInstance({
|
|
390
|
+
name: 'user-data',
|
|
391
|
+
storeName: 'kv',
|
|
392
|
+
coalesceWrites: true,
|
|
393
|
+
coalesceWindowMs: 8,
|
|
394
|
+
coalesceMaxBatchSize: 32,
|
|
395
|
+
coalesceReadConsistency: 'strong',
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
- Writes resolve after persistence.
|
|
399
|
+
- Reads flush pending writes first.
|
|
400
|
+
- Batching still reduces transaction count.
|
|
401
|
+
|
|
402
|
+
### Caveats
|
|
403
|
+
|
|
404
|
+
- Coalesced writes apply to the IndexedDB driver only; localStorage always writes per operation.
|
|
405
|
+
- In `eventual` mode, writes can be lost if the page closes before flush completes, and errors surface only via `console.warn`.
|
|
406
|
+
- For critical durability (orders, payments, irreversible state), avoid `eventual` and consider leaving `coalesceWrites` off entirely.
|
|
407
|
+
|
|
408
|
+
## Plugin System
|
|
409
|
+
|
|
410
|
+
localspace now ships with a first-class plugin engine. Attach middleware when creating an instance or call `use()` later; plugins can mutate payloads, observe driver context, and run async interceptors around every storage call.
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
const store = localspace.createInstance({
|
|
414
|
+
name: 'secure-store',
|
|
415
|
+
storeName: 'primary',
|
|
416
|
+
plugins: [
|
|
417
|
+
ttlPlugin({ defaultTTL: 60_000 }),
|
|
418
|
+
encryptionPlugin({ key: '0123456789abcdef0123456789abcdef' }),
|
|
419
|
+
compressionPlugin({ threshold: 1024 }),
|
|
420
|
+
syncPlugin({ channelName: 'localspace-sync' }),
|
|
421
|
+
quotaPlugin({ maxSize: 5 * 1024 * 1024, evictionPolicy: 'lru' }),
|
|
422
|
+
],
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Lifecycle and hooks
|
|
427
|
+
|
|
428
|
+
- **Registration** β supply `plugins` when calling `createInstance()` or chain `instance.use(plugin)` later. Each plugin can also expose `enabled` (boolean or function) and `priority` to control execution order.
|
|
429
|
+
- **Lifecycle events** β `onInit(context)` is invoked after `ready()`, and `onDestroy` lets you tear down timers or channels. Call `await instance.destroy()` when disposing of an instance to run every `onDestroy` hook (executed in reverse priority order). Context exposes the active driver, db info, config, and a shared `metadata` bag for cross-plugin coordination.
|
|
430
|
+
- **Interceptors** β hook into `beforeSet/afterSet`, `beforeGet/afterGet`, `beforeRemove/afterRemove`, plus batch-specific methods such as `beforeSetItems` or `beforeGetItems`. Hooks run sequentially: `before*` hooks execute from highest to lowest priority, while `after*` hooks unwind in reverse order so layered transformations (compression β encryption β TTL) remain invertible. Returning a value passes it to the next plugin, while throwing a `LocalSpaceError` aborts the operation.
|
|
431
|
+
- **Per-call state** β plugins can stash data on `context.operationState` (e.g., capture the original value in `beforeSet` and reuse it in `afterSet`). For batch operations, `context.operationState.isBatch` is `true` and `context.operationState.batchSize` provides the total count.
|
|
432
|
+
- **Error handling & init policy** β unexpected exceptions are reported through `plugin.onError`. Throw a `LocalSpaceError` if you need to stop the pipeline (quota violations, failed decryptions, etc.). If a plugin `onInit` throws, the default policy is fail-fast (propagate and abort init). Set `pluginInitPolicy: 'disable-and-continue'` in config to log and skip the failing plugin instead (use with care for critical plugins like encryption).
|
|
433
|
+
|
|
434
|
+
### Plugin execution order
|
|
435
|
+
|
|
436
|
+
Plugins are sorted by `priority` (higher runs first in `before*`, last in `after*`). Default priorities:
|
|
437
|
+
|
|
438
|
+
| Plugin | Priority | Notes |
|
|
439
|
+
|--------|----------|-------|
|
|
440
|
+
| sync | -100 | Runs last in `afterSet` to broadcast original (untransformed) values |
|
|
441
|
+
| quota | -10 | Runs late so it measures final payload sizes |
|
|
442
|
+
| ttl, encryption, compression | 0 | Default; chain in registration order |
|
|
443
|
+
|
|
444
|
+
**Recommended order**: `[ttlPlugin, encryptionPlugin, compressionPlugin, syncPlugin, quotaPlugin]`
|
|
445
|
+
|
|
446
|
+
### Built-in plugins
|
|
447
|
+
|
|
448
|
+
#### TTL plugin
|
|
449
|
+
Wraps values as `{ data, expiresAt }`, invalidates stale reads, and optionally runs background cleanup. Options:
|
|
450
|
+
|
|
451
|
+
- `defaultTTL` (ms) and `keyTTL` overrides
|
|
452
|
+
- `cleanupInterval` to periodically scan `iterate()` output
|
|
453
|
+
- `onExpire(key, value)` callback before removal
|
|
454
|
+
|
|
455
|
+
#### Encryption plugin
|
|
456
|
+
Encrypts serialized payloads using the Web Crypto API (AES-GCM by default) and decrypts transparently on reads.
|
|
457
|
+
|
|
458
|
+
- Provide a `key` (CryptoKey/ArrayBuffer/string) or `keyDerivation` block (PBKDF2)
|
|
459
|
+
- Customize `algorithm`, `ivLength`, `ivGenerator`, or `randomSource`
|
|
460
|
+
- Works in browsers and modern Node runtimes (pass your own `subtle` when needed)
|
|
461
|
+
|
|
462
|
+
#### Compression plugin
|
|
463
|
+
Runs LZ-string compression (or a custom codec) when payloads exceed a `threshold` and restores them on read.
|
|
464
|
+
|
|
465
|
+
- `threshold` (bytes) controls when compression kicks in
|
|
466
|
+
- Supply a custom `{ compress, decompress }` codec if you prefer pako/Brotli
|
|
467
|
+
|
|
468
|
+
#### Sync plugin
|
|
469
|
+
Keeps multiple tabs/processes in sync via `BroadcastChannel` (with `storage`-event fallback).
|
|
470
|
+
|
|
471
|
+
- `channelName` separates logical buses
|
|
472
|
+
- `syncKeys` lets you scope which keys broadcast
|
|
473
|
+
- `conflictStrategy` defaults to `last-write-wins`; provide `onConflict` (return `false` to drop remote writes) for merge logic
|
|
474
|
+
|
|
475
|
+
#### Quota plugin
|
|
476
|
+
Tracks approximate storage usage after every mutation and enforces limits.
|
|
477
|
+
|
|
478
|
+
- `maxSize` (bytes) and optional `useNavigatorEstimate` to read the browserβs quota
|
|
479
|
+
- `evictionPolicy: 'error' | 'lru'` (LRU removes least-recently-used keys automatically)
|
|
480
|
+
- `onQuotaExceeded(info)` fires before throwing so you can log/alert users
|
|
481
|
+
|
|
482
|
+
> Tip: place quota plugins last so they see the final payload size after other transformations (TTL, encryption, compression, etc.).
|
|
483
|
+
|
|
286
484
|
## Migration Guide
|
|
287
485
|
|
|
288
486
|
### Note differences from localForage before upgrading
|
|
@@ -324,14 +522,15 @@ localspace.setItem('key', 'value', (err, value) => {
|
|
|
324
522
|
```
|
|
325
523
|
|
|
326
524
|
## Performance notes
|
|
327
|
-
- **Automatic write coalescing (
|
|
525
|
+
- **Automatic write coalescing (opt-in):** localspace can merge rapid single writes (`setItem`/`removeItem`) within an 8ms window into one transaction for IndexedDB, delivering 3-10x speedups under bursty writes. Enable with `coalesceWrites: true` and see **Advanced: Coalesced Writes** for consistency modes.
|
|
526
|
+
- **Read-your-writes consistency with coalescing:** Pending coalesced writes are flushed before reads (`getItem`, `getItems`, `iterate`, `keys`, `length`, `key`) and destructive ops (`clear`, `dropInstance`), so immediate reads always observe the latest value. If you need eventual reads for speed, you can switch `coalesceReadConsistency` to `'eventual'`.
|
|
328
527
|
- **Batch APIs outperform loops:** Playwright benchmark (`test/playwright/benchmark.spec.ts`) on 500 items x 256B showed `setItems()` ~6x faster and `getItems()` ~7.7x faster than per-item loops, with `removeItems()` ~2.8x faster (Chromium, relaxed durability).
|
|
329
528
|
- **Transaction helpers:** `runTransaction()` lets you co-locate reads/writes in a single transaction for atomic migrations and to shorten lock time.
|
|
330
529
|
- **Batch sizing:** Use `maxBatchSize` to split very large batch operations (`setItems`/`removeItems`/`getItems`) and keep transaction size in check. This works independently from `coalesceWrites`, which optimizes single-item operations.
|
|
331
530
|
- **IndexedDB durability defaults:** Chrome 121+ uses relaxed durability by default; keep it for speed or set `durability: 'strict'` in `config` for migration-style writes.
|
|
332
531
|
- **Storage Buckets (Chromium 122+):** supply a `bucket` option to isolate critical data and hint durability/persistence per bucket.
|
|
333
532
|
- **Connection warmup:** IndexedDB instances pre-warm a transaction after init to reduce first-op latency (`prewarmTransactions` enabled by default; set to `false` to skip).
|
|
334
|
-
- **Recommended defaults:**
|
|
533
|
+
- **Recommended defaults:** leave `coalesceWrites` off unless you know you need higher write throughput; if you enable it, prefer the default `strong` consistency. Keep `durability` relaxed and `prewarmTransactions` on. Set `connectionIdleMs` only if you want idle connections to auto-close, and `maxBatchSize` only for very large bulk writes. Prefer IndexedDB for atomic/bulk writes since localStorage batches are non-atomic. Use `maxConcurrentTransactions` to throttle heavy parallel workloads when needed.
|
|
335
534
|
- **localStorage batch atomicity:** When using localStorage driver, batch operations (`setItems()`, `removeItems()`) are **not atomic**. If an error occurs mid-operation, some items may be written or removed while others are not. In contrast, IndexedDB batch operations use transactions and guarantee atomicity (all-or-nothing). If atomicity is critical for your use case, prefer IndexedDB driver or implement application-level rollback logic.
|
|
336
535
|
|
|
337
536
|
When `compatibilityMode` is off, driver setup methods also use Node-style callbacks. Promises are recommended for all new code.
|
|
@@ -342,6 +541,8 @@ When `compatibilityMode` is off, driver setup methods also use Node-style callba
|
|
|
342
541
|
- **Read structured errors:** Rejections surface as `LocalSpaceError` with a `code`, contextual `details` (driver, operation, key, attemptedDrivers), and the original `cause`. Branch on `error.code` instead of parsing strings.
|
|
343
542
|
- **Handle quota errors:** Check for `error.code === 'QUOTA_EXCEEDED'` (or inspect `error.cause`) from `setItem` to inform users about storage limits.
|
|
344
543
|
- **Run unit tests:** The project ships with Vitest and Playwright suites covering API behavior; run `yarn test` to verify changes.
|
|
544
|
+
- **Collect Playwright coverage:** Run `yarn test:e2e:coverage` to re-build the bundle, execute the Playwright suite with Chromium V8 coverage enabled, and emit both text + HTML reports via `nyc` (open `coverage/index.html` after the run; raw JSON sits in `.nyc_output`).
|
|
545
|
+
- **Collect combined Vitest + Playwright coverage:** Run `yarn coverage:full` to clean previous artifacts, run `vitest --coverage`, stash its Istanbul JSON into `.nyc_output`, then execute the coverage-enabled Playwright suite and emit merged `nyc` reports.
|
|
345
546
|
|
|
346
547
|
## License
|
|
347
548
|
[MIT](./LICENSE)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BatchItems, BatchResponse, DbInfo, LocalSpaceConfig, LocalSpaceInstance, LocalSpacePlugin, PluginContext, PluginOperation } from '../types';
|
|
2
|
+
export declare class PluginAbortError extends Error {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
type PluginHost = LocalSpaceInstance & {
|
|
6
|
+
_config: LocalSpaceConfig;
|
|
7
|
+
_dbInfo: DbInfo | null;
|
|
8
|
+
};
|
|
9
|
+
export declare class PluginManager {
|
|
10
|
+
private readonly host;
|
|
11
|
+
private readonly sharedMetadata;
|
|
12
|
+
private readonly pluginRegistry;
|
|
13
|
+
private readonly initialized;
|
|
14
|
+
private readonly initPromises;
|
|
15
|
+
private readonly destroyed;
|
|
16
|
+
private readonly disabled;
|
|
17
|
+
private orderCounter;
|
|
18
|
+
constructor(host: PluginHost, initialPlugins?: LocalSpacePlugin[]);
|
|
19
|
+
hasPlugins(): boolean;
|
|
20
|
+
registerPlugins(plugins: LocalSpacePlugin[]): void;
|
|
21
|
+
private sortPlugins;
|
|
22
|
+
private getActivePlugins;
|
|
23
|
+
ensureInitialized(): Promise<void>;
|
|
24
|
+
createContext(operation: PluginOperation | null): PluginContext;
|
|
25
|
+
beforeSet<T>(key: string, value: T, context: PluginContext): Promise<T>;
|
|
26
|
+
afterSet<T>(key: string, value: T, context: PluginContext): Promise<void>;
|
|
27
|
+
beforeGet(key: string, context: PluginContext): Promise<string>;
|
|
28
|
+
afterGet<T>(key: string, value: T | null, context: PluginContext): Promise<T | null>;
|
|
29
|
+
beforeRemove(key: string, context: PluginContext): Promise<string>;
|
|
30
|
+
afterRemove(key: string, context: PluginContext): Promise<void>;
|
|
31
|
+
beforeSetItems<T>(entries: BatchItems<T>, context: PluginContext): Promise<BatchItems<T>>;
|
|
32
|
+
afterSetItems<T>(entries: BatchResponse<T>, context: PluginContext): Promise<BatchResponse<T>>;
|
|
33
|
+
beforeGetItems(keys: string[], context: PluginContext): Promise<string[]>;
|
|
34
|
+
afterGetItems<T>(entries: BatchResponse<T>, context: PluginContext): Promise<BatchResponse<T>>;
|
|
35
|
+
beforeRemoveItems(keys: string[], context: PluginContext): Promise<string[]>;
|
|
36
|
+
afterRemoveItems(keys: string[], context: PluginContext): Promise<void>;
|
|
37
|
+
destroy(): Promise<void>;
|
|
38
|
+
normalizeBatch<T>(items: BatchItems<T>): Array<{
|
|
39
|
+
key: string;
|
|
40
|
+
value: T;
|
|
41
|
+
}>;
|
|
42
|
+
private shouldPropagate;
|
|
43
|
+
private dispatchPluginError;
|
|
44
|
+
private invokeValueHook;
|
|
45
|
+
private invokeVoidHook;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=plugin-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-manager.d.ts","sourceRoot":"","sources":["../../src/core/plugin-manager.ts"],"names":[],"mappings":"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"}
|