@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.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
@@ -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
- ctx: OnResponseContext,
60
- ) => Promise<Response | undefined> | Response | undefined
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) { const s = coerceParam(item); if (s !== null) params.append(k, s); }
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); if (s !== null) params.set(k, s);
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
- url: URL,
114
- search: Record<string, unknown>,
115
- config: ClientConfig,
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
- config: ClientConfig,
127
- path: string,
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
- config: ClientConfig,
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 = typeof body === "object" && body !== null && "message" in body
225
- ? (body as Record<string, unknown>).message
226
- : undefined
227
- const message = typeof msgVal === "string"
228
- /* oxlint-disable-next-line no-control-regex -- intentional strip of ASCII control characters */
229
- ? msgVal.replace(/[\x00-\x1f]/g, "").slice(0, 512)
230
- : `HTTP ${response.status}`
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++) { const f = v[i]; if (f) fd.append(k, f) }
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
- fd.append(k, item);
283
- else { const s = coerceParam(item); if (s !== null) fd.append(k, s); }
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); if (s !== null) fd.append(k, s);
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); if (s !== null) params.set(k, s);
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
- async (r) => {
340
- if (!r.response.ok) throw await parseErrorAsClientError(r.response)
341
- return r.response
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: string,
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
  }