@chelone/core 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/assistant/assistant-session.d.ts +28 -0
  2. package/dist/assistant/assistant-session.js +208 -0
  3. package/dist/assistant/echo-transport.d.ts +7 -0
  4. package/dist/assistant/echo-transport.js +28 -0
  5. package/dist/assistant/http-transport.d.ts +10 -0
  6. package/dist/assistant/http-transport.js +98 -0
  7. package/dist/assistant/types.d.ts +54 -0
  8. package/dist/auth/access-control.d.ts +13 -5
  9. package/dist/auth/access-control.js +25 -21
  10. package/dist/auth/auth-options.d.ts +21 -17
  11. package/dist/auth/auth-options.js +80 -48
  12. package/dist/auth/permission-matrix.d.ts +12 -0
  13. package/dist/auth/permission-matrix.js +67 -0
  14. package/dist/auth/types.d.ts +14 -4
  15. package/dist/block-editor/block-document.d.ts +60 -0
  16. package/dist/block-editor/block-document.js +143 -0
  17. package/dist/block-editor/data-blocks.d.ts +63 -0
  18. package/dist/block-editor/data-blocks.js +117 -0
  19. package/dist/chart/chart-data.d.ts +25 -0
  20. package/dist/chart/chart-data.js +61 -0
  21. package/dist/command-menu/hotkey.js +58 -47
  22. package/dist/command-menu/recents.js +8 -11
  23. package/dist/command-menu/registry.d.ts +9 -5
  24. package/dist/command-menu/registry.js +36 -46
  25. package/dist/controller/calendar-dates.js +82 -113
  26. package/dist/controller/calendar-events.js +93 -88
  27. package/dist/controller/client-list-controller.d.ts +2 -2
  28. package/dist/controller/client-list-controller.js +251 -254
  29. package/dist/controller/data-table-columns.d.ts +2 -0
  30. package/dist/controller/data-table-columns.js +50 -56
  31. package/dist/controller/deep-equal.d.ts +1 -0
  32. package/dist/controller/deep-equal.js +14 -0
  33. package/dist/controller/filter-toggle.js +36 -44
  34. package/dist/controller/in-place-editor.d.ts +42 -0
  35. package/dist/controller/in-place-editor.js +42 -0
  36. package/dist/controller/infinite-list-controller.js +220 -190
  37. package/dist/controller/inspector.d.ts +49 -0
  38. package/dist/controller/inspector.js +64 -0
  39. package/dist/controller/job-monitor.d.ts +41 -0
  40. package/dist/controller/job-monitor.js +49 -0
  41. package/dist/controller/kanban-board.d.ts +2 -3
  42. package/dist/controller/kanban-board.js +43 -24
  43. package/dist/controller/list-controller.d.ts +5 -5
  44. package/dist/controller/list-controller.js +444 -400
  45. package/dist/controller/list-params.js +96 -87
  46. package/dist/controller/pivot-grid.d.ts +36 -0
  47. package/dist/controller/pivot-grid.js +99 -0
  48. package/dist/controller/prev-next.d.ts +2 -2
  49. package/dist/controller/prev-next.js +74 -54
  50. package/dist/controller/record-controllers.d.ts +14 -6
  51. package/dist/controller/record-controllers.js +288 -257
  52. package/dist/controller/record-diff.d.ts +21 -0
  53. package/dist/controller/record-diff.js +50 -0
  54. package/dist/controller/record-timeline.d.ts +35 -0
  55. package/dist/controller/record-timeline.js +48 -0
  56. package/dist/controller/record-tree.js +57 -47
  57. package/dist/controller/record-workflow.d.ts +38 -16
  58. package/dist/controller/record-workflow.js +85 -64
  59. package/dist/controller/reference-many-controller.js +198 -192
  60. package/dist/controller/reference-one-controller.d.ts +1 -1
  61. package/dist/controller/reference-one-controller.js +63 -56
  62. package/dist/controller/saved-queries.js +104 -91
  63. package/dist/controller/translate-fallback.js +10 -6
  64. package/dist/csv/export.d.ts +2 -1
  65. package/dist/csv/export.js +43 -40
  66. package/dist/csv/import-controller.js +185 -169
  67. package/dist/csv/match-headers.js +29 -22
  68. package/dist/csv/parse-with.js +60 -54
  69. package/dist/csv/run-import.js +69 -72
  70. package/dist/csv/validate-rows.d.ts +1 -1
  71. package/dist/csv/validate-rows.js +65 -52
  72. package/dist/data/cache-ops.d.ts +5 -4
  73. package/dist/data/cache-ops.js +208 -190
  74. package/dist/data/combine-data-providers.js +25 -23
  75. package/dist/data/data-provider-log.d.ts +45 -0
  76. package/dist/data/data-provider-log.js +93 -0
  77. package/dist/data/errors.js +14 -10
  78. package/dist/data/fake-provider.d.ts +6 -3
  79. package/dist/data/fake-provider.js +129 -114
  80. package/dist/data/fetch-utils.js +61 -44
  81. package/dist/data/filter-branch.d.ts +5 -0
  82. package/dist/data/filter-branch.js +10 -0
  83. package/dist/data/get-many-aggregate.js +84 -76
  84. package/dist/data/http-error.js +17 -12
  85. package/dist/data/keys.js +50 -36
  86. package/dist/data/mutation-options.d.ts +14 -9
  87. package/dist/data/mutation-options.js +93 -109
  88. package/dist/data/query-options.js +73 -68
  89. package/dist/data/remove-empty.d.ts +3 -0
  90. package/dist/data/remove-empty.js +11 -0
  91. package/dist/data/undoable.d.ts +2 -2
  92. package/dist/data/undoable.js +190 -174
  93. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  94. package/dist/data/with-lifecycle-callbacks.js +66 -52
  95. package/dist/environment/application-update-store.js +58 -55
  96. package/dist/environment/offline-store.js +21 -21
  97. package/dist/form/array-binding.d.ts +30 -11
  98. package/dist/form/array-binding.js +184 -166
  99. package/dist/form/choice-ids.d.ts +7 -0
  100. package/dist/form/choice-ids.js +11 -0
  101. package/dist/form/choices-controller.d.ts +3 -2
  102. package/dist/form/choices-controller.js +302 -275
  103. package/dist/form/date-format.d.ts +38 -0
  104. package/dist/form/date-format.js +89 -0
  105. package/dist/form/dirty-values.d.ts +2 -2
  106. package/dist/form/dirty-values.js +22 -11
  107. package/dist/form/duration.d.ts +22 -0
  108. package/dist/form/duration.js +49 -0
  109. package/dist/form/edit-create-form.d.ts +12 -5
  110. package/dist/form/edit-create-form.js +243 -188
  111. package/dist/form/event-value.d.ts +6 -0
  112. package/dist/form/event-value.js +17 -0
  113. package/dist/form/field-error.d.ts +14 -0
  114. package/dist/form/field-error.js +28 -0
  115. package/dist/form/field-name.d.ts +20 -0
  116. package/dist/form/field-name.js +6 -0
  117. package/dist/form/filter-form-values.js +63 -49
  118. package/dist/form/form-controller.d.ts +9 -1
  119. package/dist/form/form-controller.js +165 -160
  120. package/dist/form/form-groups.d.ts +4 -3
  121. package/dist/form/form-groups.js +79 -79
  122. package/dist/form/form-handle.d.ts +175 -0
  123. package/dist/form/form-handle.js +68 -0
  124. package/dist/form/input-binding.d.ts +56 -31
  125. package/dist/form/input-binding.js +169 -118
  126. package/dist/form/input-values.d.ts +130 -0
  127. package/dist/form/input-values.js +177 -0
  128. package/dist/form/json-text.js +51 -37
  129. package/dist/form/save-pipeline.d.ts +6 -8
  130. package/dist/form/save-pipeline.js +91 -82
  131. package/dist/form/set-form-values.d.ts +6 -0
  132. package/dist/form/set-form-values.js +10 -0
  133. package/dist/form/source.js +97 -65
  134. package/dist/form/text-of.d.ts +2 -0
  135. package/dist/form/text-of.js +7 -0
  136. package/dist/form/validators.d.ts +58 -19
  137. package/dist/form/validators.js +97 -71
  138. package/dist/geo/bbox.d.ts +5 -4
  139. package/dist/geo/bbox.js +69 -48
  140. package/dist/geo/bounds.d.ts +14 -3
  141. package/dist/geo/bounds.js +98 -68
  142. package/dist/geo/constraints.js +88 -93
  143. package/dist/geo/coordinates.js +31 -23
  144. package/dist/geo/editor.d.ts +13 -13
  145. package/dist/geo/editor.js +176 -153
  146. package/dist/geo/geocoding.d.ts +4 -4
  147. package/dist/geo/geocoding.js +38 -41
  148. package/dist/geo/geojson.d.ts +12 -74
  149. package/dist/geo/geojson.js +52 -40
  150. package/dist/geo/geometry-math.d.ts +4 -4
  151. package/dist/geo/geometry-math.js +100 -93
  152. package/dist/geo/index.d.ts +7 -6
  153. package/dist/geo/map-layers.d.ts +42 -7
  154. package/dist/geo/map-layers.js +88 -62
  155. package/dist/geo/map-style.js +31 -32
  156. package/dist/geo/messages.js +48 -45
  157. package/dist/geo/nominatim.js +88 -80
  158. package/dist/geo/osm-presets.d.ts +4 -3
  159. package/dist/geo/osm-presets.js +108 -86
  160. package/dist/geo/osm-tags.js +14 -12
  161. package/dist/geo/overpass.d.ts +14 -2
  162. package/dist/geo/overpass.js +83 -90
  163. package/dist/geo/shape-value.d.ts +9 -5
  164. package/dist/geo/shape-value.js +169 -146
  165. package/dist/geo/snap-to-roads.d.ts +3 -3
  166. package/dist/geo/snap-to-roads.js +55 -50
  167. package/dist/geo/throttle.js +36 -39
  168. package/dist/guards.d.ts +37 -0
  169. package/dist/guards.js +62 -0
  170. package/dist/i18n/default-messages.js +34 -29
  171. package/dist/i18n/english-messages.js +187 -189
  172. package/dist/i18n/i18n.d.ts +8 -2
  173. package/dist/i18n/i18n.js +47 -48
  174. package/dist/i18n/inflection.d.ts +34 -0
  175. package/dist/i18n/inflection.js +82 -0
  176. package/dist/i18n/messages.d.ts +10 -2
  177. package/dist/i18n/messages.js +17 -4
  178. package/dist/i18n/missing-keys.d.ts +19 -0
  179. package/dist/i18n/missing-keys.js +68 -0
  180. package/dist/i18n/translatable.d.ts +16 -1
  181. package/dist/i18n/translatable.js +40 -47
  182. package/dist/index.d.ts +96 -22
  183. package/dist/index.js +52 -257
  184. package/dist/json/deep-equal.d.ts +8 -0
  185. package/dist/json/deep-equal.js +24 -0
  186. package/dist/json/strip-json-comments.js +38 -42
  187. package/dist/noop.d.ts +2 -0
  188. package/dist/noop.js +4 -0
  189. package/dist/notification/error-message.d.ts +2 -0
  190. package/dist/notification/error-message.js +10 -0
  191. package/dist/notification/notification-store.d.ts +3 -2
  192. package/dist/notification/notification-store.js +41 -45
  193. package/dist/onboarding-tour/placement.d.ts +34 -0
  194. package/dist/onboarding-tour/placement.js +67 -0
  195. package/dist/onboarding-tour/tour-controller.d.ts +25 -0
  196. package/dist/onboarding-tour/tour-controller.js +50 -0
  197. package/dist/realtime/add-events-for-mutations.js +75 -81
  198. package/dist/realtime/announce-lock.d.ts +7 -0
  199. package/dist/realtime/announce-lock.js +18 -0
  200. package/dist/realtime/broadcast-channel-transport.d.ts +14 -0
  201. package/dist/realtime/broadcast-channel-transport.js +69 -0
  202. package/dist/realtime/copy-members.js +22 -14
  203. package/dist/realtime/in-memory-lock-provider.js +41 -60
  204. package/dist/realtime/live.js +95 -101
  205. package/dist/realtime/lock-options.js +16 -16
  206. package/dist/realtime/lock-session.js +91 -78
  207. package/dist/realtime/memory-transport.js +26 -67
  208. package/dist/realtime/presence.js +120 -122
  209. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  210. package/dist/realtime/realtime-data-provider.js +45 -42
  211. package/dist/realtime/reconnect-backoff.d.ts +15 -0
  212. package/dist/realtime/reconnect-backoff.js +17 -0
  213. package/dist/realtime/sse-transport.d.ts +31 -0
  214. package/dist/realtime/sse-transport.js +235 -0
  215. package/dist/realtime/status-store.js +23 -28
  216. package/dist/realtime/topic-hub.d.ts +24 -0
  217. package/dist/realtime/topic-hub.js +66 -0
  218. package/dist/realtime/topics.js +10 -12
  219. package/dist/realtime/types.js +10 -12
  220. package/dist/realtime/unread-count.js +21 -21
  221. package/dist/realtime/websocket-transport.d.ts +32 -0
  222. package/dist/realtime/websocket-transport.js +306 -0
  223. package/dist/resource/describe-resource.d.ts +2 -0
  224. package/dist/resource/describe-resource.js +232 -219
  225. package/dist/resource/get-at-path.js +29 -14
  226. package/dist/resource/identifier.d.ts +18 -0
  227. package/dist/resource/identifier.js +27 -0
  228. package/dist/resource/record.d.ts +4 -0
  229. package/dist/resource/record.js +10 -0
  230. package/dist/resource/register.d.ts +7 -0
  231. package/dist/resource/registry-store.d.ts +45 -15
  232. package/dist/resource/registry-store.js +67 -60
  233. package/dist/resource/registry.d.ts +10 -0
  234. package/dist/resource/registry.js +8 -4
  235. package/dist/resource/source-path.d.ts +16 -1
  236. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  237. package/dist/rich-text/rich-text-extras.js +179 -0
  238. package/dist/rich-text/rich-text-image.d.ts +124 -0
  239. package/dist/rich-text/rich-text-image.js +163 -0
  240. package/dist/rich-text/rich-text.d.ts +62 -0
  241. package/dist/rich-text/rich-text.js +142 -0
  242. package/dist/router/location.d.ts +22 -4
  243. package/dist/router/location.js +41 -18
  244. package/dist/router/match.js +47 -39
  245. package/dist/router/paths.js +40 -34
  246. package/dist/router/record-from-location.d.ts +1 -1
  247. package/dist/router/record-from-location.js +25 -17
  248. package/dist/store/adapters.js +51 -55
  249. package/dist/store/micro-store.js +57 -59
  250. package/dist/store/shallow-equal.d.ts +12 -0
  251. package/dist/store/shallow-equal.js +20 -0
  252. package/dist/store/store.js +54 -57
  253. package/package.json +13 -11
