@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.
@@ -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 && event.ok) {
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
- treshold: 5,
776
+ threshold: 5,
733
777
  timeout: 30000,
734
778
  shouldFail: () => true,
735
779
  shouldFailForever: () => false,
736
780
  };
737
781
  /** @internal */
738
- function internalCeateCircuitBreaker(treshold = 5, resetTimeout = 30000, shouldFail = () => true, shouldFailForever = () => false) {
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() >= treshold)
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(treshold - 1);
801
+ failureCount.set(threshold - 1);
757
802
  };
758
- let failForeverResetId = null;
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
- if (failForeverResetId)
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`: Creates a "no-op" circuit breaker that is always open (never trips).
838
- * - `true`: Creates a circuit breaker with default settings (threshold: 5, timeout: 30000ms).
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 { treshold, timeout, shouldFail, shouldFailForever } = {
944
+ const { threshold, timeout, shouldFail, shouldFailForever } = {
862
945
  ...injectCircuitBreakerOptions(injector),
863
- ...opt,
946
+ ...normalizeThreshold(opt),
864
947
  };
865
- return internalCeateCircuitBreaker(treshold, timeout, shouldFail, shouldFailForever);
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 onError = () => {
1242
- if (retries >= max)
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 onError();
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.0", ngImport: i0, type: ResourceSensors, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1274
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ResourceSensors, providedIn: 'root' });
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.0", ngImport: i0, type: ResourceSensors, decorators: [{
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 (!networkAvailable() || cb.isOpen())
1437
+ if (disabledReason() !== null)
1342
1438
  return undefined;
1343
- const req = request();
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(() => cb.isOpen() || stableRequest() === undefined),
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);