@web-ts-toolkit/access-router-client 0.42.2 → 0.44.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/index.js CHANGED
@@ -57,6 +57,39 @@ var MissingPersistenceIdentityError = class extends Error {
57
57
  this.name = "MissingPersistenceIdentityError";
58
58
  }
59
59
  };
60
+ var MODEL_PATH_PATTERN = /[^.[\]]+|\[(?:([^"'[\]]+)|["']([^"']+)["'])\]/g;
61
+ var MODEL_UNSAFE_PATH_PARTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
62
+ function toModelPathParts(path) {
63
+ if (path.length === 0) {
64
+ return [];
65
+ }
66
+ const result = [];
67
+ path.replace(MODEL_PATH_PATTERN, (_match, bare, quoted) => {
68
+ const token = bare ?? quoted ?? _match;
69
+ result.push(/^\d+$/.test(token) ? Number(token) : token);
70
+ return "";
71
+ });
72
+ return result.length > 0 ? result : [path];
73
+ }
74
+ function modelPathRoot(path) {
75
+ const parts = toModelPathParts(path);
76
+ if (parts.length === 0) {
77
+ return "";
78
+ }
79
+ return String(parts[0]);
80
+ }
81
+ function assertSupportedModelPath(path) {
82
+ const parts = toModelPathParts(path);
83
+ if (parts.length === 0) {
84
+ throw new Error(`Model path must not be empty.`);
85
+ }
86
+ for (const part of parts) {
87
+ if (typeof part === "string" && MODEL_UNSAFE_PATH_PARTS.has(part)) {
88
+ throw new Error(`Model path "${path}" contains a reserved segment "${part}".`);
89
+ }
90
+ }
91
+ return String(parts[0]);
92
+ }
60
93
  var Model = class _Model {
61
94
  constructor(data, adapter, persistenceId, fromExisting) {
62
95
  this.modifiedPaths = /* @__PURE__ */ new Set();
@@ -129,12 +162,6 @@ var Model = class _Model {
129
162
  return queuedSave;
130
163
  }
131
164
  async saveNow(reqConfig) {
132
- const submittedPaths = new Set(this.modifiedPaths);
133
- const submittedValues = {};
134
- for (const path of submittedPaths) {
135
- submittedValues[path] = (0, import_utils.cloneDeep)((0, import_utils.get)(this._data, path));
136
- }
137
- const submittedData = this.prepareData();
138
165
  const persistenceId = this._data._id ?? this._persistenceId;
139
166
  if (persistenceId == null && this._fromExisting) {
140
167
  throw new MissingPersistenceIdentityError(
@@ -142,17 +169,26 @@ var Model = class _Model {
142
169
  );
143
170
  }
144
171
  const isCreate = persistenceId == null;
172
+ const submittedPaths = new Set(this.modifiedPaths);
173
+ const submittedValues = {};
174
+ for (const path of submittedPaths) {
175
+ submittedValues[path] = (0, import_utils.cloneDeep)((0, import_utils.get)(this._data, path));
176
+ }
177
+ const submittedData = this.prepareData(isCreate);
145
178
  const result = isCreate ? await this._service.create(submittedData, void 0, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
146
179
  if (!result.success) {
147
180
  return { ...result, data: null };
148
181
  }
182
+ const preMergeDirty = new Set(this.modifiedPaths);
183
+ const preMergeValues = {};
184
+ for (const path of submittedPaths) {
185
+ preMergeValues[path] = (0, import_utils.cloneDeep)((0, import_utils.get)(this._data, path));
186
+ }
149
187
  const isConcurrentEdit = (path) => {
150
188
  if (!submittedPaths.has(path)) {
151
- return this.modifiedPaths.has(path);
189
+ return preMergeDirty.has(path);
152
190
  }
153
- const current = (0, import_utils.get)(this._data, path);
154
- const submitted = submittedValues[path];
155
- return !(0, import_utils.isEqual)(current, submitted);
191
+ return !(0, import_utils.isEqual)(preMergeValues[path], submittedValues[path]);
156
192
  };
157
193
  const serverData = result.raw ?? {};
158
194
  for (const key of Object.keys(serverData)) {
@@ -171,6 +207,8 @@ var Model = class _Model {
171
207
  for (const path of submittedPaths) {
172
208
  if (!isConcurrentEdit(path)) {
173
209
  this.modifiedPaths.delete(path);
210
+ } else {
211
+ this.modifiedPaths.add(path);
174
212
  }
175
213
  }
176
214
  if (isCreate && serverData._id != null) {
@@ -197,14 +235,13 @@ var Model = class _Model {
197
235
  }
198
236
  this._snapshot = nextSnapshot;
199
237
  this.definePublicDataProps();
238
+ const returned = _Model.create(this._data, this._service, this._persistenceId, true);
239
+ returned._snapshot = (0, import_utils.cloneDeep)(this._snapshot);
240
+ returned.modifiedPaths = new Set(this.modifiedPaths);
241
+ returned.definePublicDataProps();
200
242
  return {
201
243
  ...result,
202
- // The post-save snapshot is always an existing document, so propagate
203
- // `_fromExisting=true` plus the refreshed persistence identity so the
204
- // returned wrapper cannot later silently create a duplicate. (If the
205
- // caller intends a fresh draft, they construct `new Model({...}, s)`
206
- // directly — `${_fromExisting}` defaults to `false` there.)
207
- data: _Model.create(this._data, this._service, this._persistenceId, true)
244
+ data: returned
208
245
  };
209
246
  }
210
247
  isDirty(path) {
@@ -224,6 +261,7 @@ var Model = class _Model {
224
261
  * run `reconcilePath` after the write.
225
262
  */
226
263
  markModified(path) {
264
+ assertSupportedModelPath(String(path));
227
265
  this.trackModified(String(path));
228
266
  return this;
229
267
  }
@@ -231,6 +269,7 @@ var Model = class _Model {
231
269
  return (0, import_utils.get)(this._data, path);
232
270
  }
233
271
  set(path, value) {
272
+ const root = assertSupportedModelPath(path);
234
273
  const currentValue = (0, import_utils.get)(this._data, path);
235
274
  if (Object.is(currentValue, value)) {
236
275
  return this;
@@ -238,7 +277,7 @@ var Model = class _Model {
238
277
  (0, import_utils.set)(this._data, path, value);
239
278
  this.trackModified(path);
240
279
  this.definePublicDataProps();
241
- this.reconcilePath(this.normalizePath(path));
280
+ this.reconcilePath(root);
242
281
  return this;
243
282
  }
244
283
  assign(partial) {
@@ -257,8 +296,12 @@ var Model = class _Model {
257
296
  return this;
258
297
  }
259
298
  reset() {
299
+ const wasDraft = this.isUnsavedDraft();
260
300
  this.replaceData(this._snapshot);
261
301
  this.modifiedPaths.clear();
302
+ if (wasDraft) {
303
+ this.initializeDirtyState();
304
+ }
262
305
  return this;
263
306
  }
264
307
  toObject() {
@@ -291,7 +334,10 @@ var Model = class _Model {
291
334
  }
292
335
  }
293
336
  }
294
- prepareData() {
337
+ prepareData(isCreate) {
338
+ if (isCreate) {
339
+ return (0, import_utils.cloneDeep)((0, import_utils.omit)(this._data, ["_id"]));
340
+ }
295
341
  return (0, import_utils.omit)((0, import_utils.pick)(this._data, Array.from(this.modifiedPaths).map(String)), ["_id"]);
296
342
  }
297
343
  defineHiddenDataProp(initialValue) {
@@ -338,21 +384,20 @@ var Model = class _Model {
338
384
  this.modifiedPaths.add(this.normalizePath(path));
339
385
  }
340
386
  normalizePath(path) {
341
- return path.split(".")[0];
387
+ return modelPathRoot(path);
342
388
  }
343
389
  /**
344
- * Removes `path` from the dirty set when its current top-level value deeply
345
- * equals the snapshot baseline. Used uniformly by `set()`, `assign()`,
346
- * public property setters (via the proxy), and `markModified()` so all
347
- * entry points share the same tracking rule.
348
- *
349
- * Note: `_id` is intentionally never reconciled away here — it is excluded
350
- * from `initializeDirtyState` and managed explicitly during `save()`
351
- * reconciliation.
390
+ * Removes `path` from the dirty set when its current value deeply equals
391
+ * the snapshot baseline. Invariant: unsaved drafts never reconcile clean
392
+ * (snapshot is unpersisted); `_id` is never reconciled here.
352
393
  */
394
+ isUnsavedDraft() {
395
+ return !this._fromExisting && (this._data._id ?? this._persistenceId) == null;
396
+ }
353
397
  reconcilePath(path) {
354
398
  if (path === "_id") return;
355
399
  if (!this.modifiedPaths.has(path)) return;
400
+ if (this.isUnsavedDraft()) return;
356
401
  const current = this._data[path];
357
402
  const base = this._snapshot[path];
358
403
  if ((0, import_utils.isEqual)(current, base)) {
@@ -429,6 +474,10 @@ var UnsupportedGroupedRequestConfigError = class extends Error {
429
474
  this.name = "UnsupportedGroupedRequestConfigError";
430
475
  }
431
476
  };
477
+ var isPlainObjectRecord = (value) => {
478
+ const prototype = Object.getPrototypeOf(value);
479
+ return prototype === Object.prototype || prototype === null;
480
+ };
432
481
  var normalizeConfigValue = (value) => {
433
482
  if (value == null) return value;
434
483
  if (value instanceof import_axios.AxiosHeaders) {
@@ -459,11 +508,52 @@ var normalizeGroupedRequestConfig = (config) => {
459
508
  `Grouped requests do not support symbol-valued axios config at ${path}`
460
509
  );
461
510
  }
511
+ if (typeof value === "bigint") {
512
+ throw new UnsupportedGroupedRequestConfigError(
513
+ `Grouped requests do not support bigint-valued axios config at ${path}`
514
+ );
515
+ }
516
+ if (typeof value === "number") {
517
+ if (!Number.isFinite(value)) {
518
+ throw new UnsupportedGroupedRequestConfigError(
519
+ `Grouped requests do not support non-finite numeric axios config at ${path}`
520
+ );
521
+ }
522
+ return value;
523
+ }
524
+ if (typeof value === "string" || typeof value === "boolean") {
525
+ return value;
526
+ }
462
527
  if (value instanceof import_axios.AxiosHeaders) {
463
528
  return normalize(value.toJSON(), path);
464
529
  }
530
+ if (value instanceof Date) {
531
+ const time = value.getTime();
532
+ if (Number.isNaN(time)) {
533
+ throw new UnsupportedGroupedRequestConfigError(
534
+ `Grouped requests do not support invalid Date axios config at ${path}`
535
+ );
536
+ }
537
+ return { __type: "Date", iso: value.toISOString() };
538
+ }
539
+ if (value instanceof URLSearchParams) {
540
+ const entries = Array.from(value.entries()).sort(
541
+ ([leftKey, leftValue], [rightKey, rightValue]) => leftKey === rightKey ? leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0 : leftKey < rightKey ? -1 : 1
542
+ );
543
+ return { __type: "URLSearchParams", entries };
544
+ }
465
545
  if (Array.isArray(value)) {
466
- return value.map((item, index) => normalize(item, `${path}[${index}]`));
546
+ if (seen.has(value)) {
547
+ throw new UnsupportedGroupedRequestConfigError(
548
+ `Grouped requests do not support circular axios config at ${path}`
549
+ );
550
+ }
551
+ seen.add(value);
552
+ try {
553
+ return value.map((item, index) => normalize(item, `${path}[${index}]`));
554
+ } finally {
555
+ seen.delete(value);
556
+ }
467
557
  }
468
558
  if (typeof value === "object") {
469
559
  if (seen.has(value)) {
@@ -471,19 +561,27 @@ var normalizeGroupedRequestConfig = (config) => {
471
561
  `Grouped requests do not support circular axios config at ${path}`
472
562
  );
473
563
  }
564
+ if (!isPlainObjectRecord(value)) {
565
+ throw new UnsupportedGroupedRequestConfigError(
566
+ `Grouped requests do not support non-plain object axios config at ${path}`
567
+ );
568
+ }
474
569
  seen.add(value);
475
- const normalized = Object.entries((0, import_utils3.omitBy)(value, (item) => item === void 0)).sort(([left], [right]) => left.localeCompare(right)).reduce((acc, [key, item]) => {
476
- const itemPath = path === "config" ? key : `${path}.${key}`;
477
- if (unsupportedGroupConfigKeys.has(key)) {
478
- throw new UnsupportedGroupedRequestConfigError(
479
- `Grouped requests do not support axios config key ${itemPath}`
480
- );
481
- }
482
- acc[key] = normalize(item, itemPath);
483
- return acc;
484
- }, {});
485
- seen.delete(value);
486
- return normalized;
570
+ try {
571
+ const normalized = Object.entries((0, import_utils3.omitBy)(value, (item) => item === void 0)).sort(([left], [right]) => left.localeCompare(right)).reduce((acc, [key, item]) => {
572
+ const itemPath = path === "config" ? key : `${path}.${key}`;
573
+ if (path === "config" && unsupportedGroupConfigKeys.has(key)) {
574
+ throw new UnsupportedGroupedRequestConfigError(
575
+ `Grouped requests do not support axios config key ${itemPath}`
576
+ );
577
+ }
578
+ acc[key] = normalize(item, itemPath);
579
+ return acc;
580
+ }, {});
581
+ return normalized;
582
+ } finally {
583
+ seen.delete(value);
584
+ }
487
585
  }
488
586
  return value;
489
587
  };
@@ -496,13 +594,6 @@ var CACHEABLE_METHODS = /* @__PURE__ */ new Set(["get"]);
496
594
  var CACHEABLE_RESPONSE_TYPES = /* @__PURE__ */ new Set(["", "json", "text"]);
497
595
  var CACHE_INVALIDATE_ON_SUCCESS = "__accessRouterClientCacheInvalidateOnSuccess";
498
596
  var CACHE_INVALIDATE_HEADER = "x-axios-cache-invalidate-on-success";
499
- var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
500
- "authorization",
501
- "cookie",
502
- "set-cookie",
503
- "proxy-authorization",
504
- "www-authenticate"
505
- ]);
506
597
  var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
507
598
  "authorization",
508
599
  "cookie",
@@ -511,6 +602,7 @@ var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
511
602
  "x-auth-token",
512
603
  "x-access-token"
513
604
  ]);
605
+ var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([...AUTHENTICATION_REQUEST_HEADERS, "set-cookie", "www-authenticate"]);
514
606
  var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
515
607
  const baseConfig = config ?? {};
516
608
  const next = { ...baseConfig };
@@ -522,16 +614,46 @@ var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
522
614
  } else {
523
615
  next.headers = {};
524
616
  }
617
+ for (const key of Object.keys(next.headers)) {
618
+ if (key.toLowerCase() === CACHE_HEADER.toLowerCase()) {
619
+ delete next.headers[key];
620
+ }
621
+ }
525
622
  next.headers[CACHE_HEADER] = "false";
526
623
  if (invalidateOnSuccess) {
527
624
  next[CACHE_INVALIDATE_ON_SUCCESS] = true;
528
- next.headers[CACHE_INVALIDATE_HEADER] = "true";
529
625
  }
530
626
  return next;
531
627
  };
628
+ var findHeaderKey = (headers, name) => {
629
+ const target = name.toLowerCase();
630
+ return Object.keys(headers).find((key) => key.toLowerCase() === target);
631
+ };
632
+ var getCacheControlValue = (headers) => {
633
+ if (!headers || typeof headers !== "object") return void 0;
634
+ if (headers instanceof import_axios2.AxiosHeaders) return headers.get(CACHE_HEADER);
635
+ const key = findHeaderKey(headers, CACHE_HEADER);
636
+ return key === void 0 ? void 0 : headers[key];
637
+ };
638
+ var hasCacheControlHeader = (headers) => getCacheControlValue(headers) !== void 0;
639
+ var hasInvalidateSignal = (headers) => {
640
+ if (!headers || typeof headers !== "object") return false;
641
+ if (headers instanceof import_axios2.AxiosHeaders) return headers.has(CACHE_INVALIDATE_HEADER);
642
+ return findHeaderKey(headers, CACHE_INVALIDATE_HEADER) !== void 0;
643
+ };
644
+ var deleteInvalidateSignal = (headers) => {
645
+ if (headers instanceof import_axios2.AxiosHeaders) {
646
+ headers.delete(CACHE_INVALIDATE_HEADER);
647
+ return;
648
+ }
649
+ const key = findHeaderKey(headers, CACHE_INVALIDATE_HEADER);
650
+ if (key !== void 0) {
651
+ delete headers[key];
652
+ }
653
+ };
532
654
  var removeCacheInvalidationSignal = (config) => {
533
655
  const headers = config.headers;
534
- const hasHeaderSignal = headers instanceof import_axios2.AxiosHeaders ? headers.has(CACHE_INVALIDATE_HEADER) : Boolean(headers && typeof headers === "object" && CACHE_INVALIDATE_HEADER in headers);
656
+ const hasHeaderSignal = hasInvalidateSignal(headers);
535
657
  if (CACHE_INVALIDATE_ON_SUCCESS in config || hasHeaderSignal) {
536
658
  const next = { ...config };
537
659
  delete next[CACHE_INVALIDATE_ON_SUCCESS];
@@ -541,7 +663,7 @@ var removeCacheInvalidationSignal = (config) => {
541
663
  next.headers = clonedHeaders;
542
664
  } else if (headers && typeof headers === "object") {
543
665
  next.headers = { ...headers };
544
- delete next.headers[CACHE_INVALIDATE_HEADER];
666
+ deleteInvalidateSignal(next.headers);
545
667
  }
546
668
  return next;
547
669
  }
@@ -651,11 +773,42 @@ var snapshotResponse = (response, clone) => {
651
773
  headers: clone(headers)
652
774
  };
653
775
  };
776
+ var identityTransform = (data) => data;
777
+ var bypassResponseTransform = (config) => {
778
+ config.transformResponse = [identityTransform];
779
+ };
780
+ var settleSyntheticResponse = (callerConfig, response) => {
781
+ const status = response.status;
782
+ const validateStatus = callerConfig.validateStatus;
783
+ if (!status || !validateStatus || validateStatus(status)) {
784
+ return response;
785
+ }
786
+ throw new import_axios2.AxiosError(
787
+ `Request failed with status code ${status}`,
788
+ status >= 400 && status < 500 ? import_axios2.AxiosError.ERR_BAD_REQUEST : import_axios2.AxiosError.ERR_BAD_RESPONSE,
789
+ callerConfig,
790
+ void 0,
791
+ response
792
+ );
793
+ };
794
+ var isSettlementRejectionFor = (error, sourceConfig) => {
795
+ const response = error?.response;
796
+ if (!response || !response.status) return false;
797
+ const validateStatus = sourceConfig.validateStatus;
798
+ if (!validateStatus) {
799
+ return false;
800
+ }
801
+ try {
802
+ return !validateStatus(response.status);
803
+ } catch {
804
+ return false;
805
+ }
806
+ };
654
807
  var serializeHeaders = (headers) => {
655
808
  const resolvedHeaders = headers instanceof import_axios2.AxiosHeaders ? headers.toJSON() : headers;
656
809
  const normalizedHeaders = Object.entries(resolvedHeaders ?? {}).filter(([key, value]) => {
657
810
  const normalizedKey = key.toLowerCase();
658
- return normalizedKey !== CACHE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
811
+ return normalizedKey !== CACHE_HEADER.toLowerCase() && normalizedKey !== CACHE_INVALIDATE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
659
812
  }).reduce((acc, [key, value]) => {
660
813
  acc[key.toLowerCase()] = value;
661
814
  return acc;
@@ -670,13 +823,13 @@ var hasHeaderValue = (value) => {
670
823
  };
671
824
  var consumeCacheInvalidationSignal = (config) => {
672
825
  const headers = config.headers;
673
- const hasSignal = headers instanceof import_axios2.AxiosHeaders ? headers.has(CACHE_INVALIDATE_HEADER) : Boolean(headers && typeof headers === "object" && CACHE_INVALIDATE_HEADER in headers);
826
+ const hasSignal = hasInvalidateSignal(headers);
674
827
  if (!hasSignal && !config[CACHE_INVALIDATE_ON_SUCCESS]) return;
675
828
  config[CACHE_INVALIDATE_ON_SUCCESS] = true;
676
829
  if (headers instanceof import_axios2.AxiosHeaders) {
677
830
  headers.delete(CACHE_INVALIDATE_HEADER);
678
831
  } else if (headers && typeof headers === "object") {
679
- delete headers[CACHE_INVALIDATE_HEADER];
832
+ deleteInvalidateSignal(headers);
680
833
  }
681
834
  };
682
835
  var hasAuthenticationHeader = (headers) => {
@@ -711,10 +864,12 @@ var sameConfigIdentity = (configured, defaultValue) => {
711
864
  }
712
865
  return configured === defaultValue;
713
866
  };
867
+ var PRISTINE_TRANSFORM_REQUEST = import_axios2.default.defaults.transformRequest;
868
+ var PRISTINE_TRANSFORM_RESPONSE = import_axios2.default.defaults.transformResponse;
714
869
  var isCacheEligible = (config, instance) => {
715
870
  const method = (config.method ?? "get").toLowerCase();
716
871
  const responseType = config.responseType ?? "";
717
- return CACHEABLE_METHODS.has(method) && CACHEABLE_RESPONSE_TYPES.has(responseType) && config.paramsSerializer === void 0 && config.auth === void 0 && config.signal === void 0 && config.cancelToken === void 0 && config.onDownloadProgress === void 0 && config.onUploadProgress === void 0 && sameConfigIdentity(config.adapter, instance.defaults.adapter) && sameTransform(config.transformRequest, instance.defaults.transformRequest) && sameTransform(config.transformResponse, instance.defaults.transformResponse) && hasStableCacheValue(config.params) && hasStableCacheValue(config.data) && hasStableCacheValue(config.headers);
872
+ return CACHEABLE_METHODS.has(method) && CACHEABLE_RESPONSE_TYPES.has(responseType) && config.paramsSerializer === void 0 && config.auth === void 0 && config.signal === void 0 && config.cancelToken === void 0 && config.onDownloadProgress === void 0 && config.onUploadProgress === void 0 && sameConfigIdentity(config.adapter, instance.defaults.adapter) && sameTransform(config.transformRequest, PRISTINE_TRANSFORM_REQUEST) && sameTransform(config.transformResponse, PRISTINE_TRANSFORM_RESPONSE) && config.parseReviver === void 0 && config.formSerializer === void 0 && hasStableCacheValue(config.params) && hasStableCacheValue(config.data) && hasStableCacheValue(config.headers);
718
873
  };
719
874
  function generateCacheKey(config, partition) {
720
875
  const responseSemantics = JSON.stringify({
@@ -753,12 +908,14 @@ function useCacheInterceptors(instance, policyOrTtl) {
753
908
  const store = new SimpleCache({ capacity: policy.capacity, clone: policy.clone });
754
909
  const withCredentialsDefault = policy.withCredentialsDefault ?? Boolean(instance.defaults.withCredentials);
755
910
  const inflight = /* @__PURE__ */ new Map();
911
+ const activeSlots = /* @__PURE__ */ new Set();
756
912
  let generation = 0;
757
913
  let disposed = false;
758
914
  const finalizeInflight = (slot) => {
759
915
  if (inflight.get(slot.key) === slot) {
760
916
  inflight.delete(slot.key);
761
917
  }
918
+ activeSlots.delete(slot);
762
919
  };
763
920
  const resolveInflight = (slot, response) => {
764
921
  if (slot.settled) return;
@@ -780,7 +937,8 @@ function useCacheInterceptors(instance, policyOrTtl) {
780
937
  instance.interceptors.request.use(
781
938
  async (config) => {
782
939
  consumeCacheInvalidationSignal(config);
783
- if (disposed || config.headers[CACHE_HEADER] === "false" || !isCacheEligible(config, instance)) return config;
940
+ if (disposed || getCacheControlValue(config.headers) === "false" || !isCacheEligible(config, instance))
941
+ return config;
784
942
  const isCredentialed = resolveWithCredentials(config, withCredentialsDefault) || hasAuthenticationHeader(config.headers);
785
943
  const partitionKey = policy.partitionForRequest?.(config);
786
944
  if (isCredentialed && !hasUsablePartition(partitionKey)) {
@@ -791,29 +949,33 @@ function useCacheInterceptors(instance, policyOrTtl) {
791
949
  const snapshot = store.get(key);
792
950
  if (snapshot) {
793
951
  setCacheRequestState(config, Object.freeze({ key, generation, role: "hit" }));
952
+ bypassResponseTransform(config);
794
953
  config.adapter = async (_config) => {
795
- return {
954
+ const response = {
796
955
  data: snapshot.data,
797
956
  status: snapshot.status,
798
957
  statusText: snapshot.statusText,
799
958
  headers: { ...snapshot.headers, [CACHE_HEADER]: "true" },
800
959
  config: _config
801
960
  };
961
+ return settleSyntheticResponse(_config, response);
802
962
  };
803
963
  return config;
804
964
  }
805
965
  const existing = inflight.get(key);
806
966
  if (existing) {
967
+ bypassResponseTransform(config);
807
968
  config.adapter = async (_config) => {
808
969
  const response = await existing.promise;
809
970
  const shared = response;
810
- return {
971
+ const tailResponse = {
811
972
  data: clone(shared.data),
812
973
  status: shared.status,
813
974
  statusText: shared.statusText,
814
975
  headers: { ...shared.headers, [CACHE_HEADER]: "true" },
815
976
  config: _config
816
977
  };
978
+ return settleSyntheticResponse(_config, tailResponse);
817
979
  };
818
980
  setCacheRequestState(config, Object.freeze({ key, generation, role: "tail", slot: existing }));
819
981
  return config;
@@ -835,8 +997,33 @@ function useCacheInterceptors(instance, policyOrTtl) {
835
997
  settled: false
836
998
  };
837
999
  inflight.set(key, slot);
1000
+ activeSlots.add(slot);
838
1001
  const nextConfig = { ...config };
839
1002
  setCacheRequestState(nextConfig, Object.freeze({ key, generation, role: "source", slot }));
1003
+ const settleTransformFailure = (error) => {
1004
+ rejectInflight(slot, error);
1005
+ throw error;
1006
+ };
1007
+ const wrapTransformList = (value) => {
1008
+ const list = Array.isArray(value) ? value : [value];
1009
+ return list.map((fn) => {
1010
+ if (typeof fn !== "function") return fn;
1011
+ const original = fn;
1012
+ return function(...args) {
1013
+ try {
1014
+ const result = original.apply(this, args);
1015
+ if (result && typeof result.catch === "function") {
1016
+ return result.catch((error) => settleTransformFailure(error));
1017
+ }
1018
+ return result;
1019
+ } catch (error) {
1020
+ return settleTransformFailure(error);
1021
+ }
1022
+ };
1023
+ });
1024
+ };
1025
+ nextConfig.transformRequest = wrapTransformList(nextConfig.transformRequest);
1026
+ nextConfig.transformResponse = wrapTransformList(nextConfig.transformResponse);
840
1027
  let realAdapter = config.adapter;
841
1028
  if (realAdapter === void 0 || realAdapter === null) {
842
1029
  realAdapter = instance.defaults.adapter;
@@ -854,7 +1041,12 @@ function useCacheInterceptors(instance, policyOrTtl) {
854
1041
  const response = await dispatch(adapterConfig);
855
1042
  return response;
856
1043
  } catch (error) {
857
- rejectInflight(slot, error);
1044
+ const errResponse = error?.response;
1045
+ if (errResponse && isSettlementRejectionFor(error, adapterConfig)) {
1046
+ resolveInflight(slot, errResponse);
1047
+ } else {
1048
+ rejectInflight(slot, error);
1049
+ }
858
1050
  throw error;
859
1051
  }
860
1052
  };
@@ -890,7 +1082,13 @@ function useCacheInterceptors(instance, policyOrTtl) {
890
1082
  (error) => {
891
1083
  const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
892
1084
  if (state?.role === "source" && state.slot) {
893
- rejectInflight(state.slot, error);
1085
+ const errResponse = error?.response;
1086
+ const sourceConfig = error?.config ?? {};
1087
+ if (errResponse && isSettlementRejectionFor(error, sourceConfig)) {
1088
+ resolveInflight(state.slot, errResponse);
1089
+ } else {
1090
+ rejectInflight(state.slot, error);
1091
+ }
894
1092
  }
895
1093
  return Promise.reject(error);
896
1094
  }
@@ -903,10 +1101,11 @@ function useCacheInterceptors(instance, policyOrTtl) {
903
1101
  generation += 1;
904
1102
  store.dispose();
905
1103
  const error = new Error(CACHE_DISPOSED_ERROR);
906
- for (const slot of inflight.values()) {
1104
+ for (const slot of [...activeSlots]) {
907
1105
  rejectInflight(slot, error);
908
1106
  }
909
1107
  inflight.clear();
1108
+ activeSlots.clear();
910
1109
  }
911
1110
  };
912
1111
  }
@@ -1150,7 +1349,7 @@ var Service = class {
1150
1349
  cloned.set(CACHE_HEADER, cacheValue);
1151
1350
  return cloned;
1152
1351
  }
1153
- if (CACHE_HEADER in headers) return headers;
1352
+ if (hasCacheControlHeader(headers)) return headers;
1154
1353
  return {
1155
1354
  ...headers,
1156
1355
  [CACHE_HEADER]: cacheValue
@@ -1307,26 +1506,86 @@ var isLegacyListPayload = (value) => {
1307
1506
  }
1308
1507
  return "count" in value && typeof value.count === "number" && "rows" in value && Array.isArray(value.rows);
1309
1508
  };
1310
- var cloneDefaultValue = (value) => {
1509
+ var UnsupportedServiceDefaultValueError = class extends Error {
1510
+ constructor(message) {
1511
+ super(message);
1512
+ this.name = "UnsupportedServiceDefaultValueError";
1513
+ }
1514
+ };
1515
+ var isPlainDefaultObject = (value) => {
1516
+ const prototype = Object.getPrototypeOf(value);
1517
+ return prototype === Object.prototype || prototype === null;
1518
+ };
1519
+ var cloneDefaultValueInner = (value, seen, path) => {
1520
+ if (value == null) return value;
1521
+ if (typeof value === "string" || typeof value === "boolean") return value;
1522
+ if (typeof value === "number") {
1523
+ if (!Number.isFinite(value)) {
1524
+ throw new UnsupportedServiceDefaultValueError(
1525
+ `Service defaults do not support non-finite numeric value at ${path}`
1526
+ );
1527
+ }
1528
+ return value;
1529
+ }
1530
+ if (typeof value === "function") {
1531
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support function value at ${path}`);
1532
+ }
1533
+ if (typeof value === "symbol") {
1534
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support symbol value at ${path}`);
1535
+ }
1536
+ if (typeof value === "bigint") {
1537
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support bigint value at ${path}`);
1538
+ }
1539
+ if (value instanceof Date) {
1540
+ const time = value.getTime();
1541
+ if (Number.isNaN(time)) {
1542
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support invalid Date at ${path}`);
1543
+ }
1544
+ return new Date(time);
1545
+ }
1311
1546
  if (Array.isArray(value)) {
1312
- return value.map((item) => cloneDefaultValue(item));
1547
+ if (seen.has(value)) {
1548
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support circular value at ${path}`);
1549
+ }
1550
+ seen.add(value);
1551
+ try {
1552
+ return value.map((item, index) => cloneDefaultValueInner(item, seen, `${path}[${index}]`));
1553
+ } finally {
1554
+ seen.delete(value);
1555
+ }
1313
1556
  }
1314
- if (value && typeof value === "object") {
1315
- const cloned = {};
1316
- for (const [key, item] of Object.entries(value)) {
1317
- cloned[key] = cloneDefaultValue(item);
1557
+ if (typeof value === "object") {
1558
+ if (seen.has(value)) {
1559
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support circular value at ${path}`);
1560
+ }
1561
+ if (!isPlainDefaultObject(value)) {
1562
+ throw new UnsupportedServiceDefaultValueError(
1563
+ `Service defaults do not support non-plain object instance at ${path}`
1564
+ );
1565
+ }
1566
+ seen.add(value);
1567
+ try {
1568
+ const cloned = {};
1569
+ for (const [key, item] of Object.entries(value)) {
1570
+ cloned[key] = cloneDefaultValueInner(item, seen, path === "defaults" ? `defaults.${key}` : `${path}.${key}`);
1571
+ }
1572
+ return cloned;
1573
+ } finally {
1574
+ seen.delete(value);
1318
1575
  }
1319
- return cloned;
1320
1576
  }
1321
1577
  return value;
1322
1578
  };
1323
- var deepFreeze = (value) => {
1324
- if (!value || typeof value !== "object" || Object.isFrozen(value)) {
1579
+ var cloneDefaultValue = (value) => cloneDefaultValueInner(value, /* @__PURE__ */ new WeakSet(), "defaults");
1580
+ var cloneServiceDefaultValue = (value) => cloneDefaultValueInner(value, /* @__PURE__ */ new WeakSet(), "defaults");
1581
+ var deepFreeze = (value, seen = /* @__PURE__ */ new WeakSet()) => {
1582
+ if (!value || typeof value !== "object" || Object.isFrozen(value) || seen.has(value)) {
1325
1583
  return value;
1326
1584
  }
1585
+ seen.add(value);
1327
1586
  Object.freeze(value);
1328
1587
  for (const item of Object.values(value)) {
1329
- deepFreeze(item);
1588
+ deepFreeze(item, seen);
1330
1589
  }
1331
1590
  return value;
1332
1591
  };
@@ -1766,9 +2025,9 @@ var ModelService = class extends Service {
1766
2025
  includePermissions = this._defaults.listOptions.includePermissions ?? false,
1767
2026
  includeCount = this._defaults.listOptions.includeCount ?? false,
1768
2027
  includeExtraHeaders = this._defaults.listOptions.includeExtraHeaders ?? false,
1769
- ignoreCache = this._defaults.listOptions.ignoreCache ?? false,
1770
- sq
2028
+ ignoreCache = this._defaults.listOptions.ignoreCache ?? false
1771
2029
  } = options ?? {};
2030
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.listOptions.sq);
1772
2031
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1773
2032
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
1774
2033
  return makeRequest(
@@ -1817,25 +2076,25 @@ var ModelService = class extends Service {
1817
2076
  }
1818
2077
  listAdvanced(filter, args, options, axiosRequestConfig) {
1819
2078
  const {
1820
- populate = this._defaults.listAdvancedArgs.populate,
1821
- include = this._defaults.listAdvancedArgs.include,
1822
- sort = this._defaults.listAdvancedArgs.sort,
1823
2079
  skip = this._defaults.listAdvancedArgs.skip,
1824
2080
  limit = this._defaults.listAdvancedArgs.limit,
1825
2081
  page = this._defaults.listAdvancedArgs.page,
1826
- pageSize = this._defaults.listAdvancedArgs.pageSize,
1827
- tasks = this._defaults.listAdvancedArgs.tasks
2082
+ pageSize = this._defaults.listAdvancedArgs.pageSize
1828
2083
  } = args ?? {};
1829
- const select = args?.select ?? this._defaults.listAdvancedArgs.select;
2084
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.populate);
2085
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.include);
2086
+ const sort = args?.sort ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.sort);
2087
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.tasks);
2088
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.select);
1830
2089
  const {
1831
2090
  skim = this._defaults.listAdvancedOptions.skim ?? true,
1832
2091
  includePermissions = this._defaults.listAdvancedOptions.includePermissions ?? false,
1833
2092
  includeCount = this._defaults.listAdvancedOptions.includeCount ?? false,
1834
2093
  includeExtraHeaders = this._defaults.listAdvancedOptions.includeExtraHeaders ?? false,
1835
2094
  populateAccess = this._defaults.listAdvancedOptions.populateAccess,
1836
- ignoreCache = this._defaults.listAdvancedOptions.ignoreCache ?? false,
1837
- sq
2095
+ ignoreCache = this._defaults.listAdvancedOptions.ignoreCache ?? false
1838
2096
  } = options ?? {};
2097
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.listAdvancedOptions.sq);
1839
2098
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1840
2099
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
1841
2100
  const _filter = replaceSubQuery(filter);
@@ -1924,8 +2183,9 @@ var ModelService = class extends Service {
1924
2183
  );
1925
2184
  }
1926
2185
  createAdvanced(data, args, options, axiosRequestConfig) {
1927
- const { populate = this._defaults.createAdvancedArgs.populate, tasks = this._defaults.createAdvancedArgs.tasks } = args ?? {};
1928
- const select = args?.select ?? this._defaults.createAdvancedArgs.select;
2186
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.populate);
2187
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.tasks);
2188
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.select);
1929
2189
  const {
1930
2190
  includePermissions = this._defaults.createAdvancedOptions.includePermissions ?? true,
1931
2191
  populateAccess = this._defaults.createAdvancedOptions.populateAccess
@@ -2000,8 +2260,9 @@ var ModelService = class extends Service {
2000
2260
  );
2001
2261
  }
2002
2262
  upsertAdvanced(data, args, options, axiosRequestConfig) {
2003
- const { populate = this._defaults.upsertAdvancedArgs.populate, tasks = this._defaults.upsertAdvancedArgs.tasks } = args ?? {};
2004
- const select = args?.select ?? this._defaults.upsertAdvancedArgs.select;
2263
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.populate);
2264
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.tasks);
2265
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.select);
2005
2266
  const {
2006
2267
  returningAll = this._defaults.upsertAdvancedOptions.returningAll ?? true,
2007
2268
  includePermissions = this._defaults.upsertAdvancedOptions.includePermissions ?? true,
@@ -2088,6 +2349,14 @@ var ModelService = class extends Service {
2088
2349
  }
2089
2350
  );
2090
2351
  }
2352
+ /**
2353
+ * BND-11: distinct values are `unknown[]`, not `string[]`. The sibling
2354
+ * server returns raw distinct values without string conversion, so numeric
2355
+ * and boolean values arrive as-is. Source-compat: callers that assumed
2356
+ * `string[]` must narrow first (e.g. `typeof v === 'string'` or a type
2357
+ * guard) before calling string methods; see the BND-11 task record for
2358
+ * migration. No server values are stringified to satisfy the old type.
2359
+ */
2091
2360
  distinct(field, axiosRequestConfig) {
2092
2361
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2093
2362
  return makeRequest(
@@ -2110,6 +2379,11 @@ var ModelService = class extends Service {
2110
2379
  }
2111
2380
  );
2112
2381
  }
2382
+ /**
2383
+ * BND-11: filtered distinct variant. Same `unknown[]` contract as
2384
+ * {@link distinct}: narrow elements before assuming strings. No
2385
+ * stringification is applied; dynamic field names are accepted as `string`.
2386
+ */
2113
2387
  distinctAdvanced(field, conditions, axiosRequestConfig) {
2114
2388
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2115
2389
  return makeRequest(
@@ -2183,9 +2457,9 @@ var ModelService = class extends Service {
2183
2457
  const {
2184
2458
  includePermissions = this._defaults.readOptions.includePermissions ?? true,
2185
2459
  tryList = this._defaults.readOptions.tryList ?? true,
2186
- ignoreCache = this._defaults.readOptions.ignoreCache ?? false,
2187
- sq
2460
+ ignoreCache = this._defaults.readOptions.ignoreCache ?? false
2188
2461
  } = options ?? {};
2462
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readOptions.sq);
2189
2463
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2190
2464
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2191
2465
  return makeRequest(
@@ -2217,20 +2491,18 @@ var ModelService = class extends Service {
2217
2491
  );
2218
2492
  }
2219
2493
  readAdvanced(identifier, args, options, axiosRequestConfig) {
2220
- const {
2221
- populate = this._defaults.readAdvancedArgs.populate,
2222
- include = this._defaults.readAdvancedArgs.include,
2223
- tasks = this._defaults.readAdvancedArgs.tasks
2224
- } = args ?? {};
2225
- const select = args?.select ?? this._defaults.readAdvancedArgs.select;
2494
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.populate);
2495
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.include);
2496
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.tasks);
2497
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.select);
2226
2498
  const {
2227
2499
  skim = this._defaults.readAdvancedOptions.skim ?? true,
2228
2500
  includePermissions = this._defaults.readAdvancedOptions.includePermissions ?? true,
2229
2501
  tryList = this._defaults.readAdvancedOptions.tryList ?? true,
2230
2502
  populateAccess = this._defaults.readAdvancedOptions.populateAccess,
2231
- ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false,
2232
- sq
2503
+ ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false
2233
2504
  } = options ?? {};
2505
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readAdvancedOptions.sq);
2234
2506
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2235
2507
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2236
2508
  return makeRequest(
@@ -2269,21 +2541,19 @@ var ModelService = class extends Service {
2269
2541
  );
2270
2542
  }
2271
2543
  readAdvancedFilter(filter, args, options, axiosRequestConfig) {
2272
- const {
2273
- sort = this._defaults.readAdvancedArgs.sort,
2274
- populate = this._defaults.readAdvancedArgs.populate,
2275
- include = this._defaults.readAdvancedArgs.include,
2276
- tasks = this._defaults.readAdvancedArgs.tasks
2277
- } = args ?? {};
2278
- const select = args?.select ?? this._defaults.readAdvancedArgs.select;
2544
+ const sort = args?.sort ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.sort);
2545
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.populate);
2546
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.include);
2547
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.tasks);
2548
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.select);
2279
2549
  const {
2280
2550
  skim = this._defaults.readAdvancedOptions.skim ?? true,
2281
2551
  includePermissions = this._defaults.readAdvancedOptions.includePermissions ?? true,
2282
2552
  tryList = this._defaults.readAdvancedOptions.tryList ?? true,
2283
2553
  populateAccess = this._defaults.readAdvancedOptions.populateAccess,
2284
- ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false,
2285
- sq
2554
+ ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false
2286
2555
  } = options ?? {};
2556
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readAdvancedOptions.sq);
2287
2557
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2288
2558
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2289
2559
  const _filter = replaceSubQuery(filter);
@@ -2359,8 +2629,9 @@ var ModelService = class extends Service {
2359
2629
  );
2360
2630
  }
2361
2631
  updateAdvanced(identifier, data, args, options, axiosRequestConfig) {
2362
- const { populate = this._defaults.updateAdvancedArgs.populate, tasks = this._defaults.updateAdvancedArgs.tasks } = args ?? {};
2363
- const select = args?.select ?? this._defaults.updateAdvancedArgs.select;
2632
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.populate);
2633
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.tasks);
2634
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.select);
2364
2635
  const {
2365
2636
  returningAll = this._defaults.updateAdvancedOptions.returningAll ?? true,
2366
2637
  includePermissions = this._defaults.updateAdvancedOptions.includePermissions ?? true,