@syncular/client 0.0.6-95 → 0.1.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 (356) hide show
  1. package/README.md +713 -11
  2. package/dist/auth-leases.d.ts +11 -0
  3. package/dist/auth-leases.d.ts.map +1 -0
  4. package/dist/auth-leases.js +140 -0
  5. package/dist/auth-leases.js.map +1 -0
  6. package/dist/blob-limits.d.ts +15 -0
  7. package/dist/blob-limits.d.ts.map +1 -0
  8. package/dist/blob-limits.js +66 -0
  9. package/dist/blob-limits.js.map +1 -0
  10. package/dist/bridge-client.d.ts +69 -0
  11. package/dist/bridge-client.d.ts.map +1 -0
  12. package/dist/bridge-client.js +282 -0
  13. package/dist/bridge-client.js.map +1 -0
  14. package/dist/client-config.d.ts +6 -0
  15. package/dist/client-config.d.ts.map +1 -0
  16. package/dist/client-config.js +18 -0
  17. package/dist/client-config.js.map +1 -0
  18. package/dist/client.d.ts +65 -397
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +150 -960
  21. package/dist/client.js.map +1 -1
  22. package/dist/command-history.d.ts +35 -0
  23. package/dist/command-history.d.ts.map +1 -0
  24. package/dist/command-history.js +378 -0
  25. package/dist/command-history.js.map +1 -0
  26. package/dist/console-diagnostics.d.ts +33 -0
  27. package/dist/console-diagnostics.d.ts.map +1 -0
  28. package/dist/console-diagnostics.js +496 -0
  29. package/dist/console-diagnostics.js.map +1 -0
  30. package/dist/crdt-yjs/index.d.ts +4 -0
  31. package/dist/crdt-yjs/index.d.ts.map +1 -0
  32. package/dist/crdt-yjs/index.js +4 -0
  33. package/dist/crdt-yjs/index.js.map +1 -0
  34. package/dist/crdt-yjs/webview-host-facade.d.ts +126 -0
  35. package/dist/crdt-yjs/webview-host-facade.d.ts.map +1 -0
  36. package/dist/crdt-yjs/webview-host-facade.js +284 -0
  37. package/dist/crdt-yjs/webview-host-facade.js.map +1 -0
  38. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts +153 -0
  39. package/dist/crdt-yjs/yjs-document-field-adapter.d.ts.map +1 -0
  40. package/dist/crdt-yjs/yjs-document-field-adapter.js +406 -0
  41. package/dist/crdt-yjs/yjs-document-field-adapter.js.map +1 -0
  42. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts +73 -0
  43. package/dist/crdt-yjs/yjs-prosemirror-bridge.d.ts.map +1 -0
  44. package/dist/crdt-yjs/yjs-prosemirror-bridge.js +169 -0
  45. package/dist/crdt-yjs/yjs-prosemirror-bridge.js.map +1 -0
  46. package/dist/database.d.ts +83 -0
  47. package/dist/database.d.ts.map +1 -0
  48. package/dist/database.js +916 -0
  49. package/dist/database.js.map +1 -0
  50. package/dist/diagnostics.d.ts +10 -0
  51. package/dist/diagnostics.d.ts.map +1 -0
  52. package/dist/diagnostics.js +83 -0
  53. package/dist/diagnostics.js.map +1 -0
  54. package/dist/errors.d.ts +24 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/errors.js +211 -0
  57. package/dist/errors.js.map +1 -0
  58. package/dist/generated-bridge.d.ts +365 -0
  59. package/dist/generated-bridge.d.ts.map +1 -0
  60. package/dist/generated-bridge.js +250 -0
  61. package/dist/generated-bridge.js.map +1 -0
  62. package/dist/index.d.ts +13 -23
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +14 -25
  65. package/dist/index.js.map +1 -1
  66. package/dist/mutations.d.ts +4 -72
  67. package/dist/mutations.d.ts.map +1 -1
  68. package/dist/mutations.js +2 -568
  69. package/dist/mutations.js.map +1 -1
  70. package/dist/network.d.ts +3 -0
  71. package/dist/network.d.ts.map +1 -0
  72. package/dist/network.js +17 -0
  73. package/dist/network.js.map +1 -0
  74. package/dist/react/index.d.ts +169 -0
  75. package/dist/react/index.d.ts.map +1 -0
  76. package/dist/react/index.js +628 -0
  77. package/dist/react/index.js.map +1 -0
  78. package/dist/react-native/index.d.ts +35 -0
  79. package/dist/react-native/index.d.ts.map +1 -0
  80. package/dist/react-native/index.js +49 -0
  81. package/dist/react-native/index.js.map +1 -0
  82. package/dist/runtime-contract.d.ts +13 -0
  83. package/dist/runtime-contract.d.ts.map +1 -0
  84. package/dist/runtime-contract.js +24 -0
  85. package/dist/runtime-contract.js.map +1 -0
  86. package/dist/rust-client.d.ts +106 -0
  87. package/dist/rust-client.d.ts.map +1 -0
  88. package/dist/rust-client.js +936 -0
  89. package/dist/rust-client.js.map +1 -0
  90. package/dist/sentry.d.ts +35 -0
  91. package/dist/sentry.d.ts.map +1 -0
  92. package/dist/sentry.js +155 -0
  93. package/dist/sentry.js.map +1 -0
  94. package/dist/sql-safety.d.ts +3 -0
  95. package/dist/sql-safety.d.ts.map +1 -0
  96. package/dist/sql-safety.js +62 -0
  97. package/dist/sql-safety.js.map +1 -0
  98. package/dist/syncular-runtime-artifacts.json +61 -0
  99. package/dist/tauri/index.d.ts +35 -0
  100. package/dist/tauri/index.d.ts.map +1 -0
  101. package/dist/tauri/index.js +114 -0
  102. package/dist/tauri/index.js.map +1 -0
  103. package/dist/types.d.ts +1059 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +2 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/wasm/.syncular-wasm-profile +1 -0
  108. package/dist/wasm/syncular-runtime-artifact.json +21 -0
  109. package/dist/wasm/syncular.d.ts +207 -0
  110. package/dist/wasm/syncular.js +2341 -0
  111. package/dist/wasm/syncular_bg.wasm +0 -0
  112. package/dist/wasm/syncular_bg.wasm.d.ts +97 -0
  113. package/dist/wasm-bindings/runtime-contract.d.ts +22 -0
  114. package/dist/wasm-bindings/runtime-contract.d.ts.map +1 -0
  115. package/dist/wasm-bindings/runtime-contract.js +112 -0
  116. package/dist/wasm-bindings/runtime-contract.js.map +1 -0
  117. package/dist/wasm-core/.syncular-wasm-profile +1 -0
  118. package/dist/wasm-core/syncular-runtime-artifact.json +17 -0
  119. package/dist/wasm-core/syncular.d.ts +162 -0
  120. package/dist/wasm-core/syncular.js +1847 -0
  121. package/dist/wasm-core/syncular_bg.wasm +0 -0
  122. package/dist/wasm-core/syncular_bg.wasm.d.ts +77 -0
  123. package/dist/wasm-perf/.syncular-wasm-profile +1 -0
  124. package/dist/wasm-perf/syncular-runtime-artifact.json +21 -0
  125. package/dist/wasm-perf/syncular.d.ts +207 -0
  126. package/dist/wasm-perf/syncular.js +2341 -0
  127. package/dist/wasm-perf/syncular_bg.wasm +0 -0
  128. package/dist/wasm-perf/syncular_bg.wasm.d.ts +97 -0
  129. package/dist/wasm-runtime.d.ts +18 -0
  130. package/dist/wasm-runtime.d.ts.map +1 -0
  131. package/dist/wasm-runtime.js +28 -0
  132. package/dist/wasm-runtime.js.map +1 -0
  133. package/dist/worker-client.d.ts +123 -0
  134. package/dist/worker-client.d.ts.map +1 -0
  135. package/dist/worker-client.js +1686 -0
  136. package/dist/worker-client.js.map +1 -0
  137. package/dist/worker-entry.d.ts +2 -0
  138. package/dist/worker-entry.d.ts.map +1 -0
  139. package/dist/worker-entry.js +479 -0
  140. package/dist/worker-entry.js.map +1 -0
  141. package/dist/worker-protocol.d.ts +64 -0
  142. package/dist/worker-protocol.d.ts.map +1 -0
  143. package/dist/worker-protocol.js +16 -0
  144. package/dist/worker-protocol.js.map +1 -0
  145. package/dist/worker-realtime.d.ts +39 -0
  146. package/dist/worker-realtime.d.ts.map +1 -0
  147. package/dist/worker-realtime.js +677 -0
  148. package/dist/worker-realtime.js.map +1 -0
  149. package/package.json +124 -17
  150. package/src/auth-leases.ts +251 -0
  151. package/src/blob-limits.ts +98 -0
  152. package/src/bridge-client.ts +512 -0
  153. package/src/client-config.ts +29 -0
  154. package/src/client.ts +256 -1414
  155. package/src/command-history.ts +623 -0
  156. package/src/console-diagnostics.ts +617 -0
  157. package/src/crdt-yjs/index.ts +3 -0
  158. package/src/crdt-yjs/webview-host-facade.ts +477 -0
  159. package/src/crdt-yjs/yjs-document-field-adapter.ts +733 -0
  160. package/src/crdt-yjs/yjs-prosemirror-bridge.ts +272 -0
  161. package/src/database.ts +1342 -0
  162. package/src/diagnostics.ts +116 -0
  163. package/src/errors.ts +269 -0
  164. package/src/generated-bridge.ts +741 -0
  165. package/src/index.ts +21 -25
  166. package/src/mutations.ts +12 -821
  167. package/src/network.ts +32 -0
  168. package/src/react/index.ts +1036 -0
  169. package/src/react-native/index.ts +152 -0
  170. package/src/runtime-contract.ts +48 -0
  171. package/src/rust-client.ts +1488 -0
  172. package/src/sentry.ts +215 -0
  173. package/src/sql-safety.ts +61 -0
  174. package/src/tauri/index.ts +211 -0
  175. package/src/types.ts +1356 -0
  176. package/src/wasm-bindings/generated-wasm-bindings.d.ts +70 -0
  177. package/src/wasm-bindings/runtime-contract.ts +158 -0
  178. package/src/wasm-runtime.ts +76 -0
  179. package/src/worker-client.ts +2215 -0
  180. package/src/worker-entry.ts +584 -0
  181. package/src/worker-protocol.ts +109 -0
  182. package/src/worker-realtime.ts +843 -0
  183. package/dist/blobs/index.d.ts +0 -7
  184. package/dist/blobs/index.d.ts.map +0 -1
  185. package/dist/blobs/index.js +0 -7
  186. package/dist/blobs/index.js.map +0 -1
  187. package/dist/blobs/manager.d.ts +0 -345
  188. package/dist/blobs/manager.d.ts.map +0 -1
  189. package/dist/blobs/manager.js +0 -749
  190. package/dist/blobs/manager.js.map +0 -1
  191. package/dist/blobs/migrate.d.ts +0 -14
  192. package/dist/blobs/migrate.d.ts.map +0 -1
  193. package/dist/blobs/migrate.js +0 -59
  194. package/dist/blobs/migrate.js.map +0 -1
  195. package/dist/blobs/types.d.ts +0 -62
  196. package/dist/blobs/types.d.ts.map +0 -1
  197. package/dist/blobs/types.js +0 -5
  198. package/dist/blobs/types.js.map +0 -1
  199. package/dist/conflicts.d.ts +0 -31
  200. package/dist/conflicts.d.ts.map +0 -1
  201. package/dist/conflicts.js +0 -112
  202. package/dist/conflicts.js.map +0 -1
  203. package/dist/create-client.d.ts +0 -118
  204. package/dist/create-client.d.ts.map +0 -1
  205. package/dist/create-client.js +0 -178
  206. package/dist/create-client.js.map +0 -1
  207. package/dist/engine/SyncEngine.d.ts +0 -272
  208. package/dist/engine/SyncEngine.d.ts.map +0 -1
  209. package/dist/engine/SyncEngine.js +0 -1860
  210. package/dist/engine/SyncEngine.js.map +0 -1
  211. package/dist/engine/index.d.ts +0 -6
  212. package/dist/engine/index.d.ts.map +0 -1
  213. package/dist/engine/index.js +0 -6
  214. package/dist/engine/index.js.map +0 -1
  215. package/dist/engine/types.d.ts +0 -345
  216. package/dist/engine/types.d.ts.map +0 -1
  217. package/dist/engine/types.js +0 -7
  218. package/dist/engine/types.js.map +0 -1
  219. package/dist/handlers/collection.d.ts +0 -6
  220. package/dist/handlers/collection.d.ts.map +0 -1
  221. package/dist/handlers/collection.js +0 -21
  222. package/dist/handlers/collection.js.map +0 -1
  223. package/dist/handlers/create-handler.d.ts +0 -120
  224. package/dist/handlers/create-handler.d.ts.map +0 -1
  225. package/dist/handlers/create-handler.js +0 -145
  226. package/dist/handlers/create-handler.js.map +0 -1
  227. package/dist/handlers/types.d.ts +0 -83
  228. package/dist/handlers/types.d.ts.map +0 -1
  229. package/dist/handlers/types.js +0 -5
  230. package/dist/handlers/types.js.map +0 -1
  231. package/dist/migrate.d.ts +0 -19
  232. package/dist/migrate.d.ts.map +0 -1
  233. package/dist/migrate.js +0 -238
  234. package/dist/migrate.js.map +0 -1
  235. package/dist/outbox.d.ts +0 -112
  236. package/dist/outbox.d.ts.map +0 -1
  237. package/dist/outbox.js +0 -294
  238. package/dist/outbox.js.map +0 -1
  239. package/dist/plugins/incrementing-version.d.ts +0 -34
  240. package/dist/plugins/incrementing-version.d.ts.map +0 -1
  241. package/dist/plugins/incrementing-version.js +0 -83
  242. package/dist/plugins/incrementing-version.js.map +0 -1
  243. package/dist/plugins/index.d.ts +0 -3
  244. package/dist/plugins/index.d.ts.map +0 -1
  245. package/dist/plugins/index.js +0 -3
  246. package/dist/plugins/index.js.map +0 -1
  247. package/dist/plugins/types.d.ts +0 -49
  248. package/dist/plugins/types.d.ts.map +0 -1
  249. package/dist/plugins/types.js +0 -15
  250. package/dist/plugins/types.js.map +0 -1
  251. package/dist/proxy/connection.d.ts +0 -33
  252. package/dist/proxy/connection.d.ts.map +0 -1
  253. package/dist/proxy/connection.js +0 -153
  254. package/dist/proxy/connection.js.map +0 -1
  255. package/dist/proxy/dialect.d.ts +0 -46
  256. package/dist/proxy/dialect.d.ts.map +0 -1
  257. package/dist/proxy/dialect.js +0 -58
  258. package/dist/proxy/dialect.js.map +0 -1
  259. package/dist/proxy/driver.d.ts +0 -42
  260. package/dist/proxy/driver.d.ts.map +0 -1
  261. package/dist/proxy/driver.js +0 -78
  262. package/dist/proxy/driver.js.map +0 -1
  263. package/dist/proxy/index.d.ts +0 -10
  264. package/dist/proxy/index.d.ts.map +0 -1
  265. package/dist/proxy/index.js +0 -10
  266. package/dist/proxy/index.js.map +0 -1
  267. package/dist/proxy/mutations.d.ts +0 -9
  268. package/dist/proxy/mutations.d.ts.map +0 -1
  269. package/dist/proxy/mutations.js +0 -11
  270. package/dist/proxy/mutations.js.map +0 -1
  271. package/dist/pull-engine.d.ts +0 -48
  272. package/dist/pull-engine.d.ts.map +0 -1
  273. package/dist/pull-engine.js +0 -648
  274. package/dist/pull-engine.js.map +0 -1
  275. package/dist/push-engine.d.ts +0 -18
  276. package/dist/push-engine.d.ts.map +0 -1
  277. package/dist/push-engine.js +0 -167
  278. package/dist/push-engine.js.map +0 -1
  279. package/dist/query/FingerprintCollector.d.ts +0 -18
  280. package/dist/query/FingerprintCollector.d.ts.map +0 -1
  281. package/dist/query/FingerprintCollector.js +0 -28
  282. package/dist/query/FingerprintCollector.js.map +0 -1
  283. package/dist/query/QueryContext.d.ts +0 -33
  284. package/dist/query/QueryContext.d.ts.map +0 -1
  285. package/dist/query/QueryContext.js +0 -16
  286. package/dist/query/QueryContext.js.map +0 -1
  287. package/dist/query/fingerprint.d.ts +0 -61
  288. package/dist/query/fingerprint.d.ts.map +0 -1
  289. package/dist/query/fingerprint.js +0 -91
  290. package/dist/query/fingerprint.js.map +0 -1
  291. package/dist/query/index.d.ts +0 -7
  292. package/dist/query/index.d.ts.map +0 -1
  293. package/dist/query/index.js +0 -7
  294. package/dist/query/index.js.map +0 -1
  295. package/dist/query/tracked-select.d.ts +0 -18
  296. package/dist/query/tracked-select.d.ts.map +0 -1
  297. package/dist/query/tracked-select.js +0 -90
  298. package/dist/query/tracked-select.js.map +0 -1
  299. package/dist/schema.d.ts +0 -83
  300. package/dist/schema.d.ts.map +0 -1
  301. package/dist/schema.js +0 -7
  302. package/dist/schema.js.map +0 -1
  303. package/dist/subscription-state.d.ts +0 -46
  304. package/dist/subscription-state.d.ts.map +0 -1
  305. package/dist/subscription-state.js +0 -185
  306. package/dist/subscription-state.js.map +0 -1
  307. package/dist/sync-loop.d.ts +0 -34
  308. package/dist/sync-loop.d.ts.map +0 -1
  309. package/dist/sync-loop.js +0 -253
  310. package/dist/sync-loop.js.map +0 -1
  311. package/dist/sync.d.ts +0 -32
  312. package/dist/sync.d.ts.map +0 -1
  313. package/dist/sync.js +0 -55
  314. package/dist/sync.js.map +0 -1
  315. package/dist/utils/id.d.ts +0 -17
  316. package/dist/utils/id.d.ts.map +0 -1
  317. package/dist/utils/id.js +0 -46
  318. package/dist/utils/id.js.map +0 -1
  319. package/src/blobs/index.ts +0 -7
  320. package/src/blobs/manager.ts +0 -1027
  321. package/src/blobs/migrate.ts +0 -67
  322. package/src/blobs/types.ts +0 -84
  323. package/src/client.test.ts +0 -417
  324. package/src/conflicts.ts +0 -171
  325. package/src/create-client.test.ts +0 -83
  326. package/src/create-client.ts +0 -330
  327. package/src/engine/SyncEngine.test.ts +0 -254
  328. package/src/engine/SyncEngine.ts +0 -2375
  329. package/src/engine/index.ts +0 -6
  330. package/src/engine/types.ts +0 -419
  331. package/src/handlers/collection.ts +0 -36
  332. package/src/handlers/create-handler.ts +0 -330
  333. package/src/handlers/types.ts +0 -108
  334. package/src/migrate.ts +0 -318
  335. package/src/outbox.ts +0 -383
  336. package/src/plugins/incrementing-version.ts +0 -133
  337. package/src/plugins/index.ts +0 -2
  338. package/src/plugins/types.ts +0 -63
  339. package/src/proxy/connection.ts +0 -191
  340. package/src/proxy/dialect.ts +0 -76
  341. package/src/proxy/driver.ts +0 -126
  342. package/src/proxy/index.ts +0 -10
  343. package/src/proxy/mutations.ts +0 -18
  344. package/src/pull-engine.test.ts +0 -151
  345. package/src/pull-engine.ts +0 -855
  346. package/src/push-engine.ts +0 -216
  347. package/src/query/FingerprintCollector.ts +0 -29
  348. package/src/query/QueryContext.ts +0 -54
  349. package/src/query/fingerprint.ts +0 -109
  350. package/src/query/index.ts +0 -10
  351. package/src/query/tracked-select.ts +0 -139
  352. package/src/schema.ts +0 -94
  353. package/src/subscription-state.ts +0 -259
  354. package/src/sync-loop.ts +0 -379
  355. package/src/sync.ts +0 -170
  356. package/src/utils/id.ts +0 -55
