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/README.md +28 -0
- package/dist/index.cjs +105 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.global.js +105 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +105 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/collection.ts +45 -2
- package/src/collections.ts +41 -5
- package/src/http.ts +64 -0
- package/src/types.ts +11 -0
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:
|
|
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
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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;
|