@syncular/server 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 (601) hide show
  1. package/README.md +1 -2
  2. package/dist/auth-leases.d.ts +85 -0
  3. package/dist/auth-leases.d.ts.map +1 -0
  4. package/dist/auth-leases.js +368 -0
  5. package/dist/auth-leases.js.map +1 -0
  6. package/dist/better-sqlite3.d.ts +22 -0
  7. package/dist/better-sqlite3.d.ts.map +1 -0
  8. package/dist/better-sqlite3.js +16 -0
  9. package/dist/better-sqlite3.js.map +1 -0
  10. package/dist/blobs/access.d.ts +35 -0
  11. package/dist/blobs/access.d.ts.map +1 -0
  12. package/dist/blobs/access.js +149 -0
  13. package/dist/blobs/access.js.map +1 -0
  14. package/dist/blobs/adapters/database.d.ts +26 -9
  15. package/dist/blobs/adapters/database.d.ts.map +1 -1
  16. package/dist/blobs/adapters/database.js +68 -23
  17. package/dist/blobs/adapters/database.js.map +1 -1
  18. package/dist/blobs/index.d.ts +1 -0
  19. package/dist/blobs/index.d.ts.map +1 -1
  20. package/dist/blobs/index.js +5 -4
  21. package/dist/blobs/index.js.map +1 -1
  22. package/dist/blobs/manager.d.ts +69 -44
  23. package/dist/blobs/manager.d.ts.map +1 -1
  24. package/dist/blobs/manager.js +250 -65
  25. package/dist/blobs/manager.js.map +1 -1
  26. package/dist/blobs/migrate.d.ts.map +1 -1
  27. package/dist/blobs/migrate.js +16 -8
  28. package/dist/blobs/migrate.js.map +1 -1
  29. package/dist/blobs/types.d.ts +4 -0
  30. package/dist/blobs/types.d.ts.map +1 -1
  31. package/dist/bun-sqlite.d.ts +19 -0
  32. package/dist/bun-sqlite.d.ts.map +1 -0
  33. package/dist/bun-sqlite.js +19 -0
  34. package/dist/bun-sqlite.js.map +1 -0
  35. package/dist/clients.d.ts +1 -0
  36. package/dist/clients.d.ts.map +1 -1
  37. package/dist/clients.js.map +1 -1
  38. package/dist/cloudflare/durable-object.d.ts +93 -0
  39. package/dist/cloudflare/durable-object.d.ts.map +1 -0
  40. package/dist/cloudflare/durable-object.js +210 -0
  41. package/dist/cloudflare/durable-object.js.map +1 -0
  42. package/dist/cloudflare/index.d.ts +22 -0
  43. package/dist/cloudflare/index.d.ts.map +1 -0
  44. package/dist/cloudflare/index.js +22 -0
  45. package/dist/cloudflare/index.js.map +1 -0
  46. package/dist/cloudflare/r2.d.ts +180 -0
  47. package/dist/cloudflare/r2.d.ts.map +1 -0
  48. package/dist/cloudflare/r2.js +258 -0
  49. package/dist/cloudflare/r2.js.map +1 -0
  50. package/dist/cloudflare/scope-cache.d.ts +54 -0
  51. package/dist/cloudflare/scope-cache.d.ts.map +1 -0
  52. package/dist/cloudflare/scope-cache.js +223 -0
  53. package/dist/cloudflare/scope-cache.js.map +1 -0
  54. package/dist/cloudflare/sentry.d.ts +47 -0
  55. package/dist/cloudflare/sentry.d.ts.map +1 -0
  56. package/dist/cloudflare/sentry.js +163 -0
  57. package/dist/cloudflare/sentry.js.map +1 -0
  58. package/dist/cloudflare/worker.d.ts +46 -0
  59. package/dist/cloudflare/worker.d.ts.map +1 -0
  60. package/dist/cloudflare/worker.js +63 -0
  61. package/dist/cloudflare/worker.js.map +1 -0
  62. package/dist/commit-integrity.d.ts +38 -0
  63. package/dist/commit-integrity.d.ts.map +1 -0
  64. package/dist/commit-integrity.js +260 -0
  65. package/dist/commit-integrity.js.map +1 -0
  66. package/dist/compaction.js.map +1 -1
  67. package/dist/crdt-yjs/index.d.ts +99 -0
  68. package/dist/crdt-yjs/index.d.ts.map +1 -0
  69. package/dist/crdt-yjs/index.js +629 -0
  70. package/dist/crdt-yjs/index.js.map +1 -0
  71. package/dist/d1.d.ts +13 -0
  72. package/dist/d1.d.ts.map +1 -0
  73. package/dist/d1.js +14 -0
  74. package/dist/d1.js.map +1 -0
  75. package/dist/dialect/base.d.ts +11 -1
  76. package/dist/dialect/base.d.ts.map +1 -1
  77. package/dist/dialect/base.js +43 -6
  78. package/dist/dialect/base.js.map +1 -1
  79. package/dist/dialect/helpers.d.ts +6 -0
  80. package/dist/dialect/helpers.d.ts.map +1 -1
  81. package/dist/dialect/helpers.js +24 -0
  82. package/dist/dialect/helpers.js.map +1 -1
  83. package/dist/dialect/index.js +3 -3
  84. package/dist/dialect/types.d.ts +40 -0
  85. package/dist/dialect/types.d.ts.map +1 -1
  86. package/dist/encrypted-crdt.d.ts +67 -0
  87. package/dist/encrypted-crdt.d.ts.map +1 -0
  88. package/dist/encrypted-crdt.js +426 -0
  89. package/dist/encrypted-crdt.js.map +1 -0
  90. package/dist/errors.d.ts +10 -0
  91. package/dist/errors.d.ts.map +1 -0
  92. package/dist/errors.js +13 -0
  93. package/dist/errors.js.map +1 -0
  94. package/dist/filesystem/index.d.ts +44 -0
  95. package/dist/filesystem/index.d.ts.map +1 -0
  96. package/dist/filesystem/index.js +164 -0
  97. package/dist/filesystem/index.js.map +1 -0
  98. package/dist/handlers/collection.d.ts +11 -3
  99. package/dist/handlers/collection.d.ts.map +1 -1
  100. package/dist/handlers/collection.js +34 -56
  101. package/dist/handlers/collection.js.map +1 -1
  102. package/dist/handlers/create-handler.d.ts +31 -7
  103. package/dist/handlers/create-handler.d.ts.map +1 -1
  104. package/dist/handlers/create-handler.js +332 -115
  105. package/dist/handlers/create-handler.js.map +1 -1
  106. package/dist/handlers/index.js +3 -3
  107. package/dist/handlers/types.d.ts +86 -1
  108. package/dist/handlers/types.d.ts.map +1 -1
  109. package/dist/helpers/conflict.js.map +1 -1
  110. package/dist/helpers/emitted-change.js.map +1 -1
  111. package/dist/helpers/index.d.ts +2 -0
  112. package/dist/helpers/index.d.ts.map +1 -1
  113. package/dist/helpers/index.js +6 -4
  114. package/dist/helpers/index.js.map +1 -1
  115. package/dist/helpers/paginate.js.map +1 -1
  116. package/dist/helpers/scope-authorization.d.ts +7 -0
  117. package/dist/helpers/scope-authorization.d.ts.map +1 -0
  118. package/dist/helpers/scope-authorization.js +19 -0
  119. package/dist/helpers/scope-authorization.js.map +1 -0
  120. package/dist/helpers/scope-commit-index.d.ts +12 -0
  121. package/dist/helpers/scope-commit-index.d.ts.map +1 -0
  122. package/dist/helpers/scope-commit-index.js +35 -0
  123. package/dist/helpers/scope-commit-index.js.map +1 -0
  124. package/dist/helpers/scope-strings.js.map +1 -1
  125. package/dist/hono/api-key-auth.d.ts +49 -0
  126. package/dist/hono/api-key-auth.d.ts.map +1 -0
  127. package/dist/hono/api-key-auth.js +108 -0
  128. package/dist/hono/api-key-auth.js.map +1 -0
  129. package/dist/hono/audit-redaction.d.ts +20 -0
  130. package/dist/hono/audit-redaction.d.ts.map +1 -0
  131. package/dist/hono/audit-redaction.js +85 -0
  132. package/dist/hono/audit-redaction.js.map +1 -0
  133. package/dist/hono/blobs.d.ts +75 -0
  134. package/dist/hono/blobs.d.ts.map +1 -0
  135. package/dist/hono/blobs.js +586 -0
  136. package/dist/hono/blobs.js.map +1 -0
  137. package/dist/hono/console/gateway.d.ts +42 -0
  138. package/dist/hono/console/gateway.d.ts.map +1 -0
  139. package/dist/hono/console/gateway.js +2158 -0
  140. package/dist/hono/console/gateway.js.map +1 -0
  141. package/dist/hono/console/live-auth.d.ts +7 -0
  142. package/dist/hono/console/live-auth.d.ts.map +1 -0
  143. package/dist/hono/console/live-auth.js +39 -0
  144. package/dist/hono/console/live-auth.js.map +1 -0
  145. package/dist/hono/console/route-descriptor.d.ts +6 -0
  146. package/dist/hono/console/route-descriptor.d.ts.map +1 -0
  147. package/dist/hono/console/route-descriptor.js +16 -0
  148. package/dist/hono/console/route-descriptor.js.map +1 -0
  149. package/dist/hono/console/routes/api-keys.d.ts +8 -0
  150. package/dist/hono/console/routes/api-keys.d.ts.map +1 -0
  151. package/dist/hono/console/routes/api-keys.js +575 -0
  152. package/dist/hono/console/routes/api-keys.js.map +1 -0
  153. package/dist/hono/console/routes/clients.d.ts +8 -0
  154. package/dist/hono/console/routes/clients.d.ts.map +1 -0
  155. package/dist/hono/console/routes/clients.js +322 -0
  156. package/dist/hono/console/routes/clients.js.map +1 -0
  157. package/dist/hono/console/routes/commits.d.ts +8 -0
  158. package/dist/hono/console/routes/commits.d.ts.map +1 -0
  159. package/dist/hono/console/routes/commits.js +882 -0
  160. package/dist/hono/console/routes/commits.js.map +1 -0
  161. package/dist/hono/console/routes/context.d.ts +188 -0
  162. package/dist/hono/console/routes/context.d.ts.map +1 -0
  163. package/dist/hono/console/routes/context.js +633 -0
  164. package/dist/hono/console/routes/context.js.map +1 -0
  165. package/dist/hono/console/routes/events.d.ts +8 -0
  166. package/dist/hono/console/routes/events.d.ts.map +1 -0
  167. package/dist/hono/console/routes/events.js +501 -0
  168. package/dist/hono/console/routes/events.js.map +1 -0
  169. package/dist/hono/console/routes/maintenance.d.ts +8 -0
  170. package/dist/hono/console/routes/maintenance.d.ts.map +1 -0
  171. package/dist/hono/console/routes/maintenance.js +341 -0
  172. package/dist/hono/console/routes/maintenance.js.map +1 -0
  173. package/dist/hono/console/routes/shared.d.ts +174 -0
  174. package/dist/hono/console/routes/shared.d.ts.map +1 -0
  175. package/dist/hono/console/routes/shared.js +583 -0
  176. package/dist/hono/console/routes/shared.js.map +1 -0
  177. package/dist/hono/console/routes/stats.d.ts +8 -0
  178. package/dist/hono/console/routes/stats.d.ts.map +1 -0
  179. package/dist/hono/console/routes/stats.js +295 -0
  180. package/dist/hono/console/routes/stats.js.map +1 -0
  181. package/dist/hono/console/routes/storage.d.ts +8 -0
  182. package/dist/hono/console/routes/storage.d.ts.map +1 -0
  183. package/dist/hono/console/routes/storage.js +121 -0
  184. package/dist/hono/console/routes/storage.js.map +1 -0
  185. package/dist/hono/console/routes.d.ts +36 -0
  186. package/dist/hono/console/routes.d.ts.map +1 -0
  187. package/dist/hono/console/routes.js +112 -0
  188. package/dist/hono/console/routes.js.map +1 -0
  189. package/dist/hono/console/schema-errors.d.ts +2 -0
  190. package/dist/hono/console/schema-errors.d.ts.map +1 -0
  191. package/dist/hono/console/schema-errors.js +17 -0
  192. package/dist/hono/console/schema-errors.js.map +1 -0
  193. package/dist/hono/console/schemas.d.ts +1515 -0
  194. package/dist/hono/console/schemas.d.ts.map +1 -0
  195. package/dist/hono/console/schemas.js +661 -0
  196. package/dist/hono/console/schemas.js.map +1 -0
  197. package/dist/hono/console/types.d.ts +213 -0
  198. package/dist/hono/console/types.d.ts.map +1 -0
  199. package/dist/hono/console/types.js +2 -0
  200. package/dist/hono/console/types.js.map +1 -0
  201. package/dist/hono/console/ui.d.ts +38 -0
  202. package/dist/hono/console/ui.d.ts.map +1 -0
  203. package/dist/hono/console/ui.js +43 -0
  204. package/dist/hono/console/ui.js.map +1 -0
  205. package/dist/hono/create-server.d.ts +71 -0
  206. package/dist/hono/create-server.d.ts.map +1 -0
  207. package/dist/hono/create-server.js +121 -0
  208. package/dist/hono/create-server.js.map +1 -0
  209. package/dist/hono/errors.d.ts +13 -0
  210. package/dist/hono/errors.d.ts.map +1 -0
  211. package/dist/hono/errors.js +21 -0
  212. package/dist/hono/errors.js.map +1 -0
  213. package/dist/hono/index.d.ts +20 -0
  214. package/dist/hono/index.d.ts.map +1 -0
  215. package/dist/hono/index.js +31 -0
  216. package/dist/hono/index.js.map +1 -0
  217. package/dist/hono/openapi.d.ts +72 -0
  218. package/dist/hono/openapi.d.ts.map +1 -0
  219. package/dist/hono/openapi.js +99 -0
  220. package/dist/hono/openapi.js.map +1 -0
  221. package/dist/hono/proxy/connection-manager.d.ts +78 -0
  222. package/dist/hono/proxy/connection-manager.d.ts.map +1 -0
  223. package/dist/hono/proxy/connection-manager.js +251 -0
  224. package/dist/hono/proxy/connection-manager.js.map +1 -0
  225. package/dist/hono/proxy/index.d.ts +8 -0
  226. package/dist/hono/proxy/index.d.ts.map +1 -0
  227. package/dist/hono/proxy/index.js +8 -0
  228. package/dist/hono/proxy/index.js.map +1 -0
  229. package/dist/hono/proxy/routes.d.ts +86 -0
  230. package/dist/hono/proxy/routes.d.ts.map +1 -0
  231. package/dist/hono/proxy/routes.js +183 -0
  232. package/dist/hono/proxy/routes.js.map +1 -0
  233. package/dist/hono/rate-limit.d.ts +101 -0
  234. package/dist/hono/rate-limit.d.ts.map +1 -0
  235. package/dist/hono/rate-limit.js +184 -0
  236. package/dist/hono/rate-limit.js.map +1 -0
  237. package/dist/hono/realtime-sync-packs.d.ts +43 -0
  238. package/dist/hono/realtime-sync-packs.d.ts.map +1 -0
  239. package/dist/hono/realtime-sync-packs.js +219 -0
  240. package/dist/hono/realtime-sync-packs.js.map +1 -0
  241. package/dist/hono/routes/audit.d.ts +12 -0
  242. package/dist/hono/routes/audit.d.ts.map +1 -0
  243. package/dist/hono/routes/audit.js +385 -0
  244. package/dist/hono/routes/audit.js.map +1 -0
  245. package/dist/hono/routes/auth-leases.d.ts +8 -0
  246. package/dist/hono/routes/auth-leases.d.ts.map +1 -0
  247. package/dist/hono/routes/auth-leases.js +88 -0
  248. package/dist/hono/routes/auth-leases.js.map +1 -0
  249. package/dist/hono/routes/combined.d.ts +8 -0
  250. package/dist/hono/routes/combined.d.ts.map +1 -0
  251. package/dist/hono/routes/combined.js +392 -0
  252. package/dist/hono/routes/combined.js.map +1 -0
  253. package/dist/hono/routes/context.d.ts +314 -0
  254. package/dist/hono/routes/context.d.ts.map +1 -0
  255. package/dist/hono/routes/context.js +1147 -0
  256. package/dist/hono/routes/context.js.map +1 -0
  257. package/dist/hono/routes/health.d.ts +8 -0
  258. package/dist/hono/routes/health.d.ts.map +1 -0
  259. package/dist/hono/routes/health.js +16 -0
  260. package/dist/hono/routes/health.js.map +1 -0
  261. package/dist/hono/routes/realtime.d.ts +9 -0
  262. package/dist/hono/routes/realtime.d.ts.map +1 -0
  263. package/dist/hono/routes/realtime.js +626 -0
  264. package/dist/hono/routes/realtime.js.map +1 -0
  265. package/dist/hono/routes/shared.d.ts +689 -0
  266. package/dist/hono/routes/shared.d.ts.map +1 -0
  267. package/dist/hono/routes/shared.js +972 -0
  268. package/dist/hono/routes/shared.js.map +1 -0
  269. package/dist/hono/routes/snapshots.d.ts +10 -0
  270. package/dist/hono/routes/snapshots.d.ts.map +1 -0
  271. package/dist/hono/routes/snapshots.js +281 -0
  272. package/dist/hono/routes/snapshots.js.map +1 -0
  273. package/dist/hono/routes.d.ts +19 -0
  274. package/dist/hono/routes.d.ts.map +1 -0
  275. package/dist/hono/routes.js +37 -0
  276. package/dist/hono/routes.js.map +1 -0
  277. package/dist/hono/validation.d.ts +5 -0
  278. package/dist/hono/validation.d.ts.map +1 -0
  279. package/dist/hono/validation.js +47 -0
  280. package/dist/hono/validation.js.map +1 -0
  281. package/dist/hono/websocket-origin.d.ts +9 -0
  282. package/dist/hono/websocket-origin.d.ts.map +1 -0
  283. package/dist/hono/websocket-origin.js +96 -0
  284. package/dist/hono/websocket-origin.js.map +1 -0
  285. package/dist/hono/ws.d.ts +341 -0
  286. package/dist/hono/ws.d.ts.map +1 -0
  287. package/dist/hono/ws.js +711 -0
  288. package/dist/hono/ws.js.map +1 -0
  289. package/dist/index.d.ts +6 -0
  290. package/dist/index.d.ts.map +1 -1
  291. package/dist/index.js +24 -18
  292. package/dist/index.js.map +1 -1
  293. package/dist/libsql.d.ts +29 -0
  294. package/dist/libsql.d.ts.map +1 -0
  295. package/dist/libsql.js +25 -0
  296. package/dist/libsql.js.map +1 -0
  297. package/dist/migrate.js.map +1 -1
  298. package/dist/neon.d.ts +21 -0
  299. package/dist/neon.d.ts.map +1 -0
  300. package/dist/neon.js +22 -0
  301. package/dist/neon.js.map +1 -0
  302. package/dist/notify.d.ts +30 -0
  303. package/dist/notify.d.ts.map +1 -1
  304. package/dist/notify.js +160 -41
  305. package/dist/notify.js.map +1 -1
  306. package/dist/pglite.d.ts +37 -0
  307. package/dist/pglite.d.ts.map +1 -0
  308. package/dist/pglite.js +37 -0
  309. package/dist/pglite.js.map +1 -0
  310. package/dist/plugins/index.d.ts +2 -0
  311. package/dist/plugins/index.d.ts.map +1 -0
  312. package/dist/plugins/index.js +2 -0
  313. package/dist/plugins/index.js.map +1 -0
  314. package/dist/plugins/types.d.ts +73 -0
  315. package/dist/plugins/types.d.ts.map +1 -0
  316. package/dist/plugins/types.js +30 -0
  317. package/dist/plugins/types.js.map +1 -0
  318. package/dist/postgres/index.d.ts +68 -0
  319. package/dist/postgres/index.d.ts.map +1 -0
  320. package/dist/postgres/index.js +924 -0
  321. package/dist/postgres/index.js.map +1 -0
  322. package/dist/proxy/collection.d.ts +0 -2
  323. package/dist/proxy/collection.d.ts.map +1 -1
  324. package/dist/proxy/collection.js +2 -17
  325. package/dist/proxy/collection.js.map +1 -1
  326. package/dist/proxy/handler.d.ts +1 -1
  327. package/dist/proxy/handler.d.ts.map +1 -1
  328. package/dist/proxy/handler.js +3 -4
  329. package/dist/proxy/handler.js.map +1 -1
  330. package/dist/proxy/index.d.ts +1 -1
  331. package/dist/proxy/index.d.ts.map +1 -1
  332. package/dist/proxy/index.js +3 -3
  333. package/dist/proxy/index.js.map +1 -1
  334. package/dist/proxy/mutation-detector.js.map +1 -1
  335. package/dist/proxy/oplog.d.ts.map +1 -1
  336. package/dist/proxy/oplog.js +32 -8
  337. package/dist/proxy/oplog.js.map +1 -1
  338. package/dist/prune.d.ts +8 -1
  339. package/dist/prune.d.ts.map +1 -1
  340. package/dist/prune.js +68 -28
  341. package/dist/prune.js.map +1 -1
  342. package/dist/pull.d.ts +31 -0
  343. package/dist/pull.d.ts.map +1 -1
  344. package/dist/pull.js +1014 -474
  345. package/dist/pull.js.map +1 -1
  346. package/dist/push.d.ts +36 -2
  347. package/dist/push.d.ts.map +1 -1
  348. package/dist/push.js +603 -358
  349. package/dist/push.js.map +1 -1
  350. package/dist/realtime/in-memory.js.map +1 -1
  351. package/dist/realtime/index.d.ts +2 -1
  352. package/dist/realtime/index.d.ts.map +1 -1
  353. package/dist/realtime/index.js +2 -1
  354. package/dist/realtime/index.js.map +1 -1
  355. package/dist/realtime/types.d.ts +12 -0
  356. package/dist/realtime/types.d.ts.map +1 -1
  357. package/dist/realtime/types.js +13 -1
  358. package/dist/realtime/types.js.map +1 -1
  359. package/dist/relay/client-role/forward-engine.d.ts +63 -0
  360. package/dist/relay/client-role/forward-engine.d.ts.map +1 -0
  361. package/dist/relay/client-role/forward-engine.js +267 -0
  362. package/dist/relay/client-role/forward-engine.js.map +1 -0
  363. package/dist/relay/client-role/index.d.ts +9 -0
  364. package/dist/relay/client-role/index.d.ts.map +1 -0
  365. package/dist/relay/client-role/index.js +9 -0
  366. package/dist/relay/client-role/index.js.map +1 -0
  367. package/dist/relay/client-role/pull-engine.d.ts +72 -0
  368. package/dist/relay/client-role/pull-engine.d.ts.map +1 -0
  369. package/dist/relay/client-role/pull-engine.js +249 -0
  370. package/dist/relay/client-role/pull-engine.js.map +1 -0
  371. package/dist/relay/client-role/sequence-mapper.d.ts +65 -0
  372. package/dist/relay/client-role/sequence-mapper.d.ts.map +1 -0
  373. package/dist/relay/client-role/sequence-mapper.js +161 -0
  374. package/dist/relay/client-role/sequence-mapper.js.map +1 -0
  375. package/dist/relay/evaluation/relay-paths.d.ts +41 -0
  376. package/dist/relay/evaluation/relay-paths.d.ts.map +1 -0
  377. package/dist/relay/evaluation/relay-paths.js +504 -0
  378. package/dist/relay/evaluation/relay-paths.js.map +1 -0
  379. package/dist/relay/evaluation/rust-boundary.d.ts +47 -0
  380. package/dist/relay/evaluation/rust-boundary.d.ts.map +1 -0
  381. package/dist/relay/evaluation/rust-boundary.js +220 -0
  382. package/dist/relay/evaluation/rust-boundary.js.map +1 -0
  383. package/dist/relay/index.d.ts +37 -0
  384. package/dist/relay/index.d.ts.map +1 -0
  385. package/dist/relay/index.js +44 -0
  386. package/dist/relay/index.js.map +1 -0
  387. package/dist/relay/migrate.d.ts +18 -0
  388. package/dist/relay/migrate.d.ts.map +1 -0
  389. package/dist/relay/migrate.js +99 -0
  390. package/dist/relay/migrate.js.map +1 -0
  391. package/dist/relay/mode-manager.d.ts +60 -0
  392. package/dist/relay/mode-manager.d.ts.map +1 -0
  393. package/dist/relay/mode-manager.js +114 -0
  394. package/dist/relay/mode-manager.js.map +1 -0
  395. package/dist/relay/realtime.d.ts +90 -0
  396. package/dist/relay/realtime.d.ts.map +1 -0
  397. package/dist/relay/realtime.js +147 -0
  398. package/dist/relay/realtime.js.map +1 -0
  399. package/dist/relay/relay.d.ts +190 -0
  400. package/dist/relay/relay.d.ts.map +1 -0
  401. package/dist/relay/relay.js +320 -0
  402. package/dist/relay/relay.js.map +1 -0
  403. package/dist/relay/schema.d.ts +158 -0
  404. package/dist/relay/schema.d.ts.map +1 -0
  405. package/dist/relay/schema.js +7 -0
  406. package/dist/relay/schema.js.map +1 -0
  407. package/dist/relay/server-role/index.d.ts +56 -0
  408. package/dist/relay/server-role/index.d.ts.map +1 -0
  409. package/dist/relay/server-role/index.js +193 -0
  410. package/dist/relay/server-role/index.js.map +1 -0
  411. package/dist/relay/server-role/pull.d.ts +27 -0
  412. package/dist/relay/server-role/pull.d.ts.map +1 -0
  413. package/dist/relay/server-role/pull.js +24 -0
  414. package/dist/relay/server-role/pull.js.map +1 -0
  415. package/dist/relay/server-role/push.d.ts +29 -0
  416. package/dist/relay/server-role/push.d.ts.map +1 -0
  417. package/dist/relay/server-role/push.js +122 -0
  418. package/dist/relay/server-role/push.js.map +1 -0
  419. package/dist/s3/index.d.ts +83 -0
  420. package/dist/s3/index.d.ts.map +1 -0
  421. package/dist/s3/index.js +223 -0
  422. package/dist/s3/index.js.map +1 -0
  423. package/dist/schema.d.ts +137 -1
  424. package/dist/schema.d.ts.map +1 -1
  425. package/dist/schema.js +1 -0
  426. package/dist/schema.js.map +1 -1
  427. package/dist/service-worker/index.d.ts +114 -0
  428. package/dist/service-worker/index.d.ts.map +1 -0
  429. package/dist/service-worker/index.js +487 -0
  430. package/dist/service-worker/index.js.map +1 -0
  431. package/dist/snapshot-artifacts/sqlite-bun.d.ts +15 -0
  432. package/dist/snapshot-artifacts/sqlite-bun.d.ts.map +1 -0
  433. package/dist/snapshot-artifacts/sqlite-bun.js +128 -0
  434. package/dist/snapshot-artifacts/sqlite-bun.js.map +1 -0
  435. package/dist/snapshot-artifacts.d.ts +206 -0
  436. package/dist/snapshot-artifacts.d.ts.map +1 -0
  437. package/dist/snapshot-artifacts.js +545 -0
  438. package/dist/snapshot-artifacts.js.map +1 -0
  439. package/dist/snapshot-chunks/db-metadata.d.ts +18 -0
  440. package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
  441. package/dist/snapshot-chunks/db-metadata.js +91 -63
  442. package/dist/snapshot-chunks/db-metadata.js.map +1 -1
  443. package/dist/snapshot-chunks/index.js +2 -2
  444. package/dist/snapshot-chunks/types.d.ts +2 -0
  445. package/dist/snapshot-chunks/types.d.ts.map +1 -1
  446. package/dist/snapshot-chunks.d.ts +33 -3
  447. package/dist/snapshot-chunks.d.ts.map +1 -1
  448. package/dist/snapshot-chunks.js +136 -55
  449. package/dist/snapshot-chunks.js.map +1 -1
  450. package/dist/sqlite/index.d.ts +53 -0
  451. package/dist/sqlite/index.d.ts.map +1 -0
  452. package/dist/sqlite/index.js +795 -0
  453. package/dist/sqlite/index.js.map +1 -0
  454. package/dist/sqlite3.d.ts +22 -0
  455. package/dist/sqlite3.d.ts.map +1 -0
  456. package/dist/sqlite3.js +99 -0
  457. package/dist/sqlite3.js.map +1 -0
  458. package/dist/stats.d.ts +8 -0
  459. package/dist/stats.d.ts.map +1 -1
  460. package/dist/stats.js +45 -13
  461. package/dist/stats.js.map +1 -1
  462. package/dist/subscriptions/cache.d.ts +3 -0
  463. package/dist/subscriptions/cache.d.ts.map +1 -1
  464. package/dist/subscriptions/cache.js +44 -0
  465. package/dist/subscriptions/cache.js.map +1 -1
  466. package/dist/subscriptions/index.js +2 -2
  467. package/dist/subscriptions/resolve.d.ts +3 -1
  468. package/dist/subscriptions/resolve.d.ts.map +1 -1
  469. package/dist/subscriptions/resolve.js +91 -52
  470. package/dist/subscriptions/resolve.js.map +1 -1
  471. package/dist/sync.d.ts +3 -0
  472. package/dist/sync.d.ts.map +1 -1
  473. package/dist/sync.js +9 -5
  474. package/dist/sync.js.map +1 -1
  475. package/package.json +297 -8
  476. package/src/auth-leases.ts +649 -0
  477. package/src/better-sqlite3.ts +35 -0
  478. package/src/blobs/access.ts +244 -0
  479. package/src/blobs/adapters/database.ts +125 -43
  480. package/src/blobs/index.ts +1 -0
  481. package/src/blobs/manager.ts +366 -65
  482. package/src/blobs/migrate.ts +16 -8
  483. package/src/blobs/types.ts +4 -0
  484. package/src/bun-sqlite-ambient.d.ts +19 -0
  485. package/src/bun-sqlite.ts +27 -0
  486. package/src/clients.ts +1 -0
  487. package/src/cloudflare/durable-object.ts +289 -0
  488. package/src/cloudflare/index.ts +22 -0
  489. package/src/cloudflare/r2.ts +526 -0
  490. package/src/cloudflare/scope-cache.ts +341 -0
  491. package/src/cloudflare/sentry.ts +230 -0
  492. package/src/cloudflare/worker.ts +77 -0
  493. package/src/commit-integrity.ts +371 -0
  494. package/src/crdt-yjs/index.ts +931 -0
  495. package/src/d1.ts +16 -0
  496. package/src/dialect/base.ts +71 -5
  497. package/src/dialect/helpers.ts +31 -0
  498. package/src/dialect/types.ts +47 -0
  499. package/src/encrypted-crdt.ts +786 -0
  500. package/src/errors.ts +22 -0
  501. package/src/filesystem/index.ts +262 -0
  502. package/src/handlers/collection.ts +78 -78
  503. package/src/handlers/create-handler.ts +540 -147
  504. package/src/handlers/types.ts +116 -0
  505. package/src/helpers/index.ts +2 -0
  506. package/src/helpers/scope-authorization.ts +27 -0
  507. package/src/helpers/scope-commit-index.ts +52 -0
  508. package/src/hono/api-key-auth.ts +177 -0
  509. package/src/hono/audit-redaction.ts +135 -0
  510. package/src/hono/blobs.ts +851 -0
  511. package/src/hono/console/gateway.ts +3046 -0
  512. package/src/hono/console/live-auth.ts +46 -0
  513. package/src/hono/console/route-descriptor.ts +22 -0
  514. package/src/hono/console/routes/api-keys.ts +721 -0
  515. package/src/hono/console/routes/clients.ts +447 -0
  516. package/src/hono/console/routes/commits.ts +1137 -0
  517. package/src/hono/console/routes/context.ts +956 -0
  518. package/src/hono/console/routes/events.ts +669 -0
  519. package/src/hono/console/routes/maintenance.ts +461 -0
  520. package/src/hono/console/routes/shared.ts +816 -0
  521. package/src/hono/console/routes/stats.ts +392 -0
  522. package/src/hono/console/routes/storage.ts +159 -0
  523. package/src/hono/console/routes.ts +146 -0
  524. package/src/hono/console/schema-errors.ts +23 -0
  525. package/src/hono/console/schemas.ts +914 -0
  526. package/src/hono/console/types.ts +223 -0
  527. package/src/hono/console/ui.ts +100 -0
  528. package/src/hono/create-server.ts +230 -0
  529. package/src/hono/errors.ts +50 -0
  530. package/src/hono/index.ts +54 -0
  531. package/src/hono/openapi.ts +139 -0
  532. package/src/hono/proxy/connection-manager.ts +340 -0
  533. package/src/hono/proxy/index.ts +8 -0
  534. package/src/hono/proxy/routes.ts +272 -0
  535. package/src/hono/rate-limit.ts +319 -0
  536. package/src/hono/realtime-sync-packs.ts +354 -0
  537. package/src/hono/routes/audit.ts +499 -0
  538. package/src/hono/routes/auth-leases.ts +119 -0
  539. package/src/hono/routes/combined.ts +583 -0
  540. package/src/hono/routes/context.ts +1629 -0
  541. package/src/hono/routes/health.ts +26 -0
  542. package/src/hono/routes/realtime.ts +808 -0
  543. package/src/hono/routes/shared.ts +1626 -0
  544. package/src/hono/routes/snapshots.ts +345 -0
  545. package/src/hono/routes.ts +68 -0
  546. package/src/hono/validation.ts +81 -0
  547. package/src/hono/websocket-origin.ts +131 -0
  548. package/src/hono/ws.ts +1134 -0
  549. package/src/index.ts +6 -0
  550. package/src/libsql.ts +51 -0
  551. package/src/neon.ts +28 -0
  552. package/src/notify.ts +224 -44
  553. package/src/pglite.ts +68 -0
  554. package/src/plugins/index.ts +1 -0
  555. package/src/plugins/types.ts +144 -0
  556. package/src/postgres/index.ts +1291 -0
  557. package/src/proxy/collection.ts +5 -27
  558. package/src/proxy/handler.ts +2 -2
  559. package/src/proxy/index.ts +0 -2
  560. package/src/proxy/oplog.ts +41 -10
  561. package/src/prune.ts +137 -36
  562. package/src/pull.ts +1504 -591
  563. package/src/push.ts +881 -443
  564. package/src/realtime/index.ts +2 -0
  565. package/src/realtime/types.ts +35 -0
  566. package/src/relay/bun-types.d.ts +50 -0
  567. package/src/relay/client-role/forward-engine.ts +355 -0
  568. package/src/relay/client-role/index.ts +9 -0
  569. package/src/relay/client-role/pull-engine.ts +329 -0
  570. package/src/relay/client-role/sequence-mapper.ts +201 -0
  571. package/src/relay/evaluation/relay-paths.ts +699 -0
  572. package/src/relay/evaluation/rust-boundary.ts +464 -0
  573. package/src/relay/index.ts +50 -0
  574. package/src/relay/migrate.ts +113 -0
  575. package/src/relay/mode-manager.ts +142 -0
  576. package/src/relay/realtime.ts +207 -0
  577. package/src/relay/relay.ts +431 -0
  578. package/src/relay/schema.ts +171 -0
  579. package/src/relay/server-role/index.ts +338 -0
  580. package/src/relay/server-role/pull.ts +43 -0
  581. package/src/relay/server-role/push.ts +164 -0
  582. package/src/s3/index.ts +346 -0
  583. package/src/schema.ts +141 -1
  584. package/src/service-worker/index.ts +773 -0
  585. package/src/snapshot-artifacts/sqlite-bun.ts +168 -0
  586. package/src/snapshot-artifacts.ts +896 -0
  587. package/src/snapshot-chunks/db-metadata.ts +131 -72
  588. package/src/snapshot-chunks/types.ts +2 -0
  589. package/src/snapshot-chunks.ts +194 -70
  590. package/src/sqlite/index.ts +1064 -0
  591. package/src/sqlite3.ts +137 -0
  592. package/src/stats.ts +76 -13
  593. package/src/subscriptions/cache.ts +58 -0
  594. package/src/subscriptions/resolve.ts +99 -58
  595. package/src/sync.ts +16 -6
  596. package/src/blobs/adapters/database.test.ts +0 -67
  597. package/src/notify.test.ts +0 -520
  598. package/src/proxy/handler.test.ts +0 -126
  599. package/src/proxy/mutation-detector.test.ts +0 -71
  600. package/src/snapshot-chunks/db-metadata.test.ts +0 -172
  601. package/src/subscriptions/resolve.test.ts +0 -180
