@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.mjs CHANGED
@@ -41,22 +41,26 @@ var Model = class _Model {
41
41
  *
42
42
  * Concurrency contract:
43
43
  *
44
- * 1. Submitted paths and their values are snapshotted before the request
44
+ * 1. Multiple `save()` calls on the same wrapper are serialized in call
45
+ * order. A later save snapshots its dirty paths only after the previous
46
+ * save has finished reconciling, so overlapping callers cannot submit
47
+ * the same stale dirty set concurrently.
48
+ * 2. Submitted paths and their values are snapshotted before the request
45
49
  * starts, so an in-flight response cannot wipe edits that were made
46
50
  * while the request was pending.
47
- * 2. On success, a submitted path is cleared from `modifiedPaths` only if
51
+ * 3. On success, a submitted path is cleared from `modifiedPaths` only if
48
52
  * its current local value still equals the submitted value — i.e. the
49
53
  * user has not concurrently re-edited it to a different value.
50
- * 3. Server-returned values overwrite local values for paths the user did
54
+ * 4. Server-returned values overwrite local values for paths the user did
51
55
  * NOT concurrently re-modify during the in-flight save; for paths the
52
56
  * user did concurrently re-modify, the local value is preserved and
53
57
  * the dirty flag is retained so the concurrent edit is resubmitted on
54
58
  * the next `save()`. (Deterministic conflict rule: the newer local
55
59
  * edit wins for the same path; the server value becomes its reset
56
60
  * baseline without replacing the newer local value.)
57
- * 4. On failure, no dirty state is cleared and no local value is
61
+ * 5. On failure, no dirty state is cleared and no local value is
58
62
  * overwritten; the caller can retry `save()` with the same set.
59
- * 5. The return value echoes `{ ...result, data }` where `data` is a
63
+ * 6. The return value echoes `{ ...result, data }` where `data` is a
60
64
  * refreshed `Model` snapshot of the post-save local state (or `null`
61
65
  * on failure), matching `ModelResponse<T, TData>`.
62
66
  *
@@ -72,6 +76,23 @@ var Model = class _Model {
72
76
  * of POSTing a new document.
73
77
  */
74
78
  async save(reqConfig) {
79
+ const queuedSave = this._saveQueue ? this._saveQueue.then(
80
+ () => this.saveNow(reqConfig),
81
+ () => this.saveNow(reqConfig)
82
+ ) : this.saveNow(reqConfig);
83
+ const queueSlot = queuedSave.then(
84
+ () => void 0,
85
+ () => void 0
86
+ );
87
+ this._saveQueue = queueSlot;
88
+ void queueSlot.then(() => {
89
+ if (this._saveQueue === queueSlot) {
90
+ this._saveQueue = void 0;
91
+ }
92
+ });
93
+ return queuedSave;
94
+ }
95
+ async saveNow(reqConfig) {
75
96
  const submittedPaths = new Set(this.modifiedPaths);
76
97
  const submittedValues = {};
77
98
  for (const path of submittedPaths) {
@@ -85,7 +106,7 @@ var Model = class _Model {
85
106
  );
86
107
  }
87
108
  const isCreate = persistenceId == null;
88
- const result = isCreate ? await this._service.create(submittedData, null, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
109
+ const result = isCreate ? await this._service.create(submittedData, void 0, reqConfig) : await this._service.update(String(persistenceId), submittedData, { returningAll: false }, reqConfig);
89
110
  if (!result.success) {
90
111
  return { ...result, data: null };
91
112
  }
@@ -305,13 +326,13 @@ var Model = class _Model {
305
326
  };
306
327
 
307
328
  // src/services/service.ts
308
- import { AxiosHeaders as AxiosHeaders2 } from "axios";
329
+ import { AxiosHeaders as AxiosHeaders4 } from "axios";
309
330
 
310
331
  // src/constants.ts
311
332
  var CACHE_HEADER = "x-axios-cache";
312
333
 
313
334
  // src/services/wrap.ts
314
- import { AxiosHeaders, mergeConfig } from "axios";
335
+ import { AxiosHeaders as AxiosHeaders3, mergeConfig } from "axios";
315
336
 
316
337
  // src/helpers.ts
317
338
  import { isPlainObject, mapValues } from "@web-ts-toolkit/utils";
@@ -349,272 +370,32 @@ function getWrapContext(url, options, config) {
349
370
  return { finalUrl, finalConfig };
350
371
  }
351
372
 
352
- // src/services/wrap.ts
353
- var removeTrailingSlash = (s) => s.replace(/\/$/, "");
354
- var removeLeadingSlash = (s) => s.replace(/^\/+/g, "");
355
- function resolveUrl(basePath, url) {
356
- return basePath ? `${removeTrailingSlash(basePath)}/${removeLeadingSlash(url)}` : url;
357
- }
358
- function prepareConfig(defaultConfig, cacheValue, requestConfig) {
359
- const headerClone = new AxiosHeaders(defaultConfig.headers);
360
- headerClone.set(CACHE_HEADER, cacheValue);
361
- const defaulted = { ...defaultConfig, headers: headerClone };
362
- return mergeConfig(defaulted, requestConfig);
363
- }
364
- function createWrapHelper(axios3, basePath) {
365
- return {
366
- wrapGet: (url, defaultConfig = {}) => {
367
- const _url = resolveUrl(basePath, url);
368
- return (options, requestConfig) => {
369
- const { finalUrl, finalConfig } = getWrapContext(
370
- _url,
371
- options,
372
- prepareConfig(defaultConfig, "true", requestConfig)
373
- );
374
- return axios3.get(finalUrl, finalConfig);
375
- };
376
- },
377
- wrapPost: (url, defaultConfig = {}) => {
378
- const _url = resolveUrl(basePath, url);
379
- return (data, options, requestConfig) => {
380
- const { finalUrl, finalConfig } = getWrapContext(
381
- _url,
382
- options,
383
- prepareConfig(defaultConfig, "false", requestConfig)
384
- );
385
- return axios3.post(finalUrl, data, finalConfig);
386
- };
387
- },
388
- wrapPut: (url, defaultConfig = {}) => {
389
- const _url = resolveUrl(basePath, url);
390
- return (data, options, requestConfig) => {
391
- const { finalUrl, finalConfig } = getWrapContext(
392
- _url,
393
- options,
394
- prepareConfig(defaultConfig, "false", requestConfig)
395
- );
396
- return axios3.put(finalUrl, data, finalConfig);
397
- };
398
- },
399
- wrapPatch: (url, defaultConfig = {}) => {
400
- const _url = resolveUrl(basePath, url);
401
- return (data, options, requestConfig) => {
402
- const { finalUrl, finalConfig } = getWrapContext(
403
- _url,
404
- options,
405
- prepareConfig(defaultConfig, "false", requestConfig)
406
- );
407
- return axios3.patch(finalUrl, data, finalConfig);
408
- };
409
- },
410
- wrapDelete: (url, defaultConfig = {}) => {
411
- const _url = resolveUrl(basePath, url);
412
- return (options, requestConfig) => {
413
- const { finalUrl, finalConfig } = getWrapContext(
414
- _url,
415
- options,
416
- prepareConfig(defaultConfig, "false", requestConfig)
417
- );
418
- return axios3.delete(finalUrl, finalConfig);
419
- };
420
- }
421
- };
422
- }
423
-
424
- // src/services/service.ts
425
- var readProblemDetail = (value) => {
426
- if (!value || typeof value !== "object") {
427
- return void 0;
428
- }
429
- if ("detail" in value && typeof value.detail === "string" && value.detail) {
430
- return value.detail;
431
- }
432
- if ("message" in value && typeof value.message === "string" && value.message) {
433
- return value.message;
434
- }
435
- if ("title" in value && typeof value.title === "string" && value.title) {
436
- return value.title;
437
- }
438
- if ("errors" in value && Array.isArray(value.errors)) {
439
- for (const item of value.errors) {
440
- if (typeof item === "string" && item) {
441
- return item;
442
- }
443
- const nested = readProblemDetail(item);
444
- if (nested) {
445
- return nested;
446
- }
447
- }
448
- }
449
- return void 0;
450
- };
451
- var stringifyErrorPayload = (value) => {
452
- if (typeof value === "string") {
453
- return value;
454
- }
455
- const detail = readProblemDetail(value);
456
- if (detail) {
457
- return detail;
458
- }
459
- if (value == null) {
460
- return "";
461
- }
462
- try {
463
- return JSON.stringify(value);
464
- } catch {
465
- return String(value);
466
- }
467
- };
468
- function finalizeOperationResult({
469
- success,
470
- raw,
471
- status,
472
- headers = {},
473
- message,
474
- totalCount
475
- }) {
476
- if (success) {
477
- return {
478
- success: true,
479
- raw,
480
- data: raw,
481
- message: "",
482
- status,
483
- headers,
484
- ...totalCount == null ? {} : { totalCount }
485
- };
486
- }
487
- return {
488
- success: false,
489
- raw,
490
- data: null,
491
- message: message ?? stringifyErrorPayload(raw),
492
- status,
493
- headers,
494
- ...totalCount == null ? {} : { totalCount }
495
- };
496
- }
497
- var normalizeTransportFailure = (error) => {
498
- const transportError = error && typeof error === "object" ? error : {};
499
- let raw = null;
500
- let status = 0;
501
- let headers = {};
502
- let message;
503
- if (transportError.response) {
504
- status = transportError.response.status;
505
- headers = transportError.response.headers;
506
- raw = transportError.response.data;
507
- } else if (transportError.request) {
508
- message = "The server is not responding";
509
- } else {
510
- message = transportError.message;
511
- }
512
- return finalizeOperationResult({ success: false, raw, status, headers, message });
513
- };
514
- var Service = class {
515
- constructor(axios3, basePath, throwOnError = false) {
516
- this._axios = axios3;
517
- this._basePath = basePath;
518
- this._wrap = createWrapHelper(axios3, basePath);
519
- this._throwOnError = throwOnError;
520
- }
521
- handleSuccess(res, extra = {}) {
522
- return {
523
- ...finalizeOperationResult({
524
- success: true,
525
- raw: res.data,
526
- status: res.status,
527
- headers: res.headers
528
- }),
529
- ...extra
530
- };
531
- }
532
- // See https://axios-http.com/docs/handling-errors
533
- handleError(error) {
534
- return normalizeTransportFailure(error);
535
- }
536
- /** Resolves per-call policy against the already-resolved service/adapter default. */
537
- resolveThrowOnError(override) {
538
- return override ?? this._throwOnError;
539
- }
540
- wrapGet(url, defaultAxiosRequestConfig = {}) {
541
- return this._wrap.wrapGet(url, defaultAxiosRequestConfig);
542
- }
543
- wrapPost(url, defaultAxiosRequestConfig = {}) {
544
- return this._wrap.wrapPost(url, defaultAxiosRequestConfig);
545
- }
546
- wrapPut(url, defaultAxiosRequestConfig = {}) {
547
- return this._wrap.wrapPut(url, defaultAxiosRequestConfig);
548
- }
549
- wrapPatch(url, defaultAxiosRequestConfig = {}) {
550
- return this._wrap.wrapPatch(url, defaultAxiosRequestConfig);
551
- }
552
- wrapDelete(url, defaultAxiosRequestConfig = {}) {
553
- return this._wrap.wrapDelete(url, defaultAxiosRequestConfig);
554
- }
555
- /**
556
- * Public bridge to the per-service success/failure callback pipeline and
557
- * `throwOnError` policy. Adapter-internal grouping machinery calls this so
558
- * that grouped entries go through the same finalization the direct path
559
- * uses (`createResponseHandler`). Returns `res` unchanged on success and
560
- * throws `ServiceError` when both `res.success === false` and the
561
- * `throwOnError` override (or the service-level default) are enabled.
562
- */
563
- applyResponseCallbacks(res, throwOnErrorOverride) {
564
- const handler = this._handleCallbacks;
565
- return handler ? handler(res, throwOnErrorOverride) : res;
566
- }
567
- /**
568
- * Returns a fresh headers object that includes the package-owned
569
- * `CACHE_HEADER` set to `"true"` (cache eligible) or `"false"` (bypass)
570
- * according to the `ignoreCache` option. The caller's `CACHE_HEADER`
571
- * value, if any, wins over the `ignoreCache` default.
572
- *
573
- * The input `headers` object is **never mutated**: an `AxiosHeaders`
574
- * instance is cloned via `.toJSON()` before any value is set, and a
575
- * plain-object headers input is shallow-copied. Reusing the same
576
- * caller-owned headers across multiple requests therefore has no
577
- * hidden side effects, and the order of invocations is irrelevant.
578
- */
579
- updateHeaders(headers, { ignoreCache }) {
580
- const cacheValue = ignoreCache ? "false" : "true";
581
- if (!headers) {
582
- return { [CACHE_HEADER]: cacheValue };
583
- }
584
- if (headers instanceof AxiosHeaders2) {
585
- if (headers.has(CACHE_HEADER)) return headers;
586
- const cloned = new AxiosHeaders2(headers.toJSON());
587
- cloned.set(CACHE_HEADER, cacheValue);
588
- return cloned;
589
- }
590
- if (CACHE_HEADER in headers) return headers;
591
- return {
592
- ...headers,
593
- [CACHE_HEADER]: cacheValue
594
- };
595
- }
596
- };
597
- var ServiceError = class extends Error {
598
- constructor(result) {
599
- super(result.message);
600
- this.name = "ServiceError";
601
- this.success = false;
602
- this.raw = result.raw;
603
- this.data = null;
604
- this.status = result.status;
605
- this.headers = result.headers;
606
- }
607
- };
608
-
609
373
  // src/services/interceptors.ts
610
- import axios, { AxiosHeaders as AxiosHeaders4 } from "axios";
374
+ import axios, { AxiosHeaders as AxiosHeaders2 } from "axios";
611
375
 
612
376
  // src/services/cache-utils.ts
613
- import { AxiosHeaders as AxiosHeaders3 } from "axios";
377
+ import { AxiosHeaders } from "axios";
614
378
  import { omitBy } from "@web-ts-toolkit/utils";
379
+ var unsupportedGroupConfigKeys = /* @__PURE__ */ new Set([
380
+ "adapter",
381
+ "cancelToken",
382
+ "onDownloadProgress",
383
+ "onUploadProgress",
384
+ "paramsSerializer",
385
+ "signal",
386
+ "transformRequest",
387
+ "transformResponse",
388
+ "validateStatus"
389
+ ]);
390
+ var UnsupportedGroupedRequestConfigError = class extends Error {
391
+ constructor(message) {
392
+ super(message);
393
+ this.name = "UnsupportedGroupedRequestConfigError";
394
+ }
395
+ };
615
396
  var normalizeConfigValue = (value) => {
616
397
  if (value == null) return value;
617
- if (value instanceof AxiosHeaders3) {
398
+ if (value instanceof AxiosHeaders) {
618
399
  return normalizeConfigValue(value.toJSON());
619
400
  }
620
401
  if (Array.isArray(value)) {
@@ -628,12 +409,57 @@ var normalizeConfigValue = (value) => {
628
409
  }
629
410
  return value;
630
411
  };
412
+ var normalizeGroupedRequestConfig = (config) => {
413
+ const seen = /* @__PURE__ */ new WeakSet();
414
+ const normalize = (value, path) => {
415
+ if (value == null) return value;
416
+ if (typeof value === "function") {
417
+ throw new UnsupportedGroupedRequestConfigError(
418
+ `Grouped requests do not support function-valued axios config at ${path}`
419
+ );
420
+ }
421
+ if (typeof value === "symbol") {
422
+ throw new UnsupportedGroupedRequestConfigError(
423
+ `Grouped requests do not support symbol-valued axios config at ${path}`
424
+ );
425
+ }
426
+ if (value instanceof AxiosHeaders) {
427
+ return normalize(value.toJSON(), path);
428
+ }
429
+ if (Array.isArray(value)) {
430
+ return value.map((item, index) => normalize(item, `${path}[${index}]`));
431
+ }
432
+ if (typeof value === "object") {
433
+ if (seen.has(value)) {
434
+ throw new UnsupportedGroupedRequestConfigError(
435
+ `Grouped requests do not support circular axios config at ${path}`
436
+ );
437
+ }
438
+ 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;
451
+ }
452
+ return value;
453
+ };
454
+ return normalize(config, "config");
455
+ };
631
456
 
632
457
  // src/services/interceptors.ts
633
458
  var DEFAULT_CACHE_CAPACITY = 100;
634
459
  var CACHEABLE_METHODS = /* @__PURE__ */ new Set(["get"]);
635
- var MUTATION_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
636
460
  var CACHEABLE_RESPONSE_TYPES = /* @__PURE__ */ new Set(["", "json", "text"]);
461
+ var CACHE_INVALIDATE_ON_SUCCESS = "__accessRouterClientCacheInvalidateOnSuccess";
462
+ var CACHE_INVALIDATE_HEADER = "x-axios-cache-invalidate-on-success";
637
463
  var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
638
464
  "authorization",
639
465
  "cookie",
@@ -641,11 +467,19 @@ var SENSITIVE_CACHE_HEADERS = /* @__PURE__ */ new Set([
641
467
  "proxy-authorization",
642
468
  "www-authenticate"
643
469
  ]);
644
- var cloneConfigWithCacheBypass = (config) => {
470
+ var AUTHENTICATION_REQUEST_HEADERS = /* @__PURE__ */ new Set([
471
+ "authorization",
472
+ "cookie",
473
+ "proxy-authorization",
474
+ "x-api-key",
475
+ "x-auth-token",
476
+ "x-access-token"
477
+ ]);
478
+ var cloneConfigWithCacheBypass = (config, invalidateOnSuccess = true) => {
645
479
  const baseConfig = config ?? {};
646
480
  const next = { ...baseConfig };
647
481
  const sourceHeaders = config?.headers;
648
- if (sourceHeaders instanceof AxiosHeaders4) {
482
+ if (sourceHeaders instanceof AxiosHeaders2) {
649
483
  next.headers = sourceHeaders.toJSON();
650
484
  } else if (sourceHeaders && typeof sourceHeaders === "object") {
651
485
  next.headers = { ...sourceHeaders };
@@ -653,8 +487,30 @@ var cloneConfigWithCacheBypass = (config) => {
653
487
  next.headers = {};
654
488
  }
655
489
  next.headers[CACHE_HEADER] = "false";
490
+ if (invalidateOnSuccess) {
491
+ next[CACHE_INVALIDATE_ON_SUCCESS] = true;
492
+ next.headers[CACHE_INVALIDATE_HEADER] = "true";
493
+ }
656
494
  return next;
657
495
  };
496
+ var removeCacheInvalidationSignal = (config) => {
497
+ 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);
499
+ if (CACHE_INVALIDATE_ON_SUCCESS in config || hasHeaderSignal) {
500
+ const next = { ...config };
501
+ delete next[CACHE_INVALIDATE_ON_SUCCESS];
502
+ if (headers instanceof AxiosHeaders2) {
503
+ const clonedHeaders = AxiosHeaders2.from(headers);
504
+ clonedHeaders.delete(CACHE_INVALIDATE_HEADER);
505
+ next.headers = clonedHeaders;
506
+ } else if (headers && typeof headers === "object") {
507
+ next.headers = { ...headers };
508
+ delete next.headers[CACHE_INVALIDATE_HEADER];
509
+ }
510
+ return next;
511
+ }
512
+ return config;
513
+ };
658
514
  var SimpleCache = class {
659
515
  constructor(opts = {}) {
660
516
  this.cache = /* @__PURE__ */ new Map();
@@ -662,7 +518,7 @@ var SimpleCache = class {
662
518
  this.capacity = opts.capacity !== void 0 && Number.isFinite(opts.capacity) && opts.capacity > 0 ? Math.floor(opts.capacity) : DEFAULT_CACHE_CAPACITY;
663
519
  this.clone = opts.clone ?? defaultClone;
664
520
  }
665
- set(key, value, ttl) {
521
+ set(key, value, ttlMs) {
666
522
  if (this.cache.size >= this.capacity && !this.cache.has(key)) {
667
523
  const oldestKey = this.cache.keys().next().value;
668
524
  if (oldestKey !== void 0) {
@@ -671,13 +527,13 @@ var SimpleCache = class {
671
527
  }
672
528
  this.cache.delete(key);
673
529
  this.cache.set(key, value);
674
- if (ttl && ttl > 0) {
530
+ if (ttlMs && ttlMs > 0) {
675
531
  const existing = this.timers.get(key);
676
532
  if (existing) clearTimeout(existing);
677
533
  const timer = setTimeout(() => {
678
534
  this.cache.delete(key);
679
535
  this.timers.delete(key);
680
- }, ttl);
536
+ }, ttlMs);
681
537
  if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
682
538
  timer.unref();
683
539
  }
@@ -750,17 +606,17 @@ var isUnsupportedResponseBody = (response) => {
750
606
  return true;
751
607
  }
752
608
  };
753
- var snapshotResponse = (response) => {
754
- const headers = response.headers instanceof AxiosHeaders4 ? response.headers.toJSON() : { ...response.headers ?? {} };
609
+ var snapshotResponse = (response, clone) => {
610
+ const headers = response.headers instanceof AxiosHeaders2 ? response.headers.toJSON() : { ...response.headers ?? {} };
755
611
  return {
756
- data: defaultClone(response.data),
612
+ data: clone(response.data),
757
613
  status: response.status,
758
614
  statusText: response.statusText,
759
- headers: defaultClone(headers)
615
+ headers: clone(headers)
760
616
  };
761
617
  };
762
618
  var serializeHeaders = (headers) => {
763
- const resolvedHeaders = headers instanceof AxiosHeaders4 ? headers.toJSON() : headers;
619
+ const resolvedHeaders = headers instanceof AxiosHeaders2 ? headers.toJSON() : headers;
764
620
  const normalizedHeaders = Object.entries(resolvedHeaders ?? {}).filter(([key, value]) => {
765
621
  const normalizedKey = key.toLowerCase();
766
622
  return normalizedKey !== CACHE_HEADER.toLowerCase() && !SENSITIVE_CACHE_HEADERS.has(normalizedKey) && value !== void 0;
@@ -770,12 +626,35 @@ var serializeHeaders = (headers) => {
770
626
  }, {});
771
627
  return JSON.stringify(normalizeConfigValue(normalizedHeaders));
772
628
  };
629
+ var hasHeaderValue = (value) => {
630
+ if (Array.isArray(value)) return value.some(hasHeaderValue);
631
+ if (value == null || value === false) return false;
632
+ if (typeof value === "string") return value.trim().length > 0;
633
+ return true;
634
+ };
635
+ var consumeCacheInvalidationSignal = (config) => {
636
+ 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);
638
+ if (!hasSignal && !config[CACHE_INVALIDATE_ON_SUCCESS]) return;
639
+ config[CACHE_INVALIDATE_ON_SUCCESS] = true;
640
+ if (headers instanceof AxiosHeaders2) {
641
+ headers.delete(CACHE_INVALIDATE_HEADER);
642
+ } else if (headers && typeof headers === "object") {
643
+ delete headers[CACHE_INVALIDATE_HEADER];
644
+ }
645
+ };
646
+ var hasAuthenticationHeader = (headers) => {
647
+ const resolvedHeaders = headers instanceof AxiosHeaders2 ? headers.toJSON() : headers;
648
+ return Object.entries(resolvedHeaders ?? {}).some(
649
+ ([key, value]) => AUTHENTICATION_REQUEST_HEADERS.has(key.toLowerCase()) && hasHeaderValue(value)
650
+ );
651
+ };
773
652
  var hasStableCacheValue = (value, seen = /* @__PURE__ */ new Set()) => {
774
653
  if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean")
775
654
  return true;
776
655
  if (typeof value !== "object") return false;
777
656
  if (seen.has(value)) return false;
778
- if (value instanceof AxiosHeaders4) {
657
+ if (value instanceof AxiosHeaders2) {
779
658
  return hasStableCacheValue(value.toJSON(), seen);
780
659
  }
781
660
  const prototype = Object.getPrototypeOf(value);
@@ -831,8 +710,10 @@ var resolveWithCredentials = (config, withCredentialsDefault) => {
831
710
  }
832
711
  return withCredentialsDefault;
833
712
  };
713
+ var hasUsablePartition = (partition) => typeof partition === "string" && partition.trim().length > 0;
834
714
  function useCacheInterceptors(instance, policyOrTtl) {
835
- const policy = typeof policyOrTtl === "number" ? { ttl: policyOrTtl } : policyOrTtl;
715
+ const policy = typeof policyOrTtl === "number" ? { ttlMs: policyOrTtl } : policyOrTtl;
716
+ const clone = policy.clone ?? defaultClone;
836
717
  const store = new SimpleCache({ capacity: policy.capacity, clone: policy.clone });
837
718
  const withCredentialsDefault = policy.withCredentialsDefault ?? Boolean(instance.defaults.withCredentials);
838
719
  const inflight = /* @__PURE__ */ new Map();
@@ -862,10 +743,11 @@ function useCacheInterceptors(instance, policyOrTtl) {
862
743
  };
863
744
  instance.interceptors.request.use(
864
745
  async (config) => {
746
+ consumeCacheInvalidationSignal(config);
865
747
  if (disposed || config.headers[CACHE_HEADER] === "false" || !isCacheEligible(config, instance)) return config;
866
- const isCredentialed = resolveWithCredentials(config, withCredentialsDefault);
748
+ const isCredentialed = resolveWithCredentials(config, withCredentialsDefault) || hasAuthenticationHeader(config.headers);
867
749
  const partitionKey = policy.partitionForRequest?.(config);
868
- if (isCredentialed && !partitionKey) {
750
+ if (isCredentialed && !hasUsablePartition(partitionKey)) {
869
751
  return config;
870
752
  }
871
753
  const key = generateCacheKey(config, partitionKey);
@@ -890,7 +772,7 @@ function useCacheInterceptors(instance, policyOrTtl) {
890
772
  const response = await existing.promise;
891
773
  const shared = response;
892
774
  return {
893
- data: defaultClone(shared.data),
775
+ data: clone(shared.data),
894
776
  status: shared.status,
895
777
  statusText: shared.statusText,
896
778
  headers: { ...shared.headers, [CACHE_HEADER]: "true" },
@@ -946,8 +828,7 @@ function useCacheInterceptors(instance, policyOrTtl) {
946
828
  );
947
829
  instance.interceptors.response.use(
948
830
  (response) => {
949
- const method = (response.config.method ?? "get").toLowerCase();
950
- if (response.config.headers[CACHE_HEADER] === "false" || MUTATION_METHODS.has(method)) {
831
+ if (response.config[CACHE_INVALIDATE_ON_SUCCESS]) {
951
832
  if (response.status >= 200 && response.status < 300) {
952
833
  invalidate();
953
834
  }
@@ -957,40 +838,303 @@ function useCacheInterceptors(instance, policyOrTtl) {
957
838
  if (!state || state.role !== "source" || !state.slot) {
958
839
  return response;
959
840
  }
960
- if (response.status >= 200 && response.status < 300) {
961
- if (!disposed && state.generation === generation && !isUnsupportedResponseBody(response)) {
962
- store.set(state.key, snapshotResponse(response), policy.ttl);
841
+ if (response.status >= 200 && response.status < 300 && !isUnsupportedResponseBody(response)) {
842
+ const snapshot = snapshotResponse(response, clone);
843
+ response.data = clone(snapshot.data);
844
+ response.headers = clone(snapshot.headers);
845
+ if (!disposed && state.generation === generation) {
846
+ store.set(state.key, snapshot, policy.ttlMs);
963
847
  }
848
+ resolveInflight(state.slot, snapshot);
849
+ return response;
964
850
  }
965
851
  resolveInflight(state.slot, response);
966
852
  return response;
967
853
  },
968
- (error) => {
969
- const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
970
- if (state?.role === "source" && state.slot) {
971
- rejectInflight(state.slot, error);
972
- }
973
- return Promise.reject(error);
854
+ (error) => {
855
+ const state = (error?.config ?? {})[CACHE_REQUEST_STATE];
856
+ if (state?.role === "source" && state.slot) {
857
+ rejectInflight(state.slot, error);
858
+ }
859
+ return Promise.reject(error);
860
+ }
861
+ );
862
+ return {
863
+ clear: invalidate,
864
+ dispose: () => {
865
+ if (disposed) return;
866
+ disposed = true;
867
+ generation += 1;
868
+ store.dispose();
869
+ const error = new Error(CACHE_DISPOSED_ERROR);
870
+ for (const slot of inflight.values()) {
871
+ rejectInflight(slot, error);
872
+ }
873
+ inflight.clear();
874
+ }
875
+ };
876
+ }
877
+
878
+ // src/services/wrap.ts
879
+ var removeTrailingSlash = (s) => s.replace(/\/$/, "");
880
+ var removeLeadingSlash = (s) => s.replace(/^\/+/g, "");
881
+ function resolveUrl(basePath, url) {
882
+ return basePath ? `${removeTrailingSlash(basePath)}/${removeLeadingSlash(url)}` : url;
883
+ }
884
+ function prepareConfig(defaultConfig, cacheValue, requestConfig, invalidateOnSuccess = false) {
885
+ const headerClone = new AxiosHeaders3(defaultConfig.headers);
886
+ headerClone.set(CACHE_HEADER, cacheValue);
887
+ const defaulted = { ...defaultConfig, headers: headerClone };
888
+ const merged = mergeConfig(defaulted, requestConfig ?? {});
889
+ return invalidateOnSuccess ? cloneConfigWithCacheBypass(merged) : merged;
890
+ }
891
+ function createWrapHelper(axios3, basePath) {
892
+ return {
893
+ wrapGet: (url, defaultConfig = {}) => {
894
+ const _url = resolveUrl(basePath, url);
895
+ return (options, requestConfig) => {
896
+ const { finalUrl, finalConfig } = getWrapContext(
897
+ _url,
898
+ options,
899
+ prepareConfig(defaultConfig, "true", requestConfig)
900
+ );
901
+ return axios3.get(finalUrl, finalConfig);
902
+ };
903
+ },
904
+ wrapPost: (url, defaultConfig = {}) => {
905
+ const _url = resolveUrl(basePath, url);
906
+ return (data, options, requestConfig) => {
907
+ const { finalUrl, finalConfig } = getWrapContext(
908
+ _url,
909
+ options,
910
+ prepareConfig(defaultConfig, "false", requestConfig, true)
911
+ );
912
+ return axios3.post(finalUrl, data, finalConfig);
913
+ };
914
+ },
915
+ wrapPut: (url, defaultConfig = {}) => {
916
+ const _url = resolveUrl(basePath, url);
917
+ return (data, options, requestConfig) => {
918
+ const { finalUrl, finalConfig } = getWrapContext(
919
+ _url,
920
+ options,
921
+ prepareConfig(defaultConfig, "false", requestConfig, true)
922
+ );
923
+ return axios3.put(finalUrl, data, finalConfig);
924
+ };
925
+ },
926
+ wrapPatch: (url, defaultConfig = {}) => {
927
+ const _url = resolveUrl(basePath, url);
928
+ return (data, options, requestConfig) => {
929
+ const { finalUrl, finalConfig } = getWrapContext(
930
+ _url,
931
+ options,
932
+ prepareConfig(defaultConfig, "false", requestConfig, true)
933
+ );
934
+ return axios3.patch(finalUrl, data, finalConfig);
935
+ };
936
+ },
937
+ wrapDelete: (url, defaultConfig = {}) => {
938
+ const _url = resolveUrl(basePath, url);
939
+ return (options, requestConfig) => {
940
+ const { finalUrl, finalConfig } = getWrapContext(
941
+ _url,
942
+ options,
943
+ prepareConfig(defaultConfig, "false", requestConfig, true)
944
+ );
945
+ return axios3.delete(finalUrl, finalConfig);
946
+ };
974
947
  }
975
- );
976
- return {
977
- clear: invalidate,
978
- dispose: () => {
979
- if (disposed) return;
980
- disposed = true;
981
- generation += 1;
982
- store.dispose();
983
- const error = new Error(CACHE_DISPOSED_ERROR);
984
- for (const slot of inflight.values()) {
985
- rejectInflight(slot, error);
948
+ };
949
+ }
950
+
951
+ // src/services/service.ts
952
+ var readProblemDetail = (value) => {
953
+ if (!value || typeof value !== "object") {
954
+ return void 0;
955
+ }
956
+ if ("detail" in value && typeof value.detail === "string" && value.detail) {
957
+ return value.detail;
958
+ }
959
+ if ("message" in value && typeof value.message === "string" && value.message) {
960
+ return value.message;
961
+ }
962
+ if ("title" in value && typeof value.title === "string" && value.title) {
963
+ return value.title;
964
+ }
965
+ if ("errors" in value && Array.isArray(value.errors)) {
966
+ for (const item of value.errors) {
967
+ if (typeof item === "string" && item) {
968
+ return item;
969
+ }
970
+ const nested = readProblemDetail(item);
971
+ if (nested) {
972
+ return nested;
986
973
  }
987
- inflight.clear();
988
974
  }
975
+ }
976
+ return void 0;
977
+ };
978
+ var stringifyErrorPayload = (value) => {
979
+ if (typeof value === "string") {
980
+ return value;
981
+ }
982
+ const detail = readProblemDetail(value);
983
+ if (detail) {
984
+ return detail;
985
+ }
986
+ if (value == null) {
987
+ return "";
988
+ }
989
+ try {
990
+ return JSON.stringify(value);
991
+ } catch {
992
+ return String(value);
993
+ }
994
+ };
995
+ function finalizeOperationResult({
996
+ success,
997
+ raw,
998
+ status,
999
+ headers = {},
1000
+ message,
1001
+ totalCount
1002
+ }) {
1003
+ if (success) {
1004
+ return {
1005
+ success: true,
1006
+ raw,
1007
+ data: raw,
1008
+ message: "",
1009
+ status,
1010
+ headers,
1011
+ ...totalCount == null ? {} : { totalCount }
1012
+ };
1013
+ }
1014
+ return {
1015
+ success: false,
1016
+ raw,
1017
+ data: null,
1018
+ message: message ?? stringifyErrorPayload(raw),
1019
+ status,
1020
+ headers,
1021
+ ...totalCount == null ? {} : { totalCount }
989
1022
  };
990
1023
  }
1024
+ var normalizeTransportFailure = (error) => {
1025
+ const transportError = error && typeof error === "object" ? error : {};
1026
+ let raw = null;
1027
+ let status = 0;
1028
+ let headers = {};
1029
+ let message;
1030
+ if (transportError.response) {
1031
+ status = transportError.response.status;
1032
+ headers = transportError.response.headers;
1033
+ raw = transportError.response.data;
1034
+ } else if (transportError.request) {
1035
+ message = "The server is not responding";
1036
+ } else {
1037
+ message = transportError.message;
1038
+ }
1039
+ return finalizeOperationResult({ success: false, raw, status, headers, message });
1040
+ };
1041
+ var Service = class {
1042
+ constructor(axios3, basePath, throwOnError = false) {
1043
+ this._axios = axios3;
1044
+ this._basePath = basePath;
1045
+ this._wrap = createWrapHelper(axios3, basePath);
1046
+ this._throwOnError = throwOnError;
1047
+ }
1048
+ handleSuccess(res, extra = {}) {
1049
+ return {
1050
+ ...finalizeOperationResult({
1051
+ success: true,
1052
+ raw: res.data,
1053
+ status: res.status,
1054
+ headers: res.headers
1055
+ }),
1056
+ ...extra
1057
+ };
1058
+ }
1059
+ // See https://axios-http.com/docs/handling-errors
1060
+ handleError(error) {
1061
+ return normalizeTransportFailure(error);
1062
+ }
1063
+ /** Resolves per-call policy against the already-resolved service/adapter default. */
1064
+ resolveThrowOnError(override) {
1065
+ return override ?? this._throwOnError;
1066
+ }
1067
+ wrapGet(url, defaultAxiosRequestConfig = {}) {
1068
+ return this._wrap.wrapGet(url, defaultAxiosRequestConfig);
1069
+ }
1070
+ wrapPost(url, defaultAxiosRequestConfig = {}) {
1071
+ return this._wrap.wrapPost(url, defaultAxiosRequestConfig);
1072
+ }
1073
+ wrapPut(url, defaultAxiosRequestConfig = {}) {
1074
+ return this._wrap.wrapPut(url, defaultAxiosRequestConfig);
1075
+ }
1076
+ wrapPatch(url, defaultAxiosRequestConfig = {}) {
1077
+ return this._wrap.wrapPatch(url, defaultAxiosRequestConfig);
1078
+ }
1079
+ wrapDelete(url, defaultAxiosRequestConfig = {}) {
1080
+ return this._wrap.wrapDelete(url, defaultAxiosRequestConfig);
1081
+ }
1082
+ /**
1083
+ * Public bridge to the per-service success/failure callback pipeline and
1084
+ * `throwOnError` policy. Adapter-internal grouping machinery calls this so
1085
+ * that grouped entries go through the same finalization the direct path
1086
+ * uses (`createResponseHandler`). Returns `res` unchanged on success and
1087
+ * throws `ServiceError` when both `res.success === false` and the
1088
+ * `throwOnError` override (or the service-level default) are enabled.
1089
+ */
1090
+ applyResponseCallbacks(res, throwOnErrorOverride) {
1091
+ const handler = this._handleCallbacks;
1092
+ return handler ? handler(res, throwOnErrorOverride) : res;
1093
+ }
1094
+ /**
1095
+ * Returns a fresh headers object that includes the package-owned
1096
+ * `CACHE_HEADER` set to `"true"` (cache eligible) or `"false"` (bypass)
1097
+ * according to the `ignoreCache` option. The caller's `CACHE_HEADER`
1098
+ * value, if any, wins over the `ignoreCache` default.
1099
+ *
1100
+ * The input `headers` object is **never mutated**: an `AxiosHeaders`
1101
+ * instance is cloned via `.toJSON()` before any value is set, and a
1102
+ * plain-object headers input is shallow-copied. Reusing the same
1103
+ * caller-owned headers across multiple requests therefore has no
1104
+ * hidden side effects, and the order of invocations is irrelevant.
1105
+ */
1106
+ updateHeaders(headers, { ignoreCache }) {
1107
+ const cacheValue = ignoreCache ? "false" : "true";
1108
+ if (!headers) {
1109
+ return { [CACHE_HEADER]: cacheValue };
1110
+ }
1111
+ if (headers instanceof AxiosHeaders4) {
1112
+ if (headers.has(CACHE_HEADER)) return headers;
1113
+ const cloned = new AxiosHeaders4(headers.toJSON());
1114
+ cloned.set(CACHE_HEADER, cacheValue);
1115
+ return cloned;
1116
+ }
1117
+ if (CACHE_HEADER in headers) return headers;
1118
+ return {
1119
+ ...headers,
1120
+ [CACHE_HEADER]: cacheValue
1121
+ };
1122
+ }
1123
+ };
1124
+ var ServiceError = class extends Error {
1125
+ constructor(result) {
1126
+ super(result.message);
1127
+ this.name = "ServiceError";
1128
+ this.success = false;
1129
+ this.raw = result.raw;
1130
+ this.data = null;
1131
+ this.status = result.status;
1132
+ this.headers = result.headers;
1133
+ }
1134
+ };
991
1135
 
992
1136
  // src/services/shared.ts
993
- import { castArray, get, noop, set } from "@web-ts-toolkit/utils";
1137
+ import { castArray, get, noop } from "@web-ts-toolkit/utils";
994
1138
 
995
1139
  // src/enums.ts
996
1140
  var CustomHeaders = /* @__PURE__ */ ((CustomHeaders2) => {
@@ -1067,7 +1211,7 @@ function finalizeRootEntry(query, entry, responseHeaders, service) {
1067
1211
  }
1068
1212
  if (modelService) {
1069
1213
  const fromExisting = op !== "new";
1070
- const persistenceId = op === "read" && query.target === "model" && "id" in query ? query.id : void 0;
1214
+ const persistenceId = (op === "read" || op === "update") && query.target === "model" && "id" in query ? query.id : void 0;
1071
1215
  _data = Model.create(_data, modelService, persistenceId, fromExisting);
1072
1216
  }
1073
1217
  }
@@ -1127,10 +1271,35 @@ var isLegacyListPayload = (value) => {
1127
1271
  }
1128
1272
  return "count" in value && typeof value.count === "number" && "rows" in value && Array.isArray(value.rows);
1129
1273
  };
1130
- var setDefaultObjectProp = (obj, key, value) => {
1131
- if (!get(obj, key)) {
1132
- set(obj, key, value);
1274
+ var cloneDefaultValue = (value) => {
1275
+ if (Array.isArray(value)) {
1276
+ return value.map((item) => cloneDefaultValue(item));
1277
+ }
1278
+ if (value && typeof value === "object") {
1279
+ const cloned = {};
1280
+ for (const [key, item] of Object.entries(value)) {
1281
+ cloned[key] = cloneDefaultValue(item);
1282
+ }
1283
+ return cloned;
1284
+ }
1285
+ return value;
1286
+ };
1287
+ var deepFreeze = (value) => {
1288
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
1289
+ return value;
1290
+ }
1291
+ Object.freeze(value);
1292
+ for (const item of Object.values(value)) {
1293
+ deepFreeze(item);
1294
+ }
1295
+ return value;
1296
+ };
1297
+ var normalizeServiceDefaults = (defaults, objectKeys) => {
1298
+ const normalized = cloneDefaultValue(defaults ?? {});
1299
+ for (const key of objectKeys) {
1300
+ normalized[key] ??= {};
1133
1301
  }
1302
+ return deepFreeze(normalized);
1134
1303
  };
1135
1304
  var ensureListResultCount = (result) => {
1136
1305
  result.totalCount ??= 0;
@@ -1288,7 +1457,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1288
1457
  () => axios3.get(
1289
1458
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1290
1459
  mergeConfig2(reqConfig, { params: {} })
1291
- ).then(handleSuccess).then((result) => {
1460
+ ).then((res) => handleSuccess(res)).then((result) => {
1292
1461
  const rawArray = toArray(result.raw);
1293
1462
  result.raw = rawArray;
1294
1463
  result.count = rawArray.length;
@@ -1322,7 +1491,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1322
1491
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${queryPath}`,
1323
1492
  { filter, select },
1324
1493
  reqConfig
1325
- ).then(handleSuccess).then((result) => {
1494
+ ).then((res) => handleSuccess(res)).then((result) => {
1326
1495
  const rawArray = toArray(result.raw);
1327
1496
  result.raw = rawArray;
1328
1497
  result.count = rawArray.length;
@@ -1356,7 +1525,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1356
1525
  () => axios3.get(
1357
1526
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1358
1527
  mergeConfig2(reqConfig, { params: {} })
1359
- ).then(handleSuccess).then((result) => {
1528
+ ).then((res) => handleSuccess(res)).then((result) => {
1360
1529
  result.data = result.success ? result.raw : null;
1361
1530
  return result;
1362
1531
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1387,7 +1556,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1387
1556
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}/${queryPath}`,
1388
1557
  { select, populate },
1389
1558
  reqConfig
1390
- ).then(handleSuccess).then((result) => {
1559
+ ).then((res) => handleSuccess(res)).then((result) => {
1391
1560
  result.data = result.success ? result.raw : null;
1392
1561
  return result;
1393
1562
  }).catch(handleError).then(
@@ -1419,7 +1588,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1419
1588
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1420
1589
  data,
1421
1590
  mergeConfig2(reqConfig, { params: {} })
1422
- ).then(handleSuccess).then((result) => {
1591
+ ).then((res) => handleSuccess(res)).then((result) => {
1423
1592
  result.data = result.success ? result.raw : null;
1424
1593
  return result;
1425
1594
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1449,7 +1618,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1449
1618
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1450
1619
  data,
1451
1620
  mergeConfig2(reqConfig, { params: {} })
1452
- ).then(handleSuccess).then((result) => {
1621
+ ).then((res) => handleSuccess(res)).then((result) => {
1453
1622
  const rawArray = toArray(result.raw);
1454
1623
  result.raw = rawArray;
1455
1624
  result.count = rawArray.length;
@@ -1481,7 +1650,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1481
1650
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}`,
1482
1651
  data,
1483
1652
  mergeConfig2(reqConfig, { params: {} })
1484
- ).then(handleSuccess).then((result) => {
1653
+ ).then((res) => handleSuccess(res)).then((result) => {
1485
1654
  const rawArray = toArray(result.raw);
1486
1655
  result.raw = rawArray;
1487
1656
  result.count = rawArray.length;
@@ -1503,7 +1672,7 @@ function buildSubDocumentOps(ctx, id, sub) {
1503
1672
  () => axios3.delete(
1504
1673
  `${basePath}/${encodePathSegment(id)}/${encodePathSegment(sub)}/${encodePathSegment(subId)}`,
1505
1674
  reqConfig
1506
- ).then(handleSuccess).then((result) => {
1675
+ ).then((res) => handleSuccess(res)).then((result) => {
1507
1676
  if (result.success) result.data = result.raw;
1508
1677
  return result;
1509
1678
  }).catch(handleError).then((res) => _handleCallbacks(res, throwOnError)),
@@ -1526,9 +1695,7 @@ var ModelService = class extends Service {
1526
1695
  this._modelName = modelName;
1527
1696
  this._queryPath = queryPath;
1528
1697
  this._mutationPath = mutationPath;
1529
- this._defaults = defaults ?? {};
1530
- this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
1531
- [
1698
+ this._defaults = normalizeServiceDefaults(defaults, [
1532
1699
  "listArgs",
1533
1700
  "listOptions",
1534
1701
  "listAdvancedArgs",
@@ -1545,7 +1712,8 @@ var ModelService = class extends Service {
1545
1712
  "upsertOptions",
1546
1713
  "upsertAdvancedArgs",
1547
1714
  "upsertAdvancedOptions"
1548
- ].forEach((key) => setDefaultObjectProp(this._defaults, key, {}));
1715
+ ]);
1716
+ this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
1549
1717
  }
1550
1718
  // ---------------------------------------------------------------------------
1551
1719
  // Collection operations
@@ -1582,7 +1750,7 @@ var ModelService = class extends Service {
1582
1750
  include_extra_headers: includeExtraHeaders
1583
1751
  }
1584
1752
  })
1585
- ).then(this.handleSuccess).then((result) => {
1753
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1586
1754
  return processListResult(
1587
1755
  result,
1588
1756
  { includeCount, includeExtraHeaders },
@@ -1652,7 +1820,7 @@ var ModelService = class extends Service {
1652
1820
  options: { skim, includePermissions, includeCount, includeExtraHeaders, populateAccess }
1653
1821
  },
1654
1822
  reqConfig
1655
- ).then(this.handleSuccess).then((result) => {
1823
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1656
1824
  return processListResult(
1657
1825
  result,
1658
1826
  { includeCount, includeExtraHeaders },
@@ -1689,7 +1857,7 @@ var ModelService = class extends Service {
1689
1857
  const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
1690
1858
  const bulk = Array.isArray(data);
1691
1859
  return makeRequest(
1692
- () => this._axios.post(this._basePath, data, mergeConfig3(reqConfig, { params: { include_permissions: includePermissions } })).then(this.handleSuccess).then((result) => {
1860
+ () => this._axios.post(this._basePath, data, mergeConfig3(reqConfig, { params: { include_permissions: includePermissions } })).then((res) => this.handleSuccess(res)).then((result) => {
1693
1861
  if (result.success) {
1694
1862
  if (bulk) {
1695
1863
  const rows = Array.isArray(result.raw) ? result.raw : [result.raw];
@@ -1733,7 +1901,7 @@ var ModelService = class extends Service {
1733
1901
  `${this._basePath}/${this._mutationPath}`,
1734
1902
  { data, select, populate, tasks, options: { includePermissions, populateAccess } },
1735
1903
  reqConfig
1736
- ).then(this.handleSuccess).then((result) => {
1904
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1737
1905
  if (result.success) {
1738
1906
  if (bulk) {
1739
1907
  const rows = Array.isArray(result.raw) ? result.raw : [result.raw];
@@ -1775,7 +1943,7 @@ var ModelService = class extends Service {
1775
1943
  mergeConfig3(reqConfig, {
1776
1944
  params: { returning_all: returningAll, include_permissions: includePermissions }
1777
1945
  })
1778
- ).then(this.handleSuccess).then((result) => {
1946
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1779
1947
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
1780
1948
  return result;
1781
1949
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1815,7 +1983,7 @@ var ModelService = class extends Service {
1815
1983
  options: { returningAll, includePermissions, populateAccess }
1816
1984
  },
1817
1985
  reqConfig
1818
- ).then(this.handleSuccess).then((result) => {
1986
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1819
1987
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
1820
1988
  return result;
1821
1989
  }).catch(this.handleError).then(
@@ -1841,7 +2009,7 @@ var ModelService = class extends Service {
1841
2009
  delete(identifier, axiosRequestConfig) {
1842
2010
  const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
1843
2011
  return makeRequest(
1844
- () => this._axios.delete(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then(this.handleSuccess).then((result) => {
2012
+ () => this._axios.delete(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1845
2013
  if (result.success) result.data = result.raw;
1846
2014
  return result;
1847
2015
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1861,9 +2029,9 @@ var ModelService = class extends Service {
1861
2029
  );
1862
2030
  }
1863
2031
  new(axiosRequestConfig) {
1864
- const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {});
2032
+ const { throwOnError, ...reqConfig } = cloneConfigWithCacheBypass(axiosRequestConfig ?? {}, false);
1865
2033
  return makeRequest(
1866
- () => this._axios.get(`${this._basePath}/new`, reqConfig).then(this.handleSuccess).then((result) => {
2034
+ () => this._axios.get(`${this._basePath}/new`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1867
2035
  if (result.success) {
1868
2036
  delete result.raw._id;
1869
2037
  result.data = Model.create(result.raw, this);
@@ -1887,7 +2055,7 @@ var ModelService = class extends Service {
1887
2055
  distinct(field, axiosRequestConfig) {
1888
2056
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1889
2057
  return makeRequest(
1890
- () => this._axios.get(`${this._basePath}/distinct/${encodePathSegment(field)}`, reqConfig).then(this.handleSuccess).then((result) => {
2058
+ () => this._axios.get(`${this._basePath}/distinct/${encodePathSegment(field)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1891
2059
  if (result.success) result.data = result.raw;
1892
2060
  return result;
1893
2061
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1909,7 +2077,7 @@ var ModelService = class extends Service {
1909
2077
  distinctAdvanced(field, conditions, axiosRequestConfig) {
1910
2078
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1911
2079
  return makeRequest(
1912
- () => this._axios.post(`${this._basePath}/distinct/${encodePathSegment(field)}`, { filter: conditions }, reqConfig).then(this.handleSuccess).then((result) => {
2080
+ () => this._axios.post(`${this._basePath}/distinct/${encodePathSegment(field)}`, { filter: conditions }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1913
2081
  if (result.success) result.data = result.raw;
1914
2082
  return result;
1915
2083
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1932,7 +2100,7 @@ var ModelService = class extends Service {
1932
2100
  count(axiosRequestConfig) {
1933
2101
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1934
2102
  return makeRequest(
1935
- () => this._axios.get(`${this._basePath}/count`, reqConfig).then(this.handleSuccess).then((result) => {
2103
+ () => this._axios.get(`${this._basePath}/count`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1936
2104
  if (result.success) result.data = result.raw;
1937
2105
  return result;
1938
2106
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1953,7 +2121,7 @@ var ModelService = class extends Service {
1953
2121
  countAdvanced(filter, axiosRequestConfig) {
1954
2122
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
1955
2123
  return makeRequest(
1956
- () => this._axios.post(`${this._basePath}/count`, { filter }, reqConfig).then(this.handleSuccess).then((result) => {
2124
+ () => this._axios.post(`${this._basePath}/count`, { filter }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
1957
2125
  if (result.success) result.data = result.raw;
1958
2126
  return result;
1959
2127
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -1990,7 +2158,7 @@ var ModelService = class extends Service {
1990
2158
  mergeConfig3(reqConfig, {
1991
2159
  params: { include_permissions: includePermissions, try_list: tryList }
1992
2160
  })
1993
- ).then(this.handleSuccess).then((result) => {
2161
+ ).then((res) => this.handleSuccess(res)).then((result) => {
1994
2162
  result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
1995
2163
  return result;
1996
2164
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -2040,7 +2208,7 @@ var ModelService = class extends Service {
2040
2208
  options: { skim, includePermissions, tryList, populateAccess }
2041
2209
  },
2042
2210
  reqConfig
2043
- ).then(this.handleSuccess).then((result) => {
2211
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2044
2212
  result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2045
2213
  return result;
2046
2214
  }).catch(this.handleError).then(
@@ -2096,7 +2264,7 @@ var ModelService = class extends Service {
2096
2264
  options: { skim, includePermissions, tryList, populateAccess }
2097
2265
  },
2098
2266
  reqConfig
2099
- ).then(this.handleSuccess).then((result) => {
2267
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2100
2268
  result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2101
2269
  return result;
2102
2270
  }).catch(this.handleError).then(
@@ -2133,8 +2301,8 @@ var ModelService = class extends Service {
2133
2301
  mergeConfig3(reqConfig, {
2134
2302
  params: { returning_all: returningAll, include_permissions: includePermissions }
2135
2303
  })
2136
- ).then(this.handleSuccess).then((result) => {
2137
- result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2304
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2305
+ result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2138
2306
  return result;
2139
2307
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
2140
2308
  {
@@ -2174,8 +2342,8 @@ var ModelService = class extends Service {
2174
2342
  options: { returningAll, includePermissions, populateAccess }
2175
2343
  },
2176
2344
  reqConfig
2177
- ).then(this.handleSuccess).then((result) => {
2178
- result.data = result.success ? Model.create(result.raw, this, void 0, true) : null;
2345
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2346
+ result.data = result.success ? Model.create(result.raw, this, identifier, true) : null;
2179
2347
  return result;
2180
2348
  }).catch(this.handleError).then(
2181
2349
  (res) => this._handleCallbacks(res, throwOnError)
@@ -2234,9 +2402,7 @@ var DataService = class extends Service {
2234
2402
  super(axios3, basePath, throwOnError);
2235
2403
  this._dataName = dataName;
2236
2404
  this._queryPath = queryPath;
2237
- this._defaults = defaults ?? {};
2238
- this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
2239
- [
2405
+ this._defaults = normalizeServiceDefaults(defaults, [
2240
2406
  "listArgs",
2241
2407
  "listOptions",
2242
2408
  "listAdvancedArgs",
@@ -2244,7 +2410,8 @@ var DataService = class extends Service {
2244
2410
  "readOptions",
2245
2411
  "readAdvancedArgs",
2246
2412
  "readAdvancedOptions"
2247
- ].forEach((key) => setDefaultObjectProp(this._defaults, key, {}));
2413
+ ]);
2414
+ this._handleCallbacks = createResponseHandler(onSuccess, onFailure, throwOnError);
2248
2415
  }
2249
2416
  // ---------------------------------------------------------------------------
2250
2417
  // Collection operations
@@ -2276,7 +2443,7 @@ var DataService = class extends Service {
2276
2443
  include_extra_headers: includeExtraHeaders
2277
2444
  }
2278
2445
  })
2279
- ).then(this.handleSuccess).then((result) => {
2446
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2280
2447
  return processListResult(result, { includeCount, includeExtraHeaders });
2281
2448
  }).catch(this.handleError).then(ensureListResultCount).then((res) => this._handleCallbacks(res, throwOnError)),
2282
2449
  {
@@ -2326,7 +2493,7 @@ var DataService = class extends Service {
2326
2493
  options: { includeCount, includeExtraHeaders }
2327
2494
  },
2328
2495
  reqConfig
2329
- ).then(this.handleSuccess).then((result) => {
2496
+ ).then((res) => this.handleSuccess(res)).then((result) => {
2330
2497
  return processListResult(result, {
2331
2498
  includeCount,
2332
2499
  includeExtraHeaders
@@ -2358,7 +2525,7 @@ var DataService = class extends Service {
2358
2525
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2359
2526
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2360
2527
  return makeRequest(
2361
- () => this._axios.get(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then(this.handleSuccess).then((result) => {
2528
+ () => this._axios.get(`${this._basePath}/${encodePathSegment(identifier)}`, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2362
2529
  if (result.success) result.data = result.raw;
2363
2530
  return result;
2364
2531
  }).catch(this.handleError).then((res) => this._handleCallbacks(res, throwOnError)),
@@ -2384,7 +2551,7 @@ var DataService = class extends Service {
2384
2551
  const { throwOnError, ...reqConfig } = axiosRequestConfig ?? {};
2385
2552
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2386
2553
  return makeRequest(
2387
- () => this._axios.post(`${this._basePath}/${this._queryPath}/${encodePathSegment(identifier)}`, { select }, reqConfig).then(this.handleSuccess).then((result) => {
2554
+ () => this._axios.post(`${this._basePath}/${this._queryPath}/${encodePathSegment(identifier)}`, { select }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2388
2555
  if (result.success) result.data = result.raw;
2389
2556
  return result;
2390
2557
  }).catch(this.handleError).then(
@@ -2413,7 +2580,7 @@ var DataService = class extends Service {
2413
2580
  reqConfig.headers = this.updateHeaders(reqConfig.headers, { ignoreCache });
2414
2581
  const _filter = replaceSubQuery(filter);
2415
2582
  return makeRequest(
2416
- () => this._axios.post(`${this._basePath}/${this._queryPath}/__filter`, { filter: _filter, select }, reqConfig).then(this.handleSuccess).then((result) => {
2583
+ () => this._axios.post(`${this._basePath}/${this._queryPath}/__filter`, { filter: _filter, select }, reqConfig).then((res) => this.handleSuccess(res)).then((result) => {
2417
2584
  if (result.success) result.data = result.raw;
2418
2585
  return result;
2419
2586
  }).catch(this.handleError).then(
@@ -2457,7 +2624,71 @@ var noopCacheController = {
2457
2624
  dispose: () => {
2458
2625
  }
2459
2626
  };
2460
- var serializeRequestConfig = (config) => JSON.stringify(normalizeConfigValue(config ?? {}));
2627
+ var noopResponseCallback = () => {
2628
+ };
2629
+ var ROOT_MUTATION_OPS = /* @__PURE__ */ new Set([
2630
+ "create",
2631
+ "update",
2632
+ "upsert",
2633
+ "delete",
2634
+ "subCreate",
2635
+ "subUpdate",
2636
+ "subBulkUpdate",
2637
+ "subDelete"
2638
+ ]);
2639
+ var serializeRequestConfig = (config) => JSON.stringify(normalizeGroupedRequestConfig(removeCacheInvalidationSignal(config ?? {})));
2640
+ var createMalformedRootResponseError = (message) => {
2641
+ const error = new Error(message);
2642
+ error.name = "MalformedRootResponseError";
2643
+ return error;
2644
+ };
2645
+ var validateRootResponseEntries = (data, expectedLength) => {
2646
+ if (!Array.isArray(data)) {
2647
+ throw createMalformedRootResponseError("Malformed root response: expected an array");
2648
+ }
2649
+ if (data.length !== expectedLength) {
2650
+ throw createMalformedRootResponseError(
2651
+ `Malformed root response: expected ${expectedLength} entries but received ${data.length}`
2652
+ );
2653
+ }
2654
+ return data.map((entry, index) => {
2655
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
2656
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} is not an object`);
2657
+ }
2658
+ const { result, message, statusCode, op } = entry;
2659
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
2660
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} result is not an object`);
2661
+ }
2662
+ if (typeof result.success !== "boolean") {
2663
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} result.success is not boolean`);
2664
+ }
2665
+ if (typeof statusCode !== "number" || !Number.isFinite(statusCode)) {
2666
+ throw createMalformedRootResponseError(
2667
+ `Malformed root response: entry ${index} statusCode is not a finite number`
2668
+ );
2669
+ }
2670
+ if (message != null && typeof message !== "string") {
2671
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} message is not a string`);
2672
+ }
2673
+ if (op != null && typeof op !== "string") {
2674
+ throw createMalformedRootResponseError(`Malformed root response: entry ${index} op is not a string`);
2675
+ }
2676
+ return {
2677
+ result,
2678
+ message: message ?? "",
2679
+ statusCode,
2680
+ op
2681
+ };
2682
+ });
2683
+ };
2684
+ var applyRootTransportFailure = (proms, groupThrowOnError, error) => {
2685
+ const failures = proms.map((prom) => finalizeRootTransportFailure(prom.__query, error));
2686
+ return applyGroupCallbacks(
2687
+ failures,
2688
+ proms.map((p) => p.__service),
2689
+ groupThrowOnError ?? false
2690
+ );
2691
+ };
2461
2692
  var isObjectRecord = (value) => value != null && typeof value === "object" && !Array.isArray(value);
2462
2693
  var mergeServiceDefaults = (adapterDefaults, serviceDefaults) => {
2463
2694
  if (!adapterDefaults && !serviceDefaults) return void 0;
@@ -2494,7 +2725,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2494
2725
  dataDefaults: adapterDataDefaults
2495
2726
  } = adapterOptions ?? {};
2496
2727
  const cacheController = cacheTTL > 0 ? useCacheInterceptors(instance, {
2497
- ttl: cacheTTL,
2728
+ ttlMs: cacheTTL,
2498
2729
  capacity: cacheCapacity,
2499
2730
  withCredentialsDefault: Boolean(instance.defaults.withCredentials),
2500
2731
  partitionForRequest: cachePartition
@@ -2530,8 +2761,8 @@ function createAdapter(axiosConfig, adapterOptions) {
2530
2761
  basePath,
2531
2762
  queryPath,
2532
2763
  mutationPath,
2533
- onSuccess: onSuccess ?? onSuccessRoot,
2534
- onFailure: onFailure ?? onFailureRoot,
2764
+ onSuccess: onSuccess ?? onSuccessRoot ?? noopResponseCallback,
2765
+ onFailure: onFailure ?? onFailureRoot ?? noopResponseCallback,
2535
2766
  throwOnError: throwOnError ?? throwOnErrorRoot ?? false
2536
2767
  },
2537
2768
  mergeServiceDefaults(adapterModelDefaults, defaults)
@@ -2545,8 +2776,8 @@ function createAdapter(axiosConfig, adapterOptions) {
2545
2776
  dataName,
2546
2777
  basePath,
2547
2778
  queryPath,
2548
- onSuccess: onSuccess ?? onSuccessRoot,
2549
- onFailure: onFailure ?? onFailureRoot,
2779
+ onSuccess: onSuccess ?? onSuccessRoot ?? noopResponseCallback,
2780
+ onFailure: onFailure ?? onFailureRoot ?? noopResponseCallback,
2550
2781
  throwOnError: throwOnError ?? throwOnErrorRoot ?? false
2551
2782
  },
2552
2783
  mergeServiceDefaults(adapterDataDefaults, defaults)
@@ -2577,7 +2808,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2577
2808
  if (sharedConfigKey != null && sharedConfigKey !== configKey) {
2578
2809
  throw new Error("Grouped requests must share the same axios request config");
2579
2810
  }
2580
- sharedConfig = prom.__requestConfig ?? {};
2811
+ sharedConfig ??= prom.__requestConfig ?? {};
2581
2812
  sharedConfigKey = configKey;
2582
2813
  const query = { ...prom.__query };
2583
2814
  if (query.target === "model") {
@@ -2601,14 +2832,20 @@ function createAdapter(axiosConfig, adapterOptions) {
2601
2832
  }
2602
2833
  throw error;
2603
2834
  }
2604
- const result = await instance.post(rootRouterPath, defs, sharedConfig ?? {}).then(
2835
+ const groupConfig = removeCacheInvalidationSignal(sharedConfig ?? {});
2836
+ const result = await instance.post(rootRouterPath, defs, groupConfig).then(
2605
2837
  (res) => {
2606
- const rawEntries = res.data.map(({ result: result2, message, statusCode, op }) => ({
2607
- result: result2,
2608
- message,
2609
- statusCode,
2610
- op
2611
- }));
2838
+ let rawEntries;
2839
+ try {
2840
+ rawEntries = validateRootResponseEntries(res.data, proms.length);
2841
+ } catch (error) {
2842
+ return applyRootTransportFailure(proms, groupThrowOnError, error);
2843
+ }
2844
+ if (rawEntries.some(
2845
+ (entry, index) => ROOT_MUTATION_OPS.has(proms[index].__query.op) && entry.result?.success === true && entry.statusCode >= 200 && entry.statusCode < 300
2846
+ )) {
2847
+ cacheController.clear();
2848
+ }
2612
2849
  const finalized = rawEntries.map(
2613
2850
  (rawEntry, index) => finalizeRootEntry(proms[index].__query, rawEntry, {}, proms[index].__service)
2614
2851
  );
@@ -2619,12 +2856,7 @@ function createAdapter(axiosConfig, adapterOptions) {
2619
2856
  );
2620
2857
  },
2621
2858
  (error) => {
2622
- const failures = proms.map((prom) => finalizeRootTransportFailure(prom.__query, error));
2623
- return applyGroupCallbacks(
2624
- failures,
2625
- proms.map((p) => p.__service),
2626
- groupThrowOnError ?? false
2627
- );
2859
+ return applyRootTransportFailure(proms, groupThrowOnError, error);
2628
2860
  }
2629
2861
  );
2630
2862
  return result;