@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,122 +1,137 @@
1
+ import { getAtPath } from "../resource/get-at-path.js";
2
+ // src/data/fake-provider.ts
1
3
  function applyFilter(rows, filter) {
2
- if (!filter) return rows;
3
- return rows.filter(
4
- (row) => Object.entries(filter).every(([key, value]) => row[key] === value)
5
- );
4
+ if (!filter) return rows;
5
+ return rows.filter((row) => Object.entries(filter).every(([key, value]) => getAtPath(row, key) === value));
6
+ }
7
+ /** What `<` reduces a value to: dates by time, booleans and null as numbers. */
8
+ function toComparable(value) {
9
+ if (value instanceof Date) return value.getTime();
10
+ if (typeof value === "number" || typeof value === "string") return value;
11
+ if (typeof value === "boolean") return Number(value);
12
+ if (value === null) return 0;
13
+ return NaN;
14
+ }
15
+ /**
16
+ * Orders as `<` does, coercion included: null sorts as 0, a numeric string
17
+ * against a number compares as a number, and values `<` cannot order
18
+ * (undefined, objects, a non-numeric string against a number) tie.
19
+ */
20
+ function compareValues(a, b) {
21
+ const x = toComparable(a);
22
+ const y = toComparable(b);
23
+ if (typeof x === "string" && typeof y === "string") {
24
+ if (x < y) return -1;
25
+ return x > y ? 1 : 0;
26
+ }
27
+ const nx = Number(x);
28
+ const ny = Number(y);
29
+ if (nx < ny) return -1;
30
+ return nx > ny ? 1 : 0;
6
31
  }
7
32
  function applySort(rows, sort) {
8
- if (!sort) return rows;
9
- const keys = Array.isArray(sort) ? sort : [sort];
10
- if (keys.length === 0) return rows;
11
- return [...rows].sort((a, b) => {
12
- for (const { field, order } of keys) {
13
- const dir = order === "ASC" ? 1 : -1;
14
- const av = a[field];
15
- const bv = b[field];
16
- if (av < bv) return -dir;
17
- if (av > bv) return dir;
18
- }
19
- return 0;
20
- });
33
+ if (!sort) return rows;
34
+ const keys = Array.isArray(sort) ? sort : [sort];
35
+ if (keys.length === 0) return rows;
36
+ return [...rows].sort((a, b) => {
37
+ for (const { field, order } of keys) {
38
+ const dir = order === "ASC" ? 1 : -1;
39
+ const cmp = compareValues(getAtPath(a, field), getAtPath(b, field));
40
+ if (cmp !== 0) return cmp * dir;
41
+ }
42
+ return 0;
43
+ });
21
44
  }
22
45
  function paginate(rows, pagination) {
23
- if (!pagination) return { data: rows, total: rows.length };
24
- if ("page" in pagination) {
25
- const start = (pagination.page - 1) * pagination.perPage;
26
- return {
27
- data: rows.slice(start, start + pagination.perPage),
28
- total: rows.length
29
- };
30
- }
31
- const offset = pagination.cursor === null ? 0 : Number(pagination.cursor);
32
- const data = rows.slice(offset, offset + pagination.limit);
33
- const next = offset + pagination.limit;
34
- return {
35
- data,
36
- total: rows.length,
37
- pageInfo: {
38
- nextCursor: next < rows.length ? String(next) : null,
39
- prevCursor: offset > 0 ? String(Math.max(0, offset - pagination.limit)) : null,
40
- hasNextPage: next < rows.length,
41
- hasPreviousPage: offset > 0
42
- }
43
- };
46
+ if (!pagination) return {
47
+ data: rows,
48
+ total: rows.length
49
+ };
50
+ if ("page" in pagination) {
51
+ const start = (pagination.page - 1) * pagination.perPage;
52
+ return {
53
+ data: rows.slice(start, start + pagination.perPage),
54
+ total: rows.length
55
+ };
56
+ }
57
+ const offset = pagination.cursor === null ? 0 : Number(pagination.cursor);
58
+ const data = rows.slice(offset, offset + pagination.limit);
59
+ const next = offset + pagination.limit;
60
+ return {
61
+ data,
62
+ total: rows.length,
63
+ pageInfo: {
64
+ nextCursor: next < rows.length ? String(next) : null,
65
+ prevCursor: offset > 0 ? String(Math.max(0, offset - pagination.limit)) : null,
66
+ hasNextPage: next < rows.length,
67
+ hasPreviousPage: offset > 0
68
+ }
69
+ };
44
70
  }
