@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.
- 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 +408 -148
- 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 +819 -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 +1887 -1756
- 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 +253 -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 +603 -423
- 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
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
from urllib.parse import quote
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _now() -> float:
|
|
14
|
+
return time.monotonic()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# `{name}` (any name without braces or slashes) or a `:name` segment; a colon
|
|
18
|
+
# inside a segment (`/ops/{id}:cancel`) is literal.
|
|
19
|
+
_PATH_PARAM_RE = re.compile(r"\{([^{}/]+)\}|(?:(?<=/)|^):([a-zA-Z_][a-zA-Z0-9_]*)")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class _StaleEntry:
|
|
24
|
+
by: list[str] = field(default_factory=list)
|
|
25
|
+
seq: int = 0
|
|
26
|
+
until: float = 0.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class _RequestMeta:
|
|
31
|
+
selector: str
|
|
32
|
+
is_stale: bool
|
|
33
|
+
invalidated_by: list[str]
|
|
34
|
+
seq_snapshot: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def path_matches_pattern(
|
|
38
|
+
path: str,
|
|
39
|
+
pattern: str,
|
|
40
|
+
_cache: dict[str, re.Pattern[str]] = {},
|
|
41
|
+
) -> bool:
|
|
42
|
+
"""Match *path* against *pattern* containing ``{name}`` / ``:name`` placeholders."""
|
|
43
|
+
if pattern not in _cache:
|
|
44
|
+
escaped_parts: list[str] = []
|
|
45
|
+
last_end = 0
|
|
46
|
+
for match in _PATH_PARAM_RE.finditer(pattern):
|
|
47
|
+
escaped_parts.append(re.escape(pattern[last_end : match.start()]))
|
|
48
|
+
escaped_parts.append("[^/]+")
|
|
49
|
+
last_end = match.end()
|
|
50
|
+
escaped_parts.append(re.escape(pattern[last_end:]))
|
|
51
|
+
_cache[pattern] = re.compile(f"^{''.join(escaped_parts)}$")
|
|
52
|
+
return bool(_cache[pattern].match(path))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def interpolate_path(template: str, params: dict[str, str] | None) -> str:
|
|
56
|
+
"""Substitute ``{name}`` / ``:name`` placeholders in *template* using *params*.
|
|
57
|
+
|
|
58
|
+
Raises ValueError if any placeholder has no matching key in *params*.
|
|
59
|
+
"""
|
|
60
|
+
if not params:
|
|
61
|
+
if _PATH_PARAM_RE.search(template) is not None:
|
|
62
|
+
raise ValueError(f"Missing path params for template {template!r}")
|
|
63
|
+
return template
|
|
64
|
+
|
|
65
|
+
def _sub(match: re.Match[str]) -> str:
|
|
66
|
+
key = match.group(1) or match.group(2)
|
|
67
|
+
if key not in params or params[key] is None:
|
|
68
|
+
raise ValueError(f"Missing path param: {key}")
|
|
69
|
+
return quote(str(params[key]), safe="")
|
|
70
|
+
|
|
71
|
+
return _PATH_PARAM_RE.sub(_sub, template)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _has_unresolved(value: str) -> bool:
|
|
75
|
+
return _PATH_PARAM_RE.search(value) is not None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def resolve_invalidation_targets_for_mutation(
|
|
79
|
+
targets: list[str],
|
|
80
|
+
params: dict[str, str] | None,
|
|
81
|
+
) -> list[str]:
|
|
82
|
+
"""Expand ``METHOD /path/{id}`` template invalidation targets to concrete selectors.
|
|
83
|
+
|
|
84
|
+
Targets without placeholders are passed through. A target whose placeholders cannot
|
|
85
|
+
all be resolved stays a pattern, so it marks every instance stale (TS parity).
|
|
86
|
+
"""
|
|
87
|
+
resolved: list[str] = []
|
|
88
|
+
for entry in targets:
|
|
89
|
+
space_idx = entry.find(" ")
|
|
90
|
+
if space_idx == -1:
|
|
91
|
+
resolved.append(entry)
|
|
92
|
+
continue
|
|
93
|
+
method = entry[:space_idx]
|
|
94
|
+
template = entry[space_idx + 1 :]
|
|
95
|
+
if not _has_unresolved(template):
|
|
96
|
+
resolved.append(entry)
|
|
97
|
+
continue
|
|
98
|
+
try:
|
|
99
|
+
concrete = interpolate_path(template, params)
|
|
100
|
+
except ValueError:
|
|
101
|
+
resolved.append(entry)
|
|
102
|
+
continue
|
|
103
|
+
resolved.append(f"{method} {concrete}")
|
|
104
|
+
return resolved
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def resolve_invalidation_targets(
|
|
108
|
+
invalidate: list[str],
|
|
109
|
+
stale_map: "OrderedDict[str, _StaleEntry] | OrderedDict[str, Any]",
|
|
110
|
+
) -> list[str]:
|
|
111
|
+
"""Return *stale_map* keys matching any 'METHOD /pattern' invalidation entry.
|
|
112
|
+
|
|
113
|
+
Kept for back-compat with existing call sites that pass an already-built map.
|
|
114
|
+
"""
|
|
115
|
+
matched: list[str] = []
|
|
116
|
+
for entry in invalidate:
|
|
117
|
+
for key in list(stale_map.keys()):
|
|
118
|
+
parts = key.split(" ", 1)
|
|
119
|
+
if len(parts) != 2:
|
|
120
|
+
continue
|
|
121
|
+
key_method, key_path = parts
|
|
122
|
+
entry_parts = entry.split(" ", 1)
|
|
123
|
+
if len(entry_parts) != 2:
|
|
124
|
+
continue
|
|
125
|
+
entry_method, entry_pattern = entry_parts
|
|
126
|
+
if key_method != entry_method:
|
|
127
|
+
continue
|
|
128
|
+
if path_matches_pattern(key_path, entry_pattern):
|
|
129
|
+
matched.append(key)
|
|
130
|
+
return matched
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def lookup_stale(key: str, stale_map: "OrderedDict[str, Any]") -> bool:
|
|
134
|
+
"""Return True if *key* exists in *stale_map* and has not yet expired."""
|
|
135
|
+
entry = stale_map.get(key)
|
|
136
|
+
if entry is None:
|
|
137
|
+
return False
|
|
138
|
+
until = entry.until if isinstance(entry, _StaleEntry) else entry["until"]
|
|
139
|
+
return _now() < until
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class _StaleStore:
|
|
143
|
+
"""Stale state shared by the async and sync trackers; callers hold their own lock.
|
|
144
|
+
|
|
145
|
+
Concrete keys are looked up directly; keys that still hold ``{name}``
|
|
146
|
+
placeholders are also indexed per method, so a lookup scans only the patterns
|
|
147
|
+
of its method, not every entry. Expired concrete entries go on access, expired
|
|
148
|
+
patterns when scanned, and the rest when the map is over capacity.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self, stale_time: float, stale_max_entries: int, max_sources_per_target: int
|
|
153
|
+
) -> None:
|
|
154
|
+
self.enabled = stale_time > 0
|
|
155
|
+
self.stale_time = max(stale_time, 0.0)
|
|
156
|
+
self.max_entries = max(stale_max_entries, 1)
|
|
157
|
+
self.max_sources = max(max_sources_per_target, 1)
|
|
158
|
+
self.map: OrderedDict[str, _StaleEntry] = OrderedDict()
|
|
159
|
+
self.patterns: dict[str, dict[str, None]] = {}
|
|
160
|
+
self.seq = 0
|
|
161
|
+
|
|
162
|
+
def delete(self, key: str) -> None:
|
|
163
|
+
if self.map.pop(key, None) is None:
|
|
164
|
+
return
|
|
165
|
+
method, path = _split_key(key)
|
|
166
|
+
bucket = self.patterns.get(method)
|
|
167
|
+
if bucket is not None and _has_unresolved(path):
|
|
168
|
+
bucket.pop(key, None)
|
|
169
|
+
if not bucket:
|
|
170
|
+
del self.patterns[method]
|
|
171
|
+
|
|
172
|
+
def upsert(
|
|
173
|
+
self, key: str, until: float, seq: int, mutation_selector: str | None
|
|
174
|
+
) -> None:
|
|
175
|
+
existing = self.map.get(key)
|
|
176
|
+
if existing is not None:
|
|
177
|
+
existing.until = until
|
|
178
|
+
existing.seq = seq
|
|
179
|
+
if mutation_selector and mutation_selector not in existing.by:
|
|
180
|
+
if len(existing.by) < self.max_sources:
|
|
181
|
+
existing.by.append(mutation_selector)
|
|
182
|
+
self.map.move_to_end(key)
|
|
183
|
+
return
|
|
184
|
+
by = [mutation_selector] if mutation_selector else []
|
|
185
|
+
self.map[key] = _StaleEntry(by=by, seq=seq, until=until)
|
|
186
|
+
method, path = _split_key(key)
|
|
187
|
+
if _has_unresolved(path):
|
|
188
|
+
self.patterns.setdefault(method, {})[key] = None
|
|
189
|
+
|
|
190
|
+
def invalidate(self, patterns: list[str]) -> None:
|
|
191
|
+
self.evict_expired()
|
|
192
|
+
targets = resolve_invalidation_targets(patterns, self.map)
|
|
193
|
+
self.seq += 1
|
|
194
|
+
until = _now() + self.stale_time
|
|
195
|
+
for t in targets:
|
|
196
|
+
self.upsert(t, until, self.seq, None)
|
|
197
|
+
self.enforce_capacity()
|
|
198
|
+
|
|
199
|
+
def mark_stale(
|
|
200
|
+
self,
|
|
201
|
+
invalidate: list[str],
|
|
202
|
+
params: dict[str, str] | None,
|
|
203
|
+
mutation_selector: str,
|
|
204
|
+
) -> None:
|
|
205
|
+
self.seq += 1
|
|
206
|
+
until = _now() + self.stale_time
|
|
207
|
+
for target in resolve_invalidation_targets_for_mutation(invalidate, params):
|
|
208
|
+
self.upsert(target, until, self.seq, mutation_selector)
|
|
209
|
+
self.enforce_capacity()
|
|
210
|
+
|
|
211
|
+
def mark(self, key: str) -> None:
|
|
212
|
+
self.seq += 1
|
|
213
|
+
self.upsert(key, _now() + self.stale_time, self.seq, None)
|
|
214
|
+
self.enforce_capacity()
|
|
215
|
+
|
|
216
|
+
def lookup(
|
|
217
|
+
self, concrete_selector: str, concrete_path: str, method: str, now: float
|
|
218
|
+
) -> tuple[list[str], bool]:
|
|
219
|
+
all_by: list[str] = []
|
|
220
|
+
exact = self.map.get(concrete_selector)
|
|
221
|
+
if exact is not None:
|
|
222
|
+
if exact.until > now:
|
|
223
|
+
all_by.extend(exact.by)
|
|
224
|
+
else:
|
|
225
|
+
self.delete(concrete_selector)
|
|
226
|
+
expired: list[str] = []
|
|
227
|
+
for key in self.patterns.get(method, {}):
|
|
228
|
+
entry = self.map[key]
|
|
229
|
+
if entry.until <= now:
|
|
230
|
+
expired.append(key)
|
|
231
|
+
continue
|
|
232
|
+
if key != concrete_selector and path_matches_pattern(
|
|
233
|
+
concrete_path, _split_key(key)[1]
|
|
234
|
+
):
|
|
235
|
+
all_by.extend(entry.by)
|
|
236
|
+
for key in expired:
|
|
237
|
+
self.delete(key)
|
|
238
|
+
deduped = list(dict.fromkeys(all_by))
|
|
239
|
+
return deduped, len(deduped) > 0
|
|
240
|
+
|
|
241
|
+
def clear(
|
|
242
|
+
self, concrete_selector: str, concrete_path: str, method: str, seq_snapshot: int
|
|
243
|
+
) -> None:
|
|
244
|
+
exact = self.map.get(concrete_selector)
|
|
245
|
+
if exact and exact.seq <= seq_snapshot:
|
|
246
|
+
self.delete(concrete_selector)
|
|
247
|
+
to_delete = [
|
|
248
|
+
key
|
|
249
|
+
for key in self.patterns.get(method, {})
|
|
250
|
+
if self.map[key].seq <= seq_snapshot
|
|
251
|
+
and path_matches_pattern(concrete_path, _split_key(key)[1])
|
|
252
|
+
]
|
|
253
|
+
for key in to_delete:
|
|
254
|
+
self.delete(key)
|
|
255
|
+
|
|
256
|
+
def request_meta(
|
|
257
|
+
self, concrete_selector: str, concrete_path: str, method: str
|
|
258
|
+
) -> _RequestMeta:
|
|
259
|
+
by, is_stale = self.lookup(concrete_selector, concrete_path, method, _now())
|
|
260
|
+
return _RequestMeta(
|
|
261
|
+
selector=concrete_selector,
|
|
262
|
+
is_stale=is_stale,
|
|
263
|
+
invalidated_by=by,
|
|
264
|
+
seq_snapshot=self.seq,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def evict_expired(self) -> None:
|
|
268
|
+
now = _now()
|
|
269
|
+
for key in [k for k, v in self.map.items() if now >= v.until]:
|
|
270
|
+
self.delete(key)
|
|
271
|
+
|
|
272
|
+
def enforce_capacity(self) -> None:
|
|
273
|
+
if len(self.map) <= self.max_entries:
|
|
274
|
+
return
|
|
275
|
+
self.evict_expired()
|
|
276
|
+
target = max(self.max_entries // 2, 1)
|
|
277
|
+
while len(self.map) > target:
|
|
278
|
+
self.delete(next(iter(self.map)))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _split_key(key: str) -> tuple[str, str]:
|
|
282
|
+
space_idx = key.find(" ")
|
|
283
|
+
if space_idx == -1:
|
|
284
|
+
return key, ""
|
|
285
|
+
return key[:space_idx], key[space_idx + 1 :]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _split_method_path(method_or_key: str, path: str | None) -> tuple[str, str]:
|
|
289
|
+
if path is None:
|
|
290
|
+
return _split_key(method_or_key)
|
|
291
|
+
return method_or_key, path
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class _StaleTracker:
|
|
295
|
+
"""Async-aware stale tracker backed by ``asyncio.Lock``."""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
stale_time: float,
|
|
300
|
+
stale_max_entries: int = 1000,
|
|
301
|
+
max_sources_per_target: int = 16,
|
|
302
|
+
) -> None:
|
|
303
|
+
"""stale_time == 0 disables the tracker (no stale window applied)."""
|
|
304
|
+
self._store = _StaleStore(stale_time, stale_max_entries, max_sources_per_target)
|
|
305
|
+
self._lock = asyncio.Lock()
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def enabled(self) -> bool:
|
|
309
|
+
return self._store.enabled
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def _map(self) -> "OrderedDict[str, _StaleEntry]":
|
|
313
|
+
return self._store.map
|
|
314
|
+
|
|
315
|
+
async def invalidate(self, patterns: list[str]) -> None:
|
|
316
|
+
"""Mark every existing key matching *patterns* as freshly stale."""
|
|
317
|
+
if not self.enabled:
|
|
318
|
+
return
|
|
319
|
+
async with self._lock:
|
|
320
|
+
self._store.invalidate(patterns)
|
|
321
|
+
|
|
322
|
+
async def mark_stale(
|
|
323
|
+
self,
|
|
324
|
+
invalidate: list[str],
|
|
325
|
+
params: dict[str, str] | None,
|
|
326
|
+
mutation_selector: str,
|
|
327
|
+
) -> None:
|
|
328
|
+
"""Bump seq, expand templated invalidation entries, store stale keys."""
|
|
329
|
+
if not self.enabled or not invalidate:
|
|
330
|
+
return
|
|
331
|
+
async with self._lock:
|
|
332
|
+
self._store.mark_stale(invalidate, params, mutation_selector)
|
|
333
|
+
|
|
334
|
+
async def mark(self, key: str) -> None:
|
|
335
|
+
"""Force *key* into the stale map. Used by SSE/WS open hooks and tests."""
|
|
336
|
+
if not self.enabled:
|
|
337
|
+
return
|
|
338
|
+
async with self._lock:
|
|
339
|
+
self._store.mark(key)
|
|
340
|
+
|
|
341
|
+
async def is_stale(self, method_or_key: str, path: str | None = None) -> bool:
|
|
342
|
+
"""Return True if (method, path) is currently stale.
|
|
343
|
+
|
|
344
|
+
Accepts either ``is_stale("GET /users")`` or ``is_stale("GET", "/users")``.
|
|
345
|
+
Walks pattern entries too.
|
|
346
|
+
"""
|
|
347
|
+
if not self.enabled:
|
|
348
|
+
return False
|
|
349
|
+
method, concrete_path = _split_method_path(method_or_key, path)
|
|
350
|
+
async with self._lock:
|
|
351
|
+
return self._store.lookup(
|
|
352
|
+
f"{method} {concrete_path}", concrete_path, method, _now()
|
|
353
|
+
)[1]
|
|
354
|
+
|
|
355
|
+
async def lookup_stale(
|
|
356
|
+
self,
|
|
357
|
+
concrete_selector: str,
|
|
358
|
+
concrete_path: str,
|
|
359
|
+
method: str,
|
|
360
|
+
) -> tuple[list[str], bool]:
|
|
361
|
+
"""Return (invalidated_by_list, is_stale): exact + pattern lookup with sweep."""
|
|
362
|
+
if not self.enabled:
|
|
363
|
+
return [], False
|
|
364
|
+
async with self._lock:
|
|
365
|
+
return self._store.lookup(concrete_selector, concrete_path, method, _now())
|
|
366
|
+
|
|
367
|
+
async def clear_stale(
|
|
368
|
+
self,
|
|
369
|
+
concrete_selector: str,
|
|
370
|
+
concrete_path: str,
|
|
371
|
+
method: str,
|
|
372
|
+
seq_snapshot: int,
|
|
373
|
+
) -> None:
|
|
374
|
+
"""Drop seq-older entries matching *concrete_path* (exact + pattern keys)."""
|
|
375
|
+
if not self.enabled:
|
|
376
|
+
return
|
|
377
|
+
async with self._lock:
|
|
378
|
+
self._store.clear(concrete_selector, concrete_path, method, seq_snapshot)
|
|
379
|
+
|
|
380
|
+
async def build_request_meta(
|
|
381
|
+
self,
|
|
382
|
+
concrete_selector: str,
|
|
383
|
+
concrete_path: str,
|
|
384
|
+
method: str,
|
|
385
|
+
) -> _RequestMeta | None:
|
|
386
|
+
if not self.enabled:
|
|
387
|
+
return None
|
|
388
|
+
async with self._lock:
|
|
389
|
+
return self._store.request_meta(concrete_selector, concrete_path, method)
|
|
390
|
+
|
|
391
|
+
@staticmethod
|
|
392
|
+
def _split_key(key: str) -> tuple[str, str]:
|
|
393
|
+
return _split_key(key)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class _StaleTrackerSync:
|
|
397
|
+
"""Sync variant backed by ``threading.Lock``; mirrors :class:`_StaleTracker`."""
|
|
398
|
+
|
|
399
|
+
def __init__(
|
|
400
|
+
self,
|
|
401
|
+
stale_time: float,
|
|
402
|
+
stale_max_entries: int = 1000,
|
|
403
|
+
max_sources_per_target: int = 16,
|
|
404
|
+
) -> None:
|
|
405
|
+
"""stale_time == 0 disables the tracker (no stale window applied)."""
|
|
406
|
+
self._store = _StaleStore(stale_time, stale_max_entries, max_sources_per_target)
|
|
407
|
+
self._lock = threading.Lock()
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def enabled(self) -> bool:
|
|
411
|
+
return self._store.enabled
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def _map(self) -> "OrderedDict[str, _StaleEntry]":
|
|
415
|
+
return self._store.map
|
|
416
|
+
|
|
417
|
+
def invalidate(self, patterns: list[str]) -> None:
|
|
418
|
+
if not self.enabled:
|
|
419
|
+
return
|
|
420
|
+
with self._lock:
|
|
421
|
+
self._store.invalidate(patterns)
|
|
422
|
+
|
|
423
|
+
def mark_stale(
|
|
424
|
+
self,
|
|
425
|
+
invalidate: list[str],
|
|
426
|
+
params: dict[str, str] | None,
|
|
427
|
+
mutation_selector: str,
|
|
428
|
+
) -> None:
|
|
429
|
+
if not self.enabled or not invalidate:
|
|
430
|
+
return
|
|
431
|
+
with self._lock:
|
|
432
|
+
self._store.mark_stale(invalidate, params, mutation_selector)
|
|
433
|
+
|
|
434
|
+
def mark(self, key: str) -> None:
|
|
435
|
+
if not self.enabled:
|
|
436
|
+
return
|
|
437
|
+
with self._lock:
|
|
438
|
+
self._store.mark(key)
|
|
439
|
+
|
|
440
|
+
def is_stale(self, method_or_key: str, path: str | None = None) -> bool:
|
|
441
|
+
if not self.enabled:
|
|
442
|
+
return False
|
|
443
|
+
method, concrete_path = _split_method_path(method_or_key, path)
|
|
444
|
+
with self._lock:
|
|
445
|
+
return self._store.lookup(
|
|
446
|
+
f"{method} {concrete_path}", concrete_path, method, _now()
|
|
447
|
+
)[1]
|
|
448
|
+
|
|
449
|
+
def lookup_stale(
|
|
450
|
+
self,
|
|
451
|
+
concrete_selector: str,
|
|
452
|
+
concrete_path: str,
|
|
453
|
+
method: str,
|
|
454
|
+
) -> tuple[list[str], bool]:
|
|
455
|
+
if not self.enabled:
|
|
456
|
+
return [], False
|
|
457
|
+
with self._lock:
|
|
458
|
+
return self._store.lookup(concrete_selector, concrete_path, method, _now())
|
|
459
|
+
|
|
460
|
+
def clear_stale(
|
|
461
|
+
self,
|
|
462
|
+
concrete_selector: str,
|
|
463
|
+
concrete_path: str,
|
|
464
|
+
method: str,
|
|
465
|
+
seq_snapshot: int,
|
|
466
|
+
) -> None:
|
|
467
|
+
if not self.enabled:
|
|
468
|
+
return
|
|
469
|
+
with self._lock:
|
|
470
|
+
self._store.clear(concrete_selector, concrete_path, method, seq_snapshot)
|
|
471
|
+
|
|
472
|
+
def build_request_meta(
|
|
473
|
+
self,
|
|
474
|
+
concrete_selector: str,
|
|
475
|
+
concrete_path: str,
|
|
476
|
+
method: str,
|
|
477
|
+
) -> _RequestMeta | None:
|
|
478
|
+
if not self.enabled:
|
|
479
|
+
return None
|
|
480
|
+
with self._lock:
|
|
481
|
+
return self._store.request_meta(concrete_selector, concrete_path, method)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
__all__ = [
|
|
485
|
+
"_RequestMeta",
|
|
486
|
+
"_StaleEntry",
|
|
487
|
+
"_StaleTracker",
|
|
488
|
+
"_StaleTrackerSync",
|
|
489
|
+
"interpolate_path",
|
|
490
|
+
"lookup_stale",
|
|
491
|
+
"path_matches_pattern",
|
|
492
|
+
"resolve_invalidation_targets",
|
|
493
|
+
"resolve_invalidation_targets_for_mutation",
|
|
494
|
+
]
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import random
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any, AsyncIterator
|
|
7
|
+
|
|
8
|
+
from ._transport import (
|
|
9
|
+
TransportAdapter,
|
|
10
|
+
TransportConn,
|
|
11
|
+
TransportKind,
|
|
12
|
+
TransportOpts,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
MAX_RECONNECT_DELAY = 30.0
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectionState(str, Enum):
|
|
19
|
+
CONNECTING = "connecting"
|
|
20
|
+
CONNECTED = "connected"
|
|
21
|
+
RECONNECTING = "reconnecting"
|
|
22
|
+
DISCONNECTED = "disconnected"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ResumableConnection:
|
|
26
|
+
"""Orchestrates a pluggable TransportAdapter chain.
|
|
27
|
+
|
|
28
|
+
Mirrors TS `createResumableConnection` + Rust `ResumableConnection::connect`:
|
|
29
|
+
- Ordered `transports` chain tried in order on first connect.
|
|
30
|
+
- First successful adapter index memoized in `_proven_index`.
|
|
31
|
+
- On reconnect, proven adapter tried first; full chain on failure.
|
|
32
|
+
- State transitions drive `.state` property.
|
|
33
|
+
|
|
34
|
+
A dropped connection (including a clean end of an SSE stream) reconnects
|
|
35
|
+
with exponential backoff and jitter. The attempt counter resets after every
|
|
36
|
+
delivered message, so `max_reconnect_attempts` limits consecutive failures,
|
|
37
|
+
not lifetime drops. When the attempts run out the iterator raises the last
|
|
38
|
+
error instead of ending silently. Cancellation is never swallowed.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
base_url: str,
|
|
44
|
+
transports: list[TransportAdapter],
|
|
45
|
+
opts: TransportOpts | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
if not transports:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"ResumableConnection requires at least one transport adapter",
|
|
50
|
+
)
|
|
51
|
+
self._url = base_url
|
|
52
|
+
self._transports = transports
|
|
53
|
+
self._opts = opts or TransportOpts()
|
|
54
|
+
self._state: ConnectionState = ConnectionState.DISCONNECTED
|
|
55
|
+
self._conn: TransportConn | None = None
|
|
56
|
+
self._proven_index: int | None = None
|
|
57
|
+
self._reconnect_attempts: int = 0
|
|
58
|
+
self._closed: bool = False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def state(self) -> ConnectionState:
|
|
62
|
+
return self._state
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def proven_transport(self) -> TransportKind | None:
|
|
66
|
+
if self._proven_index is None:
|
|
67
|
+
return None
|
|
68
|
+
if self._proven_index >= len(self._transports):
|
|
69
|
+
return None
|
|
70
|
+
return self._transports[self._proven_index].kind()
|
|
71
|
+
|
|
72
|
+
async def connect(self) -> None:
|
|
73
|
+
if self._closed:
|
|
74
|
+
raise RuntimeError("ResumableConnection is closed")
|
|
75
|
+
if self._state == ConnectionState.CONNECTED:
|
|
76
|
+
return
|
|
77
|
+
has_prior = self._conn is not None or self._proven_index is not None
|
|
78
|
+
self._state = (
|
|
79
|
+
ConnectionState.RECONNECTING if has_prior else ConnectionState.CONNECTING
|
|
80
|
+
)
|
|
81
|
+
await self._open_chain()
|
|
82
|
+
|
|
83
|
+
async def _open_chain(self) -> None:
|
|
84
|
+
last_err: Exception | None = None
|
|
85
|
+
order: list[int] = []
|
|
86
|
+
if self._proven_index is not None and self._proven_index < len(
|
|
87
|
+
self._transports
|
|
88
|
+
):
|
|
89
|
+
order.append(self._proven_index)
|
|
90
|
+
order.extend(i for i in range(len(self._transports)) if i not in order)
|
|
91
|
+
for idx in order:
|
|
92
|
+
adapter = self._transports[idx]
|
|
93
|
+
try:
|
|
94
|
+
conn = await adapter.connect(self._url, self._opts)
|
|
95
|
+
except Exception as exc: # CancelledError is a BaseException: it propagates
|
|
96
|
+
last_err = exc
|
|
97
|
+
continue
|
|
98
|
+
if self._closed:
|
|
99
|
+
await _close_quietly(conn)
|
|
100
|
+
raise RuntimeError("ResumableConnection is closed")
|
|
101
|
+
self._conn = conn
|
|
102
|
+
self._proven_index = idx
|
|
103
|
+
self._state = ConnectionState.CONNECTED
|
|
104
|
+
return
|
|
105
|
+
self._state = ConnectionState.DISCONNECTED
|
|
106
|
+
if last_err is not None:
|
|
107
|
+
raise last_err
|
|
108
|
+
raise RuntimeError("no transports configured")
|
|
109
|
+
|
|
110
|
+
async def send(self, data: Any) -> None:
|
|
111
|
+
if self._conn is None:
|
|
112
|
+
raise RuntimeError("not connected")
|
|
113
|
+
await self._conn.send(data)
|
|
114
|
+
|
|
115
|
+
async def close(self) -> None:
|
|
116
|
+
self._closed = True
|
|
117
|
+
self._state = ConnectionState.DISCONNECTED
|
|
118
|
+
if self._conn is not None:
|
|
119
|
+
conn = self._conn
|
|
120
|
+
self._conn = None
|
|
121
|
+
await _close_quietly(conn)
|
|
122
|
+
|
|
123
|
+
def _delay(self, attempt: int) -> float:
|
|
124
|
+
base = max(self._opts.reconnect_delay_ms, 0) / 1000.0
|
|
125
|
+
delay = min(base * (2 ** (attempt - 1)), MAX_RECONNECT_DELAY)
|
|
126
|
+
return delay / 2 + random.uniform(0, delay / 2)
|
|
127
|
+
|
|
128
|
+
def __aiter__(self) -> AsyncIterator[Any]:
|
|
129
|
+
return self._iter()
|
|
130
|
+
|
|
131
|
+
async def _iter(self) -> AsyncIterator[Any]:
|
|
132
|
+
if self._conn is None and not self._closed:
|
|
133
|
+
await self.connect()
|
|
134
|
+
while not self._closed:
|
|
135
|
+
conn = self._conn
|
|
136
|
+
if conn is None:
|
|
137
|
+
return
|
|
138
|
+
try:
|
|
139
|
+
value = await conn.recv()
|
|
140
|
+
except asyncio.CancelledError:
|
|
141
|
+
raise
|
|
142
|
+
except Exception as exc:
|
|
143
|
+
# transport died or the stream ended: drop it and reconnect with backoff
|
|
144
|
+
last_err: Exception = exc
|
|
145
|
+
self._conn = None
|
|
146
|
+
self._proven_index = None
|
|
147
|
+
self._state = ConnectionState.RECONNECTING
|
|
148
|
+
await _close_quietly(conn)
|
|
149
|
+
while True:
|
|
150
|
+
if self._closed:
|
|
151
|
+
return
|
|
152
|
+
self._reconnect_attempts += 1
|
|
153
|
+
max_attempts = self._opts.max_reconnect_attempts
|
|
154
|
+
if max_attempts > 0 and self._reconnect_attempts > max_attempts:
|
|
155
|
+
self._state = ConnectionState.DISCONNECTED
|
|
156
|
+
if isinstance(last_err, StopAsyncIteration):
|
|
157
|
+
return
|
|
158
|
+
raise last_err
|
|
159
|
+
await asyncio.sleep(self._delay(self._reconnect_attempts))
|
|
160
|
+
try:
|
|
161
|
+
await self._open_chain()
|
|
162
|
+
break
|
|
163
|
+
except asyncio.CancelledError:
|
|
164
|
+
raise
|
|
165
|
+
except Exception as reopen_exc:
|
|
166
|
+
last_err = reopen_exc
|
|
167
|
+
continue
|
|
168
|
+
self._reconnect_attempts = 0
|
|
169
|
+
yield value
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
async def _close_quietly(conn: TransportConn) -> None:
|
|
173
|
+
try:
|
|
174
|
+
await conn.close()
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
__all__ = ["ConnectionState", "ResumableConnection"]
|