@jsenv/navi 0.29.98 → 0.29.100

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.
@@ -11938,6 +11938,39 @@ const reportErrorIfNobodyDisplaysIt = (error, { action } = {}) => {
11938
11938
 
11939
11939
  const SYMBOL_OBJECT_SIGNAL = Symbol.for("navi_object_signal");
11940
11940
 
11941
+ /*
11942
+ * Actions: async callbacks wrapped in reactive state.
11943
+ *
11944
+ * An action owns a set of signals (params, runningState, error, value, data)
11945
+ * and moves through IDLE → RUNNING → COMPLETED / FAILED / ABORTED (see
11946
+ * action_run_states.js). `createAction(callback)` returns the root action;
11947
+ * `.bindParams(params)` derives child actions (one per params value, cached),
11948
+ * and binding a signal (or an object containing signals) returns an action
11949
+ * *proxy* that retargets itself to the right child action as the signal
11950
+ * changes (see createActionProxyFromSignal).
11951
+ *
11952
+ * How things run: prerun/run/rerun/reset never execute the action directly —
11953
+ * they go through `dispatchActions`, which the navigation integration can
11954
+ * replace via `setActionDispatcher` so that every action update participates
11955
+ * in the browser navigation lifecycle (abort signals, navigation events).
11956
+ * The default dispatcher calls `updateActions`, the single entry point that
11957
+ * resolves priorities between the four operation sets (reset > rerun > run >
11958
+ * prerun) and performs them.
11959
+ *
11960
+ * Memory design (the surprising part): nothing here keeps actions alive.
11961
+ * Child actions are held through ephemerons (createJsValueWeakMap) so a child
11962
+ * and its params are garbage-collected together, running actions live in
11963
+ * iterable *weak* sets, and property/signal mirroring uses weakEffect. Two
11964
+ * consequences to be aware of:
11965
+ * - an action can exist in several places only if everyone shares the same
11966
+ * instance (the caches above are what makes lookups return it);
11967
+ * - prerun actions may have no other reference yet, so
11968
+ * prerunProtectionRegistry pins them for a few minutes.
11969
+ *
11970
+ * An action run never throws: failures land in errorSignal and are reported
11971
+ * once, by one rule, in action_error_report.js (see the comment in onRunError).
11972
+ */
11973
+
11941
11974
  let DEBUG$1 = false;
11942
11975
  const enableDebugActions = () => {
11943
11976
  DEBUG$1 = true;
@@ -11976,7 +12009,7 @@ const getActionDispatcher = () => dispatchActions;
11976
12009
  const rerunActions = async (actionSet, options) => {
11977
12010
  return dispatchActions({
11978
12011
  rerunSet: actionSet,
11979
- reason: "rerunActions was calle",
12012
+ reason: "rerunActions was called",
11980
12013
  ...options,
11981
12014
  });
11982
12015
  };
@@ -11995,7 +12028,7 @@ const rerunActions = async (actionSet, options) => {
11995
12028
  */
11996
12029
  const prerunProtectionRegistry = (() => {
11997
12030
  const protectedActionMap = new Map(); // action -> { timeoutId, timestamp }
11998
- const PROTECTION_DURATION = 5 * 60 * 1000; // 5 minutes en millisecondes
12031
+ const PROTECTION_DURATION = 5 * 60 * 1000; // 5 minutes
11999
12032
 
12000
12033
  const unprotect = (action) => {
12001
12034
  const protection = protectedActionMap.get(action);
@@ -12009,7 +12042,7 @@ const prerunProtectionRegistry = (() => {
12009
12042
 
12010
12043
  return {
12011
12044
  protect(action) {
12012
- // Si déjà protégée, étendre la protection
12045
+ // already protected: extend the protection
12013
12046
  if (protectedActionMap.has(action)) {
12014
12047
  const existing = protectedActionMap.get(action);
12015
12048
  clearTimeout(existing.timeoutId);
@@ -12029,29 +12062,11 @@ const prerunProtectionRegistry = (() => {
12029
12062
  },
12030
12063
 
12031
12064
  unprotect,
12032
-
12033
- isProtected(action) {
12034
- return protectedActionMap.has(action);
12035
- },
12036
-
12037
- // Pour debugging
12038
- getProtectedActions() {
12039
- return Array.from(protectedActionMap.keys());
12040
- },
12041
-
12042
- // Nettoyage manuel si nécessaire
12043
- clear() {
12044
- for (const [, protection] of protectedActionMap) {
12045
- clearTimeout(protection.timeoutId);
12046
- }
12047
- protectedActionMap.clear();
12048
- },
12049
12065
  };
12050
12066
  })();
12051
12067
 
12052
12068
  const formatActionSet = (actionSet, prefix = "") => {
12053
- let message = "";
12054
- message += `${prefix}`;
12069
+ let message = prefix;
12055
12070
  for (const action of actionSet) {
12056
12071
  message += "\n";
12057
12072
  message += prefixFirstAndIndentRemainingLines(String(action), {
@@ -12386,7 +12401,6 @@ ${lines.join("\n")}`);
12386
12401
  };
12387
12402
 
12388
12403
  const NO_PARAMS = { __no_params__: true };
12389
- const initialParamsDefault = NO_PARAMS;
12390
12404
  const mergeActionParams = (currentParams, newParams) => {
12391
12405
  if (currentParams === NO_PARAMS) {
12392
12406
  return newParams;
@@ -12434,7 +12448,7 @@ const createAction = (callback, rootOptions = {}) => {
12434
12448
  } = options;
12435
12449
  if (!Object.hasOwn(options, "params")) {
12436
12450
  // even undefined should be respected it's only when not provided at all we use default
12437
- params = initialParamsDefault;
12451
+ params = NO_PARAMS;
12438
12452
  }
12439
12453
  if (value === undefined && data !== undefined) {
12440
12454
  value = data;
@@ -12447,11 +12461,7 @@ const createAction = (callback, rootOptions = {}) => {
12447
12461
  const errorSignal = signal(error);
12448
12462
  const valueSignal = signal(valueInitial);
12449
12463
  const dataSignal = valueToData
12450
- ? computed(() => {
12451
- const value = valueSignal.value;
12452
- const data = valueToData(value);
12453
- return data;
12454
- })
12464
+ ? computed(() => valueToData(valueSignal.value))
12455
12465
  : valueSignal;
12456
12466
 
12457
12467
  const prerun = (options) => {
@@ -12484,7 +12494,7 @@ const createAction = (callback, rootOptions = {}) => {
12484
12494
  return dispatchSingleAction(action, "reset", options);
12485
12495
  };
12486
12496
  const abort = (reason) => {
12487
- if (runningState !== RUNNING) {
12497
+ if (runningStateSignal.peek() !== RUNNING) {
12488
12498
  return false;
12489
12499
  }
12490
12500
  const actionAbort = actionAbortMap.get(action);
@@ -12510,7 +12520,7 @@ const createAction = (callback, rootOptions = {}) => {
12510
12520
  */
12511
12521
  const childActionWeakMap = createJsValueWeakMap();
12512
12522
  const _bindParams = (newParamsOrSignal, options = {}) => {
12513
- // CAS 1: Signal direct -> proxy
12523
+ // Case 1: a signal proxy that retargets as the signal changes
12514
12524
  if (isSignal(newParamsOrSignal)) {
12515
12525
  const combinedParamsSignal = computed(() => {
12516
12526
  const newParams = newParamsOrSignal.value;
@@ -12524,7 +12534,7 @@ const createAction = (callback, rootOptions = {}) => {
12524
12534
  );
12525
12535
  }
12526
12536
 
12527
- // CAS 2: Objet -> vérifier s'il contient des signals
12537
+ // Case 2: a plain object child action, or proxy when it contains signals
12528
12538
  if (isPlainObject$1(newParamsOrSignal)) {
12529
12539
  const staticParams = {};
12530
12540
  const signalMap = new Map();
@@ -12545,7 +12555,7 @@ const createAction = (callback, rootOptions = {}) => {
12545
12555
  }
12546
12556
 
12547
12557
  if (signalMap.size === 0) {
12548
- // Pas de signals, merge statique normal
12558
+ // no signals: plain static merge
12549
12559
  if (
12550
12560
  params === null ||
12551
12561
  typeof params !== "object" ||
@@ -12563,24 +12573,27 @@ const createAction = (callback, rootOptions = {}) => {
12563
12573
  });
12564
12574
  }
12565
12575
 
12566
- // Combiner avec les params existants pour les valeurs statiques
12567
- const paramsSignal = computed(() => {
12568
- const params = {};
12576
+ const combinedParamsSignal = computed(() => {
12577
+ const combinedParams = {};
12569
12578
  for (const key of keyArray) {
12570
12579
  const signalForThisKey = signalMap.get(key);
12571
12580
  if (signalForThisKey) {
12572
12581
  // eslint-disable-next-line signals/no-conditional-value-read
12573
- params[key] = signalForThisKey.value;
12582
+ combinedParams[key] = signalForThisKey.value;
12574
12583
  } else {
12575
- params[key] = staticParams[key];
12584
+ combinedParams[key] = staticParams[key];
12576
12585
  }
12577
12586
  }
12578
- return params;
12587
+ return combinedParams;
12579
12588
  });
12580
- return createActionProxyFromSignal(action, paramsSignal, options);
12589
+ return createActionProxyFromSignal(
12590
+ action,
12591
+ combinedParamsSignal,
12592
+ options,
12593
+ );
12581
12594
  }
12582
12595
 
12583
- // CAS 3: Primitive or objects like DOMEvents etc -> action enfant
12596
+ // Case 3: a primitive or non-plain object (DOM event, …) → child action
12584
12597
  return createChildAction({
12585
12598
  params: newParamsOrSignal,
12586
12599
  ...options,
@@ -12613,7 +12626,6 @@ const createAction = (callback, rootOptions = {}) => {
12613
12626
  return childAction;
12614
12627
  };
12615
12628
 
12616
- // ✅ Implement matchAllSelfOrDescendant
12617
12629
  const matchAllSelfOrDescendant = (predicate, { includeProxies } = {}) => {
12618
12630
  const matches = [];
12619
12631
 
@@ -12648,32 +12660,31 @@ const createAction = (callback, rootOptions = {}) => {
12648
12660
  generateActionCallSource(name, params),
12649
12661
  );
12650
12662
 
12651
- {
12652
- // Create the action as a function that can be called directly
12653
- action = function actionFunction(...args) {
12654
- if (args.length === 0) {
12655
- return action.rerun();
12656
- }
12657
- const boundAction = bindParams(...args);
12658
- return boundAction.rerun();
12659
- };
12660
- Object.defineProperty(action, "name", {
12661
- configurable: true,
12662
- get() {
12663
- return actionNameSignal.value;
12664
- },
12665
- });
12666
- Object.defineProperty(action, "callSource", {
12667
- configurable: true,
12668
- get() {
12669
- return actionCallSourceSignal.value;
12670
- },
12671
- set(v) {
12672
- actionCallSourceSignal.value = v;
12673
- },
12674
- });
12675
- actionWeakMap.set(action, action);
12676
- }
12663
+ // The action is a callable: `ACTION(params)` is `ACTION.bindParams(params).rerun()`
12664
+ action = function actionFunction(...args) {
12665
+ if (args.length === 0) {
12666
+ return action.rerun();
12667
+ }
12668
+ const boundAction = bindParams(...args);
12669
+ return boundAction.rerun();
12670
+ };
12671
+ Object.defineProperty(action, "name", {
12672
+ configurable: true,
12673
+ get() {
12674
+ return actionNameSignal.value;
12675
+ },
12676
+ });
12677
+ Object.defineProperty(action, "callSource", {
12678
+ configurable: true,
12679
+ get() {
12680
+ return actionCallSourceSignal.value;
12681
+ },
12682
+ set(v) {
12683
+ actionCallSourceSignal.value = v;
12684
+ },
12685
+ });
12686
+ // makes createAction(anAction) return the action itself
12687
+ actionWeakMap.set(action, action);
12677
12688
 
12678
12689
  // Assign all the action properties and methods to the function
12679
12690
  Object.assign(action, {
@@ -12695,7 +12706,7 @@ const createAction = (callback, rootOptions = {}) => {
12695
12706
  reset,
12696
12707
  abort,
12697
12708
  bindParams,
12698
- matchAllSelfOrDescendant, // ✅ Add the new method
12709
+ matchAllSelfOrDescendant,
12699
12710
  replaceParams: (newParams) => {
12700
12711
  const currentParams = paramsSignal.value;
12701
12712
  const nextParams = mergeActionParams(currentParams, newParams);
@@ -12723,7 +12734,7 @@ const createAction = (callback, rootOptions = {}) => {
12723
12734
  toString: () => action.callSource,
12724
12735
  meta,
12725
12736
  debug: (...args) => {
12726
- if (!meta.debug || DEBUG$1) {
12737
+ if (!meta.debug && !DEBUG$1) {
12727
12738
  return;
12728
12739
  }
12729
12740
  console.debug(...args);
@@ -12738,7 +12749,8 @@ const createAction = (callback, rootOptions = {}) => {
12738
12749
  });
12739
12750
  Object.preventExtensions(action);
12740
12751
 
12741
- // Effects pour synchroniser les propriétés
12752
+ // Mirror signals into plain properties (action.error, action.data, …)
12753
+ // so non-reactive code can read them without subscribing.
12742
12754
  {
12743
12755
  weakEffect([action], (actionRef) => {
12744
12756
  isPrerun = isPrerunSignal.value;
@@ -12764,7 +12776,6 @@ const createAction = (callback, rootOptions = {}) => {
12764
12776
  });
12765
12777
  }
12766
12778
 
12767
- // Propriétés privées
12768
12779
  {
12769
12780
  const ui = {
12770
12781
  renderLoaded: null,
@@ -13043,8 +13054,8 @@ const createAction = (callback, rootOptions = {}) => {
13043
13054
  * @param {boolean} options.rerunOnChange - Ensures the action is rerun every time a signal value is modified.
13044
13055
  * This enables live updates - for example, performing an HTTP GET request every time
13045
13056
  * a list of filters changes, providing real-time results without user interaction.
13046
- * @param {boolean} options.inheritData - When true, each new target action starts fresh with no inherited state.
13047
- * By default (false), the proxy carries over the previous target's value and error into the new action.
13057
+ * @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
13058
+ * By default (true), the proxy carries over the previous target's value and error into the new action.
13048
13059
  * This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
13049
13060
  * new action completes, not when it starts loading. Code that needs to distinguish loading state can still
13050
13061
  * check `action.runningState`, while code that just reads `action.data` always sees the most recent
@@ -13138,6 +13149,7 @@ const createActionProxyFromSignal = (
13138
13149
  currentAction = actionTarget;
13139
13150
  currentActionPrivateProperties = getActionPrivateProperties(actionTarget);
13140
13151
  }
13152
+
13141
13153
  actionTargetPreviousWeakRef = actionTarget
13142
13154
  ? new WeakRef(actionTarget)
13143
13155
  : null;
@@ -13163,25 +13175,22 @@ const createActionProxyFromSignal = (
13163
13175
 
13164
13176
  const nameSignal = signal(action.name);
13165
13177
  const callSourceSignal = signal(`[Proxy] ${action.callSource}`);
13166
- let actionProxy;
13167
- {
13168
- actionProxy = function actionProxyFunction() {
13169
- return actionProxy.rerun();
13170
- };
13171
- Object.defineProperty(actionProxy, "name", {
13172
- configurable: true,
13173
- get() {
13174
- return nameSignal.value;
13175
- },
13176
- });
13177
- Object.defineProperty(actionProxy, "callSource", {
13178
- configurable: true,
13179
- get() {
13180
- return callSourceSignal.value;
13181
- },
13182
- });
13183
- actionWeakMap.set(actionProxy, actionProxy);
13184
- }
13178
+ const actionProxy = function actionProxyFunction() {
13179
+ return actionProxy.rerun();
13180
+ };
13181
+ Object.defineProperty(actionProxy, "name", {
13182
+ configurable: true,
13183
+ get() {
13184
+ return nameSignal.value;
13185
+ },
13186
+ });
13187
+ Object.defineProperty(actionProxy, "callSource", {
13188
+ configurable: true,
13189
+ get() {
13190
+ return callSourceSignal.value;
13191
+ },
13192
+ });
13193
+ actionWeakMap.set(actionProxy, actionProxy);
13185
13194
 
13186
13195
  // Create our own signal for params that we control completely
13187
13196
  const proxyParamsSignal = signal(paramsSignal.value);
@@ -13384,11 +13393,6 @@ const isPlainObject$1 = (obj) => {
13384
13393
  );
13385
13394
  };
13386
13395
 
13387
- const COMPLETED_ACTION = createAction(() => undefined, {
13388
- name: "ACTION.COMPLETED",
13389
- });
13390
- getActionPrivateProperties(COMPLETED_ACTION).performRun({});
13391
-
13392
13396
  // used by form elements such as <input>, <select>, <textarea> to have their own action bound to a single parameter
13393
13397
  // when inside a <form> the form params are updated when the form element single param is updated
13394
13398
  const useActionBoundToOneParam = (action, paramsSignal) => {
@@ -13410,19 +13414,27 @@ const useAction = (action, paramsSignal) => {
13410
13414
  };
13411
13415
 
13412
13416
  const useBoundAction = (action, actionParamsSignal) => {
13413
- const actionRef = useRef();
13417
+ // The cache gives an inline function a stable action identity across renders.
13418
+ // That identity is only wanted while `action` stays the same kind
13419
+ // (function to function); when the kind changes — none ↔ function ↔ action
13420
+ // object — each branch clears the other kind's refs so the control picks up
13421
+ // its new role instead of the action it was born with.
13422
+ const noopActionRef = useRef();
13423
+ const actionFromFunctionRef = useRef();
13414
13424
  const actionCallbackRef = useRef();
13415
13425
 
13416
13426
  if (!action) {
13417
- const existingAction = actionRef.current;
13418
- if (existingAction) {
13419
- return existingAction;
13427
+ actionFromFunctionRef.current = undefined;
13428
+ actionCallbackRef.current = undefined;
13429
+ const existingNoopAction = noopActionRef.current;
13430
+ if (existingNoopAction) {
13431
+ return existingNoopAction;
13420
13432
  }
13421
13433
  const noopAction = createAction(() => {}, { params: undefined });
13422
13434
  const noopActionBound = actionParamsSignal
13423
13435
  ? noopAction.bindParams(actionParamsSignal)
13424
13436
  : noopAction;
13425
- actionRef.current = noopActionBound;
13437
+ noopActionRef.current = noopActionBound;
13426
13438
  return noopActionBound;
13427
13439
  }
13428
13440
  const isFunction = typeof action === "function";
@@ -13433,7 +13445,7 @@ const useBoundAction = (action, actionParamsSignal) => {
13433
13445
  }
13434
13446
  if (isFunctionButNotAnActionFunction(action)) {
13435
13447
  actionCallbackRef.current = action;
13436
- const existingAction = actionRef.current;
13448
+ const existingAction = actionFromFunctionRef.current;
13437
13449
  if (existingAction) {
13438
13450
  return existingAction;
13439
13451
  }
@@ -13449,14 +13461,16 @@ const useBoundAction = (action, actionParamsSignal) => {
13449
13461
  },
13450
13462
  );
13451
13463
  if (!actionParamsSignal) {
13452
- actionRef.current = actionFromFunction;
13464
+ actionFromFunctionRef.current = actionFromFunction;
13453
13465
  return actionFromFunction;
13454
13466
  }
13455
13467
  const actionBoundToParams =
13456
13468
  actionFromFunction.bindParams(actionParamsSignal);
13457
- actionRef.current = actionBoundToParams;
13469
+ actionFromFunctionRef.current = actionBoundToParams;
13458
13470
  return actionBoundToParams;
13459
13471
  }
13472
+ actionFromFunctionRef.current = undefined;
13473
+ actionCallbackRef.current = undefined;
13460
13474
  if (actionParamsSignal) {
13461
13475
  return action.bindParams(actionParamsSignal);
13462
13476
  }
@@ -34602,7 +34616,7 @@ const describeRangeAsked = (rangeParams) => {
34602
34616
  };
34603
34617
 
34604
34618
  const resourceLifecycleManager = createResourceLifecycleManager();
34605
- const debug$2 = (args) => {
34619
+ const debug$2 = (...args) => {
34606
34620
  {
34607
34621
  return;
34608
34622
  }
@@ -34667,6 +34681,7 @@ const resource = (
34667
34681
  DELETE_MANY,
34668
34682
  } = {},
34669
34683
  ) => {
34684
+ const declarationSite = getDeclarationSite();
34670
34685
  if (idKey === undefined) {
34671
34686
  idKey = uniqueKeys.length === 0 ? "id" : uniqueKeys[0];
34672
34687
  }
@@ -34715,6 +34730,7 @@ const resource = (
34715
34730
  const createRestActionForRoot = createRestActionFactoryForRoot(name, {
34716
34731
  idKey,
34717
34732
  store,
34733
+ declarationSite,
34718
34734
  });
34719
34735
  return createResource(name, {
34720
34736
  idKey,
@@ -34753,11 +34769,8 @@ const createResource = (
34753
34769
  paramScope,
34754
34770
  rerunOn,
34755
34771
  dependencies,
34756
- } = {},
34772
+ },
34757
34773
  ) => {
34758
- if (idKey === undefined) {
34759
- idKey = uniqueKeys.length === 0 ? "id" : uniqueKeys[0];
34760
- }
34761
34774
  const params = paramScope.params;
34762
34775
  const stateFacade = {
34763
34776
  // public
@@ -34778,7 +34791,6 @@ const createResource = (
34778
34791
  store,
34779
34792
  addItemSetup,
34780
34793
  };
34781
- const lifecycleCtx = { onComplete: null };
34782
34794
 
34783
34795
  resourceLifecycleManager.registerResource(stateFacade, {
34784
34796
  rerunOn,
@@ -34786,7 +34798,7 @@ const createResource = (
34786
34798
  dependencies,
34787
34799
  uniqueKeys,
34788
34800
  });
34789
- lifecycleCtx.onComplete = (actionCompleted) => {
34801
+ const onActionComplete = (actionCompleted) => {
34790
34802
  resourceLifecycleManager.onActionComplete(actionCompleted, {
34791
34803
  resourceScope: stateFacade,
34792
34804
  });
@@ -34823,6 +34835,7 @@ const createResource = (
34823
34835
  paramsToInject,
34824
34836
  { dependencies: withParamsDeps, rerunOn: withParamsRerunOn } = {},
34825
34837
  ) => {
34838
+ const declarationSite = getDeclarationSite();
34826
34839
  if (!paramsToInject || Object.keys(paramsToInject).length === 0) {
34827
34840
  throw new Error(`resource(${name}).withParams() requires parameters`);
34828
34841
  }
@@ -34833,6 +34846,7 @@ const createResource = (
34833
34846
  const createRestActionWithParams = createRestActionFactoryForRoot(name, {
34834
34847
  idKey,
34835
34848
  store,
34849
+ declarationSite,
34836
34850
  });
34837
34851
  return createResource(name, {
34838
34852
  idKey,
@@ -34885,40 +34899,37 @@ const createResource = (
34885
34899
  DELETE,
34886
34900
  } = {},
34887
34901
  ) => {
34902
+ const declarationSite = getDeclarationSite();
34888
34903
  const childName = `${name}.${propertyName}`;
34904
+ const childIdKey = childResource.idKey;
34905
+ const childStore = childResource.store;
34889
34906
  addItemSetup((item) => {
34890
- const childIdKeyForSetup = childResource.idKey;
34891
34907
  const childItemIdSignal = signal();
34892
34908
  const updateChildItemId = (value) => {
34893
34909
  const currentChildItemId = childItemIdSignal.peek();
34910
+ let childItemProps;
34894
34911
  if (isProps(value)) {
34895
- const childItem = childResource.store.upsert(value);
34896
- const childItemId = childItem[childIdKeyForSetup];
34897
- if (currentChildItemId === childItemId) {
34898
- return false;
34899
- }
34900
- childItemIdSignal.value = childItemId;
34901
- return true;
34902
- }
34903
- if (primitiveCanBeId(value)) {
34904
- const childItemProps = { [childIdKeyForSetup]: value };
34905
- const childItem = childResource.store.upsert(childItemProps);
34906
- const childItemId = childItem[childIdKeyForSetup];
34907
- if (currentChildItemId === childItemId) {
34912
+ childItemProps = value;
34913
+ } else if (primitiveCanBeId(value)) {
34914
+ childItemProps = { [childIdKey]: value };
34915
+ } else {
34916
+ if (currentChildItemId === undefined) {
34908
34917
  return false;
34909
34918
  }
34910
- childItemIdSignal.value = childItemId;
34919
+ childItemIdSignal.value = undefined;
34911
34920
  return true;
34912
34921
  }
34913
- if (currentChildItemId === undefined) {
34922
+ const childItem = childStore.upsert(childItemProps);
34923
+ const childItemId = childItem[childIdKey];
34924
+ if (currentChildItemId === childItemId) {
34914
34925
  return false;
34915
34926
  }
34916
- childItemIdSignal.value = undefined;
34927
+ childItemIdSignal.value = childItemId;
34917
34928
  return true;
34918
34929
  };
34919
34930
  updateChildItemId(item[propertyName]);
34920
34931
  const childItemSignal = computed(() =>
34921
- childResource.store.select(childItemIdSignal.value),
34932
+ childStore.select(childItemIdSignal.value),
34922
34933
  );
34923
34934
  const childItemFacadeSignal = computed(() => {
34924
34935
  const childItem = childItemSignal.value;
@@ -34949,9 +34960,7 @@ const createResource = (
34949
34960
  );
34950
34961
  });
34951
34962
 
34952
- const childIdKey = childResource.idKey;
34953
- const childStore = childResource.store;
34954
- const createRestActionForOne = (verb, callback, { lifecycleCtx }) => {
34963
+ const createRestActionForOne = (verb, callback, { onActionComplete }) => {
34955
34964
  const applyResultToValue =
34956
34965
  verb === "DELETE"
34957
34966
  ? (itemId) => {
@@ -34963,33 +34972,18 @@ const createResource = (
34963
34972
  });
34964
34973
  return childItemId;
34965
34974
  }
34966
- : // callback must return object with the following format:
34967
- // {
34968
- // [idKey]: 123,
34969
- // [propertyName]: {
34970
- // [childIdKey]: 456, ...childProps
34971
- // }
34972
- // }
34973
- // the following could happen too if there is no relationship
34974
- // {
34975
- // [idKey]: 123,
34976
- // [propertyName]: null
34977
- // }
34975
+ : // GET/PUT contract (see .one() JSDoc): the parent object with the
34976
+ // relationship nested inside, or null for no relationship.
34978
34977
  (result) => {
34979
34978
  const item = store.upsert(result);
34980
34979
  const childItem = item[propertyName];
34981
- const childItemId = childItem ? childItem[childIdKey] : undefined;
34982
- return childItemId;
34980
+ return childItem ? childItem[childIdKey] : undefined;
34983
34981
  };
34984
-
34985
- const callerInfo = getCallerInfo(null, 2);
34986
- const locationInfo =
34987
- callerInfo.file && callerInfo.line && callerInfo.column
34988
- ? `${callerInfo.file}:${callerInfo.line}:${callerInfo.column}`
34989
- : callerInfo.raw || "unknown location";
34990
- const originalActionName = `${name}.${verb}`;
34991
-
34992
- const actionAffectingOneItem = createAction(callback, {
34982
+ const throwInvalidResult = createInvalidResultThrower(
34983
+ `${name}.${verb}`,
34984
+ declarationSite,
34985
+ );
34986
+ return createAction(callback, {
34993
34987
  meta: {
34994
34988
  verb,
34995
34989
  isMany: false,
@@ -34997,35 +34991,30 @@ const createResource = (
34997
34991
  },
34998
34992
  name: `${name}.${verb}`,
34999
34993
  resultToValue: (result, action) => {
35000
- const actionLabel = action.name;
35001
-
35002
34994
  if (verb === "DELETE") {
35003
34995
  if (!isProps(result) && !primitiveCanBeId(result)) {
35004
- throw new TypeError(
35005
- `${actionLabel} must return an object (that will be used to drop "${name}" resource), received ${result}.
35006
- ${originalActionName} source location: ${locationInfo}`,
34996
+ throwInvalidResult(
34997
+ action.name,
34998
+ `an object (that will be used to drop "${name}" resource)`,
34999
+ result,
35007
35000
  );
35008
35001
  }
35009
- return applyResultToValue(result);
35010
- }
35011
- if (!isProps(result)) {
35012
- throw new TypeError(
35013
- `${actionLabel} must return an object (that will be used to upsert "${name}" resource), received ${result}.
35014
- ${originalActionName} source location: ${locationInfo}`,
35002
+ } else if (!isProps(result)) {
35003
+ throwInvalidResult(
35004
+ action.name,
35005
+ `an object (that will be used to upsert "${name}" resource)`,
35006
+ result,
35015
35007
  );
35016
35008
  }
35017
35009
  return applyResultToValue(result);
35018
35010
  },
35019
35011
  valueToData: (childItemId) => childStore.select(childItemId),
35020
- completeSideEffect: (actionCompleted) => {
35021
- lifecycleCtx.onComplete(actionCompleted);
35022
- },
35012
+ completeSideEffect: onActionComplete,
35023
35013
  });
35024
- return actionAffectingOneItem;
35025
35014
  };
35026
35015
 
35027
35016
  return createResource(childName, {
35028
- idKey: childResource.idKey,
35017
+ idKey: childIdKey,
35029
35018
  restCallbacks: {
35030
35019
  GET,
35031
35020
  PUT,
@@ -35082,49 +35071,21 @@ ${originalActionName} source location: ${locationInfo}`,
35082
35071
  DELETE_MANY,
35083
35072
  } = {},
35084
35073
  ) => {
35074
+ const declarationSite = getDeclarationSite();
35085
35075
  const childStore = childResource.store;
35086
35076
  const childIdKey = childResource.idKey;
35087
35077
  const childName = `${name}.${propertyName}`;
35088
35078
  addItemSetup((item) => {
35089
35079
  const childItemIdArraySignal = signal([]);
35090
- const updateChildItemIdArray = (valueArray) => {
35091
- const currentIdArray = childItemIdArraySignal.peek();
35092
- if (!Array.isArray(valueArray)) {
35093
- if (currentIdArray.length === 0) return;
35094
- childItemIdArraySignal.value = [];
35095
- return;
35096
- }
35097
- let i = 0;
35098
- const idArray = [];
35099
- let modified = false;
35100
- while (i < valueArray.length) {
35101
- const value = valueArray[i];
35102
- const currentIdAtIndex = currentIdArray[idArray.length];
35103
- i++;
35104
- if (isProps(value)) {
35105
- const childItem = childResource.store.upsert(value);
35106
- const childItemId = childItem[childIdKey];
35107
- if (currentIdAtIndex !== childItemId) modified = true;
35108
- idArray.push(childItemId);
35109
- continue;
35110
- }
35111
- if (primitiveCanBeId(value)) {
35112
- const childItemProps = { [childIdKey]: value };
35113
- const childItem = childResource.store.upsert(childItemProps);
35114
- const childItemId = childItem[childIdKey];
35115
- if (currentIdAtIndex !== childItemId) modified = true;
35116
- idArray.push(childItemId);
35117
- continue;
35118
- }
35119
- }
35120
- if (modified || currentIdArray.length !== idArray.length) {
35121
- childItemIdArraySignal.value = idArray;
35122
- }
35123
- };
35080
+ const updateChildItemIdArray = createChildIdArrayUpdater(
35081
+ childStore,
35082
+ childIdKey,
35083
+ childItemIdArraySignal,
35084
+ );
35124
35085
  updateChildItemIdArray(item[propertyName]);
35125
35086
  const childItemArraySignal = computed(() => {
35126
35087
  const idArray = childItemIdArraySignal.value;
35127
- const arr = childResource.store.selectAll(idArray);
35088
+ const arr = childStore.selectAll(idArray);
35128
35089
  Object.defineProperty(arr, SYMBOL_OBJECT_SIGNAL, {
35129
35090
  value: childItemArraySignal,
35130
35091
  writable: false,
@@ -35137,23 +35098,27 @@ ${originalActionName} source location: ${locationInfo}`,
35137
35098
  get: () => childItemArraySignal.value,
35138
35099
  set: updateChildItemIdArray,
35139
35100
  });
35140
- syncIdArrayOnRename(
35141
- childResource.store,
35142
- childIdKey,
35143
- childItemIdArraySignal,
35144
- );
35101
+ syncIdArrayOnRename(childStore, childIdKey, childItemIdArraySignal);
35145
35102
  });
35146
35103
  const createRestActionForMany = (
35147
35104
  verb,
35148
35105
  callback,
35149
- { isMany, lifecycleCtx },
35106
+ { isMany, onActionComplete },
35150
35107
  ) => {
35151
35108
  if (!isMany) {
35152
- return createRestActionAffectingOneItem(verb, callback, lifecycleCtx);
35109
+ return createRestActionAffectingOneItem(verb, callback, {
35110
+ onActionComplete,
35111
+ });
35153
35112
  }
35154
- return createRestActionAffectingManyItems(verb, callback, lifecycleCtx);
35113
+ return createRestActionAffectingManyItems(verb, callback, {
35114
+ onActionComplete,
35115
+ });
35155
35116
  };
35156
- const createRestActionAffectingOneItem = (verb, callback, lifecycleCtx) => {
35117
+ const createRestActionAffectingOneItem = (
35118
+ verb,
35119
+ callback,
35120
+ { onActionComplete },
35121
+ ) => {
35157
35122
  const applyResultToValue =
35158
35123
  verb === "DELETE"
35159
35124
  ? ([itemId, childItemId]) => {
@@ -35162,8 +35127,7 @@ ${originalActionName} source location: ${locationInfo}`,
35162
35127
  const childItemArrayWithoutThisOne = [];
35163
35128
  let found = false;
35164
35129
  for (const childItemCandidate of childItemArray) {
35165
- const childItemCandidateId = childItemCandidate[childIdKey];
35166
- if (childItemCandidateId === childItemId) {
35130
+ if (childItemCandidate[childIdKey] === childItemId) {
35167
35131
  found = true;
35168
35132
  } else {
35169
35133
  childItemArrayWithoutThisOne.push(childItemCandidate);
@@ -35178,162 +35142,127 @@ ${originalActionName} source location: ${locationInfo}`,
35178
35142
  return childItemId;
35179
35143
  }
35180
35144
  : (childData) => {
35145
+ // an array is [property, value, props], used to rename the child id
35181
35146
  const childItem = Array.isArray(childData)
35182
35147
  ? childStore.upsert(...childData)
35183
35148
  : childStore.upsert(childData);
35184
- const childItemId = childItem[childIdKey];
35185
- return childItemId;
35149
+ return childItem[childIdKey];
35186
35150
  };
35187
-
35188
- const callerInfo = getCallerInfo(null, 2);
35189
- const locationInfo =
35190
- callerInfo.file && callerInfo.line && callerInfo.column
35191
- ? `${callerInfo.file}:${callerInfo.line}:${callerInfo.column}`
35192
- : callerInfo.raw || "unknown location";
35193
- const originalActionName = `${name}.${verb}`;
35194
-
35195
- const actionAffectingOneItem = createAction(callback, {
35151
+ const throwInvalidResult = createInvalidResultThrower(
35152
+ `${name}.${verb}`,
35153
+ declarationSite,
35154
+ );
35155
+ return createAction(callback, {
35196
35156
  meta: { verb, isMany: false, paramScope },
35197
35157
  name: `${name}.${verb}`,
35198
35158
  resultToValue: (result, action) => {
35199
- const actionLabel = action.name;
35200
-
35201
35159
  if (verb === "DELETE") {
35202
35160
  if (!Array.isArray(result) || result.length !== 2) {
35203
- throw new TypeError(
35204
- `${actionLabel} must return an array [itemId, childItemId] (that will be used to remove relationship), received ${result}.
35205
- ${originalActionName} source location: ${locationInfo}`,
35161
+ throwInvalidResult(
35162
+ action.name,
35163
+ `an array [itemId, childItemId] (that will be used to remove relationship)`,
35164
+ result,
35206
35165
  );
35207
35166
  }
35208
- return applyResultToValue(result);
35209
- }
35210
- if (!isProps(result)) {
35211
- throw new TypeError(
35212
- `${actionLabel} must return an object (that will be used to upsert child item), received ${result}.
35213
- ${originalActionName} source location: ${locationInfo}`,
35167
+ } else if (!isProps(result)) {
35168
+ throwInvalidResult(
35169
+ action.name,
35170
+ `an object (that will be used to upsert child item)`,
35171
+ result,
35214
35172
  );
35215
35173
  }
35216
35174
  return applyResultToValue(result);
35217
35175
  },
35218
35176
  valueToData: (childItemId) => childStore.select(childItemId),
35219
- completeSideEffect: (actionCompleted) => {
35220
- lifecycleCtx.onComplete(actionCompleted);
35221
- },
35177
+ completeSideEffect: onActionComplete,
35222
35178
  });
35223
- return actionAffectingOneItem;
35224
35179
  };
35225
35180
  const createRestActionAffectingManyItems = (
35226
35181
  verb,
35227
35182
  callback,
35228
- lifecycleCtx,
35183
+ { onActionComplete },
35229
35184
  ) => {
35230
35185
  const applyResultToValue =
35231
35186
  verb === "GET"
35232
35187
  ? (result) => {
35233
- // callback must return object with the following format:
35234
- // {
35235
- // [idKey]: 123,
35236
- // [propertyName]: [
35237
- // { [childIdKey]: 456, ...childProps },
35238
- // { [childIdKey]: 789, ...childProps },
35239
- // ...
35240
- // ]
35241
- // }
35242
- // the array can be empty
35188
+ // GET_MANY contract (see .many() JSDoc): the parent object with
35189
+ // the child array nested inside; the array replaces the relationship.
35243
35190
  const item = store.upsert(result);
35244
35191
  const childItemArray = item[propertyName];
35245
- const childItemIdArray = childItemArray.map(
35246
- (childItem) => childItem[childIdKey],
35247
- );
35248
- return childItemIdArray;
35192
+ return childItemArray.map((childItem) => childItem[childIdKey]);
35249
35193
  }
35250
35194
  : verb === "DELETE"
35251
35195
  ? ([itemIdOrMutableId, childItemIdOrMutableIdArray]) => {
35252
35196
  const item = store.select(itemIdOrMutableId);
35253
35197
  const childItemArray = item[propertyName];
35254
- const deletedChildItemIdArray = [];
35255
- const childItemArrayWithoutThoose = [];
35256
- let someFound = false;
35257
- const deletedChildItemArray = childStore.select(
35258
- childItemIdOrMutableIdArray,
35198
+ const deletedChildItemSet = new Set(
35199
+ childStore.selectAll(childItemIdOrMutableIdArray),
35259
35200
  );
35201
+ const deletedChildItemIdArray = [];
35202
+ const childItemArrayWithoutThose = [];
35260
35203
  for (const childItemCandidate of childItemArray) {
35261
- if (deletedChildItemArray.includes(childItemCandidate)) {
35262
- someFound = true;
35204
+ if (deletedChildItemSet.has(childItemCandidate)) {
35263
35205
  deletedChildItemIdArray.push(
35264
35206
  childItemCandidate[childIdKey],
35265
35207
  );
35266
35208
  } else {
35267
- childItemArrayWithoutThoose.push(childItemCandidate);
35209
+ childItemArrayWithoutThose.push(childItemCandidate);
35268
35210
  }
35269
35211
  }
35270
- if (someFound) {
35212
+ if (deletedChildItemIdArray.length > 0) {
35271
35213
  store.upsert({
35272
35214
  [idKey]: item[idKey],
35273
- [propertyName]: childItemArrayWithoutThoose,
35215
+ [propertyName]: childItemArrayWithoutThose,
35274
35216
  });
35275
35217
  }
35276
35218
  return deletedChildItemIdArray;
35277
35219
  }
35278
35220
  : (childDataArray) => {
35279
35221
  const childItemArray = childStore.upsert(childDataArray);
35280
- const childItemIdArray = childItemArray.map(
35281
- (childItem) => childItem[childIdKey],
35282
- );
35283
- return childItemIdArray;
35222
+ return childItemArray.map((childItem) => childItem[childIdKey]);
35284
35223
  };
35285
-
35286
- const callerInfo = getCallerInfo(null, 2);
35287
- const locationInfo =
35288
- callerInfo.file && callerInfo.line && callerInfo.column
35289
- ? `${callerInfo.file}:${callerInfo.line}:${callerInfo.column}`
35290
- : callerInfo.raw || "unknown location";
35291
- const originalActionName = `${name}.${verb}[many]`;
35292
-
35293
- const actionAffectingManyItem = createAction(callback, {
35224
+ const throwInvalidResult = createInvalidResultThrower(
35225
+ `${name}.${verb}[many]`,
35226
+ declarationSite,
35227
+ );
35228
+ return createAction(callback, {
35294
35229
  meta: { verb, isMany: true, paramScope },
35295
35230
  name: `${name}.${verb}[many]`,
35296
35231
  dataDefault: [],
35297
35232
  resultToValue: (result, action) => {
35298
- const actionLabel = action.name;
35299
-
35300
35233
  if (verb === "GET") {
35301
35234
  if (!isProps(result)) {
35302
- throw new TypeError(
35303
- `${actionLabel} must return an object (that will be used to upsert "${name}" resource with many relationships), received ${result}.
35304
- ${originalActionName} source location: ${locationInfo}`,
35235
+ throwInvalidResult(
35236
+ action.name,
35237
+ `an object (that will be used to upsert "${name}" resource with many relationships)`,
35238
+ result,
35305
35239
  );
35306
35240
  }
35307
- return applyResultToValue(result);
35308
- }
35309
- if (verb === "DELETE") {
35241
+ } else if (verb === "DELETE") {
35310
35242
  if (
35311
35243
  !Array.isArray(result) ||
35312
35244
  result.length !== 2 ||
35313
35245
  !Array.isArray(result[1])
35314
35246
  ) {
35315
- throw new TypeError(
35316
- `${actionLabel} must return an array [itemId, childItemIdArray] (that will be used to remove relationships), received ${result}.
35317
- ${originalActionName} source location: ${locationInfo}`,
35247
+ throwInvalidResult(
35248
+ action.name,
35249
+ `an array [itemId, childItemIdArray] (that will be used to remove relationships)`,
35250
+ result,
35318
35251
  );
35319
35252
  }
35320
- return applyResultToValue(result);
35321
- }
35322
- if (!Array.isArray(result)) {
35323
- throw new TypeError(
35324
- `${actionLabel} must return an array of objects (that will be used to upsert child items), received ${result}.
35325
- ${originalActionName} source location: ${locationInfo}`,
35253
+ } else if (!Array.isArray(result)) {
35254
+ throwInvalidResult(
35255
+ action.name,
35256
+ `an array of objects (that will be used to upsert child items)`,
35257
+ result,
35326
35258
  );
35327
35259
  }
35328
35260
  return applyResultToValue(result);
35329
35261
  },
35330
35262
  valueToData: (childItemIdArray) =>
35331
35263
  childStore.selectAll(childItemIdArray),
35332
- completeSideEffect: (actionCompleted) => {
35333
- lifecycleCtx.onComplete(actionCompleted);
35334
- },
35264
+ completeSideEffect: onActionComplete,
35335
35265
  });
35336
- return actionAffectingManyItem;
35337
35266
  };
35338
35267
 
35339
35268
  return createResource(childName, {
@@ -35398,26 +35327,20 @@ ${originalActionName} source location: ${locationInfo}`,
35398
35327
  ) => {
35399
35328
  const childName = `${name}.${propertyName}`;
35400
35329
 
35401
- // setupCallbackSet: callbacks added by chained .one()/.many()
35402
- // Applied to each per-scope child item object when it is first created.
35403
- const childItemSetupCallbackSet = new Set();
35404
- const childAddItemSetup = (callback) =>
35405
- childItemSetupCallbackSet.add(callback);
35406
- const scopedItemMap = new Map(); // ownerId → stable child item object
35407
- const scopedSignalMap = new Map(); // ownerId → signal<childItem | null>
35330
+ // Callbacks added by chained .one()/.many() on the child resource,
35331
+ // applied to each per-owner child object when it is first created.
35332
+ const childSetupCallbackSet = new Set();
35333
+ const childAddItemSetup = (callback) => childSetupCallbackSet.add(callback);
35334
+ const applyPropsMap = new Map(); // ownerId → applyProps(props | null)
35408
35335
  addItemSetup((ownerItem) => {
35409
35336
  const ownerId = ownerItem[idKey];
35410
- // Create a stable child item mutated in place via applyProps.
35411
- // Reactive getters/setters from chained .one() etc. are defined on this object now
35412
- // so they survive across multiple prop updates.
35337
+ // A stable child object, mutated in place: reactive getters/setters from
35338
+ // chained .one() etc. are defined on it once and survive prop updates.
35413
35339
  const childItem = {};
35414
- for (const childSetup of childItemSetupCallbackSet) {
35340
+ for (const childSetup of childSetupCallbackSet) {
35415
35341
  childSetup(childItem);
35416
35342
  }
35417
- scopedItemMap.set(ownerId, childItem);
35418
35343
  const childSignal = signal(null);
35419
- scopedSignalMap.set(ownerId, childSignal);
35420
-
35421
35344
  const applyProps = (props) => {
35422
35345
  if (!props) {
35423
35346
  childSignal.value = null;
@@ -35431,6 +35354,7 @@ ${originalActionName} source location: ${locationInfo}`,
35431
35354
  childSignal.value = childItem; // first activation: null → childItem
35432
35355
  }
35433
35356
  };
35357
+ applyPropsMap.set(ownerId, applyProps);
35434
35358
 
35435
35359
  applyProps(ownerItem[propertyName]);
35436
35360
 
@@ -35439,9 +35363,13 @@ ${originalActionName} source location: ${locationInfo}`,
35439
35363
  set: applyProps,
35440
35364
  });
35441
35365
  });
35442
- const createRestActionForScopedOne = (verb, callback, { lifecycleCtx }) => {
35366
+ const createRestActionForScopedOne = (
35367
+ verb,
35368
+ callback,
35369
+ { onActionComplete },
35370
+ ) => {
35443
35371
  const childActionName = `${childName}.${verb}`;
35444
- const restAction = createAction(callback, {
35372
+ return createAction(callback, {
35445
35373
  name: childActionName,
35446
35374
  meta: { verb, isMany: false, paramScope },
35447
35375
  resultToValue: (result) => {
@@ -35458,33 +35386,20 @@ ${originalActionName} source location: ${locationInfo}`,
35458
35386
  uniqueKeys,
35459
35387
  childActionName,
35460
35388
  );
35461
- const childItem = scopedItemMap.get(ownerId);
35462
- if (!childItem) {
35389
+ const applyProps = applyPropsMap.get(ownerId);
35390
+ if (!applyProps) {
35463
35391
  throw new Error(
35464
35392
  `${childActionName}: no item found for scope id "${ownerId}"`,
35465
35393
  );
35466
35394
  }
35467
- const childSignal = scopedSignalMap.get(ownerId);
35468
- if (props) {
35469
- for (const [key, value] of Object.entries(props)) {
35470
- childItem[key] = value;
35471
- }
35472
- if (childSignal.peek() !== childItem) {
35473
- childSignal.value = childItem;
35474
- }
35475
- } else {
35476
- childSignal.value = null;
35477
- }
35395
+ applyProps(props);
35478
35396
  return [ownerId, props];
35479
35397
  },
35480
- completeSideEffect: (actionCompleted) => {
35481
- lifecycleCtx.onComplete(actionCompleted);
35482
- },
35398
+ completeSideEffect: onActionComplete,
35483
35399
  });
35484
- return restAction;
35485
35400
  };
35486
35401
 
35487
- const childResource = createResource(childName, {
35402
+ return createResource(childName, {
35488
35403
  idKey: childIdKey,
35489
35404
  restCallbacks: {
35490
35405
  GET,
@@ -35500,7 +35415,6 @@ ${originalActionName} source location: ${locationInfo}`,
35500
35415
  rerunOn: scopedOneRerunOn ?? rerunOn,
35501
35416
  dependencies: scopedOneDependencies ?? dependencies,
35502
35417
  });
35503
- return childResource;
35504
35418
  };
35505
35419
 
35506
35420
  /**
@@ -35554,108 +35468,76 @@ ${originalActionName} source location: ${locationInfo}`,
35554
35468
  ) => {
35555
35469
  const childName = `${name}.${propertyName}`;
35556
35470
 
35557
- // setupCallbackSet: callbacks added by chained .one()/.many()
35558
- // Applied to each child item when it is created in a per-scope store.
35471
+ // Callbacks added by chained .one()/.many() on the child resource,
35472
+ // applied to each child item created in a per-owner store.
35559
35473
  const childSetupCallbackSet = new Set();
35560
35474
  const childAddItemSetup = (callback) => childSetupCallbackSet.add(callback);
35561
- const scopedStoreMap = new Map(); // ownerId → childStore
35562
- const scopedIdArraySignalMap = new Map(); // ownerId childItemIdArraySignal
35475
+ // ownerKey (id or any uniqueKey value){ childStore, idArraySignal }
35476
+ // One owner can be registered under several keys, all pointing to the same scope.
35477
+ const scopeMap = new Map();
35478
+ const createScope = (ownerKey) => {
35479
+ const childStore = arraySignalStore([], childIdKey, {
35480
+ name: `${childName}#${ownerKey} store`,
35481
+ createItem: (props) => {
35482
+ const childItem = {};
35483
+ Object.assign(childItem, props);
35484
+ for (const childSetup of childSetupCallbackSet) {
35485
+ childSetup(childItem);
35486
+ }
35487
+ return childItem;
35488
+ },
35489
+ });
35490
+ const scope = { childStore, idArraySignal: signal([]) };
35491
+ scopeMap.set(ownerKey, scope);
35492
+ return scope;
35493
+ };
35563
35494
  addItemSetup((item) => {
35564
35495
  const ownerId = item[idKey];
35565
35496
 
35566
- // Reuse an existing scoped store if one was already created via a uniqueKey
35567
- // (e.g. rows were fetched by tablename before the full table was loaded).
35568
- let childStore = scopedStoreMap.get(ownerId);
35569
- let childItemIdArraySignal = scopedIdArraySignalMap.get(ownerId);
35570
- if (!childStore) {
35497
+ // Reuse an existing scope if one was already created under a uniqueKey
35498
+ // value (e.g. rows were fetched by tablename before the full table was loaded).
35499
+ let scope = scopeMap.get(ownerId);
35500
+ if (!scope) {
35571
35501
  for (const uniqueKey of uniqueKeys) {
35572
35502
  const uniqueKeyValue = item[uniqueKey];
35573
- if (uniqueKeyValue !== undefined) {
35574
- const existing = scopedStoreMap.get(uniqueKeyValue);
35575
- if (existing) {
35576
- childStore = existing;
35577
- childItemIdArraySignal =
35578
- scopedIdArraySignalMap.get(uniqueKeyValue);
35579
- break;
35580
- }
35503
+ if (uniqueKeyValue !== undefined && scopeMap.has(uniqueKeyValue)) {
35504
+ scope = scopeMap.get(uniqueKeyValue);
35505
+ break;
35581
35506
  }
35582
35507
  }
35583
35508
  }
35584
- if (!childStore) {
35585
- childStore = arraySignalStore([], childIdKey, {
35586
- name: `${childName}#${ownerId} store`,
35587
- createItem: (props) => {
35588
- const childItem = {};
35589
- Object.assign(childItem, props);
35590
- for (const childSetup of childSetupCallbackSet) {
35591
- childSetup(childItem);
35592
- }
35593
- return childItem;
35594
- },
35595
- });
35596
- childItemIdArraySignal = signal([]);
35509
+ if (!scope) {
35510
+ scope = createScope(ownerId);
35597
35511
  }
35598
- scopedStoreMap.set(ownerId, childStore);
35599
- // Also register by each uniqueKey value so that resolveOwnerId works
35600
- // when a callback returns { [uniqueKey]: value } before the full item is loaded.
35512
+ // Register the scope under the id and every uniqueKey value so that
35513
+ // resolveOwnerId can address it whichever key a callback returns.
35514
+ scopeMap.set(ownerId, scope);
35601
35515
  for (const uniqueKey of uniqueKeys) {
35602
35516
  const uniqueKeyValue = item[uniqueKey];
35603
35517
  if (uniqueKeyValue !== undefined) {
35604
- scopedStoreMap.set(uniqueKeyValue, childStore);
35518
+ scopeMap.set(uniqueKeyValue, scope);
35605
35519
  }
35606
35520
  }
35607
35521
 
35608
- scopedIdArraySignalMap.set(ownerId, childItemIdArraySignal);
35609
- for (const uniqueKey of uniqueKeys) {
35610
- const uniqueKeyValue = item[uniqueKey];
35611
- if (uniqueKeyValue !== undefined) {
35612
- scopedIdArraySignalMap.set(uniqueKeyValue, childItemIdArraySignal);
35613
- }
35522
+ const { childStore, idArraySignal } = scope;
35523
+ const updateChildItemIdArray = createChildIdArrayUpdater(
35524
+ childStore,
35525
+ childIdKey,
35526
+ idArraySignal,
35527
+ );
35528
+ // The parent may not carry the property at all (e.g. created by a POST
35529
+ // that does not embed it): leave the collection of a reused scope
35530
+ // untouched — children may have been fetched by uniqueKey before the
35531
+ // parent was loaded. Only an explicit value replaces the collection.
35532
+ if (item[propertyName] !== undefined) {
35533
+ updateChildItemIdArray(item[propertyName]);
35614
35534
  }
35615
35535
 
35616
- const updateChildItemIdArray = (valueArray) => {
35617
- const currentIdArray = childItemIdArraySignal.peek();
35618
- if (!Array.isArray(valueArray)) {
35619
- if (currentIdArray.length === 0) return;
35620
- childItemIdArraySignal.value = [];
35621
- return;
35622
- }
35623
- let i = 0;
35624
- const idArray = [];
35625
- let modified = false;
35626
- while (i < valueArray.length) {
35627
- const value = valueArray[i];
35628
- const currentIdAtIndex = currentIdArray[idArray.length];
35629
- i++;
35630
- if (isProps(value)) {
35631
- const childItem = childStore.upsert(value);
35632
- const childItemId = childItem[childIdKey];
35633
- if (currentIdAtIndex !== childItemId) modified = true;
35634
- idArray.push(childItemId);
35635
- continue;
35636
- }
35637
- if (primitiveCanBeId(value)) {
35638
- const childItemProps = { [childIdKey]: value };
35639
- const childItem = childStore.upsert(childItemProps);
35640
- const childItemId = childItem[childIdKey];
35641
- if (currentIdAtIndex !== childItemId) modified = true;
35642
- idArray.push(childItemId);
35643
- continue;
35644
- }
35645
- }
35646
- if (modified || currentIdArray.length !== idArray.length) {
35647
- childItemIdArraySignal.value = idArray;
35648
- }
35649
- };
35650
-
35651
- updateChildItemIdArray(item[propertyName]);
35652
-
35653
35536
  // When an id is renamed (PUT/PATCH changes the idKey), patch the id array.
35654
- syncIdArrayOnRename(childStore, childIdKey, childItemIdArraySignal);
35537
+ syncIdArrayOnRename(childStore, childIdKey, idArraySignal);
35655
35538
 
35656
35539
  const childItemArraySignal = computed(() => {
35657
- const childItemIdArray = childItemIdArraySignal.value;
35658
- const childItemArray = childStore.selectAll(childItemIdArray);
35540
+ const childItemArray = childStore.selectAll(idArraySignal.value);
35659
35541
  Object.defineProperty(childItemArray, SYMBOL_OBJECT_SIGNAL, {
35660
35542
  value: childItemArraySignal,
35661
35543
  writable: false,
@@ -35673,13 +35555,10 @@ ${originalActionName} source location: ${locationInfo}`,
35673
35555
  const createRestActionForScopedMany = (
35674
35556
  verb,
35675
35557
  callback,
35676
- { isMany, lifecycleCtx },
35558
+ { isMany, onActionComplete },
35677
35559
  ) => {
35678
- if (!callback) {
35679
- return undefined;
35680
- }
35681
35560
  const childActionName = `${childName}.${verb}`;
35682
- const childAction = createAction(callback, {
35561
+ return createAction(callback, {
35683
35562
  name: childActionName,
35684
35563
  meta: { verb, isMany, paramScope },
35685
35564
  resultToValue: (result) => {
@@ -35696,48 +35575,33 @@ ${originalActionName} source location: ${locationInfo}`,
35696
35575
  uniqueKeys,
35697
35576
  childActionName,
35698
35577
  );
35699
- let childStore = scopedStoreMap.get(ownerId);
35700
- if (!childStore) {
35701
- // Owner not yet in store — lazily create scoped store so actions can run
35702
- // before the parent item has been fully loaded (e.g. rows fetched before table).
35703
- childStore = arraySignalStore([], childIdKey, {
35704
- name: `${childName}#${ownerId} store`,
35705
- createItem: (props) => {
35706
- const childItem = {};
35707
- Object.assign(childItem, props);
35708
- for (const childSetup of childSetupCallbackSet) {
35709
- childSetup(childItem);
35710
- }
35711
- return childItem;
35712
- },
35713
- });
35714
- scopedStoreMap.set(ownerId, childStore);
35715
- const newIdArraySignal = signal([]);
35716
- scopedIdArraySignalMap.set(ownerId, newIdArraySignal);
35717
- }
35718
- const childItemIdArraySignal = scopedIdArraySignalMap.get(ownerId);
35578
+ // Owner not in store yet: create the scope so actions can run before
35579
+ // the parent item has been loaded (e.g. rows fetched before their table).
35580
+ const scope = scopeMap.get(ownerId) || createScope(ownerId);
35581
+ const { childStore, idArraySignal } = scope;
35719
35582
 
35720
35583
  if (verb === "DELETE") {
35721
35584
  if (isMany) {
35722
35585
  const idArray = childStore.drop(rest[0]);
35723
35586
  const toRemoveSet = new Set(idArray);
35724
- childItemIdArraySignal.value = childItemIdArraySignal
35587
+ idArraySignal.value = idArraySignal
35725
35588
  .peek()
35726
35589
  .filter((id) => !toRemoveSet.has(id));
35727
35590
  return [ownerId, idArray];
35728
35591
  }
35729
35592
  const childId = childStore.drop(rest[0]);
35730
- childItemIdArraySignal.value = childItemIdArraySignal
35593
+ idArraySignal.value = idArraySignal
35731
35594
  .peek()
35732
35595
  .filter((id) => id !== childId);
35733
35596
  return [ownerId, childId];
35734
35597
  }
35735
35598
 
35736
35599
  if (isMany) {
35737
- // GET_MANY, POST_MANY, PUT_MANY etc: rest[0] is the array of items
35600
+ // GET_MANY, POST_MANY, PUT_MANY etc: rest[0] is the array of items,
35601
+ // and it replaces the whole collection.
35738
35602
  const itemArray = childStore.upsert(rest[0]);
35739
- const idArray = itemArray.map((i) => i[childIdKey]);
35740
- childItemIdArraySignal.value = idArray;
35603
+ const idArray = itemArray.map((childItem) => childItem[childIdKey]);
35604
+ idArraySignal.value = idArray;
35741
35605
  return [ownerId, idArray];
35742
35606
  }
35743
35607
 
@@ -35749,25 +35613,23 @@ ${originalActionName} source location: ${locationInfo}`,
35749
35613
  return [ownerId, childItem[childIdKey]];
35750
35614
  },
35751
35615
  valueToData: (value) => {
35752
- if (!value) return isMany ? [] : undefined;
35616
+ if (!value) {
35617
+ return isMany ? [] : undefined;
35618
+ }
35753
35619
  const [ownerId, idOrIdArray] = value;
35754
- const childStore = scopedStoreMap.get(ownerId);
35755
- if (!childStore) return isMany ? [] : undefined;
35756
- if (isMany) return childStore.selectAll(idOrIdArray);
35757
- return childStore.select(idOrIdArray);
35758
- },
35759
- completeSideEffect: (actionCompleted) => {
35760
- lifecycleCtx.onComplete(actionCompleted);
35620
+ const scope = scopeMap.get(ownerId);
35621
+ if (!scope) {
35622
+ return isMany ? [] : undefined;
35623
+ }
35624
+ if (isMany) {
35625
+ return scope.childStore.selectAll(idOrIdArray);
35626
+ }
35627
+ return scope.childStore.select(idOrIdArray);
35761
35628
  },
35629
+ completeSideEffect: onActionComplete,
35762
35630
  });
35763
- return childAction;
35764
35631
  };
35765
35632
 
35766
- // When a child (scopedMany) item is mutated via POST, the parent GET must
35767
- // re-fetch because the parent embeds the child array and we cannot know the
35768
- // new ordering without asking the backend again.
35769
- // (scopedOne does NOT need this: the mutation result contains the updated
35770
- // item directly, so no parent re-fetch is necessary.)
35771
35633
  const childResource = createResource(childName, {
35772
35634
  idKey: childIdKey,
35773
35635
  restCallbacks: {
@@ -35789,19 +35651,23 @@ ${originalActionName} source location: ${locationInfo}`,
35789
35651
  rerunOn: scopedManyRerunOn ?? rerunOn,
35790
35652
  dependencies: scopedManyDependencies ?? dependencies,
35791
35653
  });
35792
- // Register: when childResource fires, rerun parent (stateFacade) GETs.
35654
+ // When a scoped child collection is mutated (POST etc.), the parent GET must
35655
+ // re-fetch: the parent embeds the child array and only the backend knows the
35656
+ // new ordering. (scopedOne does not need this: the mutation result contains
35657
+ // the updated object directly.)
35793
35658
  resourceLifecycleManager.addDependency(
35794
35659
  childResource,
35795
35660
  stateFacade,
35796
35661
  propertyName,
35797
35662
  );
35798
- childResource.getChildStore = (ownerKey) => scopedStoreMap.get(ownerKey);
35663
+ childResource.getChildStore = (ownerKey) =>
35664
+ scopeMap.get(ownerKey)?.childStore;
35799
35665
  return childResource;
35800
35666
  };
35801
35667
 
35802
- // expose rest actions on the stateFacade
35668
+ // expose one action (or range reader) per provided rest callback
35803
35669
  for (const [restCallbackKey, restCallback] of Object.entries(restCallbacks)) {
35804
- if (restCallback === undefined) {
35670
+ if (!restCallback) {
35805
35671
  continue;
35806
35672
  }
35807
35673
  if (restCallbackKey === "GET_RANGE") {
@@ -35823,25 +35689,16 @@ ${originalActionName} source location: ${locationInfo}`,
35823
35689
  const verb = isMany
35824
35690
  ? restCallbackKey.replace("_MANY", "")
35825
35691
  : restCallbackKey;
35826
- const restAction = createRestAction(verb, restCallback, {
35692
+ let restAction = createRestAction(verb, restCallback, {
35827
35693
  isMany,
35828
- lifecycleCtx,
35694
+ onActionComplete,
35829
35695
  paramScope,
35830
35696
  });
35831
- if (!restAction) {
35832
- console.error("no action returned (here to see when it happens)");
35833
- continue;
35834
- }
35835
- let actionToRegister;
35836
35697
  if (params) {
35837
- const restActionBound = restAction.bindParams(params);
35838
- stateFacade[restCallbackKey] = restActionBound;
35839
- actionToRegister = restActionBound;
35840
- } else {
35841
- stateFacade[restCallbackKey] = restAction;
35842
- actionToRegister = restAction;
35698
+ restAction = restAction.bindParams(params);
35843
35699
  }
35844
- resourceLifecycleManager.registerAction(stateFacade, actionToRegister);
35700
+ stateFacade[restCallbackKey] = restAction;
35701
+ resourceLifecycleManager.registerAction(stateFacade, restAction);
35845
35702
  }
35846
35703
 
35847
35704
  return stateFacade;
@@ -35852,76 +35709,64 @@ const createRestActionFactoryForRoot = (
35852
35709
  {
35853
35710
  idKey,
35854
35711
  store, // see array_signal_store.js
35712
+ declarationSite,
35855
35713
  },
35856
35714
  ) => {
35857
35715
  const createActionForRoot = (
35858
35716
  verb,
35859
35717
  restCallback,
35860
- { isMany, lifecycleCtx, paramScope },
35718
+ { isMany, onActionComplete, paramScope },
35861
35719
  ) => {
35862
35720
  if (!isMany) {
35863
35721
  return createActionAffectingOneItem(verb, restCallback, {
35864
- lifecycleCtx,
35722
+ onActionComplete,
35865
35723
  paramScope,
35866
35724
  });
35867
35725
  }
35868
35726
  return createActionAffectingManyItems(verb, restCallback, {
35869
- lifecycleCtx,
35727
+ onActionComplete,
35870
35728
  paramScope,
35871
35729
  });
35872
35730
  };
35873
35731
  const createActionAffectingOneItem = (
35874
35732
  verb,
35875
35733
  callback,
35876
- { lifecycleCtx, paramScope },
35734
+ { onActionComplete, paramScope },
35877
35735
  ) => {
35878
35736
  const applyResultToValue =
35879
35737
  verb === "DELETE"
35880
- ? (itemIdOrItemProps) => {
35881
- const itemId = store.drop(itemIdOrItemProps);
35882
- return itemId;
35883
- }
35738
+ ? (itemIdOrItemProps) => store.drop(itemIdOrItemProps)
35884
35739
  : (result) => {
35885
- let item;
35886
- if (Array.isArray(result)) {
35887
- // the callback is returning something like [property, value, props]
35888
- // this is to support a case like:
35889
- // store.upsert("name", "currentName", { name: "newName" })
35890
- // where we want to update the idKey of an item
35891
- item = store.upsert(...result);
35892
- } else {
35893
- item = store.upsert(result);
35894
- }
35895
- const itemId = item[idKey];
35896
- return itemId;
35740
+ // An array result is [property, value, props] — used to rename the
35741
+ // idKey of an item: store.upsert("name", "currentName", { name: "newName" })
35742
+ const item = Array.isArray(result)
35743
+ ? store.upsert(...result)
35744
+ : store.upsert(result);
35745
+ return item[idKey];
35897
35746
  };
35898
-
35899
- const callerInfo = getCallerInfo(null, 2);
35900
- // Provide more fallback options for better debugging
35901
- const locationInfo =
35902
- callerInfo.file && callerInfo.line && callerInfo.column
35903
- ? `${callerInfo.file}:${callerInfo.line}:${callerInfo.column}`
35904
- : callerInfo.raw || "unknown location";
35905
- const originalActionName = `${name}.${verb}`;
35906
- const actionAffectingOneItem = createAction(callback, {
35747
+ const throwInvalidResult = createInvalidResultThrower(
35748
+ `${name}.${verb}`,
35749
+ declarationSite,
35750
+ );
35751
+ return createAction(callback, {
35907
35752
  name: `${name}.${verb}`,
35908
35753
  meta: { verb, isMany: false, paramScope },
35909
35754
  resultToValue: (result, action) => {
35910
- const actionLabel = action.name;
35911
-
35912
35755
  if (verb === "DELETE") {
35913
35756
  if (!isProps(result) && !primitiveCanBeId(result)) {
35914
- throw new TypeError(
35915
- `${actionLabel} must return an object (that will be used to drop "${name}" resource), received ${result}.
35916
- ${originalActionName} source location: ${locationInfo}`,
35757
+ throwInvalidResult(
35758
+ action.name,
35759
+ `an object (that will be used to drop "${name}" resource)`,
35760
+ result,
35917
35761
  );
35918
35762
  }
35919
35763
  return applyResultToValue(result);
35920
35764
  }
35921
35765
  if (!isProps(result)) {
35922
- throw new TypeError(
35923
- `${actionLabel} must return an object (that will be used to upsert "${name}" resource), received ${result}.
35924
- ${originalActionName} source location: ${locationInfo}`,
35766
+ throwInvalidResult(
35767
+ action.name,
35768
+ `an object (that will be used to upsert "${name}" resource)`,
35769
+ result,
35925
35770
  );
35926
35771
  }
35927
35772
  // Track which top-level properties the GET response contained so that
@@ -35932,40 +35777,30 @@ ${originalActionName} source location: ${locationInfo}`,
35932
35777
  return applyResultToValue(result);
35933
35778
  },
35934
35779
  valueToData: (itemId) => store.select(itemId),
35935
- completeSideEffect: (actionCompleted) => {
35936
- lifecycleCtx.onComplete(actionCompleted);
35937
- },
35780
+ completeSideEffect: onActionComplete,
35938
35781
  });
35939
- return actionAffectingOneItem;
35940
35782
  };
35941
35783
  const createActionAffectingManyItems = (
35942
35784
  verb,
35943
35785
  callback,
35944
- { lifecycleCtx, paramScope },
35786
+ { onActionComplete, paramScope },
35945
35787
  ) => {
35946
35788
  const applyResultToValue =
35947
35789
  verb === "DELETE"
35948
- ? (idOrMutableIdArray) => {
35949
- const idArray = store.drop(idOrMutableIdArray);
35950
- return idArray;
35951
- }
35790
+ ? (idOrMutableIdArray) => store.drop(idOrMutableIdArray)
35952
35791
  : (dataArray) => {
35953
35792
  const itemArray = store.upsert(dataArray);
35954
- const idArray = itemArray.map((item) => item[idKey]);
35955
- return idArray;
35793
+ return itemArray.map((item) => item[idKey]);
35956
35794
  };
35957
35795
 
35958
- const actionAffectingManyItems = createAction(callback, {
35796
+ return createAction(callback, {
35959
35797
  meta: { verb, isMany: true, paramScope },
35960
35798
  name: `${name}.${verb}_MANY`,
35961
35799
  dataDefault: [],
35962
35800
  resultToValue: applyResultToValue,
35963
- valueToData: (idArray) => {
35964
- const items = store.selectAll(idArray);
35965
- return items;
35966
- },
35801
+ valueToData: (idArray) => store.selectAll(idArray),
35967
35802
  completeSideEffect: (actionCompleted) => {
35968
- lifecycleCtx.onComplete(actionCompleted);
35803
+ onActionComplete(actionCompleted);
35969
35804
  if (
35970
35805
  verb === "DELETE" ||
35971
35806
  actionCompleted.valueSignal.peek().length === 0
@@ -35979,12 +35814,70 @@ ${originalActionName} source location: ${locationInfo}`,
35979
35814
  return syncIdArrayOnRename(store, idKey, actionCompleted.valueSignal);
35980
35815
  },
35981
35816
  });
35982
- return actionAffectingManyItems;
35983
35817
  };
35984
35818
 
35985
35819
  return createActionForRoot;
35986
35820
  };
35987
35821
 
35822
+ // Captures the "file:line:column" of the user code that invoked the public
35823
+ // function (resource(), .one(), .many(), withParams, …), so invalid-result
35824
+ // errors can point at where the callbacks were declared, not at this file.
35825
+ // Must be called directly from the public function: the stack offset accounts
35826
+ // for exactly two frames (getCallerInfo → getDeclarationSite → public fn → user code).
35827
+ const getDeclarationSite = () => {
35828
+ const callerInfo = getCallerInfo(null, 1);
35829
+ if (callerInfo.file && callerInfo.line && callerInfo.column) {
35830
+ return `${callerInfo.file}:${callerInfo.line}:${callerInfo.column}`;
35831
+ }
35832
+ return callerInfo.raw || "unknown location";
35833
+ };
35834
+
35835
+ const createInvalidResultThrower = (originalActionName, declarationSite) => {
35836
+ return (actionLabel, expected, result) => {
35837
+ throw new TypeError(
35838
+ `${actionLabel} must return ${expected}, received ${result}.
35839
+ ${originalActionName} source location: ${declarationSite}`,
35840
+ );
35841
+ };
35842
+ };
35843
+
35844
+ // Shared by .many() and .scopedMany(): converts a raw relationship value (an
35845
+ // array of child props/ids, or anything else meaning "empty") into an array of
35846
+ // child ids, upserting each entry into the child store. The id array signal is
35847
+ // only touched when the resulting ids actually differ.
35848
+ const createChildIdArrayUpdater = (childStore, childIdKey, idArraySignal) => {
35849
+ return (valueArray) => {
35850
+ const currentIdArray = idArraySignal.peek();
35851
+ if (!Array.isArray(valueArray)) {
35852
+ if (currentIdArray.length > 0) {
35853
+ idArraySignal.value = [];
35854
+ }
35855
+ return;
35856
+ }
35857
+ const idArray = [];
35858
+ let modified = false;
35859
+ for (const value of valueArray) {
35860
+ let childItemProps;
35861
+ if (isProps(value)) {
35862
+ childItemProps = value;
35863
+ } else if (primitiveCanBeId(value)) {
35864
+ childItemProps = { [childIdKey]: value };
35865
+ } else {
35866
+ continue;
35867
+ }
35868
+ const childItem = childStore.upsert(childItemProps);
35869
+ const childItemId = childItem[childIdKey];
35870
+ if (currentIdArray[idArray.length] !== childItemId) {
35871
+ modified = true;
35872
+ }
35873
+ idArray.push(childItemId);
35874
+ }
35875
+ if (modified || currentIdArray.length !== idArray.length) {
35876
+ idArraySignal.value = idArray;
35877
+ }
35878
+ };
35879
+ };
35880
+
35988
35881
  const syncIdArrayOnRename = (store, idKey, idArraySignal) => {
35989
35882
  return store.observeProperties((mutations) => {
35990
35883
  const idArray = idArraySignal.peek();
@@ -36044,7 +35937,7 @@ const resolveOwnerId = (rawOwnerId, store, idKey, uniqueKeys, actionName) => {
36044
35937
  return item[idKey];
36045
35938
  }
36046
35939
  throw new TypeError(
36047
- `${actionName}: the first element of the returned array is { ${propName}: "${propValue}" } but "${propName}" is neither the idKey ("${idKey}") nor a declared uniqueKey (${uniqueKeys.length ? uniqueKeys.join(", ") : "none"}).
35940
+ `${actionName}: the first element of the returned array is { ${propName}: "${propValue}" } but "${propName}" is neither the idKey ("${idKey}") nor a declared uniqueKey (${uniqueKeys.length ? uniqueKeys.join(", ") : "none"}).
36048
35941
  Return a primitive id or a single-property object whose key is the idKey or a uniqueKey.`,
36049
35942
  );
36050
35943
  }
@@ -36053,7 +35946,7 @@ Return a primitive id or a single-property object whose key is the idKey or a un
36053
35946
  if (idKey in rawOwnerId) {
36054
35947
  const resolvedId = rawOwnerId[idKey];
36055
35948
  console.warn(
36056
- `${actionName}: the first element of the returned array is an object with multiple properties.
35949
+ `${actionName}: the first element of the returned array is an object with multiple properties.
36057
35950
  Only "${idKey}" is needed. Consider returning a primitive id or { ${idKey}: value } instead.`,
36058
35951
  );
36059
35952
  return resolvedId;
@@ -36065,8 +35958,10 @@ Received an object with keys: ${keys.join(", ")}.`,
36065
35958
  );
36066
35959
  };
36067
35960
 
36068
- /** so that when a tracked property changes
36069
- * on an item the corresponding signal is updated automatically.
35961
+ /**
35962
+ * Keeps external signals in sync with properties of the resource's store items:
35963
+ * when a tracked property changes on an item, the corresponding signal is
35964
+ * updated automatically.
36070
35965
  *
36071
35966
  * Since signals are typically connected to route parameters via the route template
36072
35967
  * syntax, this keeps the URL in sync when a store item's mutable key is renamed.