@jsenv/navi 0.29.437 → 0.29.438

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.
@@ -2943,6 +2943,41 @@ const setActionPrivateProperties = (action, properties) => {
2943
2943
  actionPrivatePropertiesWeakMap.set(action, properties);
2944
2944
  };
2945
2945
 
2946
+ /*
2947
+ * Where an action that declares no provisionalValue of its own finds one: the
2948
+ * value the page kept from the previous document, for a route action that is
2949
+ * not a resource read (see keepPageOnScreen in nav/page_kept.js). routeAction
2950
+ * adds the source when it binds the action; a run asks for it at its start,
2951
+ * like it asks provisionalValue (actions.js).
2952
+ *
2953
+ * A registry beside actions.js rather than an import of the kept reads: those
2954
+ * know about stores and params keys, which import actions.js.
2955
+ */
2956
+
2957
+ const keptValueSourcesWeakMap = new WeakMap();
2958
+ const addKeptValueSource = (rootAction, source) => {
2959
+ let sources = keptValueSourcesWeakMap.get(rootAction);
2960
+ if (!sources) {
2961
+ sources = [];
2962
+ keptValueSourcesWeakMap.set(rootAction, sources);
2963
+ }
2964
+ sources.push(source);
2965
+ };
2966
+ const takeKeptValue = (action, params) => {
2967
+ const rootAction = action.rootAction || action;
2968
+ const sources = keptValueSourcesWeakMap.get(rootAction);
2969
+ if (!sources) {
2970
+ return undefined;
2971
+ }
2972
+ for (const source of sources) {
2973
+ const value = source(params);
2974
+ if (value !== undefined) {
2975
+ return value;
2976
+ }
2977
+ }
2978
+ return undefined;
2979
+ };
2980
+
2946
2981
  /*
2947
2982
  * Actions: async callbacks wrapped in reactive state.
2948
2983
  *
@@ -4058,8 +4093,12 @@ const createAction = (callback, rootOptions = {}) => {
4058
4093
  actionAbortMap.set(action, abort);
4059
4094
 
4060
4095
  batch(() => {
4061
- if (provisionalValue && valueSignal.peek() === undefined) {
4062
- const provisional = provisionalValue(params, action);
4096
+ if (valueSignal.peek() === undefined) {
4097
+ // An action saying nothing of its own may still have a value the
4098
+ // page kept from the previous document (see kept_values.js).
4099
+ const provisional = provisionalValue
4100
+ ? provisionalValue(params, action)
4101
+ : takeKeptValue(action, params);
4063
4102
  if (provisional !== undefined) {
4064
4103
  valueSignal.value = provisional;
4065
4104
  }
@@ -25852,10 +25891,10 @@ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
25852
25891
  * out again, after a reset say, it would write the previous document's rows
25853
25892
  * over a fresher answer.
25854
25893
  *
25855
- * This module is the half the resources see: `resource()` and the range reader
25856
- * take from it, and say which of their reads can be kept at all. Which reads
25857
- * are on the page on screen, and what the slot is written into, is
25858
- * page_kept.js.
25894
+ * This module is the half the reads see: `resource()` and the range reader
25895
+ * take from it and say which of their reads are kept as rows, routeAction takes
25896
+ * from it for any other action (through action/kept_values.js). Which reads are
25897
+ * on the page on screen, and what the slot is written into, is page_kept.js.
25859
25898
  */
25860
25899
 
25861
25900
  // key → what the read answered in the previous document, not handed out yet
@@ -25878,9 +25917,10 @@ const takeKeptRead = (key) => {
25878
25917
  return entry;
25879
25918
  };
25880
25919
 
25881
- // The root actions whose answer is rows of a store, written and read back the
25882
- // same way: a resource's GET and GET_MANY. Anything else a route runs (a page's
25883
- // code, a computation) has nothing a later document could draw.
25920
+ // The root actions whose answer is rows of a store: a resource's GET and
25921
+ // GET_MANY. They are kept as rows, since their value (ids) names rows a new
25922
+ // document's store does not hold. Any other route action is kept by its value
25923
+ // (see page_kept.js).
25884
25924
  const keepableReadWeakSet = new WeakSet();
