@chelone/core 0.6.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 (242) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +21 -17
  10. package/dist/auth/auth-options.js +80 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +60 -0
  15. package/dist/block-editor/block-document.js +143 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.js +8 -11
  21. package/dist/command-menu/registry.d.ts +9 -5
  22. package/dist/command-menu/registry.js +36 -46
  23. package/dist/controller/calendar-dates.js +82 -113
  24. package/dist/controller/calendar-events.js +93 -88
  25. package/dist/controller/client-list-controller.d.ts +2 -2
  26. package/dist/controller/client-list-controller.js +251 -254
  27. package/dist/controller/data-table-columns.d.ts +2 -0
  28. package/dist/controller/data-table-columns.js +50 -56
  29. package/dist/controller/deep-equal.d.ts +1 -0
  30. package/dist/controller/deep-equal.js +14 -0
  31. package/dist/controller/filter-toggle.js +36 -44
  32. package/dist/controller/in-place-editor.d.ts +42 -0
  33. package/dist/controller/in-place-editor.js +42 -0
  34. package/dist/controller/infinite-list-controller.js +219 -189
  35. package/dist/controller/inspector.d.ts +49 -0
  36. package/dist/controller/inspector.js +64 -0
  37. package/dist/controller/job-monitor.d.ts +3 -4
  38. package/dist/controller/job-monitor.js +37 -47
  39. package/dist/controller/kanban-board.d.ts +2 -3
  40. package/dist/controller/kanban-board.js +43 -24
  41. package/dist/controller/list-controller.d.ts +5 -5
  42. package/dist/controller/list-controller.js +444 -400
  43. package/dist/controller/list-params.js +96 -87
  44. package/dist/controller/pivot-grid.js +89 -95
  45. package/dist/controller/prev-next.d.ts +2 -2
  46. package/dist/controller/prev-next.js +74 -54
  47. package/dist/controller/record-controllers.d.ts +14 -6
  48. package/dist/controller/record-controllers.js +288 -257
  49. package/dist/controller/record-diff.d.ts +21 -0
  50. package/dist/controller/record-diff.js +50 -0
  51. package/dist/controller/record-timeline.js +42 -30
  52. package/dist/controller/record-tree.js +57 -47
  53. package/dist/controller/record-workflow.d.ts +38 -16
  54. package/dist/controller/record-workflow.js +85 -64
  55. package/dist/controller/reference-many-controller.js +198 -192
  56. package/dist/controller/reference-one-controller.d.ts +1 -1
  57. package/dist/controller/reference-one-controller.js +63 -56
  58. package/dist/controller/saved-queries.js +104 -91
  59. package/dist/controller/translate-fallback.js +10 -6
  60. package/dist/csv/export.d.ts +2 -1
  61. package/dist/csv/export.js +43 -40
  62. package/dist/csv/import-controller.js +185 -169
  63. package/dist/csv/match-headers.js +29 -22
  64. package/dist/csv/parse-with.js +60 -54
  65. package/dist/csv/run-import.js +69 -72
  66. package/dist/csv/validate-rows.d.ts +1 -1
  67. package/dist/csv/validate-rows.js +65 -52
  68. package/dist/data/cache-ops.d.ts +5 -4
  69. package/dist/data/cache-ops.js +208 -188
  70. package/dist/data/combine-data-providers.js +25 -23
  71. package/dist/data/data-provider-log.d.ts +45 -0
  72. package/dist/data/data-provider-log.js +93 -0
  73. package/dist/data/errors.js +14 -10
  74. package/dist/data/fake-provider.d.ts +6 -3
  75. package/dist/data/fake-provider.js +129 -114
  76. package/dist/data/fetch-utils.js +61 -44
  77. package/dist/data/filter-branch.d.ts +5 -0
  78. package/dist/data/filter-branch.js +10 -0
  79. package/dist/data/get-many-aggregate.js +84 -76
  80. package/dist/data/http-error.js +17 -12
  81. package/dist/data/keys.js +50 -36
  82. package/dist/data/mutation-options.d.ts +14 -9
  83. package/dist/data/mutation-options.js +93 -109
  84. package/dist/data/query-options.js +73 -68
  85. package/dist/data/remove-empty.d.ts +3 -0
  86. package/dist/data/remove-empty.js +11 -0
  87. package/dist/data/undoable.d.ts +2 -2
  88. package/dist/data/undoable.js +190 -174
  89. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  90. package/dist/data/with-lifecycle-callbacks.js +66 -52
  91. package/dist/environment/application-update-store.js +58 -55
  92. package/dist/environment/offline-store.js +21 -21
  93. package/dist/form/array-binding.d.ts +30 -11
  94. package/dist/form/array-binding.js +184 -166
  95. package/dist/form/choice-ids.d.ts +7 -0
  96. package/dist/form/choice-ids.js +11 -0
  97. package/dist/form/choices-controller.d.ts +3 -2
  98. package/dist/form/choices-controller.js +302 -275
  99. package/dist/form/date-format.d.ts +38 -0
  100. package/dist/form/date-format.js +89 -0
  101. package/dist/form/dirty-values.d.ts +2 -2
  102. package/dist/form/dirty-values.js +22 -11
  103. package/dist/form/duration.d.ts +22 -0
  104. package/dist/form/duration.js +49 -0
  105. package/dist/form/edit-create-form.d.ts +12 -5
  106. package/dist/form/edit-create-form.js +243 -188
  107. package/dist/form/event-value.d.ts +6 -0
  108. package/dist/form/event-value.js +17 -0
  109. package/dist/form/field-error.d.ts +14 -0
  110. package/dist/form/field-error.js +28 -0
  111. package/dist/form/field-name.d.ts +20 -0
  112. package/dist/form/field-name.js +6 -0
  113. package/dist/form/filter-form-values.js +63 -49
  114. package/dist/form/form-controller.d.ts +9 -1
  115. package/dist/form/form-controller.js +165 -160
  116. package/dist/form/form-groups.d.ts +4 -3
  117. package/dist/form/form-groups.js +79 -79
  118. package/dist/form/form-handle.d.ts +175 -0
  119. package/dist/form/form-handle.js +68 -0
  120. package/dist/form/input-binding.d.ts +56 -31
  121. package/dist/form/input-binding.js +169 -118
  122. package/dist/form/input-values.d.ts +130 -0
  123. package/dist/form/input-values.js +177 -0
  124. package/dist/form/json-text.js +51 -37
  125. package/dist/form/save-pipeline.d.ts +6 -8
  126. package/dist/form/save-pipeline.js +91 -82
  127. package/dist/form/set-form-values.d.ts +6 -0
  128. package/dist/form/set-form-values.js +10 -0
  129. package/dist/form/source.js +97 -65
  130. package/dist/form/text-of.d.ts +2 -0
  131. package/dist/form/text-of.js +7 -0
  132. package/dist/form/validators.d.ts +58 -19
  133. package/dist/form/validators.js +97 -71
  134. package/dist/geo/bbox.d.ts +5 -4
  135. package/dist/geo/bbox.js +69 -48
  136. package/dist/geo/bounds.d.ts +14 -3
  137. package/dist/geo/bounds.js +98 -68
  138. package/dist/geo/constraints.js +88 -93
  139. package/dist/geo/coordinates.js +31 -23
  140. package/dist/geo/editor.d.ts +13 -13
  141. package/dist/geo/editor.js +176 -153
  142. package/dist/geo/geocoding.d.ts +4 -4
  143. package/dist/geo/geocoding.js +38 -41
  144. package/dist/geo/geojson.d.ts +12 -74
  145. package/dist/geo/geojson.js +52 -40
  146. package/dist/geo/geometry-math.d.ts +4 -4
  147. package/dist/geo/geometry-math.js +100 -93
  148. package/dist/geo/index.d.ts +7 -6
  149. package/dist/geo/map-layers.d.ts +42 -7
  150. package/dist/geo/map-layers.js +88 -62
  151. package/dist/geo/map-style.js +31 -32
  152. package/dist/geo/messages.js +48 -45
  153. package/dist/geo/nominatim.js +88 -80
  154. package/dist/geo/osm-presets.d.ts +4 -3
  155. package/dist/geo/osm-presets.js +108 -86
  156. package/dist/geo/osm-tags.js +14 -12
  157. package/dist/geo/overpass.d.ts +14 -2
  158. package/dist/geo/overpass.js +83 -90
  159. package/dist/geo/shape-value.d.ts +9 -5
  160. package/dist/geo/shape-value.js +169 -146
  161. package/dist/geo/snap-to-roads.d.ts +3 -3
  162. package/dist/geo/snap-to-roads.js +55 -50
  163. package/dist/geo/throttle.js +36 -39
  164. package/dist/guards.d.ts +37 -0
  165. package/dist/guards.js +62 -0
  166. package/dist/i18n/default-messages.js +34 -29
  167. package/dist/i18n/english-messages.js +187 -189
  168. package/dist/i18n/i18n.d.ts +2 -2
  169. package/dist/i18n/i18n.js +47 -60
  170. package/dist/i18n/inflection.d.ts +34 -0
  171. package/dist/i18n/inflection.js +82 -0
  172. package/dist/i18n/messages.d.ts +10 -2
  173. package/dist/i18n/messages.js +17 -4
  174. package/dist/i18n/missing-keys.js +66 -62
  175. package/dist/i18n/translatable.d.ts +16 -1
  176. package/dist/i18n/translatable.js +40 -47
  177. package/dist/index.d.ts +71 -24
  178. package/dist/index.js +40 -288
  179. package/dist/json/deep-equal.d.ts +8 -0
  180. package/dist/json/deep-equal.js +24 -0
  181. package/dist/json/strip-json-comments.js +38 -42
  182. package/dist/noop.d.ts +2 -0
  183. package/dist/noop.js +4 -0
  184. package/dist/notification/error-message.d.ts +2 -0
  185. package/dist/notification/error-message.js +10 -0
  186. package/dist/notification/notification-store.d.ts +3 -2
  187. package/dist/notification/notification-store.js +41 -45
  188. package/dist/onboarding-tour/placement.js +63 -78
  189. package/dist/onboarding-tour/tour-controller.js +46 -54
  190. package/dist/realtime/add-events-for-mutations.js +75 -81
  191. package/dist/realtime/announce-lock.js +16 -13
  192. package/dist/realtime/broadcast-channel-transport.js +65 -59
  193. package/dist/realtime/copy-members.js +22 -14
  194. package/dist/realtime/in-memory-lock-provider.js +40 -48
  195. package/dist/realtime/live.js +95 -101
  196. package/dist/realtime/lock-options.js +16 -16
  197. package/dist/realtime/lock-session.js +91 -78
  198. package/dist/realtime/memory-transport.js +25 -25
  199. package/dist/realtime/presence.js +120 -122
  200. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  201. package/dist/realtime/realtime-data-provider.js +45 -42
  202. package/dist/realtime/reconnect-backoff.js +17 -0
  203. package/dist/realtime/sse-transport.d.ts +31 -0
  204. package/dist/realtime/sse-transport.js +235 -0
  205. package/dist/realtime/status-store.js +23 -28
  206. package/dist/realtime/topic-hub.js +62 -71
  207. package/dist/realtime/topics.js +10 -12
  208. package/dist/realtime/types.js +10 -12
  209. package/dist/realtime/unread-count.js +21 -21
  210. package/dist/realtime/websocket-transport.d.ts +32 -0
  211. package/dist/realtime/websocket-transport.js +306 -0
  212. package/dist/resource/describe-resource.d.ts +2 -0
  213. package/dist/resource/describe-resource.js +232 -219
  214. package/dist/resource/get-at-path.js +29 -14
  215. package/dist/resource/identifier.d.ts +18 -0
  216. package/dist/resource/identifier.js +27 -0
  217. package/dist/resource/record.d.ts +4 -0
  218. package/dist/resource/record.js +10 -0
  219. package/dist/resource/register.d.ts +7 -0
  220. package/dist/resource/registry-store.d.ts +45 -15
  221. package/dist/resource/registry-store.js +67 -60
  222. package/dist/resource/registry.d.ts +10 -0
  223. package/dist/resource/registry.js +8 -4
  224. package/dist/resource/source-path.d.ts +16 -1
  225. package/dist/rich-text/rich-text-extras.d.ts +132 -0
  226. package/dist/rich-text/rich-text-extras.js +179 -0
  227. package/dist/rich-text/rich-text-image.d.ts +124 -0
  228. package/dist/rich-text/rich-text-image.js +163 -0
  229. package/dist/rich-text/rich-text.d.ts +6 -0
  230. package/dist/rich-text/rich-text.js +125 -88
  231. package/dist/router/location.d.ts +22 -4
  232. package/dist/router/location.js +41 -18
  233. package/dist/router/match.js +47 -39
  234. package/dist/router/paths.js +40 -34
  235. package/dist/router/record-from-location.d.ts +1 -1
  236. package/dist/router/record-from-location.js +25 -17
  237. package/dist/store/adapters.js +51 -55
  238. package/dist/store/micro-store.js +57 -59
  239. package/dist/store/shallow-equal.d.ts +12 -0
  240. package/dist/store/shallow-equal.js +20 -0
  241. package/dist/store/store.js +54 -57
  242. package/package.json +13 -11
