@syncular/server 0.0.6-96 → 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,1064 @@
1
+ /**
2
+ * @syncular/server/sqlite - SQLite Server Sync Dialect
3
+ *
4
+ * SQLite adaptation of the commit-log based sync system.
5
+ * Works with SQLite-compatible Kysely dialects such as bun:sqlite, better-sqlite3, and sqlite3.
6
+ *
7
+ * Key differences from Postgres:
8
+ * - No bigserial → INTEGER PRIMARY KEY AUTOINCREMENT
9
+ * - No JSONB → JSON stored as TEXT (with json_extract for filtering)
10
+ * - No array && overlap → JSON object key matching
11
+ * - No timestamptz → TEXT with ISO format
12
+ * - No GIN index → regular index + manual filtering
13
+ * - REPEATABLE READ → no-op (SQLite uses serializable by default)
14
+ */
15
+
16
+ import type { ScopeValues, StoredScopes, SyncOp } from '@syncular/core';
17
+ import type { DbExecutor } from '@syncular/server';
18
+ import {
19
+ type AuditRowHistoryArgs,
20
+ type AuditRowHistoryRow,
21
+ BaseServerSyncDialect,
22
+ coerceIsoString,
23
+ coerceNumber,
24
+ createSavepointName,
25
+ type IncrementalPullRow,
26
+ type IncrementalPullRowsArgs,
27
+ isActiveTransaction,
28
+ parseScopes,
29
+ } from '@syncular/server';
30
+ import type { SyncChangeRow, SyncCoreDb } from '@syncular/server/schema';
31
+ import type { Kysely, RawBuilder } from 'kysely';
32
+ import { sql } from 'kysely';
33
+
34
+ function parseJsonValue(value: unknown): unknown {
35
+ if (value === null || value === undefined) return null;
36
+ if (typeof value === 'object') return value;
37
+ if (typeof value === 'string') {
38
+ try {
39
+ return JSON.parse(value);
40
+ } catch {
41
+ return value;
42
+ }
43
+ }
44
+ return value;
45
+ }
46
+
47
+ function toStringArray(value: unknown): string[] {
48
+ if (Array.isArray(value)) {
49
+ return value.filter((k: unknown): k is string => typeof k === 'string');
50
+ }
51
+ if (typeof value === 'string') {
52
+ try {
53
+ const parsed = JSON.parse(value);
54
+ if (Array.isArray(parsed)) {
55
+ return parsed.filter(
56
+ (k: unknown): k is string => typeof k === 'string'
57
+ );
58
+ }
59
+ } catch {
60
+ // ignore
61
+ }
62
+ }
63
+ return [];
64
+ }
65
+
66
+ /**
67
+ * Build a safe SQLite JSON path for a scope key.
68
+ */
69
+ function toSqliteJsonPath(scopeKey: string): string {
70
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(scopeKey)) {
71
+ return `$.${scopeKey}`;
72
+ }
73
+ const escaped = scopeKey
74
+ .replaceAll('\\', '\\\\')
75
+ .replaceAll('"', '\\"')
76
+ .replaceAll("'", "\\'");
77
+ return `$."${escaped}"`;
78
+ }
79
+
80
+ /**
81
+ * Build scope filter SQL for a JSON scopes column.
82
+ * Returns `0 = 1` when a requested array scope is empty.
83
+ */
84
+ function buildScopeFilterSql(
85
+ scopes: ScopeValues,
86
+ scopeColumnSql: string
87
+ ): ReturnType<typeof sql> {
88
+ const conditions: ReturnType<typeof sql>[] = [];
89
+
90
+ for (const [scopeKey, requested] of Object.entries(scopes)) {
91
+ const jsonPath = toSqliteJsonPath(scopeKey);
92
+ if (Array.isArray(requested)) {
93
+ const values = requested.filter(
94
+ (value): value is string => typeof value === 'string'
95
+ );
96
+ if (values.length === 0) {
97
+ return sql`0 = 1`;
98
+ }
99
+ const valuesSql = sql.join(
100
+ values.map((value) => sql`${value}`),
101
+ sql`, `
102
+ );
103
+ conditions.push(
104
+ sql`json_extract(${sql.raw(scopeColumnSql)}, ${jsonPath}) IN (${valuesSql})`
105
+ );
106
+ continue;
107
+ }
108
+
109
+ conditions.push(
110
+ sql`json_extract(${sql.raw(scopeColumnSql)}, ${jsonPath}) = ${requested}`
111
+ );
112
+ }
113
+
114
+ if (conditions.length === 0) {
115
+ return sql`1 = 1`;
116
+ }
117
+
118
+ return sql.join(conditions, sql` AND `);
119
+ }
120
+
121
+ async function ensurePartitionColumn<DB extends SyncCoreDb>(
122
+ db: Kysely<DB>,
123
+ table: string
124
+ ): Promise<void> {
125
+ try {
126
+ await sql
127
+ .raw(
128
+ `ALTER TABLE ${table} ADD COLUMN partition_id TEXT NOT NULL DEFAULT 'default'`
129
+ )
130
+ .execute(db);
131
+ } catch {
132
+ // Ignore when column already exists (or table is immutable in the current backend).
133
+ }
134
+ }
135
+
136
+ async function ensureCommitVerificationColumns<DB extends SyncCoreDb>(
137
+ db: Kysely<DB>
138
+ ): Promise<void> {
139
+ const alterStatements = [
140
+ 'ALTER TABLE sync_commits ADD COLUMN commit_digest TEXT',
141
+ 'ALTER TABLE sync_commits ADD COLUMN commit_chain_root TEXT',
142
+ ];
143
+
144
+ for (const statement of alterStatements) {
145
+ try {
146
+ await sql.raw(statement).execute(db);
147
+ } catch {
148
+ // Ignore when column already exists.
149
+ }
150
+ }
151
+ }
152
+
153
+ async function ensureConsoleEventColumns<DB extends SyncCoreDb>(
154
+ db: Kysely<DB>
155
+ ): Promise<void> {
156
+ const alterStatements = [
157
+ "ALTER TABLE sync_request_events ADD COLUMN partition_id TEXT NOT NULL DEFAULT 'default'",
158
+ 'ALTER TABLE sync_request_events ADD COLUMN request_id TEXT',
159
+ 'ALTER TABLE sync_request_events ADD COLUMN trace_id TEXT',
160
+ 'ALTER TABLE sync_request_events ADD COLUMN span_id TEXT',
161
+ "ALTER TABLE sync_request_events ADD COLUMN transport_path TEXT NOT NULL DEFAULT 'direct'",
162
+ "ALTER TABLE sync_request_events ADD COLUMN sync_path TEXT NOT NULL DEFAULT 'http-combined'",
163
+ "ALTER TABLE sync_request_events ADD COLUMN response_status TEXT NOT NULL DEFAULT 'unknown'",
164
+ 'ALTER TABLE sync_request_events ADD COLUMN error_code TEXT',
165
+ 'ALTER TABLE sync_request_events ADD COLUMN subscription_count INTEGER',
166
+ 'ALTER TABLE sync_request_events ADD COLUMN scopes_summary TEXT',
167
+ 'ALTER TABLE sync_request_events ADD COLUMN response_summary TEXT',
168
+ 'ALTER TABLE sync_request_events ADD COLUMN payload_ref TEXT',
169
+ ];
170
+
171
+ for (const statement of alterStatements) {
172
+ try {
173
+ await sql.raw(statement).execute(db);
174
+ } catch {
175
+ // Ignore when column already exists (or table is immutable in the current backend).
176
+ }
177
+ }
178
+ }
179
+
180
+ async function ensureSnapshotChunkContinuationColumns<DB extends SyncCoreDb>(
181
+ db: Kysely<DB>
182
+ ): Promise<void> {
183
+ const alterStatements = [
184
+ 'ALTER TABLE sync_snapshot_chunks ADD COLUMN next_row_cursor TEXT',
185
+ 'ALTER TABLE sync_snapshot_chunks ADD COLUMN is_last_page INTEGER NOT NULL DEFAULT 0',
186
+ ];
187
+
188
+ for (const statement of alterStatements) {
189
+ try {
190
+ await sql.raw(statement).execute(db);
191
+ } catch {
192
+ // Ignore when column already exists.
193
+ }
194
+ }
195
+ }
196
+
197
+ export class SqliteServerSyncDialect extends BaseServerSyncDialect<'sqlite'> {
198
+ readonly family = 'sqlite' as const;
199
+ readonly supportsForUpdate = false;
200
+ readonly supportsSavepoints: boolean;
201
+ readonly supportsInsertReturning = false;
202
+ private readonly _supportsTransactions: boolean;
203
+
204
+ constructor(options?: { supportsTransactions?: boolean }) {
205
+ super();
206
+ this._supportsTransactions = options?.supportsTransactions ?? true;
207
+ this.supportsSavepoints = this._supportsTransactions;
208
+ }
209
+
210
+ // ===========================================================================
211
+ // SQL Fragment Hooks
212
+ // ===========================================================================
213
+
214
+ protected buildNumberListFilter(values: number[]): RawBuilder<unknown> {
215
+ const list = sql.join(
216
+ values.map((v) => sql`${v}`),
217
+ sql`, `
218
+ );
219
+ return sql`IN (${list})`;
220
+ }
221
+
222
+ protected buildStringListFilter(values: string[]): RawBuilder<unknown> {
223
+ const list = sql.join(
224
+ values.map((v) => sql`${v}`),
225
+ sql`, `
226
+ );
227
+ return sql`IN (${list})`;
228
+ }
229
+
230
+ // ===========================================================================
231
+ // Schema Setup
232
+ // ===========================================================================
233
+
234
+ async ensureSyncSchema<DB extends SyncCoreDb>(db: Kysely<DB>): Promise<void> {
235
+ await sql`PRAGMA foreign_keys = ON`.execute(db);
236
+
237
+ const nowIso = sql`(strftime('%Y-%m-%dT%H:%M:%fZ','now'))`;
238
+
239
+ // sync_commits table
240
+ await db.schema
241
+ .createTable('sync_commits')
242
+ .ifNotExists()
243
+ .addColumn('commit_seq', 'integer', (col) =>
244
+ col.primaryKey().autoIncrement()
245
+ )
246
+ .addColumn('partition_id', 'text', (col) =>
247
+ col.notNull().defaultTo('default')
248
+ )
249
+ .addColumn('actor_id', 'text', (col) => col.notNull())
250
+ .addColumn('client_id', 'text', (col) => col.notNull())
251
+ .addColumn('client_commit_id', 'text', (col) => col.notNull())
252
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
253
+ .addColumn('meta', 'json')
254
+ .addColumn('result_json', 'json')
255
+ .addColumn('change_count', 'integer', (col) => col.notNull().defaultTo(0))
256
+ .addColumn('commit_digest', 'text')
257
+ .addColumn('commit_chain_root', 'text')
258
+ .addColumn('affected_tables', 'text', (col) =>
259
+ col.notNull().defaultTo('[]')
260
+ )
261
+ .execute();
262
+ await ensurePartitionColumn(db, 'sync_commits');
263
+ await ensureCommitVerificationColumns(db);
264
+
265
+ await sql`DROP INDEX IF EXISTS idx_sync_commits_client_commit`.execute(db);
266
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_commits_client_commit
267
+ ON sync_commits(partition_id, client_id, client_commit_id)`.execute(db);
268
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_commits_partition_client_commit_seq
269
+ ON sync_commits(partition_id, client_id, commit_seq)`.execute(db);
270
+
271
+ // sync_table_commits table (index of which commits affect which tables)
272
+ await db.schema
273
+ .createTable('sync_table_commits')
274
+ .ifNotExists()
275
+ .addColumn('partition_id', 'text', (col) =>
276
+ col.notNull().defaultTo('default')
277
+ )
278
+ .addColumn('table', 'text', (col) => col.notNull())
279
+ .addColumn('commit_seq', 'integer', (col) =>
280
+ col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
281
+ )
282
+ .addPrimaryKeyConstraint('sync_table_commits_pk', [
283
+ 'partition_id',
284
+ 'table',
285
+ 'commit_seq',
286
+ ])
287
+ .execute();
288
+ await ensurePartitionColumn(db, 'sync_table_commits');
289
+
290
+ // Ensure unique index matches ON CONFLICT clause in push.ts
291
+ // (needed when migrating from old schema where PK was only (table, commit_seq))
292
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_table_commits_partition_pk
293
+ ON sync_table_commits(partition_id, "table", commit_seq)`.execute(db);
294
+
295
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_table_commits_commit_seq
296
+ ON sync_table_commits(partition_id, commit_seq)`.execute(db);
297
+
298
+ // sync_scope_commits table (index of which commits affect table/scope routes)
299
+ await db.schema
300
+ .createTable('sync_scope_commits')
301
+ .ifNotExists()
302
+ .addColumn('partition_id', 'text', (col) =>
303
+ col.notNull().defaultTo('default')
304
+ )
305
+ .addColumn('table', 'text', (col) => col.notNull())
306
+ .addColumn('scope_key', 'text', (col) => col.notNull())
307
+ .addColumn('commit_seq', 'integer', (col) =>
308
+ col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
309
+ )
310
+ .addPrimaryKeyConstraint('sync_scope_commits_pk', [
311
+ 'partition_id',
312
+ 'table',
313
+ 'scope_key',
314
+ 'commit_seq',
315
+ ])
316
+ .execute();
317
+ await ensurePartitionColumn(db, 'sync_scope_commits');
318
+
319
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_scope_commits_partition_pk
320
+ ON sync_scope_commits(partition_id, "table", scope_key, commit_seq)`.execute(
321
+ db
322
+ );
323
+
324
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_scope_commits_commit_seq
325
+ ON sync_scope_commits(partition_id, commit_seq)`.execute(db);
326
+
327
+ // sync_changes table - uses JSON for scopes
328
+ await db.schema
329
+ .createTable('sync_changes')
330
+ .ifNotExists()
331
+ .addColumn('change_id', 'integer', (col) =>
332
+ col.primaryKey().autoIncrement()
333
+ )
334
+ .addColumn('partition_id', 'text', (col) =>
335
+ col.notNull().defaultTo('default')
336
+ )
337
+ .addColumn('commit_seq', 'integer', (col) =>
338
+ col.notNull().references('sync_commits.commit_seq').onDelete('cascade')
339
+ )
340
+ .addColumn('table', 'text', (col) => col.notNull())
341
+ .addColumn('row_id', 'text', (col) => col.notNull())
342
+ .addColumn('op', 'text', (col) => col.notNull())
343
+ .addColumn('row_json', 'json')
344
+ .addColumn('row_version', 'integer')
345
+ .addColumn('scopes', 'json', (col) => col.notNull())
346
+ .execute();
347
+ await ensurePartitionColumn(db, 'sync_changes');
348
+
349
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_changes_commit_seq
350
+ ON sync_changes(partition_id, commit_seq)`.execute(db);
351
+
352
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_changes_table
353
+ ON sync_changes(partition_id, "table")`.execute(db);
354
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_changes_table_commit_seq_change_id
355
+ ON sync_changes(partition_id, "table", commit_seq, change_id)`.execute(
356
+ db
357
+ );
358
+
359
+ // sync_client_cursors table
360
+ await db.schema
361
+ .createTable('sync_client_cursors')
362
+ .ifNotExists()
363
+ .addColumn('partition_id', 'text', (col) =>
364
+ col.notNull().defaultTo('default')
365
+ )
366
+ .addColumn('client_id', 'text', (col) => col.notNull())
367
+ .addColumn('actor_id', 'text', (col) => col.notNull())
368
+ .addColumn('cursor', 'integer', (col) => col.notNull().defaultTo(0))
369
+ .addColumn('effective_scopes', 'json', (col) =>
370
+ col.notNull().defaultTo('{}')
371
+ )
372
+ .addColumn('realtime_subscriptions', 'json')
373
+ .addColumn('updated_at', 'text', (col) => col.notNull().defaultTo(nowIso))
374
+ .addPrimaryKeyConstraint('sync_client_cursors_pk', [
375
+ 'partition_id',
376
+ 'client_id',
377
+ ])
378
+ .execute();
379
+ await ensurePartitionColumn(db, 'sync_client_cursors');
380
+ try {
381
+ await sql`ALTER TABLE sync_client_cursors ADD COLUMN realtime_subscriptions json`.execute(
382
+ db
383
+ );
384
+ } catch {
385
+ // Ignore when column already exists.
386
+ }
387
+
388
+ // Ensure unique index matches ON CONFLICT clause in recordClientCursor
389
+ // (needed when migrating from old schema where PK was only (client_id))
390
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_client_cursors_partition_pk
391
+ ON sync_client_cursors(partition_id, client_id)`.execute(db);
392
+
393
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_client_cursors_updated_at
394
+ ON sync_client_cursors(updated_at)`.execute(db);
395
+
396
+ // sync_snapshot_chunks table
397
+ await db.schema
398
+ .createTable('sync_snapshot_chunks')
399
+ .ifNotExists()
400
+ .addColumn('chunk_id', 'text', (col) => col.primaryKey())
401
+ .addColumn('partition_id', 'text', (col) =>
402
+ col.notNull().defaultTo('default')
403
+ )
404
+ .addColumn('scope_key', 'text', (col) => col.notNull())
405
+ .addColumn('scope', 'text', (col) => col.notNull())
406
+ .addColumn('as_of_commit_seq', 'integer', (col) => col.notNull())
407
+ .addColumn('row_cursor', 'text', (col) => col.notNull().defaultTo(''))
408
+ .addColumn('row_limit', 'integer', (col) => col.notNull())
409
+ .addColumn('next_row_cursor', 'text')
410
+ .addColumn('is_last_page', 'integer', (col) => col.notNull().defaultTo(0))
411
+ .addColumn('encoding', 'text', (col) => col.notNull())
412
+ .addColumn('compression', 'text', (col) => col.notNull())
413
+ .addColumn('sha256', 'text', (col) => col.notNull())
414
+ .addColumn('byte_length', 'integer', (col) => col.notNull())
415
+ .addColumn('blob_hash', 'text', (col) => col.notNull().defaultTo(''))
416
+ .addColumn('body', 'blob') // Deprecated: use blob storage
417
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
418
+ .addColumn('expires_at', 'text', (col) => col.notNull())
419
+ .execute();
420
+ await ensurePartitionColumn(db, 'sync_snapshot_chunks');
421
+ await ensureSnapshotChunkContinuationColumns(db);
422
+
423
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_snapshot_chunks_expires_at
424
+ ON sync_snapshot_chunks(expires_at)`.execute(db);
425
+
426
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_snapshot_chunks_page_key
427
+ ON sync_snapshot_chunks(partition_id, scope_key, scope, as_of_commit_seq, row_cursor, row_limit, encoding, compression)`.execute(
428
+ db
429
+ );
430
+
431
+ // sync_snapshot_artifacts table. Artifact bodies live in object storage;
432
+ // this table owns scoped eligibility and manifest metadata.
433
+ await db.schema
434
+ .createTable('sync_snapshot_artifacts')
435
+ .ifNotExists()
436
+ .addColumn('artifact_id', 'text', (col) => col.primaryKey())
437
+ .addColumn('partition_id', 'text', (col) =>
438
+ col.notNull().defaultTo('default')
439
+ )
440
+ .addColumn('scope_key', 'text', (col) => col.notNull())
441
+ .addColumn('subscription_id', 'text', (col) => col.notNull())
442
+ .addColumn('table', 'text', (col) => col.notNull())
443
+ .addColumn('artifact_kind', 'text', (col) => col.notNull())
444
+ .addColumn('schema_version', 'text', (col) => col.notNull())
445
+ .addColumn('as_of_commit_seq', 'integer', (col) => col.notNull())
446
+ .addColumn('row_cursor', 'text', (col) => col.notNull().defaultTo(''))
447
+ .addColumn('row_limit', 'integer', (col) => col.notNull())
448
+ .addColumn('row_count', 'integer', (col) => col.notNull())
449
+ .addColumn('next_row_cursor', 'text')
450
+ .addColumn('is_first_page', 'integer', (col) =>
451
+ col.notNull().defaultTo(0)
452
+ )
453
+ .addColumn('is_last_page', 'integer', (col) => col.notNull().defaultTo(0))
454
+ .addColumn('compression', 'text', (col) => col.notNull())
455
+ .addColumn('sha256', 'text', (col) => col.notNull())
456
+ .addColumn('byte_length', 'integer', (col) => col.notNull())
457
+ .addColumn('manifest_digest', 'text', (col) => col.notNull())
458
+ .addColumn('feature_set_json', 'text', (col) => col.notNull())
459
+ .addColumn('manifest_json', 'text', (col) => col.notNull())
460
+ .addColumn('blob_hash', 'text', (col) => col.notNull())
461
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
462
+ .addColumn('expires_at', 'text', (col) => col.notNull())
463
+ .execute();
464
+
465
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_snapshot_artifacts_expires_at
466
+ ON sync_snapshot_artifacts(expires_at)`.execute(db);
467
+
468
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_snapshot_artifacts_page_key
469
+ ON sync_snapshot_artifacts(partition_id, scope_key, subscription_id, "table", as_of_commit_seq, row_cursor, row_limit, artifact_kind, schema_version, compression)`.execute(
470
+ db
471
+ );
472
+
473
+ // Encrypted CRDT system tables. These are shared by all encrypted CRDT
474
+ // fields and are exposed through hidden handlers, not app query builders.
475
+ await db.schema
476
+ .createTable('sync_crdt_updates')
477
+ .ifNotExists()
478
+ .addColumn('seq', 'integer', (col) => col.primaryKey().autoIncrement())
479
+ .addColumn('partition_id', 'text', (col) =>
480
+ col.notNull().defaultTo('default')
481
+ )
482
+ .addColumn('stream_id', 'text', (col) => col.notNull())
483
+ .addColumn('app_table', 'text', (col) => col.notNull())
484
+ .addColumn('row_id', 'text', (col) => col.notNull())
485
+ .addColumn('field_name', 'text', (col) => col.notNull())
486
+ .addColumn('update_id', 'text', (col) => col.notNull())
487
+ .addColumn('actor_id', 'text')
488
+ .addColumn('client_id', 'text')
489
+ .addColumn('key_id', 'text', (col) => col.notNull())
490
+ .addColumn('ciphertext', 'text', (col) => col.notNull())
491
+ .addColumn('scopes', 'json', (col) => col.notNull().defaultTo('{}'))
492
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
493
+ .execute();
494
+ await ensurePartitionColumn(db, 'sync_crdt_updates');
495
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_crdt_updates_update_id
496
+ ON sync_crdt_updates(partition_id, update_id)`.execute(db);
497
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_crdt_updates_stream_seq
498
+ ON sync_crdt_updates(partition_id, stream_id, seq)`.execute(db);
499
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_crdt_updates_scope_table
500
+ ON sync_crdt_updates(partition_id, app_table, row_id, field_name)`.execute(
501
+ db
502
+ );
503
+
504
+ await db.schema
505
+ .createTable('sync_crdt_checkpoints')
506
+ .ifNotExists()
507
+ .addColumn('seq', 'integer', (col) => col.primaryKey().autoIncrement())
508
+ .addColumn('partition_id', 'text', (col) =>
509
+ col.notNull().defaultTo('default')
510
+ )
511
+ .addColumn('stream_id', 'text', (col) => col.notNull())
512
+ .addColumn('app_table', 'text', (col) => col.notNull())
513
+ .addColumn('row_id', 'text', (col) => col.notNull())
514
+ .addColumn('field_name', 'text', (col) => col.notNull())
515
+ .addColumn('checkpoint_id', 'text', (col) => col.notNull())
516
+ .addColumn('covers_seq', 'integer', (col) => col.notNull())
517
+ .addColumn('actor_id', 'text')
518
+ .addColumn('client_id', 'text')
519
+ .addColumn('key_id', 'text', (col) => col.notNull())
520
+ .addColumn('ciphertext', 'text', (col) => col.notNull())
521
+ .addColumn('scopes', 'json', (col) => col.notNull().defaultTo('{}'))
522
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
523
+ .execute();
524
+ await ensurePartitionColumn(db, 'sync_crdt_checkpoints');
525
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_sync_crdt_checkpoints_checkpoint_id
526
+ ON sync_crdt_checkpoints(partition_id, checkpoint_id)`.execute(db);
527
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_crdt_checkpoints_stream_covers
528
+ ON sync_crdt_checkpoints(partition_id, stream_id, covers_seq)`.execute(
529
+ db
530
+ );
531
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_crdt_checkpoints_scope_table
532
+ ON sync_crdt_checkpoints(partition_id, app_table, row_id, field_name)`.execute(
533
+ db
534
+ );
535
+
536
+ // Cleanup orphaned rows
537
+ await sql`
538
+ DELETE FROM sync_table_commits
539
+ WHERE commit_seq NOT IN (SELECT commit_seq FROM sync_commits)
540
+ `.execute(db);
541
+ await sql`
542
+ DELETE FROM sync_changes
543
+ WHERE commit_seq NOT IN (SELECT commit_seq FROM sync_commits)
544
+ `.execute(db);
545
+ }
546
+
547
+ // ===========================================================================
548
+ // Transaction Control
549
+ // ===========================================================================
550
+
551
+ async executeInTransaction<DB extends SyncCoreDb, T>(
552
+ db: Kysely<DB>,
553
+ fn: (executor: DbExecutor<DB>) => Promise<T>
554
+ ): Promise<T> {
555
+ if (isActiveTransaction(db)) {
556
+ if (!this._supportsTransactions) {
557
+ return fn(db);
558
+ }
559
+ const savepoint = createSavepointName();
560
+ await sql.raw(`SAVEPOINT ${savepoint}`).execute(db);
561
+ try {
562
+ const result = await fn(db);
563
+ await sql.raw(`RELEASE SAVEPOINT ${savepoint}`).execute(db);
564
+ return result;
565
+ } catch (error) {
566
+ await sql.raw(`ROLLBACK TO SAVEPOINT ${savepoint}`).execute(db);
567
+ await sql.raw(`RELEASE SAVEPOINT ${savepoint}`).execute(db);
568
+ throw error;
569
+ }
570
+ }
571
+ if (this._supportsTransactions) {
572
+ return db.transaction().execute(fn);
573
+ }
574
+ return fn(db);
575
+ }
576
+
577
+ async setRepeatableRead<DB extends SyncCoreDb>(
578
+ _trx: DbExecutor<DB>
579
+ ): Promise<void> {
580
+ // SQLite uses serializable isolation by default in WAL mode.
581
+ }
582
+
583
+ // ===========================================================================
584
+ // Commit/Change Log Queries (dialect-specific)
585
+ // ===========================================================================
586
+
587
+ async readChangesForCommits<DB extends SyncCoreDb>(
588
+ db: DbExecutor<DB>,
589
+ args: {
590
+ commitSeqs: number[];
591
+ table: string;
592
+ scopes: ScopeValues;
593
+ partitionId?: string;
594
+ }
595
+ ): Promise<SyncChangeRow[]> {
596
+ const partitionId = args.partitionId ?? 'default';
597
+ if (args.commitSeqs.length === 0) return [];
598
+ const scopeFilter = buildScopeFilterSql(args.scopes, 'scopes');
599
+
600
+ const commitSeqsIn = sql.join(
601
+ args.commitSeqs.map((seq) => sql`${seq}`),
602
+ sql`, `
603
+ );
604
+
605
+ // Fetch all changes for the table and commit sequences
606
+ const res = await sql<{
607
+ commit_seq: unknown;
608
+ table: string;
609
+ row_id: string;
610
+ op: string;
611
+ row_json: unknown | null;
612
+ row_version: unknown | null;
613
+ scopes: unknown;
614
+ }>`
615
+ SELECT commit_seq, "table", row_id, op, row_json, row_version, scopes
616
+ FROM sync_changes
617
+ WHERE commit_seq IN (${commitSeqsIn})
618
+ AND partition_id = ${partitionId}
619
+ AND "table" = ${args.table}
620
+ AND (${scopeFilter})
621
+ ORDER BY commit_seq ASC, change_id ASC
622
+ `.execute(db);
623
+
624
+ return res.rows.map((row) => ({
625
+ commit_seq: coerceNumber(row.commit_seq) ?? 0,
626
+ table: row.table,
627
+ row_id: row.row_id,
628
+ op: row.op as SyncOp,
629
+ row_json: parseJsonValue(row.row_json),
630
+ row_version: coerceNumber(row.row_version),
631
+ scopes: parseScopes(row.scopes),
632
+ }));
633
+ }
634
+
635
+ async readAuditRowHistory<DB extends SyncCoreDb>(
636
+ db: DbExecutor<DB>,
637
+ args: AuditRowHistoryArgs
638
+ ): Promise<AuditRowHistoryRow[]> {
639
+ const partitionId = args.partitionId ?? 'default';
640
+ const scopeFilter = buildScopeFilterSql(args.scopes, 'c.scopes');
641
+ const limit = Math.max(1, Math.min(200, args.limit));
642
+ const whereClauses = [
643
+ sql`c.partition_id = ${partitionId}`,
644
+ sql`cm.partition_id = ${partitionId}`,
645
+ sql`c."table" = ${args.table}`,
646
+ sql`c.row_id = ${args.rowId}`,
647
+ sql`(${scopeFilter})`,
648
+ ];
649
+
650
+ if (args.beforeCommitSeq !== undefined) {
651
+ whereClauses.push(sql`c.commit_seq < ${args.beforeCommitSeq}`);
652
+ }
653
+ if (args.afterCommitSeq !== undefined) {
654
+ whereClauses.push(sql`c.commit_seq >= ${args.afterCommitSeq}`);
655
+ }
656
+
657
+ const res = await sql<{
658
+ commit_seq: unknown;
659
+ actor_id: string;
660
+ client_id: string;
661
+ client_commit_id: string;
662
+ created_at: unknown;
663
+ change_id: unknown;
664
+ table: string;
665
+ row_id: string;
666
+ op: string;
667
+ row_json: unknown | null;
668
+ row_version: unknown | null;
669
+ scopes: unknown;
670
+ }>`
671
+ SELECT
672
+ c.commit_seq,
673
+ cm.actor_id,
674
+ cm.client_id,
675
+ cm.client_commit_id,
676
+ cm.created_at,
677
+ c.change_id,
678
+ c."table",
679
+ c.row_id,
680
+ c.op,
681
+ c.row_json,
682
+ c.row_version,
683
+ c.scopes
684
+ FROM sync_changes c
685
+ JOIN sync_commits cm
686
+ ON cm.partition_id = c.partition_id
687
+ AND cm.commit_seq = c.commit_seq
688
+ WHERE ${sql.join(whereClauses, sql` AND `)}
689
+ ORDER BY c.commit_seq DESC, c.change_id DESC
690
+ LIMIT ${limit + 1}
691
+ `.execute(db);
692
+
693
+ return res.rows.map((row) => ({
694
+ commit_seq: coerceNumber(row.commit_seq) ?? 0,
695
+ actor_id: row.actor_id,
696
+ client_id: row.client_id,
697
+ client_commit_id: row.client_commit_id,
698
+ created_at: coerceIsoString(row.created_at),
699
+ change_id: coerceNumber(row.change_id) ?? 0,
700
+ table: row.table,
701
+ row_id: row.row_id,
702
+ op: row.op as SyncOp,
703
+ row_json: parseJsonValue(row.row_json),
704
+ row_version: coerceNumber(row.row_version),
705
+ scopes: parseScopes(row.scopes),
706
+ }));
707
+ }
708
+
709
+ protected override async readIncrementalPullRowsBatch<DB extends SyncCoreDb>(
710
+ db: DbExecutor<DB>,
711
+ args: Omit<IncrementalPullRowsArgs, 'batchSize'>
712
+ ): Promise<IncrementalPullRow[]> {
713
+ const partitionId = args.partitionId ?? 'default';
714
+ const limitCommits = Math.max(1, Math.min(1000, args.limitCommits));
715
+ const scopeFilter = buildScopeFilterSql(args.scopes, 'c.scopes');
716
+
717
+ const commitSeqs = await this.readScopeIndexedCommitSeqsForPull(db, {
718
+ partitionId,
719
+ table: args.table,
720
+ scopes: args.scopes,
721
+ cursor: args.cursor,
722
+ limitCommits,
723
+ });
724
+
725
+ if (commitSeqs.length === 0) return [];
726
+ const scannedMaxCommitSeq =
727
+ commitSeqs[commitSeqs.length - 1] ?? args.cursor;
728
+
729
+ const commitSeqsIn = sql.join(
730
+ commitSeqs.map((seq) => sql`${seq}`),
731
+ sql`, `
732
+ );
733
+
734
+ // Get commits and changes for these commit_seqs
735
+ const changesRes = await sql<{
736
+ commit_seq: unknown;
737
+ actor_id: string;
738
+ created_at: unknown;
739
+ commit_digest: string | null;
740
+ commit_chain_root: string | null;
741
+ change_id: unknown;
742
+ table: string;
743
+ row_id: string;
744
+ op: string;
745
+ row_json: unknown | null;
746
+ row_version: unknown | null;
747
+ scopes: unknown;
748
+ }>`
749
+ SELECT
750
+ cm.commit_seq,
751
+ cm.actor_id,
752
+ cm.created_at,
753
+ cm.commit_digest,
754
+ cm.commit_chain_root,
755
+ c.change_id,
756
+ c."table",
757
+ c.row_id,
758
+ c.op,
759
+ c.row_json,
760
+ c.row_version,
761
+ c.scopes
762
+ FROM sync_commits cm
763
+ JOIN sync_changes c ON c.commit_seq = cm.commit_seq
764
+ WHERE cm.commit_seq IN (${commitSeqsIn})
765
+ AND cm.partition_id = ${partitionId}
766
+ AND c.partition_id = ${partitionId}
767
+ AND c."table" = ${args.table}
768
+ AND (${scopeFilter})
769
+ ORDER BY cm.commit_seq ASC, c.change_id ASC
770
+ `.execute(db);
771
+
772
+ return changesRes.rows.map((row) => ({
773
+ commit_seq: coerceNumber(row.commit_seq) ?? 0,
774
+ scanned_max_commit_seq: scannedMaxCommitSeq,
775
+ actor_id: row.actor_id,
776
+ created_at: coerceIsoString(row.created_at),
777
+ commit_digest: row.commit_digest ?? null,
778
+ commit_chain_root: row.commit_chain_root ?? null,
779
+ change_id: coerceNumber(row.change_id) ?? 0,
780
+ table: row.table,
781
+ row_id: row.row_id,
782
+ op: row.op as SyncOp,
783
+ row_json: parseJsonValue(row.row_json),
784
+ row_version: coerceNumber(row.row_version),
785
+ scopes: parseScopes(row.scopes),
786
+ }));
787
+ }
788
+
789
+ async compactChanges<DB extends SyncCoreDb>(
790
+ db: DbExecutor<DB>,
791
+ args: { fullHistoryHours: number }
792
+ ): Promise<number> {
793
+ const cutoffIso = new Date(
794
+ Date.now() - args.fullHistoryHours * 60 * 60 * 1000
795
+ ).toISOString();
796
+
797
+ const res = await sql`
798
+ WITH ranked AS (
799
+ SELECT
800
+ c.change_id,
801
+ row_number() OVER (
802
+ PARTITION BY c.partition_id, c."table", c.row_id, c.scopes
803
+ ORDER BY c.commit_seq DESC, c.change_id DESC
804
+ ) AS rn
805
+ FROM sync_changes c
806
+ JOIN sync_commits cm ON cm.commit_seq = c.commit_seq
807
+ WHERE cm.created_at < ${cutoffIso}
808
+ )
809
+ DELETE FROM sync_changes
810
+ WHERE change_id IN (SELECT change_id FROM ranked WHERE rn > 1)
811
+ `.execute(db);
812
+ const deleted = Number(res.numAffectedRows ?? 0);
813
+
814
+ // Remove routing index entries that no longer have any remaining changes
815
+ await sql`
816
+ DELETE FROM sync_scope_commits
817
+ WHERE commit_seq IN (
818
+ SELECT commit_seq
819
+ FROM sync_commits
820
+ WHERE created_at < ${cutoffIso}
821
+ AND partition_id = sync_scope_commits.partition_id
822
+ )
823
+ AND NOT EXISTS (
824
+ SELECT 1
825
+ FROM sync_changes c
826
+ WHERE c.commit_seq = sync_scope_commits.commit_seq
827
+ AND c.partition_id = sync_scope_commits.partition_id
828
+ AND c."table" = sync_scope_commits."table"
829
+ )
830
+ `.execute(db);
831
+
832
+ await sql`
833
+ DELETE FROM sync_table_commits
834
+ WHERE commit_seq IN (
835
+ SELECT commit_seq
836
+ FROM sync_commits
837
+ WHERE created_at < ${cutoffIso}
838
+ AND partition_id = sync_table_commits.partition_id
839
+ )
840
+ AND NOT EXISTS (
841
+ SELECT 1
842
+ FROM sync_changes c
843
+ WHERE c.commit_seq = sync_table_commits.commit_seq
844
+ AND c.partition_id = sync_table_commits.partition_id
845
+ AND c."table" = sync_table_commits."table"
846
+ )
847
+ `.execute(db);
848
+
849
+ return deleted;
850
+ }
851
+
852
+ // ===========================================================================
853
+ // Scope Conversion Helpers
854
+ // ===========================================================================
855
+
856
+ scopesToDb(scopes: StoredScopes): string {
857
+ return JSON.stringify(scopes);
858
+ }
859
+
860
+ dbToArray(value: unknown): string[] {
861
+ return toStringArray(value);
862
+ }
863
+
864
+ arrayToDb(values: string[]): string {
865
+ return JSON.stringify(values.filter((v) => v.length > 0));
866
+ }
867
+
868
+ // ===========================================================================
869
+ // Console Schema (Request Events)
870
+ // ===========================================================================
871
+
872
+ async ensureConsoleSchema<DB extends SyncCoreDb>(
873
+ db: Kysely<DB>
874
+ ): Promise<void> {
875
+ const nowIso = sql`(strftime('%Y-%m-%dT%H:%M:%fZ','now'))`;
876
+
877
+ await db.schema
878
+ .createTable('sync_request_events')
879
+ .ifNotExists()
880
+ .addColumn('event_id', 'integer', (col) =>
881
+ col.primaryKey().autoIncrement()
882
+ )
883
+ .addColumn('partition_id', 'text', (col) =>
884
+ col.notNull().defaultTo('default')
885
+ )
886
+ .addColumn('request_id', 'text')
887
+ .addColumn('trace_id', 'text')
888
+ .addColumn('span_id', 'text')
889
+ .addColumn('event_type', 'text', (col) => col.notNull())
890
+ .addColumn('actor_id', 'text', (col) => col.notNull())
891
+ .addColumn('client_id', 'text', (col) => col.notNull())
892
+ .addColumn('transport_path', 'text', (col) =>
893
+ col.notNull().defaultTo('direct')
894
+ )
895
+ .addColumn('sync_path', 'text', (col) =>
896
+ col.notNull().defaultTo('http-combined')
897
+ )
898
+ .addColumn('status_code', 'integer', (col) => col.notNull())
899
+ .addColumn('outcome', 'text', (col) => col.notNull())
900
+ .addColumn('response_status', 'text', (col) =>
901
+ col.notNull().defaultTo('unknown')
902
+ )
903
+ .addColumn('error_code', 'text')
904
+ .addColumn('duration_ms', 'integer', (col) => col.notNull())
905
+ .addColumn('commit_seq', 'integer')
906
+ .addColumn('operation_count', 'integer')
907
+ .addColumn('row_count', 'integer')
908
+ .addColumn('subscription_count', 'integer')
909
+ .addColumn('scopes_summary', 'text')
910
+ .addColumn('response_summary', 'text')
911
+ .addColumn('tables', 'text', (col) => col.notNull().defaultTo('[]'))
912
+ .addColumn('error_message', 'text')
913
+ .addColumn('payload_ref', 'text')
914
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
915
+ .execute();
916
+ await ensureConsoleEventColumns(db);
917
+
918
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_created_at
919
+ ON sync_request_events(created_at DESC)`.execute(db);
920
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_event_type
921
+ ON sync_request_events(event_type)`.execute(db);
922
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_client_id
923
+ ON sync_request_events(client_id)`.execute(db);
924
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_request_id
925
+ ON sync_request_events(request_id)`.execute(db);
926
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_trace_id
927
+ ON sync_request_events(trace_id)`.execute(db);
928
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_events_partition_created_at
929
+ ON sync_request_events(partition_id, created_at DESC)`.execute(db);
930
+
931
+ await db.schema
932
+ .createTable('sync_request_payloads')
933
+ .ifNotExists()
934
+ .addColumn('payload_ref', 'text', (col) => col.primaryKey())
935
+ .addColumn('partition_id', 'text', (col) =>
936
+ col.notNull().defaultTo('default')
937
+ )
938
+ .addColumn('request_payload', 'text', (col) => col.notNull())
939
+ .addColumn('response_payload', 'text')
940
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
941
+ .execute();
942
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_payloads_created_at
943
+ ON sync_request_payloads(created_at DESC)`.execute(db);
944
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_request_payloads_partition_created_at
945
+ ON sync_request_payloads(partition_id, created_at DESC)`.execute(db);
946
+
947
+ await db.schema
948
+ .createTable('sync_operation_events')
949
+ .ifNotExists()
950
+ .addColumn('operation_id', 'integer', (col) =>
951
+ col.primaryKey().autoIncrement()
952
+ )
953
+ .addColumn('operation_type', 'text', (col) => col.notNull())
954
+ .addColumn('console_user_id', 'text')
955
+ .addColumn('partition_id', 'text')
956
+ .addColumn('target_client_id', 'text')
957
+ .addColumn('request_payload', 'text')
958
+ .addColumn('result_payload', 'text')
959
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
960
+ .execute();
961
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_operation_events_created_at
962
+ ON sync_operation_events(created_at DESC)`.execute(db);
963
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_operation_events_type
964
+ ON sync_operation_events(operation_type)`.execute(db);
965
+
966
+ await db.schema
967
+ .createTable('sync_realtime_events')
968
+ .ifNotExists()
969
+ .addColumn('event_id', 'integer', (col) =>
970
+ col.primaryKey().autoIncrement()
971
+ )
972
+ .addColumn('partition_id', 'text', (col) =>
973
+ col.notNull().defaultTo('default')
974
+ )
975
+ .addColumn('actor_id', 'text', (col) => col.notNull())
976
+ .addColumn('client_id', 'text', (col) => col.notNull())
977
+ .addColumn('transport_path', 'text', (col) =>
978
+ col.notNull().defaultTo('direct')
979
+ )
980
+ .addColumn('event_type', 'text', (col) => col.notNull())
981
+ .addColumn('reason', 'text')
982
+ .addColumn('cursor', 'integer')
983
+ .addColumn('latest_cursor', 'integer')
984
+ .addColumn('commit_seq', 'integer')
985
+ .addColumn('scope_count', 'integer')
986
+ .addColumn('skipped_count', 'integer')
987
+ .addColumn('sync_pack_encoding', 'text')
988
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
989
+ .execute();
990
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_realtime_events_created_at
991
+ ON sync_realtime_events(created_at DESC)`.execute(db);
992
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_realtime_events_partition_client_created_at
993
+ ON sync_realtime_events(partition_id, client_id, created_at DESC)`.execute(
994
+ db
995
+ );
996
+
997
+ await db.schema
998
+ .createTable('sync_client_diagnostic_snapshots')
999
+ .ifNotExists()
1000
+ .addColumn('snapshot_id', 'integer', (col) =>
1001
+ col.primaryKey().autoIncrement()
1002
+ )
1003
+ .addColumn('partition_id', 'text', (col) =>
1004
+ col.notNull().defaultTo('default')
1005
+ )
1006
+ .addColumn('client_id', 'text', (col) => col.notNull())
1007
+ .addColumn('actor_id', 'text')
1008
+ .addColumn('runtime_kind', 'text')
1009
+ .addColumn('runtime_version', 'text')
1010
+ .addColumn('schema_version', 'integer')
1011
+ .addColumn('reported_at', 'text', (col) => col.notNull())
1012
+ .addColumn('received_at', 'text', (col) =>
1013
+ col.notNull().defaultTo(nowIso)
1014
+ )
1015
+ .addColumn('lifecycle_phase', 'text')
1016
+ .addColumn('connection_state', 'text')
1017
+ .addColumn('freshness_state', 'text', (col) => col.notNull())
1018
+ .addColumn('health_max_severity', 'text')
1019
+ .addColumn('diagnostic_codes_summary', 'text')
1020
+ .addColumn('queue_summary', 'text')
1021
+ .addColumn('timing_summary', 'text')
1022
+ .addColumn('redaction_summary', 'text')
1023
+ .addColumn('snapshot_json', 'text', (col) => col.notNull())
1024
+ .execute();
1025
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_client_diagnostic_snapshots_received_at
1026
+ ON sync_client_diagnostic_snapshots(received_at DESC)`.execute(db);
1027
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_client_diagnostic_snapshots_latest
1028
+ ON sync_client_diagnostic_snapshots(partition_id, client_id, received_at DESC)`.execute(
1029
+ db
1030
+ );
1031
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_client_diagnostic_snapshots_health
1032
+ ON sync_client_diagnostic_snapshots(partition_id, health_max_severity, received_at DESC)`.execute(
1033
+ db
1034
+ );
1035
+
1036
+ // API Keys table
1037
+ await db.schema
1038
+ .createTable('sync_api_keys')
1039
+ .ifNotExists()
1040
+ .addColumn('key_id', 'text', (col) => col.primaryKey())
1041
+ .addColumn('key_hash', 'text', (col) => col.notNull())
1042
+ .addColumn('key_prefix', 'text', (col) => col.notNull())
1043
+ .addColumn('name', 'text', (col) => col.notNull())
1044
+ .addColumn('key_type', 'text', (col) => col.notNull())
1045
+ .addColumn('scope_keys', 'text', (col) => col.defaultTo('[]'))
1046
+ .addColumn('actor_id', 'text')
1047
+ .addColumn('created_at', 'text', (col) => col.notNull().defaultTo(nowIso))
1048
+ .addColumn('expires_at', 'text')
1049
+ .addColumn('last_used_at', 'text')
1050
+ .addColumn('revoked_at', 'text')
1051
+ .execute();
1052
+
1053
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_api_keys_key_hash
1054
+ ON sync_api_keys(key_hash)`.execute(db);
1055
+ await sql`CREATE INDEX IF NOT EXISTS idx_sync_api_keys_key_type
1056
+ ON sync_api_keys(key_type)`.execute(db);
1057
+ }
1058
+ }
1059
+
1060
+ export function createSqliteServerDialect(options?: {
1061
+ supportsTransactions?: boolean;
1062
+ }): SqliteServerSyncDialect {
1063
+ return new SqliteServerSyncDialect(options);
1064
+ }