@syncular/server 0.1.3 → 0.2.1

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 (758) hide show
  1. package/README.md +773 -13
  2. package/dist/admin.d.ts +136 -0
  3. package/dist/admin.js +168 -0
  4. package/dist/blob-handlers.d.ts +69 -0
  5. package/dist/blob-handlers.js +245 -0
  6. package/dist/blob-store.d.ts +111 -0
  7. package/dist/blob-store.js +0 -0
  8. package/dist/content-encoding.d.ts +27 -0
  9. package/dist/content-encoding.js +69 -0
  10. package/dist/context.d.ts +151 -0
  11. package/dist/context.js +21 -0
  12. package/dist/crdt-merger.d.ts +28 -0
  13. package/dist/crdt-merger.js +11 -0
  14. package/dist/d1-storage.d.ts +50 -0
  15. package/dist/d1-storage.js +0 -0
  16. package/dist/errors.d.ts +35 -9
  17. package/dist/errors.js +220 -11
  18. package/dist/events-ring.d.ts +55 -0
  19. package/dist/events-ring.js +96 -0
  20. package/dist/events.d.ts +231 -0
  21. package/dist/events.js +22 -0
  22. package/dist/frame-bytes.d.ts +20 -0
  23. package/dist/frame-bytes.js +75 -0
  24. package/dist/handler.d.ts +9 -0
  25. package/dist/handler.js +490 -0
  26. package/dist/index.d.ts +42 -33
  27. package/dist/index.js +45 -27
  28. package/dist/lease-store.d.ts +50 -0
  29. package/dist/lease-store.js +0 -0
  30. package/dist/pg-executor-pglite.d.ts +27 -0
  31. package/dist/pg-executor-pglite.js +33 -0
  32. package/dist/pg-executor.d.ts +67 -0
  33. package/dist/pg-executor.js +56 -0
  34. package/dist/postgres-fanout.d.ts +83 -0
  35. package/dist/postgres-fanout.js +71 -0
  36. package/dist/postgres-storage.d.ts +69 -0
  37. package/dist/postgres-storage.js +766 -0
  38. package/dist/prune.d.ts +24 -39
  39. package/dist/prune.js +44 -146
  40. package/dist/pull.d.ts +42 -67
  41. package/dist/pull.js +387 -1187
  42. package/dist/push.d.ts +31 -66
  43. package/dist/push.js +406 -655
  44. package/dist/realtime.d.ts +197 -0
  45. package/dist/realtime.js +848 -0
  46. package/dist/relational-rows.d.ts +229 -0
  47. package/dist/relational-rows.js +514 -0
  48. package/dist/s3-blob-store.d.ts +142 -0
  49. package/dist/s3-blob-store.js +458 -0
  50. package/dist/s3-segment-store.d.ts +95 -0
  51. package/dist/s3-segment-store.js +380 -0
  52. package/dist/schema.d.ts +82 -286
  53. package/dist/schema.js +133 -9
  54. package/dist/scopes.d.ts +48 -0
  55. package/dist/scopes.js +98 -0
  56. package/dist/segment-download.d.ts +14 -0
  57. package/dist/segment-download.js +125 -0
  58. package/dist/segment-store.d.ts +81 -0
  59. package/dist/segment-store.js +63 -0
  60. package/dist/signed-url.d.ts +139 -0
  61. package/dist/signed-url.js +143 -0
  62. package/dist/sigv4.d.ts +97 -0
  63. package/dist/sigv4.js +161 -0
  64. package/dist/sqlite-blob-store.d.ts +22 -0
  65. package/dist/sqlite-blob-store.js +86 -0
  66. package/dist/sqlite-dialect.d.ts +101 -0
  67. package/dist/sqlite-dialect.js +250 -0
  68. package/dist/sqlite-image.d.ts +27 -0
  69. package/dist/sqlite-image.js +83 -0
  70. package/dist/sqlite-lease-store.d.ts +21 -0
  71. package/dist/sqlite-lease-store.js +94 -0
  72. package/dist/sqlite-segment-store.d.ts +25 -0
  73. package/dist/sqlite-segment-store.js +115 -0
  74. package/dist/sqlite-storage.d.ts +47 -0
  75. package/dist/sqlite-storage.js +477 -0
  76. package/dist/storage.d.ts +252 -0
  77. package/dist/storage.js +1 -0
  78. package/dist/validate.d.ts +83 -0
  79. package/dist/validate.js +44 -0
  80. package/package.json +17 -325
  81. package/src/admin.ts +312 -0
  82. package/src/blob-handlers.ts +340 -0
  83. package/src/blob-store.ts +0 -0
  84. package/src/content-encoding.ts +94 -0
  85. package/src/context.ts +176 -0
  86. package/src/crdt-merger.ts +33 -0
  87. package/src/d1-storage.ts +0 -0
  88. package/src/errors.ts +241 -19
  89. package/src/events-ring.ts +116 -0
  90. package/src/events.ts +291 -0
  91. package/src/frame-bytes.ts +94 -0
  92. package/src/handler.ts +678 -0
  93. package/src/index.ts +44 -27
  94. package/src/lease-store.ts +0 -0
  95. package/src/pg-executor-pglite.ts +63 -0
  96. package/src/pg-executor.ts +87 -0
  97. package/src/postgres-fanout.ts +143 -0
  98. package/src/postgres-storage.ts +1182 -0
  99. package/src/prune.ts +63 -289
  100. package/src/pull.ts +505 -1805
  101. package/src/push.ts +623 -1006
  102. package/src/realtime.ts +1082 -0
  103. package/src/relational-rows.ts +633 -0
  104. package/src/s3-blob-store.ts +616 -0
  105. package/src/s3-segment-store.ts +519 -0
  106. package/src/schema.ts +242 -281
  107. package/src/scopes.ts +127 -0
  108. package/src/segment-download.ts +169 -0
  109. package/src/segment-store.ts +154 -0
  110. package/src/signed-url.ts +308 -0
  111. package/src/sigv4.ts +267 -0
  112. package/src/sqlite-blob-store.ts +125 -0
  113. package/src/sqlite-dialect.ts +337 -0
  114. package/src/sqlite-image.ts +124 -0
  115. package/src/sqlite-lease-store.ts +144 -0
  116. package/src/sqlite-segment-store.ts +204 -0
  117. package/src/sqlite-storage.ts +792 -0
  118. package/src/storage.ts +316 -0
  119. package/src/validate.ts +123 -0
  120. package/dist/auth-leases.d.ts +0 -85
  121. package/dist/auth-leases.d.ts.map +0 -1
  122. package/dist/auth-leases.js +0 -368
  123. package/dist/auth-leases.js.map +0 -1
  124. package/dist/better-sqlite3.d.ts +0 -22
  125. package/dist/better-sqlite3.d.ts.map +0 -1
  126. package/dist/better-sqlite3.js +0 -16
  127. package/dist/better-sqlite3.js.map +0 -1
  128. package/dist/blobs/access.d.ts +0 -35
  129. package/dist/blobs/access.d.ts.map +0 -1
  130. package/dist/blobs/access.js +0 -149
  131. package/dist/blobs/access.js.map +0 -1
  132. package/dist/blobs/adapters/database.d.ts +0 -100
  133. package/dist/blobs/adapters/database.d.ts.map +0 -1
  134. package/dist/blobs/adapters/database.js +0 -247
  135. package/dist/blobs/adapters/database.js.map +0 -1
  136. package/dist/blobs/index.d.ts +0 -9
  137. package/dist/blobs/index.d.ts.map +0 -1
  138. package/dist/blobs/index.js +0 -9
  139. package/dist/blobs/index.js.map +0 -1
  140. package/dist/blobs/manager.d.ts +0 -220
  141. package/dist/blobs/manager.d.ts.map +0 -1
  142. package/dist/blobs/manager.js +0 -625
  143. package/dist/blobs/manager.js.map +0 -1
  144. package/dist/blobs/migrate.d.ts +0 -27
  145. package/dist/blobs/migrate.d.ts.map +0 -1
  146. package/dist/blobs/migrate.js +0 -127
  147. package/dist/blobs/migrate.js.map +0 -1
  148. package/dist/blobs/types.d.ts +0 -58
  149. package/dist/blobs/types.d.ts.map +0 -1
  150. package/dist/blobs/types.js +0 -5
  151. package/dist/blobs/types.js.map +0 -1
  152. package/dist/bun-sqlite.d.ts +0 -19
  153. package/dist/bun-sqlite.d.ts.map +0 -1
  154. package/dist/bun-sqlite.js +0 -19
  155. package/dist/bun-sqlite.js.map +0 -1
  156. package/dist/clients.d.ts +0 -15
  157. package/dist/clients.d.ts.map +0 -1
  158. package/dist/clients.js +0 -7
  159. package/dist/clients.js.map +0 -1
  160. package/dist/cloudflare/durable-object.d.ts +0 -93
  161. package/dist/cloudflare/durable-object.d.ts.map +0 -1
  162. package/dist/cloudflare/durable-object.js +0 -210
  163. package/dist/cloudflare/durable-object.js.map +0 -1
  164. package/dist/cloudflare/index.d.ts +0 -22
  165. package/dist/cloudflare/index.d.ts.map +0 -1
  166. package/dist/cloudflare/index.js +0 -22
  167. package/dist/cloudflare/index.js.map +0 -1
  168. package/dist/cloudflare/r2.d.ts +0 -180
  169. package/dist/cloudflare/r2.d.ts.map +0 -1
  170. package/dist/cloudflare/r2.js +0 -258
  171. package/dist/cloudflare/r2.js.map +0 -1
  172. package/dist/cloudflare/scope-cache.d.ts +0 -54
  173. package/dist/cloudflare/scope-cache.d.ts.map +0 -1
  174. package/dist/cloudflare/scope-cache.js +0 -223
  175. package/dist/cloudflare/scope-cache.js.map +0 -1
  176. package/dist/cloudflare/sentry.d.ts +0 -47
  177. package/dist/cloudflare/sentry.d.ts.map +0 -1
  178. package/dist/cloudflare/sentry.js +0 -163
  179. package/dist/cloudflare/sentry.js.map +0 -1
  180. package/dist/cloudflare/worker.d.ts +0 -46
  181. package/dist/cloudflare/worker.d.ts.map +0 -1
  182. package/dist/cloudflare/worker.js +0 -63
  183. package/dist/cloudflare/worker.js.map +0 -1
  184. package/dist/commit-integrity.d.ts +0 -38
  185. package/dist/commit-integrity.d.ts.map +0 -1
  186. package/dist/commit-integrity.js +0 -260
  187. package/dist/commit-integrity.js.map +0 -1
  188. package/dist/compaction.d.ts +0 -27
  189. package/dist/compaction.d.ts.map +0 -1
  190. package/dist/compaction.js +0 -49
  191. package/dist/compaction.js.map +0 -1
  192. package/dist/crdt-yjs/index.d.ts +0 -99
  193. package/dist/crdt-yjs/index.d.ts.map +0 -1
  194. package/dist/crdt-yjs/index.js +0 -629
  195. package/dist/crdt-yjs/index.js.map +0 -1
  196. package/dist/d1.d.ts +0 -13
  197. package/dist/d1.d.ts.map +0 -1
  198. package/dist/d1.js +0 -14
  199. package/dist/d1.js.map +0 -1
  200. package/dist/dialect/base.d.ts +0 -93
  201. package/dist/dialect/base.d.ts.map +0 -1
  202. package/dist/dialect/base.js +0 -181
  203. package/dist/dialect/base.js.map +0 -1
  204. package/dist/dialect/helpers.d.ts +0 -16
  205. package/dist/dialect/helpers.d.ts.map +0 -1
  206. package/dist/dialect/helpers.js +0 -83
  207. package/dist/dialect/helpers.js.map +0 -1
  208. package/dist/dialect/index.d.ts +0 -7
  209. package/dist/dialect/index.d.ts.map +0 -1
  210. package/dist/dialect/index.js +0 -7
  211. package/dist/dialect/index.js.map +0 -1
  212. package/dist/dialect/types.d.ts +0 -187
  213. package/dist/dialect/types.d.ts.map +0 -1
  214. package/dist/dialect/types.js +0 -8
  215. package/dist/dialect/types.js.map +0 -1
  216. package/dist/encrypted-crdt.d.ts +0 -67
  217. package/dist/encrypted-crdt.d.ts.map +0 -1
  218. package/dist/encrypted-crdt.js +0 -426
  219. package/dist/encrypted-crdt.js.map +0 -1
  220. package/dist/errors.d.ts.map +0 -1
  221. package/dist/errors.js.map +0 -1
  222. package/dist/filesystem/index.d.ts +0 -44
  223. package/dist/filesystem/index.d.ts.map +0 -1
  224. package/dist/filesystem/index.js +0 -164
  225. package/dist/filesystem/index.js.map +0 -1
  226. package/dist/handlers/collection.d.ts +0 -20
  227. package/dist/handlers/collection.d.ts.map +0 -1
  228. package/dist/handlers/collection.js +0 -42
  229. package/dist/handlers/collection.js.map +0 -1
  230. package/dist/handlers/create-handler.d.ts +0 -156
  231. package/dist/handlers/create-handler.d.ts.map +0 -1
  232. package/dist/handlers/create-handler.js +0 -626
  233. package/dist/handlers/create-handler.js.map +0 -1
  234. package/dist/handlers/index.d.ts +0 -4
  235. package/dist/handlers/index.d.ts.map +0 -1
  236. package/dist/handlers/index.js +0 -4
  237. package/dist/handlers/index.js.map +0 -1
  238. package/dist/handlers/types.d.ts +0 -295
  239. package/dist/handlers/types.d.ts.map +0 -1
  240. package/dist/handlers/types.js +0 -2
  241. package/dist/handlers/types.js.map +0 -1
  242. package/dist/helpers/conflict.d.ts +0 -52
  243. package/dist/helpers/conflict.d.ts.map +0 -1
  244. package/dist/helpers/conflict.js +0 -49
  245. package/dist/helpers/conflict.js.map +0 -1
  246. package/dist/helpers/emitted-change.d.ts +0 -56
  247. package/dist/helpers/emitted-change.d.ts.map +0 -1
  248. package/dist/helpers/emitted-change.js +0 -46
  249. package/dist/helpers/emitted-change.js.map +0 -1
  250. package/dist/helpers/index.d.ts +0 -12
  251. package/dist/helpers/index.d.ts.map +0 -1
  252. package/dist/helpers/index.js +0 -12
  253. package/dist/helpers/index.js.map +0 -1
  254. package/dist/helpers/paginate.d.ts +0 -49
  255. package/dist/helpers/paginate.d.ts.map +0 -1
  256. package/dist/helpers/paginate.js +0 -54
  257. package/dist/helpers/paginate.js.map +0 -1
  258. package/dist/helpers/scope-authorization.d.ts +0 -7
  259. package/dist/helpers/scope-authorization.d.ts.map +0 -1
  260. package/dist/helpers/scope-authorization.js +0 -19
  261. package/dist/helpers/scope-authorization.js.map +0 -1
  262. package/dist/helpers/scope-commit-index.d.ts +0 -12
  263. package/dist/helpers/scope-commit-index.d.ts.map +0 -1
  264. package/dist/helpers/scope-commit-index.js +0 -35
  265. package/dist/helpers/scope-commit-index.js.map +0 -1
  266. package/dist/helpers/scope-strings.d.ts +0 -74
  267. package/dist/helpers/scope-strings.d.ts.map +0 -1
  268. package/dist/helpers/scope-strings.js +0 -82
  269. package/dist/helpers/scope-strings.js.map +0 -1
  270. package/dist/hono/api-key-auth.d.ts +0 -49
  271. package/dist/hono/api-key-auth.d.ts.map +0 -1
  272. package/dist/hono/api-key-auth.js +0 -108
  273. package/dist/hono/api-key-auth.js.map +0 -1
  274. package/dist/hono/audit-redaction.d.ts +0 -20
  275. package/dist/hono/audit-redaction.d.ts.map +0 -1
  276. package/dist/hono/audit-redaction.js +0 -85
  277. package/dist/hono/audit-redaction.js.map +0 -1
  278. package/dist/hono/blobs.d.ts +0 -75
  279. package/dist/hono/blobs.d.ts.map +0 -1
  280. package/dist/hono/blobs.js +0 -586
  281. package/dist/hono/blobs.js.map +0 -1
  282. package/dist/hono/console/gateway.d.ts +0 -42
  283. package/dist/hono/console/gateway.d.ts.map +0 -1
  284. package/dist/hono/console/gateway.js +0 -2158
  285. package/dist/hono/console/gateway.js.map +0 -1
  286. package/dist/hono/console/live-auth.d.ts +0 -7
  287. package/dist/hono/console/live-auth.d.ts.map +0 -1
  288. package/dist/hono/console/live-auth.js +0 -39
  289. package/dist/hono/console/live-auth.js.map +0 -1
  290. package/dist/hono/console/route-descriptor.d.ts +0 -6
  291. package/dist/hono/console/route-descriptor.d.ts.map +0 -1
  292. package/dist/hono/console/route-descriptor.js +0 -16
  293. package/dist/hono/console/route-descriptor.js.map +0 -1
  294. package/dist/hono/console/routes/api-keys.d.ts +0 -8
  295. package/dist/hono/console/routes/api-keys.d.ts.map +0 -1
  296. package/dist/hono/console/routes/api-keys.js +0 -575
  297. package/dist/hono/console/routes/api-keys.js.map +0 -1
  298. package/dist/hono/console/routes/clients.d.ts +0 -8
  299. package/dist/hono/console/routes/clients.d.ts.map +0 -1
  300. package/dist/hono/console/routes/clients.js +0 -322
  301. package/dist/hono/console/routes/clients.js.map +0 -1
  302. package/dist/hono/console/routes/commits.d.ts +0 -8
  303. package/dist/hono/console/routes/commits.d.ts.map +0 -1
  304. package/dist/hono/console/routes/commits.js +0 -882
  305. package/dist/hono/console/routes/commits.js.map +0 -1
  306. package/dist/hono/console/routes/context.d.ts +0 -188
  307. package/dist/hono/console/routes/context.d.ts.map +0 -1
  308. package/dist/hono/console/routes/context.js +0 -633
  309. package/dist/hono/console/routes/context.js.map +0 -1
  310. package/dist/hono/console/routes/events.d.ts +0 -8
  311. package/dist/hono/console/routes/events.d.ts.map +0 -1
  312. package/dist/hono/console/routes/events.js +0 -501
  313. package/dist/hono/console/routes/events.js.map +0 -1
  314. package/dist/hono/console/routes/maintenance.d.ts +0 -8
  315. package/dist/hono/console/routes/maintenance.d.ts.map +0 -1
  316. package/dist/hono/console/routes/maintenance.js +0 -341
  317. package/dist/hono/console/routes/maintenance.js.map +0 -1
  318. package/dist/hono/console/routes/shared.d.ts +0 -174
  319. package/dist/hono/console/routes/shared.d.ts.map +0 -1
  320. package/dist/hono/console/routes/shared.js +0 -583
  321. package/dist/hono/console/routes/shared.js.map +0 -1
  322. package/dist/hono/console/routes/stats.d.ts +0 -8
  323. package/dist/hono/console/routes/stats.d.ts.map +0 -1
  324. package/dist/hono/console/routes/stats.js +0 -295
  325. package/dist/hono/console/routes/stats.js.map +0 -1
  326. package/dist/hono/console/routes/storage.d.ts +0 -8
  327. package/dist/hono/console/routes/storage.d.ts.map +0 -1
  328. package/dist/hono/console/routes/storage.js +0 -121
  329. package/dist/hono/console/routes/storage.js.map +0 -1
  330. package/dist/hono/console/routes.d.ts +0 -36
  331. package/dist/hono/console/routes.d.ts.map +0 -1
  332. package/dist/hono/console/routes.js +0 -112
  333. package/dist/hono/console/routes.js.map +0 -1
  334. package/dist/hono/console/schema-errors.d.ts +0 -2
  335. package/dist/hono/console/schema-errors.d.ts.map +0 -1
  336. package/dist/hono/console/schema-errors.js +0 -17
  337. package/dist/hono/console/schema-errors.js.map +0 -1
  338. package/dist/hono/console/schemas.d.ts +0 -1515
  339. package/dist/hono/console/schemas.d.ts.map +0 -1
  340. package/dist/hono/console/schemas.js +0 -661
  341. package/dist/hono/console/schemas.js.map +0 -1
  342. package/dist/hono/console/types.d.ts +0 -213
  343. package/dist/hono/console/types.d.ts.map +0 -1
  344. package/dist/hono/console/types.js +0 -2
  345. package/dist/hono/console/types.js.map +0 -1
  346. package/dist/hono/console/ui.d.ts +0 -38
  347. package/dist/hono/console/ui.d.ts.map +0 -1
  348. package/dist/hono/console/ui.js +0 -43
  349. package/dist/hono/console/ui.js.map +0 -1
  350. package/dist/hono/create-server.d.ts +0 -71
  351. package/dist/hono/create-server.d.ts.map +0 -1
  352. package/dist/hono/create-server.js +0 -121
  353. package/dist/hono/create-server.js.map +0 -1
  354. package/dist/hono/errors.d.ts +0 -13
  355. package/dist/hono/errors.d.ts.map +0 -1
  356. package/dist/hono/errors.js +0 -21
  357. package/dist/hono/errors.js.map +0 -1
  358. package/dist/hono/index.d.ts +0 -20
  359. package/dist/hono/index.d.ts.map +0 -1
  360. package/dist/hono/index.js +0 -31
  361. package/dist/hono/index.js.map +0 -1
  362. package/dist/hono/openapi.d.ts +0 -72
  363. package/dist/hono/openapi.d.ts.map +0 -1
  364. package/dist/hono/openapi.js +0 -99
  365. package/dist/hono/openapi.js.map +0 -1
  366. package/dist/hono/proxy/connection-manager.d.ts +0 -78
  367. package/dist/hono/proxy/connection-manager.d.ts.map +0 -1
  368. package/dist/hono/proxy/connection-manager.js +0 -251
  369. package/dist/hono/proxy/connection-manager.js.map +0 -1
  370. package/dist/hono/proxy/index.d.ts +0 -8
  371. package/dist/hono/proxy/index.d.ts.map +0 -1
  372. package/dist/hono/proxy/index.js +0 -8
  373. package/dist/hono/proxy/index.js.map +0 -1
  374. package/dist/hono/proxy/routes.d.ts +0 -86
  375. package/dist/hono/proxy/routes.d.ts.map +0 -1
  376. package/dist/hono/proxy/routes.js +0 -183
  377. package/dist/hono/proxy/routes.js.map +0 -1
  378. package/dist/hono/rate-limit.d.ts +0 -101
  379. package/dist/hono/rate-limit.d.ts.map +0 -1
  380. package/dist/hono/rate-limit.js +0 -184
  381. package/dist/hono/rate-limit.js.map +0 -1
  382. package/dist/hono/realtime-sync-packs.d.ts +0 -43
  383. package/dist/hono/realtime-sync-packs.d.ts.map +0 -1
  384. package/dist/hono/realtime-sync-packs.js +0 -219
  385. package/dist/hono/realtime-sync-packs.js.map +0 -1
  386. package/dist/hono/routes/audit.d.ts +0 -12
  387. package/dist/hono/routes/audit.d.ts.map +0 -1
  388. package/dist/hono/routes/audit.js +0 -385
  389. package/dist/hono/routes/audit.js.map +0 -1
  390. package/dist/hono/routes/auth-leases.d.ts +0 -8
  391. package/dist/hono/routes/auth-leases.d.ts.map +0 -1
  392. package/dist/hono/routes/auth-leases.js +0 -88
  393. package/dist/hono/routes/auth-leases.js.map +0 -1
  394. package/dist/hono/routes/combined.d.ts +0 -8
  395. package/dist/hono/routes/combined.d.ts.map +0 -1
  396. package/dist/hono/routes/combined.js +0 -392
  397. package/dist/hono/routes/combined.js.map +0 -1
  398. package/dist/hono/routes/context.d.ts +0 -314
  399. package/dist/hono/routes/context.d.ts.map +0 -1
  400. package/dist/hono/routes/context.js +0 -1147
  401. package/dist/hono/routes/context.js.map +0 -1
  402. package/dist/hono/routes/health.d.ts +0 -8
  403. package/dist/hono/routes/health.d.ts.map +0 -1
  404. package/dist/hono/routes/health.js +0 -16
  405. package/dist/hono/routes/health.js.map +0 -1
  406. package/dist/hono/routes/realtime.d.ts +0 -9
  407. package/dist/hono/routes/realtime.d.ts.map +0 -1
  408. package/dist/hono/routes/realtime.js +0 -626
  409. package/dist/hono/routes/realtime.js.map +0 -1
  410. package/dist/hono/routes/shared.d.ts +0 -689
  411. package/dist/hono/routes/shared.d.ts.map +0 -1
  412. package/dist/hono/routes/shared.js +0 -972
  413. package/dist/hono/routes/shared.js.map +0 -1
  414. package/dist/hono/routes/snapshots.d.ts +0 -10
  415. package/dist/hono/routes/snapshots.d.ts.map +0 -1
  416. package/dist/hono/routes/snapshots.js +0 -281
  417. package/dist/hono/routes/snapshots.js.map +0 -1
  418. package/dist/hono/routes.d.ts +0 -19
  419. package/dist/hono/routes.d.ts.map +0 -1
  420. package/dist/hono/routes.js +0 -37
  421. package/dist/hono/routes.js.map +0 -1
  422. package/dist/hono/validation.d.ts +0 -5
  423. package/dist/hono/validation.d.ts.map +0 -1
  424. package/dist/hono/validation.js +0 -47
  425. package/dist/hono/validation.js.map +0 -1
  426. package/dist/hono/websocket-origin.d.ts +0 -9
  427. package/dist/hono/websocket-origin.d.ts.map +0 -1
  428. package/dist/hono/websocket-origin.js +0 -96
  429. package/dist/hono/websocket-origin.js.map +0 -1
  430. package/dist/hono/ws.d.ts +0 -341
  431. package/dist/hono/ws.d.ts.map +0 -1
  432. package/dist/hono/ws.js +0 -711
  433. package/dist/hono/ws.js.map +0 -1
  434. package/dist/index.d.ts.map +0 -1
  435. package/dist/index.js.map +0 -1
  436. package/dist/libsql.d.ts +0 -29
  437. package/dist/libsql.d.ts.map +0 -1
  438. package/dist/libsql.js +0 -25
  439. package/dist/libsql.js.map +0 -1
  440. package/dist/migrate.d.ts +0 -14
  441. package/dist/migrate.d.ts.map +0 -1
  442. package/dist/migrate.js +0 -13
  443. package/dist/migrate.js.map +0 -1
  444. package/dist/neon.d.ts +0 -21
  445. package/dist/neon.d.ts.map +0 -1
  446. package/dist/neon.js +0 -22
  447. package/dist/neon.js.map +0 -1
  448. package/dist/notify.d.ts +0 -77
  449. package/dist/notify.d.ts.map +0 -1
  450. package/dist/notify.js +0 -233
  451. package/dist/notify.js.map +0 -1
  452. package/dist/pglite.d.ts +0 -37
  453. package/dist/pglite.d.ts.map +0 -1
  454. package/dist/pglite.js +0 -37
  455. package/dist/pglite.js.map +0 -1
  456. package/dist/plugins/index.d.ts +0 -2
  457. package/dist/plugins/index.d.ts.map +0 -1
  458. package/dist/plugins/index.js +0 -2
  459. package/dist/plugins/index.js.map +0 -1
  460. package/dist/plugins/types.d.ts +0 -73
  461. package/dist/plugins/types.d.ts.map +0 -1
  462. package/dist/plugins/types.js +0 -30
  463. package/dist/plugins/types.js.map +0 -1
  464. package/dist/postgres/index.d.ts +0 -68
  465. package/dist/postgres/index.d.ts.map +0 -1
  466. package/dist/postgres/index.js +0 -924
  467. package/dist/postgres/index.js.map +0 -1
  468. package/dist/proxy/collection.d.ts +0 -7
  469. package/dist/proxy/collection.d.ts.map +0 -1
  470. package/dist/proxy/collection.js +0 -6
  471. package/dist/proxy/collection.js.map +0 -1
  472. package/dist/proxy/handler.d.ts +0 -42
  473. package/dist/proxy/handler.d.ts.map +0 -1
  474. package/dist/proxy/handler.js +0 -102
  475. package/dist/proxy/handler.js.map +0 -1
  476. package/dist/proxy/index.d.ts +0 -9
  477. package/dist/proxy/index.d.ts.map +0 -1
  478. package/dist/proxy/index.js +0 -14
  479. package/dist/proxy/index.js.map +0 -1
  480. package/dist/proxy/mutation-detector.d.ts +0 -35
  481. package/dist/proxy/mutation-detector.d.ts.map +0 -1
  482. package/dist/proxy/mutation-detector.js +0 -246
  483. package/dist/proxy/mutation-detector.js.map +0 -1
  484. package/dist/proxy/oplog.d.ts +0 -30
  485. package/dist/proxy/oplog.d.ts.map +0 -1
  486. package/dist/proxy/oplog.js +0 -137
  487. package/dist/proxy/oplog.js.map +0 -1
  488. package/dist/proxy/types.d.ts +0 -44
  489. package/dist/proxy/types.d.ts.map +0 -1
  490. package/dist/proxy/types.js +0 -7
  491. package/dist/proxy/types.js.map +0 -1
  492. package/dist/prune.d.ts.map +0 -1
  493. package/dist/prune.js.map +0 -1
  494. package/dist/pull.d.ts.map +0 -1
  495. package/dist/pull.js.map +0 -1
  496. package/dist/push.d.ts.map +0 -1
  497. package/dist/push.js.map +0 -1
  498. package/dist/realtime/in-memory.d.ts +0 -13
  499. package/dist/realtime/in-memory.d.ts.map +0 -1
  500. package/dist/realtime/in-memory.js +0 -28
  501. package/dist/realtime/in-memory.js.map +0 -1
  502. package/dist/realtime/index.d.ts +0 -4
  503. package/dist/realtime/index.d.ts.map +0 -1
  504. package/dist/realtime/index.js +0 -3
  505. package/dist/realtime/index.js.map +0 -1
  506. package/dist/realtime/types.d.ts +0 -62
  507. package/dist/realtime/types.d.ts.map +0 -1
  508. package/dist/realtime/types.js +0 -19
  509. package/dist/realtime/types.js.map +0 -1
  510. package/dist/relay/client-role/forward-engine.d.ts +0 -63
  511. package/dist/relay/client-role/forward-engine.d.ts.map +0 -1
  512. package/dist/relay/client-role/forward-engine.js +0 -267
  513. package/dist/relay/client-role/forward-engine.js.map +0 -1
  514. package/dist/relay/client-role/index.d.ts +0 -9
  515. package/dist/relay/client-role/index.d.ts.map +0 -1
  516. package/dist/relay/client-role/index.js +0 -9
  517. package/dist/relay/client-role/index.js.map +0 -1
  518. package/dist/relay/client-role/pull-engine.d.ts +0 -72
  519. package/dist/relay/client-role/pull-engine.d.ts.map +0 -1
  520. package/dist/relay/client-role/pull-engine.js +0 -249
  521. package/dist/relay/client-role/pull-engine.js.map +0 -1
  522. package/dist/relay/client-role/sequence-mapper.d.ts +0 -65
  523. package/dist/relay/client-role/sequence-mapper.d.ts.map +0 -1
  524. package/dist/relay/client-role/sequence-mapper.js +0 -161
  525. package/dist/relay/client-role/sequence-mapper.js.map +0 -1
  526. package/dist/relay/evaluation/relay-paths.d.ts +0 -41
  527. package/dist/relay/evaluation/relay-paths.d.ts.map +0 -1
  528. package/dist/relay/evaluation/relay-paths.js +0 -504
  529. package/dist/relay/evaluation/relay-paths.js.map +0 -1
  530. package/dist/relay/evaluation/rust-boundary.d.ts +0 -47
  531. package/dist/relay/evaluation/rust-boundary.d.ts.map +0 -1
  532. package/dist/relay/evaluation/rust-boundary.js +0 -220
  533. package/dist/relay/evaluation/rust-boundary.js.map +0 -1
  534. package/dist/relay/index.d.ts +0 -37
  535. package/dist/relay/index.d.ts.map +0 -1
  536. package/dist/relay/index.js +0 -44
  537. package/dist/relay/index.js.map +0 -1
  538. package/dist/relay/migrate.d.ts +0 -18
  539. package/dist/relay/migrate.d.ts.map +0 -1
  540. package/dist/relay/migrate.js +0 -99
  541. package/dist/relay/migrate.js.map +0 -1
  542. package/dist/relay/mode-manager.d.ts +0 -60
  543. package/dist/relay/mode-manager.d.ts.map +0 -1
  544. package/dist/relay/mode-manager.js +0 -114
  545. package/dist/relay/mode-manager.js.map +0 -1
  546. package/dist/relay/realtime.d.ts +0 -90
  547. package/dist/relay/realtime.d.ts.map +0 -1
  548. package/dist/relay/realtime.js +0 -147
  549. package/dist/relay/realtime.js.map +0 -1
  550. package/dist/relay/relay.d.ts +0 -190
  551. package/dist/relay/relay.d.ts.map +0 -1
  552. package/dist/relay/relay.js +0 -320
  553. package/dist/relay/relay.js.map +0 -1
  554. package/dist/relay/schema.d.ts +0 -158
  555. package/dist/relay/schema.d.ts.map +0 -1
  556. package/dist/relay/schema.js +0 -7
  557. package/dist/relay/schema.js.map +0 -1
  558. package/dist/relay/server-role/index.d.ts +0 -56
  559. package/dist/relay/server-role/index.d.ts.map +0 -1
  560. package/dist/relay/server-role/index.js +0 -193
  561. package/dist/relay/server-role/index.js.map +0 -1
  562. package/dist/relay/server-role/pull.d.ts +0 -27
  563. package/dist/relay/server-role/pull.d.ts.map +0 -1
  564. package/dist/relay/server-role/pull.js +0 -24
  565. package/dist/relay/server-role/pull.js.map +0 -1
  566. package/dist/relay/server-role/push.d.ts +0 -29
  567. package/dist/relay/server-role/push.d.ts.map +0 -1
  568. package/dist/relay/server-role/push.js +0 -122
  569. package/dist/relay/server-role/push.js.map +0 -1
  570. package/dist/s3/index.d.ts +0 -83
  571. package/dist/s3/index.d.ts.map +0 -1
  572. package/dist/s3/index.js +0 -223
  573. package/dist/s3/index.js.map +0 -1
  574. package/dist/schema.d.ts.map +0 -1
  575. package/dist/schema.js.map +0 -1
  576. package/dist/service-worker/index.d.ts +0 -114
  577. package/dist/service-worker/index.d.ts.map +0 -1
  578. package/dist/service-worker/index.js +0 -487
  579. package/dist/service-worker/index.js.map +0 -1
  580. package/dist/snapshot-artifacts/sqlite-bun.d.ts +0 -15
  581. package/dist/snapshot-artifacts/sqlite-bun.d.ts.map +0 -1
  582. package/dist/snapshot-artifacts/sqlite-bun.js +0 -128
  583. package/dist/snapshot-artifacts/sqlite-bun.js.map +0 -1
  584. package/dist/snapshot-artifacts.d.ts +0 -206
  585. package/dist/snapshot-artifacts.d.ts.map +0 -1
  586. package/dist/snapshot-artifacts.js +0 -545
  587. package/dist/snapshot-artifacts.js.map +0 -1
  588. package/dist/snapshot-chunks/db-metadata.d.ts +0 -56
  589. package/dist/snapshot-chunks/db-metadata.d.ts.map +0 -1
  590. package/dist/snapshot-chunks/db-metadata.js +0 -360
  591. package/dist/snapshot-chunks/db-metadata.js.map +0 -1
  592. package/dist/snapshot-chunks/index.d.ts +0 -8
  593. package/dist/snapshot-chunks/index.d.ts.map +0 -1
  594. package/dist/snapshot-chunks/index.js +0 -8
  595. package/dist/snapshot-chunks/index.js.map +0 -1
  596. package/dist/snapshot-chunks/types.d.ts +0 -80
  597. package/dist/snapshot-chunks/types.d.ts.map +0 -1
  598. package/dist/snapshot-chunks/types.js +0 -8
  599. package/dist/snapshot-chunks/types.js.map +0 -1
  600. package/dist/snapshot-chunks.d.ts +0 -90
  601. package/dist/snapshot-chunks.d.ts.map +0 -1
  602. package/dist/snapshot-chunks.js +0 -304
  603. package/dist/snapshot-chunks.js.map +0 -1
  604. package/dist/sqlite/index.d.ts +0 -53
  605. package/dist/sqlite/index.d.ts.map +0 -1
  606. package/dist/sqlite/index.js +0 -795
  607. package/dist/sqlite/index.js.map +0 -1
  608. package/dist/sqlite3.d.ts +0 -22
  609. package/dist/sqlite3.d.ts.map +0 -1
  610. package/dist/sqlite3.js +0 -99
  611. package/dist/sqlite3.js.map +0 -1
  612. package/dist/stats.d.ts +0 -28
  613. package/dist/stats.d.ts.map +0 -1
  614. package/dist/stats.js +0 -93
  615. package/dist/stats.js.map +0 -1
  616. package/dist/subscriptions/cache.d.ts +0 -58
  617. package/dist/subscriptions/cache.d.ts.map +0 -1
  618. package/dist/subscriptions/cache.js +0 -250
  619. package/dist/subscriptions/cache.js.map +0 -1
  620. package/dist/subscriptions/index.d.ts +0 -3
  621. package/dist/subscriptions/index.d.ts.map +0 -1
  622. package/dist/subscriptions/index.js +0 -3
  623. package/dist/subscriptions/index.js.map +0 -1
  624. package/dist/subscriptions/resolve.d.ts +0 -40
  625. package/dist/subscriptions/resolve.d.ts.map +0 -1
  626. package/dist/subscriptions/resolve.js +0 -275
  627. package/dist/subscriptions/resolve.js.map +0 -1
  628. package/dist/sync.d.ts +0 -24
  629. package/dist/sync.d.ts.map +0 -1
  630. package/dist/sync.js +0 -27
  631. package/dist/sync.js.map +0 -1
  632. package/src/auth-leases.ts +0 -649
  633. package/src/better-sqlite3.ts +0 -35
  634. package/src/blobs/access.ts +0 -244
  635. package/src/blobs/adapters/database.ts +0 -397
  636. package/src/blobs/index.ts +0 -9
  637. package/src/blobs/manager.ts +0 -901
  638. package/src/blobs/migrate.ts +0 -158
  639. package/src/blobs/types.ts +0 -74
  640. package/src/bun-sqlite-ambient.d.ts +0 -19
  641. package/src/bun-sqlite.ts +0 -27
  642. package/src/clients.ts +0 -22
  643. package/src/cloudflare/durable-object.ts +0 -289
  644. package/src/cloudflare/index.ts +0 -22
  645. package/src/cloudflare/r2.ts +0 -526
  646. package/src/cloudflare/scope-cache.ts +0 -341
  647. package/src/cloudflare/sentry.ts +0 -230
  648. package/src/cloudflare/worker.ts +0 -77
  649. package/src/commit-integrity.ts +0 -371
  650. package/src/compaction.ts +0 -77
  651. package/src/crdt-yjs/index.ts +0 -931
  652. package/src/d1.ts +0 -16
  653. package/src/dialect/base.ts +0 -360
  654. package/src/dialect/helpers.ts +0 -92
  655. package/src/dialect/index.ts +0 -7
  656. package/src/dialect/types.ts +0 -247
  657. package/src/encrypted-crdt.ts +0 -786
  658. package/src/filesystem/index.ts +0 -262
  659. package/src/handlers/collection.ts +0 -121
  660. package/src/handlers/create-handler.ts +0 -1134
  661. package/src/handlers/index.ts +0 -3
  662. package/src/handlers/types.ts +0 -403
  663. package/src/helpers/conflict.ts +0 -64
  664. package/src/helpers/emitted-change.ts +0 -69
  665. package/src/helpers/index.ts +0 -12
  666. package/src/helpers/paginate.ts +0 -82
  667. package/src/helpers/scope-authorization.ts +0 -27
  668. package/src/helpers/scope-commit-index.ts +0 -52
  669. package/src/helpers/scope-strings.ts +0 -101
  670. package/src/hono/api-key-auth.ts +0 -177
  671. package/src/hono/audit-redaction.ts +0 -135
  672. package/src/hono/blobs.ts +0 -851
  673. package/src/hono/console/gateway.ts +0 -3046
  674. package/src/hono/console/live-auth.ts +0 -46
  675. package/src/hono/console/route-descriptor.ts +0 -22
  676. package/src/hono/console/routes/api-keys.ts +0 -721
  677. package/src/hono/console/routes/clients.ts +0 -447
  678. package/src/hono/console/routes/commits.ts +0 -1137
  679. package/src/hono/console/routes/context.ts +0 -956
  680. package/src/hono/console/routes/events.ts +0 -669
  681. package/src/hono/console/routes/maintenance.ts +0 -461
  682. package/src/hono/console/routes/shared.ts +0 -816
  683. package/src/hono/console/routes/stats.ts +0 -392
  684. package/src/hono/console/routes/storage.ts +0 -159
  685. package/src/hono/console/routes.ts +0 -146
  686. package/src/hono/console/schema-errors.ts +0 -23
  687. package/src/hono/console/schemas.ts +0 -914
  688. package/src/hono/console/types.ts +0 -223
  689. package/src/hono/console/ui.ts +0 -100
  690. package/src/hono/create-server.ts +0 -230
  691. package/src/hono/errors.ts +0 -50
  692. package/src/hono/index.ts +0 -54
  693. package/src/hono/openapi.ts +0 -139
  694. package/src/hono/proxy/connection-manager.ts +0 -340
  695. package/src/hono/proxy/index.ts +0 -8
  696. package/src/hono/proxy/routes.ts +0 -272
  697. package/src/hono/rate-limit.ts +0 -319
  698. package/src/hono/realtime-sync-packs.ts +0 -354
  699. package/src/hono/routes/audit.ts +0 -499
  700. package/src/hono/routes/auth-leases.ts +0 -119
  701. package/src/hono/routes/combined.ts +0 -583
  702. package/src/hono/routes/context.ts +0 -1629
  703. package/src/hono/routes/health.ts +0 -26
  704. package/src/hono/routes/realtime.ts +0 -808
  705. package/src/hono/routes/shared.ts +0 -1626
  706. package/src/hono/routes/snapshots.ts +0 -345
  707. package/src/hono/routes.ts +0 -68
  708. package/src/hono/validation.ts +0 -81
  709. package/src/hono/websocket-origin.ts +0 -131
  710. package/src/hono/ws.ts +0 -1134
  711. package/src/libsql.ts +0 -51
  712. package/src/migrate.ts +0 -20
  713. package/src/neon.ts +0 -28
  714. package/src/notify.ts +0 -341
  715. package/src/pglite.ts +0 -68
  716. package/src/plugins/index.ts +0 -1
  717. package/src/plugins/types.ts +0 -144
  718. package/src/postgres/index.ts +0 -1291
  719. package/src/proxy/collection.ts +0 -17
  720. package/src/proxy/handler.ts +0 -159
  721. package/src/proxy/index.ts +0 -21
  722. package/src/proxy/mutation-detector.ts +0 -281
  723. package/src/proxy/oplog.ts +0 -181
  724. package/src/proxy/types.ts +0 -46
  725. package/src/realtime/in-memory.ts +0 -33
  726. package/src/realtime/index.ts +0 -7
  727. package/src/realtime/types.ts +0 -90
  728. package/src/relay/bun-types.d.ts +0 -50
  729. package/src/relay/client-role/forward-engine.ts +0 -355
  730. package/src/relay/client-role/index.ts +0 -9
  731. package/src/relay/client-role/pull-engine.ts +0 -329
  732. package/src/relay/client-role/sequence-mapper.ts +0 -201
  733. package/src/relay/evaluation/relay-paths.ts +0 -699
  734. package/src/relay/evaluation/rust-boundary.ts +0 -464
  735. package/src/relay/index.ts +0 -50
  736. package/src/relay/migrate.ts +0 -113
  737. package/src/relay/mode-manager.ts +0 -142
  738. package/src/relay/realtime.ts +0 -207
  739. package/src/relay/relay.ts +0 -431
  740. package/src/relay/schema.ts +0 -171
  741. package/src/relay/server-role/index.ts +0 -338
  742. package/src/relay/server-role/pull.ts +0 -43
  743. package/src/relay/server-role/push.ts +0 -164
  744. package/src/s3/index.ts +0 -346
  745. package/src/service-worker/index.ts +0 -773
  746. package/src/snapshot-artifacts/sqlite-bun.ts +0 -168
  747. package/src/snapshot-artifacts.ts +0 -896
  748. package/src/snapshot-chunks/db-metadata.ts +0 -537
  749. package/src/snapshot-chunks/index.ts +0 -8
  750. package/src/snapshot-chunks/types.ts +0 -105
  751. package/src/snapshot-chunks.ts +0 -453
  752. package/src/sqlite/index.ts +0 -1064
  753. package/src/sqlite3.ts +0 -137
  754. package/src/stats.ts +0 -180
  755. package/src/subscriptions/cache.ts +0 -376
  756. package/src/subscriptions/index.ts +0 -2
  757. package/src/subscriptions/resolve.ts +0 -357
  758. package/src/sync.ts +0 -111
