@chelone/core 0.6.0 → 0.7.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.
Files changed (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -1,210 +1,230 @@
1
- import { oneKey, infiniteListKeyPrefix, manyKeyPrefix } from "./keys.js";
1
+ import { infiniteListKeyPrefix, manyKeyPrefix, oneKey } from "./keys.js";
2
+ // src/data/cache-ops.ts
3
+ /**
4
+ * Vanilla (react/vue-free) query-cache mutations shared by the undoable
5
+ * orchestration in BOTH adapters. They are the `applyOptimistic` bodies passed
6
+ * to {@link startUndoableMutation}: an optimistic delete removes rows now, an
7
+ * optimistic update patches them now, and the deferred dataProvider call is
8
+ * withheld until commit. Rollback is handled by the undoable snapshot (these
9
+ * never mutate an existing cache object in place — every write is a fresh
10
+ * object — so the pre-apply snapshot stays a valid rollback target).
11
+ */
12
+ /** Partial-match keys of the caches holding one `GetListResult` per query (not one per page). */
2
13
  function singleResultListKeyPrefixes(resource) {
3
- return [
4
- [resource, "getList"],
5
- [resource, "getManyReference"]
6
- ];
14
+ return [[resource, "getList"], [resource, "getManyReference"]];
7
15
  }
16
+ function singleResultEntries(queryClient, queryKey) {
17
+ return queryClient.getQueriesData({ queryKey });
18
+ }
19
+ function infiniteEntries(queryClient, resource) {
20
+ return queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
21
+ }
22
+ /** Filters ids out of a page; `undefined` when it holds none, so the cached object is kept. */
8
23
  function withoutIds(page, idSet) {
9
- const kept = page.data.filter((record) => !idSet.has(String(record.id)));
10
- return kept.length === page.data.length ? void 0 : { ...page, data: kept };
24
+ const kept = page.data.filter((record) => !idSet.has(String(record.id)));
25
+ return kept.length === page.data.length ? void 0 : {
26
+ ...page,
27
+ data: kept
28
+ };
11
29
  }
30
+ /** Shallow-merges `data` into the matching rows; `undefined` when none match. */
12
31
  function withPatchedRows(page, idSet, data) {
13
- let changed = false;
14
- const nextData = page.data.map((record) => {
15
- if (idSet.has(String(record.id))) {
16
- changed = true;
17
- return { ...record, ...data };
18
- }
19
- return record;
20
- });
21
- return changed ? { ...page, data: nextData } : void 0;
32
+ let changed = false;
33
+ const nextData = page.data.map((record) => {
34
+ if (idSet.has(String(record.id))) {
35
+ changed = true;
36
+ return {
37
+ ...record,
38
+ ...data
39
+ };
40
+ }
41
+ return record;
42
+ });
43
+ return changed ? {
44
+ ...page,
45
+ data: nextData
46
+ } : void 0;
22
47
  }
48
+ /** `total` after `removed` rows left; stays at 0 if a stale total is lower than the rows removed. */
23
49
  function lowerTotal(total, removed) {
24
- return total == null ? total : Math.max(0, total - removed);
50
+ return total == null ? total : Math.max(0, total - removed);
25
51
  }
52
+ /**
53
+ * Optimistically remove records: drop each `getOne` entry and filter the id(s)
54
+ * out of every cached `getList`, `getManyReference` and `getInfiniteList` page
55
+ * (decrementing `total` when known) and `getMany` entry.
56
+ */
26
57
  function removeRecordsFromCache(queryClient, resource, ids) {
27
- const idSet = new Set(ids.map(String));
28
- for (const id of ids) {
29
- queryClient.removeQueries({ queryKey: oneKey(resource, { id }) });
30
- }
31
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
32
- for (const [key, value] of queryClient.getQueriesData({
33
- queryKey
34
- })) {
35
- if (!value) continue;
36
- const next = withoutIds(value, idSet);
37
- if (!next) continue;
38
- const removed = value.data.length - next.data.length;
39
- queryClient.setQueryData(key, {
40
- ...next,
41
- total: lowerTotal(value.total, removed)
42
- });
43
- }
44
- }
45
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
46
- for (const [key, value] of infinite) {
47
- if (!value) continue;
48
- const kept = value.pages.map((page) => withoutIds(page, idSet) ?? page);
49
- const removed = value.pages.reduce(
50
- (n, page, i) => n + page.data.length - (kept[i]?.data.length ?? 0),
51
- 0
52
- );
53
- if (removed === 0) continue;
54
- queryClient.setQueryData(key, {
55
- ...value,
56
- pages: kept.map((page) => ({
57
- ...page,
58
- total: lowerTotal(page.total, removed)
59
- }))
60
- });
61
- }
62
- removeManyEntries(queryClient, resource, idSet);
58
+ const idSet = new Set(ids.map(String));
59
+ for (const id of ids) queryClient.removeQueries({ queryKey: oneKey(resource, { id }) });
60
+ for (const queryKey of singleResultListKeyPrefixes(resource)) for (const [key, value] of singleResultEntries(queryClient, queryKey)) {
61
+ if (!value) continue;
62
+ const next = withoutIds(value, idSet);
63
+ if (!next) continue;
64
+ const removed = value.data.length - next.data.length;
65
+ queryClient.setQueryData(key, {
66
+ ...next,
67
+ total: lowerTotal(value.total, removed)
68
+ });
69
+ }
70
+ const infinite = infiniteEntries(queryClient, resource);
71
+ for (const [key, value] of infinite) {
72
+ if (!value) continue;
73
+ const kept = value.pages.map((page) => withoutIds(page, idSet) ?? page);
74
+ const removed = value.pages.reduce((n, page, i) => n + page.data.length - (kept[i]?.data.length ?? 0), 0);
75
+ if (removed === 0) continue;
76
+ queryClient.setQueryData(key, {
77
+ ...value,
78
+ pages: kept.map((page) => ({
79
+ ...page,
80
+ total: lowerTotal(page.total, removed)
81
+ }))
82
+ });
83
+ }
84
+ removeManyEntries(queryClient, resource, idSet);
63
85
  }
