lazypock 0.4.0 → 0.6.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.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  fieldTypeScriptType,
5
5
  generateTypes,
6
6
  schemaFieldType
7
- } from "./chunk-Y6PMBL2S.js";
7
+ } from "./chunk-HOBNUW5Z.js";
8
8
 
9
9
  // src/types.ts
10
10
  var ApiError = class extends Error {
@@ -17,201 +17,6 @@ var ApiError = class extends Error {
17
17
  }
18
18
  };
19
19
 
20
- // src/cache.ts
21
- var CacheStore = class {
22
- constructor(config = {}) {
23
- this.memory = /* @__PURE__ */ new Map();
24
- this.hits = 0;
25
- this.misses = 0;
26
- this.namespaceEntries = /* @__PURE__ */ new Map();
27
- /** Key → set of prefix tags registered for that key (e.g. `getList:posts`). */
28
- this.prefixEntries = /* @__PURE__ */ new Map();
29
- this.ttl = config.defaultTTL ?? 6e4;
30
- this.persistence = config.store;
31
- this.maxEntries = config.maxEntries ?? 500;
32
- }
33
- /** Resolve the effective TTL: request override → global default. */
34
- resolveTTL(ttl) {
35
- return ttl && ttl > 0 ? ttl : this.ttl;
36
- }
37
- /**
38
- * Read a cached value. Fast sync path (memory) with async persistence
39
- * fallback for adapters whose `get` returns a Promise.
40
- * @param key Cache key (e.g. `"GET /posts?page=1"`).
41
- * @returns The cached value, or undefined when absent/expired (the hit is
42
- * cleared on expiry so a stale value is never served).
43
- */
44
- async get(key) {
45
- const mem = this.memory.get(key);
46
- if (mem !== void 0) {
47
- if (Date.now() > mem.expiresAt) {
48
- this.delete(key);
49
- this.misses++;
50
- return void 0;
51
- }
52
- this.memory.delete(key);
53
- this.memory.set(key, mem);
54
- this.hits++;
55
- return mem.value;
56
- }
57
- if (this.persistence) {
58
- const entry = await this.readPersisted(key);
59
- if (entry) {
60
- if (Date.now() > entry.expiresAt) {
61
- this.delete(key);
62
- this.misses++;
63
- return void 0;
64
- }
65
- this.hits++;
66
- return entry.value;
67
- }
68
- }
69
- this.misses++;
70
- return void 0;
71
- }
72
- /**
73
- * Store a value.
74
- * @param key Cache key.
75
- * @param value The response payload.
76
- * @param ttlOverride Optional TTL override (ms).
77
- * @param namespace Optional namespace for group invalidation.
78
- */
79
- set(key, value, ttlOverride, namespace, tags) {
80
- const expiresAt = Date.now() + this.resolveTTL(ttlOverride);
81
- const entry = { value, expiresAt, namespace, tags };
82
- this.memory.set(key, entry);
83
- if (this.memory.size > this.maxEntries) {
84
- const oldest = this.memory.keys().next().value;
85
- if (oldest !== void 0) this.delete(oldest);
86
- }
87
- if (namespace) {
88
- let keys = this.namespaceEntries.get(namespace);
89
- if (!keys) {
90
- keys = /* @__PURE__ */ new Set();
91
- this.namespaceEntries.set(namespace, keys);
92
- }
93
- keys.add(key);
94
- }
95
- for (const tag of tags ?? []) {
96
- let keys = this.prefixEntries.get(tag);
97
- if (!keys) {
98
- keys = /* @__PURE__ */ new Set();
99
- this.prefixEntries.set(tag, keys);
100
- }
101
- keys.add(key);
102
- }
103
- if (this.persistence) {
104
- void this.persistence.set(this.persistKey(key), JSON.stringify(entry));
105
- }
106
- }
107
- /**
108
- * Invalidate entries belonging to a namespace (e.g. a collection name).
109
- * Also clears the namespace index entry.
110
- */
111
- invalidate(namespace) {
112
- const keys = Array.from(this.namespaceEntries.get(namespace) ?? []);
113
- for (const key of keys) this.delete(key);
114
- this.namespaceEntries.delete(namespace);
115
- }
116
- /** Remove a single key. */
117
- delete(key) {
118
- const entry = this.memory.get(key);
119
- if (entry?.namespace) {
120
- const set = this.namespaceEntries.get(entry.namespace);
121
- if (set) {
122
- set.delete(key);
123
- if (set.size === 0) this.namespaceEntries.delete(entry.namespace);
124
- }
125
- }
126
- for (const tag of entry?.tags ?? []) {
127
- const set = this.prefixEntries.get(tag);
128
- if (set) {
129
- set.delete(key);
130
- if (set.size === 0) this.prefixEntries.delete(tag);
131
- }
132
- }
133
- this.memory.delete(key);
134
- if (this.persistence) {
135
- void this.persistence.remove(this.persistKey(key));
136
- }
137
- }
138
- /**
139
- * Delete every entry whose key starts with `prefix`.
140
- *
141
- * Useful for fine-grained invalidation, e.g.:
142
- * ```ts
143
- * client.cache.deleteByPrefix('getList:posts'); // delete all getList cache
144
- * client.cache.deleteByPrefix('getOne:posts'); // delete all getOne cache
145
- * ```
146
- */
147
- deleteByPrefix(prefix) {
148
- if (!prefix) return;
149
- const tagged = this.prefixEntries.get(prefix);
150
- if (tagged) {
151
- for (const key of Array.from(tagged)) this.delete(key);
152
- this.prefixEntries.delete(prefix);
153
- return;
154
- }
155
- for (const key of Array.from(this.memory.keys())) {
156
- if (key.startsWith(prefix)) this.delete(key);
157
- }
158
- }
159
- /** Drop every cached entry (memory + persistence). */
160
- clear() {
161
- this.memory.clear();
162
- this.namespaceEntries.clear();
163
- this.prefixEntries.clear();
164
- }
165
- /** Cache hit/miss/entry statistics. */
166
- stats() {
167
- return { hits: this.hits, misses: this.misses, entries: this.memory.size };
168
- }
169
- persistKey(key) {
170
- return "lazypock:cache:" + key;
171
- }
172
- async readPersisted(key) {
173
- if (!this.persistence) return void 0;
174
- const raw = await this.persistence.get(this.persistKey(key));
175
- if (raw == null) return void 0;
176
- try {
177
- const entry = JSON.parse(raw);
178
- this.memory.set(key, entry);
179
- if (entry.namespace) {
180
- let keys = this.namespaceEntries.get(entry.namespace);
181
- if (!keys) {
182
- keys = /* @__PURE__ */ new Set();
183
- this.namespaceEntries.set(entry.namespace, keys);
184
- }
185
- keys.add(key);
186
- }
187
- for (const tag of entry.tags ?? []) {
188
- let keys = this.prefixEntries.get(tag);
189
- if (!keys) {
190
- keys = /* @__PURE__ */ new Set();
191
- this.prefixEntries.set(tag, keys);
192
- }
193
- keys.add(key);
194
- }
195
- return entry;
196
- } catch {
197
- void this.persistence.remove(this.persistKey(key));
198
- return void 0;
199
- }
200
- }
201
- };
202
- function resolveCacheDirective(opts) {
203
- if (!opts) return null;
204
- if (typeof opts.ttl === "number" && opts.ttl > 0) {
205
- return { enabled: true, ttl: opts.ttl };
206
- }
207
- const c = opts.cache;
208
- if (c === void 0) return null;
209
- if (c === true) return { enabled: true };
210
- if (c === false) return { enabled: false };
211
- if (typeof c === "number") return { enabled: true, ttl: c > 0 ? c : void 0 };
212
- return { enabled: true, ttl: c.ttl, key: c.key };
213
- }
214
-
215
20
  // src/http.ts