25885
25925
  const markKeepableRead = (rootAction) => {
25886
25926
  keepableReadWeakSet.add(rootAction);
@@ -38215,6 +38255,15 @@ const writeCover = (edge, element, size) => {
38215
38255
  // Every route action, with whether it asks something of the address at the
38216
38256
  // moment: its route matches and its params getter returns something.
38217
38257
  const routeActionEntriesSignal = signal([]);
38258
+ // What a route action is called in the page kept for the next document (see
38259
+ // keepPageOnScreen): its place among the route actions declared, which the
38260
+ // next document of the same build declares in the same order. A resource read
38261
+ // has a name of its own and is kept by it; an action made from a callback is
38262
+ // most often "anonymous", and several of them read the same page.
38263
+ const keptNameWeakMap = new WeakMap();
38264
+ const getRouteActionKeptName = (routeAction) => {
38265
+ return keptNameWeakMap.get(routeAction);
38266
+ };
38218
38267
  let activeRouteActionsPrevious = new Set();
38219
38268
  /**
38220
38269
  * The route actions asking something for the page on screen: their route
@@ -38299,7 +38348,16 @@ const routeAction = (
38299
38348
  }
38300
38349
  return params;
38301
38350
  };
38351
+ const rootAction = rootActionOf(action);
38352
+ const keptName = `route_action#${routeActionEntriesSignal.peek().length} ${rootAction.name}`;
38353
+ if (!isKeepableRead(rootAction)) {
38354
+ // Before the effect below: it may run the action at once.
38355
+ addKeptValueSource(rootAction, (params) =>
38356
+ takeKeptRead(getKeptReadKey(keptName, params)),
38357
+ );
38358
+ }
38302
38359
  const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
38360
+ keptNameWeakMap.set(actionBoundToRoute, keptName);
38303
38361
  routeActionEntriesSignal.value = [
38304
38362
  ...routeActionEntriesSignal.peek(),
38305
38363
  {
@@ -38390,6 +38448,13 @@ const anyMatchingRouteSignal = (routes) => {
38390
38448
  return anyMatchingSignal;
38391
38449
  };
38392
38450
 
38451
+ const rootActionOf = (action) => {
38452
+ if (action.isProxy) {
38453
+ return rootActionOf(getActionPrivateProperties(action).currentAction);
38454
+ }
38455
+ return action.rootAction || action;
38456
+ };
38457
+
38393
38458
  const haveSameMembers = (setA, setB) => {
38394
38459
  if (setA.size !== setB.size) {
38395
38460
  return false;
@@ -38417,6 +38482,15 @@ const haveSameMembers = (setA, setB) => {
38417
38482
  * (shownRangeReadersSignal). A search typed on the page is neither, and is not
38418
38483
  * kept.
38419
38484
  *
38485
+ * What a read is made of on the way out depends on what it holds. A resource
38486
+ * read holds ids, which name rows a new document's store does not have: its
38487
+ * rows are written, and re-enter the store. Any other route action holds its
38488
+ * value, written as it is when JSON brings it back as it was — a page's code
38489
+ * (a function), a Date built by the callback, a Map, are not, and are left
38490
+ * out. Such an action is recognized in the next document by its place among
38491
+ * the route actions declared (see getRouteActionKeptName), not by a name the
38492
+ * app would have to give every action made from a callback.
38493
+ *
38420
38494
  * A read is written once it has answered in this document. Before that it
38421
38495
  * keeps what the previous document kept for it, never what it shows meanwhile:
38422
38496
  * an answer handed over from other params (a list standing in for the next
@@ -38443,17 +38517,19 @@ let stopKeepingCurrent = null;
38443
38517
  * draws it again while its reads go out, rather than skeletons.
38444
38518
  *
38445
38519
  * What the page reads is what navi runs for it: the route actions asking
38446
- * something for it (see `activeRouteActionsSignal`) whose action is a resource
38447
- * `GET` or `GET_MANY`, and the `GET_RANGE` compositions the `<List.Items>` on
38448
- * screen read, around the window they draw. A navigation replaces the slot with
38449
- * the next page's reads, and the page left is dropped. In the next document,
38450
- * the first run of the same read with the same params draws the kept answer as
38451
- * its provisional value: `data` set while `loading` is `true`, the answer
38452
- * replacing it. Under a network policy answering reads from the store, the
38453
- * kept answer answers the read. The copy follows the store: a `PUT` on a row
38454
- * rewrites it.
38455
- *
38456
- * Call it before the routes start: the first runs are the ones that look.
38520
+ * something for it (see `activeRouteActionsSignal`), and the `GET_RANGE`
38521
+ * compositions the `<List.Items>` on screen read, around the window they draw.
38522
+ * A resource read keeps its rows; any other route action keeps its value when
38523
+ * JSON brings it back as it was. A navigation replaces the slot with the next
38524
+ * page's reads, and the page left is dropped. In the next document, the first
38525
+ * run of the same read with the same params draws the kept answer as its
38526
+ * provisional value: `data` set while `loading` is `true`, the answer replacing
38527
+ * it. Under a network policy answering reads from the store, a resource read
38528
+ * completes with it. The copy follows the store: a `PUT` on a row rewrites it.
38529
+ *
38530
+ * Call it before the routes start: the first runs are the ones that look. And
38531
+ * declare the route actions where the app starts: one that is not a resource
38532
+ * read is recognized by its place among them.
38457
38533
  *
38458
38534
  * @param {object} options
38459
38535
  * @param {import("@preact/signals").Signal} options.signal - where the slot is
@@ -38576,7 +38652,7 @@ const keepPageOnScreen = ({
38576
38652
  });
38577
38653
 
38578
38654
  const actionReadWeakMap = new WeakMap();
38579
- const actionReadOf = (action) => {
38655
+ const actionReadOf = (action, keptName, readEntryOf) => {
38580
38656
  let read = actionReadWeakMap.get(action);
38581
38657
  if (read) {
38582
38658
  return read;
@@ -38590,16 +38666,10 @@ const keepPageOnScreen = ({
38590
38666
  answeredActionWeakSet.add(action);
38591
38667
  }
38592
38668
  return {
38593
- key: getKeptReadKey(action.name, action.params),
38669
+ key: getKeptReadKey(keptName, action.params),
38594
38670
  answered: answeredActionWeakSet.has(action),
38595
38671
  landed,
38596
- readEntry: () => {
38597
- const data = action.dataSignal.value;
38598
- if (data === undefined || data === null) {
38599
- return undefined;
38600
- }
38601
- return serializeData(data);
38602
- },
38672
+ readEntry: () => readEntryOf(action),
38603
38673
  };
38604
38674
  };
38605
38675
  actionReadWeakMap.set(action, read);
@@ -38638,10 +38708,20 @@ const keepPageOnScreen = ({
38638
38708
  // eslint-disable-next-line no-unused-expressions
38639
38709
  routeAction.callSource;
38640
38710
  const action = getActionPrivateProperties(routeAction).currentAction;
38641
- if (action.params === NO_PARAMS || !isKeepableRead(action)) {
38711
+ if (action.params === NO_PARAMS) {
38642
38712
  continue;
38643
38713
  }
38644
- reads.push(actionReadOf(action));
38714
+ if (isKeepableRead(action)) {
38715
+ reads.push(actionReadOf(action, action.name, readRows));
38716
+ } else {
38717
+ reads.push(
38718
+ actionReadOf(
38719
+ action,
38720
+ getRouteActionKeptName(routeAction),
38721
+ readPlainValue,
38722
+ ),
38723
+ );
38724
+ }
38645
38725
  }
38646
38726
  for (const reader of shownRangeReadersSignal.value.keys()) {
38647
38727
  reads.push(rangeReadOf(reader));
@@ -38727,6 +38807,57 @@ const keepPageOnScreen = ({
38727
38807
  return stop;
38728
38808
  };
38729
38809
 
38810
+ const readRows = (action) => {
38811
+ const data = action.dataSignal.value;
38812
+ if (data === undefined || data === null) {
38813
+ return undefined;
38814
+ }
38815
+ return serializeData(data);
38816
+ };
38817
+ const readPlainValue = (action) => {
38818
+ const value = action.valueSignal.value;
38819
+ if (value === undefined || !isPlainData(value, new Set())) {
38820
+ return undefined;
38821
+ }
38822
+ return value;
38823
+ };
38824
+ // What JSON writes and reads back as it was. `undefined` inside is accepted:
38825
+ // JSON drops the key, which reads the same.
38826
+ const isPlainData = (value, ancestorSet) => {
38827
+ if (value === null || value === undefined) {
38828
+ return true;
38829
+ }
38830
+ const type = typeof value;
38831
+ if (type === "string" || type === "boolean") {
38832
+ return true;
38833
+ }
38834
+ if (type === "number") {
38835
+ return Number.isFinite(value);
38836
+ }
38837
+ if (type !== "object") {
38838
+ return false;
38839
+ }
38840
+ if (ancestorSet.has(value)) {
38841
+ return false;
38842
+ }
38843
+ const isArray = Array.isArray(value);
38844
+ if (!isArray) {
38845
+ const proto = Object.getPrototypeOf(value);
38846
+ if (proto !== Object.prototype && proto !== null) {
38847
+ return false;
38848
+ }
38849
+ }
38850
+ ancestorSet.add(value);
38851
+ const entries = isArray ? value : Object.values(value);
38852
+ for (const entry of entries) {
38853
+ if (!isPlainData(entry, ancestorSet)) {
38854
+ return false;
38855
+ }
38856
+ }
38857
+ ancestorSet.delete(value);
38858
+ return true;
38859
+ };
38860
+
38730
38861
  /**
38731
38862
  * A popup that is the anchor, brought to the front.
38732
38863
  *