@@ -0,0 +1,235 @@
1
+ import { isRecord } from "../guards.js";
2
+ import { reconnectDelay, resolveReconnectConfig } from "./reconnect-backoff.js";
3
+ import { createTopicHub } from "./topic-hub.js";
4
+ // src/realtime/sse-transport.ts
5
+ function isWireEvent(value) {
6
+ return isRecord(value) && typeof value.type === "string" && (value.meta === void 0 || isRecord(value.meta));
7
+ }
8
+ var DEFAULT_IDLE_DISCONNECT_MS = 3e4;
9
+ /**
10
+ * Receives realtime events over server-sent events, one named event per topic,
11
+ * and publishes with a POST.
12
+ */
13
+ function sseTransport({ url, publishUrl, getAuthToken, withCredentials = false, reconnect, idleDisconnectMs = DEFAULT_IDLE_DISCONNECT_MS, topicFilterParam = "topics", onError, fetch: fetchOption }) {
14
+ const hub = createTopicHub({ onError });
15
+ hub.setStatus("idle");
16
+ const config = resolveReconnectConfig(reconnect);
17
+ let source = null;
18
+ let filter = /* @__PURE__ */ new Set();
19
+ let active = false;
20
+ let generation = 0;
21
+ let attempt = 0;
22
+ let dropped = false;
23
+ let openQueued = false;
24
+ let resolvingToken = false;
25
+ let retryTimer;
26
+ let idleTimer;
27
+ const readToken = async () => await getAuthToken?.() || void 0;
28
+ const stopStream = () => {
29
+ clearTimeout(retryTimer);
30
+ retryTimer = void 0;
31
+ source?.close();
32
+ source = null;
33
+ filter = /* @__PURE__ */ new Set();
34
+ };
35
+ const streamUrl = (topics, token) => {
36
+ const query = [`${encodeURIComponent(topicFilterParam)}=${encodeURIComponent(topics.join(","))}`, ...token === void 0 ? [] : [`token=${encodeURIComponent(token)}`]].join("&");
37
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
38
+ };
39
+ const retryOrGiveUp = (failure) => {
40
+ const retrying = config.enabled && attempt < config.maxAttempts;
41
+ hub.report(failure(retrying));
42
+ if (!retrying) {
43
+ active = false;
44
+ hub.setStatus("disconnected");
45
+ return;
46
+ }
47
+ hub.setStatus("reconnecting");
48
+ retryTimer = setTimeout(() => {
49
+ retryTimer = void 0;
50
+ open(true);
51
+ }, reconnectDelay(attempt, config));
52
+ attempt += 1;
53
+ };
54
+ const handleError = (stream, opened) => {
55
+ if (source !== stream) return;
56
+ stopStream();
57
+ dropped ||= opened;
58
+ retryOrGiveUp((retrying) => ({
59
+ kind: "connect_failed",
60
+ retrying
61
+ }));
62
+ };
63
+ async function open(isRetry = false) {
64
+ stopStream();
65
+ const current = ++generation;
66
+ active = true;
67
+ resolvingToken = true;
68
+ let token;
69
+ try {
70
+ token = await readToken();
71
+ } catch (cause) {
72
+ if (current !== generation) return;
73
+ resolvingToken = false;
74
+ if (isRetry) {
75
+ retryOrGiveUp((retrying) => ({
76
+ kind: "auth_failed",
77
+ cause,
78
+ retrying
79
+ }));
80
+ return;
81
+ }
82
+ active = false;
83
+ hub.report({
84
+ kind: "auth_failed",
85
+ cause,
86
+ retrying: false
87
+ });
88
+ hub.setStatus("disconnected");
89
+ return;
90
+ }
91
+ if (current !== generation) return;
92
+ resolvingToken = false;
93
+ const topics = hub.topics();
94
+ if (topics.length === 0) {
95
+ goIdle();
96
+ return;
97
+ }
98
+ let stream;
99
+ try {
100
+ stream = new EventSource(streamUrl(topics, token), { withCredentials });
101
+ } catch {
102
+ active = false;
103
+ hub.report({
104
+ kind: "connect_failed",
105
+ retrying: false
106
+ });
107
+ hub.setStatus("disconnected");
108
+ return;
109
+ }
110
+ source = stream;
111
+ filter = new Set(topics);
112
+ let opened = false;
113
+ stream.addEventListener("open", () => {
114
+ if (source !== stream) return;
115
+ opened = true;
116
+ attempt = 0;
117
+ hub.setStatus("connected");
118
+ if (dropped) {
119
+ dropped = false;
120
+ hub.reconnected();
121
+ }
122
+ });
123
+ stream.addEventListener("error", () => handleError(stream, opened));
124
+ for (const topic of topics) stream.addEventListener(topic, (message) => {
125
+ if (source !== stream || !("data" in message)) return;
126
+ if (typeof message.data === "string") receive(topic, message.data);
127
+ });
128
+ }
129
+ const receive = (topic, data) => {
130
+ let parsed;
131
+ try {
132
+ parsed = JSON.parse(data);
133
+ } catch (cause) {
134
+ hub.report({
135
+ kind: "parse_failed",
136
+ topic,
137
+ cause,
138
+ retrying: false
139
+ });
140
+ return;
141
+ }
142
+ if (isWireEvent(parsed)) hub.dispatch({
143
+ ...parsed,
144
+ topic
145
+ });
146
+ };
147
+ const queueOpen = () => {
148
+ if (openQueued) return;
149
+ openQueued = true;
150
+ Promise.resolve().then(() => {
151
+ if (!openQueued) return;
152
+ openQueued = false;
153
+ if (!active) attempt = 0;
154
+ return open();
155
+ });
156
+ };
157
+ function goIdle() {
158
+ generation += 1;
159
+ resolvingToken = false;
160
+ clearTimeout(idleTimer);
161
+ idleTimer = void 0;
162
+ stopStream();
163
+ active = false;
164
+ dropped = false;
165
+ if (hub.getStatus() !== "idle") hub.setStatus("idle");
166
+ }
167
+ return {
168
+ subscribe(topic, callback) {
169
+ const stop = hub.subscribe(topic, callback);
170
+ clearTimeout(idleTimer);
171
+ idleTimer = void 0;
172
+ const covered = retryTimer !== void 0 || resolvingToken || filter.has(topic);
173
+ if (!active || !covered) queueOpen();
174
+ return () => {
175
+ stop();
176
+ if (hub.topics().length > 0 || !active) return;
177
+ clearTimeout(idleTimer);
178
+ idleTimer = setTimeout(goIdle, idleDisconnectMs);
179
+ };
180
+ },
181
+ async publish(topic, event) {
182
+ if (!publishUrl) throw new Error("sseTransport needs the publishUrl option to publish");
183
+ const headers = { "Content-Type": "application/json" };
184
+ try {
185
+ const token = await readToken();
186
+ if (token !== void 0) headers.Authorization = `Bearer ${token}`;
187
+ } catch (cause) {
188
+ hub.report({
189
+ kind: "auth_failed",
190
+ topic,
191
+ cause,
192
+ retrying: false
193
+ });
194
+ throw cause;
195
+ }
196
+ const doFetch = fetchOption ?? ((input, init) => globalThis.fetch(input, init));
197
+ try {
198
+ const response = await doFetch(publishUrl, {
199
+ method: "POST",
200
+ headers,
201
+ body: JSON.stringify({
202
+ topic,
203
+ event
204
+ }),
205
+ credentials: withCredentials ? "include" : void 0
206
+ });
207
+ if (!response.ok) throw new Error(`Publishing to ${topic} failed: ${response.status}`);
208
+ } catch (cause) {
209
+ hub.report({
210
+ kind: "send_failed",
211
+ topic,
212
+ cause,
213
+ retrying: false
214
+ });
215
+ throw cause;
216
+ }
217
+ },
218
+ onReconnect: hub.onReconnect,
219
+ onStatusChange: hub.onStatusChange,
220
+ onError: hub.onError,
221
+ getStatus: hub.getStatus,
222
+ close() {
223
+ openQueued = false;
224
+ generation += 1;
225
+ resolvingToken = false;
226
+ clearTimeout(idleTimer);
227
+ idleTimer = void 0;
228
+ stopStream();
229
+ active = false;
230
+ dropped = false;
231
+ hub.setStatus("disconnected");
232
+ }
233
+ };
234
+ }
235
+ export { sseTransport };
@@ -1,33 +1,28 @@
1
1
  import { createMicroStore } from "../store/micro-store.js";
2
+ // src/realtime/status-store.ts
2
3
  function readStatus(source) {
3
- if (source.getStatus) return source.getStatus();
4
- return source.onStatusChange ? "disconnected" : "idle";
4
+ if (source.getStatus) return source.getStatus();
5
+ return source.onStatusChange ? "disconnected" : "idle";
5
6
  }
7
+ /** Follows the connection while it has a subscriber, and lets go of it at zero. */
6
8
  function createRealtimeStatusStore(source) {
7
- const store = createMicroStore(
8
- { status: readStatus(source), lastError: null },
9
- {
10
- onSubscribed() {
11
- const status = readStatus(source);
12
- store.setState(
13
- status === "connected" ? { status, lastError: null } : { status }
14
- );
15
- const stops = [
16
- source.onStatusChange?.(
17
- (status2) => store.setState(
18
- status2 === "connected" ? { status: status2, lastError: null } : { status: status2 }
19
- )
20
- ),
21
- source.onError?.((lastError) => store.setState({ lastError }))
22
- ];
23
- return () => {
24
- for (const stop of stops) stop?.();
25
- };
26
- }
27
- }
28
- );
29
- return store;
9
+ const store = createMicroStore({
10
+ status: readStatus(source),
11
+ lastError: null
12
+ }, { onSubscribed() {
13
+ const status = readStatus(source);
14
+ store.setState(status === "connected" ? {
15
+ status,
16
+ lastError: null
17
+ } : { status });
18
+ const stops = [source.onStatusChange?.((status) => store.setState(status === "connected" ? {
19
+ status,
20
+ lastError: null
21
+ } : { status })), source.onError?.((lastError) => store.setState({ lastError }))];
22
+ return () => {
23
+ for (const stop of stops) stop?.();
24
+ };
25
+ } });
26
+ return store;
30
27
  }
