@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/client/http.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { ClientError } from "./error.ts"
|
|
2
|
-
import type { SSEEvent } from "./sse.ts"
|
|
3
|
-
import { parseSSEStream } from "./sse.ts"
|
|
1
|
+
import { ClientError } from "./error.ts"
|
|
2
|
+
import type { SSEEvent } from "./sse.ts"
|
|
3
|
+
import { parseSSEStream } from "./sse.ts"
|
|
4
4
|
|
|
5
5
|
export type HeadersContext = {
|
|
6
|
-
method: string
|
|
7
|
-
path: string
|
|
8
|
-
}
|
|
6
|
+
method: string
|
|
7
|
+
path: string
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
type HeadersRecord = Record<string, string | undefined
|
|
10
|
+
type HeadersRecord = Record<string, string | undefined>
|
|
11
11
|
|
|
12
|
-
export type HeadersInit =
|
|
13
|
-
| ((ctx: HeadersContext) => HeadersRecord | Promise<HeadersRecord>)
|
|
14
|
-
| HeadersRecord;
|
|
12
|
+
export type HeadersInit = ((ctx: HeadersContext) => HeadersRecord | Promise<HeadersRecord>) | HeadersRecord
|
|
15
13
|
|
|
16
14
|
export type RequestMeta = {
|
|
17
15
|
invalidatedBy: string[]
|
|
@@ -21,161 +19,146 @@ export type RequestMeta = {
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
export type OnRequestContext = {
|
|
24
|
-
body?: BodyInit
|
|
25
|
-
headers: Headers
|
|
22
|
+
body?: BodyInit
|
|
23
|
+
headers: Headers
|
|
26
24
|
invalidatedBy?: string[]
|
|
27
25
|
isStale?: boolean
|
|
28
|
-
method: string
|
|
29
|
-
path: string
|
|
26
|
+
method: string
|
|
27
|
+
path: string
|
|
30
28
|
selector?: string
|
|
31
|
-
state: Record<string, unknown
|
|
32
|
-
url: string
|
|
33
|
-
}
|
|
29
|
+
state: Record<string, unknown>
|
|
30
|
+
url: string
|
|
31
|
+
}
|
|
34
32
|
|
|
35
33
|
export type OnResponseContext = {
|
|
36
34
|
invalidatedBy?: string[]
|
|
37
|
-
isRetry: boolean
|
|
35
|
+
isRetry: boolean
|
|
38
36
|
isStale?: boolean
|
|
39
|
-
method: string
|
|
40
|
-
path: string
|
|
41
|
-
request: Request
|
|
42
|
-
response: Response
|
|
43
|
-
retry: () => Promise<Response
|
|
37
|
+
method: string
|
|
38
|
+
path: string
|
|
39
|
+
request: Request
|
|
40
|
+
response: Response
|
|
41
|
+
retry: () => Promise<Response>
|
|
44
42
|
selector?: string
|
|
45
|
-
state: Record<string, unknown
|
|
46
|
-
url: string
|
|
47
|
-
}
|
|
43
|
+
state: Record<string, unknown>
|
|
44
|
+
url: string
|
|
45
|
+
}
|
|
48
46
|
|
|
49
47
|
export type ClientConfig = {
|
|
50
|
-
baseURL: string
|
|
51
|
-
buildSearchParams?: (query: Record<string, unknown>) => URLSearchParams
|
|
52
|
-
credentials?: RequestCredentials
|
|
53
|
-
fetch?: typeof fetch
|
|
54
|
-
headers?: HeadersInit
|
|
55
|
-
mode?: RequestMode
|
|
56
|
-
onRequest?: Array<(ctx: OnRequestContext) => Promise<void> | void
|
|
57
|
-
onResponse?: Array<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
sortSearchParams?: boolean;
|
|
63
|
-
state?: Record<string, unknown>;
|
|
64
|
-
throwOnError?: boolean;
|
|
65
|
-
timeout?: number;
|
|
48
|
+
baseURL: string
|
|
49
|
+
buildSearchParams?: (query: Record<string, unknown>) => URLSearchParams
|
|
50
|
+
credentials?: RequestCredentials
|
|
51
|
+
fetch?: typeof fetch
|
|
52
|
+
headers?: HeadersInit
|
|
53
|
+
mode?: RequestMode
|
|
54
|
+
onRequest?: Array<(ctx: OnRequestContext) => Promise<void> | void>
|
|
55
|
+
onResponse?: Array<(ctx: OnResponseContext) => Promise<Response | undefined> | Response | undefined>
|
|
56
|
+
sortSearchParams?: boolean
|
|
57
|
+
state?: Record<string, unknown>
|
|
58
|
+
throwOnError?: boolean
|
|
59
|
+
timeout?: number
|
|
66
60
|
|
|
67
61
|
/* Realtime hooks — called by the generated runtime module */
|
|
68
|
-
onAuthExpired?: () => Promise<string | null | undefined
|
|
69
|
-
onReconnecting?: (attempt: number, transport: string) => void
|
|
70
|
-
onReconnected?: () => void
|
|
71
|
-
}
|
|
62
|
+
onAuthExpired?: () => Promise<string | null | undefined>
|
|
63
|
+
onReconnecting?: (attempt: number, transport: string) => void
|
|
64
|
+
onReconnected?: () => void
|
|
65
|
+
}
|
|
72
66
|
|
|
73
67
|
export type RequestOptions = {
|
|
74
|
-
cookies?: Record<string, string
|
|
75
|
-
form?: Record<string, unknown
|
|
76
|
-
headers?: Record<string, string
|
|
77
|
-
json?: unknown
|
|
78
|
-
lastEventId?: string
|
|
79
|
-
params?: Record<string, string
|
|
80
|
-
search?: Record<string, unknown
|
|
81
|
-
signal?: AbortSignal
|
|
82
|
-
}
|
|
68
|
+
cookies?: Record<string, string>
|
|
69
|
+
form?: Record<string, unknown>
|
|
70
|
+
headers?: Record<string, string>
|
|
71
|
+
json?: unknown
|
|
72
|
+
lastEventId?: string
|
|
73
|
+
params?: Record<string, string>
|
|
74
|
+
search?: Record<string, unknown>
|
|
75
|
+
signal?: AbortSignal
|
|
76
|
+
}
|
|
83
77
|
|
|
84
78
|
export function interpolatePath(path: string, params: Record<string, string>): string {
|
|
85
79
|
return path.replace(/:(\w+)/g, (_, key: string) => {
|
|
86
|
-
const val = params[key]
|
|
87
|
-
if (val === undefined) throw new Error(`Missing path param: ${key}`)
|
|
88
|
-
return encodeURIComponent(val)
|
|
89
|
-
})
|
|
80
|
+
const val = params[key]
|
|
81
|
+
if (val === undefined) throw new Error(`Missing path param: ${key}`)
|
|
82
|
+
return encodeURIComponent(val)
|
|
83
|
+
})
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
function coerceParam(v: unknown): string | null {
|
|
93
|
-
if (v === undefined || v === null) return null
|
|
94
|
-
if (v instanceof Date) return v.toISOString()
|
|
95
|
-
if (typeof v === "symbol") return null
|
|
96
|
-
return String(v)
|
|
87
|
+
if (v === undefined || v === null) return null
|
|
88
|
+
if (v instanceof Date) return v.toISOString()
|
|
89
|
+
if (typeof v === "symbol") return null
|
|
90
|
+
return String(v)
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
function defaultSearchParams(query: Record<string, unknown>): URLSearchParams {
|
|
100
|
-
const params = new URLSearchParams()
|
|
94
|
+
const params = new URLSearchParams()
|
|
101
95
|
for (const [k, v] of Object.entries(query)) {
|
|
102
|
-
if (v === undefined || v === null) continue
|
|
96
|
+
if (v === undefined || v === null) continue
|
|
103
97
|
if (Array.isArray(v)) {
|
|
104
|
-
for (const item of v) {
|
|
98
|
+
for (const item of v) {
|
|
99
|
+
const s = coerceParam(item)
|
|
100
|
+
if (s !== null) params.append(k, s)
|
|
101
|
+
}
|
|
105
102
|
} else {
|
|
106
|
-
const s = coerceParam(v)
|
|
103
|
+
const s = coerceParam(v)
|
|
104
|
+
if (s !== null) params.set(k, s)
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
|
-
return params
|
|
107
|
+
return params
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
function applySearch(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
config
|
|
116
|
-
): void {
|
|
117
|
-
const serializer = config.buildSearchParams ?? defaultSearchParams;
|
|
118
|
-
const params = serializer(search);
|
|
119
|
-
if (config.sortSearchParams) params.sort();
|
|
110
|
+
function applySearch(url: URL, search: Record<string, unknown>, config: ClientConfig): void {
|
|
111
|
+
const serializer = config.buildSearchParams ?? defaultSearchParams
|
|
112
|
+
const params = serializer(search)
|
|
113
|
+
if (config.sortSearchParams) params.sort()
|
|
120
114
|
for (const [k, v] of params.entries()) {
|
|
121
|
-
url.searchParams.append(k, v)
|
|
115
|
+
url.searchParams.append(k, v)
|
|
122
116
|
}
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
function buildURL(
|
|
126
|
-
|
|
127
|
-
path
|
|
128
|
-
opts: RequestOptions,
|
|
129
|
-
): string {
|
|
130
|
-
let resolvedPath = path;
|
|
131
|
-
if (opts.params) resolvedPath = interpolatePath(path, opts.params);
|
|
119
|
+
function buildURL(config: ClientConfig, path: string, opts: RequestOptions): string {
|
|
120
|
+
let resolvedPath = path
|
|
121
|
+
if (opts.params) resolvedPath = interpolatePath(path, opts.params)
|
|
132
122
|
|
|
133
|
-
const baseUrl = new URL(config.baseURL)
|
|
134
|
-
const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : `${baseUrl.pathname}
|
|
135
|
-
const relative = resolvedPath.startsWith("/") ? resolvedPath.slice(1) : resolvedPath
|
|
136
|
-
const url = new URL(`${basePath}${relative}`, baseUrl)
|
|
123
|
+
const baseUrl = new URL(config.baseURL)
|
|
124
|
+
const basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : `${baseUrl.pathname}/`
|
|
125
|
+
const relative = resolvedPath.startsWith("/") ? resolvedPath.slice(1) : resolvedPath
|
|
126
|
+
const url = new URL(`${basePath}${relative}`, baseUrl)
|
|
137
127
|
|
|
138
|
-
for (const [k, v] of baseUrl.searchParams.entries()) url.searchParams.append(k, v)
|
|
139
|
-
if (opts.search) applySearch(url, opts.search, config)
|
|
128
|
+
for (const [k, v] of baseUrl.searchParams.entries()) url.searchParams.append(k, v)
|
|
129
|
+
if (opts.search) applySearch(url, opts.search, config)
|
|
140
130
|
|
|
141
|
-
return url.toString()
|
|
131
|
+
return url.toString()
|
|
142
132
|
}
|
|
143
133
|
|
|
144
|
-
async function buildHeaders(
|
|
145
|
-
|
|
146
|
-
opts: RequestOptions,
|
|
147
|
-
ctx: HeadersContext,
|
|
148
|
-
): Promise<Headers> {
|
|
149
|
-
const headers = new Headers();
|
|
134
|
+
async function buildHeaders(config: ClientConfig, opts: RequestOptions, ctx: HeadersContext): Promise<Headers> {
|
|
135
|
+
const headers = new Headers()
|
|
150
136
|
|
|
151
137
|
if (config.headers) {
|
|
152
|
-
const resolved =
|
|
153
|
-
typeof config.headers === "function"
|
|
154
|
-
? await config.headers(ctx)
|
|
155
|
-
: config.headers;
|
|
138
|
+
const resolved = typeof config.headers === "function" ? await config.headers(ctx) : config.headers
|
|
156
139
|
for (const [k, v] of Object.entries(resolved)) {
|
|
157
|
-
if (v !== undefined) headers.set(k, v)
|
|
140
|
+
if (v !== undefined) headers.set(k, v)
|
|
158
141
|
}
|
|
159
142
|
}
|
|
160
143
|
|
|
161
144
|
/* per-request headers override config headers */
|
|
162
145
|
if (opts.headers) {
|
|
163
146
|
for (const [k, v] of Object.entries(opts.headers)) {
|
|
164
|
-
headers.set(k, v)
|
|
147
|
+
headers.set(k, v)
|
|
165
148
|
}
|
|
166
149
|
}
|
|
167
150
|
|
|
168
151
|
if (opts.cookies) {
|
|
169
|
-
const existing = headers.get("cookie")
|
|
152
|
+
const existing = headers.get("cookie")
|
|
170
153
|
const pairs = Object.entries(opts.cookies)
|
|
171
154
|
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
172
|
-
.join("; ")
|
|
155
|
+
.join("; ")
|
|
173
156
|
if (pairs) {
|
|
174
|
-
headers.set("cookie", existing ? `${existing}; ${pairs}` : pairs)
|
|
157
|
+
headers.set("cookie", existing ? `${existing}; ${pairs}` : pairs)
|
|
175
158
|
}
|
|
176
159
|
}
|
|
177
160
|
|
|
178
|
-
return headers
|
|
161
|
+
return headers
|
|
179
162
|
}
|
|
180
163
|
|
|
181
164
|
function buildSignal(
|
|
@@ -183,61 +166,63 @@ function buildSignal(
|
|
|
183
166
|
opts: RequestOptions,
|
|
184
167
|
isStream?: boolean,
|
|
185
168
|
): { signal: AbortSignal | undefined; cleanup: () => void } {
|
|
186
|
-
const userSignal = opts.signal
|
|
169
|
+
const userSignal = opts.signal
|
|
187
170
|
/* skip timeout for streams — they're long-lived */
|
|
188
|
-
const timeout = isStream ? undefined : config.timeout
|
|
171
|
+
const timeout = isStream ? undefined : config.timeout
|
|
189
172
|
|
|
190
|
-
if (!timeout) return { cleanup: () => {}, signal: userSignal }
|
|
173
|
+
if (!timeout) return { cleanup: () => {}, signal: userSignal }
|
|
191
174
|
|
|
192
|
-
const ctrl = new AbortController()
|
|
193
|
-
const timer = setTimeout(() => ctrl.abort(new Error("timeout")), timeout)
|
|
175
|
+
const ctrl = new AbortController()
|
|
176
|
+
const timer = setTimeout(() => ctrl.abort(new Error("timeout")), timeout)
|
|
194
177
|
if (typeof (timer as unknown as { unref?: () => void }).unref === "function") {
|
|
195
|
-
(timer as unknown as { unref: () => void }).unref()
|
|
178
|
+
;(timer as unknown as { unref: () => void }).unref()
|
|
196
179
|
}
|
|
197
|
-
const onUserAbort = () => ctrl.abort(userSignal?.reason)
|
|
180
|
+
const onUserAbort = () => ctrl.abort(userSignal?.reason)
|
|
198
181
|
if (userSignal?.aborted) {
|
|
199
182
|
/* already aborted before we subscribed — propagate synchronously */
|
|
200
|
-
ctrl.abort(userSignal.reason)
|
|
183
|
+
ctrl.abort(userSignal.reason)
|
|
201
184
|
} else {
|
|
202
|
-
userSignal?.addEventListener("abort", onUserAbort, { once: true })
|
|
185
|
+
userSignal?.addEventListener("abort", onUserAbort, { once: true })
|
|
203
186
|
}
|
|
204
187
|
return {
|
|
205
188
|
cleanup: () => {
|
|
206
|
-
clearTimeout(timer)
|
|
207
|
-
userSignal?.removeEventListener("abort", onUserAbort)
|
|
189
|
+
clearTimeout(timer)
|
|
190
|
+
userSignal?.removeEventListener("abort", onUserAbort)
|
|
208
191
|
},
|
|
209
192
|
signal: ctrl.signal,
|
|
210
|
-
}
|
|
193
|
+
}
|
|
211
194
|
}
|
|
212
195
|
|
|
213
196
|
async function parseErrorBody(response: Response): Promise<unknown> {
|
|
214
197
|
try {
|
|
215
|
-
return await response.json()
|
|
198
|
+
return await response.json()
|
|
216
199
|
} catch {
|
|
217
|
-
return undefined
|
|
200
|
+
return undefined
|
|
218
201
|
}
|
|
219
202
|
}
|
|
220
203
|
|
|
221
204
|
async function parseErrorAsClientError(response: Response): Promise<ClientError> {
|
|
222
205
|
const preserved = response.clone()
|
|
223
206
|
const body = await parseErrorBody(response)
|
|
224
|
-
const msgVal =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
207
|
+
const msgVal =
|
|
208
|
+
typeof body === "object" && body !== null && "message" in body
|
|
209
|
+
? (body as Record<string, unknown>).message
|
|
210
|
+
: undefined
|
|
211
|
+
const message =
|
|
212
|
+
typeof msgVal === "string"
|
|
213
|
+
? /* oxlint-disable-next-line no-control-regex -- intentional strip of ASCII control characters */
|
|
214
|
+
msgVal.replace(/[\x00-\x1f]/g, "").slice(0, 512)
|
|
215
|
+
: `HTTP ${response.status}`
|
|
231
216
|
return new ClientError({ body, message, response: preserved, status: response.status })
|
|
232
217
|
}
|
|
233
218
|
|
|
234
219
|
export class HTTPClient {
|
|
235
|
-
private _config: ClientConfig
|
|
236
|
-
private _fetch: typeof fetch
|
|
220
|
+
private _config: ClientConfig
|
|
221
|
+
private _fetch: typeof fetch
|
|
237
222
|
|
|
238
223
|
constructor(config: ClientConfig) {
|
|
239
|
-
this._config = config
|
|
240
|
-
this._fetch = config.fetch ?? globalThis.fetch
|
|
224
|
+
this._config = config
|
|
225
|
+
this._fetch = config.fetch ?? globalThis.fetch
|
|
241
226
|
}
|
|
242
227
|
|
|
243
228
|
private async _doRequest(
|
|
@@ -247,15 +232,15 @@ export class HTTPClient {
|
|
|
247
232
|
isRetry = false,
|
|
248
233
|
_requestMeta?: RequestMeta,
|
|
249
234
|
): Promise<{
|
|
250
|
-
response: Response
|
|
235
|
+
response: Response
|
|
251
236
|
}> {
|
|
252
|
-
const url = buildURL(this._config, path, opts)
|
|
253
|
-
const headers = await buildHeaders(this._config, opts, { method, path })
|
|
254
|
-
let body: BodyInit | undefined
|
|
237
|
+
const url = buildURL(this._config, path, opts)
|
|
238
|
+
const headers = await buildHeaders(this._config, opts, { method, path })
|
|
239
|
+
let body: BodyInit | undefined
|
|
255
240
|
|
|
256
241
|
if (opts.json !== undefined) {
|
|
257
|
-
headers.set("content-type", "application/json")
|
|
258
|
-
body = JSON.stringify(opts.json)
|
|
242
|
+
headers.set("content-type", "application/json")
|
|
243
|
+
body = JSON.stringify(opts.json)
|
|
259
244
|
} else if (opts.form !== undefined) {
|
|
260
245
|
const hasFiles = Object.values(opts.form).some(
|
|
261
246
|
(v) =>
|
|
@@ -268,35 +253,42 @@ export class HTTPClient {
|
|
|
268
253
|
(typeof File !== "undefined" && item instanceof File) ||
|
|
269
254
|
(typeof Blob !== "undefined" && item instanceof Blob),
|
|
270
255
|
)),
|
|
271
|
-
)
|
|
256
|
+
)
|
|
272
257
|
|
|
273
258
|
if (hasFiles) {
|
|
274
|
-
const fd = new FormData()
|
|
259
|
+
const fd = new FormData()
|
|
275
260
|
for (const [k, v] of Object.entries(opts.form)) {
|
|
276
|
-
if (v === undefined || v === null) continue
|
|
261
|
+
if (v === undefined || v === null) continue
|
|
277
262
|
if (typeof FileList !== "undefined" && v instanceof FileList) {
|
|
278
|
-
for (let i = 0; i < v.length; i++) {
|
|
263
|
+
for (let i = 0; i < v.length; i++) {
|
|
264
|
+
const f = v[i]
|
|
265
|
+
if (f) fd.append(k, f)
|
|
266
|
+
}
|
|
279
267
|
} else if (Array.isArray(v)) {
|
|
280
268
|
for (const item of v) {
|
|
281
|
-
if (item instanceof File || item instanceof Blob)
|
|
282
|
-
|
|
283
|
-
|
|
269
|
+
if (item instanceof File || item instanceof Blob) fd.append(k, item)
|
|
270
|
+
else {
|
|
271
|
+
const s = coerceParam(item)
|
|
272
|
+
if (s !== null) fd.append(k, s)
|
|
273
|
+
}
|
|
284
274
|
}
|
|
285
275
|
} else if (v instanceof File || v instanceof Blob) {
|
|
286
|
-
fd.append(k, v)
|
|
276
|
+
fd.append(k, v)
|
|
287
277
|
} else {
|
|
288
|
-
const s = coerceParam(v)
|
|
278
|
+
const s = coerceParam(v)
|
|
279
|
+
if (s !== null) fd.append(k, s)
|
|
289
280
|
}
|
|
290
281
|
}
|
|
291
282
|
/* don't set content-type — browser sets multipart boundary */
|
|
292
|
-
body = fd
|
|
283
|
+
body = fd
|
|
293
284
|
} else {
|
|
294
|
-
headers.set("content-type", "application/x-www-form-urlencoded")
|
|
295
|
-
const params = new URLSearchParams()
|
|
285
|
+
headers.set("content-type", "application/x-www-form-urlencoded")
|
|
286
|
+
const params = new URLSearchParams()
|
|
296
287
|
for (const [k, v] of Object.entries(opts.form)) {
|
|
297
|
-
const s = coerceParam(v)
|
|
288
|
+
const s = coerceParam(v)
|
|
289
|
+
if (s !== null) params.set(k, s)
|
|
298
290
|
}
|
|
299
|
-
body = params.toString()
|
|
291
|
+
body = params.toString()
|
|
300
292
|
}
|
|
301
293
|
}
|
|
302
294
|
|
|
@@ -309,21 +301,21 @@ export class HTTPClient {
|
|
|
309
301
|
reqCtx.selector = _requestMeta.selector
|
|
310
302
|
}
|
|
311
303
|
for (const hook of this._config.onRequest) {
|
|
312
|
-
await hook(reqCtx)
|
|
304
|
+
await hook(reqCtx)
|
|
313
305
|
}
|
|
314
|
-
if (reqCtx.body !== body) body = reqCtx.body
|
|
306
|
+
if (reqCtx.body !== body) body = reqCtx.body
|
|
315
307
|
}
|
|
316
308
|
|
|
317
|
-
const { signal, cleanup } = buildSignal(this._config, opts)
|
|
318
|
-
const init: RequestInit = { body, headers, method, signal }
|
|
309
|
+
const { signal, cleanup } = buildSignal(this._config, opts)
|
|
310
|
+
const init: RequestInit = { body, headers, method, signal }
|
|
319
311
|
|
|
320
|
-
if (this._config.credentials) init.credentials = this._config.credentials
|
|
321
|
-
if (this._config.mode) init.mode = this._config.mode
|
|
312
|
+
if (this._config.credentials) init.credentials = this._config.credentials
|
|
313
|
+
if (this._config.mode) init.mode = this._config.mode
|
|
322
314
|
|
|
323
315
|
try {
|
|
324
316
|
/* fail fast if the signal is already aborted — matches native fetch semantics */
|
|
325
|
-
signal?.throwIfAborted()
|
|
326
|
-
let response = await this._fetch(url, init)
|
|
317
|
+
signal?.throwIfAborted()
|
|
318
|
+
let response = await this._fetch(url, init)
|
|
327
319
|
|
|
328
320
|
/* onResponse interceptors */
|
|
329
321
|
if (this._config.onResponse) {
|
|
@@ -335,12 +327,10 @@ export class HTTPClient {
|
|
|
335
327
|
response,
|
|
336
328
|
retry: () => {
|
|
337
329
|
if (isRetry) throw new Error("Max 1 retry per request")
|
|
338
|
-
return this._doRequest(method, path, opts, true, _requestMeta).then(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
343
|
-
)
|
|
330
|
+
return this._doRequest(method, path, opts, true, _requestMeta).then(async (r) => {
|
|
331
|
+
if (!r.response.ok) throw await parseErrorAsClientError(r.response)
|
|
332
|
+
return r.response
|
|
333
|
+
})
|
|
344
334
|
},
|
|
345
335
|
state: this._config.state ?? {},
|
|
346
336
|
url,
|
|
@@ -351,17 +341,17 @@ export class HTTPClient {
|
|
|
351
341
|
resCtx.selector = _requestMeta.selector
|
|
352
342
|
}
|
|
353
343
|
for (const hook of this._config.onResponse) {
|
|
354
|
-
const result = await hook(resCtx)
|
|
344
|
+
const result = await hook(resCtx)
|
|
355
345
|
if (result instanceof Response) {
|
|
356
|
-
response = result
|
|
357
|
-
resCtx.response = result
|
|
346
|
+
response = result
|
|
347
|
+
resCtx.response = result
|
|
358
348
|
}
|
|
359
349
|
}
|
|
360
350
|
}
|
|
361
351
|
|
|
362
|
-
return { response }
|
|
352
|
+
return { response }
|
|
363
353
|
} finally {
|
|
364
|
-
cleanup()
|
|
354
|
+
cleanup()
|
|
365
355
|
}
|
|
366
356
|
}
|
|
367
357
|
|
|
@@ -380,19 +370,14 @@ export class HTTPClient {
|
|
|
380
370
|
}
|
|
381
371
|
|
|
382
372
|
/** Throw mode — throws ClientError on non-2xx */
|
|
383
|
-
async request(
|
|
384
|
-
method
|
|
385
|
-
path: string,
|
|
386
|
-
opts: RequestOptions,
|
|
387
|
-
_requestMeta?: RequestMeta,
|
|
388
|
-
): Promise<unknown> {
|
|
389
|
-
const { response } = await this._doRequest(method, path, opts, false, _requestMeta);
|
|
373
|
+
async request(method: string, path: string, opts: RequestOptions, _requestMeta?: RequestMeta): Promise<unknown> {
|
|
374
|
+
const { response } = await this._doRequest(method, path, opts, false, _requestMeta)
|
|
390
375
|
|
|
391
376
|
if (!response.ok) {
|
|
392
|
-
throw await parseErrorAsClientError(response)
|
|
377
|
+
throw await parseErrorAsClientError(response)
|
|
393
378
|
}
|
|
394
379
|
|
|
395
|
-
return this._parseBody(response)
|
|
380
|
+
return this._parseBody(response)
|
|
396
381
|
}
|
|
397
382
|
|
|
398
383
|
/** Safe mode — returns { data, error, response, status } — error is the raw parsed body */
|
|
@@ -402,41 +387,37 @@ export class HTTPClient {
|
|
|
402
387
|
opts: RequestOptions,
|
|
403
388
|
_requestMeta?: RequestMeta,
|
|
404
389
|
): Promise<{
|
|
405
|
-
data: unknown
|
|
406
|
-
error: unknown
|
|
407
|
-
response: Response
|
|
408
|
-
status: number
|
|
390
|
+
data: unknown
|
|
391
|
+
error: unknown
|
|
392
|
+
response: Response
|
|
393
|
+
status: number
|
|
409
394
|
}> {
|
|
410
|
-
const { response } = await this._doRequest(method, path, opts, false, _requestMeta)
|
|
395
|
+
const { response } = await this._doRequest(method, path, opts, false, _requestMeta)
|
|
411
396
|
|
|
412
397
|
if (!response.ok) {
|
|
413
|
-
const preserved = response.clone()
|
|
414
|
-
const error = await parseErrorBody(response)
|
|
398
|
+
const preserved = response.clone()
|
|
399
|
+
const error = await parseErrorBody(response)
|
|
415
400
|
return {
|
|
416
401
|
data: null,
|
|
417
402
|
error,
|
|
418
403
|
response: preserved,
|
|
419
404
|
status: response.status,
|
|
420
|
-
}
|
|
405
|
+
}
|
|
421
406
|
}
|
|
422
407
|
|
|
423
|
-
let data: unknown
|
|
408
|
+
let data: unknown
|
|
424
409
|
try {
|
|
425
|
-
data = await this._parseBody(response)
|
|
410
|
+
data = await this._parseBody(response)
|
|
426
411
|
} catch (e) {
|
|
427
|
-
return { data: null, error: e, response, status: response.status }
|
|
412
|
+
return { data: null, error: e, response, status: response.status }
|
|
428
413
|
}
|
|
429
|
-
return { data, error: null, response, status: response.status }
|
|
414
|
+
return { data, error: null, response, status: response.status }
|
|
430
415
|
}
|
|
431
416
|
|
|
432
|
-
requestStream(
|
|
433
|
-
method: string,
|
|
434
|
-
path: string,
|
|
435
|
-
opts: RequestOptions,
|
|
436
|
-
): AsyncIterable<SSEEvent> {
|
|
417
|
+
requestStream(method: string, path: string, opts: RequestOptions): AsyncIterable<SSEEvent> {
|
|
437
418
|
return {
|
|
438
419
|
[Symbol.asyncIterator]: () => this._doStream(method, path, opts),
|
|
439
|
-
}
|
|
420
|
+
}
|
|
440
421
|
}
|
|
441
422
|
|
|
442
423
|
private async *_doStream(
|
|
@@ -444,59 +425,59 @@ export class HTTPClient {
|
|
|
444
425
|
path: string,
|
|
445
426
|
opts: RequestOptions,
|
|
446
427
|
): AsyncGenerator<SSEEvent, void, undefined> {
|
|
447
|
-
const url = buildURL(this._config, path, opts)
|
|
448
|
-
const headers = await buildHeaders(this._config, opts, { method, path })
|
|
449
|
-
headers.set("accept", "text/event-stream")
|
|
428
|
+
const url = buildURL(this._config, path, opts)
|
|
429
|
+
const headers = await buildHeaders(this._config, opts, { method, path })
|
|
430
|
+
headers.set("accept", "text/event-stream")
|
|
450
431
|
|
|
451
432
|
if (opts.lastEventId) {
|
|
452
|
-
headers.set("last-event-id", opts.lastEventId)
|
|
433
|
+
headers.set("last-event-id", opts.lastEventId)
|
|
453
434
|
}
|
|
454
435
|
|
|
455
436
|
/* no timeout on streams — they're long-lived, but user abort still composes */
|
|
456
|
-
const { signal, cleanup } = buildSignal(this._config, opts, true)
|
|
457
|
-
const init: RequestInit = { headers, method, signal }
|
|
437
|
+
const { signal, cleanup } = buildSignal(this._config, opts, true)
|
|
438
|
+
const init: RequestInit = { headers, method, signal }
|
|
458
439
|
|
|
459
|
-
if (this._config.credentials) init.credentials = this._config.credentials
|
|
460
|
-
if (this._config.mode) init.mode = this._config.mode
|
|
440
|
+
if (this._config.credentials) init.credentials = this._config.credentials
|
|
441
|
+
if (this._config.mode) init.mode = this._config.mode
|
|
461
442
|
|
|
462
443
|
try {
|
|
463
|
-
signal?.throwIfAborted()
|
|
464
|
-
const response = await this._fetch(url, init)
|
|
444
|
+
signal?.throwIfAborted()
|
|
445
|
+
const response = await this._fetch(url, init)
|
|
465
446
|
|
|
466
447
|
if (!response.ok) {
|
|
467
|
-
throw await parseErrorAsClientError(response)
|
|
448
|
+
throw await parseErrorAsClientError(response)
|
|
468
449
|
}
|
|
469
450
|
|
|
470
|
-
if (!response.body) return
|
|
451
|
+
if (!response.body) return
|
|
471
452
|
|
|
472
|
-
yield* parseSSEStream(response.body)
|
|
453
|
+
yield* parseSSEStream(response.body)
|
|
473
454
|
} finally {
|
|
474
|
-
cleanup()
|
|
455
|
+
cleanup()
|
|
475
456
|
}
|
|
476
457
|
}
|
|
477
458
|
|
|
478
459
|
buildUrl(path: string, opts: RequestOptions): string {
|
|
479
|
-
return buildURL(this._config, path, opts)
|
|
460
|
+
return buildURL(this._config, path, opts)
|
|
480
461
|
}
|
|
481
462
|
|
|
482
463
|
buildPath(path: string, opts: RequestOptions): string {
|
|
483
|
-
let resolved = path
|
|
464
|
+
let resolved = path
|
|
484
465
|
if (opts.params) {
|
|
485
|
-
resolved = interpolatePath(path, opts.params)
|
|
466
|
+
resolved = interpolatePath(path, opts.params)
|
|
486
467
|
}
|
|
487
468
|
|
|
488
|
-
if (!opts.search) return resolved
|
|
469
|
+
if (!opts.search) return resolved
|
|
489
470
|
|
|
490
|
-
const serializer = this._config.buildSearchParams ?? defaultSearchParams
|
|
491
|
-
const params = serializer(opts.search)
|
|
492
|
-
if (this._config.sortSearchParams) params.sort()
|
|
471
|
+
const serializer = this._config.buildSearchParams ?? defaultSearchParams
|
|
472
|
+
const params = serializer(opts.search)
|
|
473
|
+
if (this._config.sortSearchParams) params.sort()
|
|
493
474
|
|
|
494
|
-
const qs = params.toString()
|
|
495
|
-
return qs ? `${resolved}?${qs}` : resolved
|
|
475
|
+
const qs = params.toString()
|
|
476
|
+
return qs ? `${resolved}?${qs}` : resolved
|
|
496
477
|
}
|
|
497
478
|
|
|
498
479
|
buildWSUrl(path: string, opts: RequestOptions): string {
|
|
499
|
-
const url = buildURL(this._config, path, opts)
|
|
500
|
-
return url.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://")
|
|
480
|
+
const url = buildURL(this._config, path, opts)
|
|
481
|
+
return url.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://")
|
|
501
482
|
}
|
|
502
483
|
}
|