86
+ /**
87
+ * Optimistically patch records: shallow-merge `data` into each matching
88
+ * `getOne` entry and every matching row of every cached `getList`,
89
+ * `getManyReference`, `getInfiniteList` page and `getMany` entry.
90
+ */
64
91
  function updateRecordsInCache(queryClient, resource, ids, data) {
65
- const idSet = new Set(ids.map(String));
66
- for (const id of ids) {
67
- const key = oneKey(resource, { id });
68
- const previous = queryClient.getQueryData(key);
69
- if (previous !== void 0) {
70
- queryClient.setQueryData(key, { ...previous, ...data });
71
- }
72
- }
73
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
74
- for (const [key, value] of queryClient.getQueriesData({
75
- queryKey
76
- })) {
77
- if (!value) continue;
78
- const next = withPatchedRows(value, idSet, data);
79
- if (next) queryClient.setQueryData(key, next);
80
- }
81
- }
82
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
83
- for (const [key, value] of infinite) {
84
- if (!value) continue;
85
- const pages = value.pages.map(
86
- (page) => withPatchedRows(page, idSet, data) ?? page
87
- );
88
- if (pages.every((page, i) => page === value.pages[i])) continue;
89
- queryClient.setQueryData(key, {
90
- ...value,
91
- pages
92
- });
93
- }
94
- patchManyEntries(queryClient, resource, idSet, data);
92
+ const idSet = new Set(ids.map(String));
93
+ for (const id of ids) {
94
+ const key = oneKey(resource, { id });
95
+ const previous = queryClient.getQueryData(key);
96
+ if (previous !== void 0) queryClient.setQueryData(key, {
97
+ ...previous,
98
+ ...data
99
+ });
100
+ }
101
+ for (const queryKey of singleResultListKeyPrefixes(resource)) for (const [key, value] of singleResultEntries(queryClient, queryKey)) {
102
+ if (!value) continue;
103
+ const next = withPatchedRows(value, idSet, data);
104
+ if (next) queryClient.setQueryData(key, next);
105
+ }
106
+ const infinite = infiniteEntries(queryClient, resource);
107
+ for (const [key, value] of infinite) {
108
+ if (!value) continue;
109
+ const pages = value.pages.map((page) => withPatchedRows(page, idSet, data) ?? page);
110
+ if (pages.every((page, i) => page === value.pages[i])) continue;
111
+ queryClient.setQueryData(key, {
112
+ ...value,
113
+ pages
114
+ });
115
+ }
116
+ patchManyEntries(queryClient, resource, idSet, data);
95
117
  }
