@chelone/core 0.5.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 (253) hide show
  1. package/dist/assistant/assistant-session.d.ts +28 -0
  2. package/dist/assistant/assistant-session.js +208 -0
  3. package/dist/assistant/echo-transport.d.ts +7 -0
  4. package/dist/assistant/echo-transport.js +28 -0
  5. package/dist/assistant/http-transport.d.ts +10 -0
  6. package/dist/assistant/http-transport.js +98 -0
  7. package/dist/assistant/types.d.ts +54 -0
  8. package/dist/auth/access-control.d.ts +13 -5
  9. package/dist/auth/access-control.js +25 -21
  10. package/dist/auth/auth-options.d.ts +21 -17
  11. package/dist/auth/auth-options.js +80 -48
  12. package/dist/auth/permission-matrix.d.ts +12 -0
  13. package/dist/auth/permission-matrix.js +67 -0
  14. package/dist/auth/types.d.ts +14 -4
  15. package/dist/block-editor/block-document.d.ts +60 -0
  16. package/dist/block-editor/block-document.js +143 -0
  17. package/dist/block-editor/data-blocks.d.ts +63 -0
  18. package/dist/block-editor/data-blocks.js +117 -0
  19. package/dist/chart/chart-data.d.ts +25 -0
  20. package/dist/chart/chart-data.js +61 -0
  21. package/dist/command-menu/hotkey.js +58 -47
  22. package/dist/command-menu/recents.js +8 -11
  23. package/dist/command-menu/registry.d.ts +9 -5
  24. package/dist/command-menu/registry.js +36 -46
  25. package/dist/controller/calendar-dates.js +82 -113
  26. package/dist/controller/calendar-events.js +93 -88
  27. package/dist/controller/client-list-controller.d.ts +2 -2
  28. package/dist/controller/client-list-controller.js +251 -254
  29. package/dist/controller/data-table-columns.d.ts +2 -0
  30. package/dist/controller/data-table-columns.js +50 -56
  31. package/dist/controller/deep-equal.d.ts +1 -0
  32. package/dist/controller/deep-equal.js +14 -0
  33. package/dist/controller/filter-toggle.js +36 -44
  34. package/dist/controller/in-place-editor.d.ts +42 -0
  35. package/dist/controller/in-place-editor.js +42 -0
  36. package/dist/controller/infinite-list-controller.js +220 -190
  37. package/dist/controller/inspector.d.ts +49 -0
  38. package/dist/controller/inspector.js +64 -0
  39. package/dist/controller/job-monitor.d.ts +41 -0
  40. package/dist/controller/job-monitor.js +49 -0
  41. package/dist/controller/kanban-board.d.ts +2 -3
  42. package/dist/controller/kanban-board.js +43 -24
  43. package/dist/controller/list-controller.d.ts +5 -5
  44. package/dist/controller/list-controller.js +444 -400
  45. package/dist/controller/list-params.js +96 -87
  46. package/dist/controller/pivot-grid.d.ts +36 -0
  47. package/dist/controller/pivot-grid.js +99 -0
  48. package/dist/controller/prev-next.d.ts +2 -2
  49. package/dist/controller/prev-next.js +74 -54
  50. package/dist/controller/record-controllers.d.ts +14 -6
  51. package/dist/controller/record-controllers.js +288 -257
  52. package/dist/controller/record-diff.d.ts +21 -0
  53. package/dist/controller/record-diff.js +50 -0
  54. package/dist/controller/record-timeline.d.ts +35 -0
  55. package/dist/controller/record-timeline.js +48 -0
  56. package/dist/controller/record-tree.js +57 -47
  57. package/dist/controller/record-workflow.d.ts +38 -16
  58. package/dist/controller/record-workflow.js +85 -64
  59. package/dist/controller/reference-many-controller.js +198 -192
  60. package/dist/controller/reference-one-controller.d.ts +1 -1
  61. package/dist/controller/reference-one-controller.js +63 -56
  62. package/dist/controller/saved-queries.js +104 -91
  63. package/dist/controller/translate-fallback.js +10 -6
  64. package/dist/csv/export.d.ts +2 -1
  65. package/dist/csv/export.js +43 -40
  66. package/dist/csv/import-controller.js +185 -169
  67. package/dist/csv/match-headers.js +29 -22
  68. package/dist/csv/parse-with.js +60 -54
  69. package/dist/csv/run-import.js +69 -72
  70. package/dist/csv/validate-rows.d.ts +1 -1
  71. package/dist/csv/validate-rows.js +65 -52
  72. package/dist/data/cache-ops.d.ts +5 -4
  73. package/dist/data/cache-ops.js +208 -190
  74. package/dist/data/combine-data-providers.js +25 -23
  75. package/dist/data/data-provider-log.d.ts +45 -0
  76. package/dist/data/data-provider-log.js +93 -0
  77. package/dist/data/errors.js +14 -10
  78. package/dist/data/fake-provider.d.ts +6 -3
  79. package/dist/data/fake-provider.js +129 -114
  80. package/dist/data/fetch-utils.js +61 -44
  81. package/dist/data/filter-branch.d.ts +5 -0
  82. package/dist/data/filter-branch.js +10 -0
  83. package/dist/data/get-many-aggregate.js +84 -76
  84. package/dist/data/http-error.js +17 -12
  85. package/dist/data/keys.js +50 -36
  86. package/dist/data/mutation-options.d.ts +14 -9
  87. package/dist/data/mutation-options.js +93 -109
  88. package/dist/data/query-options.js +73 -68
  89. package/dist/data/remove-empty.d.ts +3 -0
  90. package/dist/data/remove-empty.js +11 -0
  91. package/dist/data/undoable.d.ts +2 -2
  92. package/dist/data/undoable.js +190 -174
  93. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  94. package/dist/data/with-lifecycle-callbacks.js +66 -52
  95. package/dist/environment/application-update-store.js +58 -55
  96. package/dist/environment/offline-store.js +21 -21
  97. package/dist/form/array-binding.d.ts +30 -11
  98. package/dist/form/array-binding.js +184 -166
  99. package/dist/form/choice-ids.d.ts +7 -0
  100. package/dist/form/choice-ids.js +11 -0
  101. package/dist/form/choices-controller.d.ts +3 -2
  102. package/dist/form/choices-controller.js +302 -275
  103. package/dist/form/date-format.d.ts +38 -0
  104. package/dist/form/date-format.js +89 -0
  105. package/dist/form/dirty-values.d.ts +2 -2
  106. package/dist/form/dirty-values.js +22 -11
  107. package/dist/form/duration.d.ts +22 -0
  108. package/dist/form/duration.js +49 -0
  109. package/dist/form/edit-create-form.d.ts +12 -5
  110. package/dist/form/edit-create-form.js +243 -188
  111. package/dist/form/event-value.d.ts +6 -0
  112. package/dist/form/event-value.js +17 -0
  113. package/dist/form/field-error.d.ts +14 -0
  114. package/dist/form/field-error.js +28 -0
  115. package/dist/form/field-name.d.ts +20 -0
  116. package/dist/form/field-name.js +6 -0
  117. package/dist/form/filter-form-values.js +63 -49
  118. package/dist/form/form-controller.d.ts +9 -1
  119. package/dist/form/form-controller.js +165 -160
  120. package/dist/form/form-groups.d.ts +4 -3
  121. package/dist/form/form-groups.js +79 -79
  122. package/dist/form/form-handle.d.ts +175 -0
  123. package/dist/form/form-handle.js +68 -0
  124. package/dist/form/input-binding.d.ts +56 -31
  125. package/dist/form/input-binding.js +169 -118
  126. package/dist/form/input-values.d.ts +130 -0
  127. package/dist/form/input-values.js +177 -0
  128. package/dist/form/json-text.js +51 -37
  129. package/dist/form/save-pipeline.d.ts +6 -8
  130. package/dist/form/save-pipeline.js +91 -82
  131. package/dist/form/set-form-values.d.ts +6 -0
  132. package/dist/form/set-form-values.js +10 -0
  133. package/dist/form/source.js +97 -65
  134. package/dist/form/text-of.d.ts +2 -0
  135. package/dist/form/text-of.js +7 -0
  136. package/dist/form/validators.d.ts +58 -19
  137. package/dist/form/validators.js +97 -71
  138. package/dist/geo/bbox.d.ts +5 -4
  139. package/dist/geo/bbox.js +69 -48
  140. package/dist/geo/bounds.d.ts +14 -3
  141. package/dist/geo/bounds.js +98 -68
  142. package/dist/geo/constraints.js +88 -93
  143. package/dist/geo/coordinates.js +31 -23
  144. package/dist/geo/editor.d.ts +13 -13
  145. package/dist/geo/editor.js +176 -153
  146. package/dist/geo/geocoding.d.ts +4 -4
  147. package/dist/geo/geocoding.js +38 -41
  148. package/dist/geo/geojson.d.ts +12 -74
  149. package/dist/geo/geojson.js +52 -40
  150. package/dist/geo/geometry-math.d.ts +4 -4
  151. package/dist/geo/geometry-math.js +100 -93
  152. package/dist/geo/index.d.ts +7 -6
  153. package/dist/geo/map-layers.d.ts +42 -7
  154. package/dist/geo/map-layers.js +88 -62
  155. package/dist/geo/map-style.js +31 -32
  156. package/dist/geo/messages.js +48 -45
  157. package/dist/geo/nominatim.js +88 -80
  158. package/dist/geo/osm-presets.d.ts +4 -3
  159. package/dist/geo/osm-presets.js +108 -86
  160. package/dist/geo/osm-tags.js +14 -12
  161. package/dist/geo/overpass.d.ts +14 -2
  162. package/dist/geo/overpass.js +83 -90
  163. package/dist/geo/shape-value.d.ts +9 -5
  164. package/dist/geo/shape-value.js +169 -146
  165. package/dist/geo/snap-to-roads.d.ts +3 -3
  166. package/dist/geo/snap-to-roads.js +55 -50
  167. package/dist/geo/throttle.js +36 -39
  168. package/dist/guards.d.ts +37 -0
  169. package/dist/guards.js +62 -0
  170. package/dist/i18n/default-messages.js +34 -29
  171. package/dist/i18n/english-messages.js +187 -189
  172. package/dist/i18n/i18n.d.ts +8 -2
  173. package/dist/i18n/i18n.js +47 -48
  174. package/dist/i18n/inflection.d.ts +34 -0
  175. package/dist/i18n/inflection.js +82 -0
  176. package/dist/i18n/messages.d.ts +10 -2
  177. package/dist/i18n/messages.js +17 -4
  178. package/dist/i18n/missing-keys.d.ts +19 -0
  179. package/dist/i18n/missing-keys.js +68 -0
  180. package/dist/i18n/translatable.d.ts +16 -1
  181. package/dist/i18n/translatable.js +40 -47
  182. package/dist/index.d.ts +96 -22
  183. package/dist/index.js +52 -257
  184. package/dist/json/deep-equal.d.ts +8 -0
  185. package/dist/json/deep-equal.js +24 -0
  186. package/dist/json/strip-json-comments.js +38 -42
  187. package/dist/noop.d.ts +2 -0
  188. package/dist/noop.js +4 -0
  189. package/dist/notification/error-message.d.ts +2 -0
  190. package/dist/notification/error-message.js +10 -0
  191. package/dist/notification/notification-store.d.ts +3 -2
  192. package/dist/notification/notification-store.js +41 -45
  193. package/dist/onboarding-tour/placement.d.ts +34 -0
  194. package/dist/onboarding-tour/placement.js +67 -0
  195. package/dist/onboarding-tour/tour-controller.d.ts +25 -0
  196. package/dist/onboarding-tour/tour-controller.js +50 -0
  197. package/dist/realtime/add-events-for-mutations.js +75 -81
  198. package/dist/realtime/announce-lock.d.ts +7 -0
  199. package/dist/realtime/announce-lock.js +18 -0
  200. package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
  201. package/dist/realtime/broadcast-channel-transport.js +69 -0
  202. package/dist/realtime/copy-members.js +22 -14
  203. package/dist/realtime/in-memory-lock-provider.js +41 -60
  204. package/dist/realtime/live.js +95 -101
  205. package/dist/realtime/lock-options.js +16 -16
  206. package/dist/realtime/lock-session.js +91 -78
  207. package/dist/realtime/memory-transport.js +26 -67
  208. package/dist/realtime/presence.js +120 -122
  209. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  210. package/dist/realtime/realtime-data-provider.js +45 -42
  211. package/dist/realtime/reconnect-backoff.d.ts +15 -0
  212. package/dist/realtime/reconnect-backoff.js +17 -0
  213. package/dist/realtime/sse-transport.d.ts +31 -0
  214. package/dist/realtime/sse-transport.js +235 -0
  215. package/dist/realtime/status-store.js +23 -28
  216. package/dist/realtime/topic-hub.d.ts +24 -0
  217. package/dist/realtime/topic-hub.js +66 -0
  218. package/dist/realtime/topics.js +10 -12
  219. package/dist/realtime/types.js +10 -12
  220. package/dist/realtime/unread-count.js +21 -21
  221. package/dist/realtime/websocket-transport.d.ts +32 -0
  222. package/dist/realtime/websocket-transport.js +306 -0
  223. package/dist/resource/describe-resource.d.ts +2 -0
  224. package/dist/resource/describe-resource.js +232 -219
  225. package/dist/resource/get-at-path.js +29 -14
  226. package/dist/resource/identifier.d.ts +18 -0
  227. package/dist/resource/identifier.js +27 -0
  228. package/dist/resource/record.d.ts +4 -0
  229. package/dist/resource/record.js +10 -0
  230. package/dist/resource/register.d.ts +7 -0
  231. package/dist/resource/registry-store.d.ts +45 -15
  232. package/dist/resource/registry-store.js +67 -60
  233. package/dist/resource/registry.d.ts +10 -0
  234. package/dist/resource/registry.js +8 -4
  235. package/dist/resource/source-path.d.ts +16 -1
  236. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  237. package/dist/rich-text/rich-text-extras.js +179 -0
  238. package/dist/rich-text/rich-text-image.d.ts +124 -0
  239. package/dist/rich-text/rich-text-image.js +163 -0
  240. package/dist/rich-text/rich-text.d.ts +62 -0
  241. package/dist/rich-text/rich-text.js +142 -0
  242. package/dist/router/location.d.ts +22 -4
  243. package/dist/router/location.js +41 -18
  244. package/dist/router/match.js +47 -39
  245. package/dist/router/paths.js +40 -34
  246. package/dist/router/record-from-location.d.ts +1 -1
  247. package/dist/router/record-from-location.js +25 -17
  248. package/dist/store/adapters.js +51 -55
  249. package/dist/store/micro-store.js +57 -59
  250. package/dist/store/shallow-equal.d.ts +12 -0
  251. package/dist/store/shallow-equal.js +20 -0
  252. package/dist/store/store.js +54 -57
  253. package/package.json +13 -11
