@lovrozagar/honey 0.6.8 → 0.7.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 (674) hide show
  1. package/README.md +514 -192
  2. package/bin/honey.js +25 -0
  3. package/dist/accepts.d.ts +7 -1
  4. package/dist/accepts.d.ts.map +1 -1
  5. package/dist/accepts.js +126 -0
  6. package/dist/accepts.js.map +1 -0
  7. package/dist/body-kind.d.ts +26 -0
  8. package/dist/body-kind.d.ts.map +1 -0
  9. package/dist/body-kind.js +70 -0
  10. package/dist/body-kind.js.map +1 -0
  11. package/dist/body-limit.d.ts +5 -1
  12. package/dist/body-limit.d.ts.map +1 -1
  13. package/dist/body-limit.js +126 -0
  14. package/dist/body-limit.js.map +1 -0
  15. package/dist/build/index.d.ts +19 -3
  16. package/dist/build/index.d.ts.map +1 -1
  17. package/dist/build/index.js +173 -0
  18. package/dist/build/index.js.map +1 -0
  19. package/dist/cli-args.d.ts +33 -0
  20. package/dist/cli-args.d.ts.map +1 -0
  21. package/dist/cli-args.js +102 -0
  22. package/dist/cli-args.js.map +1 -0
  23. package/dist/cli-go/auth.go +26 -0
  24. package/dist/cli-go/config.go +192 -0
  25. package/dist/cli-go/errors.go +75 -0
  26. package/dist/cli-go/multipart.go +79 -0
  27. package/dist/cli-go/output.go +163 -0
  28. package/dist/cli-go/root.go +30 -0
  29. package/dist/cli-go/stream.go +88 -0
  30. package/dist/cli-go/version.go +5 -0
  31. package/dist/cli.d.ts +1 -1
  32. package/dist/cli.js +209 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/client/defaults.js +48 -0
  35. package/dist/client/defaults.js.map +1 -0
  36. package/dist/client/error.d.ts +35 -0
  37. package/dist/client/error.d.ts.map +1 -1
  38. package/dist/client/error.js +71 -0
  39. package/dist/client/error.js.map +1 -0
  40. package/dist/client/http.d.ts +67 -8
  41. package/dist/client/http.d.ts.map +1 -1
  42. package/dist/client/http.js +571 -0
  43. package/dist/client/http.js.map +1 -0
  44. package/dist/client/index.d.ts +3 -2
  45. package/dist/client/index.d.ts.map +1 -1
  46. package/dist/client/index.js +95 -0
  47. package/dist/client/index.js.map +1 -0
  48. package/dist/client/path.d.ts +26 -0
  49. package/dist/client/path.d.ts.map +1 -0
  50. package/dist/client/path.js +163 -0
  51. package/dist/client/path.js.map +1 -0
  52. package/dist/client/redirect-policy.d.ts +12 -0
  53. package/dist/client/redirect-policy.d.ts.map +1 -0
  54. package/dist/client/redirect-policy.js +20 -0
  55. package/dist/client/redirect-policy.js.map +1 -0
  56. package/dist/client/sdk.d.ts +36 -0
  57. package/dist/client/sdk.d.ts.map +1 -1
  58. package/dist/client/sdk.js +292 -0
  59. package/dist/client/sdk.js.map +1 -0
  60. package/dist/client/sse.d.ts +13 -0
  61. package/dist/client/sse.d.ts.map +1 -1
  62. package/dist/client/sse.js +129 -0
  63. package/dist/client/sse.js.map +1 -0
  64. package/dist/client/types.js +2 -0
  65. package/dist/client/types.js.map +1 -0
  66. package/dist/client/ws.d.ts +9 -2
  67. package/dist/client/ws.d.ts.map +1 -1
  68. package/dist/client/ws.js +112 -0
  69. package/dist/client/ws.js.map +1 -0
  70. package/dist/client-go/errors.go +236 -0
  71. package/dist/client-go/export.go +44 -0
  72. package/dist/client-go/invalidation.go +371 -0
  73. package/dist/client-go/nullable.go +62 -0
  74. package/dist/client-go/realtime.go +354 -0
  75. package/dist/client-go/result.go +20 -0
  76. package/dist/client-go/runtime.go +975 -0
  77. package/dist/client-go/sse.go +159 -0
  78. package/dist/client-go/transport.go +495 -0
  79. package/dist/client-go/ws.go +128 -0
  80. package/dist/client-info.d.ts +36 -0
  81. package/dist/client-info.d.ts.map +1 -0
  82. package/dist/client-info.js +53 -0
  83. package/dist/client-info.js.map +1 -0
  84. package/dist/client-mcp/README.md +10 -0
  85. package/dist/client-mcp/server.ts +221 -0
  86. package/dist/client-mcp/types.ts +19 -0
  87. package/dist/client-python/_errors.py +115 -0
  88. package/dist/client-python/_invalidation.py +494 -0
  89. package/dist/client-python/_realtime.py +179 -0
  90. package/dist/client-python/_runtime.py +860 -0
  91. package/dist/client-python/_sse.py +155 -0
  92. package/dist/client-python/_transport.py +366 -0
  93. package/dist/client-python/_ws.py +159 -0
  94. package/dist/client-python/py.typed +0 -0
  95. package/dist/client-rust/errors.rs +220 -0
  96. package/dist/client-rust/invalidation.rs +412 -0
  97. package/dist/client-rust/invalidation_sync.rs +93 -0
  98. package/dist/client-rust/nullable.rs +17 -0
  99. package/dist/client-rust/realtime.rs +702 -0
  100. package/dist/client-rust/result.rs +22 -0
  101. package/dist/client-rust/runtime.rs +980 -0
  102. package/dist/client-rust/runtime_sync.rs +484 -0
  103. package/dist/client-rust/sse.rs +188 -0
  104. package/dist/client-rust/ws.rs +167 -0
  105. package/dist/codegen-go-cli.d.ts.map +1 -1
  106. package/dist/codegen-go-cli.js +964 -0
  107. package/dist/codegen-go-cli.js.map +1 -0
  108. package/dist/codegen-go.d.ts +15 -3
  109. package/dist/codegen-go.d.ts.map +1 -1
  110. package/dist/codegen-go.js +872 -0
  111. package/dist/codegen-go.js.map +1 -0
  112. package/dist/codegen-hash.js +9 -0
  113. package/dist/codegen-hash.js.map +1 -0
  114. package/dist/codegen-ir.d.ts +69 -2
  115. package/dist/codegen-ir.d.ts.map +1 -1
  116. package/dist/codegen-ir.js +661 -0
  117. package/dist/codegen-ir.js.map +1 -0
  118. package/dist/codegen-lang.d.ts +65 -0
  119. package/dist/codegen-lang.d.ts.map +1 -0
  120. package/dist/codegen-lang.js +526 -0
  121. package/dist/codegen-lang.js.map +1 -0
  122. package/dist/codegen-load.d.ts +31 -0
  123. package/dist/codegen-load.d.ts.map +1 -0
  124. package/dist/codegen-load.js +138 -0
  125. package/dist/codegen-load.js.map +1 -0
  126. package/dist/codegen-loaders.js +55 -0
  127. package/dist/codegen-loaders.js.map +1 -0
  128. package/dist/codegen-mcp.d.ts.map +1 -1
  129. package/dist/codegen-mcp.js +368 -0
  130. package/dist/codegen-mcp.js.map +1 -0
  131. package/dist/codegen-python.d.ts +13 -1
  132. package/dist/codegen-python.d.ts.map +1 -1
  133. package/dist/codegen-python.js +828 -0
  134. package/dist/codegen-python.js.map +1 -0
  135. package/dist/codegen-route-tree-intern.d.ts +15 -5
  136. package/dist/codegen-route-tree-intern.d.ts.map +1 -1
  137. package/dist/codegen-route-tree-intern.js +221 -0
  138. package/dist/codegen-route-tree-intern.js.map +1 -0
  139. package/dist/codegen-rust.d.ts.map +1 -1
  140. package/dist/codegen-rust.js +1293 -0
  141. package/dist/codegen-rust.js.map +1 -0
  142. package/dist/codegen-sanitize.d.ts.map +1 -1
  143. package/dist/codegen-sanitize.js +56 -0
  144. package/dist/codegen-sanitize.js.map +1 -0
  145. package/dist/codegen-schema-naming.d.ts +5 -0
  146. package/dist/codegen-schema-naming.d.ts.map +1 -1
  147. package/dist/codegen-schema-naming.js +150 -0
  148. package/dist/codegen-schema-naming.js.map +1 -0
  149. package/dist/codegen-sdk-model.d.ts +71 -0
  150. package/dist/codegen-sdk-model.d.ts.map +1 -0
  151. package/dist/codegen-sdk-model.js +179 -0
  152. package/dist/codegen-sdk-model.js.map +1 -0
  153. package/dist/codegen.d.ts +41 -23
  154. package/dist/codegen.d.ts.map +1 -1
  155. package/dist/codegen.js +4610 -0
  156. package/dist/codegen.js.map +1 -0
  157. package/dist/context.d.ts +36 -6
  158. package/dist/context.d.ts.map +1 -1
  159. package/dist/context.js +242 -0
  160. package/dist/context.js.map +1 -0
  161. package/dist/cookie-sign.d.ts +16 -2
  162. package/dist/cookie-sign.d.ts.map +1 -1
  163. package/dist/cookie-sign.js +104 -0
  164. package/dist/cookie-sign.js.map +1 -0
  165. package/dist/cookie.d.ts +12 -0
  166. package/dist/cookie.d.ts.map +1 -1
  167. package/dist/cookie.js +138 -0
  168. package/dist/cookie.js.map +1 -0
  169. package/dist/cors.d.ts +1 -0
  170. package/dist/cors.d.ts.map +1 -1
  171. package/dist/cors.js +87 -0
  172. package/dist/cors.js.map +1 -0
  173. package/dist/crypto.d.ts +6 -1
  174. package/dist/crypto.d.ts.map +1 -1
  175. package/dist/crypto.js +38 -0
  176. package/dist/crypto.js.map +1 -0
  177. package/dist/csrf.d.ts +19 -0
  178. package/dist/csrf.d.ts.map +1 -1
  179. package/dist/csrf.js +79 -0
  180. package/dist/csrf.js.map +1 -0
  181. package/dist/curl-logger.d.ts +14 -3
  182. package/dist/curl-logger.d.ts.map +1 -1
  183. package/dist/curl-logger.js +199 -0
  184. package/dist/curl-logger.js.map +1 -0
  185. package/dist/detect-runtime.js +13 -0
  186. package/dist/detect-runtime.js.map +1 -0
  187. package/dist/dict.d.ts +11 -0
  188. package/dist/dict.d.ts.map +1 -0
  189. package/dist/dict.js +13 -0
  190. package/dist/dict.js.map +1 -0
  191. package/dist/error.d.ts.map +1 -1
  192. package/dist/error.js +44 -0
  193. package/dist/error.js.map +1 -0
  194. package/dist/errors.js +39 -0
  195. package/dist/errors.js.map +1 -0
  196. package/dist/etag.d.ts +2 -0
  197. package/dist/etag.d.ts.map +1 -1
  198. package/dist/etag.js +82 -0
  199. package/dist/etag.js.map +1 -0
  200. package/dist/feature-detect.d.ts +25 -0
  201. package/dist/feature-detect.d.ts.map +1 -0
  202. package/dist/feature-detect.js +144 -0
  203. package/dist/feature-detect.js.map +1 -0
  204. package/dist/feature-load.js +36 -0
  205. package/dist/feature-load.js.map +1 -0
  206. package/dist/feature-slots.d.ts +44 -0
  207. package/dist/feature-slots.d.ts.map +1 -0
  208. package/dist/feature-slots.js +48 -0
  209. package/dist/feature-slots.js.map +1 -0
  210. package/dist/fetch-request.d.ts +10 -0
  211. package/dist/fetch-request.d.ts.map +1 -0
  212. package/dist/fetch-request.js +13 -0
  213. package/dist/fetch-request.js.map +1 -0
  214. package/dist/gen-process.d.ts +15 -0
  215. package/dist/gen-process.d.ts.map +1 -0
  216. package/dist/gen-process.js +43 -0
  217. package/dist/gen-process.js.map +1 -0
  218. package/dist/gen-write.d.ts +19 -0
  219. package/dist/gen-write.d.ts.map +1 -0
  220. package/dist/gen-write.js +120 -0
  221. package/dist/gen-write.js.map +1 -0
  222. package/dist/glob.d.ts +21 -0
  223. package/dist/glob.d.ts.map +1 -0
  224. package/dist/glob.js +115 -0
  225. package/dist/glob.js.map +1 -0
  226. package/dist/go-type-emitter.d.ts +18 -4
  227. package/dist/go-type-emitter.d.ts.map +1 -1
  228. package/dist/go-type-emitter.js +594 -0
  229. package/dist/go-type-emitter.js.map +1 -0
  230. package/dist/honey-response.d.ts.map +1 -1
  231. package/dist/honey-response.js +338 -0
  232. package/dist/honey-response.js.map +1 -0
  233. package/dist/i18n-slot.d.ts +8 -0
  234. package/dist/i18n-slot.d.ts.map +1 -1
  235. package/dist/i18n-slot.js +14 -0
  236. package/dist/i18n-slot.js.map +1 -0
  237. package/dist/i18n.d.ts +3 -0
  238. package/dist/i18n.d.ts.map +1 -1
  239. package/dist/i18n.js +250 -0
  240. package/dist/i18n.js.map +1 -0
  241. package/dist/index.d.ts +408 -148
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +2324 -0
  244. package/dist/index.js.map +1 -0
  245. package/dist/init.d.ts +3 -5
  246. package/dist/init.d.ts.map +1 -1
  247. package/dist/init.js +140 -0
  248. package/dist/init.js.map +1 -0
  249. package/dist/input.js +4 -0
  250. package/dist/input.js.map +1 -0
  251. package/dist/invalidate-check.d.ts +11 -0
  252. package/dist/invalidate-check.d.ts.map +1 -1
  253. package/dist/invalidate-check.js +163 -0
  254. package/dist/invalidate-check.js.map +1 -0
  255. package/dist/invoke-user.d.ts +35 -0
  256. package/dist/invoke-user.d.ts.map +1 -0
  257. package/dist/invoke-user.js +73 -0
  258. package/dist/invoke-user.js.map +1 -0
  259. package/dist/ip-restrict.d.ts +13 -1
  260. package/dist/ip-restrict.d.ts.map +1 -1
  261. package/dist/ip-restrict.js +60 -0
  262. package/dist/ip-restrict.js.map +1 -0
  263. package/dist/ip.d.ts +39 -0
  264. package/dist/ip.d.ts.map +1 -0
  265. package/dist/ip.js +209 -0
  266. package/dist/ip.js.map +1 -0
  267. package/dist/logger.d.ts.map +1 -1
  268. package/dist/logger.js +113 -0
  269. package/dist/logger.js.map +1 -0
  270. package/dist/media-type.d.ts +30 -0
  271. package/dist/media-type.d.ts.map +1 -0
  272. package/dist/media-type.js +97 -0
  273. package/dist/media-type.js.map +1 -0
  274. package/dist/meta-spec-merge.d.ts +32 -0
  275. package/dist/meta-spec-merge.d.ts.map +1 -0
  276. package/dist/meta-spec-merge.js +68 -0
  277. package/dist/meta-spec-merge.js.map +1 -0
  278. package/dist/meta-spec.d.ts +16 -3
  279. package/dist/meta-spec.d.ts.map +1 -1
  280. package/dist/meta-spec.js +535 -0
  281. package/dist/meta-spec.js.map +1 -0
  282. package/dist/middleware.d.ts +23 -6
  283. package/dist/middleware.d.ts.map +1 -1
  284. package/dist/middleware.js +253 -0
  285. package/dist/middleware.js.map +1 -0
  286. package/dist/node-request.d.ts +57 -12
  287. package/dist/node-request.d.ts.map +1 -1
  288. package/dist/node-request.js +407 -0
  289. package/dist/node-request.js.map +1 -0
  290. package/dist/node.d.ts +20 -3
  291. package/dist/node.d.ts.map +1 -1
  292. package/dist/node.js +396 -0
  293. package/dist/node.js.map +1 -0
  294. package/dist/openapi/collect.d.ts +6 -3
  295. package/dist/openapi/collect.d.ts.map +1 -1
  296. package/dist/openapi/collect.js +30 -0
  297. package/dist/openapi/collect.js.map +1 -0
  298. package/dist/openapi/docs-page.d.ts +42 -0
  299. package/dist/openapi/docs-page.d.ts.map +1 -0
  300. package/dist/openapi/docs-page.js +80 -0
  301. package/dist/openapi/docs-page.js.map +1 -0
  302. package/dist/openapi/document.d.ts +4 -0
  303. package/dist/openapi/document.d.ts.map +1 -1
  304. package/dist/openapi/document.js +785 -0
  305. package/dist/openapi/document.js.map +1 -0
  306. package/dist/openapi/json-schema-slot.d.ts +7 -0
  307. package/dist/openapi/json-schema-slot.d.ts.map +1 -1
  308. package/dist/openapi/json-schema-slot.js +38 -0
  309. package/dist/openapi/json-schema-slot.js.map +1 -0
  310. package/dist/openapi/register.d.ts.map +1 -1
  311. package/dist/openapi/register.js +20 -0
  312. package/dist/openapi/register.js.map +1 -0
  313. package/dist/openapi/scalar.d.ts +3 -1
  314. package/dist/openapi/scalar.d.ts.map +1 -1
  315. package/dist/openapi/scalar.js +21 -0
  316. package/dist/openapi/scalar.js.map +1 -0
  317. package/dist/openapi/serve.d.ts +9 -0
  318. package/dist/openapi/serve.d.ts.map +1 -0
  319. package/dist/openapi/serve.js +36 -0
  320. package/dist/openapi/serve.js.map +1 -0
  321. package/dist/openapi/spec-factory.d.ts +30 -1
  322. package/dist/openapi/spec-factory.d.ts.map +1 -1
  323. package/dist/openapi/spec-factory.js +52 -0
  324. package/dist/openapi/spec-factory.js.map +1 -0
  325. package/dist/openapi/spec.d.ts +14 -5
  326. package/dist/openapi/spec.d.ts.map +1 -1
  327. package/dist/openapi/spec.js +50 -0
  328. package/dist/openapi/spec.js.map +1 -0
  329. package/dist/openapi/swagger.d.ts +3 -1
  330. package/dist/openapi/swagger.d.ts.map +1 -1
  331. package/dist/openapi/swagger.js +22 -0
  332. package/dist/openapi/swagger.js.map +1 -0
  333. package/dist/pattern.d.ts +71 -0
  334. package/dist/pattern.d.ts.map +1 -0
  335. package/dist/pattern.js +239 -0
  336. package/dist/pattern.js.map +1 -0
  337. package/dist/peer.d.ts +17 -0
  338. package/dist/peer.d.ts.map +1 -0
  339. package/dist/peer.js +50 -0
  340. package/dist/peer.js.map +1 -0
  341. package/dist/plugin.d.ts +67 -16
  342. package/dist/plugin.d.ts.map +1 -1
  343. package/dist/plugin.js +649 -0
  344. package/dist/plugin.js.map +1 -0
  345. package/dist/powered-by.d.ts.map +1 -1
  346. package/dist/powered-by.js +11 -0
  347. package/dist/powered-by.js.map +1 -0
  348. package/dist/pretty-json.d.ts.map +1 -1
  349. package/dist/pretty-json.js +47 -0
  350. package/dist/pretty-json.js.map +1 -0
  351. package/dist/producer-stream.d.ts +47 -0
  352. package/dist/producer-stream.d.ts.map +1 -0
  353. package/dist/producer-stream.js +267 -0
  354. package/dist/producer-stream.js.map +1 -0
  355. package/dist/proxy.d.ts +47 -8
  356. package/dist/proxy.d.ts.map +1 -1
  357. package/dist/proxy.js +274 -0
  358. package/dist/proxy.js.map +1 -0
  359. package/dist/python-type-emitter.d.ts +14 -1
  360. package/dist/python-type-emitter.d.ts.map +1 -1
  361. package/dist/python-type-emitter.js +157 -0
  362. package/dist/python-type-emitter.js.map +1 -0
  363. package/dist/realtime/bus.d.ts +17 -1
  364. package/dist/realtime/bus.d.ts.map +1 -1
  365. package/dist/realtime/bus.js +112 -0
  366. package/dist/realtime/bus.js.map +1 -0
  367. package/dist/realtime/register.d.ts +3 -0
  368. package/dist/realtime/register.d.ts.map +1 -0
  369. package/dist/realtime/register.js +10 -0
  370. package/dist/realtime/register.js.map +1 -0
  371. package/dist/realtime/route.d.ts +65 -26
  372. package/dist/realtime/route.d.ts.map +1 -1
  373. package/dist/realtime/route.js +72 -0
  374. package/dist/realtime/route.js.map +1 -0
  375. package/dist/realtime/server.d.ts +43 -0
  376. package/dist/realtime/server.d.ts.map +1 -0
  377. package/dist/realtime/server.js +255 -0
  378. package/dist/realtime/server.js.map +1 -0
  379. package/dist/request-id.d.ts +7 -0
  380. package/dist/request-id.d.ts.map +1 -1
  381. package/dist/request-id.js +16 -0
  382. package/dist/request-id.js.map +1 -0
  383. package/dist/request-limits.d.ts +3 -0
  384. package/dist/request-limits.d.ts.map +1 -0
  385. package/dist/request-limits.js +3 -0
  386. package/dist/request-limits.js.map +1 -0
  387. package/dist/request-path.d.ts +33 -0
  388. package/dist/request-path.d.ts.map +1 -0
  389. package/dist/request-path.js +178 -0
  390. package/dist/request-path.js.map +1 -0
  391. package/dist/request-to-curl.d.ts +7 -1
  392. package/dist/request-to-curl.d.ts.map +1 -1
  393. package/dist/request-to-curl.js +48 -0
  394. package/dist/request-to-curl.js.map +1 -0
  395. package/dist/response.d.ts +25 -2
  396. package/dist/response.d.ts.map +1 -1
  397. package/dist/response.js +313 -0
  398. package/dist/response.js.map +1 -0
  399. package/dist/rust-type-emitter.d.ts +14 -2
  400. package/dist/rust-type-emitter.d.ts.map +1 -1
  401. package/dist/rust-type-emitter.js +463 -0
  402. package/dist/rust-type-emitter.js.map +1 -0
  403. package/dist/secure-headers.d.ts.map +1 -1
  404. package/dist/secure-headers.js +53 -0
  405. package/dist/secure-headers.js.map +1 -0
  406. package/dist/serve-register.d.ts +4 -0
  407. package/dist/serve-register.d.ts.map +1 -1
  408. package/dist/serve-register.js +9 -0
  409. package/dist/serve-register.js.map +1 -0
  410. package/dist/serve-slot.d.ts +3 -0
  411. package/dist/serve-slot.d.ts.map +1 -1
  412. package/dist/serve-slot.js +52 -0
  413. package/dist/serve-slot.js.map +1 -0
  414. package/dist/serve.d.ts +18 -1
  415. package/dist/serve.d.ts.map +1 -1
  416. package/dist/serve.js +157 -0
  417. package/dist/serve.js.map +1 -0
  418. package/dist/server-timing.d.ts.map +1 -1
  419. package/dist/server-timing.js +51 -0
  420. package/dist/server-timing.js.map +1 -0
  421. package/dist/static.d.ts +9 -0
  422. package/dist/static.d.ts.map +1 -1
  423. package/dist/static.js +102 -0
  424. package/dist/static.js.map +1 -0
  425. package/dist/telemetry/otel.d.ts +2 -0
  426. package/dist/telemetry/otel.d.ts.map +1 -1
  427. package/dist/telemetry/otel.js +135 -0
  428. package/dist/telemetry/otel.js.map +1 -0
  429. package/dist/testing.d.ts +12 -0
  430. package/dist/testing.d.ts.map +1 -1
  431. package/dist/testing.js +124 -0
  432. package/dist/testing.js.map +1 -0
  433. package/dist/timeout.d.ts +6 -0
  434. package/dist/timeout.d.ts.map +1 -1
  435. package/dist/timeout.js +36 -0
  436. package/dist/timeout.js.map +1 -0
  437. package/dist/tree.d.ts +133 -36
  438. package/dist/tree.d.ts.map +1 -1
  439. package/dist/tree.js +437 -0
  440. package/dist/tree.js.map +1 -0
  441. package/dist/trust.d.ts +21 -0
  442. package/dist/trust.d.ts.map +1 -0
  443. package/dist/trust.js +100 -0
  444. package/dist/trust.js.map +1 -0
  445. package/dist/ts-type-emitter.d.ts.map +1 -1
  446. package/dist/ts-type-emitter.js +150 -0
  447. package/dist/ts-type-emitter.js.map +1 -0
  448. package/dist/type-emitter.d.ts +16 -2
  449. package/dist/type-emitter.d.ts.map +1 -1
  450. package/dist/type-emitter.js +729 -0
  451. package/dist/type-emitter.js.map +1 -0
  452. package/dist/type-extractor.d.ts +8 -4
  453. package/dist/type-extractor.d.ts.map +1 -1
  454. package/dist/type-extractor.js +819 -0
  455. package/dist/type-extractor.js.map +1 -0
  456. package/dist/types.d.ts +7 -0
  457. package/dist/types.d.ts.map +1 -1
  458. package/dist/types.js +96 -0
  459. package/dist/types.js.map +1 -0
  460. package/dist/validation.d.ts +14 -2
  461. package/dist/validation.d.ts.map +1 -1
  462. package/dist/validation.js +335 -0
  463. package/dist/validation.js.map +1 -0
  464. package/dist/with-headers.d.ts +15 -0
  465. package/dist/with-headers.d.ts.map +1 -0
  466. package/dist/with-headers.js +45 -0
  467. package/dist/with-headers.js.map +1 -0
  468. package/dist/ws/bun.d.ts +11 -5
  469. package/dist/ws/bun.d.ts.map +1 -1
  470. package/dist/ws/bun.js +82 -0
  471. package/dist/ws/bun.js.map +1 -0
  472. package/dist/ws/cloudflare.d.ts +25 -3
  473. package/dist/ws/cloudflare.d.ts.map +1 -1
  474. package/dist/ws/cloudflare.js +117 -0
  475. package/dist/ws/cloudflare.js.map +1 -0
  476. package/dist/ws/deno.d.ts +4 -4
  477. package/dist/ws/deno.d.ts.map +1 -1
  478. package/dist/ws/deno.js +133 -0
  479. package/dist/ws/deno.js.map +1 -0
  480. package/dist/ws/node.d.ts +20 -3
  481. package/dist/ws/node.d.ts.map +1 -1
  482. package/dist/ws/node.js +173 -0
  483. package/dist/ws/node.js.map +1 -0
  484. package/dist/ws/session.d.ts +11 -0
  485. package/dist/ws/session.d.ts.map +1 -0
  486. package/dist/ws/session.js +60 -0
  487. package/dist/ws/session.js.map +1 -0
  488. package/dist/ws/shared.d.ts +65 -0
  489. package/dist/ws/shared.d.ts.map +1 -0
  490. package/dist/ws/shared.js +103 -0
  491. package/dist/ws/shared.js.map +1 -0
  492. package/dist/ws-origin.d.ts +20 -0
  493. package/dist/ws-origin.d.ts.map +1 -0
  494. package/dist/ws-origin.js +41 -0
  495. package/dist/ws-origin.js.map +1 -0
  496. package/dist/yaml.d.ts +15 -1
  497. package/dist/yaml.d.ts.map +1 -1
  498. package/dist/yaml.js +101 -0
  499. package/dist/yaml.js.map +1 -0
  500. package/docs/invalidate-spec.md +13 -1
  501. package/docs/meta-spec.md +36 -12
  502. package/docs/packaging.md +43 -24
  503. package/docs/regression-matrix/README.md +32 -0
  504. package/docs/regression-matrix/review.md +44 -0
  505. package/docs/regression-matrix/ws1-3.md +97 -0
  506. package/docs/regression-matrix/ws4-8.md +107 -0
  507. package/docs/regression-matrix/ws9-12.md +266 -0
  508. package/docs/sdk.md +102 -52
  509. package/package.json +183 -52
  510. package/src/accepts.ts +91 -33
  511. package/src/body-kind.ts +67 -0
  512. package/src/body-limit.ts +102 -87
  513. package/src/build/index.ts +104 -54
  514. package/src/cli-args.ts +123 -0
  515. package/src/cli-go/auth.go +12 -1
  516. package/src/cli-go/config.go +29 -2
  517. package/src/cli-go/errors.go +28 -10
  518. package/src/cli-go/multipart.go +51 -27
  519. package/src/cli-go/output.go +58 -4
  520. package/src/cli-go/root.go +1 -1
  521. package/src/cli-go/version.go +2 -2
  522. package/src/cli.ts +154 -148
  523. package/src/client/error.ts +56 -1
  524. package/src/client/http.ts +479 -256
  525. package/src/client/index.ts +55 -17
  526. package/src/client/path.ts +169 -0
  527. package/src/client/redirect-policy.ts +19 -0
  528. package/src/client/sdk.ts +180 -101
  529. package/src/client/sse.ts +92 -66
  530. package/src/client/ws.ts +67 -31
  531. package/src/client-go/errors.go +41 -12
  532. package/src/client-go/export.go +44 -0
  533. package/src/client-go/invalidation.go +52 -54
  534. package/src/client-go/nullable.go +62 -0
  535. package/src/client-go/realtime.go +147 -110
  536. package/src/client-go/runtime.go +646 -88
  537. package/src/client-go/sse.go +104 -94
  538. package/src/client-go/transport.go +67 -27
  539. package/src/client-go/ws.go +49 -25
  540. package/src/client-info.ts +79 -0
  541. package/src/client-mcp/server.ts +133 -3
  542. package/src/client-python/_invalidation.py +207 -291
  543. package/src/client-python/_realtime.py +63 -40
  544. package/src/client-python/_runtime.py +501 -119
  545. package/src/client-python/_sse.py +130 -74
  546. package/src/client-python/_transport.py +37 -9
  547. package/src/client-python/_ws.py +50 -22
  548. package/src/client-rust/errors.rs +21 -1
  549. package/src/client-rust/invalidation.rs +202 -231
  550. package/src/client-rust/invalidation_sync.rs +16 -229
  551. package/src/client-rust/nullable.rs +17 -0
  552. package/src/client-rust/realtime.rs +217 -217
  553. package/src/client-rust/runtime.rs +602 -177
  554. package/src/client-rust/runtime_sync.rs +169 -145
  555. package/src/client-rust/sse.rs +142 -121
  556. package/src/client-rust/ws.rs +61 -37
  557. package/src/codegen-go-cli.ts +607 -728
  558. package/src/codegen-go.ts +710 -770
  559. package/src/codegen-ir.ts +381 -75
  560. package/src/codegen-lang.ts +517 -0
  561. package/src/codegen-load.ts +150 -0
  562. package/src/codegen-mcp.ts +207 -108
  563. package/src/codegen-python.ts +684 -816
  564. package/src/codegen-route-tree-intern.ts +51 -25
  565. package/src/codegen-rust.ts +946 -1240
  566. package/src/codegen-sanitize.ts +0 -4
  567. package/src/codegen-schema-naming.ts +19 -6
  568. package/src/codegen-sdk-model.ts +228 -0
  569. package/src/codegen.ts +1488 -1140
  570. package/src/context.ts +130 -11
  571. package/src/cookie-sign.ts +85 -16
  572. package/src/cookie.ts +66 -11
  573. package/src/cors.ts +55 -54
  574. package/src/crypto.ts +27 -5
  575. package/src/csrf.ts +57 -25
  576. package/src/curl-logger.ts +155 -114
  577. package/src/dict.ts +12 -0
  578. package/src/error.ts +4 -2
  579. package/src/etag.ts +58 -34
  580. package/src/feature-detect.ts +147 -0
  581. package/src/feature-slots.ts +70 -0
  582. package/src/fetch-request.ts +13 -0
  583. package/src/gen-process.ts +42 -0
  584. package/src/gen-write.ts +127 -0
  585. package/src/glob.ts +109 -0
  586. package/src/go-type-emitter.ts +406 -155
  587. package/src/honey-response.ts +36 -9
  588. package/src/i18n-slot.ts +10 -0
  589. package/src/i18n.ts +98 -23
  590. package/src/index.ts +1887 -1756
  591. package/src/init.ts +40 -28
  592. package/src/invalidate-check.ts +42 -4
  593. package/src/invoke-user.ts +95 -0
  594. package/src/ip-restrict.ts +47 -161
  595. package/src/ip.ts +194 -0
  596. package/src/logger.ts +65 -36
  597. package/src/media-type.ts +104 -0
  598. package/src/meta-spec-merge.ts +80 -0
  599. package/src/meta-spec.ts +0 -0
  600. package/src/middleware.ts +112 -12
  601. package/src/node-request.ts +242 -52
  602. package/src/node.ts +318 -75
  603. package/src/openapi/collect.ts +19 -44
  604. package/src/openapi/docs-page.ts +89 -0
  605. package/src/openapi/document.ts +503 -224
  606. package/src/openapi/json-schema-slot.ts +40 -0
  607. package/src/openapi/register.ts +7 -2
  608. package/src/openapi/scalar.ts +11 -10
  609. package/src/openapi/serve.ts +39 -0
  610. package/src/openapi/spec-factory.ts +50 -1
  611. package/src/openapi/spec.ts +53 -30
  612. package/src/openapi/swagger.ts +12 -11
  613. package/src/pattern.ts +245 -0
  614. package/src/peer.ts +53 -0
  615. package/src/plugin.ts +270 -185
  616. package/src/powered-by.ts +2 -2
  617. package/src/pretty-json.ts +25 -9
  618. package/src/producer-stream.ts +296 -0
  619. package/src/proxy.ts +253 -55
  620. package/src/python-type-emitter.ts +68 -56
  621. package/src/realtime/bus.ts +56 -9
  622. package/src/realtime/register.ts +13 -0
  623. package/src/realtime/route.ts +129 -80
  624. package/src/realtime/server.ts +298 -0
  625. package/src/request-id.ts +13 -3
  626. package/src/request-limits.ts +2 -0
  627. package/src/request-path.ts +165 -0
  628. package/src/request-to-curl.ts +28 -7
  629. package/src/response.ts +143 -103
  630. package/src/rust-type-emitter.ts +265 -170
  631. package/src/secure-headers.ts +8 -4
  632. package/src/serve-register.ts +4 -0
  633. package/src/serve-slot.ts +38 -0
  634. package/src/serve.ts +113 -25
  635. package/src/server-timing.ts +11 -7
  636. package/src/static.ts +85 -32
  637. package/src/telemetry/otel.ts +29 -7
  638. package/src/testing.ts +76 -14
  639. package/src/timeout.ts +15 -7
  640. package/src/tree.ts +405 -333
  641. package/src/trust.ts +117 -0
  642. package/src/ts-type-emitter.ts +18 -12
  643. package/src/type-emitter.ts +227 -89
  644. package/src/type-extractor.ts +603 -423
  645. package/src/types.ts +8 -0
  646. package/src/validation.ts +221 -96
  647. package/src/with-headers.ts +45 -0
  648. package/src/ws/bun.ts +53 -15
  649. package/src/ws/cloudflare.ts +81 -19
  650. package/src/ws/deno.ts +124 -36
  651. package/src/ws/node.ts +173 -79
  652. package/src/ws/session.ts +73 -0
  653. package/src/ws/shared.ts +140 -0
  654. package/src/ws-origin.ts +53 -0
  655. package/src/yaml.ts +45 -22
  656. package/dist/client-mcp/server.d.ts +0 -10
  657. package/dist/client-mcp/server.d.ts.map +0 -1
  658. package/dist/client-mcp/types.d.ts +0 -16
  659. package/dist/client-mcp/types.d.ts.map +0 -1
  660. package/dist/cloudflare/realtime.d.ts +0 -30
  661. package/dist/cloudflare/realtime.d.ts.map +0 -1
  662. package/dist/realtime/buffer.d.ts +0 -18
  663. package/dist/realtime/buffer.d.ts.map +0 -1
  664. package/dist/realtime/longpoll.d.ts +0 -23
  665. package/dist/realtime/longpoll.d.ts.map +0 -1
  666. package/dist/realtime/protocol.d.ts +0 -50
  667. package/dist/realtime/protocol.d.ts.map +0 -1
  668. package/dist/realtime/runtime.d.ts +0 -74
  669. package/dist/realtime/runtime.d.ts.map +0 -1
  670. package/src/cloudflare/realtime.ts +0 -234
  671. package/src/realtime/buffer.ts +0 -101
  672. package/src/realtime/longpoll.ts +0 -148
  673. package/src/realtime/protocol.ts +0 -128
  674. package/src/realtime/runtime.ts +0 -427