96
- const ABSENT = /* @__PURE__ */ Symbol("absent");
118
+ var ABSENT = Symbol("absent");
97
119
  function priorsOf(page, idSet, data) {
98
- const priors = /* @__PURE__ */ new Map();
99
- for (const row of page.data) {
100
- const id = String(row.id);
101
- if (!idSet.has(id)) continue;
102
- const prior = {};
103
- for (const field of Object.keys(data)) {
104
- prior[field] = field in row ? row[field] : ABSENT;
105
- }
106
- priors.set(id, prior);
107
- }
108
- return priors;
120
+ const priors = /* @__PURE__ */ new Map();
121
+ for (const row of page.data) {
122
+ const id = String(row.id);
123
+ if (!idSet.has(id)) continue;
124
+ const prior = {};
125
+ for (const field in data) {
126
+ if (!Object.hasOwn(data, field)) continue;
127
+ prior[field] = field in row ? row[field] : ABSENT;
128
+ }
129
+ priors.set(id, prior);
130
+ }
131
+ return priors;
109
132
  }
133
+ /** Puts the prior values back into the rows they were taken from. */
110
134
  function withRestoredRows(page, priors) {
111
- return {
112
- ...page,
113
- data: page.data.map((record) => {
114
- const prior = priors.get(String(record.id));
115
- if (!prior) return record;
116
- const restored = { ...record };
117
- for (const [field, value] of Object.entries(prior)) {
118
- if (value === ABSENT) delete restored[field];
119
- else restored[field] = value;
120
- }
121
- return restored;
122
- })
123
- };
135
+ return {
136
+ ...page,
137
+ data: page.data.map((record) => {
138
+ const prior = priors.get(String(record.id));
139
+ if (!prior) return record;
140
+ const restored = {
141
+ ...record,
142
+ ...prior
143
+ };
144
+ for (const field in prior) if (prior[field] === ABSENT) delete restored[field];
145
+ return restored;
146
+ })
147
+ };
124
148
  }
