@syncular/server 0.0.6-95 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (601) hide show
  1. package/README.md +1 -2
  2. package/dist/auth-leases.d.ts +85 -0
  3. package/dist/auth-leases.d.ts.map +1 -0
  4. package/dist/auth-leases.js +368 -0
  5. package/dist/auth-leases.js.map +1 -0
  6. package/dist/better-sqlite3.d.ts +22 -0
  7. package/dist/better-sqlite3.d.ts.map +1 -0
  8. package/dist/better-sqlite3.js +16 -0
  9. package/dist/better-sqlite3.js.map +1 -0
  10. package/dist/blobs/access.d.ts +35 -0
  11. package/dist/blobs/access.d.ts.map +1 -0
  12. package/dist/blobs/access.js +149 -0
  13. package/dist/blobs/access.js.map +1 -0
  14. package/dist/blobs/adapters/database.d.ts +26 -9
  15. package/dist/blobs/adapters/database.d.ts.map +1 -1
  16. package/dist/blobs/adapters/database.js +68 -23
  17. package/dist/blobs/adapters/database.js.map +1 -1
  18. package/dist/blobs/index.d.ts +1 -0
  19. package/dist/blobs/index.d.ts.map +1 -1
  20. package/dist/blobs/index.js +5 -4
  21. package/dist/blobs/index.js.map +1 -1
  22. package/dist/blobs/manager.d.ts +69 -44
  23. package/dist/blobs/manager.d.ts.map +1 -1
  24. package/dist/blobs/manager.js +250 -65
  25. package/dist/blobs/manager.js.map +1 -1
  26. package/dist/blobs/migrate.d.ts.map +1 -1
  27. package/dist/blobs/migrate.js +16 -8
  28. package/dist/blobs/migrate.js.map +1 -1
  29. package/dist/blobs/types.d.ts +4 -0
  30. package/dist/blobs/types.d.ts.map +1 -1
  31. package/dist/bun-sqlite.d.ts +19 -0
  32. package/dist/bun-sqlite.d.ts.map +1 -0
  33. package/dist/bun-sqlite.js +19 -0
  34. package/dist/bun-sqlite.js.map +1 -0
  35. package/dist/clients.d.ts +1 -0
  36. package/dist/clients.d.ts.map +1 -1
  37. package/dist/clients.js.map +1 -1
  38. package/dist/cloudflare/durable-object.d.ts +93 -0
  39. package/dist/cloudflare/durable-object.d.ts.map +1 -0
  40. package/dist/cloudflare/durable-object.js +210 -0
  41. package/dist/cloudflare/durable-object.js.map +1 -0
  42. package/dist/cloudflare/index.d.ts +22 -0
  43. package/dist/cloudflare/index.d.ts.map +1 -0
  44. package/dist/cloudflare/index.js +22 -0
  45. package/dist/cloudflare/index.js.map +1 -0
  46. package/dist/cloudflare/r2.d.ts +180 -0
  47. package/dist/cloudflare/r2.d.ts.map +1 -0
  48. package/dist/cloudflare/r2.js +258 -0
  49. package/dist/cloudflare/r2.js.map +1 -0
  50. package/dist/cloudflare/scope-cache.d.ts +54 -0
  51. package/dist/cloudflare/scope-cache.d.ts.map +1 -0
  52. package/dist/cloudflare/scope-cache.js +223 -0
  53. package/dist/cloudflare/scope-cache.js.map +1 -0
  54. package/dist/cloudflare/sentry.d.ts +47 -0
  55. package/dist/cloudflare/sentry.d.ts.map +1 -0
  56. package/dist/cloudflare/sentry.js +163 -0
  57. package/dist/cloudflare/sentry.js.map +1 -0
  58. package/dist/cloudflare/worker.d.ts +46 -0
  59. package/dist/cloudflare/worker.d.ts.map +1 -0
  60. package/dist/cloudflare/worker.js +63 -0
  61. package/dist/cloudflare/worker.js.map +1 -0
  62. package/dist/commit-integrity.d.ts +38 -0
  63. package/dist/commit-integrity.d.ts.map +1 -0
  64. package/dist/commit-integrity.js +260 -0
  65. package/dist/commit-integrity.js.map +1 -0
  66. package/dist/compaction.js.map +1 -1
  67. package/dist/crdt-yjs/index.d.ts +99 -0
  68. package/dist/crdt-yjs/index.d.ts.map +1 -0
  69. package/dist/crdt-yjs/index.js +629 -0
  70. package/dist/crdt-yjs/index.js.map +1 -0
  71. package/dist/d1.d.ts +13 -0
  72. package/dist/d1.d.ts.map +1 -0
  73. package/dist/d1.js +14 -0
  74. package/dist/d1.js.map +1 -0
  75. package/dist/dialect/base.d.ts +11 -1
  76. package/dist/dialect/base.d.ts.map +1 -1
  77. package/dist/dialect/base.js +43 -6
  78. package/dist/dialect/base.js.map +1 -1
  79. package/dist/dialect/helpers.d.ts +6 -0
  80. package/dist/dialect/helpers.d.ts.map +1 -1
  81. package/dist/dialect/helpers.js +24 -0
  82. package/dist/dialect/helpers.js.map +1 -1
  83. package/dist/dialect/index.js +3 -3
  84. package/dist/dialect/types.d.ts +40 -0
  85. package/dist/dialect/types.d.ts.map +1 -1
  86. package/dist/encrypted-crdt.d.ts +67 -0
  87. package/dist/encrypted-crdt.d.ts.map +1 -0
  88. package/dist/encrypted-crdt.js +426 -0
  89. package/dist/encrypted-crdt.js.map +1 -0
  90. package/dist/errors.d.ts +10 -0
  91. package/dist/errors.d.ts.map +1 -0
  92. package/dist/errors.js +13 -0
  93. package/dist/errors.js.map +1 -0
  94. package/dist/filesystem/index.d.ts +44 -0
  95. package/dist/filesystem/index.d.ts.map +1 -0
  96. package/dist/filesystem/index.js +164 -0
  97. package/dist/filesystem/index.js.map +1 -0
  98. package/dist/handlers/collection.d.ts +11 -3
  99. package/dist/handlers/collection.d.ts.map +1 -1
  100. package/dist/handlers/collection.js +34 -56
  101. package/dist/handlers/collection.js.map +1 -1
  102. package/dist/handlers/create-handler.d.ts +31 -7
  103. package/dist/handlers/create-handler.d.ts.map +1 -1
  104. package/dist/handlers/create-handler.js +332 -115
  105. package/dist/handlers/create-handler.js.map +1 -1
  106. package/dist/handlers/index.js +3 -3
  107. package/dist/handlers/types.d.ts +86 -1
  108. package/dist/handlers/types.d.ts.map +1 -1
  109. package/dist/helpers/conflict.js.map +1 -1
  110. package/dist/helpers/emitted-change.js.map +1 -1
  111. package/dist/helpers/index.d.ts +2 -0
  112. package/dist/helpers/index.d.ts.map +1 -1
  113. package/dist/helpers/index.js +6 -4
  114. package/dist/helpers/index.js.map +1 -1
  115. package/dist/helpers/paginate.js.map +1 -1
  116. package/dist/helpers/scope-authorization.d.ts +7 -0
  117. package/dist/helpers/scope-authorization.d.ts.map +1 -0
  118. package/dist/helpers/scope-authorization.js +19 -0
  119. package/dist/helpers/scope-authorization.js.map +1 -0
  120. package/dist/helpers/scope-commit-index.d.ts +12 -0
  121. package/dist/helpers/scope-commit-index.d.ts.map +1 -0
  122. package/dist/helpers/scope-commit-index.js +35 -0
  123. package/dist/helpers/scope-commit-index.js.map +1 -0
  124. package/dist/helpers/scope-strings.js.map +1 -1
  125. package/dist/hono/api-key-auth.d.ts +49 -0
  126. package/dist/hono/api-key-auth.d.ts.map +1 -0
  127. package/dist/hono/api-key-auth.js +108 -0
  128. package/dist/hono/api-key-auth.js.map +1 -0
  129. package/dist/hono/audit-redaction.d.ts +20 -0
  130. package/dist/hono/audit-redaction.d.ts.map +1 -0
  131. package/dist/hono/audit-redaction.js +85 -0
  132. package/dist/hono/audit-redaction.js.map +1 -0
  133. package/dist/hono/blobs.d.ts +75 -0
  134. package/dist/hono/blobs.d.ts.map +1 -0
  135. package/dist/hono/blobs.js +586 -0
  136. package/dist/hono/blobs.js.map +1 -0
  137. package/dist/hono/console/gateway.d.ts +42 -0
  138. package/dist/hono/console/gateway.d.ts.map +1 -0
  139. package/dist/hono/console/gateway.js +2158 -0
  140. package/dist/hono/console/gateway.js.map +1 -0
  141. package/dist/hono/console/live-auth.d.ts +7 -0
  142. package/dist/hono/console/live-auth.d.ts.map +1 -0
  143. package/dist/hono/console/live-auth.js +39 -0
  144. package/dist/hono/console/live-auth.js.map +1 -0
  145. package/dist/hono/console/route-descriptor.d.ts +6 -0
  146. package/dist/hono/console/route-descriptor.d.ts.map +1 -0
  147. package/dist/hono/console/route-descriptor.js +16 -0
  148. package/dist/hono/console/route-descriptor.js.map +1 -0
  149. package/dist/hono/console/routes/api-keys.d.ts +8 -0
  150. package/dist/hono/console/routes/api-keys.d.ts.map +1 -0
  151. package/dist/hono/console/routes/api-keys.js +575 -0
  152. package/dist/hono/console/routes/api-keys.js.map +1 -0
  153. package/dist/hono/console/routes/clients.d.ts +8 -0
  154. package/dist/hono/console/routes/clients.d.ts.map +1 -0
  155. package/dist/hono/console/routes/clients.js +322 -0
  156. package/dist/hono/console/routes/clients.js.map +1 -0
  157. package/dist/hono/console/routes/commits.d.ts +8 -0
  158. package/dist/hono/console/routes/commits.d.ts.map +1 -0
  159. package/dist/hono/console/routes/commits.js +882 -0
  160. package/dist/hono/console/routes/commits.js.map +1 -0
  161. package/dist/hono/console/routes/context.d.ts +188 -0
  162. package/dist/hono/console/routes/context.d.ts.map +1 -0
  163. package/dist/hono/console/routes/context.js +633 -0
  164. package/dist/hono/console/routes/context.js.map +1 -0
  165. package/dist/hono/console/routes/events.d.ts +8 -0
  166. package/dist/hono/console/routes/events.d.ts.map +1 -0
  167. package/dist/hono/console/routes/events.js +501 -0
  168. package/dist/hono/console/routes/events.js.map +1 -0
  169. package/dist/hono/console/routes/maintenance.d.ts +8 -0
  170. package/dist/hono/console/routes/maintenance.d.ts.map +1 -0
  171. package/dist/hono/console/routes/maintenance.js +341 -0
  172. package/dist/hono/console/routes/maintenance.js.map +1 -0
  173. package/dist/hono/console/routes/shared.d.ts +174 -0
  174. package/dist/hono/console/routes/shared.d.ts.map +1 -0
  175. package/dist/hono/console/routes/shared.js +583 -0
  176. package/dist/hono/console/routes/shared.js.map +1 -0
  177. package/dist/hono/console/routes/stats.d.ts +8 -0
  178. package/dist/hono/console/routes/stats.d.ts.map +1 -0
  179. package/dist/hono/console/routes/stats.js +295 -0
  180. package/dist/hono/console/routes/stats.js.map +1 -0
  181. package/dist/hono/console/routes/storage.d.ts +8 -0
  182. package/dist/hono/console/routes/storage.d.ts.map +1 -0
  183. package/dist/hono/console/routes/storage.js +121 -0
  184. package/dist/hono/console/routes/storage.js.map +1 -0
  185. package/dist/hono/console/routes.d.ts +36 -0
  186. package/dist/hono/console/routes.d.ts.map +1 -0
  187. package/dist/hono/console/routes.js +112 -0
  188. package/dist/hono/console/routes.js.map +1 -0
  189. package/dist/hono/console/schema-errors.d.ts +2 -0
  190. package/dist/hono/console/schema-errors.d.ts.map +1 -0
  191. package/dist/hono/console/schema-errors.js +17 -0
  192. package/dist/hono/console/schema-errors.js.map +1 -0
  193. package/dist/hono/console/schemas.d.ts +1515 -0
  194. package/dist/hono/console/schemas.d.ts.map +1 -0
  195. package/dist/hono/console/schemas.js +661 -0
  196. package/dist/hono/console/schemas.js.map +1 -0
  197. package/dist/hono/console/types.d.ts +213 -0
  198. package/dist/hono/console/types.d.ts.map +1 -0
  199. package/dist/hono/console/types.js +2 -0
  200. package/dist/hono/console/types.js.map +1 -0
  201. package/dist/hono/console/ui.d.ts +38 -0
  202. package/dist/hono/console/ui.d.ts.map +1 -0
  203. package/dist/hono/console/ui.js +43 -0
  204. package/dist/hono/console/ui.js.map +1 -0
  205. package/dist/hono/create-server.d.ts +71 -0
  206. package/dist/hono/create-server.d.ts.map +1 -0
  207. package/dist/hono/create-server.js +121 -0
  208. package/dist/hono/create-server.js.map +1 -0
  209. package/dist/hono/errors.d.ts +13 -0
  210. package/dist/hono/errors.d.ts.map +1 -0
  211. package/dist/hono/errors.js +21 -0
  212. package/dist/hono/errors.js.map +1 -0
  213. package/dist/hono/index.d.ts +20 -0
  214. package/dist/hono/index.d.ts.map +1 -0
  215. package/dist/hono/index.js +31 -0
  216. package/dist/hono/index.js.map +1 -0
  217. package/dist/hono/openapi.d.ts +72 -0
  218. package/dist/hono/openapi.d.ts.map +1 -0
  219. package/dist/hono/openapi.js +99 -0
  220. package/dist/hono/openapi.js.map +1 -0
  221. package/dist/hono/proxy/connection-manager.d.ts +78 -0
  222. package/dist/hono/proxy/connection-manager.d.ts.map +1 -0
  223. package/dist/hono/proxy/connection-manager.js +251 -0
  224. package/dist/hono/proxy/connection-manager.js.map +1 -0
  225. package/dist/hono/proxy/index.d.ts +8 -0
  226. package/dist/hono/proxy/index.d.ts.map +1 -0
  227. package/dist/hono/proxy/index.js +8 -0
  228. package/dist/hono/proxy/index.js.map +1 -0
  229. package/dist/hono/proxy/routes.d.ts +86 -0
  230. package/dist/hono/proxy/routes.d.ts.map +1 -0
  231. package/dist/hono/proxy/routes.js +183 -0
  232. package/dist/hono/proxy/routes.js.map +1 -0
  233. package/dist/hono/rate-limit.d.ts +101 -0
  234. package/dist/hono/rate-limit.d.ts.map +1 -0
  235. package/dist/hono/rate-limit.js +184 -0
  236. package/dist/hono/rate-limit.js.map +1 -0
  237. package/dist/hono/realtime-sync-packs.d.ts +43 -0
  238. package/dist/hono/realtime-sync-packs.d.ts.map +1 -0
  239. package/dist/hono/realtime-sync-packs.js +219 -0
  240. package/dist/hono/realtime-sync-packs.js.map +1 -0
  241. package/dist/hono/routes/audit.d.ts +12 -0
  242. package/dist/hono/routes/audit.d.ts.map +1 -0
  243. package/dist/hono/routes/audit.js +385 -0
  244. package/dist/hono/routes/audit.js.map +1 -0
  245. package/dist/hono/routes/auth-leases.d.ts +8 -0
  246. package/dist/hono/routes/auth-leases.d.ts.map +1 -0
  247. package/dist/hono/routes/auth-leases.js +88 -0
  248. package/dist/hono/routes/auth-leases.js.map +1 -0
  249. package/dist/hono/routes/combined.d.ts +8 -0
  250. package/dist/hono/routes/combined.d.ts.map +1 -0
  251. package/dist/hono/routes/combined.js +392 -0
  252. package/dist/hono/routes/combined.js.map +1 -0
  253. package/dist/hono/routes/context.d.ts +314 -0
  254. package/dist/hono/routes/context.d.ts.map +1 -0
  255. package/dist/hono/routes/context.js +1147 -0
  256. package/dist/hono/routes/context.js.map +1 -0
  257. package/dist/hono/routes/health.d.ts +8 -0
  258. package/dist/hono/routes/health.d.ts.map +1 -0
  259. package/dist/hono/routes/health.js +16 -0
  260. package/dist/hono/routes/health.js.map +1 -0
  261. package/dist/hono/routes/realtime.d.ts +9 -0
  262. package/dist/hono/routes/realtime.d.ts.map +1 -0
  263. package/dist/hono/routes/realtime.js +626 -0
  264. package/dist/hono/routes/realtime.js.map +1 -0
  265. package/dist/hono/routes/shared.d.ts +689 -0
  266. package/dist/hono/routes/shared.d.ts.map +1 -0
  267. package/dist/hono/routes/shared.js +972 -0
  268. package/dist/hono/routes/shared.js.map +1 -0
  269. package/dist/hono/routes/snapshots.d.ts +10 -0
  270. package/dist/hono/routes/snapshots.d.ts.map +1 -0
  271. package/dist/hono/routes/snapshots.js +281 -0
  272. package/dist/hono/routes/snapshots.js.map +1 -0
  273. package/dist/hono/routes.d.ts +19 -0
  274. package/dist/hono/routes.d.ts.map +1 -0
  275. package/dist/hono/routes.js +37 -0
  276. package/dist/hono/routes.js.map +1 -0
  277. package/dist/hono/validation.d.ts +5 -0
  278. package/dist/hono/validation.d.ts.map +1 -0
  279. package/dist/hono/validation.js +47 -0
  280. package/dist/hono/validation.js.map +1 -0
  281. package/dist/hono/websocket-origin.d.ts +9 -0
  282. package/dist/hono/websocket-origin.d.ts.map +1 -0
  283. package/dist/hono/websocket-origin.js +96 -0
  284. package/dist/hono/websocket-origin.js.map +1 -0
  285. package/dist/hono/ws.d.ts +341 -0
  286. package/dist/hono/ws.d.ts.map +1 -0
  287. package/dist/hono/ws.js +711 -0
  288. package/dist/hono/ws.js.map +1 -0
  289. package/dist/index.d.ts +6 -0
  290. package/dist/index.d.ts.map +1 -1
  291. package/dist/index.js +24 -18
  292. package/dist/index.js.map +1 -1
  293. package/dist/libsql.d.ts +29 -0
  294. package/dist/libsql.d.ts.map +1 -0
  295. package/dist/libsql.js +25 -0
  296. package/dist/libsql.js.map +1 -0
  297. package/dist/migrate.js.map +1 -1
  298. package/dist/neon.d.ts +21 -0
  299. package/dist/neon.d.ts.map +1 -0
  300. package/dist/neon.js +22 -0
  301. package/dist/neon.js.map +1 -0
  302. package/dist/notify.d.ts +30 -0
  303. package/dist/notify.d.ts.map +1 -1
  304. package/dist/notify.js +160 -41
  305. package/dist/notify.js.map +1 -1
  306. package/dist/pglite.d.ts +37 -0
  307. package/dist/pglite.d.ts.map +1 -0
  308. package/dist/pglite.js +37 -0
  309. package/dist/pglite.js.map +1 -0
  310. package/dist/plugins/index.d.ts +2 -0
  311. package/dist/plugins/index.d.ts.map +1 -0
  312. package/dist/plugins/index.js +2 -0
  313. package/dist/plugins/index.js.map +1 -0
  314. package/dist/plugins/types.d.ts +73 -0
  315. package/dist/plugins/types.d.ts.map +1 -0
  316. package/dist/plugins/types.js +30 -0
  317. package/dist/plugins/types.js.map +1 -0
  318. package/dist/postgres/index.d.ts +68 -0
  319. package/dist/postgres/index.d.ts.map +1 -0
  320. package/dist/postgres/index.js +924 -0
  321. package/dist/postgres/index.js.map +1 -0
  322. package/dist/proxy/collection.d.ts +0 -2
  323. package/dist/proxy/collection.d.ts.map +1 -1
  324. package/dist/proxy/collection.js +2 -17
  325. package/dist/proxy/collection.js.map +1 -1
  326. package/dist/proxy/handler.d.ts +1 -1
  327. package/dist/proxy/handler.d.ts.map +1 -1
  328. package/dist/proxy/handler.js +3 -4
  329. package/dist/proxy/handler.js.map +1 -1
  330. package/dist/proxy/index.d.ts +1 -1
  331. package/dist/proxy/index.d.ts.map +1 -1
  332. package/dist/proxy/index.js +3 -3
  333. package/dist/proxy/index.js.map +1 -1
  334. package/dist/proxy/mutation-detector.js.map +1 -1
  335. package/dist/proxy/oplog.d.ts.map +1 -1
  336. package/dist/proxy/oplog.js +32 -8
  337. package/dist/proxy/oplog.js.map +1 -1
  338. package/dist/prune.d.ts +8 -1
  339. package/dist/prune.d.ts.map +1 -1
  340. package/dist/prune.js +68 -28
  341. package/dist/prune.js.map +1 -1
  342. package/dist/pull.d.ts +31 -0
  343. package/dist/pull.d.ts.map +1 -1
  344. package/dist/pull.js +1014 -474
  345. package/dist/pull.js.map +1 -1
  346. package/dist/push.d.ts +36 -2
  347. package/dist/push.d.ts.map +1 -1
  348. package/dist/push.js +603 -358
  349. package/dist/push.js.map +1 -1
  350. package/dist/realtime/in-memory.js.map +1 -1
  351. package/dist/realtime/index.d.ts +2 -1
  352. package/dist/realtime/index.d.ts.map +1 -1
  353. package/dist/realtime/index.js +2 -1
  354. package/dist/realtime/index.js.map +1 -1
  355. package/dist/realtime/types.d.ts +12 -0
  356. package/dist/realtime/types.d.ts.map +1 -1
  357. package/dist/realtime/types.js +13 -1
  358. package/dist/realtime/types.js.map +1 -1
  359. package/dist/relay/client-role/forward-engine.d.ts +63 -0
  360. package/dist/relay/client-role/forward-engine.d.ts.map +1 -0
  361. package/dist/relay/client-role/forward-engine.js +267 -0
  362. package/dist/relay/client-role/forward-engine.js.map +1 -0
  363. package/dist/relay/client-role/index.d.ts +9 -0
  364. package/dist/relay/client-role/index.d.ts.map +1 -0
  365. package/dist/relay/client-role/index.js +9 -0
  366. package/dist/relay/client-role/index.js.map +1 -0
  367. package/dist/relay/client-role/pull-engine.d.ts +72 -0
  368. package/dist/relay/client-role/pull-engine.d.ts.map +1 -0
  369. package/dist/relay/client-role/pull-engine.js +249 -0
  370. package/dist/relay/client-role/pull-engine.js.map +1 -0
  371. package/dist/relay/client-role/sequence-mapper.d.ts +65 -0
  372. package/dist/relay/client-role/sequence-mapper.d.ts.map +1 -0
  373. package/dist/relay/client-role/sequence-mapper.js +161 -0
  374. package/dist/relay/client-role/sequence-mapper.js.map +1 -0
  375. package/dist/relay/evaluation/relay-paths.d.ts +41 -0
  376. package/dist/relay/evaluation/relay-paths.d.ts.map +1 -0
  377. package/dist/relay/evaluation/relay-paths.js +504 -0
  378. package/dist/relay/evaluation/relay-paths.js.map +1 -0
  379. package/dist/relay/evaluation/rust-boundary.d.ts +47 -0
  380. package/dist/relay/evaluation/rust-boundary.d.ts.map +1 -0
  381. package/dist/relay/evaluation/rust-boundary.js +220 -0
  382. package/dist/relay/evaluation/rust-boundary.js.map +1 -0
  383. package/dist/relay/index.d.ts +37 -0
  384. package/dist/relay/index.d.ts.map +1 -0
  385. package/dist/relay/index.js +44 -0
  386. package/dist/relay/index.js.map +1 -0
  387. package/dist/relay/migrate.d.ts +18 -0
  388. package/dist/relay/migrate.d.ts.map +1 -0
  389. package/dist/relay/migrate.js +99 -0
  390. package/dist/relay/migrate.js.map +1 -0
  391. package/dist/relay/mode-manager.d.ts +60 -0
  392. package/dist/relay/mode-manager.d.ts.map +1 -0
  393. package/dist/relay/mode-manager.js +114 -0
  394. package/dist/relay/mode-manager.js.map +1 -0
  395. package/dist/relay/realtime.d.ts +90 -0
  396. package/dist/relay/realtime.d.ts.map +1 -0
  397. package/dist/relay/realtime.js +147 -0
  398. package/dist/relay/realtime.js.map +1 -0
  399. package/dist/relay/relay.d.ts +190 -0
  400. package/dist/relay/relay.d.ts.map +1 -0
  401. package/dist/relay/relay.js +320 -0
  402. package/dist/relay/relay.js.map +1 -0
  403. package/dist/relay/schema.d.ts +158 -0
  404. package/dist/relay/schema.d.ts.map +1 -0
  405. package/dist/relay/schema.js +7 -0
  406. package/dist/relay/schema.js.map +1 -0
  407. package/dist/relay/server-role/index.d.ts +56 -0
  408. package/dist/relay/server-role/index.d.ts.map +1 -0
  409. package/dist/relay/server-role/index.js +193 -0
  410. package/dist/relay/server-role/index.js.map +1 -0
  411. package/dist/relay/server-role/pull.d.ts +27 -0
  412. package/dist/relay/server-role/pull.d.ts.map +1 -0
  413. package/dist/relay/server-role/pull.js +24 -0
  414. package/dist/relay/server-role/pull.js.map +1 -0
  415. package/dist/relay/server-role/push.d.ts +29 -0
  416. package/dist/relay/server-role/push.d.ts.map +1 -0
  417. package/dist/relay/server-role/push.js +122 -0
  418. package/dist/relay/server-role/push.js.map +1 -0
  419. package/dist/s3/index.d.ts +83 -0
  420. package/dist/s3/index.d.ts.map +1 -0
  421. package/dist/s3/index.js +223 -0
  422. package/dist/s3/index.js.map +1 -0
  423. package/dist/schema.d.ts +137 -1
  424. package/dist/schema.d.ts.map +1 -1
  425. package/dist/schema.js +1 -0
  426. package/dist/schema.js.map +1 -1
  427. package/dist/service-worker/index.d.ts +114 -0
  428. package/dist/service-worker/index.d.ts.map +1 -0
  429. package/dist/service-worker/index.js +487 -0
  430. package/dist/service-worker/index.js.map +1 -0
  431. package/dist/snapshot-artifacts/sqlite-bun.d.ts +15 -0
  432. package/dist/snapshot-artifacts/sqlite-bun.d.ts.map +1 -0
  433. package/dist/snapshot-artifacts/sqlite-bun.js +128 -0
  434. package/dist/snapshot-artifacts/sqlite-bun.js.map +1 -0
  435. package/dist/snapshot-artifacts.d.ts +206 -0
  436. package/dist/snapshot-artifacts.d.ts.map +1 -0
  437. package/dist/snapshot-artifacts.js +545 -0
  438. package/dist/snapshot-artifacts.js.map +1 -0
  439. package/dist/snapshot-chunks/db-metadata.d.ts +18 -0
  440. package/dist/snapshot-chunks/db-metadata.d.ts.map +1 -1
  441. package/dist/snapshot-chunks/db-metadata.js +91 -63
  442. package/dist/snapshot-chunks/db-metadata.js.map +1 -1
  443. package/dist/snapshot-chunks/index.js +2 -2
  444. package/dist/snapshot-chunks/types.d.ts +2 -0
  445. package/dist/snapshot-chunks/types.d.ts.map +1 -1
  446. package/dist/snapshot-chunks.d.ts +33 -3
  447. package/dist/snapshot-chunks.d.ts.map +1 -1
  448. package/dist/snapshot-chunks.js +136 -55
  449. package/dist/snapshot-chunks.js.map +1 -1
  450. package/dist/sqlite/index.d.ts +53 -0
  451. package/dist/sqlite/index.d.ts.map +1 -0
  452. package/dist/sqlite/index.js +795 -0
  453. package/dist/sqlite/index.js.map +1 -0
  454. package/dist/sqlite3.d.ts +22 -0
  455. package/dist/sqlite3.d.ts.map +1 -0
  456. package/dist/sqlite3.js +99 -0
  457. package/dist/sqlite3.js.map +1 -0
  458. package/dist/stats.d.ts +8 -0
  459. package/dist/stats.d.ts.map +1 -1
  460. package/dist/stats.js +45 -13
  461. package/dist/stats.js.map +1 -1
  462. package/dist/subscriptions/cache.d.ts +3 -0
  463. package/dist/subscriptions/cache.d.ts.map +1 -1
  464. package/dist/subscriptions/cache.js +44 -0
  465. package/dist/subscriptions/cache.js.map +1 -1
  466. package/dist/subscriptions/index.js +2 -2
  467. package/dist/subscriptions/resolve.d.ts +3 -1
  468. package/dist/subscriptions/resolve.d.ts.map +1 -1
  469. package/dist/subscriptions/resolve.js +91 -52
  470. package/dist/subscriptions/resolve.js.map +1 -1
  471. package/dist/sync.d.ts +3 -0
  472. package/dist/sync.d.ts.map +1 -1
  473. package/dist/sync.js +9 -5
  474. package/dist/sync.js.map +1 -1
  475. package/package.json +297 -8
  476. package/src/auth-leases.ts +649 -0
  477. package/src/better-sqlite3.ts +35 -0
  478. package/src/blobs/access.ts +244 -0
  479. package/src/blobs/adapters/database.ts +125 -43
  480. package/src/blobs/index.ts +1 -0
  481. package/src/blobs/manager.ts +366 -65
  482. package/src/blobs/migrate.ts +16 -8
  483. package/src/blobs/types.ts +4 -0
  484. package/src/bun-sqlite-ambient.d.ts +19 -0
  485. package/src/bun-sqlite.ts +27 -0
  486. package/src/clients.ts +1 -0
  487. package/src/cloudflare/durable-object.ts +289 -0
  488. package/src/cloudflare/index.ts +22 -0
  489. package/src/cloudflare/r2.ts +526 -0
  490. package/src/cloudflare/scope-cache.ts +341 -0
  491. package/src/cloudflare/sentry.ts +230 -0
  492. package/src/cloudflare/worker.ts +77 -0
  493. package/src/commit-integrity.ts +371 -0
  494. package/src/crdt-yjs/index.ts +931 -0
  495. package/src/d1.ts +16 -0
  496. package/src/dialect/base.ts +71 -5
  497. package/src/dialect/helpers.ts +31 -0
  498. package/src/dialect/types.ts +47 -0
  499. package/src/encrypted-crdt.ts +786 -0
  500. package/src/errors.ts +22 -0
  501. package/src/filesystem/index.ts +262 -0
  502. package/src/handlers/collection.ts +78 -78
  503. package/src/handlers/create-handler.ts +540 -147
  504. package/src/handlers/types.ts +116 -0
  505. package/src/helpers/index.ts +2 -0
  506. package/src/helpers/scope-authorization.ts +27 -0
  507. package/src/helpers/scope-commit-index.ts +52 -0
  508. package/src/hono/api-key-auth.ts +177 -0
  509. package/src/hono/audit-redaction.ts +135 -0
  510. package/src/hono/blobs.ts +851 -0
  511. package/src/hono/console/gateway.ts +3046 -0
  512. package/src/hono/console/live-auth.ts +46 -0
  513. package/src/hono/console/route-descriptor.ts +22 -0
  514. package/src/hono/console/routes/api-keys.ts +721 -0
  515. package/src/hono/console/routes/clients.ts +447 -0
  516. package/src/hono/console/routes/commits.ts +1137 -0
  517. package/src/hono/console/routes/context.ts +956 -0
  518. package/src/hono/console/routes/events.ts +669 -0
  519. package/src/hono/console/routes/maintenance.ts +461 -0
  520. package/src/hono/console/routes/shared.ts +816 -0
  521. package/src/hono/console/routes/stats.ts +392 -0
  522. package/src/hono/console/routes/storage.ts +159 -0
  523. package/src/hono/console/routes.ts +146 -0
  524. package/src/hono/console/schema-errors.ts +23 -0
  525. package/src/hono/console/schemas.ts +914 -0
  526. package/src/hono/console/types.ts +223 -0
  527. package/src/hono/console/ui.ts +100 -0
  528. package/src/hono/create-server.ts +230 -0
  529. package/src/hono/errors.ts +50 -0
  530. package/src/hono/index.ts +54 -0
  531. package/src/hono/openapi.ts +139 -0
  532. package/src/hono/proxy/connection-manager.ts +340 -0
  533. package/src/hono/proxy/index.ts +8 -0
  534. package/src/hono/proxy/routes.ts +272 -0
  535. package/src/hono/rate-limit.ts +319 -0
  536. package/src/hono/realtime-sync-packs.ts +354 -0
  537. package/src/hono/routes/audit.ts +499 -0
  538. package/src/hono/routes/auth-leases.ts +119 -0
  539. package/src/hono/routes/combined.ts +583 -0
  540. package/src/hono/routes/context.ts +1629 -0
  541. package/src/hono/routes/health.ts +26 -0
  542. package/src/hono/routes/realtime.ts +808 -0
  543. package/src/hono/routes/shared.ts +1626 -0
  544. package/src/hono/routes/snapshots.ts +345 -0
  545. package/src/hono/routes.ts +68 -0
  546. package/src/hono/validation.ts +81 -0
  547. package/src/hono/websocket-origin.ts +131 -0
  548. package/src/hono/ws.ts +1134 -0
  549. package/src/index.ts +6 -0
  550. package/src/libsql.ts +51 -0
  551. package/src/neon.ts +28 -0
  552. package/src/notify.ts +224 -44
  553. package/src/pglite.ts +68 -0
  554. package/src/plugins/index.ts +1 -0
  555. package/src/plugins/types.ts +144 -0
  556. package/src/postgres/index.ts +1291 -0
  557. package/src/proxy/collection.ts +5 -27
  558. package/src/proxy/handler.ts +2 -2
  559. package/src/proxy/index.ts +0 -2
  560. package/src/proxy/oplog.ts +41 -10
  561. package/src/prune.ts +137 -36
  562. package/src/pull.ts +1504 -591
  563. package/src/push.ts +881 -443
  564. package/src/realtime/index.ts +2 -0
  565. package/src/realtime/types.ts +35 -0
  566. package/src/relay/bun-types.d.ts +50 -0
  567. package/src/relay/client-role/forward-engine.ts +355 -0
  568. package/src/relay/client-role/index.ts +9 -0
  569. package/src/relay/client-role/pull-engine.ts +329 -0
  570. package/src/relay/client-role/sequence-mapper.ts +201 -0
  571. package/src/relay/evaluation/relay-paths.ts +699 -0
  572. package/src/relay/evaluation/rust-boundary.ts +464 -0
  573. package/src/relay/index.ts +50 -0
  574. package/src/relay/migrate.ts +113 -0
  575. package/src/relay/mode-manager.ts +142 -0
  576. package/src/relay/realtime.ts +207 -0
  577. package/src/relay/relay.ts +431 -0
  578. package/src/relay/schema.ts +171 -0
  579. package/src/relay/server-role/index.ts +338 -0
  580. package/src/relay/server-role/pull.ts +43 -0
  581. package/src/relay/server-role/push.ts +164 -0
  582. package/src/s3/index.ts +346 -0
  583. package/src/schema.ts +141 -1
  584. package/src/service-worker/index.ts +773 -0
  585. package/src/snapshot-artifacts/sqlite-bun.ts +168 -0
  586. package/src/snapshot-artifacts.ts +896 -0
  587. package/src/snapshot-chunks/db-metadata.ts +131 -72
  588. package/src/snapshot-chunks/types.ts +2 -0
  589. package/src/snapshot-chunks.ts +194 -70
  590. package/src/sqlite/index.ts +1064 -0
  591. package/src/sqlite3.ts +137 -0
  592. package/src/stats.ts +76 -13
  593. package/src/subscriptions/cache.ts +58 -0
  594. package/src/subscriptions/resolve.ts +99 -58
  595. package/src/sync.ts +16 -6
  596. package/src/blobs/adapters/database.test.ts +0 -67
  597. package/src/notify.test.ts +0 -520
  598. package/src/proxy/handler.test.ts +0 -126
  599. package/src/proxy/mutation-detector.test.ts +0 -71
  600. package/src/snapshot-chunks/db-metadata.test.ts +0 -172
  601. package/src/subscriptions/resolve.test.ts +0 -180
