@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.mjs CHANGED
@@ -21,6 +21,39 @@ var MissingPersistenceIdentityError = class extends Error {
21
21
  this.name = "MissingPersistenceIdentityError";
22
22
  }
23
23
  };
24
+ var MODEL_PATH_PATTERN = /[^.[\]]+|\[(?:([^"'[\]]+)|["']([^"']+)["'])\]/g;
25
+ var MODEL_UNSAFE_PATH_PARTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
26
+ function toModelPathParts(path) {
27
+ if (path.length === 0) {
28
+ return [];
29
+ }
30
+ const result = [];
31
+ path.replace(MODEL_PATH_PATTERN, (_match, bare, quoted) => {
32
+ const token = bare ?? quoted ?? _match;
33
+ result.push(/^\d+$/.test(token) ? Number(token) : token);
34
+ return "";
35
+ });
36
+ return result.length > 0 ? result : [path];
37
+ }
38
+ function modelPathRoot(path) {
39
+ const parts = toModelPathParts(path);
40
+ if (parts.length === 0) {
41
+ return "";
42
+ }
43
+ return String(parts[0]);
44
+ }
45
+ function assertSupportedModelPath(path) {
46
+ const parts = toModelPathParts(path);
47
+ if (parts.length === 0) {
48
+ throw new Error(`Model path must not be empty.`);
49
+ }
50
+ for (const part of parts) {
51
+ if (typeof part === "string" && MODEL_UNSAFE_PATH_PARTS.has(part)) {
52
+ throw new Error(`Model path "${path}" contains a reserved segment "${part}".`);
53
+ }
54
+ }
55
+ return String(parts[0]);
56
+ }
24
57
  var Model = class _Model {
25
58
  constructor(data, adapter, persistenceId, fromExisting) {
26
59
  this.modifiedPaths = /* @__PURE__ */ new Set();
@@ -93,12 +126,6 @@ var Model = class _Model {
93
126
  return queuedSave;
94
127
  }
95
128
  async saveNow(reqConfig) {
96
- const submittedPaths = new Set(this.modifiedPaths);
97
- const submittedValues = {};
98
- for (const path of submittedPaths) {
99
- submittedValues[path] = cloneDeep(getValue(this._data, path));
100
- }
101
- const submittedData = this.prepareData();
102
129
  const persistenceId = this._data._id ?? this._persistenceId;
103
130
  if (persistenceId == null && this._fromExisting) {
104
131
  throw new MissingPersistenceIdentityError(
@@ -106,17 +133,26 @@ var Model = class _Model {
106
133
  );
107
134
  }
108
135
  const isCreate = persistenceId == null;
136
+ const submittedPaths = new Set(this.modifiedPaths);
137
+ const submittedValues = {};
138
+ for (const path of submittedPaths) {
139
+ submittedValues[path] = cloneDeep(getValue(this._data, path));
140
+ }
141
+ const submittedData = this.prepareData(isCreate);
109
142
  const result = isCreate ? await this._service.create(submittedData, void 0, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
110
143
  if (!result.success) {
111
144
  return { ...result, data: null };
112
145
  }
146
+ const preMergeDirty = new Set(this.modifiedPaths);
147
+ const preMergeValues = {};
148
+ for (const path of submittedPaths) {
149
+ preMergeValues[path] = cloneDeep(getValue(this._data, path));
150
+ }
113
151
  const isConcurrentEdit = (path) => {
114
152
  if (!submittedPaths.has(path)) {
115
- return this.modifiedPaths.has(path);
153
+ return preMergeDirty.has(path);
116
154
  }
117
- const current = getValue(this._data, path);
118
- const submitted = submittedValues[path];
119
- return !isEqual(current, submitted);
155
+ return !isEqual(preMergeValues[path], submittedValues[path]);
120
156
  };
121
157
  const serverData = result.raw ?? {};
122
158
  for (const key of Object.keys(serverData)) {
@@ -135,6 +171,8 @@ var Model = class _Model {
135
171
  for (const path of submittedPaths) {
136
172
  if (!isConcurrentEdit(path)) {
137
173
  this.modifiedPaths.delete(path);
174
+ } else {
175
+ this.modifiedPaths.add(path);
138
176
  }
139
177
  }
140
178
  if (isCreate && serverData._id != null) {
@@ -161,14 +199,13 @@ var Model = class _Model {
161
199
  }
162
200
  this._snapshot = nextSnapshot;
163
201
  this.definePublicDataProps();
202
+ const returned = _Model.create(this._data, this._service, this._persistenceId, true);
203
+ returned._snapshot = cloneDeep(this._snapshot);
204
+ returned.modifiedPaths = new Set(this.modifiedPaths);
205
+ returned.definePublicDataProps();
164
206
  return {
165
207
  ...result,
166
- // The post-save snapshot is always an existing document, so propagate
167
- // `_fromExisting=true` plus the refreshed persistence identity so the
168
- // returned wrapper cannot later silently create a duplicate. (If the
169
- // caller intends a fresh draft, they construct `new Model({...}, s)`
170
- // directly — `${_fromExisting}` defaults to `false` there.)
171
- data: _Model.create(this._data, this._service, this._persistenceId, true)
208
+ data: returned
172
209
  };
173
210
  }
174
211
  isDirty(path) {
@@ -188,6 +225,7 @@ var Model = class _Model {
188
225
  * run `reconcilePath` after the write.
189
226
  */
190
227
  markModified(path) {
228
+ assertSupportedModelPath(String(path));
191
229
  this.trackModified(String(path));
192
230
  return this;
193
231
  }
@@ -195,6 +233,7 @@ var Model = class _Model {
195
233
  return getValue(this._data, path);
196
234
  }
197
235
  set(path, value) {
236
+ const root = assertSupportedModelPath(path);
198
237
  const currentValue = getValue(this._data, path);
199
238
  if (Object.is(currentValue, value)) {
200
239
  return this;
@@ -202,7 +241,7 @@ var Model = class _Model {
202
241
  setValue(this._data, path, value);
203
242
  this.trackModified(path);
204
243
  this.definePublicDataProps();
205
- this.reconcilePath(this.normalizePath(path));
244
+ this.reconcilePath(root);
206
245
  return this;
207
246
  }
208
247
  assign(partial) {
@@ -221,8 +260,12 @@ var Model = class _Model {
221
260
  return this;
222
261
  }
223
262
  reset() {
263
+ const wasDraft = this.isUnsavedDraft();
224
264
  this.replaceData(this._snapshot);
225
265
  this.modifiedPaths.clear();
266
+ if (wasDraft) {
267
+ this.initializeDirtyState();
268
+ }
226
269
  return this;
227
270
  }
228
271
  toObject() {
@@ -255,7 +298,10 @@ var Model = class _Model {
255
298
  }
256
299
  }
257
300
  }
258
- prepareData() {
301
+ prepareData(isCreate) {
302
+ if (isCreate) {
303
+ return cloneDeep(omit(this._data, ["_id"]));
304
+ }
259
305
  return omit(pick(this._data, Array.from(this.modifiedPaths).map(String)), ["_id"]);
260
306
  }
261
307
  defineHiddenDataProp(initialValue) {
@@ -302,21 +348,20 @@ var Model = class _Model {
302
348
  this.modifiedPaths.add(this.normalizePath(path));
303
349
  }
304
350
  normalizePath(path) {
305
- return path.split(".")[0];
351
+ return modelPathRoot(path);
306
352
  }
307
353
  /**
308
- * Removes `path` from the dirty set when its current top-level value deeply
309
- * equals the snapshot baseline. Used uniformly by `set()`, `assign()`,
310
- * public property setters (via the proxy), and `markModified()` so all
311
- * entry points share the same tracking rule.
312
- *
313
- * Note: `_id` is intentionally never reconciled away here — it is excluded
314
- * from `initializeDirtyState` and managed explicitly during `save()`
315
- * reconciliation.
354
+ * Removes `path` from the dirty set when its current value deeply equals
355
+ * the snapshot baseline. Invariant: unsaved drafts never reconcile clean
356
+ * (snapshot is unpersisted); `_id` is never reconciled here.
316
357
  */
358
+ isUnsavedDraft() {
359
+ return !this._fromExisting && (this._data._id ?? this._persistenceId) == null;
360
+ }
317
361
  reconcilePath(path) {
318
362
  if (path === "_id") return;
319
363
  if (!this.modifiedPaths.has(path)) return;
364
+ if (this.isUnsavedDraft()) return;
320
365
  const current = this._data[path];
321
366
  const base = this._snapshot[path];
322
367
  if (isEqual(current, base)) {
@@ -371,7 +416,7 @@ function getWrapContext(url, options, config) {
371
416
  }
372
417
 
373
418
  // src/services/interceptors.ts
374
- import axios, { AxiosHeaders as AxiosHeaders2 } from "axios";
419
+ import axios, { AxiosError, AxiosHeaders as AxiosHeaders2 } from "axios";
375
420
 
376
421
  // src/services/cache-utils.ts
377
422
  import { AxiosHeaders } from "axios";
@@ -393,6 +438,10 @@ var UnsupportedGroupedRequestConfigError = class extends Error {
393
438
  this.name = "UnsupportedGroupedRequestConfigError";
394
439
  }
395
440
  };
441
+ var isPlainObjectRecord = (value) => {
442
+ const prototype = Object.getPrototypeOf(value);
443
+ return prototype === Object.prototype || prototype === null;
444
+ };
396
445
  var normalizeConfigValue = (value) => {
397
446
  if (value == null) return value;
398
447
  if (value instanceof AxiosHeaders) {
@@ -423,11 +472,52 @@ var normalizeGroupedRequestConfig = (config) => {
423
472
  `Grouped requests do not support symbol-valued axios config at ${path}`
424
473
  );
425
474
  }
475
+ if (typeof value === "bigint") {
476
+ throw new UnsupportedGroupedRequestConfigError(
477
+ `Grouped requests do not support bigint-valued axios config at ${path}`
478
+ );
479
+ }
480
+ if (typeof value === "number") {
481
+ if (!Number.isFinite(value)) {
482
+ throw new UnsupportedGroupedRequestConfigError(
483
+ `Grouped requests do not support non-finite numeric axios config at ${path}`
484
+ );
485
+ }
486
+ return value;
487
+ }
488
+ if (typeof value === "string" || typeof value === "boolean") {
489
+ return value;
490
+ }
426
491
  if (value instanceof AxiosHeaders) {
427
492
  return normalize(value.toJSON(), path);
428
493
  }
494
+ if (value instanceof Date) {
495
+ const time = value.getTime();
496
+ if (Number.isNaN(time)) {
497
+ throw new UnsupportedGroupedRequestConfigError(
498
+ `Grouped requests do not support invalid Date axios config at ${path}`
499
+ );
500
+ }
501
+ return { __type: "Date", iso: value.toISOString() };
502
+ }
503
+ if (value instanceof URLSearchParams) {
504
+ const entries = Array.from(value.entries()).sort(
505
+ ([leftKey, leftValue], [rightKey, rightValue]) => leftKey === rightKey ? leftValue < rightValue ? -1 : leftValue > rightValue ? 1 : 0 : leftKey < rightKey ? -1 : 1
506
+ );
507
+ return { __type: "URLSearchParams", entries };
508
+ }
429
509
  if (Array.isArray(value)) {
430
- return value.map((item, index) => normalize(item, `${path}[${index}]`));
510
+ if (seen.has(value)) {
511
+ throw new UnsupportedGroupedRequestConfigError(
512
+ `Grouped requests do not support circular axios config at ${path}`
513
+ );
514
+ }
515
+ seen.add(value);
516
+ try {
517
+ return value.map((item, index) => normalize(item, `${path}[${index}]`));
518
+ } finally {
519
+ seen.delete(value);
520
+ }
431
521
  }
432
522
  if (typeof value === "object") {
433
523
  if (seen.has(value)) {
@@ -435,19 +525,27 @@ var normalizeGroupedRequestConfig = (config) => {
435
525
  `Grouped requests do not support circular axios config at ${path}`
436
526
  );
437
527
  }
528
+ if (!isPlainObjectRecord(value)) {
529
+ throw new UnsupportedGroupedRequestConfigError(
530
+ `Grouped requests do not support non-plain object axios config at ${path}`
531
+ );
532
+ }
438
533
  seen.add(value);
439
- const normalized = Object.entries(omitBy(value, (item) => item === void 0)).sort(([left], [right]) => left.localeCompare(right)).reduce((acc, [key, item]) => {
440
- const itemPath = path === "config" ? key : `${path}.${key}`;
441
- if (unsupportedGroupConfigKeys.has(key)) {
442
- throw new UnsupportedGroupedRequestConfigError(
443
- `Grouped requests do not support axios config key ${itemPath}`
444
- );
445
- }
446
- acc[key] = normalize(item, itemPath);
447
- return acc;
448
- }, {});
449
- seen.delete(value);
450
- return normalized;
534
+ try {
535
+ const normalized = Object.entries(omitBy(value, (item) => item === void 0)).sort(([left], [right]) => left.localeCompare(right)).reduce((acc, [key, item]) => {
536
+ const itemPath = path === "config" ? key : `${path}.${key}`;
537
+ if (path === "config" && unsupportedGroupConfigKeys.has(key)) {
538
+ throw new UnsupportedGroupedRequestConfigError(
539
+ `Grouped requests do not support axios config key ${itemPath}`
540
+ );
541
+ }
542
+ acc[key] = normalize(item, itemPath);
543
+ return acc;
544
+ }, {});
545
+ return normalized;
546
+ } finally {
547
+ seen.delete(value);
548
+ }
451
549
  }
452
550
  return value;
453
551
  };
@@ -460,13 +558,6 @@ var CACHEABLE_METHODS = /* @__PURE__ */ new Set(["get"]);
460
558
  var CACHEABLE_RESPONSE_TYPES = /* @__PURE__ */ new Set(["", "json", "text"]);
461
559
  var CACHE_INVALIDATE_ON_SUCCESS = "__accessRouterClientCacheInvalidateOnSuccess";
462
560
  var CACHE_INVALIDATE_HEADER = "x-axios-cache-invalidate-on-success";
463
- var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
464
- "authorization",
465
- "cookie",
466
- "set-cookie",
467
- "proxy-authorization",
468
- "www-authenticate"
469
- ]);
470
561
  var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
471
562
  "authorization",
472
563
  "cookie",
@@ -475,6 +566,7 @@ var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
475
566
  "x-auth-token",
476
567
  "x-access-token"
477
568
  ]);
569
+ var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([...AUTHENTICATION_REQUEST_HEADERS, "set-cookie", "www-authenticate"]);
478
570
  var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
479
571
  const baseConfig = config ?? {};
480
572
  const next = { ...baseConfig };
@@ -486,16 +578,46 @@ var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
486
578
  } else {
487
579
  next.headers = {};
488
580
  }
581
+ for (const key of Object.keys(next.headers)) {
582
+ if (key.toLowerCase() === CACHE_HEADER.toLowerCase()) {
583
+ delete next.headers[key];
584
+ }
585
+ }
489
586
  next.headers[CACHE_HEADER] = "false";
490
587
  if (invalidateOnSuccess) {
491
588
  next[CACHE_INVALIDATE_ON_SUCCESS] = true;
492
- next.headers[CACHE_INVALIDATE_HEADER] = "true";
493
589
  }
494
590
  return next;
495
591
  };
592
+ var findHeaderKey = (headers, name) => {
593
+ const target = name.toLowerCase();
594
+ return Object.keys(headers).find((key) => key.toLowerCase() === target);
595
+ };
596
+ var getCacheControlValue = (headers) => {
597
+ if (!headers || typeof headers !== "object") return void 0;
598
+ if (headers instanceof AxiosHeaders2) return headers.get(CACHE_HEADER);
599
+ const key = findHeaderKey(headers, CACHE_HEADER);
600
+ return key === void 0 ? void 0 : headers[key];
601
+ };
602
+ var hasCacheControlHeader = (headers) => getCacheControlValue(headers) !== void 0;
603
+ var hasInvalidateSignal = (headers) => {
604
+ if (!headers || typeof headers !== "object") return false;
605
+ if (headers instanceof AxiosHeaders2) return headers.has(CACHE_INVALIDATE_HEADER);
606
+ return findHeaderKey(headers, CACHE_INVALIDATE_HEADER) !== void 0;
607
+ };
608
+ var deleteInvalidateSignal = (headers) => {
609
+ if (headers instanceof AxiosHeaders2) {
610
+ headers.delete(CACHE_INVALIDATE_HEADER);
611
+ return;
612
+ }
613
+ const key = findHeaderKey(headers, CACHE_INVALIDATE_HEADER);
614
+ if (key !== void 0) {
615
+ delete headers[key];
616
+ }
617
+ };
496
618
  var removeCacheInvalidationSignal = (config) => {
497
619
  const headers = config.headers;
498
- const hasHeaderSignal = headers instanceof AxiosHeaders2 ? headers.has(CACHE_INVALIDATE_HEADER) : Boolean(headers && typeof headers === "object" && CACHE_INVALIDATE_HEADER in headers);
620
+ const hasHeaderSignal = hasInvalidateSignal(headers);
499
621
  if (CACHE_INVALIDATE_ON_SUCCESS in config || hasHeaderSignal) {
500
622
  const next = { ...config };
501
623
  delete next[CACHE_INVALIDATE_ON_SUCCESS];
@@ -505,7 +627,7 @@ var removeCacheInvalidationSignal = (config) => {
505
627
  next.headers = clonedHeaders;
506
628
  } else if (headers && typeof headers === "object") {
507
629
  next.headers = { ...headers };
508
- delete next.headers[CACHE_INVALIDATE_HEADER];
630
+ deleteInvalidateSignal(next.headers);
509
631
  }
510
632
  return next;
511
633
  }
@@ -615,11 +737,42 @@ var snapshotResponse = (response, clone) => {
615
737
  headers: clone(headers)
616
738
  };
617
739
  };
740
+ var identityTransform = (data) => data;
741
+ var bypassResponseTransform = (config) => {
742
+ config.transformResponse = [identityTransform];
743
+ };
744
+ var settleSyntheticResponse = (callerConfig, response) => {
745
+ const status = response.status;
746
+ const validateStatus = callerConfig.validateStatus;
747
+ if (!status || !validateStatus || validateStatus(status)) {
748
+ return response;
749
+ }
750
+ throw new AxiosError(
751
+ `Request failed with status code ${status}`,
752
+ status >= 400 && status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE,
753
+ callerConfig,
754
+ void 0,
755
+ response
756
+ );
757
+ };
758
+ var isSettlementRejectionFor = (error, sourceConfig) => {
759
+ const response = error?.response;
760
+ if (!response || !response.status) return false;
761
+ const validateStatus = sourceConfig.validateStatus;
762
+ if (!validateStatus) {
763
+ return false;
764
+ }
765
+ try {
766
+ return !validateStatus(response.status);
767
+ } catch {
768
+ return false;
769
+ }
770
+ };
618
771
  var serializeHeaders = (headers) => {
619
772
  const resolvedHeaders = headers instanceof AxiosHeaders2 ? headers.toJSON() : headers;
620
773
  const normalizedHeaders = Object.entries(resolvedHeaders ?? {}).filter(([key, value]) => {
621
774
  const normalizedKey = key.toLowerCase();
622
- return normalizedKey !== CACHE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
775
+ return normalizedKey !== CACHE_HEADER.toLowerCase() && normalizedKey !== CACHE_INVALIDATE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
623
776
  }).reduce((acc, [key, value]) => {
624
777
  acc[key.toLowerCase()] = value;
625
778
  return acc;
@@ -634,13 +787,13 @@ var hasHeaderValue = (value) => {
634
787
  };
635
788
  var consumeCacheInvalidationSignal = (config) => {
636
789
  const headers = config.headers;
637
- const hasSignal = headers instanceof AxiosHeaders2 ? headers.has(CACHE_INVALIDATE_HEADER) : Boolean(headers && typeof headers === "object" && CACHE_INVALIDATE_HEADER in headers);
790
+ const hasSignal = hasInvalidateSignal(headers);
638
791
  if (!hasSignal && !config[CACHE_INVALIDATE_ON_SUCCESS]) return;
639
792
  config[CACHE_INVALIDATE_ON_SUCCESS] = true;
640
793
  if (headers instanceof AxiosHeaders2) {
641
794
  headers.delete(CACHE_INVALIDATE_HEADER);
642
795
  } else if (headers && typeof headers === "object") {
643
- delete headers[CACHE_INVALIDATE_HEADER];
796
+ deleteInvalidateSignal(headers);
644
797
  }
645
798
  };
646
799
  var hasAuthenticationHeader = (headers) => {
@@ -675,10 +828,12 @@ var sameConfigIdentity = (configured, defaultValue) => {
675
828
  }
676
829
  return configured === defaultValue;
677
830
  };
831
+ var PRISTINE_TRANSFORM_REQUEST = axios.defaults.transformRequest;
832
+ var PRISTINE_TRANSFORM_RESPONSE = axios.defaults.transformResponse;
678
833
  var isCacheEligible = (config, instance) => {
679
834
  const method = (config.method ?? "get").toLowerCase();
680
835
  const responseType = config.responseType ?? "";
681
- 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);
836
+ 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);
682
837
  };
683
838
  function generateCacheKey(config, partition) {
684
839
  const responseSemantics = JSON.stringify({
@@ -717,12 +872,14 @@ function useCacheInterceptors(instance, policyOrTtl) {
717
872
  const store = new SimpleCache({ capacity: policy.capacity, clone: policy.clone });
718
873
  const withCredentialsDefault = policy.withCredentialsDefault ?? Boolean(instance.defaults.withCredentials);
719
874
  const inflight = /* @__PURE__ */ new Map();
875
+ const activeSlots = /* @__PURE__ */ new Set();
720
876
  let generation = 0;
721
877
  let disposed = false;
722
878
  const finalizeInflight = (slot) => {
723
879
  if (inflight.get(slot.key) === slot) {
724
880
  inflight.delete(slot.key);
725
881
  }
882
+ activeSlots.delete(slot);
726
883
  };
727
884
  const resolveInflight = (slot, response) => {
728
885
  if (slot.settled) return;
@@ -744,7 +901,8 @@ function useCacheInterceptors(instance, policyOrTtl) {
744
901
  instance.interceptors.request.use(
745
902
  async (config) => {
746
903
  consumeCacheInvalidationSignal(config);
747
- if (disposed || config.headers[CACHE_HEADER] === "false" || !isCacheEligible(config, instance)) return config;
904
+ if (disposed || getCacheControlValue(config.headers) === "false" || !isCacheEligible(config, instance))
905
+ return config;
748
906
  const isCredentialed = resolveWithCredentials(config, withCredentialsDefault) || hasAuthenticationHeader(config.headers);
749
907
  const partitionKey = policy.partitionForRequest?.(config);
750
908
  if (isCredentialed && !hasUsablePartition(partitionKey)) {
@@ -755,29 +913,33 @@ function useCacheInterceptors(instance, policyOrTtl) {
755
913
  const snapshot = store.get(key);
756
914
  if (snapshot) {
757
915
  setCacheRequestState(config, Object.freeze({ key, generation, role: "hit" }));
916
+ bypassResponseTransform(config);
758
917
  config.adapter = async (_config) => {
759
- return {
918
+ const response = {
760
919
  data: snapshot.data,
761
920
  status: snapshot.status,
762
921
  statusText: snapshot.statusText,
763
922
  headers: { ...snapshot.headers, [CACHE_HEADER]: "true" },
764
923
  config: _config
765
924
  };
925
+ return settleSyntheticResponse(_config, response);
766
926
  };
767
927
  return config;
768
928
  }
769
929
  const existing = inflight.get(key);
770
930
  if (existing) {
931
+ bypassResponseTransform(config);
771
932
  config.adapter = async (_config) => {
772
933
  const response = await existing.promise;
773
934
  const shared = response;
774
- return {
935
+ const tailResponse = {
775
936
  data: clone(shared.data),
776
937
  status: shared.status,
777
938
  statusText: shared.statusText,
778
939
  headers: { ...shared.headers, [CACHE_HEADER]: "true" },
779
940
  config: _config
780
941
  };
942
+ return settleSyntheticResponse(_config, tailResponse);
781
943
  };
782
944
  setCacheRequestState(config, Object.freeze({ key, generation, role: "tail", slot: existing }));
783
945
  return config;
@@ -799,8 +961,33 @@ function useCacheInterceptors(instance, policyOrTtl) {
799
961
  settled: false
800
962
  };
801
963
  inflight.set(key, slot);
964
+ activeSlots.add(slot);
802
965
  const nextConfig = { ...config };
803
966
  setCacheRequestState(nextConfig, Object.freeze({ key, generation, role: "source", slot }));
967
+ const settleTransformFailure = (error) => {
968
+ rejectInflight(slot, error);
969
+ throw error;
970
+ };
971
+ const wrapTransformList = (value) => {
972
+ const list = Array.isArray(value) ? value : [value];
973
+ return list.map((fn) => {
974
+ if (typeof fn !== "function") return fn;
975
+ const original = fn;
976
+ return function(...args) {
977
+ try {
978
+ const result = original.apply(this, args);
979
+ if (result && typeof result.catch === "function") {
980
+ return result.catch((error) => settleTransformFailure(error));
981
+ }
982
+ return result;
983
+ } catch (error) {
984
+ return settleTransformFailure(error);
985
+ }
986
+ };
987
+ });
988
+ };
989
+ nextConfig.transformRequest = wrapTransformList(nextConfig.transformRequest);
990
+ nextConfig.transformResponse = wrapTransformList(nextConfig.transformResponse);
804
991
  let realAdapter = config.adapter;
805
992
  if (realAdapter === void 0 || realAdapter === null) {
806
993
  realAdapter = instance.defaults.adapter;
@@ -818,7 +1005,12 @@ function useCacheInterceptors(instance, policyOrTtl) {
818
1005
  const response = await dispatch(adapterConfig);
819
1006
  return response;
820
1007
  } catch (error) {
821
- rejectInflight(slot, error);
1008
+ const errResponse = error?.response;
1009
+ if (errResponse && isSettlementRejectionFor(error, adapterConfig)) {
1010
+ resolveInflight(slot, errResponse);
1011
+ } else {
1012
+ rejectInflight(slot, error);
1013
+ }
822
1014
  throw error;
823
1015
  }
824
1016
  };
@@ -854,7 +1046,13 @@ function useCacheInterceptors(instance, policyOrTtl) {
854
1046
  (error) => {
855
1047
  const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
856
1048
  if (state?.role === "source" && state.slot) {
857
- rejectInflight(state.slot, error);
1049
+ const errResponse = error?.response;
1050
+ const sourceConfig = error?.config ?? {};
1051
+ if (errResponse && isSettlementRejectionFor(error, sourceConfig)) {
1052
+ resolveInflight(state.slot, errResponse);
1053
+ } else {
1054
+ rejectInflight(state.slot, error);
1055
+ }
858
1056
  }
859
1057
  return Promise.reject(error);
860
1058
  }
@@ -867,10 +1065,11 @@ function useCacheInterceptors(instance, policyOrTtl) {
867
1065
  generation += 1;
868
1066
  store.dispose();
869
1067
  const error = new Error(CACHE_DISPOSED_ERROR);
870
- for (const slot of inflight.values()) {
1068
+ for (const slot of [...activeSlots]) {
871
1069
  rejectInflight(slot, error);
872
1070
  }
873
1071
  inflight.clear();
1072
+ activeSlots.clear();
874
1073
  }
875
1074
  };
876
1075
  }
@@ -1114,7 +1313,7 @@ var Service = class {
1114
1313
  cloned.set(CACHE_HEADER, cacheValue);
1115
1314
  return cloned;
1116
1315
  }
1117
- if (CACHE_HEADER in headers) return headers;
1316
+ if (hasCacheControlHeader(headers)) return headers;
1118
1317
  return {
1119
1318
  ...headers,
1120
1319
  [CACHE_HEADER]: cacheValue
@@ -1271,26 +1470,86 @@ var isLegacyListPayload = (value) => {
1271
1470
  }
1272
1471
  return "count" in value && typeof value.count === "number" && "rows" in value && Array.isArray(value.rows);
1273
1472
  };
1274
- var cloneDefaultValue = (value) => {
1473
+ var UnsupportedServiceDefaultValueError = class extends Error {
1474
+ constructor(message) {
1475
+ super(message);
1476
+ this.name = "UnsupportedServiceDefaultValueError";
1477
+ }
1478
+ };
1479
+ var isPlainDefaultObject = (value) => {
1480
+ const prototype = Object.getPrototypeOf(value);
1481
+ return prototype === Object.prototype || prototype === null;
1482
+ };
1483
+ var cloneDefaultValueInner = (value, seen, path) => {
1484
+ if (value == null) return value;
1485
+ if (typeof value === "string" || typeof value === "boolean") return value;
1486
+ if (typeof value === "number") {
1487
+ if (!Number.isFinite(value)) {
1488
+ throw new UnsupportedServiceDefaultValueError(
1489
+ `Service defaults do not support non-finite numeric value at ${path}`
1490
+ );
1491
+ }
1492
+ return value;
1493
+ }
1494
+ if (typeof value === "function") {
1495
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support function value at ${path}`);
1496
+ }
1497
+ if (typeof value === "symbol") {
1498
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support symbol value at ${path}`);
1499
+ }
1500
+ if (typeof value === "bigint") {
1501
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support bigint value at ${path}`);
1502
+ }
1503
+ if (value instanceof Date) {
1504
+ const time = value.getTime();
1505
+ if (Number.isNaN(time)) {
1506
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support invalid Date at ${path}`);
1507
+ }
1508
+ return new Date(time);
1509
+ }
1275
1510
  if (Array.isArray(value)) {
1276
- return value.map((item) => cloneDefaultValue(item));
1511
+ if (seen.has(value)) {
1512
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support circular value at ${path}`);
1513
+ }
1514
+ seen.add(value);
1515
+ try {
1516
+ return value.map((item, index) => cloneDefaultValueInner(item, seen, `${path}[${index}]`));
1517
+ } finally {
1518
+ seen.delete(value);
1519
+ }
1277
1520
  }
1278
- if (value && typeof value === "object") {
1279
- const cloned = {};
1280
- for (const [key, item] of Object.entries(value)) {
1281
- cloned[key] = cloneDefaultValue(item);
1521
+ if (typeof value === "object") {
1522
+ if (seen.has(value)) {
1523
+ throw new UnsupportedServiceDefaultValueError(`Service defaults do not support circular value at ${path}`);
1524
+ }
1525
+ if (!isPlainDefaultObject(value)) {
1526
+ throw new UnsupportedServiceDefaultValueError(
1527
+ `Service defaults do not support non-plain object instance at ${path}`
1528
+ );
1529
+ }
1530
+ seen.add(value);
1531
+ try {
1532
+ const cloned = {};
1533
+ for (const [key, item] of Object.entries(value)) {
1534
+ cloned[key] = cloneDefaultValueInner(item, seen, path === "defaults" ? `defaults.${key}` : `${path}.${key}`);
1535
+ }
1536
+ return cloned;
1537
+ } finally {
1538
+ seen.delete(value);
1282
1539
  }
1283
- return cloned;
1284
1540
  }
1285
1541
  return value;
1286
1542
  };
1287
- var deepFreeze = (value) => {
1288
- if (!value || typeof value !== "object" || Object.isFrozen(value)) {
1543
+ var cloneDefaultValue = (value) => cloneDefaultValueInner(value, /* @__PURE__ */ new WeakSet(), "defaults");
1544
+ var cloneServiceDefaultValue = (value) => cloneDefaultValueInner(value, /* @__PURE__ */ new WeakSet(), "defaults");
1545
+ var deepFreeze = (value, seen = /* @__PURE__ */ new WeakSet()) => {
1546
+ if (!value || typeof value !== "object" || Object.isFrozen(value) || seen.has(value)) {
1289
1547
  return value;
1290
1548
  }
1549
+ seen.add(value);
1291
1550
  Object.freeze(value);
1292
1551
  for (const item of Object.values(value)) {
1293
- deepFreeze(item);
1552
+ deepFreeze(item, seen);
1294
1553
  }
1295
1554
  return value;
1296
1555
  };
@@ -1730,9 +1989,9 @@ var ModelService = class extends Service {
1730
1989
  includePermissions = this._defaults.listOptions.includePermissions ?? false,
1731
1990
  includeCount = this._defaults.listOptions.includeCount ?? false,
1732
1991
  includeExtraHeaders = this._defaults.listOptions.includeExtraHeaders ?? false,
1733
- ignoreCache = this._defaults.listOptions.ignoreCache ?? false,
1734
- sq
1992
+ ignoreCache = this._defaults.listOptions.ignoreCache ?? false
1735
1993
  } = options ?? {};
1994
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.listOptions.sq);
1736
1995
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1737
1996
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
1738
1997
  return makeRequest(
@@ -1781,25 +2040,25 @@ var ModelService = class extends Service {
1781
2040
  }
1782
2041
  listAdvanced(filter, args, options, axiosRequestConfig) {
1783
2042
  const {
1784
- populate = this._defaults.listAdvancedArgs.populate,
1785
- include = this._defaults.listAdvancedArgs.include,
1786
- sort = this._defaults.listAdvancedArgs.sort,
1787
2043
  skip = this._defaults.listAdvancedArgs.skip,
1788
2044
  limit = this._defaults.listAdvancedArgs.limit,
1789
2045
  page = this._defaults.listAdvancedArgs.page,
1790
- pageSize = this._defaults.listAdvancedArgs.pageSize,
1791
- tasks = this._defaults.listAdvancedArgs.tasks
2046
+ pageSize = this._defaults.listAdvancedArgs.pageSize
1792
2047
  } = args ?? {};
1793
- const select = args?.select ?? this._defaults.listAdvancedArgs.select;
2048
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.populate);
2049
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.include);
2050
+ const sort = args?.sort ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.sort);
2051
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.tasks);
2052
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.listAdvancedArgs.select);
1794
2053
  const {
1795
2054
  skim = this._defaults.listAdvancedOptions.skim ?? true,
1796
2055
  includePermissions = this._defaults.listAdvancedOptions.includePermissions ?? false,
1797
2056
  includeCount = this._defaults.listAdvancedOptions.includeCount ?? false,
1798
2057
  includeExtraHeaders = this._defaults.listAdvancedOptions.includeExtraHeaders ?? false,
1799
2058
  populateAccess = this._defaults.listAdvancedOptions.populateAccess,
1800
- ignoreCache = this._defaults.listAdvancedOptions.ignoreCache ?? false,
1801
- sq
2059
+ ignoreCache = this._defaults.listAdvancedOptions.ignoreCache ?? false
1802
2060
  } = options ?? {};
2061
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.listAdvancedOptions.sq);
1803
2062
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1804
2063
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
1805
2064
  const _filter = replaceSubQuery(filter);
@@ -1888,8 +2147,9 @@ var ModelService = class extends Service {
1888
2147
  );
1889
2148
  }
1890
2149
  createAdvanced(data, args, options, axiosRequestConfig) {
1891
- const { populate = this._defaults.createAdvancedArgs.populate, tasks = this._defaults.createAdvancedArgs.tasks } = args ?? {};
1892
- const select = args?.select ?? this._defaults.createAdvancedArgs.select;
2150
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.populate);
2151
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.tasks);
2152
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.createAdvancedArgs.select);
1893
2153
  const {
1894
2154
  includePermissions = this._defaults.createAdvancedOptions.includePermissions ?? true,
1895
2155
  populateAccess = this._defaults.createAdvancedOptions.populateAccess
@@ -1964,8 +2224,9 @@ var ModelService = class extends Service {
1964
2224
  );
1965
2225
  }
1966
2226
  upsertAdvanced(data, args, options, axiosRequestConfig) {
1967
- const { populate = this._defaults.upsertAdvancedArgs.populate, tasks = this._defaults.upsertAdvancedArgs.tasks } = args ?? {};
1968
- const select = args?.select ?? this._defaults.upsertAdvancedArgs.select;
2227
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.populate);
2228
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.tasks);
2229
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.upsertAdvancedArgs.select);
1969
2230
  const {
1970
2231
  returningAll = this._defaults.upsertAdvancedOptions.returningAll ?? true,
1971
2232
  includePermissions = this._defaults.upsertAdvancedOptions.includePermissions ?? true,
@@ -2052,6 +2313,14 @@ var ModelService = class extends Service {
2052
2313
  }
2053
2314
  );
2054
2315
  }
2316
+ /**
2317
+ * BND-11: distinct values are `unknown[]`, not `string[]`. The sibling
2318
+ * server returns raw distinct values without string conversion, so numeric
2319
+ * and boolean values arrive as-is. Source-compat: callers that assumed
2320
+ * `string[]` must narrow first (e.g. `typeof v === 'string'` or a type
2321
+ * guard) before calling string methods; see the BND-11 task record for
2322
+ * migration. No server values are stringified to satisfy the old type.
2323
+ */
2055
2324
  distinct(field, axiosRequestConfig) {
2056
2325
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2057
2326
  return makeRequest(
@@ -2074,6 +2343,11 @@ var ModelService = class extends Service {
2074
2343
  }
2075
2344
  );
2076
2345
  }
2346
+ /**
2347
+ * BND-11: filtered distinct variant. Same `unknown[]` contract as
2348
+ * {@link distinct}: narrow elements before assuming strings. No
2349
+ * stringification is applied; dynamic field names are accepted as `string`.
2350
+ */
2077
2351
  distinctAdvanced(field, conditions, axiosRequestConfig) {
2078
2352
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2079
2353
  return makeRequest(
@@ -2147,9 +2421,9 @@ var ModelService = class extends Service {
2147
2421
  const {
2148
2422
  includePermissions = this._defaults.readOptions.includePermissions ?? true,
2149
2423
  tryList = this._defaults.readOptions.tryList ?? true,
2150
- ignoreCache = this._defaults.readOptions.ignoreCache ?? false,
2151
- sq
2424
+ ignoreCache = this._defaults.readOptions.ignoreCache ?? false
2152
2425
  } = options ?? {};
2426
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readOptions.sq);
2153
2427
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2154
2428
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2155
2429
  return makeRequest(
@@ -2181,20 +2455,18 @@ var ModelService = class extends Service {
2181
2455
  );
2182
2456
  }
2183
2457
  readAdvanced(identifier, args, options, axiosRequestConfig) {
2184
- const {
2185
- populate = this._defaults.readAdvancedArgs.populate,
2186
- include = this._defaults.readAdvancedArgs.include,
2187
- tasks = this._defaults.readAdvancedArgs.tasks
2188
- } = args ?? {};
2189
- const select = args?.select ?? this._defaults.readAdvancedArgs.select;
2458
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.populate);
2459
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.include);
2460
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.tasks);
2461
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.select);
2190
2462
  const {
2191
2463
  skim = this._defaults.readAdvancedOptions.skim ?? true,
2192
2464
  includePermissions = this._defaults.readAdvancedOptions.includePermissions ?? true,
2193
2465
  tryList = this._defaults.readAdvancedOptions.tryList ?? true,
2194
2466
  populateAccess = this._defaults.readAdvancedOptions.populateAccess,
2195
- ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false,
2196
- sq
2467
+ ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false
2197
2468
  } = options ?? {};
2469
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readAdvancedOptions.sq);
2198
2470
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2199
2471
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2200
2472
  return makeRequest(
@@ -2233,21 +2505,19 @@ var ModelService = class extends Service {
2233
2505
  );
2234
2506
  }
2235
2507
  readAdvancedFilter(filter, args, options, axiosRequestConfig) {
2236
- const {
2237
- sort = this._defaults.readAdvancedArgs.sort,
2238
- populate = this._defaults.readAdvancedArgs.populate,
2239
- include = this._defaults.readAdvancedArgs.include,
2240
- tasks = this._defaults.readAdvancedArgs.tasks
2241
- } = args ?? {};
2242
- const select = args?.select ?? this._defaults.readAdvancedArgs.select;
2508
+ const sort = args?.sort ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.sort);
2509
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.populate);
2510
+ const include = args?.include ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.include);
2511
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.tasks);
2512
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.readAdvancedArgs.select);
2243
2513
  const {
2244
2514
  skim = this._defaults.readAdvancedOptions.skim ?? true,
2245
2515
  includePermissions = this._defaults.readAdvancedOptions.includePermissions ?? true,
2246
2516
  tryList = this._defaults.readAdvancedOptions.tryList ?? true,
2247
2517
  populateAccess = this._defaults.readAdvancedOptions.populateAccess,
2248
- ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false,
2249
- sq
2518
+ ignoreCache = this._defaults.readAdvancedOptions.ignoreCache ?? false
2250
2519
  } = options ?? {};
2520
+ const sq = options?.sq ?? cloneServiceDefaultValue(this._defaults.readAdvancedOptions.sq);
2251
2521
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2252
2522
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2253
2523
  const _filter = replaceSubQuery(filter);
@@ -2323,8 +2593,9 @@ var ModelService = class extends Service {
2323
2593
  );
2324
2594
  }
2325
2595
  updateAdvanced(identifier, data, args, options, axiosRequestConfig) {
2326
- const { populate = this._defaults.updateAdvancedArgs.populate, tasks = this._defaults.updateAdvancedArgs.tasks } = args ?? {};
2327
- const select = args?.select ?? this._defaults.updateAdvancedArgs.select;
2596
+ const populate = args?.populate ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.populate);
2597
+ const tasks = args?.tasks ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.tasks);
2598
+ const select = args?.select ?? cloneServiceDefaultValue(this._defaults.updateAdvancedArgs.select);
2328
2599
  const {
2329
2600
  returningAll = this._defaults.updateAdvancedOptions.returningAll ?? true,
2330
2601
  includePermissions = this._defaults.updateAdvancedOptions.includePermissions ?? true,