149
+ /**
150
+ * Shallow-merge `data` into the matching rows of every cached `getList`,
151
+ * `getManyReference` and `getInfiniteList` page, and return a `rollback` that
152
+ * puts back only the patched keys of those rows into whatever each entry holds
153
+ * when it is called, so another update's patch to a different row survives.
154
+ * An entry that holds none of the ids keeps its cached object.
155
+ */
125
156
  function patchListEntries(queryClient, resource, idSet, data) {
126
- const rollbacks = [];
127
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
128
- for (const [key, value] of queryClient.getQueriesData({
129
- queryKey
130
- })) {
131
- if (!value) continue;
132
- const next = withPatchedRows(value, idSet, data);
133
- if (!next) continue;
134
- const priors = priorsOf(value, idSet, data);
135
- queryClient.setQueryData(key, next);
136
- rollbacks.push(() => {
137
- const current = queryClient.getQueryData(key);
138
- if (current) {
139
- queryClient.setQueryData(
140
- key,
141
- withRestoredRows(current, priors)
142
- );
143
- }
144
- });
145
- }
146
- }
147
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
148
- for (const [key, value] of infinite) {
149
- if (!value) continue;
150
- const pages = value.pages.map(
151
- (page) => withPatchedRows(page, idSet, data) ?? page
152
- );
153
- if (pages.every((page, i) => page === value.pages[i])) continue;
154
- const priorsByPage = value.pages.map((page) => priorsOf(page, idSet, data));
155
- queryClient.setQueryData(key, {
156
- ...value,
157
- pages
158
- });
159
- rollbacks.push(() => {
160
- const current = queryClient.getQueryData(key);
161
- if (!current) return;
162
- queryClient.setQueryData(key, {
163
- ...current,
164
- pages: current.pages.map((page, i) => {
165
- const priors = priorsByPage[i];
166
- return priors && priors.size > 0 ? withRestoredRows(page, priors) : page;
167
- })
168
- });
169
- });
170
- }
171
- return () => {
172
- for (const rollback of rollbacks) rollback();
173
- };
157
+ const rollbacks = [];
158
+ for (const queryKey of singleResultListKeyPrefixes(resource)) for (const [key, value] of singleResultEntries(queryClient, queryKey)) {
159
+ if (!value) continue;
160
+ const next = withPatchedRows(value, idSet, data);
161
+ if (!next) continue;
162
+ const priors = priorsOf(value, idSet, data);
163
+ queryClient.setQueryData(key, next);
164
+ rollbacks.push(() => {
165
+ const current = queryClient.getQueryData(key);
166
+ if (current) queryClient.setQueryData(key, withRestoredRows(current, priors));
167
+ });
168
+ }
169
+ const infinite = infiniteEntries(queryClient, resource);
170
+ for (const [key, value] of infinite) {
171
+ if (!value) continue;
172
+ const pages = value.pages.map((page) => withPatchedRows(page, idSet, data) ?? page);
173
+ if (pages.every((page, i) => page === value.pages[i])) continue;
174
+ const priorsByPage = value.pages.map((page) => priorsOf(page, idSet, data));
175
+ queryClient.setQueryData(key, {
176
+ ...value,
177
+ pages
178
+ });
179
+ rollbacks.push(() => {
180
+ const current = queryClient.getQueryData(key);
181
+ if (!current) return;
182
+ queryClient.setQueryData(key, {
183
+ ...current,
184
+ pages: current.pages.map((page, i) => {
185
+ const priors = priorsByPage[i];
186
+ return priors && priors.size > 0 ? withRestoredRows(page, priors) : page;
187
+ })
188
+ });
189
+ });
190
+ }
191
+ return () => {
192
+ for (const rollback of rollbacks) rollback();
193
+ };
174
194
  }
195
+ /**
196
+ * Shallow-merge `data` into the matching records of every cached `getMany`
197
+ * entry (what `ReferenceField` and `ReferenceArrayField` read through).
198
+ */
175
199
  function patchManyEntries(queryClient, resource, idSet, data) {
176
- const manys = queryClient.getQueriesData({
177
- queryKey: manyKeyPrefix(resource)
178
- });
179
- for (const [key, value] of manys) {
180
- if (!value) continue;
181
- let changed = false;
182
- const next = value.map((record) => {
183
- if (idSet.has(String(record.id))) {
184
- changed = true;
185
- return { ...record, ...data };
186
- }
187
- return record;
188
- });
189
- if (changed) queryClient.setQueryData(key, next);
190
- }
200
+ const manys = queryClient.getQueriesData({ queryKey: manyKeyPrefix(resource) });
201
+ for (const [key, value] of manys) {
202
+ if (!value) continue;
203
+ let changed = false;
204
+ const next = value.map((record) => {
205
+ if (idSet.has(String(record.id))) {
206
+ changed = true;
207
+ return {
208
+ ...record,
209
+ ...data
210
+ };
211
+ }
212
+ return record;
213
+ });
214
+ if (changed) queryClient.setQueryData(key, next);
215
+ }
191
216
  }
