@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
|
@@ -1,62 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { announceLock } from "./announce-lock.js";
|
|
2
2
|
import { LockConflictError } from "./types.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} = {}) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
if (!existing) {
|
|
43
|
-
throw new Error(`No lock on ${resource}/${String(id)} to release`);
|
|
44
|
-
}
|
|
45
|
-
if (String(existing.identity) !== String(identity)) {
|
|
46
|
-
throw new LockConflictError(existing);
|
|
47
|
-
}
|
|
48
|
-
store.delete(key);
|
|
49
|
-
await announce("unlocked", existing);
|
|
50
|
-
return existing;
|
|
51
|
-
},
|
|
52
|
-
async getLock(resource, { id }) {
|
|
53
|
-
return store.get(storeKey(resource, id)) ?? null;
|
|
54
|
-
},
|
|
55
|
-
async getLocks(resource) {
|
|
56
|
-
return [...store.values()].filter((lock) => lock.resource === resource);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
3
|
+
// src/realtime/in-memory-lock-provider.ts
|
|
4
|
+
var storeKey = (resource, id) => JSON.stringify([resource, String(id)]);
|
|
5
|
+
/** A `LockProvider` kept in a `Map`, for tests and demos. */
|
|
6
|
+
function inMemoryLockProvider({ publisher } = {}) {
|
|
7
|
+
const store = /* @__PURE__ */ new Map();
|
|
8
|
+
return {
|
|
9
|
+
async lock(resource, { id, identity }) {
|
|
10
|
+
const key = storeKey(resource, id);
|
|
11
|
+
const existing = store.get(key);
|
|
12
|
+
if (existing) {
|
|
13
|
+
if (String(existing.identity) !== String(identity)) throw new LockConflictError(existing);
|
|
14
|
+
return existing;
|
|
15
|
+
}
|
|
16
|
+
const lock = {
|
|
17
|
+
resource,
|
|
18
|
+
recordId: id,
|
|
19
|
+
identity,
|
|
20
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21
|
+
};
|
|
22
|
+
store.set(key, lock);
|
|
23
|
+
await announceLock(publisher, "locked", lock);
|
|
24
|
+
return lock;
|
|
25
|
+
},
|
|
26
|
+
async unlock(resource, { id, identity }) {
|
|
27
|
+
const key = storeKey(resource, id);
|
|
28
|
+
const existing = store.get(key);
|
|
29
|
+
if (!existing) throw new Error(`No lock on ${resource}/${String(id)} to release`);
|
|
30
|
+
if (String(existing.identity) !== String(identity)) throw new LockConflictError(existing);
|
|
31
|
+
store.delete(key);
|
|
32
|
+
await announceLock(publisher, "unlocked", existing);
|
|
33
|
+
return existing;
|
|
34
|
+
},
|
|
35
|
+
async getLock(resource, { id }) {
|
|
36
|
+
return store.get(storeKey(resource, id)) ?? null;
|
|
37
|
+
},
|
|
38
|
+
async getLocks(resource) {
|
|
39
|
+
return [...store.values()].filter((lock) => lock.resource === resource);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
59
42
|
}
|
|
60
|
-
export {
|
|
61
|
-
inMemoryLockProvider
|
|
62
|
-
};
|
|
43
|
+
export { inMemoryLockProvider };
|
package/dist/realtime/live.js
CHANGED
|
@@ -1,118 +1,112 @@
|
|
|
1
1
|
import { lockKey, locksKey, resourceKey } from "../data/keys.js";
|
|
2
2
|
import { hasUnsettledUndoables, onUndoablesSettled } from "../data/undoable.js";
|
|
3
3
|
import { assertRealtimeDataProvider } from "./realtime-data-provider.js";
|
|
4
|
-
import {
|
|
4
|
+
import { lockResourceTopic, lockTopic, recordTopic, resourceTopic } from "./topics.js";
|
|
5
|
+
// src/realtime/live.ts
|
|
5
6
|
function subscribeLive(ctx, spec) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
missed = false;
|
|
56
|
-
onEvent();
|
|
57
|
-
}
|
|
58
|
-
};
|
|
7
|
+
const { dataProvider } = ctx;
|
|
8
|
+
assertRealtimeDataProvider(dataProvider, spec.caller);
|
|
9
|
+
let stopped = false;
|
|
10
|
+
let scheduled = false;
|
|
11
|
+
let missed = false;
|
|
12
|
+
const schedule = () => {
|
|
13
|
+
if (scheduled) return;
|
|
14
|
+
scheduled = true;
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
scheduled = false;
|
|
17
|
+
if (!stopped) spec.invalidate();
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
let stopWaiting;
|
|
21
|
+
const holdForUndoable = () => {
|
|
22
|
+
if (stopWaiting) return;
|
|
23
|
+
stopWaiting = onUndoablesSettled(() => {
|
|
24
|
+
stopWaiting?.();
|
|
25
|
+
stopWaiting = void 0;
|
|
26
|
+
onEvent();
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const onEvent = () => {
|
|
30
|
+
if (stopped) return;
|
|
31
|
+
if (spec.holdWhileUndoable && hasUnsettledUndoables()) {
|
|
32
|
+
holdForUndoable();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (spec.options?.paused?.()) {
|
|
36
|
+
missed = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
schedule();
|
|
40
|
+
};
|
|
41
|
+
const unsubscribes = spec.topics.map((topic) => dataProvider.subscribe(topic, onEvent));
|
|
42
|
+
if (spec.topics.length > 0 && dataProvider.onReconnect) unsubscribes.push(dataProvider.onReconnect(onEvent));
|
|
43
|
+
return {
|
|
44
|
+
stop() {
|
|
45
|
+
stopped = true;
|
|
46
|
+
stopWaiting?.();
|
|
47
|
+
stopWaiting = void 0;
|
|
48
|
+
for (const unsubscribe of unsubscribes.splice(0)) unsubscribe();
|
|
49
|
+
},
|
|
50
|
+
resume() {
|
|
51
|
+
if (stopped || !missed) return;
|
|
52
|
+
missed = false;
|
|
53
|
+
onEvent();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
59
56
|
}
|
|
57
|
+
/** Invalidates everything under the resource except lock queries, which have their own subscribers. */
|
|
60
58
|
function invalidateResource(ctx, resource) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
ctx.queryClient.invalidateQueries({
|
|
60
|
+
queryKey: resourceKey(resource),
|
|
61
|
+
predicate: ({ queryKey }) => queryKey[1] !== "getLock" && queryKey[1] !== "getLocks"
|
|
62
|
+
});
|
|
65
63
|
}
|
|
64
|
+
/** Refetches the resource's lists, shows and edits when anything in it changes or the connection comes back. */
|
|
66
65
|
function subscribeLiveResource(ctx, resource, options) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
return subscribeLive(ctx, {
|
|
67
|
+
caller: "subscribeLiveResource",
|
|
68
|
+
topics: [resourceTopic(resource)],
|
|
69
|
+
invalidate: () => invalidateResource(ctx, resource),
|
|
70
|
+
options,
|
|
71
|
+
holdWhileUndoable: true
|
|
72
|
+
});
|
|
74
73
|
}
|
|
74
|
+
/** Like `subscribeLiveResource`, for the events of one record. */
|
|
75
75
|
function subscribeLiveRecord(ctx, resource, id, options) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
return subscribeLive(ctx, {
|
|
77
|
+
caller: "subscribeLiveRecord",
|
|
78
|
+
topics: [recordTopic(resource, id)],
|
|
79
|
+
invalidate: () => invalidateResource(ctx, resource),
|
|
80
|
+
options,
|
|
81
|
+
holdWhileUndoable: true
|
|
82
|
+
});
|
|
83
83
|
}
|
|
84
|
+
/** Like `subscribeLiveRecord`, for several records: one invalidation however many of them change. */
|
|
84
85
|
function subscribeLiveRecords(ctx, resource, ids, options) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
86
|
+
return subscribeLive(ctx, {
|
|
87
|
+
caller: "subscribeLiveRecords",
|
|
88
|
+
topics: [...new Set(ids.map((id) => recordTopic(resource, id)))],
|
|
89
|
+
invalidate: () => invalidateResource(ctx, resource),
|
|
90
|
+
options,
|
|
91
|
+
holdWhileUndoable: true
|
|
92
|
+
});
|
|
93
93
|
}
|
|
94
|
+
/** Refetches one record's lock when it is taken or released. */
|
|
94
95
|
function subscribeLiveLock(ctx, resource, id) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
holdWhileUndoable: false
|
|
102
|
-
});
|
|
96
|
+
return subscribeLive(ctx, {
|
|
97
|
+
caller: "subscribeLiveLock",
|
|
98
|
+
topics: [lockTopic(resource, id)],
|
|
99
|
+
invalidate: () => void ctx.queryClient.invalidateQueries({ queryKey: lockKey(resource, { id }) }),
|
|
100
|
+
holdWhileUndoable: false
|
|
101
|
+
});
|
|
103
102
|
}
|
|
103
|
+
/** Refetches the resource's lock list when any lock is taken or released. */
|
|
104
104
|
function subscribeLiveLocks(ctx, resource) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
return subscribeLive(ctx, {
|
|
106
|
+
caller: "subscribeLiveLocks",
|
|
107
|
+
topics: [lockResourceTopic(resource)],
|
|
108
|
+
invalidate: () => void ctx.queryClient.invalidateQueries({ queryKey: locksKey(resource) }),
|
|
109
|
+
holdWhileUndoable: false
|
|
110
|
+
});
|
|
111
111
|
}
|
|
112
|
-
export {
|
|
113
|
-
subscribeLiveLock,
|
|
114
|
-
subscribeLiveLocks,
|
|
115
|
-
subscribeLiveRecord,
|
|
116
|
-
subscribeLiveRecords,
|
|
117
|
-
subscribeLiveResource
|
|
118
|
-
};
|
|
112
|
+
export { subscribeLiveLock, subscribeLiveLocks, subscribeLiveRecord, subscribeLiveRecords, subscribeLiveResource };
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { lockKey, locksKey } from "../data/keys.js";
|
|
2
2
|
import { assertRealtimeDataProvider } from "./realtime-data-provider.js";
|
|
3
|
+
// src/realtime/lock-options.ts
|
|
4
|
+
/** One record's lock, cached under `lockKey`. Resolves to `null` when unlocked. */
|
|
3
5
|
function getLockOptions(ctx, resource, params) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const { dataProvider } = ctx;
|
|
7
|
+
assertRealtimeDataProvider(dataProvider, "getLockOptions");
|
|
8
|
+
return {
|
|
9
|
+
queryKey: lockKey(resource, params),
|
|
10
|
+
queryFn: () => dataProvider.getLock(resource, params)
|
|
11
|
+
};
|
|
10
12
|
}
|
|
13
|
+
/** Every lock held on a resource, cached under `locksKey`. */
|
|
11
14
|
function getLocksOptions(ctx, resource) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const { dataProvider } = ctx;
|
|
16
|
+
assertRealtimeDataProvider(dataProvider, "getLocksOptions");
|
|
17
|
+
return {
|
|
18
|
+
queryKey: locksKey(resource),
|
|
19
|
+
queryFn: () => dataProvider.getLocks(resource)
|
|
20
|
+
};
|
|
18
21
|
}
|
|
19
|
-
export {
|
|
20
|
-
getLockOptions,
|
|
21
|
-
getLocksOptions
|
|
22
|
-
};
|
|
22
|
+
export { getLockOptions, getLocksOptions };
|
|
@@ -1,82 +1,95 @@
|
|
|
1
1
|
import { createMicroStore } from "../store/micro-store.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
2
|
+
// src/realtime/lock-session.ts
|
|
3
|
+
var toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
4
|
+
/**
|
|
5
|
+
* Takes a record's lock on `start()` and gives it back on `stop()`. Steps run
|
|
6
|
+
* one after another, so a `stop()` that arrives while `lock` is in flight
|
|
7
|
+
* releases the lock once it lands, and `start()`, `stop()`, `start()` in one
|
|
8
|
+
* tick ends holding it. Locks carry no expiry: a crashed tab leaves its lock
|
|
9
|
+
* until the lock provider drops it.
|
|
10
|
+
*/
|
|
11
|
+
function createLockSession({ dataProvider, resource, id, getIdentity, meta, onError }) {
|
|
12
|
+
const store = createMicroStore({
|
|
13
|
+
lock: null,
|
|
14
|
+
isLocking: false,
|
|
15
|
+
error: null
|
|
16
|
+
});
|
|
17
|
+
const metaParam = meta ? { meta } : {};
|
|
18
|
+
let held = null;
|
|
19
|
+
let running = false;
|
|
20
|
+
let epoch = 0;
|
|
21
|
+
let queue = Promise.resolve();
|
|
22
|
+
const enqueue = (step) => {
|
|
23
|
+
queue = queue.then(step).catch(() => {});
|
|
24
|
+
return queue;
|
|
25
|
+
};
|
|
26
|
+
const acquire = async (myEpoch) => {
|
|
27
|
+
const isCurrent = () => epoch === myEpoch;
|
|
28
|
+
try {
|
|
29
|
+
const identity = await getIdentity();
|
|
30
|
+
const lock = await dataProvider.lock(resource, {
|
|
31
|
+
id,
|
|
32
|
+
identity,
|
|
33
|
+
...metaParam
|
|
34
|
+
});
|
|
35
|
+
held = { identity };
|
|
36
|
+
if (isCurrent()) store.setState({
|
|
37
|
+
lock,
|
|
38
|
+
isLocking: false
|
|
39
|
+
});
|
|
40
|
+
} catch (cause) {
|
|
41
|
+
if (!isCurrent()) return;
|
|
42
|
+
const error = toError(cause);
|
|
43
|
+
store.setState({
|
|
44
|
+
isLocking: false,
|
|
45
|
+
error
|
|
46
|
+
});
|
|
47
|
+
onError?.(error);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const release = async () => {
|
|
51
|
+
if (!held) return;
|
|
52
|
+
const { identity } = held;
|
|
53
|
+
held = null;
|
|
54
|
+
try {
|
|
55
|
+
await dataProvider.unlock(resource, {
|
|
56
|
+
id,
|
|
57
|
+
identity,
|
|
58
|
+
...metaParam
|
|
59
|
+
});
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
onError?.(toError(cause));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
store,
|
|
66
|
+
start() {
|
|
67
|
+
if (running) return queue;
|
|
68
|
+
running = true;
|
|
69
|
+
const myEpoch = ++epoch;
|
|
70
|
+
store.setState({
|
|
71
|
+
isLocking: true,
|
|
72
|
+
error: null
|
|
73
|
+
});
|
|
74
|
+
return enqueue(() => acquire(myEpoch));
|
|
75
|
+
},
|
|
76
|
+
stop() {
|
|
77
|
+
if (!running) return queue;
|
|
78
|
+
running = false;
|
|
79
|
+
epoch++;
|
|
80
|
+
store.setState({
|
|
81
|
+
lock: null,
|
|
82
|
+
isLocking: false,
|
|
83
|
+
error: null
|
|
84
|
+
});
|
|
85
|
+
return enqueue(release);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
71
88
|
}
|
|
89
|
+
/** The explicit identity wins; otherwise the auth provider's. */
|
|
72
90
|
async function resolveLockIdentity(authProvider, explicit) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"no identity available. Pass identity explicitly or configure an authProvider."
|
|
77
|
-
);
|
|
91
|
+
if (explicit !== void 0) return explicit;
|
|
92
|
+
if (authProvider?.getIdentity) return (await authProvider.getIdentity()).id;
|
|
93
|
+
throw new Error("no identity available. Pass identity explicitly or configure an authProvider.");
|
|
78
94
|
}
|
|
79
|
-
export {
|
|
80
|
-
createLockSession,
|
|
81
|
-
resolveLockIdentity
|
|
82
|
-
};
|
|
95
|
+
export { createLockSession, resolveLockIdentity };
|
|
@@ -1,68 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
publishedEvents,
|
|
27
|
-
subscribe(topic, callback) {
|
|
28
|
-
const subscription = {
|
|
29
|
-
callback
|
|
30
|
-
};
|
|
31
|
-
const set = subscribers.get(topic) ?? /* @__PURE__ */ new Set();
|
|
32
|
-
set.add(subscription);
|
|
33
|
-
subscribers.set(topic, set);
|
|
34
|
-
return () => {
|
|
35
|
-
set.delete(subscription);
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
async publish(topic, event) {
|
|
39
|
-
const full = { topic, ...event };
|
|
40
|
-
publishedEvents.push(full);
|
|
41
|
-
if (delayMs) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
42
|
-
const live = subscribers.get(topic);
|
|
43
|
-
for (const subscription of [...live ?? []]) {
|
|
44
|
-
if (!live?.has(subscription)) continue;
|
|
45
|
-
const { callback } = subscription;
|
|
46
|
-
try {
|
|
47
|
-
callback(full);
|
|
48
|
-
} catch (cause) {
|
|
49
|
-
report({ kind: "handler_threw", topic, cause, retrying: false });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
onReconnect: (callback) => addListener(reconnectListeners, callback),
|
|
54
|
-
onStatusChange: (callback) => addListener(statusListeners, callback),
|
|
55
|
-
onError: (callback) => addListener(errorListeners, callback),
|
|
56
|
-
getStatus: () => status,
|
|
57
|
-
simulateReconnect() {
|
|
58
|
-
for (const listener of [...reconnectListeners]) listener();
|
|
59
|
-
},
|
|
60
|
-
setStatus(next) {
|
|
61
|
-
status = next;
|
|
62
|
-
for (const listener of [...statusListeners]) listener(next);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
1
|
+
import { createTopicHub } from "./topic-hub.js";
|
|
2
|
+
// src/realtime/memory-transport.ts
|
|
3
|
+
/** Routes events by topic inside one process, with no server or client library. */
|
|
4
|
+
function memoryTransport({ delayMs, onError } = {}) {
|
|
5
|
+
const hub = createTopicHub({ onError });
|
|
6
|
+
const publishedEvents = [];
|
|
7
|
+
return {
|
|
8
|
+
publishedEvents,
|
|
9
|
+
subscribe: hub.subscribe,
|
|
10
|
+
async publish(topic, event) {
|
|
11
|
+
const full = {
|
|
12
|
+
topic,
|
|
13
|
+
...event
|
|
14
|
+
};
|
|
15
|
+
publishedEvents.push(full);
|
|
16
|
+
if (delayMs) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
17
|
+
hub.dispatch(full);
|
|
18
|
+
},
|
|
19
|
+
onReconnect: hub.onReconnect,
|
|
20
|
+
onStatusChange: hub.onStatusChange,
|
|
21
|
+
onError: hub.onError,
|
|
22
|
+
getStatus: hub.getStatus,
|
|
23
|
+
simulateReconnect: hub.reconnected,
|
|
24
|
+
setStatus: hub.setStatus
|
|
25
|
+
};
|
|
65
26
|
}
|
|
66
|
-
export {
|
|
67
|
-
memoryTransport
|
|
68
|
-
};
|
|
27
|
+
export { memoryTransport };
|