@web-ts-toolkit/access-router-client 0.38.0 → 0.40.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
@@ -77,22 +77,26 @@ var Model = class _Model {
77
77
  *
78
78
  * Concurrency contract:
79
79
  *
80
- * 1. Submitted paths and their values are snapshotted before the request
80
+ * 1. Multiple `save()` calls on the same wrapper are serialized in call
81
+ * order. A later save snapshots its dirty paths only after the previous
82
+ * save has finished reconciling, so overlapping callers cannot submit
83
+ * the same stale dirty set concurrently.
84
+ * 2. Submitted paths and their values are snapshotted before the request
81
85
  * starts, so an in-flight response cannot wipe edits that were made
82
86
  * while the request was pending.
83
- * 2. On success, a submitted path is cleared from `modifiedPaths` only if
87
+ * 3. On success, a submitted path is cleared from `modifiedPaths` only if
84
88
  * its current local value still equals the submitted value — i.e. the
85
89
  * user has not concurrently re-edited it to a different value.
86
- * 3. Server-returned values overwrite local values for paths the user did
90
+ * 4. Server-returned values overwrite local values for paths the user did
87
91
  * NOT concurrently re-modify during the in-flight save; for paths the
88
92
  * user did concurrently re-modify, the local value is preserved and
89
93
  * the dirty flag is retained so the concurrent edit is resubmitted on
90
94
  * the next `save()`. (Deterministic conflict rule: the newer local
91
95
  * edit wins for the same path; the server value becomes its reset
92
96
  * baseline without replacing the newer local value.)
93
- * 4. On failure, no dirty state is cleared and no local value is
97
+ * 5. On failure, no dirty state is cleared and no local value is
94
98
  * overwritten; the caller can retry `save()` with the same set.
95
- * 5. The return value echoes `{ ...result, data }` where `data` is a
99
+ * 6. The return value echoes `{ ...result, data }` where `data` is a
96
100
  * refreshed `Model` snapshot of the post-save local state (or `null`
97
101
  * on failure), matching `ModelResponse<T, TData>`.
98
102
  *
@@ -108,6 +112,23 @@ var Model = class _Model {
108
112
  * of POSTing a new document.
109
113
  */
110
114
  async save(reqConfig) {
115
+ const queuedSave = this._saveQueue ? this._saveQueue.then(
116
+ () => this.saveNow(reqConfig),
117
+ () => this.saveNow(reqConfig)
118
+ ) : this.saveNow(reqConfig);
119
+ const queueSlot = queuedSave.then(
120
+ () => void 0,
121
+ () => void 0
122
+ );
123
+ this._saveQueue = queueSlot;
124
+ void queueSlot.then(() => {
125
+ if (this._saveQueue === queueSlot) {
126
+ this._saveQueue = void 0;
127
+ }
128
+ });
129
+ return queuedSave;
130
+ }
131
+ async saveNow(reqConfig) {
111
132
  const submittedPaths = new Set(this.modifiedPaths);
112
133
  const submittedValues = {};
113
134
  for (const path of submittedPaths) {
@@ -121,7 +142,7 @@ var Model = class _Model {
121
142
  );
122
143
  }
123
144
  const isCreate = persistenceId == null;
124
- const result = isCreate ? await this._service.create(submittedData, null, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
145
+ const result = isCreate ? await this._service.create(submittedData, void 0, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
125
146
  if (!result.success) {
126
147
  return { ...result, data: null };
127
148
  }
@@ -341,13 +362,13 @@ var Model = class _Model {
341
362
  };
342
363
 
343
364
  // src/services/service.ts
344
- var import_axios2 = require("axios");
365
+ var import_axios4 = require("axios");
345
366
 
346
367
  // src/constants.ts
347
368
  var CACHE_HEADER = "x-axios-cache";
348
369
 
349
370
  // src/services/wrap.ts
350
- var import_axios = require("axios");
371
+ var import_axios3 = require("axios");
351
372
 
352
373
  // src/helpers.ts
353
374
  var import_utils2 = require("@web-ts-toolkit/utils");
@@ -385,272 +406,32 @@ function getWrapContext(url, options, config) {
385
406
  return { finalUrl, finalConfig };
386
407
  }
387
408
 
388
- // src/services/wrap.ts
389
- var removeTrailingSlash = (s) => s.replace(/\/$/, "");
390
- var removeLeadingSlash = (s) => s.replace(/^\/+/g, "");
391
- function resolveUrl(basePath, url) {
392
- return basePath ? `${removeTrailingSlash(basePath)}/${removeLeadingSlash(url)}` : url;
393
- }
394
- function prepareConfig(defaultConfig, cacheValue, requestConfig) {
395
- const headerClone = new import_axios.AxiosHeaders(defaultConfig.headers);
396
- headerClone.set(CACHE_HEADER, cacheValue);
397
- const defaulted = { ...defaultConfig, headers: headerClone };
398
- return (0, import_axios.mergeConfig)(defaulted, requestConfig);
399
- }
400
- function createWrapHelper(axios3, basePath) {
401
- return {
402
- wrapGet: (url, defaultConfig = {}) => {
403
- const _url = resolveUrl(basePath, url);
404
- return (options, requestConfig) => {
405
- const { finalUrl, finalConfig } = getWrapContext(
406
- _url,
407
- options,
408
- prepareConfig(defaultConfig, "true", requestConfig)
409
- );
410
- return axios3.get(finalUrl, finalConfig);
411
- };
412
- },
413
- wrapPost: (url, defaultConfig = {}) => {
414
- const _url = resolveUrl(basePath, url);
415
- return (data, options, requestConfig) => {
416
- const { finalUrl, finalConfig } = getWrapContext(
417
- _url,
418
- options,
419
- prepareConfig(defaultConfig, "false", requestConfig)
420
- );
421
- return axios3.post(finalUrl, data, finalConfig);
422
- };
423
- },
424
- wrapPut: (url, defaultConfig = {}) => {
425
- const _url = resolveUrl(basePath, url);
426
- return (data, options, requestConfig) => {
427
- const { finalUrl, finalConfig } = getWrapContext(
428
- _url,
429
- options,
430
- prepareConfig(defaultConfig, "false", requestConfig)
431
- );
432
- return axios3.put(finalUrl, data, finalConfig);
433
- };
434
- },
435
- wrapPatch: (url, defaultConfig = {}) => {
436
- const _url = resolveUrl(basePath, url);
437
- return (data, options, requestConfig) => {
438
- const { finalUrl, finalConfig } = getWrapContext(
439
- _url,
440
- options,
441
- prepareConfig(defaultConfig, "false", requestConfig)
442
- );
443
- return axios3.patch(finalUrl, data, finalConfig);
444
- };
445
- },
446
- wrapDelete: (url, defaultConfig = {}) => {
447
- const _url = resolveUrl(basePath, url);
448
- return (options, requestConfig) => {
449
- const { finalUrl, finalConfig } = getWrapContext(
450
- _url,
451
- options,
452
- prepareConfig(defaultConfig, "false", requestConfig)
453
- );
454
- return axios3.delete(finalUrl, finalConfig);
455
- };
456
- }
457
- };
458
- }
459
-
460
- // src/services/service.ts
461
- var readProblemDetail = (value) => {
462
- if (!value || typeof value !== "object") {
463
- return void 0;
464
- }
465
- if ("detail" in value && typeof value.detail === "string" && value.detail) {
466
- return value.detail;
467
- }
468
- if ("message" in value && typeof value.message === "string" && value.message) {
469
- return value.message;
470
- }
471
- if ("title" in value && typeof value.title === "string" && value.title) {
472
- return value.title;
473
- }
474
- if ("errors" in value && Array.isArray(value.errors)) {
475
- for (const item of value.errors) {
476
- if (typeof item === "string" && item) {
477
- return item;
478
- }
479
- const nested = readProblemDetail(item);
480
- if (nested) {
481
- return nested;
482
- }
483
- }
484
- }
485
- return void 0;
486
- };
487
- var stringifyErrorPayload = (value) => {
488
- if (typeof value === "string") {
489
- return value;
490
- }
491
- const detail = readProblemDetail(value);
492
- if (detail) {
493
- return detail;
494
- }
495
- if (value == null) {
496
- return "";
497
- }
498
- try {
499
- return JSON.stringify(value);
500
- } catch {
501
- return String(value);
502
- }
503
- };
504
- function finalizeOperationResult({
505
- success,
506
- raw,
507
- status,
508
- headers = {},
509
- message,
510
- totalCount
511
- }) {
512
- if (success) {
513
- return {
514
- success: true,
515
- raw,
516
- data: raw,
517
- message: "",
518
- status,
519
- headers,
520
- ...totalCount == null ? {} : { totalCount }
521
- };
522
- }
523
- return {
524
- success: false,
525
- raw,
526
- data: null,
527
- message: message ?? stringifyErrorPayload(raw),
528
- status,
529
- headers,
530
- ...totalCount == null ? {} : { totalCount }
531
- };
532
- }
533
- var normalizeTransportFailure = (error) => {
534
- const transportError = error && typeof error === "object" ? error : {};
535
- let raw = null;
536
- let status = 0;
537
- let headers = {};
538
- let message;
539
- if (transportError.response) {
540
- status = transportError.response.status;
541
- headers = transportError.response.headers;
542
- raw = transportError.response.data;
543
- } else if (transportError.request) {
544
- message = "The server is not responding";
545
- } else {
546
- message = transportError.message;
547
- }
548
- return finalizeOperationResult({ success: false, raw, status, headers, message });
549
- };
550
- var Service = class {
551
- constructor(axios3, basePath, throwOnError = false) {
552
- this._axios = axios3;
553
- this._basePath = basePath;
554
- this._wrap = createWrapHelper(axios3, basePath);
555
- this._throwOnError = throwOnError;
556
- }
557
- handleSuccess(res, extra = {}) {
558
- return {
559
- ...finalizeOperationResult({
560
- success: true,
561
- raw: res.data,
562
- status: res.status,
563
- headers: res.headers
564
- }),
565
- ...extra
566
- };
567
- }
568
- // See https://axios-http.com/docs/handling-errors
569
- handleError(error) {
570
- return normalizeTransportFailure(error);
571
- }
572
- /** Resolves per-call policy against the already-resolved service/adapter default. */
573
- resolveThrowOnError(override) {
574
- return override ?? this._throwOnError;
575
- }
576
- wrapGet(url, defaultAxiosRequestConfig = {}) {
577
- return this._wrap.wrapGet(url, defaultAxiosRequestConfig);
578
- }
579
- wrapPost(url, defaultAxiosRequestConfig = {}) {
580
- return this._wrap.wrapPost(url, defaultAxiosRequestConfig);
581
- }
582
- wrapPut(url, defaultAxiosRequestConfig = {}) {
583
- return this._wrap.wrapPut(url, defaultAxiosRequestConfig);
584
- }
585
- wrapPatch(url, defaultAxiosRequestConfig = {}) {
586
- return this._wrap.wrapPatch(url, defaultAxiosRequestConfig);
587
- }
588
- wrapDelete(url, defaultAxiosRequestConfig = {}) {
589
- return this._wrap.wrapDelete(url, defaultAxiosRequestConfig);
590
- }
591
- /**
592
- * Public bridge to the per-service success/failure callback pipeline and
593
- * `throwOnError` policy. Adapter-internal grouping machinery calls this so
594
- * that grouped entries go through the same finalization the direct path
595
- * uses (`createResponseHandler`). Returns `res` unchanged on success and
596
- * throws `ServiceError` when both `res.success === false` and the
597
- * `throwOnError` override (or the service-level default) are enabled.
598
- */
599
- applyResponseCallbacks(res, throwOnErrorOverride) {
600
- const handler = this._handleCallbacks;
601
- return handler ? handler(res, throwOnErrorOverride) : res;
602
- }
603
- /**
604
- * Returns a fresh headers object that includes the package-owned
605
- * `CACHE_HEADER` set to `"true"` (cache eligible) or `"false"` (bypass)
606
- * according to the `ignoreCache` option. The caller's `CACHE_HEADER`
607
- * value, if any, wins over the `ignoreCache` default.
608
- *
609
- * The input `headers` object is **never mutated**: an `AxiosHeaders`
610
- * instance is cloned via `.toJSON()` before any value is set, and a
611
- * plain-object headers input is shallow-copied. Reusing the same
612
- * caller-owned headers across multiple requests therefore has no
613
- * hidden side effects, and the order of invocations is irrelevant.
614
- */
615
- updateHeaders(headers, { ignoreCache }) {
616
- const cacheValue = ignoreCache ? "false" : "true";
617
- if (!headers) {
618
- return { [CACHE_HEADER]: cacheValue };
619
- }
620
- if (headers instanceof import_axios2.AxiosHeaders) {
621
- if (headers.has(CACHE_HEADER)) return headers;
622
- const cloned = new import_axios2.AxiosHeaders(headers.toJSON());
623
- cloned.set(CACHE_HEADER, cacheValue);
624
- return cloned;
625
- }
626
- if (CACHE_HEADER in headers) return headers;
627
- return {
628
- ...headers,
629
- [CACHE_HEADER]: cacheValue
630
- };
631
- }
632
- };
633
- var ServiceError = class extends Error {
634
- constructor(result) {
635
- super(result.message);
636
- this.name = "ServiceError";
637
- this.success = false;
638
- this.raw = result.raw;
639
- this.data = null;
640
- this.status = result.status;
641
- this.headers = result.headers;
642
- }
643
- };
644
-
645
409
  // src/services/interceptors.ts
646
- var import_axios4 = __toESM(require("axios"));
410
+ var import_axios2 = __toESM(require("axios"));
647
411
 
648
412
  // src/services/cache-utils.ts
649
- var import_axios3 = require("axios");
413
+ var import_axios = require("axios");
650
414
  var import_utils3 = require("@web-ts-toolkit/utils");
415
+ var unsupportedGroupConfigKeys = /* @__PURE__ */ new Set([
416
+ "adapter",
417
+ "cancelToken",
418
+ "onDownloadProgress",
419
+ "onUploadProgress",
420
+ "paramsSerializer",
421
+ "signal",
422
+ "transformRequest",
423
+ "transformResponse",
424
+ "validateStatus"
425
+ ]);
426
+ var UnsupportedGroupedRequestConfigError = class extends Error {
427
+ constructor(message) {
428
+ super(message);
429
+ this.name = "UnsupportedGroupedRequestConfigError";
430
+ }
431
+ };
651
432
  var normalizeConfigValue = (value) => {
652
433
  if (value == null) return value;
653
- if (value instanceof import_axios3.AxiosHeaders) {
434
+ if (value instanceof import_axios.AxiosHeaders) {
654
435
  return normalizeConfigValue(value.toJSON());
655
436
  }
656
437
  if (Array.isArray(value)) {
@@ -664,12 +445,57 @@ var normalizeConfigValue = (value) => {
664
445
  }
665
446
  return value;
666
447
  };
448
+ var normalizeGroupedRequestConfig = (config) => {
449
+ const seen = /* @__PURE__ */ new WeakSet();
450
+ const normalize = (value, path) => {
451
+ if (value == null) return value;
452
+ if (typeof value === "function") {
453
+ throw new UnsupportedGroupedRequestConfigError(
454
+ `Grouped requests do not support function-valued axios config at ${path}`
455
+ );
456
+ }
457
+ if (typeof value === "symbol") {
458
+ throw new UnsupportedGroupedRequestConfigError(
459
+ `Grouped requests do not support symbol-valued axios config at ${path}`
460
+ );
461
+ }
462
+ if (value instanceof import_axios.AxiosHeaders) {
463
+ return normalize(value.toJSON(), path);
464
+ }
465
+ if (Array.isArray(value)) {
466
+ return value.map((item, index) => normalize(item, `${path}[${index}]`));
467
+ }
468
+ if (typeof value === "object") {
469
+ if (seen.has(value)) {
470
+ throw new UnsupportedGroupedRequestConfigError(
471
+ `Grouped requests do not support circular axios config at ${path}`
472
+ );
473
+ }
474
+ 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;
487
+ }
488
+ return value;
489
+ };
490
+ return normalize(config, "config");
491
+ };
667
492
 
668
493
  // src/services/interceptors.ts
669
494
  var DEFAULT_CACHE_CAPACITY = 100;
670
495
  var CACHEABLE_METHODS = /* @__PURE__ */ new Set(["get"]);
671
- var MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
672
496
  var CACHEABLE_RESPONSE_TYPES = /* @__PURE__ */ new Set(["", "json", "text"]);
497
+ var CACHE_INVALIDATE_ON_SUCCESS = "__accessRouterClientCacheInvalidateOnSuccess";
498
+ var CACHE_INVALIDATE_HEADER = "x-axios-cache-invalidate-on-success";
673
499
  var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
674
500
  "authorization",
675
501
  "cookie",
@@ -677,11 +503,19 @@ var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
677
503
  "proxy-authorization",
678
504
  "www-authenticate"
679
505
  ]);
680
- var cloneConfigWithCacheBypass = (config) => {
506
+ var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
507
+ "authorization",
508
+ "cookie",
509
+ "proxy-authorization",
510
+ "x-api-key",
511
+ "x-auth-token",
512
+ "x-access-token"
513
+ ]);
514
+ var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
681
515
  const baseConfig = config ?? {};
682
516
  const next = { ...baseConfig };
683
517
  const sourceHeaders = config?.headers;
684
- if (sourceHeaders instanceof import_axios4.AxiosHeaders) {
518
+ if (sourceHeaders instanceof import_axios2.AxiosHeaders) {
685
519
  next.headers = sourceHeaders.toJSON();
686
520
  } else if (sourceHeaders && typeof sourceHeaders === "object") {
687
521
  next.headers = { ...sourceHeaders };
@@ -689,8 +523,30 @@ var cloneConfigWithCacheBypass = (config) => {
689
523
  next.headers = {};
690
524
  }
691
525
  next.headers[CACHE_HEADER] = "false";
526
+ if (invalidateOnSuccess) {
527
+ next[CACHE_INVALIDATE_ON_SUCCESS] = true;
528
+ next.headers[CACHE_INVALIDATE_HEADER] = "true";
529
+ }
692
530
  return next;
693
531
  };
532
+ var removeCacheInvalidationSignal = (config) => {
533
+ 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);
535
+ if (CACHE_INVALIDATE_ON_SUCCESS in config || hasHeaderSignal) {
536
+ const next = { ...config };
537
+ delete next[CACHE_INVALIDATE_ON_SUCCESS];
538
+ if (headers instanceof import_axios2.AxiosHeaders) {
539
+ const clonedHeaders = import_axios2.AxiosHeaders.from(headers);
540
+ clonedHeaders.delete(CACHE_INVALIDATE_HEADER);
541
+ next.headers = clonedHeaders;
542
+ } else if (headers && typeof headers === "object") {
543
+ next.headers = { ...headers };
544
+ delete next.headers[CACHE_INVALIDATE_HEADER];
545
+ }
546
+ return next;
547
+ }
548
+ return config;
549
+ };
694
550
  var SimpleCache = class {
695
551
  constructor(opts = {}) {
696
552
  this.cache = /* @__PURE__ */ new Map();
@@ -698,7 +554,7 @@ var SimpleCache = class {
698
554
  this.capacity = opts.capacity !== void 0 && Number.isFinite(opts.capacity) && opts.capacity > 0 ? Math.floor(opts.capacity) : DEFAULT_CACHE_CAPACITY;
699
555
  this.clone = opts.clone ?? defaultClone;
700
556
  }
701
- set(key, value, ttl) {
557
+ set(key, value, ttlMs) {
702
558
  if (this.cache.size >= this.capacity && !this.cache.has(key)) {
703
559
  const oldestKey = this.cache.keys().next().value;
704
560
  if (oldestKey !== void 0) {
@@ -707,13 +563,13 @@ var SimpleCache = class {
707
563
  }
708
564
  this.cache.delete(key);
709
565
  this.cache.set(key, value);
710
- if (ttl && ttl > 0) {
566
+ if (ttlMs && ttlMs > 0) {
711
567
  const existing = this.timers.get(key);
712
568
  if (existing) clearTimeout(existing);
713
569
  const timer = setTimeout(() => {
714
570
  this.cache.delete(key);
715
571
  this.timers.delete(key);
716
- }, ttl);
572
+ }, ttlMs);
717
573
  if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
718
574
  timer.unref();
719
575
  }
@@ -786,17 +642,17 @@ var isUnsupportedResponseBody = (response) => {
786
642
  return true;
787
643
  }
788
644
  };
789
- var snapshotResponse = (response) => {
790
- const headers = response.headers instanceof import_axios4.AxiosHeaders ? response.headers.toJSON() : { ...response.headers ?? {} };
645
+ var snapshotResponse = (response, clone) => {
646
+ const headers = response.headers instanceof import_axios2.AxiosHeaders ? response.headers.toJSON() : { ...response.headers ?? {} };
791
647
  return {
792
- data: defaultClone(response.data),
648
+ data: clone(response.data),
793
649
  status: response.status,
794
650
  statusText: response.statusText,
795
- headers: defaultClone(headers)
651
+ headers: clone(headers)
796
652
  };
797
653
  };
798
654
  var serializeHeaders = (headers) => {
799
- const resolvedHeaders = headers instanceof import_axios4.AxiosHeaders ? headers.toJSON() : headers;
655
+ const resolvedHeaders = headers instanceof import_axios2.AxiosHeaders ? headers.toJSON() : headers;
800
656
  const normalizedHeaders = Object.entries(resolvedHeaders ?? {}).filter(([key, value]) => {
801
657
  const normalizedKey = key.toLowerCase();
802
658
  return normalizedKey !== CACHE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
@@ -806,12 +662,35 @@ var serializeHeaders = (headers) => {
806
662
  }, {});
807
663
  return JSON.stringify(normalizeConfigValue(normalizedHeaders));
808
664
  };
665
+ var hasHeaderValue = (value) => {
666
+ if (Array.isArray(value)) return value.some(hasHeaderValue);
667
+ if (value == null || value === false) return false;
668
+ if (typeof value === "string") return value.trim().length > 0;
669
+ return true;
670
+ };
671
+ var consumeCacheInvalidationSignal = (config) => {
672
+ 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);
674
+ if (!hasSignal && !config[CACHE_INVALIDATE_ON_SUCCESS]) return;
675
+ config[CACHE_INVALIDATE_ON_SUCCESS] = true;
676
+ if (headers instanceof import_axios2.AxiosHeaders) {
677
+ headers.delete(CACHE_INVALIDATE_HEADER);
678
+ } else if (headers && typeof headers === "object") {
679
+ delete headers[CACHE_INVALIDATE_HEADER];
680
+ }
681
+ };
682
+ var hasAuthenticationHeader = (headers) => {
683
+ const resolvedHeaders = headers instanceof import_axios2.AxiosHeaders ? headers.toJSON() : headers;
684
+ return Object.entries(resolvedHeaders ?? {}).some(
685
+ ([key, value]) => AUTHENTICATION_REQUEST_HEADERS.has(key.toLowerCase()) && hasHeaderValue(value)
686
+ );
687
+ };
809
688
  var hasStableCacheValue = (value, seen = /* @__PURE__ */ new Set()) => {
810
689
  if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean")
811
690
  return true;
812
691
  if (typeof value !== "object") return false;
813
692
  if (seen.has(value)) return false;
814
- if (value instanceof import_axios4.AxiosHeaders) {
693
+ if (value instanceof import_axios2.AxiosHeaders) {
815
694
  return hasStableCacheValue(value.toJSON(), seen);
816
695
  }
817
696
  const prototype = Object.getPrototypeOf(value);
@@ -867,8 +746,10 @@ var resolveWithCredentials = (config, withCredentialsDefault) => {
867
746
  }
868
747
  return withCredentialsDefault;
869
748
  };
749
+ var hasUsablePartition = (partition) => typeof partition === "string" && partition.trim().length > 0;
870
750
  function useCacheInterceptors(instance, policyOrTtl) {
871
- const policy = typeof policyOrTtl === "number" ? { ttl: policyOrTtl } : policyOrTtl;
751
+ const policy = typeof policyOrTtl === "number" ? { ttlMs: policyOrTtl } : policyOrTtl;
752
+ const clone = policy.clone ?? defaultClone;
872
753
  const store = new SimpleCache({ capacity: policy.capacity, clone: policy.clone });
873
754
  const withCredentialsDefault = policy.withCredentialsDefault ?? Boolean(instance.defaults.withCredentials);
874
755
  const inflight = /* @__PURE__ */ new Map();
@@ -898,10 +779,11 @@ function useCacheInterceptors(instance, policyOrTtl) {
898
779
  };
899
780
  instance.interceptors.request.use(
900
781
  async (config) => {
782
+ consumeCacheInvalidationSignal(config);
901
783
  if (disposed || config.headers[CACHE_HEADER] === "false" || !isCacheEligible(config, instance)) return config;
902
- const isCredentialed = resolveWithCredentials(config, withCredentialsDefault);
784
+ const isCredentialed = resolveWithCredentials(config, withCredentialsDefault) || hasAuthenticationHeader(config.headers);
903
785
  const partitionKey = policy.partitionForRequest?.(config);
904
- if (isCredentialed && !partitionKey) {
786
+ if (isCredentialed && !hasUsablePartition(partitionKey)) {
905
787
  return config;
906
788
  }
907
789
  const key = generateCacheKey(config, partitionKey);
@@ -926,7 +808,7 @@ function useCacheInterceptors(instance, policyOrTtl) {
926
808
  const response = await existing.promise;
927
809
  const shared = response;
928
810
  return {
929
- data: defaultClone(shared.data),
811
+ data: clone(shared.data),
930
812
  status: shared.status,
931
813
  statusText: shared.statusText,
932
814
  headers: { ...shared.headers, [CACHE_HEADER]: "true" },
@@ -959,7 +841,7 @@ function useCacheInterceptors(instance, policyOrTtl) {
959
841
  if (realAdapter === void 0 || realAdapter === null) {
960
842
  realAdapter = instance.defaults.adapter;
961
843
  }
962
- const dispatch = typeof realAdapter === "function" ? realAdapter : typeof import_axios4.default.getAdapter === "function" ? import_axios4.default.getAdapter(
844
+ const dispatch = typeof realAdapter === "function" ? realAdapter : typeof import_axios2.default.getAdapter === "function" ? import_axios2.default.getAdapter(
963
845
  realAdapter,
964
846
  instance.defaults
965
847
  ) : void 0;
@@ -982,8 +864,7 @@ function useCacheInterceptors(instance, policyOrTtl) {
982
864
  );
983
865
  instance.interceptors.response.use(
984
866
  (response) => {
985
- const method = (response.config.method ?? "get").toLowerCase();
986
- if (response.config.headers[CACHE_HEADER] === "false" || MUTATION_METHODS.has(method)) {
867
+ if (response.config[CACHE_INVALIDATE_ON_SUCCESS]) {
987
868
  if (response.status >= 200 && response.status < 300) {
988
869
  invalidate();
989
870
  }
@@ -993,37 +874,300 @@ function useCacheInterceptors(instance, policyOrTtl) {
993
874
  if (!state || state.role !== "source" || !state.slot) {
994
875
  return response;
995
876
  }
996
- if (response.status >= 200 && response.status < 300) {
997
- if (!disposed && state.generation === generation && !isUnsupportedResponseBody(response)) {
998
- store.set(state.key, snapshotResponse(response), policy.ttl);
877
+ if (response.status >= 200 && response.status < 300 && !isUnsupportedResponseBody(response)) {
878
+ const snapshot = snapshotResponse(response, clone);
879
+ response.data = clone(snapshot.data);
880
+ response.headers = clone(snapshot.headers);
881
+ if (!disposed && state.generation === generation) {
882
+ store.set(state.key, snapshot, policy.ttlMs);
999
883
  }
884
+ resolveInflight(state.slot, snapshot);
885
+ return response;
1000
886
  }
1001
887
  resolveInflight(state.slot, response);
1002
888
  return response;
1003
889
  },
1004
- (error) => {
1005
- const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
1006
- if (state?.role === "source" && state.slot) {
1007
- rejectInflight(state.slot, error);
1008
- }
1009
- return Promise.reject(error);
890
+ (error) => {
891
+ const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
892
+ if (state?.role === "source" && state.slot) {
893
+ rejectInflight(state.slot, error);
894
+ }
895
+ return Promise.reject(error);
896
+ }
897
+ );
898
+ return {
899
+ clear: invalidate,
900
+ dispose: () => {
901
+ if (disposed) return;
902
+ disposed = true;
903
+ generation += 1;
904
+ store.dispose();
905
+ const error = new Error(CACHE_DISPOSED_ERROR);
906
+ for (const slot of inflight.values()) {
907
+ rejectInflight(slot, error);
908
+ }
909
+ inflight.clear();
910
+ }
911
+ };
912
+ }
913
+
914
+ // src/services/wrap.ts
915
+ var removeTrailingSlash = (s) => s.replace(/\/$/, "");
916
+ var removeLeadingSlash = (s) => s.replace(/^\/+/g, "");
917
+ function resolveUrl(basePath, url) {
918
+ return basePath ? `${removeTrailingSlash(basePath)}/${removeLeadingSlash(url)}` : url;
919
+ }
920
+ function prepareConfig(defaultConfig, cacheValue, requestConfig, invalidateOnSuccess = false) {
921
+ const headerClone = new import_axios3.AxiosHeaders(defaultConfig.headers);
922
+ headerClone.set(CACHE_HEADER, cacheValue);
923
+ const defaulted = { ...defaultConfig, headers: headerClone };
924
+ const merged = (0, import_axios3.mergeConfig)(defaulted, requestConfig ?? {});
925
+ return invalidateOnSuccess ? cloneConfigWithCacheBypass(merged) : merged;
926
+ }
927
+ function createWrapHelper(axios3, basePath) {
928
+ return {
929
+ wrapGet: (url, defaultConfig = {}) => {
930
+ const _url = resolveUrl(basePath, url);
931
+ return (options, requestConfig) => {
932
+ const { finalUrl, finalConfig } = getWrapContext(
933
+ _url,
934
+ options,
935
+ prepareConfig(defaultConfig, "true", requestConfig)
936
+ );
937
+ return axios3.get(finalUrl, finalConfig);
938
+ };
939
+ },
940
+ wrapPost: (url, defaultConfig = {}) => {
941
+ const _url = resolveUrl(basePath, url);
942
+ return (data, options, requestConfig) => {
943
+ const { finalUrl, finalConfig } = getWrapContext(
944
+ _url,
945
+ options,
946
+ prepareConfig(defaultConfig, "false", requestConfig, true)
947
+ );
948
+ return axios3.post(finalUrl, data, finalConfig);
949
+ };
950
+ },
951
+ wrapPut: (url, defaultConfig = {}) => {
952
+ const _url = resolveUrl(basePath, url);
953
+ return (data, options, requestConfig) => {
954
+ const { finalUrl, finalConfig } = getWrapContext(
955
+ _url,
956
+ options,
957
+ prepareConfig(defaultConfig, "false", requestConfig, true)
958
+ );
959
+ return axios3.put(finalUrl, data, finalConfig);
960
+ };
961
+ },
962
+ wrapPatch: (url, defaultConfig = {}) => {
963
+ const _url = resolveUrl(basePath, url);
964
+ return (data, options, requestConfig) => {
965
+ const { finalUrl, finalConfig } = getWrapContext(
966
+ _url,
967
+ options,
968
+ prepareConfig(defaultConfig, "false", requestConfig, true)
969
+ );
970
+ return axios3.patch(finalUrl, data, finalConfig);
971
+ };
972
+ },
973
+ wrapDelete: (url, defaultConfig = {}) => {
974
+ const _url = resolveUrl(basePath, url);
975
+ return (options, requestConfig) => {
976
+ const { finalUrl, finalConfig } = getWrapContext(
977
+ _url,
978
+ options,
979
+ prepareConfig(defaultConfig, "false", requestConfig, true)
980
+ );
981
+ return axios3.delete(finalUrl, finalConfig);
982
+ };
1010
983
  }
1011
- );
1012
- return {
1013
- clear: invalidate,
1014
- dispose: () => {
1015
- if (disposed) return;
1016
- disposed = true;
1017
- generation += 1;
1018
- store.dispose();
1019
- const error = new Error(CACHE_DISPOSED_ERROR);
1020
- for (const slot of inflight.values()) {
1021
- rejectInflight(slot, error);
984
+ };
985
+ }
986
+
987
+ // src/services/service.ts
988
+ var readProblemDetail = (value) => {
989
+ if (!value || typeof value !== "object") {
990
+ return void 0;
991
+ }
992
+ if ("detail" in value && typeof value.detail === "string" && value.detail) {
993
+ return value.detail;
994
+ }
995
+ if ("message" in value && typeof value.message === "string" && value.message) {
996
+ return value.message;
997
+ }
998
+ if ("title" in value && typeof value.title === "string" && value.title) {
999
+ return value.title;
1000
+ }
1001
+ if ("errors" in value && Array.isArray(value.errors)) {
1002
+ for (const item of value.errors) {
1003
+ if (typeof item === "string" && item) {
1004
+ return item;
1005
+ }
1006
+ const nested = readProblemDetail(item);
1007
+ if (nested) {
1008
+ return nested;
1022
1009
  }
1023
- inflight.clear();
1024
1010
  }
1011
+ }
1012
+ return void 0;
1013
+ };
1014
+ var stringifyErrorPayload = (value) => {
1015
+ if (typeof value === "string") {
1016
+ return value;
1017
+ }
1018
+ const detail = readProblemDetail(value);
1019
+ if (detail) {
1020
+ return detail;
1021
+ }
1022
+ if (value == null) {
1023
+ return "";
1024
+ }
1025
+ try {
1026
+ return JSON.stringify(value);
1027
+ } catch {
1028
+ return String(value);
1029
+ }
1030
+ };
1031
+ function finalizeOperationResult({
1032
+ success,
1033
+ raw,
1034
+ status,
1035
+ headers = {},
1036
+ message,
1037
+ totalCount
1038
+ }) {
1039
+ if (success) {
1040
+ return {
1041
+ success: true,
1042
+ raw,
1043
+ data: raw,
1044
+ message: "",
1045
+ status,
1046
+ headers,
1047
+ ...totalCount == null ? {} : { totalCount }
1048
+ };
1049
+ }
1050
+ return {
1051
+ success: false,
1052
+ raw,
1053
+ data: null,
1054
+ message: message ?? stringifyErrorPayload(raw),
1055
+ status,
1056
+ headers,
1057
+ ...totalCount == null ? {} : { totalCount }
1025
1058
  };
1026
1059
  }
1060
+ var normalizeTransportFailure = (error) => {
1061
+ const transportError = error && typeof error === "object" ? error : {};
1062
+ let raw = null;
1063
+ let status = 0;
1064
+ let headers = {};
1065
+ let message;
1066
+ if (transportError.response) {
1067
+ status = transportError.response.status;
1068
+ headers = transportError.response.headers;
1069
+ raw = transportError.response.data;
1070
+ } else if (transportError.request) {
1071
+ message = "The server is not responding";
1072
+ } else {
1073
+ message = transportError.message;
1074
+ }
1075
+ return finalizeOperationResult({ success: false, raw, status, headers, message });
1076
+ };
1077
+ var Service = class {
1078
+ constructor(axios3, basePath, throwOnError = false) {
1079
+ this._axios = axios3;
1080
+ this._basePath = basePath;
1081
+ this._wrap = createWrapHelper(axios3, basePath);
1082
+ this._throwOnError = throwOnError;
1083
+ }
1084
+ handleSuccess(res, extra = {}) {
1085
+ return {
1086
+ ...finalizeOperationResult({
1087
+ success: true,
1088
+ raw: res.data,
1089
+ status: res.status,
1090
+ headers: res.headers
1091
+ }),
1092
+ ...extra
1093
+ };
1094
+ }
1095
+ // See https://axios-http.com/docs/handling-errors
1096
+ handleError(error) {
1097
+ return normalizeTransportFailure(error);
1098
+ }
1099
+ /** Resolves per-call policy against the already-resolved service/adapter default. */
1100
+ resolveThrowOnError(override) {
1101
+ return override ?? this._throwOnError;
1102
+ }
1103
+ wrapGet(url, defaultAxiosRequestConfig = {}) {
1104
+ return this._wrap.wrapGet(url, defaultAxiosRequestConfig);
1105
+ }
1106
+ wrapPost(url, defaultAxiosRequestConfig = {}) {
1107
+ return this._wrap.wrapPost(url, defaultAxiosRequestConfig);
1108
+ }
1109
+ wrapPut(url, defaultAxiosRequestConfig = {}) {
1110
+ return this._wrap.wrapPut(url, defaultAxiosRequestConfig);
1111
+ }
1112
+ wrapPatch(url, defaultAxiosRequestConfig = {}) {
1113
+ return this._wrap.wrapPatch(url, defaultAxiosRequestConfig);
1114
+ }
1115
+ wrapDelete(url, defaultAxiosRequestConfig = {}) {
1116
+ return this._wrap.wrapDelete(url, defaultAxiosRequestConfig);
1117
+ }
1118
+ /**
1119
+ * Public bridge to the per-service success/failure callback pipeline and
1120
+ * `throwOnError` policy. Adapter-internal grouping machinery calls this so
1121
+ * that grouped entries go through the same finalization the direct path
1122
+ * uses (`createResponseHandler`). Returns `res` unchanged on success and
1123
+ * throws `ServiceError` when both `res.success === false` and the
1124
+ * `throwOnError` override (or the service-level default) are enabled.
1125
+ */
1126
+ applyResponseCallbacks(res, throwOnErrorOverride) {
1127
+ const handler = this._handleCallbacks;
1128
+ return handler ? handler(res, throwOnErrorOverride) : res;
1129
+ }
1130
+ /**
1131
+ * Returns a fresh headers object that includes the package-owned
1132
+ * `CACHE_HEADER` set to `"true"` (cache eligible) or `"false"` (bypass)
1133
+ * according to the `ignoreCache` option. The caller's `CACHE_HEADER`
1134
+ * value, if any, wins over the `ignoreCache` default.
1135
+ *
1136
+ * The input `headers` object is **never mutated**: an `AxiosHeaders`
1137
+ * instance is cloned via `.toJSON()` before any value is set, and a
1138
+ * plain-object headers input is shallow-copied. Reusing the same
1139
+ * caller-owned headers across multiple requests therefore has no
1140
+ * hidden side effects, and the order of invocations is irrelevant.
1141
+ */
1142
+ updateHeaders(headers, { ignoreCache }) {
1143
+ const cacheValue = ignoreCache ? "false" : "true";
1144
+ if (!headers) {
1145
+ return { [CACHE_HEADER]: cacheValue };
1146
+ }
1147
+ if (headers instanceof import_axios4.AxiosHeaders) {
1148
+ if (headers.has(CACHE_HEADER)) return headers;
1149
+ const cloned = new import_axios4.AxiosHeaders(headers.toJSON());
1150
+ cloned.set(CACHE_HEADER, cacheValue);
1151
+ return cloned;
1152
+ }
1153
+ if (CACHE_HEADER in headers) return headers;
1154
+ return {
1155
+ ...headers,
1156
+ [CACHE_HEADER]: cacheValue
1157
+ };
1158
+ }
1159
+ };
1160
+ var ServiceError = class extends Error {
1161
+ constructor(result) {
1162
+ super(result.message);
1163
+ this.name = "ServiceError";
1164
+ this.success = false;
1165
+ this.raw = result.raw;
1166
+ this.data = null;
1167
+ this.status = result.status;
1168
+ this.headers = result.headers;
1169
+ }
1170
+ };
1027
1171
 
1028
1172
  // src/services/shared.ts
1029
1173
  var import_utils4 = require("@web-ts-toolkit/utils");
@@ -1103,7 +1247,7 @@ function finalizeRootEntry(query, entry, responseHeaders, service) {
1103
1247
  }
1104
1248
  if (modelService) {
1105
1249
  const fromExisting = op !== "new";
1106
- const persistenceId = op === "read" && query.target === "model" && "id" in query ? query.id : void 0;
1250
+ const persistenceId = (op === "read" || op === "update") && query.target === "model" && "id" in query ? query.id : void 0;
1107
1251
  _data = Model.create(_data, modelService, persistenceId, fromExisting);
1108
1252
  }
1109
1253
  }
@@ -1163,10 +1307,35 @@ var isLegacyListPayload = (value) => {
1163
1307
  }
1164
1308
  return "count" in value && typeof value.count === "number" && "rows" in value && Array.isArray(value.rows);
1165
1309
  };
1166
- var setDefaultObjectProp = (obj, key, value) => {
1167
- if (!(0, import_utils4.get)(obj, key)) {
1168
- (0, import_utils4.set)(obj, key, value);
1310
+ var cloneDefaultValue = (value) => {
1311
+ if (Array.isArray(value)) {
1312
+ return value.map((item) => cloneDefaultValue(item));
1313
+ }
1314
+ if (value && typeof value === "object") {
1315
+ const cloned = {};
1316
+ for (const [key, item] of Object.entries(value)) {
1317
+ cloned[key] = cloneDefaultValue(item);
1318
+ }
1319
+ return cloned;
1320
+ }
1321
+ return value;
1322
+ };
1323
+ var deepFreeze = (value) => {
1324
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
1325
+ return value;
1326
+ }
1327
+ Object.freeze(value);
1328
+ for (const item of Object.values(value)) {
1329
+ deepFreeze(item);
1330
+ }
1331
+ return value;
1332
+ };
1333
+ var normalizeServiceDefaults = (defaults, objectKeys) => {
1334
+ const normalized = cloneDefaultValue(defaults ?? {});
1335
+ for (const key of objectKeys) {
1336
+ normalized[key] ??= {};
1169
1337
  }
1338
+ return deepFreeze(normalized);
1170
1339
  };
1171
1340
  var ensureListResultCount = (result) => {
1172
1341
  result.totalCount ??= 0;
@@ -1324,7 +1493,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1324
1493
  () => axios3.get(
1325
1494
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1326
1495
  (0, import_axios5.mergeConfig)(reqConfig, { params: {} })
1327
- ).then(handleSuccess).then((result) => {
1496
+ ).then((res) => handleSuccess(res)).then((result) => {
1328
1497
  const rawArray = toArray(result.raw);
1329
1498
  result.raw = rawArray;
1330
1499
  result.count = rawArray.length;
@@ -1358,7 +1527,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1358
1527
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${queryPath}`,
1359
1528
  { filter, select },
1360
1529
  reqConfig
1361
- ).then(handleSuccess).then((result) => {
1530
+ ).then((res) => handleSuccess(res)).then((result) => {
1362
1531
  const rawArray = toArray(result.raw);
1363
1532
  result.raw = rawArray;
1364
1533
  result.count = rawArray.length;
@@ -1392,7 +1561,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1392
1561
  () => axios3.get(
1393
1562
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1394
1563
  (0, import_axios5.mergeConfig)(reqConfig, { params: {} })
1395
- ).then(handleSuccess).then((result) => {
1564
+ ).then((res) => handleSuccess(res)).then((result) => {
1396
1565
  result.data = result.success ? result.raw : null;
1397
1566
  return result;
1398
1567
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1423,7 +1592,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1423
1592
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}/${queryPath}`,
1424
1593
  { select, populate },
1425
1594
  reqConfig
1426
- ).then(handleSuccess).then((result) => {
1595
+ ).then((res) => handleSuccess(res)).then((result) => {
1427
1596
  result.data = result.success ? result.raw : null;
1428
1597
  return result;
1429
1598
  }).catch(handleError).then(
@@ -1455,7 +1624,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1455
1624
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1456
1625
  data,
1457
1626
  (0, import_axios5.mergeConfig)(reqConfig, { params: {} })
1458
- ).then(handleSuccess).then((result) => {
1627
+ ).then((res) => handleSuccess(res)).then((result) => {
1459
1628
  result.data = result.success ? result.raw : null;
1460
1629
  return result;
1461
1630
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1485,7 +1654,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1485
1654
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1486
1655
  data,
1487
1656
  (0, import_axios5.mergeConfig)(reqConfig, { params: {} })
1488
- ).then(handleSuccess).then((result) => {
1657
+ ).then((res) => handleSuccess(res)).then((result) => {
1489
1658
  const rawArray = toArray(result.raw);
1490
1659
  result.raw = rawArray;
1491
1660
  result.count = rawArray.length;
@@ -1517,7 +1686,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1517
1686
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1518
1687
  data,
1519
1688
  (0, import_axios5.mergeConfig)(reqConfig, { params: {} })
1520
- ).then(handleSuccess).then((result) => {
1689
+ ).then((res) => handleSuccess(res)).then((result) => {
1521
1690
  const rawArray = toArray(result.raw);
1522
1691
  result.raw = rawArray;
1523
1692
  result.count = rawArray.length;
@@ -1539,7 +1708,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1539
1708
  () => axios3.delete(
1540
1709
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1541
1710
  reqConfig
1542
- ).then(handleSuccess).then((result) => {
1711
+ ).then((res) => handleSuccess(res)).then((result) => {
1543
1712
  if (result.success) result.data = result.raw;
1544
1713
  return result;
1545
1714
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1562,9 +1731,7 @@ var ModelService = class extends Service {
1562
1731
  this._modelName = modelName;
1563
1732
  this._queryPath = queryPath;
1564
1733
  this._mutationPath = mutationPath;
1565
- this._defaults = defaults ?? {};
1566
- this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
1567
- [
1734
+ this._defaults = normalizeServiceDefaults(defaults, [
1568
1735
  "listArgs",
1569
1736
  "listOptions",
1570
1737
  "listAdvancedArgs",
@@ -1581,7 +1748,8 @@ var ModelService = class extends Service {
1581
1748
  "upsertOptions",
1582
1749
  "upsertAdvancedArgs",
1583
1750
  "upsertAdvancedOptions"
1584
- ].forEach((key) => setDefaultObjectProp(this._defaults, key, {}));
1751
+ ]);
1752
+ this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
1585
1753
  }
1586
1754
  // ---------------------------------------------------------------------------
1587
1755
  // Collection operations
@@ -1618,7 +1786,7 @@ var ModelService = class extends Service {
1618
1786
  include_extra_headers: includeExtraHeaders
1619
1787
  }
1620
1788
  })
1621
- ).then(this.handleSuccess).then((result) => {
1789
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1622
1790
  return processListResult(
1623
1791
  result,
1624
1792
  { includeCount, includeExtraHeaders },
@@ -1688,7 +1856,7 @@ var ModelService = class extends Service {
1688
1856
  options: { skim, includePermissions, includeCount, includeExtraHeaders, populateAccess }
1689
1857
  },
1690
1858
  reqConfig
1691
- ).then(this.handleSuccess).then((result) => {
1859
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1692
1860
  return processListResult(
1693
1861
  result,
1694
1862
  { includeCount, includeExtraHeaders },
@@ -1725,7 +1893,7 @@ var ModelService = class extends Service {
1725
1893
  const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
1726
1894
  const bulk = Array.isArray(data);
1727
1895
  return makeRequest(
1728
- () => this._axios.post(this._basePath, data, (0, import_axios6.mergeConfig)(reqConfig, { params: { include_permissions: includePermissions } })).then(this.handleSuccess).then((result) => {
1896
+ () => this._axios.post(this._basePath, data, (0, import_axios6.mergeConfig)(reqConfig, { params: { include_permissions: includePermissions } })).then((res) => this.handleSuccess(res)).then((result) => {
1729
1897
  if (result.success) {
1730
1898
  if (bulk) {
1731
1899
  const rows = Array.isArray(result.raw) ? result.raw : [result.raw];
@@ -1769,7 +1937,7 @@ var ModelService = class extends Service {
1769
1937
  `${this._basePath}/${this._mutationPath}`,
1770
1938
  { data, select, populate, tasks, options: { includePermissions, populateAccess } },
1771
1939
  reqConfig
1772
- ).then(this.handleSuccess).then((result) => {
1940
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1773
1941
  if (result.success) {
1774
1942
  if (bulk) {
1775
1943
  const rows = Array.isArray(result.raw) ? result.raw : [result.raw];
@@ -1811,7 +1979,7 @@ var ModelService = class extends Service {
1811
1979
  (0, import_axios6.mergeConfig)(reqConfig, {
1812
1980
  params: { returning_all: returningAll, include_permissions: includePermissions }
1813
1981
  })
1814
- ).then(this.handleSuccess).then((result) => {
1982
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1815
1983
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
1816
1984
  return result;
1817
1985
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1851,7 +2019,7 @@ var ModelService = class extends Service {
1851
2019
  options: { returningAll, includePermissions, populateAccess }
1852
2020
  },
1853
2021
  reqConfig
1854
- ).then(this.handleSuccess).then((result) => {
2022
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1855
2023
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
1856
2024
  return result;
1857
2025
  }).catch(this.handleError).then(
@@ -1877,7 +2045,7 @@ var ModelService = class extends Service {
1877
2045
  delete(identifier, axiosRequestConfig) {
1878
2046
  const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
1879
2047
  return makeRequest(
1880
- () => this._axios.delete(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then(this.handleSuccess).then((result) => {
2048
+ () => this._axios.delete(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1881
2049
  if (result.success) result.data = result.raw;
1882
2050
  return result;
1883
2051
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1897,9 +2065,9 @@ var ModelService = class extends Service {
1897
2065
  );
1898
2066
  }
1899
2067
  new(axiosRequestConfig) {
1900
- const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
2068
+ const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {}, false);
1901
2069
  return makeRequest(
1902
- () => this._axios.get(`${this._basePath}/new`, reqConfig).then(this.handleSuccess).then((result) => {
2070
+ () => this._axios.get(`${this._basePath}/new`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1903
2071
  if (result.success) {
1904
2072
  delete result.raw._id;
1905
2073
  result.data = Model.create(result.raw, this);
@@ -1923,7 +2091,7 @@ var ModelService = class extends Service {
1923
2091
  distinct(field, axiosRequestConfig) {
1924
2092
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1925
2093
  return makeRequest(
1926
- () => this._axios.get(`${this._basePath}/distinct/${encodePathSegment(field)}`, reqConfig).then(this.handleSuccess).then((result) => {
2094
+ () => this._axios.get(`${this._basePath}/distinct/${encodePathSegment(field)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1927
2095
  if (result.success) result.data = result.raw;
1928
2096
  return result;
1929
2097
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1945,7 +2113,7 @@ var ModelService = class extends Service {
1945
2113
  distinctAdvanced(field, conditions, axiosRequestConfig) {
1946
2114
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1947
2115
  return makeRequest(
1948
- () => this._axios.post(`${this._basePath}/distinct/${encodePathSegment(field)}`, { filter: conditions }, reqConfig).then(this.handleSuccess).then((result) => {
2116
+ () => this._axios.post(`${this._basePath}/distinct/${encodePathSegment(field)}`, { filter: conditions }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1949
2117
  if (result.success) result.data = result.raw;
1950
2118
  return result;
1951
2119
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1968,7 +2136,7 @@ var ModelService = class extends Service {
1968
2136
  count(axiosRequestConfig) {
1969
2137
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1970
2138
  return makeRequest(
1971
- () => this._axios.get(`${this._basePath}/count`, reqConfig).then(this.handleSuccess).then((result) => {
2139
+ () => this._axios.get(`${this._basePath}/count`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1972
2140
  if (result.success) result.data = result.raw;
1973
2141
  return result;
1974
2142
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1989,7 +2157,7 @@ var ModelService = class extends Service {
1989
2157
  countAdvanced(filter, axiosRequestConfig) {
1990
2158
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1991
2159
  return makeRequest(
1992
- () => this._axios.post(`${this._basePath}/count`, { filter }, reqConfig).then(this.handleSuccess).then((result) => {
2160
+ () => this._axios.post(`${this._basePath}/count`, { filter }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1993
2161
  if (result.success) result.data = result.raw;
1994
2162
  return result;
1995
2163
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -2026,7 +2194,7 @@ var ModelService = class extends Service {
2026
2194
  (0, import_axios6.mergeConfig)(reqConfig, {
2027
2195
  params: { include_permissions: includePermissions, try_list: tryList }
2028
2196
  })
2029
- ).then(this.handleSuccess).then((result) => {
2197
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2030
2198
  result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2031
2199
  return result;
2032
2200
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -2076,7 +2244,7 @@ var ModelService = class extends Service {
2076
2244
  options: { skim, includePermissions, tryList, populateAccess }
2077
2245
  },
2078
2246
  reqConfig
2079
- ).then(this.handleSuccess).then((result) => {
2247
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2080
2248
  result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2081
2249
  return result;
2082
2250
  }).catch(this.handleError).then(
@@ -2132,7 +2300,7 @@ var ModelService = class extends Service {
2132
2300
  options: { skim, includePermissions, tryList, populateAccess }
2133
2301
  },
2134
2302
  reqConfig
2135
- ).then(this.handleSuccess).then((result) => {
2303
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2136
2304
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2137
2305
  return result;
2138
2306
  }).catch(this.handleError).then(
@@ -2169,8 +2337,8 @@ var ModelService = class extends Service {
2169
2337
  (0, import_axios6.mergeConfig)(reqConfig, {
2170
2338
  params: { returning_all: returningAll, include_permissions: includePermissions }
2171
2339
  })
2172
- ).then(this.handleSuccess).then((result) => {
2173
- result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2340
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2341
+ result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2174
2342
  return result;
2175
2343
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
2176
2344
  {
@@ -2210,8 +2378,8 @@ var ModelService = class extends Service {
2210
2378
  options: { returningAll, includePermissions, populateAccess }
2211
2379
  },
2212
2380
  reqConfig
2213
- ).then(this.handleSuccess).then((result) => {
2214
- result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2381
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2382
+ result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2215
2383
  return result;
2216
2384
  }).catch(this.handleError).then(
2217
2385
  (res) => this._handleCallbacks(res, throwOnError)
@@ -2270,9 +2438,7 @@ var DataService = class extends Service {
2270
2438
  super(axios3, basePath, throwOnError);
2271
2439
  this._dataName = dataName;
2272
2440
  this._queryPath = queryPath;
2273
- this._defaults = defaults ?? {};
2274
- this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
2275
- [
2441
+ this._defaults = normalizeServiceDefaults(defaults, [
2276
2442
  "listArgs",
2277
2443
  "listOptions",
2278
2444
  "listAdvancedArgs",
@@ -2280,7 +2446,8 @@ var DataService = class extends Service {
2280
2446
  "readOptions",
2281
2447
  "readAdvancedArgs",
2282
2448
  "readAdvancedOptions"
2283
- ].forEach((key) => setDefaultObjectProp(this._defaults, key, {}));
2449
+ ]);
2450
+ this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
2284
2451
  }
2285
2452
  // ---------------------------------------------------------------------------
2286
2453
  // Collection operations
@@ -2312,7 +2479,7 @@ var DataService = class extends Service {
2312
2479
  include_extra_headers: includeExtraHeaders
2313
2480
  }
2314
2481
  })
2315
- ).then(this.handleSuccess).then((result) => {
2482
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2316
2483
  return processListResult(result, { includeCount, includeExtraHeaders });
2317
2484
  }).catch(this.handleError).then(ensureListResultCount).then((res) => this._handleCallbacks(res, throwOnError)),
2318
2485
  {
@@ -2362,7 +2529,7 @@ var DataService = class extends Service {
2362
2529
  options: { includeCount, includeExtraHeaders }
2363
2530
  },
2364
2531
  reqConfig
2365
- ).then(this.handleSuccess).then((result) => {
2532
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2366
2533
  return processListResult(result, {
2367
2534
  includeCount,
2368
2535
  includeExtraHeaders
@@ -2394,7 +2561,7 @@ var DataService = class extends Service {
2394
2561
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2395
2562
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2396
2563
  return makeRequest(
2397
- () => this._axios.get(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then(this.handleSuccess).then((result) => {
2564
+ () => this._axios.get(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2398
2565
  if (result.success) result.data = result.raw;
2399
2566
  return result;
2400
2567
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -2420,7 +2587,7 @@ var DataService = class extends Service {
2420
2587
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2421
2588
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2422
2589
  return makeRequest(
2423
- () => this._axios.post(`${this._basePath}/${this._queryPath}/${encodePathSegment(identifier)}`, { select }, reqConfig).then(this.handleSuccess).then((result) => {
2590
+ () => this._axios.post(`${this._basePath}/${this._queryPath}/${encodePathSegment(identifier)}`, { select }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2424
2591
  if (result.success) result.data = result.raw;
2425
2592
  return result;
2426
2593
  }).catch(this.handleError).then(
@@ -2449,7 +2616,7 @@ var DataService = class extends Service {
2449
2616
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2450
2617
  const _filter = replaceSubQuery(filter);
2451
2618
  return makeRequest(
2452
- () => this._axios.post(`${this._basePath}/${this._queryPath}/__filter`, { filter: _filter, select }, reqConfig).then(this.handleSuccess).then((result) => {
2619
+ () => this._axios.post(`${this._basePath}/${this._queryPath}/__filter`, { filter: _filter, select }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2453
2620
  if (result.success) result.data = result.raw;
2454
2621
  return result;
2455
2622
  }).catch(this.handleError).then(
@@ -2493,7 +2660,71 @@ var noopCacheController = {
2493
2660
  dispose: () => {
2494
2661
  }
2495
2662
  };
2496
- var serializeRequestConfig = (config) => JSON.stringify(normalizeConfigValue(config ?? {}));
2663
+ var noopResponseCallback = () => {
2664
+ };
2665
+ var ROOT_MUTATION_OPS = /* @__PURE__ */ new Set([
2666
+ "create",
2667
+ "update",
2668
+ "upsert",
2669
+ "delete",
2670
+ "subCreate",
2671
+ "subUpdate",
2672
+ "subBulkUpdate",
2673
+ "subDelete"
2674
+ ]);
2675
+ var serializeRequestConfig = (config) => JSON.stringify(normalizeGroupedRequestConfig(removeCacheInvalidationSignal(config ?? {})));
2676
+ var createMalformedRootResponseError = (message) => {
2677
+ const error = new Error(message);
2678
+ error.name = "MalformedRootResponseError";
2679
+ return error;
2680
+ };
2681
+ var validateRootResponseEntries = (data, expectedLength) => {
2682
+ if (!Array.isArray(data)) {
2683
+ throw createMalformedRootResponseError("Malformed root response: expected an array");
2684
+ }
2685
+ if (data.length !== expectedLength) {
2686
+ throw createMalformedRootResponseError(
2687
+ `Malformed root response: expected ${expectedLength} entries but received ${data.length}`
2688
+ );
2689
+ }
2690
+ return data.map((entry, index) => {
2691
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
2692
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} is not an object`);
2693
+ }
2694
+ const { result, message, statusCode, op } = entry;
2695
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
2696
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} result is not an object`);
2697
+ }
2698
+ if (typeof result.success !== "boolean") {
2699
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} result.success is not boolean`);
2700
+ }
2701
+ if (typeof statusCode !== "number" || !Number.isFinite(statusCode)) {
2702
+ throw createMalformedRootResponseError(
2703
+ `Malformed root response: entry ${index} statusCode is not a finite number`
2704
+ );
2705
+ }
2706
+ if (message != null && typeof message !== "string") {
2707
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} message is not a string`);
2708
+ }
2709
+ if (op != null && typeof op !== "string") {
2710
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} op is not a string`);
2711
+ }
2712
+ return {
2713
+ result,
2714
+ message: message ?? "",
2715
+ statusCode,
2716
+ op
2717
+ };
2718
+ });
2719
+ };
2720
+ var applyRootTransportFailure = (proms, groupThrowOnError, error) => {
2721
+ const failures = proms.map((prom) => finalizeRootTransportFailure(prom.__query, error));
2722
+ return applyGroupCallbacks(
2723
+ failures,
2724
+ proms.map((p) => p.__service),
2725
+ groupThrowOnError ?? false
2726
+ );
2727
+ };
2497
2728
  var isObjectRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
2498
2729
  var mergeServiceDefaults = (adapterDefaults, serviceDefaults) => {
2499
2730
  if (!adapterDefaults && !serviceDefaults) return void 0;
@@ -2530,7 +2761,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2530
2761
  dataDefaults: adapterDataDefaults
2531
2762
  } = adapterOptions ?? {};
2532
2763
  const cacheController = cacheTTL > 0 ? useCacheInterceptors(instance, {
2533
- ttl: cacheTTL,
2764
+ ttlMs: cacheTTL,
2534
2765
  capacity: cacheCapacity,
2535
2766
  withCredentialsDefault: Boolean(instance.defaults.withCredentials),
2536
2767
  partitionForRequest: cachePartition
@@ -2566,8 +2797,8 @@ function createAdapter(axiosConfig, adapterOptions) {
2566
2797
  basePath,
2567
2798
  queryPath,
2568
2799
  mutationPath,
2569
- onSuccess: onSuccess ?? onSuccessRoot,
2570
- onFailure: onFailure ?? onFailureRoot,
2800
+ onSuccess: onSuccess ?? onSuccessRoot ?? noopResponseCallback,
2801
+ onFailure: onFailure ?? onFailureRoot ?? noopResponseCallback,
2571
2802
  throwOnError: throwOnError ?? throwOnErrorRoot ?? false
2572
2803
  },
2573
2804
  mergeServiceDefaults(adapterModelDefaults, defaults)
@@ -2581,8 +2812,8 @@ function createAdapter(axiosConfig, adapterOptions) {
2581
2812
  dataName,
2582
2813
  basePath,
2583
2814
  queryPath,
2584
- onSuccess: onSuccess ?? onSuccessRoot,
2585
- onFailure: onFailure ?? onFailureRoot,
2815
+ onSuccess: onSuccess ?? onSuccessRoot ?? noopResponseCallback,
2816
+ onFailure: onFailure ?? onFailureRoot ?? noopResponseCallback,
2586
2817
  throwOnError: throwOnError ?? throwOnErrorRoot ?? false
2587
2818
  },
2588
2819
  mergeServiceDefaults(adapterDataDefaults, defaults)
@@ -2613,7 +2844,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2613
2844
  if (sharedConfigKey != null && sharedConfigKey !== configKey) {
2614
2845
  throw new Error("Grouped requests must share the same axios request config");
2615
2846
  }
2616
- sharedConfig = prom.__requestConfig ?? {};
2847
+ sharedConfig ??= prom.__requestConfig ?? {};
2617
2848
  sharedConfigKey = configKey;
2618
2849
  const query = { ...prom.__query };
2619
2850
  if (query.target === "model") {
@@ -2637,14 +2868,20 @@ function createAdapter(axiosConfig, adapterOptions) {
2637
2868
  }
2638
2869
  throw error;
2639
2870
  }
2640
- const result = await instance.post(rootRouterPath, defs, sharedConfig ?? {}).then(
2871
+ const groupConfig = removeCacheInvalidationSignal(sharedConfig ?? {});
2872
+ const result = await instance.post(rootRouterPath, defs, groupConfig).then(
2641
2873
  (res) => {
2642
- const rawEntries = res.data.map(({ result: result2, message, statusCode, op }) => ({
2643
- result: result2,
2644
- message,
2645
- statusCode,
2646
- op
2647
- }));
2874
+ let rawEntries;
2875
+ try {
2876
+ rawEntries = validateRootResponseEntries(res.data, proms.length);
2877
+ } catch (error) {
2878
+ return applyRootTransportFailure(proms, groupThrowOnError, error);
2879
+ }
2880
+ if (rawEntries.some(
2881
+ (entry, index) => ROOT_MUTATION_OPS.has(proms[index].__query.op) && entry.result?.success === true && entry.statusCode >= 200 && entry.statusCode < 300
2882
+ )) {
2883
+ cacheController.clear();
2884
+ }
2648
2885
  const finalized = rawEntries.map(
2649
2886
  (rawEntry, index) => finalizeRootEntry(proms[index].__query, rawEntry, {}, proms[index].__service)
2650
2887
  );
@@ -2655,12 +2892,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2655
2892
  );
2656
2893
  },
2657
2894
  (error) => {
2658
- const failures = proms.map((prom) => finalizeRootTransportFailure(prom.__query, error));
2659
- return applyGroupCallbacks(
2660
- failures,
2661
- proms.map((p) => p.__service),
2662
- groupThrowOnError ?? false
2663
- );
2895
+ return applyRootTransportFailure(proms, groupThrowOnError, error);
2664
2896
  }
2665
2897
  );
2666
2898
  return result;