216
21
  function isAbortError(err) {
217
22
  return err instanceof Error && (err.name === "AbortError" || err.message === "Aborted");
@@ -222,8 +27,6 @@ var HttpClient = class {
222
27
  * @param authStore The auth store providing the token for Authorization headers.
223
28
  */
224
29
  constructor(baseUrl, authStore) {
225
- /** Master switch resolved from CacheConfig.enabled. */
226
- this.cacheEnabled = false;
227
30
  /**
228
31
  * Abort controllers for in-flight requests, keyed by their cancellation key
229
32
  * (default `METHOD path`). A new request with the same key aborts the
@@ -243,18 +46,6 @@ var HttpClient = class {
243
46
  this.authStore = authStore;
244
47
  this.defaultFetch = globalThis.fetch.bind(globalThis);
245
48
  }
246
- /**
247
- * Attach a cache store + master switch.
248
- * Called by the client constructor when cache config is present.
249
- */
250
- setCache(cache, enabled) {
251
- this.cache = cache;
252
- this.cacheEnabled = enabled;
253
- }
254
- /** Whether the global cache flag is on (requests opt in/out individually too). */
255
- get cacheIsEnabled() {
256
- return this.cacheEnabled;
257
- }
258
49
  async refreshAuth() {
259
50
  const collection = this.authStore.collectionName;
260
51
  if (!collection) return null;
@@ -331,25 +122,10 @@ var HttpClient = class {
331
122
  * @throws {ApiError} On non-2xx responses or when the request is aborted
332
123
  * (aborted requests throw an `ApiError` with `isAbort === true`).
333
124
  */
334
- /** Invalidate a namespace (collection name). No-op when cache is off. */
335
- invalidateCache(namespace) {
336
- this.cache?.invalidate(namespace);
337
- }
338
- /** Current cache statistics (hits/misses/entries), or null when disabled. */
339
- cacheStats() {
340
- return this.cache ? this.cache.stats() : null;
341
- }
342
125
  async request(method, path, body, options) {
343
126
  if (this.authStore.isExpired && this.authStore.collectionName) {
344
127
  await this.refreshAuth();
345
128
  }
346
- const cacheDirective = resolveCacheDirective(options);
347
- const wantCache = cacheDirective !== null ? cacheDirective.enabled : this.cacheEnabled;
348
- const cacheKey = method === "GET" && this.cache && wantCache ? this.cacheKeyFor(method, path, options?.params) : null;
349
- if (cacheKey !== null) {
350
- const hit = await this.cache?.get(cacheKey);
351
- if (hit !== void 0) return hit;
352
- }
353
129
  let requestKey = options?.requestKey === void 0 ? options?.cancelKey ?? `${method} ${path}` : options.requestKey;
354
130
  if (options?.autoCancel === false) requestKey = null;
355
131
  if (options?.singleFlight && requestKey !== null) {
@@ -384,9 +160,7 @@ var HttpClient = class {
384
160
  options,
385
161
  signal,
386
162
  requestKey,
387
- controller,
388
- cacheKey,
389
- cacheDirective
163
+ controller
390
164
  );
391
165
  } finally {
392
166
  if (requestKey !== null) {
@@ -403,10 +177,10 @@ var HttpClient = class {
403
177
  return promise;
404
178
  }
405
179
  /**
406
- * Execute the actual HTTP request (fetch + parse + cache). Called by {@link request}
180
+ * Execute the actual HTTP request (fetch + parse). Called by {@link request}
407
181
  * as the inner in-flight unit so single-flight callers can reuse the promise.
408
182
  */
409
- async doRequest(method, path, body, options, signal, requestKey, controller, cacheKey, cacheDirective) {
183
+ async doRequest(method, path, body, options, signal, requestKey, controller) {
410
184
  let url = this.baseUrl + path;
411
185
  if (options?.params) {
412
186
  const qs = new URLSearchParams(options.params).toString();
@@ -471,63 +245,8 @@ var HttpClient = class {
471
245
  res.status
472
246
  );
473
247
  }
474
- if (cacheKey !== null && this.cache) {
475
- const namespace = this.namespaceFromPath(path);
476
- const ttl = cacheDirective?.ttl;
477
- const tags = this.cacheTagsFor(path, namespace);
478
- this.cache.set(
479
- cacheKey,
480
- data,
481
- ttl,
482
- namespace ?? void 0,
483
- tags
484
- );
485
- }
486
- if (method !== "GET" && this.cache) {
487
- const namespaces = /* @__PURE__ */ new Set();
488
- const ns = this.namespaceFromPath(path);
489
- if (ns) namespaces.add(ns);
490
- for (const extra of options?.invalidate ?? []) {
491
- if (extra) namespaces.add(extra);
492
- }
493
- for (const nsName of namespaces) this.cache.invalidate(nsName);
494
- }
495
248
  return data;
496
249
  }
497
- // ── Cache key/namespace helpers ──
498
- /** Build a token-scoped cache key: `METHOD path|token-hash|params`. */
499
- cacheKeyFor(method, path, params) {
500
- const token = this.authStore.token || "anon";
501
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
502
- return `${method} ${path}${qs}|${token}`;
503
- }
504
- /** Best-effort namespace (collection name) from a REST path. */
505
- namespaceFromPath(path) {
506
- const clean = path.split("?")[0];
507
- const parts = clean.split("/").filter(Boolean);
508
- if (parts.length === 0) return void 0;
509
- if (parts[0] === "collections" || parts[0] === "_superusers") {
510
- return parts[0];
511
- }
512
- return parts[0];
513
- }
514
- /**
515
- * Semantic prefix tags for `deleteByPrefix`, derived from the REST shape:
516
- * - `/{collection}?...` → `getList:{collection}`
517
- * - `/{collection}/{id}` → `getOne:{collection}`
518
- * - `/collections?...` / `/collections/{id}` → `collections:getList` / `collections:getOne`
519
- */
520
- cacheTagsFor(path, namespace) {
521
- if (!namespace) return [];
522
- const clean = path.split("?")[0];
523
- const parts = clean.split("/").filter(Boolean);
524
- if (parts[0] === "collections" || parts[0] === "_superusers") {
525
- const op2 = parts.length >= 2 ? "getOne" : "getList";
526
- return [`${namespace}:${op2}`];
527
- }
528
- const op = parts.length >= 2 ? "getOne" : "getList";
529
- return [`${op}:${namespace}`];
530
- }
531
250
  /**
532
251
  * HTTP GET.
533
252
  * @param path URL path.
@@ -739,17 +458,104 @@ function normalizeAction(event, rawAction) {
739
458
  if (event === "delete" || event === "record_delete") return "delete";
740
459
  return "update";
741
460
  }
742
- var CollectionService = class {
461
+ var CollectionService = class _CollectionService {
743
462
  /** @internal */
744
- constructor(http, collectionName, authStore, realtime) {
463
+ constructor(http, collectionName, authStore, realtime, schema) {
745
464
  this.http = http;
746
465
  this.collectionName = collectionName;
747
466
  this.authStore = authStore;
748
467
  this.realtime = realtime;
468
+ this.schema = schema;
749
469
  }
750
470
  encodeId(id) {
751
471
  return encodeURIComponent(id);
752
472
  }
473
+ // ── Field projection (`.select`) ──
474
+ /**
475
+ * Project list/read responses to the given fields (PocketBase `fields`
476
+ * param). Field names are type-checked when this service is typed
477
+ * (codegen / `typed<T>()`).
478
+ *
479
+ * Returns a derived service — the original is untouched.
480
+ *
481
+ * @example
482
+ * ```ts
483
+ * const t = await client.collection("posts").select("id", "title").getList();
484
+ * // GET /api/posts?fields=id,title
485
+ *
486
+ * // All visible fields (hidden fields are excluded server-side)
487
+ * const all = await client.collection("posts").select("*").getList();
488
+ * // GET /api/posts?fields=id,title,published,… (schema known)
489
+ * ```
490
+ *
491
+ * When no schema is known, the default (no `select()` call) sends no
492
+ * `fields` param — the server returns every non-password field.
493
+ * Pass `select("*")` to restore the default after a projection.
494
+ */
495
+ select(...fields) {
496
+ const preset = fields.length === 0 ? "*" : fields.join(",");
497
+ const derived = new _CollectionService(
498
+ this.http,
499
+ this.collectionName,
500
+ this.authStore,
501
+ this.realtime,
502
+ this.schema
503
+ );
504
+ derived.fieldsPreset = preset;
505
+ if (this.schema && preset !== "*") {
506
+ const known = new Set(this.schema.fields?.map((f) => f.name) ?? []);
507
+ for (const f of fields) {
508
+ if (!known.has(f)) {
509
+ console.warn(
510
+ `[lazypock] select("${f}"): unknown field on collection "${this.collectionName}"`
511
+ );
512
+ }
513
+ }
514
+ }
515
+ return derived;
516
+ }
517
+ /**
518
+ * Fields to send with reads:
519
+ * 1. explicit `options.fields` (caller wins)
520
+ * 2. `select()` preset
521
+ * 3. schema default — all visible (non-hidden, non-system) fields
522
+ */
523
+ effectiveFields(optionsFields) {
524
+ if (optionsFields !== void 0) return optionsFields;
525
+ if (this.fieldsPreset !== void 0) {
526
+ if (this.fieldsPreset === "*") {
527
+ return this.visibleFields();
528
+ }
529
+ return this.fieldsPreset;
530
+ }
531
+ return this.visibleFields();
532
+ }
533
+ /**
534
+ * All non-hidden, non-system, non-password field names from the schema.
535
+ * `undefined` when no schema is available (server decides).
536
+ */
537
+ visibleFields() {
538
+ if (!this.schema?.fields) return void 0;
539
+ const visible = this.schema.fields.filter(
540
+ (f) => !f.hidden && f.type !== "password" && f.type !== "autodate"
541
+ ).map((f) => f.name);
542
+ return visible.length > 0 ? visible.join(",") : void 0;
543
+ }
544
+ /** Warn once when an expand field is not a relation (schema known). */
545
+ validateExpand(expand) {
546
+ if (!expand || !this.schema?.fields) return;
547
+ const relations = new Set(
548
+ this.schema.fields.filter((f) => f.type === "relation").map((f) => f.name)
549
+ );
550
+ for (const f of expand.split(",")) {
551
+ const name = f.trim();
552
+ if (name && !relations.has(name)) {
553
+ console.warn(
554
+ `[lazypock] expand("${name}"): field is not a relation on collection "${this.collectionName}"`
555
+ );
556
+ }
557
+ }
558
+ }
753
559
  /**
754
560
  * Fetch a paginated list of records (PocketBase `getList`).
755
561
  *
@@ -765,13 +571,17 @@ var CollectionService = class {
765
571
  fetch,
766
572
  headers,
767
573
  signal,
768
- cache,
769
- ttl,
770
- invalidate,
771
574
  singleFlight,
772
575
  params,
773
576
  ...queryParams
774
577
  } = options ?? {};
578
+ const fields = this.effectiveFields(queryParams.fields);
579
+ if (fields !== void 0 && queryParams.fields === void 0) {
580
+ queryParams.fields = fields;
581
+ }
582
+ if (typeof queryParams.expand === "string") {
583
+ this.validateExpand(queryParams.expand);
584
+ }
775
585
  const qs = new URLSearchParams(
776
586
  Object.fromEntries(
777
587
  Object.entries({
@@ -790,9 +600,6 @@ var CollectionService = class {
790
600
  fetch,
791
601
  headers,
792
602
  signal,
793
- cache,
794
- ttl,
795
- invalidate,
796
603
  singleFlight,
797
604
  params
798
605
  }
@@ -845,8 +652,16 @@ var CollectionService = class {
845
652
  * @param options Optional request options.
846
653
  */
847
654
  getOne(id, options) {
655
+ const fields = this.effectiveFields(options?.fields);
656
+ if (typeof options?.expand === "string") {
657
+ this.validateExpand(options.expand);
658
+ }
659
+ const qs = new URLSearchParams();
660
+ if (fields !== void 0) qs.set("fields", fields);
661
+ if (typeof options?.expand === "string") qs.set("expand", options.expand);
662
+ const qsStr = qs.toString();
848
663
  return this.http.get(
849
- "/" + this.encodeId(this.collectionName) + "/" + this.encodeId(id),
664
+ "/" + this.encodeId(this.collectionName) + "/" + this.encodeId(id) + (qsStr ? "?" + qsStr : ""),
850
665
  options
851
666
  );
852
667
  }
@@ -921,6 +736,22 @@ var CollectionService = class {
921
736
  typed() {
922
737
  return this;
923
738
  }
739
+ /**
740
+ * Explicitly bind a schema for this service (overrides the client-level
741
+ * `types.schemas`). Enables schema-aware defaults: hidden fields are
742
+ * excluded from responses, select/expand are validated.
743
+ */
744
+ withSchema(schema) {
745
+ const derived = new _CollectionService(
746
+ this.http,
747
+ this.collectionName,
748
+ this.authStore,
749
+ this.realtime,
750
+ schema
751
+ );
752
+ derived.fieldsPreset = this.fieldsPreset;
753
+ return derived;
754
+ }
924
755
  // ── Realtime Subscriptions (PocketBase-style) ──
925
756
  /**
926
757
  * Subscribe to realtime changes for this collection.
@@ -1345,9 +1176,6 @@ var CollectionsService = class {
1345
1176
  fetch: fetchFn,
1346
1177
  headers: hdrs,
1347
1178
  signal: sig,
1348
- cache: cacheOpt,
1349
- ttl: ttlOpt,
1350
- invalidate: inval,
1351
1179
  params: passthroughParams,
1352
1180
  ...queryParams
1353
1181
  } = rest;
@@ -1367,10 +1195,7 @@ var CollectionsService = class {
1367
1195
  singleFlight: true,
1368
1196
  ...fetchFn ? { fetch: fetchFn } : {},
1369
1197
  ...hdrs ? { headers: hdrs } : {},
1370
- ...sig ? { signal: sig } : {},
1371
- ...cacheOpt !== void 0 ? { cache: cacheOpt } : {},
1372
- ...ttlOpt !== void 0 ? { ttl: ttlOpt } : {},
1373
- ...inval ? { invalidate: inval } : {}
1198
+ ...sig ? { signal: sig } : {}
1374
1199
  }
1375
1200
  );
1376
1201
  if (!res || !res.items || res.items.length === 0) break;
@@ -1471,50 +1296,6 @@ var LazypockClient = class {
1471
1296
  */
1472
1297
  constructor(options) {
1473
1298
  this.collectionCache = /* @__PURE__ */ new Map();
1474
- /** Namespace → realtime unsubscribe; used for realtime-driven invalidation. */
1475
- this.realtimeInvalidators = /* @__PURE__ */ new Map();
1476
- // ── Query cache (opt-in by default; opt-out per request) ──
1477
- /**
1478
- * Configure the query cache at runtime (also a namespace for cache
1479
- * management methods).
1480
- *
1481
- * ```ts
1482
- * client.cache({ enabled: true, defaultTTL: 30_000 });
1483
- * client.cache.deleteByPrefix('getList:posts'); // all list caches for posts
1484
- * client.cache.deleteByPrefix('getOne:posts'); // all one-record caches
1485
- * ```
1486
- *
1487
- * When enabled, GET requests cache their payload; mutations invalidate the
1488
- * affected collection automatically. Individual requests can opt out with
1489
- * `{ cache: false }` or override the TTL with `{ ttl: ms }`.
1490
- */
1491
- this.cache = Object.assign(
1492
- ((config) => {
1493
- if (!this.cacheStore) {
1494
- this.cacheStore = new CacheStore({
1495
- defaultTTL: config?.defaultTTL,
1496
- store: config?.store,
1497
- maxEntries: config?.maxEntries
1498
- });
1499
- this.http.setCache(this.cacheStore, config?.enabled ?? true);
1500
- } else {
1501
- if (config?.enabled !== void 0) {
1502
- this.http.setCache(this.cacheStore, config.enabled);
1503
- }
1504
- }
1505
- return this;
1506
- }),
1507
- {
1508
- deleteByPrefix: (prefix) => this.cacheStore?.deleteByPrefix(prefix),
1509
- invalidate: (namespace) => this.cacheStore?.invalidate(namespace),
1510
- clear: () => {
1511
- this.cacheStore?.clear();
1512
- for (const unsub of this.realtimeInvalidators.values()) unsub();
1513
- this.realtimeInvalidators.clear();
1514
- },
1515
- stats: () => this.cacheStore ? this.cacheStore.stats() : null
1516
- }
1517
- );
1518
1299
  const baseUrl = options.baseUrl.replace(/\/+$/, "");
1519
1300
  this.authStore = options.authStore ?? new AuthStore(options.storage ?? memoryStorage);
1520
1301
  this.http = new HttpClient(baseUrl, this.authStore);
@@ -1524,14 +1305,6 @@ var LazypockClient = class {
1524
1305
  }
1525
1306
  this.files = new FilesService(this.http);
1526
1307
  this.collections = new CollectionsService(this.http, this.realtime);
1527
- if (options.cache) {
1528
- this.cacheStore = new CacheStore({
1529
- defaultTTL: options.cache.defaultTTL,
1530
- store: options.cache.store,
1531
- maxEntries: options.cache.maxEntries
1532
- });
1533
- this.http.setCache(this.cacheStore, options.cache.enabled ?? false);
1534
- }
1535
1308
  if (options.types?.schemas) {
1536
1309
  this.schemaByName = new Map(
1537
1310
  options.types.schemas.map((s) => [s.name, s])
@@ -1549,14 +1322,16 @@ var LazypockClient = class {
1549
1322
  * @param name The collection name.
1550
1323
  * @returns A {@link CollectionService} instance.
1551
1324
  */
1552
- collection(name) {
1325
+ collection(name, schema) {
1553
1326
  let svc = this.collectionCache.get(name);
1554
1327
  if (!svc) {
1555
1328
  svc = new CollectionService(
1556
1329
  this.http,
1557
1330
  name,
1558
1331
  this.authStore,
1559
- this.realtime
1332
+ this.realtime,
1333
+ // Prefer the explicit schema, then the client-level `types.schemas`.
1334
+ schema ?? this.schemaByName?.get(name)
1560
1335
  );
1561
1336
  this.collectionCache.set(name, svc);
1562
1337
  }
@@ -1612,49 +1387,6 @@ var LazypockClient = class {
1612
1387
  this.http.cancelAllRequests();
1613
1388
  return this;
1614
1389
  }
1615
- /**
1616
- * Drop every cached entry (all collections / namespaces).
1617
- * Also disables realtime-driven invalidation subscriptions.
1618
- */
1619
- clearCache() {
1620
- this.cacheStore?.clear();
1621
- for (const unsub of this.realtimeInvalidators.values()) unsub();
1622
- this.realtimeInvalidators.clear();
1623
- return this;
1624
- }
1625
- /**
1626
- * Invalidate cached entries for a collection (or custom namespace).
1627
- * Runs automatically on mutations — call explicitly when data changed
1628
- * out-of-band (e.g. another client wrote to the same collection).
1629
- */
1630
- invalidateCache(namespace) {
1631
- this.cacheStore?.invalidate(namespace);
1632
- return this;
1633
- }
1634
- /**
1635
- * Cache hit/miss/entry statistics.
1636
- * Returns null when caching was never configured.
1637
- */
1638
- cacheStats() {
1639
- return this.cacheStore ? this.cacheStore.stats() : null;
1640
- }
1641
- /**
1642
- * Subscribe a collection's cache to realtime invalidation: any inbound
1643
- * create/update/delete event for the collection clears its cached entries.
1644
- * Returns an unsubscribe function.
1645
- */
1646
- invalidateCacheOnRealtime(collectionName) {
1647
- if (!this.cacheStore) {
1648
- this.cache({ enabled: false });
1649
- }
1650
- const existing = this.realtimeInvalidators.get(collectionName);
1651
- if (existing) return existing;
1652
- const unsub = this.collection(collectionName).subscribe(() => {
1653
- this.cacheStore?.invalidate(collectionName);
1654
- });
1655
- this.realtimeInvalidators.set(collectionName, unsub);
1656
- return unsub;
1657
- }
1658
1390
  // ── Auth ──
1659
1391
  /** Check whether any superuser exists (for login vs setup screen routing). */
1660
1392
  async checkSuperuser() {
@@ -1791,7 +1523,6 @@ function createClient(options) {
1791
1523
  export {
1792
1524
  ApiError,
1793
1525
  AuthStore,
1794
- CacheStore,
1795
1526
  CollectionService,
1796
1527
  CollectionsService,
1797
1528
  FilesService,
@@ -1807,7 +1538,6 @@ export {
1807
1538
  getFileUrl,
1808
1539
  getScaleUrl,
1809
1540
  getThumbUrl,
1810
- resolveCacheDirective,
1811
1541
  schemaFieldType,
1812
1542
  wsUrlFromBaseUrl
1813
1543
  };