@lovrozagar/honey 0.1.0 → 0.2.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 +1596 -6
- package/dist/accepts.d.ts +6 -0
- package/dist/accepts.d.ts.map +1 -0
- package/dist/body-limit.d.ts +22 -0
- package/dist/body-limit.d.ts.map +1 -0
- package/dist/build/index.d.ts +42 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/client/error.d.ts +14 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/http.d.ts +88 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/sdk.d.ts +34 -0
- package/dist/client/sdk.d.ts.map +1 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/types.d.ts +124 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client-mcp/server.d.ts +10 -0
- package/dist/client-mcp/server.d.ts.map +1 -0
- package/dist/client-mcp/types.d.ts +16 -0
- package/dist/client-mcp/types.d.ts.map +1 -0
- package/dist/cloudflare/realtime.d.ts +30 -0
- package/dist/cloudflare/realtime.d.ts.map +1 -0
- package/dist/codegen-go-cli.d.ts +22 -0
- package/dist/codegen-go-cli.d.ts.map +1 -0
- package/dist/codegen-go.d.ts +29 -0
- package/dist/codegen-go.d.ts.map +1 -0
- package/dist/codegen-hash.d.ts +3 -0
- package/dist/codegen-hash.d.ts.map +1 -0
- package/dist/codegen-ir.d.ts +123 -0
- package/dist/codegen-ir.d.ts.map +1 -0
- package/dist/codegen-loaders.d.ts +12 -0
- package/dist/codegen-loaders.d.ts.map +1 -0
- package/dist/codegen-mcp.d.ts +13 -0
- package/dist/codegen-mcp.d.ts.map +1 -0
- package/dist/codegen-python.d.ts +21 -0
- package/dist/codegen-python.d.ts.map +1 -0
- package/dist/codegen-rust.d.ts +18 -0
- package/dist/codegen-rust.d.ts.map +1 -0
- package/dist/codegen-sanitize.d.ts +3 -0
- package/dist/codegen-sanitize.d.ts.map +1 -0
- package/dist/codegen-schema-naming.d.ts +22 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -0
- package/dist/codegen.d.ts +178 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/cookie-sign.d.ts +3 -0
- package/dist/cookie-sign.d.ts.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cors.d.ts +13 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/csrf.d.ts +9 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/curl-logger.d.ts +33 -0
- package/dist/curl-logger.d.ts.map +1 -0
- package/dist/detect-runtime.d.ts +16 -0
- package/dist/detect-runtime.d.ts.map +1 -0
- package/dist/error.d.ts +21 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/etag.d.ts +9 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/feature-load.d.ts +3 -0
- package/dist/feature-load.d.ts.map +1 -0
- package/dist/go-type-emitter.d.ts +33 -0
- package/dist/go-type-emitter.d.ts.map +1 -0
- package/dist/honey-response.d.ts +82 -0
- package/dist/honey-response.d.ts.map +1 -0
- package/dist/i18n-slot.d.ts +7 -0
- package/dist/i18n-slot.d.ts.map +1 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/index.d.ts +470 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/ip-restrict.d.ts +12 -0
- package/dist/ip-restrict.d.ts.map +1 -0
- package/dist/logger.d.ts +45 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/meta-spec.d.ts +81 -0
- package/dist/meta-spec.d.ts.map +1 -0
- package/dist/middleware.d.ts +60 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/node-request.d.ts +59 -0
- package/dist/node-request.d.ts.map +1 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/openapi/register.d.ts +3 -0
- package/dist/openapi/register.d.ts.map +1 -0
- package/dist/openapi/scalar.d.ts +12 -0
- package/dist/openapi/scalar.d.ts.map +1 -0
- package/dist/openapi/spec-factory.d.ts +31 -0
- package/dist/openapi/spec-factory.d.ts.map +1 -0
- package/dist/openapi/spec.d.ts +19 -0
- package/dist/openapi/spec.d.ts.map +1 -0
- package/dist/openapi/swagger.d.ts +12 -0
- package/dist/openapi/swagger.d.ts.map +1 -0
- package/dist/plugin.d.ts +167 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/powered-by.d.ts +7 -0
- package/dist/powered-by.d.ts.map +1 -0
- package/dist/pretty-json.d.ts +10 -0
- package/dist/pretty-json.d.ts.map +1 -0
- package/dist/proxy.d.ts +51 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/python-type-emitter.d.ts +11 -0
- package/dist/python-type-emitter.d.ts.map +1 -0
- package/dist/realtime/buffer.d.ts +18 -0
- package/dist/realtime/buffer.d.ts.map +1 -0
- package/dist/realtime/bus.d.ts +11 -0
- package/dist/realtime/bus.d.ts.map +1 -0
- package/dist/realtime/longpoll.d.ts +23 -0
- package/dist/realtime/longpoll.d.ts.map +1 -0
- package/dist/realtime/protocol.d.ts +50 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/route.d.ts +42 -0
- package/dist/realtime/route.d.ts.map +1 -0
- package/dist/realtime/runtime.d.ts +74 -0
- package/dist/realtime/runtime.d.ts.map +1 -0
- package/dist/request-id.d.ts +12 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-to-curl.d.ts +9 -0
- package/dist/request-to-curl.d.ts.map +1 -0
- package/dist/response.d.ts +57 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/rust-type-emitter.d.ts +32 -0
- package/dist/rust-type-emitter.d.ts.map +1 -0
- package/dist/secure-headers.d.ts +16 -0
- package/dist/secure-headers.d.ts.map +1 -0
- package/dist/serve-register.d.ts +2 -0
- package/dist/serve-register.d.ts.map +1 -0
- package/dist/serve-slot.d.ts +6 -0
- package/dist/serve-slot.d.ts.map +1 -0
- package/dist/serve.d.ts +21 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/server-timing.d.ts +9 -0
- package/dist/server-timing.d.ts.map +1 -0
- package/dist/static.d.ts +12 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/telemetry/otel.d.ts +59 -0
- package/dist/telemetry/otel.d.ts.map +1 -0
- package/dist/testing.d.ts +24 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/timeout.d.ts +7 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/tree.d.ts +90 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/ts-type-emitter.d.ts +4 -0
- package/dist/ts-type-emitter.d.ts.map +1 -0
- package/dist/type-emitter.d.ts +8 -0
- package/dist/type-emitter.d.ts.map +1 -0
- package/dist/type-extractor.d.ts +39 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/types.d.ts +451 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +18 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/ws/bun.d.ts +28 -0
- package/dist/ws/bun.d.ts.map +1 -0
- package/dist/ws/cloudflare.d.ts +55 -0
- package/dist/ws/cloudflare.d.ts.map +1 -0
- package/dist/ws/deno.d.ts +13 -0
- package/dist/ws/deno.d.ts.map +1 -0
- package/dist/ws/node.d.ts +10 -0
- package/dist/ws/node.d.ts.map +1 -0
- package/dist/yaml.d.ts +4 -0
- package/dist/yaml.d.ts.map +1 -0
- package/docs/api-ref/go.md +82 -82
- package/docs/api-ref/python.md +82 -82
- package/docs/api-ref/rust.md +82 -82
- package/docs/api-ref/typescript.md +82 -82
- package/docs/invalidate-spec.md +13 -13
- package/docs/meta-spec.md +513 -0
- package/docs/packaging.md +85 -0
- package/docs/sdk.md +87 -73
- package/package.json +195 -51
- package/src/body-limit.ts +2 -1
- package/src/build/index.ts +2 -9
- package/src/cli.ts +2 -3
- package/src/client/http.ts +204 -223
- package/src/client/index.ts +6 -14
- package/src/client/sdk.ts +24 -17
- package/src/client/types.ts +4 -9
- package/src/client/ws.ts +3 -5
- package/src/client-mcp/server.ts +1 -4
- package/src/cloudflare/realtime.ts +8 -10
- package/src/codegen-go-cli.ts +74 -67
- package/src/codegen-go.ts +71 -51
- package/src/codegen-ir.ts +16 -27
- package/src/codegen-loaders.ts +5 -8
- package/src/codegen-mcp.ts +9 -27
- package/src/codegen-python.ts +47 -27
- package/src/codegen-rust.ts +110 -66
- package/src/codegen-sanitize.ts +1 -7
- package/src/codegen-schema-naming.ts +8 -2
- package/src/codegen.ts +299 -196
- package/src/context.ts +91 -95
- package/src/cookie-sign.ts +5 -13
- package/src/cookie.ts +1 -2
- package/src/cors.ts +4 -6
- package/src/crypto.ts +1 -4
- package/src/csrf.ts +3 -3
- package/src/curl-logger.ts +14 -32
- package/src/detect-runtime.ts +1 -3
- package/src/errors.ts +1 -3
- package/src/etag.ts +3 -4
- package/src/go-type-emitter.ts +45 -40
- package/src/honey-response.ts +1 -3
- package/src/i18n.ts +3 -12
- package/src/index.ts +1168 -1412
- package/src/ip-restrict.ts +2 -1
- package/src/logger.ts +13 -13
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +111 -79
- package/src/node-request.ts +2 -1
- package/src/node.ts +9 -10
- package/src/openapi/register.ts +1 -2
- package/src/openapi/scalar.ts +9 -9
- package/src/openapi/spec-factory.ts +2 -2
- package/src/openapi/spec.ts +26 -19
- package/src/openapi/swagger.ts +9 -9
- package/src/plugin.ts +226 -256
- package/src/powered-by.ts +2 -1
- package/src/pretty-json.ts +2 -1
- package/src/proxy.ts +1 -3
- package/src/python-type-emitter.ts +37 -12
- package/src/realtime/route.ts +1 -1
- package/src/realtime/runtime.ts +9 -12
- package/src/request-id.ts +3 -4
- package/src/request-to-curl.ts +1 -4
- package/src/response.ts +150 -199
- package/src/rust-type-emitter.ts +66 -25
- package/src/secure-headers.ts +2 -1
- package/src/serve.ts +3 -12
- package/src/server-timing.ts +2 -1
- package/src/static.ts +3 -4
- package/src/telemetry/otel.ts +1 -6
- package/src/testing.ts +2 -11
- package/src/timeout.ts +2 -1
- package/src/tree.ts +12 -27
- package/src/ts-type-emitter.ts +71 -15
- package/src/type-emitter.ts +226 -237
- package/src/type-extractor.ts +260 -334
- package/src/types.ts +168 -68
- package/src/validation.ts +7 -30
- package/src/ws/bun.ts +2 -9
- package/src/ws/cloudflare.ts +13 -16
- package/src/ws/deno.ts +31 -40
- package/src/ws/node.ts +4 -19
package/src/index.ts
CHANGED
|
@@ -1,62 +1,47 @@
|
|
|
1
|
-
import { HoneyContext } from "./context.ts"
|
|
2
|
-
import { HoneyError } from "./error.ts"
|
|
3
|
-
import { ERROR_META } from "./errors.ts"
|
|
4
|
-
import type { MiddlewareFn, RuntimeMiddleware } from "./middleware.ts"
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
1
|
+
import { HoneyContext } from "./context.ts"
|
|
2
|
+
import { HoneyError } from "./error.ts"
|
|
3
|
+
import { ERROR_META } from "./errors.ts"
|
|
4
|
+
import type { MiddlewareFn, RuntimeMiddleware } from "./middleware.ts"
|
|
5
|
+
import { collectMiddlewareMeta } from "./middleware.ts"
|
|
6
|
+
import { compileChain, executeChain } from "./middleware.ts"
|
|
7
|
+
import type { ProxyConfig } from "./proxy.ts"
|
|
8
|
+
import { createProxyHandler } from "./proxy.ts"
|
|
9
|
+
import type { CustomErrorFormatter, ErrorFormatter, ResponseOptions, TypedResponse } from "./response.ts"
|
|
10
|
+
import { createErrorResponse, type HoneyRes } from "./response.ts"
|
|
11
|
+
import type { OutputValidator, RouteHandler, RouteTree, TreeNode, WSRouteHandler } from "./tree.ts"
|
|
12
|
+
import { createNode, insertRoute, insertWsRoute, matchRoute, matchWsRoute, mergeInto } from "./tree.ts"
|
|
13
|
+
import { createBus } from "./realtime/bus.ts"
|
|
14
|
+
import type { RealtimeBus } from "./realtime/bus.ts"
|
|
15
|
+
import { createConnContext } from "./realtime/route.ts"
|
|
16
|
+
import type { RealtimeRouteOpts } from "./realtime/route.ts"
|
|
8
17
|
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ResponseOptions,
|
|
12
|
-
TypedResponse,
|
|
13
|
-
} from "./response.ts";
|
|
14
|
-
import { createErrorResponse, type HoneyRes } from "./response.ts";
|
|
15
|
-
import type {
|
|
16
|
-
OutputValidator,
|
|
17
|
-
RouteHandler,
|
|
18
|
-
RouteTree,
|
|
19
|
-
TreeNode,
|
|
20
|
-
WSRouteHandler,
|
|
21
|
-
} from "./tree.ts";
|
|
22
|
-
import {
|
|
23
|
-
createNode,
|
|
24
|
-
insertRoute,
|
|
25
|
-
insertWsRoute,
|
|
26
|
-
matchRoute,
|
|
27
|
-
matchWsRoute,
|
|
28
|
-
mergeInto,
|
|
29
|
-
} from "./tree.ts";
|
|
30
|
-
import { createBus } from "./realtime/bus.ts";
|
|
31
|
-
import type { RealtimeBus } from "./realtime/bus.ts";
|
|
32
|
-
import { createConnContext } from "./realtime/route.ts";
|
|
33
|
-
import type { RealtimeRouteOpts } from "./realtime/route.ts";
|
|
34
|
-
import type {
|
|
35
|
-
ComputeErrorsByStatus,
|
|
36
|
-
DefaultMeta,
|
|
18
|
+
ComputeErrorsByStatus,
|
|
19
|
+
DefaultMeta,
|
|
37
20
|
ExtractSchemas,
|
|
38
21
|
HttpMethod,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
22
|
+
InferInputMap,
|
|
23
|
+
InferOutput,
|
|
24
|
+
HoneyMetaSpec,
|
|
25
|
+
InputSchemasDef,
|
|
26
|
+
MergePath,
|
|
27
|
+
MergeRoute,
|
|
28
|
+
MetaSpecConfig,
|
|
29
|
+
OutputSchemaDef,
|
|
30
|
+
ParamsFromPath,
|
|
31
|
+
StandardSchemaLike,
|
|
32
|
+
StatusKey,
|
|
48
33
|
TapContext,
|
|
49
|
-
} from "./types.ts"
|
|
50
|
-
import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts"
|
|
51
|
-
import { validateInput, validateOutput } from "./validation.ts"
|
|
52
|
-
import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts"
|
|
53
|
-
import { loadHoneyFeature } from "./feature-load.ts"
|
|
54
|
-
import { getI18nRuntime } from "./i18n-slot.ts"
|
|
55
|
-
import { getOpenApiRuntime } from "./openapi/spec-factory.ts"
|
|
56
|
-
import { getServeRuntime } from "./serve-slot.ts"
|
|
57
|
-
import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
58
|
-
|
|
59
|
-
export { HoneyContext } from "./context.ts"
|
|
34
|
+
} from "./types.ts"
|
|
35
|
+
import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts"
|
|
36
|
+
import { validateInput, validateOutput } from "./validation.ts"
|
|
37
|
+
import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts"
|
|
38
|
+
import { loadHoneyFeature } from "./feature-load.ts"
|
|
39
|
+
import { getI18nRuntime } from "./i18n-slot.ts"
|
|
40
|
+
import { getOpenApiRuntime } from "./openapi/spec-factory.ts"
|
|
41
|
+
import { getServeRuntime } from "./serve-slot.ts"
|
|
42
|
+
import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
43
|
+
|
|
44
|
+
export { HoneyContext } from "./context.ts"
|
|
60
45
|
/** HoneyContext without internal backing fields — use this for consumer-facing types */
|
|
61
46
|
export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
|
|
62
47
|
import("./context.ts").HoneyContext<TEnv>,
|
|
@@ -68,14 +53,14 @@ export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
|
|
|
68
53
|
| "_lzSearchAll"
|
|
69
54
|
| "_lzUrlFn"
|
|
70
55
|
| "_setErrors"
|
|
71
|
-
|
|
72
|
-
export { HoneyError } from "./error.ts"
|
|
73
|
-
export { defineErrors, ERROR_META } from "./errors.ts"
|
|
74
|
-
export type { ErrorMetaEntry } from "./errors.ts"
|
|
75
|
-
export type { MiddlewareFn } from "./middleware.ts"
|
|
76
|
-
export { createMiddleware, defineMiddleware } from "./middleware.ts"
|
|
77
|
-
export type { ProxyConfig } from "./proxy.ts"
|
|
78
|
-
export type { PendingTap, TapContext, TapHandler } from "./types.ts"
|
|
56
|
+
>
|
|
57
|
+
export { HoneyError } from "./error.ts"
|
|
58
|
+
export { defineErrors, ERROR_META } from "./errors.ts"
|
|
59
|
+
export type { ErrorMetaEntry } from "./errors.ts"
|
|
60
|
+
export type { MiddlewareFn } from "./middleware.ts"
|
|
61
|
+
export { createMiddleware, defineMiddleware } from "./middleware.ts"
|
|
62
|
+
export type { ProxyConfig } from "./proxy.ts"
|
|
63
|
+
export type { PendingTap, TapContext, TapHandler } from "./types.ts"
|
|
79
64
|
export type {
|
|
80
65
|
CookieOptions,
|
|
81
66
|
CustomErrorFormatter,
|
|
@@ -85,9 +70,9 @@ export type {
|
|
|
85
70
|
SSEOptions,
|
|
86
71
|
SSEStream,
|
|
87
72
|
TypedResponse,
|
|
88
|
-
} from "./response.ts"
|
|
89
|
-
export { createErrorResponse, HoneyRes } from "./response.ts"
|
|
90
|
-
export { mergeTree } from "./tree.ts"
|
|
73
|
+
} from "./response.ts"
|
|
74
|
+
export { createErrorResponse, HoneyRes } from "./response.ts"
|
|
75
|
+
export { mergeTree } from "./tree.ts"
|
|
91
76
|
export type {
|
|
92
77
|
DefaultMeta,
|
|
93
78
|
HoneyCodegen,
|
|
@@ -110,6 +95,18 @@ export type {
|
|
|
110
95
|
InferRoutes,
|
|
111
96
|
InputSchemasDef,
|
|
112
97
|
MergePath,
|
|
98
|
+
HoneyMetaSpec,
|
|
99
|
+
MetaSpecConfig,
|
|
100
|
+
MetaSpecContext,
|
|
101
|
+
MetaSpecEntry,
|
|
102
|
+
MetaSpecExpand,
|
|
103
|
+
MetaSpecProfile,
|
|
104
|
+
MetaSpecSchemaEntry,
|
|
105
|
+
MetaSpecSchemaSearch,
|
|
106
|
+
MetaSpecSchemaSource,
|
|
107
|
+
MetaSpecSingle,
|
|
108
|
+
MetaSpecStrictness,
|
|
109
|
+
MetaSpecTarget,
|
|
113
110
|
OpenApiMeta,
|
|
114
111
|
OutputSchemaDef,
|
|
115
112
|
Overwrite,
|
|
@@ -118,171 +115,170 @@ export type {
|
|
|
118
115
|
StandardSchemaLike,
|
|
119
116
|
StatusKey,
|
|
120
117
|
SuccessStatusKey,
|
|
121
|
-
} from "./types.ts"
|
|
122
|
-
export type { WSAdapter, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts"
|
|
123
|
-
export type { ConnContext, RealtimeRouteOpts } from "./realtime/route.ts"
|
|
124
|
-
export type { RealtimeBus } from "./realtime/bus.ts"
|
|
125
|
-
export type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
126
|
-
export type { ServeRuntime } from "./detect-runtime.ts"
|
|
127
|
-
export { detectRuntime } from "./detect-runtime.ts"
|
|
118
|
+
} from "./types.ts"
|
|
119
|
+
export type { WSAdapter, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts"
|
|
120
|
+
export type { ConnContext, RealtimeRouteOpts } from "./realtime/route.ts"
|
|
121
|
+
export type { RealtimeBus } from "./realtime/bus.ts"
|
|
122
|
+
export type { HoneyServeOptions, ServeHandle } from "./serve.ts"
|
|
123
|
+
export type { ServeRuntime } from "./detect-runtime.ts"
|
|
124
|
+
export { detectRuntime } from "./detect-runtime.ts"
|
|
128
125
|
|
|
129
126
|
/** Type predicate for incoming wire-protocol msg frames from the realtime client. */
|
|
130
127
|
function isMsgFrame(value: unknown): value is { data: unknown; t: "msg" } {
|
|
131
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
|
132
|
-
if (!("t" in value) || !("data" in value)) return false
|
|
133
|
-
return value.t === "msg"
|
|
128
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
|
129
|
+
if (!("t" in value) || !("data" in value)) return false
|
|
130
|
+
return value.t === "msg"
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
type TelemetryAdapter = {
|
|
137
|
-
onError?(ctx: {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}): void
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
path: string;
|
|
147
|
-
status: number;
|
|
148
|
-
}): void;
|
|
149
|
-
onMethodNotAllowed?(ctx: {
|
|
150
|
-
allowed: string[];
|
|
151
|
-
method: string;
|
|
152
|
-
path: string;
|
|
153
|
-
req: Request;
|
|
154
|
-
}): void;
|
|
155
|
-
onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void;
|
|
156
|
-
onNotFound?(ctx: { method: string; path: string; req: Request }): void;
|
|
157
|
-
onRequest?(ctx: { env: unknown; req: Request }): void;
|
|
158
|
-
onResponse?(ctx: { duration: number; req: Request; status: number }): void;
|
|
159
|
-
onRoute?(ctx: {
|
|
160
|
-
method: string;
|
|
161
|
-
params: Record<string, string>;
|
|
162
|
-
path: string;
|
|
163
|
-
req: Request;
|
|
164
|
-
}): void;
|
|
165
|
-
};
|
|
134
|
+
onError?(ctx: { duration: number; error: HoneyError; method: string; path: string }): void
|
|
135
|
+
onHandler?(ctx: { duration: number; method: string; path: string; status: number }): void
|
|
136
|
+
onMethodNotAllowed?(ctx: { allowed: string[]; method: string; path: string; req: Request }): void
|
|
137
|
+
onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void
|
|
138
|
+
onNotFound?(ctx: { method: string; path: string; req: Request }): void
|
|
139
|
+
onRequest?(ctx: { env: unknown; req: Request }): void
|
|
140
|
+
onResponse?(ctx: { duration: number; req: Request; status: number }): void
|
|
141
|
+
onRoute?(ctx: { method: string; params: Record<string, string>; path: string; req: Request }): void
|
|
142
|
+
}
|
|
166
143
|
|
|
167
144
|
type ErrorI18nConfig<TEnv> = {
|
|
168
|
-
errors?: Record<string, Record<string, string
|
|
169
|
-
fieldNames?: Record<string, Record<string, string
|
|
145
|
+
errors?: Record<string, Record<string, string>>
|
|
146
|
+
fieldNames?: Record<string, Record<string, string>>
|
|
170
147
|
resolveLocale: (ctx: {
|
|
171
|
-
cookies: Record<string, string
|
|
172
|
-
env: TEnv
|
|
173
|
-
headers: Record<string, string
|
|
174
|
-
params: Record<string, string
|
|
175
|
-
req: Request
|
|
176
|
-
search: Record<string, string
|
|
177
|
-
}) => string | Promise<string
|
|
178
|
-
}
|
|
148
|
+
cookies: Record<string, string>
|
|
149
|
+
env: TEnv
|
|
150
|
+
headers: Record<string, string>
|
|
151
|
+
params: Record<string, string>
|
|
152
|
+
req: Request
|
|
153
|
+
search: Record<string, string>
|
|
154
|
+
}) => string | Promise<string>
|
|
155
|
+
}
|
|
179
156
|
|
|
180
157
|
type Logger = {
|
|
181
|
-
warn?(msg: string, ...args: unknown[]): void
|
|
182
|
-
}
|
|
158
|
+
warn?(msg: string, ...args: unknown[]): void
|
|
159
|
+
}
|
|
183
160
|
|
|
184
161
|
function mergePath(base: string, path: string): string {
|
|
185
|
-
if (base === "/") return path
|
|
186
|
-
if (path === "/") return base
|
|
187
|
-
return `${base}${path}
|
|
162
|
+
if (base === "/") return path
|
|
163
|
+
if (path === "/") return base
|
|
164
|
+
return `${base}${path}`
|
|
188
165
|
}
|
|
189
166
|
|
|
190
167
|
/** Normalize a scope prefix: ensure leading slash, strip trailing slash (except "/"). */
|
|
191
168
|
function normalizeScopePath(raw: string): string {
|
|
192
|
-
let p = raw
|
|
193
|
-
if (p.length === 0) return "/"
|
|
194
|
-
if (p.charCodeAt(0) !== 47) p = `/${p}
|
|
195
|
-
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1)
|
|
196
|
-
return p
|
|
169
|
+
let p = raw
|
|
170
|
+
if (p.length === 0) return "/"
|
|
171
|
+
if (p.charCodeAt(0) !== 47) p = `/${p}`
|
|
172
|
+
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1)
|
|
173
|
+
return p
|
|
197
174
|
}
|
|
198
175
|
|
|
199
176
|
/** Check whether fullPath falls under scope prefix — exact match or next char is '/'. */
|
|
200
177
|
function scopeMatches(prefix: string, fullPath: string): boolean {
|
|
201
|
-
if (prefix === "/") return true
|
|
202
|
-
if (fullPath === prefix) return true
|
|
203
|
-
if (fullPath.length <= prefix.length) return false
|
|
204
|
-
if (fullPath.charCodeAt(prefix.length) !== 47) return false
|
|
205
|
-
return fullPath.startsWith(prefix)
|
|
178
|
+
if (prefix === "/") return true
|
|
179
|
+
if (fullPath === prefix) return true
|
|
180
|
+
if (fullPath.length <= prefix.length) return false
|
|
181
|
+
if (fullPath.charCodeAt(prefix.length) !== 47) return false
|
|
182
|
+
return fullPath.startsWith(prefix)
|
|
206
183
|
}
|
|
207
184
|
|
|
208
185
|
type HoneyGraph = {
|
|
209
186
|
handlerMap: Record<string, RouteHandler> | null
|
|
210
187
|
hasRouteTree: boolean
|
|
188
|
+
/** Codegen-time meta → OpenAPI policy. Never read on the request path */
|
|
189
|
+
metaSpec: MetaSpecConfig | null
|
|
211
190
|
realtimeBus: RealtimeBus | null
|
|
212
191
|
root: TreeNode
|
|
213
192
|
}
|
|
214
193
|
|
|
215
|
-
function walkTreeHandlers(
|
|
216
|
-
root: TreeNode,
|
|
217
|
-
cb: (h: RouteHandler) => void,
|
|
218
|
-
wsCb?: (h: WSRouteHandler) => void,
|
|
219
|
-
): void {
|
|
194
|
+
function walkTreeHandlers(root: TreeNode, cb: (h: RouteHandler) => void, wsCb?: (h: WSRouteHandler) => void): void {
|
|
220
195
|
if (root.m) {
|
|
221
|
-
for (const h of Object.values(root.m) as RouteHandler[]) cb(h)
|
|
196
|
+
for (const h of Object.values(root.m) as RouteHandler[]) cb(h)
|
|
222
197
|
}
|
|
223
198
|
if (root.w) {
|
|
224
|
-
for (const h of Object.values(root.w.m) as RouteHandler[]) cb(h)
|
|
199
|
+
for (const h of Object.values(root.w.m) as RouteHandler[]) cb(h)
|
|
225
200
|
}
|
|
226
|
-
if (root.ws && wsCb) wsCb(root.ws)
|
|
227
|
-
for (const child of Object.values(root.s)) walkTreeHandlers(child, cb, wsCb)
|
|
228
|
-
if (root.d) walkTreeHandlers(root.d.c, cb, wsCb)
|
|
201
|
+
if (root.ws && wsCb) wsCb(root.ws)
|
|
202
|
+
for (const child of Object.values(root.s)) walkTreeHandlers(child, cb, wsCb)
|
|
203
|
+
if (root.d) walkTreeHandlers(root.d.c, cb, wsCb)
|
|
229
204
|
}
|
|
230
205
|
|
|
231
206
|
const EMPTY_PARAMS = EMPTY_OBJ as Record<string, string>
|
|
232
207
|
const EMPTY_MW: RuntimeMiddleware[] = []
|
|
233
208
|
|
|
209
|
+
function requestIsWsUpgrade(request: Request): boolean {
|
|
210
|
+
try {
|
|
211
|
+
return request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
212
|
+
} catch {
|
|
213
|
+
/* Deno closes the Request after upgradeWebSocket */
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
234
218
|
/** Find first '?' or '#' in url starting from pos */
|
|
235
219
|
function findSearchOrHash(url: string, pos: number): number {
|
|
236
220
|
for (let i = pos; i < url.length; i++) {
|
|
237
|
-
const c = url.charCodeAt(i)
|
|
238
|
-
if (c === 63 || c === 35) return i
|
|
221
|
+
const c = url.charCodeAt(i)
|
|
222
|
+
if (c === 63 || c === 35) return i
|
|
239
223
|
}
|
|
240
|
-
return -1
|
|
224
|
+
return -1
|
|
241
225
|
}
|
|
242
226
|
|
|
243
227
|
function safeFire(fn: (() => unknown) | undefined, logger?: Logger): void {
|
|
244
|
-
if (fn === undefined) return
|
|
228
|
+
if (fn === undefined) return
|
|
245
229
|
try {
|
|
246
|
-
const result = fn()
|
|
230
|
+
const result = fn()
|
|
247
231
|
if (result && typeof result === "object" && "catch" in result) {
|
|
248
|
-
(result as Promise<unknown>).catch((e: unknown) => {
|
|
249
|
-
logger?.warn?.("telemetry callback failed", e)
|
|
250
|
-
})
|
|
232
|
+
;(result as Promise<unknown>).catch((e: unknown) => {
|
|
233
|
+
logger?.warn?.("telemetry callback failed", e)
|
|
234
|
+
})
|
|
251
235
|
}
|
|
252
236
|
} catch (e) {
|
|
253
|
-
logger?.warn?.("telemetry callback failed", e)
|
|
237
|
+
logger?.warn?.("telemetry callback failed", e)
|
|
254
238
|
}
|
|
255
239
|
}
|
|
256
240
|
|
|
257
241
|
/** Internal context shared across extracted fetch sub-methods */
|
|
258
242
|
type FetchCtx<TEnv> = {
|
|
259
|
-
env: TEnv
|
|
260
|
-
executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined
|
|
261
|
-
log: Logger | undefined
|
|
262
|
-
request: Request
|
|
263
|
-
startTime: number
|
|
264
|
-
url: () => URL
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
defaultShape: Record<string, unknown>,
|
|
270
|
-
): Record<string, unknown> {
|
|
271
|
-
return defaultShape;
|
|
243
|
+
env: TEnv
|
|
244
|
+
executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined
|
|
245
|
+
log: Logger | undefined
|
|
246
|
+
request: Request
|
|
247
|
+
startTime: number
|
|
248
|
+
url: () => URL
|
|
249
|
+
/** Already known from fetch() before Deno.upgradeWebSocket consumes the request. */
|
|
250
|
+
wsUpgrade?: boolean
|
|
251
|
+
/** Headers copied before Deno.upgradeWebSocket closes the Request. */
|
|
252
|
+
headerSnap?: Headers
|
|
272
253
|
}
|
|
273
254
|
|
|
274
|
-
|
|
255
|
+
function requestWithHeaders(req: Request, headers: Headers): Request {
|
|
256
|
+
return new Proxy(req, {
|
|
257
|
+
get(target, prop) {
|
|
258
|
+
if (prop === "headers") return headers
|
|
259
|
+
const value = Reflect.get(target, prop, target)
|
|
260
|
+
return typeof value === "function" ? (value as (...args: never[]) => unknown).bind(target) : value
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
}
|
|
275
264
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
265
|
+
function ctxRequest(fc: FetchCtx<unknown>): Request {
|
|
266
|
+
if (!fc.headerSnap) return fc.request
|
|
267
|
+
try {
|
|
268
|
+
void fc.request.headers.get("upgrade")
|
|
269
|
+
return fc.request
|
|
270
|
+
} catch {
|
|
271
|
+
return requestWithHeaders(fc.request, fc.headerSnap)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function defaultErrorFormatter(_error: HoneyError, defaultShape: Record<string, unknown>): Record<string, unknown> {
|
|
276
|
+
return defaultShape
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
type ErrorFormatterFn = ErrorFormatter
|
|
280
|
+
|
|
281
|
+
const STATIC_CTX_RESERVED = new Set(["background", "cookies", "env", "headers", "params", "req", "res", "search"])
|
|
286
282
|
|
|
287
283
|
/* errorKeys the framework throws on its own behalf — input/output validation, content negotiation,
|
|
288
284
|
* routing. Always passes the boundary check; users never declare these via .errors(). */
|
|
@@ -298,7 +294,7 @@ const FRAMEWORK_EKS = new Set<(typeof EK)[keyof typeof EK]>([
|
|
|
298
294
|
EK.request_timeout,
|
|
299
295
|
EK.gateway_timeout,
|
|
300
296
|
EK.bad_gateway,
|
|
301
|
-
])
|
|
297
|
+
])
|
|
302
298
|
|
|
303
299
|
export class Honey<
|
|
304
300
|
TEnv,
|
|
@@ -311,171 +307,176 @@ export class Honey<
|
|
|
311
307
|
TTaps extends Record<string, unknown> = {},
|
|
312
308
|
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
313
309
|
> {
|
|
314
|
-
declare readonly $basePath: TBasePath
|
|
315
|
-
declare readonly $ctx: TCtx
|
|
316
|
-
declare readonly $env: TEnv
|
|
317
|
-
declare readonly $errorFactory: TErrorFactory
|
|
318
|
-
declare readonly $meta: TMeta
|
|
319
|
-
declare readonly $routes: TRoutes
|
|
320
|
-
declare readonly $taps: TTaps
|
|
321
|
-
private _basePath: string
|
|
322
|
-
private _defaultBoundaryKey: string | null
|
|
323
|
-
private _defaultErrorKeys: Set<string
|
|
324
|
-
private _errorFactory: unknown
|
|
325
|
-
private _errorSchema: StandardSchemaLike | null
|
|
326
|
-
private _customErrorFormatter: CustomErrorFormatter | null
|
|
327
|
-
private _customErrorSchema: StandardSchemaLike | null
|
|
328
|
-
private _chainMeta: Record<string, unknown> | null
|
|
329
|
-
private _chainMiddlewares: RuntimeMiddleware[]
|
|
330
|
-
private _scopedMiddlewares: ScopedEntry[]
|
|
331
|
-
private _contextValues: Record<string, unknown> | null
|
|
332
|
-
private _errorFormatter: ErrorFormatterFn
|
|
333
|
-
private _errorI18n: ErrorI18nConfig<TEnv> | null
|
|
334
|
-
private _globalMiddlewares: RuntimeMiddleware[]
|
|
335
|
-
private _graph: HoneyGraph
|
|
336
|
-
private _hasWsRoutes: boolean
|
|
337
|
-
private _logger: Logger | null
|
|
338
|
-
private _outputValidation: "always" | "dev" | "off"
|
|
339
|
-
private _staticRoutes: { map: Record<string, RouteHandler> | null }
|
|
340
|
-
private _stripPrefix: string | null
|
|
341
|
-
private _trailingSlash: "enforce" | "ignore" | "strip"
|
|
342
|
-
private _wsAdapter: WSAdapter | null
|
|
343
|
-
private _openApiCache: { epoch: number; value: Promise<unknown> } | null
|
|
344
|
-
private _openApiYamlCache: { epoch: number; value: Promise<string> } | null
|
|
345
|
-
private _manifestCache: { epoch: number; value: Promise<unknown> } | null
|
|
310
|
+
declare readonly $basePath: TBasePath
|
|
311
|
+
declare readonly $ctx: TCtx
|
|
312
|
+
declare readonly $env: TEnv
|
|
313
|
+
declare readonly $errorFactory: TErrorFactory
|
|
314
|
+
declare readonly $meta: TMeta
|
|
315
|
+
declare readonly $routes: TRoutes
|
|
316
|
+
declare readonly $taps: TTaps
|
|
317
|
+
private _basePath: string
|
|
318
|
+
private _defaultBoundaryKey: string | null
|
|
319
|
+
private _defaultErrorKeys: Set<string>
|
|
320
|
+
private _errorFactory: unknown
|
|
321
|
+
private _errorSchema: StandardSchemaLike | null
|
|
322
|
+
private _customErrorFormatter: CustomErrorFormatter | null
|
|
323
|
+
private _customErrorSchema: StandardSchemaLike | null
|
|
324
|
+
private _chainMeta: Record<string, unknown> | null
|
|
325
|
+
private _chainMiddlewares: RuntimeMiddleware[]
|
|
326
|
+
private _scopedMiddlewares: ScopedEntry[]
|
|
327
|
+
private _contextValues: Record<string, unknown> | null
|
|
328
|
+
private _errorFormatter: ErrorFormatterFn
|
|
329
|
+
private _errorI18n: ErrorI18nConfig<TEnv> | null
|
|
330
|
+
private _globalMiddlewares: RuntimeMiddleware[]
|
|
331
|
+
private _graph: HoneyGraph
|
|
332
|
+
private _hasWsRoutes: boolean
|
|
333
|
+
private _logger: Logger | null
|
|
334
|
+
private _outputValidation: "always" | "dev" | "off"
|
|
335
|
+
private _staticRoutes: { map: Record<string, RouteHandler> | null }
|
|
336
|
+
private _stripPrefix: string | null
|
|
337
|
+
private _trailingSlash: "enforce" | "ignore" | "strip"
|
|
338
|
+
private _wsAdapter: WSAdapter | null
|
|
339
|
+
private _openApiCache: { epoch: number; value: Promise<unknown> } | null
|
|
340
|
+
private _openApiYamlCache: { epoch: number; value: Promise<string> } | null
|
|
341
|
+
private _manifestCache: { epoch: number; value: Promise<unknown> } | null
|
|
346
342
|
private _onError:
|
|
347
343
|
| ((
|
|
348
344
|
error: unknown,
|
|
349
345
|
ctx: {
|
|
350
|
-
env: TEnv
|
|
351
|
-
jsonFromError: (err: HoneyError) => Response
|
|
352
|
-
req: Request
|
|
346
|
+
env: TEnv
|
|
347
|
+
jsonFromError: (err: HoneyError) => Response
|
|
348
|
+
req: Request
|
|
353
349
|
},
|
|
354
350
|
) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void)
|
|
355
|
-
| null
|
|
351
|
+
| null
|
|
356
352
|
private _onMethodNotAllowed:
|
|
357
353
|
| ((ctx: {
|
|
358
|
-
allowed: string[]
|
|
359
|
-
env: TEnv
|
|
360
|
-
jsonFromError: (err: HoneyError) => Response
|
|
361
|
-
req: Request
|
|
354
|
+
allowed: string[]
|
|
355
|
+
env: TEnv
|
|
356
|
+
jsonFromError: (err: HoneyError) => Response
|
|
357
|
+
req: Request
|
|
362
358
|
}) => Response | Promise<Response>)
|
|
363
|
-
| null
|
|
359
|
+
| null
|
|
364
360
|
private _onNotFound:
|
|
365
|
-
| ((ctx: {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
| null;
|
|
371
|
-
private _taps: Map<
|
|
361
|
+
| ((ctx: { env: TEnv; jsonFromError: (err: HoneyError) => Response; req: Request }) => Response | Promise<Response>)
|
|
362
|
+
| null
|
|
363
|
+
private _taps: Map<string, (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>> | null
|
|
364
|
+
private _telemetry: TelemetryAdapter | null
|
|
365
|
+
private _realtimeRoutes: Map<
|
|
372
366
|
string,
|
|
373
|
-
|
|
374
|
-
>
|
|
375
|
-
private _telemetry: TelemetryAdapter | null;
|
|
376
|
-
private _realtimeRoutes: Map<string, { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number }>;
|
|
367
|
+
{ handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number }
|
|
368
|
+
>
|
|
377
369
|
|
|
378
370
|
constructor(opts?: {
|
|
379
|
-
chainMiddlewares?: RuntimeMiddleware[]
|
|
380
|
-
defaultErrorKeys?: Set<string
|
|
381
|
-
globalMiddlewares?: RuntimeMiddleware[]
|
|
382
|
-
graph?: HoneyGraph
|
|
383
|
-
handlerMap?: Record<string, RouteHandler> | null
|
|
384
|
-
root?: TreeNode
|
|
385
|
-
scopedMiddlewares?: ScopedEntry[]
|
|
371
|
+
chainMiddlewares?: RuntimeMiddleware[]
|
|
372
|
+
defaultErrorKeys?: Set<string>
|
|
373
|
+
globalMiddlewares?: RuntimeMiddleware[]
|
|
374
|
+
graph?: HoneyGraph
|
|
375
|
+
handlerMap?: Record<string, RouteHandler> | null
|
|
376
|
+
root?: TreeNode
|
|
377
|
+
scopedMiddlewares?: ScopedEntry[]
|
|
386
378
|
}) {
|
|
387
|
-
this._basePath = "/"
|
|
379
|
+
this._basePath = "/"
|
|
388
380
|
this._graph = opts?.graph ?? {
|
|
389
381
|
handlerMap: opts?.handlerMap ?? null,
|
|
390
382
|
hasRouteTree: false,
|
|
383
|
+
metaSpec: null,
|
|
391
384
|
realtimeBus: null,
|
|
392
385
|
root: opts?.root ?? createNode(),
|
|
393
|
-
}
|
|
394
|
-
this._globalMiddlewares = opts?.globalMiddlewares ?? []
|
|
395
|
-
this._scopedMiddlewares = opts?.scopedMiddlewares ?? []
|
|
396
|
-
this._chainMeta = null
|
|
397
|
-
this._chainMiddlewares = opts?.chainMiddlewares ?? []
|
|
398
|
-
this._contextValues = null
|
|
399
|
-
this._defaultBoundaryKey = null
|
|
400
|
-
this._defaultErrorKeys = opts?.defaultErrorKeys ?? new Set()
|
|
401
|
-
this._errorFactory = null
|
|
402
|
-
this._errorSchema = null
|
|
403
|
-
this._customErrorFormatter = null
|
|
404
|
-
this._customErrorSchema = null
|
|
405
|
-
this._errorFormatter = defaultErrorFormatter
|
|
406
|
-
this._errorI18n = null
|
|
407
|
-
this._hasWsRoutes = false
|
|
408
|
-
this._logger = null
|
|
409
|
-
this._outputValidation = "off"
|
|
410
|
-
this._staticRoutes = { map: null }
|
|
411
|
-
this._stripPrefix = null
|
|
412
|
-
this._trailingSlash = "ignore"
|
|
413
|
-
this._wsAdapter = null
|
|
414
|
-
this._openApiCache = null
|
|
415
|
-
this._openApiYamlCache = null
|
|
416
|
-
this._manifestCache = null
|
|
417
|
-
this._onError = null
|
|
418
|
-
this._onNotFound = null
|
|
419
|
-
this._onMethodNotAllowed = null
|
|
420
|
-
this._taps = null
|
|
421
|
-
this._telemetry = null
|
|
422
|
-
this._realtimeRoutes = new Map()
|
|
386
|
+
}
|
|
387
|
+
this._globalMiddlewares = opts?.globalMiddlewares ?? []
|
|
388
|
+
this._scopedMiddlewares = opts?.scopedMiddlewares ?? []
|
|
389
|
+
this._chainMeta = null
|
|
390
|
+
this._chainMiddlewares = opts?.chainMiddlewares ?? []
|
|
391
|
+
this._contextValues = null
|
|
392
|
+
this._defaultBoundaryKey = null
|
|
393
|
+
this._defaultErrorKeys = opts?.defaultErrorKeys ?? new Set()
|
|
394
|
+
this._errorFactory = null
|
|
395
|
+
this._errorSchema = null
|
|
396
|
+
this._customErrorFormatter = null
|
|
397
|
+
this._customErrorSchema = null
|
|
398
|
+
this._errorFormatter = defaultErrorFormatter
|
|
399
|
+
this._errorI18n = null
|
|
400
|
+
this._hasWsRoutes = false
|
|
401
|
+
this._logger = null
|
|
402
|
+
this._outputValidation = "off"
|
|
403
|
+
this._staticRoutes = { map: null }
|
|
404
|
+
this._stripPrefix = null
|
|
405
|
+
this._trailingSlash = "ignore"
|
|
406
|
+
this._wsAdapter = null
|
|
407
|
+
this._openApiCache = null
|
|
408
|
+
this._openApiYamlCache = null
|
|
409
|
+
this._manifestCache = null
|
|
410
|
+
this._onError = null
|
|
411
|
+
this._onNotFound = null
|
|
412
|
+
this._onMethodNotAllowed = null
|
|
413
|
+
this._taps = null
|
|
414
|
+
this._telemetry = null
|
|
415
|
+
this._realtimeRoutes = new Map()
|
|
423
416
|
}
|
|
424
417
|
|
|
425
418
|
private get _handlerMap(): Record<string, RouteHandler> | null {
|
|
426
|
-
return this._graph.handlerMap
|
|
419
|
+
return this._graph.handlerMap
|
|
427
420
|
}
|
|
428
421
|
private set _handlerMap(value: Record<string, RouteHandler> | null) {
|
|
429
|
-
this._graph.handlerMap = value
|
|
422
|
+
this._graph.handlerMap = value
|
|
430
423
|
}
|
|
431
424
|
|
|
432
425
|
private get _hasRouteTree(): boolean {
|
|
433
|
-
return this._graph.hasRouteTree
|
|
426
|
+
return this._graph.hasRouteTree
|
|
434
427
|
}
|
|
435
428
|
private set _hasRouteTree(value: boolean) {
|
|
436
|
-
this._graph.hasRouteTree = value
|
|
429
|
+
this._graph.hasRouteTree = value
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** @internal — read by codegen */
|
|
433
|
+
private get _metaSpec(): MetaSpecConfig | null {
|
|
434
|
+
return this._graph.metaSpec
|
|
435
|
+
}
|
|
436
|
+
private set _metaSpec(value: MetaSpecConfig | null) {
|
|
437
|
+
this._graph.metaSpec = value
|
|
437
438
|
}
|
|
438
439
|
|
|
439
440
|
private get _root(): TreeNode {
|
|
440
|
-
return this._graph.root
|
|
441
|
+
return this._graph.root
|
|
441
442
|
}
|
|
442
443
|
private set _root(value: TreeNode) {
|
|
443
|
-
this._graph.root = value
|
|
444
|
+
this._graph.root = value
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
/** @internal — used by codegen */
|
|
447
448
|
private get _tree(): TreeNode {
|
|
448
|
-
return this._root
|
|
449
|
+
return this._root
|
|
449
450
|
}
|
|
450
451
|
|
|
451
452
|
private get _realtimeBus(): RealtimeBus | null {
|
|
452
|
-
return this._graph.realtimeBus
|
|
453
|
+
return this._graph.realtimeBus
|
|
453
454
|
}
|
|
454
455
|
private set _realtimeBus(value: RealtimeBus | null) {
|
|
455
|
-
this._graph.realtimeBus = value
|
|
456
|
+
this._graph.realtimeBus = value
|
|
456
457
|
}
|
|
457
458
|
|
|
458
459
|
/** @internal — mark that this app has WS routes */
|
|
459
460
|
_markWsRoutes(): void {
|
|
460
|
-
this._hasWsRoutes = true
|
|
461
|
+
this._hasWsRoutes = true
|
|
461
462
|
}
|
|
462
463
|
|
|
463
464
|
/** @internal — register a static route for O(1) lookup */
|
|
464
465
|
_registerStatic(key: string, handler: RouteHandler): void {
|
|
465
466
|
if (this._staticRoutes.map === null) {
|
|
466
|
-
this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler
|
|
467
|
+
this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>
|
|
467
468
|
}
|
|
468
|
-
this._staticRoutes.map[key] = handler
|
|
469
|
+
this._staticRoutes.map[key] = handler
|
|
469
470
|
}
|
|
470
471
|
|
|
471
472
|
/** @internal — used by RouteBuilder for pre-filtered error factory */
|
|
472
473
|
get _factory(): unknown {
|
|
473
|
-
return this._errorFactory
|
|
474
|
+
return this._errorFactory
|
|
474
475
|
}
|
|
475
476
|
|
|
476
477
|
/** @internal — used by runtime error boundary */
|
|
477
478
|
get _boundaryKey(): string | null {
|
|
478
|
-
return this._defaultBoundaryKey
|
|
479
|
+
return this._defaultBoundaryKey
|
|
479
480
|
}
|
|
480
481
|
|
|
481
482
|
/** Convert unknown thrown value to error Response — used in WS, 404, 405 catch blocks */
|
|
@@ -487,78 +488,106 @@ export class Honey<
|
|
|
487
488
|
cause: thrown,
|
|
488
489
|
errorKey: EK.internal_server_error,
|
|
489
490
|
status: SK.internal_server_error,
|
|
490
|
-
})
|
|
491
|
-
return createErrorResponse(error, this._errorFormatter, this._customErrorFormatter)
|
|
491
|
+
})
|
|
492
|
+
return createErrorResponse(error, this._errorFormatter, this._customErrorFormatter)
|
|
492
493
|
}
|
|
493
494
|
|
|
494
495
|
private _createBoundaryError(errorKey: string, cause: unknown): HoneyError {
|
|
495
496
|
const factory = this._errorFactory as Record<
|
|
496
497
|
string,
|
|
497
498
|
((opts?: { cause?: unknown }) => HoneyError) | undefined
|
|
498
|
-
> | null
|
|
499
|
-
const factoryFn = factory?.[errorKey]
|
|
499
|
+
> | null
|
|
500
|
+
const factoryFn = factory?.[errorKey]
|
|
500
501
|
if (factoryFn) {
|
|
501
502
|
/* check if this is a custom schema error via ERROR_META — boundary must use standard errors only */
|
|
502
|
-
const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META]
|
|
503
|
+
const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META]
|
|
503
504
|
if (meta?.[errorKey]?.schema) {
|
|
504
505
|
/* custom schema error — cannot be used as boundary, fall through to manual construction */
|
|
505
506
|
} else {
|
|
506
|
-
return factoryFn({ cause })
|
|
507
|
+
return factoryFn({ cause })
|
|
507
508
|
}
|
|
508
509
|
}
|
|
509
510
|
return new HoneyError({
|
|
510
511
|
cause,
|
|
511
512
|
errorKey,
|
|
512
513
|
status: SK.internal_server_error,
|
|
513
|
-
})
|
|
514
|
+
})
|
|
514
515
|
}
|
|
515
516
|
|
|
516
517
|
private _createError(errorKey: string, statusKey: StatusKey): HoneyError {
|
|
517
|
-
const factory = this._errorFactory as Record<
|
|
518
|
-
|
|
519
|
-
(() => HoneyError) | undefined
|
|
520
|
-
> | null;
|
|
521
|
-
const factoryFn = factory?.[errorKey];
|
|
518
|
+
const factory = this._errorFactory as Record<string, (() => HoneyError) | undefined> | null
|
|
519
|
+
const factoryFn = factory?.[errorKey]
|
|
522
520
|
if (factoryFn) {
|
|
523
|
-
return factoryFn()
|
|
521
|
+
return factoryFn()
|
|
524
522
|
}
|
|
525
|
-
return new HoneyError({ errorKey, status: statusKey })
|
|
523
|
+
return new HoneyError({ errorKey, status: statusKey })
|
|
526
524
|
}
|
|
527
525
|
|
|
528
526
|
/** Apply error keys from a single scoped entry to every matching handler currently in the tree */
|
|
529
527
|
private _applyScopedEntryErrors(entry: ScopedEntry): void {
|
|
530
|
-
const errors = entry.errors
|
|
531
|
-
if (!errors || errors.length === 0) return
|
|
528
|
+
const errors = entry.errors
|
|
529
|
+
if (!errors || errors.length === 0) return
|
|
532
530
|
walkTreeHandlers(
|
|
533
531
|
this._root,
|
|
534
532
|
(h) => {
|
|
535
533
|
if (scopeMatches(entry.prefix, h.rp)) {
|
|
536
|
-
for (const k of errors) h.ek.add(k)
|
|
534
|
+
for (const k of errors) h.ek.add(k)
|
|
537
535
|
}
|
|
538
536
|
},
|
|
539
537
|
(wh) => {
|
|
540
538
|
if (scopeMatches(entry.prefix, wh.rp)) {
|
|
541
|
-
for (const k of errors) wh.ek.add(k)
|
|
539
|
+
for (const k of errors) wh.ek.add(k)
|
|
542
540
|
}
|
|
543
541
|
},
|
|
544
|
-
)
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Meta contributed by every middleware that will run for `routePath`, in runtime order:
|
|
547
|
+
* global, chain, then scoped entries matching the path. Route-level `.use()` is folded in
|
|
548
|
+
* later, by the builder. Resolved here — at registration — because the precompiled route
|
|
549
|
+
* tree bakes `mt` as a literal, so a per-request derivation would diverge from it.
|
|
550
|
+
*/
|
|
551
|
+
private _contributedMetaFor(routePath: string): Record<string, unknown> | null {
|
|
552
|
+
const scoped: RuntimeMiddleware[] = []
|
|
553
|
+
for (const entry of this._scopedMiddlewares) {
|
|
554
|
+
if (scopeMatches(entry.prefix, routePath)) scoped.push(entry.mw)
|
|
555
|
+
}
|
|
556
|
+
return collectMiddlewareMeta([this._globalMiddlewares, this._chainMiddlewares, scoped])
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Back-fill meta from one scoped entry onto handlers already in the tree. Mirrors
|
|
561
|
+
* `_applyScopedEntryErrors` — `.use("/prefix", mw)` may be registered after the routes it
|
|
562
|
+
* covers, and a tag missing where enforcement happens is the worst failure direction.
|
|
563
|
+
* `mt` is frozen at registration, so this replaces the object rather than mutating it.
|
|
564
|
+
*/
|
|
565
|
+
private _applyScopedEntryMeta(entry: ScopedEntry): void {
|
|
566
|
+
const meta = (entry.mw as { meta?: Record<string, unknown> }).meta
|
|
567
|
+
if (!meta) return
|
|
568
|
+
const apply = (h: { mt: Record<string, unknown> | null; rp: string }): void => {
|
|
569
|
+
if (!scopeMatches(entry.prefix, h.rp)) return
|
|
570
|
+
/* contributed meta never overwrites what the route or chain stated explicitly */
|
|
571
|
+
h.mt = Object.freeze(h.mt ? { ...meta, ...h.mt } : { ...meta })
|
|
572
|
+
}
|
|
573
|
+
walkTreeHandlers(this._root, apply, apply)
|
|
545
574
|
}
|
|
546
575
|
|
|
547
576
|
/** Apply error keys from every scoped entry on this chain to every matching handler in the tree */
|
|
548
577
|
private _applyAllScopedErrors(): void {
|
|
549
578
|
for (const entry of this._scopedMiddlewares) {
|
|
550
|
-
this._applyScopedEntryErrors(entry)
|
|
579
|
+
this._applyScopedEntryErrors(entry)
|
|
551
580
|
}
|
|
552
581
|
}
|
|
553
582
|
|
|
554
583
|
/** Return scoped middleware functions that match the given route path */
|
|
555
584
|
private _filterScopedForPath(routePath: string): RuntimeMiddleware[] {
|
|
556
|
-
if (this._scopedMiddlewares.length === 0) return EMPTY_MW
|
|
557
|
-
const out: RuntimeMiddleware[] = []
|
|
585
|
+
if (this._scopedMiddlewares.length === 0) return EMPTY_MW
|
|
586
|
+
const out: RuntimeMiddleware[] = []
|
|
558
587
|
for (const s of this._scopedMiddlewares) {
|
|
559
|
-
if (scopeMatches(s.prefix, routePath)) out.push(s.mw)
|
|
588
|
+
if (scopeMatches(s.prefix, routePath)) out.push(s.mw)
|
|
560
589
|
}
|
|
561
|
-
return out
|
|
590
|
+
return out
|
|
562
591
|
}
|
|
563
592
|
|
|
564
593
|
/** Mutates `honeyError.message` in-place with the i18n-resolved template for its errorKey.
|
|
@@ -570,7 +599,7 @@ export class Honey<
|
|
|
570
599
|
request: Request,
|
|
571
600
|
log?: Logger,
|
|
572
601
|
): Promise<void> {
|
|
573
|
-
if (!this._errorI18n) return
|
|
602
|
+
if (!this._errorI18n) return
|
|
574
603
|
try {
|
|
575
604
|
const locale = await this._errorI18n.resolveLocale({
|
|
576
605
|
cookies: ctx.cookies,
|
|
@@ -579,39 +608,36 @@ export class Honey<
|
|
|
579
608
|
params: ctx.params,
|
|
580
609
|
req: request,
|
|
581
610
|
search: ctx.search,
|
|
582
|
-
})
|
|
583
|
-
const translations = this._errorI18n.errors?.[locale]
|
|
611
|
+
})
|
|
612
|
+
const translations = this._errorI18n.errors?.[locale]
|
|
584
613
|
if (translations) {
|
|
585
|
-
const template = translations[honeyError.errorKey]
|
|
614
|
+
const template = translations[honeyError.errorKey]
|
|
586
615
|
if (template) {
|
|
587
616
|
await loadHoneyFeature("i18n")
|
|
588
|
-
honeyError.message = getI18nRuntime().interpolate(
|
|
589
|
-
template,
|
|
590
|
-
honeyError.vars ?? {},
|
|
591
|
-
);
|
|
617
|
+
honeyError.message = getI18nRuntime().interpolate(template, honeyError.vars ?? {})
|
|
592
618
|
}
|
|
593
619
|
}
|
|
594
620
|
|
|
595
|
-
const fieldTranslations = this._errorI18n.fieldNames?.[locale]
|
|
621
|
+
const fieldTranslations = this._errorI18n.fieldNames?.[locale]
|
|
596
622
|
if (fieldTranslations && Object.keys(honeyError.fields).length > 0) {
|
|
597
623
|
for (const fieldErrors of Object.values(honeyError.fields)) {
|
|
598
624
|
for (const fe of fieldErrors) {
|
|
599
|
-
let candidate = fe.path
|
|
625
|
+
let candidate = fe.path
|
|
600
626
|
while (candidate) {
|
|
601
|
-
const translated = fieldTranslations[candidate]
|
|
627
|
+
const translated = fieldTranslations[candidate]
|
|
602
628
|
if (translated) {
|
|
603
|
-
fe.path = translated
|
|
604
|
-
break
|
|
629
|
+
fe.path = translated
|
|
630
|
+
break
|
|
605
631
|
}
|
|
606
|
-
const dotIdx = candidate.indexOf(".")
|
|
607
|
-
if (dotIdx === -1) break
|
|
608
|
-
candidate = candidate.slice(dotIdx + 1)
|
|
632
|
+
const dotIdx = candidate.indexOf(".")
|
|
633
|
+
if (dotIdx === -1) break
|
|
634
|
+
candidate = candidate.slice(dotIdx + 1)
|
|
609
635
|
}
|
|
610
636
|
}
|
|
611
637
|
}
|
|
612
638
|
}
|
|
613
639
|
} catch (e) {
|
|
614
|
-
log?.warn?.("i18n resolution failed", e)
|
|
640
|
+
log?.warn?.("i18n resolution failed", e)
|
|
615
641
|
}
|
|
616
642
|
}
|
|
617
643
|
|
|
@@ -628,51 +654,51 @@ export class Honey<
|
|
|
628
654
|
path: string,
|
|
629
655
|
ctx: HoneyContext<TEnv>,
|
|
630
656
|
): Promise<Response> {
|
|
631
|
-
const { env, log, request, startTime } = fc
|
|
632
|
-
let honeyError: HoneyError
|
|
633
|
-
const boundaryKey = handler.bek ?? this._defaultBoundaryKey
|
|
657
|
+
const { env, log, request, startTime } = fc
|
|
658
|
+
let honeyError: HoneyError
|
|
659
|
+
const boundaryKey = handler.bek ?? this._defaultBoundaryKey
|
|
634
660
|
|
|
635
661
|
if (thrown instanceof HoneyError) {
|
|
636
662
|
/* framework-managed errorKeys (input/output validation, content negotiation, etc.) are always allowed
|
|
637
663
|
* regardless of handler.ek — users never declare them, the framework owns them. */
|
|
638
|
-
const isFrameworkEk = FRAMEWORK_EKS.has(thrown.errorKey as (typeof EK)[keyof typeof EK])
|
|
664
|
+
const isFrameworkEk = FRAMEWORK_EKS.has(thrown.errorKey as (typeof EK)[keyof typeof EK])
|
|
639
665
|
if (!isFrameworkEk && handler.ek.size > 0 && !handler.ek.has(thrown.errorKey)) {
|
|
640
666
|
if (boundaryKey) {
|
|
641
|
-
honeyError = this._createBoundaryError(boundaryKey, thrown)
|
|
667
|
+
honeyError = this._createBoundaryError(boundaryKey, thrown)
|
|
642
668
|
} else {
|
|
643
669
|
honeyError = new HoneyError({
|
|
644
670
|
cause: thrown,
|
|
645
671
|
errorKey: EK.internal_server_error,
|
|
646
672
|
status: SK.internal_server_error,
|
|
647
|
-
})
|
|
673
|
+
})
|
|
648
674
|
}
|
|
649
675
|
} else {
|
|
650
|
-
honeyError = thrown
|
|
676
|
+
honeyError = thrown
|
|
651
677
|
}
|
|
652
678
|
} else {
|
|
653
679
|
if (boundaryKey) {
|
|
654
|
-
honeyError = this._createBoundaryError(boundaryKey, thrown)
|
|
680
|
+
honeyError = this._createBoundaryError(boundaryKey, thrown)
|
|
655
681
|
} else {
|
|
656
682
|
honeyError = new HoneyError({
|
|
657
683
|
cause: thrown,
|
|
658
684
|
errorKey: EK.internal_server_error,
|
|
659
685
|
status: SK.internal_server_error,
|
|
660
|
-
})
|
|
686
|
+
})
|
|
661
687
|
}
|
|
662
688
|
}
|
|
663
689
|
|
|
664
|
-
await this._resolveI18n(honeyError, ctx, env, request, log)
|
|
690
|
+
await this._resolveI18n(honeyError, ctx, env, request, log)
|
|
665
691
|
|
|
666
692
|
/* onError handler */
|
|
667
693
|
if (this._onError) {
|
|
668
694
|
try {
|
|
669
|
-
const customResult = await this._onError(thrown, this._makeErrorCtx(fc))
|
|
695
|
+
const customResult = await this._onError(thrown, this._makeErrorCtx(fc))
|
|
670
696
|
if (customResult instanceof HoneyError) {
|
|
671
697
|
/* user-mapped boundary error — re-run i18n against new errorKey,
|
|
672
698
|
* then fall through to default response path so telemetry +
|
|
673
699
|
* jsonFromError run exactly once. */
|
|
674
|
-
honeyError = customResult
|
|
675
|
-
await this._resolveI18n(honeyError, ctx, env, request, log)
|
|
700
|
+
honeyError = customResult
|
|
701
|
+
await this._resolveI18n(honeyError, ctx, env, request, log)
|
|
676
702
|
} else if (customResult) {
|
|
677
703
|
safeFire(
|
|
678
704
|
() =>
|
|
@@ -683,7 +709,7 @@ export class Honey<
|
|
|
683
709
|
path,
|
|
684
710
|
}),
|
|
685
711
|
log,
|
|
686
|
-
)
|
|
712
|
+
)
|
|
687
713
|
safeFire(
|
|
688
714
|
() =>
|
|
689
715
|
this._telemetry?.onResponse?.({
|
|
@@ -692,9 +718,9 @@ export class Honey<
|
|
|
692
718
|
status: customResult.status,
|
|
693
719
|
}),
|
|
694
720
|
log,
|
|
695
|
-
)
|
|
696
|
-
ctx._isErrorResponse = true
|
|
697
|
-
return customResult
|
|
721
|
+
)
|
|
722
|
+
ctx._isErrorResponse = true
|
|
723
|
+
return customResult
|
|
698
724
|
}
|
|
699
725
|
/* customResult === undefined | void → fall through to default path */
|
|
700
726
|
} catch {
|
|
@@ -712,8 +738,8 @@ export class Honey<
|
|
|
712
738
|
path,
|
|
713
739
|
}),
|
|
714
740
|
log,
|
|
715
|
-
)
|
|
716
|
-
const res = this._makeErrorCtx(fc).jsonFromError(honeyError)
|
|
741
|
+
)
|
|
742
|
+
const res = this._makeErrorCtx(fc).jsonFromError(honeyError)
|
|
717
743
|
safeFire(
|
|
718
744
|
() =>
|
|
719
745
|
this._telemetry?.onResponse?.({
|
|
@@ -722,24 +748,14 @@ export class Honey<
|
|
|
722
748
|
status: res.status,
|
|
723
749
|
}),
|
|
724
750
|
log,
|
|
725
|
-
)
|
|
726
|
-
ctx._isErrorResponse = true
|
|
727
|
-
return res
|
|
751
|
+
)
|
|
752
|
+
ctx._isErrorResponse = true
|
|
753
|
+
return res
|
|
728
754
|
}
|
|
729
755
|
|
|
730
756
|
basePath<P extends string>(
|
|
731
757
|
prefix: P,
|
|
732
|
-
): Honey<
|
|
733
|
-
TEnv,
|
|
734
|
-
TCtx,
|
|
735
|
-
TRoutes,
|
|
736
|
-
TMeta,
|
|
737
|
-
TErrorFactory,
|
|
738
|
-
TDefaultErrors,
|
|
739
|
-
MergePath<TBasePath, P>,
|
|
740
|
-
TTaps,
|
|
741
|
-
TScopedMw
|
|
742
|
-
> {
|
|
758
|
+
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, MergePath<TBasePath, P>, TTaps, TScopedMw> {
|
|
743
759
|
const next = new Honey<
|
|
744
760
|
TEnv,
|
|
745
761
|
TCtx,
|
|
@@ -756,32 +772,32 @@ export class Honey<
|
|
|
756
772
|
globalMiddlewares: this._globalMiddlewares,
|
|
757
773
|
graph: this._graph,
|
|
758
774
|
scopedMiddlewares: this._scopedMiddlewares,
|
|
759
|
-
})
|
|
760
|
-
next._basePath = mergePath(this._basePath, prefix)
|
|
761
|
-
next._defaultBoundaryKey = this._defaultBoundaryKey
|
|
762
|
-
next._errorFactory = this._errorFactory
|
|
763
|
-
next._errorSchema = this._errorSchema
|
|
764
|
-
next._customErrorFormatter = this._customErrorFormatter
|
|
765
|
-
next._customErrorSchema = this._customErrorSchema
|
|
766
|
-
next._errorFormatter = this._errorFormatter
|
|
767
|
-
next._errorI18n = this._errorI18n
|
|
768
|
-
next._logger = this._logger
|
|
769
|
-
next._outputValidation = this._outputValidation
|
|
770
|
-
next._stripPrefix = this._stripPrefix
|
|
771
|
-
next._trailingSlash = this._trailingSlash
|
|
772
|
-
next._wsAdapter = this._wsAdapter
|
|
773
|
-
next._onError = this._onError
|
|
774
|
-
next._onNotFound = this._onNotFound
|
|
775
|
-
next._onMethodNotAllowed = this._onMethodNotAllowed
|
|
776
|
-
next._chainMeta = this._chainMeta
|
|
777
|
-
next._contextValues = this._contextValues
|
|
778
|
-
next._taps = this._taps
|
|
779
|
-
next._telemetry = this._telemetry
|
|
780
|
-
next._realtimeBus = this._realtimeBus
|
|
781
|
-
next._realtimeRoutes = this._realtimeRoutes
|
|
782
|
-
next._staticRoutes = this._staticRoutes
|
|
783
|
-
next._hasRouteTree = this._hasRouteTree
|
|
784
|
-
next._hasWsRoutes = this._hasWsRoutes
|
|
775
|
+
})
|
|
776
|
+
next._basePath = mergePath(this._basePath, prefix)
|
|
777
|
+
next._defaultBoundaryKey = this._defaultBoundaryKey
|
|
778
|
+
next._errorFactory = this._errorFactory
|
|
779
|
+
next._errorSchema = this._errorSchema
|
|
780
|
+
next._customErrorFormatter = this._customErrorFormatter
|
|
781
|
+
next._customErrorSchema = this._customErrorSchema
|
|
782
|
+
next._errorFormatter = this._errorFormatter
|
|
783
|
+
next._errorI18n = this._errorI18n
|
|
784
|
+
next._logger = this._logger
|
|
785
|
+
next._outputValidation = this._outputValidation
|
|
786
|
+
next._stripPrefix = this._stripPrefix
|
|
787
|
+
next._trailingSlash = this._trailingSlash
|
|
788
|
+
next._wsAdapter = this._wsAdapter
|
|
789
|
+
next._onError = this._onError
|
|
790
|
+
next._onNotFound = this._onNotFound
|
|
791
|
+
next._onMethodNotAllowed = this._onMethodNotAllowed
|
|
792
|
+
next._chainMeta = this._chainMeta
|
|
793
|
+
next._contextValues = this._contextValues
|
|
794
|
+
next._taps = this._taps
|
|
795
|
+
next._telemetry = this._telemetry
|
|
796
|
+
next._realtimeBus = this._realtimeBus
|
|
797
|
+
next._realtimeRoutes = this._realtimeRoutes
|
|
798
|
+
next._staticRoutes = this._staticRoutes
|
|
799
|
+
next._hasRouteTree = this._hasRouteTree
|
|
800
|
+
next._hasWsRoutes = this._hasWsRoutes
|
|
785
801
|
return next as unknown as Honey<
|
|
786
802
|
TEnv,
|
|
787
803
|
TCtx,
|
|
@@ -792,22 +808,12 @@ export class Honey<
|
|
|
792
808
|
MergePath<TBasePath, P>,
|
|
793
809
|
TTaps,
|
|
794
810
|
TScopedMw
|
|
795
|
-
|
|
811
|
+
>
|
|
796
812
|
}
|
|
797
813
|
|
|
798
814
|
context<TAdds extends Record<string, unknown>>(
|
|
799
815
|
values: TAdds,
|
|
800
|
-
): Honey<
|
|
801
|
-
TEnv,
|
|
802
|
-
TCtx & Readonly<TAdds>,
|
|
803
|
-
TRoutes,
|
|
804
|
-
TMeta,
|
|
805
|
-
TErrorFactory,
|
|
806
|
-
TDefaultErrors,
|
|
807
|
-
TBasePath,
|
|
808
|
-
TTaps,
|
|
809
|
-
TScopedMw
|
|
810
|
-
> {
|
|
816
|
+
): Honey<TEnv, TCtx & Readonly<TAdds>, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
811
817
|
for (const key in values) {
|
|
812
818
|
if (STATIC_CTX_RESERVED.has(key)) {
|
|
813
819
|
throw new Error(`context() cannot set reserved key "${key}"`)
|
|
@@ -829,65 +835,73 @@ export class Honey<
|
|
|
829
835
|
globalMiddlewares: this._globalMiddlewares,
|
|
830
836
|
graph: this._graph,
|
|
831
837
|
scopedMiddlewares: this._scopedMiddlewares,
|
|
832
|
-
})
|
|
833
|
-
next._basePath = this._basePath
|
|
834
|
-
next._chainMeta = this._chainMeta
|
|
835
|
-
next._contextValues = this._contextValues
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
next.
|
|
839
|
-
next.
|
|
840
|
-
next.
|
|
841
|
-
next.
|
|
842
|
-
next.
|
|
843
|
-
next.
|
|
844
|
-
next.
|
|
845
|
-
next.
|
|
846
|
-
next.
|
|
847
|
-
next.
|
|
848
|
-
next.
|
|
849
|
-
next.
|
|
850
|
-
next.
|
|
851
|
-
next.
|
|
852
|
-
next.
|
|
853
|
-
next.
|
|
854
|
-
next.
|
|
855
|
-
next.
|
|
856
|
-
next.
|
|
857
|
-
next.
|
|
858
|
-
next
|
|
859
|
-
|
|
860
|
-
|
|
838
|
+
})
|
|
839
|
+
next._basePath = this._basePath
|
|
840
|
+
next._chainMeta = this._chainMeta
|
|
841
|
+
next._contextValues = this._contextValues ? { ...this._contextValues, ...values } : { ...values }
|
|
842
|
+
next._defaultBoundaryKey = this._defaultBoundaryKey
|
|
843
|
+
next._errorFactory = this._errorFactory
|
|
844
|
+
next._errorSchema = this._errorSchema
|
|
845
|
+
next._customErrorFormatter = this._customErrorFormatter
|
|
846
|
+
next._customErrorSchema = this._customErrorSchema
|
|
847
|
+
next._errorFormatter = this._errorFormatter
|
|
848
|
+
next._errorI18n = this._errorI18n
|
|
849
|
+
next._logger = this._logger
|
|
850
|
+
next._outputValidation = this._outputValidation
|
|
851
|
+
next._stripPrefix = this._stripPrefix
|
|
852
|
+
next._trailingSlash = this._trailingSlash
|
|
853
|
+
next._wsAdapter = this._wsAdapter
|
|
854
|
+
next._onError = this._onError
|
|
855
|
+
next._onNotFound = this._onNotFound
|
|
856
|
+
next._onMethodNotAllowed = this._onMethodNotAllowed
|
|
857
|
+
next._taps = this._taps
|
|
858
|
+
next._telemetry = this._telemetry
|
|
859
|
+
next._realtimeBus = this._realtimeBus
|
|
860
|
+
next._realtimeRoutes = this._realtimeRoutes
|
|
861
|
+
next._staticRoutes = this._staticRoutes
|
|
862
|
+
next._hasRouteTree = this._hasRouteTree
|
|
863
|
+
next._hasWsRoutes = this._hasWsRoutes
|
|
864
|
+
return next as unknown as Honey<
|
|
865
|
+
TEnv,
|
|
866
|
+
TCtx & Readonly<TAdds>,
|
|
867
|
+
TRoutes,
|
|
868
|
+
TMeta,
|
|
869
|
+
TErrorFactory,
|
|
870
|
+
TDefaultErrors,
|
|
871
|
+
TBasePath,
|
|
872
|
+
TTaps,
|
|
873
|
+
TScopedMw
|
|
874
|
+
>
|
|
861
875
|
}
|
|
862
876
|
|
|
863
877
|
logger(logger: Logger): this {
|
|
864
|
-
this._logger = logger
|
|
865
|
-
return this
|
|
878
|
+
this._logger = logger
|
|
879
|
+
return this
|
|
866
880
|
}
|
|
867
881
|
|
|
868
882
|
outputValidation(mode: "always" | "dev" | "off"): this {
|
|
869
|
-
this._outputValidation = mode
|
|
870
|
-
return this
|
|
883
|
+
this._outputValidation = mode
|
|
884
|
+
return this
|
|
871
885
|
}
|
|
872
886
|
|
|
873
887
|
trailingSlash(mode: "enforce" | "ignore" | "strip"): this {
|
|
874
|
-
this._trailingSlash = mode
|
|
875
|
-
return this
|
|
888
|
+
this._trailingSlash = mode
|
|
889
|
+
return this
|
|
876
890
|
}
|
|
877
891
|
|
|
878
892
|
/** Strip a URL path prefix before route matching — boundary-safe (won't strip partial segments), paths without the prefix pass through unchanged */
|
|
879
893
|
stripPrefix(prefix: string): this {
|
|
880
|
-
let normalized = prefix.replace(/\/+$/, "")
|
|
894
|
+
let normalized = prefix.replace(/\/+$/, "")
|
|
881
895
|
if (normalized.length > 0 && normalized.charCodeAt(0) !== 47) {
|
|
882
|
-
normalized = `/${normalized}
|
|
896
|
+
normalized = `/${normalized}`
|
|
883
897
|
}
|
|
884
|
-
this._stripPrefix = normalized === "" || normalized === "/" ? null : normalized
|
|
885
|
-
return this
|
|
898
|
+
this._stripPrefix = normalized === "" || normalized === "/" ? null : normalized
|
|
899
|
+
return this
|
|
886
900
|
}
|
|
887
901
|
|
|
888
902
|
wsAdapter(adapter: WSAdapter): this {
|
|
889
|
-
this._wsAdapter = adapter
|
|
890
|
-
return this
|
|
903
|
+
this._wsAdapter = adapter
|
|
904
|
+
return this
|
|
891
905
|
}
|
|
892
906
|
|
|
893
907
|
openapi(options: {
|
|
@@ -896,6 +910,8 @@ export class Honey<
|
|
|
896
910
|
docsPath?: string
|
|
897
911
|
filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
|
|
898
912
|
path?: string
|
|
913
|
+
/** Named metaSpec profile selecting which emitted keys this document carries */
|
|
914
|
+
profile?: string
|
|
899
915
|
securitySchemes?: Record<string, unknown>
|
|
900
916
|
title: string
|
|
901
917
|
version: string
|
|
@@ -917,6 +933,7 @@ export class Honey<
|
|
|
917
933
|
title: options.title,
|
|
918
934
|
version: options.version,
|
|
919
935
|
},
|
|
936
|
+
profile: options.profile,
|
|
920
937
|
securitySchemes: options.securitySchemes,
|
|
921
938
|
}),
|
|
922
939
|
)
|
|
@@ -943,12 +960,8 @@ export class Honey<
|
|
|
943
960
|
return pending
|
|
944
961
|
}
|
|
945
962
|
this._mountInternalGet(`${stem}.json`, async (ctx) => ctx.res.json("ok", await loadJson()))
|
|
946
|
-
this._mountInternalGet(`${stem}.yml`, async (ctx) =>
|
|
947
|
-
|
|
948
|
-
)
|
|
949
|
-
this._mountInternalGet(`${stem}.yaml`, async (ctx) =>
|
|
950
|
-
ctx.res.text("ok", await loadYaml(), yamlHeaders),
|
|
951
|
-
)
|
|
963
|
+
this._mountInternalGet(`${stem}.yml`, async (ctx) => ctx.res.text("ok", await loadYaml(), yamlHeaders))
|
|
964
|
+
this._mountInternalGet(`${stem}.yaml`, async (ctx) => ctx.res.text("ok", await loadYaml(), yamlHeaders))
|
|
952
965
|
if (options.docs) {
|
|
953
966
|
const specUrl = mergePath(this._basePath, `${stem}.json`)
|
|
954
967
|
const docs = options.docs
|
|
@@ -966,9 +979,7 @@ export class Honey<
|
|
|
966
979
|
}
|
|
967
980
|
}
|
|
968
981
|
if (!mounted) {
|
|
969
|
-
throw new Error(
|
|
970
|
-
`Honey.openapi({ docs }) cannot mount at ${preferred}: already registered. Pass docsPath.`,
|
|
971
|
-
)
|
|
982
|
+
throw new Error(`Honey.openapi({ docs }) cannot mount at ${preferred}: already registered. Pass docsPath.`)
|
|
972
983
|
}
|
|
973
984
|
}
|
|
974
985
|
return this
|
|
@@ -1009,10 +1020,7 @@ export class Honey<
|
|
|
1009
1020
|
}
|
|
1010
1021
|
|
|
1011
1022
|
/** Mount an internal GET. Returns false when a user route already owns the path. */
|
|
1012
|
-
private _mountInternalGet(
|
|
1013
|
-
path: string,
|
|
1014
|
-
fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>,
|
|
1015
|
-
): boolean {
|
|
1023
|
+
private _mountInternalGet(path: string, fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>): boolean {
|
|
1016
1024
|
const fullPath = mergePath(this._basePath, path)
|
|
1017
1025
|
const existing = this._lookupGet(fullPath)
|
|
1018
1026
|
if (existing) return existing._skip === true
|
|
@@ -1039,114 +1047,111 @@ export class Honey<
|
|
|
1039
1047
|
defaultErrorFormatter<TSchema extends StandardSchemaLike>(
|
|
1040
1048
|
schema: TSchema,
|
|
1041
1049
|
fn: (error: HoneyError) => InferOutput<TSchema>,
|
|
1042
|
-
): this
|
|
1043
|
-
defaultErrorFormatter(fn: ErrorFormatterFn): this
|
|
1050
|
+
): this
|
|
1051
|
+
defaultErrorFormatter(fn: ErrorFormatterFn): this
|
|
1044
1052
|
defaultErrorFormatter(
|
|
1045
1053
|
schemaOrFn: ErrorFormatterFn | StandardSchemaLike,
|
|
1046
1054
|
maybeFn?: (error: HoneyError) => unknown,
|
|
1047
1055
|
): this {
|
|
1048
1056
|
if (typeof schemaOrFn === "function") {
|
|
1049
|
-
this._errorSchema = null
|
|
1050
|
-
this._errorFormatter = schemaOrFn
|
|
1057
|
+
this._errorSchema = null
|
|
1058
|
+
this._errorFormatter = schemaOrFn
|
|
1051
1059
|
} else {
|
|
1052
|
-
this._errorSchema = schemaOrFn
|
|
1053
|
-
const mapper = maybeFn as (error: HoneyError) => Record<string, unknown
|
|
1054
|
-
this._errorFormatter = (error) => mapper(error)
|
|
1060
|
+
this._errorSchema = schemaOrFn
|
|
1061
|
+
const mapper = maybeFn as (error: HoneyError) => Record<string, unknown>
|
|
1062
|
+
this._errorFormatter = (error) => mapper(error)
|
|
1055
1063
|
}
|
|
1056
|
-
return this
|
|
1064
|
+
return this
|
|
1057
1065
|
}
|
|
1058
1066
|
|
|
1059
1067
|
customErrorFormatter<TSchema extends StandardSchemaLike>(
|
|
1060
1068
|
schema: TSchema,
|
|
1061
1069
|
fn: (error: HoneyError, data: Record<string, unknown>) => InferOutput<TSchema>,
|
|
1062
|
-
): this
|
|
1063
|
-
customErrorFormatter(fn: CustomErrorFormatter): this
|
|
1070
|
+
): this
|
|
1071
|
+
customErrorFormatter(fn: CustomErrorFormatter): this
|
|
1064
1072
|
customErrorFormatter(
|
|
1065
1073
|
schemaOrFn: CustomErrorFormatter | StandardSchemaLike,
|
|
1066
1074
|
maybeFn?: (error: HoneyError, data: Record<string, unknown>) => unknown,
|
|
1067
1075
|
): this {
|
|
1068
1076
|
if (typeof schemaOrFn === "function") {
|
|
1069
|
-
this._customErrorSchema = null
|
|
1070
|
-
this._customErrorFormatter = schemaOrFn
|
|
1077
|
+
this._customErrorSchema = null
|
|
1078
|
+
this._customErrorFormatter = schemaOrFn
|
|
1071
1079
|
} else {
|
|
1072
|
-
this._customErrorSchema = schemaOrFn
|
|
1073
|
-
const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown
|
|
1074
|
-
this._customErrorFormatter = (error, data) => mapper(error, data)
|
|
1080
|
+
this._customErrorSchema = schemaOrFn
|
|
1081
|
+
const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown>
|
|
1082
|
+
this._customErrorFormatter = (error, data) => mapper(error, data)
|
|
1075
1083
|
}
|
|
1076
|
-
return this
|
|
1084
|
+
return this
|
|
1077
1085
|
}
|
|
1078
1086
|
|
|
1079
1087
|
errorI18n(config: ErrorI18nConfig<TEnv>): this {
|
|
1080
|
-
this._errorI18n = config
|
|
1081
|
-
return this
|
|
1088
|
+
this._errorI18n = config
|
|
1089
|
+
return this
|
|
1082
1090
|
}
|
|
1083
1091
|
|
|
1084
1092
|
onError(
|
|
1085
1093
|
handler: (
|
|
1086
1094
|
error: unknown,
|
|
1087
1095
|
ctx: {
|
|
1088
|
-
env: TEnv
|
|
1089
|
-
jsonFromError: (err: HoneyError) => Response
|
|
1090
|
-
req: Request
|
|
1096
|
+
env: TEnv
|
|
1097
|
+
jsonFromError: (err: HoneyError) => Response
|
|
1098
|
+
req: Request
|
|
1091
1099
|
},
|
|
1092
1100
|
) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void,
|
|
1093
1101
|
): this {
|
|
1094
|
-
this._onError = handler
|
|
1095
|
-
return this
|
|
1102
|
+
this._onError = handler
|
|
1103
|
+
return this
|
|
1096
1104
|
}
|
|
1097
1105
|
|
|
1098
1106
|
onMethodNotAllowed(
|
|
1099
1107
|
handler: (ctx: {
|
|
1100
|
-
allowed: string[]
|
|
1101
|
-
env: TEnv
|
|
1102
|
-
jsonFromError: (err: HoneyError) => Response
|
|
1103
|
-
req: Request
|
|
1108
|
+
allowed: string[]
|
|
1109
|
+
env: TEnv
|
|
1110
|
+
jsonFromError: (err: HoneyError) => Response
|
|
1111
|
+
req: Request
|
|
1104
1112
|
}) => Response | Promise<Response>,
|
|
1105
1113
|
): this {
|
|
1106
|
-
this._onMethodNotAllowed = handler
|
|
1107
|
-
return this
|
|
1114
|
+
this._onMethodNotAllowed = handler
|
|
1115
|
+
return this
|
|
1108
1116
|
}
|
|
1109
1117
|
|
|
1110
1118
|
onNotFound(
|
|
1111
1119
|
handler: (ctx: {
|
|
1112
|
-
env: TEnv
|
|
1113
|
-
jsonFromError: (err: HoneyError) => Response
|
|
1114
|
-
req: Request
|
|
1120
|
+
env: TEnv
|
|
1121
|
+
jsonFromError: (err: HoneyError) => Response
|
|
1122
|
+
req: Request
|
|
1115
1123
|
}) => Response | Promise<Response>,
|
|
1116
1124
|
): this {
|
|
1117
|
-
this._onNotFound = handler
|
|
1118
|
-
return this
|
|
1125
|
+
this._onNotFound = handler
|
|
1126
|
+
return this
|
|
1119
1127
|
}
|
|
1120
1128
|
|
|
1121
1129
|
/** Register a tap handler keyed by name — fires after successful handler response */
|
|
1122
1130
|
tap<K extends string>(
|
|
1123
1131
|
key: K,
|
|
1124
|
-
handler: (
|
|
1125
|
-
ctx: TapContext<TEnv>,
|
|
1126
|
-
payload: K extends keyof TTaps ? TTaps[K] : unknown,
|
|
1127
|
-
) => void | Promise<void>,
|
|
1132
|
+
handler: (ctx: TapContext<TEnv>, payload: K extends keyof TTaps ? TTaps[K] : unknown) => void | Promise<void>,
|
|
1128
1133
|
): this {
|
|
1129
1134
|
if (this._taps === null) {
|
|
1130
|
-
this._taps = new Map()
|
|
1135
|
+
this._taps = new Map()
|
|
1131
1136
|
}
|
|
1132
|
-
this._taps.set(key, handler as (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>)
|
|
1133
|
-
return this
|
|
1137
|
+
this._taps.set(key, handler as (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>)
|
|
1138
|
+
return this
|
|
1134
1139
|
}
|
|
1135
1140
|
|
|
1136
1141
|
telemetry(adapter: TelemetryAdapter): this {
|
|
1137
|
-
this._telemetry = adapter
|
|
1138
|
-
return this
|
|
1142
|
+
this._telemetry = adapter
|
|
1143
|
+
return this
|
|
1139
1144
|
}
|
|
1140
1145
|
|
|
1141
1146
|
routeTree(tree: RouteTree): this {
|
|
1142
|
-
this._root = tree.root
|
|
1143
|
-
this._handlerMap = tree.handlers ?? null
|
|
1144
|
-
this._hasRouteTree = true
|
|
1145
|
-
return this
|
|
1147
|
+
this._root = tree.root
|
|
1148
|
+
this._handlerMap = tree.handlers ?? null
|
|
1149
|
+
this._hasRouteTree = true
|
|
1150
|
+
return this
|
|
1146
1151
|
}
|
|
1147
1152
|
|
|
1148
1153
|
toRouteTree(): RouteTree {
|
|
1149
|
-
return { meta: {}, root: this._root }
|
|
1154
|
+
return { meta: {}, root: this._root }
|
|
1150
1155
|
}
|
|
1151
1156
|
|
|
1152
1157
|
errorFactory<TFactory extends Record<string, (...args: never[]) => unknown>>(
|
|
@@ -1162,28 +1167,14 @@ export class Honey<
|
|
|
1162
1167
|
TBasePath,
|
|
1163
1168
|
TTaps,
|
|
1164
1169
|
TScopedMw
|
|
1165
|
-
|
|
1166
|
-
next._errorFactory = factory
|
|
1167
|
-
return next
|
|
1170
|
+
>
|
|
1171
|
+
next._errorFactory = factory
|
|
1172
|
+
return next
|
|
1168
1173
|
}
|
|
1169
1174
|
|
|
1170
|
-
defaultErrors<
|
|
1171
|
-
TKeys extends [TErrorFactory] extends [never]
|
|
1172
|
-
? never
|
|
1173
|
-
: keyof TErrorFactory & string,
|
|
1174
|
-
>(
|
|
1175
|
+
defaultErrors<TKeys extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
|
|
1175
1176
|
...keys: TKeys[]
|
|
1176
|
-
): Honey<
|
|
1177
|
-
TEnv,
|
|
1178
|
-
TCtx,
|
|
1179
|
-
TRoutes,
|
|
1180
|
-
TMeta,
|
|
1181
|
-
TErrorFactory,
|
|
1182
|
-
TDefaultErrors | TKeys,
|
|
1183
|
-
TBasePath,
|
|
1184
|
-
TTaps,
|
|
1185
|
-
TScopedMw
|
|
1186
|
-
> {
|
|
1177
|
+
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKeys, TBasePath, TTaps, TScopedMw> {
|
|
1187
1178
|
const next = this as unknown as Honey<
|
|
1188
1179
|
TEnv,
|
|
1189
1180
|
TCtx,
|
|
@@ -1194,28 +1185,16 @@ export class Honey<
|
|
|
1194
1185
|
TBasePath,
|
|
1195
1186
|
TTaps,
|
|
1196
1187
|
TScopedMw
|
|
1197
|
-
|
|
1188
|
+
>
|
|
1198
1189
|
for (const k of keys) {
|
|
1199
|
-
next._defaultErrorKeys.add(k)
|
|
1190
|
+
next._defaultErrorKeys.add(k)
|
|
1200
1191
|
}
|
|
1201
|
-
return next
|
|
1192
|
+
return next
|
|
1202
1193
|
}
|
|
1203
1194
|
|
|
1204
|
-
defaultBoundary<
|
|
1205
|
-
TKey
|
|
1206
|
-
|
|
1207
|
-
: keyof TErrorFactory & string,
|
|
1208
|
-
>(key: TKey): Honey<
|
|
1209
|
-
TEnv,
|
|
1210
|
-
TCtx,
|
|
1211
|
-
TRoutes,
|
|
1212
|
-
TMeta,
|
|
1213
|
-
TErrorFactory,
|
|
1214
|
-
TDefaultErrors | TKey,
|
|
1215
|
-
TBasePath,
|
|
1216
|
-
TTaps,
|
|
1217
|
-
TScopedMw
|
|
1218
|
-
> {
|
|
1195
|
+
defaultBoundary<TKey extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
|
|
1196
|
+
key: TKey,
|
|
1197
|
+
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKey, TBasePath, TTaps, TScopedMw> {
|
|
1219
1198
|
const next = this as unknown as Honey<
|
|
1220
1199
|
TEnv,
|
|
1221
1200
|
TCtx,
|
|
@@ -1226,10 +1205,10 @@ export class Honey<
|
|
|
1226
1205
|
TBasePath,
|
|
1227
1206
|
TTaps,
|
|
1228
1207
|
TScopedMw
|
|
1229
|
-
|
|
1230
|
-
next._defaultBoundaryKey = key
|
|
1231
|
-
next._defaultErrorKeys.add(key)
|
|
1232
|
-
return next
|
|
1208
|
+
>
|
|
1209
|
+
next._defaultBoundaryKey = key
|
|
1210
|
+
next._defaultErrorKeys.add(key)
|
|
1211
|
+
return next
|
|
1233
1212
|
}
|
|
1234
1213
|
|
|
1235
1214
|
/** Phantom overload — constrains what route-level .meta() accepts */
|
|
@@ -1243,102 +1222,84 @@ export class Honey<
|
|
|
1243
1222
|
TBasePath,
|
|
1244
1223
|
TTaps,
|
|
1245
1224
|
TScopedMw
|
|
1246
|
-
|
|
1225
|
+
>
|
|
1247
1226
|
/** Typed chain-level default meta — constrains route meta and sets runtime defaults in one call */
|
|
1248
|
-
meta<
|
|
1249
|
-
TNewMeta extends Record<string, unknown>,
|
|
1250
|
-
TValues extends Partial<DefaultMeta> & Partial<TNewMeta>,
|
|
1251
|
-
>(
|
|
1227
|
+
meta<TNewMeta extends Record<string, unknown>, TValues extends Partial<DefaultMeta> & Partial<TNewMeta>>(
|
|
1252
1228
|
values: TValues,
|
|
1253
|
-
): Honey<
|
|
1254
|
-
TEnv,
|
|
1255
|
-
TCtx,
|
|
1256
|
-
TRoutes,
|
|
1257
|
-
TNewMeta,
|
|
1258
|
-
TErrorFactory,
|
|
1259
|
-
TDefaultErrors,
|
|
1260
|
-
TBasePath,
|
|
1261
|
-
TTaps,
|
|
1262
|
-
TScopedMw
|
|
1263
|
-
>;
|
|
1229
|
+
): Honey<TEnv, TCtx, TRoutes, TNewMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1264
1230
|
/** Chain-level default meta — merged into every route registered on this chain */
|
|
1265
|
-
meta<
|
|
1266
|
-
TValues extends Partial<DefaultMeta> &
|
|
1267
|
-
([TMeta] extends [never] ? {} : Partial<TMeta>),
|
|
1268
|
-
>(
|
|
1231
|
+
meta<TValues extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : Partial<TMeta>)>(
|
|
1269
1232
|
values: TValues,
|
|
1270
|
-
): Honey<
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
TMeta,
|
|
1275
|
-
TErrorFactory,
|
|
1276
|
-
TDefaultErrors,
|
|
1277
|
-
TBasePath,
|
|
1278
|
-
TTaps,
|
|
1279
|
-
TScopedMw
|
|
1280
|
-
>;
|
|
1281
|
-
meta(values?: Record<string, unknown>): Honey<
|
|
1282
|
-
TEnv,
|
|
1283
|
-
TCtx,
|
|
1284
|
-
TRoutes,
|
|
1285
|
-
unknown,
|
|
1286
|
-
TErrorFactory,
|
|
1287
|
-
TDefaultErrors,
|
|
1288
|
-
TBasePath,
|
|
1289
|
-
TTaps,
|
|
1290
|
-
TScopedMw
|
|
1291
|
-
> {
|
|
1233
|
+
): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1234
|
+
meta(
|
|
1235
|
+
values?: Record<string, unknown>,
|
|
1236
|
+
): Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
1292
1237
|
if (values === undefined) {
|
|
1293
1238
|
/* phantom overload — type-only, no runtime effect */
|
|
1294
|
-
return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw
|
|
1239
|
+
return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1295
1240
|
}
|
|
1296
1241
|
/* chain-level meta — copy-on-write */
|
|
1297
|
-
const next = new Honey<
|
|
1298
|
-
TEnv,
|
|
1299
|
-
TCtx,
|
|
1300
|
-
TRoutes,
|
|
1301
|
-
unknown,
|
|
1302
|
-
TErrorFactory,
|
|
1303
|
-
TDefaultErrors,
|
|
1304
|
-
TBasePath,
|
|
1305
|
-
TTaps,
|
|
1306
|
-
TScopedMw
|
|
1307
|
-
>({
|
|
1242
|
+
const next = new Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>({
|
|
1308
1243
|
chainMiddlewares: this._chainMiddlewares,
|
|
1309
1244
|
defaultErrorKeys: this._defaultErrorKeys,
|
|
1310
1245
|
globalMiddlewares: this._globalMiddlewares,
|
|
1311
1246
|
graph: this._graph,
|
|
1312
1247
|
scopedMiddlewares: this._scopedMiddlewares,
|
|
1313
|
-
})
|
|
1314
|
-
next._basePath = this._basePath
|
|
1315
|
-
next._chainMeta = this._chainMeta
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
next.
|
|
1319
|
-
next.
|
|
1320
|
-
next.
|
|
1321
|
-
next.
|
|
1322
|
-
next.
|
|
1323
|
-
next.
|
|
1324
|
-
next.
|
|
1325
|
-
next.
|
|
1326
|
-
next.
|
|
1327
|
-
next.
|
|
1328
|
-
next.
|
|
1329
|
-
next.
|
|
1330
|
-
next.
|
|
1331
|
-
next.
|
|
1332
|
-
next.
|
|
1333
|
-
next.
|
|
1334
|
-
next.
|
|
1335
|
-
next.
|
|
1336
|
-
next.
|
|
1337
|
-
next.
|
|
1338
|
-
next.
|
|
1339
|
-
next
|
|
1340
|
-
|
|
1341
|
-
|
|
1248
|
+
})
|
|
1249
|
+
next._basePath = this._basePath
|
|
1250
|
+
next._chainMeta = this._chainMeta ? { ...this._chainMeta, ...values } : { ...values }
|
|
1251
|
+
next._contextValues = this._contextValues
|
|
1252
|
+
next._defaultBoundaryKey = this._defaultBoundaryKey
|
|
1253
|
+
next._errorFactory = this._errorFactory
|
|
1254
|
+
next._errorSchema = this._errorSchema
|
|
1255
|
+
next._customErrorFormatter = this._customErrorFormatter
|
|
1256
|
+
next._customErrorSchema = this._customErrorSchema
|
|
1257
|
+
next._errorFormatter = this._errorFormatter
|
|
1258
|
+
next._errorI18n = this._errorI18n
|
|
1259
|
+
next._logger = this._logger
|
|
1260
|
+
next._outputValidation = this._outputValidation
|
|
1261
|
+
next._stripPrefix = this._stripPrefix
|
|
1262
|
+
next._trailingSlash = this._trailingSlash
|
|
1263
|
+
next._wsAdapter = this._wsAdapter
|
|
1264
|
+
next._onError = this._onError
|
|
1265
|
+
next._onNotFound = this._onNotFound
|
|
1266
|
+
next._onMethodNotAllowed = this._onMethodNotAllowed
|
|
1267
|
+
next._taps = this._taps
|
|
1268
|
+
next._telemetry = this._telemetry
|
|
1269
|
+
next._realtimeBus = this._realtimeBus
|
|
1270
|
+
next._realtimeRoutes = this._realtimeRoutes
|
|
1271
|
+
next._staticRoutes = this._staticRoutes
|
|
1272
|
+
next._hasRouteTree = this._hasRouteTree
|
|
1273
|
+
next._hasWsRoutes = this._hasWsRoutes
|
|
1274
|
+
return next
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Declare what flows from route meta and route schemas into the OpenAPI document.
|
|
1279
|
+
* Codegen-time only — never consulted on the request path. See docs/meta-spec.md.
|
|
1280
|
+
*/
|
|
1281
|
+
metaSpec<TSpec extends HoneyMetaSpec<TMeta>>(spec: TSpec): this {
|
|
1282
|
+
if (this._metaSpec !== null) {
|
|
1283
|
+
throw new Error("metaSpec() was already declared on this app — declare the whole policy in one call")
|
|
1284
|
+
}
|
|
1285
|
+
this._metaSpec = spec as MetaSpecConfig
|
|
1286
|
+
return this
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/** Mounted sub-apps fill policy gaps; the parent's entries win on conflict. */
|
|
1290
|
+
private _absorbMetaSpec(sub: MetaSpecConfig | null): void {
|
|
1291
|
+
if (!sub) return
|
|
1292
|
+
const own = this._metaSpec
|
|
1293
|
+
if (!own) {
|
|
1294
|
+
this._metaSpec = sub
|
|
1295
|
+
return
|
|
1296
|
+
}
|
|
1297
|
+
this._metaSpec = {
|
|
1298
|
+
meta: { ...sub.meta, ...own.meta },
|
|
1299
|
+
profiles: { ...sub.profiles, ...own.profiles },
|
|
1300
|
+
schema: { ...sub.schema, ...own.schema },
|
|
1301
|
+
strict: own.strict ?? sub.strict,
|
|
1302
|
+
}
|
|
1342
1303
|
}
|
|
1343
1304
|
|
|
1344
1305
|
/** Declare tap payload types — auto-extends meta with Partial<T> for meta-driven taps */
|
|
@@ -1363,12 +1324,12 @@ export class Honey<
|
|
|
1363
1324
|
TBasePath,
|
|
1364
1325
|
TNewTaps,
|
|
1365
1326
|
TScopedMw
|
|
1366
|
-
|
|
1327
|
+
>
|
|
1367
1328
|
}
|
|
1368
1329
|
|
|
1369
1330
|
use<TAdds>(
|
|
1370
1331
|
mw: MiddlewareFn<TCtx, TAdds>,
|
|
1371
|
-
): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw
|
|
1332
|
+
): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1372
1333
|
|
|
1373
1334
|
use<const TPath extends string, TAdds>(
|
|
1374
1335
|
path: TPath,
|
|
@@ -1383,153 +1344,132 @@ export class Honey<
|
|
|
1383
1344
|
TBasePath,
|
|
1384
1345
|
TTaps,
|
|
1385
1346
|
readonly [...TScopedMw, { readonly path: MergePath<TBasePath, TPath>; readonly adds: TAdds }]
|
|
1386
|
-
|
|
1347
|
+
>
|
|
1387
1348
|
|
|
1388
1349
|
/* oxlint-disable-next-line typescript/no-explicit-any -- overload impl requires erased types */
|
|
1389
1350
|
use(pathOrMw: string | MiddlewareFn<any, any>, maybeMw?: MiddlewareFn<any, any>): any {
|
|
1390
1351
|
if (typeof pathOrMw !== "string") {
|
|
1391
|
-
const mw = pathOrMw as RuntimeMiddleware
|
|
1352
|
+
const mw = pathOrMw as RuntimeMiddleware
|
|
1392
1353
|
const newChain = new Honey<TEnv>({
|
|
1393
1354
|
chainMiddlewares: [...this._chainMiddlewares, mw],
|
|
1394
1355
|
defaultErrorKeys: this._defaultErrorKeys,
|
|
1395
1356
|
globalMiddlewares: this._globalMiddlewares,
|
|
1396
1357
|
graph: this._graph,
|
|
1397
1358
|
scopedMiddlewares: this._scopedMiddlewares,
|
|
1398
|
-
})
|
|
1399
|
-
newChain._basePath = this._basePath
|
|
1400
|
-
newChain._chainMeta = this._chainMeta
|
|
1401
|
-
newChain._contextValues = this._contextValues
|
|
1402
|
-
newChain._defaultBoundaryKey = this._defaultBoundaryKey
|
|
1403
|
-
newChain._errorFactory = this._errorFactory
|
|
1404
|
-
newChain._errorSchema = this._errorSchema
|
|
1405
|
-
newChain._customErrorFormatter = this._customErrorFormatter
|
|
1406
|
-
newChain._customErrorSchema = this._customErrorSchema
|
|
1407
|
-
newChain._errorFormatter = this._errorFormatter
|
|
1408
|
-
newChain._errorI18n = this._errorI18n
|
|
1409
|
-
newChain._logger = this._logger
|
|
1410
|
-
newChain._outputValidation = this._outputValidation
|
|
1411
|
-
newChain._stripPrefix = this._stripPrefix
|
|
1412
|
-
newChain._trailingSlash = this._trailingSlash
|
|
1413
|
-
newChain._wsAdapter = this._wsAdapter
|
|
1414
|
-
newChain._onError = this._onError
|
|
1415
|
-
newChain._onNotFound = this._onNotFound
|
|
1416
|
-
newChain._onMethodNotAllowed = this._onMethodNotAllowed
|
|
1417
|
-
newChain._taps = this._taps
|
|
1418
|
-
newChain._telemetry = this._telemetry
|
|
1419
|
-
newChain._realtimeBus = this._realtimeBus
|
|
1420
|
-
newChain._realtimeRoutes = this._realtimeRoutes
|
|
1421
|
-
newChain._staticRoutes = this._staticRoutes
|
|
1422
|
-
newChain._hasRouteTree = this._hasRouteTree
|
|
1423
|
-
newChain._hasWsRoutes = this._hasWsRoutes
|
|
1424
|
-
return newChain
|
|
1359
|
+
})
|
|
1360
|
+
newChain._basePath = this._basePath
|
|
1361
|
+
newChain._chainMeta = this._chainMeta
|
|
1362
|
+
newChain._contextValues = this._contextValues
|
|
1363
|
+
newChain._defaultBoundaryKey = this._defaultBoundaryKey
|
|
1364
|
+
newChain._errorFactory = this._errorFactory
|
|
1365
|
+
newChain._errorSchema = this._errorSchema
|
|
1366
|
+
newChain._customErrorFormatter = this._customErrorFormatter
|
|
1367
|
+
newChain._customErrorSchema = this._customErrorSchema
|
|
1368
|
+
newChain._errorFormatter = this._errorFormatter
|
|
1369
|
+
newChain._errorI18n = this._errorI18n
|
|
1370
|
+
newChain._logger = this._logger
|
|
1371
|
+
newChain._outputValidation = this._outputValidation
|
|
1372
|
+
newChain._stripPrefix = this._stripPrefix
|
|
1373
|
+
newChain._trailingSlash = this._trailingSlash
|
|
1374
|
+
newChain._wsAdapter = this._wsAdapter
|
|
1375
|
+
newChain._onError = this._onError
|
|
1376
|
+
newChain._onNotFound = this._onNotFound
|
|
1377
|
+
newChain._onMethodNotAllowed = this._onMethodNotAllowed
|
|
1378
|
+
newChain._taps = this._taps
|
|
1379
|
+
newChain._telemetry = this._telemetry
|
|
1380
|
+
newChain._realtimeBus = this._realtimeBus
|
|
1381
|
+
newChain._realtimeRoutes = this._realtimeRoutes
|
|
1382
|
+
newChain._staticRoutes = this._staticRoutes
|
|
1383
|
+
newChain._hasRouteTree = this._hasRouteTree
|
|
1384
|
+
newChain._hasWsRoutes = this._hasWsRoutes
|
|
1385
|
+
return newChain
|
|
1425
1386
|
}
|
|
1426
1387
|
|
|
1427
1388
|
/* scoped path */
|
|
1428
|
-
const normalizedPrefix = normalizeScopePath(pathOrMw)
|
|
1429
|
-
const fullPrefix = mergePath(this._basePath, normalizedPrefix)
|
|
1430
|
-
const mw = maybeMw as RuntimeMiddleware
|
|
1431
|
-
const mwWithErrors = maybeMw as MiddlewareFn<unknown, unknown
|
|
1389
|
+
const normalizedPrefix = normalizeScopePath(pathOrMw)
|
|
1390
|
+
const fullPrefix = mergePath(this._basePath, normalizedPrefix)
|
|
1391
|
+
const mw = maybeMw as RuntimeMiddleware
|
|
1392
|
+
const mwWithErrors = maybeMw as MiddlewareFn<unknown, unknown>
|
|
1432
1393
|
const entry: ScopedEntry = {
|
|
1433
1394
|
errors: mwWithErrors.errors ? [...mwWithErrors.errors] : undefined,
|
|
1434
1395
|
mw,
|
|
1435
1396
|
prefix: fullPrefix,
|
|
1436
|
-
}
|
|
1437
|
-
this._scopedMiddlewares.push(entry)
|
|
1397
|
+
}
|
|
1398
|
+
this._scopedMiddlewares.push(entry)
|
|
1438
1399
|
const newChain = new Honey<TEnv>({
|
|
1439
1400
|
chainMiddlewares: this._chainMiddlewares,
|
|
1440
1401
|
defaultErrorKeys: this._defaultErrorKeys,
|
|
1441
1402
|
globalMiddlewares: this._globalMiddlewares,
|
|
1442
1403
|
graph: this._graph,
|
|
1443
1404
|
scopedMiddlewares: this._scopedMiddlewares,
|
|
1444
|
-
})
|
|
1445
|
-
newChain._basePath = this._basePath
|
|
1446
|
-
newChain._chainMeta = this._chainMeta
|
|
1447
|
-
newChain._contextValues = this._contextValues
|
|
1448
|
-
newChain._defaultBoundaryKey = this._defaultBoundaryKey
|
|
1449
|
-
newChain._errorFactory = this._errorFactory
|
|
1450
|
-
newChain._errorSchema = this._errorSchema
|
|
1451
|
-
newChain._customErrorFormatter = this._customErrorFormatter
|
|
1452
|
-
newChain._customErrorSchema = this._customErrorSchema
|
|
1453
|
-
newChain._errorFormatter = this._errorFormatter
|
|
1454
|
-
newChain._errorI18n = this._errorI18n
|
|
1455
|
-
newChain._logger = this._logger
|
|
1456
|
-
newChain._outputValidation = this._outputValidation
|
|
1457
|
-
newChain._stripPrefix = this._stripPrefix
|
|
1458
|
-
newChain._trailingSlash = this._trailingSlash
|
|
1459
|
-
newChain._wsAdapter = this._wsAdapter
|
|
1460
|
-
newChain._onError = this._onError
|
|
1461
|
-
newChain._onNotFound = this._onNotFound
|
|
1462
|
-
newChain._onMethodNotAllowed = this._onMethodNotAllowed
|
|
1463
|
-
newChain._taps = this._taps
|
|
1464
|
-
newChain._telemetry = this._telemetry
|
|
1465
|
-
newChain._realtimeBus = this._realtimeBus
|
|
1466
|
-
newChain._realtimeRoutes = this._realtimeRoutes
|
|
1467
|
-
newChain._staticRoutes = this._staticRoutes
|
|
1468
|
-
newChain._hasRouteTree = this._hasRouteTree
|
|
1469
|
-
newChain._hasWsRoutes = this._hasWsRoutes
|
|
1470
|
-
newChain._applyScopedEntryErrors(entry)
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
TSubMeta,
|
|
1477
|
-
|
|
1478
|
-
TSubDefaultErrors extends string,
|
|
1479
|
-
TSubBasePath extends string,
|
|
1480
|
-
>(
|
|
1481
|
-
sub: Honey<
|
|
1482
|
-
TEnv,
|
|
1483
|
-
TCtx,
|
|
1484
|
-
TSubRoutes,
|
|
1485
|
-
TSubMeta,
|
|
1486
|
-
TSubErrorFactory,
|
|
1487
|
-
TSubDefaultErrors,
|
|
1488
|
-
TSubBasePath
|
|
1489
|
-
>,
|
|
1490
|
-
): Honey<
|
|
1491
|
-
TEnv,
|
|
1492
|
-
TCtx,
|
|
1493
|
-
TRoutes & TSubRoutes,
|
|
1494
|
-
TMeta,
|
|
1495
|
-
TErrorFactory,
|
|
1496
|
-
TDefaultErrors,
|
|
1497
|
-
TBasePath,
|
|
1498
|
-
TTaps,
|
|
1499
|
-
TScopedMw
|
|
1500
|
-
> {
|
|
1405
|
+
})
|
|
1406
|
+
newChain._basePath = this._basePath
|
|
1407
|
+
newChain._chainMeta = this._chainMeta
|
|
1408
|
+
newChain._contextValues = this._contextValues
|
|
1409
|
+
newChain._defaultBoundaryKey = this._defaultBoundaryKey
|
|
1410
|
+
newChain._errorFactory = this._errorFactory
|
|
1411
|
+
newChain._errorSchema = this._errorSchema
|
|
1412
|
+
newChain._customErrorFormatter = this._customErrorFormatter
|
|
1413
|
+
newChain._customErrorSchema = this._customErrorSchema
|
|
1414
|
+
newChain._errorFormatter = this._errorFormatter
|
|
1415
|
+
newChain._errorI18n = this._errorI18n
|
|
1416
|
+
newChain._logger = this._logger
|
|
1417
|
+
newChain._outputValidation = this._outputValidation
|
|
1418
|
+
newChain._stripPrefix = this._stripPrefix
|
|
1419
|
+
newChain._trailingSlash = this._trailingSlash
|
|
1420
|
+
newChain._wsAdapter = this._wsAdapter
|
|
1421
|
+
newChain._onError = this._onError
|
|
1422
|
+
newChain._onNotFound = this._onNotFound
|
|
1423
|
+
newChain._onMethodNotAllowed = this._onMethodNotAllowed
|
|
1424
|
+
newChain._taps = this._taps
|
|
1425
|
+
newChain._telemetry = this._telemetry
|
|
1426
|
+
newChain._realtimeBus = this._realtimeBus
|
|
1427
|
+
newChain._realtimeRoutes = this._realtimeRoutes
|
|
1428
|
+
newChain._staticRoutes = this._staticRoutes
|
|
1429
|
+
newChain._hasRouteTree = this._hasRouteTree
|
|
1430
|
+
newChain._hasWsRoutes = this._hasWsRoutes
|
|
1431
|
+
newChain._applyScopedEntryErrors(entry)
|
|
1432
|
+
newChain._applyScopedEntryMeta(entry)
|
|
1433
|
+
return newChain
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
route<TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors extends string, TSubBasePath extends string>(
|
|
1437
|
+
sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
|
|
1438
|
+
): Honey<TEnv, TCtx, TRoutes & TSubRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
|
|
1501
1439
|
/* skip self-merge: .handler() already inserted into shared _root */
|
|
1502
1440
|
if (sub._tree !== this._root) {
|
|
1503
|
-
mergeInto(this._root, sub._tree)
|
|
1504
|
-
|
|
1505
|
-
if (sub.
|
|
1441
|
+
mergeInto(this._root, sub._tree)
|
|
1442
|
+
this._absorbMetaSpec(sub._metaSpec)
|
|
1443
|
+
if (sub._hasWsRoutes) this._hasWsRoutes = true
|
|
1444
|
+
if (sub._hasRouteTree) this._hasRouteTree = true
|
|
1506
1445
|
/* carry sub's scoped mw entries into parent's runtime list (parent scopes run first) */
|
|
1507
1446
|
for (const entry of sub._scopedMiddlewares) {
|
|
1508
|
-
this._scopedMiddlewares.push(entry)
|
|
1447
|
+
this._scopedMiddlewares.push(entry)
|
|
1509
1448
|
}
|
|
1510
1449
|
if (sub._staticRoutes.map !== null) {
|
|
1511
1450
|
if (this._staticRoutes.map === null) {
|
|
1512
|
-
this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler
|
|
1451
|
+
this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>
|
|
1513
1452
|
}
|
|
1514
|
-
Object.assign(this._staticRoutes.map, sub._staticRoutes.map)
|
|
1453
|
+
Object.assign(this._staticRoutes.map, sub._staticRoutes.map)
|
|
1515
1454
|
}
|
|
1516
1455
|
for (const [path, cfg] of sub._realtimeRoutes) {
|
|
1517
1456
|
if (this._realtimeRoutes.has(path)) {
|
|
1518
|
-
throw new Error(`Duplicate realtime route: ${path}`)
|
|
1457
|
+
throw new Error(`Duplicate realtime route: ${path}`)
|
|
1519
1458
|
}
|
|
1520
|
-
this._realtimeRoutes.set(path, cfg)
|
|
1459
|
+
this._realtimeRoutes.set(path, cfg)
|
|
1521
1460
|
}
|
|
1522
1461
|
if (!this._realtimeBus && sub._realtimeBus) {
|
|
1523
|
-
this._realtimeBus = sub._realtimeBus
|
|
1462
|
+
this._realtimeBus = sub._realtimeBus
|
|
1524
1463
|
}
|
|
1525
1464
|
if (sub._taps !== null) {
|
|
1526
|
-
if (this._taps === null) this._taps = new Map()
|
|
1465
|
+
if (this._taps === null) this._taps = new Map()
|
|
1527
1466
|
for (const [key, fn] of sub._taps) {
|
|
1528
|
-
if (!this._taps.has(key)) this._taps.set(key, fn)
|
|
1467
|
+
if (!this._taps.has(key)) this._taps.set(key, fn)
|
|
1529
1468
|
}
|
|
1530
1469
|
}
|
|
1531
|
-
/* walk tree and apply all scoped error keys to matching handlers */
|
|
1532
|
-
this._applyAllScopedErrors()
|
|
1470
|
+
/* walk tree and apply all scoped error keys + contributed meta to matching handlers */
|
|
1471
|
+
this._applyAllScopedErrors()
|
|
1472
|
+
for (const entry of this._scopedMiddlewares) this._applyScopedEntryMeta(entry)
|
|
1533
1473
|
}
|
|
1534
1474
|
return this as unknown as Honey<
|
|
1535
1475
|
TEnv,
|
|
@@ -1541,13 +1481,10 @@ export class Honey<
|
|
|
1541
1481
|
TBasePath,
|
|
1542
1482
|
TTaps,
|
|
1543
1483
|
TScopedMw
|
|
1544
|
-
|
|
1484
|
+
>
|
|
1545
1485
|
}
|
|
1546
1486
|
|
|
1547
|
-
private _registerRoute<
|
|
1548
|
-
TPath extends string,
|
|
1549
|
-
TMethod extends HttpMethod | "ALL",
|
|
1550
|
-
>(
|
|
1487
|
+
private _registerRoute<TPath extends string, TMethod extends HttpMethod | "ALL">(
|
|
1551
1488
|
method: TMethod,
|
|
1552
1489
|
path: TPath,
|
|
1553
1490
|
extraMethods?: (HttpMethod | "ALL")[],
|
|
@@ -1570,11 +1507,11 @@ export class Honey<
|
|
|
1570
1507
|
TTaps,
|
|
1571
1508
|
TScopedMw
|
|
1572
1509
|
> {
|
|
1573
|
-
const fullPath = mergePath(this._basePath, path)
|
|
1574
|
-
const errorKeys = new Set(this._defaultErrorKeys)
|
|
1510
|
+
const fullPath = mergePath(this._basePath, path)
|
|
1511
|
+
const errorKeys = new Set(this._defaultErrorKeys)
|
|
1575
1512
|
for (const entry of this._scopedMiddlewares) {
|
|
1576
1513
|
if (entry.errors && scopeMatches(entry.prefix, fullPath)) {
|
|
1577
|
-
for (const k of entry.errors) errorKeys.add(k)
|
|
1514
|
+
for (const k of entry.errors) errorKeys.add(k)
|
|
1578
1515
|
}
|
|
1579
1516
|
}
|
|
1580
1517
|
return new RouteBuilder<
|
|
@@ -1604,6 +1541,7 @@ export class Honey<
|
|
|
1604
1541
|
meta: this._chainMeta ? { ...this._chainMeta } : null,
|
|
1605
1542
|
method,
|
|
1606
1543
|
middlewares: [],
|
|
1544
|
+
mwMeta: this._contributedMetaFor(fullPath),
|
|
1607
1545
|
outputSchemas: null,
|
|
1608
1546
|
parent: this,
|
|
1609
1547
|
parentMiddlewares: this._chainMiddlewares,
|
|
@@ -1627,15 +1565,12 @@ export class Honey<
|
|
|
1627
1565
|
TBasePath,
|
|
1628
1566
|
TTaps,
|
|
1629
1567
|
TScopedMw
|
|
1630
|
-
|
|
1568
|
+
>
|
|
1631
1569
|
}
|
|
1632
1570
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
>(
|
|
1637
|
-
methods: TMethods,
|
|
1638
|
-
path: TPath,
|
|
1571
|
+
on<const TPath extends string, const TMethods extends readonly [HttpMethod | "ALL", ...(HttpMethod | "ALL")[]]>(
|
|
1572
|
+
methods: TMethods,
|
|
1573
|
+
path: TPath,
|
|
1639
1574
|
): BuilderChain<
|
|
1640
1575
|
TEnv,
|
|
1641
1576
|
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
@@ -1655,37 +1590,37 @@ export class Honey<
|
|
|
1655
1590
|
TTaps,
|
|
1656
1591
|
TScopedMw
|
|
1657
1592
|
> {
|
|
1658
|
-
const [first, ...rest] = methods
|
|
1593
|
+
const [first, ...rest] = methods
|
|
1659
1594
|
return this._registerRoute<TPath, TMethods[number]>(
|
|
1660
1595
|
first,
|
|
1661
1596
|
path,
|
|
1662
1597
|
rest.length > 0 ? (rest as (HttpMethod | "ALL")[]) : undefined,
|
|
1663
|
-
)
|
|
1598
|
+
)
|
|
1664
1599
|
}
|
|
1665
1600
|
|
|
1666
1601
|
all<const TPath extends string>(path: TPath) {
|
|
1667
|
-
return this._registerRoute<TPath, "ALL">("ALL", path)
|
|
1602
|
+
return this._registerRoute<TPath, "ALL">("ALL", path)
|
|
1668
1603
|
}
|
|
1669
1604
|
delete<const TPath extends string>(path: TPath) {
|
|
1670
|
-
return this._registerRoute<TPath, "DELETE">("DELETE", path)
|
|
1605
|
+
return this._registerRoute<TPath, "DELETE">("DELETE", path)
|
|
1671
1606
|
}
|
|
1672
1607
|
get<const TPath extends string>(path: TPath) {
|
|
1673
|
-
return this._registerRoute<TPath, "GET">("GET", path)
|
|
1608
|
+
return this._registerRoute<TPath, "GET">("GET", path)
|
|
1674
1609
|
}
|
|
1675
1610
|
head<const TPath extends string>(path: TPath) {
|
|
1676
|
-
return this._registerRoute<TPath, "HEAD">("HEAD", path)
|
|
1611
|
+
return this._registerRoute<TPath, "HEAD">("HEAD", path)
|
|
1677
1612
|
}
|
|
1678
1613
|
options<const TPath extends string>(path: TPath) {
|
|
1679
|
-
return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path)
|
|
1614
|
+
return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path)
|
|
1680
1615
|
}
|
|
1681
1616
|
patch<const TPath extends string>(path: TPath) {
|
|
1682
|
-
return this._registerRoute<TPath, "PATCH">("PATCH", path)
|
|
1617
|
+
return this._registerRoute<TPath, "PATCH">("PATCH", path)
|
|
1683
1618
|
}
|
|
1684
1619
|
post<const TPath extends string>(path: TPath) {
|
|
1685
|
-
return this._registerRoute<TPath, "POST">("POST", path)
|
|
1620
|
+
return this._registerRoute<TPath, "POST">("POST", path)
|
|
1686
1621
|
}
|
|
1687
1622
|
put<const TPath extends string>(path: TPath) {
|
|
1688
|
-
return this._registerRoute<TPath, "PUT">("PUT", path)
|
|
1623
|
+
return this._registerRoute<TPath, "PUT">("PUT", path)
|
|
1689
1624
|
}
|
|
1690
1625
|
|
|
1691
1626
|
ws<const TPath extends string>(
|
|
@@ -1702,51 +1637,42 @@ export class Honey<
|
|
|
1702
1637
|
TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
|
|
1703
1638
|
{},
|
|
1704
1639
|
never,
|
|
1705
|
-
Honey<
|
|
1706
|
-
TEnv,
|
|
1707
|
-
TCtx,
|
|
1708
|
-
TRoutes,
|
|
1709
|
-
TMeta,
|
|
1710
|
-
TErrorFactory,
|
|
1711
|
-
TDefaultErrors,
|
|
1712
|
-
TBasePath,
|
|
1713
|
-
TTaps,
|
|
1714
|
-
TScopedMw
|
|
1715
|
-
>
|
|
1640
|
+
Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
1716
1641
|
>({
|
|
1717
1642
|
boundaryErrorKey: this._defaultBoundaryKey,
|
|
1718
1643
|
errorKeys: new Set(),
|
|
1719
1644
|
inputSchemas: null,
|
|
1720
1645
|
meta: null,
|
|
1721
1646
|
middlewares: [],
|
|
1647
|
+
mwMeta: this._contributedMetaFor(mergePath(this._basePath, path)),
|
|
1722
1648
|
parent: this,
|
|
1723
1649
|
parentMiddlewares: this._chainMiddlewares,
|
|
1724
1650
|
path: mergePath(this._basePath, path),
|
|
1725
1651
|
root: this._root,
|
|
1726
|
-
})
|
|
1652
|
+
})
|
|
1727
1653
|
}
|
|
1728
1654
|
|
|
1729
1655
|
realtime(path: string, opts: RealtimeRouteOpts): this {
|
|
1730
|
-
const fullPath = mergePath(this._basePath, path) || "/"
|
|
1656
|
+
const fullPath = mergePath(this._basePath, path) || "/"
|
|
1731
1657
|
|
|
1732
1658
|
if (!this._realtimeBus) {
|
|
1733
|
-
this._realtimeBus = createBus()
|
|
1659
|
+
this._realtimeBus = createBus()
|
|
1734
1660
|
}
|
|
1735
1661
|
|
|
1736
1662
|
if (this._realtimeRoutes.has(fullPath)) {
|
|
1737
|
-
throw new Error(`Duplicate realtime route: ${fullPath}`)
|
|
1663
|
+
throw new Error(`Duplicate realtime route: ${fullPath}`)
|
|
1738
1664
|
}
|
|
1739
1665
|
|
|
1740
1666
|
this._realtimeRoutes.set(fullPath, {
|
|
1741
1667
|
handler: opts.handler,
|
|
1742
1668
|
middlewares: opts.use,
|
|
1743
1669
|
reconnectBuffer: opts.reconnectBuffer,
|
|
1744
|
-
})
|
|
1670
|
+
})
|
|
1745
1671
|
|
|
1746
|
-
const mw: RuntimeMiddleware[] = []
|
|
1672
|
+
const mw: RuntimeMiddleware[] = []
|
|
1747
1673
|
if (opts.use) {
|
|
1748
1674
|
for (const fn of opts.use) {
|
|
1749
|
-
mw.push(fn as RuntimeMiddleware)
|
|
1675
|
+
mw.push(fn as RuntimeMiddleware)
|
|
1750
1676
|
}
|
|
1751
1677
|
}
|
|
1752
1678
|
|
|
@@ -1758,10 +1684,10 @@ export class Honey<
|
|
|
1758
1684
|
mt: null,
|
|
1759
1685
|
mw: [...this._chainMiddlewares, ...mw],
|
|
1760
1686
|
rp: fullPath,
|
|
1761
|
-
})
|
|
1687
|
+
})
|
|
1762
1688
|
|
|
1763
|
-
this._hasWsRoutes = true
|
|
1764
|
-
return this
|
|
1689
|
+
this._hasWsRoutes = true
|
|
1690
|
+
return this
|
|
1765
1691
|
}
|
|
1766
1692
|
|
|
1767
1693
|
/**
|
|
@@ -1775,9 +1701,10 @@ export class Honey<
|
|
|
1775
1701
|
executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
1776
1702
|
): Response | Promise<Response> {
|
|
1777
1703
|
if (this._wsAdapter === null && !this._hasWsRoutes) {
|
|
1778
|
-
return this._doFetch(request, env, executionCtx)
|
|
1704
|
+
return this._doFetch(request, env, executionCtx)
|
|
1779
1705
|
}
|
|
1780
1706
|
const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
1707
|
+
const headerSnap = isWsUpgrade ? new Headers(request.headers) : undefined
|
|
1781
1708
|
const path = this.pathFromRequest(request)
|
|
1782
1709
|
const canPreUpgrade =
|
|
1783
1710
|
isWsUpgrade &&
|
|
@@ -1785,7 +1712,14 @@ export class Honey<
|
|
|
1785
1712
|
!this.trailingSlashRedirects(path) &&
|
|
1786
1713
|
matchWsRoute(this._root, this.pathAfterPrefix(path)) !== null
|
|
1787
1714
|
const pre = canPreUpgrade ? this._wsAdapter?.preUpgrade?.(request) : undefined
|
|
1788
|
-
|
|
1715
|
+
/* After Deno.upgradeWebSocket the Request is closed. A sync throw here
|
|
1716
|
+
* used to be boxed by async _doFetch; keep 101 returning either way. */
|
|
1717
|
+
let work: Response | Promise<Response>
|
|
1718
|
+
try {
|
|
1719
|
+
work = this._doFetch(request, env, executionCtx, isWsUpgrade === true, headerSnap)
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
work = Promise.reject(err)
|
|
1722
|
+
}
|
|
1789
1723
|
if (!pre) return work
|
|
1790
1724
|
void Promise.resolve(work)
|
|
1791
1725
|
.then((res) => {
|
|
@@ -1842,32 +1776,33 @@ export class Honey<
|
|
|
1842
1776
|
request: Request,
|
|
1843
1777
|
env: TEnv,
|
|
1844
1778
|
executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
|
|
1779
|
+
knownWsUpgrade = false,
|
|
1780
|
+
headerSnap?: Headers,
|
|
1845
1781
|
): Response | Promise<Response> {
|
|
1846
|
-
const startTime = performance.now()
|
|
1782
|
+
const startTime = performance.now()
|
|
1847
1783
|
|
|
1848
1784
|
/* fast path extraction — avoids expensive new URL() allocation */
|
|
1849
|
-
const rawUrl = request.url
|
|
1850
|
-
const protoEnd = rawUrl.indexOf("//")
|
|
1851
|
-
const pathStart = protoEnd === -1 ? 0 : rawUrl.indexOf("/", protoEnd + 2)
|
|
1852
|
-
const searchOrHash =
|
|
1853
|
-
|
|
1854
|
-
let path: string;
|
|
1785
|
+
const rawUrl = request.url
|
|
1786
|
+
const protoEnd = rawUrl.indexOf("//")
|
|
1787
|
+
const pathStart = protoEnd === -1 ? 0 : rawUrl.indexOf("/", protoEnd + 2)
|
|
1788
|
+
const searchOrHash = pathStart === -1 ? -1 : findSearchOrHash(rawUrl, pathStart)
|
|
1789
|
+
let path: string
|
|
1855
1790
|
if (pathStart === -1) {
|
|
1856
|
-
path = "/"
|
|
1791
|
+
path = "/"
|
|
1857
1792
|
} else if (searchOrHash === -1) {
|
|
1858
|
-
path = rawUrl.substring(pathStart)
|
|
1793
|
+
path = rawUrl.substring(pathStart)
|
|
1859
1794
|
} else {
|
|
1860
|
-
path = rawUrl.substring(pathStart, searchOrHash)
|
|
1795
|
+
path = rawUrl.substring(pathStart, searchOrHash)
|
|
1861
1796
|
}
|
|
1862
1797
|
|
|
1863
1798
|
/* lazily create URL only when actually needed (search params, redirects) */
|
|
1864
|
-
let _url: URL | undefined
|
|
1799
|
+
let _url: URL | undefined
|
|
1865
1800
|
const getUrl = (): URL => {
|
|
1866
|
-
if (_url === undefined) _url = new URL(rawUrl)
|
|
1867
|
-
return _url
|
|
1868
|
-
}
|
|
1801
|
+
if (_url === undefined) _url = new URL(rawUrl)
|
|
1802
|
+
return _url
|
|
1803
|
+
}
|
|
1869
1804
|
|
|
1870
|
-
const log = this._logger ?? undefined
|
|
1805
|
+
const log = this._logger ?? undefined
|
|
1871
1806
|
const fc: FetchCtx<TEnv> = {
|
|
1872
1807
|
env,
|
|
1873
1808
|
executionCtx,
|
|
@@ -1875,109 +1810,96 @@ export class Honey<
|
|
|
1875
1810
|
request,
|
|
1876
1811
|
startTime,
|
|
1877
1812
|
url: getUrl,
|
|
1878
|
-
|
|
1813
|
+
wsUpgrade: knownWsUpgrade,
|
|
1814
|
+
headerSnap,
|
|
1815
|
+
}
|
|
1879
1816
|
|
|
1880
1817
|
if (this._telemetry !== null) {
|
|
1881
|
-
safeFire(() => this._telemetry?.onRequest?.({ env, req: request }), log)
|
|
1818
|
+
safeFire(() => this._telemetry?.onRequest?.({ env, req: request }), log)
|
|
1882
1819
|
}
|
|
1883
1820
|
|
|
1884
1821
|
/* trailing slash handling */
|
|
1885
1822
|
if (path.length > 1) {
|
|
1886
1823
|
if (this._trailingSlash === "strip" && path.endsWith("/")) {
|
|
1887
|
-
const redirectUrl = getUrl()
|
|
1888
|
-
redirectUrl.pathname = path.slice(0, -1)
|
|
1824
|
+
const redirectUrl = getUrl()
|
|
1825
|
+
redirectUrl.pathname = path.slice(0, -1)
|
|
1889
1826
|
return new Response(null, {
|
|
1890
1827
|
headers: { location: redirectUrl.toString() },
|
|
1891
1828
|
status: 308,
|
|
1892
|
-
})
|
|
1829
|
+
})
|
|
1893
1830
|
}
|
|
1894
1831
|
if (this._trailingSlash === "enforce" && !path.endsWith("/")) {
|
|
1895
|
-
const redirectUrl = getUrl()
|
|
1896
|
-
redirectUrl.pathname = `${path}
|
|
1832
|
+
const redirectUrl = getUrl()
|
|
1833
|
+
redirectUrl.pathname = `${path}/`
|
|
1897
1834
|
return new Response(null, {
|
|
1898
1835
|
headers: { location: redirectUrl.toString() },
|
|
1899
1836
|
status: 308,
|
|
1900
|
-
})
|
|
1837
|
+
})
|
|
1901
1838
|
}
|
|
1902
1839
|
}
|
|
1903
1840
|
|
|
1904
1841
|
/* prefix stripping — must run AFTER trailing slash so redirects preserve the full prefixed URL */
|
|
1905
1842
|
if (this._stripPrefix !== null) {
|
|
1906
1843
|
if (path === this._stripPrefix) {
|
|
1907
|
-
path = "/"
|
|
1908
|
-
} else if (
|
|
1909
|
-
path.
|
|
1910
|
-
path.charCodeAt(this._stripPrefix.length) === 47
|
|
1911
|
-
) {
|
|
1912
|
-
path = path.slice(this._stripPrefix.length);
|
|
1844
|
+
path = "/"
|
|
1845
|
+
} else if (path.startsWith(this._stripPrefix) && path.charCodeAt(this._stripPrefix.length) === 47) {
|
|
1846
|
+
path = path.slice(this._stripPrefix.length)
|
|
1913
1847
|
}
|
|
1914
1848
|
}
|
|
1915
1849
|
|
|
1916
|
-
/* WebSocket route check */
|
|
1917
|
-
const isWsUpgrade = request
|
|
1850
|
+
/* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
|
|
1851
|
+
const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request)
|
|
1918
1852
|
if (isWsUpgrade) {
|
|
1919
|
-
const wsMatch = matchWsRoute(this._root, path)
|
|
1853
|
+
const wsMatch = matchWsRoute(this._root, path)
|
|
1920
1854
|
if (wsMatch !== null) {
|
|
1921
|
-
return this._handleWs(fc, wsMatch)
|
|
1855
|
+
return this._handleWs(fc, wsMatch)
|
|
1922
1856
|
}
|
|
1923
1857
|
} else if (this._realtimeRoutes.size > 0) {
|
|
1924
1858
|
/* Non-upgrade request hitting a realtime-only path → 426 Upgrade Required */
|
|
1925
|
-
const wsMatch = matchWsRoute(this._root, path)
|
|
1859
|
+
const wsMatch = matchWsRoute(this._root, path)
|
|
1926
1860
|
if (wsMatch !== null && this._realtimeRoutes.has(wsMatch.handler.rp)) {
|
|
1927
|
-
return new Response(null, { headers: { upgrade: "websocket" }, status: 426 })
|
|
1861
|
+
return new Response(null, { headers: { upgrade: "websocket" }, status: 426 })
|
|
1928
1862
|
}
|
|
1929
1863
|
}
|
|
1930
1864
|
|
|
1931
|
-
const method = request.method.toUpperCase() as HttpMethod
|
|
1865
|
+
const method = request.method.toUpperCase() as HttpMethod
|
|
1932
1866
|
|
|
1933
1867
|
/* fn:null fallthrough — tree match sets meta, catch-all dispatches */
|
|
1934
|
-
let fnNullMeta: Record<string, unknown> | null = null
|
|
1935
|
-
let fnNullParams: Record<string, string> | null = null
|
|
1936
|
-
let fnNullHit = false
|
|
1868
|
+
let fnNullMeta: Record<string, unknown> | null = null
|
|
1869
|
+
let fnNullParams: Record<string, string> | null = null
|
|
1870
|
+
let fnNullHit = false
|
|
1937
1871
|
|
|
1938
1872
|
/* Tier 2: O(1) static route lookup — checks both precompiled and runtime maps */
|
|
1939
|
-
const smap = this._staticRoutes.map ?? this._handlerMap
|
|
1873
|
+
const smap = this._staticRoutes.map ?? this._handlerMap
|
|
1940
1874
|
if (smap !== null) {
|
|
1941
|
-
const key = `${method} ${path}
|
|
1942
|
-
const staticHandler = smap[key]
|
|
1875
|
+
const key = `${method} ${path}`
|
|
1876
|
+
const staticHandler = smap[key]
|
|
1943
1877
|
if (staticHandler) {
|
|
1944
1878
|
if (staticHandler.fn === null) {
|
|
1945
|
-
fnNullMeta = staticHandler.mt
|
|
1946
|
-
fnNullParams = EMPTY_PARAMS
|
|
1947
|
-
fnNullHit = true
|
|
1879
|
+
fnNullMeta = staticHandler.mt
|
|
1880
|
+
fnNullParams = EMPTY_PARAMS
|
|
1881
|
+
fnNullHit = true
|
|
1948
1882
|
} else {
|
|
1949
|
-
return this._handleMatched(
|
|
1950
|
-
fc,
|
|
1951
|
-
method,
|
|
1952
|
-
path,
|
|
1953
|
-
staticHandler,
|
|
1954
|
-
EMPTY_PARAMS,
|
|
1955
|
-
);
|
|
1883
|
+
return this._handleMatched(fc, method, path, staticHandler, EMPTY_PARAMS)
|
|
1956
1884
|
}
|
|
1957
1885
|
}
|
|
1958
1886
|
/* HEAD falls back to GET */
|
|
1959
1887
|
if (!fnNullHit && method === "HEAD") {
|
|
1960
|
-
const getHandler = smap[`GET ${path}`]
|
|
1888
|
+
const getHandler = smap[`GET ${path}`]
|
|
1961
1889
|
if (getHandler) {
|
|
1962
1890
|
if (getHandler.fn === null) {
|
|
1963
|
-
fnNullMeta = getHandler.mt
|
|
1964
|
-
fnNullParams = EMPTY_PARAMS
|
|
1965
|
-
fnNullHit = true
|
|
1891
|
+
fnNullMeta = getHandler.mt
|
|
1892
|
+
fnNullParams = EMPTY_PARAMS
|
|
1893
|
+
fnNullHit = true
|
|
1966
1894
|
} else {
|
|
1967
|
-
return this._handleMatched(
|
|
1968
|
-
fc,
|
|
1969
|
-
method,
|
|
1970
|
-
path,
|
|
1971
|
-
getHandler,
|
|
1972
|
-
EMPTY_PARAMS,
|
|
1973
|
-
);
|
|
1895
|
+
return this._handleMatched(fc, method, path, getHandler, EMPTY_PARAMS)
|
|
1974
1896
|
}
|
|
1975
1897
|
}
|
|
1976
1898
|
}
|
|
1977
1899
|
}
|
|
1978
1900
|
|
|
1979
1901
|
if (!fnNullHit) {
|
|
1980
|
-
const result = matchRoute(this._root, method, path)
|
|
1902
|
+
const result = matchRoute(this._root, method, path)
|
|
1981
1903
|
|
|
1982
1904
|
if (result?.matched) {
|
|
1983
1905
|
/*
|
|
@@ -1989,41 +1911,34 @@ export class Honey<
|
|
|
1989
1911
|
this._hasRouteTree &&
|
|
1990
1912
|
this._root.w !== null &&
|
|
1991
1913
|
result.handler.fn !== null &&
|
|
1992
|
-
(this._root.w.m[method] === result.handler ||
|
|
1993
|
-
this._root.w.m["ALL"] === result.handler);
|
|
1914
|
+
(this._root.w.m[method] === result.handler || this._root.w.m["ALL"] === result.handler)
|
|
1994
1915
|
|
|
1995
1916
|
if (isWildcardCatchAll) {
|
|
1996
1917
|
/* path not in routeTree — 404 */
|
|
1997
|
-
return this._handle404(fc, method, path)
|
|
1918
|
+
return this._handle404(fc, method, path)
|
|
1998
1919
|
}
|
|
1999
1920
|
|
|
2000
1921
|
if (result.handler.fn === null) {
|
|
2001
|
-
fnNullMeta = result.handler.mt
|
|
2002
|
-
fnNullParams = result.params
|
|
2003
|
-
fnNullHit = true
|
|
1922
|
+
fnNullMeta = result.handler.mt
|
|
1923
|
+
fnNullParams = result.params
|
|
1924
|
+
fnNullHit = true
|
|
2004
1925
|
} else {
|
|
2005
|
-
return this._handleMatched(
|
|
2006
|
-
fc,
|
|
2007
|
-
method,
|
|
2008
|
-
path,
|
|
2009
|
-
result.handler,
|
|
2010
|
-
result.params,
|
|
2011
|
-
);
|
|
1926
|
+
return this._handleMatched(fc, method, path, result.handler, result.params)
|
|
2012
1927
|
}
|
|
2013
1928
|
}
|
|
2014
1929
|
|
|
2015
1930
|
if (!fnNullHit) {
|
|
2016
1931
|
if (result === null) {
|
|
2017
1932
|
if (!isWsUpgrade) {
|
|
2018
|
-
const wsMatch = matchWsRoute(this._root, path)
|
|
1933
|
+
const wsMatch = matchWsRoute(this._root, path)
|
|
2019
1934
|
if (wsMatch !== null) {
|
|
2020
1935
|
return new Response("Upgrade Required", {
|
|
2021
1936
|
headers: { connection: "Upgrade", upgrade: "websocket" },
|
|
2022
1937
|
status: 426,
|
|
2023
|
-
})
|
|
1938
|
+
})
|
|
2024
1939
|
}
|
|
2025
1940
|
}
|
|
2026
|
-
return this._handle404(fc, method, path)
|
|
1941
|
+
return this._handle404(fc, method, path)
|
|
2027
1942
|
}
|
|
2028
1943
|
|
|
2029
1944
|
if (!result.matched) {
|
|
@@ -2032,47 +1947,33 @@ export class Honey<
|
|
|
2032
1947
|
fc.request.headers.has("access-control-request-method") &&
|
|
2033
1948
|
result.allowed.length > 0
|
|
2034
1949
|
) {
|
|
2035
|
-
const fallback = this._preflightFallbackMethod(result.allowed)
|
|
2036
|
-
const retry = matchRoute(this._root, fallback, path)
|
|
1950
|
+
const fallback = this._preflightFallbackMethod(result.allowed)
|
|
1951
|
+
const retry = matchRoute(this._root, fallback, path)
|
|
2037
1952
|
if (retry?.matched && retry.handler.fn !== null) {
|
|
2038
|
-
return this._handleCorsPreflight(
|
|
2039
|
-
fc,
|
|
2040
|
-
path,
|
|
2041
|
-
retry.handler,
|
|
2042
|
-
retry.params,
|
|
2043
|
-
result.allowed,
|
|
2044
|
-
);
|
|
1953
|
+
return this._handleCorsPreflight(fc, path, retry.handler, retry.params, result.allowed)
|
|
2045
1954
|
}
|
|
2046
1955
|
}
|
|
2047
|
-
return this._handle405(fc, method, path, result.allowed)
|
|
1956
|
+
return this._handle405(fc, method, path, result.allowed)
|
|
2048
1957
|
}
|
|
2049
1958
|
}
|
|
2050
1959
|
}
|
|
2051
1960
|
|
|
2052
1961
|
/* fn:null hit — find catch-all/wildcard to dispatch with stashed meta */
|
|
2053
|
-
const wildcardResult = matchRoute(this._root, method, "/*")
|
|
1962
|
+
const wildcardResult = matchRoute(this._root, method, "/*")
|
|
2054
1963
|
if (wildcardResult?.matched && wildcardResult.handler.fn !== null) {
|
|
2055
|
-
return this._handleMatched(
|
|
2056
|
-
fc,
|
|
2057
|
-
method,
|
|
2058
|
-
path,
|
|
2059
|
-
wildcardResult.handler,
|
|
2060
|
-
fnNullParams ?? EMPTY_PARAMS,
|
|
2061
|
-
fnNullMeta,
|
|
2062
|
-
);
|
|
1964
|
+
return this._handleMatched(fc, method, path, wildcardResult.handler, fnNullParams ?? EMPTY_PARAMS, fnNullMeta)
|
|
2063
1965
|
}
|
|
2064
1966
|
|
|
2065
|
-
return this._handle404(fc, method, path)
|
|
1967
|
+
return this._handle404(fc, method, path)
|
|
2066
1968
|
}
|
|
2067
1969
|
|
|
2068
1970
|
private _makeErrorCtx(fc: FetchCtx<TEnv>, allowed?: string[]) {
|
|
2069
1971
|
return {
|
|
2070
1972
|
env: fc.env,
|
|
2071
|
-
jsonFromError: (err: HoneyError) =>
|
|
2072
|
-
createErrorResponse(err, this._errorFormatter, this._customErrorFormatter),
|
|
1973
|
+
jsonFromError: (err: HoneyError) => createErrorResponse(err, this._errorFormatter, this._customErrorFormatter),
|
|
2073
1974
|
req: fc.request,
|
|
2074
1975
|
...(allowed ? { allowed } : {}),
|
|
2075
|
-
}
|
|
1976
|
+
}
|
|
2076
1977
|
}
|
|
2077
1978
|
|
|
2078
1979
|
private async _handleWs(
|
|
@@ -2080,73 +1981,68 @@ export class Honey<
|
|
|
2080
1981
|
wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
|
|
2081
1982
|
): Promise<Response> {
|
|
2082
1983
|
/* Dispatch to realtime handler if this path is registered as a realtime route */
|
|
2083
|
-
const realtimeConfig = this._realtimeRoutes.get(wsMatch.handler.rp)
|
|
1984
|
+
const realtimeConfig = this._realtimeRoutes.get(wsMatch.handler.rp)
|
|
2084
1985
|
if (realtimeConfig) {
|
|
2085
|
-
return this._handleRealtime(fc, wsMatch, realtimeConfig)
|
|
1986
|
+
return this._handleRealtime(fc, wsMatch, realtimeConfig)
|
|
2086
1987
|
}
|
|
2087
1988
|
|
|
2088
|
-
const isUpgrade =
|
|
2089
|
-
fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
1989
|
+
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
|
|
2090
1990
|
if (!isUpgrade) {
|
|
2091
1991
|
return new Response(null, {
|
|
2092
1992
|
headers: { upgrade: "websocket" },
|
|
2093
1993
|
status: 426,
|
|
2094
|
-
})
|
|
1994
|
+
})
|
|
2095
1995
|
}
|
|
2096
1996
|
|
|
2097
|
-
const wsAdapter = this._wsAdapter
|
|
1997
|
+
const wsAdapter = this._wsAdapter
|
|
2098
1998
|
if (!wsAdapter) {
|
|
2099
|
-
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
|
|
1999
|
+
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
|
|
2100
2000
|
return createErrorResponse(
|
|
2101
2001
|
this._createError(EK.internal_server_error, SK.internal_server_error),
|
|
2102
2002
|
this._errorFormatter,
|
|
2103
2003
|
this._customErrorFormatter,
|
|
2104
|
-
)
|
|
2004
|
+
)
|
|
2105
2005
|
}
|
|
2106
2006
|
|
|
2107
2007
|
const wsCtx = new HoneyContext({
|
|
2108
2008
|
env: fc.env,
|
|
2109
2009
|
executionCtx: fc.executionCtx,
|
|
2110
2010
|
params: wsMatch.params,
|
|
2111
|
-
req: fc
|
|
2011
|
+
req: ctxRequest(fc),
|
|
2112
2012
|
urlFn: fc.url,
|
|
2113
|
-
})
|
|
2114
|
-
if (this._contextValues) Object.assign(wsCtx, this._contextValues)
|
|
2013
|
+
})
|
|
2014
|
+
if (this._contextValues) Object.assign(wsCtx, this._contextValues)
|
|
2115
2015
|
|
|
2116
2016
|
/*
|
|
2117
2017
|
* WS ordering: [global → scoped → chain+handler-route-specific]
|
|
2118
2018
|
* WS bakes chain mw into handler.mw at registration, so scoped runs before chain.
|
|
2119
2019
|
* This is an unavoidable inconsistency vs HTTP (where chain runs before scoped).
|
|
2120
2020
|
*/
|
|
2121
|
-
const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
|
|
2122
|
-
const allWsMw: RuntimeMiddleware[] = [
|
|
2123
|
-
...this._globalMiddlewares,
|
|
2124
|
-
...scopedForPath,
|
|
2125
|
-
...wsMatch.handler.mw,
|
|
2126
|
-
];
|
|
2021
|
+
const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
|
|
2022
|
+
const allWsMw: RuntimeMiddleware[] = [...this._globalMiddlewares, ...scopedForPath, ...wsMatch.handler.mw]
|
|
2127
2023
|
|
|
2128
2024
|
try {
|
|
2129
2025
|
return await executeChain(allWsMw, wsCtx, async (finalCtx) => {
|
|
2130
|
-
const userHandler = wsMatch.handler.fn
|
|
2131
|
-
let messageQueue: Promise<void> = Promise.resolve()
|
|
2026
|
+
const userHandler = wsMatch.handler.fn
|
|
2027
|
+
let messageQueue: Promise<void> = Promise.resolve()
|
|
2132
2028
|
|
|
2133
|
-
const onOpenFn = userHandler.onOpen
|
|
2134
|
-
const onMsgFn = userHandler.onMessage
|
|
2135
|
-
const onCloseFn = userHandler.onClose
|
|
2136
|
-
const onErrorFn = userHandler.onError
|
|
2137
|
-
const onReconnectFn = userHandler.onReconnect
|
|
2138
|
-
const reconnectToken = fc.url().searchParams.get("reconnect_token")
|
|
2029
|
+
const onOpenFn = userHandler.onOpen
|
|
2030
|
+
const onMsgFn = userHandler.onMessage
|
|
2031
|
+
const onCloseFn = userHandler.onClose
|
|
2032
|
+
const onErrorFn = userHandler.onError
|
|
2033
|
+
const onReconnectFn = userHandler.onReconnect
|
|
2034
|
+
const reconnectToken = fc.url().searchParams.get("reconnect_token")
|
|
2139
2035
|
|
|
2140
|
-
const wrappedHandler: WSHandler<unknown> = {}
|
|
2036
|
+
const wrappedHandler: WSHandler<unknown> = {}
|
|
2141
2037
|
|
|
2142
2038
|
if (reconnectToken && onReconnectFn) {
|
|
2143
2039
|
wrappedHandler.onOpen = (_ctx, ws) => {
|
|
2144
|
-
onReconnectFn(finalCtx, ws, reconnectToken)
|
|
2145
|
-
}
|
|
2040
|
+
onReconnectFn(finalCtx, ws, reconnectToken)
|
|
2041
|
+
}
|
|
2146
2042
|
} else if (onOpenFn) {
|
|
2147
2043
|
wrappedHandler.onOpen = (_ctx, ws) => {
|
|
2148
|
-
onOpenFn(finalCtx, ws)
|
|
2149
|
-
}
|
|
2044
|
+
onOpenFn(finalCtx, ws)
|
|
2045
|
+
}
|
|
2150
2046
|
}
|
|
2151
2047
|
|
|
2152
2048
|
if (onMsgFn) {
|
|
@@ -2154,32 +2050,28 @@ export class Honey<
|
|
|
2154
2050
|
messageQueue = messageQueue
|
|
2155
2051
|
.then(() => onMsgFn(finalCtx, ws, data))
|
|
2156
2052
|
.catch((err: unknown) => {
|
|
2157
|
-
onErrorFn?.(finalCtx, ws, err)
|
|
2158
|
-
})
|
|
2159
|
-
}
|
|
2053
|
+
onErrorFn?.(finalCtx, ws, err)
|
|
2054
|
+
})
|
|
2055
|
+
}
|
|
2160
2056
|
}
|
|
2161
2057
|
|
|
2162
2058
|
if (onCloseFn) {
|
|
2163
2059
|
wrappedHandler.onClose = (_ctx, ws, code, reason) => {
|
|
2164
|
-
onCloseFn(finalCtx, ws, code, reason)
|
|
2165
|
-
}
|
|
2060
|
+
onCloseFn(finalCtx, ws, code, reason)
|
|
2061
|
+
}
|
|
2166
2062
|
}
|
|
2167
2063
|
|
|
2168
2064
|
if (onErrorFn) {
|
|
2169
2065
|
wrappedHandler.onError = (_ctx, ws, error) => {
|
|
2170
|
-
onErrorFn(finalCtx, ws, error)
|
|
2171
|
-
}
|
|
2066
|
+
onErrorFn(finalCtx, ws, error)
|
|
2067
|
+
}
|
|
2172
2068
|
}
|
|
2173
2069
|
|
|
2174
|
-
const upgradeResult = await wsAdapter.upgrade(
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
wrappedHandler,
|
|
2178
|
-
);
|
|
2179
|
-
return upgradeResult.response;
|
|
2180
|
-
});
|
|
2070
|
+
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler)
|
|
2071
|
+
return upgradeResult.response
|
|
2072
|
+
})
|
|
2181
2073
|
} catch (thrown) {
|
|
2182
|
-
return this._toErrorResponse(thrown)
|
|
2074
|
+
return this._toErrorResponse(thrown)
|
|
2183
2075
|
}
|
|
2184
2076
|
}
|
|
2185
2077
|
|
|
@@ -2188,56 +2080,51 @@ export class Honey<
|
|
|
2188
2080
|
wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
|
|
2189
2081
|
config: { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number },
|
|
2190
2082
|
): Promise<Response> {
|
|
2191
|
-
const isUpgrade =
|
|
2192
|
-
fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
2083
|
+
const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
|
|
2193
2084
|
if (!isUpgrade) {
|
|
2194
2085
|
return new Response(null, {
|
|
2195
2086
|
headers: { upgrade: "websocket" },
|
|
2196
2087
|
status: 426,
|
|
2197
|
-
})
|
|
2088
|
+
})
|
|
2198
2089
|
}
|
|
2199
2090
|
|
|
2200
|
-
const wsAdapter = this._wsAdapter
|
|
2091
|
+
const wsAdapter = this._wsAdapter
|
|
2201
2092
|
if (!wsAdapter) {
|
|
2202
|
-
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
|
|
2093
|
+
fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
|
|
2203
2094
|
return createErrorResponse(
|
|
2204
2095
|
this._createError(EK.internal_server_error, SK.internal_server_error),
|
|
2205
2096
|
this._errorFormatter,
|
|
2206
2097
|
this._customErrorFormatter,
|
|
2207
|
-
)
|
|
2098
|
+
)
|
|
2208
2099
|
}
|
|
2209
2100
|
|
|
2210
2101
|
/* Lazily create bus if not yet initialized (happens when realtime() was called on a child chain) */
|
|
2211
2102
|
if (!this._realtimeBus) {
|
|
2212
|
-
this._realtimeBus = createBus()
|
|
2103
|
+
this._realtimeBus = createBus()
|
|
2213
2104
|
}
|
|
2214
|
-
const bus = this._realtimeBus
|
|
2105
|
+
const bus = this._realtimeBus
|
|
2215
2106
|
|
|
2216
2107
|
const ctx = new HoneyContext({
|
|
2217
2108
|
env: fc.env,
|
|
2218
2109
|
executionCtx: fc.executionCtx,
|
|
2219
2110
|
params: wsMatch.params,
|
|
2220
|
-
req: fc
|
|
2111
|
+
req: ctxRequest(fc),
|
|
2221
2112
|
urlFn: fc.url,
|
|
2222
|
-
})
|
|
2223
|
-
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2113
|
+
})
|
|
2114
|
+
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2224
2115
|
Object.assign(ctx, {
|
|
2225
2116
|
realtime: { publish: (topic: string, data: unknown) => bus.publish(topic, data) },
|
|
2226
|
-
})
|
|
2117
|
+
})
|
|
2227
2118
|
|
|
2228
|
-
const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
|
|
2229
|
-
const allMw: RuntimeMiddleware[] = [
|
|
2230
|
-
...this._globalMiddlewares,
|
|
2231
|
-
...scopedForPath,
|
|
2232
|
-
...wsMatch.handler.mw,
|
|
2233
|
-
];
|
|
2119
|
+
const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
|
|
2120
|
+
const allMw: RuntimeMiddleware[] = [...this._globalMiddlewares, ...scopedForPath, ...wsMatch.handler.mw]
|
|
2234
2121
|
|
|
2235
2122
|
try {
|
|
2236
2123
|
return await executeChain(allMw, ctx, async (finalCtx) => {
|
|
2237
|
-
const connId = crypto.randomUUID()
|
|
2124
|
+
const connId = crypto.randomUUID()
|
|
2238
2125
|
|
|
2239
|
-
let socket: WSContext<unknown> | null = null
|
|
2240
|
-
let conn: ReturnType<typeof createConnContext> | null = null
|
|
2126
|
+
let socket: WSContext<unknown> | null = null
|
|
2127
|
+
let conn: ReturnType<typeof createConnContext> | null = null
|
|
2241
2128
|
|
|
2242
2129
|
/*
|
|
2243
2130
|
* initConn creates the ConnContext and calls the user handler.
|
|
@@ -2245,88 +2132,82 @@ export class Honey<
|
|
|
2245
2132
|
* or inline after upgrade (for Node/CF where socket is immediate).
|
|
2246
2133
|
*/
|
|
2247
2134
|
const initConn = (ws: WSContext<unknown>) => {
|
|
2248
|
-
socket = ws
|
|
2135
|
+
socket = ws
|
|
2249
2136
|
conn = createConnContext({
|
|
2250
2137
|
bus,
|
|
2251
2138
|
closeFn: (reason) => {
|
|
2252
|
-
if (socket) socket.close(1000, reason)
|
|
2139
|
+
if (socket) socket.close(1000, reason)
|
|
2253
2140
|
},
|
|
2254
2141
|
id: connId,
|
|
2255
2142
|
sendFn: (payload) => {
|
|
2256
|
-
if (socket)
|
|
2143
|
+
if (socket)
|
|
2144
|
+
socket.send(typeof payload === "object" && payload !== null ? JSON.stringify(payload) : String(payload))
|
|
2257
2145
|
},
|
|
2258
2146
|
transport: "ws",
|
|
2259
2147
|
userId: null,
|
|
2260
|
-
})
|
|
2148
|
+
})
|
|
2261
2149
|
|
|
2262
2150
|
bus.onMessage(connId, (data) => {
|
|
2263
2151
|
if (socket) {
|
|
2264
|
-
socket.send(typeof data === "object" && data !== null ? JSON.stringify(data) : String(data))
|
|
2152
|
+
socket.send(typeof data === "object" && data !== null ? JSON.stringify(data) : String(data))
|
|
2265
2153
|
}
|
|
2266
|
-
})
|
|
2154
|
+
})
|
|
2267
2155
|
|
|
2268
|
-
config.handler(finalCtx, conn)
|
|
2269
|
-
}
|
|
2156
|
+
config.handler(finalCtx, conn)
|
|
2157
|
+
}
|
|
2270
2158
|
|
|
2271
2159
|
const wrappedHandler: WSHandler<unknown> = {
|
|
2272
2160
|
onClose: (_ctx, _ws, _code, reason) => {
|
|
2273
|
-
if (!conn) return
|
|
2274
|
-
const handlers = conn._handlers
|
|
2161
|
+
if (!conn) return
|
|
2162
|
+
const handlers = conn._handlers
|
|
2275
2163
|
if (handlers.close) {
|
|
2276
|
-
handlers.close(reason || "normal")
|
|
2164
|
+
handlers.close(reason || "normal")
|
|
2277
2165
|
}
|
|
2278
|
-
bus.unsubscribeAll(connId)
|
|
2279
|
-
bus.removeHandler(connId)
|
|
2166
|
+
bus.unsubscribeAll(connId)
|
|
2167
|
+
bus.removeHandler(connId)
|
|
2280
2168
|
},
|
|
2281
2169
|
onMessage: (_ctx, _ws, data) => {
|
|
2282
|
-
if (!conn) return
|
|
2283
|
-
const handlers = conn._handlers
|
|
2170
|
+
if (!conn) return
|
|
2171
|
+
const handlers = conn._handlers
|
|
2284
2172
|
if (handlers.message && typeof data === "string") {
|
|
2285
2173
|
try {
|
|
2286
|
-
const parsed: unknown = JSON.parse(data)
|
|
2174
|
+
const parsed: unknown = JSON.parse(data)
|
|
2287
2175
|
if (isMsgFrame(parsed)) {
|
|
2288
|
-
handlers.message(parsed.data)
|
|
2176
|
+
handlers.message(parsed.data)
|
|
2289
2177
|
}
|
|
2290
|
-
} catch {
|
|
2178
|
+
} catch {
|
|
2179
|
+
/* ignore malformed frames */
|
|
2180
|
+
}
|
|
2291
2181
|
}
|
|
2292
2182
|
},
|
|
2293
2183
|
onOpen: (_ctx, ws) => {
|
|
2294
|
-
if (!socket) initConn(ws)
|
|
2184
|
+
if (!socket) initConn(ws)
|
|
2295
2185
|
},
|
|
2296
|
-
}
|
|
2186
|
+
}
|
|
2297
2187
|
|
|
2298
|
-
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler)
|
|
2188
|
+
const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler)
|
|
2299
2189
|
|
|
2300
2190
|
/* Node/CF adapters return the socket from upgrade(); Bun returns undefined (socket comes via onOpen).
|
|
2301
2191
|
* Deno pre-upgrade may still be CONNECTING — wait for onOpen so the first send is not dropped. */
|
|
2302
2192
|
if (upgradeResult.socket && !socket && upgradeResult.socket.readyState === 1) {
|
|
2303
|
-
initConn(upgradeResult.socket)
|
|
2193
|
+
initConn(upgradeResult.socket)
|
|
2304
2194
|
}
|
|
2305
2195
|
|
|
2306
|
-
return upgradeResult.response
|
|
2307
|
-
})
|
|
2196
|
+
return upgradeResult.response
|
|
2197
|
+
})
|
|
2308
2198
|
} catch (thrown) {
|
|
2309
|
-
return this._toErrorResponse(thrown)
|
|
2199
|
+
return this._toErrorResponse(thrown)
|
|
2310
2200
|
}
|
|
2311
2201
|
}
|
|
2312
2202
|
|
|
2313
|
-
private async _handle404(
|
|
2314
|
-
|
|
2315
|
-
method: string,
|
|
2316
|
-
path: string,
|
|
2317
|
-
): Promise<Response> {
|
|
2318
|
-
safeFire(
|
|
2319
|
-
() => this._telemetry?.onNotFound?.({ method, path, req: fc.request }),
|
|
2320
|
-
fc.log,
|
|
2321
|
-
);
|
|
2203
|
+
private async _handle404(fc: FetchCtx<TEnv>, method: string, path: string): Promise<Response> {
|
|
2204
|
+
safeFire(() => this._telemetry?.onNotFound?.({ method, path, req: fc.request }), fc.log)
|
|
2322
2205
|
const make404 = () => {
|
|
2323
2206
|
if (this._onNotFound) {
|
|
2324
|
-
return this._onNotFound(this._makeErrorCtx(fc))
|
|
2207
|
+
return this._onNotFound(this._makeErrorCtx(fc))
|
|
2325
2208
|
}
|
|
2326
|
-
return this._makeErrorCtx(fc).jsonFromError(
|
|
2327
|
-
|
|
2328
|
-
);
|
|
2329
|
-
};
|
|
2209
|
+
return this._makeErrorCtx(fc).jsonFromError(this._createError(EK.not_found, SK.not_found))
|
|
2210
|
+
}
|
|
2330
2211
|
try {
|
|
2331
2212
|
const ctx404 = new HoneyContext({
|
|
2332
2213
|
env: fc.env,
|
|
@@ -2334,12 +2215,12 @@ export class Honey<
|
|
|
2334
2215
|
params: {},
|
|
2335
2216
|
req: fc.request,
|
|
2336
2217
|
urlFn: fc.url,
|
|
2337
|
-
})
|
|
2338
|
-
if (this._contextValues) Object.assign(ctx404, this._contextValues)
|
|
2218
|
+
})
|
|
2219
|
+
if (this._contextValues) Object.assign(ctx404, this._contextValues)
|
|
2339
2220
|
const res =
|
|
2340
2221
|
this._chainMiddlewares.length > 0
|
|
2341
2222
|
? await executeChain(this._chainMiddlewares, ctx404, make404)
|
|
2342
|
-
: await make404()
|
|
2223
|
+
: await make404()
|
|
2343
2224
|
safeFire(
|
|
2344
2225
|
() =>
|
|
2345
2226
|
this._telemetry?.onResponse?.({
|
|
@@ -2348,18 +2229,18 @@ export class Honey<
|
|
|
2348
2229
|
status: res.status,
|
|
2349
2230
|
}),
|
|
2350
2231
|
fc.log,
|
|
2351
|
-
)
|
|
2352
|
-
return res
|
|
2232
|
+
)
|
|
2233
|
+
return res
|
|
2353
2234
|
} catch (thrown) {
|
|
2354
|
-
return this._toErrorResponse(thrown)
|
|
2235
|
+
return this._toErrorResponse(thrown)
|
|
2355
2236
|
}
|
|
2356
2237
|
}
|
|
2357
2238
|
|
|
2358
2239
|
private _preflightFallbackMethod(allowed: string[]): HttpMethod {
|
|
2359
|
-
if (allowed.includes("GET")) return "GET"
|
|
2360
|
-
if (allowed.includes("HEAD")) return "HEAD"
|
|
2361
|
-
if (allowed.includes("POST")) return "POST"
|
|
2362
|
-
return allowed[0] as HttpMethod
|
|
2240
|
+
if (allowed.includes("GET")) return "GET"
|
|
2241
|
+
if (allowed.includes("HEAD")) return "HEAD"
|
|
2242
|
+
if (allowed.includes("POST")) return "POST"
|
|
2243
|
+
return allowed[0] as HttpMethod
|
|
2363
2244
|
}
|
|
2364
2245
|
|
|
2365
2246
|
/** Run the existing method's middleware for a CORS preflight. Do not invoke the route handler. */
|
|
@@ -2379,25 +2260,18 @@ export class Honey<
|
|
|
2379
2260
|
req: fc.request,
|
|
2380
2261
|
routePattern: handler.rp,
|
|
2381
2262
|
urlFn: fc.url,
|
|
2382
|
-
})
|
|
2383
|
-
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2263
|
+
})
|
|
2264
|
+
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2384
2265
|
try {
|
|
2385
|
-
return await executeChain(
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
() => this._handle405(fc, "OPTIONS", path, allowed),
|
|
2389
|
-
);
|
|
2266
|
+
return await executeChain([...this._globalMiddlewares, ...handler.mw], ctx, () =>
|
|
2267
|
+
this._handle405(fc, "OPTIONS", path, allowed),
|
|
2268
|
+
)
|
|
2390
2269
|
} catch (thrown) {
|
|
2391
|
-
return this._toErrorResponse(thrown)
|
|
2270
|
+
return this._toErrorResponse(thrown)
|
|
2392
2271
|
}
|
|
2393
2272
|
}
|
|
2394
2273
|
|
|
2395
|
-
private async _handle405(
|
|
2396
|
-
fc: FetchCtx<TEnv>,
|
|
2397
|
-
method: string,
|
|
2398
|
-
path: string,
|
|
2399
|
-
allowed: string[],
|
|
2400
|
-
): Promise<Response> {
|
|
2274
|
+
private async _handle405(fc: FetchCtx<TEnv>, method: string, path: string, allowed: string[]): Promise<Response> {
|
|
2401
2275
|
safeFire(
|
|
2402
2276
|
() =>
|
|
2403
2277
|
this._telemetry?.onMethodNotAllowed?.({
|
|
@@ -2407,36 +2281,33 @@ export class Honey<
|
|
|
2407
2281
|
req: fc.request,
|
|
2408
2282
|
}),
|
|
2409
2283
|
fc.log,
|
|
2410
|
-
)
|
|
2284
|
+
)
|
|
2411
2285
|
const make405 = async () => {
|
|
2412
2286
|
if (this._onMethodNotAllowed) {
|
|
2413
2287
|
const res = await this._onMethodNotAllowed(
|
|
2414
2288
|
this._makeErrorCtx(fc, allowed) as {
|
|
2415
|
-
allowed: string[]
|
|
2416
|
-
env: TEnv
|
|
2417
|
-
jsonFromError: (err: HoneyError) => Response
|
|
2418
|
-
req: Request
|
|
2289
|
+
allowed: string[]
|
|
2290
|
+
env: TEnv
|
|
2291
|
+
jsonFromError: (err: HoneyError) => Response
|
|
2292
|
+
req: Request
|
|
2419
2293
|
},
|
|
2420
|
-
)
|
|
2421
|
-
const responseHeaders = new Headers(res.headers)
|
|
2422
|
-
responseHeaders.set("allow", allowed.join(", "))
|
|
2294
|
+
)
|
|
2295
|
+
const responseHeaders = new Headers(res.headers)
|
|
2296
|
+
responseHeaders.set("allow", allowed.join(", "))
|
|
2423
2297
|
return new Response(res.body, {
|
|
2424
2298
|
headers: responseHeaders,
|
|
2425
2299
|
status: res.status,
|
|
2426
|
-
})
|
|
2300
|
+
})
|
|
2427
2301
|
}
|
|
2428
|
-
const err = this._createError(
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
)
|
|
2432
|
-
const res = this._makeErrorCtx(fc).jsonFromError(err);
|
|
2433
|
-
const responseHeaders = new Headers(res.headers);
|
|
2434
|
-
responseHeaders.set("allow", allowed.join(", "));
|
|
2302
|
+
const err = this._createError(EK.method_not_allowed, SK.method_not_allowed)
|
|
2303
|
+
const res = this._makeErrorCtx(fc).jsonFromError(err)
|
|
2304
|
+
const responseHeaders = new Headers(res.headers)
|
|
2305
|
+
responseHeaders.set("allow", allowed.join(", "))
|
|
2435
2306
|
return new Response(res.body, {
|
|
2436
2307
|
headers: responseHeaders,
|
|
2437
2308
|
status: res.status,
|
|
2438
|
-
})
|
|
2439
|
-
}
|
|
2309
|
+
})
|
|
2310
|
+
}
|
|
2440
2311
|
try {
|
|
2441
2312
|
const ctx405 = new HoneyContext({
|
|
2442
2313
|
env: fc.env,
|
|
@@ -2444,12 +2315,12 @@ export class Honey<
|
|
|
2444
2315
|
params: {},
|
|
2445
2316
|
req: fc.request,
|
|
2446
2317
|
urlFn: fc.url,
|
|
2447
|
-
})
|
|
2448
|
-
if (this._contextValues) Object.assign(ctx405, this._contextValues)
|
|
2318
|
+
})
|
|
2319
|
+
if (this._contextValues) Object.assign(ctx405, this._contextValues)
|
|
2449
2320
|
const finalRes =
|
|
2450
2321
|
this._chainMiddlewares.length > 0
|
|
2451
2322
|
? await executeChain(this._chainMiddlewares, ctx405, make405)
|
|
2452
|
-
: await make405()
|
|
2323
|
+
: await make405()
|
|
2453
2324
|
safeFire(
|
|
2454
2325
|
() =>
|
|
2455
2326
|
this._telemetry?.onResponse?.({
|
|
@@ -2458,10 +2329,10 @@ export class Honey<
|
|
|
2458
2329
|
status: finalRes.status,
|
|
2459
2330
|
}),
|
|
2460
2331
|
fc.log,
|
|
2461
|
-
)
|
|
2462
|
-
return finalRes
|
|
2332
|
+
)
|
|
2333
|
+
return finalRes
|
|
2463
2334
|
} catch (thrown) {
|
|
2464
|
-
return this._toErrorResponse(thrown)
|
|
2335
|
+
return this._toErrorResponse(thrown)
|
|
2465
2336
|
}
|
|
2466
2337
|
}
|
|
2467
2338
|
|
|
@@ -2473,38 +2344,29 @@ export class Honey<
|
|
|
2473
2344
|
params: Record<string, string>,
|
|
2474
2345
|
stashedMeta?: Record<string, unknown> | null,
|
|
2475
2346
|
): Response | Promise<Response> {
|
|
2476
|
-
const { env, executionCtx, log, request } = fc
|
|
2347
|
+
const { env, executionCtx, log, request } = fc
|
|
2477
2348
|
|
|
2478
2349
|
/* resolve error factory — pre-computed ef preferred, else build/use global */
|
|
2479
|
-
let errors: Record<string, (...args: never[]) => unknown> | undefined
|
|
2350
|
+
let errors: Record<string, (...args: never[]) => unknown> | undefined
|
|
2480
2351
|
if (handler.ef !== null) {
|
|
2481
|
-
errors = handler.ef
|
|
2352
|
+
errors = handler.ef
|
|
2482
2353
|
} else if (this._errorFactory !== null) {
|
|
2483
2354
|
if (handler.ek.size > 0) {
|
|
2484
|
-
const factory = this._errorFactory as Record<
|
|
2485
|
-
|
|
2486
|
-
(...args: never[]) => unknown
|
|
2487
|
-
>;
|
|
2488
|
-
const subset = Object.create(null) as Record<string, unknown>;
|
|
2355
|
+
const factory = this._errorFactory as Record<string, (...args: never[]) => unknown>
|
|
2356
|
+
const subset = Object.create(null) as Record<string, unknown>
|
|
2489
2357
|
for (const key of handler.ek) {
|
|
2490
2358
|
if (key in factory) {
|
|
2491
|
-
subset[key] = factory[key]
|
|
2359
|
+
subset[key] = factory[key]
|
|
2492
2360
|
}
|
|
2493
2361
|
}
|
|
2494
|
-
errors = Object.freeze(subset) as Record<
|
|
2495
|
-
string,
|
|
2496
|
-
(...args: never[]) => unknown
|
|
2497
|
-
>;
|
|
2362
|
+
errors = Object.freeze(subset) as Record<string, (...args: never[]) => unknown>
|
|
2498
2363
|
} else {
|
|
2499
|
-
errors = this._errorFactory as Record<
|
|
2500
|
-
string,
|
|
2501
|
-
(...args: never[]) => unknown
|
|
2502
|
-
>;
|
|
2364
|
+
errors = this._errorFactory as Record<string, (...args: never[]) => unknown>
|
|
2503
2365
|
}
|
|
2504
2366
|
}
|
|
2505
2367
|
|
|
2506
2368
|
/* meta: stashed meta from fn:null tree match takes priority over handler meta */
|
|
2507
|
-
const resolvedMeta = stashedMeta ?? handler.mt
|
|
2369
|
+
const resolvedMeta = stashedMeta ?? handler.mt
|
|
2508
2370
|
|
|
2509
2371
|
const ctx = new HoneyContext({
|
|
2510
2372
|
env,
|
|
@@ -2515,21 +2377,21 @@ export class Honey<
|
|
|
2515
2377
|
req: request,
|
|
2516
2378
|
routePattern: handler.rp,
|
|
2517
2379
|
urlFn: fc.url,
|
|
2518
|
-
})
|
|
2519
|
-
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2380
|
+
})
|
|
2381
|
+
if (this._contextValues) Object.assign(ctx, this._contextValues)
|
|
2520
2382
|
if (this._realtimeBus) {
|
|
2521
|
-
const rtBus = this._realtimeBus
|
|
2383
|
+
const rtBus = this._realtimeBus
|
|
2522
2384
|
Object.assign(ctx, {
|
|
2523
2385
|
realtime: { publish: (topic: string, data: unknown) => rtBus.publish(topic, data) },
|
|
2524
|
-
})
|
|
2386
|
+
})
|
|
2525
2387
|
}
|
|
2526
2388
|
if (errors) {
|
|
2527
|
-
ctx._setErrors(errors)
|
|
2389
|
+
ctx._setErrors(errors)
|
|
2528
2390
|
}
|
|
2529
2391
|
|
|
2530
2392
|
if (this._telemetry !== null) {
|
|
2531
2393
|
try {
|
|
2532
|
-
this._telemetry.onRoute?.({ method, params, path, req: request })
|
|
2394
|
+
this._telemetry.onRoute?.({ method, params, path, req: request })
|
|
2533
2395
|
} catch {
|
|
2534
2396
|
/* telemetry must not crash request */
|
|
2535
2397
|
}
|
|
@@ -2542,69 +2404,65 @@ export class Honey<
|
|
|
2542
2404
|
* scoped middleware is in play (scoped mw cannot be baked into the compiled cache
|
|
2543
2405
|
* because each route may match a different subset).
|
|
2544
2406
|
*/
|
|
2545
|
-
const hasTelemetryMw = this._telemetry?.onMiddleware !== undefined
|
|
2546
|
-
const hasInputValidation = handler.iv !== null
|
|
2407
|
+
const hasTelemetryMw = this._telemetry?.onMiddleware !== undefined
|
|
2408
|
+
const hasInputValidation = handler.iv !== null
|
|
2547
2409
|
|
|
2548
2410
|
/*
|
|
2549
2411
|
* Error resolver — stored on ctx so the cached handler wrapper can read it.
|
|
2550
2412
|
* Converts handler errors into error Responses inside the middleware chain,
|
|
2551
2413
|
* allowing all post-next() middleware code (headers, logging, timing) to run.
|
|
2552
2414
|
*/
|
|
2553
|
-
ctx._errorToResponse = (thrown: unknown) =>
|
|
2554
|
-
this._resolveErrorResponse(thrown, handler, fc, method, path, ctx);
|
|
2415
|
+
ctx._errorToResponse = (thrown: unknown) => this._resolveErrorResponse(thrown, handler, fc, method, path, ctx)
|
|
2555
2416
|
|
|
2556
2417
|
const onError = (thrown: unknown): Response | Promise<Response> => {
|
|
2557
|
-
if (ctx._errorToResponse) return ctx._errorToResponse(thrown)
|
|
2558
|
-
return this._toErrorResponse(thrown)
|
|
2559
|
-
}
|
|
2418
|
+
if (ctx._errorToResponse) return ctx._errorToResponse(thrown)
|
|
2419
|
+
return this._toErrorResponse(thrown)
|
|
2420
|
+
}
|
|
2560
2421
|
|
|
2561
2422
|
const after = (response: Response): Response | Promise<Response> => {
|
|
2562
2423
|
try {
|
|
2563
|
-
const done = this._afterMatched(fc, method, path, handler, ctx, response)
|
|
2564
|
-
if (done instanceof Promise) return done.catch(onError)
|
|
2565
|
-
return done
|
|
2424
|
+
const done = this._afterMatched(fc, method, path, handler, ctx, response)
|
|
2425
|
+
if (done instanceof Promise) return done.catch(onError)
|
|
2426
|
+
return done
|
|
2566
2427
|
} catch (thrown) {
|
|
2567
|
-
return onError(thrown)
|
|
2428
|
+
return onError(thrown)
|
|
2568
2429
|
}
|
|
2569
|
-
}
|
|
2430
|
+
}
|
|
2570
2431
|
|
|
2571
2432
|
try {
|
|
2572
2433
|
if (!hasTelemetryMw && !hasInputValidation && this._scopedMiddlewares.length === 0) {
|
|
2573
2434
|
if (!handler._compiled) {
|
|
2574
|
-
const chainMw = this._chainMiddlewares
|
|
2575
|
-
const handlerHasChain =
|
|
2576
|
-
chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw);
|
|
2435
|
+
const chainMw = this._chainMiddlewares
|
|
2436
|
+
const handlerHasChain = chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw)
|
|
2577
2437
|
const allMw = handlerHasChain
|
|
2578
2438
|
? [...this._globalMiddlewares, ...handler.mw]
|
|
2579
|
-
: [...this._globalMiddlewares, ...chainMw, ...handler.mw]
|
|
2439
|
+
: [...this._globalMiddlewares, ...chainMw, ...handler.mw]
|
|
2580
2440
|
handler._compiled = compileChain(allMw, (c) => {
|
|
2581
2441
|
try {
|
|
2582
|
-
const result = handler.fn(c)
|
|
2442
|
+
const result = handler.fn(c)
|
|
2583
2443
|
if (result instanceof Promise) {
|
|
2584
2444
|
return result.catch((thrown: unknown) => {
|
|
2585
|
-
const hCtx = c as HoneyContext<TEnv
|
|
2586
|
-
if (hCtx._errorToResponse)
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
});
|
|
2445
|
+
const hCtx = c as HoneyContext<TEnv>
|
|
2446
|
+
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2447
|
+
throw thrown
|
|
2448
|
+
})
|
|
2590
2449
|
}
|
|
2591
|
-
return result
|
|
2450
|
+
return result
|
|
2592
2451
|
} catch (thrown) {
|
|
2593
|
-
const hCtx = c as HoneyContext<TEnv
|
|
2594
|
-
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2595
|
-
throw thrown
|
|
2452
|
+
const hCtx = c as HoneyContext<TEnv>
|
|
2453
|
+
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2454
|
+
throw thrown
|
|
2596
2455
|
}
|
|
2597
|
-
})
|
|
2456
|
+
})
|
|
2598
2457
|
}
|
|
2599
|
-
const result = handler._compiled(ctx)
|
|
2600
|
-
if (result instanceof Promise) return result.then(after, onError)
|
|
2601
|
-
return after(result)
|
|
2458
|
+
const result = handler._compiled(ctx)
|
|
2459
|
+
if (result instanceof Promise) return result.then(after, onError)
|
|
2460
|
+
return after(result)
|
|
2602
2461
|
}
|
|
2603
2462
|
|
|
2604
|
-
const scopedForPath = this._filterScopedForPath(handler.rp)
|
|
2605
|
-
const chainMw = this._chainMiddlewares
|
|
2606
|
-
const handlerHasChain =
|
|
2607
|
-
chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw);
|
|
2463
|
+
const scopedForPath = this._filterScopedForPath(handler.rp)
|
|
2464
|
+
const chainMw = this._chainMiddlewares
|
|
2465
|
+
const handlerHasChain = chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw)
|
|
2608
2466
|
/*
|
|
2609
2467
|
* Ordering: [global → chain → scoped → handler-route-specific]
|
|
2610
2468
|
* When handlerHasChain, handler.mw = [chain..., routeSpecific...].
|
|
@@ -2617,22 +2475,19 @@ export class Honey<
|
|
|
2617
2475
|
...scopedForPath,
|
|
2618
2476
|
...handler.mw.slice(chainMw.length),
|
|
2619
2477
|
]
|
|
2620
|
-
: [...this._globalMiddlewares, ...chainMw, ...scopedForPath, ...handler.mw]
|
|
2478
|
+
: [...this._globalMiddlewares, ...chainMw, ...scopedForPath, ...handler.mw]
|
|
2621
2479
|
|
|
2622
2480
|
if (hasTelemetryMw) {
|
|
2623
|
-
const onMw = this._telemetry?.onMiddleware
|
|
2481
|
+
const onMw = this._telemetry?.onMiddleware
|
|
2624
2482
|
if (onMw) {
|
|
2625
2483
|
allMiddlewares = allMiddlewares.map((mw) => {
|
|
2626
|
-
const name = mw.name || "anonymous"
|
|
2484
|
+
const name = mw.name || "anonymous"
|
|
2627
2485
|
const wrapped: RuntimeMiddleware = async (wCtx, wNext) => {
|
|
2628
|
-
const mwStart = performance.now()
|
|
2486
|
+
const mwStart = performance.now()
|
|
2629
2487
|
try {
|
|
2630
|
-
const res = await mw(wCtx, wNext)
|
|
2631
|
-
safeFire(
|
|
2632
|
-
|
|
2633
|
-
log,
|
|
2634
|
-
);
|
|
2635
|
-
return res;
|
|
2488
|
+
const res = await mw(wCtx, wNext)
|
|
2489
|
+
safeFire(() => onMw({ duration: performance.now() - mwStart, name }), log)
|
|
2490
|
+
return res
|
|
2636
2491
|
} catch (error) {
|
|
2637
2492
|
safeFire(
|
|
2638
2493
|
() =>
|
|
@@ -2642,50 +2497,46 @@ export class Honey<
|
|
|
2642
2497
|
name,
|
|
2643
2498
|
}),
|
|
2644
2499
|
log,
|
|
2645
|
-
)
|
|
2646
|
-
throw error
|
|
2500
|
+
)
|
|
2501
|
+
throw error
|
|
2647
2502
|
}
|
|
2648
|
-
}
|
|
2649
|
-
return wrapped
|
|
2650
|
-
})
|
|
2503
|
+
}
|
|
2504
|
+
return wrapped
|
|
2505
|
+
})
|
|
2651
2506
|
}
|
|
2652
2507
|
}
|
|
2653
2508
|
|
|
2654
2509
|
if (hasInputValidation) {
|
|
2655
|
-
const schemas = handler.iv
|
|
2510
|
+
const schemas = handler.iv
|
|
2656
2511
|
if (schemas) {
|
|
2657
2512
|
const inputMw: RuntimeMiddleware = async (inputCtx, inputNext) => {
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
);
|
|
2663
|
-
return inputNext({ input: validated });
|
|
2664
|
-
};
|
|
2665
|
-
allMiddlewares.push(inputMw);
|
|
2513
|
+
const validated = await validateInput(schemas, inputCtx["req"] as Request, params)
|
|
2514
|
+
return inputNext({ input: validated })
|
|
2515
|
+
}
|
|
2516
|
+
allMiddlewares.push(inputMw)
|
|
2666
2517
|
}
|
|
2667
2518
|
}
|
|
2668
2519
|
|
|
2669
2520
|
return executeChain(allMiddlewares, ctx, (finalCtx) => {
|
|
2670
2521
|
try {
|
|
2671
|
-
const result = handler.fn(finalCtx)
|
|
2522
|
+
const result = handler.fn(finalCtx)
|
|
2672
2523
|
if (result instanceof Promise) {
|
|
2673
2524
|
return result.catch((thrown: unknown) => {
|
|
2674
|
-
const hCtx = finalCtx as HoneyContext<TEnv
|
|
2675
|
-
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2676
|
-
throw thrown
|
|
2677
|
-
})
|
|
2525
|
+
const hCtx = finalCtx as HoneyContext<TEnv>
|
|
2526
|
+
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2527
|
+
throw thrown
|
|
2528
|
+
})
|
|
2678
2529
|
}
|
|
2679
|
-
return result
|
|
2530
|
+
return result
|
|
2680
2531
|
} catch (thrown) {
|
|
2681
|
-
const hCtx = finalCtx as HoneyContext<TEnv
|
|
2682
|
-
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2683
|
-
throw thrown
|
|
2532
|
+
const hCtx = finalCtx as HoneyContext<TEnv>
|
|
2533
|
+
if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
|
|
2534
|
+
throw thrown
|
|
2684
2535
|
}
|
|
2685
|
-
}).then(after, onError)
|
|
2536
|
+
}).then(after, onError)
|
|
2686
2537
|
} catch (thrown) {
|
|
2687
2538
|
/* safety net — middleware-level errors (input validation, middleware crash) */
|
|
2688
|
-
return onError(thrown)
|
|
2539
|
+
return onError(thrown)
|
|
2689
2540
|
}
|
|
2690
2541
|
}
|
|
2691
2542
|
|
|
@@ -2700,17 +2551,11 @@ export class Honey<
|
|
|
2700
2551
|
const validateOut =
|
|
2701
2552
|
this._outputValidation === "always" ||
|
|
2702
2553
|
(this._outputValidation === "dev" &&
|
|
2703
|
-
(globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
handler.os &&
|
|
2707
|
-
validateOut &&
|
|
2708
|
-
response.body !== null &&
|
|
2709
|
-
!ctx._isErrorResponse
|
|
2710
|
-
) {
|
|
2711
|
-
return this._validateThenFinish(fc, method, path, handler, ctx, response);
|
|
2554
|
+
(globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV !== "production")
|
|
2555
|
+
if (handler.os && validateOut && response.body !== null && !ctx._isErrorResponse) {
|
|
2556
|
+
return this._validateThenFinish(fc, method, path, handler, ctx, response)
|
|
2712
2557
|
}
|
|
2713
|
-
return this._finishMatched(fc, method, path, handler, ctx, response)
|
|
2558
|
+
return this._finishMatched(fc, method, path, handler, ctx, response)
|
|
2714
2559
|
}
|
|
2715
2560
|
|
|
2716
2561
|
private async _validateThenFinish(
|
|
@@ -2721,31 +2566,31 @@ export class Honey<
|
|
|
2721
2566
|
ctx: HoneyContext<TEnv>,
|
|
2722
2567
|
response: Response,
|
|
2723
2568
|
): Promise<Response> {
|
|
2724
|
-
const ct = response.headers.get("content-type")
|
|
2569
|
+
const ct = response.headers.get("content-type")
|
|
2725
2570
|
|
|
2726
2571
|
/* content-type mismatch check */
|
|
2727
2572
|
if (ct && handler.os) {
|
|
2728
|
-
const declaredTypes = Object.keys(handler.os)
|
|
2729
|
-
const matches = declaredTypes.some((t) => ct.startsWith(t))
|
|
2573
|
+
const declaredTypes = Object.keys(handler.os)
|
|
2574
|
+
const matches = declaredTypes.some((t) => ct.startsWith(t))
|
|
2730
2575
|
if (!matches) {
|
|
2731
2576
|
throw new HoneyError({
|
|
2732
2577
|
errorKey: EK.output_content_type_mismatch,
|
|
2733
2578
|
status: SK.internal_server_error,
|
|
2734
|
-
})
|
|
2579
|
+
})
|
|
2735
2580
|
}
|
|
2736
2581
|
}
|
|
2737
2582
|
|
|
2738
2583
|
/* JSON schema validation — read original, return clone (Bun clone() drains original) */
|
|
2739
2584
|
if (ct?.startsWith("application/json") && handler.ov) {
|
|
2740
|
-
const sk = codeToStatusKey[response.status]
|
|
2585
|
+
const sk = codeToStatusKey[response.status]
|
|
2741
2586
|
if (sk) {
|
|
2742
|
-
const forReturn = response.clone()
|
|
2743
|
-
const data: unknown = await response.json()
|
|
2744
|
-
await handler.ov(sk, data)
|
|
2745
|
-
response = forReturn
|
|
2587
|
+
const forReturn = response.clone()
|
|
2588
|
+
const data: unknown = await response.json()
|
|
2589
|
+
await handler.ov(sk, data)
|
|
2590
|
+
response = forReturn
|
|
2746
2591
|
}
|
|
2747
2592
|
}
|
|
2748
|
-
return this._finishMatched(fc, method, path, handler, ctx, response)
|
|
2593
|
+
return this._finishMatched(fc, method, path, handler, ctx, response)
|
|
2749
2594
|
}
|
|
2750
2595
|
|
|
2751
2596
|
private _finishMatched(
|
|
@@ -2758,19 +2603,19 @@ export class Honey<
|
|
|
2758
2603
|
): Response {
|
|
2759
2604
|
/* taps — fire after successful handler, non-blocking */
|
|
2760
2605
|
if (this._taps !== null && !ctx._isErrorResponse) {
|
|
2761
|
-
const taps = this._taps
|
|
2762
|
-
const log = fc.log
|
|
2606
|
+
const taps = this._taps
|
|
2607
|
+
const log = fc.log
|
|
2763
2608
|
|
|
2764
2609
|
/* meta-driven taps — fire for each registered key found in route meta */
|
|
2765
2610
|
if (handler.mt !== null) {
|
|
2766
2611
|
for (const [key, tapFn] of taps) {
|
|
2767
|
-
const metaValue = handler.mt[key]
|
|
2612
|
+
const metaValue = handler.mt[key]
|
|
2768
2613
|
if (metaValue !== undefined) {
|
|
2769
2614
|
ctx.background(
|
|
2770
2615
|
Promise.resolve()
|
|
2771
2616
|
.then(() => tapFn(ctx, metaValue))
|
|
2772
2617
|
.catch((e) => log?.warn?.("tap failed", key, e)),
|
|
2773
|
-
)
|
|
2618
|
+
)
|
|
2774
2619
|
}
|
|
2775
2620
|
}
|
|
2776
2621
|
}
|
|
@@ -2778,33 +2623,33 @@ export class Honey<
|
|
|
2778
2623
|
/* dynamic taps — fire for each c.tap() call */
|
|
2779
2624
|
if (ctx._pendingTaps !== null) {
|
|
2780
2625
|
for (const pending of ctx._pendingTaps) {
|
|
2781
|
-
const tapFn = taps.get(pending.key)
|
|
2626
|
+
const tapFn = taps.get(pending.key)
|
|
2782
2627
|
if (tapFn !== undefined) {
|
|
2783
2628
|
ctx.background(
|
|
2784
2629
|
Promise.resolve()
|
|
2785
2630
|
.then(() => tapFn(ctx, pending.payload))
|
|
2786
2631
|
.catch((e) => log?.warn?.("tap failed", pending.key, e)),
|
|
2787
|
-
)
|
|
2632
|
+
)
|
|
2788
2633
|
}
|
|
2789
2634
|
}
|
|
2790
|
-
ctx._pendingTaps = null
|
|
2635
|
+
ctx._pendingTaps = null
|
|
2791
2636
|
}
|
|
2792
2637
|
}
|
|
2793
2638
|
|
|
2794
2639
|
if (this._telemetry !== null) {
|
|
2795
2640
|
try {
|
|
2796
|
-
const duration = performance.now() - fc.startTime
|
|
2641
|
+
const duration = performance.now() - fc.startTime
|
|
2797
2642
|
this._telemetry.onHandler?.({
|
|
2798
2643
|
duration,
|
|
2799
2644
|
method,
|
|
2800
2645
|
path,
|
|
2801
2646
|
status: response.status,
|
|
2802
|
-
})
|
|
2647
|
+
})
|
|
2803
2648
|
this._telemetry.onResponse?.({
|
|
2804
2649
|
duration,
|
|
2805
2650
|
req: fc.request,
|
|
2806
2651
|
status: response.status,
|
|
2807
|
-
})
|
|
2652
|
+
})
|
|
2808
2653
|
} catch {
|
|
2809
2654
|
/* telemetry must never crash the response path */
|
|
2810
2655
|
}
|
|
@@ -2814,9 +2659,9 @@ export class Honey<
|
|
|
2814
2659
|
return new Response(null, {
|
|
2815
2660
|
headers: response.headers,
|
|
2816
2661
|
status: response.status,
|
|
2817
|
-
})
|
|
2662
|
+
})
|
|
2818
2663
|
}
|
|
2819
|
-
return response
|
|
2664
|
+
return response
|
|
2820
2665
|
}
|
|
2821
2666
|
}
|
|
2822
2667
|
|
|
@@ -2832,19 +2677,9 @@ export class Honey<
|
|
|
2832
2677
|
* Narrowed methods return TypedResponse<CT, K> for compile-time CT+SK safety.
|
|
2833
2678
|
*/
|
|
2834
2679
|
/** Resolve body type: schema → InferOutput, plain type → use as-is */
|
|
2835
|
-
type ResolveBody<T, TBody> = TBody extends "infer"
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
: T
|
|
2839
|
-
: TBody;
|
|
2840
|
-
|
|
2841
|
-
type NarrowMethod<
|
|
2842
|
-
TRes,
|
|
2843
|
-
TSchemas,
|
|
2844
|
-
Method extends string,
|
|
2845
|
-
CT extends string,
|
|
2846
|
-
TBody,
|
|
2847
|
-
> = [TSchemas] extends [never]
|
|
2680
|
+
type ResolveBody<T, TBody> = TBody extends "infer" ? (T extends StandardSchemaLike ? InferOutput<T> : T) : TBody
|
|
2681
|
+
|
|
2682
|
+
type NarrowMethod<TRes, TSchemas, Method extends string, CT extends string, TBody> = [TSchemas] extends [never]
|
|
2848
2683
|
? Omit<TRes, Method>
|
|
2849
2684
|
: TSchemas extends Record<string, unknown>
|
|
2850
2685
|
? Omit<TRes, Method> & {
|
|
@@ -2852,22 +2687,18 @@ type NarrowMethod<
|
|
|
2852
2687
|
statusKey: K,
|
|
2853
2688
|
body: ResolveBody<TSchemas[K], TBody>,
|
|
2854
2689
|
opts?: ResponseOptions,
|
|
2855
|
-
) => TypedResponse<CT, K
|
|
2690
|
+
) => TypedResponse<CT, K>
|
|
2856
2691
|
}
|
|
2857
|
-
: Omit<TRes, Method
|
|
2692
|
+
: Omit<TRes, Method>
|
|
2858
2693
|
|
|
2859
2694
|
/**
|
|
2860
2695
|
* SSE uses a callback signature, not statusKey+body like other methods.
|
|
2861
2696
|
* Gate presence on text/event-stream declaration, preserve original signature.
|
|
2862
2697
|
*/
|
|
2863
|
-
type NarrowSSE<TOutput> = [
|
|
2864
|
-
ExtractSchemas<TOutput, "text/event-stream">,
|
|
2865
|
-
] extends [never]
|
|
2866
|
-
? {}
|
|
2867
|
-
: { sse: HoneyRes["sse"] };
|
|
2698
|
+
type NarrowSSE<TOutput> = [ExtractSchemas<TOutput, "text/event-stream">] extends [never] ? {} : { sse: HoneyRes["sse"] }
|
|
2868
2699
|
|
|
2869
2700
|
/** Universal methods — always available, not gated by output declaration */
|
|
2870
|
-
type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream"
|
|
2701
|
+
type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream">
|
|
2871
2702
|
|
|
2872
2703
|
/**
|
|
2873
2704
|
* Constrain ctx.res when output schemas are declared.
|
|
@@ -2880,41 +2711,11 @@ type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
|
|
|
2880
2711
|
? TCtx
|
|
2881
2712
|
: Omit<TCtx, "res"> & {
|
|
2882
2713
|
readonly res: UniversalRes &
|
|
2883
|
-
NarrowMethod<
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
"infer"
|
|
2889
|
-
> &
|
|
2890
|
-
NarrowMethod<
|
|
2891
|
-
{},
|
|
2892
|
-
ExtractSchemas<TOutput, "text/plain">,
|
|
2893
|
-
"text",
|
|
2894
|
-
"text/plain",
|
|
2895
|
-
string
|
|
2896
|
-
> &
|
|
2897
|
-
NarrowMethod<
|
|
2898
|
-
{},
|
|
2899
|
-
ExtractSchemas<TOutput, "text/html">,
|
|
2900
|
-
"html",
|
|
2901
|
-
"text/html",
|
|
2902
|
-
string
|
|
2903
|
-
> &
|
|
2904
|
-
NarrowMethod<
|
|
2905
|
-
{},
|
|
2906
|
-
ExtractSchemas<TOutput, "application/xml">,
|
|
2907
|
-
"xml",
|
|
2908
|
-
"application/xml",
|
|
2909
|
-
string
|
|
2910
|
-
> &
|
|
2911
|
-
NarrowMethod<
|
|
2912
|
-
{},
|
|
2913
|
-
ExtractSchemas<TOutput, "text/csv">,
|
|
2914
|
-
"csv",
|
|
2915
|
-
"text/csv",
|
|
2916
|
-
string
|
|
2917
|
-
> &
|
|
2714
|
+
NarrowMethod<{}, ExtractSchemas<TOutput, "application/json">, "json", "application/json", "infer"> &
|
|
2715
|
+
NarrowMethod<{}, ExtractSchemas<TOutput, "text/plain">, "text", "text/plain", string> &
|
|
2716
|
+
NarrowMethod<{}, ExtractSchemas<TOutput, "text/html">, "html", "text/html", string> &
|
|
2717
|
+
NarrowMethod<{}, ExtractSchemas<TOutput, "application/xml">, "xml", "application/xml", string> &
|
|
2718
|
+
NarrowMethod<{}, ExtractSchemas<TOutput, "text/csv">, "csv", "text/csv", string> &
|
|
2918
2719
|
NarrowMethod<
|
|
2919
2720
|
{},
|
|
2920
2721
|
ExtractSchemas<TOutput, "application/octet-stream">,
|
|
@@ -2922,11 +2723,11 @@ type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
|
|
|
2922
2723
|
"application/octet-stream",
|
|
2923
2724
|
ArrayBuffer | Uint8Array<ArrayBuffer>
|
|
2924
2725
|
> &
|
|
2925
|
-
NarrowSSE<TOutput
|
|
2926
|
-
}
|
|
2726
|
+
NarrowSSE<TOutput>
|
|
2727
|
+
}
|
|
2927
2728
|
|
|
2928
2729
|
/** Public alias for codegen — applies output schema constraints to a context type */
|
|
2929
|
-
export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput
|
|
2730
|
+
export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput>
|
|
2930
2731
|
|
|
2931
2732
|
/** @internal — tuple entry describing a scoped middleware at the type level */
|
|
2932
2733
|
type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
|
|
@@ -2939,10 +2740,7 @@ type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
|
|
|
2939
2740
|
* the user passes a widened variable), skip the entry via `string extends Head["path"] ? {}`.
|
|
2940
2741
|
* This prevents the always-true `'/anywhere' extends string` from polluting every route.
|
|
2941
2742
|
*/
|
|
2942
|
-
type ApplyScoped<
|
|
2943
|
-
TScopedMw extends readonly ScopedMwEntry[],
|
|
2944
|
-
TFullPath extends string,
|
|
2945
|
-
> = TScopedMw extends readonly [
|
|
2743
|
+
type ApplyScoped<TScopedMw extends readonly ScopedMwEntry[], TFullPath extends string> = TScopedMw extends readonly [
|
|
2946
2744
|
infer Head extends ScopedMwEntry,
|
|
2947
2745
|
...infer Rest extends readonly ScopedMwEntry[],
|
|
2948
2746
|
]
|
|
@@ -2953,7 +2751,7 @@ type ApplyScoped<
|
|
|
2953
2751
|
: TFullPath extends `${Head["path"] & string}/${string}`
|
|
2954
2752
|
? Head["adds"]
|
|
2955
2753
|
: {}) &
|
|
2956
|
-
|
|
2754
|
+
ApplyScoped<Rest, TFullPath>
|
|
2957
2755
|
: {}
|
|
2958
2756
|
|
|
2959
2757
|
/** @internal — runtime entry for a scoped middleware */
|
|
@@ -2970,13 +2768,12 @@ type ApplyParams<TCtx, TParams> = [keyof TParams] extends [string]
|
|
|
2970
2768
|
? string extends keyof TParams
|
|
2971
2769
|
? TCtx
|
|
2972
2770
|
: TCtx & { readonly params: TParams }
|
|
2973
|
-
: TCtx
|
|
2771
|
+
: TCtx
|
|
2974
2772
|
|
|
2975
2773
|
/** Typed tap method — constrained to registered tap keys when TTaps is non-empty */
|
|
2976
|
-
type TypedTap<TTaps extends Record<string, unknown>> =
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
: { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void };
|
|
2774
|
+
type TypedTap<TTaps extends Record<string, unknown>> = [keyof TTaps] extends [never]
|
|
2775
|
+
? { tap(key: string, payload: unknown): void }
|
|
2776
|
+
: { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void }
|
|
2980
2777
|
|
|
2981
2778
|
/** Build the full handler context: base ctx + params + input + meta + errors + taps + output-constrained methods */
|
|
2982
2779
|
type HandlerCtx<
|
|
@@ -2990,48 +2787,56 @@ type HandlerCtx<
|
|
|
2990
2787
|
TPath extends string = string,
|
|
2991
2788
|
TTaps extends Record<string, unknown> = {},
|
|
2992
2789
|
> = ApplyOutput<
|
|
2993
|
-
ApplyParams<
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
> & {
|
|
2997
|
-
readonly meta: Readonly<Omit<TAccMeta, "openApi">>;
|
|
2998
|
-
readonly routePattern: TPath;
|
|
2790
|
+
ApplyParams<[keyof TInput] extends [never] ? TCtx : TCtx & { input: TInput }, TParams> & {
|
|
2791
|
+
readonly meta: Readonly<Omit<TAccMeta, "openApi">>
|
|
2792
|
+
readonly routePattern: TPath
|
|
2999
2793
|
} & ([TErrorFactory] extends [never]
|
|
3000
2794
|
? {}
|
|
3001
2795
|
: [TErrorKeys] extends [never]
|
|
3002
2796
|
? { readonly errors: TErrorFactory }
|
|
3003
2797
|
: {
|
|
3004
|
-
readonly errors: Pick<
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
>;
|
|
3008
|
-
})
|
|
3009
|
-
& TypedTap<TTaps>,
|
|
2798
|
+
readonly errors: Pick<TErrorFactory, TErrorKeys & keyof TErrorFactory>
|
|
2799
|
+
}) &
|
|
2800
|
+
TypedTap<TTaps>,
|
|
3010
2801
|
TOutput
|
|
3011
|
-
|
|
2802
|
+
>
|
|
3012
2803
|
|
|
3013
2804
|
/** @internal — exposes private Honey members for RouteBuilder/WSRouteBuilder access */
|
|
3014
2805
|
type HoneyInternal = {
|
|
3015
|
-
_factory: unknown
|
|
3016
|
-
_markWsRoutes(): void
|
|
3017
|
-
_registerStatic(key: string, handler: RouteHandler): void
|
|
3018
|
-
}
|
|
2806
|
+
_factory: unknown
|
|
2807
|
+
_markWsRoutes(): void
|
|
2808
|
+
_registerStatic(key: string, handler: RouteHandler): void
|
|
2809
|
+
}
|
|
3019
2810
|
|
|
3020
2811
|
type RouteBuilderState<TParent> = {
|
|
3021
|
-
boundaryErrorKey: string | null
|
|
3022
|
-
errorKeys: Set<string
|
|
3023
|
-
extraMethods: (HttpMethod | "ALL")[] | null
|
|
3024
|
-
handlerMap: Record<string, RouteHandler> | null
|
|
3025
|
-
inputSchemas: InputSchemasDef | null
|
|
3026
|
-
meta: Record<string, unknown> | null
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
2812
|
+
boundaryErrorKey: string | null
|
|
2813
|
+
errorKeys: Set<string>
|
|
2814
|
+
extraMethods: (HttpMethod | "ALL")[] | null
|
|
2815
|
+
handlerMap: Record<string, RouteHandler> | null
|
|
2816
|
+
inputSchemas: InputSchemasDef | null
|
|
2817
|
+
meta: Record<string, unknown> | null
|
|
2818
|
+
/** meta contributed by middleware — kept apart so explicit .meta() always outranks it */
|
|
2819
|
+
mwMeta: Record<string, unknown> | null
|
|
2820
|
+
method: HttpMethod | "ALL"
|
|
2821
|
+
middlewares: RuntimeMiddleware[]
|
|
2822
|
+
outputSchemas: OutputSchemaDef | null
|
|
2823
|
+
parent: TParent
|
|
2824
|
+
parentMiddlewares: RuntimeMiddleware[]
|
|
2825
|
+
path: string
|
|
2826
|
+
root: TreeNode
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
/**
|
|
2830
|
+
* Route meta = middleware-contributed meta, overlaid by everything stated explicitly
|
|
2831
|
+
* (chain `.meta()`, then route `.meta()`). Explicit always wins.
|
|
2832
|
+
*/
|
|
2833
|
+
function mergeContributedMeta(
|
|
2834
|
+
contributed: Record<string, unknown> | null | undefined,
|
|
2835
|
+
explicit: Record<string, unknown> | null,
|
|
2836
|
+
): Record<string, unknown> | null {
|
|
2837
|
+
if (!contributed) return explicit ? Object.freeze(explicit) : null
|
|
2838
|
+
return Object.freeze(explicit ? { ...contributed, ...explicit } : { ...contributed })
|
|
2839
|
+
}
|
|
3035
2840
|
|
|
3036
2841
|
/** Honey.TMeta defaults to never; never & X is never, so start acc meta at {}. */
|
|
3037
2842
|
type InitAccMeta<TMeta> = [TMeta] extends [never] ? {} : TMeta
|
|
@@ -3093,9 +2898,9 @@ type HandlerReturn<
|
|
|
3093
2898
|
TBasePath,
|
|
3094
2899
|
TTaps,
|
|
3095
2900
|
TScopedMw
|
|
3096
|
-
|
|
2901
|
+
>
|
|
3097
2902
|
|
|
3098
|
-
type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output"
|
|
2903
|
+
type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output"
|
|
3099
2904
|
|
|
3100
2905
|
type BuilderChain<
|
|
3101
2906
|
TEnv,
|
|
@@ -3117,12 +2922,26 @@ type BuilderChain<
|
|
|
3117
2922
|
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
3118
2923
|
> = Omit<
|
|
3119
2924
|
RouteBuilder<
|
|
3120
|
-
TEnv,
|
|
3121
|
-
|
|
3122
|
-
|
|
2925
|
+
TEnv,
|
|
2926
|
+
TCtx,
|
|
2927
|
+
TInput,
|
|
2928
|
+
TErrorKeys,
|
|
2929
|
+
TOutput,
|
|
2930
|
+
TPath,
|
|
2931
|
+
TMethod,
|
|
2932
|
+
TRoutes,
|
|
2933
|
+
TUsed,
|
|
2934
|
+
TBaseCtx,
|
|
2935
|
+
TMeta,
|
|
2936
|
+
TAccMeta,
|
|
2937
|
+
TErrorFactory,
|
|
2938
|
+
TDefaultErrors,
|
|
2939
|
+
TBasePath,
|
|
2940
|
+
TTaps,
|
|
2941
|
+
TScopedMw
|
|
3123
2942
|
>,
|
|
3124
2943
|
TUsed & OneShotKey
|
|
3125
|
-
|
|
2944
|
+
>
|
|
3126
2945
|
|
|
3127
2946
|
class RouteBuilder<
|
|
3128
2947
|
TEnv,
|
|
@@ -3144,35 +2963,15 @@ class RouteBuilder<
|
|
|
3144
2963
|
TScopedMw extends readonly ScopedMwEntry[] = [],
|
|
3145
2964
|
> {
|
|
3146
2965
|
private _s: RouteBuilderState<
|
|
3147
|
-
Honey<
|
|
3148
|
-
|
|
3149
|
-
TBaseCtx,
|
|
3150
|
-
TRoutes,
|
|
3151
|
-
TMeta,
|
|
3152
|
-
TErrorFactory,
|
|
3153
|
-
TDefaultErrors,
|
|
3154
|
-
TBasePath,
|
|
3155
|
-
TTaps,
|
|
3156
|
-
TScopedMw
|
|
3157
|
-
>
|
|
3158
|
-
>;
|
|
2966
|
+
Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
2967
|
+
>
|
|
3159
2968
|
|
|
3160
2969
|
constructor(
|
|
3161
2970
|
state: RouteBuilderState<
|
|
3162
|
-
Honey<
|
|
3163
|
-
TEnv,
|
|
3164
|
-
TBaseCtx,
|
|
3165
|
-
TRoutes,
|
|
3166
|
-
TMeta,
|
|
3167
|
-
TErrorFactory,
|
|
3168
|
-
TDefaultErrors,
|
|
3169
|
-
TBasePath,
|
|
3170
|
-
TTaps,
|
|
3171
|
-
TScopedMw
|
|
3172
|
-
>
|
|
2971
|
+
Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
|
|
3173
2972
|
>,
|
|
3174
2973
|
) {
|
|
3175
|
-
this._s = state
|
|
2974
|
+
this._s = state
|
|
3176
2975
|
}
|
|
3177
2976
|
|
|
3178
2977
|
errors<
|
|
@@ -3199,11 +2998,9 @@ class RouteBuilder<
|
|
|
3199
2998
|
TBasePath,
|
|
3200
2999
|
TTaps,
|
|
3201
3000
|
TScopedMw
|
|
3202
|
-
|
|
3001
|
+
>
|
|
3203
3002
|
errors<
|
|
3204
|
-
TKeys extends [TErrorFactory] extends [never]
|
|
3205
|
-
? never
|
|
3206
|
-
: Exclude<keyof TErrorFactory & string, TDefaultErrors>,
|
|
3003
|
+
TKeys extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
|
|
3207
3004
|
>(
|
|
3208
3005
|
...keys: TKeys[]
|
|
3209
3006
|
): BuilderChain<
|
|
@@ -3224,7 +3021,7 @@ class RouteBuilder<
|
|
|
3224
3021
|
TBasePath,
|
|
3225
3022
|
TTaps,
|
|
3226
3023
|
TScopedMw
|
|
3227
|
-
|
|
3024
|
+
>
|
|
3228
3025
|
errors(
|
|
3229
3026
|
...args: unknown[]
|
|
3230
3027
|
): BuilderChain<
|
|
@@ -3246,12 +3043,9 @@ class RouteBuilder<
|
|
|
3246
3043
|
TTaps,
|
|
3247
3044
|
TScopedMw
|
|
3248
3045
|
> {
|
|
3249
|
-
const keys =
|
|
3250
|
-
typeof args[0] === "object" && args[0] !== null
|
|
3251
|
-
? (args.slice(1) as string[])
|
|
3252
|
-
: (args as string[]);
|
|
3046
|
+
const keys = typeof args[0] === "object" && args[0] !== null ? (args.slice(1) as string[]) : (args as string[])
|
|
3253
3047
|
for (const k of keys) {
|
|
3254
|
-
this._s.errorKeys.add(k)
|
|
3048
|
+
this._s.errorKeys.add(k)
|
|
3255
3049
|
}
|
|
3256
3050
|
return new RouteBuilder<
|
|
3257
3051
|
TEnv,
|
|
@@ -3274,13 +3068,11 @@ class RouteBuilder<
|
|
|
3274
3068
|
>({
|
|
3275
3069
|
...this._s,
|
|
3276
3070
|
errorKeys: this._s.errorKeys,
|
|
3277
|
-
})
|
|
3071
|
+
})
|
|
3278
3072
|
}
|
|
3279
3073
|
|
|
3280
3074
|
boundary<
|
|
3281
|
-
TKey extends [TErrorFactory] extends [never]
|
|
3282
|
-
? never
|
|
3283
|
-
: Exclude<keyof TErrorFactory & string, TDefaultErrors>,
|
|
3075
|
+
TKey extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
|
|
3284
3076
|
>(
|
|
3285
3077
|
key: TKey,
|
|
3286
3078
|
): BuilderChain<
|
|
@@ -3302,8 +3094,8 @@ class RouteBuilder<
|
|
|
3302
3094
|
TTaps,
|
|
3303
3095
|
TScopedMw
|
|
3304
3096
|
> {
|
|
3305
|
-
this._s.boundaryErrorKey = key
|
|
3306
|
-
this._s.errorKeys.add(key)
|
|
3097
|
+
this._s.boundaryErrorKey = key
|
|
3098
|
+
this._s.errorKeys.add(key)
|
|
3307
3099
|
return new RouteBuilder<
|
|
3308
3100
|
TEnv,
|
|
3309
3101
|
TCtx,
|
|
@@ -3326,7 +3118,7 @@ class RouteBuilder<
|
|
|
3326
3118
|
...this._s,
|
|
3327
3119
|
boundaryErrorKey: key,
|
|
3328
3120
|
errorKeys: this._s.errorKeys,
|
|
3329
|
-
})
|
|
3121
|
+
})
|
|
3330
3122
|
}
|
|
3331
3123
|
|
|
3332
3124
|
handler(
|
|
@@ -3361,17 +3153,17 @@ class RouteBuilder<
|
|
|
3361
3153
|
TTaps,
|
|
3362
3154
|
TScopedMw
|
|
3363
3155
|
> {
|
|
3364
|
-
let ov: OutputValidator | null = null
|
|
3365
|
-
const outputSchemas = this._s.outputSchemas
|
|
3156
|
+
let ov: OutputValidator | null = null
|
|
3157
|
+
const outputSchemas = this._s.outputSchemas
|
|
3366
3158
|
if (outputSchemas) {
|
|
3367
|
-
const jsonSchemas = outputSchemas["application/json"]
|
|
3159
|
+
const jsonSchemas = outputSchemas["application/json"]
|
|
3368
3160
|
if (jsonSchemas) {
|
|
3369
3161
|
ov = async (statusKey: string, data: unknown) => {
|
|
3370
|
-
const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas]
|
|
3162
|
+
const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas]
|
|
3371
3163
|
if (schema) {
|
|
3372
|
-
await validateOutput(schema, statusKey, data)
|
|
3164
|
+
await validateOutput(schema, statusKey, data)
|
|
3373
3165
|
}
|
|
3374
|
-
}
|
|
3166
|
+
}
|
|
3375
3167
|
}
|
|
3376
3168
|
}
|
|
3377
3169
|
|
|
@@ -3387,39 +3179,36 @@ class RouteBuilder<
|
|
|
3387
3179
|
ek: this._s.errorKeys,
|
|
3388
3180
|
fn: fn as (ctx: unknown) => Response | Promise<Response>,
|
|
3389
3181
|
iv: this._s.inputSchemas,
|
|
3390
|
-
mt: this._s.
|
|
3182
|
+
mt: mergeContributedMeta(this._s.mwMeta, this._s.meta),
|
|
3391
3183
|
mw: [...this._s.parentMiddlewares, ...this._s.middlewares],
|
|
3392
3184
|
os: this._s.outputSchemas,
|
|
3393
3185
|
ov,
|
|
3394
3186
|
rp: this._s.path,
|
|
3395
|
-
}
|
|
3187
|
+
}
|
|
3396
3188
|
|
|
3397
3189
|
/* patch mode: if handler map has this route, patch existing handler instead of insertRoute */
|
|
3398
|
-
const handlerMap = this._s.handlerMap
|
|
3190
|
+
const handlerMap = this._s.handlerMap
|
|
3399
3191
|
if (handlerMap) {
|
|
3400
|
-
const key = `${this._s.method} ${this._s.path}
|
|
3401
|
-
const existing = handlerMap[key]
|
|
3192
|
+
const key = `${this._s.method} ${this._s.path}`
|
|
3193
|
+
const existing = handlerMap[key]
|
|
3402
3194
|
if (existing) {
|
|
3403
|
-
existing.fn = routeHandler.fn
|
|
3404
|
-
existing.mw = routeHandler.mw
|
|
3405
|
-
existing.iv = routeHandler.iv
|
|
3406
|
-
existing.os = routeHandler.os
|
|
3407
|
-
existing.ov = routeHandler.ov
|
|
3195
|
+
existing.fn = routeHandler.fn
|
|
3196
|
+
existing.mw = routeHandler.mw
|
|
3197
|
+
existing.iv = routeHandler.iv
|
|
3198
|
+
existing.os = routeHandler.os
|
|
3199
|
+
existing.ov = routeHandler.ov
|
|
3408
3200
|
/* pre-compute filtered error factory */
|
|
3409
|
-
const factory = (this._s.parent as unknown as HoneyInternal)._factory
|
|
3201
|
+
const factory = (this._s.parent as unknown as HoneyInternal)._factory
|
|
3410
3202
|
if (factory !== null && existing.ek.size > 0) {
|
|
3411
|
-
const ef = Object.create(null) as Record<
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
const factoryFn = fac[k]
|
|
3418
|
-
if (factoryFn !== undefined) {
|
|
3419
|
-
ef[k] = factoryFn
|
|
3420
|
-
}
|
|
3203
|
+
const ef = Object.create(null) as Record<string, (...args: never[]) => unknown>
|
|
3204
|
+
const fac = factory as Record<string, (...args: never[]) => unknown>
|
|
3205
|
+
for (const k of existing.ek) {
|
|
3206
|
+
const factoryFn = fac[k]
|
|
3207
|
+
if (factoryFn !== undefined) {
|
|
3208
|
+
ef[k] = factoryFn
|
|
3421
3209
|
}
|
|
3422
|
-
|
|
3210
|
+
}
|
|
3211
|
+
existing.ef = Object.freeze(ef)
|
|
3423
3212
|
}
|
|
3424
3213
|
return this._s.parent as HandlerReturn<
|
|
3425
3214
|
TEnv,
|
|
@@ -3438,28 +3227,25 @@ class RouteBuilder<
|
|
|
3438
3227
|
TBasePath,
|
|
3439
3228
|
TTaps,
|
|
3440
3229
|
TScopedMw
|
|
3441
|
-
|
|
3230
|
+
>
|
|
3442
3231
|
}
|
|
3443
3232
|
}
|
|
3444
3233
|
|
|
3445
|
-
insertRoute(this._s.root, this._s.method, this._s.path, routeHandler)
|
|
3234
|
+
insertRoute(this._s.root, this._s.method, this._s.path, routeHandler)
|
|
3446
3235
|
|
|
3447
3236
|
/* .on() extra methods — insert same handler for each additional method */
|
|
3448
3237
|
if (this._s.extraMethods) {
|
|
3449
3238
|
for (const m of this._s.extraMethods) {
|
|
3450
|
-
insertRoute(this._s.root, m, this._s.path, routeHandler)
|
|
3239
|
+
insertRoute(this._s.root, m, this._s.path, routeHandler)
|
|
3451
3240
|
if (!this._s.path.includes(":") && !this._s.path.includes("*")) {
|
|
3452
|
-
(this._s.parent as unknown as HoneyInternal)._registerStatic(`${m} ${this._s.path}`, routeHandler)
|
|
3241
|
+
;(this._s.parent as unknown as HoneyInternal)._registerStatic(`${m} ${this._s.path}`, routeHandler)
|
|
3453
3242
|
}
|
|
3454
3243
|
}
|
|
3455
3244
|
}
|
|
3456
3245
|
|
|
3457
3246
|
/* populate static route map for O(1) lookup on non-parameterized routes */
|
|
3458
3247
|
if (!this._s.path.includes(":") && !this._s.path.includes("*")) {
|
|
3459
|
-
(this._s.parent as unknown as HoneyInternal)._registerStatic(
|
|
3460
|
-
`${this._s.method} ${this._s.path}`,
|
|
3461
|
-
routeHandler,
|
|
3462
|
-
);
|
|
3248
|
+
;(this._s.parent as unknown as HoneyInternal)._registerStatic(`${this._s.method} ${this._s.path}`, routeHandler)
|
|
3463
3249
|
}
|
|
3464
3250
|
return this._s.parent as HandlerReturn<
|
|
3465
3251
|
TEnv,
|
|
@@ -3478,7 +3264,7 @@ class RouteBuilder<
|
|
|
3478
3264
|
TBasePath,
|
|
3479
3265
|
TTaps,
|
|
3480
3266
|
TScopedMw
|
|
3481
|
-
|
|
3267
|
+
>
|
|
3482
3268
|
}
|
|
3483
3269
|
|
|
3484
3270
|
proxy(
|
|
@@ -3513,7 +3299,7 @@ class RouteBuilder<
|
|
|
3513
3299
|
TTaps,
|
|
3514
3300
|
TScopedMw
|
|
3515
3301
|
> {
|
|
3516
|
-
const proxyHandler = createProxyHandler(config)
|
|
3302
|
+
const proxyHandler = createProxyHandler(config)
|
|
3517
3303
|
return this.handler(
|
|
3518
3304
|
proxyHandler as (
|
|
3519
3305
|
ctx: HandlerCtx<
|
|
@@ -3528,7 +3314,7 @@ class RouteBuilder<
|
|
|
3528
3314
|
TTaps
|
|
3529
3315
|
>,
|
|
3530
3316
|
) => Promise<TypedResponse>,
|
|
3531
|
-
)
|
|
3317
|
+
)
|
|
3532
3318
|
}
|
|
3533
3319
|
|
|
3534
3320
|
input<TSchemas extends InputSchemasDef>(
|
|
@@ -3573,13 +3359,10 @@ class RouteBuilder<
|
|
|
3573
3359
|
>({
|
|
3574
3360
|
...this._s,
|
|
3575
3361
|
inputSchemas: schemas,
|
|
3576
|
-
})
|
|
3362
|
+
})
|
|
3577
3363
|
}
|
|
3578
3364
|
|
|
3579
|
-
meta<
|
|
3580
|
-
TRouteMeta extends Partial<DefaultMeta> &
|
|
3581
|
-
([TMeta] extends [never] ? {} : TMeta),
|
|
3582
|
-
>(
|
|
3365
|
+
meta<TRouteMeta extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : TMeta)>(
|
|
3583
3366
|
meta: TRouteMeta,
|
|
3584
3367
|
): BuilderChain<
|
|
3585
3368
|
TEnv,
|
|
@@ -3628,7 +3411,7 @@ class RouteBuilder<
|
|
|
3628
3411
|
}
|
|
3629
3412
|
return merged
|
|
3630
3413
|
})(),
|
|
3631
|
-
})
|
|
3414
|
+
})
|
|
3632
3415
|
}
|
|
3633
3416
|
|
|
3634
3417
|
output<TOutputSchemas extends OutputSchemaDef>(
|
|
@@ -3673,7 +3456,7 @@ class RouteBuilder<
|
|
|
3673
3456
|
>({
|
|
3674
3457
|
...this._s,
|
|
3675
3458
|
outputSchemas: _schemas,
|
|
3676
|
-
})
|
|
3459
|
+
})
|
|
3677
3460
|
}
|
|
3678
3461
|
|
|
3679
3462
|
use<TAdds>(
|
|
@@ -3699,7 +3482,7 @@ class RouteBuilder<
|
|
|
3699
3482
|
> {
|
|
3700
3483
|
if (mw.errors) {
|
|
3701
3484
|
for (const k of mw.errors) {
|
|
3702
|
-
this._s.errorKeys.add(k)
|
|
3485
|
+
this._s.errorKeys.add(k)
|
|
3703
3486
|
}
|
|
3704
3487
|
}
|
|
3705
3488
|
return new RouteBuilder<
|
|
@@ -3723,21 +3506,23 @@ class RouteBuilder<
|
|
|
3723
3506
|
>({
|
|
3724
3507
|
...this._s,
|
|
3725
3508
|
middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
|
|
3726
|
-
|
|
3509
|
+
mwMeta: mw.meta ? { ...this._s.mwMeta, ...mw.meta } : this._s.mwMeta,
|
|
3510
|
+
})
|
|
3727
3511
|
}
|
|
3728
3512
|
}
|
|
3729
3513
|
|
|
3730
3514
|
type WSRouteBuilderState<TParent> = {
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3515
|
+
mwMeta?: Record<string, unknown> | null
|
|
3516
|
+
boundaryErrorKey: string | null
|
|
3517
|
+
errorKeys: Set<string>
|
|
3518
|
+
inputSchemas: InputSchemasDef | null
|
|
3519
|
+
meta: Record<string, unknown> | null
|
|
3520
|
+
middlewares: RuntimeMiddleware[]
|
|
3521
|
+
parent: TParent
|
|
3522
|
+
parentMiddlewares: RuntimeMiddleware[]
|
|
3523
|
+
path: string
|
|
3524
|
+
root: TreeNode
|
|
3525
|
+
}
|
|
3741
3526
|
|
|
3742
3527
|
class WSRouteBuilder<
|
|
3743
3528
|
TEnv,
|
|
@@ -3746,36 +3531,24 @@ class WSRouteBuilder<
|
|
|
3746
3531
|
_TErrorKeys extends string = never,
|
|
3747
3532
|
TParent extends { _markWsRoutes(): void } = { _markWsRoutes(): void },
|
|
3748
3533
|
> {
|
|
3749
|
-
private _s: WSRouteBuilderState<TParent
|
|
3534
|
+
private _s: WSRouteBuilderState<TParent>
|
|
3750
3535
|
|
|
3751
3536
|
constructor(state: WSRouteBuilderState<TParent>) {
|
|
3752
|
-
this._s = state
|
|
3537
|
+
this._s = state
|
|
3753
3538
|
}
|
|
3754
3539
|
|
|
3755
3540
|
errors<TFactory extends Record<string, (...args: never[]) => unknown>>(
|
|
3756
3541
|
factory: TFactory,
|
|
3757
3542
|
...keys: Array<keyof TFactory & string>
|
|
3758
|
-
): WSRouteBuilder<
|
|
3759
|
-
|
|
3760
|
-
TCtx,
|
|
3761
|
-
TInput,
|
|
3762
|
-
_TErrorKeys | (keyof TFactory & string),
|
|
3763
|
-
TParent
|
|
3764
|
-
> {
|
|
3765
|
-
void factory;
|
|
3543
|
+
): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent> {
|
|
3544
|
+
void factory
|
|
3766
3545
|
for (const k of keys) {
|
|
3767
|
-
this._s.errorKeys.add(k)
|
|
3546
|
+
this._s.errorKeys.add(k)
|
|
3768
3547
|
}
|
|
3769
|
-
return new WSRouteBuilder<
|
|
3770
|
-
TEnv,
|
|
3771
|
-
TCtx,
|
|
3772
|
-
TInput,
|
|
3773
|
-
_TErrorKeys | (keyof TFactory & string),
|
|
3774
|
-
TParent
|
|
3775
|
-
>({
|
|
3548
|
+
return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent>({
|
|
3776
3549
|
...this._s,
|
|
3777
3550
|
errorKeys: this._s.errorKeys,
|
|
3778
|
-
})
|
|
3551
|
+
})
|
|
3779
3552
|
}
|
|
3780
3553
|
|
|
3781
3554
|
handler(wsHandler: WSHandler<TCtx>): TParent {
|
|
@@ -3784,60 +3557,43 @@ class WSRouteBuilder<
|
|
|
3784
3557
|
ek: this._s.errorKeys,
|
|
3785
3558
|
fn: wsHandler as WSHandler<unknown>,
|
|
3786
3559
|
iv: this._s.inputSchemas,
|
|
3787
|
-
mt: this._s.
|
|
3560
|
+
mt: mergeContributedMeta(this._s.mwMeta, this._s.meta),
|
|
3788
3561
|
mw: [...this._s.parentMiddlewares, ...this._s.middlewares],
|
|
3789
3562
|
rp: this._s.path,
|
|
3790
|
-
}
|
|
3791
|
-
insertWsRoute(this._s.root, this._s.path, routeHandler)
|
|
3792
|
-
;(this._s.parent as unknown as HoneyInternal)._markWsRoutes()
|
|
3793
|
-
return this._s.parent
|
|
3563
|
+
}
|
|
3564
|
+
insertWsRoute(this._s.root, this._s.path, routeHandler)
|
|
3565
|
+
;(this._s.parent as unknown as HoneyInternal)._markWsRoutes()
|
|
3566
|
+
return this._s.parent
|
|
3794
3567
|
}
|
|
3795
3568
|
|
|
3796
|
-
input<
|
|
3797
|
-
TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">,
|
|
3798
|
-
>(
|
|
3569
|
+
input<TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">>(
|
|
3799
3570
|
schemas: TSchemas,
|
|
3800
|
-
): WSRouteBuilder<
|
|
3801
|
-
TEnv,
|
|
3802
|
-
TCtx,
|
|
3803
|
-
TInput & InferInputMap<TSchemas>,
|
|
3804
|
-
_TErrorKeys,
|
|
3805
|
-
TParent
|
|
3806
|
-
> {
|
|
3807
|
-
return new WSRouteBuilder<
|
|
3808
|
-
TEnv,
|
|
3809
|
-
TCtx,
|
|
3810
|
-
TInput & InferInputMap<TSchemas>,
|
|
3811
|
-
_TErrorKeys,
|
|
3812
|
-
TParent
|
|
3813
|
-
>({
|
|
3571
|
+
): WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent> {
|
|
3572
|
+
return new WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent>({
|
|
3814
3573
|
...this._s,
|
|
3815
3574
|
inputSchemas: schemas,
|
|
3816
|
-
})
|
|
3575
|
+
})
|
|
3817
3576
|
}
|
|
3818
3577
|
|
|
3819
3578
|
meta(meta: Record<string, unknown>): this {
|
|
3820
|
-
this._s.meta = meta
|
|
3821
|
-
return this
|
|
3579
|
+
this._s.meta = meta
|
|
3580
|
+
return this
|
|
3822
3581
|
}
|
|
3823
3582
|
|
|
3824
|
-
use<TAdds>(
|
|
3825
|
-
mw: MiddlewareFn<TCtx, TAdds>,
|
|
3826
|
-
): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
|
|
3583
|
+
use<TAdds>(mw: MiddlewareFn<TCtx, TAdds>): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
|
|
3827
3584
|
if (mw.errors) {
|
|
3828
3585
|
for (const k of mw.errors) {
|
|
3829
|
-
this._s.errorKeys.add(k)
|
|
3586
|
+
this._s.errorKeys.add(k)
|
|
3830
3587
|
}
|
|
3831
3588
|
}
|
|
3832
|
-
return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>(
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
);
|
|
3589
|
+
return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>({
|
|
3590
|
+
...this._s,
|
|
3591
|
+
middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
|
|
3592
|
+
mwMeta: mw.meta ? { ...this._s.mwMeta, ...mw.meta } : this._s.mwMeta,
|
|
3593
|
+
})
|
|
3838
3594
|
}
|
|
3839
3595
|
}
|
|
3840
3596
|
|
|
3841
3597
|
export function honey<TEnv>(): Honey<TEnv> {
|
|
3842
|
-
return new Honey<TEnv>()
|
|
3598
|
+
return new Honey<TEnv>()
|
|
3843
3599
|
}
|