@@ -1,11 +1,34 @@
1
+ /**
2
+ * In-process topic bus behind `app.realtime()`. Topics are opaque keys here; the realtime server
3
+ * namespaces them per route before they reach the bus. Each publish serializes its payload once and
4
+ * hands every subscriber the same JSON text frame.
5
+ */
1
6
  export interface RealtimeBus {
2
7
  subscribe(connId: string, topic: string): void
3
8
  unsubscribe(connId: string, topic: string): void
4
9
  unsubscribeAll(connId: string): void
10
+ /** Serialize `data` once and deliver the frame to every subscriber of `topic`. Throws when `data` is not JSON-serializable. */
5
11
  publish(topic: string, data: unknown): void
6
- onMessage(connId: string, handler: (data: unknown) => void): void
12
+ /** Deliver an already-serialized frame to every subscriber of `topic`. */
13
+ publishFrame(topic: string, frame: string): void
14
+ onMessage(connId: string, handler: (frame: string) => void): void
7
15
  removeHandler(connId: string): void
8
16
  presence(topic: string): string[]
17
+ /** Number of topics `connId` is subscribed to. */
18
+ topicCount(connId: string): number
19
+ isSubscribed(connId: string, topic: string): boolean
20
+ }
21
+
22
+ /**
23
+ * The wire format of the shipped bus: every frame, in both directions, is one JSON text.
24
+ * Throws a `TypeError` for values JSON cannot represent (`undefined`, functions, symbols, BigInt, cycles).
25
+ */
26
+ export function encodeRealtimeFrame(data: unknown): string {
27
+ const frame = JSON.stringify(data) as string | undefined
28
+ if (frame === undefined) {
29
+ throw new TypeError(`realtime payload is not JSON-serializable (got ${typeof data})`)
30
+ }
31
+ return frame
9
32
  }
