lazypock 0.3.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/dist/index.js CHANGED
@@ -230,6 +230,13 @@ var HttpClient = class {
230
230
  * previous one — PocketBase-style auto-cancellation of duplicated requests.
231
231
  */
232
232
  this.cancelControllers = {};
233
+ /**
234
+ * In-flight request promises, keyed by cancellation key. When auto-cancellation
235
+ * would abort a pending duplicate, the newer request instead awaits the same
236
+ * promise — single-flight coalescing (no duplicate network request, no
237
+ * spurious abort rejection for the caller).
238
+ */
239
+ this.inflight = {};
233
240
  /** Global toggle for the auto-cancellation behaviour (default: on). */
234
241
  this.enableAutoCancellation = true;
235
242
  this.baseUrl = baseUrl.replace(/\/+$/, "");
@@ -345,6 +352,12 @@ var HttpClient = class {
345
352
  }
346
353
  let requestKey = options?.requestKey === void 0 ? options?.cancelKey ?? `${method} ${path}` : options.requestKey;
347
354
  if (options?.autoCancel === false) requestKey = null;
355
+ if (options?.singleFlight && requestKey !== null) {
356
+ const pending = this.inflight[requestKey];
357
+ if (pending !== void 0) {
358
+ return pending;
359
+ }
360
+ }
348
361
  let controller = null;
349
362
  const externalSignal = options?.signal;
350
363
  if (requestKey !== null) {
@@ -362,6 +375,38 @@ var HttpClient = class {
362
375
  }
363
376
  }
364
377
  const signal = controller?.signal ?? externalSignal;
378
+ const perform = async () => {
379
+ try {
380
+ return await this.doRequest(
381
+ method,
382
+ path,
383
+ body,
384
+ options,
385
+ signal,
386
+ requestKey,
387
+ controller,
388
+ cacheKey,
389
+ cacheDirective
390
+ );
391
+ } finally {
392
+ if (requestKey !== null) {
393
+ if (this.inflight[requestKey] === promise) {
394
+ delete this.inflight[requestKey];
395
+ }
396
+ }
397
+ }
398
+ };
399
+ const promise = perform();
400
+ if (requestKey !== null) {
401
+ this.inflight[requestKey] = promise;
402
+ }
403
+ return promise;
404
+ }
405
+ /**
406
+ * Execute the actual HTTP request (fetch + parse + cache). Called by {@link request}
407
+ * as the inner in-flight unit so single-flight callers can reuse the promise.
408
+ */
409
+ async doRequest(method, path, body, options, signal, requestKey, controller, cacheKey, cacheDirective) {
365
410
  let url = this.baseUrl + path;
366
411
  if (options?.params) {
367
412
  const qs = new URLSearchParams(options.params).toString();
@@ -658,6 +703,30 @@ var memoryStorage = {
658
703
  };
659
704
 
660
705
  // src/collection.ts
706
+ function stableStringify(value) {
707
+ const seen = /* @__PURE__ */ new Set();
708
+ const sort = (v) => {
709
+ if (Array.isArray(v)) return v.map(sort);
710
+ if (v && typeof v === "object") {
711
+ if (seen.has(v)) return "[Circular]";
712
+ seen.add(v);
713
+ const out = {};
714
+ for (const k of Object.keys(v).sort()) {
715
+ if (k === "requestKey" || k === "singleFlight" || k === "fetch" || k === "signal") {
716
+ continue;
717
+ }
718
+ out[k] = sort(v[k]);
719
+ }
720
+ return out;
721
+ }
722
+ return v;
723
+ };
724
+ try {
725
+ return JSON.stringify(sort(value));
726
+ } catch {
727
+ return String(value);
728
+ }
729
+ }
661
730
  function normalizeAction(event, rawAction) {
662
731
  if (typeof rawAction === "string") {
663
732
  const a = rawAction.toLowerCase();
@@ -699,6 +768,7 @@ var CollectionService = class {
699
768
  cache,
700
769
  ttl,
701
770
  invalidate,
771
+ singleFlight,
702
772
  params,
703
773
  ...queryParams
704
774
  } = options ?? {};
@@ -723,6 +793,7 @@ var CollectionService = class {
723
793
  cache,
724
794
  ttl,
725
795
  invalidate,
796
+ singleFlight,
726
797
  params
727
798
  }
728
799
  );
@@ -735,6 +806,7 @@ var CollectionService = class {
735
806
  */
736
807
  async getFullList(options) {
737
808
  const { batch = 1e3, ...rest } = options ?? {};
809
+ const effectiveKey = typeof rest.requestKey === "string" ? rest.requestKey : `getFullList:${this.collectionName}:${stableStringify(rest)}`;
738
810
  const items = [];
739
811
  let page = 1;
740
812
  for (; ; ) {
@@ -742,9 +814,9 @@ var CollectionService = class {
742
814
  page,
743
815
  batch,
744
816
  {
745
- // disable auto-cancellation across pages — each page request is unique
746
817
  ...rest,
747
- requestKey: null
818
+ requestKey: effectiveKey,
819
+ singleFlight: true
748
820
  }
749
821
  );
750
822
  if (!res || !res.items || res.items.length === 0) break;
@@ -1267,14 +1339,40 @@ var CollectionsService = class {
1267
1339
  async getFullList(options) {
1268
1340
  if (!this.http) return [];
1269
1341
  const { batch = 1e3, ...rest } = options ?? {};
1342
+ const {
1343
+ requestKey: reqKey,
1344
+ singleFlight: _singleFlight,
1345
+ fetch: fetchFn,
1346
+ headers: hdrs,
1347
+ signal: sig,
1348
+ cache: cacheOpt,
1349
+ ttl: ttlOpt,
1350
+ invalidate: inval,
1351
+ params: passthroughParams,
1352
+ ...queryParams
1353
+ } = rest;
1354
+ const effectiveKey = typeof reqKey === "string" ? reqKey : `getFullList:collections:${stableStringify(rest)}`;
1270
1355
  const items = [];
1271
1356
  let page = 1;
1272
1357
  for (; ; ) {
1273
- const res = await this.getList({
1274
- ...rest,
1275
- page,
1276
- perPage: batch
1277
- });
1358
+ const res = await this.getList(
1359
+ {
1360
+ ...queryParams,
1361
+ ...passthroughParams ?? {},
1362
+ page,
1363
+ perPage: batch
1364
+ },
1365
+ {
1366
+ requestKey: effectiveKey,
1367
+ singleFlight: true,
1368
+ ...fetchFn ? { fetch: fetchFn } : {},
1369
+ ...hdrs ? { headers: hdrs } : {},
1370
+ ...sig ? { signal: sig } : {},
1371
+ ...cacheOpt !== void 0 ? { cache: cacheOpt } : {},
1372
+ ...ttlOpt !== void 0 ? { ttl: ttlOpt } : {},
1373
+ ...inval ? { invalidate: inval } : {}
1374
+ }
1375
+ );
1278
1376
  if (!res || !res.items || res.items.length === 0) break;
1279
1377
  items.push(...res.items);
1280
1378
  if (page >= (res.totalPages ?? page)) break;