@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/ip-restrict.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { namedMiddleware } from "./middleware.ts"
|
|
1
2
|
import { HoneyError } from "./error.ts"
|
|
2
3
|
import type { MiddlewareFn } from "./middleware.ts"
|
|
3
4
|
import { EK, SK } from "./types.ts"
|
|
@@ -188,5 +189,5 @@ export function ipRestrict(opts: IpRestrictOptions): MiddlewareFn<{ req: Request
|
|
|
188
189
|
return next()
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
return mw
|
|
192
|
+
return namedMiddleware("ipRestrict", mw)
|
|
192
193
|
}
|
package/src/logger.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { namedMiddleware } from "./middleware.ts"
|
|
1
2
|
import type { MiddlewareFn } from "./middleware.ts"
|
|
2
3
|
|
|
3
4
|
/* ── log levels (pino-compatible numeric values) ── */
|
|
@@ -45,9 +46,10 @@ function createLogger(opts?: CreateLoggerOptions): LoggerInstance {
|
|
|
45
46
|
|
|
46
47
|
function emit(level: number, first: Record<string, unknown> | string, second?: string): void {
|
|
47
48
|
if (level < threshold) return
|
|
48
|
-
const obj =
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const obj =
|
|
50
|
+
typeof first === "string"
|
|
51
|
+
? { ...base, level, msg: first, time: Date.now() }
|
|
52
|
+
: { ...base, ...first, level, msg: second ?? "", time: Date.now() }
|
|
51
53
|
write(JSON.stringify(obj))
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -95,25 +97,23 @@ function defaultLog(data: LogData): void {
|
|
|
95
97
|
const noop = () => {}
|
|
96
98
|
const noopLogger: LoggerInstance = createLogger({ level: "fatal", write: noop })
|
|
97
99
|
|
|
98
|
-
function logger(
|
|
99
|
-
options?: LoggerOptions,
|
|
100
|
-
): MiddlewareFn<{ path: string, req: Request }, { log: LoggerInstance }> {
|
|
100
|
+
function logger(options?: LoggerOptions): MiddlewareFn<{ path: string; req: Request }, { log: LoggerInstance }> {
|
|
101
101
|
const log = options?.log ?? defaultLog
|
|
102
102
|
const skip = options?.skip
|
|
103
103
|
const instance = options?.instance
|
|
104
104
|
|
|
105
|
-
return async (ctx, next) => {
|
|
105
|
+
return namedMiddleware("logger", async (ctx, next) => {
|
|
106
106
|
const start = performance.now()
|
|
107
107
|
const method = ctx.req.method
|
|
108
108
|
const path = ctx.path
|
|
109
|
-
const rid = (ctx as Record<string, unknown>)["requestId"] as string | null ?? null
|
|
109
|
+
const rid = ((ctx as Record<string, unknown>)["requestId"] as string | null) ?? null
|
|
110
110
|
|
|
111
111
|
const child = instance
|
|
112
112
|
? instance.child({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
method,
|
|
114
|
+
path,
|
|
115
|
+
...(rid ? { requestId: rid } : {}),
|
|
116
|
+
})
|
|
117
117
|
: noopLogger
|
|
118
118
|
|
|
119
119
|
if (instance) {
|
|
@@ -142,7 +142,7 @@ function logger(
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
return response
|
|
145
|
-
}
|
|
145
|
+
})
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export { createLogger, logger }
|
package/src/meta-spec.ts
ADDED
|
Binary file
|
package/src/middleware.ts
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
/** phantom wrapper — at runtime it's just Response, carries TAdds for inference */
|
|
2
2
|
export type MiddlewareResult<TAdds = {}> = Response & {
|
|
3
|
-
readonly __adds?: TAdds
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export type MiddlewareFn<
|
|
7
|
-
TCtx = {},
|
|
8
|
-
TAdds = {},
|
|
9
|
-
TErrors extends string = string,
|
|
10
|
-
> = ((
|
|
3
|
+
readonly __adds?: TAdds
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type MiddlewareFn<TCtx = {}, TAdds = {}, TErrors extends string = string> = ((
|
|
11
7
|
ctx: TCtx,
|
|
12
8
|
next: {
|
|
13
|
-
<T>(additions: T): Promise<MiddlewareResult<T
|
|
14
|
-
(): Promise<MiddlewareResult<{}
|
|
9
|
+
<T>(additions: T): Promise<MiddlewareResult<T>>
|
|
10
|
+
(): Promise<MiddlewareResult<{}>>
|
|
15
11
|
},
|
|
16
12
|
) => Promise<MiddlewareResult<TAdds>>) & {
|
|
17
|
-
errors?: readonly TErrors[]
|
|
18
|
-
|
|
13
|
+
errors?: readonly TErrors[]
|
|
14
|
+
/** Meta this middleware contributes to every route that mounts it. Codegen + `ctx.meta` */
|
|
15
|
+
meta?: Readonly<Record<string, unknown>>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Stamp a stable name on a middleware closure.
|
|
20
|
+
*
|
|
21
|
+
* `Function.prototype.name` is inferred by whatever transpiled the module — jiti (used by
|
|
22
|
+
* `honey generate`) and rollup (used by `vite build`) disagree about a closure returned from
|
|
23
|
+
* a factory, which made the generated manifest differ between the two. Naming shipped
|
|
24
|
+
* middleware explicitly makes the manifest deterministic, and "cors" reads better than
|
|
25
|
+
* "anonymous" besides.
|
|
26
|
+
*/
|
|
27
|
+
export function namedMiddleware<T extends object>(name: string, fn: T): T {
|
|
28
|
+
Object.defineProperty(fn, "name", { configurable: true, value: name })
|
|
29
|
+
return fn
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
/** runtime type for stored middleware — erased generics, used internally */
|
|
21
33
|
export type RuntimeMiddleware = (
|
|
22
34
|
ctx: Record<string, unknown>,
|
|
23
35
|
next: (additions?: Record<string, unknown>) => Promise<Response>,
|
|
24
|
-
) => Promise<Response
|
|
36
|
+
) => Promise<Response>
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
39
|
* Create middleware with error keys constrained to a factory.
|
|
@@ -33,21 +45,38 @@ export function defineMiddleware<
|
|
|
33
45
|
TFactory extends Record<string, (...args: never[]) => unknown>,
|
|
34
46
|
TKeys extends keyof TFactory & string,
|
|
35
47
|
>(opts: {
|
|
36
|
-
errors?: [TFactory, ...TKeys[]]
|
|
37
|
-
fn: MiddlewareFn<TReqs, TAdds
|
|
48
|
+
errors?: [TFactory, ...TKeys[]]
|
|
49
|
+
fn: MiddlewareFn<TReqs, TAdds>
|
|
50
|
+
meta?: Record<string, unknown>
|
|
38
51
|
}): MiddlewareFn<TReqs, TAdds, TKeys> {
|
|
39
|
-
const fn = opts.fn as MiddlewareFn<TReqs, TAdds, TKeys
|
|
52
|
+
const fn = opts.fn as MiddlewareFn<TReqs, TAdds, TKeys>
|
|
40
53
|
if (opts.errors) {
|
|
41
|
-
const [, ...keys] = opts.errors
|
|
42
|
-
Object.defineProperty(fn, "errors", { value: keys })
|
|
54
|
+
const [, ...keys] = opts.errors
|
|
55
|
+
Object.defineProperty(fn, "errors", { value: keys })
|
|
56
|
+
}
|
|
57
|
+
if (opts.meta) attachMiddlewareMeta(fn, opts.meta)
|
|
58
|
+
return fn
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `internal` decides whether a route appears in generated artifacts at all. A middleware that
|
|
63
|
+
* set it would silently remove routes from the document, which is not noticeable by reading
|
|
64
|
+
* either the route or the middleware — so it is the one key middleware may not contribute.
|
|
65
|
+
*/
|
|
66
|
+
function attachMiddlewareMeta(fn: object, meta: Record<string, unknown>): void {
|
|
67
|
+
if ("internal" in meta) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'middleware meta cannot set "internal" — it controls whether routes appear in generated ' +
|
|
70
|
+
"artifacts; set it on the route with .meta({ internal: true })",
|
|
71
|
+
)
|
|
43
72
|
}
|
|
44
|
-
|
|
73
|
+
Object.defineProperty(fn, "meta", { value: Object.freeze({ ...meta }) })
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
type NextFn = {
|
|
48
|
-
<T>(additions: T): Promise<MiddlewareResult<T
|
|
49
|
-
(): Promise<MiddlewareResult<{}
|
|
50
|
-
}
|
|
77
|
+
<T>(additions: T): Promise<MiddlewareResult<T>>
|
|
78
|
+
(): Promise<MiddlewareResult<{}>>
|
|
79
|
+
}
|
|
51
80
|
|
|
52
81
|
/** Extract TAdds from callback return type — distributes over unions */
|
|
53
82
|
type ExtractAdds<T> = T extends Promise<MiddlewareResult<infer A>> ? A : never
|
|
@@ -58,25 +87,38 @@ type ExtractAdds<T> = T extends Promise<MiddlewareResult<infer A>> ? A : never
|
|
|
58
87
|
*/
|
|
59
88
|
export function createMiddleware<TReqs = {}, TRet extends Promise<Response> = Promise<MiddlewareResult<{}>>>(
|
|
60
89
|
fn: (ctx: TReqs, next: NextFn) => TRet,
|
|
90
|
+
opts?: { meta?: Record<string, unknown> },
|
|
61
91
|
): MiddlewareFn<TReqs, ExtractAdds<TRet>> {
|
|
92
|
+
if (opts?.meta) attachMiddlewareMeta(fn, opts.meta)
|
|
62
93
|
return fn as MiddlewareFn<TReqs, ExtractAdds<TRet>>
|
|
63
94
|
}
|
|
64
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Merge meta contributed by a middleware chain, in mount order. Resolved once at route
|
|
98
|
+
* registration — never per request, because the precompiled route tree bakes `mt` as a literal
|
|
99
|
+
* and a lazily-derived value would diverge from it.
|
|
100
|
+
*/
|
|
101
|
+
export function collectMiddlewareMeta(
|
|
102
|
+
groups: ReadonlyArray<ReadonlyArray<RuntimeMiddleware>>,
|
|
103
|
+
): Record<string, unknown> | null {
|
|
104
|
+
let out: Record<string, unknown> | undefined
|
|
105
|
+
for (const group of groups) {
|
|
106
|
+
for (const mw of group) {
|
|
107
|
+
const meta = (mw as { meta?: Readonly<Record<string, unknown>> }).meta
|
|
108
|
+
if (!meta) continue
|
|
109
|
+
if (out === undefined) out = {}
|
|
110
|
+
Object.assign(out, meta)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out ?? null
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
/**
|
|
66
117
|
* Context properties that middleware additions must not overwrite.
|
|
67
118
|
* These are set by the framework during request construction.
|
|
68
119
|
* TypeScript prevents this at compile time; this guard catches runtime bypasses.
|
|
69
120
|
*/
|
|
70
|
-
const RESERVED_CTX_KEYS = new Set([
|
|
71
|
-
"background",
|
|
72
|
-
"cookies",
|
|
73
|
-
"env",
|
|
74
|
-
"headers",
|
|
75
|
-
"params",
|
|
76
|
-
"req",
|
|
77
|
-
"res",
|
|
78
|
-
"search",
|
|
79
|
-
]);
|
|
121
|
+
const RESERVED_CTX_KEYS = new Set(["background", "cookies", "env", "headers", "params", "req", "res", "search"])
|
|
80
122
|
|
|
81
123
|
/**
|
|
82
124
|
* Compile a middleware chain + handler into a single function at registration time.
|
|
@@ -89,66 +131,60 @@ export function compileChain(
|
|
|
89
131
|
if (middlewares.length === 0) {
|
|
90
132
|
/* sync fast path — avoids microtask overhead for sync handlers */
|
|
91
133
|
return (ctx) => {
|
|
92
|
-
const result = handler(ctx)
|
|
134
|
+
const result = handler(ctx)
|
|
93
135
|
if (result instanceof Promise) {
|
|
94
|
-
return result.then(validateResponse)
|
|
136
|
+
return result.then(validateResponse)
|
|
95
137
|
}
|
|
96
138
|
if (result === undefined || result === null) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
"handler must return a Response — did you forget 'return ctx.res...()'?",
|
|
99
|
-
);
|
|
139
|
+
throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
|
|
100
140
|
}
|
|
101
|
-
return result
|
|
102
|
-
}
|
|
141
|
+
return result
|
|
142
|
+
}
|
|
103
143
|
}
|
|
104
144
|
|
|
105
145
|
if (middlewares.length === 1) {
|
|
106
|
-
const mw = middlewares[0]
|
|
146
|
+
const mw = middlewares[0]
|
|
107
147
|
if (!mw) {
|
|
108
|
-
throw new Error("middleware chain invariant violated")
|
|
148
|
+
throw new Error("middleware chain invariant violated")
|
|
109
149
|
}
|
|
110
150
|
return (ctx) => {
|
|
111
|
-
const mwCtx = ctx as Record<string, unknown
|
|
112
|
-
let called = false
|
|
151
|
+
const mwCtx = ctx as Record<string, unknown>
|
|
152
|
+
let called = false
|
|
113
153
|
return Promise.resolve(
|
|
114
154
|
mw(mwCtx, (additions?: Record<string, unknown>) => {
|
|
115
|
-
if (called) throw new Error("next() called multiple times")
|
|
116
|
-
called = true
|
|
117
|
-
if (additions) mergeAdditions(ctx, additions)
|
|
118
|
-
return Promise.resolve(handler(ctx)).then(validateResponse)
|
|
155
|
+
if (called) throw new Error("next() called multiple times")
|
|
156
|
+
called = true
|
|
157
|
+
if (additions) mergeAdditions(ctx, additions)
|
|
158
|
+
return Promise.resolve(handler(ctx)).then(validateResponse)
|
|
119
159
|
}),
|
|
120
|
-
).then(validateMiddlewareResponse)
|
|
121
|
-
}
|
|
160
|
+
).then(validateMiddlewareResponse)
|
|
161
|
+
}
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
/* general case — pre-bind middleware array, no per-request index tracking */
|
|
125
|
-
return (ctx) => executeChain(middlewares, ctx, handler)
|
|
165
|
+
return (ctx) => executeChain(middlewares, ctx, handler)
|
|
126
166
|
}
|
|
127
167
|
|
|
128
168
|
function mergeAdditions(ctx: object, additions: Record<string, unknown>): void {
|
|
129
169
|
for (const key in additions) {
|
|
130
170
|
if (!RESERVED_CTX_KEYS.has(key)) {
|
|
131
|
-
(ctx as Record<string, unknown>)[key] = additions[key]
|
|
171
|
+
;(ctx as Record<string, unknown>)[key] = additions[key]
|
|
132
172
|
}
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
175
|
|
|
136
176
|
function validateResponse(result: Response): Response {
|
|
137
177
|
if (result === undefined || result === null) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
"handler must return a Response — did you forget 'return ctx.res...()'?",
|
|
140
|
-
);
|
|
178
|
+
throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
|
|
141
179
|
}
|
|
142
|
-
return result
|
|
180
|
+
return result
|
|
143
181
|
}
|
|
144
182
|
|
|
145
183
|
function validateMiddlewareResponse(result: Response): Response {
|
|
146
184
|
if (result === undefined || result === null) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
"middleware must return a Response — did you forget 'return next(...)'?",
|
|
149
|
-
);
|
|
185
|
+
throw new Error("middleware must return a Response — did you forget 'return next(...)'?")
|
|
150
186
|
}
|
|
151
|
-
return result
|
|
187
|
+
return result
|
|
152
188
|
}
|
|
153
189
|
|
|
154
190
|
export function executeChain(
|
|
@@ -156,44 +192,40 @@ export function executeChain(
|
|
|
156
192
|
ctx: object,
|
|
157
193
|
handler: (ctx: object) => Response | Promise<Response>,
|
|
158
194
|
): Promise<Response> {
|
|
159
|
-
let index = 0
|
|
195
|
+
let index = 0
|
|
160
196
|
/* middleware runtime type uses Record<string, unknown> — object satisfies at runtime */
|
|
161
|
-
const mwCtx = ctx as Record<string, unknown
|
|
197
|
+
const mwCtx = ctx as Record<string, unknown>
|
|
162
198
|
|
|
163
199
|
function dispatch(): Promise<Response> {
|
|
164
200
|
if (index >= middlewares.length) {
|
|
165
201
|
return Promise.resolve(handler(ctx)).then((result) => {
|
|
166
202
|
if (result === undefined || result === null) {
|
|
167
|
-
throw new Error(
|
|
168
|
-
"handler must return a Response — did you forget 'return ctx.res...()'?",
|
|
169
|
-
);
|
|
203
|
+
throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
|
|
170
204
|
}
|
|
171
|
-
return result
|
|
172
|
-
})
|
|
205
|
+
return result
|
|
206
|
+
})
|
|
173
207
|
}
|
|
174
|
-
const mw = middlewares[index++]
|
|
208
|
+
const mw = middlewares[index++]
|
|
175
209
|
if (!mw) {
|
|
176
|
-
throw new Error("middleware chain invariant violated")
|
|
210
|
+
throw new Error("middleware chain invariant violated")
|
|
177
211
|
}
|
|
178
|
-
let called = false
|
|
212
|
+
let called = false
|
|
179
213
|
return Promise.resolve(
|
|
180
214
|
mw(mwCtx, (additions?: Record<string, unknown>) => {
|
|
181
215
|
if (called) {
|
|
182
|
-
throw new Error("next() called multiple times")
|
|
216
|
+
throw new Error("next() called multiple times")
|
|
183
217
|
}
|
|
184
|
-
called = true
|
|
185
|
-
if (additions) mergeAdditions(ctx, additions)
|
|
186
|
-
return dispatch()
|
|
218
|
+
called = true
|
|
219
|
+
if (additions) mergeAdditions(ctx, additions)
|
|
220
|
+
return dispatch()
|
|
187
221
|
}),
|
|
188
222
|
).then((result) => {
|
|
189
223
|
if (result === undefined || result === null) {
|
|
190
|
-
throw new Error(
|
|
191
|
-
"middleware must return a Response — did you forget 'return next(...)'?",
|
|
192
|
-
);
|
|
224
|
+
throw new Error("middleware must return a Response — did you forget 'return next(...)'?")
|
|
193
225
|
}
|
|
194
|
-
return result
|
|
195
|
-
})
|
|
226
|
+
return result
|
|
227
|
+
})
|
|
196
228
|
}
|
|
197
229
|
|
|
198
|
-
return dispatch()
|
|
230
|
+
return dispatch()
|
|
199
231
|
}
|
package/src/node-request.ts
CHANGED
|
@@ -121,7 +121,8 @@ export class NodeRequest {
|
|
|
121
121
|
get body(): ReadableStream<Uint8Array> | null {
|
|
122
122
|
if (!this.#hasBody) return null
|
|
123
123
|
if (this.#webBody === undefined) {
|
|
124
|
-
|
|
124
|
+
// Node's web stream and the DOM/Bun ReadableStream brands do not overlap.
|
|
125
|
+
this.#webBody = Readable.toWeb(this.#incoming) as unknown as ReadableStream<Uint8Array>
|
|
125
126
|
}
|
|
126
127
|
return this.#webBody
|
|
127
128
|
}
|
package/src/node.ts
CHANGED
|
@@ -16,14 +16,16 @@ type ServeOptions<TEnv> = {
|
|
|
16
16
|
const BUFFER_BODY_MAX = 256 * 1024
|
|
17
17
|
const RAW_BODY = Symbol.for("honey.rawBody")
|
|
18
18
|
|
|
19
|
+
// Node's `Readable.fromWeb` wants `node:stream/web` streams; DOM/Bun brands do not overlap.
|
|
20
|
+
function asNodeWebStream(stream: ReadableStream<Uint8Array>): import("node:stream/web").ReadableStream {
|
|
21
|
+
return stream as unknown as import("node:stream/web").ReadableStream
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
function incomingToRequest(req: IncomingMessage): Request {
|
|
20
25
|
return incomingToNodeRequest(req)
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
function collectNodeHeaders(
|
|
24
|
-
response: Response,
|
|
25
|
-
extra?: Record<string, string>,
|
|
26
|
-
): Record<string, string | string[]> {
|
|
28
|
+
function collectNodeHeaders(response: Response, extra?: Record<string, string>): Record<string, string | string[]> {
|
|
27
29
|
const headerObj: Record<string, string | string[]> = {}
|
|
28
30
|
let hasSetCookie = false
|
|
29
31
|
response.headers.forEach((value, key) => {
|
|
@@ -79,7 +81,7 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
|
|
|
79
81
|
}
|
|
80
82
|
res.writeHead(response.status, response.plainHeaders)
|
|
81
83
|
try {
|
|
82
|
-
await pipeline(Readable.fromWeb(
|
|
84
|
+
await pipeline(Readable.fromWeb(asNodeWebStream(stream)), res)
|
|
83
85
|
} catch {
|
|
84
86
|
if (!res.destroyed) res.destroy()
|
|
85
87
|
}
|
|
@@ -104,17 +106,14 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
|
|
|
104
106
|
if (shouldBufferBody(response)) {
|
|
105
107
|
const buf = Buffer.from(await response.arrayBuffer())
|
|
106
108
|
if (res.destroyed) return
|
|
107
|
-
res.writeHead(
|
|
108
|
-
response.status,
|
|
109
|
-
collectNodeHeaders(response, { "content-length": String(buf.byteLength) }),
|
|
110
|
-
)
|
|
109
|
+
res.writeHead(response.status, collectNodeHeaders(response, { "content-length": String(buf.byteLength) }))
|
|
111
110
|
res.end(buf)
|
|
112
111
|
return
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
res.writeHead(response.status, collectNodeHeaders(response))
|
|
116
115
|
try {
|
|
117
|
-
await pipeline(Readable.fromWeb(body
|
|
116
|
+
await pipeline(Readable.fromWeb(asNodeWebStream(body)), res)
|
|
118
117
|
} catch {
|
|
119
118
|
if (!res.destroyed) res.destroy()
|
|
120
119
|
}
|
package/src/openapi/register.ts
CHANGED
|
@@ -6,8 +6,7 @@ import { registerOpenApiRuntime } from "./spec-factory.ts"
|
|
|
6
6
|
|
|
7
7
|
export function enableOpenApi(): void {
|
|
8
8
|
registerOpenApiRuntime({
|
|
9
|
-
docsUi: (kind, specUrl) =>
|
|
10
|
-
kind === "swagger" ? swagger({ url: specUrl }) : scalar({ url: specUrl }),
|
|
9
|
+
docsUi: (kind, specUrl) => (kind === "swagger" ? swagger({ url: specUrl }) : scalar({ url: specUrl })),
|
|
11
10
|
generateManifest: (app) => Promise.resolve(generateManifest(app as never)),
|
|
12
11
|
generateOpenApi: (app, options) => generateOpenApi(app as never, options),
|
|
13
12
|
toYaml,
|
package/src/openapi/scalar.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { TypedResponse } from "../response.ts"
|
|
2
2
|
|
|
3
3
|
type ScalarOptions = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
url: string
|
|
5
|
+
[key: string]: unknown
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function scalar(
|
|
9
|
-
|
|
9
|
+
options: ScalarOptions,
|
|
10
10
|
): (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => TypedResponse {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const { url, ...rest } = options
|
|
12
|
+
const config = JSON.stringify({ url, ...rest })
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const html = `<!DOCTYPE html>
|
|
15
15
|
<html>
|
|
16
16
|
<head>
|
|
17
17
|
<meta charset="utf-8" />
|
|
@@ -27,7 +27,7 @@ export function scalar(
|
|
|
27
27
|
</body>
|
|
28
28
|
</html>`
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
|
|
31
|
+
Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
|
|
32
|
+
return handler
|
|
33
33
|
}
|
|
@@ -5,6 +5,7 @@ export type OpenApiGenerate = (
|
|
|
5
5
|
options: {
|
|
6
6
|
filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
|
|
7
7
|
info: { description?: string; title: string; version: string }
|
|
8
|
+
profile?: string
|
|
8
9
|
securitySchemes?: Record<string, unknown>
|
|
9
10
|
},
|
|
10
11
|
) => Promise<unknown>
|
|
@@ -27,8 +28,7 @@ export type OpenApiRuntime = {
|
|
|
27
28
|
toYaml: YamlGenerate
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const MISSING =
|
|
31
|
-
'Runtime OpenAPI/manifest generation requires `import "@lovrozagar/honey/openapi"` in the app entry.'
|
|
31
|
+
const MISSING = 'Runtime OpenAPI/manifest generation requires `import "@lovrozagar/honey/openapi"` in the app entry.'
|
|
32
32
|
|
|
33
33
|
let runtime: OpenApiRuntime | undefined
|
|
34
34
|
|
package/src/openapi/spec.ts
CHANGED
|
@@ -4,28 +4,35 @@ import type { DefaultMeta } from "../types.ts"
|
|
|
4
4
|
import type { TypedResponse } from "../response.ts"
|
|
5
5
|
|
|
6
6
|
type SpecOptions<TMeta = Record<string, unknown> | null> = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
description?: string
|
|
8
|
+
filterRoutes?: (route: OpenApiRouteInfo<DefaultMeta & TMeta>) => boolean
|
|
9
|
+
/** Named metaSpec profile selecting which emitted keys this document carries */
|
|
10
|
+
profile?: string
|
|
11
|
+
securitySchemes?: Record<string, unknown>
|
|
12
|
+
title: string
|
|
13
|
+
version: string
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function spec<TMeta = Record<string, unknown> | null>(
|
|
15
|
-
|
|
17
|
+
options: SpecOptions<TMeta>,
|
|
16
18
|
): (ctx: { res: { json(sk: "ok", data: unknown): TypedResponse } }) => TypedResponse | Promise<TypedResponse> {
|
|
17
|
-
|
|
19
|
+
let cached: string | null = null
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
const handler = async (ctx: { res: { json(sk: "ok", data: unknown): TypedResponse } }) => {
|
|
22
|
+
if (cached === null) {
|
|
23
|
+
const desc = Object.getOwnPropertyDescriptor(handler, Symbol.for("honey.app"))
|
|
24
|
+
const openApiSpec = desc
|
|
25
|
+
? await generateOpenApi(desc.value, {
|
|
26
|
+
filterRoutes: options.filterRoutes,
|
|
27
|
+
info: options,
|
|
28
|
+
profile: options.profile,
|
|
29
|
+
securitySchemes: options.securitySchemes,
|
|
30
|
+
})
|
|
31
|
+
: {}
|
|
32
|
+
cached = JSON.stringify(openApiSpec)
|
|
33
|
+
}
|
|
34
|
+
return ctx.res.json("ok", JSON.parse(cached))
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
|
|
37
|
+
return handler
|
|
31
38
|
}
|
package/src/openapi/swagger.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { TypedResponse } from "../response.ts"
|
|
2
2
|
|
|
3
3
|
type SwaggerOptions = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
url: string
|
|
5
|
+
[key: string]: unknown
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function swagger(
|
|
9
|
-
|
|
9
|
+
options: SwaggerOptions,
|
|
10
10
|
): (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => TypedResponse {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const { url, ...rest } = options
|
|
12
|
+
const config = JSON.stringify({ dom_id: "#swagger-ui", url, ...rest })
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const html = `<!DOCTYPE html>
|
|
15
15
|
<html>
|
|
16
16
|
<head>
|
|
17
17
|
<meta charset="utf-8" />
|
|
@@ -28,7 +28,7 @@ export function swagger(
|
|
|
28
28
|
</body>
|
|
29
29
|
</html>`
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
|
|
32
|
+
Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
|
|
33
|
+
return handler
|
|
34
34
|
}
|