71
+ /** In-memory DataProvider for tests and dogfooding. Mutates its own copy of the seed. */
45
72
  function fakeDataProvider(initialData) {
46
- const store = /* @__PURE__ */ new Map();
47
- for (const [resource, rows] of Object.entries(initialData)) {
48
- store.set(resource, structuredClone(rows ?? []));
49
- }
50
- const table = (resource) => {
51
- const rows = store.get(resource);
52
- if (!rows)
53
- throw new Error(`fakeDataProvider: unknown resource "${resource}"`);
54
- return rows;
55
- };
56
- const find = (resource, id) => {
57
- const row = table(resource).find((r) => String(r.id) === String(id));
58
- if (!row)
59
- throw new Error(`fakeDataProvider: ${resource}#${String(id)} not found`);
60
- return row;
61
- };
62
- let nextId = 1e6;
63
- const listLike = (rows, params) => paginate(
64
- applySort(applyFilter(rows, params.filter), params.sort),
65
- params.pagination
66
- );
67
- return {
68
- async getList(resource, params) {
69
- return listLike(table(resource), params);
70
- },
71
- async getOne(resource, params) {
72
- return { data: structuredClone(find(resource, params.id)) };
73
- },
74
- async getMany(resource, params) {
75
- return {
76
- data: params.ids.map((id) => structuredClone(find(resource, id)))
77
- };
78
- },
79
- async getManyReference(resource, params) {
80
- const rows = table(resource).filter(
81
- (r) => String(r[params.target]) === String(params.id)
82
- );
83
- return listLike(rows, params);
84
- },
85
- async create(resource, params) {
86
- const row = { id: nextId++, ...structuredClone(params.data) };
87
- table(resource).push(row);
88
- return { data: structuredClone(row) };
89
- },
90
- async update(resource, params) {
91
- const row = find(resource, params.id);
92
- Object.assign(row, structuredClone(params.data));
93
- return { data: structuredClone(row) };
94
- },
95
- async updateMany(resource, params) {
96
- for (const id of params.ids)
97
- Object.assign(find(resource, id), structuredClone(params.data));
98
- return { data: params.ids };
99
- },
100
- async delete(resource, params) {
101
- const rows = table(resource);
102
- const idx = rows.findIndex((r) => String(r.id) === String(params.id));
103
- if (idx === -1)
104
- throw new Error(
105
- `fakeDataProvider: ${resource}#${String(params.id)} not found`
106
- );
107
- const [removed] = rows.splice(idx, 1);
108
- return { data: structuredClone(removed) };
109
- },
110
- async deleteMany(resource, params) {
111
- for (const id of params.ids) {
112
- const rows = table(resource);
113
- const idx = rows.findIndex((r) => String(r.id) === String(id));
114
- if (idx !== -1) rows.splice(idx, 1);
115
- }
116
- return { data: params.ids };
117
- }
118
- };
73
+ const store = structuredClone(initialData);
74
+ const table = (resource) => {
75
+ const rows = store[resource];
76
+ if (rows) return rows;
77
+ if (!Object.hasOwn(store, resource)) throw new Error(`fakeDataProvider: unknown resource "${resource}"`);
78
+ const empty = [];
79
+ store[resource] = empty;
80
+ return empty;
81
+ };
82
+ const find = (resource, id) => {
83
+ const row = table(resource).find((r) => String(r.id) === String(id));
84
+ if (!row) throw new Error(`fakeDataProvider: ${resource}#${String(id)} not found`);
85
+ return row;
86
+ };
87
+ let nextId = 1e6;
88
+ const listLike = (rows, params) => paginate(applySort(applyFilter(rows, params.filter), params.sort), params.pagination);
89
+ return {
90
+ async getList(resource, params) {
91
+ return listLike(table(resource), params);
92
+ },
93
+ async getOne(resource, params) {
94
+ return { data: structuredClone(find(resource, params.id)) };
95
+ },
96
+ async getMany(resource, params) {
97
+ return { data: params.ids.map((id) => structuredClone(find(resource, id))) };
98
+ },
99
+ async getManyReference(resource, params) {
100
+ const rows = table(resource).filter((r) => String(getAtPath(r, params.target)) === String(params.id));
101
+ return listLike(rows, params);
102
+ },
103
+ async create(resource, params) {
104
+ const row = {
105
+ id: nextId++,
106
+ ...structuredClone(params.data)
107
+ };
108
+ table(resource).push(row);
109
+ return { data: structuredClone(row) };
110
+ },
111
+ async update(resource, params) {
112
+ const row = find(resource, params.id);
113
+ Object.assign(row, structuredClone(params.data));
114
+ return { data: structuredClone(row) };
115
+ },
116
+ async updateMany(resource, params) {
117
+ for (const id of params.ids) Object.assign(find(resource, id), structuredClone(params.data));
118
+ return { data: params.ids };
119
+ },
120
+ async delete(resource, params) {
121
+ const rows = table(resource);
122
+ const idx = rows.findIndex((r) => String(r.id) === String(params.id));
123
+ const removed = idx === -1 ? void 0 : rows.splice(idx, 1)[0];
124
+ if (!removed) throw new Error(`fakeDataProvider: ${resource}#${String(params.id)} not found`);
125
+ return { data: structuredClone(removed) };
126
+ },
127
+ async deleteMany(resource, params) {
128
+ for (const id of params.ids) {
129
+ const rows = table(resource);
130
+ const idx = rows.findIndex((r) => String(r.id) === String(id));
131
+ if (idx !== -1) rows.splice(idx, 1);
132
+ }
133
+ return { data: params.ids };
134
+ }
135
+ };
119
136
  }
