@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
@@ -0,0 +1,1629 @@
1
+ /**
2
+ * Shared per-instance context for the sync route modules.
3
+ *
4
+ * createSyncRoutesContext() contains the entire setup block of the former
5
+ * monolithic createSyncRoutes() factory (Hono app creation, CORS + rate-limit
6
+ * middleware, handler registry, websocket manager, realtime broadcaster
7
+ * bridge, console request-event recording, and the shared push/pull
8
+ * execution pipeline). Route modules destructure what they need from the
9
+ * returned context object.
10
+ */
11
+
12
+ import {
13
+ captureSyncException,
14
+ collectScopeVars,
15
+ countSyncMetric,
16
+ createSyncTimer,
17
+ logSyncEvent,
18
+ type ScopeValues,
19
+ SYNC_AUTH_LEASE_CODE_INVALID,
20
+ SYNC_AUTH_LEASE_CODE_MISSING,
21
+ type SyncCommit,
22
+ type SyncPullSubscriptionResponse,
23
+ type SyncPushCommitRequestSchema,
24
+ type SyncPushResponse,
25
+ } from '@syncular/core';
26
+ import type {
27
+ SqlFamily,
28
+ SyncCoreDb,
29
+ SyncRealtimeEvent,
30
+ } from '@syncular/server';
31
+ import {
32
+ coerceNumber,
33
+ createServerHandlerCollection,
34
+ createSyncRealtimeShardKey,
35
+ maybeCompactChanges,
36
+ maybePruneSync,
37
+ type PushCommitValidator,
38
+ pushCommit,
39
+ pushCommitBatch,
40
+ rowScopesAllowed,
41
+ validateAuthLeaseOperation,
42
+ verifyAuthLeaseToken,
43
+ } from '@syncular/server';
44
+ import type { Context, MiddlewareHandler } from 'hono';
45
+ import { Hono } from 'hono';
46
+ import { sql } from 'kysely';
47
+ import type { z } from 'zod';
48
+ import { summarizeAuditChange } from '../audit-redaction';
49
+ import { isBenignConsoleSchemaError } from '../console/schema-errors';
50
+ import { syncError, syncLimitExceeded } from '../errors';
51
+ import {
52
+ createRateLimiter,
53
+ DEFAULT_SYNC_RATE_LIMITS,
54
+ type SyncRateLimitConfig,
55
+ } from '../rate-limit';
56
+ import { notifyWebSocketConnectionsWithSyncPacks } from '../realtime-sync-packs';
57
+ import {
58
+ WebSocketConnectionManager,
59
+ type WebSocketRealtimeSubscription,
60
+ } from '../ws';
61
+ import {
62
+ type AuditChangeResponse,
63
+ type AuditDebugExportEvent,
64
+ applyPartitionToScopeKeys,
65
+ applySyncCorsHeaders,
66
+ type CreateSyncRoutesOptions,
67
+ createOpaqueId,
68
+ createSyncCorsOriginDeniedResponse,
69
+ DEFAULT_MAX_SNAPSHOT_ARTIFACT_RESPONSE_BYTES,
70
+ DEFAULT_MAX_SNAPSHOT_CHUNK_RESPONSE_BYTES,
71
+ DEFAULT_MAX_SYNC_BINARY_PACK_BYTES,
72
+ DEFAULT_MAX_SYNC_REQUEST_JSON_BYTES,
73
+ DEFAULT_REQUEST_PAYLOAD_SNAPSHOT_MAX_BYTES,
74
+ emitConsoleLiveEvent,
75
+ encodePayloadSnapshot,
76
+ firstPushErrorCode,
77
+ isAuthLeaseRefreshRetriable,
78
+ isMissingRequestEventsTableError,
79
+ isSyncJsonBodyLimitError,
80
+ normalizeRequestEventType,
81
+ normalizeResponseStatus,
82
+ normalizeSyncCorsConfig,
83
+ parseScopesSummary,
84
+ parseStoredAuditScopes,
85
+ type RequestPayloadSnapshot,
86
+ readClientIdHint,
87
+ readCommitScopeKeys,
88
+ readOptionalPositiveInteger,
89
+ readOriginHeader,
90
+ readPositiveInteger,
91
+ readRequestBodyBytesWithLimit,
92
+ readRequestContentLength,
93
+ readRequestId,
94
+ readTraceContext,
95
+ readTransportPath,
96
+ realtimeUnsubscribeMap,
97
+ type SyncAuthResult,
98
+ scopeValuesToScopeKeys,
99
+ selectRequiredAuditScopes,
100
+ syncValidationError,
101
+ type TraceContext,
102
+ wsConnectionManagerMap,
103
+ } from './shared';
104
+
105
+ export function createSyncRoutesContext<
106
+ DB extends SyncCoreDb = SyncCoreDb,
107
+ Auth extends SyncAuthResult = SyncAuthResult,
108
+ F extends SqlFamily = SqlFamily,
109
+ >(options: CreateSyncRoutesOptions<DB, Auth, F>) {
110
+ const routes = new Hono();
111
+ const config = options.sync ?? {};
112
+ const authLeaseRoutesConfig = options.authLeases;
113
+ routes.onError((error, c) => {
114
+ captureSyncException(error, {
115
+ event: 'sync.route.unhandled',
116
+ method: c.req.method,
117
+ path: c.req.path,
118
+ });
119
+ return c.text('Internal Server Error', 500);
120
+ });
121
+ const corsConfig = normalizeSyncCorsConfig(config.cors);
122
+ if (corsConfig) {
123
+ routes.use('*', async (c, next) => {
124
+ const origin = readOriginHeader(c);
125
+ const allowedOrigin = await corsConfig.resolveOrigin(origin, c);
126
+
127
+ if (origin && !allowedOrigin) {
128
+ return createSyncCorsOriginDeniedResponse(origin);
129
+ }
130
+
131
+ const resolvedOrigin = allowedOrigin ?? '*';
132
+
133
+ if (c.req.method === 'OPTIONS') {
134
+ const headers = new Headers();
135
+ applySyncCorsHeaders({
136
+ headers,
137
+ allowedOrigin: resolvedOrigin,
138
+ allowCredentials: corsConfig.allowCredentials,
139
+ allowHeaders: corsConfig.allowHeaders,
140
+ exposeHeaders: corsConfig.exposeHeaders,
141
+ allowMethods: corsConfig.allowMethods,
142
+ maxAgeSeconds: corsConfig.maxAgeSeconds,
143
+ });
144
+ return new Response(null, { status: 204, headers });
145
+ }
146
+
147
+ await next();
148
+ applySyncCorsHeaders({
149
+ headers: c.res.headers,
150
+ allowedOrigin: resolvedOrigin,
151
+ allowCredentials: corsConfig.allowCredentials,
152
+ allowHeaders: corsConfig.allowHeaders,
153
+ exposeHeaders: corsConfig.exposeHeaders,
154
+ allowMethods: corsConfig.allowMethods,
155
+ maxAgeSeconds: corsConfig.maxAgeSeconds,
156
+ });
157
+ return c.res;
158
+ });
159
+ }
160
+ const handlerRegistry = createServerHandlerCollection(options.handlers, {
161
+ snapshotBinary: options.snapshotBinary,
162
+ });
163
+ const binarySyncPackChangeRowEncoders = Object.fromEntries(
164
+ handlerRegistry.handlers.flatMap((handler) =>
165
+ handler.snapshotBinaryEncoder
166
+ ? [[handler.table, handler.snapshotBinaryEncoder]]
167
+ : []
168
+ )
169
+ );
170
+ const maxPullLimitCommits = config.maxPullLimitCommits ?? 1000;
171
+ const maxSubscriptionsPerPull = config.maxSubscriptionsPerPull ?? 200;
172
+ const maxPullLimitSnapshotRows = config.maxPullLimitSnapshotRows ?? 50000;
173
+ const maxPullMaxSnapshotPages = config.maxPullMaxSnapshotPages ?? 50;
174
+ const maxOperationsPerPush = config.maxOperationsPerPush ?? 200;
175
+ const maxSyncRequestJsonBytes = readPositiveInteger(
176
+ config.maxSyncRequestJsonBytes,
177
+ DEFAULT_MAX_SYNC_REQUEST_JSON_BYTES
178
+ );
179
+ const maxSyncBinaryPackBytes = readPositiveInteger(
180
+ config.maxSyncBinaryPackBytes,
181
+ DEFAULT_MAX_SYNC_BINARY_PACK_BYTES
182
+ );
183
+ const maxSnapshotChunkResponseBytes = readPositiveInteger(
184
+ config.maxSnapshotChunkResponseBytes,
185
+ DEFAULT_MAX_SNAPSHOT_CHUNK_RESPONSE_BYTES
186
+ );
187
+ const maxSnapshotArtifactResponseBytes = readPositiveInteger(
188
+ config.maxSnapshotArtifactResponseBytes,
189
+ DEFAULT_MAX_SNAPSHOT_ARTIFACT_RESPONSE_BYTES
190
+ );
191
+ const requiredSchemaVersion = readOptionalPositiveInteger(
192
+ config.requiredSchemaVersion
193
+ );
194
+ const latestSchemaVersion = readOptionalPositiveInteger(
195
+ config.latestSchemaVersion
196
+ );
197
+ const requestPayloadSnapshots = config.requestPayloadSnapshots;
198
+ const requestPayloadSnapshotsEnabled =
199
+ requestPayloadSnapshots?.enabled ??
200
+ requestPayloadSnapshots?.maxBytes !== undefined;
201
+ const pruneConfig = config.prune;
202
+ const compactConfig = config.compact;
203
+ const pruneMinIntervalMs = readPositiveInteger(
204
+ pruneConfig?.minIntervalMs,
205
+ 5 * 60 * 1000
206
+ );
207
+ const compactMinIntervalMs = readPositiveInteger(
208
+ compactConfig?.minIntervalMs,
209
+ 30 * 60 * 1000
210
+ );
211
+ const compactOptions = compactConfig?.options;
212
+ const consoleLiveEmitter = options.consoleLiveEmitter;
213
+ const shouldEmitConsoleLiveEvents = consoleLiveEmitter !== undefined;
214
+ const shouldRecordRequestEvents = shouldEmitConsoleLiveEvents;
215
+ const shouldCaptureRequestPayloadSnapshots =
216
+ shouldRecordRequestEvents && requestPayloadSnapshotsEnabled;
217
+ const requestPayloadSnapshotMaxBytes = readPositiveInteger(
218
+ requestPayloadSnapshots?.maxBytes,
219
+ DEFAULT_REQUEST_PAYLOAD_SNAPSHOT_MAX_BYTES
220
+ );
221
+ const consoleSchemaReadyBase = shouldRecordRequestEvents
222
+ ? (options.consoleSchemaReady ??
223
+ options.dialect.ensureConsoleSchema?.(options.db) ??
224
+ Promise.resolve())
225
+ : Promise.resolve();
226
+ const consoleSchemaReady = consoleSchemaReadyBase.catch((error) => {
227
+ if (isBenignConsoleSchemaError(error)) {
228
+ return;
229
+ }
230
+ logSyncEvent({
231
+ event: 'sync.console_schema_ready_failed',
232
+ error: error instanceof Error ? error.message : String(error),
233
+ });
234
+ throw error;
235
+ });
236
+ const authCache = new WeakMap<Context, Promise<Auth | null>>();
237
+ const getAuth = (c: Context): Promise<Auth | null> => {
238
+ const cached = authCache.get(c);
239
+ if (cached) return cached;
240
+ const pending = options.authenticate(c);
241
+ authCache.set(c, pending);
242
+ return pending;
243
+ };
244
+ type AuditScopeConfig = {
245
+ scopes: ScopeValues;
246
+ requiredScopeKeys: string[];
247
+ };
248
+ const createAuditScopeResolver = (auth: Auth) => {
249
+ const auditScopesByTable = new Map<
250
+ string,
251
+ Promise<AuditScopeConfig | null>
252
+ >();
253
+
254
+ return async (table: string): Promise<AuditScopeConfig | null> => {
255
+ const cached = auditScopesByTable.get(table);
256
+ if (cached) return cached;
257
+
258
+ const pending = (async () => {
259
+ const handler = handlerRegistry.byTable.get(table);
260
+ if (!handler) return null;
261
+
262
+ let allowedScopes: ScopeValues;
263
+ try {
264
+ allowedScopes = await handler.resolveScopes({
265
+ db: options.db,
266
+ actorId: auth.actorId,
267
+ auth,
268
+ });
269
+ } catch {
270
+ return null;
271
+ }
272
+
273
+ const scopes = selectRequiredAuditScopes(
274
+ handler.scopePatterns,
275
+ allowedScopes
276
+ );
277
+ if (!scopes) return null;
278
+
279
+ return {
280
+ scopes,
281
+ requiredScopeKeys: Array.from(
282
+ collectScopeVars(handler.scopePatterns)
283
+ ),
284
+ };
285
+ })();
286
+
287
+ auditScopesByTable.set(table, pending);
288
+ return pending;
289
+ };
290
+ };
291
+ const readVisibleAuditChanges = async (args: {
292
+ auth: Auth;
293
+ partitionId: string;
294
+ commitSeqs: readonly number[];
295
+ }): Promise<Map<number, AuditChangeResponse[]>> => {
296
+ const uniqueCommitSeqs = Array.from(new Set(args.commitSeqs));
297
+ if (uniqueCommitSeqs.length === 0) return new Map();
298
+
299
+ const changesResult = await sql<{
300
+ commit_seq: number;
301
+ change_id: number;
302
+ table: string;
303
+ row_id: string;
304
+ op: 'upsert' | 'delete';
305
+ row_json: unknown | null;
306
+ row_version: number | null;
307
+ scopes: unknown;
308
+ }>`
309
+ select
310
+ ${sql.ref('commit_seq')} as ${sql.ref('commit_seq')},
311
+ ${sql.ref('change_id')} as ${sql.ref('change_id')},
312
+ ${sql.ref('table')} as ${sql.ref('table')},
313
+ ${sql.ref('row_id')} as ${sql.ref('row_id')},
314
+ ${sql.ref('op')} as ${sql.ref('op')},
315
+ ${sql.ref('row_json')} as ${sql.ref('row_json')},
316
+ ${sql.ref('row_version')} as ${sql.ref('row_version')},
317
+ ${sql.ref('scopes')} as ${sql.ref('scopes')}
318
+ from ${sql.table('sync_changes')}
319
+ where ${sql.ref('partition_id')} = ${args.partitionId}
320
+ and ${sql.ref('commit_seq')} in (${sql.join(uniqueCommitSeqs)})
321
+ order by ${sql.ref('commit_seq')} asc, ${sql.ref('change_id')} asc
322
+ `.execute(options.db);
323
+
324
+ const resolveAuditScopesForTable = createAuditScopeResolver(args.auth);
325
+ const changesByCommitSeq = new Map<number, AuditChangeResponse[]>();
326
+ for (const change of changesResult.rows) {
327
+ const scopeConfig = await resolveAuditScopesForTable(change.table);
328
+ if (!scopeConfig) continue;
329
+
330
+ const rowScopes = parseStoredAuditScopes(change.scopes);
331
+ if (
332
+ !rowScopesAllowed({
333
+ rowScopes,
334
+ allowedScopes: scopeConfig.scopes,
335
+ requiredScopeKeys: scopeConfig.requiredScopeKeys,
336
+ })
337
+ ) {
338
+ continue;
339
+ }
340
+
341
+ const commitSeq = Number(change.commit_seq);
342
+ const summary = summarizeAuditChange({
343
+ table: change.table,
344
+ op: change.op,
345
+ rowJson: change.row_json,
346
+ scopes: change.scopes,
347
+ });
348
+ const changes = changesByCommitSeq.get(commitSeq) ?? [];
349
+ changes.push({
350
+ changeId: Number(change.change_id),
351
+ table: change.table,
352
+ rowId: change.row_id,
353
+ op: change.op,
354
+ rowVersion:
355
+ change.row_version === null ? null : Number(change.row_version),
356
+ ...summary,
357
+ });
358
+ changesByCommitSeq.set(commitSeq, changes);
359
+ }
360
+
361
+ return changesByCommitSeq;
362
+ };
363
+ const readAuditDebugRequestEvents = async (args: {
364
+ auth: Auth;
365
+ partitionId: string;
366
+ limit: number;
367
+ from?: string;
368
+ to?: string;
369
+ }): Promise<{
370
+ events: AuditDebugExportEvent[];
371
+ truncated: boolean;
372
+ }> => {
373
+ const whereClauses = [
374
+ sql`partition_id = ${args.partitionId}`,
375
+ sql`actor_id = ${args.auth.actorId}`,
376
+ ];
377
+ if (args.from) {
378
+ whereClauses.push(sql`created_at >= ${args.from}`);
379
+ }
380
+ if (args.to) {
381
+ whereClauses.push(sql`created_at <= ${args.to}`);
382
+ }
383
+
384
+ try {
385
+ const result = await sql<{
386
+ event_id: number | string | null;
387
+ partition_id: string | null;
388
+ request_id: string | null;
389
+ trace_id: string | null;
390
+ span_id: string | null;
391
+ event_type: string | null;
392
+ sync_path: string | null;
393
+ transport_path: string | null;
394
+ actor_id: string | null;
395
+ client_id: string | null;
396
+ status_code: number | string | null;
397
+ outcome: string | null;
398
+ response_status: string | null;
399
+ error_code: string | null;
400
+ duration_ms: number | string | null;
401
+ commit_seq: number | string | null;
402
+ operation_count: number | string | null;
403
+ row_count: number | string | null;
404
+ subscription_count: number | string | null;
405
+ scopes_summary: unknown | null;
406
+ tables: unknown;
407
+ created_at: string | null;
408
+ }>`
409
+ select
410
+ event_id,
411
+ partition_id,
412
+ request_id,
413
+ trace_id,
414
+ span_id,
415
+ event_type,
416
+ sync_path,
417
+ transport_path,
418
+ actor_id,
419
+ client_id,
420
+ status_code,
421
+ outcome,
422
+ response_status,
423
+ error_code,
424
+ duration_ms,
425
+ commit_seq,
426
+ operation_count,
427
+ row_count,
428
+ subscription_count,
429
+ scopes_summary,
430
+ tables,
431
+ created_at
432
+ from ${sql.table('sync_request_events')}
433
+ where ${sql.join(whereClauses, sql` and `)}
434
+ order by created_at desc
435
+ limit ${args.limit + 1}
436
+ `.execute(options.db);
437
+
438
+ const selectedRows = result.rows.slice(0, args.limit);
439
+ return {
440
+ truncated: result.rows.length > args.limit,
441
+ events: selectedRows.map((row) => ({
442
+ eventId: coerceNumber(row.event_id) ?? 0,
443
+ partitionId: row.partition_id ?? args.partitionId,
444
+ requestId: row.request_id ?? '',
445
+ traceId: row.trace_id ?? null,
446
+ spanId: row.span_id ?? null,
447
+ eventType: normalizeRequestEventType(row.event_type),
448
+ syncPath: row.sync_path === 'ws-push' ? 'ws-push' : 'http-combined',
449
+ transportPath: row.transport_path === 'relay' ? 'relay' : 'direct',
450
+ actorId: row.actor_id ?? '',
451
+ clientId: row.client_id ?? '',
452
+ statusCode: coerceNumber(row.status_code) ?? 0,
453
+ outcome: row.outcome ?? '',
454
+ responseStatus: row.response_status ?? 'unknown',
455
+ errorCode: row.error_code ?? null,
456
+ durationMs: coerceNumber(row.duration_ms) ?? 0,
457
+ commitSeq: coerceNumber(row.commit_seq),
458
+ operationCount: coerceNumber(row.operation_count),
459
+ rowCount: coerceNumber(row.row_count),
460
+ subscriptionCount: coerceNumber(row.subscription_count),
461
+ scopesSummary: parseScopesSummary(row.scopes_summary),
462
+ tables: options.dialect.dbToArray(row.tables),
463
+ createdAt: row.created_at ?? '',
464
+ })),
465
+ };
466
+ } catch (error) {
467
+ if (isMissingRequestEventsTableError(error)) {
468
+ return { events: [], truncated: false };
469
+ }
470
+ throw error;
471
+ }
472
+ };
473
+ const validateAuthLeaseCommit: PushCommitValidator<DB, Auth> | undefined =
474
+ authLeaseRoutesConfig && authLeaseRoutesConfig.enabled !== false
475
+ ? async ({ trx, request, auth }) => {
476
+ const authLease = request.authLease;
477
+ if (!authLease) return null;
478
+ if (!authLease.leaseToken) {
479
+ return {
480
+ opIndex: 0,
481
+ status: 'error',
482
+ error: 'Auth lease token is missing',
483
+ code: SYNC_AUTH_LEASE_CODE_MISSING,
484
+ retriable: true,
485
+ };
486
+ }
487
+
488
+ const verification = await verifyAuthLeaseToken({
489
+ token: authLease.leaseToken,
490
+ publicKey: authLeaseRoutesConfig.publicKey,
491
+ nowMs: authLeaseRoutesConfig.nowMs?.(),
492
+ expectedIssuer: authLeaseRoutesConfig.issuer,
493
+ expectedAudience: authLeaseRoutesConfig.audience,
494
+ expectedSchemaVersion: request.schemaVersion,
495
+ });
496
+ if (!verification.ok) {
497
+ return {
498
+ opIndex: 0,
499
+ status: 'error',
500
+ error: verification.message,
501
+ code: verification.code,
502
+ retriable: isAuthLeaseRefreshRetriable(verification.code),
503
+ };
504
+ }
505
+ const verifiedPayload = verification.payload;
506
+ if (
507
+ verifiedPayload.actorId !== auth.actorId ||
508
+ verifiedPayload.leaseId !== authLease.leaseId
509
+ ) {
510
+ return {
511
+ opIndex: 0,
512
+ status: 'error',
513
+ error: 'Auth lease does not match the current replay context',
514
+ code: SYNC_AUTH_LEASE_CODE_INVALID,
515
+ retriable: true,
516
+ };
517
+ }
518
+ for (
519
+ let opIndex = 0;
520
+ opIndex < request.operations.length;
521
+ opIndex += 1
522
+ ) {
523
+ const operation = request.operations[opIndex]!;
524
+ const handler = handlerRegistry.byTable.get(operation.table);
525
+ if (!handler) {
526
+ return {
527
+ opIndex,
528
+ status: 'error',
529
+ error: 'Auth lease operation table is not registered',
530
+ code: SYNC_AUTH_LEASE_CODE_INVALID,
531
+ retriable: false,
532
+ };
533
+ }
534
+ const operationError = await validateAuthLeaseOperation({
535
+ db: trx,
536
+ auth,
537
+ handler,
538
+ payload: verifiedPayload,
539
+ operation,
540
+ opIndex,
541
+ });
542
+ if (operationError) return operationError;
543
+ }
544
+ return null;
545
+ }
546
+ : undefined;
547
+
548
+ type SyncJsonReadFailure = {
549
+ statusCode: number;
550
+ errorCode: string;
551
+ errorMessage: string;
552
+ };
553
+ type SyncJsonReadResult =
554
+ | { ok: true; value: unknown }
555
+ | { ok: false; response: Response; failure?: SyncJsonReadFailure };
556
+ const syncJsonBodyCache = new WeakMap<Request, Promise<SyncJsonReadResult>>();
557
+ const readLimitedSyncJsonBody = (c: Context): Promise<SyncJsonReadResult> => {
558
+ const cached = syncJsonBodyCache.get(c.req.raw);
559
+ if (cached) return cached;
560
+
561
+ const pending = (async (): Promise<SyncJsonReadResult> => {
562
+ const declaredLength = readRequestContentLength(c);
563
+ if (declaredLength === 'invalid') {
564
+ return {
565
+ ok: false,
566
+ response: syncError(
567
+ c,
568
+ 400,
569
+ 'sync.invalid_request',
570
+ 'Invalid Content-Length'
571
+ ),
572
+ };
573
+ }
574
+ if (
575
+ typeof declaredLength === 'number' &&
576
+ declaredLength > maxSyncRequestJsonBytes
577
+ ) {
578
+ return {
579
+ ok: false,
580
+ response: syncLimitExceeded(c, {
581
+ limit: 'maxSyncRequestJsonBytes',
582
+ observed: declaredLength,
583
+ max: maxSyncRequestJsonBytes,
584
+ }),
585
+ failure: {
586
+ statusCode: 413,
587
+ errorCode: 'runtime.limit_exceeded',
588
+ errorMessage: 'maxSyncRequestJsonBytes exceeded',
589
+ },
590
+ };
591
+ }
592
+
593
+ let bytes: Uint8Array;
594
+ try {
595
+ bytes = await readRequestBodyBytesWithLimit(c.req.raw, {
596
+ maxBytes: maxSyncRequestJsonBytes,
597
+ limit: 'maxSyncRequestJsonBytes',
598
+ });
599
+ } catch (error) {
600
+ if (isSyncJsonBodyLimitError(error)) {
601
+ return {
602
+ ok: false,
603
+ response: syncLimitExceeded(c, {
604
+ limit: error.limit,
605
+ observed: error.observed,
606
+ max: error.max,
607
+ }),
608
+ failure: {
609
+ statusCode: 413,
610
+ errorCode: 'runtime.limit_exceeded',
611
+ errorMessage: `${error.limit} exceeded`,
612
+ },
613
+ };
614
+ }
615
+ throw error;
616
+ }
617
+
618
+ try {
619
+ const text = new TextDecoder().decode(bytes);
620
+ return { ok: true, value: JSON.parse(text) };
621
+ } catch {
622
+ return {
623
+ ok: false,
624
+ response: syncValidationError(c, 'json', [
625
+ { message: 'Invalid JSON body.', path: [] },
626
+ ]),
627
+ };
628
+ }
629
+ })();
630
+
631
+ syncJsonBodyCache.set(c.req.raw, pending);
632
+ return pending;
633
+ };
634
+
635
+ // -------------------------------------------------------------------------
636
+ // Optional WebSocket manager (scope-key based wake-ups)
637
+ // -------------------------------------------------------------------------
638
+
639
+ const websocketConfig = config.websocket;
640
+ if (websocketConfig?.enabled && !websocketConfig.upgradeWebSocket) {
641
+ throw new Error(
642
+ 'sync.websocket.enabled requires sync.websocket.upgradeWebSocket'
643
+ );
644
+ }
645
+
646
+ const wsConnectionManager = websocketConfig?.enabled
647
+ ? (options.wsConnectionManager ??
648
+ new WebSocketConnectionManager({
649
+ heartbeatIntervalMs: websocketConfig.heartbeatIntervalMs ?? 30_000,
650
+ maxInFlightSyncsPerConnection:
651
+ websocketConfig.maxInFlightSyncsPerConnection ?? 64,
652
+ maxSyncPackBytes: websocketConfig.maxSyncPackBytes,
653
+ replayWindowSize: websocketConfig.replayWindowSize ?? 64,
654
+ }))
655
+ : null;
656
+
657
+ if (wsConnectionManager) {
658
+ wsConnectionManagerMap.set(routes, wsConnectionManager);
659
+ }
660
+
661
+ // -------------------------------------------------------------------------
662
+ // Multi-instance realtime broadcaster (optional)
663
+ // -------------------------------------------------------------------------
664
+
665
+ const realtimeBroadcaster = config.realtime?.broadcaster ?? null;
666
+ const instanceId =
667
+ config.realtime?.instanceId ??
668
+ (typeof crypto !== 'undefined' && 'randomUUID' in crypto
669
+ ? crypto.randomUUID()
670
+ : `${Date.now()}-${Math.random().toString(16).slice(2)}`);
671
+ const loggedAsyncFailureKeys = new Set<string>();
672
+ const logAsyncFailureOnce = (
673
+ key: string,
674
+ event: {
675
+ event: string;
676
+ error: string;
677
+ [key: string]: unknown;
678
+ }
679
+ ) => {
680
+ if (loggedAsyncFailureKeys.has(key)) return;
681
+ loggedAsyncFailureKeys.add(key);
682
+ logSyncEvent(event);
683
+ };
684
+
685
+ if (compactConfig && !compactOptions) {
686
+ logSyncEvent({
687
+ event: 'sync.compact_auto_disabled',
688
+ reason: 'missing_options',
689
+ });
690
+ }
691
+
692
+ const triggerAutoMaintenance = (ctx: {
693
+ actorId: string;
694
+ clientId: string;
695
+ partitionId: string;
696
+ }): void => {
697
+ if (!pruneConfig && !compactConfig) return;
698
+
699
+ void (async () => {
700
+ if (pruneConfig) {
701
+ try {
702
+ const deleted = await maybePruneSync(options.db, {
703
+ minIntervalMs: pruneMinIntervalMs,
704
+ options: pruneConfig.options,
705
+ });
706
+ if (deleted > 0) {
707
+ logSyncEvent({
708
+ event: 'sync.prune_auto',
709
+ userId: ctx.actorId,
710
+ clientId: ctx.clientId,
711
+ partitionId: ctx.partitionId,
712
+ deletedCount: deleted,
713
+ });
714
+ }
715
+ } catch (error) {
716
+ logAsyncFailureOnce('sync.prune_auto_failed', {
717
+ event: 'sync.prune_auto_failed',
718
+ userId: ctx.actorId,
719
+ clientId: ctx.clientId,
720
+ partitionId: ctx.partitionId,
721
+ error: error instanceof Error ? error.message : String(error),
722
+ });
723
+ }
724
+ }
725
+
726
+ if (compactConfig && compactOptions) {
727
+ try {
728
+ const deleted = await maybeCompactChanges(options.db, {
729
+ dialect: options.dialect,
730
+ minIntervalMs: compactMinIntervalMs,
731
+ options: compactOptions,
732
+ });
733
+ if (deleted > 0) {
734
+ logSyncEvent({
735
+ event: 'sync.compact_auto',
736
+ userId: ctx.actorId,
737
+ clientId: ctx.clientId,
738
+ partitionId: ctx.partitionId,
739
+ deletedCount: deleted,
740
+ });
741
+ }
742
+ } catch (error) {
743
+ logAsyncFailureOnce('sync.compact_auto_failed', {
744
+ event: 'sync.compact_auto_failed',
745
+ userId: ctx.actorId,
746
+ clientId: ctx.clientId,
747
+ partitionId: ctx.partitionId,
748
+ error: error instanceof Error ? error.message : String(error),
749
+ });
750
+ }
751
+ }
752
+ })();
753
+ };
754
+
755
+ if (wsConnectionManager && realtimeBroadcaster) {
756
+ const unsubscribe = realtimeBroadcaster.subscribe(
757
+ (event: SyncRealtimeEvent) => {
758
+ void handleRealtimeEvent(event).catch((error) => {
759
+ logAsyncFailureOnce('sync.realtime.broadcast_delivery_failed', {
760
+ event: 'sync.realtime.broadcast_delivery_failed',
761
+ error: error instanceof Error ? error.message : String(error),
762
+ sourceEventType: event.type,
763
+ });
764
+ });
765
+ }
766
+ );
767
+
768
+ realtimeUnsubscribeMap.set(routes, unsubscribe);
769
+ }
770
+
771
+ // -------------------------------------------------------------------------
772
+ // Request event recording (for console inspector)
773
+ // -------------------------------------------------------------------------
774
+
775
+ type RequestEvent = {
776
+ partitionId: string;
777
+ requestId: string;
778
+ traceId?: string | null;
779
+ spanId?: string | null;
780
+ eventType: 'sync' | 'push' | 'pull';
781
+ syncPath: 'http-combined' | 'ws-push';
782
+ actorId: string;
783
+ clientId: string;
784
+ transportPath: 'direct' | 'relay';
785
+ statusCode: number;
786
+ outcome: string;
787
+ responseStatus: string;
788
+ durationMs: number;
789
+ errorCode?: string | null;
790
+ commitSeq?: number | null;
791
+ operationCount?: number | null;
792
+ rowCount?: number | null;
793
+ subscriptionCount?: number | null;
794
+ scopesSummary?: Record<string, string | string[]> | null;
795
+ responseSummary?: Record<string, unknown> | null;
796
+ tables?: string[];
797
+ errorMessage?: string | null;
798
+ payloadRef?: string | null;
799
+ payloadSnapshot?: RequestPayloadSnapshot | null;
800
+ };
801
+
802
+ type RealtimeConsoleEvent = {
803
+ partitionId: string;
804
+ actorId: string;
805
+ clientId: string;
806
+ transportPath: 'direct' | 'relay';
807
+ eventType:
808
+ | 'connected'
809
+ | 'disconnected'
810
+ | 'error'
811
+ | 'pull_required'
812
+ | 'ack'
813
+ | 'rejected';
814
+ reason?: string | null;
815
+ cursor?: number | null;
816
+ latestCursor?: number | null;
817
+ commitSeq?: number | null;
818
+ scopeCount?: number | null;
819
+ skippedCount?: number | null;
820
+ syncPackEncoding?: string | null;
821
+ };
822
+
823
+ const recordRequestEvent = async (event: RequestEvent) => {
824
+ let payloadRef = event.payloadRef ?? null;
825
+ if (event.payloadSnapshot) {
826
+ const nextPayloadRef = payloadRef ?? createOpaqueId('payload');
827
+ const nowIso = new Date().toISOString();
828
+
829
+ try {
830
+ await sql`
831
+ INSERT INTO sync_request_payloads (
832
+ payload_ref, partition_id, request_payload, response_payload, created_at
833
+ ) VALUES (
834
+ ${nextPayloadRef}, ${event.partitionId},
835
+ ${encodePayloadSnapshot(
836
+ event.payloadSnapshot.request,
837
+ requestPayloadSnapshotMaxBytes
838
+ )},
839
+ ${encodePayloadSnapshot(
840
+ event.payloadSnapshot.response,
841
+ requestPayloadSnapshotMaxBytes
842
+ )},
843
+ ${nowIso}
844
+ )
845
+ ON CONFLICT (payload_ref) DO UPDATE SET
846
+ partition_id = EXCLUDED.partition_id,
847
+ request_payload = EXCLUDED.request_payload,
848
+ response_payload = EXCLUDED.response_payload,
849
+ created_at = EXCLUDED.created_at
850
+ `.execute(options.db);
851
+ payloadRef = nextPayloadRef;
852
+ } catch (error) {
853
+ payloadRef = null;
854
+ logAsyncFailureOnce('sync.request_payload_record_failed', {
855
+ event: 'sync.request_payload_record_failed',
856
+ userId: event.actorId,
857
+ clientId: event.clientId,
858
+ requestEventType: event.eventType,
859
+ error: error instanceof Error ? error.message : String(error),
860
+ });
861
+ }
862
+ }
863
+
864
+ const tablesValue = options.dialect.arrayToDb(event.tables ?? []);
865
+ const scopesSummaryValue = event.scopesSummary
866
+ ? JSON.stringify(event.scopesSummary)
867
+ : null;
868
+ const responseSummaryValue = event.responseSummary
869
+ ? JSON.stringify(event.responseSummary)
870
+ : null;
871
+
872
+ await sql`
873
+ INSERT INTO sync_request_events (
874
+ partition_id, request_id, trace_id, span_id,
875
+ event_type, sync_path, actor_id, client_id, transport_path,
876
+ status_code, outcome, response_status, error_code,
877
+ duration_ms, commit_seq, operation_count, row_count, subscription_count,
878
+ scopes_summary, response_summary, tables, error_message, payload_ref
879
+ ) VALUES (
880
+ ${event.partitionId}, ${event.requestId}, ${event.traceId ?? null},
881
+ ${event.spanId ?? null}, ${event.eventType}, ${event.syncPath},
882
+ ${event.actorId}, ${event.clientId}, ${event.transportPath},
883
+ ${event.statusCode}, ${event.outcome}, ${event.responseStatus},
884
+ ${event.errorCode ?? null}, ${event.durationMs}, ${event.commitSeq ?? null},
885
+ ${event.operationCount ?? null}, ${event.rowCount ?? null},
886
+ ${event.subscriptionCount ?? null}, ${scopesSummaryValue},
887
+ ${responseSummaryValue}, ${tablesValue}, ${event.errorMessage ?? null},
888
+ ${payloadRef}
889
+ )
890
+ `.execute(options.db);
891
+ };
892
+
893
+ const recordRequestEventInBackground = (
894
+ event: RequestEvent | (() => RequestEvent)
895
+ ): void => {
896
+ if (!shouldRecordRequestEvents) return;
897
+
898
+ const resolvedEvent = typeof event === 'function' ? event() : event;
899
+
900
+ void consoleSchemaReady
901
+ .then(() => recordRequestEvent(resolvedEvent))
902
+ .catch((error) => {
903
+ logAsyncFailureOnce('sync.request_event_record_failed', {
904
+ event: 'sync.request_event_record_failed',
905
+ userId: resolvedEvent.actorId,
906
+ clientId: resolvedEvent.clientId,
907
+ requestEventType: resolvedEvent.eventType,
908
+ error: error instanceof Error ? error.message : String(error),
909
+ });
910
+ });
911
+ };
912
+
913
+ const recordRealtimeEvent = async (
914
+ event: RealtimeConsoleEvent
915
+ ): Promise<void> => {
916
+ await sql`
917
+ INSERT INTO sync_realtime_events (
918
+ partition_id, actor_id, client_id, transport_path, event_type, reason,
919
+ cursor, latest_cursor, commit_seq, scope_count, skipped_count,
920
+ sync_pack_encoding
921
+ ) VALUES (
922
+ ${event.partitionId}, ${event.actorId}, ${event.clientId},
923
+ ${event.transportPath}, ${event.eventType}, ${event.reason ?? null},
924
+ ${event.cursor ?? null}, ${event.latestCursor ?? null},
925
+ ${event.commitSeq ?? null}, ${event.scopeCount ?? null},
926
+ ${event.skippedCount ?? null}, ${event.syncPackEncoding ?? null}
927
+ )
928
+ `.execute(options.db);
929
+ };
930
+
931
+ const recordRealtimeEventInBackground = (
932
+ event: RealtimeConsoleEvent | (() => RealtimeConsoleEvent)
933
+ ): void => {
934
+ if (!shouldRecordRequestEvents) return;
935
+
936
+ const resolvedEvent = typeof event === 'function' ? event() : event;
937
+
938
+ void consoleSchemaReady
939
+ .then(() => recordRealtimeEvent(resolvedEvent))
940
+ .catch((error) => {
941
+ logAsyncFailureOnce('sync.realtime_event_record_failed', {
942
+ event: 'sync.realtime_event_record_failed',
943
+ userId: resolvedEvent.actorId,
944
+ clientId: resolvedEvent.clientId,
945
+ realtimeEventType: resolvedEvent.eventType,
946
+ error: error instanceof Error ? error.message : String(error),
947
+ });
948
+ });
949
+ };
950
+
951
+ const recordHttpCombinedFailure = (args: {
952
+ partitionId: string;
953
+ requestId: string;
954
+ traceContext: TraceContext;
955
+ actorId: string;
956
+ clientId: string;
957
+ transportPath: 'direct' | 'relay';
958
+ eventType: 'sync' | 'push' | 'pull';
959
+ statusCode: number;
960
+ outcome: 'rejected' | 'error';
961
+ durationMs: number;
962
+ errorCode: string;
963
+ errorMessage: string;
964
+ operationCount?: number | null;
965
+ rowCount?: number | null;
966
+ subscriptionCount?: number | null;
967
+ scopesSummary?: Record<string, string | string[]> | null;
968
+ responseSummary?: Record<string, unknown> | null;
969
+ payloadSnapshot?: RequestPayloadSnapshot | null;
970
+ }): void => {
971
+ recordRequestEventInBackground(() => ({
972
+ partitionId: args.partitionId,
973
+ requestId: args.requestId,
974
+ traceId: args.traceContext.traceId,
975
+ spanId: args.traceContext.spanId,
976
+ eventType: args.eventType,
977
+ syncPath: 'http-combined',
978
+ actorId: args.actorId,
979
+ clientId: args.clientId,
980
+ transportPath: args.transportPath,
981
+ statusCode: args.statusCode,
982
+ outcome: args.outcome,
983
+ responseStatus: normalizeResponseStatus(args.statusCode, args.outcome),
984
+ durationMs: args.durationMs,
985
+ errorCode: args.errorCode,
986
+ errorMessage: args.errorMessage,
987
+ operationCount: args.operationCount ?? null,
988
+ rowCount: args.rowCount ?? null,
989
+ subscriptionCount: args.subscriptionCount ?? null,
990
+ scopesSummary: args.scopesSummary ?? null,
991
+ responseSummary: args.responseSummary ?? null,
992
+ payloadSnapshot: args.payloadSnapshot ?? null,
993
+ }));
994
+
995
+ emitConsoleLiveEvent(consoleLiveEmitter, args.eventType, () => ({
996
+ partitionId: args.partitionId,
997
+ requestId: args.requestId,
998
+ traceId: args.traceContext.traceId,
999
+ spanId: args.traceContext.spanId,
1000
+ actorId: args.actorId,
1001
+ clientId: args.clientId,
1002
+ transportPath: args.transportPath,
1003
+ syncPath: 'http-combined',
1004
+ outcome: args.outcome,
1005
+ statusCode: args.statusCode,
1006
+ durationMs: args.durationMs,
1007
+ operationCount: args.operationCount ?? null,
1008
+ rowCount: args.rowCount ?? null,
1009
+ subscriptionCount: args.subscriptionCount ?? null,
1010
+ errorCode: args.errorCode,
1011
+ errorMessage: args.errorMessage,
1012
+ }));
1013
+ };
1014
+
1015
+ const recordHttpCombinedReadFailure = async (
1016
+ c: Context,
1017
+ failure: SyncJsonReadFailure
1018
+ ): Promise<void> => {
1019
+ if (!shouldRecordRequestEvents && !shouldEmitConsoleLiveEvents) return;
1020
+
1021
+ const auth = await getAuth(c).catch(() => null);
1022
+ if (!auth) return;
1023
+
1024
+ recordHttpCombinedFailure({
1025
+ partitionId: auth.partitionId ?? 'default',
1026
+ requestId: readRequestId(c),
1027
+ traceContext: readTraceContext(c),
1028
+ actorId: auth.actorId,
1029
+ clientId: readClientIdHint(c),
1030
+ transportPath: readTransportPath(c),
1031
+ eventType: 'sync',
1032
+ statusCode: failure.statusCode,
1033
+ outcome: failure.statusCode >= 500 ? 'error' : 'rejected',
1034
+ durationMs: 0,
1035
+ errorCode: failure.errorCode,
1036
+ errorMessage: failure.errorMessage,
1037
+ });
1038
+ };
1039
+
1040
+ type PushRequestBody = Omit<
1041
+ z.infer<typeof SyncPushCommitRequestSchema>,
1042
+ never
1043
+ >;
1044
+
1045
+ type PushExecutionContext = {
1046
+ auth: Auth;
1047
+ clientId: string;
1048
+ partitionId: string;
1049
+ requestId: string;
1050
+ traceContext: TraceContext;
1051
+ transportPath: 'direct' | 'relay';
1052
+ syncPath: 'http-combined' | 'ws-push';
1053
+ };
1054
+
1055
+ type ExecutedPushCommit = Awaited<ReturnType<typeof pushCommit>>;
1056
+
1057
+ type PushExecutionSummary = {
1058
+ durationMs: number;
1059
+ outcome: string;
1060
+ commitSeq: number | null;
1061
+ operationCount: number;
1062
+ tables: string[];
1063
+ results: SyncPushResponse['results'];
1064
+ payloadSnapshot: RequestPayloadSnapshot | null;
1065
+ };
1066
+
1067
+ async function notifyRealtimeForAppliedPushes(
1068
+ ctx: PushExecutionContext,
1069
+ pushedCommits: ExecutedPushCommit[]
1070
+ ): Promise<void> {
1071
+ if (!wsConnectionManager && !realtimeBroadcaster) {
1072
+ return;
1073
+ }
1074
+
1075
+ let latestCommitSeq = 0;
1076
+ const scopeKeys = new Set<string>();
1077
+ const emittedCommits: SyncCommit[] = [];
1078
+
1079
+ for (const pushed of pushedCommits) {
1080
+ if (
1081
+ pushed.response.ok !== true ||
1082
+ pushed.response.status !== 'applied' ||
1083
+ typeof pushed.response.commitSeq !== 'number'
1084
+ ) {
1085
+ continue;
1086
+ }
1087
+
1088
+ latestCommitSeq = Math.max(latestCommitSeq, pushed.response.commitSeq);
1089
+ for (const scopeKey of applyPartitionToScopeKeys(
1090
+ ctx.partitionId,
1091
+ pushed.scopeKeys
1092
+ )) {
1093
+ scopeKeys.add(scopeKey);
1094
+ }
1095
+ if (pushed.emittedChanges.length > 0) {
1096
+ emittedCommits.push({
1097
+ commitSeq: pushed.response.commitSeq,
1098
+ createdAt: pushed.commitCreatedAt ?? new Date().toISOString(),
1099
+ actorId: pushed.commitActorId ?? ctx.auth.actorId,
1100
+ changes: [...pushed.emittedChanges],
1101
+ });
1102
+ }
1103
+ }
1104
+
1105
+ if (latestCommitSeq <= 0 || scopeKeys.size === 0) {
1106
+ return;
1107
+ }
1108
+
1109
+ const combinedScopeKeys = Array.from(scopeKeys);
1110
+ if (wsConnectionManager) {
1111
+ await notifyWebSocketConnectionsWithSyncPacks({
1112
+ manager: wsConnectionManager,
1113
+ partitionId: ctx.partitionId,
1114
+ scopeKeys: combinedScopeKeys,
1115
+ cursor: latestCommitSeq,
1116
+ commits: emittedCommits,
1117
+ changeRowEncoders: binarySyncPackChangeRowEncoders,
1118
+ excludeClientIds: [ctx.clientId],
1119
+ onPackUnavailable: (event) => {
1120
+ logSyncEvent({
1121
+ event: 'sync.realtime.binary_pack_unavailable',
1122
+ userId: ctx.auth.actorId,
1123
+ clientId: ctx.clientId,
1124
+ reason: event.reason,
1125
+ subscriptionCount: event.subscriptionCount,
1126
+ emittedCommitCount: event.emittedCommitCount,
1127
+ });
1128
+ },
1129
+ onPackEncodeFailed: (event) => {
1130
+ logAsyncFailureOnce('sync.realtime.binary_pack_encode_failed', {
1131
+ event: 'sync.realtime.binary_pack_encode_failed',
1132
+ userId: ctx.auth.actorId,
1133
+ clientId: ctx.clientId,
1134
+ error:
1135
+ event.error instanceof Error
1136
+ ? event.error.message
1137
+ : String(event.error),
1138
+ });
1139
+ },
1140
+ });
1141
+ }
1142
+
1143
+ if (realtimeBroadcaster) {
1144
+ realtimeBroadcaster
1145
+ .publish({
1146
+ type: 'commit',
1147
+ commitSeq: latestCommitSeq,
1148
+ shardKey: createSyncRealtimeShardKey({
1149
+ partitionId: ctx.partitionId,
1150
+ }),
1151
+ partitionId: ctx.partitionId,
1152
+ scopeKeys: combinedScopeKeys,
1153
+ sourceInstanceId: instanceId,
1154
+ })
1155
+ .catch((error) => {
1156
+ logAsyncFailureOnce('sync.realtime.broadcast_publish_failed', {
1157
+ event: 'sync.realtime.broadcast_publish_failed',
1158
+ userId: ctx.auth.actorId,
1159
+ clientId: ctx.clientId,
1160
+ error: error instanceof Error ? error.message : String(error),
1161
+ });
1162
+ });
1163
+ }
1164
+ }
1165
+
1166
+ function buildRealtimeSubscriptionsForPull(args: {
1167
+ partitionId: string;
1168
+ requestSubscriptions: Array<{
1169
+ id: string;
1170
+ table: string;
1171
+ scopes: Record<string, string | string[]>;
1172
+ cursor: number;
1173
+ verifiedRoot?: string;
1174
+ }>;
1175
+ responseSubscriptions: SyncPullSubscriptionResponse[];
1176
+ }): WebSocketRealtimeSubscription[] {
1177
+ const requestById = new Map(
1178
+ args.requestSubscriptions.map((subscription) => [
1179
+ subscription.id,
1180
+ subscription,
1181
+ ])
1182
+ );
1183
+ const subscriptions: WebSocketRealtimeSubscription[] = [];
1184
+
1185
+ for (const response of args.responseSubscriptions) {
1186
+ if (response.status !== 'active') continue;
1187
+ const request = requestById.get(response.id);
1188
+ const scopeKeys = applyPartitionToScopeKeys(
1189
+ args.partitionId,
1190
+ scopeValuesToScopeKeys(response.scopes)
1191
+ );
1192
+ if (scopeKeys.length === 0) continue;
1193
+
1194
+ subscriptions.push({
1195
+ id: response.id,
1196
+ table: request?.table ?? response.id,
1197
+ scopes: response.scopes,
1198
+ scopeKeys,
1199
+ cursor: response.nextCursor,
1200
+ verifiedRoot:
1201
+ response.integrity?.commitChainRoot ?? request?.verifiedRoot ?? null,
1202
+ });
1203
+ }
1204
+
1205
+ return subscriptions;
1206
+ }
1207
+
1208
+ function serializeRealtimeSubscriptions(
1209
+ subscriptions: readonly WebSocketRealtimeSubscription[]
1210
+ ): unknown[] {
1211
+ return subscriptions.map((subscription) => ({
1212
+ id: subscription.id,
1213
+ table: subscription.table,
1214
+ scopes: subscription.scopes,
1215
+ cursor: subscription.cursor,
1216
+ verifiedRoot: subscription.verifiedRoot ?? null,
1217
+ }));
1218
+ }
1219
+
1220
+ function recordPushExecutionSideEffects(
1221
+ ctx: PushExecutionContext,
1222
+ summary: PushExecutionSummary
1223
+ ): void {
1224
+ recordRequestEventInBackground(() => ({
1225
+ partitionId: ctx.partitionId,
1226
+ requestId: ctx.requestId,
1227
+ traceId: ctx.traceContext.traceId,
1228
+ spanId: ctx.traceContext.spanId,
1229
+ eventType: 'push',
1230
+ syncPath: ctx.syncPath,
1231
+ actorId: ctx.auth.actorId,
1232
+ clientId: ctx.clientId,
1233
+ transportPath: ctx.transportPath,
1234
+ statusCode: 200,
1235
+ outcome: summary.outcome,
1236
+ responseStatus: normalizeResponseStatus(200, summary.outcome),
1237
+ durationMs: summary.durationMs,
1238
+ errorCode: firstPushErrorCode(summary.results),
1239
+ commitSeq: summary.commitSeq,
1240
+ operationCount: summary.operationCount,
1241
+ tables: summary.tables,
1242
+ payloadSnapshot: summary.payloadSnapshot,
1243
+ }));
1244
+
1245
+ emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
1246
+ partitionId: ctx.partitionId,
1247
+ requestId: ctx.requestId,
1248
+ traceId: ctx.traceContext.traceId,
1249
+ spanId: ctx.traceContext.spanId,
1250
+ actorId: ctx.auth.actorId,
1251
+ clientId: ctx.clientId,
1252
+ transportPath: ctx.transportPath,
1253
+ syncPath: ctx.syncPath,
1254
+ outcome: summary.outcome,
1255
+ statusCode: 200,
1256
+ durationMs: summary.durationMs,
1257
+ commitSeq: summary.commitSeq,
1258
+ operationCount: summary.operationCount,
1259
+ tables: summary.tables,
1260
+ }));
1261
+ }
1262
+
1263
+ function maybeCountPushConflicts(
1264
+ ctx: PushExecutionContext,
1265
+ results: SyncPushResponse['results'],
1266
+ enabled?: boolean
1267
+ ): void {
1268
+ if (enabled !== true) {
1269
+ return;
1270
+ }
1271
+
1272
+ const detectedConflicts = results.reduce(
1273
+ (count, result) => count + (result.status === 'conflict' ? 1 : 0),
1274
+ 0
1275
+ );
1276
+ if (detectedConflicts <= 0) {
1277
+ return;
1278
+ }
1279
+
1280
+ countSyncMetric('sync.conflicts.detected', detectedConflicts, {
1281
+ attributes: {
1282
+ syncPath: ctx.syncPath,
1283
+ transportPath: ctx.transportPath,
1284
+ },
1285
+ });
1286
+ }
1287
+
1288
+ function emitCommitLiveEvents(
1289
+ ctx: PushExecutionContext,
1290
+ pushedCommits: ExecutedPushCommit[]
1291
+ ): void {
1292
+ for (const pushed of pushedCommits) {
1293
+ if (
1294
+ pushed.response.ok !== true ||
1295
+ pushed.response.status !== 'applied' ||
1296
+ typeof pushed.response.commitSeq !== 'number'
1297
+ ) {
1298
+ continue;
1299
+ }
1300
+
1301
+ emitConsoleLiveEvent(consoleLiveEmitter, 'commit', () => ({
1302
+ partitionId: ctx.partitionId,
1303
+ commitSeq: pushed.response.commitSeq,
1304
+ actorId: ctx.auth.actorId,
1305
+ clientId: ctx.clientId,
1306
+ affectedTables: pushed.affectedTables,
1307
+ }));
1308
+ }
1309
+ }
1310
+
1311
+ async function executePushCommitBatchWithSideEffects(
1312
+ ctx: PushExecutionContext,
1313
+ pushBodies: PushRequestBody[],
1314
+ execOptions: {
1315
+ countConflictsMetric?: boolean;
1316
+ } = {}
1317
+ ): Promise<ExecutedPushCommit[]> {
1318
+ const timer = createSyncTimer();
1319
+ const totalOperationCount = pushBodies.reduce(
1320
+ (count, pushBody) => count + (pushBody.operations?.length ?? 0),
1321
+ 0
1322
+ );
1323
+ const executedPushes = await pushCommitBatch({
1324
+ db: options.db,
1325
+ dialect: options.dialect,
1326
+ handlers: handlerRegistry,
1327
+ plugins: options.plugins,
1328
+ auth: ctx.auth,
1329
+ validateCommit: validateAuthLeaseCommit,
1330
+ suppressTelemetry: true,
1331
+ requests: pushBodies.map((pushBody) => ({
1332
+ clientId: ctx.clientId,
1333
+ clientCommitId: pushBody.clientCommitId,
1334
+ operations: pushBody.operations,
1335
+ schemaVersion: pushBody.schemaVersion,
1336
+ authLease: pushBody.authLease,
1337
+ })),
1338
+ });
1339
+ const affectedTables = new Set<string>();
1340
+ for (const pushed of executedPushes) {
1341
+ for (const table of pushed.affectedTables) {
1342
+ affectedTables.add(table);
1343
+ }
1344
+ }
1345
+
1346
+ const pushDurationMs = timer();
1347
+ const latestCommitSeq = executedPushes.reduce((latest, pushed) => {
1348
+ if (typeof pushed.response.commitSeq === 'number') {
1349
+ return Math.max(latest, pushed.response.commitSeq);
1350
+ }
1351
+ return latest;
1352
+ }, 0);
1353
+ const aggregateStatus = executedPushes.every(
1354
+ (pushed) => pushed.response.status === 'cached'
1355
+ )
1356
+ ? 'cached'
1357
+ : executedPushes.every(
1358
+ (pushed) =>
1359
+ pushed.response.status === 'applied' ||
1360
+ pushed.response.status === 'cached'
1361
+ )
1362
+ ? 'applied'
1363
+ : 'rejected';
1364
+ const aggregatedResults = executedPushes.flatMap(
1365
+ (pushed) => pushed.response.results
1366
+ );
1367
+
1368
+ logSyncEvent({
1369
+ event: 'sync.push',
1370
+ userId: ctx.auth.actorId,
1371
+ durationMs: pushDurationMs,
1372
+ operationCount: totalOperationCount,
1373
+ status: aggregateStatus,
1374
+ commitSeq: latestCommitSeq > 0 ? latestCommitSeq : undefined,
1375
+ });
1376
+
1377
+ recordPushExecutionSideEffects(ctx, {
1378
+ durationMs: pushDurationMs,
1379
+ outcome: aggregateStatus,
1380
+ commitSeq: latestCommitSeq > 0 ? latestCommitSeq : null,
1381
+ operationCount: totalOperationCount,
1382
+ tables: Array.from(affectedTables),
1383
+ results: aggregatedResults,
1384
+ payloadSnapshot: shouldCaptureRequestPayloadSnapshots
1385
+ ? {
1386
+ request: {
1387
+ clientId: ctx.clientId,
1388
+ commits: pushBodies.map((pushBody) => ({
1389
+ clientCommitId: pushBody.clientCommitId,
1390
+ schemaVersion: pushBody.schemaVersion,
1391
+ authLease: pushBody.authLease,
1392
+ operations: pushBody.operations,
1393
+ })),
1394
+ },
1395
+ response: {
1396
+ ok: true,
1397
+ commits: executedPushes.map((pushed, index) => ({
1398
+ clientCommitId: pushBodies[index]?.clientCommitId ?? '',
1399
+ ...pushed.response,
1400
+ })),
1401
+ },
1402
+ }
1403
+ : null,
1404
+ });
1405
+
1406
+ maybeCountPushConflicts(
1407
+ ctx,
1408
+ aggregatedResults,
1409
+ execOptions.countConflictsMetric
1410
+ );
1411
+
1412
+ await notifyRealtimeForAppliedPushes(ctx, executedPushes);
1413
+ emitCommitLiveEvents(ctx, executedPushes);
1414
+
1415
+ return executedPushes;
1416
+ }
1417
+
1418
+ async function executePushCommitWithSideEffects(
1419
+ ctx: PushExecutionContext,
1420
+ pushBody: PushRequestBody,
1421
+ execOptions: {
1422
+ countConflictsMetric?: boolean;
1423
+ deferRealtimeNotifications?: boolean;
1424
+ } = {}
1425
+ ): Promise<ExecutedPushCommit> {
1426
+ const timer = createSyncTimer();
1427
+ const pushOps = pushBody.operations ?? [];
1428
+
1429
+ const pushed = await pushCommit({
1430
+ db: options.db,
1431
+ dialect: options.dialect,
1432
+ handlers: handlerRegistry,
1433
+ plugins: options.plugins,
1434
+ auth: ctx.auth,
1435
+ validateCommit: validateAuthLeaseCommit,
1436
+ request: {
1437
+ clientId: ctx.clientId,
1438
+ clientCommitId: pushBody.clientCommitId,
1439
+ operations: pushBody.operations,
1440
+ schemaVersion: pushBody.schemaVersion,
1441
+ authLease: pushBody.authLease,
1442
+ },
1443
+ });
1444
+
1445
+ const pushDurationMs = timer();
1446
+
1447
+ logSyncEvent({
1448
+ event: 'sync.push',
1449
+ userId: ctx.auth.actorId,
1450
+ durationMs: pushDurationMs,
1451
+ operationCount: pushOps.length,
1452
+ status: pushed.response.status,
1453
+ commitSeq: pushed.response.commitSeq,
1454
+ });
1455
+
1456
+ recordPushExecutionSideEffects(ctx, {
1457
+ durationMs: pushDurationMs,
1458
+ outcome: pushed.response.status,
1459
+ commitSeq: pushed.response.commitSeq ?? null,
1460
+ operationCount: pushOps.length,
1461
+ tables: pushed.affectedTables,
1462
+ results: pushed.response.results,
1463
+ payloadSnapshot: shouldCaptureRequestPayloadSnapshots
1464
+ ? {
1465
+ request: {
1466
+ clientId: ctx.clientId,
1467
+ clientCommitId: pushBody.clientCommitId,
1468
+ schemaVersion: pushBody.schemaVersion,
1469
+ authLease: pushBody.authLease,
1470
+ operations: pushBody.operations,
1471
+ },
1472
+ response: pushed.response,
1473
+ }
1474
+ : null,
1475
+ });
1476
+
1477
+ maybeCountPushConflicts(
1478
+ ctx,
1479
+ pushed.response.results,
1480
+ execOptions.countConflictsMetric
1481
+ );
1482
+
1483
+ if (execOptions.deferRealtimeNotifications !== true) {
1484
+ await notifyRealtimeForAppliedPushes(ctx, [pushed]);
1485
+ }
1486
+ emitCommitLiveEvents(ctx, [pushed]);
1487
+
1488
+ return pushed;
1489
+ }
1490
+
1491
+ // -------------------------------------------------------------------------
1492
+ // Rate limiting (optional)
1493
+ // -------------------------------------------------------------------------
1494
+
1495
+ const rateLimitConfig = config.rateLimit;
1496
+ if (rateLimitConfig !== false) {
1497
+ const pullRateLimit =
1498
+ rateLimitConfig?.pull ?? DEFAULT_SYNC_RATE_LIMITS.pull;
1499
+ const pushRateLimit =
1500
+ rateLimitConfig?.push ?? DEFAULT_SYNC_RATE_LIMITS.push;
1501
+
1502
+ const createAuthBasedRateLimiter = (
1503
+ limitConfig: Omit<SyncRateLimitConfig['pull'], never> | false | undefined
1504
+ ) => {
1505
+ if (limitConfig === false || !limitConfig) return null;
1506
+ return createRateLimiter({
1507
+ ...limitConfig,
1508
+ keyGenerator: async (c) => {
1509
+ const auth = await getAuth(c);
1510
+ return auth?.actorId ?? null;
1511
+ },
1512
+ });
1513
+ };
1514
+
1515
+ const pullLimiter = createAuthBasedRateLimiter(pullRateLimit);
1516
+ const pushLimiter = createAuthBasedRateLimiter(pushRateLimit);
1517
+
1518
+ const syncRateLimiter: MiddlewareHandler = async (c, next) => {
1519
+ if (!pullLimiter && !pushLimiter) return next();
1520
+
1521
+ let shouldApplyPull = pullLimiter !== null;
1522
+ let shouldApplyPush = pushLimiter !== null;
1523
+
1524
+ if (pullLimiter && pushLimiter && c.req.method === 'POST') {
1525
+ const parsed = await readLimitedSyncJsonBody(c);
1526
+ if (!parsed.ok) {
1527
+ if (parsed.failure) {
1528
+ await recordHttpCombinedReadFailure(c, parsed.failure);
1529
+ }
1530
+ return parsed.response;
1531
+ }
1532
+ if (parsed.value !== null && typeof parsed.value === 'object') {
1533
+ shouldApplyPull = Reflect.get(parsed.value, 'pull') !== undefined;
1534
+ shouldApplyPush = Reflect.get(parsed.value, 'push') !== undefined;
1535
+ }
1536
+ }
1537
+
1538
+ if (pullLimiter && shouldApplyPull && pushLimiter && shouldApplyPush) {
1539
+ return pullLimiter(c, async () => {
1540
+ const pushResult = await pushLimiter(c, next);
1541
+ if (pushResult instanceof Response) {
1542
+ c.res = pushResult;
1543
+ }
1544
+ });
1545
+ }
1546
+ if (pullLimiter && shouldApplyPull) {
1547
+ return pullLimiter(c, next);
1548
+ }
1549
+ if (pushLimiter && shouldApplyPush) {
1550
+ return pushLimiter(c, next);
1551
+ }
1552
+
1553
+ return next();
1554
+ };
1555
+
1556
+ routes.use('/', syncRateLimiter);
1557
+ }
1558
+
1559
+ async function handleRealtimeEvent(event: SyncRealtimeEvent): Promise<void> {
1560
+ if (!wsConnectionManager) return;
1561
+ if (event.type !== 'commit') return;
1562
+ if (event.sourceInstanceId && event.sourceInstanceId === instanceId) return;
1563
+
1564
+ const commitSeq = event.commitSeq;
1565
+ const partitionId = event.partitionId ?? 'default';
1566
+ const scopeKeys =
1567
+ event.scopeKeys && event.scopeKeys.length > 0
1568
+ ? event.scopeKeys
1569
+ : await readCommitScopeKeys(options.db, commitSeq, partitionId);
1570
+
1571
+ if (scopeKeys.length === 0) return;
1572
+ wsConnectionManager.notifyScopeKeys(scopeKeys, commitSeq);
1573
+ }
1574
+
1575
+ return {
1576
+ routes,
1577
+ config,
1578
+ options,
1579
+ authLeaseRoutesConfig,
1580
+ corsConfig,
1581
+ handlerRegistry,
1582
+ binarySyncPackChangeRowEncoders,
1583
+ maxPullLimitCommits,
1584
+ maxSubscriptionsPerPull,
1585
+ maxPullLimitSnapshotRows,
1586
+ maxPullMaxSnapshotPages,
1587
+ maxOperationsPerPush,
1588
+ maxSyncRequestJsonBytes,
1589
+ maxSyncBinaryPackBytes,
1590
+ maxSnapshotChunkResponseBytes,
1591
+ maxSnapshotArtifactResponseBytes,
1592
+ requiredSchemaVersion,
1593
+ latestSchemaVersion,
1594
+ consoleLiveEmitter,
1595
+ shouldEmitConsoleLiveEvents,
1596
+ shouldRecordRequestEvents,
1597
+ shouldCaptureRequestPayloadSnapshots,
1598
+ getAuth,
1599
+ createAuditScopeResolver,
1600
+ readVisibleAuditChanges,
1601
+ readAuditDebugRequestEvents,
1602
+ validateAuthLeaseCommit,
1603
+ readLimitedSyncJsonBody,
1604
+ websocketConfig,
1605
+ wsConnectionManager,
1606
+ realtimeBroadcaster,
1607
+ instanceId,
1608
+ logAsyncFailureOnce,
1609
+ triggerAutoMaintenance,
1610
+ recordRequestEventInBackground,
1611
+ recordRealtimeEventInBackground,
1612
+ recordHttpCombinedFailure,
1613
+ recordHttpCombinedReadFailure,
1614
+ notifyRealtimeForAppliedPushes,
1615
+ buildRealtimeSubscriptionsForPull,
1616
+ serializeRealtimeSubscriptions,
1617
+ recordPushExecutionSideEffects,
1618
+ maybeCountPushConflicts,
1619
+ emitCommitLiveEvents,
1620
+ executePushCommitBatchWithSideEffects,
1621
+ executePushCommitWithSideEffects,
1622
+ };
1623
+ }
1624
+
1625
+ export type SyncRoutesContext<
1626
+ DB extends SyncCoreDb = SyncCoreDb,
1627
+ Auth extends SyncAuthResult = SyncAuthResult,
1628
+ F extends SqlFamily = SqlFamily,
1629
+ > = ReturnType<typeof createSyncRoutesContext<DB, Auth, F>>;