@@ -1,62 +1,43 @@
1
- import { lockTopic, lockResourceTopic } from "./topics.js";
1
+ import { announceLock } from "./announce-lock.js";
2
2
  import { LockConflictError } from "./types.js";
3
- const storeKey = (resource, id) => JSON.stringify([resource, String(id)]);
4
- function inMemoryLockProvider({
5
- publisher
6
- } = {}) {
7
- const store = /* @__PURE__ */ new Map();
8
- const announce = async (type, lock) => {
9
- if (!publisher) return;
10
- const event = { type, payload: lock };
11
- try {
12
- await Promise.all([
13
- publisher.publish(lockTopic(lock.resource, lock.recordId), event),
14
- publisher.publish(lockResourceTopic(lock.resource), event)
15
- ]);
16
- } catch {
17
- }
18
- };
19
- return {
20
- async lock(resource, { id, identity }) {
21
- const key = storeKey(resource, id);
22
- const existing = store.get(key);
23
- if (existing) {
24
- if (String(existing.identity) !== String(identity)) {
25
- throw new LockConflictError(existing);
26
- }
27
- return existing;
28
- }
29
- const lock = {
30
- resource,
31
- recordId: id,
32
- identity,
33
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
34
- };
35
- store.set(key, lock);
36
- await announce("locked", lock);
37
- return lock;
38
- },
39
- async unlock(resource, { id, identity }) {
40
- const key = storeKey(resource, id);
41
- const existing = store.get(key);
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 };
@@ -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 { recordTopic, resourceTopic, lockTopic, lockResourceTopic } from "./topics.js";
4
+ import { lockResourceTopic, lockTopic, recordTopic, resourceTopic } from "./topics.js";
5
+ // src/realtime/live.ts
5
6
  function subscribeLive(ctx, spec) {
6
- const { dataProvider } = ctx;
7
- assertRealtimeDataProvider(dataProvider, spec.caller);
8
- let stopped = false;
9
- let scheduled = false;
10
- let missed = false;
11
- const schedule = () => {
12
- if (scheduled) return;
13
- scheduled = true;
14
- queueMicrotask(() => {
15
- scheduled = false;
16
- if (!stopped) spec.invalidate();
17
- });
18
- };
19
- let stopWaiting;
20
- const holdForUndoable = () => {
21
- if (stopWaiting) return;
22
- stopWaiting = onUndoablesSettled(() => {
23
- stopWaiting?.();
24
- stopWaiting = void 0;
25
- onEvent();
26
- });
27
- };
28
- const onEvent = () => {
29
- if (stopped) return;
30
- if (spec.holdWhileUndoable && hasUnsettledUndoables()) {
31
- holdForUndoable();
32
- return;
33
- }
34
- if (spec.options?.paused?.()) {
35
- missed = true;
36
- return;
37
- }
38
- schedule();
39
- };
40
- const unsubscribes = spec.topics.map(
41
- (topic) => dataProvider.subscribe(topic, onEvent)
42
- );
43
- if (spec.topics.length > 0 && dataProvider.onReconnect) {
44
- unsubscribes.push(dataProvider.onReconnect(onEvent));
45
- }
46
- return {
47
- stop() {
48
- stopped = true;
49
- stopWaiting?.();
50
- stopWaiting = void 0;
51
- for (const unsubscribe of unsubscribes.splice(0)) unsubscribe();
52
- },
53
- resume() {
54
- if (stopped || !missed) return;
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
- void ctx.queryClient.invalidateQueries({
62
- queryKey: resourceKey(resource),
63
- predicate: ({ queryKey }) => queryKey[1] !== "getLock" && queryKey[1] !== "getLocks"
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
- return subscribeLive(ctx, {
68
- caller: "subscribeLiveResource",
69
- topics: [resourceTopic(resource)],
70
- invalidate: () => invalidateResource(ctx, resource),
71
- options,
72
- holdWhileUndoable: true
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
- return subscribeLive(ctx, {
77
- caller: "subscribeLiveRecord",
78
- topics: [recordTopic(resource, id)],
79
- invalidate: () => invalidateResource(ctx, resource),
80
- options,
81
- holdWhileUndoable: true
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
- return subscribeLive(ctx, {
86
- caller: "subscribeLiveRecords",
87
- // `1` and `"1"` give the same topic; listen once.
88
- topics: [...new Set(ids.map((id) => recordTopic(resource, id)))],
89
- invalidate: () => invalidateResource(ctx, resource),
90
- options,
91
- holdWhileUndoable: true
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
- return subscribeLive(ctx, {
96
- caller: "subscribeLiveLock",
97
- topics: [lockTopic(resource, id)],
98
- invalidate: () => void ctx.queryClient.invalidateQueries({
99
- queryKey: lockKey(resource, { id })
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
- return subscribeLive(ctx, {
106
- caller: "subscribeLiveLocks",
107
- topics: [lockResourceTopic(resource)],
108
- invalidate: () => void ctx.queryClient.invalidateQueries({ queryKey: locksKey(resource) }),
109
- holdWhileUndoable: false
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
- const { dataProvider } = ctx;
5
- assertRealtimeDataProvider(dataProvider, "getLockOptions");
6
- return {
7
- queryKey: lockKey(resource, params),
8
- queryFn: () => dataProvider.getLock(resource, params)
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
- const { dataProvider } = ctx;
13
- assertRealtimeDataProvider(dataProvider, "getLocksOptions");
14
- return {
15
- queryKey: locksKey(resource),
16
- queryFn: () => dataProvider.getLocks(resource)
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
- const toError = (error) => error instanceof Error ? error : new Error(String(error));
3
- function createLockSession({
4
- dataProvider,
5
- resource,
6
- id,
7
- getIdentity,
8
- meta,
9
- onError
10
- }) {
11
- const store = createMicroStore({
12
- lock: null,
13
- isLocking: false,
14
- error: null
15
- });
16
- const metaParam = meta ? { meta } : {};
17
- let held = null;
18
- let running = false;
19
- let epoch = 0;
20
- let queue = Promise.resolve();
21
- const enqueue = (step) => {
22
- queue = queue.then(step).catch(() => {
23
- });
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({ lock, isLocking: false });
37
- } catch (cause) {
38
- if (!isCurrent()) return;
39
- const error = toError(cause);
40
- store.setState({ isLocking: false, error });
41
- onError?.(error);
42
- }
43
- };
44
- const release = async () => {
45
- if (!held) return;
46
- const { identity } = held;
47
- held = null;
48
- try {
49
- await dataProvider.unlock(resource, { id, identity, ...metaParam });
50
- } catch (cause) {
51
- onError?.(toError(cause));
52
- }
53
- };
54
- return {
55
- store,
56
- start() {
57
- if (running) return queue;
58
- running = true;
59
- const myEpoch = ++epoch;
60
- store.setState({ isLocking: true, error: null });
61
- return enqueue(() => acquire(myEpoch));
62
- },
63
- stop() {
64
- if (!running) return queue;
65
- running = false;
66
- epoch++;
67
- store.setState({ lock: null, isLocking: false, error: null });
68
- return enqueue(release);
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
- if (explicit !== void 0) return explicit;
74
- if (authProvider?.getIdentity) return (await authProvider.getIdentity()).id;
75
- throw new Error(
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
- const addListener = (listeners, listener) => {
2
- listeners.add(listener);
3
- return () => {
4
- listeners.delete(listener);
5
- };
6
- };
7
- function memoryTransport({
8
- delayMs,
9
- onError
10
- } = {}) {
11
- const subscribers = /* @__PURE__ */ new Map();
12
- const publishedEvents = [];
13
- const reconnectListeners = /* @__PURE__ */ new Set();
14
- const statusListeners = /* @__PURE__ */ new Set();
15
- const errorListeners = /* @__PURE__ */ new Set();
16
- let status = "connected";
17
- const report = (error) => {
18
- for (const handler of [...onError ? [onError] : [], ...errorListeners]) {
19
- try {
20
- handler(error);
21
- } catch {
22
- }
23
- }
24
- };
25
- return {
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 };