217
+ /**
218
+ * Drop the matching records from every cached `getMany` entry (what
219
+ * `ReferenceField` and `ReferenceArrayField` read through). An entry that
220
+ * holds none of the ids keeps its cached object.
221
+ */
192
222
  function removeManyEntries(queryClient, resource, idSet) {
193
- const manys = queryClient.getQueriesData({
194
- queryKey: manyKeyPrefix(resource)
195
- });
196
- for (const [key, value] of manys) {
197
- if (!value) continue;
198
- const kept = value.filter((record) => !idSet.has(String(record.id)));
199
- if (kept.length !== value.length) {
200
- queryClient.setQueryData(key, kept);
201
- }
202
- }
223
+ const manys = queryClient.getQueriesData({ queryKey: manyKeyPrefix(resource) });
224
+ for (const [key, value] of manys) {
225
+ if (!value) continue;
226
+ const kept = value.filter((record) => !idSet.has(String(record.id)));
227
+ if (kept.length !== value.length) queryClient.setQueryData(key, kept);
228
+ }
203
229
  }
204
- export {
205
- patchListEntries,
206
- patchManyEntries,
207
- removeManyEntries,
208
- removeRecordsFromCache,
209
- updateRecordsInCache
210
- };
230
+ export { patchListEntries, patchManyEntries, removeManyEntries, removeRecordsFromCache, updateRecordsInCache };
@@ -1,25 +1,27 @@
1
+ // src/data/combine-data-providers.ts
2
+ /**
3
+ * One provider for an app whose resources live in different backends.
4
+ * `matcher` runs on every call (nothing is cached, so routing can change at
5
+ * run time) and picks the provider that gets `(resource, params)`. A matcher
6
+ * that returns nothing or throws rejects the call, and only the nine verbs of
7
+ * the chosen provider are used.
8
+ */
1
9
  function combineDataProviders(matcher) {
2
- const providerFor = (resource) => {
3
- const provider = matcher(resource);
4
- if (!provider) {
5
- throw new Error(
6
- `combineDataProviders: no data provider matches resource "${resource}"`
7
- );
8
- }
9
- return provider;
10
- };
11
- return {
12
- getList: async (resource, params) => providerFor(resource).getList(resource, params),
13
- getOne: async (resource, params) => providerFor(resource).getOne(resource, params),
14
- create: async (resource, params) => providerFor(resource).create(resource, params),
15
- update: async (resource, params) => providerFor(resource).update(resource, params),
16
- delete: async (resource, params) => providerFor(resource).delete(resource, params),
17
- getMany: async (resource, params) => providerFor(resource).getMany(resource, params),
18
- getManyReference: async (resource, params) => providerFor(resource).getManyReference(resource, params),
19
- updateMany: async (resource, params) => providerFor(resource).updateMany(resource, params),
20
- deleteMany: async (resource, params) => providerFor(resource).deleteMany(resource, params)
21
- };
10
+ const providerFor = (resource) => {
11
+ const provider = matcher(resource);
12
+ if (!provider) throw new Error(`combineDataProviders: no data provider matches resource "${resource}"`);
13
+ return provider;
14
+ };
15
+ return {
16
+ getList: async (resource, params) => providerFor(resource).getList(resource, params),
17
+ getOne: async (resource, params) => providerFor(resource).getOne(resource, params),
18
+ create: async (resource, params) => providerFor(resource).create(resource, params),
19
+ update: async (resource, params) => providerFor(resource).update(resource, params),
20
+ delete: async (resource, params) => providerFor(resource).delete(resource, params),
21
+ getMany: async (resource, params) => providerFor(resource).getMany(resource, params),
22
+ getManyReference: async (resource, params) => providerFor(resource).getManyReference(resource, params),
23
+ updateMany: async (resource, params) => providerFor(resource).updateMany(resource, params),
24
+ deleteMany: async (resource, params) => providerFor(resource).deleteMany(resource, params)
25
+ };
22
26
  }