@@ -0,0 +1,851 @@
1
+ /**
2
+ * @syncular/server/hono - Blob routes for media/binary handling
3
+ *
4
+ * Provides:
5
+ * - POST /blobs/upload - Initiate a blob upload (get presigned URL)
6
+ * - POST /blobs/:hash/complete - Complete a blob upload
7
+ * - GET /blobs/:hash/url - Get a presigned download URL
8
+ * - PUT /blobs/:hash/upload - Direct upload (for database adapter)
9
+ * - GET /blobs/:hash/download - Direct download (for database adapter)
10
+ */
11
+
12
+ import {
13
+ BlobUploadCompleteResponseSchema,
14
+ BlobUploadInitRequestSchema,
15
+ BlobUploadInitResponseSchema,
16
+ createIncrementalSha256,
17
+ ErrorResponseSchema,
18
+ parseBlobHash,
19
+ type SyncularErrorCode,
20
+ sha256Hex,
21
+ } from '@syncular/core';
22
+ import type {
23
+ BlobManager,
24
+ BlobNotFoundError,
25
+ BlobValidationError,
26
+ } from '@syncular/server';
27
+ import {
28
+ type BlobTokenSigner,
29
+ readBlobFromDatabase,
30
+ type SyncBlobsDb,
31
+ storeBlobInDatabase,
32
+ } from '@syncular/server';
33
+ import type { Context } from 'hono';
34
+ import { Hono } from 'hono';
35
+ import { describeRoute, resolver } from 'hono-openapi';
36
+ import type { Kysely } from 'kysely';
37
+ import { z } from 'zod';
38
+ import { syncError } from './errors';
39
+ import { blobValidator as zValidator } from './validation';
40
+
41
+ interface BlobAuthResult {
42
+ actorId: string;
43
+ partitionId?: string;
44
+ }
45
+
46
+ export interface CreateBlobRoutesOptions<DB extends SyncBlobsDb = SyncBlobsDb> {
47
+ /** Blob manager instance */
48
+ blobManager: BlobManager;
49
+ /** Authentication function */
50
+ authenticate: (c: Context) => Promise<BlobAuthResult | null>;
51
+ /**
52
+ * Token signer for database adapter direct uploads/downloads.
53
+ * Required if using the database blob storage adapter.
54
+ */
55
+ tokenSigner?: BlobTokenSigner;
56
+ /**
57
+ * Database instance for direct blob storage.
58
+ * Required if using the database blob storage adapter.
59
+ */
60
+ db?: Kysely<DB>;
61
+ /**
62
+ * Check whether an authenticated actor can access a blob hash.
63
+ * This must enforce your tenant or ownership model.
64
+ */
65
+ canAccessBlob: (args: {
66
+ actorId: string;
67
+ hash: string;
68
+ partitionId: string;
69
+ }) => Promise<boolean>;
70
+ /**
71
+ * Maximum upload size in bytes.
72
+ * Default: 100MB (104857600)
73
+ */
74
+ maxUploadSize?: number;
75
+ }
76
+
77
+ const hashParamsSchema = z.object({
78
+ hash: z.string().min(1),
79
+ });
80
+
81
+ const tokenQuerySchema = z.object({
82
+ token: z.string().min(1),
83
+ });
84
+
85
+ /**
86
+ * Create blob routes for Hono.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const blobRoutes = createBlobRoutes({
91
+ * blobManager,
92
+ * authenticate: async (c) => {
93
+ * const token = c.req.header('Authorization')?.replace('Bearer ', '');
94
+ * if (!token) return null;
95
+ * const user = await verifyToken(token);
96
+ * return user ? { actorId: user.id } : null;
97
+ * },
98
+ * canAccessBlob: async ({ actorId, hash, partitionId }) => {
99
+ * // Enforce tenant/ownership permissions here.
100
+ * // partitionId defaults to "default" when not provided by auth.
101
+ * return true;
102
+ * },
103
+ * });
104
+ *
105
+ * app.route('/api/sync', blobRoutes);
106
+ * ```
107
+ */
108
+ export function createBlobRoutes<DB extends SyncBlobsDb>(
109
+ options: CreateBlobRoutesOptions<DB>
110
+ ): Hono {
111
+ const {
112
+ blobManager,
113
+ authenticate,
114
+ tokenSigner,
115
+ db,
116
+ canAccessBlob,
117
+ maxUploadSize = 100 * 1024 * 1024, // 100MB
118
+ } = options;
119
+
120
+ const routes = new Hono();
121
+
122
+ // -------------------------------------------------------------------------
123
+ // POST /blobs/upload - Initiate upload
124
+ // -------------------------------------------------------------------------
125
+
126
+ routes.post(
127
+ '/blobs/upload',
128
+ describeRoute({
129
+ tags: ['blobs'],
130
+ summary: 'Initiate blob upload',
131
+ description:
132
+ 'Initiates a blob upload and returns a presigned URL for uploading',
133
+ responses: {
134
+ 200: {
135
+ description: 'Upload initiated (or blob already exists)',
136
+ content: {
137
+ 'application/json': {
138
+ schema: resolver(BlobUploadInitResponseSchema),
139
+ },
140
+ },
141
+ },
142
+ 400: {
143
+ description: 'Invalid request',
144
+ content: {
145
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
146
+ },
147
+ },
148
+ 401: {
149
+ description: 'Unauthenticated',
150
+ content: {
151
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
152
+ },
153
+ },
154
+ },
155
+ }),
156
+ zValidator('json', BlobUploadInitRequestSchema),
157
+ async (c) => {
158
+ const auth = await authenticate(c);
159
+ if (!auth) return syncError(c, 401, 'sync.auth_required');
160
+
161
+ const body = c.req.valid('json');
162
+
163
+ // Validate size
164
+ if (body.size > maxUploadSize) {
165
+ return syncError(
166
+ c,
167
+ 400,
168
+ 'blob.too_large',
169
+ `Maximum upload size is ${maxUploadSize} bytes`
170
+ );
171
+ }
172
+
173
+ try {
174
+ const partitionId = auth.partitionId ?? 'default';
175
+ const result = await blobManager.initiateUpload({
176
+ hash: body.hash,
177
+ size: body.size,
178
+ mimeType: body.mimeType,
179
+ actorId: auth.actorId,
180
+ partitionId,
181
+ });
182
+
183
+ return c.json(result, 200);
184
+ } catch (err) {
185
+ if (isBlobValidationError(err)) {
186
+ return syncError(c, 400, 'blob.invalid_request', err.message);
187
+ }
188
+ throw err;
189
+ }
190
+ }
191
+ );
192
+
193
+ // -------------------------------------------------------------------------
194
+ // POST /blobs/:hash/complete - Complete upload
195
+ // -------------------------------------------------------------------------
196
+
197
+ routes.post(
198
+ '/blobs/:hash/complete',
199
+ describeRoute({
200
+ tags: ['blobs'],
201
+ summary: 'Complete blob upload',
202
+ description:
203
+ 'Marks a blob upload as complete after the client has uploaded to the presigned URL',
204
+ responses: {
205
+ 200: {
206
+ description: 'Upload completed',
207
+ content: {
208
+ 'application/json': {
209
+ schema: resolver(BlobUploadCompleteResponseSchema),
210
+ },
211
+ },
212
+ },
213
+ 400: {
214
+ description: 'Invalid request or upload failed',
215
+ content: {
216
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
217
+ },
218
+ },
219
+ 401: {
220
+ description: 'Unauthenticated',
221
+ content: {
222
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
223
+ },
224
+ },
225
+ },
226
+ }),
227
+ zValidator('param', hashParamsSchema),
228
+ async (c) => {
229
+ const auth = await authenticate(c);
230
+ if (!auth) return syncError(c, 401, 'sync.auth_required');
231
+
232
+ const { hash } = c.req.valid('param');
233
+
234
+ // Validate hash format
235
+ if (!parseBlobHash(hash)) {
236
+ return syncError(
237
+ c,
238
+ 400,
239
+ 'blob.invalid_request',
240
+ 'Invalid blob hash format'
241
+ );
242
+ }
243
+
244
+ const partitionId = auth.partitionId ?? 'default';
245
+ const result = await blobManager.completeUpload(hash, {
246
+ actorId: auth.actorId,
247
+ partitionId,
248
+ });
249
+
250
+ if (!result.ok) {
251
+ const code = result.code ?? 'blob.upload_failed';
252
+ const status = code === 'blob.forbidden' ? 403 : 400;
253
+ return syncError(c, status, code, result.error);
254
+ }
255
+
256
+ return c.json(result, 200);
257
+ }
258
+ );
259
+
260
+ // -------------------------------------------------------------------------
261
+ // GET /blobs/:hash/url - Get download URL
262
+ // -------------------------------------------------------------------------
263
+
264
+ routes.get(
265
+ '/blobs/:hash/url',
266
+ describeRoute({
267
+ tags: ['blobs'],
268
+ summary: 'Get blob download URL',
269
+ description: 'Returns a presigned URL for downloading a blob',
270
+ responses: {
271
+ 200: {
272
+ description: 'Download URL',
273
+ content: {
274
+ 'application/json': {
275
+ schema: resolver(
276
+ z.object({
277
+ url: z.string().url(),
278
+ expiresAt: z.string(),
279
+ metadata: z.object({
280
+ hash: z.string(),
281
+ size: z.number(),
282
+ mimeType: z.string(),
283
+ createdAt: z.string(),
284
+ uploadComplete: z.boolean(),
285
+ }),
286
+ })
287
+ ),
288
+ },
289
+ },
290
+ },
291
+ 401: {
292
+ description: 'Unauthenticated',
293
+ content: {
294
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
295
+ },
296
+ },
297
+ 403: {
298
+ description: 'Forbidden',
299
+ content: {
300
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
301
+ },
302
+ },
303
+ 404: {
304
+ description: 'Not found',
305
+ content: {
306
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
307
+ },
308
+ },
309
+ },
310
+ }),
311
+ zValidator('param', hashParamsSchema),
312
+ async (c) => {
313
+ const auth = await authenticate(c);
314
+ if (!auth) return syncError(c, 401, 'sync.auth_required');
315
+
316
+ const { hash } = c.req.valid('param');
317
+
318
+ // Validate hash format
319
+ if (!parseBlobHash(hash)) {
320
+ return syncError(c, 404, 'blob.not_found');
321
+ }
322
+
323
+ const partitionId = auth.partitionId ?? 'default';
324
+ const canAccess = await canAccessBlob({
325
+ actorId: auth.actorId,
326
+ hash,
327
+ partitionId,
328
+ });
329
+ if (!canAccess) {
330
+ return syncError(c, 403, 'blob.forbidden');
331
+ }
332
+
333
+ try {
334
+ const result = await blobManager.getDownloadUrl({
335
+ hash,
336
+ actorId: auth.actorId,
337
+ partitionId,
338
+ });
339
+ return c.json(result, 200);
340
+ } catch (err) {
341
+ if (isBlobNotFoundError(err)) {
342
+ return syncError(c, 404, 'blob.not_found');
343
+ }
344
+ throw err;
345
+ }
346
+ }
347
+ );
348
+
349
+ // -------------------------------------------------------------------------
350
+ // PUT /blobs/:hash/upload - Direct upload (database adapter)
351
+ // -------------------------------------------------------------------------
352
+
353
+ if (tokenSigner && db) {
354
+ routes.put(
355
+ '/blobs/:hash/upload',
356
+ describeRoute({
357
+ tags: ['blobs'],
358
+ summary: 'Direct blob upload',
359
+ description:
360
+ 'Direct upload endpoint for database storage adapter. Requires a signed token.',
361
+ responses: {
362
+ 200: {
363
+ description: 'Upload successful',
364
+ },
365
+ 400: {
366
+ description: 'Invalid request',
367
+ content: {
368
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
369
+ },
370
+ },
371
+ 401: {
372
+ description: 'Invalid or expired token',
373
+ content: {
374
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
375
+ },
376
+ },
377
+ },
378
+ }),
379
+ zValidator('param', hashParamsSchema),
380
+ zValidator('query', tokenQuerySchema),
381
+ async (c) => {
382
+ const { hash } = c.req.valid('param');
383
+ const { token } = c.req.valid('query');
384
+
385
+ // Verify token
386
+ const payload = await tokenSigner.verify(token);
387
+ if (!payload || payload.action !== 'upload' || payload.hash !== hash) {
388
+ return syncError(c, 401, 'blob.invalid_token');
389
+ }
390
+
391
+ const uploadRecord = await blobManager.getUploadRecord(hash, {
392
+ partitionId: payload.partitionId,
393
+ });
394
+ if (!uploadRecord) {
395
+ return syncError(c, 404, 'blob.not_found', 'Upload record not found');
396
+ }
397
+ if (uploadRecord.status !== 'pending') {
398
+ return syncError(
399
+ c,
400
+ 409,
401
+ 'blob.upload_failed',
402
+ 'Upload is not pending'
403
+ );
404
+ }
405
+ if (payload.size !== uploadRecord.size) {
406
+ return syncError(c, 401, 'blob.invalid_token');
407
+ }
408
+ if (uploadRecord.size > maxUploadSize) {
409
+ return syncError(
410
+ c,
411
+ 400,
412
+ 'blob.too_large',
413
+ `Maximum upload size is ${maxUploadSize} bytes`
414
+ );
415
+ }
416
+
417
+ const contentLengthHeader = c.req.header('Content-Length');
418
+ if (contentLengthHeader) {
419
+ const contentLength = Number(contentLengthHeader);
420
+ if (!Number.isFinite(contentLength) || contentLength < 0) {
421
+ return syncError(
422
+ c,
423
+ 400,
424
+ 'blob.invalid_request',
425
+ 'Invalid Content-Length'
426
+ );
427
+ }
428
+ if (contentLength > maxUploadSize) {
429
+ return syncError(
430
+ c,
431
+ 400,
432
+ 'blob.too_large',
433
+ `Maximum upload size is ${maxUploadSize} bytes`
434
+ );
435
+ }
436
+ if (contentLength !== uploadRecord.size) {
437
+ return syncError(
438
+ c,
439
+ 400,
440
+ 'blob.size_mismatch',
441
+ `Expected ${uploadRecord.size} bytes, got ${contentLength}`
442
+ );
443
+ }
444
+ }
445
+
446
+ const mimeType =
447
+ c.req.header('Content-Type') ??
448
+ uploadRecord.mimeType ??
449
+ 'application/octet-stream';
450
+ const storagePartitionOptions = { partitionId: payload.partitionId };
451
+
452
+ const streamingUpload = blobManager.adapter.putStream
453
+ ? await createValidatedUploadStream(c.req.raw, {
454
+ expectedSize: uploadRecord.size,
455
+ maxSize: maxUploadSize,
456
+ })
457
+ : null;
458
+
459
+ if (streamingUpload && blobManager.adapter.putStream) {
460
+ try {
461
+ await blobManager.adapter.putStream(
462
+ hash,
463
+ streamingUpload.stream,
464
+ { mimeType },
465
+ storagePartitionOptions
466
+ );
467
+ } catch (err) {
468
+ if (isBlobUploadBodyError(err)) {
469
+ void streamingUpload.hashHex.catch(() => {});
470
+ return syncError(
471
+ c,
472
+ 400,
473
+ uploadBodyErrorCode(err.code),
474
+ err.message
475
+ );
476
+ }
477
+ void streamingUpload.hashHex.catch(() => {});
478
+ throw err;
479
+ }
480
+
481
+ const computedHash = await streamingUpload.hashHex;
482
+ const expectedHex = parseBlobHash(hash);
483
+ if (!expectedHex || computedHash !== expectedHex) {
484
+ await deleteUploadedBlobBestEffort(blobManager, hash, {
485
+ partitionId: payload.partitionId,
486
+ });
487
+ return syncError(
488
+ c,
489
+ 400,
490
+ 'blob.hash_mismatch',
491
+ 'Content hash does not match'
492
+ );
493
+ }
494
+
495
+ return c.text('OK', 200);
496
+ }
497
+
498
+ let bodyBytes: Uint8Array;
499
+ try {
500
+ bodyBytes = await readRequestBodyWithLimit(c.req.raw, {
501
+ expectedSize: uploadRecord.size,
502
+ maxSize: maxUploadSize,
503
+ });
504
+ } catch (err) {
505
+ if (isBlobUploadBodyError(err)) {
506
+ return syncError(
507
+ c,
508
+ 400,
509
+ uploadBodyErrorCode(err.code),
510
+ err.message
511
+ );
512
+ }
513
+ throw err;
514
+ }
515
+
516
+ // Verify hash
517
+ const computedHash = await computeSha256Hash(bodyBytes);
518
+ const expectedHex = parseBlobHash(hash);
519
+ if (!expectedHex || computedHash !== expectedHex) {
520
+ return syncError(
521
+ c,
522
+ 400,
523
+ 'blob.hash_mismatch',
524
+ 'Content hash does not match'
525
+ );
526
+ }
527
+
528
+ if (blobManager.adapter.put) {
529
+ await blobManager.adapter.put(
530
+ hash,
531
+ bodyBytes,
532
+ { mimeType },
533
+ storagePartitionOptions
534
+ );
535
+ } else {
536
+ await storeBlobInDatabase(db, {
537
+ partitionId: payload.partitionId,
538
+ hash,
539
+ size: bodyBytes.length,
540
+ mimeType,
541
+ body: bodyBytes,
542
+ });
543
+ }
544
+
545
+ return c.text('OK', 200);
546
+ }
547
+ );
548
+
549
+ // -------------------------------------------------------------------------
550
+ // GET /blobs/:hash/download - Direct download (database adapter)
551
+ // -------------------------------------------------------------------------
552
+
553
+ routes.get(
554
+ '/blobs/:hash/download',
555
+ describeRoute({
556
+ tags: ['blobs'],
557
+ summary: 'Direct blob download',
558
+ description:
559
+ 'Direct download endpoint for database storage adapter. Requires a signed token.',
560
+ responses: {
561
+ 200: {
562
+ description: 'Blob content',
563
+ },
564
+ 401: {
565
+ description: 'Invalid or expired token',
566
+ content: {
567
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
568
+ },
569
+ },
570
+ 404: {
571
+ description: 'Not found',
572
+ content: {
573
+ 'application/json': { schema: resolver(ErrorResponseSchema) },
574
+ },
575
+ },
576
+ },
577
+ }),
578
+ zValidator('param', hashParamsSchema),
579
+ zValidator('query', tokenQuerySchema),
580
+ async (c) => {
581
+ const { hash } = c.req.valid('param');
582
+ const { token } = c.req.valid('query');
583
+
584
+ // Verify token
585
+ const payload = await tokenSigner.verify(token);
586
+ if (
587
+ !payload ||
588
+ payload.action !== 'download' ||
589
+ payload.hash !== hash
590
+ ) {
591
+ return syncError(c, 401, 'blob.invalid_token');
592
+ }
593
+
594
+ // Read via the blob adapter (R2, database, etc.)
595
+ if (blobManager.adapter.get) {
596
+ const data = await blobManager.adapter.get(hash, {
597
+ partitionId: payload.partitionId,
598
+ });
599
+ if (!data) {
600
+ return syncError(c, 404, 'blob.not_found');
601
+ }
602
+ const meta = blobManager.adapter.getMetadata
603
+ ? await blobManager.adapter.getMetadata(hash, {
604
+ partitionId: payload.partitionId,
605
+ })
606
+ : null;
607
+ return new Response(data as BodyInit, {
608
+ status: 200,
609
+ headers: {
610
+ 'Content-Type': meta?.mimeType ?? 'application/octet-stream',
611
+ 'Content-Length': String(data.length),
612
+ 'Cache-Control': 'private, max-age=31536000, immutable',
613
+ },
614
+ });
615
+ }
616
+
617
+ // Fallback: read from database directly
618
+ const blob = await readBlobFromDatabase(db, hash, {
619
+ partitionId: payload.partitionId,
620
+ });
621
+ if (!blob) {
622
+ return syncError(c, 404, 'blob.not_found');
623
+ }
624
+
625
+ return new Response(blob.body as BodyInit, {
626
+ status: 200,
627
+ headers: {
628
+ 'Content-Type': blob.mimeType,
629
+ 'Content-Length': String(blob.size),
630
+ 'Cache-Control': 'private, max-age=31536000, immutable',
631
+ },
632
+ });
633
+ }
634
+ );
635
+ }
636
+
637
+ return routes;
638
+ }
639
+
640
+ // ============================================================================
641
+ // Helpers
642
+ // ============================================================================
643
+
644
+ function isBlobValidationError(err: unknown): err is BlobValidationError {
645
+ return (
646
+ typeof err === 'object' &&
647
+ err !== null &&
648
+ (err as { name?: string }).name === 'BlobValidationError'
649
+ );
650
+ }
651
+
652
+ function isBlobNotFoundError(err: unknown): err is BlobNotFoundError {
653
+ return (
654
+ typeof err === 'object' &&
655
+ err !== null &&
656
+ (err as { name?: string }).name === 'BlobNotFoundError'
657
+ );
658
+ }
659
+
660
+ class BlobUploadBodyError extends Error {
661
+ constructor(
662
+ public readonly code: 'blob.too_large' | 'blob.size_mismatch',
663
+ message: string
664
+ ) {
665
+ super(message);
666
+ this.name = 'BlobUploadBodyError';
667
+ }
668
+ }
669
+
670
+ function isBlobUploadBodyError(err: unknown): err is BlobUploadBodyError {
671
+ return (
672
+ typeof err === 'object' &&
673
+ err !== null &&
674
+ (err as { name?: string }).name === 'BlobUploadBodyError'
675
+ );
676
+ }
677
+
678
+ function uploadBodyErrorCode(
679
+ code: BlobUploadBodyError['code']
680
+ ): SyncularErrorCode {
681
+ return code;
682
+ }
683
+
684
+ async function deleteUploadedBlobBestEffort(
685
+ blobManager: BlobManager,
686
+ hash: string,
687
+ options: { partitionId: string }
688
+ ): Promise<void> {
689
+ try {
690
+ await blobManager.adapter.delete(hash, options);
691
+ } catch {
692
+ // Best-effort cleanup only.
693
+ }
694
+ }
695
+
696
+ interface ValidatedUploadStream {
697
+ stream: ReadableStream<Uint8Array>;
698
+ hashHex: Promise<string>;
699
+ }
700
+
701
+ async function createValidatedUploadStream(
702
+ request: Request,
703
+ args: { expectedSize: number; maxSize: number }
704
+ ): Promise<ValidatedUploadStream | null> {
705
+ const body = request.body;
706
+ if (!body) return null;
707
+
708
+ const hasher = await createIncrementalSha256();
709
+ const reader = body.getReader();
710
+
711
+ let resolveHash: ((hashHex: string) => void) | null = null;
712
+ let rejectHash: ((reason: Error) => void) | null = null;
713
+ const hashHex = new Promise<string>((resolve, reject) => {
714
+ resolveHash = resolve;
715
+ rejectHash = reject;
716
+ });
717
+
718
+ let totalSize = 0;
719
+ let finalized = false;
720
+
721
+ const fail = (error: Error): void => {
722
+ if (finalized) return;
723
+ finalized = true;
724
+ rejectHash?.(error);
725
+ };
726
+
727
+ const stream = new ReadableStream<Uint8Array>({
728
+ async pull(controller) {
729
+ try {
730
+ const { done, value } = await reader.read();
731
+ if (done) {
732
+ if (totalSize !== args.expectedSize) {
733
+ const sizeError = new BlobUploadBodyError(
734
+ 'blob.size_mismatch',
735
+ `Expected ${args.expectedSize} bytes, got ${totalSize}`
736
+ );
737
+ fail(sizeError);
738
+ controller.error(sizeError);
739
+ return;
740
+ }
741
+ if (!finalized) {
742
+ try {
743
+ const hash = await hasher.digestHex();
744
+ finalized = true;
745
+ resolveHash?.(hash);
746
+ } catch (err) {
747
+ const hashError =
748
+ err instanceof Error
749
+ ? err
750
+ : new Error('Failed to finalize upload body hash');
751
+ fail(hashError);
752
+ controller.error(hashError);
753
+ return;
754
+ }
755
+ }
756
+ controller.close();
757
+ return;
758
+ }
759
+
760
+ if (!value || value.length === 0) {
761
+ return;
762
+ }
763
+
764
+ totalSize += value.length;
765
+ if (totalSize > args.maxSize) {
766
+ const limitError = new BlobUploadBodyError(
767
+ 'blob.too_large',
768
+ `Maximum upload size is ${args.maxSize} bytes`
769
+ );
770
+ fail(limitError);
771
+ controller.error(limitError);
772
+ return;
773
+ }
774
+ if (totalSize > args.expectedSize) {
775
+ const mismatchError = new BlobUploadBodyError(
776
+ 'blob.size_mismatch',
777
+ `Expected ${args.expectedSize} bytes, got more than expected`
778
+ );
779
+ fail(mismatchError);
780
+ controller.error(mismatchError);
781
+ return;
782
+ }
783
+
784
+ hasher.update(value);
785
+ controller.enqueue(value);
786
+ } catch (err) {
787
+ const streamError =
788
+ err instanceof Error ? err : new Error('Failed to read upload body');
789
+ fail(streamError);
790
+ controller.error(streamError);
791
+ }
792
+ },
793
+ cancel() {
794
+ reader.cancel().catch(() => {});
795
+ },
796
+ });
797
+
798
+ return { stream, hashHex };
799
+ }
800
+
801
+ async function readRequestBodyWithLimit(
802
+ request: Request,
803
+ args: { expectedSize: number; maxSize: number }
804
+ ): Promise<Uint8Array> {
805
+ const body = request.body;
806
+ if (!body) {
807
+ if (args.expectedSize === 0) return new Uint8Array();
808
+ throw new BlobUploadBodyError(
809
+ 'blob.size_mismatch',
810
+ `Expected ${args.expectedSize} bytes, got 0`
811
+ );
812
+ }
813
+
814
+ const reader = body.getReader();
815
+ const merged = new Uint8Array(args.expectedSize);
816
+ let totalSize = 0;
817
+
818
+ while (true) {
819
+ const { done, value } = await reader.read();
820
+ if (done) break;
821
+ if (!value || value.length === 0) continue;
822
+
823
+ totalSize += value.length;
824
+ if (totalSize > args.maxSize) {
825
+ throw new BlobUploadBodyError(
826
+ 'blob.too_large',
827
+ `Maximum upload size is ${args.maxSize} bytes`
828
+ );
829
+ }
830
+ if (totalSize > args.expectedSize) {
831
+ throw new BlobUploadBodyError(
832
+ 'blob.size_mismatch',
833
+ `Expected ${args.expectedSize} bytes, got more than expected`
834
+ );
835
+ }
836
+ merged.set(value, totalSize - value.length);
837
+ }
838
+
839
+ if (totalSize !== args.expectedSize) {
840
+ throw new BlobUploadBodyError(
841
+ 'blob.size_mismatch',
842
+ `Expected ${args.expectedSize} bytes, got ${totalSize}`
843
+ );
844
+ }
845
+
846
+ return merged;
847
+ }
848
+
849
+ async function computeSha256Hash(data: Uint8Array): Promise<string> {
850
+ return sha256Hex(data);
851
+ }