lazypock 0.2.0 → 0.4.0

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 CHANGED
@@ -179,6 +179,7 @@ The main client class.
179
179
  | `storage` | `StorageAdapter` | `memoryStorage` | Custom storage adapter for token persistence |
180
180
  | `authStore` | `AuthStore` | auto-created | Explicit auth store instance |
181
181
  | `realtime` | `RealtimeService` | auto-created | Real-time service for WebSocket subscriptions |
182
+ | `cache` | [`CacheConfig`](#query-cache) | disabled | Query-cache configuration (opt-in) |
182
183
 
183
184
  #### Auto-Cancellation Methods
184
185
 
@@ -186,6 +187,14 @@ The main client class.
186
187
  - `cancelRequest(requestKey)` — Abort a single pending request by key (default `HTTP_METHOD + path`)
187
188
  - `cancelAllRequests()` — Abort all pending requests
188
189
 
190
+ #### Query-Cache Methods
191
+
192
+ - `cache(config?)` — Enable/configure the query cache at runtime (see [Query Cache](#query-cache))
193
+ - `clearCache()` — Drop every cached entry
194
+ - `invalidateCache(namespace)` — Invalidate entries for a collection / custom namespace
195
+ - `cacheStats()` — `{ hits, misses, entries }` cache statistics
196
+ - `invalidateCacheOnRealtime(collection)` — Subscribe the cache to realtime events for a collection; returns an unsubscribe fn
197
+
189
198
  #### Authentication Methods
190
199
 
191
200
  - `login(email, password, collection?)` — Login as superuser or auth collection user
@@ -349,6 +358,132 @@ client.cancelRequest('GET /api/posts?page=1');
349
358
  client.cancelAllRequests();
350
359
  ```
351
360
 
361
+ #### Single-flight dedup (`getFullList`)
362
+
363
+ `getFullList()` (and `collections.getFullList()`) are **single-flight**: concurrent
364
+ calls with the same effective options share one in-flight request instead of
365
+ firing duplicates. This means the common pattern below results in **one**
366
+ network request, and **both** callers resolve with the same data — no abort
367
+ rejection:
368
+
369
+ ```typescript
370
+ const [a, b] = await Promise.all([
371
+ client.collection('posts').getFullList(),
372
+ client.collection('posts').getFullList(),
373
+ ]);
374
+ // one GET fired; a === b
375
+ ```
376
+
377
+ Calls with **different** options (e.g. different `sort`/`filter`) are still
378
+ distinct requests. Multi-page fetches continue to work normally — each page
379
+ request is unique (page number is part of the URL), so pages never cancel each
380
+ other.
381
+
382
+ The underlying `singleFlight` option is also available on any request when you
383
+ want to coalesce concurrent identical calls yourself:
384
+
385
+ ```typescript
386
+ await client.collection('posts').getList(1, 20, { singleFlight: true });
387
+ ```
388
+
389
+ ## Query Cache
390
+
391
+ Lazypock has a built-in query cache for **GET** requests — disabled by default.
392
+ It's useful for read-heavy UIs (lists, dashboards) to avoid hammering the server.
393
+
394
+ ### Enabling
395
+
396
+ ```typescript
397
+ import { createClient } from "lazypock";
398
+
399
+ const client = createClient({
400
+ baseUrl: "https://api.example.com",
401
+ cache: {
402
+ enabled: true,
403
+ defaultTTL: 30_000, // 30s
404
+ // store: myStorage, // optional: reuse any StorageAdapter (localStorage/AsyncStorage)
405
+ },
406
+ });
407
+ ```
408
+
409
+ When enabled, **all** GET requests are cached with the default TTL, and
410
+ mutations (`create`/`update`/`delete`) automatically invalidate the affected
411
+ collection's cached entries.
412
+
413
+ ### Per-request control
414
+
415
+ ```typescript
416
+ // Cache this request (works even when the global cache is off)
417
+ await client.collection('posts').getList(1, 20, { cache: true });
418
+
419
+ // Bypass the cache — always fetch fresh (and don't store the result)
420
+ const fresh = await client.collection('posts').getList(1, 20, { cache: false });
421
+
422
+ // Custom TTL for this request
423
+ await client.collection('posts').getOne('abc', { ttl: 120_000 });
424
+
425
+ // Cache with a custom key (dedupe/override the default `METHOD path|token` key)
426
+ await client.collection('posts').getList(1, 20, { cache: { ttl: 60_000, key: 'my-list' } });
427
+ ```
428
+
429
+ ### Prefix deletion (per-operation invalidation)
430
+
431
+ Every cached entry is tagged with its operation and collection, so you can
432
+ invalidate a whole class of caches without touching the rest:
433
+
434
+ ```typescript
435
+ client.cache.deleteByPrefix('getList:posts'); // delete all getList cache for posts
436
+ client.cache.deleteByPrefix('getOne:posts'); // delete all getOne cache for posts
437
+ client.cache.deleteByPrefix('collections:getList'); // admin collection list caches
438
+ ```
439
+
440
+ The `client.cache` namespace also exposes `invalidate(ns)`, `clear()`, `stats()`,
441
+ and is callable to (re)configure (`client.cache({ enabled: true })`).
442
+
443
+ ### Invalidation
444
+
445
+ ```typescript
446
+ // Mutations invalidate the collection automatically:
447
+ await client.collection('posts').create({ title: 'New' });
448
+ await client.collection('posts').getList(1, 20); // re-fetched (cache cleared)
449
+
450
+ // Invalidate extra namespaces explicitly:
451
+ await client.collection('posts').create(
452
+ { title: 'New' },
453
+ { invalidate: ['users'] },
454
+ );
455
+
456
+ // Manual / out-of-band invalidation:
457
+ client.invalidateCache('posts');
458
+ client.clearCache();
459
+ ```
460
+
461
+ ### Cache key scoping
462
+
463
+ Cache keys are **scoped by auth token** — a logged-in user's cached data can
464
+ never leak to another user (or to anonymous visitors). Logging out/in changes
465
+ the token, so cached entries are naturally isolated per identity.
466
+
467
+ ### Realtime-driven invalidation
468
+
469
+ ```typescript
470
+ // Keep the posts cache fresh: any create/update/delete event clears it.
471
+ const stop = client.invalidateCacheOnRealtime('posts');
472
+ // later:
473
+ stop();
474
+ ```
475
+
476
+ When a realtime event arrives for the collection, its cached entries are
477
+ cleared so the next read fetches fresh data. This is **invalidate-only** —
478
+ cached list payloads are never mutated in place (a filter/sort change could
479
+ make an in-place patch serve wrong data).
480
+
481
+ ### Stats
482
+
483
+ ```typescript
484
+ client.cacheStats(); // { hits, misses, entries }
485
+ ```
486
+
352
487
  ## Error Handling
353
488
 
354
489
  The SDK throws `ApiError` on non-2xx responses: