@syncular/client 0.1.2 → 0.2.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 (233) hide show
  1. package/README.md +227 -709
  2. package/dist/apply.d.ts +80 -0
  3. package/dist/apply.js +301 -0
  4. package/dist/blob.d.ts +127 -0
  5. package/dist/blob.js +201 -0
  6. package/dist/bun-database.d.ts +22 -0
  7. package/dist/bun-database.js +61 -0
  8. package/dist/client.d.ts +320 -69
  9. package/dist/client.js +1627 -163
  10. package/dist/content-type.d.ts +2 -0
  11. package/dist/content-type.js +2 -0
  12. package/dist/database.d.ts +35 -80
  13. package/dist/database.js +32 -947
  14. package/dist/encryption.d.ts +47 -0
  15. package/dist/encryption.js +75 -0
  16. package/dist/errors.d.ts +8 -22
  17. package/dist/errors.js +10 -207
  18. package/dist/http.d.ts +32 -0
  19. package/dist/http.js +196 -0
  20. package/dist/index.d.ts +28 -16
  21. package/dist/index.js +25 -12
  22. package/dist/invalidation.d.ts +69 -0
  23. package/dist/invalidation.js +84 -0
  24. package/dist/leader-lock.d.ts +28 -0
  25. package/dist/leader-lock.js +38 -0
  26. package/dist/multi-tab.d.ts +134 -0
  27. package/dist/multi-tab.js +399 -0
  28. package/dist/node-database.d.ts +41 -0
  29. package/dist/node-database.js +133 -0
  30. package/dist/outbox.d.ts +56 -0
  31. package/dist/outbox.js +140 -0
  32. package/dist/schema.d.ts +107 -0
  33. package/dist/schema.js +267 -0
  34. package/dist/state.d.ts +40 -0
  35. package/dist/state.js +77 -0
  36. package/dist/transport.d.ts +44 -0
  37. package/dist/transport.js +7 -0
  38. package/dist/wasm-database.d.ts +30 -0
  39. package/dist/wasm-database.js +170 -0
  40. package/dist/window.d.ts +64 -0
  41. package/dist/window.js +0 -0
  42. package/dist/worker-entry.d.ts +16 -2
  43. package/dist/worker-entry.js +300 -456
  44. package/dist/worker-host.d.ts +174 -0
  45. package/dist/worker-host.js +536 -0
  46. package/dist/worker-protocol.d.ts +173 -61
  47. package/dist/worker-protocol.js +7 -16
  48. package/package.json +45 -112
  49. package/src/apply.ts +435 -0
  50. package/src/blob.ts +333 -0
  51. package/src/bun-database.ts +75 -0
  52. package/src/client.ts +2247 -277
  53. package/src/content-type.ts +2 -0
  54. package/src/database.ts +63 -1389
  55. package/src/encryption.ts +123 -0
  56. package/src/errors.ts +11 -265
  57. package/src/http.ts +254 -0
  58. package/src/index.ts +25 -20
  59. package/src/invalidation.ts +128 -0
  60. package/src/leader-lock.ts +68 -0
  61. package/src/multi-tab.ts +550 -0
  62. package/src/node-database.ts +180 -0
  63. package/src/outbox.ts +206 -0
  64. package/src/schema.ts +397 -0
  65. package/src/state.ts +126 -0
  66. package/src/transport.ts +52 -0
  67. package/src/wasm-database.ts +279 -0
  68. package/src/window.ts +0 -0
  69. package/src/worker-entry.ts +391 -545
  70. package/src/worker-host.ts +800 -0
  71. package/src/worker-protocol.ts +204 -99
  72. package/dist/auth-leases.d.ts +0 -11
  73. package/dist/auth-leases.d.ts.map +0 -1
  74. package/dist/auth-leases.js +0 -140
  75. package/dist/auth-leases.js.map +0 -1
  76. package/dist/blob-limits.d.ts +0 -15
  77. package/dist/blob-limits.d.ts.map +0 -1
  78. package/dist/blob-limits.js +0 -66
  79. package/dist/blob-limits.js.map +0 -1
  80. package/dist/bridge-client.d.ts +0 -69
  81. package/dist/bridge-client.d.ts.map +0 -1
  82. package/dist/bridge-client.js +0 -282
  83. package/dist/bridge-client.js.map +0 -1
  84. package/dist/client-config.d.ts +0 -6
  85. package/dist/client-config.d.ts.map +0 -1
  86. package/dist/client-config.js +0 -18
  87. package/dist/client-config.js.map +0 -1
  88. package/dist/client.d.ts.map +0 -1
  89. package/dist/client.js.map +0 -1
  90. package/dist/command-history.d.ts +0 -35
  91. package/dist/command-history.d.ts.map +0 -1
  92. package/dist/command-history.js +0 -378
  93. package/dist/command-history.js.map +0 -1
  94. package/dist/console-diagnostics.d.ts +0 -33
  95. package/dist/console-diagnostics.d.ts.map +0 -1
  96. package/dist/console-diagnostics.js +0 -496
  97. package/dist/console-diagnostics.js.map +0 -1
  98. package/dist/crdt-yjs/index.d.ts +0 -4
  99. package/dist/crdt-yjs/index.d.ts.map +0 -1
  100. package/dist/crdt-yjs/index.js +0 -4
  101. package/dist/crdt-yjs/index.js.map +0 -1
  102. package/dist/crdt-yjs/webview-host-facade.d.ts +0 -126
  103. package/dist/crdt-yjs/webview-host-facade.d.ts.map +0 -1
  104. package/dist/crdt-yjs/webview-host-facade.js +0 -284
  105. package/dist/crdt-yjs/webview-host-facade.js.map +0 -1
  106. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +0 -153
  107. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +0 -1
  108. package/dist/crdt-yjs/yjs-document-field-adapter.js +0 -406
  109. package/dist/crdt-yjs/yjs-document-field-adapter.js.map +0 -1
  110. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +0 -73
  111. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +0 -1
  112. package/dist/crdt-yjs/yjs-prosemirror-bridge.js +0 -169
  113. package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +0 -1
  114. package/dist/database.d.ts.map +0 -1
  115. package/dist/database.js.map +0 -1
  116. package/dist/diagnostics.d.ts +0 -10
  117. package/dist/diagnostics.d.ts.map +0 -1
  118. package/dist/diagnostics.js +0 -83
  119. package/dist/diagnostics.js.map +0 -1
  120. package/dist/errors.d.ts.map +0 -1
  121. package/dist/errors.js.map +0 -1
  122. package/dist/generated-bridge.d.ts +0 -365
  123. package/dist/generated-bridge.d.ts.map +0 -1
  124. package/dist/generated-bridge.js +0 -250
  125. package/dist/generated-bridge.js.map +0 -1
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js.map +0 -1
  128. package/dist/mutations.d.ts +0 -72
  129. package/dist/mutations.d.ts.map +0 -1
  130. package/dist/mutations.js +0 -63
  131. package/dist/mutations.js.map +0 -1
  132. package/dist/network.d.ts +0 -3
  133. package/dist/network.d.ts.map +0 -1
  134. package/dist/network.js +0 -17
  135. package/dist/network.js.map +0 -1
  136. package/dist/react/index.d.ts +0 -169
  137. package/dist/react/index.d.ts.map +0 -1
  138. package/dist/react/index.js +0 -628
  139. package/dist/react/index.js.map +0 -1
  140. package/dist/react-native/index.d.ts +0 -35
  141. package/dist/react-native/index.d.ts.map +0 -1
  142. package/dist/react-native/index.js +0 -49
  143. package/dist/react-native/index.js.map +0 -1
  144. package/dist/runtime-contract.d.ts +0 -13
  145. package/dist/runtime-contract.d.ts.map +0 -1
  146. package/dist/runtime-contract.js +0 -24
  147. package/dist/runtime-contract.js.map +0 -1
  148. package/dist/rust-client.d.ts +0 -106
  149. package/dist/rust-client.d.ts.map +0 -1
  150. package/dist/rust-client.js +0 -938
  151. package/dist/rust-client.js.map +0 -1
  152. package/dist/sentry.d.ts +0 -35
  153. package/dist/sentry.d.ts.map +0 -1
  154. package/dist/sentry.js +0 -155
  155. package/dist/sentry.js.map +0 -1
  156. package/dist/sql-safety.d.ts +0 -3
  157. package/dist/sql-safety.d.ts.map +0 -1
  158. package/dist/sql-safety.js +0 -62
  159. package/dist/sql-safety.js.map +0 -1
  160. package/dist/syncular-runtime-artifacts.json +0 -61
  161. package/dist/tauri/index.d.ts +0 -35
  162. package/dist/tauri/index.d.ts.map +0 -1
  163. package/dist/tauri/index.js +0 -114
  164. package/dist/tauri/index.js.map +0 -1
  165. package/dist/types.d.ts +0 -1096
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -2
  168. package/dist/types.js.map +0 -1
  169. package/dist/wasm/.syncular-wasm-profile +0 -1
  170. package/dist/wasm/syncular-runtime-artifact.json +0 -21
  171. package/dist/wasm/syncular.d.ts +0 -207
  172. package/dist/wasm/syncular.js +0 -2341
  173. package/dist/wasm/syncular_bg.wasm +0 -0
  174. package/dist/wasm/syncular_bg.wasm.d.ts +0 -97
  175. package/dist/wasm-bindings/runtime-contract.d.ts +0 -22
  176. package/dist/wasm-bindings/runtime-contract.d.ts.map +0 -1
  177. package/dist/wasm-bindings/runtime-contract.js +0 -112
  178. package/dist/wasm-bindings/runtime-contract.js.map +0 -1
  179. package/dist/wasm-core/.syncular-wasm-profile +0 -1
  180. package/dist/wasm-core/syncular-runtime-artifact.json +0 -17
  181. package/dist/wasm-core/syncular.d.ts +0 -162
  182. package/dist/wasm-core/syncular.js +0 -1847
  183. package/dist/wasm-core/syncular_bg.wasm +0 -0
  184. package/dist/wasm-core/syncular_bg.wasm.d.ts +0 -77
  185. package/dist/wasm-perf/.syncular-wasm-profile +0 -1
  186. package/dist/wasm-perf/syncular-runtime-artifact.json +0 -21
  187. package/dist/wasm-perf/syncular.d.ts +0 -207
  188. package/dist/wasm-perf/syncular.js +0 -2341
  189. package/dist/wasm-perf/syncular_bg.wasm +0 -0
  190. package/dist/wasm-perf/syncular_bg.wasm.d.ts +0 -97
  191. package/dist/wasm-runtime.d.ts +0 -23
  192. package/dist/wasm-runtime.d.ts.map +0 -1
  193. package/dist/wasm-runtime.js +0 -69
  194. package/dist/wasm-runtime.js.map +0 -1
  195. package/dist/worker-client.d.ts +0 -123
  196. package/dist/worker-client.d.ts.map +0 -1
  197. package/dist/worker-client.js +0 -1735
  198. package/dist/worker-client.js.map +0 -1
  199. package/dist/worker-entry.d.ts.map +0 -1
  200. package/dist/worker-entry.js.map +0 -1
  201. package/dist/worker-protocol.d.ts.map +0 -1
  202. package/dist/worker-protocol.js.map +0 -1
  203. package/dist/worker-realtime.d.ts +0 -39
  204. package/dist/worker-realtime.d.ts.map +0 -1
  205. package/dist/worker-realtime.js +0 -677
  206. package/dist/worker-realtime.js.map +0 -1
  207. package/src/auth-leases.ts +0 -251
  208. package/src/blob-limits.ts +0 -98
  209. package/src/bridge-client.ts +0 -512
  210. package/src/client-config.ts +0 -29
  211. package/src/command-history.ts +0 -623
  212. package/src/console-diagnostics.ts +0 -617
  213. package/src/crdt-yjs/index.ts +0 -3
  214. package/src/crdt-yjs/webview-host-facade.ts +0 -477
  215. package/src/crdt-yjs/yjs-document-field-adapter.ts +0 -733
  216. package/src/crdt-yjs/yjs-prosemirror-bridge.ts +0 -272
  217. package/src/diagnostics.ts +0 -116
  218. package/src/generated-bridge.ts +0 -741
  219. package/src/mutations.ts +0 -168
  220. package/src/network.ts +0 -32
  221. package/src/react/index.ts +0 -1036
  222. package/src/react-native/index.ts +0 -152
  223. package/src/runtime-contract.ts +0 -48
  224. package/src/rust-client.ts +0 -1491
  225. package/src/sentry.ts +0 -215
  226. package/src/sql-safety.ts +0 -61
  227. package/src/tauri/index.ts +0 -211
  228. package/src/types.ts +0 -1397
  229. package/src/wasm-bindings/generated-wasm-bindings.d.ts +0 -70
  230. package/src/wasm-bindings/runtime-contract.ts +0 -158
  231. package/src/wasm-runtime.ts +0 -145
  232. package/src/worker-client.ts +0 -2289
  233. package/src/worker-realtime.ts +0 -843