31
- export {
32
- createRealtimeStatusStore
33
- };
28
+ export { createRealtimeStatusStore };
@@ -1,75 +1,66 @@
1
- const addListener = (listeners, listener) => {
2
- listeners.add(listener);
3
- return () => {
4
- listeners.delete(listener);
5
- };
1
+ // src/realtime/topic-hub.ts
2
+ var addListener = (listeners, listener) => {
3
+ listeners.add(listener);
4
+ return () => {
5
+ listeners.delete(listener);
6
+ };
6
7
  };
7
- const isolate = (call) => {
8
- try {
9
- call();
10
- } catch {
11
- }
8
+ var isolate = (call) => {
9
+ try {
10
+ call();
11
+ } catch {}
12
12
  };
13
+ /** Starts `"connected"`; a transport that connects asynchronously sets its own status. */
13
14
  function createTopicHub({ onError } = {}) {
14
- const subscribers = /* @__PURE__ */ new Map();
15
- const reconnectListeners = /* @__PURE__ */ new Set();
16
- const statusListeners = /* @__PURE__ */ new Set();
17
- const errorListeners = /* @__PURE__ */ new Set();
18
- let status = "connected";
19
- const report = (error) => {
20
- for (const listener of [...onError ? [onError] : [], ...errorListeners]) {
21
- isolate(() => listener(error));
22
- }
23
- };
24
- return {
25
- subscribe(topic, callback) {
26
- const subscription = {
27
- // type-escape: the hub stores callbacks for any payload; dispatch passes the event on unchanged.
28
- callback
29
- };
30
- const set = subscribers.get(topic) ?? /* @__PURE__ */ new Set();
31
- set.add(subscription);
32
- subscribers.set(topic, set);
33
- return () => {
34
- set.delete(subscription);
35
- if (set.size === 0 && subscribers.get(topic) === set) {
36
- subscribers.delete(topic);
37
- }
38
- };
39
- },
40
- dispatch(event) {
41
- const live = subscribers.get(event.topic);
42
- for (const subscription of [...live ?? []]) {
43
- if (!live?.has(subscription)) continue;
44
- try {
45
- subscription.callback(event);
46
- } catch (cause) {
47
- report({
48
- kind: "handler_threw",
49
- topic: event.topic,
50
- cause,
51
- retrying: false
52
- });
53
- }
54
- }
55
- },
56
- has: (topic) => subscribers.has(topic),
57
- topics: () => [...subscribers.keys()],
58
- report,
59
- onReconnect: (callback) => addListener(reconnectListeners, callback),
60
- onStatusChange: (callback) => addListener(statusListeners, callback),
61
- onError: (callback) => addListener(errorListeners, callback),
62
- getStatus: () => status,
63
- setStatus(next) {
64
- status = next;
65
- for (const listener of [...statusListeners])
66
- isolate(() => listener(next));
67
- },
68
- reconnected() {
69
- for (const listener of [...reconnectListeners]) isolate(listener);
70
- }
71
- };
15
+ const subscribers = /* @__PURE__ */ new Map();
16
+ const reconnectListeners = /* @__PURE__ */ new Set();
17
+ const statusListeners = /* @__PURE__ */ new Set();
18
+ const errorListeners = /* @__PURE__ */ new Set();
19
+ let status = "connected";
20
+ const report = (error) => {
21
+ for (const listener of [...onError ? [onError] : [], ...errorListeners]) isolate(() => listener(error));
22
+ };
23
+ return {
24
+ subscribe(topic, callback) {
25
+ const subscription = { callback };
26
+ const set = subscribers.get(topic) ?? /* @__PURE__ */ new Set();
27
+ set.add(subscription);
28
+ subscribers.set(topic, set);
29
+ return () => {
30
+ set.delete(subscription);
31
+ if (set.size === 0 && subscribers.get(topic) === set) subscribers.delete(topic);
32
+ };
33
+ },
34
+ dispatch(event) {
35
+ const live = subscribers.get(event.topic);
36
+ for (const subscription of [...live ?? []]) {
37
+ if (!live?.has(subscription)) continue;
38
+ try {
39
+ subscription.callback(event);
40
+ } catch (cause) {
41
+ report({
42
+ kind: "handler_threw",
43
+ topic: event.topic,
44
+ cause,
45
+ retrying: false
46
+ });
47
+ }
48
+ }
49
+ },
50
+ has: (topic) => subscribers.has(topic),
51
+ topics: () => [...subscribers.keys()],
52
+ report,
53
+ onReconnect: (callback) => addListener(reconnectListeners, callback),
54
+ onStatusChange: (callback) => addListener(statusListeners, callback),
55
+ onError: (callback) => addListener(errorListeners, callback),
56
+ getStatus: () => status,
57
+ setStatus(next) {
58
+ status = next;
59
+ for (const listener of [...statusListeners]) isolate(() => listener(next));
60
+ },
61
+ reconnected() {
62
+ for (const listener of [...reconnectListeners]) isolate(listener);
63
+ }
64
+ };
72
65
  }
73
- export {
74
- createTopicHub
75
- };
66
+ export { createTopicHub };
@@ -1,12 +1,10 @@
1
- const resourceTopic = (resource) => `resource/${resource}`;
2
- const recordTopic = (resource, id) => `resource/${resource}/${id}`;
3
- const lockResourceTopic = (resource) => `lock/${resource}`;
4
- const lockTopic = (resource, id) => `lock/${resource}/${id}`;
5
- const presenceTopic = (resource, id) => `presence/${resource}/${id}`;
6
- export {
7
- lockResourceTopic,
8
- lockTopic,
9
- presenceTopic,
10
- recordTopic,
11
- resourceTopic
12
- };
1
+ // src/realtime/topics.ts
2
+ /** Events for the resource as a whole: creates, and the ids of any write. */
3
+ var resourceTopic = (resource) => `resource/${resource}`;
4
+ /** Events for one record. `1` and `"1"` give the same topic. */
5
+ var recordTopic = (resource, id) => `resource/${resource}/${id}`;
6
+ var lockResourceTopic = (resource) => `lock/${resource}`;
7
+ var lockTopic = (resource, id) => `lock/${resource}/${id}`;
8
+ /** Who is on one record. `1` and `"1"` give the same topic. */
9
+ var presenceTopic = (resource, id) => `presence/${resource}/${id}`;
10
+ export { lockResourceTopic, lockTopic, presenceTopic, recordTopic, resourceTopic };
@@ -1,13 +1,11 @@
1
- class LockConflictError extends Error {
2
- kind = "LockConflictError";
3
- existingLock;
4
- constructor(existingLock) {
5
- super(
6
- `Resource ${existingLock.resource}/${String(existingLock.recordId)} is locked by ${String(existingLock.identity)}`
7
- );
8
- this.existingLock = existingLock;
9
- }
10
- }
11
- export {
12
- LockConflictError
1
+ // src/realtime/types.ts
2
+ /** Rejected by `lock()` when another identity already holds the record. */
3
+ var LockConflictError = class extends Error {
4
+ kind = "LockConflictError";
5
+ existingLock;
6
+ constructor(existingLock) {
7
+ super(`Resource ${existingLock.resource}/${String(existingLock.recordId)} is locked by ${String(existingLock.identity)}`);
8
+ this.existingLock = existingLock;
9
+ }
13
10
  };
11
+ export { LockConflictError };
@@ -1,27 +1,27 @@
1
1
  import { resourceTopic } from "./topics.js";
2
- const DEFAULT_EVENTS = ["created"];
2
+ // src/realtime/unread-count.ts
3
+ var DEFAULT_EVENTS = ["created"];
4
+ /**
5
+ * The store key of a resource's unread count for an event list. The default
6
+ * `["created"]` keeps `realtime.unread.<resource>`; any other list adds its
7
+ * sorted, de-duplicated types as a suffix, so the order of `events` never
8
+ * splits a count and links watching different events never share one.
9
+ */
3
10
  function unreadKey(resource, events = DEFAULT_EVENTS) {
4
- const types = [...new Set(events)].sort();
5
- const base = `realtime.unread.${resource}`;
6
- return types.length === 1 && types[0] === "created" ? base : `${base}.${types.join("+")}`;
11
+ const types = [...new Set(events)].sort();
12
+ const base = `realtime.unread.${resource}`;
13
+ return types.length === 1 && types[0] === "created" ? base : `${base}.${types.join("+")}`;
7
14
  }
8
- function subscribeUnreadCount({
9
- dataProvider,
10
- store,
11
- resource,
12
- events = DEFAULT_EVENTS
13
- }) {
14
- const key = unreadKey(resource, events);
15
- return dataProvider.subscribe(resourceTopic(resource), (event) => {
16
- if (!events.includes(event.type)) return;
17
- store.setItem(key, (store.getItem(key) ?? 0) + 1);
18
- });
15
+ /** Adds one to the count under `unreadKey(resource, events)` for each listed event. Returns the teardown. */
16
+ function subscribeUnreadCount({ dataProvider, store, resource, events = DEFAULT_EVENTS }) {
17
+ const key = unreadKey(resource, events);
18
+ return dataProvider.subscribe(resourceTopic(resource), (event) => {
19
+ if (!events.includes(event.type)) return;
20
+ store.setItem(key, (store.getItem(key) ?? 0) + 1);
21
+ });
19
22
  }
23
+ /** Resets the count of one event list; pass the same `events` the link subscribed with. */
20
24
  function clearUnread(store, resource, events) {
21
- store.setItem(unreadKey(resource, events), 0);
25
+ store.setItem(unreadKey(resource, events), 0);
22
26
  }
23
- export {
24
- clearUnread,
25
- subscribeUnreadCount,
26
- unreadKey
27
- };
27
+ export { clearUnread, subscribeUnreadCount, unreadKey };
@@ -0,0 +1,32 @@
1
+ import { type RealtimeReconnectConfig } from "./reconnect-backoff.js";
2
+ import type { RealtimeTransport, RealtimeTransportError } from "./types.js";
3
+ export interface WebSocketTransportOptions {
4
+ url: string;
5
+ /** Subprotocols offered on every connection. */
6
+ protocols?: string[];
7
+ /** Read before every connection, so a retry carries a fresh token. */
8
+ getAuthToken?: () => string | null | undefined | Promise<string | null | undefined>;
9
+ /**
10
+ * Where the token goes. `"query"` (default) adds a URL-encoded `token`
11
+ * parameter; `"subprotocol"` offers it after `protocols` and keeps it out of
12
+ * the URL.
13
+ */
14
+ authMode?: "query" | "subprotocol";
15
+ reconnect?: RealtimeReconnectConfig;
16
+ /** Close the socket after this long with no subscriber. Default 30000. */
17
+ idleDisconnectMs?: number;
18
+ /** Ping this often; a ping with no reply by the next one is a drop. Default 30000. */
19
+ heartbeatMs?: number;
20
+ /** Called for every transport error, like a listener from `onError()`. */
21
+ onError?: (error: RealtimeTransportError) => void;
22
+ }
23
+ /** A `RealtimeTransport` with every optional member, plus `close()`. */
24
+ export interface WebSocketTransport extends Required<RealtimeTransport> {
25
+ /**
26
+ * Closes the socket, rejects queued publishes and sets `"disconnected"`.
27
+ * A later subscribe or publish opens again.
28
+ */
29
+ close(): void;
30
+ }
31
+ /** Carries realtime events over one WebSocket and reconnects with backoff. */
32
+ export declare function webSocketTransport({ url, protocols, getAuthToken, authMode, reconnect, idleDisconnectMs, heartbeatMs, onError, }: WebSocketTransportOptions): WebSocketTransport;