first commit
This commit is contained in:
87
docs/foundation/data/pwa/src/sync-queue/sync-queue.md
Normal file
87
docs/foundation/data/pwa/src/sync-queue/sync-queue.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# `sync-queue/sync-queue.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Offline-first request queue that persists requests while the device is offline
|
||||
and replays them when connectivity returns. The queue is backend-agnostic: it
|
||||
delegates persistence to any `SyncQueueStore` (memory, `localStorage`, or
|
||||
IndexedDB), validates every entry through `SyncQueueEntrySchema` at the
|
||||
enqueue boundary, runs an exponential-backoff retry loop, and emits OTel
|
||||
counters and histograms covering enqueue / processed / failed counts plus
|
||||
flush duration. Migration from older schema versions happens automatically on
|
||||
every load.
|
||||
|
||||
## Features
|
||||
|
||||
- Pluggable store (`MemoryStore`, `LocalStorageStore`, `IndexedDbStore`).
|
||||
- Zod boundary validation on every enqueue; failures raise
|
||||
`PwaError(SYNC_QUEUE_VALIDATION)`.
|
||||
- Capacity enforcement via `PwaError(SYNC_QUEUE_FULL)` when `maxSize` is hit.
|
||||
- Auto-migration of older schema versions on every load (idempotent).
|
||||
- Single-flight `process()` — concurrent invocations short-circuit to `0`.
|
||||
- Exponential-backoff `retryDelayFor(retryCount)` accessor.
|
||||
- Injectable `Clock` and `IdGenerator` for deterministic tests.
|
||||
- OTel emission: `pwa_sync_enqueued_total`, `pwa_sync_processed_total`,
|
||||
`pwa_sync_failed_total`, `pwa_sync_flush_ms`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { SyncQueue, IndexedDbStore } from "@suaveplan/pwa";
|
||||
|
||||
const queue = new SyncQueue({ store: new IndexedDbStore() });
|
||||
await queue.enqueue("/api/v1/orders", "POST", {
|
||||
body: JSON.stringify({ sku: "abc", qty: 2 }),
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
await queue.process();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Inject a clock + id generator + custom processor + failure hook for tests:
|
||||
|
||||
```ts
|
||||
import { SyncQueue, MemoryStore, type Clock } from "@suaveplan/pwa";
|
||||
|
||||
const clock: Clock = { now: () => 1_700_000_000_000 };
|
||||
let n = 0;
|
||||
const queue = new SyncQueue({
|
||||
store: new MemoryStore(),
|
||||
clock,
|
||||
idGenerator: () => `sq_${++n}`,
|
||||
maxRetries: 2,
|
||||
retryDelay: 100,
|
||||
retryBackoffFactor: 3,
|
||||
onProcess: async (entry) => entry.url !== "/fail",
|
||||
onFailure: (entry) => console.error("permanently failed", entry.id),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new SyncQueue(options: SyncQueueOptions)`.
|
||||
- `enqueue(url, method, options?): Promise<SyncQueueEntry>`.
|
||||
- `remove(id): Promise<boolean>`.
|
||||
- `getAll(): Promise<SyncQueueEntry[]>`.
|
||||
- `size(): Promise<number>`.
|
||||
- `clear(): Promise<void>`.
|
||||
- `process(): Promise<number>`.
|
||||
- `retryDelayFor(retryCount): number`.
|
||||
- `isProcessing: boolean` (readonly).
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The queue serialises the entire snapshot on every mutation rather than
|
||||
performing per-entry CRUD against the underlying store. This keeps the
|
||||
`SyncQueueStore` contract minimal (`load` / `save`) and lets every store
|
||||
implementation guarantee atomicity simply by wrapping `save()` in its native
|
||||
transaction primitive. The `process()` body avoids `Promise.all` so failures
|
||||
in one entry cannot cancel sibling work; instead each entry is awaited
|
||||
sequentially and its outcome recorded. The histogram records the entire flush
|
||||
duration including loads and saves so the observed value matches user-felt
|
||||
latency rather than CPU-only time.
|
||||
Reference in New Issue
Block a user