@@ -1,677 +0,0 @@
1
- import { createSyncularSyncAttempt, syncularDiagnosticAttemptFields, } from './diagnostics.js';
2
- import { createSyncularWorkerErrorPayload, SYNCULAR_WORKER_PROTOCOL_VERSION, } from './worker-protocol.js';
3
- export class SyncularWorkerRealtimeController {
4
- controllerOptions;
5
- #socket;
6
- #state = 'disconnected';
7
- #options;
8
- #reconnectTimer;
9
- #reconnectCatchupTimer;
10
- #heartbeatTimer;
11
- #reconnectAttempts = 0;
12
- #stopped = true;
13
- #syncInFlight;
14
- #syncAgainMessage;
15
- #syncJitterTimers = new Set();
16
- #setTimeout;
17
- #clearTimeout;
18
- #random;
19
- constructor(controllerOptions) {
20
- this.controllerOptions = controllerOptions;
21
- this.#setTimeout =
22
- controllerOptions.setTimeout ??
23
- globalThis.setTimeout.bind(globalThis);
24
- this.#clearTimeout =
25
- controllerOptions.clearTimeout ??
26
- globalThis.clearTimeout.bind(globalThis);
27
- this.#random = controllerOptions.random ?? Math.random;
28
- }
29
- start(options) {
30
- if (!this.controllerOptions.getConfig()) {
31
- throw createSyncularWorkerErrorPayload('worker.not_open', 'Syncular worker client is not open');
32
- }
33
- this.stop();
34
- this.#options = options;
35
- this.#stopped = false;
36
- this.#reconnectAttempts = 0;
37
- this.#connect();
38
- }
39
- stop() {
40
- this.#stopped = true;
41
- this.#syncAgainMessage = undefined;
42
- this.#syncInFlight = undefined;
43
- this.#clearSyncJitterTimers();
44
- this.#clearReconnectCatchupTimer();
45
- this.#clearReconnectTimer();
46
- this.#clearHeartbeatTimer();
47
- this.#closeSocket();
48
- this.#setState('disconnected');
49
- }
50
- sendPresence(action, scopeKey, metadata) {
51
- const socket = this.#socket;
52
- if (!socket || this.#state !== 'connected') {
53
- this.#diagnostic({
54
- level: 'warn',
55
- code: 'realtime.presence_not_connected',
56
- message: 'Syncular realtime presence send skipped while disconnected',
57
- details: { action, scopeKey },
58
- });
59
- return;
60
- }
61
- socket.send(JSON.stringify({
62
- type: 'presence',
63
- action,
64
- scopeKey,
65
- ...(metadata === undefined ? {} : { metadata }),
66
- }));
67
- }
68
- #connect() {
69
- const config = this.controllerOptions.getConfig();
70
- const options = this.#options;
71
- if (!config || !options || this.#stopped)
72
- return;
73
- this.#clearReconnectTimer();
74
- this.#clearHeartbeatTimer();
75
- this.#setState('connecting');
76
- let socket;
77
- try {
78
- socket = this.controllerOptions.createWebSocket(resolveSyncularRealtimeUrl(config, options, this.controllerOptions.getLocationOrigin()));
79
- }
80
- catch {
81
- this.#diagnostic({
82
- level: 'warn',
83
- code: 'realtime.connect_failed',
84
- message: 'Syncular realtime websocket creation failed',
85
- details: { attempt: this.#reconnectAttempts + 1 },
86
- });
87
- this.#scheduleReconnect();
88
- return;
89
- }
90
- this.#socket = socket;
91
- socket.onopen = () => {
92
- if (socket !== this.#socket)
93
- return;
94
- this.#reconnectAttempts = 0;
95
- this.#setState('connected');
96
- this.#resetHeartbeatTimer();
97
- };
98
- socket.onmessage = (event) => {
99
- if (socket !== this.#socket)
100
- return;
101
- this.#resetHeartbeatTimer();
102
- void this.#handleMessage(socket, event.data);
103
- };
104
- socket.onerror = () => {
105
- if (socket !== this.#socket)
106
- return;
107
- this.#diagnostic({
108
- level: 'warn',
109
- code: 'realtime.socket_error',
110
- message: 'Syncular realtime websocket reported an error',
111
- });
112
- this.#reconnect();
113
- };
114
- socket.onclose = () => {
115
- if (socket !== this.#socket)
116
- return;
117
- this.#reconnect();
118
- };
119
- }
120
- async #handleMessage(socket, data) {
121
- const bytes = await readRealtimeMessageBytes(data);
122
- if (socket !== this.#socket)
123
- return;
124
- if (bytes && isSyncPackBytes(bytes)) {
125
- this.#diagnostic({
126
- level: 'debug',
127
- code: 'realtime.sync_wakeup',
128
- message: 'Syncular realtime binary sync-pack received',
129
- details: {
130
- bytes: bytes.byteLength,
131
- },
132
- });
133
- this.#clearReconnectCatchupTimer();
134
- this.#scheduleSync({ syncPackBytes: bytes });
135
- return;
136
- }
137
- const text = bytes ? new TextDecoder().decode(bytes) : data;
138
- if (text == null)
139
- return;
140
- if (typeof text !== 'string')
141
- return;
142
- let message;
143
- try {
144
- message = JSON.parse(text);
145
- }
146
- catch {
147
- return;
148
- }
149
- const helloMessage = readSyncularRealtimeHelloMessage(message);
150
- if (helloMessage) {
151
- this.#diagnostic({
152
- level: 'debug',
153
- code: 'realtime.hello',
154
- message: 'Syncular realtime session accepted',
155
- details: { ...helloMessage },
156
- });
157
- this.#scheduleHelloCatchup(helloMessage);
158
- return;
159
- }
160
- const presenceMessage = readSyncularRealtimePresenceMessage(message);
161
- if (presenceMessage) {
162
- this.controllerOptions.postEvent({
163
- protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
164
- type: 'presenceEvent',
165
- ...presenceMessage,
166
- });
167
- return;
168
- }
169
- const syncMessage = readSyncularRealtimeSyncMessage(message);
170
- if (syncMessage) {
171
- this.#diagnostic({
172
- level: 'debug',
173
- code: 'realtime.sync_wakeup',
174
- message: 'Syncular realtime sync wakeup received',
175
- details: {
176
- reason: syncMessage.reason ?? null,
177
- requiresPull: syncMessage.requiresPull === true,
178
- droppedCount: syncMessage.droppedCount ?? 0,
179
- payloadBytes: syncMessage.payloadBytes ?? null,
180
- },
181
- });
182
- this.#clearReconnectCatchupTimer();
183
- this.#scheduleRealtimeWakeupSync(syncMessage);
184
- }
185
- }
186
- #reconnect() {
187
- this.#clearReconnectCatchupTimer();
188
- this.#clearHeartbeatTimer();
189
- this.#closeSocket();
190
- this.#setState('disconnected');
191
- this.#scheduleReconnect();
192
- }
193
- #scheduleReconnect() {
194
- if (this.#stopped)
195
- return;
196
- this.#clearReconnectTimer();
197
- const initial = this.#options?.initialReconnectDelayMs ?? 1_000;
198
- const max = this.#options?.maxReconnectDelayMs ?? 30_000;
199
- const factor = this.#options?.reconnectBackoffFactor ?? 2;
200
- const baseDelay = Math.min(initial * factor ** this.#reconnectAttempts, max);
201
- const jitterRatio = Math.max(0, this.#options?.reconnectJitterRatio ?? 0);
202
- const jitter = baseDelay * jitterRatio * this.#random();
203
- const delay = Math.min(baseDelay + jitter, max);
204
- this.#diagnostic({
205
- level: 'info',
206
- code: 'realtime.reconnect_scheduled',
207
- message: 'Syncular realtime reconnect scheduled',
208
- details: {
209
- attempt: this.#reconnectAttempts + 1,
210
- baseDelayMs: baseDelay,
211
- jitterMs: jitter,
212
- delayMs: delay,
213
- },
214
- });
215
- this.#reconnectAttempts += 1;
216
- this.#reconnectTimer = this.#setTimeout(() => this.#connect(), delay);
217
- }
218
- #scheduleHelloCatchup(message) {
219
- this.#clearReconnectCatchupTimer();
220
- if (!message.requiresSync)
221
- return;
222
- const maxJitter = Math.max(0, this.#options?.pullRecoveryJitterMs ?? 0);
223
- const delay = maxJitter > 0 ? maxJitter * this.#random() : 10;
224
- const cursor = Number.isFinite(message.latestCursor)
225
- ? message.latestCursor
226
- : message.cursor;
227
- const syncMessage = {
228
- reason: 'reconnect-catchup',
229
- requiresPull: true,
230
- ...(Number.isFinite(cursor) ? { cursor } : {}),
231
- };
232
- this.#diagnostic({
233
- level: 'debug',
234
- code: 'realtime.reconnect_catchup_scheduled',
235
- message: 'Syncular realtime reconnect catch-up scheduled from hello',
236
- details: {
237
- cursor: syncMessage.cursor ?? null,
238
- delayMs: delay,
239
- maxJitterMs: maxJitter,
240
- },
241
- });
242
- this.#reconnectCatchupTimer = this.#setTimeout(() => {
243
- this.#reconnectCatchupTimer = undefined;
244
- this.#scheduleSync(syncMessage);
245
- }, delay);
246
- }
247
- #scheduleSync(message) {
248
- if (this.#stopped)
249
- return;
250
- if (this.#syncInFlight) {
251
- this.#syncAgainMessage = mergeRealtimeSyncMessages(this.#syncAgainMessage, message);
252
- return;
253
- }
254
- this.#syncInFlight = this.#runSync(message);
255
- }
256
- #scheduleRealtimeWakeupSync(message) {
257
- const maxJitter = Math.max(0, this.#options?.pullRecoveryJitterMs ?? 0);
258
- if (maxJitter <= 0 || !realtimeMessageRequiresPull(message)) {
259
- this.#scheduleSync(message);
260
- return;
261
- }
262
- const delay = maxJitter * this.#random();
263
- this.#diagnostic({
264
- level: 'debug',
265
- code: 'realtime.pull_recovery_jitter_scheduled',
266
- message: 'Syncular realtime HTTP pull recovery delayed by jitter',
267
- details: {
268
- delayMs: delay,
269
- maxJitterMs: maxJitter,
270
- reason: message.reason ?? null,
271
- cursor: message.cursor ?? null,
272
- },
273
- });
274
- const timer = this.#setTimeout(() => {
275
- this.#syncJitterTimers.delete(timer);
276
- this.#scheduleSync(message);
277
- }, delay);
278
- this.#syncJitterTimers.add(timer);
279
- }
280
- async #runSync(message) {
281
- const socket = this.#socket;
282
- let result;
283
- try {
284
- result = await this.#syncForMessage(message);
285
- if (this.#stopped)
286
- return;
287
- this.#ackRealtimeCursor(socket, message, result);
288
- this.controllerOptions.postEvent({
289
- protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
290
- type: 'bootstrapChanged',
291
- bootstrap: result.bootstrap,
292
- });
293
- if (result.changedTables.length > 0 || result.changedRows.length > 0) {
294
- this.controllerOptions.postEvent({
295
- protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
296
- type: 'rowsChanged',
297
- source: 'remotePull',
298
- changedTables: result.changedTables,
299
- changedRows: result.changedRows,
300
- changedRowsTruncated: result.changedRowsTruncated,
301
- });
302
- }
303
- const events = this.controllerOptions
304
- .getClient()
305
- .drainLiveQueryEvents();
306
- if (events.length > 0) {
307
- this.controllerOptions.postEvent({
308
- protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
309
- type: 'liveQueryEvents',
310
- events,
311
- });
312
- }
313
- }
314
- catch {
315
- this.#diagnostic({
316
- level: 'warn',
317
- code: 'realtime.sync_pull_failed',
318
- message: 'Syncular realtime-triggered pull failed',
319
- });
320
- // Realtime is best-effort. Explicit sync methods still surface errors.
321
- }
322
- finally {
323
- this.#syncInFlight = undefined;
324
- const nextMessage = this.#syncAgainMessage;
325
- this.#syncAgainMessage = undefined;
326
- if (nextMessage &&
327
- !this.#stopped &&
328
- !syncResultCoversRealtimeMessage(result, nextMessage)) {
329
- this.#scheduleSync(nextMessage);
330
- }
331
- }
332
- }
333
- #ackRealtimeCursor(socket, message, result) {
334
- if (!socket || socket !== this.#socket)
335
- return;
336
- const subscriptionCursor = result.subscriptions.reduce((cursor, subscription) => Number.isFinite(subscription.nextCursor)
337
- ? Math.max(cursor, subscription.nextCursor)
338
- : cursor, -1);
339
- const messageCursor = Number.isFinite(message?.cursor)
340
- ? message.cursor
341
- : -1;
342
- const cursor = Math.max(subscriptionCursor, messageCursor);
343
- if (!Number.isSafeInteger(cursor) || cursor < 0)
344
- return;
345
- try {
346
- socket.send(JSON.stringify({ type: 'ack', cursor }));
347
- this.#diagnostic({
348
- level: 'debug',
349
- code: 'realtime.ack_sent',
350
- message: 'Syncular realtime cursor ack sent',
351
- details: { cursor },
352
- });
353
- }
354
- catch {
355
- this.#diagnostic({
356
- level: 'warn',
357
- code: 'realtime.ack_failed',
358
- message: 'Syncular realtime cursor ack failed',
359
- details: { cursor },
360
- });
361
- }
362
- }
363
- async #syncForMessage(message) {
364
- const client = this.controllerOptions.getClient();
365
- if (message && realtimeMessageRequiresPull(message)) {
366
- const syncAttempt = createSyncularSyncAttempt();
367
- this.#diagnostic({
368
- level: 'debug',
369
- code: 'realtime.pull_required',
370
- message: 'Syncular realtime event requires HTTP pull recovery',
371
- ...syncularDiagnosticAttemptFields(syncAttempt),
372
- details: {
373
- cursor: message.cursor ?? null,
374
- reason: message.reason ?? null,
375
- droppedCount: message.droppedCount ?? 0,
376
- payloadBytes: message.payloadBytes ?? null,
377
- },
378
- });
379
- return client.syncPull({ syncAttempt });
380
- }
381
- if (message?.syncPackBytes &&
382
- typeof client.applyRealtimeSyncPack === 'function') {
383
- try {
384
- const result = await client.applyRealtimeSyncPack(message.syncPackBytes);
385
- this.#diagnostic({
386
- level: 'debug',
387
- code: 'realtime.binary_applied',
388
- message: 'Syncular realtime binary sync-pack applied',
389
- details: {
390
- bytes: message.syncPackBytes.byteLength,
391
- totalMs: result.timings.totalMs,
392
- syncPackDecodeMs: result.timings.syncPackDecodeMs,
393
- pullTransformMs: result.timings.pullTransformMs,
394
- integrityVerifyMs: result.timings.integrityVerifyMs,
395
- pullApplyMs: result.timings.pullApplyMs,
396
- commitApplyMs: result.timings.commitApplyMs,
397
- subscriptionStateMs: result.timings.subscriptionStateMs,
398
- notifyMs: result.timings.notifyMs,
399
- changedRows: result.changedRows.length,
400
- changedRowsTruncated: result.changedRowsTruncated,
401
- },
402
- });
403
- return result;
404
- }
405
- catch {
406
- this.#diagnostic({
407
- level: 'warn',
408
- code: 'realtime.binary_apply_failed',
409
- message: 'Syncular realtime binary sync-pack apply failed; using HTTP pull recovery',
410
- details: {
411
- bytes: message.syncPackBytes.byteLength,
412
- },
413
- });
414
- }
415
- }
416
- const syncAttempt = createSyncularSyncAttempt();
417
- this.#diagnostic({
418
- level: 'debug',
419
- code: 'realtime.pull_required',
420
- message: 'Syncular realtime event requires HTTP pull recovery',
421
- ...syncularDiagnosticAttemptFields(syncAttempt),
422
- details: {
423
- cursor: message?.cursor ?? null,
424
- reason: message?.syncPackBytes ? 'binary-apply-failed' : null,
425
- droppedCount: message?.droppedCount ?? 0,
426
- payloadBytes: message?.payloadBytes ?? null,
427
- },
428
- });
429
- return client.syncPull({ syncAttempt });
430
- }
431
- #clearReconnectTimer() {
432
- if (!this.#reconnectTimer)
433
- return;
434
- this.#clearTimeout(this.#reconnectTimer);
435
- this.#reconnectTimer = undefined;
436
- }
437
- #clearReconnectCatchupTimer() {
438
- if (!this.#reconnectCatchupTimer)
439
- return;
440
- this.#clearTimeout(this.#reconnectCatchupTimer);
441
- this.#reconnectCatchupTimer = undefined;
442
- }
443
- #clearHeartbeatTimer() {
444
- if (!this.#heartbeatTimer)
445
- return;
446
- this.#clearTimeout(this.#heartbeatTimer);
447
- this.#heartbeatTimer = undefined;
448
- }
449
- #clearSyncJitterTimers() {
450
- for (const timer of this.#syncJitterTimers) {
451
- this.#clearTimeout(timer);
452
- }
453
- this.#syncJitterTimers.clear();
454
- }
455
- #resetHeartbeatTimer() {
456
- this.#clearHeartbeatTimer();
457
- const heartbeatTimeoutMs = this.#options?.heartbeatTimeoutMs ?? 60_000;
458
- if (heartbeatTimeoutMs <= 0)
459
- return;
460
- this.#heartbeatTimer = this.#setTimeout(() => this.#reconnect(), heartbeatTimeoutMs);
461
- }
462
- #closeSocket() {
463
- if (!this.#socket)
464
- return;
465
- const socket = this.#socket;
466
- this.#socket = undefined;
467
- socket.onopen = null;
468
- socket.onmessage = null;
469
- socket.onerror = null;
470
- socket.onclose = null;
471
- try {
472
- socket.close();
473
- }
474
- catch {
475
- // best effort
476
- }
477
- }
478
- #setState(state) {
479
- if (this.#state === state)
480
- return;
481
- this.#state = state;
482
- this.controllerOptions.postEvent({
483
- protocolVersion: SYNCULAR_WORKER_PROTOCOL_VERSION,
484
- type: 'realtimeState',
485
- state,
486
- });
487
- }
488
- #diagnostic(event) {
489
- this.controllerOptions.postDiagnostic?.({
490
- ...event,
491
- source: 'realtime',
492
- });
493
- }
494
- }
495
- function realtimeMessageRequiresPull(message) {
496
- return message.requiresPull === true || (message.droppedCount ?? 0) > 0;
497
- }
498
- function mergeRealtimeSyncMessages(current, next) {
499
- if (!current)
500
- return next ?? {};
501
- if (!next)
502
- return current;
503
- const cursor = current.cursor === undefined
504
- ? next.cursor
505
- : next.cursor === undefined
506
- ? current.cursor
507
- : Math.max(current.cursor, next.cursor);
508
- return {
509
- cursor,
510
- reason: next.reason ?? current.reason,
511
- requiresPull: current.requiresPull === true || next.requiresPull === true,
512
- droppedCount: Math.max(current.droppedCount ?? 0, next.droppedCount ?? 0),
513
- payloadBytes: next.payloadBytes ?? current.payloadBytes,
514
- syncPackBytes: next.syncPackBytes ?? current.syncPackBytes,
515
- };
516
- }
517
- function syncResultCoversRealtimeMessage(result, message) {
518
- if (!result || message.cursor === undefined)
519
- return false;
520
- return syncResultCursor(result) >= message.cursor;
521
- }
522
- function syncResultCursor(result) {
523
- return result.subscriptions.reduce((cursor, subscription) => Number.isFinite(subscription.nextCursor)
524
- ? Math.max(cursor, subscription.nextCursor)
525
- : cursor, -1);
526
- }
527
- export function resolveSyncularRealtimeUrl(config, options, locationOrigin) {
528
- const url = new URL(options.wsUrl ?? config.baseUrl, locationOrigin);
529
- if (!options.wsUrl) {
530
- url.pathname = `${url.pathname.replace(/\/$/, '')}/realtime`;
531
- }
532
- if (url.protocol === 'http:')
533
- url.protocol = 'ws:';
534
- if (url.protocol === 'https:')
535
- url.protocol = 'wss:';
536
- url.searchParams.set('clientId', config.clientId);
537
- url.searchParams.set('transportPath', 'direct');
538
- url.searchParams.set('syncPackEncoding', 'binary-sync-pack-v1');
539
- for (const [key, value] of Object.entries(options.params ?? {})) {
540
- if (value)
541
- url.searchParams.set(key, value);
542
- }
543
- return url.toString();
544
- }
545
- export function isSyncularRealtimeSyncMessage(value) {
546
- return readSyncularRealtimeSyncMessage(value) !== null;
547
- }
548
- function readSyncularRealtimeHelloMessage(value) {
549
- if (!value || typeof value !== 'object')
550
- return null;
551
- if (value.event !== 'hello')
552
- return null;
553
- const data = value.data;
554
- if (!data || typeof data !== 'object')
555
- return {};
556
- const record = data;
557
- return {
558
- protocolVersion: typeof record.protocolVersion === 'number'
559
- ? record.protocolVersion
560
- : undefined,
561
- sessionId: typeof record.sessionId === 'string' ? record.sessionId : undefined,
562
- shardKey: typeof record.shardKey === 'string' ? record.shardKey : undefined,
563
- cursor: typeof record.cursor === 'number' ? record.cursor : undefined,
564
- latestCursor: typeof record.latestCursor === 'number' ? record.latestCursor : undefined,
565
- scopeCount: typeof record.scopeCount === 'number' ? record.scopeCount : undefined,
566
- requiresSync: typeof record.requiresSync === 'boolean'
567
- ? record.requiresSync
568
- : undefined,
569
- syncPackEncoding: typeof record.syncPackEncoding === 'string'
570
- ? record.syncPackEncoding
571
- : record.syncPackEncoding === null
572
- ? null
573
- : undefined,
574
- };
575
- }
576
- function readSyncularRealtimeSyncMessage(value) {
577
- if (!value || typeof value !== 'object')
578
- return null;
579
- if (value.event !== 'sync')
580
- return null;
581
- const data = value.data;
582
- if (!data || typeof data !== 'object')
583
- return {};
584
- const record = data;
585
- const cursor = typeof record.cursor === 'number' ? record.cursor : undefined;
586
- const reason = typeof record.reason === 'string' ? record.reason : null;
587
- const requiresPull = typeof record.requiresPull === 'boolean' ? record.requiresPull : undefined;
588
- const droppedCount = typeof record.droppedCount === 'number' &&
589
- Number.isSafeInteger(record.droppedCount)
590
- ? record.droppedCount
591
- : undefined;
592
- const payloadBytes = typeof record.payloadBytes === 'number' &&
593
- Number.isSafeInteger(record.payloadBytes)
594
- ? record.payloadBytes
595
- : undefined;
596
- return {
597
- cursor,
598
- reason,
599
- requiresPull,
600
- droppedCount,
601
- payloadBytes,
602
- };
603
- }
604
- function readSyncularRealtimePresenceMessage(value) {
605
- if (!value || typeof value !== 'object')
606
- return null;
607
- if (value.event !== 'presence')
608
- return null;
609
- const data = value.data;
610
- if (!data || typeof data !== 'object')
611
- return null;
612
- const presence = data.presence;
613
- if (!presence || typeof presence !== 'object')
614
- return null;
615
- const record = presence;
616
- const action = record.action;
617
- if (action !== 'join' &&
618
- action !== 'leave' &&
619
- action !== 'update' &&
620
- action !== 'snapshot') {
621
- return null;
622
- }
623
- if (typeof record.scopeKey !== 'string' || !record.scopeKey)
624
- return null;
625
- return {
626
- action,
627
- scopeKey: record.scopeKey,
628
- clientId: typeof record.clientId === 'string' ? record.clientId : undefined,
629
- actorId: typeof record.actorId === 'string' ? record.actorId : undefined,
630
- metadata: objectRecordOrUndefined(record.metadata),
631
- entries: Array.isArray(record.entries)
632
- ? record.entries.flatMap(readPresenceEntry)
633
- : undefined,
634
- };
635
- }
636
- function readPresenceEntry(value) {
637
- if (!value || typeof value !== 'object')
638
- return [];
639
- const record = value;
640
- if (typeof record.clientId !== 'string')
641
- return [];
642
- if (typeof record.actorId !== 'string')
643
- return [];
644
- return [
645
- {
646
- clientId: record.clientId,
647
- actorId: record.actorId,
648
- joinedAt: typeof record.joinedAt === 'number' ? record.joinedAt : Date.now(),
649
- metadata: objectRecordOrUndefined(record.metadata),
650
- },
651
- ];
652
- }
653
- function objectRecordOrUndefined(value) {
654
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
655
- return undefined;
656
- }
657
- return { ...value };
658
- }
659
- async function readRealtimeMessageBytes(data) {
660
- if (data instanceof ArrayBuffer)
661
- return new Uint8Array(data);
662
- if (ArrayBuffer.isView(data)) {
663
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
664
- }
665
- if (typeof Blob !== 'undefined' && data instanceof Blob) {
666
- return new Uint8Array(await data.arrayBuffer());
667
- }
668
- return null;
669
- }
670
- function isSyncPackBytes(bytes) {
671
- return (bytes.byteLength >= 4 &&
672
- bytes[0] === 0x53 &&
673
- bytes[1] === 0x53 &&
674
- bytes[2] === 0x50 &&
675
- bytes[3] === 0x31);
676
- }
677
- //# sourceMappingURL=worker-realtime.js.map