@@ -1,212 +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(
10
- (record) => !idSet.has(String(record.id))
11
- );
12
- 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
+ };
13
29
  }
30
+ /** Shallow-merges `data` into the matching rows; `undefined` when none match. */
14
31
  function withPatchedRows(page, idSet, data) {
15
- let changed = false;
16
- const nextData = page.data.map((record) => {
17
- if (idSet.has(String(record.id))) {
18
- changed = true;
19
- return { ...record, ...data };
20
- }
21
- return record;
22
- });
23
- 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;
24
47
  }
48
+ /** `total` after `removed` rows left; stays at 0 if a stale total is lower than the rows removed. */
25
49
  function lowerTotal(total, removed) {
26
- return total == null ? total : Math.max(0, total - removed);
50
+ return total == null ? total : Math.max(0, total - removed);
27
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
+ */
28
57
  function removeRecordsFromCache(queryClient, resource, ids) {
29
- const idSet = new Set(ids.map(String));
30
- for (const id of ids) {
31
- queryClient.removeQueries({ queryKey: oneKey(resource, { id }) });
32
- }
33
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
34
- for (const [key, value] of queryClient.getQueriesData({
35
- queryKey
36
- })) {
37
- if (!value) continue;
38
- const next = withoutIds(value, idSet);
39
- if (!next) continue;
40
- const removed = value.data.length - next.data.length;
41
- queryClient.setQueryData(key, {
42
- ...next,
43
- total: lowerTotal(value.total, removed)
44
- });
45
- }
46
- }
47
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
48
- for (const [key, value] of infinite) {
49
- if (!value) continue;
50
- const kept = value.pages.map((page) => withoutIds(page, idSet) ?? page);
51
- const removed = value.pages.reduce(
52
- (n, page, i) => n + page.data.length - (kept[i]?.data.length ?? 0),
53
- 0
54
- );
55
- if (removed === 0) continue;
56
- queryClient.setQueryData(key, {
57
- ...value,
58
- pages: kept.map((page) => ({
59
- ...page,
60
- total: lowerTotal(page.total, removed)
61
- }))
62
- });
63
- }
64
- 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);
65
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
+ */
66
91
  function updateRecordsInCache(queryClient, resource, ids, data) {
67
- const idSet = new Set(ids.map(String));
68
- for (const id of ids) {
69
- const key = oneKey(resource, { id });
70
- const previous = queryClient.getQueryData(key);
71
- if (previous !== void 0) {
72
- queryClient.setQueryData(key, { ...previous, ...data });
73
- }
74
- }
75
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
76
- for (const [key, value] of queryClient.getQueriesData({
77
- queryKey
78
- })) {
79
- if (!value) continue;
80
- const next = withPatchedRows(value, idSet, data);
81
- if (next) queryClient.setQueryData(key, next);
82
- }
83
- }
84
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
85
- for (const [key, value] of infinite) {
86
- if (!value) continue;
87
- const pages = value.pages.map(
88
- (page) => withPatchedRows(page, idSet, data) ?? page
89
- );
90
- if (pages.every((page, i) => page === value.pages[i])) continue;
91
- queryClient.setQueryData(key, {
92
- ...value,
93
- pages
94
- });
95
- }
96
- 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);
97
117
  }
