@lovrozagar/honey 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +514 -192
- package/bin/honey.js +25 -0
- package/dist/accepts.d.ts +7 -1
- package/dist/accepts.d.ts.map +1 -1
- package/dist/accepts.js +126 -0
- package/dist/accepts.js.map +1 -0
- package/dist/body-kind.d.ts +26 -0
- package/dist/body-kind.d.ts.map +1 -0
- package/dist/body-kind.js +70 -0
- package/dist/body-kind.js.map +1 -0
- package/dist/body-limit.d.ts +5 -1
- package/dist/body-limit.d.ts.map +1 -1
- package/dist/body-limit.js +126 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/build/index.d.ts +19 -3
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +173 -0
- package/dist/build/index.js.map +1 -0
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +102 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-go/auth.go +26 -0
- package/dist/cli-go/config.go +192 -0
- package/dist/cli-go/errors.go +75 -0
- package/dist/cli-go/multipart.go +79 -0
- package/dist/cli-go/output.go +163 -0
- package/dist/cli-go/root.go +30 -0
- package/dist/cli-go/stream.go +88 -0
- package/dist/cli-go/version.go +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +209 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/defaults.js +48 -0
- package/dist/client/defaults.js.map +1 -0
- package/dist/client/error.d.ts +35 -0
- package/dist/client/error.d.ts.map +1 -1
- package/dist/client/error.js +71 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +67 -8
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +571 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/path.d.ts +26 -0
- package/dist/client/path.d.ts.map +1 -0
- package/dist/client/path.js +163 -0
- package/dist/client/path.js.map +1 -0
- package/dist/client/redirect-policy.d.ts +12 -0
- package/dist/client/redirect-policy.d.ts.map +1 -0
- package/dist/client/redirect-policy.js +20 -0
- package/dist/client/redirect-policy.js.map +1 -0
- package/dist/client/sdk.d.ts +36 -0
- package/dist/client/sdk.d.ts.map +1 -1
- package/dist/client/sdk.js +292 -0
- package/dist/client/sdk.js.map +1 -0
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.d.ts.map +1 -1
- package/dist/client/sse.js +129 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/ws.d.ts +9 -2
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +112 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client-go/errors.go +236 -0
- package/dist/client-go/export.go +44 -0
- package/dist/client-go/invalidation.go +371 -0
- package/dist/client-go/nullable.go +62 -0
- package/dist/client-go/realtime.go +354 -0
- package/dist/client-go/result.go +20 -0
- package/dist/client-go/runtime.go +975 -0
- package/dist/client-go/sse.go +159 -0
- package/dist/client-go/transport.go +495 -0
- package/dist/client-go/ws.go +128 -0
- package/dist/client-info.d.ts +36 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +53 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-mcp/README.md +10 -0
- package/dist/client-mcp/server.ts +221 -0
- package/dist/client-mcp/types.ts +19 -0
- package/dist/client-python/_errors.py +115 -0
- package/dist/client-python/_invalidation.py +494 -0
- package/dist/client-python/_realtime.py +179 -0
- package/dist/client-python/_runtime.py +860 -0
- package/dist/client-python/_sse.py +155 -0
- package/dist/client-python/_transport.py +366 -0
- package/dist/client-python/_ws.py +159 -0
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +220 -0
- package/dist/client-rust/invalidation.rs +412 -0
- package/dist/client-rust/invalidation_sync.rs +93 -0
- package/dist/client-rust/nullable.rs +17 -0
- package/dist/client-rust/realtime.rs +702 -0
- package/dist/client-rust/result.rs +22 -0
- package/dist/client-rust/runtime.rs +980 -0
- package/dist/client-rust/runtime_sync.rs +484 -0
- package/dist/client-rust/sse.rs +188 -0
- package/dist/client-rust/ws.rs +167 -0
- package/dist/codegen-go-cli.d.ts.map +1 -1
- package/dist/codegen-go-cli.js +964 -0
- package/dist/codegen-go-cli.js.map +1 -0
- package/dist/codegen-go.d.ts +15 -3
- package/dist/codegen-go.d.ts.map +1 -1
- package/dist/codegen-go.js +872 -0
- package/dist/codegen-go.js.map +1 -0
- package/dist/codegen-hash.js +9 -0
- package/dist/codegen-hash.js.map +1 -0
- package/dist/codegen-ir.d.ts +69 -2
- package/dist/codegen-ir.d.ts.map +1 -1
- package/dist/codegen-ir.js +661 -0
- package/dist/codegen-ir.js.map +1 -0
- package/dist/codegen-lang.d.ts +65 -0
- package/dist/codegen-lang.d.ts.map +1 -0
- package/dist/codegen-lang.js +526 -0
- package/dist/codegen-lang.js.map +1 -0
- package/dist/codegen-load.d.ts +31 -0
- package/dist/codegen-load.d.ts.map +1 -0
- package/dist/codegen-load.js +138 -0
- package/dist/codegen-load.js.map +1 -0
- package/dist/codegen-loaders.js +55 -0
- package/dist/codegen-loaders.js.map +1 -0
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-mcp.js +368 -0
- package/dist/codegen-mcp.js.map +1 -0
- package/dist/codegen-python.d.ts +13 -1
- package/dist/codegen-python.d.ts.map +1 -1
- package/dist/codegen-python.js +828 -0
- package/dist/codegen-python.js.map +1 -0
- package/dist/codegen-route-tree-intern.d.ts +15 -5
- package/dist/codegen-route-tree-intern.d.ts.map +1 -1
- package/dist/codegen-route-tree-intern.js +221 -0
- package/dist/codegen-route-tree-intern.js.map +1 -0
- package/dist/codegen-rust.d.ts.map +1 -1
- package/dist/codegen-rust.js +1293 -0
- package/dist/codegen-rust.js.map +1 -0
- package/dist/codegen-sanitize.d.ts.map +1 -1
- package/dist/codegen-sanitize.js +56 -0
- package/dist/codegen-sanitize.js.map +1 -0
- package/dist/codegen-schema-naming.d.ts +5 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -1
- package/dist/codegen-schema-naming.js +150 -0
- package/dist/codegen-schema-naming.js.map +1 -0
- package/dist/codegen-sdk-model.d.ts +71 -0
- package/dist/codegen-sdk-model.d.ts.map +1 -0
- package/dist/codegen-sdk-model.js +179 -0
- package/dist/codegen-sdk-model.js.map +1 -0
- package/dist/codegen.d.ts +41 -23
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +4610 -0
- package/dist/codegen.js.map +1 -0
- package/dist/context.d.ts +36 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +242 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie-sign.d.ts +16 -2
- package/dist/cookie-sign.d.ts.map +1 -1
- package/dist/cookie-sign.js +104 -0
- package/dist/cookie-sign.js.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -1
- package/dist/cookie.js +138 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +1 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +87 -0
- package/dist/cors.js.map +1 -0
- package/dist/crypto.d.ts +6 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +38 -0
- package/dist/crypto.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +79 -0
- package/dist/csrf.js.map +1 -0
- package/dist/curl-logger.d.ts +14 -3
- package/dist/curl-logger.d.ts.map +1 -1
- package/dist/curl-logger.js +199 -0
- package/dist/curl-logger.js.map +1 -0
- package/dist/detect-runtime.js +13 -0
- package/dist/detect-runtime.js.map +1 -0
- package/dist/dict.d.ts +11 -0
- package/dist/dict.d.ts.map +1 -0
- package/dist/dict.js +13 -0
- package/dist/dict.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +44 -0
- package/dist/error.js.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +82 -0
- package/dist/etag.js.map +1 -0
- package/dist/feature-detect.d.ts +25 -0
- package/dist/feature-detect.d.ts.map +1 -0
- package/dist/feature-detect.js +144 -0
- package/dist/feature-detect.js.map +1 -0
- package/dist/feature-load.js +36 -0
- package/dist/feature-load.js.map +1 -0
- package/dist/feature-slots.d.ts +44 -0
- package/dist/feature-slots.d.ts.map +1 -0
- package/dist/feature-slots.js +48 -0
- package/dist/feature-slots.js.map +1 -0
- package/dist/fetch-request.d.ts +10 -0
- package/dist/fetch-request.d.ts.map +1 -0
- package/dist/fetch-request.js +13 -0
- package/dist/fetch-request.js.map +1 -0
- package/dist/gen-process.d.ts +15 -0
- package/dist/gen-process.d.ts.map +1 -0
- package/dist/gen-process.js +43 -0
- package/dist/gen-process.js.map +1 -0
- package/dist/gen-write.d.ts +19 -0
- package/dist/gen-write.d.ts.map +1 -0
- package/dist/gen-write.js +120 -0
- package/dist/gen-write.js.map +1 -0
- package/dist/glob.d.ts +21 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +115 -0
- package/dist/glob.js.map +1 -0
- package/dist/go-type-emitter.d.ts +18 -4
- package/dist/go-type-emitter.d.ts.map +1 -1
- package/dist/go-type-emitter.js +594 -0
- package/dist/go-type-emitter.js.map +1 -0
- package/dist/honey-response.d.ts.map +1 -1
- package/dist/honey-response.js +338 -0
- package/dist/honey-response.js.map +1 -0
- package/dist/i18n-slot.d.ts +8 -0
- package/dist/i18n-slot.d.ts.map +1 -1
- package/dist/i18n-slot.js +14 -0
- package/dist/i18n-slot.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +250 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +410 -149
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2324 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/input.js +4 -0
- package/dist/input.js.map +1 -0
- package/dist/invalidate-check.d.ts +11 -0
- package/dist/invalidate-check.d.ts.map +1 -1
- package/dist/invalidate-check.js +163 -0
- package/dist/invalidate-check.js.map +1 -0
- package/dist/invoke-user.d.ts +35 -0
- package/dist/invoke-user.d.ts.map +1 -0
- package/dist/invoke-user.js +73 -0
- package/dist/invoke-user.js.map +1 -0
- package/dist/ip-restrict.d.ts +13 -1
- package/dist/ip-restrict.d.ts.map +1 -1
- package/dist/ip-restrict.js +60 -0
- package/dist/ip-restrict.js.map +1 -0
- package/dist/ip.d.ts +39 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +209 -0
- package/dist/ip.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +113 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-type.d.ts +30 -0
- package/dist/media-type.d.ts.map +1 -0
- package/dist/media-type.js +97 -0
- package/dist/media-type.js.map +1 -0
- package/dist/meta-spec-merge.d.ts +32 -0
- package/dist/meta-spec-merge.d.ts.map +1 -0
- package/dist/meta-spec-merge.js +68 -0
- package/dist/meta-spec-merge.js.map +1 -0
- package/dist/meta-spec.d.ts +16 -3
- package/dist/meta-spec.d.ts.map +1 -1
- package/dist/meta-spec.js +535 -0
- package/dist/meta-spec.js.map +1 -0
- package/dist/middleware.d.ts +23 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/node-request.d.ts +57 -12
- package/dist/node-request.d.ts.map +1 -1
- package/dist/node-request.js +407 -0
- package/dist/node-request.js.map +1 -0
- package/dist/node.d.ts +20 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +396 -0
- package/dist/node.js.map +1 -0
- package/dist/openapi/collect.d.ts +6 -3
- package/dist/openapi/collect.d.ts.map +1 -1
- package/dist/openapi/collect.js +30 -0
- package/dist/openapi/collect.js.map +1 -0
- package/dist/openapi/docs-page.d.ts +42 -0
- package/dist/openapi/docs-page.d.ts.map +1 -0
- package/dist/openapi/docs-page.js +80 -0
- package/dist/openapi/docs-page.js.map +1 -0
- package/dist/openapi/document.d.ts +4 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +785 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/json-schema-slot.d.ts +7 -0
- package/dist/openapi/json-schema-slot.d.ts.map +1 -1
- package/dist/openapi/json-schema-slot.js +38 -0
- package/dist/openapi/json-schema-slot.js.map +1 -0
- package/dist/openapi/register.d.ts.map +1 -1
- package/dist/openapi/register.js +20 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/scalar.d.ts +3 -1
- package/dist/openapi/scalar.d.ts.map +1 -1
- package/dist/openapi/scalar.js +21 -0
- package/dist/openapi/scalar.js.map +1 -0
- package/dist/openapi/serve.d.ts +9 -0
- package/dist/openapi/serve.d.ts.map +1 -0
- package/dist/openapi/serve.js +36 -0
- package/dist/openapi/serve.js.map +1 -0
- package/dist/openapi/spec-factory.d.ts +30 -1
- package/dist/openapi/spec-factory.d.ts.map +1 -1
- package/dist/openapi/spec-factory.js +52 -0
- package/dist/openapi/spec-factory.js.map +1 -0
- package/dist/openapi/spec.d.ts +14 -5
- package/dist/openapi/spec.d.ts.map +1 -1
- package/dist/openapi/spec.js +50 -0
- package/dist/openapi/spec.js.map +1 -0
- package/dist/openapi/swagger.d.ts +3 -1
- package/dist/openapi/swagger.d.ts.map +1 -1
- package/dist/openapi/swagger.js +22 -0
- package/dist/openapi/swagger.js.map +1 -0
- package/dist/pattern.d.ts +71 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +239 -0
- package/dist/pattern.js.map +1 -0
- package/dist/peer.d.ts +17 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +50 -0
- package/dist/peer.js.map +1 -0
- package/dist/plugin.d.ts +67 -16
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +649 -0
- package/dist/plugin.js.map +1 -0
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +11 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +47 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/producer-stream.d.ts +47 -0
- package/dist/producer-stream.d.ts.map +1 -0
- package/dist/producer-stream.js +267 -0
- package/dist/producer-stream.js.map +1 -0
- package/dist/proxy.d.ts +47 -8
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +274 -0
- package/dist/proxy.js.map +1 -0
- package/dist/python-type-emitter.d.ts +14 -1
- package/dist/python-type-emitter.d.ts.map +1 -1
- package/dist/python-type-emitter.js +157 -0
- package/dist/python-type-emitter.js.map +1 -0
- package/dist/realtime/bus.d.ts +17 -1
- package/dist/realtime/bus.d.ts.map +1 -1
- package/dist/realtime/bus.js +112 -0
- package/dist/realtime/bus.js.map +1 -0
- package/dist/realtime/register.d.ts +3 -0
- package/dist/realtime/register.d.ts.map +1 -0
- package/dist/realtime/register.js +10 -0
- package/dist/realtime/register.js.map +1 -0
- package/dist/realtime/route.d.ts +65 -26
- package/dist/realtime/route.d.ts.map +1 -1
- package/dist/realtime/route.js +72 -0
- package/dist/realtime/route.js.map +1 -0
- package/dist/realtime/server.d.ts +43 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +255 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/request-id.d.ts +7 -0
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +16 -0
- package/dist/request-id.js.map +1 -0
- package/dist/request-limits.d.ts +3 -0
- package/dist/request-limits.d.ts.map +1 -0
- package/dist/request-limits.js +3 -0
- package/dist/request-limits.js.map +1 -0
- package/dist/request-path.d.ts +33 -0
- package/dist/request-path.d.ts.map +1 -0
- package/dist/request-path.js +178 -0
- package/dist/request-path.js.map +1 -0
- package/dist/request-to-curl.d.ts +7 -1
- package/dist/request-to-curl.d.ts.map +1 -1
- package/dist/request-to-curl.js +48 -0
- package/dist/request-to-curl.js.map +1 -0
- package/dist/response.d.ts +25 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +313 -0
- package/dist/response.js.map +1 -0
- package/dist/rust-type-emitter.d.ts +14 -2
- package/dist/rust-type-emitter.d.ts.map +1 -1
- package/dist/rust-type-emitter.js +463 -0
- package/dist/rust-type-emitter.js.map +1 -0
- package/dist/secure-headers.d.ts.map +1 -1
- package/dist/secure-headers.js +53 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve-register.d.ts +4 -0
- package/dist/serve-register.d.ts.map +1 -1
- package/dist/serve-register.js +9 -0
- package/dist/serve-register.js.map +1 -0
- package/dist/serve-slot.d.ts +3 -0
- package/dist/serve-slot.d.ts.map +1 -1
- package/dist/serve-slot.js +52 -0
- package/dist/serve-slot.js.map +1 -0
- package/dist/serve.d.ts +18 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +157 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-timing.d.ts.map +1 -1
- package/dist/server-timing.js +51 -0
- package/dist/server-timing.js.map +1 -0
- package/dist/static.d.ts +9 -0
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +102 -0
- package/dist/static.js.map +1 -0
- package/dist/telemetry/otel.d.ts +2 -0
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/otel.js +135 -0
- package/dist/telemetry/otel.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +124 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +36 -0
- package/dist/timeout.js.map +1 -0
- package/dist/tree.d.ts +133 -36
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +437 -0
- package/dist/tree.js.map +1 -0
- package/dist/trust.d.ts +21 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +100 -0
- package/dist/trust.js.map +1 -0
- package/dist/ts-type-emitter.d.ts.map +1 -1
- package/dist/ts-type-emitter.js +150 -0
- package/dist/ts-type-emitter.js.map +1 -0
- package/dist/type-emitter.d.ts +16 -2
- package/dist/type-emitter.d.ts.map +1 -1
- package/dist/type-emitter.js +729 -0
- package/dist/type-emitter.js.map +1 -0
- package/dist/type-extractor.d.ts +8 -4
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +776 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +96 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +14 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +335 -0
- package/dist/validation.js.map +1 -0
- package/dist/with-headers.d.ts +15 -0
- package/dist/with-headers.d.ts.map +1 -0
- package/dist/with-headers.js +45 -0
- package/dist/with-headers.js.map +1 -0
- package/dist/ws/bun.d.ts +11 -5
- package/dist/ws/bun.d.ts.map +1 -1
- package/dist/ws/bun.js +82 -0
- package/dist/ws/bun.js.map +1 -0
- package/dist/ws/cloudflare.d.ts +25 -3
- package/dist/ws/cloudflare.d.ts.map +1 -1
- package/dist/ws/cloudflare.js +117 -0
- package/dist/ws/cloudflare.js.map +1 -0
- package/dist/ws/deno.d.ts +4 -4
- package/dist/ws/deno.d.ts.map +1 -1
- package/dist/ws/deno.js +133 -0
- package/dist/ws/deno.js.map +1 -0
- package/dist/ws/node.d.ts +20 -3
- package/dist/ws/node.d.ts.map +1 -1
- package/dist/ws/node.js +173 -0
- package/dist/ws/node.js.map +1 -0
- package/dist/ws/session.d.ts +11 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +60 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/shared.d.ts +65 -0
- package/dist/ws/shared.d.ts.map +1 -0
- package/dist/ws/shared.js +103 -0
- package/dist/ws/shared.js.map +1 -0
- package/dist/ws-origin.d.ts +20 -0
- package/dist/ws-origin.d.ts.map +1 -0
- package/dist/ws-origin.js +41 -0
- package/dist/ws-origin.js.map +1 -0
- package/dist/yaml.d.ts +15 -1
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +101 -0
- package/dist/yaml.js.map +1 -0
- package/docs/invalidate-spec.md +13 -1
- package/docs/meta-spec.md +36 -12
- package/docs/packaging.md +43 -24
- package/docs/regression-matrix/README.md +32 -0
- package/docs/regression-matrix/review.md +44 -0
- package/docs/regression-matrix/ws1-3.md +97 -0
- package/docs/regression-matrix/ws4-8.md +107 -0
- package/docs/regression-matrix/ws9-12.md +266 -0
- package/docs/sdk.md +102 -52
- package/package.json +183 -52
- package/src/accepts.ts +91 -33
- package/src/body-kind.ts +67 -0
- package/src/body-limit.ts +102 -87
- package/src/build/index.ts +104 -54
- package/src/cli-args.ts +123 -0
- package/src/cli-go/auth.go +12 -1
- package/src/cli-go/config.go +29 -2
- package/src/cli-go/errors.go +28 -10
- package/src/cli-go/multipart.go +51 -27
- package/src/cli-go/output.go +58 -4
- package/src/cli-go/root.go +1 -1
- package/src/cli-go/version.go +2 -2
- package/src/cli.ts +154 -148
- package/src/client/error.ts +56 -1
- package/src/client/http.ts +479 -256
- package/src/client/index.ts +55 -17
- package/src/client/path.ts +169 -0
- package/src/client/redirect-policy.ts +19 -0
- package/src/client/sdk.ts +180 -101
- package/src/client/sse.ts +92 -66
- package/src/client/ws.ts +67 -31
- package/src/client-go/errors.go +41 -12
- package/src/client-go/export.go +44 -0
- package/src/client-go/invalidation.go +52 -54
- package/src/client-go/nullable.go +62 -0
- package/src/client-go/realtime.go +147 -110
- package/src/client-go/runtime.go +646 -88
- package/src/client-go/sse.go +104 -94
- package/src/client-go/transport.go +67 -27
- package/src/client-go/ws.go +49 -25
- package/src/client-info.ts +79 -0
- package/src/client-mcp/server.ts +133 -3
- package/src/client-python/_invalidation.py +207 -291
- package/src/client-python/_realtime.py +63 -40
- package/src/client-python/_runtime.py +501 -119
- package/src/client-python/_sse.py +130 -74
- package/src/client-python/_transport.py +37 -9
- package/src/client-python/_ws.py +50 -22
- package/src/client-rust/errors.rs +21 -1
- package/src/client-rust/invalidation.rs +202 -231
- package/src/client-rust/invalidation_sync.rs +16 -229
- package/src/client-rust/nullable.rs +17 -0
- package/src/client-rust/realtime.rs +217 -217
- package/src/client-rust/runtime.rs +602 -177
- package/src/client-rust/runtime_sync.rs +169 -145
- package/src/client-rust/sse.rs +142 -121
- package/src/client-rust/ws.rs +61 -37
- package/src/codegen-go-cli.ts +607 -728
- package/src/codegen-go.ts +710 -770
- package/src/codegen-ir.ts +381 -75
- package/src/codegen-lang.ts +517 -0
- package/src/codegen-load.ts +150 -0
- package/src/codegen-mcp.ts +207 -108
- package/src/codegen-python.ts +684 -816
- package/src/codegen-route-tree-intern.ts +51 -25
- package/src/codegen-rust.ts +946 -1240
- package/src/codegen-sanitize.ts +0 -4
- package/src/codegen-schema-naming.ts +19 -6
- package/src/codegen-sdk-model.ts +228 -0
- package/src/codegen.ts +1488 -1140
- package/src/context.ts +130 -11
- package/src/cookie-sign.ts +85 -16
- package/src/cookie.ts +66 -11
- package/src/cors.ts +55 -54
- package/src/crypto.ts +27 -5
- package/src/csrf.ts +57 -25
- package/src/curl-logger.ts +155 -114
- package/src/dict.ts +12 -0
- package/src/error.ts +4 -2
- package/src/etag.ts +58 -34
- package/src/feature-detect.ts +147 -0
- package/src/feature-slots.ts +70 -0
- package/src/fetch-request.ts +13 -0
- package/src/gen-process.ts +42 -0
- package/src/gen-write.ts +127 -0
- package/src/glob.ts +109 -0
- package/src/go-type-emitter.ts +406 -155
- package/src/honey-response.ts +36 -9
- package/src/i18n-slot.ts +10 -0
- package/src/i18n.ts +98 -23
- package/src/index.ts +1893 -1761
- package/src/init.ts +40 -28
- package/src/invalidate-check.ts +42 -4
- package/src/invoke-user.ts +95 -0
- package/src/ip-restrict.ts +47 -161
- package/src/ip.ts +194 -0
- package/src/logger.ts +65 -36
- package/src/media-type.ts +104 -0
- package/src/meta-spec-merge.ts +80 -0
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +112 -12
- package/src/node-request.ts +242 -52
- package/src/node.ts +318 -75
- package/src/openapi/collect.ts +19 -44
- package/src/openapi/docs-page.ts +89 -0
- package/src/openapi/document.ts +503 -224
- package/src/openapi/json-schema-slot.ts +40 -0
- package/src/openapi/register.ts +7 -2
- package/src/openapi/scalar.ts +11 -10
- package/src/openapi/serve.ts +39 -0
- package/src/openapi/spec-factory.ts +50 -1
- package/src/openapi/spec.ts +53 -30
- package/src/openapi/swagger.ts +12 -11
- package/src/pattern.ts +245 -0
- package/src/peer.ts +53 -0
- package/src/plugin.ts +270 -185
- package/src/powered-by.ts +2 -2
- package/src/pretty-json.ts +25 -9
- package/src/producer-stream.ts +296 -0
- package/src/proxy.ts +271 -55
- package/src/python-type-emitter.ts +68 -56
- package/src/realtime/bus.ts +56 -9
- package/src/realtime/register.ts +13 -0
- package/src/realtime/route.ts +129 -80
- package/src/realtime/server.ts +298 -0
- package/src/request-id.ts +13 -3
- package/src/request-limits.ts +2 -0
- package/src/request-path.ts +165 -0
- package/src/request-to-curl.ts +28 -7
- package/src/response.ts +143 -103
- package/src/rust-type-emitter.ts +265 -170
- package/src/secure-headers.ts +8 -4
- package/src/serve-register.ts +4 -0
- package/src/serve-slot.ts +38 -0
- package/src/serve.ts +113 -25
- package/src/server-timing.ts +11 -7
- package/src/static.ts +85 -32
- package/src/telemetry/otel.ts +29 -7
- package/src/testing.ts +76 -14
- package/src/timeout.ts +15 -7
- package/src/tree.ts +405 -333
- package/src/trust.ts +117 -0
- package/src/ts-type-emitter.ts +18 -12
- package/src/type-emitter.ts +227 -89
- package/src/type-extractor.ts +562 -424
- package/src/types.ts +8 -0
- package/src/validation.ts +221 -96
- package/src/with-headers.ts +45 -0
- package/src/ws/bun.ts +53 -15
- package/src/ws/cloudflare.ts +81 -19
- package/src/ws/deno.ts +124 -36
- package/src/ws/node.ts +173 -79
- package/src/ws/session.ts +73 -0
- package/src/ws/shared.ts +140 -0
- package/src/ws-origin.ts +53 -0
- package/src/yaml.ts +45 -22
- package/dist/client-mcp/server.d.ts +0 -10
- package/dist/client-mcp/server.d.ts.map +0 -1
- package/dist/client-mcp/types.d.ts +0 -16
- package/dist/client-mcp/types.d.ts.map +0 -1
- package/dist/cloudflare/realtime.d.ts +0 -30
- package/dist/cloudflare/realtime.d.ts.map +0 -1
- package/dist/realtime/buffer.d.ts +0 -18
- package/dist/realtime/buffer.d.ts.map +0 -1
- package/dist/realtime/longpoll.d.ts +0 -23
- package/dist/realtime/longpoll.d.ts.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -50
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/runtime.d.ts +0 -74
- package/dist/realtime/runtime.d.ts.map +0 -1
- package/src/cloudflare/realtime.ts +0 -234
- package/src/realtime/buffer.ts +0 -101
- package/src/realtime/longpoll.ts +0 -148
- package/src/realtime/protocol.ts +0 -128
- package/src/realtime/runtime.ts +0 -427
package/src/realtime/bus.ts
CHANGED
|
@@ -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
|
-
|
|
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 ->
|
|
17
|
-
const handlers = new Map<string, (
|
|
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
|
|
86
|
+
function publishFrame(topic: string, frame: string): void {
|
|
64
87
|
const subs = topicSubs.get(topic)
|
|
65
88
|
if (!subs) return
|
|
66
89
|
|
|
67
|
-
|
|
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(
|
|
95
|
+
handler(frame)
|
|
72
96
|
} catch {
|
|
73
|
-
/*
|
|
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
|
|
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
|
-
|
|
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"
|
package/src/realtime/route.ts
CHANGED
|
@@ -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"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
69
|
+
/** A realtime route after validation, with every default applied. */
|
|
70
|
+
export type RealtimeConfig = {
|
|
71
|
+
allowedOrigins: WSOriginPolicy | null
|
|
23
72
|
handler: RealtimeRouteOpts["handler"]
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26
|
+
const id = existing !== null && validate(existing) ? existing : generate()
|
|
16
27
|
const response = await next({ requestId: id })
|
|
17
|
-
response
|
|
18
|
-
return response
|
|
28
|
+
return withHeaders(response, (headers) => headers.set(headerName, id))
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
return namedMiddleware("requestId", mw)
|