@@ -0,0 +1,733 @@
1
+ import type {
2
+ SyncularChangedRow,
3
+ SyncularCrdtDocumentSnapshot,
4
+ SyncularCrdtFieldCompactionReceipt,
5
+ SyncularCrdtFieldDescriptor,
6
+ SyncularCrdtFieldMaterialization,
7
+ SyncularCrdtFieldRequest,
8
+ SyncularCrdtFieldWriteReceipt,
9
+ SyncularCrdtFieldYjsUpdateRequest,
10
+ SyncularCrdtUpdateLogEntry,
11
+ SyncularRowsChangedEvent,
12
+ SyncularYjsUpdateEnvelope,
13
+ } from '@syncular/client';
14
+
15
+ export interface SyncularCrdtFieldHost {
16
+ openCrdtField(
17
+ request: SyncularCrdtFieldRequest
18
+ ): Promise<SyncularCrdtFieldDescriptor>;
19
+ applyCrdtFieldYjsUpdate(
20
+ request: SyncularCrdtFieldYjsUpdateRequest
21
+ ): Promise<SyncularCrdtFieldWriteReceipt>;
22
+ enqueueCrdtFieldYjsUpdate?(
23
+ request: SyncularCrdtFieldYjsUpdateRequest
24
+ ): Promise<string>;
25
+ materializeCrdtField(
26
+ request: SyncularCrdtFieldRequest
27
+ ): Promise<SyncularCrdtFieldMaterialization>;
28
+ crdtDocumentSnapshot?(
29
+ request: SyncularCrdtFieldRequest
30
+ ): Promise<SyncularCrdtDocumentSnapshot>;
31
+ crdtUpdateLog?(
32
+ request: SyncularCrdtFieldRequest & { limit?: number }
33
+ ): Promise<SyncularCrdtUpdateLogEntry[]>;
34
+ snapshotCrdtFieldStateVector(
35
+ request: SyncularCrdtFieldRequest
36
+ ): Promise<{ stateVectorBase64: string }>;
37
+ compactCrdtField(request: {
38
+ table: string;
39
+ rowId: string;
40
+ field: string;
41
+ minUncheckpointedUpdates?: number;
42
+ }): Promise<SyncularCrdtFieldCompactionReceipt>;
43
+ }
44
+
45
+ export interface SyncularCrdtProjectionHost extends SyncularCrdtFieldHost {
46
+ addRowsChangedListener(
47
+ listener: (event: SyncularRowsChangedEvent) => void
48
+ ): () => void;
49
+ }
50
+
51
+ export interface YjsDocumentBinding {
52
+ subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;
53
+ applyRemoteUpdate?: (update: Uint8Array) => void;
54
+ replaceDocumentState?: (
55
+ state: Uint8Array,
56
+ receipt: YjsDocumentRestoreReceipt
57
+ ) => void;
58
+ replaceMaterializedValue?: (value: unknown) => void;
59
+ }
60
+
61
+ export interface YjsDocumentFieldAdapterOptions {
62
+ flushDelayMs?: number;
63
+ maxPendingUpdates?: number;
64
+ restoreOnStart?: boolean;
65
+ updateId?: () => string;
66
+ onBackpressure?: (event: YjsDocumentFieldBackpressureEvent) => void;
67
+ onFlushStart?: (event: YjsDocumentFieldFlushEvent) => void;
68
+ onFlushSuccess?: (event: YjsDocumentFieldFlushEvent) => void;
69
+ onFlushError?: (error: unknown) => void;
70
+ }
71
+
72
+ export interface YjsDocumentFieldBackpressureEvent {
73
+ pendingUpdates: number;
74
+ maxPendingUpdates: number;
75
+ }
76
+
77
+ export interface YjsDocumentFieldFlushEvent {
78
+ pendingUpdates: number;
79
+ }
80
+
81
+ export interface YjsDocumentFieldAdapter {
82
+ open(): Promise<SyncularCrdtFieldDescriptor>;
83
+ start(): Promise<() => Promise<void>>;
84
+ flush(): Promise<void>;
85
+ pendingUpdateCount(): number;
86
+ restoreFromPersistedState(): Promise<YjsDocumentRestoreReceipt>;
87
+ refreshMaterializedValue(): Promise<SyncularCrdtFieldMaterialization>;
88
+ snapshotStateVector(): Promise<string>;
89
+ applyRemoteUpdate(update: SyncularYjsUpdateEnvelope): void;
90
+ compact(
91
+ minUncheckpointedUpdates?: number
92
+ ): Promise<SyncularCrdtFieldCompactionReceipt>;
93
+ }
94
+
95
+ export interface YjsDocumentRestoreReceipt {
96
+ field: SyncularCrdtFieldRequest;
97
+ descriptor: SyncularCrdtFieldDescriptor;
98
+ materialization: SyncularCrdtFieldMaterialization;
99
+ documentSnapshot?: SyncularCrdtDocumentSnapshot;
100
+ stateBase64?: string | null;
101
+ stateVectorBase64: string;
102
+ restoredState: boolean;
103
+ }
104
+
105
+ export type SyncularCrdtProjectionReason =
106
+ | 'local-write'
107
+ | 'remote-apply'
108
+ | 'compaction'
109
+ | 'manual'
110
+ | 'startup';
111
+
112
+ export interface SyncularCrdtProjectionCause {
113
+ reason: SyncularCrdtProjectionReason;
114
+ source: SyncularRowsChangedEvent['source'] | 'manual' | 'startup';
115
+ changedRow?: SyncularChangedRow;
116
+ }
117
+
118
+ export interface SyncularCrdtProjectionEvent {
119
+ field: SyncularCrdtFieldRequest;
120
+ descriptor: SyncularCrdtFieldDescriptor;
121
+ materialization: SyncularCrdtFieldMaterialization;
122
+ documentSnapshot?: SyncularCrdtDocumentSnapshot;
123
+ updateLog?: SyncularCrdtUpdateLogEntry[];
124
+ latestUpdate?: SyncularCrdtUpdateLogEntry;
125
+ reason: SyncularCrdtProjectionReason;
126
+ source: SyncularCrdtProjectionCause['source'];
127
+ operation?: SyncularChangedRow['operation'];
128
+ commitId?: string | null;
129
+ commitSeq?: number | null;
130
+ serverVersion?: number | null;
131
+ stateVectorBase64: string;
132
+ changedRow?: SyncularChangedRow;
133
+ }
134
+
135
+ export interface SyncularCrdtProjectionDefinition<TProjection> {
136
+ derive(
137
+ materialization: SyncularCrdtFieldMaterialization,
138
+ event: SyncularCrdtProjectionEvent
139
+ ): TProjection | Promise<TProjection>;
140
+ apply(
141
+ projection: TProjection,
142
+ event: SyncularCrdtProjectionEvent
143
+ ): void | Promise<void>;
144
+ }
145
+
146
+ export interface SyncularCrdtProjectionMaterializerOptions<TProjection> {
147
+ materializeOnStart?: boolean;
148
+ updateLogLimit?: number;
149
+ onMaterialized?: (
150
+ projection: TProjection,
151
+ event: SyncularCrdtProjectionEvent
152
+ ) => void;
153
+ onError?: (error: unknown, event?: SyncularCrdtProjectionEvent) => void;
154
+ }
155
+
156
+ export interface SyncularCrdtProjectionMaterializer<_TProjection> {
157
+ start(): Promise<() => Promise<void>>;
158
+ stop(): Promise<void>;
159
+ flush(): Promise<void>;
160
+ materialize(
161
+ cause?: Partial<SyncularCrdtProjectionCause>
162
+ ): Promise<SyncularCrdtProjectionEvent>;
163
+ }
164
+
165
+ export interface RichEditorCrdtAdapterOptions<TProjection> {
166
+ field?: YjsDocumentFieldAdapterOptions;
167
+ projections?: SyncularCrdtProjectionMaterializerOptions<TProjection>;
168
+ }
169
+
170
+ export interface RichEditorCrdtAdapter<_TProjection> {
171
+ document: YjsDocumentFieldAdapter;
172
+ projections: SyncularCrdtProjectionMaterializer<_TProjection>;
173
+ start(): Promise<() => Promise<void>>;
174
+ stop(): Promise<void>;
175
+ flush(): Promise<void>;
176
+ restoreFromPersistedState(): Promise<YjsDocumentRestoreReceipt>;
177
+ compact(
178
+ minUncheckpointedUpdates?: number
179
+ ): Promise<SyncularCrdtFieldCompactionReceipt>;
180
+ }
181
+
182
+ export type YjsEditorBackpressureState = 'open' | 'blocked' | 'recovering';
183
+
184
+ export interface YjsEditorBackpressureEvent {
185
+ state: YjsEditorBackpressureState;
186
+ pendingUpdates: number;
187
+ maxPendingUpdates?: number;
188
+ retryAttempt: number;
189
+ nextRetryDelayMs?: number;
190
+ error?: unknown;
191
+ }
192
+
193
+ export interface YjsEditorBackpressureControllerOptions {
194
+ retryDelayMs?: number;
195
+ maxRetryDelayMs?: number;
196
+ autoRetry?: boolean;
197
+ setEditorReadOnly?: (
198
+ readOnly: boolean,
199
+ event: YjsEditorBackpressureEvent
200
+ ) => void;
201
+ showSavingBlocked?: (event: YjsEditorBackpressureEvent) => void;
202
+ clearSavingBlocked?: (event: YjsEditorBackpressureEvent) => void;
203
+ onStateChange?: (event: YjsEditorBackpressureEvent) => void;
204
+ onRetryError?: (error: unknown, event: YjsEditorBackpressureEvent) => void;
205
+ }
206
+
207
+ export interface YjsEditorBackpressureController {
208
+ readonly fieldOptions: Pick<
209
+ YjsDocumentFieldAdapterOptions,
210
+ 'onBackpressure' | 'onFlushError' | 'onFlushStart' | 'onFlushSuccess'
211
+ >;
212
+ attach(
213
+ adapter: Pick<YjsDocumentFieldAdapter, 'flush' | 'pendingUpdateCount'>
214
+ ): void;
215
+ detach(): void;
216
+ state(): YjsEditorBackpressureState;
217
+ retryNow(): Promise<void>;
218
+ stop(): void;
219
+ }
220
+
221
+ export function createYjsDocumentFieldAdapter(
222
+ host: SyncularCrdtFieldHost,
223
+ field: SyncularCrdtFieldRequest,
224
+ binding: YjsDocumentBinding,
225
+ options: YjsDocumentFieldAdapterOptions = {}
226
+ ): YjsDocumentFieldAdapter {
227
+ const flushDelayMs = options.flushDelayMs ?? 16;
228
+ const maxPendingUpdates = options.maxPendingUpdates ?? 1024;
229
+ const updateId = options.updateId ?? randomUpdateId;
230
+ const pending: SyncularYjsUpdateEnvelope[] = [];
231
+ let flushTimer: ReturnType<typeof setTimeout> | undefined;
232
+ let activeFlush: Promise<void> | undefined;
233
+ let descriptor: SyncularCrdtFieldDescriptor | undefined;
234
+
235
+ const scheduleFlush = () => {
236
+ if (flushTimer != null) return;
237
+ flushTimer = setTimeout(() => {
238
+ flushTimer = undefined;
239
+ void adapter.flush().catch((error) => {
240
+ options.onFlushError?.(error);
241
+ });
242
+ }, flushDelayMs);
243
+ };
244
+
245
+ const adapter: YjsDocumentFieldAdapter = {
246
+ open() {
247
+ return host.openCrdtField(field).then((nextDescriptor) => {
248
+ descriptor = nextDescriptor;
249
+ return nextDescriptor;
250
+ });
251
+ },
252
+
253
+ async start() {
254
+ await adapter.open();
255
+ if (options.restoreOnStart) {
256
+ await adapter.restoreFromPersistedState();
257
+ }
258
+ const unsubscribe = binding.subscribeLocalUpdates((update) => {
259
+ if (pending.length >= maxPendingUpdates) {
260
+ options.onBackpressure?.({
261
+ pendingUpdates: pending.length,
262
+ maxPendingUpdates,
263
+ });
264
+ throw new Error(
265
+ `Syncular CRDT adapter pending update limit exceeded (${maxPendingUpdates})`
266
+ );
267
+ }
268
+ pending.push({
269
+ updateId: updateId(),
270
+ updateBase64: bytesToBase64(update),
271
+ });
272
+ scheduleFlush();
273
+ });
274
+
275
+ return async () => {
276
+ if (flushTimer != null) {
277
+ clearTimeout(flushTimer);
278
+ flushTimer = undefined;
279
+ }
280
+ unsubscribe();
281
+ await adapter.flush();
282
+ };
283
+ },
284
+
285
+ flush() {
286
+ activeFlush ??= (async () => {
287
+ options.onFlushStart?.({ pendingUpdates: pending.length });
288
+ while (pending.length > 0) {
289
+ const update = pending[0];
290
+ if (update == null) continue;
291
+ if (host.enqueueCrdtFieldYjsUpdate) {
292
+ await host.enqueueCrdtFieldYjsUpdate({ ...field, update });
293
+ } else {
294
+ await host.applyCrdtFieldYjsUpdate({ ...field, update });
295
+ }
296
+ pending.shift();
297
+ }
298
+ options.onFlushSuccess?.({ pendingUpdates: pending.length });
299
+ })().finally(() => {
300
+ activeFlush = undefined;
301
+ });
302
+ return activeFlush;
303
+ },
304
+
305
+ pendingUpdateCount() {
306
+ return pending.length;
307
+ },
308
+
309
+ async restoreFromPersistedState() {
310
+ const currentDescriptor = descriptor ?? (await adapter.open());
311
+ const [materialized, documentSnapshot] = await Promise.all([
312
+ host.materializeCrdtField(field),
313
+ host.crdtDocumentSnapshot?.(field),
314
+ ]);
315
+ const stateBase64 =
316
+ documentSnapshot?.stateBase64 ?? materialized.stateBase64 ?? null;
317
+ const receipt: YjsDocumentRestoreReceipt = {
318
+ field,
319
+ descriptor: currentDescriptor,
320
+ materialization: materialized,
321
+ documentSnapshot,
322
+ stateBase64,
323
+ stateVectorBase64:
324
+ documentSnapshot?.stateVectorBase64 ?? materialized.stateVectorBase64,
325
+ restoredState: stateBase64 != null && stateBase64 !== '',
326
+ };
327
+ if (stateBase64 != null && stateBase64 !== '') {
328
+ binding.replaceDocumentState?.(base64ToBytes(stateBase64), receipt);
329
+ }
330
+ binding.replaceMaterializedValue?.(materialized.value);
331
+ return receipt;
332
+ },
333
+
334
+ async refreshMaterializedValue() {
335
+ const materialized = await host.materializeCrdtField(field);
336
+ binding.replaceMaterializedValue?.(materialized.value);
337
+ return materialized;
338
+ },
339
+
340
+ async snapshotStateVector() {
341
+ const snapshot = await host.snapshotCrdtFieldStateVector(field);
342
+ return snapshot.stateVectorBase64;
343
+ },
344
+
345
+ applyRemoteUpdate(update) {
346
+ binding.applyRemoteUpdate?.(base64ToBytes(update.updateBase64));
347
+ },
348
+
349
+ compact(minUncheckpointedUpdates = 100) {
350
+ return host.compactCrdtField({ ...field, minUncheckpointedUpdates });
351
+ },
352
+ };
353
+
354
+ return adapter;
355
+ }
356
+
357
+ export function createYjsEditorBackpressureController(
358
+ options: YjsEditorBackpressureControllerOptions = {}
359
+ ): YjsEditorBackpressureController {
360
+ const retryDelayMs = options.retryDelayMs ?? 750;
361
+ const maxRetryDelayMs = options.maxRetryDelayMs ?? 8_000;
362
+ const autoRetry = options.autoRetry ?? true;
363
+ let adapter:
364
+ | Pick<YjsDocumentFieldAdapter, 'flush' | 'pendingUpdateCount'>
365
+ | undefined;
366
+ let currentState: YjsEditorBackpressureState = 'open';
367
+ let retryAttempt = 0;
368
+ let retryTimer: ReturnType<typeof setTimeout> | undefined;
369
+ let lastEvent: YjsEditorBackpressureEvent = {
370
+ state: 'open',
371
+ pendingUpdates: 0,
372
+ retryAttempt: 0,
373
+ };
374
+
375
+ const clearRetryTimer = () => {
376
+ if (retryTimer != null) {
377
+ clearTimeout(retryTimer);
378
+ retryTimer = undefined;
379
+ }
380
+ };
381
+
382
+ const publish = (
383
+ state: YjsEditorBackpressureState,
384
+ event: Partial<YjsEditorBackpressureEvent> = {}
385
+ ): YjsEditorBackpressureEvent => {
386
+ currentState = state;
387
+ lastEvent = {
388
+ ...lastEvent,
389
+ ...event,
390
+ state,
391
+ retryAttempt,
392
+ pendingUpdates:
393
+ event.pendingUpdates ?? adapter?.pendingUpdateCount() ?? 0,
394
+ };
395
+ options.onStateChange?.(lastEvent);
396
+ return lastEvent;
397
+ };
398
+
399
+ const block = (event: Partial<YjsEditorBackpressureEvent>) => {
400
+ const nextEvent = publish('blocked', event);
401
+ options.setEditorReadOnly?.(true, nextEvent);
402
+ options.showSavingBlocked?.(nextEvent);
403
+ scheduleRetry(nextEvent);
404
+ };
405
+
406
+ const unblock = (event: Partial<YjsEditorBackpressureEvent> = {}) => {
407
+ clearRetryTimer();
408
+ retryAttempt = 0;
409
+ const nextEvent = publish('open', event);
410
+ options.clearSavingBlocked?.(nextEvent);
411
+ options.setEditorReadOnly?.(false, nextEvent);
412
+ };
413
+
414
+ const scheduleRetry = (event: YjsEditorBackpressureEvent) => {
415
+ if (!autoRetry || adapter == null || retryTimer != null) return;
416
+ const nextRetryDelayMs = Math.min(
417
+ maxRetryDelayMs,
418
+ retryDelayMs * 2 ** retryAttempt
419
+ );
420
+ lastEvent = { ...event, nextRetryDelayMs };
421
+ retryTimer = setTimeout(() => {
422
+ retryTimer = undefined;
423
+ void controller.retryNow();
424
+ }, nextRetryDelayMs);
425
+ };
426
+
427
+ const controller: YjsEditorBackpressureController = {
428
+ fieldOptions: {
429
+ onBackpressure(event) {
430
+ retryAttempt = 0;
431
+ block({
432
+ pendingUpdates: event.pendingUpdates,
433
+ maxPendingUpdates: event.maxPendingUpdates,
434
+ });
435
+ },
436
+
437
+ onFlushStart(event) {
438
+ if (currentState === 'blocked') {
439
+ publish('recovering', { pendingUpdates: event.pendingUpdates });
440
+ }
441
+ },
442
+
443
+ onFlushSuccess(event) {
444
+ if (event.pendingUpdates === 0 && currentState !== 'open') {
445
+ unblock({ pendingUpdates: 0 });
446
+ }
447
+ },
448
+
449
+ onFlushError(error) {
450
+ retryAttempt += 1;
451
+ const event = publish(
452
+ currentState === 'open' ? 'blocked' : currentState,
453
+ {
454
+ error,
455
+ }
456
+ );
457
+ options.onRetryError?.(error, event);
458
+ scheduleRetry(event);
459
+ },
460
+ },
461
+
462
+ attach(nextAdapter) {
463
+ adapter = nextAdapter;
464
+ },
465
+
466
+ detach() {
467
+ adapter = undefined;
468
+ clearRetryTimer();
469
+ },
470
+
471
+ state() {
472
+ return currentState;
473
+ },
474
+
475
+ async retryNow() {
476
+ clearRetryTimer();
477
+ if (adapter == null) return;
478
+ publish('recovering');
479
+ try {
480
+ await adapter.flush();
481
+ if (adapter.pendingUpdateCount() === 0) {
482
+ unblock({ pendingUpdates: 0 });
483
+ } else {
484
+ retryAttempt += 1;
485
+ block({ pendingUpdates: adapter.pendingUpdateCount() });
486
+ }
487
+ } catch (error) {
488
+ retryAttempt += 1;
489
+ const event = publish('blocked', { error });
490
+ options.onRetryError?.(error, event);
491
+ scheduleRetry(event);
492
+ }
493
+ },
494
+
495
+ stop() {
496
+ clearRetryTimer();
497
+ retryAttempt = 0;
498
+ if (currentState !== 'open') {
499
+ unblock({ pendingUpdates: adapter?.pendingUpdateCount() ?? 0 });
500
+ }
501
+ },
502
+ };
503
+
504
+ return controller;
505
+ }
506
+
507
+ export function createRichEditorCrdtAdapter<TProjection>(
508
+ host: SyncularCrdtProjectionHost,
509
+ field: SyncularCrdtFieldRequest,
510
+ binding: YjsDocumentBinding,
511
+ projection: SyncularCrdtProjectionDefinition<TProjection>,
512
+ options: RichEditorCrdtAdapterOptions<TProjection> = {}
513
+ ): RichEditorCrdtAdapter<TProjection> {
514
+ const document = createYjsDocumentFieldAdapter(host, field, binding, {
515
+ restoreOnStart: true,
516
+ ...options.field,
517
+ });
518
+ const projections = createCrdtFieldProjectionMaterializer(
519
+ host,
520
+ field,
521
+ projection,
522
+ {
523
+ materializeOnStart: true,
524
+ ...options.projections,
525
+ }
526
+ );
527
+ let stopDocument: (() => Promise<void>) | undefined;
528
+ let stopProjections: (() => Promise<void>) | undefined;
529
+
530
+ const adapter: RichEditorCrdtAdapter<TProjection> = {
531
+ document,
532
+ projections,
533
+
534
+ async start() {
535
+ stopDocument = await document.start();
536
+ stopProjections = await projections.start();
537
+ return () => adapter.stop();
538
+ },
539
+
540
+ async stop() {
541
+ const currentStopDocument = stopDocument;
542
+ const currentStopProjections = stopProjections;
543
+ stopProjections = undefined;
544
+ stopDocument = undefined;
545
+ await currentStopDocument?.();
546
+ await currentStopProjections?.();
547
+ },
548
+
549
+ async flush() {
550
+ await document.flush();
551
+ await projections.flush();
552
+ },
553
+
554
+ restoreFromPersistedState() {
555
+ return document.restoreFromPersistedState();
556
+ },
557
+
558
+ compact(minUncheckpointedUpdates) {
559
+ return document.compact(minUncheckpointedUpdates);
560
+ },
561
+ };
562
+
563
+ return adapter;
564
+ }
565
+
566
+ export function createCrdtFieldProjectionMaterializer<TProjection>(
567
+ host: SyncularCrdtProjectionHost,
568
+ field: SyncularCrdtFieldRequest,
569
+ projection: SyncularCrdtProjectionDefinition<TProjection>,
570
+ options: SyncularCrdtProjectionMaterializerOptions<TProjection> = {}
571
+ ): SyncularCrdtProjectionMaterializer<TProjection> {
572
+ let descriptor: SyncularCrdtFieldDescriptor | undefined;
573
+ let unsubscribeRowsChanged: (() => void) | undefined;
574
+ let queue = Promise.resolve();
575
+
576
+ const open = async () => {
577
+ descriptor ??= await host.openCrdtField(field);
578
+ return descriptor;
579
+ };
580
+
581
+ const runMaterialization = async (
582
+ cause: SyncularCrdtProjectionCause
583
+ ): Promise<SyncularCrdtProjectionEvent> => {
584
+ const currentDescriptor = await open();
585
+ const updateLogLimit = Math.max(0, options.updateLogLimit ?? 0);
586
+ const [materialization, documentSnapshot, updateLog] = await Promise.all([
587
+ host.materializeCrdtField(field),
588
+ host.crdtDocumentSnapshot?.(field),
589
+ updateLogLimit > 0
590
+ ? host.crdtUpdateLog?.({ ...field, limit: updateLogLimit })
591
+ : undefined,
592
+ ]);
593
+ const stateVectorBase64 =
594
+ documentSnapshot?.stateVectorBase64 ?? materialization.stateVectorBase64;
595
+ const event: SyncularCrdtProjectionEvent = {
596
+ field,
597
+ descriptor: currentDescriptor,
598
+ materialization,
599
+ documentSnapshot,
600
+ updateLog,
601
+ latestUpdate: updateLog?.[0],
602
+ reason: cause.reason,
603
+ source: cause.source,
604
+ operation: cause.changedRow?.operation,
605
+ commitId: cause.changedRow?.commitId,
606
+ commitSeq: cause.changedRow?.commitSeq,
607
+ serverVersion: cause.changedRow?.serverVersion,
608
+ stateVectorBase64,
609
+ changedRow: cause.changedRow,
610
+ };
611
+
612
+ try {
613
+ const nextProjection = await projection.derive(materialization, event);
614
+ await projection.apply(nextProjection, event);
615
+ options.onMaterialized?.(nextProjection, event);
616
+ return event;
617
+ } catch (error) {
618
+ options.onError?.(error, event);
619
+ throw error;
620
+ }
621
+ };
622
+
623
+ const enqueueMaterialization = (cause: SyncularCrdtProjectionCause) => {
624
+ queue = queue
625
+ .then(() => runMaterialization(cause))
626
+ .then(
627
+ () => undefined,
628
+ () => undefined
629
+ );
630
+ };
631
+
632
+ const materializer: SyncularCrdtProjectionMaterializer<TProjection> = {
633
+ async start() {
634
+ const currentDescriptor = await open();
635
+ unsubscribeRowsChanged = host.addRowsChangedListener((event) => {
636
+ const changedRow = event.changedRows.find((row) =>
637
+ crdtChangedRowMatchesField(row, field, currentDescriptor)
638
+ );
639
+ if (changedRow == null) return;
640
+ enqueueMaterialization({
641
+ reason: projectionReasonForRowsChanged(event, changedRow),
642
+ source: event.source,
643
+ changedRow,
644
+ });
645
+ });
646
+ if (options.materializeOnStart) {
647
+ enqueueMaterialization({ reason: 'startup', source: 'startup' });
648
+ }
649
+ return () => materializer.stop();
650
+ },
651
+
652
+ async stop() {
653
+ unsubscribeRowsChanged?.();
654
+ unsubscribeRowsChanged = undefined;
655
+ await materializer.flush();
656
+ },
657
+
658
+ flush() {
659
+ return queue;
660
+ },
661
+
662
+ materialize(cause = {}) {
663
+ return runMaterialization({
664
+ reason: cause.reason ?? 'manual',
665
+ source: cause.source ?? 'manual',
666
+ changedRow: cause.changedRow,
667
+ });
668
+ },
669
+ };
670
+
671
+ return materializer;
672
+ }
673
+
674
+ export function crdtChangedRowMatchesField(
675
+ row: SyncularChangedRow,
676
+ field: SyncularCrdtFieldRequest,
677
+ descriptor: SyncularCrdtFieldDescriptor
678
+ ): boolean {
679
+ if (row.table !== field.table || row.rowId !== field.rowId) return false;
680
+ return (
681
+ row.crdtFields.includes(descriptor.stateColumn) ||
682
+ row.changedFields.includes(descriptor.stateColumn) ||
683
+ row.changedFields.includes(field.field)
684
+ );
685
+ }
686
+
687
+ export function projectionReasonForRowsChanged(
688
+ event: SyncularRowsChangedEvent,
689
+ row: SyncularChangedRow
690
+ ): SyncularCrdtProjectionReason {
691
+ if (row.operation === 'compact') return 'compaction';
692
+ if (event.source === 'remotePull') return 'remote-apply';
693
+ return 'local-write';
694
+ }
695
+
696
+ export function bytesToBase64(bytes: Uint8Array): string {
697
+ const maybeBuffer = (
698
+ globalThis as {
699
+ Buffer?: {
700
+ from(input: Uint8Array): { toString(encoding: 'base64'): string };
701
+ };
702
+ }
703
+ ).Buffer;
704
+ if (maybeBuffer != null) return maybeBuffer.from(bytes).toString('base64');
705
+
706
+ let binary = '';
707
+ for (const byte of bytes) binary += String.fromCharCode(byte);
708
+ return btoa(binary);
709
+ }
710
+
711
+ export function base64ToBytes(value: string): Uint8Array {
712
+ const maybeBuffer = (
713
+ globalThis as {
714
+ Buffer?: {
715
+ from(input: string, encoding: 'base64'): Uint8Array;
716
+ };
717
+ }
718
+ ).Buffer;
719
+ if (maybeBuffer != null) return maybeBuffer.from(value, 'base64');
720
+
721
+ const binary = atob(value);
722
+ const bytes = new Uint8Array(binary.length);
723
+ for (let index = 0; index < binary.length; index += 1) {
724
+ bytes[index] = binary.charCodeAt(index);
725
+ }
726
+ return bytes;
727
+ }
728
+
729
+ function randomUpdateId(): string {
730
+ return (
731
+ globalThis.crypto?.randomUUID?.() ?? `yjs-${Date.now()}-${Math.random()}`
732
+ );
733
+ }