98
- const ABSENT = /* @__PURE__ */ Symbol("absent");
118
+ var ABSENT = Symbol("absent");
99
119
  function priorsOf(page, idSet, data) {
100
- const priors = /* @__PURE__ */ new Map();
101
- for (const row of page.data) {
102
- const id = String(row.id);
103
- if (!idSet.has(id)) continue;
104
- const prior = {};
105
- for (const field of Object.keys(data)) {
106
- prior[field] = field in row ? row[field] : ABSENT;
107
- }
108
- priors.set(id, prior);
109
- }
110
- 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;
111
132
  }
133
+ /** Puts the prior values back into the rows they were taken from. */
112
134
  function withRestoredRows(page, priors) {
113
- return {
114
- ...page,
115
- data: page.data.map((record) => {
116
- const prior = priors.get(String(record.id));
117
- if (!prior) return record;
118
- const restored = { ...record };
119
- for (const [field, value] of Object.entries(prior)) {
120
- if (value === ABSENT) delete restored[field];
121
- else restored[field] = value;
122
- }
123
- return restored;
124
- })
125
- };
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
+ };
126
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
+ */
127
156
  function patchListEntries(queryClient, resource, idSet, data) {
128
- const rollbacks = [];
129
- for (const queryKey of singleResultListKeyPrefixes(resource)) {
130
- for (const [key, value] of queryClient.getQueriesData({
131
- queryKey
132
- })) {
133
- if (!value) continue;
134
- const next = withPatchedRows(value, idSet, data);
135
- if (!next) continue;
136
- const priors = priorsOf(value, idSet, data);
137
- queryClient.setQueryData(key, next);
138
- rollbacks.push(() => {
139
- const current = queryClient.getQueryData(key);
140
- if (current) {
141
- queryClient.setQueryData(
142
- key,
143
- withRestoredRows(current, priors)
144
- );
145
- }
146
- });
147
- }
148
- }
149
- const infinite = queryClient.getQueriesData({ queryKey: infiniteListKeyPrefix(resource) });
150
- for (const [key, value] of infinite) {
151
- if (!value) continue;
152
- const pages = value.pages.map(
153
- (page) => withPatchedRows(page, idSet, data) ?? page
154
- );
155
- if (pages.every((page, i) => page === value.pages[i])) continue;
156
- const priorsByPage = value.pages.map((page) => priorsOf(page, idSet, data));
157
- queryClient.setQueryData(key, {
158
- ...value,
159
- pages
160
- });
161
- rollbacks.push(() => {
162
- const current = queryClient.getQueryData(key);
163
- if (!current) return;
164
- queryClient.setQueryData(key, {
165
- ...current,
166
- pages: current.pages.map((page, i) => {
167
- const priors = priorsByPage[i];
168
- return priors && priors.size > 0 ? withRestoredRows(page, priors) : page;
169
- })
170
- });
171
- });
172
- }
173
- return () => {
174
- for (const rollback of rollbacks) rollback();
175
- };
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
+ };
176
194
  }