120
- export {
121
- fakeDataProvider
122
- };
137
+ export { fakeDataProvider };
@@ -1,56 +1,73 @@
1
1
  import { HttpError } from "./http-error.js";
2
+ // src/data/fetch-utils.ts
2
3
  function parseJson(text) {
3
- try {
4
- return JSON.parse(text);
5
- } catch {
6
- return void 0;
7
- }
4
+ try {
5
+ return JSON.parse(text);
6
+ } catch {
7
+ return;
8
+ }
8
9
  }
9
10
  function errorMessage(json, response) {
10
- const message = json != null && typeof json === "object" ? json.message : void 0;
11
- if (typeof message === "string") return message;
12
- return response.statusText || `HTTP ${response.status}`;
11
+ const message = json != null && typeof json === "object" ? json.message : void 0;
12
+ if (typeof message === "string") return message;
13
+ return response.statusText || `HTTP ${response.status}`;
13
14
  }
15
+ /**
16
+ * A thin call to the global `fetch`.
17
+ *
18
+ * - Resolves `{ status, statusText, headers, body, json }` on a 2xx; `json` is
19
+ * `undefined` when the text is not JSON.
20
+ * - Rejects a non-2xx with an `HttpError` whose `body` is the parsed JSON.
21
+ * - Rejects a network failure with the error `fetch` threw, unchanged.
22
+ * - Sets `Accept: application/json`, and `Content-Type: application/json` for
23
+ * a string `body` when the caller set none.
24
+ * - A non-empty `token` is sent as `Authorization: Bearer <token>` and wins
25
+ * over an `Authorization` header in `options.headers`; an empty one is
26
+ * ignored.
27
+ */
14
28
  async function fetchJson(url, options = {}) {
15
- const { token, ...init } = options;
16
- const headers = new Headers(init.headers);
17
- if (!headers.has("Accept")) headers.set("Accept", "application/json");
18
- if (typeof init.body === "string" && !headers.has("Content-Type")) {
19
- headers.set("Content-Type", "application/json");
20
- }
21
- if (token) headers.set("Authorization", `Bearer ${token}`);
22
- const response = await fetch(url, { ...init, headers });
23
- const body = await response.text();
24
- const json = parseJson(body);
25
- if (!response.ok) {
26
- throw new HttpError(errorMessage(json, response), response.status, json);
27
- }
28
- return {
29
- status: response.status,
30
- statusText: response.statusText,
31
- headers: response.headers,
32
- body,
33
- json
34
- };
29
+ const { token, ...init } = options;
30
+ const headers = new Headers(init.headers);
31
+ if (!headers.has("Accept")) headers.set("Accept", "application/json");
32
+ if (typeof init.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json");
33
+ if (token) headers.set("Authorization", `Bearer ${token}`);
34
+ const response = await fetch(url, {
35
+ ...init,
36
+ headers
37
+ });
38
+ const body = await response.text();
39
+ const json = parseJson(body);
40
+ if (!response.ok) throw new HttpError(errorMessage(json, response), response.status, json);
41
+ return {
42
+ status: response.status,
43
+ statusText: response.statusText,
44
+ headers: response.headers,
45
+ body,
46
+ json
47
+ };
35
48
  }
36
49
  function encodeValue(value) {
37
- if (value instanceof Date) return value.toISOString();
38
- return typeof value === "object" ? JSON.stringify(value) : String(value);
50
+ if (value instanceof Date) return value.toISOString();
51
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
39
52
  }
53
+ /**
54
+ * Builds a query string (no leading `?`). Skips `undefined` and `null`,
55
+ * repeats the key for an array, writes a `Date` as its ISO string and
56
+ * JSON-encodes any other object.
57
+ */
40
58
  function queryParameters(params) {
41
- const parts = [];
42
- for (const [key, value] of Object.entries(params)) {
43
- const values = Array.isArray(value) ? value : [value];
44
- for (const item of values) {
45
- if (item === void 0 || item === null) continue;
46
- parts.push(
47
- `${encodeURIComponent(key)}=${encodeURIComponent(encodeValue(item))}`
48
- );
49
- }
50
- }
51
- return parts.join("&");
59
+ const parts = [];
60
+ for (const [key, value] of Object.entries(params)) {
61
+ const values = Array.isArray(value) ? value : [value];
62
+ for (const item of values) {
63
+ if (item === void 0 || item === null) continue;
64
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(encodeValue(item))}`);
65
+ }
66
+ }
67
+ return parts.join("&");
52
68
  }
53
- const fetchUtils = { fetchJson, queryParameters };
54
- export {
55
- fetchUtils
69
+ var fetchUtils = {
70
+ fetchJson,
71
+ queryParameters
56
72
  };
73
+ export { fetchUtils };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Does a filter value nest, so a walker descends into it? An object that is
3
+ * not an array and not a Date. Not the prototype check: a class instance nests.
4
+ */
5
+ export declare function isFilterBranch(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,10 @@
1
+ import { isRecord } from "../guards.js";
2
+ // src/data/filter-branch.ts
3
+ /**
4
+ * Does a filter value nest, so a walker descends into it? An object that is
5
+ * not an array and not a Date. Not the prototype check: a class instance nests.
6
+ */
7
+ function isFilterBranch(value) {
8
+ return isRecord(value) && !(value instanceof Date);
9
+ }
10
+ export { isFilterBranch };
@@ -1,85 +1,93 @@
1
- import { hashKey } from "@tanstack/query-core";
2
1
  import { manyKey, oneKey } from "./keys.js";
3
- const pending = /* @__PURE__ */ new WeakMap();
2
+ import { hashKey } from "@tanstack/query-core";
3
+ // src/data/get-many-aggregate.ts
4
+ /**
5
+ * One pending batch per resource and meta, per provider. A flushed batch leaves
6
+ * the map. Contexts that share a provider share a batch, so the flush seeds the
7
+ * cache of every caller's `queryClient`.
8
+ */
9
+ var pending = /* @__PURE__ */ new WeakMap();
4
10
  function flush(ctx, batchKey, batch) {
5
- pending.get(ctx.dataProvider)?.delete(batchKey);
6
- void settle(ctx, batch);
11
+ pending.get(ctx.dataProvider)?.delete(batchKey);
12
+ settle(ctx, batch);
7
13
  }
14
+ /**
15
+ * Every failure on the way, a synchronous throw from the provider or a
16
+ * malformed result included, rejects the whole batch. Rejecting a caller that
17
+ * already resolved is a no-op.
18
+ */
8
19
  async function settle(ctx, batch) {
9
- try {
10
- const provider = ctx.dataProvider;
11
- const params = { ids: [...batch.ids.values()] };
12
- if (batch.meta !== void 0) params.meta = batch.meta;
13
- const result = await provider.getMany(batch.resource, params);
14
- const queryClients = new Set(batch.callers.map((c) => c.queryClient));
15
- const byId = /* @__PURE__ */ new Map();
16
- for (const record of result.data) {
17
- byId.set(String(record.id), record);
18
- for (const queryClient of queryClients) {
19
- queryClient.setQueryData(
20
- oneKey(batch.resource, { id: record.id }),
21
- record
22
- );
23
- }
24
- }
25
- for (const caller of batch.callers) {
26
- const own = /* @__PURE__ */ new Map();
27
- for (const id of caller.ids) {
28
- const record = byId.get(String(id));
29
- if (record) own.set(String(id), record);
30
- }
31
- caller.resolve([...own.values()]);
32
- }
33
- } catch (error) {
34
- for (const caller of batch.callers) caller.reject(error);
35
- }
20
+ try {
21
+ const provider = ctx.dataProvider;
22
+ const params = { ids: [...batch.ids.values()] };
23
+ if (batch.meta !== void 0) params.meta = batch.meta;
24
+ const result = await provider.getMany(batch.resource, params);
25
+ const queryClients = new Set(batch.callers.map((c) => c.queryClient));
26
+ const byId = /* @__PURE__ */ new Map();
27
+ for (const record of result.data) {
28
+ byId.set(String(record.id), record);
29
+ for (const queryClient of queryClients) queryClient.setQueryData(oneKey(batch.resource, { id: record.id }), record);
30
+ }
31
+ for (const caller of batch.callers) {
32
+ const own = /* @__PURE__ */ new Map();
33
+ for (const id of caller.ids) {
34
+ const record = byId.get(String(id));
35
+ if (record) own.set(String(id), record);
36
+ }
37
+ caller.resolve([...own.values()]);
38
+ }
39
+ } catch (error) {
40
+ for (const caller of batch.callers) caller.reject(error);
41
+ }
36
42
  }
43
+ /**
44
+ * Joins the pending batch for this provider, resource and `meta`, opening it
45
+ * and scheduling its `setTimeout(0)` flush if there is none. Resolves once the
46
+ * flush has the provider's answer, with this caller's own records in its own id
47
+ * order, repeated ids once and ids the provider omitted left out. Rejects with
48
+ * the batch's error.
49
+ */
37
50
  function enqueue(ctx, resource, params) {
38
- const batchKey = hashKey([resource, { meta: params.meta }]);
39
- let batches = pending.get(ctx.dataProvider);
40
- if (!batches) {
41
- batches = /* @__PURE__ */ new Map();
42
- pending.set(ctx.dataProvider, batches);
43
- }
44
- let batch = batches.get(batchKey);
45
- if (!batch) {
46
- const created = {
47
- resource,
48
- meta: params.meta,
49
- ids: /* @__PURE__ */ new Map(),
50
- callers: []
51
- };
52
- batch = created;
53
- batches.set(batchKey, created);
54
- setTimeout(() => flush(ctx, batchKey, created), 0);
55
- }
56
- for (const id of params.ids) {
57
- if (!batch.ids.has(String(id))) batch.ids.set(String(id), id);
58
- }
59
- const target = batch;
60
- return new Promise((resolve, reject) => {
61
- target.callers.push({
62
- queryClient: ctx.queryClient,
63
- ids: [...params.ids],
64
- resolve,
65
- reject
66
- });
67
- });
51
+ const batchKey = hashKey([resource, { meta: params.meta }]);
52
+ let batches = pending.get(ctx.dataProvider);
53
+ if (!batches) {
54
+ batches = /* @__PURE__ */ new Map();
55
+ pending.set(ctx.dataProvider, batches);
56
+ }
57
+ let batch = batches.get(batchKey);
58
+ if (!batch) {
59
+ const created = {
60
+ resource,
61
+ meta: params.meta,
62
+ ids: /* @__PURE__ */ new Map(),
63
+ callers: []
64
+ };
65
+ batch = created;
66
+ batches.set(batchKey, created);
67
+ setTimeout(() => flush(ctx, batchKey, created), 0);
68
+ }
69
+ for (const id of params.ids) if (!batch.ids.has(String(id))) batch.ids.set(String(id), id);
70
+ const target = batch;
71
+ return new Promise((resolve, reject) => {
72
+ target.callers.push({
73
+ queryClient: ctx.queryClient,
74
+ ids: [...params.ids],
75
+ resolve,
76
+ reject
77
+ });
78
+ });
68
79
  }
80
+ /**
81
+ * Same signature and result as `getManyOptions`, but reads started in the same
82
+ * tick reach the data provider as one `getMany` per resource and `meta`.
83
+ */
69
84
  function getManyAggregateOptions(ctx, resource, params) {
70
- return {
71
- queryKey: manyKey(resource, params),
72
- queryFn: async () => {
73
- if (params.ids.length === 0) return [];
74
- const records = await enqueue(
75
- ctx,
76
- resource,
77
- params
78
- );
79
- return records;
80
- }
81
- };
85
+ return {
86
+ queryKey: manyKey(resource, params),
87
+ queryFn: async () => {
88
+ if (params.ids.length === 0) return [];
89
+ return await enqueue(ctx, resource, params);
90
+ }
91
+ };
82
92
  }
83
- export {
84
- getManyAggregateOptions
85
- };
93
+ export { getManyAggregateOptions };
@@ -1,13 +1,18 @@
1
- class HttpError extends Error {
2
- name = "HttpError";
3
- status;
4
- body;
5
- constructor(message, status, body) {
6
- super(message);
7
- this.status = status;
8
- this.body = body;
9
- }
10
- }
11
- export {
12
- HttpError
1
+ // src/data/http-error.ts
2
+ /**
3
+ * Rejected by `fetchUtils.fetchJson` on a non-2xx response. `status` is what
4
+ * `authProvider.checkError` reads to log a user out on 401/403; `body` is the
5
+ * parsed response body (a provider maps a 422 onto `ValidationError` from it).
6
+ * Framework-free by design.
7
+ */
8
+ var HttpError = class extends Error {
9
+ name = "HttpError";
10
+ status;
11
+ body;
12
+ constructor(message, status, body) {
13
+ super(message);
14
+ this.status = status;
15
+ this.body = body;
16
+ }
13
17
  };
18
+ export { HttpError };