@chelone/core 0.6.0 → 0.9.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 +6 -0
- package/dist/assistant/assistant-session.js +204 -181
- package/dist/assistant/echo-transport.js +26 -29
- package/dist/assistant/http-transport.d.ts +10 -0
- package/dist/assistant/http-transport.js +98 -0
- package/dist/assistant/types.d.ts +1 -7
- package/dist/auth/access-control.d.ts +13 -5
- package/dist/auth/access-control.js +25 -21
- package/dist/auth/auth-options.d.ts +23 -17
- package/dist/auth/auth-options.js +82 -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 +64 -0
- package/dist/block-editor/block-document.js +147 -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.js +46 -44
- package/dist/command-menu/hotkey.js +58 -47
- package/dist/command-menu/recents.d.ts +4 -0
- package/dist/command-menu/recents.js +12 -11
- package/dist/command-menu/registry.d.ts +9 -5
- package/dist/command-menu/registry.js +36 -46
- package/dist/controller/calendar-dates.d.ts +12 -0
- package/dist/controller/calendar-dates.js +94 -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 +43 -0
- package/dist/controller/in-place-editor.js +43 -0
- package/dist/controller/infinite-list-controller.js +219 -189
- package/dist/controller/inspector.d.ts +55 -0
- package/dist/controller/inspector.js +70 -0
- package/dist/controller/job-monitor.d.ts +10 -4
- package/dist/controller/job-monitor.js +46 -47
- package/dist/controller/kanban-board.js +45 -24
- package/dist/controller/list-controller.d.ts +8 -5
- package/dist/controller/list-controller.js +447 -400
- package/dist/controller/list-params.js +96 -87
- package/dist/controller/pivot-grid.d.ts +4 -4
- package/dist/controller/pivot-grid.js +89 -95
- 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 +5 -5
- package/dist/controller/record-timeline.js +42 -30
- 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.d.ts +2 -0
- package/dist/controller/reference-many-controller.js +199 -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 +4 -3
- package/dist/csv/export.js +43 -40
- package/dist/csv/import-controller.d.ts +4 -0
- package/dist/csv/import-controller.js +189 -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 -188
- 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 +135 -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 +22 -2
- package/dist/form/choices-controller.js +315 -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 +171 -118
- package/dist/form/input-values.d.ts +140 -0
- package/dist/form/input-values.js +206 -0
- package/dist/form/json-text.d.ts +11 -0
- package/dist/form/json-text.js +62 -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.d.ts +10 -0
- package/dist/geo/coordinates.js +44 -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 +9 -8
- package/dist/geo/map-layers.d.ts +42 -7
- package/dist/geo/map-layers.js +88 -62
- package/dist/geo/map-style.d.ts +12 -0
- package/dist/geo/map-style.js +43 -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 +44 -0
- package/dist/guards.js +77 -0
- package/dist/i18n/default-messages.js +34 -29
- package/dist/i18n/english-messages.d.ts +172 -0
- package/dist/i18n/english-messages.js +359 -189
- package/dist/i18n/i18n.d.ts +2 -2
- package/dist/i18n/i18n.js +47 -60
- 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 +4 -0
- package/dist/i18n/missing-keys.js +70 -62
- package/dist/i18n/translatable.d.ts +16 -1
- package/dist/i18n/translatable.js +40 -47
- package/dist/index.d.ts +87 -39
- package/dist/index.js +54 -301
- 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 +2 -0
- package/dist/onboarding-tour/placement.js +65 -78
- package/dist/onboarding-tour/tour-controller.js +46 -54
- package/dist/realtime/add-events-for-mutations.js +75 -81
- package/dist/realtime/announce-lock.js +16 -13
- package/dist/realtime/broadcast-channel-transport.js +65 -59
- package/dist/realtime/copy-members.js +22 -14
- package/dist/realtime/in-memory-lock-provider.js +40 -48
- 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 +25 -25
- 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.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.js +62 -71
- 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 +247 -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 +23 -2
- package/dist/rich-text/rich-text-extras.d.ts +134 -0
- package/dist/rich-text/rich-text-extras.js +181 -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 +6 -0
- package/dist/rich-text/rich-text.js +125 -88
- package/dist/router/location.d.ts +24 -4
- package/dist/router/location.js +43 -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/dist/ui-defaults.d.ts +15 -0
- package/dist/ui-defaults.js +17 -0
- package/package.json +13 -11
package/dist/data/undoable.js
CHANGED
|
@@ -1,196 +1,212 @@
|
|
|
1
|
-
import { partialMatchKey } from "@tanstack/query-core";
|
|
2
1
|
import { removeRecordsFromCache, updateRecordsInCache } from "./cache-ops.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { partialMatchKey } from "@tanstack/query-core";
|
|
3
|
+
// src/data/undoable.ts
|
|
4
|
+
/**
|
|
5
|
+
* Module-level registry of still-undoable mutations (those neither committed
|
|
6
|
+
* nor undone). Drives the adapter's `beforeunload` guard: if any remain
|
|
7
|
+
* pending when the page unloads, commit them so the server is not left behind
|
|
8
|
+
* the optimistic cache.
|
|
9
|
+
*/
|
|
10
|
+
var pendingUndoables = /* @__PURE__ */ new Set();
|
|
11
|
+
var settledListeners = /* @__PURE__ */ new Set();
|
|
12
|
+
/** Committed undoables whose deferred server call has not settled yet. */
|
|
13
|
+
var inFlightCommits = 0;
|
|
14
|
+
/** Calls the listeners once no undoable is pending: after an undo, or after a commit's server call settles. */
|
|
6
15
|
function notifySettled() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
16
|
+
if (hasUnsettledUndoables()) return;
|
|
17
|
+
for (const listener of [...settledListeners]) listener();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Per client, the undoables in start order that are pending, mid-commit, or
|
|
21
|
+
* committed behind an earlier one that has not settled. Handles snapshot the
|
|
22
|
+
* whole resource, so a rollback must replay the later ones over its own
|
|
23
|
+
* restore instead of overwriting their changes.
|
|
24
|
+
*/
|
|
25
|
+
var openUndoables = /* @__PURE__ */ new WeakMap();
|
|
11
26
|
function takeSnapshot(queryClient, queryKeys) {
|
|
12
|
-
|
|
13
|
-
(acc, queryKey) => acc.concat(queryClient.getQueriesData({ queryKey })),
|
|
14
|
-
[]
|
|
15
|
-
);
|
|
27
|
+
return queryKeys.reduce((acc, queryKey) => acc.concat(queryClient.getQueriesData({ queryKey })), []);
|
|
16
28
|
}
|
|
17
29
|
function restore(queryClient, snapshot) {
|
|
18
|
-
|
|
19
|
-
queryClient.setQueryData(key, value);
|
|
20
|
-
}
|
|
30
|
+
for (const [key, value] of snapshot) queryClient.setQueryData(key, value);
|
|
21
31
|
}
|
|
32
|
+
/** Query-key partial matching is one-way, so overlap is a prefix match either way round. */
|
|
22
33
|
function keysOverlap(a, b) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
return a.some((x) => b.some((y) => partialMatchKey(x, y) || partialMatchKey(y, x)));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The later entries a rollback of `entry` must unwind and replay: those whose
|
|
38
|
+
* keys overlap `entry`'s, or those of an earlier included entry. Others never
|
|
39
|
+
* saw its change, and restoring their snapshot would revert unrelated cache.
|
|
40
|
+
*/
|
|
41
|
+
function entriesToReplay(later, entry) {
|
|
42
|
+
const affectedKeys = [...entry.queryKeys];
|
|
43
|
+
const affected = [];
|
|
44
|
+
for (const other of later) {
|
|
45
|
+
if (!keysOverlap(affectedKeys, other.queryKeys)) continue;
|
|
46
|
+
affected.push(other);
|
|
47
|
+
affectedKeys.push(...other.queryKeys);
|
|
48
|
+
}
|
|
49
|
+
return affected;
|
|
50
|
+
}
|
|
51
|
+
/** Drops committed entries from the front: nothing earlier can roll back over them any more. */
|
|
37
52
|
function dropSettledPrefix(order) {
|
|
38
|
-
|
|
53
|
+
while (order[0]?.committed) order.shift();
|
|
39
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Start an undoable mutation: snapshot the affected cache, cancel outgoing
|
|
57
|
+
* refetches, apply the optimistic change, and return a handle whose `undo()` /
|
|
58
|
+
* `commit()` settle it. React/Vue-free — pure query-core.
|
|
59
|
+
*/
|
|
40
60
|
function startUndoableMutation(config) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
commitPromise.then(settle, settle);
|
|
109
|
-
return commitPromise;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
pendingUndoables.add(handle);
|
|
113
|
-
return handle;
|
|
114
|
-
}
|
|
61
|
+
const { queryClient, queryKeys, applyOptimistic, commit } = config;
|
|
62
|
+
const entry = {
|
|
63
|
+
queryKeys,
|
|
64
|
+
applyOptimistic,
|
|
65
|
+
snapshot: takeSnapshot(queryClient, queryKeys),
|
|
66
|
+
committed: false
|
|
67
|
+
};
|
|
68
|
+
for (const queryKey of queryKeys) queryClient.cancelQueries({ queryKey });
|
|
69
|
+
applyOptimistic();
|
|
70
|
+
let order = openUndoables.get(queryClient);
|
|
71
|
+
if (!order) {
|
|
72
|
+
order = [];
|
|
73
|
+
openUndoables.set(queryClient, order);
|
|
74
|
+
}
|
|
75
|
+
order.push(entry);
|
|
76
|
+
let state = "pending";
|
|
77
|
+
let commitPromise;
|
|
78
|
+
const rollback = () => {
|
|
79
|
+
const open = order;
|
|
80
|
+
const index = open.indexOf(entry);
|
|
81
|
+
const later = entriesToReplay(open.slice(index + 1), entry);
|
|
82
|
+
open.splice(index, 1);
|
|
83
|
+
for (const other of [...later].reverse()) restore(queryClient, other.snapshot);
|
|
84
|
+
restore(queryClient, entry.snapshot);
|
|
85
|
+
for (const other of later) {
|
|
86
|
+
other.snapshot = takeSnapshot(queryClient, other.queryKeys);
|
|
87
|
+
other.applyOptimistic();
|
|
88
|
+
}
|
|
89
|
+
dropSettledPrefix(open);
|
|
90
|
+
};
|
|
91
|
+
const handle = {
|
|
92
|
+
get isPending() {
|
|
93
|
+
return state === "pending";
|
|
94
|
+
},
|
|
95
|
+
undo() {
|
|
96
|
+
if (state !== "pending") return;
|
|
97
|
+
state = "undone";
|
|
98
|
+
pendingUndoables.delete(handle);
|
|
99
|
+
rollback();
|
|
100
|
+
notifySettled();
|
|
101
|
+
},
|
|
102
|
+
commit() {
|
|
103
|
+
if (commitPromise) return commitPromise;
|
|
104
|
+
if (state !== "pending") return Promise.resolve(void 0);
|
|
105
|
+
state = "committed";
|
|
106
|
+
pendingUndoables.delete(handle);
|
|
107
|
+
inFlightCommits += 1;
|
|
108
|
+
commitPromise = commit().then((result) => {
|
|
109
|
+
entry.committed = true;
|
|
110
|
+
dropSettledPrefix(order);
|
|
111
|
+
return result;
|
|
112
|
+
}, (error) => {
|
|
113
|
+
rollback();
|
|
114
|
+
throw error;
|
|
115
|
+
});
|
|
116
|
+
const settle = () => {
|
|
117
|
+
inFlightCommits -= 1;
|
|
118
|
+
notifySettled();
|
|
119
|
+
};
|
|
120
|
+
commitPromise.then(settle, settle);
|
|
121
|
+
return commitPromise;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
pendingUndoables.add(handle);
|
|
125
|
+
return handle;
|
|
126
|
+
}
|
|
127
|
+
/** Runs `run` after the current microtasks; a throw is logged, not raised. */
|
|
115
128
|
function later(run) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
try {
|
|
131
|
+
run();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(error);
|
|
134
|
+
}
|
|
135
|
+
}, 0);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Bind an undoable `mutate`'s callbacks to its `params`. The wrapped commit
|
|
139
|
+
* rethrows a failure, so the handle still rolls back and the adapter still
|
|
140
|
+
* notifies; the callbacks wait a macrotask to run after both.
|
|
141
|
+
*/
|
|
124
142
|
function undoableCallbacks(params, callbacks = {}) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
143
|
+
const { onSuccess, onError, onSettled } = callbacks;
|
|
144
|
+
return {
|
|
145
|
+
success: (data) => onSuccess?.(data, params),
|
|
146
|
+
commit: (commit) => async () => {
|
|
147
|
+
let result;
|
|
148
|
+
try {
|
|
149
|
+
result = await commit();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
later(() => {
|
|
152
|
+
onError?.(error, params);
|
|
153
|
+
onSettled?.(params);
|
|
154
|
+
});
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
later(() => onSettled?.(params));
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** Patch `ids` in the `resource` caches now; the notification reads "updated". */
|
|
146
163
|
function undoableUpdateOptions(queryClient, resource, ids, data) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
message: "chelone.notification.updated",
|
|
156
|
-
messageArgs: { smart_count: ids.length }
|
|
157
|
-
};
|
|
158
|
-
}
|
|
164
|
+
return {
|
|
165
|
+
queryKeys: [[resource]],
|
|
166
|
+
applyOptimistic: () => updateRecordsInCache(queryClient, resource, ids, data),
|
|
167
|
+
message: "chelone.notification.updated",
|
|
168
|
+
messageArgs: { smart_count: ids.length }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Remove `ids` from the `resource` caches now; the notification reads "deleted". */
|
|
159
172
|
function undoableDeleteOptions(queryClient, resource, ids) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
173
|
+
return {
|
|
174
|
+
queryKeys: [[resource]],
|
|
175
|
+
applyOptimistic: () => removeRecordsFromCache(queryClient, resource, ids),
|
|
176
|
+
message: "chelone.notification.deleted",
|
|
177
|
+
messageArgs: { smart_count: ids.length }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** The record an undoable update shows at once: the previous one with the patch on top. */
|
|
167
181
|
function patchedRecord(params) {
|
|
168
|
-
|
|
182
|
+
return {
|
|
183
|
+
...params.previousData,
|
|
184
|
+
...params.data,
|
|
185
|
+
id: params.id
|
|
186
|
+
};
|
|
169
187
|
}
|
|
188
|
+
/** True while at least one undoable mutation is still awaiting commit/undo. */
|
|
170
189
|
function hasPendingUndoables() {
|
|
171
|
-
|
|
190
|
+
return pendingUndoables.size > 0;
|
|
172
191
|
}
|
|
192
|
+
/** True while an undoable is pending or its committed server call is still in flight. */
|
|
173
193
|
function hasUnsettledUndoables() {
|
|
174
|
-
|
|
194
|
+
return pendingUndoables.size > 0 || inFlightCommits > 0;
|
|
175
195
|
}
|
|
196
|
+
/** Runs `listener` each time the last pending undoable mutation settles. */
|
|
176
197
|
function onUndoablesSettled(listener) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
198
|
+
settledListeners.add(listener);
|
|
199
|
+
return () => {
|
|
200
|
+
settledListeners.delete(listener);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Commit every still-pending undoable mutation (the `beforeunload` guard).
|
|
205
|
+
* Never rejects — individual commit failures are swallowed here (each already
|
|
206
|
+
* rolled its own cache back); it only guarantees the server calls are fired.
|
|
207
|
+
*/
|
|
182
208
|
async function commitAllUndoables() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
export {
|
|
187
|
-
commitAllUndoables,
|
|
188
|
-
hasPendingUndoables,
|
|
189
|
-
hasUnsettledUndoables,
|
|
190
|
-
onUndoablesSettled,
|
|
191
|
-
patchedRecord,
|
|
192
|
-
startUndoableMutation,
|
|
193
|
-
undoableCallbacks,
|
|
194
|
-
undoableDeleteOptions,
|
|
195
|
-
undoableUpdateOptions
|
|
196
|
-
};
|
|
209
|
+
const handles = [...pendingUndoables];
|
|
210
|
+
await Promise.allSettled(handles.map((handle) => handle.commit()));
|
|
211
|
+
}
|
|
212
|
+
export { commitAllUndoables, hasPendingUndoables, hasUnsettledUndoables, onUndoablesSettled, patchedRecord, startUndoableMutation, undoableCallbacks, undoableDeleteOptions, undoableUpdateOptions };
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResourceRecord } from "../resource/record.js";
|
|
2
2
|
import type { RecordOf, ResourceMap } from "../resource/registry.js";
|
|
3
3
|
import type { CreateParams, CreateResult, DataProvider, DeleteManyParams, DeleteManyResult, DeleteParams, DeleteResult, GetListParams, GetListResult, GetManyParams, GetManyReferenceParams, GetManyReferenceResult, GetManyResult, GetOneParams, GetOneResult, UpdateManyParams, UpdateManyResult, UpdateParams, UpdateResult } from "./types.js";
|
|
4
4
|
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
/**
|
|
6
|
+
* The hooks that do not depend on a record type, so a named handler and the
|
|
7
|
+
* `"*"` handler declare them once.
|
|
8
|
+
*/
|
|
9
|
+
interface RecordFreeCallbacks<TResources extends ResourceMap> {
|
|
10
|
+
beforeGetList?(params: GetListParams, dataProvider: DataProvider<TResources>): MaybePromise<GetListParams>;
|
|
11
|
+
beforeGetOne?(params: GetOneParams, dataProvider: DataProvider<TResources>): MaybePromise<GetOneParams>;
|
|
12
|
+
beforeGetMany?(params: GetManyParams, dataProvider: DataProvider<TResources>): MaybePromise<GetManyParams>;
|
|
13
|
+
beforeGetManyReference?(params: GetManyReferenceParams, dataProvider: DataProvider<TResources>): MaybePromise<GetManyReferenceParams>;
|
|
14
|
+
beforeDeleteMany?(params: DeleteManyParams, dataProvider: DataProvider<TResources>): MaybePromise<DeleteManyParams>;
|
|
15
|
+
afterUpdateMany?(result: UpdateManyResult, dataProvider: DataProvider<TResources>): MaybePromise<UpdateManyResult>;
|
|
16
|
+
afterDeleteMany?(result: DeleteManyResult, dataProvider: DataProvider<TResources>): MaybePromise<DeleteManyResult>;
|
|
17
|
+
}
|
|
5
18
|
/**
|
|
6
19
|
* The optional hooks of one handler, typed for records of type `TRecord`.
|
|
7
20
|
* Every callback must return the value: `undefined` is not "unchanged".
|
|
8
21
|
* `dataProvider` is the provider passed to `withLifecycleCallbacks`, so
|
|
9
22
|
* calling it never re-enters the callbacks.
|
|
10
23
|
*/
|
|
11
|
-
export interface LifecycleCallbacks<TResources extends ResourceMap, TRecord extends
|
|
12
|
-
beforeGetList?(params: GetListParams, dataProvider: DataProvider<TResources>): MaybePromise<GetListParams>;
|
|
24
|
+
export interface LifecycleCallbacks<TResources extends ResourceMap, TRecord extends ResourceRecord> extends RecordFreeCallbacks<TResources> {
|
|
13
25
|
afterGetList?(result: GetListResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetListResult<TRecord>>;
|
|
14
|
-
beforeGetOne?(params: GetOneParams, dataProvider: DataProvider<TResources>): MaybePromise<GetOneParams>;
|
|
15
26
|
afterGetOne?(result: GetOneResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetOneResult<TRecord>>;
|
|
16
27
|
beforeCreate?(params: CreateParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<CreateParams<TRecord>>;
|
|
17
28
|
afterCreate?(result: CreateResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<CreateResult<TRecord>>;
|
|
@@ -19,26 +30,43 @@ export interface LifecycleCallbacks<TResources extends ResourceMap, TRecord exte
|
|
|
19
30
|
afterUpdate?(result: UpdateResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<UpdateResult<TRecord>>;
|
|
20
31
|
beforeDelete?(params: DeleteParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<DeleteParams<TRecord>>;
|
|
21
32
|
afterDelete?(result: DeleteResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<DeleteResult<TRecord>>;
|
|
22
|
-
beforeGetMany?(params: GetManyParams, dataProvider: DataProvider<TResources>): MaybePromise<GetManyParams>;
|
|
23
33
|
afterGetMany?(result: GetManyResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetManyResult<TRecord>>;
|
|
24
|
-
beforeGetManyReference?(params: GetManyReferenceParams, dataProvider: DataProvider<TResources>): MaybePromise<GetManyReferenceParams>;
|
|
25
34
|
afterGetManyReference?(result: GetManyReferenceResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetManyReferenceResult<TRecord>>;
|
|
26
35
|
beforeUpdateMany?(params: UpdateManyParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<UpdateManyParams<TRecord>>;
|
|
27
|
-
afterUpdateMany?(result: UpdateManyResult, dataProvider: DataProvider<TResources>): MaybePromise<UpdateManyResult>;
|
|
28
|
-
beforeDeleteMany?(params: DeleteManyParams, dataProvider: DataProvider<TResources>): MaybePromise<DeleteManyParams>;
|
|
29
|
-
afterDeleteMany?(result: DeleteManyResult, dataProvider: DataProvider<TResources>): MaybePromise<DeleteManyResult>;
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
38
|
+
* The hooks of the `"*"` handler. Each one is generic over the record, so it
|
|
39
|
+
* hands back the record type it received: the callback runs for every
|
|
40
|
+
* resource and cannot name one.
|
|
34
41
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
export interface WildcardCallbacks<TResources extends ResourceMap> extends RecordFreeCallbacks<TResources> {
|
|
43
|
+
afterGetList?<TRecord extends ResourceRecord>(result: GetListResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetListResult<TRecord>>;
|
|
44
|
+
afterGetOne?<TRecord extends ResourceRecord>(result: GetOneResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetOneResult<TRecord>>;
|
|
45
|
+
beforeCreate?<TRecord extends ResourceRecord>(params: CreateParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<CreateParams<TRecord>>;
|
|
46
|
+
afterCreate?<TRecord extends ResourceRecord>(result: CreateResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<CreateResult<TRecord>>;
|
|
47
|
+
beforeUpdate?<TRecord extends ResourceRecord>(params: UpdateParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<UpdateParams<TRecord>>;
|
|
48
|
+
afterUpdate?<TRecord extends ResourceRecord>(result: UpdateResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<UpdateResult<TRecord>>;
|
|
49
|
+
beforeDelete?<TRecord extends ResourceRecord>(params: DeleteParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<DeleteParams<TRecord>>;
|
|
50
|
+
afterDelete?<TRecord extends ResourceRecord>(result: DeleteResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<DeleteResult<TRecord>>;
|
|
51
|
+
afterGetMany?<TRecord extends ResourceRecord>(result: GetManyResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetManyResult<TRecord>>;
|
|
52
|
+
afterGetManyReference?<TRecord extends ResourceRecord>(result: GetManyReferenceResult<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<GetManyReferenceResult<TRecord>>;
|
|
53
|
+
beforeUpdateMany?<TRecord extends ResourceRecord>(params: UpdateManyParams<TRecord>, dataProvider: DataProvider<TResources>): MaybePromise<UpdateManyParams<TRecord>>;
|
|
54
|
+
}
|
|
55
|
+
/** One handler per registry resource, typed with that resource's record. */
|
|
56
|
+
type HandlerMap<TResources extends ResourceMap> = {
|
|
57
|
+
[N in keyof TResources & string]: {
|
|
58
|
+
resource: N;
|
|
59
|
+
} & LifecycleCallbacks<TResources, RecordOf<TResources, N>>;
|
|
60
|
+
};
|
|
61
|
+
type WildcardHandler<TResources extends ResourceMap> = {
|
|
40
62
|
resource: "*";
|
|
41
|
-
} &
|
|
63
|
+
} & WildcardCallbacks<TResources>;
|
|
64
|
+
/**
|
|
65
|
+
* A handler: the callbacks for one registry resource, typed with that
|
|
66
|
+
* resource's record, or for `"*"` (every resource), which hands back the
|
|
67
|
+
* record type it receives.
|
|
68
|
+
*/
|
|
69
|
+
export type LifecycleHandler<TResources extends ResourceMap = ResourceMap> = HandlerMap<TResources>[keyof TResources & string] | WildcardHandler<TResources>;
|
|
42
70
|
/**
|
|
43
71
|
* Wraps `dataProvider` with before/after hooks per resource, without touching
|
|
44
72
|
* it. For a call on resource `r`, the handlers whose `resource` is `r` or
|
|
@@ -1,54 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// src/data/with-lifecycle-callbacks.ts
|
|
2
|
+
/**
|
|
3
|
+
* Runs `befores` over `params` in order, calls the provider with the result,
|
|
4
|
+
* then runs `afters` over the provider's result. A rejecting `call` skips the
|
|
5
|
+
* `afters`. With no hooks at all the provider is called directly.
|
|
6
|
+
*/
|
|
7
|
+
function runChain(befores, call, afters, params) {
|
|
8
|
+
if (befores.length === 0 && afters.length === 0) return call(params);
|
|
9
|
+
return (async () => {
|
|
10
|
+
let nextParams = params;
|
|
11
|
+
for (const before of befores) nextParams = await before(nextParams);
|
|
12
|
+
let result = await call(nextParams);
|
|
13
|
+
for (const after of afters) result = await after(result);
|
|
14
|
+
return result;
|
|
15
|
+
})();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Wraps `dataProvider` with before/after hooks per resource, without touching
|
|
19
|
+
* it. For a call on resource `r`, the handlers whose `resource` is `r` or
|
|
20
|
+
* `"*"` run in array order: the `before` chain feeds the provider and the
|
|
21
|
+
* `after` chain feeds the caller. A rejecting provider skips the `after`
|
|
22
|
+
* callbacks. A verb with no matching callback calls the provider directly.
|
|
23
|
+
*/
|
|
13
24
|
function withLifecycleCallbacks(dataProvider, handlers) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
25
|
+
const snapshot = [...handlers];
|
|
26
|
+
const handlersFor = (resource) => snapshot.filter((h) => h.resource === resource || h.resource === "*");
|
|
27
|
+
const hook = (handler, callback) => callback ? [(value) => callback.call(handler, value, dataProvider)] : [];
|
|
28
|
+
return {
|
|
29
|
+
...dataProvider,
|
|
30
|
+
getList: (resource, params) => {
|
|
31
|
+
const matching = handlersFor(resource);
|
|
32
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeGetList)), (nextParams) => dataProvider.getList(resource, nextParams), matching.flatMap((h) => hook(h, h.afterGetList)), params);
|
|
33
|
+
},
|
|
34
|
+
getOne: (resource, params) => {
|
|
35
|
+
const matching = handlersFor(resource);
|
|
36
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeGetOne)), (nextParams) => dataProvider.getOne(resource, nextParams), matching.flatMap((h) => hook(h, h.afterGetOne)), params);
|
|
37
|
+
},
|
|
38
|
+
create: (resource, params) => {
|
|
39
|
+
const matching = handlersFor(resource);
|
|
40
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeCreate)), (nextParams) => dataProvider.create(resource, nextParams), matching.flatMap((h) => hook(h, h.afterCreate)), params);
|
|
41
|
+
},
|
|
42
|
+
update: (resource, params) => {
|
|
43
|
+
const matching = handlersFor(resource);
|
|
44
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeUpdate)), (nextParams) => dataProvider.update(resource, nextParams), matching.flatMap((h) => hook(h, h.afterUpdate)), params);
|
|
45
|
+
},
|
|
46
|
+
delete: (resource, params) => {
|
|
47
|
+
const matching = handlersFor(resource);
|
|
48
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeDelete)), (nextParams) => dataProvider.delete(resource, nextParams), matching.flatMap((h) => hook(h, h.afterDelete)), params);
|
|
49
|
+
},
|
|
50
|
+
getMany: (resource, params) => {
|
|
51
|
+
const matching = handlersFor(resource);
|
|
52
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeGetMany)), (nextParams) => dataProvider.getMany(resource, nextParams), matching.flatMap((h) => hook(h, h.afterGetMany)), params);
|
|
53
|
+
},
|
|
54
|
+
getManyReference: (resource, params) => {
|
|
55
|
+
const matching = handlersFor(resource);
|
|
56
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeGetManyReference)), (nextParams) => dataProvider.getManyReference(resource, nextParams), matching.flatMap((h) => hook(h, h.afterGetManyReference)), params);
|
|
57
|
+
},
|
|
58
|
+
updateMany: (resource, params) => {
|
|
59
|
+
const matching = handlersFor(resource);
|
|
60
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeUpdateMany)), (nextParams) => dataProvider.updateMany(resource, nextParams), matching.flatMap((h) => hook(h, h.afterUpdateMany)), params);
|
|
61
|
+
},
|
|
62
|
+
deleteMany: (resource, params) => {
|
|
63
|
+
const matching = handlersFor(resource);
|
|
64
|
+
return runChain(matching.flatMap((h) => hook(h, h.beforeDeleteMany)), (nextParams) => dataProvider.deleteMany(resource, nextParams), matching.flatMap((h) => hook(h, h.afterDeleteMany)), params);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
51
67
|
}
|
|
52
|
-
export {
|
|
53
|
-
withLifecycleCallbacks
|
|
54
|
-
};
|
|
68
|
+
export { withLifecycleCallbacks };
|