@mmstack/resource 21.1.1 → 21.1.2
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 +508 -73
- package/fesm2022/mmstack-resource.mjs +149 -66
- package/fesm2022/mmstack-resource.mjs.map +1 -1
- package/package.json +3 -2
- package/types/mmstack-resource.d.ts +78 -8
|
@@ -193,6 +193,12 @@ class Cache {
|
|
|
193
193
|
const value = syncTabs.deserialize(msg.entry.value);
|
|
194
194
|
if (value === null)
|
|
195
195
|
return;
|
|
196
|
+
// Last-write-wins by `updated` timestamp. If our local entry was
|
|
197
|
+
// written more recently than the broadcast we just received, the
|
|
198
|
+
// broadcast is stale (in-flight when we wrote locally) — drop it.
|
|
199
|
+
const existing = untracked(this.internal).get(msg.entry.key);
|
|
200
|
+
if (existing && existing.updated >= msg.entry.updated)
|
|
201
|
+
return;
|
|
196
202
|
this.storeInternal(msg.entry.key, value, msg.entry.stale - msg.entry.updated, msg.entry.expiresAt - msg.entry.updated, true, false);
|
|
197
203
|
}
|
|
198
204
|
else if (msg.action === 'invalidate') {
|
|
@@ -239,7 +245,7 @@ class Cache {
|
|
|
239
245
|
}
|
|
240
246
|
/** @internal */
|
|
241
247
|
getInternal(key) {
|
|
242
|
-
const keySignal = computed(() => key(), ...(ngDevMode ? [{ debugName: "keySignal" }] : []));
|
|
248
|
+
const keySignal = computed(() => key(), ...(ngDevMode ? [{ debugName: "keySignal" }] : /* istanbul ignore next */ []));
|
|
243
249
|
return computed(() => {
|
|
244
250
|
const key = keySignal();
|
|
245
251
|
if (!key)
|
|
@@ -340,6 +346,31 @@ class Cache {
|
|
|
340
346
|
invalidate(key) {
|
|
341
347
|
this.invalidateInternal(key);
|
|
342
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Invalidates every cache entry whose key starts with `prefix`. Common after a
|
|
351
|
+
* list-mutating operation (e.g. invalidate every paginated `GET /api/posts*`
|
|
352
|
+
* after a POST). Returns the number of entries removed.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* cache.invalidatePrefix('GET https://api.example.com/posts');
|
|
356
|
+
*/
|
|
357
|
+
invalidatePrefix(prefix) {
|
|
358
|
+
return this.invalidateWhere((key) => key.startsWith(prefix));
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Invalidates every cache entry whose key matches the predicate. Use for
|
|
362
|
+
* arbitrary bulk invalidation that doesn't fit prefix matching (e.g.
|
|
363
|
+
* "everything containing `userId=42`"). Returns the number of entries removed.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* cache.invalidateWhere((key) => key.includes('/me/'));
|
|
367
|
+
*/
|
|
368
|
+
invalidateWhere(predicate) {
|
|
369
|
+
const keys = Array.from(untracked(this.internal).keys()).filter(predicate);
|
|
370
|
+
for (const key of keys)
|
|
371
|
+
this.invalidateInternal(key);
|
|
372
|
+
return keys.length;
|
|
373
|
+
}
|
|
343
374
|
invalidateInternal(key, fromSync = false) {
|
|
344
375
|
const entry = this.getUntracked(key);
|
|
345
376
|
if (!entry)
|
|
@@ -683,7 +714,9 @@ function createCacheInterceptor(allowedMethods = ['GET', 'HEAD', 'OPTIONS']) {
|
|
|
683
714
|
});
|
|
684
715
|
}
|
|
685
716
|
return next(req).pipe(tap((event) => {
|
|
686
|
-
if (event instanceof HttpResponse
|
|
717
|
+
if (!(event instanceof HttpResponse))
|
|
718
|
+
return;
|
|
719
|
+
if (event.ok) {
|
|
687
720
|
const cacheControl = parseCacheControlHeader(event);
|
|
688
721
|
if (cacheControl.noStore && !opt.ignoreCacheControl)
|
|
689
722
|
return;
|
|
@@ -702,6 +735,17 @@ function createCacheInterceptor(allowedMethods = ['GET', 'HEAD', 'OPTIONS']) {
|
|
|
702
735
|
})
|
|
703
736
|
: event;
|
|
704
737
|
cache.store(key, parsedResponse, staleTime, ttl, opt.persist);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
// 304 → server confirmed our cached entry is still valid. Re-stamp the
|
|
741
|
+
// existing entry so subsequent reads within the new freshness window
|
|
742
|
+
// don't trigger another revalidation round-trip.
|
|
743
|
+
if (event.status === 304 && entry) {
|
|
744
|
+
const cacheControl = parseCacheControlHeader(event);
|
|
745
|
+
const { staleTime, ttl } = opt.ignoreCacheControl
|
|
746
|
+
? opt
|
|
747
|
+
: resolveTimings(cacheControl, opt.staleTime, opt.ttl);
|
|
748
|
+
cache.store(key, entry.value, staleTime, ttl, opt.persist);
|
|
705
749
|
}
|
|
706
750
|
}), map((event) => {
|
|
707
751
|
// handle 304 responses due to eTag/last-modified
|
|
@@ -729,22 +773,23 @@ function catchValueError(resource, fallback) {
|
|
|
729
773
|
|
|
730
774
|
/** @internal */
|
|
731
775
|
const DEFAULT_OPTIONS = {
|
|
732
|
-
|
|
776
|
+
threshold: 5,
|
|
733
777
|
timeout: 30000,
|
|
734
778
|
shouldFail: () => true,
|
|
735
779
|
shouldFailForever: () => false,
|
|
736
780
|
};
|
|
737
781
|
/** @internal */
|
|
738
|
-
function internalCeateCircuitBreaker(
|
|
739
|
-
const halfOpen = signal(false, ...(ngDevMode ? [{ debugName: "halfOpen" }] : []));
|
|
740
|
-
const failureCount = signal(0, ...(ngDevMode ? [{ debugName: "failureCount" }] : []));
|
|
782
|
+
function internalCeateCircuitBreaker(threshold = 5, resetTimeout = 30000, shouldFail = () => true, shouldFailForever = () => false) {
|
|
783
|
+
const halfOpen = signal(false, ...(ngDevMode ? [{ debugName: "halfOpen" }] : /* istanbul ignore next */ []));
|
|
784
|
+
const failureCount = signal(0, ...(ngDevMode ? [{ debugName: "failureCount" }] : /* istanbul ignore next */ []));
|
|
785
|
+
const failedForever = signal(false, ...(ngDevMode ? [{ debugName: "failedForever" }] : /* istanbul ignore next */ []));
|
|
741
786
|
const status = computed(() => {
|
|
742
|
-
if (failureCount() >=
|
|
787
|
+
if (failedForever() || failureCount() >= threshold)
|
|
743
788
|
return 'OPEN';
|
|
744
789
|
return halfOpen() ? 'HALF_OPEN' : 'CLOSED';
|
|
745
|
-
}, ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
746
|
-
const isClosed = computed(() => status() !== 'OPEN', ...(ngDevMode ? [{ debugName: "isClosed" }] : []));
|
|
747
|
-
const isOpen = computed(() => status() !== 'CLOSED', ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
790
|
+
}, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
791
|
+
const isClosed = computed(() => status() !== 'OPEN', ...(ngDevMode ? [{ debugName: "isClosed" }] : /* istanbul ignore next */ []));
|
|
792
|
+
const isOpen = computed(() => status() !== 'CLOSED', ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
748
793
|
const success = () => {
|
|
749
794
|
failureCount.set(0);
|
|
750
795
|
halfOpen.set(false);
|
|
@@ -753,30 +798,26 @@ function internalCeateCircuitBreaker(treshold = 5, resetTimeout = 30000, shouldF
|
|
|
753
798
|
if (!untracked(isOpen))
|
|
754
799
|
return;
|
|
755
800
|
halfOpen.set(true);
|
|
756
|
-
failureCount.set(
|
|
801
|
+
failureCount.set(threshold - 1);
|
|
757
802
|
};
|
|
758
|
-
|
|
803
|
+
// Auto-probe effect: schedules a half-open retry after `resetTimeout` whenever
|
|
804
|
+
// the breaker is open, *unless* we've been failed forever (in which case only
|
|
805
|
+
// hardReset() can recover).
|
|
759
806
|
const effectRef = effect((cleanup) => {
|
|
760
|
-
if (!isOpen())
|
|
807
|
+
if (!isOpen() || failedForever())
|
|
761
808
|
return;
|
|
762
809
|
const timeout = setTimeout(tryOnce, resetTimeout);
|
|
763
|
-
failForeverResetId = timeout;
|
|
764
810
|
return cleanup(() => {
|
|
765
811
|
clearTimeout(timeout);
|
|
766
|
-
failForeverResetId = null;
|
|
767
812
|
});
|
|
768
|
-
}, ...(ngDevMode ? [{ debugName: "effectRef" }] : []));
|
|
813
|
+
}, ...(ngDevMode ? [{ debugName: "effectRef" }] : /* istanbul ignore next */ []));
|
|
769
814
|
const failInternal = () => {
|
|
770
815
|
failureCount.set(failureCount() + 1);
|
|
771
816
|
halfOpen.set(false);
|
|
772
817
|
};
|
|
773
818
|
const failForever = () => {
|
|
774
|
-
|
|
775
|
-
clearTimeout(failForeverResetId);
|
|
776
|
-
effectRef.destroy();
|
|
777
|
-
failureCount.set(Infinity);
|
|
819
|
+
failedForever.set(true);
|
|
778
820
|
halfOpen.set(false);
|
|
779
|
-
return;
|
|
780
821
|
};
|
|
781
822
|
const fail = (err) => {
|
|
782
823
|
if (shouldFailForever(err))
|
|
@@ -785,6 +826,11 @@ function internalCeateCircuitBreaker(treshold = 5, resetTimeout = 30000, shouldF
|
|
|
785
826
|
return failInternal();
|
|
786
827
|
// If the error does not trigger a failure, we do nothing.
|
|
787
828
|
};
|
|
829
|
+
const hardReset = () => {
|
|
830
|
+
failedForever.set(false);
|
|
831
|
+
failureCount.set(0);
|
|
832
|
+
halfOpen.set(false);
|
|
833
|
+
};
|
|
788
834
|
return {
|
|
789
835
|
status,
|
|
790
836
|
isClosed,
|
|
@@ -792,6 +838,7 @@ function internalCeateCircuitBreaker(treshold = 5, resetTimeout = 30000, shouldF
|
|
|
792
838
|
fail,
|
|
793
839
|
success,
|
|
794
840
|
halfOpen: tryOnce,
|
|
841
|
+
hardReset,
|
|
795
842
|
destroy: () => effectRef.destroy(),
|
|
796
843
|
};
|
|
797
844
|
}
|
|
@@ -810,18 +857,43 @@ function createNeverBrokenCircuitBreaker() {
|
|
|
810
857
|
halfOpen: () => {
|
|
811
858
|
// noop
|
|
812
859
|
},
|
|
860
|
+
hardReset: () => {
|
|
861
|
+
// noop
|
|
862
|
+
},
|
|
813
863
|
destroy: () => {
|
|
814
864
|
// noop
|
|
815
865
|
},
|
|
816
866
|
};
|
|
817
867
|
}
|
|
818
868
|
const CB_DEFAULT_OPTIONS = new InjectionToken('MMSTACK_CIRCUIT_BREAKER_DEFAULT_OPTIONS');
|
|
869
|
+
/**
|
|
870
|
+
* Provides application-wide default options for {@link createCircuitBreaker}.
|
|
871
|
+
* Any `createCircuitBreaker()` call without explicit options (or with only
|
|
872
|
+
* partial options) merges these defaults in, so you can centralize threshold /
|
|
873
|
+
* timeout / failure-classifier behavior in one place.
|
|
874
|
+
*
|
|
875
|
+
* Per-call options always win over the provided defaults.
|
|
876
|
+
*
|
|
877
|
+
* @example
|
|
878
|
+
* ```ts
|
|
879
|
+
* bootstrapApplication(AppComponent, {
|
|
880
|
+
* providers: [
|
|
881
|
+
* provideCircuitBreakerDefaultOptions({
|
|
882
|
+
* threshold: 10,
|
|
883
|
+
* timeout: 60_000,
|
|
884
|
+
* shouldFailForever: (err) =>
|
|
885
|
+
* err instanceof HttpErrorResponse && [401, 403].includes(err.status),
|
|
886
|
+
* }),
|
|
887
|
+
* ],
|
|
888
|
+
* });
|
|
889
|
+
* ```
|
|
890
|
+
*/
|
|
819
891
|
function provideCircuitBreakerDefaultOptions(options) {
|
|
820
892
|
return {
|
|
821
893
|
provide: CB_DEFAULT_OPTIONS,
|
|
822
894
|
useValue: {
|
|
823
895
|
...DEFAULT_OPTIONS,
|
|
824
|
-
...options,
|
|
896
|
+
...normalizeThreshold(options),
|
|
825
897
|
},
|
|
826
898
|
};
|
|
827
899
|
}
|
|
@@ -830,12 +902,23 @@ function injectCircuitBreakerOptions(injector = inject(Injector)) {
|
|
|
830
902
|
optional: true,
|
|
831
903
|
});
|
|
832
904
|
}
|
|
905
|
+
/** @internal — strips the deprecated `treshold` field and folds it into `threshold` */
|
|
906
|
+
function normalizeThreshold(opt) {
|
|
907
|
+
if (!opt || typeof opt !== 'object' || 'isClosed' in opt)
|
|
908
|
+
return {};
|
|
909
|
+
const { treshold, threshold, ...rest } = opt;
|
|
910
|
+
return {
|
|
911
|
+
...rest,
|
|
912
|
+
threshold: threshold ?? treshold,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
833
915
|
/**
|
|
834
916
|
* Creates a circuit breaker instance.
|
|
835
917
|
*
|
|
836
918
|
* @param options - Configuration options for the circuit breaker. Can be:
|
|
837
|
-
* - `undefined`:
|
|
838
|
-
* - `
|
|
919
|
+
* - `undefined`: Uses defaults (threshold: 5, timeout: 30000ms) or provided defaults via {@link provideCircuitBreakerDefaultOptions}.
|
|
920
|
+
* - `false`: Creates a "no-op" circuit breaker that is always closed (never trips).
|
|
921
|
+
* - `true`: Creates a circuit breaker with default settings.
|
|
839
922
|
* - `CircuitBreaker`: Reuses an existing `CircuitBreaker` instance.
|
|
840
923
|
* - `{ threshold?: number; timeout?: number; }`: Creates a circuit breaker with the specified threshold and timeout.
|
|
841
924
|
*
|
|
@@ -858,11 +941,11 @@ function createCircuitBreaker(opt, injector) {
|
|
|
858
941
|
return createNeverBrokenCircuitBreaker();
|
|
859
942
|
if (typeof opt === 'object' && 'isClosed' in opt)
|
|
860
943
|
return opt;
|
|
861
|
-
const {
|
|
944
|
+
const { threshold, timeout, shouldFail, shouldFailForever } = {
|
|
862
945
|
...injectCircuitBreakerOptions(injector),
|
|
863
|
-
...opt,
|
|
946
|
+
...normalizeThreshold(opt),
|
|
864
947
|
};
|
|
865
|
-
return internalCeateCircuitBreaker(
|
|
948
|
+
return internalCeateCircuitBreaker(threshold, timeout, shouldFail, shouldFailForever);
|
|
866
949
|
}
|
|
867
950
|
|
|
868
951
|
// Heavily inspired by: https://dev.to/kasual1/request-deduplication-in-angular-3pd8
|
|
@@ -1179,7 +1262,7 @@ function hasSlowConnection() {
|
|
|
1179
1262
|
|
|
1180
1263
|
function persist(src, equal) {
|
|
1181
1264
|
// linkedSignal allows us to access previous source value
|
|
1182
|
-
const persisted = linkedSignal({ ...(ngDevMode ? { debugName: "persisted" } : {}), source: () => src(),
|
|
1265
|
+
const persisted = linkedSignal({ ...(ngDevMode ? { debugName: "persisted" } : /* istanbul ignore next */ {}), source: () => src(),
|
|
1183
1266
|
computation: (next, prev) => {
|
|
1184
1267
|
if (next === undefined && prev !== undefined)
|
|
1185
1268
|
return prev.value;
|
|
@@ -1233,13 +1316,16 @@ function refresh(resource, destroyRef, refresh) {
|
|
|
1233
1316
|
}
|
|
1234
1317
|
|
|
1235
1318
|
// Retry on error, if number is provided it will retry that many times with exponential backoff, otherwise it will use the options provided
|
|
1236
|
-
function retryOnError(res, opt) {
|
|
1319
|
+
function retryOnError(res, opt, onError) {
|
|
1237
1320
|
const max = opt ? (typeof opt === 'number' ? opt : (opt.max ?? 0)) : 0;
|
|
1238
1321
|
const backoff = typeof opt === 'object' ? (opt.backoff ?? 1000) : 1000;
|
|
1239
1322
|
let retries = 0;
|
|
1240
1323
|
let timeout;
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1324
|
+
const handleError = () => {
|
|
1325
|
+
const err = untracked(res.error);
|
|
1326
|
+
const isFinal = retries >= max;
|
|
1327
|
+
onError?.(err, retries, isFinal);
|
|
1328
|
+
if (isFinal)
|
|
1243
1329
|
return;
|
|
1244
1330
|
retries++;
|
|
1245
1331
|
if (timeout)
|
|
@@ -1254,11 +1340,11 @@ function retryOnError(res, opt) {
|
|
|
1254
1340
|
const ref = effect(() => {
|
|
1255
1341
|
switch (res.status()) {
|
|
1256
1342
|
case 'error':
|
|
1257
|
-
return
|
|
1343
|
+
return handleError();
|
|
1258
1344
|
case 'resolved':
|
|
1259
1345
|
return onSuccess();
|
|
1260
1346
|
}
|
|
1261
|
-
}, ...(ngDevMode ? [{ debugName: "ref" }] : []));
|
|
1347
|
+
}, ...(ngDevMode ? [{ debugName: "ref" }] : /* istanbul ignore next */ []));
|
|
1262
1348
|
return {
|
|
1263
1349
|
...res,
|
|
1264
1350
|
destroy: () => {
|
|
@@ -1270,10 +1356,10 @@ function retryOnError(res, opt) {
|
|
|
1270
1356
|
|
|
1271
1357
|
class ResourceSensors {
|
|
1272
1358
|
networkStatus = sensor('networkStatus');
|
|
1273
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1274
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1359
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ResourceSensors, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1360
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ResourceSensors, providedIn: 'root' });
|
|
1275
1361
|
}
|
|
1276
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ResourceSensors, decorators: [{
|
|
1277
1363
|
type: Injectable,
|
|
1278
1364
|
args: [{
|
|
1279
1365
|
providedIn: 'root',
|
|
@@ -1337,16 +1423,26 @@ function queryResource(request, options) {
|
|
|
1337
1423
|
const eq = options?.triggerOnSameRequest
|
|
1338
1424
|
? undefined
|
|
1339
1425
|
: createEqualRequest(options?.equal);
|
|
1426
|
+
const rawRequest = computed(() => request() ?? undefined, ...(ngDevMode ? [{ debugName: "rawRequest" }] : /* istanbul ignore next */ []));
|
|
1427
|
+
const disabledReason = computed(() => {
|
|
1428
|
+
if (!networkAvailable())
|
|
1429
|
+
return 'offline';
|
|
1430
|
+
if (cb.isOpen())
|
|
1431
|
+
return 'circuit-open';
|
|
1432
|
+
if (!rawRequest())
|
|
1433
|
+
return 'no-request';
|
|
1434
|
+
return null;
|
|
1435
|
+
}, ...(ngDevMode ? [{ debugName: "disabledReason" }] : /* istanbul ignore next */ []));
|
|
1340
1436
|
const stableRequest = computed(() => {
|
|
1341
|
-
if (
|
|
1437
|
+
if (disabledReason() !== null)
|
|
1342
1438
|
return undefined;
|
|
1343
|
-
const req =
|
|
1439
|
+
const req = rawRequest();
|
|
1344
1440
|
if (!req)
|
|
1345
1441
|
return undefined;
|
|
1346
1442
|
if (typeof req === 'string')
|
|
1347
1443
|
return { method: 'GET', url: req };
|
|
1348
1444
|
return req;
|
|
1349
|
-
}, { ...(ngDevMode ? { debugName: "stableRequest" } : {}), equal: (a, b) => {
|
|
1445
|
+
}, { ...(ngDevMode ? { debugName: "stableRequest" } : /* istanbul ignore next */ {}), equal: (a, b) => {
|
|
1350
1446
|
if (eq)
|
|
1351
1447
|
return eq(a, b);
|
|
1352
1448
|
return a === b;
|
|
@@ -1361,7 +1457,7 @@ function queryResource(request, options) {
|
|
|
1361
1457
|
if (!r)
|
|
1362
1458
|
return null;
|
|
1363
1459
|
return hashFn(r);
|
|
1364
|
-
}, ...(ngDevMode ? [{ debugName: "cacheKey" }] : []));
|
|
1460
|
+
}, ...(ngDevMode ? [{ debugName: "cacheKey" }] : /* istanbul ignore next */ []));
|
|
1365
1461
|
const bustBrowserCache = typeof options?.cache === 'object' &&
|
|
1366
1462
|
options.cache.bustBrowserCache === true;
|
|
1367
1463
|
const ignoreCacheControl = typeof options?.cache === 'object' &&
|
|
@@ -1392,7 +1488,7 @@ function queryResource(request, options) {
|
|
|
1392
1488
|
resource = catchValueError(resource, options?.defaultValue);
|
|
1393
1489
|
// get full HttpResonse from Cache
|
|
1394
1490
|
const cachedEvent = cache.getEntryOrKey(cacheKey);
|
|
1395
|
-
const cacheEntry = linkedSignal({ ...(ngDevMode ? { debugName: "cacheEntry" } : {}), source: () => cachedEvent(),
|
|
1491
|
+
const cacheEntry = linkedSignal({ ...(ngDevMode ? { debugName: "cacheEntry" } : /* istanbul ignore next */ {}), source: () => cachedEvent(),
|
|
1396
1492
|
computation: (entry, prev) => {
|
|
1397
1493
|
if (!entry)
|
|
1398
1494
|
return null;
|
|
@@ -1412,7 +1508,7 @@ function queryResource(request, options) {
|
|
|
1412
1508
|
};
|
|
1413
1509
|
} });
|
|
1414
1510
|
resource = refresh(resource, destroyRef, options?.refresh);
|
|
1415
|
-
resource = retryOnError(resource, options?.retry);
|
|
1511
|
+
resource = retryOnError(resource, options?.retry, options?.onError);
|
|
1416
1512
|
resource = persistResourceValues(resource, options?.keepPrevious, options?.equal);
|
|
1417
1513
|
const value = options?.cache
|
|
1418
1514
|
? toWritable(computed(() => {
|
|
@@ -1420,20 +1516,6 @@ function queryResource(request, options) {
|
|
|
1420
1516
|
return cacheEntry()?.value ?? resource.value();
|
|
1421
1517
|
}), resource.value.set, resource.value.update)
|
|
1422
1518
|
: resource.value;
|
|
1423
|
-
const onError = options?.onError; // Put in own variable to ensure value remains even if options are somehow mutated in-line
|
|
1424
|
-
if (onError) {
|
|
1425
|
-
const onErrorRef = effect(() => {
|
|
1426
|
-
const err = resource.error();
|
|
1427
|
-
if (err)
|
|
1428
|
-
onError(err);
|
|
1429
|
-
}, ...(ngDevMode ? [{ debugName: "onErrorRef" }] : []));
|
|
1430
|
-
// cleanup on manual destroy, I'm comfortable setting these props in-line as we have yet to 'release' the object out of this lexical scope
|
|
1431
|
-
const destroyRest = resource.destroy;
|
|
1432
|
-
resource.destroy = () => {
|
|
1433
|
-
onErrorRef.destroy();
|
|
1434
|
-
destroyRest();
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
1519
|
// iterate circuit breaker state, is effect as a computed would cause a circular dependency (resource -> cb -> resource)
|
|
1438
1520
|
const cbEffectRef = effect(() => {
|
|
1439
1521
|
const status = resource.status();
|
|
@@ -1441,7 +1523,7 @@ function queryResource(request, options) {
|
|
|
1441
1523
|
cb.fail(untracked(resource.error));
|
|
1442
1524
|
else if (status === 'resolved')
|
|
1443
1525
|
cb.success();
|
|
1444
|
-
}, ...(ngDevMode ? [{ debugName: "cbEffectRef" }] : []));
|
|
1526
|
+
}, ...(ngDevMode ? [{ debugName: "cbEffectRef" }] : /* istanbul ignore next */ []));
|
|
1445
1527
|
const set = (value) => {
|
|
1446
1528
|
resource.value.set(value);
|
|
1447
1529
|
const k = untracked(cacheKey);
|
|
@@ -1465,7 +1547,8 @@ function queryResource(request, options) {
|
|
|
1465
1547
|
update,
|
|
1466
1548
|
statusCode: linkedSignal(resource.statusCode),
|
|
1467
1549
|
headers: linkedSignal(resource.headers),
|
|
1468
|
-
disabled: computed(() =>
|
|
1550
|
+
disabled: computed(() => disabledReason() !== null),
|
|
1551
|
+
disabledReason,
|
|
1469
1552
|
reload: () => {
|
|
1470
1553
|
cb.halfOpen(); // open the circuit for manual reload
|
|
1471
1554
|
return resource.reload();
|
|
@@ -1528,7 +1611,7 @@ function queryResource(request, options) {
|
|
|
1528
1611
|
}
|
|
1529
1612
|
|
|
1530
1613
|
function manualQueryResource(request, options) {
|
|
1531
|
-
const trigger = signal({ epoch: 0 }, { ...(ngDevMode ? { debugName: "trigger" } : {}), equal: (a, b) => a.epoch === b.epoch });
|
|
1614
|
+
const trigger = signal({ epoch: 0 }, { ...(ngDevMode ? { debugName: "trigger" } : /* istanbul ignore next */ {}), equal: (a, b) => a.epoch === b.epoch });
|
|
1532
1615
|
const injector = options?.injector ?? inject(Injector);
|
|
1533
1616
|
const req = computed(() => {
|
|
1534
1617
|
const state = trigger();
|
|
@@ -1537,7 +1620,7 @@ function manualQueryResource(request, options) {
|
|
|
1537
1620
|
if (state.override)
|
|
1538
1621
|
return state.override;
|
|
1539
1622
|
return untracked(request);
|
|
1540
|
-
}, { ...(ngDevMode ? { debugName: "req" } : {}), equal: () => false });
|
|
1623
|
+
}, { ...(ngDevMode ? { debugName: "req" } : /* istanbul ignore next */ {}), equal: () => false });
|
|
1541
1624
|
const resource = queryResource(req, options);
|
|
1542
1625
|
return {
|
|
1543
1626
|
...resource,
|
|
@@ -1586,14 +1669,14 @@ function mutationResource(request, options = {}) {
|
|
|
1586
1669
|
const { onMutate, onError, onSuccess, onSettled, equal, ...rest } = options;
|
|
1587
1670
|
const requestEqual = createEqualRequest(equal);
|
|
1588
1671
|
const eq = equal ?? Object.is;
|
|
1589
|
-
const next = signal(null, { ...(ngDevMode ? { debugName: "next" } : {}), equal: (a, b) => {
|
|
1672
|
+
const next = signal(null, { ...(ngDevMode ? { debugName: "next" } : /* istanbul ignore next */ {}), equal: (a, b) => {
|
|
1590
1673
|
if (!a && !b)
|
|
1591
1674
|
return true;
|
|
1592
1675
|
if (!a || !b)
|
|
1593
1676
|
return false;
|
|
1594
1677
|
return eq(a, b);
|
|
1595
1678
|
} });
|
|
1596
|
-
const queue = signal([], ...(ngDevMode ? [{ debugName: "queue" }] : []));
|
|
1679
|
+
const queue = signal([], ...(ngDevMode ? [{ debugName: "queue" }] : /* istanbul ignore next */ []));
|
|
1597
1680
|
let ctx = undefined;
|
|
1598
1681
|
const queueRef = effect(() => {
|
|
1599
1682
|
const nextInQueue = queue().at(0);
|
|
@@ -1603,14 +1686,14 @@ function mutationResource(request, options = {}) {
|
|
|
1603
1686
|
const [value, ictx] = nextInQueue;
|
|
1604
1687
|
ctx = onMutate?.(value, ictx);
|
|
1605
1688
|
next.set(value);
|
|
1606
|
-
}, ...(ngDevMode ? [{ debugName: "queueRef" }] : []));
|
|
1689
|
+
}, ...(ngDevMode ? [{ debugName: "queueRef" }] : /* istanbul ignore next */ []));
|
|
1607
1690
|
const req = computed(() => {
|
|
1608
1691
|
const nr = next();
|
|
1609
1692
|
if (!nr)
|
|
1610
1693
|
return;
|
|
1611
1694
|
return request(nr) ?? undefined;
|
|
1612
|
-
}, { ...(ngDevMode ? { debugName: "req" } : {}), equal: requestEqual });
|
|
1613
|
-
const lastValue = linkedSignal({ ...(ngDevMode ? { debugName: "lastValue" } : {}), source: next,
|
|
1695
|
+
}, { ...(ngDevMode ? { debugName: "req" } : /* istanbul ignore next */ {}), equal: requestEqual });
|
|
1696
|
+
const lastValue = linkedSignal({ ...(ngDevMode ? { debugName: "lastValue" } : /* istanbul ignore next */ {}), source: next,
|
|
1614
1697
|
computation: (next, prev) => {
|
|
1615
1698
|
if (next === null && !!prev)
|
|
1616
1699
|
return prev.value;
|
|
@@ -1621,7 +1704,7 @@ function mutationResource(request, options = {}) {
|
|
|
1621
1704
|
if (!nr)
|
|
1622
1705
|
return;
|
|
1623
1706
|
return request(nr) ?? undefined;
|
|
1624
|
-
}, { ...(ngDevMode ? { debugName: "lastValueRequest" } : {}), equal: requestEqual });
|
|
1707
|
+
}, { ...(ngDevMode ? { debugName: "lastValueRequest" } : /* istanbul ignore next */ {}), equal: requestEqual });
|
|
1625
1708
|
const cb = createCircuitBreaker(options?.circuitBreaker === true
|
|
1626
1709
|
? undefined
|
|
1627
1710
|
: (options?.circuitBreaker ?? false), options?.injector);
|