10
33
 
11
34
  export function createBus(): RealtimeBus {
@@ -13,8 +36,8 @@ export function createBus(): RealtimeBus {
13
36
  const topicSubs = new Map<string, Set<string>>()
14
37
  /* connId -> set of topics it is subscribed to (reverse lookup for unsubscribeAll) */
15
38
  const connTopics = new Map<string, Set<string>>()
16
- /* connId -> message handler */
17
- const handlers = new Map<string, (data: unknown) => void>()
39
+ /* connId -> frame sink */
40
+ const handlers = new Map<string, (frame: string) => void>()
18
41
 
19
42
  function subscribe(connId: string, topic: string): void {
20
43
  let subs = topicSubs.get(topic)
@@ -60,22 +83,27 @@ export function createBus(): RealtimeBus {
60
83
  connTopics.delete(connId)
61
84
  }
62
85
 
63
- function publish(topic: string, data: unknown): void {
86
+ function publishFrame(topic: string, frame: string): void {
64
87
  const subs = topicSubs.get(topic)
65
88
  if (!subs) return
66
89
 
67
- for (const connId of subs) {
90
+ /* snapshot: a sink that closes its connection unsubscribes while we iterate */
91
+ for (const connId of Array.from(subs)) {
68
92
  const handler = handlers.get(connId)
69
93
  if (!handler) continue
70
94
  try {
71
- handler(data)
95
+ handler(frame)
72
96
  } catch {
73
- /* swallow — error isolation: one broken handler must not block others */
97
+ /* sinks are internal and contain their own errors; one must never block the others */
74
98
  }
75
99
  }
76
100
  }
77
101
 
78
- function onMessage(connId: string, handler: (data: unknown) => void): void {
102
+ function publish(topic: string, data: unknown): void {
103
+ publishFrame(topic, encodeRealtimeFrame(data))
104
+ }
105
+
106
+ function onMessage(connId: string, handler: (frame: string) => void): void {
79
107
  handlers.set(connId, handler)
80
108
  }
81
109
 
@@ -89,5 +117,24 @@ export function createBus(): RealtimeBus {
89
117
  return [...subs]
90
118
  }
91
119
 
92
- return { onMessage, presence, publish, removeHandler, subscribe, unsubscribe, unsubscribeAll }
120
+ function topicCount(connId: string): number {
121
+ return connTopics.get(connId)?.size ?? 0
122
+ }
123
+
124
+ function isSubscribed(connId: string, topic: string): boolean {
125
+ return connTopics.get(connId)?.has(topic) ?? false
126
+ }
127
+
128
+ return {
129
+ isSubscribed,
130
+ onMessage,
131
+ presence,
132
+ publish,
133
+ publishFrame,
134
+ removeHandler,
135
+ subscribe,
136
+ topicCount,
137
+ unsubscribe,
138
+ unsubscribeAll,
139
+ }
93
140
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `import "@lovrozagar/honey/realtime"` makes `app.realtime()` available. The core never
3
+ * imports the realtime server, so an app without realtime routes does not bundle it.
4
+ */
5
+ import { registerFeature } from "../feature-slots.ts"
6
+ import { createBus } from "./bus.ts"
7
+ import { resolveRealtimeConfig } from "./route.ts"
8
+ import { createRealtimePublisher, createRealtimeSession } from "./server.ts"
9
+
10
+ registerFeature("realtime", { createBus, createRealtimePublisher, createRealtimeSession, resolveRealtimeConfig })
11
+
12
+ export type { ConnContext, RealtimeLimits, RealtimeRouteOpts } from "./route.ts"
13
+ export type { RealtimePublisher } from "./server.ts"
@@ -1,104 +1,153 @@
1
+ import { validateOriginPolicy, type WSOriginPolicy } from "../ws-origin.ts"
2
+
3
+ /**
4
+ * One realtime connection as the route handler sees it. Only the WebSocket transport ships.
5
+ * Every frame, in both directions, is one JSON text (see the README's realtime wire format).
6
+ */
1
7
  export type ConnContext = {
2
8
  readonly id: string
9
+ /** What the route's `identify(ctx)` returned, else `null`. */
3
10
  readonly userId: string | null
4
- readonly transport: "ws" | "sse" | "longpoll"
11
+ readonly transport: "ws"
12
+ /** `true` once the connection closed (or `close()` was called). `join` and `send` are then no-ops. */
13
+ readonly closed: boolean
5
14
  readonly state: Record<string, unknown>
15
+ /** Subscribe to `topic` in this route's namespace. Throws past `limits.maxTopics`. */
6
16
  join(topic: string): void
7
17
  leave(topic: string): void
18
+ /** Send one JSON frame to this connection. Throws when `payload` is not JSON-serializable. */
8
19
  send(payload: unknown): void
20
+ /** Send one JSON frame to every connection on `topic` in this route's namespace (this one included). */
9
21
  publish(topic: string, payload: unknown): void
22
+ /** Close with code 1000. A reason longer than 123 UTF-8 bytes is truncated. */
10
23
  close(reason?: string): void
11
- on(event: "message", handler: (payload: unknown) => void): void
12
- on(event: "close", handler: (reason: string) => void): void
13
- readonly _handlers: { message: ((payload: unknown) => void) | null; close: ((reason: string) => void) | null }
24
+ on(event: "message", handler: (payload: unknown) => void | Promise<void>): void
25
+ on(event: "close", handler: (reason: string) => void | Promise<void>): void
26
+ }
27
+
28
+ export type RealtimeLimits = {
29
+ /** Topics one connection may join. Default 128. */
30
+ maxTopics?: number
31
+ /** Largest inbound text frame in UTF-8 bytes; larger frames close the connection with 1009. Default 1 MiB. */
32
+ maxFrameBytes?: number
33
+ /**
34
+ * Inbound frames waiting for a handler (a slow async `message` handler, or none attached yet).
35
+ * Past this the connection closes with 1008. Default 64.
36
+ */
37
+ maxPendingFrames?: number
38
+ /**
39
+ * Outbound bytes the runtime may hold for a client that is not reading, where the runtime reports
40
+ * it (`bufferedAmount`). Past this `slowConsumer` applies. Default 4 MiB.
41
+ */
42
+ maxBufferedBytes?: number
43
+ /** `"close"` (default) closes a slow consumer with 1013; `"drop"` drops frames until it catches up. */
44
+ slowConsumer?: "close" | "drop"
14
45
  }
15
46
 
16
- export type RealtimeRouteOpts = {
47
+ /** `C` is the request context of the handle `realtime()` is called on. */
48
+ export type RealtimeRouteOpts<C = unknown> = {
17
49
  use?: Array<(ctx: unknown, next: () => Promise<Response>) => Promise<Response>>
18
- reconnectBuffer?: number
19
- handler: (c: unknown, conn: ConnContext) => void | Promise<void>
50
+ /**
51
+ * Topic namespace. Connections only see topics of their own namespace. Defaults to the route's
52
+ * full path pattern, so two realtime routes never share topics unless they name the same namespace.
53
+ */
54
+ namespace?: string
55
+ /** Runs after the route's middleware, before the upgrade. Its result becomes `conn.userId`; a throw rejects the upgrade. */
56
+ identify?: (ctx: C) => string | null | undefined | Promise<string | null | undefined>
57
+ /** Receives every error thrown by `handler` and the `message`/`close` handlers. Defaults to the app logger. */
58
+ onError?: (error: unknown, conn: ConnContext) => void | Promise<void>
59
+ limits?: RealtimeLimits
60
+ /**
61
+ * Browser origins allowed to connect: `"*"`, a list (`https://app.example.com`), or a
62
+ * predicate. Same-origin always passes. Unset, a cross-origin upgrade that carries `Cookie`
63
+ * or `Authorization` gets 403.
64
+ */
65
+ allowedOrigins?: WSOriginPolicy
66
+ handler: (c: C, conn: ConnContext) => void | Promise<void>
20
67
  }
21
68
 
22
- export type RealtimeRouteHandler = {
69
+ /** A realtime route after validation, with every default applied. */
70
+ export type RealtimeConfig = {
71
+ allowedOrigins: WSOriginPolicy | null
23
72
  handler: RealtimeRouteOpts["handler"]
24
- middlewares: RealtimeRouteOpts["use"]
25
- reconnectBuffer: number
73
+ identify: NonNullable<RealtimeRouteOpts["identify"]> | null
74
+ limits: Required<RealtimeLimits>
75
+ namespace: string
76
+ /** false when `namespace` is the default (the route's path), which follows the route when mounted */
77
+ namespaceExplicit: boolean
78
+ onError: NonNullable<RealtimeRouteOpts["onError"]> | null
26
79
  path: string
27
80
  }
28
81
 
29
- export function createConnContext(opts: {
30
- id: string
31
- userId: string | null
32
- transport: "ws" | "sse" | "longpoll"
33
- bus: {
34
- subscribe(connId: string, topic: string): void
35
- unsubscribe(connId: string, topic: string): void
36
- unsubscribeAll(connId: string): void
37
- publish(topic: string, data: unknown): void
38
- }
39
- sendFn: (payload: unknown) => void
40
- closeFn: (reason?: string) => void
41
- }): ConnContext {
42
- const state: Record<string, unknown> = {}
43
- const handlers: { message: ((payload: unknown) => void) | null; close: ((reason: string) => void) | null } = {
44
- close: null,
45
- message: null,
46
- }
47
-
48
- const conn: ConnContext = Object.create(null)
49
-
50
- Object.defineProperty(conn, "id", { enumerable: true, value: opts.id, writable: false })
51
- Object.defineProperty(conn, "userId", { enumerable: true, value: opts.userId, writable: false })
52
- Object.defineProperty(conn, "transport", { enumerable: true, value: opts.transport, writable: false })
53
- Object.defineProperty(conn, "state", { enumerable: true, get: () => state })
54
- Object.defineProperty(conn, "_handlers", { enumerable: false, get: () => handlers })
55
-
56
- Object.defineProperty(conn, "join", {
57
- enumerable: true,
58
- value(topic: string) {
59
- opts.bus.subscribe(opts.id, topic)
60
- },
61
- })
62
-
63
- Object.defineProperty(conn, "leave", {
64
- enumerable: true,
65
- value(topic: string) {
66
- opts.bus.unsubscribe(opts.id, topic)
67
- },
68
- })
69
-
70
- Object.defineProperty(conn, "send", {
71
- enumerable: true,
72
- value(payload: unknown) {
73
- opts.sendFn(payload)
74
- },
75
- })
82
+ const DEFAULT_LIMITS: Required<RealtimeLimits> = {
83
+ maxBufferedBytes: 4 * 1024 * 1024,
84
+ maxFrameBytes: 1024 * 1024,
85
+ maxPendingFrames: 64,
86
+ maxTopics: 128,
87
+ slowConsumer: "close",
88
+ }
76
89
 
77
- Object.defineProperty(conn, "publish", {
78
- enumerable: true,
79
- value(topic: string, payload: unknown) {
80
- opts.bus.publish(topic, payload)
81
- },
82
- })
90
+ function positiveInt(name: string, value: unknown, fallback: number): number {
91
+ if (value === undefined) return fallback
92
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
93
+ throw new TypeError(`realtime limits.${name} must be a positive integer, got ${String(value)}`)
94
+ }
95
+ return value
96
+ }
83
97
 
84
- Object.defineProperty(conn, "close", {
85
- enumerable: true,
86
- value(reason?: string) {
87
- opts.bus.unsubscribeAll(opts.id)
88
- opts.closeFn(reason)
98
+ /** Validate `app.realtime()` options. Options the shipped bus does not implement are rejected, not ignored. */
99
+ export function resolveRealtimeConfig<C>(path: string, routeOpts: RealtimeRouteOpts<C>): RealtimeConfig {
100
+ const opts = routeOpts as unknown as RealtimeRouteOpts
101
+ if (typeof opts?.handler !== "function") {
102
+ throw new TypeError(`app.realtime("${path}"): handler must be a function`)
103
+ }
104
+ const legacy = opts as { reconnectBuffer?: unknown; transports?: unknown }
105
+ if (legacy.reconnectBuffer !== undefined) {
106
+ throw new TypeError(
107
+ `app.realtime("${path}"): reconnectBuffer is not supported. Realtime ships the WebSocket transport without resume; remove the option.`,
108
+ )
109
+ }
110
+ if (legacy.transports !== undefined) {
111
+ throw new TypeError(`app.realtime("${path}"): only the WebSocket transport ships; remove transports.`)
112
+ }
113
+ if (opts.namespace !== undefined && (typeof opts.namespace !== "string" || opts.namespace === "")) {
114
+ throw new TypeError(`app.realtime("${path}"): namespace must be a non-empty string`)
115
+ }
116
+ const l = opts.limits ?? {}
117
+ if (l.slowConsumer !== undefined && l.slowConsumer !== "close" && l.slowConsumer !== "drop") {
118
+ throw new TypeError(`realtime limits.slowConsumer must be "close" or "drop"`)
119
+ }
120
+ return {
121
+ allowedOrigins: opts.allowedOrigins === undefined ? null : validateOriginPolicy(opts.allowedOrigins),
122
+ handler: opts.handler,
123
+ identify: opts.identify ?? null,
124
+ limits: {
125
+ maxBufferedBytes: positiveInt("maxBufferedBytes", l.maxBufferedBytes, DEFAULT_LIMITS.maxBufferedBytes),
126
+ maxFrameBytes: positiveInt("maxFrameBytes", l.maxFrameBytes, DEFAULT_LIMITS.maxFrameBytes),
127
+ maxPendingFrames: positiveInt("maxPendingFrames", l.maxPendingFrames, DEFAULT_LIMITS.maxPendingFrames),
128
+ maxTopics: positiveInt("maxTopics", l.maxTopics, DEFAULT_LIMITS.maxTopics),
129
+ slowConsumer: l.slowConsumer ?? DEFAULT_LIMITS.slowConsumer,
89
130
  },
90
- })
131
+ namespace: opts.namespace ?? path,
132
+ namespaceExplicit: opts.namespace !== undefined,
133
+ onError: opts.onError ?? null,
134
+ path,
135
+ }
136
+ }
91
137
 
92
- Object.defineProperty(conn, "on", {
93
- enumerable: true,
94
- value(event: "message" | "close", handler: (...args: unknown[]) => void) {
95
- if (event === "message") {
96
- handlers.message = handler as (payload: unknown) => void
97
- } else {
98
- handlers.close = handler as (reason: string) => void
99
- }
100
- },
101
- })
138
+ const encoder = new TextEncoder()
102
139
 
103
- return conn
140
+ /** Cut `reason` to at most `max` UTF-8 bytes without splitting a code point. */
141
+ export function truncateUtf8(reason: string, max: number): string {
142
+ if (reason.length * 3 <= max) return reason
143
+ if (encoder.encode(reason).byteLength <= max) return reason
144
+ let bytes = 0
145
+ let out = ""
146
+ for (const ch of reason) {
147
+ const n = encoder.encode(ch).byteLength
148
+ if (bytes + n > max) break
149
+ bytes += n
150
+ out += ch
151
+ }
152
+ return out
104
153
  }
@@ -0,0 +1,298 @@
1
+ import { invokeUser } from "../invoke-user.ts"
2
+ import type { WSContext, WSHandler } from "../ws/cloudflare.ts"
3
+ import { encodeRealtimeFrame } from "./bus.ts"
4
+ import type { RealtimeBus } from "./bus.ts"
5
+ import {
6
+ bufferedAmountOf,
7
+ CLOSE_INTERNAL,
8
+ CLOSE_NORMAL,
9
+ CLOSE_POLICY,
10
+ CLOSE_TOO_BIG,
11
+ CLOSE_TRY_AGAIN,
12
+ exceedsPayload,
13
+ MAX_CLOSE_REASON,
14
+ } from "../ws/shared.ts"
15
+ import { truncateUtf8 } from "./route.ts"
16
+ import type { ConnContext, RealtimeConfig } from "./route.ts"
17
+
18
+ type Logger = {
19
+ error?: (obj: Record<string, unknown>, msg?: string) => void
20
+ warn?: (obj: Record<string, unknown>, msg?: string) => void
21
+ }
22
+
23
+ /** `ctx.realtime` — publish into a realtime namespace from any request. */
24
+ export type RealtimePublisher = {
25
+ /**
26
+ * Publish to `topic` in the app's only realtime namespace. Throws when the app has several
27
+ * namespaces; use `namespace(name).publish()` then.
28
+ */
29
+ publish(topic: string, data: unknown): void
30
+ /** The publisher for one namespace (a route's `namespace` option, or its full path pattern). */
31
+ namespace(name: string): { publish(topic: string, data: unknown): void }
32
+ }
33
+
34
+ const SEP = "\u0000"
35
+
36
+ /** The bus key of `topic` in namespace `ns`. Namespaces never see each other's topics. */
37
+ export function topicKey(ns: string, topic: string): string {
38
+ return ns + SEP + topic
39
+ }
40
+
41
+ export function createRealtimePublisher(bus: RealtimeBus, namespaces: () => Iterable<string>): RealtimePublisher {
42
+ const forNs = (ns: string) => ({
43
+ publish(topic: string, data: unknown) {
44
+ bus.publish(topicKey(ns, String(topic)), data)
45
+ },
46
+ })
47
+ return {
48
+ namespace(name: string) {
49
+ return forNs(String(name))
50
+ },
51
+ publish(topic: string, data: unknown) {
52
+ const all = new Set(namespaces())
53
+ if (all.size !== 1) {
54
+ throw new Error(
55
+ all.size === 0
56
+ ? "ctx.realtime.publish: the app has no realtime routes"
57
+ : `ctx.realtime.publish: the app has ${all.size} realtime namespaces (${[...all].join(", ")}); use ctx.realtime.namespace(name).publish(topic, data)`,
58
+ )
59
+ }
60
+ forNs(all.values().next().value as string).publish(topic, data)
61
+ },
62
+ }
63
+ }
64
+
65
+ export type RealtimeSession = {
66
+ /** The handler to pass to the runtime's WebSocket adapter. */
67
+ handler: WSHandler<unknown>
68
+ /** Attach the socket and start the connection (idempotent). Adapters that return an open socket from `upgrade()` call this right away; others get it from `onOpen`. */
69
+ attach(ws: WSContext<unknown>): void
70
+ }
71
+
72
+ /**
73
+ * One realtime connection: binds the socket to the bus and runs the route's callbacks on one ordered
74
+ * queue — `handler`, then each inbound frame, then `close` — each awaited. Every callback is
75
+ * contained: a throw or rejection goes to the route's `onError` (or the app logger) and never escapes
76
+ * into the runtime, so it can neither crash the process nor poison the queue.
77
+ */
78
+ export function createRealtimeSession(opts: {
79
+ bus: RealtimeBus
80
+ config: RealtimeConfig
81
+ ctx: unknown
82
+ log: Logger | undefined
83
+ userId: string | null
84
+ }): RealtimeSession {
85
+ const { bus, config, ctx, log, userId } = opts
86
+ const { limits, namespace } = config
87
+ const connId = crypto.randomUUID()
88
+ const state: Record<string, unknown> = {}
89
+
90
+ let socket: WSContext<unknown> | null = null
91
+ let closed = false
92
+ let tornDown = false
93
+ let closeRan = false
94
+ let onMessageFn: ((payload: unknown) => void | Promise<void>) | null = null
95
+ let onCloseFn: ((reason: string) => void | Promise<void>) | null = null
96
+
97
+ /* ordered queue: open → messages → close. Every inbound frame waits in `inbox` in arrival
98
+ * order, including frames that beat the open or the `message` handler; a drain task delivers
99
+ * them, so a frame never overtakes one that arrived before it. */
100
+ let queue: Promise<void> = Promise.resolve()
101
+ const inbox: unknown[] = []
102
+
103
+ /* the same containment as WebSocket callbacks: onError gets the error, an onError that
104
+ * throws is logged and never invoked again for its own error, nothing reaches the runtime */
105
+ const invoke = (phase: string, fn: () => void | Promise<void>): Promise<boolean> =>
106
+ invokeUser(fn, {
107
+ fields: { connId, namespace, route: config.path },
108
+ log,
109
+ onError: config.onError ? (err) => config.onError?.(err, conn) : null,
110
+ phase,
111
+ })
112
+
113
+ const enqueue = (task: () => Promise<unknown>) => {
114
+ queue = queue.then(task).then(
115
+ () => undefined,
116
+ () => undefined,
117
+ )
118
+ }
119
+
120
+ /** Drop the connection from the bus. Runs exactly once, before any user close handler. */
121
+ const teardown = () => {
122
+ if (tornDown) return
123
+ tornDown = true
124
+ closed = true
125
+ bus.unsubscribeAll(connId)
126
+ bus.removeHandler(connId)
127
+ }
128
+
129
+ const closeSocket = (code: number, reason: string) => {
130
+ const ws = socket
131
+ teardown()
132
+ if (ws === null) return
133
+ try {
134
+ ws.close(code, truncateUtf8(reason, MAX_CLOSE_REASON))
135
+ } catch (e) {
136
+ log?.warn?.({ connId, err: e }, "realtime close failed")
137
+ }
138
+ }
139
+
140
+ const sendFrame = (frame: string) => {
141
+ const ws = socket
142
+ if (closed || ws === null) return
143
+ if (bufferedAmountOf(ws.raw) > limits.maxBufferedBytes) {
144
+ if (limits.slowConsumer === "drop") return
145
+ closeSocket(CLOSE_TRY_AGAIN, "slow consumer")
146
+ return
147
+ }
148
+ try {
149
+ ws.send(frame)
150
+ } catch (e) {
151
+ /* the socket went away between the readyState check and the write */
152
+ log?.warn?.({ connId, err: e }, "realtime send failed")
153
+ }
154
+ }
155
+
156
+ /* frames that arrived before the close still run; the close handler is queued after them.
157
+ * Without a handler the frames stay in the inbox until one is attached, or are dropped on close. */
158
+ const drain = async (): Promise<void> => {
159
+ while (inbox.length > 0) {
160
+ const fn = onMessageFn
161
+ if (fn === null) {
162
+ if (closed) inbox.length = 0
163
+ return
164
+ }
165
+ const payload = inbox.shift()
166
+ await invoke("message", () => fn(payload))
167
+ }
168
+ }
169
+
170
+ const accept = (payload: unknown) => {
171
+ if (inbox.length >= limits.maxPendingFrames) {
172
+ closeSocket(CLOSE_POLICY, "too many pending frames")
173
+ return
174
+ }
175
+ inbox.push(payload)
176
+ enqueue(drain)
177
+ }
178
+
179
+ const conn = Object.create(null) as ConnContext
180
+ Object.defineProperties(conn, {
181
+ close: {
182
+ enumerable: true,
183
+ value(reason?: string) {
184
+ if (closed) return
185
+ closeSocket(CLOSE_NORMAL, reason === undefined ? "" : String(reason))
186
+ },
187
+ },
188
+ closed: { enumerable: true, get: () => closed },
189
+ id: { enumerable: true, value: connId },
190
+ join: {
191
+ enumerable: true,
192
+ value(topic: string) {
193
+ if (closed) return
194
+ const key = topicKey(namespace, String(topic))
195
+ if (bus.topicCount(connId) >= limits.maxTopics && !bus.isSubscribed(connId, key)) {
196
+ throw new RangeError(`realtime: a connection may join at most ${limits.maxTopics} topics`)
197
+ }
198
+ bus.subscribe(connId, key)
199
+ },
200
+ },
201
+ leave: {
202
+ enumerable: true,
203
+ value(topic: string) {
204
+ if (closed) return
205
+ bus.unsubscribe(connId, topicKey(namespace, String(topic)))
206
+ },
207
+ },
208
+ on: {
209
+ enumerable: true,
210
+ value(event: "message" | "close", fn: (arg: never) => void | Promise<void>) {
211
+ if (typeof fn !== "function") throw new TypeError("conn.on: handler must be a function")
212
+ if (event === "message") {
213
+ onMessageFn = fn as (payload: unknown) => void | Promise<void>
214
+ /* frames that waited for a handler run next, in arrival order */
215
+ if (inbox.length > 0) enqueue(drain)
216
+ } else if (event === "close") {
217
+ onCloseFn = fn as (reason: string) => void | Promise<void>
218
+ } else {
219
+ throw new TypeError(`conn.on: unknown event "${String(event)}"`)
220
+ }
221
+ },
222
+ },
223
+ /* publishing after close is allowed: a close handler announcing a departure is the common case */
224
+ publish: {
225
+ enumerable: true,
226
+ value(topic: string, payload: unknown) {
227
+ bus.publish(topicKey(namespace, String(topic)), payload)
228
+ },
229
+ },
230
+ send: {
231
+ enumerable: true,
232
+ value(payload: unknown) {
233
+ const frame = encodeRealtimeFrame(payload)
234
+ sendFrame(frame)
235
+ },
236
+ },
237
+ state: { enumerable: true, get: () => state },
238
+ transport: { enumerable: true, value: "ws" },
239
+ userId: { enumerable: true, value: userId },
240
+ })
241
+
242
+ const attach = (ws: WSContext<unknown>) => {
243
+ if (socket !== null || tornDown) return
244
+ socket = ws
245
+ bus.onMessage(connId, sendFrame)
246
+ enqueue(async () => {
247
+ const ok = await invoke("handler", () => config.handler(ctx, conn))
248
+ if (!ok) closeSocket(CLOSE_INTERNAL, "internal error")
249
+ })
250
+ }
251
+
252
+ const onSocketClose = (reason: string) => {
253
+ teardown()
254
+ if (closeRan) return
255
+ closeRan = true
256
+ enqueue(() => {
257
+ const fn = onCloseFn
258
+ return fn === null ? Promise.resolve() : invoke("close", () => fn(reason))
259
+ })
260
+ }
261
+
262
+ const handler: WSHandler<unknown> = {
263
+ onClose: (_ctx, _ws, _code, reason) => {
264
+ onSocketClose(reason || "normal")
265
+ },
266
+ onError: (_ctx, _ws, error) => {
267
+ log?.warn?.({ connId, err: error }, "realtime socket error")
268
+ },
269
+ onMessage: (_ctx, _ws, data) => {
270
+ if (closed) return
271
+ /* the wire format is JSON text; binary frames are not part of it */
272
+ if (typeof data !== "string") return
273
+ if (exceedsPayload(data, limits.maxFrameBytes)) {
274
+ closeSocket(CLOSE_TOO_BIG, "frame too large")
275
+ return
276
+ }
277
+ let payload: unknown
278
+ try {
279
+ payload = JSON.parse(data)
280
+ } catch {
281
+ /* not JSON: not a frame of this protocol */
282
+ return
283
+ }
284
+ if (socket === null) {
285
+ /* a frame before open (runtime quirk): it waits in the inbox, ahead of later frames */
286
+ if (inbox.length >= limits.maxPendingFrames) return
287
+ inbox.push(payload)
288
+ return
289
+ }
290
+ accept(payload)
291
+ },
292
+ onOpen: (_ctx, ws) => {
293
+ attach(ws)
294
+ },
295
+ }
296
+
297
+ return { attach, handler }
298
+ }
package/src/request-id.ts CHANGED
@@ -1,21 +1,31 @@
1
1
  import { namedMiddleware } from "./middleware.ts"
2
2
  import type { MiddlewareFn } from "./middleware.ts"
3
+ import { withHeaders } from "./with-headers.ts"
3
4
 
4
5
  type RequestIdOptions = {
5
6
  generator?: () => string
6
7
  header?: string
8
+ /**
9
+ * Decides whether an inbound id is reused. Default: 1–128 characters of
10
+ * `A-Z a-z 0-9 . _ : + / = -` (UUIDs, ULIDs, base64, W3C trace ids).
11
+ * Anything else is replaced with a generated id, so a client cannot put
12
+ * arbitrary text into logs or response headers.
13
+ */
14
+ validate?: (id: string) => boolean
7
15
  }
8
16
 
17
+ const SAFE_ID_RE = /^[\w.:+/=-]{1,128}$/
18
+
9
19
  export function requestId(options?: RequestIdOptions): MiddlewareFn<{ req: Request }, { requestId: string }> {
10
20
  const headerName = options?.header ?? "x-request-id"
11
21
  const generate = options?.generator ?? (() => crypto.randomUUID())
22
+ const validate = options?.validate ?? ((id: string) => SAFE_ID_RE.test(id))
12
23
 
13
24
  const mw: MiddlewareFn<{ req: Request }, { requestId: string }> = async (ctx, next) => {
14
25
  const existing = ctx.req.headers.get(headerName)
15
- const id = existing ?? generate()
26
+ const id = existing !== null && validate(existing) ? existing : generate()
16
27
  const response = await next({ requestId: id })
17
- response.headers.set(headerName, id)
18
- return response
28
+ return withHeaders(response, (headers) => headers.set(headerName, id))
19
29
  }
20
30
 
21
31
  return namedMiddleware("requestId", mw)