package/dist/push.js CHANGED
@@ -1,703 +1,454 @@
1
- import { captureSyncException, countSyncMetric, distributionSyncMetric, startSyncSpan, } from '@syncular/core';
2
- import { sql } from 'kysely';
3
- import { finalizeCommitIntegrity } from './commit-integrity.js';
4
- import { coerceNumber, parseJsonValue, toDialectJsonValue, } from './dialect/helpers.js';
5
- import { createScopeCommitIndexEntries, scopeKeysFromScopeValues, } from './helpers/scope-commit-index.js';
6
- import { sortServerPushPlugins, } from './plugins/types.js';
7
- class RejectCommitError extends Error {
8
- response;
9
- constructor(response) {
10
- super('REJECT_COMMIT');
11
- this.response = response;
12
- this.name = 'RejectCommitError';
1
+ /**
2
+ * Push apply (SPEC.md §6) with §3.4 write-path authorization.
3
+ *
4
+ * Security-critical rules implemented here:
5
+ * - authorization runs against the STORED row when it exists, never the
6
+ * pushed payload (§3.4 step 2);
7
+ * - declared scope columns are stripped from every update path (§3.4
8
+ * rule 5) — updates keep the stored row's scope column values;
9
+ * - a lost `baseVersion = 0` insert race re-authorizes the winner's row
10
+ * before disclosing it in a conflict record (§6.2).
11
+ *
12
+ * Per-commit atomicity (§6.4): one storage transaction per commit; the
13
+ * idempotency record persists in the same transaction as the writes.
14
+ *
15
+ * Optional per-table write validation (§6.7) runs after decode + the §3.4
16
+ * scope check, on the row that will persist (post scope-strip, post CRDT
17
+ * merge); a validator throw rejects the whole commit atomically with a
18
+ * host code.
19
+ */
20
+ import { decodeRow, encodeRow, parseBlobRef, } from '@syncular/core';
21
+ import { clockOf } from './context.js';
22
+ import { SyncError } from './errors.js';
23
+ import { authorizeWrite, renderScopeValue, storedScopesForRow } from './scopes.js';
24
+ import { toValidateRow, ValidationRejection } from './validate.js';
25
+ /**
26
+ * Extract the blobIds a decoded row references through its `blob_ref`
27
+ * columns (§5.9.4), skipping NULLs. Malformed BlobRefs already failed at
28
+ * row-codec decode (§5.9.1), so `parseBlobRef` here is total.
29
+ */
30
+ function blobIdsInRow(table, values) {
31
+ const ids = [];
32
+ for (const index of table.blobRefColumnIndices) {
33
+ const value = values[index];
34
+ if (typeof value === 'string')
35
+ ids.push(parseBlobRef(value).blobId);
13
36
  }
37
+ return ids;
14
38
  }
15
- function isRecord(value) {
16
- return typeof value === 'object' && value !== null;
17
- }
18
- function isSyncPushResponse(value) {
19
- return (isRecord(value) && value.ok === true && typeof value.status === 'string');
39
+ function errorRecord(opIndex, code, message, retryable = false) {
40
+ return {
41
+ kind: 'terminate',
42
+ record: { opIndex, status: 'error', code, message, retryable },
43
+ };
20
44
  }
21
- function assertOperationIdentityUnchanged(pluginName, before, after) {
22
- if (before.table !== after.table) {
23
- throw new Error(`Server push plugin "${pluginName}" cannot change op.table (${before.table} -> ${after.table})`);
24
- }
25
- if (before.row_id !== after.row_id) {
26
- throw new Error(`Server push plugin "${pluginName}" cannot change op.row_id (${before.row_id} -> ${after.row_id})`);
27
- }
28
- if (before.op !== after.op) {
29
- throw new Error(`Server push plugin "${pluginName}" cannot change op.op (${before.op} -> ${after.op})`);
30
- }
45
+ function conflictRecord(opIndex, serverVersion, serverRow) {
46
+ return {
47
+ kind: 'terminate',
48
+ record: {
49
+ opIndex,
50
+ status: 'conflict',
51
+ code: 'sync.version_conflict',
52
+ message: 'row version does not match baseVersion (§6.2)',
53
+ serverVersion,
54
+ serverRow,
55
+ },
56
+ };
31
57
  }
32
- async function readCommitAffectedTables(db, dialect, commitSeq, partitionId) {
58
+ /**
59
+ * §6.7: run the table's write-validation hook, if configured, on the row
60
+ * that WILL persist (post scope-strip, post CRDT-merge — the values the
61
+ * store receives). Returns a terminating outcome iff the validator rejects
62
+ * (its `ValidationRejection` code, or `sync.constraint_violation` for a
63
+ * non-`ValidationRejection` throw), else `undefined` (accept / no hook).
64
+ * A no-op for tables with no validator — the `undefined` short-circuit
65
+ * keeps the feature zero-cost when off.
66
+ */
67
+ async function runValidator(validators, table, op, rowId, values, storedValues, opIndex, partition, actorId) {
68
+ const validator = validators?.[table.name];
69
+ if (validator === undefined)
70
+ return undefined;
33
71
  try {
34
- const commitsQ = db.selectFrom('sync_commits');
35
- const row = await commitsQ
36
- .selectAll()
37
- .where(sql `commit_seq = ${commitSeq}`)
38
- .where(sql `partition_id = ${partitionId}`)
39
- .executeTakeFirst();
40
- const raw = row?.affected_tables;
41
- return dialect.dbToArray(raw);
72
+ await validator({
73
+ op,
74
+ table: table.name,
75
+ rowId,
76
+ row: values !== undefined
77
+ ? toValidateRow(table.columns, values)
78
+ : undefined,
79
+ stored: storedValues !== undefined
80
+ ? toValidateRow(table.columns, storedValues)
81
+ : undefined,
82
+ }, { actorId, partition });
42
83
  }
43
- catch {
44
- // ignore and fall back to scanning changes (best-effort)
84
+ catch (error) {
85
+ if (error instanceof ValidationRejection) {
86
+ return errorRecord(opIndex, error.code, error.message);
87
+ }
88
+ // §6.7: a non-ValidationRejection throw is still a rejection, mapped to
89
+ // the generic server-side constraint code (§10.2) — the validator's
90
+ // failure never crashes the request or leaks its message as a code.
91
+ return errorRecord(opIndex, 'sync.constraint_violation', `write validator for table ${JSON.stringify(table.name)} threw: ${error instanceof Error ? error.message : String(error)}`);
45
92
  }
46
- // Fallback: read from changes using dialect-specific implementation
47
- return dialect.readAffectedTablesFromChanges(db, commitSeq, { partitionId });
93
+ return undefined;
48
94
  }
49
- function scopeKeysFromEmitted(emitted) {
50
- const keys = new Set();
51
- for (const c of emitted) {
52
- for (const scopeKey of scopeKeysFromScopeValues(c.scopes)) {
53
- keys.add(scopeKey);
95
+ /**
96
+ * §5.10.3: merge the row's `crdt` columns in place. For each crdt column,
97
+ * replace the incoming value with `merge(stored, incoming)` (§5.10.2) —
98
+ * never the raw pushed bytes. `storedValues` is undefined on insert (the
99
+ * stored value is `null` — the empty document). Returns `true` iff any crdt
100
+ * column value changed (so the caller re-encodes), or a terminating
101
+ * `sync.crdt_merge_failed` outcome if a merger is missing or throws.
102
+ *
103
+ * A NULL incoming crdt value is a semantic clear, not a merge — it passes
104
+ * through untouched (the app is nulling the column, the same as any other
105
+ * type). Merging only runs for a non-NULL incoming crdt value.
106
+ */
107
+ async function mergeCrdtColumns(table, values, storedValues, opIndex, mergers) {
108
+ if (table.crdtColumns.length === 0)
109
+ return { changed: false };
110
+ let changed = false;
111
+ for (const { index, crdtType } of table.crdtColumns) {
112
+ const incoming = values[index];
113
+ if (!(incoming instanceof Uint8Array))
114
+ continue; // NULL clear or absent
115
+ const merger = mergers?.[crdtType];
116
+ if (merger === undefined) {
117
+ return errorRecord(opIndex, 'sync.crdt_merge_failed', `no CRDT merger registered for crdtType ${JSON.stringify(crdtType)} (§5.10.2)`);
118
+ }
119
+ const storedRaw = storedValues?.[index];
120
+ const stored = storedRaw instanceof Uint8Array ? storedRaw : null;
121
+ let merged;
122
+ try {
123
+ merged = await merger(stored, incoming);
124
+ }
125
+ catch (error) {
126
+ return errorRecord(opIndex, 'sync.crdt_merge_failed', `CRDT merger for ${JSON.stringify(crdtType)} threw: ${error instanceof Error ? error.message : String(error)}`);
54
127
  }
128
+ values[index] = merged;
129
+ changed = true;
55
130
  }
56
- return Array.from(keys);
131
+ return { changed };
57
132
  }
58
- function recordPushMetrics(args) {
59
- const { status, durationMs, operationCount, emittedChangeCount, affectedTableCount, } = args;
60
- countSyncMetric('sync.server.push.requests', 1, {
61
- attributes: { status },
62
- });
63
- countSyncMetric('sync.server.push.operations', operationCount, {
64
- attributes: { status },
65
- });
66
- distributionSyncMetric('sync.server.push.duration_ms', durationMs, {
67
- unit: 'millisecond',
68
- attributes: { status },
69
- });
70
- distributionSyncMetric('sync.server.push.emitted_changes', emittedChangeCount, {
71
- attributes: { status },
72
- });
73
- distributionSyncMetric('sync.server.push.affected_tables', affectedTableCount, {
74
- attributes: { status },
75
- });
76
- }
77
- function createRejectedPushResult(error) {
78
- return {
79
- response: {
80
- ok: true,
81
- status: 'rejected',
82
- results: [error],
83
- },
84
- affectedTables: [],
85
- scopeKeys: [],
86
- emittedChanges: [],
87
- commitActorId: null,
88
- commitCreatedAt: null,
89
- };
90
- }
91
- function createRejectedPushResponse(error, commitSeq) {
92
- return {
93
- ok: true,
94
- status: 'rejected',
95
- ...(commitSeq !== undefined ? { commitSeq } : {}),
96
- results: [error],
97
- };
98
- }
99
- function validatePushRequest(request) {
100
- if (!request.clientId || !request.clientCommitId) {
101
- return {
102
- opIndex: 0,
103
- status: 'error',
104
- error: 'Invalid push request',
105
- code: 'sync.invalid_request',
106
- retriable: false,
107
- };
133
+ async function applyOperation(tx, schema, resolved, op, opIndex, blobCtx, crdtMergers, validators, partition, actorId) {
134
+ const table = schema.tables.get(op.table);
135
+ if (table === undefined) {
136
+ return errorRecord(opIndex, 'sync.unknown_table', `table ${JSON.stringify(op.table)} is not handled by this server`);
137
+ }
138
+ if (!resolved.ok) {
139
+ return errorRecord(opIndex, 'sync.forbidden', 'scope resolution failed (§3.4 step 4)');
108
140
  }
109
- const ops = request.operations ?? [];
110
- if (!Array.isArray(ops) || ops.length === 0) {
141
+ const stored = await tx.getRow(op.table, op.rowId);
142
+ if (op.op === 'delete') {
143
+ if (stored === undefined) {
144
+ // Deleting an absent row is applied (idempotent, §6.2); no change.
145
+ return { kind: 'applied', change: undefined };
146
+ }
147
+ if (!authorizeWrite(table, stored.scopes, resolved)) {
148
+ return errorRecord(opIndex, 'sync.forbidden', 'delete denied by scope authorization (§3.4)');
149
+ }
150
+ const missing = missingScopeVariable(table, stored.scopes);
151
+ if (missing !== undefined) {
152
+ return errorRecord(opIndex, 'sync.missing_scopes', `stored row lacks scope variable ${JSON.stringify(missing)} (§3.1)`);
153
+ }
154
+ // §6.7: validate the delete against the stored row (row = undefined,
155
+ // stored = the row about to be removed). Only reached for an existing
156
+ // row — an absent-row delete is an idempotent no-op above.
157
+ const deleteReject = await runValidator(validators, table, 'delete', op.rowId, undefined, decodeRow(table.columns, stored.payload), opIndex, partition, actorId);
158
+ if (deleteReject !== undefined)
159
+ return deleteReject;
160
+ await tx.deleteRow(op.table, op.rowId);
111
161
  return {
112
- opIndex: 0,
113
- status: 'error',
114
- error: 'Empty commit',
115
- code: 'sync.empty_commit',
116
- retriable: false,
162
+ kind: 'applied',
163
+ change: {
164
+ table: op.table,
165
+ rowId: op.rowId,
166
+ op: 'delete',
167
+ scopes: stored.scopes,
168
+ },
117
169
  };
118
170
  }
119
- return null;
120
- }
121
- function shouldUseSavepoints(args) {
122
- if (!args.dialect.supportsSavepoints) {
123
- return false;
171
+ // upsert — payload presence is enforced by the envelope codec (§6.1).
172
+ const payload = op.payload;
173
+ if (payload === undefined) {
174
+ return errorRecord(opIndex, 'sync.invalid_request', 'upsert without payload');
124
175
  }
125
- if ((args.operations?.length ?? 0) !== 1) {
126
- return true;
176
+ let values;
177
+ try {
178
+ values = decodeRow(table.columns, payload);
127
179
  }
128
- const singleOp = args.operations?.[0];
129
- if (!singleOp) {
130
- return true;
180
+ catch (error) {
181
+ return errorRecord(opIndex, 'sync.invalid_request', `row payload failed row-codec decode (§1.7): ${error instanceof Error ? error.message : String(error)}`);
131
182
  }
132
- const singleOpHandler = args.handlers.byTable.get(singleOp.table);
133
- if (!singleOpHandler) {
134
- throw new Error(`Unknown table: ${singleOp.table}`);
183
+ const pkValue = renderScopeValue(values[table.primaryKeyIndex]);
184
+ if (pkValue !== op.rowId) {
185
+ return errorRecord(opIndex, 'sync.invalid_request', 'payload primary key does not match rowId');
135
186
  }
136
- return !singleOpHandler.canRejectSingleOperationWithoutSavepoint;
137
- }
138
- async function persistEmittedChanges(args) {
139
- if (args.emittedChanges.length === 0) {
140
- return;
141
- }
142
- const syncTrx = args.trx;
143
- const changeRows = args.emittedChanges.map((change) => ({
144
- partition_id: args.partitionId,
145
- commit_seq: args.commitSeq,
146
- table: change.table,
147
- row_id: change.row_id,
148
- op: change.op,
149
- row_json: toDialectJsonValue(args.dialect, change.row_json),
150
- row_version: change.row_version,
151
- scopes: args.dialect.scopesToDb(change.scopes),
152
- }));
153
- await syncTrx.insertInto('sync_changes').values(changeRows).execute();
154
- const scopeEntries = createScopeCommitIndexEntries(args.emittedChanges);
155
- if (scopeEntries.length > 0) {
156
- await syncTrx
157
- .insertInto('sync_scope_commits')
158
- .values(scopeEntries.map((entry) => ({
159
- partition_id: args.partitionId,
160
- table: entry.table,
161
- scope_key: entry.scopeKey,
162
- commit_seq: args.commitSeq,
163
- })))
164
- .onConflict((oc) => oc
165
- .columns(['partition_id', 'table', 'scope_key', 'commit_seq'])
166
- .doNothing())
167
- .execute();
187
+ if (stored !== undefined) {
188
+ // §3.4 step 2: authorize against the STORED row, never the payload.
189
+ if (!authorizeWrite(table, stored.scopes, resolved)) {
190
+ return errorRecord(opIndex, 'sync.forbidden', 'write denied by scope authorization (§3.4)');
191
+ }
192
+ if (op.baseVersion === 0) {
193
+ // Lost insert race (§6.2); the stored row was authorized above,
194
+ // so disclosure of the winner is permitted.
195
+ return conflictRecord(opIndex, stored.serverVersion, stored.payload);
196
+ }
197
+ if (op.baseVersion !== undefined &&
198
+ op.baseVersion !== stored.serverVersion) {
199
+ return conflictRecord(opIndex, stored.serverVersion, stored.payload);
200
+ }
201
+ // §3.4 rule 5: scope columns are immutable on update — keep the
202
+ // stored row's scope column values on both the baseVersion and
203
+ // last-write-wins paths.
204
+ const storedValues = decodeRow(table.columns, stored.payload);
205
+ let mutated = false;
206
+ for (const pattern of table.scopePatterns) {
207
+ const storedValue = storedValues[pattern.columnIndex] ?? null;
208
+ if (values[pattern.columnIndex] !== storedValue) {
209
+ values[pattern.columnIndex] = storedValue;
210
+ mutated = true;
211
+ }
212
+ }
213
+ // §5.10.3: crdt columns merge (stored ⊕ incoming) — never LWW, never
214
+ // baseVersion-conflict (they were excluded from the checks above).
215
+ const mergeOutcome = await mergeCrdtColumns(table, values, storedValues, opIndex, crdtMergers);
216
+ if ('kind' in mergeOutcome)
217
+ return mergeOutcome;
218
+ if (mergeOutcome.changed)
219
+ mutated = true;
220
+ const newPayload = mutated ? encodeRow(table.columns, values) : payload;
221
+ const newVersion = stored.serverVersion + 1;
222
+ // §6.6 / §5.9.6: verify referenced blobs exist before writing.
223
+ const blobCheck = await checkAndRecordBlobs(tx, table, op.rowId, values, opIndex, blobCtx);
224
+ if (blobCheck !== undefined)
225
+ return blobCheck;
226
+ // §6.7: validate the merged, scope-stripped row that will persist —
227
+ // for a crdt column the validator sees the MERGED value (§5.10.3), the
228
+ // state the store holds, not the raw pushed update.
229
+ const updateReject = await runValidator(validators, table, 'upsert', op.rowId, values, storedValues, opIndex, partition, actorId);
230
+ if (updateReject !== undefined)
231
+ return updateReject;
232
+ const newRow = {
233
+ rowId: op.rowId,
234
+ serverVersion: newVersion,
235
+ scopes: stored.scopes,
236
+ payload: newPayload,
237
+ };
238
+ await tx.upsertRow(op.table, newRow);
239
+ return {
240
+ kind: 'applied',
241
+ change: {
242
+ table: op.table,
243
+ rowId: op.rowId,
244
+ op: 'upsert',
245
+ rowVersion: newVersion,
246
+ scopes: stored.scopes,
247
+ payload: newPayload,
248
+ },
249
+ };
168
250
  }
169
- }
170
- async function persistCommitOutcome(args) {
171
- const syncTrx = args.trx;
172
- await syncTrx
173
- .updateTable('sync_commits')
174
- .set({
175
- result_json: toDialectJsonValue(args.dialect, args.response),
176
- change_count: args.emittedChangeCount,
177
- affected_tables: args.dialect.arrayToDb(args.affectedTables),
178
- })
179
- .where('commit_seq', '=', args.commitSeq)
180
- .execute();
181
- if (args.affectedTables.length > 0) {
182
- await syncTrx
183
- .insertInto('sync_table_commits')
184
- .values(args.affectedTables.map((table) => ({
185
- partition_id: args.partitionId,
186
- table,
187
- commit_seq: args.commitSeq,
188
- })))
189
- .onConflict((oc) => oc.columns(['partition_id', 'table', 'commit_seq']).doNothing())
190
- .execute();
251
+ // Insert path: no stored row.
252
+ if (op.baseVersion !== undefined && op.baseVersion !== 0) {
253
+ // Authorize the payload first so absence is not disclosed to actors
254
+ // without the scope; then §6.2: baseVersion ≠ 0, row absent.
255
+ const extractedFirst = storedScopesForRow(table, values);
256
+ if ('missing' in extractedFirst ||
257
+ !authorizeWrite(table, extractedFirst.scopes, resolved)) {
258
+ return errorRecord(opIndex, 'sync.forbidden', 'write denied by scope authorization (§3.4)');
259
+ }
260
+ return errorRecord(opIndex, 'sync.row_missing', 'upsert with baseVersion targets an absent row (§6.2)');
191
261
  }
192
- await finalizeCommitIntegrity({
193
- db: args.trx,
194
- dialect: args.dialect,
195
- partitionId: args.partitionId,
196
- commitSeq: args.commitSeq,
197
- });
198
- }
199
- async function loadExistingCommitResult(args) {
200
- let query = args.syncTrx.selectFrom('sync_commits')
201
- .selectAll()
202
- .where('partition_id', '=', args.partitionId)
203
- .where('client_id', '=', args.request.clientId)
204
- .where('client_commit_id', '=', args.request.clientCommitId);
205
- if (args.dialect.supportsForUpdate) {
206
- query = query.forUpdate();
262
+ const extracted = storedScopesForRow(table, values);
263
+ if ('missing' in extracted) {
264
+ // §3.4 step 2: a missing or empty scope column value ⇒ deny.
265
+ return errorRecord(opIndex, 'sync.forbidden', `insert missing scope column value for ${JSON.stringify(extracted.missing)} (§3.4)`);
207
266
  }
208
- const existing = await query.executeTakeFirstOrThrow();
209
- const parsedCached = parseJsonValue(existing.result_json);
210
- if (!isSyncPushResponse(parsedCached)) {
211
- return createRejectedPushResult({
212
- opIndex: 0,
213
- status: 'error',
214
- error: 'Idempotency cache miss',
215
- code: 'sync.idempotency_cache_miss',
216
- retriable: true,
217
- });
267
+ if (!authorizeWrite(table, extracted.scopes, resolved)) {
268
+ return errorRecord(opIndex, 'sync.forbidden', 'insert denied by scope authorization (§3.4)');
218
269
  }
219
- const base = {
220
- ...parsedCached,
221
- commitSeq: Number(existing.commit_seq),
270
+ // §5.10.3: on insert a crdt column merges against the empty document
271
+ // (stored = null) — normalizes the initial state through the merger.
272
+ const insertMerge = await mergeCrdtColumns(table, values, undefined, opIndex, crdtMergers);
273
+ if ('kind' in insertMerge)
274
+ return insertMerge;
275
+ const insertPayload = insertMerge.changed
276
+ ? encodeRow(table.columns, values)
277
+ : payload;
278
+ // §6.6 / §5.9.6: verify referenced blobs exist before writing.
279
+ const blobCheck = await checkAndRecordBlobs(tx, table, op.rowId, values, opIndex, blobCtx);
280
+ if (blobCheck !== undefined)
281
+ return blobCheck;
282
+ // §6.7: validate the insert row (stored = undefined, so a validator can
283
+ // distinguish create from update); crdt columns are already merged
284
+ // against the empty document.
285
+ const insertReject = await runValidator(validators, table, 'upsert', op.rowId, values, undefined, opIndex, partition, actorId);
286
+ if (insertReject !== undefined)
287
+ return insertReject;
288
+ const newRow = {
289
+ rowId: op.rowId,
290
+ serverVersion: 1,
291
+ scopes: extracted.scopes,
292
+ payload: insertPayload,
222
293
  };
223
- if (parsedCached.status === 'applied') {
224
- const tablesFromDb = args.dialect.dbToArray(existing.affected_tables);
225
- return {
226
- response: { ...base, status: 'cached' },
227
- affectedTables: tablesFromDb.length > 0
228
- ? tablesFromDb
229
- : await readCommitAffectedTables(args.trx, args.dialect, Number(existing.commit_seq), args.partitionId),
230
- scopeKeys: [],
231
- emittedChanges: [],
232
- commitActorId: typeof existing.actor_id === 'string' ? existing.actor_id : null,
233
- commitCreatedAt: typeof existing.created_at === 'string' ? existing.created_at : null,
234
- };
235
- }
294
+ await tx.upsertRow(op.table, newRow);
236
295
  return {
237
- response: base,
238
- affectedTables: [],
239
- scopeKeys: [],
240
- emittedChanges: [],
241
- commitActorId: typeof existing.actor_id === 'string' ? existing.actor_id : null,
242
- commitCreatedAt: typeof existing.created_at === 'string' ? existing.created_at : null,
296
+ kind: 'applied',
297
+ change: {
298
+ table: op.table,
299
+ rowId: op.rowId,
300
+ op: 'upsert',
301
+ rowVersion: 1,
302
+ scopes: extracted.scopes,
303
+ payload: insertPayload,
304
+ },
243
305
  };
244
306
  }
245
- async function insertPendingCommit(args) {
246
- if (args.dialect.supportsInsertReturning) {
247
- const insertedCommit = await args.syncTrx
248
- .insertInto('sync_commits')
249
- .values(args.commitRow)
250
- .onConflict((oc) => oc
251
- .columns(['partition_id', 'client_id', 'client_commit_id'])
252
- .doNothing())
253
- .returning(['commit_seq'])
254
- .executeTakeFirst();
255
- if (!insertedCommit) {
256
- return null;
307
+ /**
308
+ * §5.9.6/§6.6: for a row's `blob_ref` columns, verify every referenced blob
309
+ * exists, then record the row's reference set in the index (§5.9.4). Returns
310
+ * a terminating `blob.not_found` outcome if any blob is absent (or the store
311
+ * is unconfigured while a ref exists), else `undefined` (proceed). No-op for
312
+ * tables with no `blob_ref` columns.
313
+ */
314
+ async function checkAndRecordBlobs(tx, table, rowId, values, opIndex, blobCtx) {
315
+ if (table.blobRefColumnIndices.length === 0)
316
+ return undefined;
317
+ const blobIds = blobIdsInRow(table, values);
318
+ if (blobIds.length > 0) {
319
+ if (blobCtx.store === undefined) {
320
+ return errorRecord(opIndex, 'blob.not_found', 'row references a blob but the server has no blob store (§5.9.6)');
321
+ }
322
+ for (const blobId of blobIds) {
323
+ if (!(await blobCtx.store.has(blobCtx.partition, blobId))) {
324
+ return errorRecord(opIndex, 'blob.not_found', `push references blob ${blobId} which has not been uploaded (§5.9.6)`);
325
+ }
257
326
  }
258
- return coerceNumber(insertedCommit.commit_seq) ?? 0;
259
327
  }
260
- const insertResult = await args.syncTrx
261
- .insertInto('sync_commits')
262
- .values(args.commitRow)
263
- .onConflict((oc) => oc.columns(['partition_id', 'client_id', 'client_commit_id']).doNothing())
264
- .executeTakeFirstOrThrow();
265
- const insertedRows = Number(insertResult.numInsertedOrUpdatedRows ?? 0);
266
- if (insertedRows === 0) {
267
- return null;
328
+ // Update the reference index for this row (empty set clears it, §5.9.4).
329
+ if (tx.setBlobRefs !== undefined) {
330
+ await tx.setBlobRefs(table.name, rowId, blobIds);
268
331
  }
269
- return coerceNumber(insertResult.insertId) ?? 0;
332
+ return undefined;
270
333
  }
271
- async function applyCommitOperations(args) {
272
- const ops = args.request.operations ?? [];
273
- const allEmitted = [];
274
- const results = [];
275
- const affectedTablesSet = new Set();
276
- for (let i = 0; i < ops.length;) {
277
- const op = ops[i];
278
- const handler = args.handlers.byTable.get(op.table);
279
- if (!handler) {
280
- throw new Error(`Unknown table: ${op.table}`);
281
- }
282
- const operationCtx = {
283
- db: args.trx,
284
- trx: args.trx,
285
- actorId: args.actorId,
286
- auth: args.auth,
287
- clientId: args.request.clientId,
288
- commitId: args.commitId,
289
- schemaVersion: args.request.schemaVersion,
290
- authLease: args.request.authLease,
291
- };
292
- let transformedOp = op;
293
- for (const plugin of args.pushPlugins) {
294
- if (!plugin.beforeApplyOperation)
295
- continue;
296
- const nextOp = await plugin.beforeApplyOperation({
297
- ctx: operationCtx,
298
- tableHandler: handler,
299
- op: transformedOp,
300
- opIndex: i,
301
- });
302
- assertOperationIdentityUnchanged(plugin.name, op, nextOp);
303
- transformedOp = nextOp;
304
- }
305
- let appliedBatch = null;
306
- let consumed = 1;
307
- if (args.pushPlugins.length === 0 && handler.applyOperationBatch) {
308
- const batchInput = [];
309
- for (let j = i; j < ops.length; j++) {
310
- const nextOp = ops[j];
311
- if (nextOp.table !== op.table)
312
- break;
313
- batchInput.push({ op: nextOp, opIndex: j });
314
- }
315
- if (batchInput.length > 1) {
316
- appliedBatch = await handler.applyOperationBatch(operationCtx, batchInput);
317
- consumed = Math.max(1, appliedBatch.length);
318
- }
319
- }
320
- if (!appliedBatch) {
321
- let appliedSingle = await handler.applyOperation(operationCtx, transformedOp, i);
322
- for (const plugin of args.pushPlugins) {
323
- if (!plugin.afterApplyOperation)
324
- continue;
325
- appliedSingle = await plugin.afterApplyOperation({
326
- ctx: operationCtx,
327
- tableHandler: handler,
328
- op: transformedOp,
329
- opIndex: i,
330
- applied: appliedSingle,
331
- });
332
- }
333
- appliedBatch = [appliedSingle];
334
- }
335
- if (appliedBatch.length === 0) {
336
- throw new Error(`Handler "${op.table}" returned no results from applyOperationBatch`);
337
- }
338
- for (const applied of appliedBatch) {
339
- if (applied.result.status !== 'applied') {
340
- results.push(applied.result);
341
- throw new RejectCommitError(createRejectedPushResponse(applied.result, args.commitSeq));
342
- }
343
- for (const change of applied.emittedChanges ?? []) {
344
- const scopes = change?.scopes;
345
- if (!scopes || typeof scopes !== 'object') {
346
- const error = {
347
- opIndex: applied.result.opIndex,
348
- status: 'error',
349
- error: 'Missing scopes',
350
- code: 'sync.missing_scopes',
351
- retriable: false,
352
- };
353
- results.push(error);
354
- throw new RejectCommitError(createRejectedPushResponse(error, args.commitSeq));
355
- }
356
- }
357
- results.push(applied.result);
358
- allEmitted.push(...applied.emittedChanges);
359
- for (const change of applied.emittedChanges) {
360
- affectedTablesSet.add(change.table);
361
- }
362
- }
363
- i += consumed;
334
+ function missingScopeVariable(table, scopes) {
335
+ for (const pattern of table.scopePatterns) {
336
+ const value = scopes[pattern.variable];
337
+ if (value === undefined || value.length === 0)
338
+ return pattern.variable;
364
339
  }
340
+ return undefined;
341
+ }
342
+ function resultFrame(clientCommitId, stored, replay) {
343
+ const status = stored.status === 'applied' ? (replay ? 'cached' : 'applied') : 'rejected';
365
344
  return {
366
- results,
367
- emittedChanges: allEmitted,
368
- affectedTables: Array.from(affectedTablesSet).sort(),
345
+ type: 'PUSH_RESULT',
346
+ clientCommitId,
347
+ status,
348
+ ...(stored.status === 'applied' && stored.commitSeq !== undefined
349
+ ? { commitSeq: stored.commitSeq }
350
+ : {}),
351
+ results: [...stored.results],
369
352
  };
370
353
  }
371
- async function executePushCommitInExecutor(args) {
372
- const { trx, dialect, handlers, request, pushPlugins } = args;
373
- const actorId = args.auth.actorId;
374
- const partitionId = args.auth.partitionId ?? 'default';
375
- const ops = request.operations ?? [];
376
- const syncTrx = trx;
377
- if (!dialect.supportsSavepoints) {
378
- await syncTrx
379
- .deleteFrom('sync_commits')
380
- .where('partition_id', '=', partitionId)
381
- .where('client_id', '=', request.clientId)
382
- .where('client_commit_id', '=', request.clientCommitId)
383
- .where('result_json', 'is', null)
384
- .execute();
385
- }
386
- const commitCreatedAt = new Date().toISOString();
387
- const commitRow = {
388
- partition_id: partitionId,
389
- actor_id: actorId,
390
- client_id: request.clientId,
391
- client_commit_id: request.clientCommitId,
392
- created_at: commitCreatedAt,
393
- meta: request.authLease
394
- ? toDialectJsonValue(dialect, {
395
- authLease: request.authLease,
396
- })
397
- : null,
398
- result_json: null,
399
- };
400
- let commitSeq = (await insertPendingCommit({
401
- syncTrx,
402
- dialect,
403
- commitRow,
404
- })) ?? 0;
405
- if (commitSeq === 0) {
406
- return loadExistingCommitResult({
407
- trx,
408
- syncTrx,
409
- dialect,
410
- request,
411
- partitionId,
412
- });
354
+ /**
355
+ * Process one `PUSH_COMMIT` frame: idempotency replay (§2.3), sequential
356
+ * atomic apply (§6.4), realtime notification for applied commits.
357
+ */
358
+ export async function processPushCommit(ctx, schema, resolved, clientId, frame) {
359
+ const { storage, partition } = ctx;
360
+ let persisted;
361
+ try {
362
+ persisted = await storage.getPushResult(partition, clientId, frame.clientCommitId);
413
363
  }
414
- if (commitSeq <= 0) {
415
- const insertedCommitRow = await syncTrx.selectFrom('sync_commits')
416
- .selectAll()
417
- .where('partition_id', '=', partitionId)
418
- .where('client_id', '=', request.clientId)
419
- .where('client_commit_id', '=', request.clientCommitId)
420
- .executeTakeFirstOrThrow();
421
- commitSeq = Number(insertedCommitRow.commit_seq);
364
+ catch (error) {
365
+ if (error instanceof SyncError &&
366
+ error.code === 'sync.idempotency_cache_miss') {
367
+ // §6.3: answer the retryable cache-miss for this commit rather than
368
+ // re-applying. Not persisted — a retry may find a readable record.
369
+ return {
370
+ type: 'PUSH_RESULT',
371
+ clientCommitId: frame.clientCommitId,
372
+ status: 'rejected',
373
+ results: [
374
+ {
375
+ opIndex: 0,
376
+ status: 'error',
377
+ code: 'sync.idempotency_cache_miss',
378
+ message: error.message,
379
+ retryable: true,
380
+ },
381
+ ],
382
+ };
383
+ }
384
+ throw error;
422
385
  }
423
- const commitId = `${request.clientId}:${request.clientCommitId}`;
424
- const validationResult = await args.validateCommit?.({
425
- trx,
426
- dialect,
427
- auth: args.auth,
428
- request,
429
- partitionId,
430
- actorId,
431
- commitSeq,
432
- });
433
- if (validationResult) {
434
- const response = createRejectedPushResponse(validationResult, commitSeq);
435
- await persistCommitOutcome({
436
- trx,
437
- dialect,
438
- partitionId,
439
- commitSeq,
440
- response,
441
- affectedTables: [],
442
- emittedChangeCount: 0,
443
- });
444
- return {
445
- response,
446
- affectedTables: [],
447
- scopeKeys: [],
448
- emittedChanges: [],
449
- commitActorId: actorId,
450
- commitCreatedAt,
451
- };
386
+ if (persisted !== undefined) {
387
+ return resultFrame(frame.clientCommitId, persisted, true);
452
388
  }
453
- const savepointName = `sync_apply_${commitSeq}`;
454
- const useSavepoints = shouldUseSavepoints({
455
- dialect,
456
- handlers,
457
- operations: ops,
458
- });
459
- let savepointCreated = false;
389
+ const createdAtMs = clockOf(ctx)();
390
+ const blobCtx = { store: ctx.blobs, partition };
391
+ const crdtMergers = ctx.crdtMergers;
392
+ const validators = ctx.validators;
393
+ const tx = await storage.begin(partition);
460
394
  try {
461
- if (useSavepoints) {
462
- await sql.raw(`SAVEPOINT ${savepointName}`).execute(trx);
463
- savepointCreated = true;
464
- }
465
- const applied = await applyCommitOperations({
466
- trx,
467
- handlers,
468
- pushPlugins,
469
- auth: args.auth,
470
- request,
471
- actorId,
472
- commitId,
473
- commitSeq,
474
- });
475
- const appliedResponse = {
476
- ok: true,
477
- status: 'applied',
478
- commitSeq,
479
- results: applied.results,
480
- };
481
- await persistEmittedChanges({
482
- trx,
483
- dialect,
484
- partitionId,
485
- commitSeq,
486
- emittedChanges: applied.emittedChanges,
487
- });
488
- await persistCommitOutcome({
489
- trx,
490
- dialect,
491
- partitionId,
492
- commitSeq,
493
- response: appliedResponse,
494
- affectedTables: applied.affectedTables,
495
- emittedChangeCount: applied.emittedChanges.length,
496
- });
497
- if (useSavepoints) {
498
- await sql.raw(`RELEASE SAVEPOINT ${savepointName}`).execute(trx);
395
+ const results = [];
396
+ const changes = [];
397
+ let terminated;
398
+ for (let opIndex = 0; opIndex < frame.operations.length; opIndex++) {
399
+ const op = frame.operations[opIndex];
400
+ if (op === undefined)
401
+ continue;
402
+ const outcome = await applyOperation(tx, schema, resolved, op, opIndex, blobCtx, crdtMergers, validators, partition, ctx.actorId);
403
+ if (outcome.kind === 'terminate') {
404
+ terminated = outcome.record;
405
+ break;
406
+ }
407
+ results.push({ opIndex, status: 'applied' });
408
+ if (outcome.change !== undefined)
409
+ changes.push(outcome.change);
499
410
  }
500
- return {
501
- response: appliedResponse,
502
- affectedTables: applied.affectedTables,
503
- scopeKeys: scopeKeysFromEmitted(applied.emittedChanges),
504
- emittedChanges: applied.emittedChanges,
505
- commitActorId: actorId,
506
- commitCreatedAt,
507
- };
508
- }
509
- catch (error) {
510
- if (savepointCreated) {
411
+ if (terminated !== undefined) {
412
+ // §6.3 rejected: only the terminating operation's record; §6.4:
413
+ // every write of the commit rolls back.
414
+ await tx.rollback();
415
+ const stored = {
416
+ status: 'rejected',
417
+ results: [terminated],
418
+ };
419
+ const rejectionTx = await storage.begin(partition);
511
420
  try {
512
- await sql.raw(`ROLLBACK TO SAVEPOINT ${savepointName}`).execute(trx);
513
- await sql.raw(`RELEASE SAVEPOINT ${savepointName}`).execute(trx);
421
+ await rejectionTx.putPushResult(clientId, frame.clientCommitId, stored);
422
+ await rejectionTx.commit();
514
423
  }
515
- catch (savepointError) {
516
- console.error('[pushCommit] Savepoint rollback failed:', savepointError);
517
- throw savepointError;
424
+ catch (error) {
425
+ await rejectionTx.rollback();
426
+ throw error;
518
427
  }
428
+ return resultFrame(frame.clientCommitId, stored, false);
519
429
  }
520
- if (!(error instanceof RejectCommitError)) {
521
- throw error;
522
- }
523
- await persistCommitOutcome({
524
- trx,
525
- dialect,
526
- partitionId,
527
- commitSeq,
528
- response: error.response,
529
- affectedTables: [],
530
- emittedChangeCount: 0,
430
+ const commitSeq = await tx.appendCommit({
431
+ clientId,
432
+ clientCommitId: frame.clientCommitId,
433
+ actorId: ctx.actorId,
434
+ createdAtMs,
435
+ changes,
531
436
  });
532
- return {
533
- response: error.response,
534
- affectedTables: [],
535
- scopeKeys: [],
536
- emittedChanges: [],
537
- commitActorId: actorId,
538
- commitCreatedAt,
539
- };
540
- }
541
- }
542
- export async function pushCommit(args) {
543
- const { db, dialect, handlers, request } = args;
544
- const pushPlugins = sortServerPushPlugins(args.plugins);
545
- const requestedOps = Array.isArray(request.operations)
546
- ? request.operations
547
- : [];
548
- const operationCount = requestedOps.length;
549
- const startedAtMs = Date.now();
550
- const suppressTelemetry = args.suppressTelemetry === true;
551
- return startSyncSpan({
552
- name: 'sync.server.push',
553
- op: 'sync.push',
554
- attributes: {
555
- operation_count: operationCount,
556
- },
557
- }, async (span) => {
558
- const finalizeResult = (result) => {
559
- const durationMs = Math.max(0, Date.now() - startedAtMs);
560
- const status = result.response.status;
561
- span.setAttribute('status', status);
562
- span.setAttribute('duration_ms', durationMs);
563
- span.setAttribute('emitted_change_count', result.emittedChanges.length);
564
- span.setAttribute('affected_table_count', result.affectedTables.length);
565
- span.setStatus('ok');
566
- if (!suppressTelemetry) {
567
- recordPushMetrics({
568
- status,
569
- durationMs,
570
- operationCount,
571
- emittedChangeCount: result.emittedChanges.length,
572
- affectedTableCount: result.affectedTables.length,
573
- });
574
- }
575
- return result;
576
- };
577
- try {
578
- const validationError = validatePushRequest(request);
579
- if (validationError) {
580
- return finalizeResult(createRejectedPushResult(validationError));
581
- }
582
- return finalizeResult(await dialect.executeInTransaction(db, async (trx) => executePushCommitInExecutor({
583
- trx,
584
- dialect,
585
- handlers,
586
- pushPlugins,
587
- auth: args.auth,
588
- request,
589
- validateCommit: args.validateCommit,
590
- })));
591
- }
592
- catch (error) {
593
- const durationMs = Math.max(0, Date.now() - startedAtMs);
594
- span.setAttribute('status', 'error');
595
- span.setAttribute('duration_ms', durationMs);
596
- span.setStatus('error');
597
- if (!suppressTelemetry) {
598
- recordPushMetrics({
599
- status: 'error',
600
- durationMs,
601
- operationCount,
602
- emittedChangeCount: 0,
603
- affectedTableCount: 0,
604
- });
605
- captureSyncException(error, {
606
- event: 'sync.server.push',
607
- operationCount,
608
- });
609
- }
610
- throw error;
611
- }
612
- });
613
- }
614
- export async function pushCommitBatch(args) {
615
- const { db, dialect, handlers, requests } = args;
616
- const pushPlugins = sortServerPushPlugins(args.plugins);
617
- const startedAtMs = Date.now();
618
- const suppressTelemetry = args.suppressTelemetry === true;
619
- const totalOperationCount = requests.reduce((count, request) => {
620
- const operations = Array.isArray(request.operations)
621
- ? request.operations
622
- : [];
623
- return count + operations.length;
624
- }, 0);
625
- return startSyncSpan({
626
- name: 'sync.server.push_batch',
627
- op: 'sync.push.batch',
628
- attributes: {
629
- commit_count: requests.length,
630
- operation_count: totalOperationCount,
631
- },
632
- }, async (span) => {
633
- try {
634
- const results = await dialect.executeInTransaction(db, async (trx) => {
635
- const executed = [];
636
- for (const request of requests) {
637
- const validationError = validatePushRequest(request);
638
- if (validationError) {
639
- executed.push(createRejectedPushResult(validationError));
640
- continue;
641
- }
642
- executed.push(await executePushCommitInExecutor({
643
- trx,
644
- dialect,
645
- handlers,
646
- pushPlugins,
647
- auth: args.auth,
648
- request,
649
- validateCommit: args.validateCommit,
650
- }));
651
- }
652
- return executed;
437
+ const stored = { status: 'applied', commitSeq, results };
438
+ await tx.putPushResult(clientId, frame.clientCommitId, stored);
439
+ await tx.commit();
440
+ if (ctx.realtime !== undefined && changes.length > 0) {
441
+ await ctx.realtime.notifyCommit(partition, {
442
+ commitSeq,
443
+ createdAtMs,
444
+ actorId: ctx.actorId,
445
+ changes,
653
446
  });
654
- const durationMs = Math.max(0, Date.now() - startedAtMs);
655
- const emittedChangeCount = results.reduce((count, result) => count + result.emittedChanges.length, 0);
656
- const affectedTableCount = results.reduce((count, result) => count + result.affectedTables.length, 0);
657
- const status = results.every((result) => result.response.status === 'cached')
658
- ? 'cached'
659
- : results.every((result) => result.response.status === 'applied' ||
660
- result.response.status === 'cached')
661
- ? 'applied'
662
- : 'rejected';
663
- span.setAttribute('status', status);
664
- span.setAttribute('duration_ms', durationMs);
665
- span.setAttribute('commit_count', results.length);
666
- span.setAttribute('emitted_change_count', emittedChangeCount);
667
- span.setAttribute('affected_table_count', affectedTableCount);
668
- span.setStatus('ok');
669
- if (!suppressTelemetry) {
670
- recordPushMetrics({
671
- status,
672
- durationMs,
673
- operationCount: totalOperationCount,
674
- emittedChangeCount,
675
- affectedTableCount,
676
- });
677
- }
678
- return results;
679
- }
680
- catch (error) {
681
- const durationMs = Math.max(0, Date.now() - startedAtMs);
682
- span.setAttribute('status', 'error');
683
- span.setAttribute('duration_ms', durationMs);
684
- span.setStatus('error');
685
- if (!suppressTelemetry) {
686
- recordPushMetrics({
687
- status: 'error',
688
- durationMs,
689
- operationCount: totalOperationCount,
690
- emittedChangeCount: 0,
691
- affectedTableCount: 0,
692
- });
693
- captureSyncException(error, {
694
- event: 'sync.server.push_batch',
695
- commitCount: requests.length,
696
- operationCount: totalOperationCount,
697
- });
698
- }
699
- throw error;
700
447
  }
701
- });
448
+ return resultFrame(frame.clientCommitId, stored, false);
449
+ }
450
+ catch (error) {
451
+ await tx.rollback();
452
+ throw error;
453
+ }
702
454
  }
703
- //# sourceMappingURL=push.js.map