localspace 0.3.0 → 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 +137 -13
- package/dist/drivers/indexeddb.d.ts.map +1 -1
- package/dist/drivers/indexeddb.js +126 -74
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/localspace.d.ts.map +1 -1
- package/dist/localspace.js +4 -2
- package/dist/plugins/compression.d.ts.map +1 -1
- package/dist/plugins/compression.js +9 -2
- package/dist/plugins/ttl.d.ts.map +1 -1
- package/dist/plugins/ttl.js +8 -10
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/package.json +2 -1
- package/src/drivers/indexeddb.ts +183 -106
- package/src/localspace.ts +3 -1
- package/src/plugins/compression.ts +12 -1
- package/src/plugins/ttl.ts +7 -11
- package/src/types.ts +14 -0
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,7 +53,7 @@ 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
|
|
|
@@ -122,8 +123,8 @@ localspace.getItem('user', (error, value) => {
|
|
|
122
123
|
});
|
|
123
124
|
```
|
|
124
125
|
|
|
125
|
-
### 🚀
|
|
126
|
-
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.
|
|
127
128
|
|
|
128
129
|
```ts
|
|
129
130
|
// Your existing code - unchanged
|
|
@@ -137,21 +138,18 @@ await Promise.all([
|
|
|
137
138
|
// ✅ Zero code changes required
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
**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.
|
|
141
142
|
|
|
142
|
-
**
|
|
143
|
+
**Turn it on or tune it**
|
|
143
144
|
```ts
|
|
144
145
|
const instance = localspace.createInstance({
|
|
145
|
-
coalesceWrites: true, //
|
|
146
|
+
coalesceWrites: true, // opt-in (default is false)
|
|
146
147
|
coalesceWindowMs: 8, // 8ms window (default)
|
|
147
148
|
});
|
|
148
|
-
|
|
149
|
-
// Or disable if you need strict per-operation durability
|
|
150
|
-
const strict = localspace.createInstance({
|
|
151
|
-
coalesceWrites: false,
|
|
152
|
-
});
|
|
153
149
|
```
|
|
154
150
|
|
|
151
|
+
For consistency modes, batch limits, and failure semantics, see **Advanced: Coalesced Writes** below.
|
|
152
|
+
|
|
155
153
|
**When is this useful?**
|
|
156
154
|
- Form auto-save that writes multiple fields rapidly
|
|
157
155
|
- Bulk state synchronization loops
|
|
@@ -281,6 +279,132 @@ await localspace.setItem('file', file);
|
|
|
281
279
|
const restored = await localspace.getItem<Blob>('file');
|
|
282
280
|
```
|
|
283
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
|
+
|
|
284
408
|
## Plugin System
|
|
285
409
|
|
|
286
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.
|
|
@@ -398,7 +522,7 @@ localspace.setItem('key', 'value', (err, value) => {
|
|
|
398
522
|
```
|
|
399
523
|
|
|
400
524
|
## Performance notes
|
|
401
|
-
- **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.
|
|
402
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'`.
|
|
403
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).
|
|
404
528
|
- **Transaction helpers:** `runTransaction()` lets you co-locate reads/writes in a single transaction for atomic migrations and to shorten lock time.
|
|
@@ -406,7 +530,7 @@ localspace.setItem('key', 'value', (err, value) => {
|
|
|
406
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.
|
|
407
531
|
- **Storage Buckets (Chromium 122+):** supply a `bucket` option to isolate critical data and hint durability/persistence per bucket.
|
|
408
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).
|
|
409
|
-
- **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.
|
|
410
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.
|
|
411
535
|
|
|
412
536
|
When `compatibilityMode` is off, driver setup methods also use Node-style callbacks. Promises are recommended for all new code.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/drivers/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EASP,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/drivers/indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EASP,MAAM,UAAU,CAAC;AAytElB,QAAA,MAAM,YAAY,EAAE,MAkBnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
|