@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
@@ -1,3046 +0,0 @@
1
- import {
2
- createSyncularErrorResponse,
3
- type SyncularErrorCode,
4
- } from '@syncular/core';
5
- import type { Context } from 'hono';
6
- import { Hono } from 'hono';
7
- import { cors } from 'hono/cors';
8
- import type { UpgradeWebSocket } from 'hono/ws';
9
- import { resolver } from 'hono-openapi';
10
- import { z } from 'zod';
11
- import { consoleValidator as zValidator } from '../validation';
12
- import { isWebSocketOriginAllowed } from '../websocket-origin';
13
- import {
14
- closeUnauthenticatedSocket,
15
- parseBearerToken,
16
- parseWebSocketAuthToken,
17
- } from './live-auth';
18
- import { describeConsoleGatewayRoute } from './route-descriptor';
19
- import type {
20
- ConsoleApiKey,
21
- ConsoleApiKeyBulkRevokeResponse,
22
- ConsoleApiKeyCreateResponse,
23
- ConsoleClient,
24
- ConsoleCommitListItem,
25
- ConsoleOperationEvent,
26
- ConsolePaginatedResponse,
27
- ConsoleRequestEvent,
28
- ConsoleTimelineItem,
29
- LatencyPercentiles,
30
- LatencyStatsResponse,
31
- SyncStats,
32
- TimeseriesBucket,
33
- TimeseriesStatsResponse,
34
- } from './schemas';
35
- import {
36
- ApiKeyTypeSchema,
37
- ConsoleApiKeyBulkRevokeRequestSchema,
38
- ConsoleApiKeyBulkRevokeResponseSchema,
39
- ConsoleApiKeyCreateRequestSchema,
40
- ConsoleApiKeyCreateResponseSchema,
41
- ConsoleApiKeyRevokeResponseSchema,
42
- ConsoleApiKeySchema,
43
- ConsoleClearEventsResultSchema,
44
- ConsoleClientSchema,
45
- ConsoleCommitDetailSchema,
46
- ConsoleCommitListItemSchema,
47
- ConsoleCompactResultSchema,
48
- ConsoleEvictResultSchema,
49
- ConsoleHandlerSchema,
50
- ConsoleOperationEventSchema,
51
- ConsoleOperationsQuerySchema,
52
- ConsolePaginatedResponseSchema,
53
- ConsolePaginationQuerySchema,
54
- ConsolePartitionedPaginationQuerySchema,
55
- ConsolePartitionQuerySchema,
56
- ConsolePruneEventsResultSchema,
57
- ConsolePrunePreviewSchema,
58
- ConsolePruneResultSchema,
59
- ConsoleRequestEventSchema,
60
- ConsoleRequestPayloadSchema,
61
- ConsoleTimelineItemSchema,
62
- ConsoleTimelineQuerySchema,
63
- LatencyQuerySchema,
64
- LatencyStatsResponseSchema,
65
- SyncStatsSchema,
66
- TimeseriesQuerySchema,
67
- TimeseriesStatsResponseSchema,
68
- } from './schemas';
69
- import type { ConsoleAuthResult } from './types';
70
-
71
- type ConsoleGatewayErrorStatus = 400 | 401 | 403 | 404 | 502;
72
-
73
- function consoleGatewayError(
74
- c: Context,
75
- status: ConsoleGatewayErrorStatus,
76
- code: SyncularErrorCode,
77
- options: { message?: string; details?: Record<string, unknown> } = {}
78
- ): Response {
79
- return c.json(createSyncularErrorResponse(code, options), status);
80
- }
81
-
82
- export interface ConsoleGatewayInstance {
83
- instanceId: string;
84
- label?: string;
85
- baseUrl: string;
86
- token?: string;
87
- enabled?: boolean;
88
- }
89
-
90
- interface ConsoleGatewayDownstreamSocket {
91
- onopen?: ((event: Event) => void) | null;
92
- onmessage: ((event: MessageEvent) => void) | null;
93
- onerror: ((event: Event) => void) | null;
94
- close: () => void;
95
- send?: (data: string) => void;
96
- }
97
-
98
- export interface CreateConsoleGatewayRoutesOptions {
99
- instances: ConsoleGatewayInstance[];
100
- authenticate: (c: Context) => Promise<ConsoleAuthResult | null>;
101
- corsOrigins?: string[] | '*';
102
- fetchImpl?: typeof fetch;
103
- websocket?: {
104
- enabled?: boolean;
105
- upgradeWebSocket?: UpgradeWebSocket;
106
- heartbeatIntervalMs?: number;
107
- createWebSocket?: (url: string) => ConsoleGatewayDownstreamSocket;
108
- maxMessageBytes?: number;
109
- maxMessagesPerWindow?: number;
110
- messageRateWindowMs?: number;
111
- /**
112
- * - undefined: allow same-origin browser upgrades and origin-less non-browser clients
113
- * - '*': allow all origins
114
- * - string[]: exact origin match (scheme + host + port)
115
- */
116
- allowedOrigins?: string[] | '*';
117
- };
118
- }
119
-
120
- interface GatewayFailure {
121
- instanceId: string;
122
- reason: string;
123
- status?: number;
124
- }
125
-
126
- const GatewayFailureSchema = z.object({
127
- instanceId: z.string(),
128
- reason: z.string(),
129
- status: z.number().int().optional(),
130
- });
131
-
132
- const GatewayMetadataSchema = z.object({
133
- partial: z.boolean(),
134
- failedInstances: z.array(GatewayFailureSchema),
135
- });
136
-
137
- const GatewayInstanceSchema = z.object({
138
- instanceId: z.string(),
139
- label: z.string(),
140
- baseUrl: z.string(),
141
- enabled: z.boolean(),
142
- });
143
-
144
- const GatewayInstancesResponseSchema = z.object({
145
- items: z.array(GatewayInstanceSchema),
146
- });
147
-
148
- const GatewayInstanceHealthSchema = GatewayInstanceSchema.extend({
149
- healthy: z.boolean(),
150
- status: z.number().int().optional(),
151
- reason: z.string().optional(),
152
- responseTimeMs: z.number().int().nonnegative(),
153
- checkedAt: z.string(),
154
- });
155
-
156
- const GatewayInstancesHealthResponseSchema = z.object({
157
- items: z.array(GatewayInstanceHealthSchema),
158
- partial: GatewayMetadataSchema.shape.partial,
159
- failedInstances: GatewayMetadataSchema.shape.failedInstances,
160
- });
161
-
162
- const GatewayInstanceFilterSchema = z.object({
163
- instanceId: z.string().min(1).optional(),
164
- instanceIds: z.string().min(1).optional(),
165
- });
166
-
167
- const GatewayStatsQuerySchema = ConsolePartitionQuerySchema.extend(
168
- GatewayInstanceFilterSchema.shape
169
- );
170
-
171
- const GatewayTimeseriesQuerySchema = TimeseriesQuerySchema.extend(
172
- GatewayInstanceFilterSchema.shape
173
- );
174
-
175
- const GatewayLatencyQuerySchema = LatencyQuerySchema.extend(
176
- GatewayInstanceFilterSchema.shape
177
- );
178
-
179
- const GatewaySingleInstanceQuerySchema = GatewayInstanceFilterSchema;
180
-
181
- const GatewaySingleInstancePartitionQuerySchema =
182
- ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape);
183
-
184
- type GatewayInstanceFilterQuery = {
185
- instanceId?: string;
186
- instanceIds?: string;
187
- };
188
-
189
- const GatewayApiKeyStatusSchema = z.enum(['active', 'revoked', 'expiring']);
190
-
191
- const GatewayApiKeysQuerySchema = ConsolePaginationQuerySchema.extend({
192
- ...GatewayInstanceFilterSchema.shape,
193
- type: ApiKeyTypeSchema.optional(),
194
- status: GatewayApiKeyStatusSchema.optional(),
195
- expiresWithinDays: z.coerce.number().int().min(1).max(365).optional(),
196
- });
197
-
198
- const GatewayPaginatedQuerySchema =
199
- ConsolePartitionedPaginationQuerySchema.extend(
200
- GatewayInstanceFilterSchema.shape
201
- );
202
-
203
- const GatewayTimelineQuerySchema = ConsoleTimelineQuerySchema.extend(
204
- GatewayInstanceFilterSchema.shape
205
- );
206
-
207
- const GatewayOperationsQuerySchema = ConsoleOperationsQuerySchema.extend(
208
- GatewayInstanceFilterSchema.shape
209
- );
210
-
211
- const GatewayEventsQuerySchema = ConsolePartitionedPaginationQuerySchema.extend(
212
- {
213
- ...GatewayInstanceFilterSchema.shape,
214
- eventType: z.enum(['sync', 'push', 'pull']).optional(),
215
- actorId: z.string().optional(),
216
- clientId: z.string().optional(),
217
- requestId: z.string().optional(),
218
- traceId: z.string().optional(),
219
- syncAttemptId: z.string().optional(),
220
- outcome: z.string().optional(),
221
- }
222
- );
223
-
224
- const GatewayEventPathParamSchema = z.object({
225
- id: z.string().min(1),
226
- });
227
-
228
- const GatewayCommitPathParamSchema = z.object({
229
- seq: z.string().min(1),
230
- });
231
-
232
- const GatewayClientPathParamSchema = z.object({
233
- id: z.string().min(1),
234
- });
235
-
236
- const GatewayApiKeyPathParamSchema = z.object({
237
- id: z.string().min(1),
238
- });
239
-
240
- const GatewayNotifyDataChangeRequestSchema = z.object({
241
- tables: z.array(z.string().min(1)).min(1),
242
- partitionId: z.string().optional(),
243
- });
244
-
245
- const GatewayNotifyDataChangeResponseSchema = z.object({
246
- commitSeq: z.number(),
247
- tables: z.array(z.string()),
248
- deletedChunks: z.number(),
249
- });
250
-
251
- const GatewayHandlersResponseSchema = z.object({
252
- items: z.array(ConsoleHandlerSchema),
253
- });
254
-
255
- const GatewayCommitItemSchema = ConsoleCommitListItemSchema.extend({
256
- instanceId: z.string(),
257
- federatedCommitId: z.string(),
258
- });
259
-
260
- const GatewayCommitDetailSchema = ConsoleCommitDetailSchema.extend({
261
- instanceId: z.string(),
262
- federatedCommitId: z.string(),
263
- localCommitSeq: z.number().int(),
264
- });
265
-
266
- const GatewayClientItemSchema = ConsoleClientSchema.extend({
267
- instanceId: z.string(),
268
- federatedClientId: z.string(),
269
- });
270
-
271
- const GatewayTimelineItemSchema = ConsoleTimelineItemSchema.extend({
272
- instanceId: z.string(),
273
- federatedTimelineId: z.string(),
274
- localCommitSeq: z.number().int().nullable(),
275
- localEventId: z.number().int().nullable(),
276
- });
277
-
278
- const GatewayOperationItemSchema = ConsoleOperationEventSchema.extend({
279
- instanceId: z.string(),
280
- federatedOperationId: z.string(),
281
- localOperationId: z.number().int(),
282
- });
283
-
284
- const GatewayEventItemSchema = ConsoleRequestEventSchema.extend({
285
- instanceId: z.string(),
286
- federatedEventId: z.string(),
287
- localEventId: z.number().int(),
288
- });
289
-
290
- const GatewayEventPayloadSchema = ConsoleRequestPayloadSchema.extend({
291
- instanceId: z.string(),
292
- federatedEventId: z.string(),
293
- localEventId: z.number().int(),
294
- });
295
-
296
- const GatewayStatsResponseSchema = SyncStatsSchema.extend({
297
- maxCommitSeqByInstance: z.record(z.string(), z.number().int()),
298
- minCommitSeqByInstance: z.record(z.string(), z.number().int()),
299
- partial: GatewayMetadataSchema.shape.partial,
300
- failedInstances: GatewayMetadataSchema.shape.failedInstances,
301
- });
302
-
303
- const GatewayTimeseriesResponseSchema = TimeseriesStatsResponseSchema.extend({
304
- partial: GatewayMetadataSchema.shape.partial,
305
- failedInstances: GatewayMetadataSchema.shape.failedInstances,
306
- });
307
-
308
- const GatewayLatencyResponseSchema = LatencyStatsResponseSchema.extend({
309
- partial: GatewayMetadataSchema.shape.partial,
310
- failedInstances: GatewayMetadataSchema.shape.failedInstances,
311
- });
312
-
313
- const GatewayPaginatedResponseSchema = <T extends z.ZodTypeAny>(
314
- itemSchema: T
315
- ) =>
316
- ConsolePaginatedResponseSchema(itemSchema).extend({
317
- partial: GatewayMetadataSchema.shape.partial,
318
- failedInstances: GatewayMetadataSchema.shape.failedInstances,
319
- });
320
-
321
- function toErrorMessage(error: unknown): string {
322
- if (error instanceof Error && error.message.trim().length > 0) {
323
- return error.message;
324
- }
325
- if (typeof error === 'string' && error.trim().length > 0) {
326
- return error;
327
- }
328
- return 'Request failed';
329
- }
330
-
331
- function resolveBaseUrl(baseUrl: string, requestUrl: string): URL {
332
- try {
333
- return new URL(baseUrl);
334
- } catch {
335
- return new URL(baseUrl, requestUrl);
336
- }
337
- }
338
-
339
- function normalizeInstances(
340
- instances: ConsoleGatewayInstance[]
341
- ): ConsoleGatewayInstance[] {
342
- if (instances.length === 0) {
343
- throw new Error('Console gateway requires at least one instance');
344
- }
345
-
346
- const seen = new Set<string>();
347
- return instances.map((instance) => {
348
- const normalizedInstanceId = instance.instanceId.trim();
349
- if (!normalizedInstanceId) {
350
- throw new Error('Console gateway instanceId cannot be empty');
351
- }
352
- if (seen.has(normalizedInstanceId)) {
353
- throw new Error(
354
- `Duplicate console gateway instanceId: ${normalizedInstanceId}`
355
- );
356
- }
357
- seen.add(normalizedInstanceId);
358
-
359
- const normalizedBaseUrl = instance.baseUrl.trim();
360
- if (!normalizedBaseUrl) {
361
- throw new Error(
362
- `Console gateway baseUrl cannot be empty for instance: ${normalizedInstanceId}`
363
- );
364
- }
365
-
366
- return {
367
- instanceId: normalizedInstanceId,
368
- label: instance.label?.trim() || normalizedInstanceId,
369
- baseUrl: normalizedBaseUrl,
370
- token: instance.token?.trim(),
371
- enabled: instance.enabled ?? true,
372
- };
373
- });
374
- }
375
-
376
- function parseRequestedInstanceIds(query: {
377
- instanceId?: string;
378
- instanceIds?: string;
379
- }): Set<string> {
380
- const ids = new Set<string>();
381
- const single = query.instanceId?.trim();
382
- if (single) {
383
- ids.add(single);
384
- }
385
-
386
- const multi = query.instanceIds
387
- ?.split(',')
388
- .map((value) => value.trim())
389
- .filter((value) => value.length > 0);
390
- for (const value of multi ?? []) {
391
- ids.add(value);
392
- }
393
-
394
- return ids;
395
- }
396
-
397
- function selectInstances(args: {
398
- instances: ConsoleGatewayInstance[];
399
- query: { instanceId?: string; instanceIds?: string };
400
- }): ConsoleGatewayInstance[] {
401
- const enabledInstances = args.instances.filter(
402
- (instance) => instance.enabled
403
- );
404
- const requestedIds = parseRequestedInstanceIds(args.query);
405
- if (requestedIds.size === 0) {
406
- return enabledInstances;
407
- }
408
- return enabledInstances.filter((instance) =>
409
- requestedIds.has(instance.instanceId)
410
- );
411
- }
412
-
413
- function findInstanceById(args: {
414
- instances: ConsoleGatewayInstance[];
415
- instanceId: string;
416
- }): ConsoleGatewayInstance | null {
417
- const instance = args.instances.find(
418
- (candidate) =>
419
- candidate.instanceId === args.instanceId && Boolean(candidate.enabled)
420
- );
421
- return instance ?? null;
422
- }
423
-
424
- function parseFederatedNumericId(value: string): {
425
- instanceId: string;
426
- localId: number;
427
- } | null {
428
- const separatorIndex = value.indexOf(':');
429
- if (separatorIndex <= 0 || separatorIndex >= value.length - 1) {
430
- return null;
431
- }
432
-
433
- const instanceId = value.slice(0, separatorIndex).trim();
434
- const localIdRaw = value.slice(separatorIndex + 1).trim();
435
- const localId = Number(localIdRaw);
436
- if (!instanceId || !Number.isInteger(localId) || localId <= 0) {
437
- return null;
438
- }
439
-
440
- return { instanceId, localId };
441
- }
442
-
443
- function parseLocalNumericId(value: string): number | null {
444
- const normalized = value.trim();
445
- if (!normalized) return null;
446
- const parsed = Number(normalized);
447
- if (!Number.isInteger(parsed) || parsed <= 0) return null;
448
- return parsed;
449
- }
450
-
451
- function noInstancesSelectedResponse(): {
452
- ok: false;
453
- status: 400;
454
- error: 'no_instances_selected';
455
- message: string;
456
- } {
457
- return {
458
- ok: false,
459
- status: 400,
460
- error: 'no_instances_selected',
461
- message: 'No enabled instances matched the provided instance filter.',
462
- };
463
- }
464
-
465
- function resolveSingleSelectedInstance(args: {
466
- instances: ConsoleGatewayInstance[];
467
- query: GatewayInstanceFilterQuery;
468
- onMultiple: { error: string; message: string };
469
- }):
470
- | { ok: true; instance: ConsoleGatewayInstance }
471
- | { ok: false; status: 400; error: string; message: string } {
472
- const selectedInstances = selectInstances(args);
473
- if (selectedInstances.length === 0) {
474
- return noInstancesSelectedResponse();
475
- }
476
- if (selectedInstances.length > 1) {
477
- return {
478
- ok: false,
479
- status: 400,
480
- error: args.onMultiple.error,
481
- message: args.onMultiple.message,
482
- };
483
- }
484
-
485
- const instance = selectedInstances[0];
486
- if (!instance) {
487
- return noInstancesSelectedResponse();
488
- }
489
- return { ok: true, instance };
490
- }
491
-
492
- function resolveFederatedOrLocalNumericTarget(args: {
493
- id: string;
494
- instances: ConsoleGatewayInstance[];
495
- query: GatewayInstanceFilterQuery;
496
- invalidMessage: string;
497
- ambiguousError: string;
498
- ambiguousMessage: string;
499
- }):
500
- | { ok: true; instance: ConsoleGatewayInstance; localId: number }
501
- | { ok: false; status: 400 | 404; error: string; message?: string } {
502
- const federated = parseFederatedNumericId(args.id);
503
- if (federated) {
504
- const instance = findInstanceById({
505
- instances: args.instances,
506
- instanceId: federated.instanceId,
507
- });
508
- if (!instance) {
509
- return {
510
- ok: false,
511
- status: 404,
512
- error: 'not_found',
513
- message: 'Instance not found',
514
- };
515
- }
516
- return { ok: true, instance, localId: federated.localId };
517
- }
518
-
519
- const localId = parseLocalNumericId(args.id);
520
- if (localId === null) {
521
- return {
522
- ok: false,
523
- status: 400,
524
- error: 'invalid_federated_id',
525
- message: args.invalidMessage,
526
- };
527
- }
528
-
529
- const selection = resolveSingleSelectedInstance({
530
- instances: args.instances,
531
- query: args.query,
532
- onMultiple: {
533
- error: args.ambiguousError,
534
- message: args.ambiguousMessage,
535
- },
536
- });
537
- if (!selection.ok) return selection;
538
-
539
- return { ok: true, instance: selection.instance, localId };
540
- }
541
-
542
- function resolveEventTarget(args: {
543
- id: string;
544
- instances: ConsoleGatewayInstance[];
545
- query: GatewayInstanceFilterQuery;
546
- }):
547
- | { ok: true; instance: ConsoleGatewayInstance; localEventId: number }
548
- | { ok: false; status: 400 | 404; error: string; message?: string } {
549
- const resolved = resolveFederatedOrLocalNumericTarget({
550
- id: args.id,
551
- instances: args.instances,
552
- query: args.query,
553
- invalidMessage:
554
- 'Expected either "<instanceId>:<eventId>" or "<eventId>" with an explicit instance filter.',
555
- ambiguousError: 'ambiguous_event_id',
556
- ambiguousMessage:
557
- 'Local event IDs are ambiguous across multiple instances. Use "<instanceId>:<eventId>" or select one instance.',
558
- });
559
- if (!resolved.ok) return resolved;
560
- return {
561
- ok: true,
562
- instance: resolved.instance,
563
- localEventId: resolved.localId,
564
- };
565
- }
566
-
567
- function resolveCommitTarget(args: {
568
- seq: string;
569
- instances: ConsoleGatewayInstance[];
570
- query: GatewayInstanceFilterQuery;
571
- }):
572
- | { ok: true; instance: ConsoleGatewayInstance; localCommitSeq: number }
573
- | { ok: false; status: 400 | 404; error: string; message?: string } {
574
- const resolved = resolveFederatedOrLocalNumericTarget({
575
- id: args.seq,
576
- instances: args.instances,
577
- query: args.query,
578
- invalidMessage:
579
- 'Expected either "<instanceId>:<commitSeq>" or "<commitSeq>" with an explicit instance filter.',
580
- ambiguousError: 'ambiguous_commit_id',
581
- ambiguousMessage:
582
- 'Local commit IDs are ambiguous across multiple instances. Use "<instanceId>:<commitSeq>" or select one instance.',
583
- });
584
- if (!resolved.ok) return resolved;
585
- return {
586
- ok: true,
587
- instance: resolved.instance,
588
- localCommitSeq: resolved.localId,
589
- };
590
- }
591
-
592
- function resolveSingleInstanceTarget(args: {
593
- instances: ConsoleGatewayInstance[];
594
- query: GatewayInstanceFilterQuery;
595
- }):
596
- | { ok: true; instance: ConsoleGatewayInstance }
597
- | { ok: false; status: 400; error: string; message: string } {
598
- return resolveSingleSelectedInstance({
599
- ...args,
600
- onMultiple: {
601
- error: 'instance_required',
602
- message:
603
- 'This endpoint requires exactly one target instance. Provide `instanceId` or a single-value `instanceIds` filter.',
604
- },
605
- });
606
- }
607
-
608
- function minNullable(values: Array<number | null>): number | null {
609
- const filtered = values.filter((value): value is number => value !== null);
610
- if (filtered.length === 0) return null;
611
- return Math.min(...filtered);
612
- }
613
-
614
- function maxNullable(values: Array<number | null>): number | null {
615
- const filtered = values.filter((value): value is number => value !== null);
616
- if (filtered.length === 0) return null;
617
- return Math.max(...filtered);
618
- }
619
-
620
- function compareIsoDesc(a: string, b: string): number {
621
- const aMs = Date.parse(a);
622
- const bMs = Date.parse(b);
623
- if (!Number.isFinite(aMs) && !Number.isFinite(bMs)) return 0;
624
- if (!Number.isFinite(aMs)) return 1;
625
- if (!Number.isFinite(bMs)) return -1;
626
- return bMs - aMs;
627
- }
628
-
629
- interface TimeseriesBucketAccumulator {
630
- pushCount: number;
631
- pullCount: number;
632
- errorCount: number;
633
- latencySum: number;
634
- eventCount: number;
635
- }
636
-
637
- function createTimeseriesBucketAccumulator(): TimeseriesBucketAccumulator {
638
- return {
639
- pushCount: 0,
640
- pullCount: 0,
641
- errorCount: 0,
642
- latencySum: 0,
643
- eventCount: 0,
644
- };
645
- }
646
-
647
- function mergeTimeseriesBuckets(
648
- responses: TimeseriesStatsResponse[]
649
- ): TimeseriesBucket[] {
650
- const bucketMap = new Map<string, TimeseriesBucketAccumulator>();
651
-
652
- for (const response of responses) {
653
- for (const bucket of response.buckets) {
654
- const existing =
655
- bucketMap.get(bucket.timestamp) ?? createTimeseriesBucketAccumulator();
656
- existing.pushCount += bucket.pushCount;
657
- existing.pullCount += bucket.pullCount;
658
- existing.errorCount += bucket.errorCount;
659
-
660
- const bucketEventCount = bucket.pushCount + bucket.pullCount;
661
- if (bucketEventCount > 0) {
662
- existing.latencySum += bucket.avgLatencyMs * bucketEventCount;
663
- existing.eventCount += bucketEventCount;
664
- }
665
-
666
- bucketMap.set(bucket.timestamp, existing);
667
- }
668
- }
669
-
670
- return Array.from(bucketMap.entries())
671
- .sort(([a], [b]) => a.localeCompare(b))
672
- .map(([timestamp, bucket]) => ({
673
- timestamp,
674
- pushCount: bucket.pushCount,
675
- pullCount: bucket.pullCount,
676
- errorCount: bucket.errorCount,
677
- avgLatencyMs:
678
- bucket.eventCount > 0 ? bucket.latencySum / bucket.eventCount : 0,
679
- }));
680
- }
681
-
682
- function averagePercentiles(values: LatencyPercentiles[]): LatencyPercentiles {
683
- if (values.length === 0) {
684
- return { p50: 0, p90: 0, p99: 0 };
685
- }
686
-
687
- return {
688
- p50: values.reduce((acc, value) => acc + value.p50, 0) / values.length,
689
- p90: values.reduce((acc, value) => acc + value.p90, 0) / values.length,
690
- p99: values.reduce((acc, value) => acc + value.p99, 0) / values.length,
691
- };
692
- }
693
-
694
- function sanitizeForwardQueryParams(query: URLSearchParams): URLSearchParams {
695
- const sanitized = new URLSearchParams(query);
696
- sanitized.delete('instanceId');
697
- sanitized.delete('instanceIds');
698
- return sanitized;
699
- }
700
-
701
- function withPaging(
702
- params: URLSearchParams,
703
- paging: { limit: number; offset: number }
704
- ): URLSearchParams {
705
- const next = new URLSearchParams(params);
706
- next.set('limit', String(paging.limit));
707
- next.set('offset', String(paging.offset));
708
- return next;
709
- }
710
-
711
- function buildConsoleEndpointUrl(args: {
712
- instance: ConsoleGatewayInstance;
713
- requestUrl: string;
714
- path: string;
715
- query?: URLSearchParams;
716
- }): string {
717
- const baseUrl = resolveBaseUrl(args.instance.baseUrl, args.requestUrl);
718
- const basePath = baseUrl.pathname.endsWith('/')
719
- ? baseUrl.pathname.slice(0, -1)
720
- : baseUrl.pathname;
721
- const suffix = args.path.startsWith('/') ? args.path : `/${args.path}`;
722
- baseUrl.pathname = `${basePath}/console${suffix}`;
723
- baseUrl.search = args.query?.toString() ?? '';
724
- return baseUrl.toString();
725
- }
726
-
727
- function resolveForwardAuthorization(args: {
728
- c: Context;
729
- instance: ConsoleGatewayInstance;
730
- }): string | null {
731
- if (args.instance.token) {
732
- return `Bearer ${args.instance.token}`;
733
- }
734
- const header = args.c.req.header('Authorization')?.trim();
735
- if (header) {
736
- return header;
737
- }
738
- return null;
739
- }
740
-
741
- async function fetchDownstreamJson<T>(args: {
742
- c: Context;
743
- instance: ConsoleGatewayInstance;
744
- path: string;
745
- query?: URLSearchParams;
746
- schema: z.ZodType<T>;
747
- fetchImpl: typeof fetch;
748
- }): Promise<{ ok: true; data: T } | { ok: false; failure: GatewayFailure }> {
749
- const url = buildConsoleEndpointUrl({
750
- instance: args.instance,
751
- requestUrl: args.c.req.url,
752
- path: args.path,
753
- query: args.query,
754
- });
755
-
756
- const headers = new Headers();
757
- headers.set('Accept', 'application/json');
758
- const authorization = resolveForwardAuthorization({
759
- c: args.c,
760
- instance: args.instance,
761
- });
762
- if (authorization) {
763
- headers.set('Authorization', authorization);
764
- }
765
-
766
- try {
767
- const response = await args.fetchImpl(url, {
768
- method: 'GET',
769
- headers,
770
- });
771
-
772
- if (!response.ok) {
773
- return {
774
- ok: false,
775
- failure: {
776
- instanceId: args.instance.instanceId,
777
- reason: `HTTP ${response.status}`,
778
- status: response.status,
779
- },
780
- };
781
- }
782
-
783
- const payload = await response.json();
784
- const parsed = args.schema.safeParse(payload);
785
- if (!parsed.success) {
786
- return {
787
- ok: false,
788
- failure: {
789
- instanceId: args.instance.instanceId,
790
- reason: 'Invalid response payload',
791
- },
792
- };
793
- }
794
-
795
- return { ok: true, data: parsed.data };
796
- } catch (error) {
797
- return {
798
- ok: false,
799
- failure: {
800
- instanceId: args.instance.instanceId,
801
- reason: toErrorMessage(error),
802
- },
803
- };
804
- }
805
- }
806
-
807
- async function parseDownstreamBody(response: Response): Promise<unknown> {
808
- const text = await response.text();
809
- if (!text.trim()) {
810
- return null;
811
- }
812
- try {
813
- return JSON.parse(text) as unknown;
814
- } catch {
815
- return text;
816
- }
817
- }
818
-
819
- function normalizeDownstreamError(args: {
820
- body: unknown;
821
- status: number;
822
- instanceId: string;
823
- }): Record<string, unknown> {
824
- if (args.body && typeof args.body === 'object' && !Array.isArray(args.body)) {
825
- return {
826
- ...(args.body as Record<string, unknown>),
827
- instanceId: args.instanceId,
828
- };
829
- }
830
-
831
- if (typeof args.body === 'string' && args.body.trim().length > 0) {
832
- return {
833
- error: 'downstream_error',
834
- message: args.body,
835
- instanceId: args.instanceId,
836
- };
837
- }
838
-
839
- return {
840
- error: 'downstream_error',
841
- status: args.status,
842
- instanceId: args.instanceId,
843
- };
844
- }
845
-
846
- async function forwardDownstreamJsonRequest<T>(args: {
847
- c: Context;
848
- instance: ConsoleGatewayInstance;
849
- method: 'GET' | 'POST' | 'DELETE';
850
- path: string;
851
- query?: URLSearchParams;
852
- body?: unknown;
853
- responseSchema: z.ZodType<T>;
854
- fetchImpl: typeof fetch;
855
- }): Promise<
856
- | { ok: true; data: T; status: number }
857
- | { ok: false; status: number; body: Record<string, unknown> }
858
- > {
859
- const url = buildConsoleEndpointUrl({
860
- instance: args.instance,
861
- requestUrl: args.c.req.url,
862
- path: args.path,
863
- query: args.query,
864
- });
865
-
866
- const headers = new Headers();
867
- headers.set('Accept', 'application/json');
868
- const authorization = resolveForwardAuthorization({
869
- c: args.c,
870
- instance: args.instance,
871
- });
872
- if (authorization) {
873
- headers.set('Authorization', authorization);
874
- }
875
-
876
- let requestBody: string | undefined;
877
- if (args.body !== undefined) {
878
- headers.set('Content-Type', 'application/json');
879
- requestBody = JSON.stringify(args.body);
880
- }
881
-
882
- try {
883
- const response = await args.fetchImpl(url, {
884
- method: args.method,
885
- headers,
886
- ...(requestBody !== undefined ? { body: requestBody } : {}),
887
- });
888
-
889
- const payload = await parseDownstreamBody(response);
890
- if (!response.ok) {
891
- return {
892
- ok: false,
893
- status: response.status,
894
- body: createSyncularErrorResponse(
895
- response.status === 404
896
- ? 'console.not_found'
897
- : 'console.downstream_unavailable',
898
- {
899
- details: {
900
- downstream: normalizeDownstreamError({
901
- body: payload,
902
- status: response.status,
903
- instanceId: args.instance.instanceId,
904
- }),
905
- },
906
- }
907
- ),
908
- };
909
- }
910
-
911
- const parsed = args.responseSchema.safeParse(payload);
912
- if (!parsed.success) {
913
- return {
914
- ok: false,
915
- status: 502,
916
- body: createSyncularErrorResponse(
917
- 'console.downstream_invalid_response',
918
- {
919
- message: 'Downstream response failed validation.',
920
- details: { instanceId: args.instance.instanceId },
921
- }
922
- ),
923
- };
924
- }
925
-
926
- return {
927
- ok: true,
928
- data: parsed.data,
929
- status: response.status,
930
- };
931
- } catch (error) {
932
- return {
933
- ok: false,
934
- status: 502,
935
- body: createSyncularErrorResponse('console.downstream_unavailable', {
936
- message: toErrorMessage(error),
937
- details: { instanceId: args.instance.instanceId },
938
- }),
939
- };
940
- }
941
- }
942
-
943
- async function fetchDownstreamPaged<T>(args: {
944
- c: Context;
945
- instance: ConsoleGatewayInstance;
946
- path: string;
947
- query: URLSearchParams;
948
- targetCount: number;
949
- schema: z.ZodType<ConsolePaginatedResponse<T>>;
950
- fetchImpl: typeof fetch;
951
- }): Promise<
952
- | { ok: true; items: T[]; total: number }
953
- | { ok: false; failure: GatewayFailure }
954
- > {
955
- const items: T[] = [];
956
- let total: number | null = null;
957
- let localOffset = 0;
958
- let pageCount = 0;
959
-
960
- while (
961
- items.length < args.targetCount &&
962
- (total === null || localOffset < total) &&
963
- pageCount < 100
964
- ) {
965
- const limit = Math.min(100, Math.max(1, args.targetCount - items.length));
966
- const pagedQuery = withPaging(args.query, { limit, offset: localOffset });
967
- const result = await fetchDownstreamJson({
968
- c: args.c,
969
- instance: args.instance,
970
- path: args.path,
971
- query: pagedQuery,
972
- schema: args.schema,
973
- fetchImpl: args.fetchImpl,
974
- });
975
-
976
- if (!result.ok) {
977
- return result;
978
- }
979
-
980
- const page = result.data;
981
- total = page.total;
982
- items.push(...page.items);
983
- localOffset += page.items.length;
984
- pageCount += 1;
985
-
986
- if (page.items.length === 0) {
987
- break;
988
- }
989
- }
990
-
991
- return {
992
- ok: true,
993
- items,
994
- total: total ?? items.length,
995
- };
996
- }
997
-
998
- async function checkDownstreamInstanceHealth(args: {
999
- c: Context;
1000
- instance: ConsoleGatewayInstance;
1001
- fetchImpl: typeof fetch;
1002
- }): Promise<z.infer<typeof GatewayInstanceHealthSchema>> {
1003
- const startedAt = Date.now();
1004
- const result = await fetchDownstreamJson({
1005
- c: args.c,
1006
- instance: args.instance,
1007
- path: '/stats',
1008
- schema: SyncStatsSchema,
1009
- fetchImpl: args.fetchImpl,
1010
- });
1011
-
1012
- const responseTimeMs = Math.max(0, Date.now() - startedAt);
1013
- const checkedAt = new Date().toISOString();
1014
- const base = {
1015
- instanceId: args.instance.instanceId,
1016
- label: args.instance.label ?? args.instance.instanceId,
1017
- baseUrl: args.instance.baseUrl,
1018
- enabled: args.instance.enabled ?? true,
1019
- responseTimeMs,
1020
- checkedAt,
1021
- };
1022
-
1023
- if (result.ok) {
1024
- return {
1025
- ...base,
1026
- healthy: true,
1027
- status: 200,
1028
- };
1029
- }
1030
-
1031
- return {
1032
- ...base,
1033
- healthy: false,
1034
- status: result.failure.status,
1035
- reason: result.failure.reason,
1036
- };
1037
- }
1038
-
1039
- function unauthorizedResponse(c: Context): Response {
1040
- return consoleGatewayError(c, 401, 'console.auth_required');
1041
- }
1042
-
1043
- function jsonResponse(payload: unknown, status: number): Response {
1044
- return new Response(JSON.stringify(payload), {
1045
- status,
1046
- headers: {
1047
- 'content-type': 'application/json; charset=utf-8',
1048
- },
1049
- });
1050
- }
1051
-
1052
- function allInstancesFailedResponse(
1053
- c: Context,
1054
- failedInstances: GatewayFailure[]
1055
- ): Response {
1056
- return consoleGatewayError(c, 502, 'console.downstream_unavailable', {
1057
- details: { failedInstances },
1058
- });
1059
- }
1060
-
1061
- function consoleTargetErrorResponse(
1062
- c: Context,
1063
- target: { status: 400 | 404; error: string; message?: string }
1064
- ): Response {
1065
- return consoleGatewayError(
1066
- c,
1067
- target.status,
1068
- target.status === 404 ? 'console.not_found' : 'console.invalid_request',
1069
- {
1070
- ...(target.message ? { message: target.message } : {}),
1071
- details: { consoleError: target.error },
1072
- }
1073
- );
1074
- }
1075
-
1076
- function downstreamFailureResponse(
1077
- c: Context,
1078
- failure: GatewayFailure
1079
- ): Response {
1080
- if (failure.status === 404) {
1081
- return consoleGatewayError(c, 404, 'console.not_found', {
1082
- details: { failure },
1083
- });
1084
- }
1085
- return consoleGatewayError(c, 502, 'console.downstream_unavailable', {
1086
- details: { failedInstances: [failure] },
1087
- });
1088
- }
1089
-
1090
- export function createConsoleGatewayRoutes(
1091
- options: CreateConsoleGatewayRoutesOptions
1092
- ): Hono {
1093
- const routes = new Hono();
1094
- const instances = normalizeInstances(options.instances);
1095
- const fetchImpl = options.fetchImpl ?? fetch;
1096
- const corsOrigins = options.corsOrigins ?? '*';
1097
-
1098
- routes.use(
1099
- '*',
1100
- cors({
1101
- origin: corsOrigins === '*' ? '*' : corsOrigins,
1102
- allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
1103
- allowHeaders: [
1104
- 'Content-Type',
1105
- 'Authorization',
1106
- 'X-Syncular-Transport-Path',
1107
- 'Baggage',
1108
- 'Sentry-Trace',
1109
- 'Traceparent',
1110
- 'Tracestate',
1111
- ],
1112
- credentials: true,
1113
- })
1114
- );
1115
-
1116
- const withGatewayAuth = async (
1117
- c: Context,
1118
- callback: () => Promise<Response>
1119
- ): Promise<Response> => {
1120
- const auth = await options.authenticate(c);
1121
- if (!auth) {
1122
- return unauthorizedResponse(c);
1123
- }
1124
- return callback();
1125
- };
1126
-
1127
- const proxySingleInstanceJsonRequest = async <T>(args: {
1128
- c: Context;
1129
- query: { instanceId?: string; instanceIds?: string };
1130
- method: 'GET' | 'POST' | 'DELETE';
1131
- path: string;
1132
- responseSchema: z.ZodType<T>;
1133
- body?: unknown;
1134
- }): Promise<Response> => {
1135
- const target = resolveSingleInstanceTarget({
1136
- instances,
1137
- query: args.query,
1138
- });
1139
- if (!target.ok) {
1140
- return consoleTargetErrorResponse(args.c, target);
1141
- }
1142
-
1143
- const forwardQuery = sanitizeForwardQueryParams(
1144
- new URL(args.c.req.url).searchParams
1145
- );
1146
- const result = await forwardDownstreamJsonRequest<T>({
1147
- c: args.c,
1148
- instance: target.instance,
1149
- method: args.method,
1150
- path: args.path,
1151
- query: forwardQuery,
1152
- ...(args.body === undefined ? {} : { body: args.body }),
1153
- responseSchema: args.responseSchema,
1154
- fetchImpl,
1155
- });
1156
-
1157
- if (!result.ok) {
1158
- return jsonResponse(result.body, result.status);
1159
- }
1160
-
1161
- return jsonResponse(result.data, result.status);
1162
- };
1163
-
1164
- const selectTargetInstances = (
1165
- c: Context,
1166
- query: GatewayInstanceFilterQuery
1167
- ):
1168
- | { ok: true; selectedInstances: ConsoleGatewayInstance[] }
1169
- | { ok: false; response: Response } => {
1170
- const selectedInstances = selectInstances({ instances, query });
1171
- if (selectedInstances.length > 0) {
1172
- return { ok: true, selectedInstances };
1173
- }
1174
-
1175
- const noInstanceError = noInstancesSelectedResponse();
1176
- return {
1177
- ok: false,
1178
- response: consoleTargetErrorResponse(c, noInstanceError),
1179
- };
1180
- };
1181
-
1182
- const fetchFromSelectedInstances = async <T>(args: {
1183
- c: Context;
1184
- selectedInstances: ConsoleGatewayInstance[];
1185
- path: string;
1186
- query: URLSearchParams;
1187
- schema: z.ZodType<T>;
1188
- }): Promise<
1189
- | {
1190
- ok: true;
1191
- successfulResults: Array<{
1192
- instance: ConsoleGatewayInstance;
1193
- data: T;
1194
- }>;
1195
- failedInstances: GatewayFailure[];
1196
- }
1197
- | { ok: false; response: Response }
1198
- > => {
1199
- const results = await Promise.all(
1200
- args.selectedInstances.map((instance) =>
1201
- fetchDownstreamJson({
1202
- c: args.c,
1203
- instance,
1204
- path: args.path,
1205
- query: args.query,
1206
- schema: args.schema,
1207
- fetchImpl,
1208
- })
1209
- )
1210
- );
1211
-
1212
- const failedInstances = results
1213
- .filter(
1214
- (result): result is { ok: false; failure: GatewayFailure } => !result.ok
1215
- )
1216
- .map((result) => result.failure);
1217
- const successfulResults = results
1218
- .map((result, index) => ({
1219
- result,
1220
- instance: args.selectedInstances[index],
1221
- }))
1222
- .filter(
1223
- (
1224
- entry
1225
- ): entry is {
1226
- result: { ok: true; data: T };
1227
- instance: ConsoleGatewayInstance;
1228
- } => Boolean(entry.instance) && entry.result.ok
1229
- )
1230
- .map((entry) => ({
1231
- instance: entry.instance,
1232
- data: entry.result.data,
1233
- }));
1234
-
1235
- if (successfulResults.length === 0) {
1236
- return {
1237
- ok: false,
1238
- response: allInstancesFailedResponse(args.c, failedInstances),
1239
- };
1240
- }
1241
-
1242
- return {
1243
- ok: true,
1244
- successfulResults,
1245
- failedInstances,
1246
- };
1247
- };
1248
-
1249
- const fetchPagedFromSelectedInstances = async <T>(args: {
1250
- c: Context;
1251
- selectedInstances: ConsoleGatewayInstance[];
1252
- path: string;
1253
- query: URLSearchParams;
1254
- targetCount: number;
1255
- schema: z.ZodType<ConsolePaginatedResponse<T>>;
1256
- }): Promise<
1257
- | {
1258
- ok: true;
1259
- successfulResults: Array<{
1260
- instance: ConsoleGatewayInstance;
1261
- items: T[];
1262
- total: number;
1263
- }>;
1264
- failedInstances: GatewayFailure[];
1265
- }
1266
- | { ok: false; response: Response }
1267
- > => {
1268
- const results = await Promise.all(
1269
- args.selectedInstances.map((instance) =>
1270
- fetchDownstreamPaged({
1271
- c: args.c,
1272
- instance,
1273
- path: args.path,
1274
- query: args.query,
1275
- targetCount: args.targetCount,
1276
- schema: args.schema,
1277
- fetchImpl,
1278
- })
1279
- )
1280
- );
1281
-
1282
- const failedInstances = results
1283
- .filter(
1284
- (result): result is { ok: false; failure: GatewayFailure } => !result.ok
1285
- )
1286
- .map((result) => result.failure);
1287
- const successfulResults = results
1288
- .map((result, index) => ({
1289
- result,
1290
- instance: args.selectedInstances[index],
1291
- }))
1292
- .filter(
1293
- (
1294
- entry
1295
- ): entry is {
1296
- result: { ok: true; items: T[]; total: number };
1297
- instance: ConsoleGatewayInstance;
1298
- } => Boolean(entry.instance) && entry.result.ok
1299
- )
1300
- .map((entry) => ({
1301
- instance: entry.instance,
1302
- items: entry.result.items,
1303
- total: entry.result.total,
1304
- }));
1305
-
1306
- if (successfulResults.length === 0) {
1307
- return {
1308
- ok: false,
1309
- response: allInstancesFailedResponse(args.c, failedInstances),
1310
- };
1311
- }
1312
-
1313
- return {
1314
- ok: true,
1315
- successfulResults,
1316
- failedInstances,
1317
- };
1318
- };
1319
-
1320
- routes.get(
1321
- '/instances',
1322
- describeConsoleGatewayRoute({
1323
- summary: 'List configured downstream console instances',
1324
- responses: {
1325
- 200: {
1326
- description: 'Configured instances',
1327
- content: {
1328
- 'application/json': {
1329
- schema: resolver(GatewayInstancesResponseSchema),
1330
- },
1331
- },
1332
- },
1333
- 401: {
1334
- description: 'Unauthenticated',
1335
- content: {
1336
- 'application/json': {
1337
- schema: resolver(z.object({ error: z.string() })),
1338
- },
1339
- },
1340
- },
1341
- },
1342
- }),
1343
- async (c) => {
1344
- return withGatewayAuth(c, async () => {
1345
- return c.json({
1346
- items: instances.map((instance) => ({
1347
- instanceId: instance.instanceId,
1348
- label: instance.label ?? instance.instanceId,
1349
- baseUrl: instance.baseUrl,
1350
- enabled: instance.enabled ?? true,
1351
- })),
1352
- });
1353
- });
1354
- }
1355
- );
1356
-
1357
- routes.get(
1358
- '/instances/health',
1359
- describeConsoleGatewayRoute({
1360
- summary: 'Probe downstream console health by instance',
1361
- responses: {
1362
- 200: {
1363
- description: 'Per-instance health results',
1364
- content: {
1365
- 'application/json': {
1366
- schema: resolver(GatewayInstancesHealthResponseSchema),
1367
- },
1368
- },
1369
- },
1370
- 401: {
1371
- description: 'Unauthenticated',
1372
- content: {
1373
- 'application/json': {
1374
- schema: resolver(z.object({ error: z.string() })),
1375
- },
1376
- },
1377
- },
1378
- },
1379
- }),
1380
- zValidator('query', GatewayInstanceFilterSchema),
1381
- async (c) => {
1382
- return withGatewayAuth(c, async () => {
1383
- const query = c.req.valid('query');
1384
- const selection = selectTargetInstances(c, query);
1385
- if (!selection.ok) {
1386
- return selection.response;
1387
- }
1388
-
1389
- const items = await Promise.all(
1390
- selection.selectedInstances.map((instance) =>
1391
- checkDownstreamInstanceHealth({
1392
- c,
1393
- instance,
1394
- fetchImpl,
1395
- })
1396
- )
1397
- );
1398
-
1399
- const failedInstances = items
1400
- .filter((item) => !item.healthy)
1401
- .map((item) => ({
1402
- instanceId: item.instanceId,
1403
- reason: item.reason ?? 'Health probe failed',
1404
- ...(item.status !== undefined ? { status: item.status } : {}),
1405
- }));
1406
-
1407
- return c.json({
1408
- items,
1409
- partial: failedInstances.length > 0,
1410
- failedInstances,
1411
- });
1412
- });
1413
- }
1414
- );
1415
-
1416
- routes.get(
1417
- '/handlers',
1418
- describeConsoleGatewayRoute({
1419
- summary:
1420
- 'List handlers for a single target instance (requires instance selection)',
1421
- responses: {
1422
- 200: {
1423
- description: 'Handlers',
1424
- content: {
1425
- 'application/json': {
1426
- schema: resolver(GatewayHandlersResponseSchema),
1427
- },
1428
- },
1429
- },
1430
- },
1431
- }),
1432
- zValidator('query', GatewaySingleInstanceQuerySchema),
1433
- async (c) => {
1434
- return withGatewayAuth(c, async () => {
1435
- const query = c.req.valid('query');
1436
- return proxySingleInstanceJsonRequest({
1437
- c,
1438
- query,
1439
- method: 'GET',
1440
- path: '/handlers',
1441
- responseSchema: GatewayHandlersResponseSchema,
1442
- });
1443
- });
1444
- }
1445
- );
1446
-
1447
- routes.post(
1448
- '/prune/preview',
1449
- describeConsoleGatewayRoute({
1450
- summary:
1451
- 'Preview prune on a single target instance (requires instance selection)',
1452
- responses: {
1453
- 200: {
1454
- description: 'Prune preview',
1455
- content: {
1456
- 'application/json': {
1457
- schema: resolver(ConsolePrunePreviewSchema),
1458
- },
1459
- },
1460
- },
1461
- },
1462
- }),
1463
- zValidator('query', GatewaySingleInstanceQuerySchema),
1464
- async (c) => {
1465
- return withGatewayAuth(c, async () => {
1466
- const query = c.req.valid('query');
1467
- return proxySingleInstanceJsonRequest({
1468
- c,
1469
- query,
1470
- method: 'POST',
1471
- path: '/prune/preview',
1472
- responseSchema: ConsolePrunePreviewSchema,
1473
- });
1474
- });
1475
- }
1476
- );
1477
-
1478
- routes.post(
1479
- '/prune',
1480
- describeConsoleGatewayRoute({
1481
- summary:
1482
- 'Trigger prune on a single target instance (requires instance selection)',
1483
- responses: {
1484
- 200: {
1485
- description: 'Prune result',
1486
- content: {
1487
- 'application/json': {
1488
- schema: resolver(ConsolePruneResultSchema),
1489
- },
1490
- },
1491
- },
1492
- },
1493
- }),
1494
- zValidator('query', GatewaySingleInstanceQuerySchema),
1495
- async (c) => {
1496
- return withGatewayAuth(c, async () => {
1497
- const query = c.req.valid('query');
1498
- return proxySingleInstanceJsonRequest({
1499
- c,
1500
- query,
1501
- method: 'POST',
1502
- path: '/prune',
1503
- responseSchema: ConsolePruneResultSchema,
1504
- });
1505
- });
1506
- }
1507
- );
1508
-
1509
- routes.post(
1510
- '/compact',
1511
- describeConsoleGatewayRoute({
1512
- summary:
1513
- 'Trigger compaction on a single target instance (requires instance selection)',
1514
- responses: {
1515
- 200: {
1516
- description: 'Compaction result',
1517
- content: {
1518
- 'application/json': {
1519
- schema: resolver(ConsoleCompactResultSchema),
1520
- },
1521
- },
1522
- },
1523
- },
1524
- }),
1525
- zValidator('query', GatewaySingleInstanceQuerySchema),
1526
- async (c) => {
1527
- return withGatewayAuth(c, async () => {
1528
- const query = c.req.valid('query');
1529
- return proxySingleInstanceJsonRequest({
1530
- c,
1531
- query,
1532
- method: 'POST',
1533
- path: '/compact',
1534
- responseSchema: ConsoleCompactResultSchema,
1535
- });
1536
- });
1537
- }
1538
- );
1539
-
1540
- routes.post(
1541
- '/notify-data-change',
1542
- describeConsoleGatewayRoute({
1543
- summary:
1544
- 'Notify data change on a single target instance (requires instance selection)',
1545
- responses: {
1546
- 200: {
1547
- description: 'Notification result',
1548
- content: {
1549
- 'application/json': {
1550
- schema: resolver(GatewayNotifyDataChangeResponseSchema),
1551
- },
1552
- },
1553
- },
1554
- },
1555
- }),
1556
- zValidator('query', GatewaySingleInstanceQuerySchema),
1557
- zValidator('json', GatewayNotifyDataChangeRequestSchema),
1558
- async (c) => {
1559
- return withGatewayAuth(c, async () => {
1560
- const query = c.req.valid('query');
1561
- const body = c.req.valid('json');
1562
- return proxySingleInstanceJsonRequest({
1563
- c,
1564
- query,
1565
- method: 'POST',
1566
- path: '/notify-data-change',
1567
- body,
1568
- responseSchema: GatewayNotifyDataChangeResponseSchema,
1569
- });
1570
- });
1571
- }
1572
- );
1573
-
1574
- routes.delete(
1575
- '/clients/:id',
1576
- describeConsoleGatewayRoute({
1577
- summary:
1578
- 'Evict client on a single target instance (requires instance selection)',
1579
- responses: {
1580
- 200: {
1581
- description: 'Evict result',
1582
- content: {
1583
- 'application/json': {
1584
- schema: resolver(ConsoleEvictResultSchema),
1585
- },
1586
- },
1587
- },
1588
- },
1589
- }),
1590
- zValidator('param', GatewayClientPathParamSchema),
1591
- zValidator('query', GatewaySingleInstancePartitionQuerySchema),
1592
- async (c) => {
1593
- return withGatewayAuth(c, async () => {
1594
- const { id } = c.req.valid('param');
1595
- const query = c.req.valid('query');
1596
- return proxySingleInstanceJsonRequest({
1597
- c,
1598
- query,
1599
- method: 'DELETE',
1600
- path: `/clients/${encodeURIComponent(id)}`,
1601
- responseSchema: ConsoleEvictResultSchema,
1602
- });
1603
- });
1604
- }
1605
- );
1606
-
1607
- routes.delete(
1608
- '/events',
1609
- describeConsoleGatewayRoute({
1610
- summary:
1611
- 'Clear request events on a single target instance (requires instance selection)',
1612
- responses: {
1613
- 200: {
1614
- description: 'Clear result',
1615
- content: {
1616
- 'application/json': {
1617
- schema: resolver(ConsoleClearEventsResultSchema),
1618
- },
1619
- },
1620
- },
1621
- },
1622
- }),
1623
- zValidator('query', GatewaySingleInstanceQuerySchema),
1624
- async (c) => {
1625
- return withGatewayAuth(c, async () => {
1626
- const query = c.req.valid('query');
1627
- return proxySingleInstanceJsonRequest({
1628
- c,
1629
- query,
1630
- method: 'DELETE',
1631
- path: '/events',
1632
- responseSchema: ConsoleClearEventsResultSchema,
1633
- });
1634
- });
1635
- }
1636
- );
1637
-
1638
- routes.post(
1639
- '/events/prune',
1640
- describeConsoleGatewayRoute({
1641
- summary:
1642
- 'Prune request events on a single target instance (requires instance selection)',
1643
- responses: {
1644
- 200: {
1645
- description: 'Prune events result',
1646
- content: {
1647
- 'application/json': {
1648
- schema: resolver(ConsolePruneEventsResultSchema),
1649
- },
1650
- },
1651
- },
1652
- },
1653
- }),
1654
- zValidator('query', GatewaySingleInstanceQuerySchema),
1655
- async (c) => {
1656
- return withGatewayAuth(c, async () => {
1657
- const query = c.req.valid('query');
1658
- return proxySingleInstanceJsonRequest({
1659
- c,
1660
- query,
1661
- method: 'POST',
1662
- path: '/events/prune',
1663
- responseSchema: ConsolePruneEventsResultSchema,
1664
- });
1665
- });
1666
- }
1667
- );
1668
-
1669
- routes.get(
1670
- '/api-keys',
1671
- describeConsoleGatewayRoute({
1672
- summary:
1673
- 'List API keys for a single target instance (requires instance selection)',
1674
- responses: {
1675
- 200: {
1676
- description: 'Paginated API key list',
1677
- content: {
1678
- 'application/json': {
1679
- schema: resolver(
1680
- ConsolePaginatedResponseSchema(ConsoleApiKeySchema)
1681
- ),
1682
- },
1683
- },
1684
- },
1685
- },
1686
- }),
1687
- zValidator('query', GatewayApiKeysQuerySchema),
1688
- async (c) => {
1689
- return withGatewayAuth(c, async () => {
1690
- const query = c.req.valid('query');
1691
- return proxySingleInstanceJsonRequest<
1692
- ConsolePaginatedResponse<ConsoleApiKey>
1693
- >({
1694
- c,
1695
- query,
1696
- method: 'GET',
1697
- path: '/api-keys',
1698
- responseSchema: ConsolePaginatedResponseSchema(ConsoleApiKeySchema),
1699
- });
1700
- });
1701
- }
1702
- );
1703
-
1704
- routes.post(
1705
- '/api-keys',
1706
- describeConsoleGatewayRoute({
1707
- summary:
1708
- 'Create API key on a single target instance (requires instance selection)',
1709
- responses: {
1710
- 201: {
1711
- description: 'Created API key',
1712
- content: {
1713
- 'application/json': {
1714
- schema: resolver(ConsoleApiKeyCreateResponseSchema),
1715
- },
1716
- },
1717
- },
1718
- },
1719
- }),
1720
- zValidator('query', GatewaySingleInstanceQuerySchema),
1721
- zValidator('json', ConsoleApiKeyCreateRequestSchema),
1722
- async (c) => {
1723
- return withGatewayAuth(c, async () => {
1724
- const query = c.req.valid('query');
1725
- const body = c.req.valid('json');
1726
- return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
1727
- c,
1728
- query,
1729
- method: 'POST',
1730
- path: '/api-keys',
1731
- body,
1732
- responseSchema: ConsoleApiKeyCreateResponseSchema,
1733
- });
1734
- });
1735
- }
1736
- );
1737
-
1738
- routes.get(
1739
- '/api-keys/:id',
1740
- describeConsoleGatewayRoute({
1741
- summary:
1742
- 'Get API key from a single target instance (requires instance selection)',
1743
- responses: {
1744
- 200: {
1745
- description: 'API key details',
1746
- content: {
1747
- 'application/json': {
1748
- schema: resolver(ConsoleApiKeySchema),
1749
- },
1750
- },
1751
- },
1752
- },
1753
- }),
1754
- zValidator('param', GatewayApiKeyPathParamSchema),
1755
- zValidator('query', GatewaySingleInstanceQuerySchema),
1756
- async (c) => {
1757
- return withGatewayAuth(c, async () => {
1758
- const { id } = c.req.valid('param');
1759
- const query = c.req.valid('query');
1760
- return proxySingleInstanceJsonRequest<ConsoleApiKey>({
1761
- c,
1762
- query,
1763
- method: 'GET',
1764
- path: `/api-keys/${encodeURIComponent(id)}`,
1765
- responseSchema: ConsoleApiKeySchema,
1766
- });
1767
- });
1768
- }
1769
- );
1770
-
1771
- routes.delete(
1772
- '/api-keys/:id',
1773
- describeConsoleGatewayRoute({
1774
- summary:
1775
- 'Revoke API key on a single target instance (requires instance selection)',
1776
- responses: {
1777
- 200: {
1778
- description: 'Revoke result',
1779
- content: {
1780
- 'application/json': {
1781
- schema: resolver(ConsoleApiKeyRevokeResponseSchema),
1782
- },
1783
- },
1784
- },
1785
- },
1786
- }),
1787
- zValidator('param', GatewayApiKeyPathParamSchema),
1788
- zValidator('query', GatewaySingleInstanceQuerySchema),
1789
- async (c) => {
1790
- return withGatewayAuth(c, async () => {
1791
- const { id } = c.req.valid('param');
1792
- const query = c.req.valid('query');
1793
- return proxySingleInstanceJsonRequest<{ revoked: boolean }>({
1794
- c,
1795
- query,
1796
- method: 'DELETE',
1797
- path: `/api-keys/${encodeURIComponent(id)}`,
1798
- responseSchema: ConsoleApiKeyRevokeResponseSchema,
1799
- });
1800
- });
1801
- }
1802
- );
1803
-
1804
- routes.post(
1805
- '/api-keys/bulk-revoke',
1806
- describeConsoleGatewayRoute({
1807
- summary:
1808
- 'Bulk revoke API keys on a single target instance (requires instance selection)',
1809
- responses: {
1810
- 200: {
1811
- description: 'Bulk revoke result',
1812
- content: {
1813
- 'application/json': {
1814
- schema: resolver(ConsoleApiKeyBulkRevokeResponseSchema),
1815
- },
1816
- },
1817
- },
1818
- },
1819
- }),
1820
- zValidator('query', GatewaySingleInstanceQuerySchema),
1821
- zValidator('json', ConsoleApiKeyBulkRevokeRequestSchema),
1822
- async (c) => {
1823
- return withGatewayAuth(c, async () => {
1824
- const query = c.req.valid('query');
1825
- const body = c.req.valid('json');
1826
- return proxySingleInstanceJsonRequest<ConsoleApiKeyBulkRevokeResponse>({
1827
- c,
1828
- query,
1829
- method: 'POST',
1830
- path: '/api-keys/bulk-revoke',
1831
- body,
1832
- responseSchema: ConsoleApiKeyBulkRevokeResponseSchema,
1833
- });
1834
- });
1835
- }
1836
- );
1837
-
1838
- routes.post(
1839
- '/api-keys/:id/rotate/stage',
1840
- describeConsoleGatewayRoute({
1841
- summary:
1842
- 'Stage-rotate API key on a single target instance (requires instance selection)',
1843
- responses: {
1844
- 200: {
1845
- description: 'Staged API key replacement',
1846
- content: {
1847
- 'application/json': {
1848
- schema: resolver(ConsoleApiKeyCreateResponseSchema),
1849
- },
1850
- },
1851
- },
1852
- },
1853
- }),
1854
- zValidator('param', GatewayApiKeyPathParamSchema),
1855
- zValidator('query', GatewaySingleInstanceQuerySchema),
1856
- async (c) => {
1857
- return withGatewayAuth(c, async () => {
1858
- const { id } = c.req.valid('param');
1859
- const query = c.req.valid('query');
1860
- return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
1861
- c,
1862
- query,
1863
- method: 'POST',
1864
- path: `/api-keys/${encodeURIComponent(id)}/rotate/stage`,
1865
- responseSchema: ConsoleApiKeyCreateResponseSchema,
1866
- });
1867
- });
1868
- }
1869
- );
1870
-
1871
- routes.post(
1872
- '/api-keys/:id/rotate',
1873
- describeConsoleGatewayRoute({
1874
- summary:
1875
- 'Rotate API key on a single target instance (requires instance selection)',
1876
- responses: {
1877
- 200: {
1878
- description: 'Rotated API key',
1879
- content: {
1880
- 'application/json': {
1881
- schema: resolver(ConsoleApiKeyCreateResponseSchema),
1882
- },
1883
- },
1884
- },
1885
- },
1886
- }),
1887
- zValidator('param', GatewayApiKeyPathParamSchema),
1888
- zValidator('query', GatewaySingleInstanceQuerySchema),
1889
- async (c) => {
1890
- return withGatewayAuth(c, async () => {
1891
- const { id } = c.req.valid('param');
1892
- const query = c.req.valid('query');
1893
- return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
1894
- c,
1895
- query,
1896
- method: 'POST',
1897
- path: `/api-keys/${encodeURIComponent(id)}/rotate`,
1898
- responseSchema: ConsoleApiKeyCreateResponseSchema,
1899
- });
1900
- });
1901
- }
1902
- );
1903
-
1904
- routes.get(
1905
- '/stats',
1906
- describeConsoleGatewayRoute({
1907
- summary: 'Get merged sync stats across instances',
1908
- responses: {
1909
- 200: {
1910
- description: 'Merged stats',
1911
- content: {
1912
- 'application/json': {
1913
- schema: resolver(GatewayStatsResponseSchema),
1914
- },
1915
- },
1916
- },
1917
- },
1918
- }),
1919
- zValidator('query', GatewayStatsQuerySchema),
1920
- async (c) => {
1921
- return withGatewayAuth(c, async () => {
1922
- const query = c.req.valid('query');
1923
- const selection = selectTargetInstances(c, query);
1924
- if (!selection.ok) {
1925
- return selection.response;
1926
- }
1927
-
1928
- const forwardQuery = sanitizeForwardQueryParams(
1929
- new URL(c.req.url).searchParams
1930
- );
1931
- const fetched = await fetchFromSelectedInstances<SyncStats>({
1932
- c,
1933
- selectedInstances: selection.selectedInstances,
1934
- path: '/stats',
1935
- query: forwardQuery,
1936
- schema: SyncStatsSchema,
1937
- });
1938
- if (!fetched.ok) {
1939
- return fetched.response;
1940
- }
1941
-
1942
- const statsByInstance = new Map<string, SyncStats>();
1943
- for (const result of fetched.successfulResults) {
1944
- statsByInstance.set(result.instance.instanceId, result.data);
1945
- }
1946
-
1947
- const statsValues = Array.from(statsByInstance.values());
1948
- const sum = (selector: (stats: SyncStats) => number): number =>
1949
- statsValues.reduce((acc, stats) => acc + selector(stats), 0);
1950
-
1951
- const minCommitSeqByInstance: Record<string, number> = {};
1952
- const maxCommitSeqByInstance: Record<string, number> = {};
1953
- for (const [instanceId, stats] of statsByInstance.entries()) {
1954
- minCommitSeqByInstance[instanceId] = stats.minCommitSeq;
1955
- maxCommitSeqByInstance[instanceId] = stats.maxCommitSeq;
1956
- }
1957
-
1958
- return c.json({
1959
- commitCount: sum((stats) => stats.commitCount),
1960
- changeCount: sum((stats) => stats.changeCount),
1961
- minCommitSeq: Math.min(
1962
- ...statsValues.map((stats) => stats.minCommitSeq)
1963
- ),
1964
- maxCommitSeq: Math.max(
1965
- ...statsValues.map((stats) => stats.maxCommitSeq)
1966
- ),
1967
- clientCount: sum((stats) => stats.clientCount),
1968
- activeClientCount: sum((stats) => stats.activeClientCount),
1969
- minActiveClientCursor: minNullable(
1970
- statsValues.map((stats) => stats.minActiveClientCursor)
1971
- ),
1972
- maxActiveClientCursor: maxNullable(
1973
- statsValues.map((stats) => stats.maxActiveClientCursor)
1974
- ),
1975
- snapshotChunkCount: sum((stats) => stats.snapshotChunkCount),
1976
- snapshotChunkBytes: sum((stats) => stats.snapshotChunkBytes),
1977
- expiredSnapshotChunkCount: sum(
1978
- (stats) => stats.expiredSnapshotChunkCount
1979
- ),
1980
- expiredSnapshotChunkBytes: sum(
1981
- (stats) => stats.expiredSnapshotChunkBytes
1982
- ),
1983
- snapshotArtifactCount: sum((stats) => stats.snapshotArtifactCount),
1984
- snapshotArtifactBytes: sum((stats) => stats.snapshotArtifactBytes),
1985
- expiredSnapshotArtifactCount: sum(
1986
- (stats) => stats.expiredSnapshotArtifactCount
1987
- ),
1988
- expiredSnapshotArtifactBytes: sum(
1989
- (stats) => stats.expiredSnapshotArtifactBytes
1990
- ),
1991
- minCommitSeqByInstance,
1992
- maxCommitSeqByInstance,
1993
- partial: fetched.failedInstances.length > 0,
1994
- failedInstances: fetched.failedInstances,
1995
- });
1996
- });
1997
- }
1998
- );
1999
-
2000
- routes.get(
2001
- '/stats/timeseries',
2002
- describeConsoleGatewayRoute({
2003
- summary: 'Get merged time-series stats across instances',
2004
- responses: {
2005
- 200: {
2006
- description: 'Merged time-series stats',
2007
- content: {
2008
- 'application/json': {
2009
- schema: resolver(GatewayTimeseriesResponseSchema),
2010
- },
2011
- },
2012
- },
2013
- },
2014
- }),
2015
- zValidator('query', GatewayTimeseriesQuerySchema),
2016
- async (c) => {
2017
- return withGatewayAuth(c, async () => {
2018
- const query = c.req.valid('query');
2019
- const selection = selectTargetInstances(c, query);
2020
- if (!selection.ok) {
2021
- return selection.response;
2022
- }
2023
-
2024
- const forwardQuery = sanitizeForwardQueryParams(
2025
- new URL(c.req.url).searchParams
2026
- );
2027
- const fetched =
2028
- await fetchFromSelectedInstances<TimeseriesStatsResponse>({
2029
- c,
2030
- selectedInstances: selection.selectedInstances,
2031
- path: '/stats/timeseries',
2032
- query: forwardQuery,
2033
- schema: TimeseriesStatsResponseSchema,
2034
- });
2035
- if (!fetched.ok) {
2036
- return fetched.response;
2037
- }
2038
-
2039
- return c.json({
2040
- buckets: mergeTimeseriesBuckets(
2041
- fetched.successfulResults.map((result) => result.data)
2042
- ),
2043
- interval: query.interval,
2044
- range: query.range,
2045
- partial: fetched.failedInstances.length > 0,
2046
- failedInstances: fetched.failedInstances,
2047
- });
2048
- });
2049
- }
2050
- );
2051
-
2052
- routes.get(
2053
- '/stats/latency',
2054
- describeConsoleGatewayRoute({
2055
- summary: 'Get merged latency stats across instances',
2056
- responses: {
2057
- 200: {
2058
- description: 'Merged latency stats',
2059
- content: {
2060
- 'application/json': {
2061
- schema: resolver(GatewayLatencyResponseSchema),
2062
- },
2063
- },
2064
- },
2065
- },
2066
- }),
2067
- zValidator('query', GatewayLatencyQuerySchema),
2068
- async (c) => {
2069
- return withGatewayAuth(c, async () => {
2070
- const query = c.req.valid('query');
2071
- const selection = selectTargetInstances(c, query);
2072
- if (!selection.ok) {
2073
- return selection.response;
2074
- }
2075
-
2076
- const forwardQuery = sanitizeForwardQueryParams(
2077
- new URL(c.req.url).searchParams
2078
- );
2079
- const fetched = await fetchFromSelectedInstances<LatencyStatsResponse>({
2080
- c,
2081
- selectedInstances: selection.selectedInstances,
2082
- path: '/stats/latency',
2083
- query: forwardQuery,
2084
- schema: LatencyStatsResponseSchema,
2085
- });
2086
- if (!fetched.ok) {
2087
- return fetched.response;
2088
- }
2089
-
2090
- return c.json({
2091
- push: averagePercentiles(
2092
- fetched.successfulResults.map((result) => result.data.push)
2093
- ),
2094
- pull: averagePercentiles(
2095
- fetched.successfulResults.map((result) => result.data.pull)
2096
- ),
2097
- range: query.range,
2098
- partial: fetched.failedInstances.length > 0,
2099
- failedInstances: fetched.failedInstances,
2100
- });
2101
- });
2102
- }
2103
- );
2104
-
2105
- routes.get(
2106
- '/commits',
2107
- describeConsoleGatewayRoute({
2108
- summary: 'List merged commits across instances',
2109
- responses: {
2110
- 200: {
2111
- description: 'Merged commits',
2112
- content: {
2113
- 'application/json': {
2114
- schema: resolver(
2115
- GatewayPaginatedResponseSchema(GatewayCommitItemSchema)
2116
- ),
2117
- },
2118
- },
2119
- },
2120
- },
2121
- }),
2122
- zValidator('query', GatewayPaginatedQuerySchema),
2123
- async (c) => {
2124
- return withGatewayAuth(c, async () => {
2125
- const query = c.req.valid('query');
2126
- const selection = selectTargetInstances(c, query);
2127
- if (!selection.ok) {
2128
- return selection.response;
2129
- }
2130
-
2131
- const targetCount = query.offset + query.limit;
2132
- const forwardQuery = sanitizeForwardQueryParams(
2133
- new URL(c.req.url).searchParams
2134
- );
2135
- forwardQuery.delete('limit');
2136
- forwardQuery.delete('offset');
2137
- const pageSchema = ConsolePaginatedResponseSchema(
2138
- ConsoleCommitListItemSchema
2139
- );
2140
- const fetched =
2141
- await fetchPagedFromSelectedInstances<ConsoleCommitListItem>({
2142
- c,
2143
- selectedInstances: selection.selectedInstances,
2144
- path: '/commits',
2145
- query: forwardQuery,
2146
- targetCount,
2147
- schema: pageSchema,
2148
- });
2149
- if (!fetched.ok) {
2150
- return fetched.response;
2151
- }
2152
-
2153
- const merged = fetched.successfulResults
2154
- .flatMap(({ items, instance }) =>
2155
- items.map((commit) => ({
2156
- ...commit,
2157
- instanceId: instance.instanceId,
2158
- federatedCommitId: `${instance.instanceId}:${commit.commitSeq}`,
2159
- }))
2160
- )
2161
- .sort((a, b) => {
2162
- const byTime = compareIsoDesc(a.createdAt, b.createdAt);
2163
- if (byTime !== 0) return byTime;
2164
- const byInstance = a.instanceId.localeCompare(b.instanceId);
2165
- if (byInstance !== 0) return byInstance;
2166
- return b.commitSeq - a.commitSeq;
2167
- });
2168
-
2169
- return c.json({
2170
- items: merged.slice(query.offset, query.offset + query.limit),
2171
- total: fetched.successfulResults.reduce(
2172
- (acc, entry) => acc + entry.total,
2173
- 0
2174
- ),
2175
- offset: query.offset,
2176
- limit: query.limit,
2177
- partial: fetched.failedInstances.length > 0,
2178
- failedInstances: fetched.failedInstances,
2179
- });
2180
- });
2181
- }
2182
- );
2183
-
2184
- routes.get(
2185
- '/commits/:seq',
2186
- describeConsoleGatewayRoute({
2187
- summary: 'Get merged commit detail by federated id',
2188
- responses: {
2189
- 200: {
2190
- description: 'Commit detail',
2191
- content: {
2192
- 'application/json': {
2193
- schema: resolver(GatewayCommitDetailSchema),
2194
- },
2195
- },
2196
- },
2197
- },
2198
- }),
2199
- zValidator('param', GatewayCommitPathParamSchema),
2200
- zValidator(
2201
- 'query',
2202
- ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
2203
- ),
2204
- async (c) => {
2205
- return withGatewayAuth(c, async () => {
2206
- const { seq } = c.req.valid('param');
2207
- const query = c.req.valid('query');
2208
- const target = resolveCommitTarget({ seq, instances, query });
2209
- if (!target.ok) {
2210
- return consoleTargetErrorResponse(c, target);
2211
- }
2212
-
2213
- const forwardQuery = sanitizeForwardQueryParams(
2214
- new URL(c.req.url).searchParams
2215
- );
2216
- const result = await fetchDownstreamJson({
2217
- c,
2218
- instance: target.instance,
2219
- path: `/commits/${target.localCommitSeq}`,
2220
- query: forwardQuery,
2221
- schema: ConsoleCommitDetailSchema,
2222
- fetchImpl,
2223
- });
2224
-
2225
- if (!result.ok) {
2226
- return downstreamFailureResponse(c, result.failure);
2227
- }
2228
-
2229
- return c.json({
2230
- ...result.data,
2231
- instanceId: target.instance.instanceId,
2232
- federatedCommitId: `${target.instance.instanceId}:${result.data.commitSeq}`,
2233
- localCommitSeq: result.data.commitSeq,
2234
- });
2235
- });
2236
- }
2237
- );
2238
-
2239
- routes.get(
2240
- '/clients',
2241
- describeConsoleGatewayRoute({
2242
- summary: 'List merged clients across instances',
2243
- responses: {
2244
- 200: {
2245
- description: 'Merged clients',
2246
- content: {
2247
- 'application/json': {
2248
- schema: resolver(
2249
- GatewayPaginatedResponseSchema(GatewayClientItemSchema)
2250
- ),
2251
- },
2252
- },
2253
- },
2254
- },
2255
- }),
2256
- zValidator('query', GatewayPaginatedQuerySchema),
2257
- async (c) => {
2258
- return withGatewayAuth(c, async () => {
2259
- const query = c.req.valid('query');
2260
- const selection = selectTargetInstances(c, query);
2261
- if (!selection.ok) {
2262
- return selection.response;
2263
- }
2264
-
2265
- const targetCount = query.offset + query.limit;
2266
- const forwardQuery = sanitizeForwardQueryParams(
2267
- new URL(c.req.url).searchParams
2268
- );
2269
- forwardQuery.delete('limit');
2270
- forwardQuery.delete('offset');
2271
- const pageSchema = ConsolePaginatedResponseSchema(ConsoleClientSchema);
2272
- const fetched = await fetchPagedFromSelectedInstances<ConsoleClient>({
2273
- c,
2274
- selectedInstances: selection.selectedInstances,
2275
- path: '/clients',
2276
- query: forwardQuery,
2277
- targetCount,
2278
- schema: pageSchema,
2279
- });
2280
- if (!fetched.ok) {
2281
- return fetched.response;
2282
- }
2283
-
2284
- const merged = fetched.successfulResults
2285
- .flatMap(({ items, instance }) =>
2286
- items.map((client) => ({
2287
- ...client,
2288
- instanceId: instance.instanceId,
2289
- federatedClientId: `${instance.instanceId}:${client.clientId}`,
2290
- }))
2291
- )
2292
- .sort((a, b) => {
2293
- const byTime = compareIsoDesc(a.updatedAt, b.updatedAt);
2294
- if (byTime !== 0) return byTime;
2295
- const byInstance = a.instanceId.localeCompare(b.instanceId);
2296
- if (byInstance !== 0) return byInstance;
2297
- return a.clientId.localeCompare(b.clientId);
2298
- });
2299
-
2300
- return c.json({
2301
- items: merged.slice(query.offset, query.offset + query.limit),
2302
- total: fetched.successfulResults.reduce(
2303
- (acc, entry) => acc + entry.total,
2304
- 0
2305
- ),
2306
- offset: query.offset,
2307
- limit: query.limit,
2308
- partial: fetched.failedInstances.length > 0,
2309
- failedInstances: fetched.failedInstances,
2310
- });
2311
- });
2312
- }
2313
- );
2314
-
2315
- routes.get(
2316
- '/timeline',
2317
- describeConsoleGatewayRoute({
2318
- summary: 'List merged timeline items across instances',
2319
- responses: {
2320
- 200: {
2321
- description: 'Merged timeline',
2322
- content: {
2323
- 'application/json': {
2324
- schema: resolver(
2325
- GatewayPaginatedResponseSchema(GatewayTimelineItemSchema)
2326
- ),
2327
- },
2328
- },
2329
- },
2330
- },
2331
- }),
2332
- zValidator('query', GatewayTimelineQuerySchema),
2333
- async (c) => {
2334
- return withGatewayAuth(c, async () => {
2335
- const query = c.req.valid('query');
2336
- const selection = selectTargetInstances(c, query);
2337
- if (!selection.ok) {
2338
- return selection.response;
2339
- }
2340
-
2341
- const targetCount = query.offset + query.limit;
2342
- const forwardQuery = sanitizeForwardQueryParams(
2343
- new URL(c.req.url).searchParams
2344
- );
2345
- forwardQuery.delete('limit');
2346
- forwardQuery.delete('offset');
2347
- const pageSchema = ConsolePaginatedResponseSchema(
2348
- ConsoleTimelineItemSchema
2349
- );
2350
- const fetched =
2351
- await fetchPagedFromSelectedInstances<ConsoleTimelineItem>({
2352
- c,
2353
- selectedInstances: selection.selectedInstances,
2354
- path: '/timeline',
2355
- query: forwardQuery,
2356
- targetCount,
2357
- schema: pageSchema,
2358
- });
2359
- if (!fetched.ok) {
2360
- return fetched.response;
2361
- }
2362
-
2363
- const merged = fetched.successfulResults
2364
- .flatMap(({ items, instance }) =>
2365
- items.map((item) => {
2366
- const localCommitSeq =
2367
- item.type === 'commit'
2368
- ? (item.commit?.commitSeq ?? null)
2369
- : null;
2370
- const localEventId =
2371
- item.type === 'event' ? (item.event?.eventId ?? null) : null;
2372
- const localIdSegment =
2373
- item.type === 'commit'
2374
- ? String(localCommitSeq ?? 'unknown')
2375
- : String(localEventId ?? 'unknown');
2376
-
2377
- return {
2378
- ...item,
2379
- instanceId: instance.instanceId,
2380
- federatedTimelineId: `${instance.instanceId}:${item.type}:${localIdSegment}`,
2381
- localCommitSeq,
2382
- localEventId,
2383
- };
2384
- })
2385
- )
2386
- .sort((a, b) => {
2387
- const byTime = compareIsoDesc(a.timestamp, b.timestamp);
2388
- if (byTime !== 0) return byTime;
2389
- const byInstance = a.instanceId.localeCompare(b.instanceId);
2390
- if (byInstance !== 0) return byInstance;
2391
- const aLocalId = a.localCommitSeq ?? a.localEventId ?? 0;
2392
- const bLocalId = b.localCommitSeq ?? b.localEventId ?? 0;
2393
- return bLocalId - aLocalId;
2394
- });
2395
-
2396
- return c.json({
2397
- items: merged.slice(query.offset, query.offset + query.limit),
2398
- total: fetched.successfulResults.reduce(
2399
- (acc, entry) => acc + entry.total,
2400
- 0
2401
- ),
2402
- offset: query.offset,
2403
- limit: query.limit,
2404
- partial: fetched.failedInstances.length > 0,
2405
- failedInstances: fetched.failedInstances,
2406
- });
2407
- });
2408
- }
2409
- );
2410
-
2411
- routes.get(
2412
- '/operations',
2413
- describeConsoleGatewayRoute({
2414
- summary: 'List merged operation events across instances',
2415
- responses: {
2416
- 200: {
2417
- description: 'Merged operations',
2418
- content: {
2419
- 'application/json': {
2420
- schema: resolver(
2421
- GatewayPaginatedResponseSchema(GatewayOperationItemSchema)
2422
- ),
2423
- },
2424
- },
2425
- },
2426
- },
2427
- }),
2428
- zValidator('query', GatewayOperationsQuerySchema),
2429
- async (c) => {
2430
- return withGatewayAuth(c, async () => {
2431
- const query = c.req.valid('query');
2432
- const selection = selectTargetInstances(c, query);
2433
- if (!selection.ok) {
2434
- return selection.response;
2435
- }
2436
-
2437
- const targetCount = query.offset + query.limit;
2438
- const forwardQuery = sanitizeForwardQueryParams(
2439
- new URL(c.req.url).searchParams
2440
- );
2441
- forwardQuery.delete('limit');
2442
- forwardQuery.delete('offset');
2443
- const pageSchema = ConsolePaginatedResponseSchema(
2444
- ConsoleOperationEventSchema
2445
- );
2446
- const fetched =
2447
- await fetchPagedFromSelectedInstances<ConsoleOperationEvent>({
2448
- c,
2449
- selectedInstances: selection.selectedInstances,
2450
- path: '/operations',
2451
- query: forwardQuery,
2452
- targetCount,
2453
- schema: pageSchema,
2454
- });
2455
- if (!fetched.ok) {
2456
- return fetched.response;
2457
- }
2458
-
2459
- const merged = fetched.successfulResults
2460
- .flatMap(({ items, instance }) =>
2461
- items.map((operation) => ({
2462
- ...operation,
2463
- instanceId: instance.instanceId,
2464
- federatedOperationId: `${instance.instanceId}:${operation.operationId}`,
2465
- localOperationId: operation.operationId,
2466
- }))
2467
- )
2468
- .sort((a, b) => {
2469
- const byTime = compareIsoDesc(a.createdAt, b.createdAt);
2470
- if (byTime !== 0) return byTime;
2471
- const byInstance = a.instanceId.localeCompare(b.instanceId);
2472
- if (byInstance !== 0) return byInstance;
2473
- return b.localOperationId - a.localOperationId;
2474
- });
2475
-
2476
- return c.json({
2477
- items: merged.slice(query.offset, query.offset + query.limit),
2478
- total: fetched.successfulResults.reduce(
2479
- (acc, entry) => acc + entry.total,
2480
- 0
2481
- ),
2482
- offset: query.offset,
2483
- limit: query.limit,
2484
- partial: fetched.failedInstances.length > 0,
2485
- failedInstances: fetched.failedInstances,
2486
- });
2487
- });
2488
- }
2489
- );
2490
-
2491
- routes.get(
2492
- '/events',
2493
- describeConsoleGatewayRoute({
2494
- summary: 'List merged request events across instances',
2495
- responses: {
2496
- 200: {
2497
- description: 'Merged events',
2498
- content: {
2499
- 'application/json': {
2500
- schema: resolver(
2501
- GatewayPaginatedResponseSchema(GatewayEventItemSchema)
2502
- ),
2503
- },
2504
- },
2505
- },
2506
- },
2507
- }),
2508
- zValidator('query', GatewayEventsQuerySchema),
2509
- async (c) => {
2510
- return withGatewayAuth(c, async () => {
2511
- const query = c.req.valid('query');
2512
- const selection = selectTargetInstances(c, query);
2513
- if (!selection.ok) {
2514
- return selection.response;
2515
- }
2516
-
2517
- const targetCount = query.offset + query.limit;
2518
- const forwardQuery = sanitizeForwardQueryParams(
2519
- new URL(c.req.url).searchParams
2520
- );
2521
- forwardQuery.delete('limit');
2522
- forwardQuery.delete('offset');
2523
- const pageSchema = ConsolePaginatedResponseSchema(
2524
- ConsoleRequestEventSchema
2525
- );
2526
- const fetched =
2527
- await fetchPagedFromSelectedInstances<ConsoleRequestEvent>({
2528
- c,
2529
- selectedInstances: selection.selectedInstances,
2530
- path: '/events',
2531
- query: forwardQuery,
2532
- targetCount,
2533
- schema: pageSchema,
2534
- });
2535
- if (!fetched.ok) {
2536
- return fetched.response;
2537
- }
2538
-
2539
- const merged = fetched.successfulResults
2540
- .flatMap(({ items, instance }) =>
2541
- items.map((event) => ({
2542
- ...event,
2543
- instanceId: instance.instanceId,
2544
- federatedEventId: `${instance.instanceId}:${event.eventId}`,
2545
- localEventId: event.eventId,
2546
- }))
2547
- )
2548
- .sort((a, b) => {
2549
- const byTime = compareIsoDesc(a.createdAt, b.createdAt);
2550
- if (byTime !== 0) return byTime;
2551
- const byInstance = a.instanceId.localeCompare(b.instanceId);
2552
- if (byInstance !== 0) return byInstance;
2553
- return b.localEventId - a.localEventId;
2554
- });
2555
-
2556
- return c.json({
2557
- items: merged.slice(query.offset, query.offset + query.limit),
2558
- total: fetched.successfulResults.reduce(
2559
- (acc, entry) => acc + entry.total,
2560
- 0
2561
- ),
2562
- offset: query.offset,
2563
- limit: query.limit,
2564
- partial: fetched.failedInstances.length > 0,
2565
- failedInstances: fetched.failedInstances,
2566
- });
2567
- });
2568
- }
2569
- );
2570
-
2571
- if (
2572
- options.websocket?.enabled &&
2573
- options.websocket?.upgradeWebSocket !== undefined
2574
- ) {
2575
- const upgradeWebSocket = options.websocket.upgradeWebSocket;
2576
- const heartbeatIntervalMs = options.websocket.heartbeatIntervalMs ?? 30000;
2577
- const maxMessageBytes = options.websocket.maxMessageBytes ?? 1024 * 1024;
2578
- const maxMessagesPerWindow = options.websocket.maxMessagesPerWindow ?? 120;
2579
- const messageRateWindowMs = options.websocket.messageRateWindowMs ?? 10000;
2580
- const createDownstreamSocket =
2581
- options.websocket.createWebSocket ??
2582
- ((url: string): ConsoleGatewayDownstreamSocket => new WebSocket(url));
2583
-
2584
- type WebSocketLike = {
2585
- send: (data: string) => void;
2586
- close: (code?: number, reason?: string) => void;
2587
- };
2588
-
2589
- const liveState = new WeakMap<
2590
- WebSocketLike,
2591
- {
2592
- downstreamSockets: ConsoleGatewayDownstreamSocket[];
2593
- heartbeatInterval: ReturnType<typeof setInterval> | null;
2594
- authTimeout: ReturnType<typeof setTimeout> | null;
2595
- isAuthenticated: boolean;
2596
- startAuthenticatedSession: ((token: string | null) => void) | null;
2597
- messageRateWindowStart: number;
2598
- messageRateWindowCount: number;
2599
- }
2600
- >();
2601
-
2602
- const liveEventsWebSocketRoute = upgradeWebSocket(async (c) => {
2603
- const initialAuth = await options.authenticate(c);
2604
- const partitionId = c.req.query('partitionId')?.trim() || undefined;
2605
- const replaySince = c.req.query('since')?.trim() || undefined;
2606
- const replayLimitRaw = c.req.query('replayLimit');
2607
- const replayLimitNumber = replayLimitRaw
2608
- ? Number.parseInt(replayLimitRaw, 10)
2609
- : Number.NaN;
2610
- const replayLimit = Number.isFinite(replayLimitNumber)
2611
- ? Math.max(1, Math.min(500, replayLimitNumber))
2612
- : 100;
2613
-
2614
- const selectedInstances = selectInstances({
2615
- instances,
2616
- query: {
2617
- instanceId: c.req.query('instanceId') ?? undefined,
2618
- instanceIds: c.req.query('instanceIds') ?? undefined,
2619
- },
2620
- });
2621
-
2622
- const authenticateWithBearer = async (
2623
- token: string
2624
- ): Promise<ConsoleAuthResult | null> => {
2625
- const trimmedToken = token.trim();
2626
- if (!trimmedToken) {
2627
- return null;
2628
- }
2629
- const authContext = {
2630
- req: {
2631
- header: (name: string) =>
2632
- name === 'Authorization' ? `Bearer ${trimmedToken}` : undefined,
2633
- query: () => undefined,
2634
- },
2635
- } as unknown as Context;
2636
- return options.authenticate(authContext);
2637
- };
2638
-
2639
- const cleanup = (ws: WebSocketLike) => {
2640
- const state = liveState.get(ws);
2641
- if (!state) return;
2642
- if (state.heartbeatInterval) {
2643
- clearInterval(state.heartbeatInterval);
2644
- }
2645
- if (state.authTimeout) {
2646
- clearTimeout(state.authTimeout);
2647
- }
2648
- for (const downstream of state.downstreamSockets) {
2649
- try {
2650
- downstream.close();
2651
- } catch {
2652
- // no-op
2653
- }
2654
- }
2655
- liveState.delete(ws);
2656
- };
2657
-
2658
- return {
2659
- onOpen(_event, ws) {
2660
- if (selectedInstances.length === 0) {
2661
- ws.send(
2662
- JSON.stringify({
2663
- type: 'error',
2664
- ...createSyncularErrorResponse('console.invalid_request', {
2665
- message:
2666
- 'No enabled instances matched the provided instance filter.',
2667
- details: { consoleError: 'no_instances_selected' },
2668
- }),
2669
- })
2670
- );
2671
- ws.close(4004, 'No instances selected');
2672
- return;
2673
- }
2674
-
2675
- const state: {
2676
- downstreamSockets: ConsoleGatewayDownstreamSocket[];
2677
- heartbeatInterval: ReturnType<typeof setInterval> | null;
2678
- authTimeout: ReturnType<typeof setTimeout> | null;
2679
- isAuthenticated: boolean;
2680
- startAuthenticatedSession: ((token: string | null) => void) | null;
2681
- messageRateWindowStart: number;
2682
- messageRateWindowCount: number;
2683
- } = {
2684
- downstreamSockets: [],
2685
- heartbeatInterval: null,
2686
- authTimeout: null,
2687
- isAuthenticated: false,
2688
- startAuthenticatedSession: null,
2689
- messageRateWindowStart: Date.now(),
2690
- messageRateWindowCount: 0,
2691
- };
2692
- liveState.set(ws, state);
2693
-
2694
- const startAuthenticatedSession = (
2695
- upstreamBearerToken: string | null
2696
- ) => {
2697
- if (state.isAuthenticated) {
2698
- return;
2699
- }
2700
- state.isAuthenticated = true;
2701
- if (state.authTimeout) {
2702
- clearTimeout(state.authTimeout);
2703
- state.authTimeout = null;
2704
- }
2705
-
2706
- for (const instance of selectedInstances) {
2707
- const downstreamQuery = new URLSearchParams();
2708
- if (partitionId) {
2709
- downstreamQuery.set('partitionId', partitionId);
2710
- }
2711
- if (replaySince) {
2712
- downstreamQuery.set('since', replaySince);
2713
- }
2714
- downstreamQuery.set('replayLimit', String(replayLimit));
2715
-
2716
- const downstreamUrl = buildConsoleEndpointUrl({
2717
- instance,
2718
- requestUrl: c.req.url,
2719
- path: '/events/live',
2720
- query: downstreamQuery,
2721
- });
2722
-
2723
- const downstreamSocket = createDownstreamSocket(downstreamUrl);
2724
- const downstreamToken =
2725
- instance.token?.trim() ?? upstreamBearerToken?.trim() ?? null;
2726
- if (downstreamToken && downstreamSocket.send) {
2727
- downstreamSocket.onopen = () => {
2728
- try {
2729
- downstreamSocket.send?.(
2730
- JSON.stringify({
2731
- type: 'auth',
2732
- token: downstreamToken,
2733
- })
2734
- );
2735
- } catch {
2736
- // no-op
2737
- }
2738
- };
2739
- }
2740
-
2741
- downstreamSocket.onmessage = (message: MessageEvent) => {
2742
- if (typeof message.data !== 'string') {
2743
- return;
2744
- }
2745
- try {
2746
- const payload = JSON.parse(message.data) as Record<
2747
- string,
2748
- unknown
2749
- >;
2750
- if (
2751
- typeof payload.type === 'string' &&
2752
- (payload.type === 'connected' ||
2753
- payload.type === 'heartbeat')
2754
- ) {
2755
- return;
2756
- }
2757
-
2758
- const payloadData =
2759
- payload.data &&
2760
- typeof payload.data === 'object' &&
2761
- !Array.isArray(payload.data)
2762
- ? { ...payload.data, instanceId: instance.instanceId }
2763
- : { instanceId: instance.instanceId };
2764
-
2765
- const event = {
2766
- ...payload,
2767
- data: payloadData,
2768
- instanceId: instance.instanceId,
2769
- timestamp:
2770
- typeof payload.timestamp === 'string'
2771
- ? payload.timestamp
2772
- : new Date().toISOString(),
2773
- };
2774
- ws.send(JSON.stringify(event));
2775
- } catch {
2776
- // Ignore malformed downstream events
2777
- }
2778
- };
2779
-
2780
- downstreamSocket.onerror = () => {
2781
- try {
2782
- ws.send(
2783
- JSON.stringify({
2784
- type: 'instance_error',
2785
- instanceId: instance.instanceId,
2786
- timestamp: new Date().toISOString(),
2787
- })
2788
- );
2789
- } catch {
2790
- // ignore send errors
2791
- }
2792
- };
2793
-
2794
- state.downstreamSockets.push(downstreamSocket);
2795
- }
2796
-
2797
- ws.send(
2798
- JSON.stringify({
2799
- type: 'connected',
2800
- timestamp: new Date().toISOString(),
2801
- instanceCount: selectedInstances.length,
2802
- })
2803
- );
2804
-
2805
- const heartbeatInterval = setInterval(() => {
2806
- try {
2807
- ws.send(
2808
- JSON.stringify({
2809
- type: 'heartbeat',
2810
- timestamp: new Date().toISOString(),
2811
- })
2812
- );
2813
- } catch {
2814
- clearInterval(heartbeatInterval);
2815
- }
2816
- }, heartbeatIntervalMs);
2817
- state.heartbeatInterval = heartbeatInterval;
2818
- };
2819
- state.startAuthenticatedSession = startAuthenticatedSession;
2820
-
2821
- if (initialAuth) {
2822
- startAuthenticatedSession(
2823
- parseBearerToken(c.req.header('Authorization'))
2824
- );
2825
- return;
2826
- }
2827
-
2828
- state.authTimeout = setTimeout(() => {
2829
- const current = liveState.get(ws);
2830
- if (!current || current.isAuthenticated) {
2831
- return;
2832
- }
2833
- closeUnauthenticatedSocket(ws);
2834
- cleanup(ws);
2835
- }, 5_000);
2836
- },
2837
- async onMessage(event, ws) {
2838
- const state = liveState.get(ws);
2839
- if (!state) {
2840
- return;
2841
- }
2842
-
2843
- const messageBytes = measureWebSocketMessageBytes(event.data);
2844
- if (messageBytes > maxMessageBytes) {
2845
- ws.close(1009, 'message too large');
2846
- cleanup(ws);
2847
- return;
2848
- }
2849
-
2850
- if (maxMessagesPerWindow > 0 && messageRateWindowMs > 0) {
2851
- const nowMs = Date.now();
2852
- if (nowMs - state.messageRateWindowStart >= messageRateWindowMs) {
2853
- state.messageRateWindowStart = nowMs;
2854
- state.messageRateWindowCount = 0;
2855
- }
2856
- state.messageRateWindowCount += 1;
2857
- if (state.messageRateWindowCount > maxMessagesPerWindow) {
2858
- ws.close(1008, 'message rate exceeded');
2859
- cleanup(ws);
2860
- return;
2861
- }
2862
- }
2863
-
2864
- if (state.isAuthenticated) {
2865
- return;
2866
- }
2867
-
2868
- if (typeof event.data !== 'string') {
2869
- closeUnauthenticatedSocket(ws);
2870
- cleanup(ws);
2871
- return;
2872
- }
2873
-
2874
- const token = parseWebSocketAuthToken(event.data);
2875
-
2876
- if (!token) {
2877
- closeUnauthenticatedSocket(ws);
2878
- cleanup(ws);
2879
- return;
2880
- }
2881
-
2882
- const auth = await authenticateWithBearer(token);
2883
- const current = liveState.get(ws);
2884
- if (!current || current.isAuthenticated) {
2885
- return;
2886
- }
2887
- if (!auth) {
2888
- closeUnauthenticatedSocket(ws);
2889
- cleanup(ws);
2890
- return;
2891
- }
2892
- current.startAuthenticatedSession?.(token);
2893
- },
2894
- onClose(_event, ws) {
2895
- cleanup(ws);
2896
- },
2897
- onError(_event, ws) {
2898
- cleanup(ws);
2899
- },
2900
- };
2901
- });
2902
-
2903
- routes.get('/events/live', async (c, next) => {
2904
- if (!isWebSocketOriginAllowed(c, options.websocket?.allowedOrigins)) {
2905
- return consoleGatewayError(c, 403, 'console.forbidden_origin');
2906
- }
2907
- return liveEventsWebSocketRoute(c, next);
2908
- });
2909
- }
2910
-
2911
- routes.get(
2912
- '/events/:id',
2913
- describeConsoleGatewayRoute({
2914
- summary: 'Get merged event detail by federated id',
2915
- responses: {
2916
- 200: {
2917
- description: 'Event detail',
2918
- content: {
2919
- 'application/json': {
2920
- schema: resolver(GatewayEventItemSchema),
2921
- },
2922
- },
2923
- },
2924
- },
2925
- }),
2926
- zValidator('param', GatewayEventPathParamSchema),
2927
- zValidator(
2928
- 'query',
2929
- ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
2930
- ),
2931
- async (c) => {
2932
- return withGatewayAuth(c, async () => {
2933
- const { id } = c.req.valid('param');
2934
- const query = c.req.valid('query');
2935
- const target = resolveEventTarget({
2936
- id,
2937
- instances,
2938
- query,
2939
- });
2940
- if (!target.ok) {
2941
- return consoleTargetErrorResponse(c, target);
2942
- }
2943
-
2944
- const forwardQuery = sanitizeForwardQueryParams(
2945
- new URL(c.req.url).searchParams
2946
- );
2947
- const result = await fetchDownstreamJson({
2948
- c,
2949
- instance: target.instance,
2950
- path: `/events/${target.localEventId}`,
2951
- query: forwardQuery,
2952
- schema: ConsoleRequestEventSchema,
2953
- fetchImpl,
2954
- });
2955
-
2956
- if (!result.ok) {
2957
- return downstreamFailureResponse(c, result.failure);
2958
- }
2959
-
2960
- return c.json({
2961
- ...result.data,
2962
- instanceId: target.instance.instanceId,
2963
- federatedEventId: `${target.instance.instanceId}:${result.data.eventId}`,
2964
- localEventId: result.data.eventId,
2965
- });
2966
- });
2967
- }
2968
- );
2969
-
2970
- routes.get(
2971
- '/events/:id/payload',
2972
- describeConsoleGatewayRoute({
2973
- summary: 'Get merged event payload by federated id',
2974
- responses: {
2975
- 200: {
2976
- description: 'Event payload',
2977
- content: {
2978
- 'application/json': {
2979
- schema: resolver(GatewayEventPayloadSchema),
2980
- },
2981
- },
2982
- },
2983
- },
2984
- }),
2985
- zValidator('param', GatewayEventPathParamSchema),
2986
- zValidator(
2987
- 'query',
2988
- ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
2989
- ),
2990
- async (c) => {
2991
- return withGatewayAuth(c, async () => {
2992
- const { id } = c.req.valid('param');
2993
- const query = c.req.valid('query');
2994
- const target = resolveEventTarget({
2995
- id,
2996
- instances,
2997
- query,
2998
- });
2999
- if (!target.ok) {
3000
- return consoleTargetErrorResponse(c, target);
3001
- }
3002
-
3003
- const forwardQuery = sanitizeForwardQueryParams(
3004
- new URL(c.req.url).searchParams
3005
- );
3006
- const result = await fetchDownstreamJson({
3007
- c,
3008
- instance: target.instance,
3009
- path: `/events/${target.localEventId}/payload`,
3010
- query: forwardQuery,
3011
- schema: ConsoleRequestPayloadSchema,
3012
- fetchImpl,
3013
- });
3014
-
3015
- if (!result.ok) {
3016
- return downstreamFailureResponse(c, result.failure);
3017
- }
3018
-
3019
- return c.json({
3020
- ...result.data,
3021
- instanceId: target.instance.instanceId,
3022
- federatedEventId: `${target.instance.instanceId}:${target.localEventId}`,
3023
- localEventId: target.localEventId,
3024
- });
3025
- });
3026
- }
3027
- );
3028
-
3029
- return routes;
3030
- }
3031
-
3032
- function measureWebSocketMessageBytes(data: unknown): number {
3033
- if (typeof data === 'string') {
3034
- return new TextEncoder().encode(data).byteLength;
3035
- }
3036
- if (data instanceof ArrayBuffer) {
3037
- return data.byteLength;
3038
- }
3039
- if (ArrayBuffer.isView(data)) {
3040
- return data.byteLength;
3041
- }
3042
- if (typeof Blob !== 'undefined' && data instanceof Blob) {
3043
- return data.size;
3044
- }
3045
- return new TextEncoder().encode(String(data)).byteLength;
3046
- }