package/src/hono/ws.ts ADDED
@@ -0,0 +1,1134 @@
1
+ /**
2
+ * @syncular/server/hono - WebSocket helpers for realtime sync wake-ups
3
+ *
4
+ * WebSockets deliver binary sync-pack deltas or explicit pull-required wake-ups.
5
+ * Also supports presence tracking for collaborative features.
6
+ */
7
+
8
+ import { RealtimeConnectionRegistry } from '@syncular/core';
9
+ import type { WSContext } from 'hono/ws';
10
+
11
+ /**
12
+ * Presence entry for a client connected to a scope
13
+ */
14
+ export interface PresenceEntry {
15
+ clientId: string;
16
+ actorId: string;
17
+ joinedAt: number;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+
21
+ export function createWebSocketConnectionOwnerKey(args: {
22
+ partitionId: string;
23
+ actorId: string;
24
+ clientId: string;
25
+ }): string {
26
+ return JSON.stringify([args.partitionId, args.actorId, args.clientId]);
27
+ }
28
+
29
+ /**
30
+ * Push response data sent back to the client over WS
31
+ */
32
+ export interface WsPushResponseData {
33
+ requestId: string;
34
+ ok: boolean;
35
+ status: string;
36
+ commitSeq?: number;
37
+ results: Array<{ opIndex: number; status: string; [k: string]: unknown }>;
38
+ }
39
+
40
+ export type WebSocketSyncPackEncoding = 'binary-sync-pack-v1';
41
+
42
+ export interface WsHelloData {
43
+ protocolVersion: 1;
44
+ sessionId: string;
45
+ shardKey: string;
46
+ actorId: string;
47
+ clientId: string;
48
+ transportPath: 'direct' | 'relay';
49
+ syncPackEncoding: WebSocketSyncPackEncoding | null;
50
+ cursor: number;
51
+ latestCursor: number;
52
+ scopeCount: number;
53
+ requiresSync: boolean;
54
+ }
55
+
56
+ export type WebSocketSyncReason =
57
+ | 'payload-too-large'
58
+ | 'reconnect-catchup'
59
+ | 'resync-required'
60
+ | 'server-wakeup';
61
+
62
+ export const DEFAULT_WS_SYNC_PACK_MAX_BYTES = 64 * 1024;
63
+
64
+ export interface WebSocketSyncMetadata {
65
+ reason?: WebSocketSyncReason;
66
+ requiresPull?: boolean;
67
+ droppedCount?: number;
68
+ payloadBytes?: number;
69
+ }
70
+
71
+ interface WebSocketReplayRecord {
72
+ scopeKeys: string[];
73
+ cursor: number;
74
+ syncPack?: Uint8Array;
75
+ payloadBytes?: number;
76
+ syncPackForConnection?: (
77
+ connection: WebSocketConnection
78
+ ) => Uint8Array | undefined;
79
+ hasSharedPayload: boolean;
80
+ }
81
+
82
+ export interface WebSocketRealtimeSubscription {
83
+ id: string;
84
+ table: string;
85
+ scopes: Record<string, string | string[]>;
86
+ scopeKeys: string[];
87
+ cursor: number;
88
+ verifiedRoot?: string | null;
89
+ }
90
+
91
+ /**
92
+ * WebSocket event data for sync notifications
93
+ */
94
+ export interface SyncWebSocketEvent {
95
+ /** Event type */
96
+ event:
97
+ | 'hello'
98
+ | 'sync'
99
+ | 'heartbeat'
100
+ | 'error'
101
+ | 'presence'
102
+ | 'push-response';
103
+ /** Data payload */
104
+ data: {
105
+ /** Realtime protocol version (for hello events) */
106
+ protocolVersion?: number;
107
+ /** Server-generated websocket session id (for hello events) */
108
+ sessionId?: string;
109
+ /** Stable sequencer/fanout shard key (for hello events) */
110
+ shardKey?: string;
111
+ /** New cursor position (for sync events) */
112
+ cursor?: number;
113
+ /** Latest server cursor known when the session was accepted */
114
+ latestCursor?: number;
115
+ /** Number of effective scope keys attached to this connection */
116
+ scopeCount?: number;
117
+ /** Whether the client should run catch-up sync */
118
+ requiresSync?: boolean;
119
+ /** Whether this notification intentionally requires HTTP pull recovery */
120
+ requiresPull?: boolean;
121
+ /** Number of realtime payload notifications skipped while waiting for ACK */
122
+ droppedCount?: number;
123
+ /** Why the server sent this sync notification */
124
+ reason?: WebSocketSyncReason;
125
+ /** Negotiated binary sync-pack encoding, if any */
126
+ syncPackEncoding?: WebSocketSyncPackEncoding | null;
127
+ /** Client/device id (for hello events) */
128
+ clientId?: string;
129
+ /** Transport path used by this connection */
130
+ transportPath?: 'direct' | 'relay';
131
+ /** Error message (for error events) */
132
+ error?: string;
133
+ /** Presence data (for presence events) */
134
+ presence?: {
135
+ action: 'join' | 'leave' | 'update' | 'snapshot';
136
+ scopeKey: string;
137
+ ownerKey?: string;
138
+ clientId?: string;
139
+ actorId?: string;
140
+ metadata?: Record<string, unknown>;
141
+ entries?: PresenceEntry[];
142
+ };
143
+ /** Push response data (for push-response events) */
144
+ requestId?: string;
145
+ ok?: boolean;
146
+ status?: string;
147
+ commitSeq?: number;
148
+ results?: Array<{ opIndex: number; status: string; [k: string]: unknown }>;
149
+ /** Timestamp */
150
+ timestamp: number;
151
+ };
152
+ }
153
+
154
+ /**
155
+ * WebSocket connection controller for managing active connections
156
+ */
157
+ export interface WebSocketConnection {
158
+ /** Send session/capability handshake metadata */
159
+ sendHello(data: WsHelloData): void;
160
+ /** Send a sync wake-up notification. */
161
+ sendSync(cursor: number, metadata?: WebSocketSyncMetadata): void;
162
+ /** Send an encoded binary sync-pack delta */
163
+ sendSyncPack(bytes: Uint8Array): void;
164
+ /** Send a heartbeat */
165
+ sendHeartbeat(): void;
166
+ /** Send a presence event */
167
+ sendPresence(data: {
168
+ action: 'join' | 'leave' | 'update' | 'snapshot';
169
+ scopeKey: string;
170
+ ownerKey?: string;
171
+ clientId?: string;
172
+ actorId?: string;
173
+ metadata?: Record<string, unknown>;
174
+ entries?: PresenceEntry[];
175
+ }): void;
176
+ /** Send a push response back to the client */
177
+ sendPushResponse(data: WsPushResponseData): void;
178
+ /** Send an error and close */
179
+ sendError(message: string): void;
180
+ /** Close the connection */
181
+ close(code?: number, reason?: string): void;
182
+ /** Whether the connection is still open */
183
+ isOpen: boolean;
184
+ /** Actor ID for this connection */
185
+ actorId: string;
186
+ /** Client/device identifier for this connection */
187
+ clientId: string;
188
+ /** Stable owner identity for this connection */
189
+ ownerKey: string;
190
+ /** Transport path used by this connection. */
191
+ transportPath: 'direct' | 'relay';
192
+ /** Optional binary sync-pack encoding negotiated by the client. */
193
+ syncPackEncoding?: WebSocketSyncPackEncoding | null;
194
+ }
195
+
196
+ function safeSend(ws: WSContext, message: string | ArrayBuffer): boolean {
197
+ try {
198
+ ws.send(message);
199
+ return true;
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ export function createRealtimeSessionId(): string {
206
+ const randomUuid = globalThis.crypto?.randomUUID?.();
207
+ if (randomUuid) return randomUuid;
208
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
209
+ }
210
+
211
+ export function createWebSocketConnection(
212
+ ws: WSContext,
213
+ args: {
214
+ actorId: string;
215
+ clientId: string;
216
+ ownerKey: string;
217
+ transportPath: 'direct' | 'relay';
218
+ syncPackEncoding?: WebSocketSyncPackEncoding | null;
219
+ }
220
+ ): WebSocketConnection {
221
+ let closed = false;
222
+
223
+ const connection: WebSocketConnection = {
224
+ get isOpen() {
225
+ if (closed) return false;
226
+ return ws.readyState === 1;
227
+ },
228
+ actorId: args.actorId,
229
+ clientId: args.clientId,
230
+ ownerKey: args.ownerKey,
231
+ transportPath: args.transportPath,
232
+ syncPackEncoding: args.syncPackEncoding ?? null,
233
+ sendHello(data: WsHelloData) {
234
+ if (!connection.isOpen) return;
235
+ const ok = safeSend(
236
+ ws,
237
+ JSON.stringify({
238
+ event: 'hello',
239
+ data: { ...data, timestamp: Date.now() },
240
+ })
241
+ );
242
+ if (!ok) closed = true;
243
+ },
244
+ sendSync(cursor: number, metadata?: WebSocketSyncMetadata) {
245
+ if (!connection.isOpen) return;
246
+ const payload: Record<string, unknown> = {
247
+ cursor,
248
+ timestamp: Date.now(),
249
+ };
250
+ if (metadata?.reason) {
251
+ payload.reason = metadata.reason;
252
+ }
253
+ if (metadata?.requiresPull) {
254
+ payload.requiresPull = true;
255
+ }
256
+ if (metadata?.droppedCount !== undefined) {
257
+ payload.droppedCount = metadata.droppedCount;
258
+ }
259
+ if (metadata?.payloadBytes !== undefined) {
260
+ payload.payloadBytes = metadata.payloadBytes;
261
+ }
262
+ const ok = safeSend(ws, JSON.stringify({ event: 'sync', data: payload }));
263
+ if (!ok) closed = true;
264
+ },
265
+ sendSyncPack(bytes: Uint8Array) {
266
+ if (!connection.isOpen) return;
267
+ const body = bytes.buffer.slice(
268
+ bytes.byteOffset,
269
+ bytes.byteOffset + bytes.byteLength
270
+ ) as ArrayBuffer;
271
+ const ok = safeSend(ws, body);
272
+ if (!ok) closed = true;
273
+ },
274
+ sendHeartbeat() {
275
+ if (!connection.isOpen) return;
276
+ const ok = safeSend(
277
+ ws,
278
+ JSON.stringify({ event: 'heartbeat', data: { timestamp: Date.now() } })
279
+ );
280
+ if (!ok) closed = true;
281
+ },
282
+ sendPresence(data: {
283
+ action: 'join' | 'leave' | 'update' | 'snapshot';
284
+ scopeKey: string;
285
+ ownerKey?: string;
286
+ clientId?: string;
287
+ actorId?: string;
288
+ metadata?: Record<string, unknown>;
289
+ entries?: PresenceEntry[];
290
+ }) {
291
+ if (!connection.isOpen) return;
292
+ const ok = safeSend(
293
+ ws,
294
+ JSON.stringify({
295
+ event: 'presence',
296
+ data: { presence: data, timestamp: Date.now() },
297
+ })
298
+ );
299
+ if (!ok) closed = true;
300
+ },
301
+ sendPushResponse(data: WsPushResponseData) {
302
+ if (!connection.isOpen) return;
303
+ const ok = safeSend(
304
+ ws,
305
+ JSON.stringify({
306
+ event: 'push-response',
307
+ data: { ...data, timestamp: Date.now() },
308
+ })
309
+ );
310
+ if (!ok) closed = true;
311
+ },
312
+ sendError(message: string) {
313
+ if (connection.isOpen) {
314
+ safeSend(
315
+ ws,
316
+ JSON.stringify({
317
+ event: 'error',
318
+ data: { error: message, timestamp: Date.now() },
319
+ })
320
+ );
321
+ }
322
+ connection.close(1011, 'server error');
323
+ },
324
+ close(code?: number, reason?: string) {
325
+ if (closed) return;
326
+ closed = true;
327
+ try {
328
+ ws.close(code, reason);
329
+ } catch {
330
+ // ignore
331
+ }
332
+ },
333
+ };
334
+
335
+ return connection;
336
+ }
337
+
338
+ /**
339
+ * Connection manager for tracking active WebSocket connections.
340
+ * Scope-key based notifications and presence tracking.
341
+ */
342
+ export class WebSocketConnectionManager {
343
+ private readonly registry: RealtimeConnectionRegistry<WebSocketConnection>;
344
+ private readonly replayWindowSize: number;
345
+ private readonly maxSyncPackBytes: number;
346
+ private readonly replayRecords: WebSocketReplayRecord[] = [];
347
+ private readonly subscriptionsByOwnerKey = new Map<
348
+ string,
349
+ WebSocketRealtimeSubscription[]
350
+ >();
351
+ private replayDroppedThroughCursor = -1;
352
+ private readonly flowStateByConnection = new WeakMap<
353
+ WebSocketConnection,
354
+ {
355
+ lastAckedCursor: number;
356
+ lastSentCursor: number;
357
+ inFlightCursors: number[];
358
+ resyncRequired: boolean;
359
+ droppedCount: number;
360
+ }
361
+ >();
362
+ private readonly maxInFlightSyncsPerConnection: number;
363
+
364
+ /**
365
+ * In-memory presence tracking by scope key.
366
+ * Map<scopeKey, Map<ownerKey, PresenceEntry>>
367
+ */
368
+ private presenceByScopeKey = new Map<string, Map<string, PresenceEntry>>();
369
+
370
+ /**
371
+ * Callback for presence changes - allows integration with SyncRealtimeBroadcaster
372
+ */
373
+ onPresenceChange?: (event: {
374
+ action: 'join' | 'leave' | 'update';
375
+ scopeKey: string;
376
+ ownerKey: string;
377
+ clientId: string;
378
+ actorId: string;
379
+ metadata?: Record<string, unknown>;
380
+ }) => void;
381
+
382
+ constructor(options?: {
383
+ heartbeatIntervalMs?: number;
384
+ maxInFlightSyncsPerConnection?: number;
385
+ maxSyncPackBytes?: number;
386
+ replayWindowSize?: number;
387
+ onPresenceChange?: WebSocketConnectionManager['onPresenceChange'];
388
+ }) {
389
+ this.onPresenceChange = options?.onPresenceChange;
390
+ this.maxInFlightSyncsPerConnection =
391
+ options?.maxInFlightSyncsPerConnection ?? 64;
392
+ this.maxSyncPackBytes = normalizeMaxSyncPackBytes(
393
+ options?.maxSyncPackBytes
394
+ );
395
+ this.replayWindowSize = normalizeReplayWindowSize(
396
+ options?.replayWindowSize
397
+ );
398
+ this.registry = new RealtimeConnectionRegistry({
399
+ heartbeatIntervalMs: options?.heartbeatIntervalMs,
400
+ onOwnerDisconnected: (ownerKey) => {
401
+ this.cleanupOwnerPresence(ownerKey);
402
+ },
403
+ });
404
+ }
405
+
406
+ recordAck(connection: WebSocketConnection, cursor: number): void {
407
+ if (!Number.isSafeInteger(cursor) || cursor < 0) return;
408
+ const state = this.getFlowState(connection);
409
+ if (cursor <= state.lastAckedCursor) return;
410
+ state.lastAckedCursor = cursor;
411
+ state.inFlightCursors = state.inFlightCursors.filter(
412
+ (inFlightCursor) => inFlightCursor > cursor
413
+ );
414
+ if (cursor >= state.lastSentCursor) {
415
+ state.inFlightCursors = [];
416
+ state.resyncRequired = false;
417
+ state.droppedCount = 0;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Register a connection for a client.
423
+ * Returns a cleanup function to unregister.
424
+ */
425
+ register(
426
+ connection: WebSocketConnection,
427
+ initialScopeKeys: string[] = []
428
+ ): () => void {
429
+ return this.registry.register(connection, initialScopeKeys);
430
+ }
431
+
432
+ /**
433
+ * Update the effective scopes for an already-connected client.
434
+ * If the client has no active connections, this is a no-op.
435
+ */
436
+ updateConnectionScopeKeys(ownerKey: string, scopeKeys: string[]): void {
437
+ this.registry.updateOwnerScopeKeys(ownerKey, scopeKeys);
438
+ }
439
+
440
+ /**
441
+ * Update the active pull subscriptions for an owner. Realtime binary deltas
442
+ * use these records to emit the same per-subscription integrity contract as
443
+ * HTTP pull responses.
444
+ */
445
+ updateConnectionSubscriptions(
446
+ ownerKey: string,
447
+ subscriptions: WebSocketRealtimeSubscription[]
448
+ ): void {
449
+ if (subscriptions.length === 0) {
450
+ this.subscriptionsByOwnerKey.delete(ownerKey);
451
+ this.registry.updateOwnerScopeKeys(ownerKey, []);
452
+ return;
453
+ }
454
+
455
+ this.subscriptionsByOwnerKey.set(
456
+ ownerKey,
457
+ subscriptions.map((subscription) => ({
458
+ ...subscription,
459
+ scopeKeys: [...subscription.scopeKeys],
460
+ }))
461
+ );
462
+ this.registry.updateOwnerScopeKeys(
463
+ ownerKey,
464
+ uniqueScopeKeys(
465
+ subscriptions.flatMap((subscription) => subscription.scopeKeys)
466
+ )
467
+ );
468
+ }
469
+
470
+ updateConnectionSubscriptionRoots(
471
+ ownerKey: string,
472
+ updates: Array<{
473
+ subscriptionId: string;
474
+ cursor: number;
475
+ verifiedRoot: string;
476
+ }>
477
+ ): void {
478
+ if (updates.length === 0) return;
479
+ const subscriptions = this.subscriptionsByOwnerKey.get(ownerKey);
480
+ if (!subscriptions || subscriptions.length === 0) return;
481
+
482
+ const updatesBySubscription = new Map(
483
+ updates.map((update) => [update.subscriptionId, update])
484
+ );
485
+ let changed = false;
486
+ const next = subscriptions.map((subscription) => {
487
+ const update = updatesBySubscription.get(subscription.id);
488
+ if (!update) return subscription;
489
+ changed = true;
490
+ return {
491
+ ...subscription,
492
+ cursor: Math.max(subscription.cursor, update.cursor),
493
+ verifiedRoot: update.verifiedRoot,
494
+ };
495
+ });
496
+ if (changed) {
497
+ this.subscriptionsByOwnerKey.set(ownerKey, next);
498
+ }
499
+ }
500
+
501
+ getConnectionSubscriptions(
502
+ ownerKey: string
503
+ ): readonly WebSocketRealtimeSubscription[] {
504
+ return this.subscriptionsByOwnerKey.get(ownerKey) ?? [];
505
+ }
506
+
507
+ getConnectionsForScopeKeys(
508
+ scopeKeys: Iterable<string>,
509
+ options?: { excludeClientIds?: readonly string[] }
510
+ ): WebSocketConnection[] {
511
+ const connections: WebSocketConnection[] = [];
512
+ this.registry.forEachConnectionInScopeKeys(
513
+ scopeKeys,
514
+ (connection) => connections.push(connection),
515
+ options
516
+ );
517
+ return connections;
518
+ }
519
+
520
+ /**
521
+ * Check whether a client is currently authorized/subscribed for a scope key.
522
+ */
523
+ isConnectionSubscribedToScopeKey(
524
+ ownerKey: string,
525
+ scopeKey: string
526
+ ): boolean {
527
+ return this.registry.isOwnerSubscribedToScopeKey(ownerKey, scopeKey);
528
+ }
529
+
530
+ getConnectionScopeKeys(ownerKey: string): readonly string[] {
531
+ return this.registry.getScopeKeysForOwner(ownerKey);
532
+ }
533
+
534
+ /**
535
+ * Replay recent websocket delta notifications for a reconnecting client.
536
+ * Returns false when the requested cursor range is no longer fully in memory,
537
+ * so the caller can send an explicit pull-required recovery frame.
538
+ */
539
+ replayScopeKeys(
540
+ connection: WebSocketConnection,
541
+ scopeKeys: string[],
542
+ fromCursor: number,
543
+ latestCursor: number
544
+ ): boolean {
545
+ if (
546
+ !Number.isSafeInteger(fromCursor) ||
547
+ !Number.isSafeInteger(latestCursor)
548
+ ) {
549
+ return false;
550
+ }
551
+ if (latestCursor <= fromCursor) {
552
+ return true;
553
+ }
554
+ if (scopeKeys.length === 0 || this.replayRecords.length === 0) {
555
+ return false;
556
+ }
557
+ if (fromCursor < this.replayDroppedThroughCursor) {
558
+ return false;
559
+ }
560
+
561
+ const scopeKeySet = new Set(scopeKeys);
562
+ const records = this.replayRecords.filter(
563
+ (record) =>
564
+ record.cursor > fromCursor &&
565
+ record.cursor <= latestCursor &&
566
+ record.scopeKeys.some((scopeKey) => scopeKeySet.has(scopeKey))
567
+ );
568
+ if (records.length === 0) {
569
+ return false;
570
+ }
571
+
572
+ const lastRecordCursor = records.reduce(
573
+ (cursor, record) => Math.max(cursor, record.cursor),
574
+ fromCursor
575
+ );
576
+ if (lastRecordCursor < latestCursor) {
577
+ return false;
578
+ }
579
+
580
+ for (const record of records) {
581
+ this.deliverSyncRecordToConnection(connection, record);
582
+ }
583
+ return true;
584
+ }
585
+
586
+ // =========================================================================
587
+ // Presence Tracking
588
+ // =========================================================================
589
+
590
+ /**
591
+ * Join presence for a scope key.
592
+ * Called when a client wants to be visible to others in a scope.
593
+ */
594
+ joinPresence(
595
+ ownerKey: string,
596
+ scopeKey: string,
597
+ metadata?: Record<string, unknown>
598
+ ): boolean {
599
+ const conns = this.registry.getConnectionsForOwner(ownerKey);
600
+ if (!conns || conns.size === 0) return false;
601
+ if (!this.isConnectionSubscribedToScopeKey(ownerKey, scopeKey))
602
+ return false;
603
+
604
+ const conn = conns.values().next().value;
605
+ if (!conn) return false;
606
+ const { actorId, clientId } = conn;
607
+
608
+ let scopePresence = this.presenceByScopeKey.get(scopeKey);
609
+ if (!scopePresence) {
610
+ scopePresence = new Map();
611
+ this.presenceByScopeKey.set(scopeKey, scopePresence);
612
+ }
613
+
614
+ const entry: PresenceEntry = {
615
+ clientId,
616
+ actorId,
617
+ joinedAt: Date.now(),
618
+ metadata,
619
+ };
620
+ scopePresence.set(ownerKey, entry);
621
+
622
+ this.broadcastPresenceEvent(scopeKey, {
623
+ action: 'join',
624
+ scopeKey,
625
+ ownerKey,
626
+ clientId,
627
+ actorId,
628
+ metadata,
629
+ });
630
+
631
+ this.onPresenceChange?.({
632
+ action: 'join',
633
+ scopeKey,
634
+ ownerKey,
635
+ clientId,
636
+ actorId,
637
+ metadata,
638
+ });
639
+
640
+ return true;
641
+ }
642
+
643
+ /**
644
+ * Leave presence for a scope key.
645
+ * Called when a client no longer wants to be visible in a scope.
646
+ */
647
+ leavePresence(ownerKey: string, scopeKey: string): boolean {
648
+ const scopePresence = this.presenceByScopeKey.get(scopeKey);
649
+ if (!scopePresence) return false;
650
+
651
+ const entry = scopePresence.get(ownerKey);
652
+ if (!entry) return false;
653
+
654
+ scopePresence.delete(ownerKey);
655
+ if (scopePresence.size === 0) {
656
+ this.presenceByScopeKey.delete(scopeKey);
657
+ }
658
+
659
+ this.broadcastPresenceEvent(scopeKey, {
660
+ action: 'leave',
661
+ scopeKey,
662
+ ownerKey,
663
+ clientId: entry.clientId,
664
+ actorId: entry.actorId,
665
+ });
666
+
667
+ this.onPresenceChange?.({
668
+ action: 'leave',
669
+ scopeKey,
670
+ ownerKey,
671
+ clientId: entry.clientId,
672
+ actorId: entry.actorId,
673
+ });
674
+
675
+ return true;
676
+ }
677
+
678
+ /**
679
+ * Update presence metadata for a client in a scope.
680
+ * Used to update what entity a user is viewing/editing.
681
+ */
682
+ updatePresenceMetadata(
683
+ ownerKey: string,
684
+ scopeKey: string,
685
+ metadata: Record<string, unknown>
686
+ ): boolean {
687
+ if (!this.isConnectionSubscribedToScopeKey(ownerKey, scopeKey))
688
+ return false;
689
+ const scopePresence = this.presenceByScopeKey.get(scopeKey);
690
+ if (!scopePresence) return false;
691
+
692
+ const entry = scopePresence.get(ownerKey);
693
+ if (!entry) return false;
694
+
695
+ entry.metadata = metadata;
696
+
697
+ this.broadcastPresenceEvent(scopeKey, {
698
+ action: 'update',
699
+ scopeKey,
700
+ ownerKey,
701
+ clientId: entry.clientId,
702
+ actorId: entry.actorId,
703
+ metadata,
704
+ });
705
+
706
+ this.onPresenceChange?.({
707
+ action: 'update',
708
+ scopeKey,
709
+ ownerKey,
710
+ clientId: entry.clientId,
711
+ actorId: entry.actorId,
712
+ metadata,
713
+ });
714
+
715
+ return true;
716
+ }
717
+
718
+ /**
719
+ * Get presence entries for a scope key.
720
+ */
721
+ getPresence(scopeKey: string): PresenceEntry[] {
722
+ const scopePresence = this.presenceByScopeKey.get(scopeKey);
723
+ if (!scopePresence) return [];
724
+ return Array.from(scopePresence.values());
725
+ }
726
+
727
+ /**
728
+ * Get presence for multiple scopes.
729
+ */
730
+ getPresenceMultiple(scopeKeys: string[]): Record<string, PresenceEntry[]> {
731
+ const result: Record<string, PresenceEntry[]> = {};
732
+ for (const scopeKey of scopeKeys) {
733
+ result[scopeKey] = this.getPresence(scopeKey);
734
+ }
735
+ return result;
736
+ }
737
+
738
+ /**
739
+ * Send current presence snapshot to a specific connection.
740
+ * Called when a client first subscribes to presence for a scope.
741
+ */
742
+ sendPresenceSnapshot(
743
+ connection: WebSocketConnection,
744
+ scopeKey: string
745
+ ): void {
746
+ const entries = this.getPresence(scopeKey);
747
+ connection.sendPresence({
748
+ action: 'snapshot',
749
+ scopeKey,
750
+ entries,
751
+ });
752
+ }
753
+
754
+ /**
755
+ * Handle a presence event from another server instance (via broadcaster).
756
+ * Updates local state and notifies local clients.
757
+ */
758
+ handleRemotePresenceEvent(event: {
759
+ action: 'join' | 'leave' | 'update';
760
+ scopeKey: string;
761
+ ownerKey?: string;
762
+ clientId: string;
763
+ actorId: string;
764
+ metadata?: Record<string, unknown>;
765
+ }): void {
766
+ const {
767
+ action,
768
+ scopeKey,
769
+ clientId,
770
+ actorId,
771
+ metadata,
772
+ ownerKey = createWebSocketConnectionOwnerKey({
773
+ partitionId: scopeKey.split(':', 1)[0] ?? 'default',
774
+ actorId,
775
+ clientId,
776
+ }),
777
+ } = event;
778
+
779
+ let scopePresence = this.presenceByScopeKey.get(scopeKey);
780
+
781
+ switch (action) {
782
+ case 'join': {
783
+ if (!scopePresence) {
784
+ scopePresence = new Map();
785
+ this.presenceByScopeKey.set(scopeKey, scopePresence);
786
+ }
787
+ scopePresence.set(ownerKey, {
788
+ clientId,
789
+ actorId,
790
+ joinedAt: Date.now(),
791
+ metadata,
792
+ });
793
+ break;
794
+ }
795
+ case 'leave': {
796
+ if (scopePresence) {
797
+ scopePresence.delete(ownerKey);
798
+ if (scopePresence.size === 0) {
799
+ this.presenceByScopeKey.delete(scopeKey);
800
+ }
801
+ }
802
+ break;
803
+ }
804
+ case 'update': {
805
+ if (scopePresence) {
806
+ const entry = scopePresence.get(ownerKey);
807
+ if (entry) {
808
+ entry.metadata = metadata;
809
+ }
810
+ }
811
+ break;
812
+ }
813
+ }
814
+
815
+ // Notify local clients
816
+ this.broadcastPresenceEvent(scopeKey, event);
817
+ }
818
+
819
+ /**
820
+ * Broadcast a presence event to all clients subscribed to a scope key.
821
+ */
822
+ private broadcastPresenceEvent(
823
+ scopeKey: string,
824
+ event: {
825
+ action: 'join' | 'leave' | 'update';
826
+ scopeKey: string;
827
+ ownerKey?: string;
828
+ clientId?: string;
829
+ actorId?: string;
830
+ metadata?: Record<string, unknown>;
831
+ }
832
+ ): void {
833
+ this.registry.forEachConnectionInScopeKeys(
834
+ [scopeKey],
835
+ (conn) => {
836
+ conn.sendPresence(event);
837
+ },
838
+ {
839
+ excludeClientIds: event.clientId ? [event.clientId] : undefined,
840
+ }
841
+ );
842
+ }
843
+
844
+ /**
845
+ * Clean up presence when an owner fully disconnects (all connections closed).
846
+ */
847
+ private cleanupOwnerPresence(ownerKey: string): void {
848
+ for (const [scopeKey, scopePresence] of this.presenceByScopeKey) {
849
+ const entry = scopePresence.get(ownerKey);
850
+ if (!entry) continue;
851
+
852
+ scopePresence.delete(ownerKey);
853
+ if (scopePresence.size === 0) {
854
+ this.presenceByScopeKey.delete(scopeKey);
855
+ }
856
+
857
+ this.broadcastPresenceEvent(scopeKey, {
858
+ action: 'leave',
859
+ scopeKey,
860
+ ownerKey,
861
+ clientId: entry.clientId,
862
+ actorId: entry.actorId,
863
+ });
864
+
865
+ this.onPresenceChange?.({
866
+ action: 'leave',
867
+ scopeKey,
868
+ ownerKey,
869
+ clientId: entry.clientId,
870
+ actorId: entry.actorId,
871
+ });
872
+ }
873
+ }
874
+
875
+ // =========================================================================
876
+ // Sync Notifications
877
+ // =========================================================================
878
+
879
+ /**
880
+ * Notify clients that new data is available for the given scopes.
881
+ * Dedupes connections that match multiple scopes.
882
+ */
883
+ notifyScopeKeys(
884
+ scopeKeys: string[],
885
+ cursor: number,
886
+ opts?: {
887
+ excludeClientIds?: string[];
888
+ syncPack?: Uint8Array;
889
+ syncPackForConnection?: (
890
+ connection: WebSocketConnection
891
+ ) => Uint8Array | undefined;
892
+ }
893
+ ): void {
894
+ const websocketSyncPack =
895
+ opts?.syncPack && opts.syncPack.byteLength <= this.maxSyncPackBytes
896
+ ? opts.syncPack
897
+ : undefined;
898
+ const replayRecord: WebSocketReplayRecord = {
899
+ scopeKeys: [...scopeKeys],
900
+ cursor,
901
+ syncPack: websocketSyncPack,
902
+ payloadBytes: opts?.syncPack?.byteLength,
903
+ syncPackForConnection: opts?.syncPackForConnection,
904
+ hasSharedPayload:
905
+ opts?.syncPack !== undefined ||
906
+ opts?.syncPackForConnection !== undefined,
907
+ };
908
+ this.rememberReplayRecord(replayRecord);
909
+
910
+ this.registry.forEachConnectionInScopeKeys(
911
+ scopeKeys,
912
+ (conn) => {
913
+ this.deliverSyncRecordToConnection(conn, replayRecord);
914
+ },
915
+ { excludeClientIds: opts?.excludeClientIds }
916
+ );
917
+ }
918
+
919
+ /**
920
+ * Notify all connected clients of a new cursor position.
921
+ * Used for external data changes that affect all clients regardless of scope.
922
+ */
923
+ notifyAllClients(cursor: number): void {
924
+ this.registry.forEachConnection((conn) => {
925
+ if (this.shouldSendResyncRequired(conn, cursor)) {
926
+ this.sendResyncRequired(conn, cursor);
927
+ return;
928
+ }
929
+ this.markSyncSent(conn, cursor);
930
+ conn.sendSync(cursor, {
931
+ reason: 'server-wakeup',
932
+ requiresPull: true,
933
+ });
934
+ });
935
+ }
936
+
937
+ /**
938
+ * Get the number of active connections for a client.
939
+ */
940
+ getConnectionCount(clientId: string): number {
941
+ return this.registry.getConnectionCount(clientId);
942
+ }
943
+
944
+ /**
945
+ * Get the number of active connections for one owner identity.
946
+ */
947
+ getScopedConnectionCount(ownerKey: string): number {
948
+ return this.registry.getScopedConnectionCount(ownerKey);
949
+ }
950
+
951
+ /**
952
+ * Get the current transport path for a client if connected.
953
+ */
954
+ getClientTransportPath(clientId: string): 'direct' | 'relay' | null {
955
+ const conns = this.registry.getConnectionsForClient(clientId);
956
+ if (!conns || conns.size === 0) {
957
+ return null;
958
+ }
959
+
960
+ for (const conn of conns) {
961
+ if (conn.transportPath === 'relay') {
962
+ return 'relay';
963
+ }
964
+ }
965
+
966
+ return 'direct';
967
+ }
968
+
969
+ /**
970
+ * Get total number of active connections.
971
+ */
972
+ getTotalConnections(): number {
973
+ return this.registry.getTotalConnections();
974
+ }
975
+
976
+ /**
977
+ * Close all connections for a client.
978
+ */
979
+ closeClientConnections(clientId: string): void {
980
+ this.registry.closeClientConnections(clientId, 1000, 'client closed');
981
+ }
982
+
983
+ /**
984
+ * Close all connections.
985
+ */
986
+ closeAll(): void {
987
+ this.registry.closeAll(1000, 'server shutdown');
988
+ this.presenceByScopeKey.clear();
989
+ }
990
+
991
+ private getFlowState(connection: WebSocketConnection): {
992
+ lastAckedCursor: number;
993
+ lastSentCursor: number;
994
+ inFlightCursors: number[];
995
+ resyncRequired: boolean;
996
+ droppedCount: number;
997
+ } {
998
+ let state = this.flowStateByConnection.get(connection);
999
+ if (!state) {
1000
+ state = {
1001
+ lastAckedCursor: -1,
1002
+ lastSentCursor: -1,
1003
+ inFlightCursors: [],
1004
+ resyncRequired: false,
1005
+ droppedCount: 0,
1006
+ };
1007
+ this.flowStateByConnection.set(connection, state);
1008
+ }
1009
+ return state;
1010
+ }
1011
+
1012
+ private shouldSendResyncRequired(
1013
+ connection: WebSocketConnection,
1014
+ cursor: number
1015
+ ): boolean {
1016
+ if (
1017
+ this.maxInFlightSyncsPerConnection <= 0 ||
1018
+ !Number.isSafeInteger(cursor) ||
1019
+ cursor < 0
1020
+ ) {
1021
+ return false;
1022
+ }
1023
+ const state = this.getFlowState(connection);
1024
+ if (cursor <= state.lastAckedCursor) return false;
1025
+ return (
1026
+ state.resyncRequired ||
1027
+ state.inFlightCursors.length >= this.maxInFlightSyncsPerConnection
1028
+ );
1029
+ }
1030
+
1031
+ private markSyncSent(connection: WebSocketConnection, cursor: number): void {
1032
+ if (
1033
+ this.maxInFlightSyncsPerConnection <= 0 ||
1034
+ !Number.isSafeInteger(cursor) ||
1035
+ cursor < 0
1036
+ ) {
1037
+ return;
1038
+ }
1039
+ const state = this.getFlowState(connection);
1040
+ if (cursor <= state.lastAckedCursor) return;
1041
+ state.lastSentCursor = Math.max(state.lastSentCursor, cursor);
1042
+ if (!state.inFlightCursors.includes(cursor)) {
1043
+ state.inFlightCursors.push(cursor);
1044
+ }
1045
+ }
1046
+
1047
+ private sendResyncRequired(
1048
+ connection: WebSocketConnection,
1049
+ cursor: number
1050
+ ): void {
1051
+ const state = this.getFlowState(connection);
1052
+ state.resyncRequired = true;
1053
+ state.lastSentCursor = Math.max(state.lastSentCursor, cursor);
1054
+ state.droppedCount += 1;
1055
+ connection.sendSync(cursor, {
1056
+ reason: 'resync-required',
1057
+ requiresPull: true,
1058
+ droppedCount: state.droppedCount,
1059
+ });
1060
+ }
1061
+
1062
+ private rememberReplayRecord(record: WebSocketReplayRecord): void {
1063
+ if (
1064
+ this.replayWindowSize <= 0 ||
1065
+ record.scopeKeys.length === 0 ||
1066
+ !Number.isSafeInteger(record.cursor) ||
1067
+ record.cursor < 0
1068
+ ) {
1069
+ return;
1070
+ }
1071
+ this.replayRecords.push(record);
1072
+ while (this.replayRecords.length > this.replayWindowSize) {
1073
+ const dropped = this.replayRecords.shift();
1074
+ if (dropped) {
1075
+ this.replayDroppedThroughCursor = Math.max(
1076
+ this.replayDroppedThroughCursor,
1077
+ dropped.cursor
1078
+ );
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ private deliverSyncRecordToConnection(
1084
+ connection: WebSocketConnection,
1085
+ record: WebSocketReplayRecord
1086
+ ): void {
1087
+ if (this.shouldSendResyncRequired(connection, record.cursor)) {
1088
+ this.sendResyncRequired(connection, record.cursor);
1089
+ return;
1090
+ }
1091
+
1092
+ const connectionSyncPack =
1093
+ record.syncPackForConnection?.(connection) ?? record.syncPack;
1094
+ if (
1095
+ connectionSyncPack &&
1096
+ connectionSyncPack.byteLength <= this.maxSyncPackBytes &&
1097
+ connection.syncPackEncoding === 'binary-sync-pack-v1'
1098
+ ) {
1099
+ this.markSyncSent(connection, record.cursor);
1100
+ connection.sendSyncPack(connectionSyncPack);
1101
+ return;
1102
+ }
1103
+
1104
+ this.markSyncSent(connection, record.cursor);
1105
+ connection.sendSync(record.cursor, {
1106
+ reason:
1107
+ connectionSyncPack || record.payloadBytes !== undefined
1108
+ ? 'payload-too-large'
1109
+ : 'server-wakeup',
1110
+ requiresPull: true,
1111
+ payloadBytes: connectionSyncPack?.byteLength ?? record.payloadBytes,
1112
+ });
1113
+ }
1114
+ }
1115
+
1116
+ function normalizeMaxSyncPackBytes(value: number | undefined): number {
1117
+ if (value === undefined) return DEFAULT_WS_SYNC_PACK_MAX_BYTES;
1118
+ if (!Number.isFinite(value)) return DEFAULT_WS_SYNC_PACK_MAX_BYTES;
1119
+ return Math.max(0, Math.floor(value));
1120
+ }
1121
+
1122
+ function normalizeReplayWindowSize(value: number | undefined): number {
1123
+ if (value === undefined) return 64;
1124
+ if (!Number.isFinite(value)) return 64;
1125
+ return Math.max(0, Math.floor(value));
1126
+ }
1127
+
1128
+ function uniqueScopeKeys(scopeKeys: Iterable<string>): string[] {
1129
+ const unique = new Set<string>();
1130
+ for (const scopeKey of scopeKeys) {
1131
+ if (scopeKey) unique.add(scopeKey);
1132
+ }
1133
+ return Array.from(unique);
1134
+ }