195
+ /**
196
+ * Shallow-merge `data` into the matching records of every cached `getMany`
197
+ * entry (what `ReferenceField` and `ReferenceArrayField` read through).
198
+ */
177
199
  function patchManyEntries(queryClient, resource, idSet, data) {
178
- const manys = queryClient.getQueriesData({
179
- queryKey: manyKeyPrefix(resource)
180
- });
181
- for (const [key, value] of manys) {
182
- if (!value) continue;
183
- let changed = false;
184
- const next = value.map((record) => {
185
- if (idSet.has(String(record.id))) {
186
- changed = true;
187
- return { ...record, ...data };
188
- }
189
- return record;
190
- });
191
- if (changed) queryClient.setQueryData(key, next);
192
- }
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
+ }
193
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
+ */
194
222
  function removeManyEntries(queryClient, resource, idSet) {
195
- const manys = queryClient.getQueriesData({
196
- queryKey: manyKeyPrefix(resource)
197
- });
198
- for (const [key, value] of manys) {
199
- if (!value) continue;
200
- const kept = value.filter((record) => !idSet.has(String(record.id)));
201
- if (kept.length !== value.length) {
202
- queryClient.setQueryData(key, kept);
203
- }
204
- }
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
+ }
205
229
  }
206
- export {
207
- patchListEntries,
208
- patchManyEntries,
209
- removeManyEntries,
210
- removeRecordsFromCache,
211
- updateRecordsInCache
212
- };
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 {};