lazypock 0.4.0 → 0.5.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.cjs CHANGED
@@ -22,7 +22,6 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ApiError: () => ApiError,
24
24
  AuthStore: () => AuthStore,
25
- CacheStore: () => CacheStore,
26
25
  CollectionService: () => CollectionService,
27
26
  CollectionsService: () => CollectionsService,
28
27
  FilesService: () => FilesService,
@@ -38,7 +37,6 @@ __export(index_exports, {
38
37
  getFileUrl: () => getFileUrl,
39
38
  getScaleUrl: () => getScaleUrl,
40
39
  getThumbUrl: () => getThumbUrl,
41
- resolveCacheDirective: () => resolveCacheDirective,
42
40
  schemaFieldType: () => schemaFieldType,
43
41
  wsUrlFromBaseUrl: () => wsUrlFromBaseUrl
44
42
  });
@@ -55,201 +53,6 @@ var ApiError = class extends Error {
55
53
  }
56
54
  };
57
55
 
58
- // src/cache.ts
59
- var CacheStore = class {
60
- constructor(config = {}) {
61
- this.memory = /* @__PURE__ */ new Map();
62
- this.hits = 0;
63
- this.misses = 0;
64
- this.namespaceEntries = /* @__PURE__ */ new Map();
65
- /** Key → set of prefix tags registered for that key (e.g. `getList:posts`). */
66
- this.prefixEntries = /* @__PURE__ */ new Map();
67
- this.ttl = config.defaultTTL ?? 6e4;
68
- this.persistence = config.store;
69
- this.maxEntries = config.maxEntries ?? 500;
70
- }
71
- /** Resolve the effective TTL: request override → global default. */
72
- resolveTTL(ttl) {
73
- return ttl && ttl > 0 ? ttl : this.ttl;
74
- }
75
- /**
76
- * Read a cached value. Fast sync path (memory) with async persistence
77
- * fallback for adapters whose `get` returns a Promise.
78
- * @param key Cache key (e.g. `"GET /posts?page=1"`).
79
- * @returns The cached value, or undefined when absent/expired (the hit is
80
- * cleared on expiry so a stale value is never served).
81
- */
82
- async get(key) {
83
- const mem = this.memory.get(key);
84
- if (mem !== void 0) {
85
- if (Date.now() > mem.expiresAt) {
86
- this.delete(key);
87
- this.misses++;
88
- return void 0;
89
- }
90
- this.memory.delete(key);
91
- this.memory.set(key, mem);
92
- this.hits++;
93
- return mem.value;
94
- }
95
- if (this.persistence) {
96
- const entry = await this.readPersisted(key);
97
- if (entry) {
98
- if (Date.now() > entry.expiresAt) {
99
- this.delete(key);
100
- this.misses++;
101
- return void 0;
102
- }
103
- this.hits++;
104
- return entry.value;
105
- }
106
- }
107
- this.misses++;
108
- return void 0;
109
- }
110
- /**
111
- * Store a value.
112
- * @param key Cache key.
113
- * @param value The response payload.
114
- * @param ttlOverride Optional TTL override (ms).
115
- * @param namespace Optional namespace for group invalidation.
116
- */
117
- set(key, value, ttlOverride, namespace, tags) {
118
- const expiresAt = Date.now() + this.resolveTTL(ttlOverride);
119
- const entry = { value, expiresAt, namespace, tags };
120
- this.memory.set(key, entry);
121
- if (this.memory.size > this.maxEntries) {
122
- const oldest = this.memory.keys().next().value;
123
- if (oldest !== void 0) this.delete(oldest);
124
- }
125
- if (namespace) {
126
- let keys = this.namespaceEntries.get(namespace);
127
- if (!keys) {
128
- keys = /* @__PURE__ */ new Set();
129
- this.namespaceEntries.set(namespace, keys);
130
- }
131
- keys.add(key);
132
- }
133
- for (const tag of tags ?? []) {
134
- let keys = this.prefixEntries.get(tag);
135
- if (!keys) {
136
- keys = /* @__PURE__ */ new Set();
137
- this.prefixEntries.set(tag, keys);
138
- }
139
- keys.add(key);
140
- }
141
- if (this.persistence) {
142
- void this.persistence.set(this.persistKey(key), JSON.stringify(entry));
143
- }
144
- }
145
- /**
146
- * Invalidate entries belonging to a namespace (e.g. a collection name).
147
- * Also clears the namespace index entry.
148
- */
149
- invalidate(namespace) {
150
- const keys = Array.from(this.namespaceEntries.get(namespace) ?? []);
151
- for (const key of keys) this.delete(key);
152
- this.namespaceEntries.delete(namespace);
153
- }
154
- /** Remove a single key. */
155
- delete(key) {
156
- const entry = this.memory.get(key);
157
- if (entry?.namespace) {
158
- const set = this.namespaceEntries.get(entry.namespace);
159
- if (set) {
160
- set.delete(key);
161
- if (set.size === 0) this.namespaceEntries.delete(entry.namespace);
162
- }
163
- }
164
- for (const tag of entry?.tags ?? []) {
165
- const set = this.prefixEntries.get(tag);
166
- if (set) {
167
- set.delete(key);
168
- if (set.size === 0) this.prefixEntries.delete(tag);
169
- }
170
- }
171
- this.memory.delete(key);
172
- if (this.persistence) {
173
- void this.persistence.remove(this.persistKey(key));
174
- }
175
- }
176
- /**
177
- * Delete every entry whose key starts with `prefix`.
178
- *
179
- * Useful for fine-grained invalidation, e.g.:
180
- * ```ts
181
- * client.cache.deleteByPrefix('getList:posts'); // delete all getList cache
182
- * client.cache.deleteByPrefix('getOne:posts'); // delete all getOne cache
183
- * ```
184
- */
185
- deleteByPrefix(prefix) {
186
- if (!prefix) return;
187
- const tagged = this.prefixEntries.get(prefix);
188
- if (tagged) {
189
- for (const key of Array.from(tagged)) this.delete(key);
190
- this.prefixEntries.delete(prefix);
191
- return;
192
- }
193
- for (const key of Array.from(this.memory.keys())) {
194
- if (key.startsWith(prefix)) this.delete(key);
195
- }
196
- }
197
- /** Drop every cached entry (memory + persistence). */
198
- clear() {
199
- this.memory.clear();
200
- this.namespaceEntries.clear();
201
- this.prefixEntries.clear();
202
- }
203
- /** Cache hit/miss/entry statistics. */
204
- stats() {
205
- return { hits: this.hits, misses: this.misses, entries: this.memory.size };
206
- }
207
- persistKey(key) {
208
- return "lazypock:cache:" + key;
209
- }
210
- async readPersisted(key) {
211
- if (!this.persistence) return void 0;
212
- const raw = await this.persistence.get(this.persistKey(key));
213
- if (raw == null) return void 0;
214
- try {
215
- const entry = JSON.parse(raw);
216
- this.memory.set(key, entry);
217
- if (entry.namespace) {
218
- let keys = this.namespaceEntries.get(entry.namespace);
219
- if (!keys) {
220
- keys = /* @__PURE__ */ new Set();
221
- this.namespaceEntries.set(entry.namespace, keys);
222
- }
223
- keys.add(key);
224
- }
225
- for (const tag of entry.tags ?? []) {
226
- let keys = this.prefixEntries.get(tag);
227
- if (!keys) {
228
- keys = /* @__PURE__ */ new Set();
229
- this.prefixEntries.set(tag, keys);
230
- }
231
- keys.add(key);
232
- }
233
- return entry;
234
- } catch {
235
- void this.persistence.remove(this.persistKey(key));
236
- return void 0;
237
- }
238
- }
239
- };
240
- function resolveCacheDirective(opts) {
241
- if (!opts) return null;
242
- if (typeof opts.ttl === "number" && opts.ttl > 0) {
243
- return { enabled: true, ttl: opts.ttl };
244
- }
245
- const c = opts.cache;
246
- if (c === void 0) return null;
247
- if (c === true) return { enabled: true };
248
- if (c === false) return { enabled: false };
249
- if (typeof c === "number") return { enabled: true, ttl: c > 0 ? c : void 0 };
250
- return { enabled: true, ttl: c.ttl, key: c.key };
251
- }
252
-
253
56
  // src/http.ts