23
- export {
24
- combineDataProviders
25
- };
27
+ export { combineDataProviders };
@@ -0,0 +1,45 @@
1
+ import { type MicroStore } from "../store/micro-store.js";
2
+ /** One settled data provider call. */
3
+ export interface DataProviderLogEntry {
4
+ /** Starts at 1 and grows by one per call, in the order calls start. */
5
+ id: number;
6
+ /** The property name called on the provider: a verb or a custom method. */
7
+ method: string;
8
+ /** The first argument when it is a string, else `""`. */
9
+ resource: string;
10
+ /** The second argument as passed. */
11
+ params: unknown;
12
+ status: "ok" | "error";
13
+ durationMs: number;
14
+ /** ISO timestamp taken at the start of the call. */
15
+ at: string;
16
+ /** What the call resolved with. Present when `status` is `"ok"`. */
17
+ result?: unknown;
18
+ /** The error's message, or `String(err)` for a non-Error. Present when `status` is `"error"`. */
19
+ error?: string;
20
+ }
21
+ export interface DataProviderLogOptions {
22
+ /** How many of the newest entries to keep. A value below 1 reads as 1. */
23
+ maxEntries?: number;
24
+ /** Timer for `durationMs`. */
25
+ now?: () => number;
26
+ /** Wall clock for `at`. */
27
+ clock?: () => Date;
28
+ }
29
+ export interface DataProviderLog {
30
+ store: MicroStore<{
31
+ entries: readonly DataProviderLogEntry[];
32
+ }>;
33
+ /**
34
+ * Returns a copy of the provider whose every function property records its
35
+ * calls. The original is never modified.
36
+ */
37
+ wrap<TProvider extends object>(dataProvider: TProvider): TProvider;
38
+ /** Empties the entries. Ids keep counting. */
39
+ clear(): void;
40
+ }
41
+ /**
42
+ * A log of every call through a wrapped data provider, kept in a store both
43
+ * adapters subscribe to. Wrap once, as `withLifecycleCallbacks` does.
44
+ */
45
+ export declare function createDataProviderLog({ maxEntries, now, clock, }?: DataProviderLogOptions): DataProviderLog;
@@ -0,0 +1,93 @@
1
+ import { createMicroStore } from "../store/micro-store.js";
2
+ // src/data/data-provider-log.ts
3
+ /**
4
+ * Runs `call` and returns a promise for it: a synchronous throw becomes a
5
+ * rejection and a native promise is chained on directly, so entries land in
6
+ * the order calls settle (resolving a new promise with it would defer them to
7
+ * the order calls started).
8
+ */
9
+ function settled(call) {
10
+ try {
11
+ return Promise.resolve(call());
12
+ } catch (err) {
13
+ return Promise.reject(err);
14
+ }
15
+ }
16
+ function isMethod(value) {
17
+ return typeof value === "function";
18
+ }
19
+ /**
20
+ * The methods of a provider by name, own and inherited, so a class-based
21
+ * provider's prototype methods are found. The nearest definition wins.
22
+ */
23
+ function methodsOf(dataProvider) {
24
+ const methods = /* @__PURE__ */ new Map();
25
+ const seen = /* @__PURE__ */ new Set();
26
+ for (let level = dataProvider; level && level !== Object.prototype; level = Object.getPrototypeOf(level)) for (const [name, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(level))) {
27
+ if (name === "constructor" || seen.has(name)) continue;
28
+ seen.add(name);
29
+ if (isMethod(descriptor.value)) methods.set(name, descriptor.value);
30
+ }
31
+ return methods;
32
+ }
33
+ /**
34
+ * A log of every call through a wrapped data provider, kept in a store both
35
+ * adapters subscribe to. Wrap once, as `withLifecycleCallbacks` does.
36
+ */
37
+ function createDataProviderLog({ maxEntries = 50, now = () => performance.now(), clock = () => /* @__PURE__ */ new Date() } = {}) {
38
+ const limit = maxEntries >= 1 ? Math.floor(maxEntries) : 1;
39
+ const store = createMicroStore({ entries: [] });
40
+ let lastId = 0;
41
+ const record = (entry) => {
42
+ store.setState((prev) => ({ entries: [...prev.entries, entry].slice(-limit) }));
43
+ };
44
+ const wrap = (dataProvider) => {
45
+ const copy = { ...dataProvider };
46
+ Object.setPrototypeOf(copy, Object.getPrototypeOf(dataProvider));
47
+ for (const [method, value] of methodsOf(dataProvider)) {
48
+ const logged = (...args) => {
49
+ const id = ++lastId;
50
+ const at = clock().toISOString();
51
+ const started = now();
52
+ const [resource, params] = args;
53
+ const base = {
54
+ id,
55
+ method,
56
+ resource: typeof resource === "string" ? resource : "",
57
+ params,
58
+ at
59
+ };
60
+ return settled(() => Reflect.apply(value, dataProvider, args)).then((result) => {
61
+ record({
62
+ ...base,
63
+ status: "ok",
64
+ durationMs: now() - started,
65
+ result
66
+ });
67
+ return result;
68
+ }, (err) => {
69
+ record({
70
+ ...base,
71
+ status: "error",
72
+ durationMs: now() - started,
73
+ error: err instanceof Error ? err.message : String(err)
74
+ });
75
+ throw err;
76
+ });
77
+ };
78
+ Object.defineProperty(copy, method, {
79
+ value: logged,
80
+ writable: true,
81
+ enumerable: true,
82
+ configurable: true
83
+ });
84
+ }
85
+ return copy;
86
+ };
87
+ return {
88
+ store,
89
+ wrap,
90
+ clear: () => store.setState({ entries: [] })
91
+ };
92
+ }
93
+ export { createDataProviderLog };
@@ -1,11 +1,15 @@
1
- class ValidationError extends Error {
2
- name = "ValidationError";
3
- fieldErrors;
4
- constructor(message, fieldErrors) {
5
- super(message);
6
- this.fieldErrors = fieldErrors;
7
- }
8
- }
9
- export {
10
- ValidationError
1
+ // src/data/errors.ts
2
+ /**
3
+ * Thrown/rejected by DataProvider mutations when the server rejects on
4
+ * validation. `fieldErrors` maps field source paths to messages — P5 maps
5
+ * this onto form state. Framework-free by design.
6
+ */
7
+ var ValidationError = class extends Error {
8
+ name = "ValidationError";
9
+ fieldErrors;
10
+ constructor(message, fieldErrors) {
11
+ super(message);
12
+ this.fieldErrors = fieldErrors;
13
+ }
11
14
  };
15
+ export { ValidationError };
@@ -1,6 +1,9 @@
1
1
  import type { RecordOf, ResourceMap } from "../resource/registry.js";
2
2
  import type { DataProvider } from "./types.js";
3
+ /** One array of records per resource name, each typed by that resource's schema. */
4
+ type FakeTables<TResources extends ResourceMap> = {
5
+ [N in keyof TResources & string]?: RecordOf<TResources, N>[];
6
+ };
3
7
  /** In-memory DataProvider for tests and dogfooding. Mutates its own copy of the seed. */
4
- export declare function fakeDataProvider<TResources extends ResourceMap>(initialData: {
5
- [K in keyof TResources]?: RecordOf<TResources, K>[];
6
- }): DataProvider<TResources>;
8
+ export declare function fakeDataProvider<TResources extends ResourceMap>(initialData: FakeTables<TResources>): DataProvider<TResources>;
9
+ export {};