lazypock 0.2.0 → 0.3.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/dist/index.d.cts CHANGED
@@ -50,6 +50,21 @@ interface RequestOptions {
50
50
  signal?: AbortSignal;
51
51
  /** Custom fetch implementation (for RN or test mocking) */
52
52
  fetch?: typeof globalThis.fetch;
53
+ /**
54
+ * Cache control for this request (see {@link CacheRequestOptions}).
55
+ * Resolved against the client's global cache config when unset.
56
+ */
57
+ cache?: boolean | number | {
58
+ ttl?: number;
59
+ key?: string;
60
+ };
61
+ /** Alias of `cache: <ms>` — cache this GET for `ttl` milliseconds. */
62
+ ttl?: number;
63
+ /**
64
+ * Extra cache namespaces to invalidate when this mutation succeeds.
65
+ * The current collection is always invalidated automatically.
66
+ */
67
+ invalidate?: string[];
53
68
  /**
54
69
  * Request identifier used by the auto-cancellation mechanism.
55
70
  *
@@ -158,10 +173,147 @@ declare class AuthStore {
158
173
  private notify;
159
174
  }
160
175
 
176
+ /** Options for enabling/customising the client's query cache. */
177
+ interface CacheConfig {
178
+ /**
179
+ * Master switch. When `true`, readable GET requests are cached with the
180
+ * default TTL unless a request opts out via `{ cache: false }`.
181
+ *
182
+ * When `false` (default), caching is disabled unless a request opts in
183
+ * via `{ cache: true }` or `{ ttl: <ms> }`. Opt-in works regardless.
184
+ */
185
+ enabled?: boolean;
186
+ /**
187
+ * Default time-to-live for cached entries, in milliseconds.
188
+ * @default 60_000 (1 minute)
189
+ */
190
+ defaultTTL?: number;
191
+ /**
192
+ * Optional persistence backend (same interface as AuthStore's storage).
193
+ * Defaults to an in-memory Map — swap for `localStorage` / `AsyncStorage`
194
+ * to keep the cache across page reloads / app restarts.
195
+ */
196
+ store?: StorageAdapter;
197
+ /**
198
+ * Max number of entries to keep in memory (LRU eviction).
199
+ * @default 500
200
+ */
201
+ maxEntries?: number;
202
+ /**
203
+ * When true and the client has an active realtime subscription for a
204
+ * collection, inbound create/update/delete events invalidate that
205
+ * collection's cached entries automatically.
206
+ * @default false — only local mutations invalidate (explicit + predictable)
207
+ */
208
+ invalidateOnRealtime?: boolean;
209
+ }
210
+ /** Per-request cache controls (mixed into {@link RequestOptions}). */
211
+ interface CacheRequestOptions {
212
+ /**
213
+ * Cache control for this request:
214
+ * - `true` — cache with the default (or global) TTL
215
+ * - `false` — always fetch fresh, bypass cache (and don't store the result)
216
+ * - a number — cache with this TTL in milliseconds
217
+ * - an object — `{ ttl, key }` for finer control
218
+ *
219
+ * When unset, the global `cache.enabled` flag decides.
220
+ */
221
+ cache?: boolean | number | {
222
+ ttl?: number;
223
+ key?: string;
224
+ };
225
+ /** Alias of `cache: <ms>` (convenience, reads naturally). */
226
+ ttl?: number;
227
+ /**
228
+ * Extra cache namespaces to invalidate when this mutation succeeds.
229
+ * The current collection is always invalidated automatically.
230
+ * @example create({ ... }, { invalidate: ['users'] })
231
+ */
232
+ invalidate?: string[];
233
+ }
234
+ /** LRU-ish memory store + optional persistent adapter hybrid. */
235
+ declare class CacheStore {
236
+ private memory;
237
+ private readonly ttl;
238
+ private readonly persistence?;
239
+ private readonly maxEntries;
240
+ private hits;
241
+ private misses;
242
+ private namespaceEntries;
243
+ /** Key → set of prefix tags registered for that key (e.g. `getList:posts`). */
244
+ private prefixEntries;
245
+ constructor(config?: {
246
+ defaultTTL?: number;
247
+ store?: StorageAdapter;
248
+ maxEntries?: number;
249
+ });
250
+ /** Resolve the effective TTL: request override → global default. */
251
+ private resolveTTL;
252
+ /**
253
+ * Read a cached value. Fast sync path (memory) with async persistence
254
+ * fallback for adapters whose `get` returns a Promise.
255
+ * @param key Cache key (e.g. `"GET /posts?page=1"`).
256
+ * @returns The cached value, or undefined when absent/expired (the hit is
257
+ * cleared on expiry so a stale value is never served).
258
+ */
259
+ get<T = unknown>(key: string): Promise<T | undefined>;
260
+ /**
261
+ * Store a value.
262
+ * @param key Cache key.
263
+ * @param value The response payload.
264
+ * @param ttlOverride Optional TTL override (ms).
265
+ * @param namespace Optional namespace for group invalidation.
266
+ */
267
+ set(key: string, value: unknown, ttlOverride?: number, namespace?: string, tags?: string[]): void;
268
+ /**
269
+ * Invalidate entries belonging to a namespace (e.g. a collection name).
270
+ * Also clears the namespace index entry.
271
+ */
272
+ invalidate(namespace: string): void;
273
+ /** Remove a single key. */
274
+ delete(key: string): void;
275
+ /**
276
+ * Delete every entry whose key starts with `prefix`.
277
+ *
278
+ * Useful for fine-grained invalidation, e.g.:
279
+ * ```ts
280
+ * client.cache.deleteByPrefix('getList:posts'); // delete all getList cache
281
+ * client.cache.deleteByPrefix('getOne:posts'); // delete all getOne cache
282
+ * ```
283
+ */
284
+ deleteByPrefix(prefix: string): void;
285
+ /** Drop every cached entry (memory + persistence). */
286
+ clear(): void;
287
+ /** Cache hit/miss/entry statistics. */
288
+ stats(): {
289
+ hits: number;
290
+ misses: number;
291
+ entries: number;
292
+ };
293
+ private persistKey;
294
+ private readPersisted;
295
+ }
296
+ /** Resolve per-request cache options into a usable directive. */
297
+ declare function resolveCacheDirective(opts?: {
298
+ cache?: boolean | number | {
299
+ ttl?: number;
300
+ key?: string;
301
+ };
302
+ ttl?: number;
303
+ }): {
304
+ enabled: boolean;
305
+ ttl?: number;
306
+ key?: string;
307
+ } | null;
308
+
161
309
  declare class HttpClient {
162
310
  private baseUrl;
163
311
  private authStore;
164
312
  private defaultFetch;
313
+ /** Optional query cache store (wired when the client enables caching). */
314
+ private cache?;
315
+ /** Master switch resolved from CacheConfig.enabled. */
316
+ private cacheEnabled;
165
317
  /**
166
318
  * Abort controllers for in-flight requests, keyed by their cancellation key
167
319
  * (default `METHOD path`). A new request with the same key aborts the
@@ -175,6 +327,13 @@ declare class HttpClient {
175
327
  * @param authStore The auth store providing the token for Authorization headers.
176
328
  */
177
329
  constructor(baseUrl: string, authStore: AuthStore);
330
+ /**
331
+ * Attach a cache store + master switch.
332
+ * Called by the client constructor when cache config is present.
333
+ */
334
+ setCache(cache: CacheStore, enabled: boolean): void;
335
+ /** Whether the global cache flag is on (requests opt in/out individually too). */
336
+ get cacheIsEnabled(): boolean;
178
337
  private refreshAuth;
179
338
  /**
180
339
  * Globally enable or disable auto-cancellation of duplicated pending requests.
@@ -204,7 +363,26 @@ declare class HttpClient {
204
363
  * @throws {ApiError} On non-2xx responses or when the request is aborted
205
364
  * (aborted requests throw an `ApiError` with `isAbort === true`).
206
365
  */
366
+ /** Invalidate a namespace (collection name). No-op when cache is off. */
367
+ invalidateCache(namespace: string): void;
368
+ /** Current cache statistics (hits/misses/entries), or null when disabled. */
369
+ cacheStats(): {
370
+ hits: number;
371
+ misses: number;
372
+ entries: number;
373
+ } | null;
207
374
  request<T = unknown>(method: Method, path: string, body?: unknown, options?: RequestOptions): Promise<T | null>;
375
+ /** Build a token-scoped cache key: `METHOD path|token-hash|params`. */
376
+ private cacheKeyFor;
377
+ /** Best-effort namespace (collection name) from a REST path. */
378
+ private namespaceFromPath;
379
+ /**
380
+ * Semantic prefix tags for `deleteByPrefix`, derived from the REST shape:
381
+ * - `/{collection}?...` → `getList:{collection}`
382
+ * - `/{collection}/{id}` → `getOne:{collection}`
383
+ * - `/collections?...` / `/collections/{id}` → `collections:getList` / `collections:getOne`
384
+ */
385
+ private cacheTagsFor;
208
386
  /**
209
387
  * HTTP GET.
210
388
  * @param path URL path.
@@ -681,6 +859,26 @@ declare function fieldTypeKind(field: SchemaField): FieldTypeKind;
681
859
  */
682
860
  declare function schemaFieldType(field: SchemaField): unknown;
683
861
 
862
+ /**
863
+ * Callable cache namespace: `client.cache(config)` configures, and
864
+ * `client.cache.deleteByPrefix(...)` etc. manage cached entries.
865
+ */
866
+ interface CacheController {
867
+ /** Configure the query cache at runtime. */
868
+ (config?: CacheConfig): LazypockClient;
869
+ /** Delete every entry whose key starts with `prefix` (e.g. `getList:posts`). */
870
+ deleteByPrefix(prefix: string): void;
871
+ /** Invalidate a collection's cached entries (alias of invalidateCache). */
872
+ invalidate(namespace: string): void;
873
+ /** Drop every cached entry. */
874
+ clear(): void;
875
+ /** Cache hit/miss/entry stats, or null when never configured. */
876
+ stats(): {
877
+ hits: number;
878
+ misses: number;
879
+ entries: number;
880
+ } | null;
881
+ }
684
882
  /** Options for constructing a {@link LazypockClient}. */
685
883
  interface LazypockClientOptions {
686
884
  /** API base URL (e.g. 'http://localhost:4000/api') */
@@ -691,6 +889,24 @@ interface LazypockClientOptions {
691
889
  authStore?: AuthStore;
692
890
  /** Real-time service for Phoenix Channel WebSocket subscriptions */
693
891
  realtime?: RealtimeService;
892
+ /**
893
+ * Query cache configuration. Disabled by default.
894
+ *
895
+ * ```ts
896
+ * const client = createClient({
897
+ * baseUrl: '...',
898
+ * cache: {
899
+ * enabled: true,
900
+ * defaultTTL: 30_000,
901
+ * store: myStorage, // optional persistence (same interface as auth)
902
+ * },
903
+ * });
904
+ * ```
905
+ *
906
+ * When enabled, readable GETs are cached. Requests can opt out via
907
+ * `{ cache: false }`, or opt in with a custom TTL via `{ ttl: ms }`.
908
+ */
909
+ cache?: CacheConfig;
694
910
  /**
695
911
  * Optional schema types for generating typed services at runtime.
696
912
  * When provided, `collection()` returns a service whose create/update
@@ -724,6 +940,9 @@ declare class LazypockClient {
724
940
  readonly files: FilesService;
725
941
  private collectionCache;
726
942
  private schemaByName?;
943
+ private cacheStore?;
944
+ /** Namespace → realtime unsubscribe; used for realtime-driven invalidation. */
945
+ private realtimeInvalidators;
727
946
  /**
728
947
  * Create a new Lazypock client.
729
948
  * @param options Configuration options.
@@ -778,6 +997,47 @@ declare class LazypockClient {
778
997
  cancelRequest(requestKey: string): this;
779
998
  /** Abort all pending requests. */
780
999
  cancelAllRequests(): this;
1000
+ /**
1001
+ * Configure the query cache at runtime (also a namespace for cache
1002
+ * management methods).
1003
+ *
1004
+ * ```ts
1005
+ * client.cache({ enabled: true, defaultTTL: 30_000 });
1006
+ * client.cache.deleteByPrefix('getList:posts'); // all list caches for posts
1007
+ * client.cache.deleteByPrefix('getOne:posts'); // all one-record caches
1008
+ * ```
1009
+ *
1010
+ * When enabled, GET requests cache their payload; mutations invalidate the
1011
+ * affected collection automatically. Individual requests can opt out with
1012
+ * `{ cache: false }` or override the TTL with `{ ttl: ms }`.
1013
+ */
1014
+ readonly cache: CacheController;
1015
+ /**
1016
+ * Drop every cached entry (all collections / namespaces).
1017
+ * Also disables realtime-driven invalidation subscriptions.
1018
+ */
1019
+ clearCache(): this;
1020
+ /**
1021
+ * Invalidate cached entries for a collection (or custom namespace).
1022
+ * Runs automatically on mutations — call explicitly when data changed
1023
+ * out-of-band (e.g. another client wrote to the same collection).
1024
+ */
1025
+ invalidateCache(namespace: string): this;
1026
+ /**
1027
+ * Cache hit/miss/entry statistics.
1028
+ * Returns null when caching was never configured.
1029
+ */
1030
+ cacheStats(): {
1031
+ hits: number;
1032
+ misses: number;
1033
+ entries: number;
1034
+ } | null;
1035
+ /**
1036
+ * Subscribe a collection's cache to realtime invalidation: any inbound
1037
+ * create/update/delete event for the collection clears its cached entries.
1038
+ * Returns an unsubscribe function.
1039
+ */
1040
+ invalidateCacheOnRealtime(collectionName: string): () => void;
781
1041
  /** Check whether any superuser exists (for login vs setup screen routing). */
782
1042
  checkSuperuser(): Promise<{
783
1043
  has_superuser: boolean;
@@ -870,4 +1130,4 @@ declare class TypedClient<TCollections extends LazypockCollections = LazypockCol
870
1130
  */
871
1131
  declare function createClient<TCollections extends LazypockCollections = LazypockCollections>(options: LazypockClientOptions): TypedClient<TCollections>;
872
1132
 
873
- export { ApiError, type ApiRecord, type AuthModel, AuthStore, type CollectionSchema, CollectionService, type CollectionsMessage, CollectionsService, type CreateData, type FileRecord, FilesService, HttpClient, LazypockClient, type LazypockClientOptions, type LazypockCollections, type ListResult, type RealtimeCallback, type RealtimeMessage, RealtimeService, type RecordShape, type RequestOptions, type SchemaField, type StorageAdapter, type SystemFields, TypedClient, type UpdateData, collectionTypeName, createClient, fieldTypeKind, fieldTypeScriptType, generateTypes, getFileUrl, getScaleUrl, getThumbUrl, schemaFieldType, wsUrlFromBaseUrl };
1133
+ export { ApiError, type ApiRecord, type AuthModel, AuthStore, type CacheConfig, type CacheRequestOptions, CacheStore, type CollectionSchema, CollectionService, type CollectionsMessage, CollectionsService, type CreateData, type FileRecord, FilesService, HttpClient, LazypockClient, type LazypockClientOptions, type LazypockCollections, type ListResult, type RealtimeCallback, type RealtimeMessage, RealtimeService, type RecordShape, type RequestOptions, type SchemaField, type StorageAdapter, type SystemFields, TypedClient, type UpdateData, collectionTypeName, createClient, fieldTypeKind, fieldTypeScriptType, generateTypes, getFileUrl, getScaleUrl, getThumbUrl, resolveCacheDirective, schemaFieldType, wsUrlFromBaseUrl };
package/dist/index.d.ts CHANGED
@@ -50,6 +50,21 @@ interface RequestOptions {
50
50
  signal?: AbortSignal;
51
51
  /** Custom fetch implementation (for RN or test mocking) */
52
52
  fetch?: typeof globalThis.fetch;
53
+ /**
54
+ * Cache control for this request (see {@link CacheRequestOptions}).
55
+ * Resolved against the client's global cache config when unset.
56
+ */
57
+ cache?: boolean | number | {
58
+ ttl?: number;
59
+ key?: string;
60
+ };
61
+ /** Alias of `cache: <ms>` — cache this GET for `ttl` milliseconds. */
62
+ ttl?: number;
63
+ /**
64
+ * Extra cache namespaces to invalidate when this mutation succeeds.
65
+ * The current collection is always invalidated automatically.
66
+ */
67
+ invalidate?: string[];
53
68
  /**
54
69
  * Request identifier used by the auto-cancellation mechanism.
55
70
  *
@@ -158,10 +173,147 @@ declare class AuthStore {
158
173
  private notify;
159
174
  }
160
175
 
176
+ /** Options for enabling/customising the client's query cache. */
177
+ interface CacheConfig {
178
+ /**
179
+ * Master switch. When `true`, readable GET requests are cached with the
180
+ * default TTL unless a request opts out via `{ cache: false }`.
181
+ *
182
+ * When `false` (default), caching is disabled unless a request opts in
183
+ * via `{ cache: true }` or `{ ttl: <ms> }`. Opt-in works regardless.
184
+ */
185
+ enabled?: boolean;
186
+ /**
187
+ * Default time-to-live for cached entries, in milliseconds.
188
+ * @default 60_000 (1 minute)
189
+ */
190
+ defaultTTL?: number;
191
+ /**
192
+ * Optional persistence backend (same interface as AuthStore's storage).
193
+ * Defaults to an in-memory Map — swap for `localStorage` / `AsyncStorage`
194
+ * to keep the cache across page reloads / app restarts.
195
+ */
196
+ store?: StorageAdapter;
197
+ /**
198
+ * Max number of entries to keep in memory (LRU eviction).
199
+ * @default 500
200
+ */
201
+ maxEntries?: number;
202
+ /**
203
+ * When true and the client has an active realtime subscription for a
204
+ * collection, inbound create/update/delete events invalidate that
205
+ * collection's cached entries automatically.
206
+ * @default false — only local mutations invalidate (explicit + predictable)
207
+ */
208
+ invalidateOnRealtime?: boolean;
209
+ }
210
+ /** Per-request cache controls (mixed into {@link RequestOptions}). */
211
+ interface CacheRequestOptions {
212
+ /**
213
+ * Cache control for this request:
214
+ * - `true` — cache with the default (or global) TTL
215
+ * - `false` — always fetch fresh, bypass cache (and don't store the result)
216
+ * - a number — cache with this TTL in milliseconds
217
+ * - an object — `{ ttl, key }` for finer control
218
+ *
219
+ * When unset, the global `cache.enabled` flag decides.
220
+ */
221
+ cache?: boolean | number | {
222
+ ttl?: number;
223
+ key?: string;
224
+ };
225
+ /** Alias of `cache: <ms>` (convenience, reads naturally). */
226
+ ttl?: number;
227
+ /**
228
+ * Extra cache namespaces to invalidate when this mutation succeeds.
229
+ * The current collection is always invalidated automatically.
230
+ * @example create({ ... }, { invalidate: ['users'] })
231
+ */
232
+ invalidate?: string[];
233
+ }
234
+ /** LRU-ish memory store + optional persistent adapter hybrid. */
235
+ declare class CacheStore {
236
+ private memory;
237
+ private readonly ttl;
238
+ private readonly persistence?;
239
+ private readonly maxEntries;
240
+ private hits;
241
+ private misses;
242
+ private namespaceEntries;
243
+ /** Key → set of prefix tags registered for that key (e.g. `getList:posts`). */
244
+ private prefixEntries;
245
+ constructor(config?: {
246
+ defaultTTL?: number;
247
+ store?: StorageAdapter;
248
+ maxEntries?: number;
249
+ });
250
+ /** Resolve the effective TTL: request override → global default. */
251
+ private resolveTTL;
252
+ /**
253
+ * Read a cached value. Fast sync path (memory) with async persistence
254
+ * fallback for adapters whose `get` returns a Promise.
255
+ * @param key Cache key (e.g. `"GET /posts?page=1"`).
256
+ * @returns The cached value, or undefined when absent/expired (the hit is
257
+ * cleared on expiry so a stale value is never served).
258
+ */
259
+ get<T = unknown>(key: string): Promise<T | undefined>;
260
+ /**
261
+ * Store a value.
262
+ * @param key Cache key.
263
+ * @param value The response payload.
264
+ * @param ttlOverride Optional TTL override (ms).
265
+ * @param namespace Optional namespace for group invalidation.
266
+ */
267
+ set(key: string, value: unknown, ttlOverride?: number, namespace?: string, tags?: string[]): void;
268
+ /**
269
+ * Invalidate entries belonging to a namespace (e.g. a collection name).
270
+ * Also clears the namespace index entry.
271
+ */
272
+ invalidate(namespace: string): void;
273
+ /** Remove a single key. */
274
+ delete(key: string): void;
275
+ /**
276
+ * Delete every entry whose key starts with `prefix`.
277
+ *
278
+ * Useful for fine-grained invalidation, e.g.:
279
+ * ```ts
280
+ * client.cache.deleteByPrefix('getList:posts'); // delete all getList cache
281
+ * client.cache.deleteByPrefix('getOne:posts'); // delete all getOne cache
282
+ * ```
283
+ */
284
+ deleteByPrefix(prefix: string): void;
285
+ /** Drop every cached entry (memory + persistence). */
286
+ clear(): void;
287
+ /** Cache hit/miss/entry statistics. */
288
+ stats(): {
289
+ hits: number;
290
+ misses: number;
291
+ entries: number;
292
+ };
293
+ private persistKey;
294
+ private readPersisted;
295
+ }
296
+ /** Resolve per-request cache options into a usable directive. */
297
+ declare function resolveCacheDirective(opts?: {
298
+ cache?: boolean | number | {
299
+ ttl?: number;
300
+ key?: string;
301
+ };
302
+ ttl?: number;
303
+ }): {
304
+ enabled: boolean;
305
+ ttl?: number;
306
+ key?: string;
307
+ } | null;
308
+
161
309
  declare class HttpClient {
162
310
  private baseUrl;
163
311
  private authStore;
164
312
  private defaultFetch;
313
+ /** Optional query cache store (wired when the client enables caching). */
314
+ private cache?;
315
+ /** Master switch resolved from CacheConfig.enabled. */
316
+ private cacheEnabled;
165
317
  /**
166
318
  * Abort controllers for in-flight requests, keyed by their cancellation key
167
319
  * (default `METHOD path`). A new request with the same key aborts the
@@ -175,6 +327,13 @@ declare class HttpClient {
175
327
  * @param authStore The auth store providing the token for Authorization headers.
176
328
  */
177
329
  constructor(baseUrl: string, authStore: AuthStore);
330
+ /**
331
+ * Attach a cache store + master switch.
332
+ * Called by the client constructor when cache config is present.
333
+ */
334
+ setCache(cache: CacheStore, enabled: boolean): void;
335
+ /** Whether the global cache flag is on (requests opt in/out individually too). */
336
+ get cacheIsEnabled(): boolean;
178
337
  private refreshAuth;
179
338
  /**
180
339
  * Globally enable or disable auto-cancellation of duplicated pending requests.
@@ -204,7 +363,26 @@ declare class HttpClient {
204
363
  * @throws {ApiError} On non-2xx responses or when the request is aborted
205
364
  * (aborted requests throw an `ApiError` with `isAbort === true`).
206
365
  */
366
+ /** Invalidate a namespace (collection name). No-op when cache is off. */
367
+ invalidateCache(namespace: string): void;
368
+ /** Current cache statistics (hits/misses/entries), or null when disabled. */
369
+ cacheStats(): {
370
+ hits: number;
371
+ misses: number;
372
+ entries: number;
373
+ } | null;
207
374
  request<T = unknown>(method: Method, path: string, body?: unknown, options?: RequestOptions): Promise<T | null>;
375
+ /** Build a token-scoped cache key: `METHOD path|token-hash|params`. */
376
+ private cacheKeyFor;
377
+ /** Best-effort namespace (collection name) from a REST path. */
378
+ private namespaceFromPath;
379
+ /**
380
+ * Semantic prefix tags for `deleteByPrefix`, derived from the REST shape:
381
+ * - `/{collection}?...` → `getList:{collection}`
382
+ * - `/{collection}/{id}` → `getOne:{collection}`
383
+ * - `/collections?...` / `/collections/{id}` → `collections:getList` / `collections:getOne`
384
+ */
385
+ private cacheTagsFor;
208
386
  /**
209
387
  * HTTP GET.
210
388
  * @param path URL path.
@@ -681,6 +859,26 @@ declare function fieldTypeKind(field: SchemaField): FieldTypeKind;
681
859
  */
682
860
  declare function schemaFieldType(field: SchemaField): unknown;
683
861
 
862
+ /**
863
+ * Callable cache namespace: `client.cache(config)` configures, and
864
+ * `client.cache.deleteByPrefix(...)` etc. manage cached entries.
865
+ */
866
+ interface CacheController {
867
+ /** Configure the query cache at runtime. */
868
+ (config?: CacheConfig): LazypockClient;
869
+ /** Delete every entry whose key starts with `prefix` (e.g. `getList:posts`). */
870
+ deleteByPrefix(prefix: string): void;
871
+ /** Invalidate a collection's cached entries (alias of invalidateCache). */
872
+ invalidate(namespace: string): void;
873
+ /** Drop every cached entry. */
874
+ clear(): void;
875
+ /** Cache hit/miss/entry stats, or null when never configured. */
876
+ stats(): {
877
+ hits: number;
878
+ misses: number;
879
+ entries: number;
880
+ } | null;
881
+ }
684
882
  /** Options for constructing a {@link LazypockClient}. */
685
883
  interface LazypockClientOptions {
686
884
  /** API base URL (e.g. 'http://localhost:4000/api') */
@@ -691,6 +889,24 @@ interface LazypockClientOptions {
691
889
  authStore?: AuthStore;
692
890
  /** Real-time service for Phoenix Channel WebSocket subscriptions */
693
891
  realtime?: RealtimeService;
892
+ /**
893
+ * Query cache configuration. Disabled by default.
894
+ *
895
+ * ```ts
896
+ * const client = createClient({
897
+ * baseUrl: '...',
898
+ * cache: {
899
+ * enabled: true,
900
+ * defaultTTL: 30_000,
901
+ * store: myStorage, // optional persistence (same interface as auth)
902
+ * },
903
+ * });
904
+ * ```
905
+ *
906
+ * When enabled, readable GETs are cached. Requests can opt out via
907
+ * `{ cache: false }`, or opt in with a custom TTL via `{ ttl: ms }`.
908
+ */
909
+ cache?: CacheConfig;
694
910
  /**
695
911
  * Optional schema types for generating typed services at runtime.
696
912
  * When provided, `collection()` returns a service whose create/update
@@ -724,6 +940,9 @@ declare class LazypockClient {
724
940
  readonly files: FilesService;
725
941
  private collectionCache;
726
942
  private schemaByName?;
943
+ private cacheStore?;
944
+ /** Namespace → realtime unsubscribe; used for realtime-driven invalidation. */
945
+ private realtimeInvalidators;
727
946
  /**
728
947
  * Create a new Lazypock client.
729
948
  * @param options Configuration options.
@@ -778,6 +997,47 @@ declare class LazypockClient {
778
997
  cancelRequest(requestKey: string): this;
779
998
  /** Abort all pending requests. */
780
999
  cancelAllRequests(): this;
1000
+ /**
1001
+ * Configure the query cache at runtime (also a namespace for cache
1002
+ * management methods).
1003
+ *
1004
+ * ```ts
1005
+ * client.cache({ enabled: true, defaultTTL: 30_000 });
1006
+ * client.cache.deleteByPrefix('getList:posts'); // all list caches for posts
1007
+ * client.cache.deleteByPrefix('getOne:posts'); // all one-record caches
1008
+ * ```
1009
+ *
1010
+ * When enabled, GET requests cache their payload; mutations invalidate the
1011
+ * affected collection automatically. Individual requests can opt out with
1012
+ * `{ cache: false }` or override the TTL with `{ ttl: ms }`.
1013
+ */
1014
+ readonly cache: CacheController;
1015
+ /**
1016
+ * Drop every cached entry (all collections / namespaces).
1017
+ * Also disables realtime-driven invalidation subscriptions.
1018
+ */
1019
+ clearCache(): this;
1020
+ /**
1021
+ * Invalidate cached entries for a collection (or custom namespace).
1022
+ * Runs automatically on mutations — call explicitly when data changed
1023
+ * out-of-band (e.g. another client wrote to the same collection).
1024
+ */
1025
+ invalidateCache(namespace: string): this;
1026
+ /**
1027
+ * Cache hit/miss/entry statistics.
1028
+ * Returns null when caching was never configured.
1029
+ */
1030
+ cacheStats(): {
1031
+ hits: number;
1032
+ misses: number;
1033
+ entries: number;
1034
+ } | null;
1035
+ /**
1036
+ * Subscribe a collection's cache to realtime invalidation: any inbound
1037
+ * create/update/delete event for the collection clears its cached entries.
1038
+ * Returns an unsubscribe function.
1039
+ */
1040
+ invalidateCacheOnRealtime(collectionName: string): () => void;
781
1041
  /** Check whether any superuser exists (for login vs setup screen routing). */
782
1042
  checkSuperuser(): Promise<{
783
1043
  has_superuser: boolean;
@@ -870,4 +1130,4 @@ declare class TypedClient<TCollections extends LazypockCollections = LazypockCol
870
1130
  */
871
1131
  declare function createClient<TCollections extends LazypockCollections = LazypockCollections>(options: LazypockClientOptions): TypedClient<TCollections>;
872
1132
 
873
- export { ApiError, type ApiRecord, type AuthModel, AuthStore, type CollectionSchema, CollectionService, type CollectionsMessage, CollectionsService, type CreateData, type FileRecord, FilesService, HttpClient, LazypockClient, type LazypockClientOptions, type LazypockCollections, type ListResult, type RealtimeCallback, type RealtimeMessage, RealtimeService, type RecordShape, type RequestOptions, type SchemaField, type StorageAdapter, type SystemFields, TypedClient, type UpdateData, collectionTypeName, createClient, fieldTypeKind, fieldTypeScriptType, generateTypes, getFileUrl, getScaleUrl, getThumbUrl, schemaFieldType, wsUrlFromBaseUrl };
1133
+ export { ApiError, type ApiRecord, type AuthModel, AuthStore, type CacheConfig, type CacheRequestOptions, CacheStore, type CollectionSchema, CollectionService, type CollectionsMessage, CollectionsService, type CreateData, type FileRecord, FilesService, HttpClient, LazypockClient, type LazypockClientOptions, type LazypockCollections, type ListResult, type RealtimeCallback, type RealtimeMessage, RealtimeService, type RecordShape, type RequestOptions, type SchemaField, type StorageAdapter, type SystemFields, TypedClient, type UpdateData, collectionTypeName, createClient, fieldTypeKind, fieldTypeScriptType, generateTypes, getFileUrl, getScaleUrl, getThumbUrl, resolveCacheDirective, schemaFieldType, wsUrlFromBaseUrl };