254
57
  function isAbortError(err) {
255
58
  return err instanceof Error && (err.name === "AbortError" || err.message === "Aborted");
@@ -260,8 +63,6 @@ var HttpClient = class {
260
63
  * @param authStore The auth store providing the token for Authorization headers.
261
64
  */
262
65
  constructor(baseUrl, authStore) {
263
- /** Master switch resolved from CacheConfig.enabled. */
264
- this.cacheEnabled = false;
265
66
  /**
266
67
  * Abort controllers for in-flight requests, keyed by their cancellation key
267
68
  * (default `METHOD path`). A new request with the same key aborts the
@@ -281,18 +82,6 @@ var HttpClient = class {
281
82
  this.authStore = authStore;
282
83
  this.defaultFetch = globalThis.fetch.bind(globalThis);
283
84
  }
284
- /**
285
- * Attach a cache store + master switch.
286
- * Called by the client constructor when cache config is present.
287
- */
288
- setCache(cache, enabled) {
289
- this.cache = cache;
290
- this.cacheEnabled = enabled;
291
- }
292
- /** Whether the global cache flag is on (requests opt in/out individually too). */
293
- get cacheIsEnabled() {
294
- return this.cacheEnabled;
295
- }
296
85
  async refreshAuth() {
297
86
  const collection = this.authStore.collectionName;
298
87
  if (!collection) return null;
@@ -369,25 +158,10 @@ var HttpClient = class {
369
158
  * @throws {ApiError} On non-2xx responses or when the request is aborted
370
159
  * (aborted requests throw an `ApiError` with `isAbort === true`).
371
160
  */
372
- /** Invalidate a namespace (collection name). No-op when cache is off. */
373
- invalidateCache(namespace) {
374
- this.cache?.invalidate(namespace);
375
- }
376
- /** Current cache statistics (hits/misses/entries), or null when disabled. */
377
- cacheStats() {
378
- return this.cache ? this.cache.stats() : null;
379
- }
380
161
  async request(method, path, body, options) {
381
162
  if (this.authStore.isExpired && this.authStore.collectionName) {
382
163
  await this.refreshAuth();
383
164
  }
384
- const cacheDirective = resolveCacheDirective(options);
385
- const wantCache = cacheDirective !== null ? cacheDirective.enabled : this.cacheEnabled;
386
- const cacheKey = method === "GET" && this.cache && wantCache ? this.cacheKeyFor(method, path, options?.params) : null;
387
- if (cacheKey !== null) {
388
- const hit = await this.cache?.get(cacheKey);
389
- if (hit !== void 0) return hit;
390
- }
391
165
  let requestKey = options?.requestKey === void 0 ? options?.cancelKey ?? `${method} ${path}` : options.requestKey;
392
166
  if (options?.autoCancel === false) requestKey = null;
393
167
  if (options?.singleFlight && requestKey !== null) {
@@ -422,9 +196,7 @@ var HttpClient = class {
422
196
  options,
423
197
  signal,
424
198
  requestKey,
425
- controller,
426
- cacheKey,
427
- cacheDirective
199
+ controller
428
200
  );
429
201
  } finally {
430
202
  if (requestKey !== null) {
@@ -441,10 +213,10 @@ var HttpClient = class {
441
213
  return promise;
442
214
  }
443
215
  /**
444
- * Execute the actual HTTP request (fetch + parse + cache). Called by {@link request}
216
+ * Execute the actual HTTP request (fetch + parse). Called by {@link request}
445
217
  * as the inner in-flight unit so single-flight callers can reuse the promise.
446
218
  */
447
- async doRequest(method, path, body, options, signal, requestKey, controller, cacheKey, cacheDirective) {
219
+ async doRequest(method, path, body, options, signal, requestKey, controller) {
448
220
  let url = this.baseUrl + path;
449
221
  if (options?.params) {
450
222
  const qs = new URLSearchParams(options.params).toString();
@@ -509,63 +281,8 @@ var HttpClient = class {
509
281
  res.status
510
282
  );
511
283
  }
512
- if (cacheKey !== null && this.cache) {
513
- const namespace = this.namespaceFromPath(path);
514
- const ttl = cacheDirective?.ttl;
515
- const tags = this.cacheTagsFor(path, namespace);
516
- this.cache.set(
517
- cacheKey,
518
- data,
519
- ttl,
520
- namespace ?? void 0,
521
- tags
522
- );
523
- }
524
- if (method !== "GET" && this.cache) {
525
- const namespaces = /* @__PURE__ */ new Set();
526
- const ns = this.namespaceFromPath(path);
527
- if (ns) namespaces.add(ns);
528
- for (const extra of options?.invalidate ?? []) {
529
- if (extra) namespaces.add(extra);
530
- }
531
- for (const nsName of namespaces) this.cache.invalidate(nsName);
532
- }
533
284
  return data;
534
285
  }
535
- // ── Cache key/namespace helpers ──
536
- /** Build a token-scoped cache key: `METHOD path|token-hash|params`. */
537
- cacheKeyFor(method, path, params) {
538
- const token = this.authStore.token || "anon";
539
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
540
- return `${method} ${path}${qs}|${token}`;
541
- }
542
- /** Best-effort namespace (collection name) from a REST path. */
543
- namespaceFromPath(path) {
544
- const clean = path.split("?")[0];
545
- const parts = clean.split("/").filter(Boolean);
546
- if (parts.length === 0) return void 0;
547
- if (parts[0] === "collections" || parts[0] === "_superusers") {
548
- return parts[0];
549
- }
550
- return parts[0];
551
- }
552
- /**
553
- * Semantic prefix tags for `deleteByPrefix`, derived from the REST shape:
554
- * - `/{collection}?...` → `getList:{collection}`
555
- * - `/{collection}/{id}` → `getOne:{collection}`
556
- * - `/collections?...` / `/collections/{id}` → `collections:getList` / `collections:getOne`
557
- */
558
- cacheTagsFor(path, namespace) {
559
- if (!namespace) return [];
560
- const clean = path.split("?")[0];
561
- const parts = clean.split("/").filter(Boolean);
562
- if (parts[0] === "collections" || parts[0] === "_superusers") {
563
- const op2 = parts.length >= 2 ? "getOne" : "getList";
564
- return [`${namespace}:${op2}`];
565
- }
566
- const op = parts.length >= 2 ? "getOne" : "getList";
567
- return [`${op}:${namespace}`];
568
- }
569
286
  /**
570
287
  * HTTP GET.
571
288
  * @param path URL path.
@@ -777,17 +494,104 @@ function normalizeAction(event, rawAction) {
777
494
  if (event === "delete" || event === "record_delete") return "delete";
778
495
  return "update";
779
496
  }
780
- var CollectionService = class {
497
+ var CollectionService = class _CollectionService {
781
498
  /** @internal */
782
- constructor(http, collectionName, authStore, realtime) {
499
+ constructor(http, collectionName, authStore, realtime, schema) {
783
500
  this.http = http;
784
501
  this.collectionName = collectionName;
785
502
  this.authStore = authStore;
786
503
  this.realtime = realtime;
504
+ this.schema = schema;
787
505
  }
788
506
  encodeId(id) {
789
507
  return encodeURIComponent(id);
790
508
  }
509
+ // ── Field projection (`.select`) ──
510
+ /**
511
+ * Project list/read responses to the given fields (PocketBase `fields`
512
+ * param). Field names are type-checked when this service is typed
513
+ * (codegen / `typed<T>()`).
514
+ *
515
+ * Returns a derived service — the original is untouched.
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * const t = await client.collection("posts").select("id", "title").getList();
520
+ * // GET /api/posts?fields=id,title
521
+ *
522
+ * // All visible fields (hidden fields are excluded server-side)
523
+ * const all = await client.collection("posts").select("*").getList();
524
+ * // GET /api/posts?fields=id,title,published,… (schema known)
525
+ * ```
526
+ *
527
+ * When no schema is known, the default (no `select()` call) sends no
528
+ * `fields` param — the server returns every non-password field.
529
+ * Pass `select("*")` to restore the default after a projection.
530
+ */
531
+ select(...fields) {
532
+ const preset = fields.length === 0 ? "*" : fields.join(",");
533
+ const derived = new _CollectionService(
534
+ this.http,
535
+ this.collectionName,
536
+ this.authStore,
537
+ this.realtime,
538
+ this.schema
539
+ );
540
+ derived.fieldsPreset = preset;
541
+ if (this.schema && preset !== "*") {
542
+ const known = new Set(this.schema.fields?.map((f) => f.name) ?? []);
543
+ for (const f of fields) {
544
+ if (!known.has(f)) {
545
+ console.warn(
546
+ `[lazypock] select("${f}"): unknown field on collection "${this.collectionName}"`
547
+ );
548
+ }
549
+ }
550
+ }
551
+ return derived;
552
+ }
553
+ /**
554
+ * Fields to send with reads:
555
+ * 1. explicit `options.fields` (caller wins)
556
+ * 2. `select()` preset
557
+ * 3. schema default — all visible (non-hidden, non-system) fields
558
+ */
559
+ effectiveFields(optionsFields) {
560
+ if (optionsFields !== void 0) return optionsFields;
561
+ if (this.fieldsPreset !== void 0) {
562
+ if (this.fieldsPreset === "*") {
563
+ return this.visibleFields();
564
+ }
565
+ return this.fieldsPreset;
566
+ }
567
+ return this.visibleFields();
568
+ }
569
+ /**
570
+ * All non-hidden, non-system, non-password field names from the schema.
571
+ * `undefined` when no schema is available (server decides).
572
+ */
573
+ visibleFields() {
574
+ if (!this.schema?.fields) return void 0;
575
+ const visible = this.schema.fields.filter(
576
+ (f) => !f.hidden && f.type !== "password" && f.type !== "autodate"
577
+ ).map((f) => f.name);
578
+ return visible.length > 0 ? visible.join(",") : void 0;
579
+ }
580
+ /** Warn once when an expand field is not a relation (schema known). */
581
+ validateExpand(expand) {
582
+ if (!expand || !this.schema?.fields) return;
583
+ const relations = new Set(
584
+ this.schema.fields.filter((f) => f.type === "relation").map((f) => f.name)
585
+ );
586
+ for (const f of expand.split(",")) {
587
+ const name = f.trim();
588
+ if (name && !relations.has(name)) {
589
+ console.warn(
590
+ `[lazypock] expand("${name}"): field is not a relation on collection "${this.collectionName}"`
591
+ );
592
+ }
593
+ }
594
+ }
791
595
  /**
792
596
  * Fetch a paginated list of records (PocketBase `getList`).
793
597
  *
@@ -803,13 +607,17 @@ var CollectionService = class {
803
607
  fetch,
804
608
  headers,
805
609
  signal,
806
- cache,
807
- ttl,
808
- invalidate,
809
610
  singleFlight,
810
611
  params,
811
612
  ...queryParams
812
613
  } = options ?? {};
614
+ const fields = this.effectiveFields(queryParams.fields);
615
+ if (fields !== void 0 && queryParams.fields === void 0) {
616
+ queryParams.fields = fields;
617
+ }
618
+ if (typeof queryParams.expand === "string") {
619
+ this.validateExpand(queryParams.expand);
620
+ }
813
621
  const qs = new URLSearchParams(
814
622
  Object.fromEntries(
815
623
  Object.entries({
@@ -828,9 +636,6 @@ var CollectionService = class {
828
636
  fetch,
829
637
  headers,
830
638
  signal,
831
- cache,
832
- ttl,
833
- invalidate,
834
639
  singleFlight,
835
640
  params
836
641
  }
@@ -883,8 +688,16 @@ var CollectionService = class {
883
688
  * @param options Optional request options.
884
689
  */
885
690
  getOne(id, options) {
691
+ const fields = this.effectiveFields(options?.fields);
692
+ if (typeof options?.expand === "string") {
693
+ this.validateExpand(options.expand);
694
+ }
695
+ const qs = new URLSearchParams();
696
+ if (fields !== void 0) qs.set("fields", fields);
697
+ if (typeof options?.expand === "string") qs.set("expand", options.expand);
698
+ const qsStr = qs.toString();
886
699
  return this.http.get(
887
- "/" + this.encodeId(this.collectionName) + "/" + this.encodeId(id),
700
+ "/" + this.encodeId(this.collectionName) + "/" + this.encodeId(id) + (qsStr ? "?" + qsStr : ""),
888
701
  options
889
702
  );
890
703
  }
@@ -959,6 +772,22 @@ var CollectionService = class {
959
772
  typed() {
960
773
  return this;
961
774
  }
775
+ /**
776
+ * Explicitly bind a schema for this service (overrides the client-level
777
+ * `types.schemas`). Enables schema-aware defaults: hidden fields are
778
+ * excluded from responses, select/expand are validated.
779
+ */
780
+ withSchema(schema) {
781
+ const derived = new _CollectionService(
782
+ this.http,
783
+ this.collectionName,
784
+ this.authStore,
785
+ this.realtime,
786
+ schema
787
+ );
788
+ derived.fieldsPreset = this.fieldsPreset;
789
+ return derived;
790
+ }
962
791
  // ── Realtime Subscriptions (PocketBase-style) ──
963
792
  /**
964
793
  * Subscribe to realtime changes for this collection.
@@ -1383,9 +1212,6 @@ var CollectionsService = class {
1383
1212
  fetch: fetchFn,
1384
1213
  headers: hdrs,
1385
1214
  signal: sig,
1386
- cache: cacheOpt,
1387
- ttl: ttlOpt,
1388
- invalidate: inval,
1389
1215
  params: passthroughParams,
1390
1216
  ...queryParams
1391
1217
  } = rest;
@@ -1405,10 +1231,7 @@ var CollectionsService = class {
1405
1231
  singleFlight: true,
1406
1232
  ...fetchFn ? { fetch: fetchFn } : {},
1407
1233
  ...hdrs ? { headers: hdrs } : {},
1408
- ...sig ? { signal: sig } : {},
1409
- ...cacheOpt !== void 0 ? { cache: cacheOpt } : {},
1410
- ...ttlOpt !== void 0 ? { ttl: ttlOpt } : {},
1411
- ...inval ? { invalidate: inval } : {}
1234
+ ...sig ? { signal: sig } : {}
1412
1235
  }
1413
1236
  );
1414
1237
  if (!res || !res.items || res.items.length === 0) break;
@@ -1649,15 +1472,39 @@ function generateTypes(collections, options = {}) {
1649
1472
  sections.push(`export interface LazypockCollections {
1650
1473
  ${mapEntries}
1651
1474
  }`);
1475
+ const schemaEntries = collections.map(
1476
+ (c) => ` {
1477
+ id: ${JSON.stringify(c.id ?? null)},
1478
+ name: ${JSON.stringify(c.name)},
1479
+ type: ${JSON.stringify(c.type)},
1480
+ system: ${JSON.stringify(c.system ?? false)},
1481
+ fields: ${JSON.stringify(c.fields ?? [], null, 2)},
1482
+ rules: ${JSON.stringify(c.rules ?? {})},
1483
+ options: ${JSON.stringify(c.options ?? {})},
1484
+ }`
1485
+ ).join(",\n");
1486
+ sections.push(`// Schema snapshot (runtime) \u2014 hidden-field exclusion + query validation.
1487
+ export const lazypockSchema: import("${packageName}").CollectionSchema[] = [
1488
+ ${schemaEntries}
1489
+ ];`);
1652
1490
  sections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from "${packageName}";
1653
1491
 
1654
1492
  /**
1655
1493
  * Create a Lazypock client typed against this schema snapshot.
1656
1494
  * Collection access is fully type-checked:
1657
1495
  * client.collection("posts").create({ title: "x" }) // title must exist
1496
+ *
1497
+ * The schema snapshot is wired into the client automatically, so hidden
1498
+ * fields are excluded from responses and select/expand are validated.
1658
1499
  */
1659
1500
  export function createClient(options: LazypockClientOptions): TypedClient {
1660
- return new TypedClient(options);
1501
+ return new TypedClient({
1502
+ ...options,
1503
+ types: {
1504
+ ...options.types,
1505
+ schemas: options.types?.schemas ?? lazypockSchema,
1506
+ },
1507
+ });
1661
1508
  }
1662
1509
 
1663
1510
  export class TypedClient extends LazypockClient {
@@ -1678,6 +1525,7 @@ ${opts.body}
1678
1525
  }`;
1679
1526
  }
1680
1527
  function memberLine(f) {
1528
+ if (f.hidden) return "";
1681
1529
  const key = fieldKey(f.name);
1682
1530
  const req = f.required || f.type === "password" ? "" : "?";
1683
1531
  const type = fieldTypeScriptType(f);
@@ -1693,50 +1541,6 @@ var LazypockClient = class {
1693
1541
  */
1694
1542
  constructor(options) {
1695
1543
  this.collectionCache = /* @__PURE__ */ new Map();
1696
- /** Namespace → realtime unsubscribe; used for realtime-driven invalidation. */
1697
- this.realtimeInvalidators = /* @__PURE__ */ new Map();
1698
- // ── Query cache (opt-in by default; opt-out per request) ──
1699
- /**
1700
- * Configure the query cache at runtime (also a namespace for cache
1701
- * management methods).
1702
- *
1703
- * ```ts
1704
- * client.cache({ enabled: true, defaultTTL: 30_000 });
1705
- * client.cache.deleteByPrefix('getList:posts'); // all list caches for posts
1706
- * client.cache.deleteByPrefix('getOne:posts'); // all one-record caches
1707
- * ```
1708
- *
1709
- * When enabled, GET requests cache their payload; mutations invalidate the
1710
- * affected collection automatically. Individual requests can opt out with
1711
- * `{ cache: false }` or override the TTL with `{ ttl: ms }`.
1712
- */
1713
- this.cache = Object.assign(
1714
- ((config) => {
1715
- if (!this.cacheStore) {
1716
- this.cacheStore = new CacheStore({
1717
- defaultTTL: config?.defaultTTL,
1718
- store: config?.store,
1719
- maxEntries: config?.maxEntries
1720
- });
1721
- this.http.setCache(this.cacheStore, config?.enabled ?? true);
1722
- } else {
1723
- if (config?.enabled !== void 0) {
1724
- this.http.setCache(this.cacheStore, config.enabled);
1725
- }
1726
- }
1727
- return this;
1728
- }),
1729
- {
1730
- deleteByPrefix: (prefix) => this.cacheStore?.deleteByPrefix(prefix),
1731
- invalidate: (namespace) => this.cacheStore?.invalidate(namespace),
1732
- clear: () => {
1733
- this.cacheStore?.clear();
1734
- for (const unsub of this.realtimeInvalidators.values()) unsub();
1735
- this.realtimeInvalidators.clear();
1736
- },
1737
- stats: () => this.cacheStore ? this.cacheStore.stats() : null
1738
- }
1739
- );
1740
1544
  const baseUrl = options.baseUrl.replace(/\/+$/, "");
1741
1545
  this.authStore = options.authStore ?? new AuthStore(options.storage ?? memoryStorage);
1742
1546
  this.http = new HttpClient(baseUrl, this.authStore);
@@ -1746,14 +1550,6 @@ var LazypockClient = class {
1746
1550
  }
1747
1551
  this.files = new FilesService(this.http);
1748
1552
  this.collections = new CollectionsService(this.http, this.realtime);
1749
- if (options.cache) {
1750
- this.cacheStore = new CacheStore({
1751
- defaultTTL: options.cache.defaultTTL,
1752
- store: options.cache.store,
1753
- maxEntries: options.cache.maxEntries
1754
- });
1755
- this.http.setCache(this.cacheStore, options.cache.enabled ?? false);
1756
- }
1757
1553
  if (options.types?.schemas) {
1758
1554
  this.schemaByName = new Map(
1759
1555
  options.types.schemas.map((s) => [s.name, s])
@@ -1771,14 +1567,16 @@ var LazypockClient = class {
1771
1567
  * @param name The collection name.
1772
1568
  * @returns A {@link CollectionService} instance.
1773
1569
  */
1774
- collection(name) {
1570
+ collection(name, schema) {
1775
1571
  let svc = this.collectionCache.get(name);
1776
1572
  if (!svc) {
1777
1573
  svc = new CollectionService(
1778
1574
  this.http,
1779
1575
  name,
1780
1576
  this.authStore,
1781
- this.realtime
1577
+ this.realtime,
1578
+ // Prefer the explicit schema, then the client-level `types.schemas`.
1579
+ schema ?? this.schemaByName?.get(name)
1782
1580
  );
1783
1581
  this.collectionCache.set(name, svc);
1784
1582
  }
@@ -1834,49 +1632,6 @@ var LazypockClient = class {
1834
1632
  this.http.cancelAllRequests();
1835
1633
  return this;
1836
1634
  }
1837
- /**
1838
- * Drop every cached entry (all collections / namespaces).
1839
- * Also disables realtime-driven invalidation subscriptions.
1840
- */
1841
- clearCache() {
1842
- this.cacheStore?.clear();
1843
- for (const unsub of this.realtimeInvalidators.values()) unsub();
1844
- this.realtimeInvalidators.clear();
1845
- return this;
1846
- }
1847
- /**
1848
- * Invalidate cached entries for a collection (or custom namespace).
1849
- * Runs automatically on mutations — call explicitly when data changed
1850
- * out-of-band (e.g. another client wrote to the same collection).
1851
- */
1852
- invalidateCache(namespace) {
1853
- this.cacheStore?.invalidate(namespace);
1854
- return this;
1855
- }
1856
- /**
1857
- * Cache hit/miss/entry statistics.
1858
- * Returns null when caching was never configured.
1859
- */
1860
- cacheStats() {
1861
- return this.cacheStore ? this.cacheStore.stats() : null;
1862
- }
1863
- /**
1864
- * Subscribe a collection's cache to realtime invalidation: any inbound
1865
- * create/update/delete event for the collection clears its cached entries.
1866
- * Returns an unsubscribe function.
1867
- */
1868
- invalidateCacheOnRealtime(collectionName) {
1869
- if (!this.cacheStore) {
1870
- this.cache({ enabled: false });
1871
- }
1872
- const existing = this.realtimeInvalidators.get(collectionName);
1873
- if (existing) return existing;
1874
- const unsub = this.collection(collectionName).subscribe(() => {
1875
- this.cacheStore?.invalidate(collectionName);
1876
- });
1877
- this.realtimeInvalidators.set(collectionName, unsub);
1878
- return unsub;
1879
- }
1880
1635
  // ── Auth ──
1881
1636
  /** Check whether any superuser exists (for login vs setup screen routing). */
1882
1637
  async checkSuperuser() {
@@ -2014,7 +1769,6 @@ function createClient(options) {
2014
1769
  0 && (module.exports = {
2015
1770
  ApiError,
2016
1771
  AuthStore,
2017
- CacheStore,
2018
1772
  CollectionService,
2019
1773
  CollectionsService,
2020
1774
  FilesService,
@@ -2030,7 +1784,6 @@ function createClient(options) {
2030
1784
  getFileUrl,
2031
1785
  getScaleUrl,
2032
1786
  getThumbUrl,
2033
- resolveCacheDirective,
2034
1787
  schemaFieldType,
2035
1788
  wsUrlFromBaseUrl
2036
1789
  });