@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.
- package/dist/assistant/assistant-session.d.ts +28 -0
- package/dist/assistant/assistant-session.js +208 -0
- package/dist/assistant/echo-transport.d.ts +7 -0
- package/dist/assistant/echo-transport.js +28 -0
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +54 -0
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +21 -17
- package/dist/auth/auth-options.js +80 -48
- package/dist/auth/permission-matrix.d.ts +12 -0
- package/dist/auth/permission-matrix.js +67 -0
- package/dist/auth/types.d.ts +14 -4
- package/dist/block-editor/block-document.d.ts +60 -0
- package/dist/block-editor/block-document.js +143 -0
- package/dist/block-editor/data-blocks.d.ts +63 -0
- package/dist/block-editor/data-blocks.js +117 -0
- package/dist/chart/chart-data.d.ts +25 -0
- package/dist/chart/chart-data.js +61 -0
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.js +8 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.js +82 -113
- package/dist/controller/calendar-events.js +93 -88
- package/dist/controller/client-list-controller.d.ts +2 -2
- package/dist/controller/client-list-controller.js +251 -254
- package/dist/controller/data-table-columns.d.ts +2 -0
- package/dist/controller/data-table-columns.js +50 -56
- package/dist/controller/deep-equal.d.ts +1 -0
- package/dist/controller/deep-equal.js +14 -0
- package/dist/controller/filter-toggle.js +36 -44
- package/dist/controller/in-place-editor.d.ts +42 -0
- package/dist/controller/in-place-editor.js +42 -0
- package/dist/controller/infinite-list-controller.js +220 -190
- package/dist/controller/inspector.d.ts +49 -0
- package/dist/controller/inspector.js +64 -0
- package/dist/controller/job-monitor.d.ts +41 -0
- package/dist/controller/job-monitor.js +49 -0
- package/dist/controller/kanban-board.d.ts +2 -3
- package/dist/controller/kanban-board.js +43 -24
- package/dist/controller/list-controller.d.ts +5 -5
- package/dist/controller/list-controller.js +444 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.d.ts +36 -0
- package/dist/controller/pivot-grid.js +99 -0
- package/dist/controller/prev-next.d.ts +2 -2
- package/dist/controller/prev-next.js +74 -54
- package/dist/controller/record-controllers.d.ts +14 -6
- package/dist/controller/record-controllers.js +288 -257
- package/dist/controller/record-diff.d.ts +21 -0
- package/dist/controller/record-diff.js +50 -0
- package/dist/controller/record-timeline.d.ts +35 -0
- package/dist/controller/record-timeline.js +48 -0
- package/dist/controller/record-tree.js +57 -47
- package/dist/controller/record-workflow.d.ts +38 -16
- package/dist/controller/record-workflow.js +85 -64
- package/dist/controller/reference-many-controller.js +198 -192
- package/dist/controller/reference-one-controller.d.ts +1 -1
- package/dist/controller/reference-one-controller.js +63 -56
- package/dist/controller/saved-queries.js +104 -91
- package/dist/controller/translate-fallback.js +10 -6
- package/dist/csv/export.d.ts +2 -1
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.js +185 -169
- package/dist/csv/match-headers.js +29 -22
- package/dist/csv/parse-with.js +60 -54
- package/dist/csv/run-import.js +69 -72
- package/dist/csv/validate-rows.d.ts +1 -1
- package/dist/csv/validate-rows.js +65 -52
- package/dist/data/cache-ops.d.ts +5 -4
- package/dist/data/cache-ops.js +208 -190
- package/dist/data/combine-data-providers.js +25 -23
- package/dist/data/data-provider-log.d.ts +45 -0
- package/dist/data/data-provider-log.js +93 -0
- package/dist/data/errors.js +14 -10
- package/dist/data/fake-provider.d.ts +6 -3
- package/dist/data/fake-provider.js +129 -114
- package/dist/data/fetch-utils.js +61 -44
- package/dist/data/filter-branch.d.ts +5 -0
- package/dist/data/filter-branch.js +10 -0
- package/dist/data/get-many-aggregate.js +84 -76
- package/dist/data/http-error.js +17 -12
- package/dist/data/keys.js +50 -36
- package/dist/data/mutation-options.d.ts +14 -9
- package/dist/data/mutation-options.js +93 -109
- package/dist/data/query-options.js +73 -68
- package/dist/data/remove-empty.d.ts +3 -0
- package/dist/data/remove-empty.js +11 -0
- package/dist/data/undoable.d.ts +2 -2
- package/dist/data/undoable.js +190 -174
- package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
- package/dist/data/with-lifecycle-callbacks.js +66 -52
- package/dist/environment/application-update-store.js +58 -55
- package/dist/environment/offline-store.js +21 -21
- package/dist/form/array-binding.d.ts +30 -11
- package/dist/form/array-binding.js +184 -166
- package/dist/form/choice-ids.d.ts +7 -0
- package/dist/form/choice-ids.js +11 -0
- package/dist/form/choices-controller.d.ts +3 -2
- package/dist/form/choices-controller.js +302 -275
- package/dist/form/date-format.d.ts +38 -0
- package/dist/form/date-format.js +89 -0
- package/dist/form/dirty-values.d.ts +2 -2
- package/dist/form/dirty-values.js +22 -11
- package/dist/form/duration.d.ts +22 -0
- package/dist/form/duration.js +49 -0
- package/dist/form/edit-create-form.d.ts +12 -5
- package/dist/form/edit-create-form.js +243 -188
- package/dist/form/event-value.d.ts +6 -0
- package/dist/form/event-value.js +17 -0
- package/dist/form/field-error.d.ts +14 -0
- package/dist/form/field-error.js +28 -0
- package/dist/form/field-name.d.ts +20 -0
- package/dist/form/field-name.js +6 -0
- package/dist/form/filter-form-values.js +63 -49
- package/dist/form/form-controller.d.ts +9 -1
- package/dist/form/form-controller.js +165 -160
- package/dist/form/form-groups.d.ts +4 -3
- package/dist/form/form-groups.js +79 -79
- package/dist/form/form-handle.d.ts +175 -0
- package/dist/form/form-handle.js +68 -0
- package/dist/form/input-binding.d.ts +56 -31
- package/dist/form/input-binding.js +169 -118
- package/dist/form/input-values.d.ts +130 -0
- package/dist/form/input-values.js +177 -0
- package/dist/form/json-text.js +51 -37
- package/dist/form/save-pipeline.d.ts +6 -8
- package/dist/form/save-pipeline.js +91 -82
- package/dist/form/set-form-values.d.ts +6 -0
- package/dist/form/set-form-values.js +10 -0
- package/dist/form/source.js +97 -65
- package/dist/form/text-of.d.ts +2 -0
- package/dist/form/text-of.js +7 -0
- package/dist/form/validators.d.ts +58 -19
- package/dist/form/validators.js +97 -71
- package/dist/geo/bbox.d.ts +5 -4
- package/dist/geo/bbox.js +69 -48
- package/dist/geo/bounds.d.ts +14 -3
- package/dist/geo/bounds.js +98 -68
- package/dist/geo/constraints.js +88 -93
- package/dist/geo/coordinates.js +31 -23
- package/dist/geo/editor.d.ts +13 -13
- package/dist/geo/editor.js +176 -153
- package/dist/geo/geocoding.d.ts +4 -4
- package/dist/geo/geocoding.js +38 -41
- package/dist/geo/geojson.d.ts +12 -74
- package/dist/geo/geojson.js +52 -40
- package/dist/geo/geometry-math.d.ts +4 -4
- package/dist/geo/geometry-math.js +100 -93
- package/dist/geo/index.d.ts +7 -6
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.js +31 -32
- package/dist/geo/messages.js +48 -45
- package/dist/geo/nominatim.js +88 -80
- package/dist/geo/osm-presets.d.ts +4 -3
- package/dist/geo/osm-presets.js +108 -86
- package/dist/geo/osm-tags.js +14 -12
- package/dist/geo/overpass.d.ts +14 -2
- package/dist/geo/overpass.js +83 -90
- package/dist/geo/shape-value.d.ts +9 -5
- package/dist/geo/shape-value.js +169 -146
- package/dist/geo/snap-to-roads.d.ts +3 -3
- package/dist/geo/snap-to-roads.js +55 -50
- package/dist/geo/throttle.js +36 -39
- package/dist/guards.d.ts +37 -0
- package/dist/guards.js +62 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.js +187 -189
- package/dist/i18n/i18n.d.ts +8 -2
- package/dist/i18n/i18n.js +47 -48
- package/dist/i18n/inflection.d.ts +34 -0
- package/dist/i18n/inflection.js +82 -0
- package/dist/i18n/messages.d.ts +10 -2
- package/dist/i18n/messages.js +17 -4
- package/dist/i18n/missing-keys.d.ts +19 -0
- package/dist/i18n/missing-keys.js +68 -0
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +96 -22
- package/dist/index.js +52 -257
- package/dist/json/deep-equal.d.ts +8 -0
- package/dist/json/deep-equal.js +24 -0
- package/dist/json/strip-json-comments.js +38 -42
- package/dist/noop.d.ts +2 -0
- package/dist/noop.js +4 -0
- package/dist/notification/error-message.d.ts +2 -0
- package/dist/notification/error-message.js +10 -0
- package/dist/notification/notification-store.d.ts +3 -2
- package/dist/notification/notification-store.js +41 -45
- package/dist/onboarding-tour/placement.d.ts +34 -0
- package/dist/onboarding-tour/placement.js +67 -0
- package/dist/onboarding-tour/tour-controller.d.ts +25 -0
- package/dist/onboarding-tour/tour-controller.js +50 -0
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.d.ts +7 -0
- package/dist/realtime/announce-lock.js +18 -0
- package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
- package/dist/realtime/broadcast-channel-transport.js +69 -0
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +41 -60
- package/dist/realtime/live.js +95 -101
- package/dist/realtime/lock-options.js +16 -16
- package/dist/realtime/lock-session.js +91 -78
- package/dist/realtime/memory-transport.js +26 -67
- package/dist/realtime/presence.js +120 -122
- package/dist/realtime/realtime-data-provider.d.ts +10 -0
- package/dist/realtime/realtime-data-provider.js +45 -42
- package/dist/realtime/reconnect-backoff.d.ts +15 -0
- package/dist/realtime/reconnect-backoff.js +17 -0
- package/dist/realtime/sse-transport.d.ts +31 -0
- package/dist/realtime/sse-transport.js +235 -0
- package/dist/realtime/status-store.js +23 -28
- package/dist/realtime/topic-hub.d.ts +24 -0
- package/dist/realtime/topic-hub.js +66 -0
- package/dist/realtime/topics.js +10 -12
- package/dist/realtime/types.js +10 -12
- package/dist/realtime/unread-count.js +21 -21
- package/dist/realtime/websocket-transport.d.ts +32 -0
- package/dist/realtime/websocket-transport.js +306 -0
- package/dist/resource/describe-resource.d.ts +2 -0
- package/dist/resource/describe-resource.js +232 -219
- package/dist/resource/get-at-path.js +29 -14
- package/dist/resource/identifier.d.ts +18 -0
- package/dist/resource/identifier.js +27 -0
- package/dist/resource/record.d.ts +4 -0
- package/dist/resource/record.js +10 -0
- package/dist/resource/register.d.ts +7 -0
- package/dist/resource/registry-store.d.ts +45 -15
- package/dist/resource/registry-store.js +67 -60
- package/dist/resource/registry.d.ts +10 -0
- package/dist/resource/registry.js +8 -4
- package/dist/resource/source-path.d.ts +16 -1
- package/dist/rich-text/rich-text-extras.d.ts +132 -0
- package/dist/rich-text/rich-text-extras.js +179 -0
- package/dist/rich-text/rich-text-image.d.ts +124 -0
- package/dist/rich-text/rich-text-image.js +163 -0
- package/dist/rich-text/rich-text.d.ts +62 -0
- package/dist/rich-text/rich-text.js +142 -0
- package/dist/router/location.d.ts +22 -4
- package/dist/router/location.js +41 -18
- package/dist/router/match.js +47 -39
- package/dist/router/paths.js +40 -34
- package/dist/router/record-from-location.d.ts +1 -1
- package/dist/router/record-from-location.js +25 -17
- package/dist/store/adapters.js +51 -55
- package/dist/store/micro-store.js +57 -59
- package/dist/store/shallow-equal.d.ts +12 -0
- package/dist/store/shallow-equal.js +20 -0
- package/dist/store/store.js +54 -57
- package/package.json +13 -11
package/dist/data/cache-ops.js
CHANGED
|
@@ -1,212 +1,230 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
118
|
+
var ABSENT = Symbol("absent");
|
|
99
119
|
function priorsOf(page, idSet, data) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 };
|
package/dist/data/errors.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
6
|
-
}): DataProvider<TResources>;
|
|
8
|
+
export declare function fakeDataProvider<TResources extends ResourceMap>(initialData: FakeTables<TResources>): DataProvider<TResources>;
|
|
9
|
+
export {};
|