@kubb/plugin-client 5.12.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/dist/client-C-0-puI2.d.ts +554 -0
  3. package/dist/index.cjs +1377 -784
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +117 -78
  6. package/dist/index.d.ts +117 -78
  7. package/dist/index.js +1375 -786
  8. package/dist/index.js.map +1 -1
  9. package/dist/templates/client.d.ts +3 -2
  10. package/dist/templates/core.d.ts +3 -3
  11. package/dist/templates/mediaTypes/formData.d.ts +5 -0
  12. package/dist/templates/mediaTypes/urlencoded.d.ts +5 -0
  13. package/dist/templates/mediaTypes.d.ts +2 -0
  14. package/dist/templates/serializers/cookies.d.ts +5 -0
  15. package/dist/templates/serializers/headers.d.ts +5 -0
  16. package/dist/templates/serializers/path.d.ts +5 -0
  17. package/dist/templates/serializers/pathStyles.d.ts +5 -0
  18. package/dist/templates/serializers/query.d.ts +5 -0
  19. package/dist/templates/serializers/queryStyles.d.ts +14 -0
  20. package/dist/templates/serializers.d.ts +118 -2
  21. package/dist/templates/sse.d.ts +20 -113
  22. package/dist/templates/standardSchema.d.ts +81 -2
  23. package/docs/guides/authentication.md +12 -2
  24. package/docs/guides/calling-operations.md +8 -6
  25. package/docs/guides/migrate-to-v6.md +139 -0
  26. package/docs/guides/validate-requests-and-responses.md +13 -8
  27. package/docs/guides/write-your-client.md +118 -12
  28. package/docs/options.md +5 -3
  29. package/docs/overview.md +3 -1
  30. package/kubb-extension.json +21 -3
  31. package/package.json +6 -5
  32. package/src/builders/callConfig.ts +80 -0
  33. package/src/builders/contentType.ts +35 -0
  34. package/src/builders/generics.ts +29 -0
  35. package/src/builders/operationBody.ts +43 -0
  36. package/src/builders/operationData.ts +64 -0
  37. package/src/builders/returnStatement.ts +103 -0
  38. package/src/builders/security.ts +125 -0
  39. package/src/builders/signature.ts +71 -0
  40. package/src/builders/styles.ts +94 -0
  41. package/src/builders/validator.ts +84 -0
  42. package/src/builders/validatorOptions.ts +53 -0
  43. package/src/clientTypes.ts +195 -0
  44. package/src/components/ClientRuntime.tsx +97 -0
  45. package/src/components/Operation.tsx +72 -0
  46. package/src/components/SdkClient.tsx +100 -0
  47. package/src/components/SdkFacade.tsx +35 -0
  48. package/src/generators/clientGenerator.tsx +91 -6
  49. package/src/generators/sdkGenerator.tsx +208 -0
  50. package/src/index.ts +3 -1
  51. package/src/macros.ts +9 -0
  52. package/src/plugin.ts +7 -8
  53. package/src/resolver.ts +28 -0
  54. package/src/setup.ts +90 -0
  55. package/src/types.ts +4 -2
  56. package/templates/client.ts +2 -1
  57. package/templates/core.ts +735 -286
  58. package/templates/mediaTypes/formData.ts +59 -0
  59. package/templates/mediaTypes/urlencoded.ts +33 -0
  60. package/templates/mediaTypes.ts +147 -0
  61. package/templates/serializers/cookies.ts +58 -0
  62. package/templates/serializers/headers.ts +35 -0
  63. package/templates/serializers/path.ts +19 -0
  64. package/templates/serializers/pathStyles.ts +27 -0
  65. package/templates/serializers/query.ts +21 -0
  66. package/templates/serializers/queryStyles.ts +107 -0
  67. package/templates/serializers.ts +48 -364
  68. package/templates/sse.ts +87 -178
  69. package/templates/standardSchema.ts +16 -37
  70. package/dist/client-BxO6Dygu.d.ts +0 -355
  71. package/dist/serializers-Ds8zZwOO.d.ts +0 -190
  72. package/dist/standardSchema-BShJWz2i.d.ts +0 -104
package/templates/sse.ts CHANGED
@@ -1,86 +1,5 @@
1
- import type { DataShape, NativeRequest, NativeResponse, Options, RequestResult, SuccessOf } from './client.ts'
2
- import { isValidationEnabled, runValidator, type StandardSchemaValidator, type ValidateOption, type ValidationErrorHandler } from './standardSchema.ts'
1
+ import type { EventStreamOptions, MediaType, MediaTypes, ServerSentEvent } from './client.ts'
3
2
 
4
- /**
5
- * One decoded Server-Sent Event. `data` is parsed as JSON when valid and kept as the raw string otherwise,
6
- * `event` is `message` when the server sent no `event` field, and `id` is the last event ID the stream has set.
7
- */
8
- export type ServerSentEvent<TData = unknown> = {
9
- data: TData
10
- event: string
11
- id?: string
12
- retry?: number
13
- }
14
-
15
- /** An event the document types by name: the member's own `event` and `data`, plus the `id` and `retry` any event may carry. */
16
- export type TypedEvent<TEvent> = TEvent & Pick<ServerSentEvent, 'id' | 'retry'>
17
-
18
- /** Per-call options of a streamed operation, passed next to its request params. */
19
- export type EventStreamOptions = {
20
- /**
21
- * The largest number of characters, line breaks included, one event may hold before the blank line that ends it.
22
- * A larger event throws a `RangeError` and cancels the stream. Set `Infinity` to turn the limit off.
23
- * @default 16777216
24
- */
25
- maxEventSize?: number
26
- /**
27
- * Ends the stream when an event's raw `data` equals this value, such as `[DONE]`.
28
- */
29
- terminator?: string
30
- /**
31
- * Maps each event before validation.
32
- */
33
- transform?: (event: ServerSentEvent) => ServerSentEvent
34
- }
35
-
36
- /** The options of a streamed operation: the call's `Options` with the per-call stream options, as `Options` is for a regular call. */
37
- export type StreamOptions<TData extends DataShape, ThrowOnError extends boolean = true> = Options<TData, ThrowOnError> & EventStreamOptions
38
-
39
- /**
40
- * The validation settings a call or its client may set.
41
- */
42
- type ValidationSettings = { validate?: ValidateOption; onValidationError?: ValidationErrorHandler }
43
-
44
- /**
45
- * What the client resolves a streamed call to: the body as a stream or a decoded value, its content type, and the native response.
46
- */
47
- export type EventStreamCall<TResponse> = { data: unknown; error?: unknown; status?: number; contentType?: string; request?: NativeRequest; response: TResponse }
48
-
49
- /** The payload wrapped in `ServerSentEvent`, or the item itself when the document types it as a whole event. */
50
- type StreamEvent<TData> = TData extends { event: string } ? TData : ServerSentEvent<TData>
51
-
52
- /** Parsed events with the fields a `RequestResult` carries. `TData` is the payload, or the typed event. Iterating the result reads `stream`. */
53
- export type EventStreamResult<TData = unknown, TResponse = NativeResponse> = AsyncIterable<StreamEvent<TData>> & {
54
- stream: AsyncGenerator<StreamEvent<TData>>
55
- /** The same events as `stream` on a 2xx response, so `.unwrap()` and `returnType: 'data'` return them. */
56
- data: AsyncGenerator<StreamEvent<TData>> | undefined
57
- status: number | undefined
58
- /** The error body of a failed call with `throwOnError: false`, whose `stream` then yields nothing. */
59
- error: unknown
60
- contentType: string | undefined
61
- request: NativeRequest | undefined
62
- response: TResponse
63
- /**
64
- * The last event ID the server sent, to resume from with a `Last-Event-ID` header on a new call. Empty until the server sends `id`.
65
- */
66
- readonly lastEventId: string
67
- }
68
-
69
- type StreamParts<TData> = AsyncIterable<StreamEvent<TData>> & { stream: AsyncGenerator<StreamEvent<TData>>; readonly lastEventId: string }
70
-
71
- type WithEvents<TResult, TData> = TResult extends { error: undefined } ? Omit<TResult, 'data'> & { data: AsyncGenerator<StreamEvent<TData>> } : TResult
72
-
73
- /** A stream call's result, as `RequestResult` is a regular call's: `status` narrows `error`, and the events are its `data`. Branching on `ThrowOnError` lets a generated call cast to it. */
74
- export type StreamResult<TResponses, ThrowOnError extends boolean = true, TData = SuccessOf<TResponses>> = ThrowOnError extends true
75
- ? WithEvents<RequestResult<TResponses, true>, TData> & StreamParts<TData>
76
- : WithEvents<RequestResult<TResponses, false>, TData> & StreamParts<TData>
77
-
78
- /** What a stream operation returns with `returnType: 'data'`, as `UnwrappedResult` is for a regular call. */
79
- export type UnwrappedStreamResult<TResponses, ThrowOnError extends boolean = true, TData = SuccessOf<TResponses>> = ThrowOnError extends true
80
- ? AsyncGenerator<StreamEvent<TData>>
81
- : StreamResult<TResponses, false, TData>
82
-
83
- /** The last event ID, shared with the result so it can be read after the loop. */
84
3
  type Cursor = { lastEventId: string }
85
4
 
86
5
  async function* readBytes(stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>): AsyncGenerator<Uint8Array> {
@@ -101,7 +20,26 @@ async function* readBytes(stream: ReadableStream<Uint8Array> | AsyncIterable<Uin
101
20
  }
102
21
  }
103
22
 
104
- function parseData<TData>(raw: string): TData {
23
+ async function* readLines(stream: EventStreamInput): AsyncGenerator<{ lines: Array<string>; rest: string }> {
24
+ const decoder = new TextDecoder()
25
+ let rest = ''
26
+ let endsWithCR = false
27
+ for await (const chunk of readBytes(stream)) {
28
+ const decoded = decoder.decode(chunk, { stream: true })
29
+ if (!decoded) continue
30
+ // A CRLF split across chunks already ended its line on the `\r`.
31
+ const text: string = endsWithCR && decoded.startsWith('\n') ? decoded.slice(1) : decoded
32
+ endsWithCR = text.endsWith('\r')
33
+
34
+ const lines = text.split(/\r\n|\r|\n/)
35
+ lines[0] = rest + lines[0]
36
+ rest = lines.pop() ?? ''
37
+ yield { lines, rest }
38
+ }
39
+ }
40
+
41
+ function parseData<TData>(raw: string, event: string, rawData?: boolean | Array<string>): TData {
42
+ if (rawData === true || (rawData && rawData.includes(event))) return raw as TData
105
43
  try {
106
44
  return JSON.parse(raw) as TData
107
45
  } catch {
@@ -109,27 +47,20 @@ function parseData<TData>(raw: string): TData {
109
47
  }
110
48
  }
111
49
 
112
- /**
113
- * Parses a `text/event-stream` body as the WHATWG event stream specification does: an event is dispatched at its blank line
114
- * when it holds `data`, `event` defaults to `message`, `id` carries across events until an empty `id` resets it, `retry` needs
115
- * all digits, and an unfinished event at the end is dropped. `data` is parsed as JSON when it is valid and kept as text
116
- * otherwise. Consume with `for await`, and break the loop to cancel the stream.
117
- */
118
50
  type EventStreamInput = ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>
119
51
 
52
+ /**
53
+ * Parses a `text/event-stream` body into events, as the WHATWG spec does. Break the loop to cancel the stream.
54
+ */
120
55
  export async function* parseEventStream<TData = unknown>(
121
- input: EventStreamInput | (Pick<EventStreamOptions, 'maxEventSize' | 'terminator'> & { stream: EventStreamInput; cursor?: Cursor }),
56
+ input: EventStreamInput | (Pick<EventStreamOptions, 'maxEventSize' | 'terminator' | 'rawData'> & { stream: EventStreamInput; cursor?: Cursor | undefined }),
122
57
  ): AsyncGenerator<ServerSentEvent<TData>> {
123
- // The stream alone is the form earlier releases took.
124
- const { stream, maxEventSize = 16 * 1024 * 1024, terminator, cursor = { lastEventId: '' } } = isStreamBody(input) ? { stream: input } : input
125
- const decoder = new TextDecoder()
58
+ const { stream, maxEventSize = 16 * 1024 * 1024, terminator, rawData, cursor = { lastEventId: '' } } = isStreamBody(input) ? { stream: input } : input
126
59
  const tooLarge = `Server-sent event exceeds maxEventSize of ${maxEventSize} characters`
127
60
  let type = ''
128
61
  let data: Array<string> = []
129
62
  let retry: number | undefined
130
63
  let size = 0
131
- let line = ''
132
- let endsWithCR = false
133
64
  let pendingId = cursor.lastEventId
134
65
 
135
66
  const readField = (part: string) => {
@@ -151,25 +82,16 @@ export async function* parseEventStream<TData = unknown>(
151
82
  }
152
83
  }
153
84
 
154
- for await (const chunk of readBytes(stream)) {
155
- const decoded = decoder.decode(chunk, { stream: true })
156
- if (!decoded) continue
157
- // A CRLF split across chunks already ended its line on the `\r`.
158
- const text: string = endsWithCR && decoded.startsWith('\n') ? decoded.slice(1) : decoded
159
- endsWithCR = text.endsWith('\r')
160
-
161
- const parts = text.split(/\r\n|\r|\n/)
162
- parts[0] = line + parts[0]
163
- line = parts.pop() ?? ''
164
- for (const part of parts) {
85
+ for await (const { lines, rest: line } of readLines(stream)) {
86
+ for (const part of lines) {
165
87
  if (!part) {
166
- // The ID is committed at the blank line, so a drop inside an event does not report an ID past the last full event.
88
+ // Committed at the blank line, so a dropped stream never reports an ID past the last full event.
167
89
  cursor.lastEventId = pendingId
168
90
  if (data.length) {
169
91
  const raw = data.join('\n')
170
92
  if (raw === terminator) return
171
93
  yield {
172
- data: parseData<TData>(raw),
94
+ data: parseData<TData>(raw, type || 'message', rawData),
173
95
  event: type || 'message',
174
96
  ...(cursor.lastEventId ? { id: cursor.lastEventId } : {}),
175
97
  ...(retry !== undefined ? { retry } : {}),
@@ -181,12 +103,10 @@ export async function* parseEventStream<TData = unknown>(
181
103
  size = 0
182
104
  continue
183
105
  }
184
- // A comment line is dropped and does not count toward the event, but must fit the limit on its own.
185
106
  if (part.startsWith(':')) {
186
107
  if (part.length + 1 > maxEventSize) throw new RangeError(tooLarge)
187
108
  continue
188
109
  }
189
- // A finished line counts one more character for its line break.
190
110
  if (size + part.length + 1 > maxEventSize) throw new RangeError(tooLarge)
191
111
  size += part.length + 1
192
112
  readField(part)
@@ -195,80 +115,69 @@ export async function* parseEventStream<TData = unknown>(
195
115
  }
196
116
  }
197
117
 
118
+ /**
119
+ * Parses a JSON Lines body into one value per line. Break the loop to cancel the stream.
120
+ */
121
+ export async function* parseJsonLines<TData = unknown>({
122
+ stream,
123
+ maxEventSize = 16 * 1024 * 1024,
124
+ terminator,
125
+ }: Pick<EventStreamOptions, 'maxEventSize' | 'terminator'> & { stream: EventStreamInput }): AsyncGenerator<TData> {
126
+ const tooLarge = `JSON line exceeds maxEventSize of ${maxEventSize} characters`
127
+ async function* lines(): AsyncGenerator<string> {
128
+ let last = ''
129
+ for await (const chunk of readLines(stream)) {
130
+ yield* chunk.lines
131
+ last = chunk.rest
132
+ if (last.length > maxEventSize) throw new RangeError(tooLarge)
133
+ }
134
+ yield last
135
+ }
136
+
137
+ for await (const raw of lines()) {
138
+ if (raw.length + 1 > maxEventSize) throw new RangeError(tooLarge)
139
+ // shortcut: a json-seq record must fit on one line, so a pretty-printed record fails to parse; split on the separator to read one.
140
+ const line = raw.split('\u001e').join('').trim()
141
+ if (!line) continue
142
+ if (line === terminator) return
143
+ yield JSON.parse(line) as TData
144
+ }
145
+ }
146
+
198
147
  function isStreamBody(data: unknown): data is ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> {
199
148
  return !!data && typeof data === 'object' && (Symbol.asyncIterator in data || 'getReader' in data)
200
149
  }
201
150
 
151
+ async function* mapEvents(events: AsyncIterable<ServerSentEvent>, transform: (event: ServerSentEvent) => ServerSentEvent): AsyncGenerator<ServerSentEvent> {
152
+ for await (const event of events) yield transform(event)
153
+ }
154
+
202
155
  /**
203
- * Reads a call as typed Server-Sent Events, transformed and validated one by one, until the terminator or the end of the
204
- * stream. A successful call whose body is not a stream, such as a decoded JSON answer, throws a `TypeError` with the body
205
- * as its `cause`. A dropped connection ends the loop with the transport's error; `lastEventId` on the result is the ID
206
- * to resume from.
156
+ * The `text/event-stream` handler. A whole body, such as an error, is read as text.
207
157
  */
208
- export async function toEventStream<TData = unknown, TResponse = NativeResponse>(
209
- call: Promise<EventStreamCall<TResponse>>,
210
- {
211
- client,
212
- config: options = {},
213
- ...meta
214
- }: {
215
- /**
216
- * The client the call went through, for the validation settings a call does not set itself.
217
- */
218
- client?: { getConfig: () => ValidationSettings }
219
- config?: EventStreamOptions & ValidationSettings
220
- /**
221
- * `event` when the document types each event with its own `event` and `data`, or `data` when it types only the payload.
222
- * @default 'data'
223
- */
224
- shape?: 'event' | 'data'
225
- /**
226
- * Validates each event (`shape: 'event'`) or its `data` (`shape: 'data'`), after `transform`.
227
- */
228
- validator?: StandardSchemaValidator
229
- } = {},
230
- ): Promise<EventStreamResult<TData, TResponse>> {
231
- const defaults = client?.getConfig() ?? {}
232
- const onValidationError = options.onValidationError ?? defaults.onValidationError
233
- const validator = isValidationEnabled(options.validate ?? defaults.validate, 'response') ? meta.validator : undefined
234
- const cursor: Cursor = { lastEventId: '' }
235
- const { data: body, error, status, contentType, request, response } = await call
236
- const ok = status === undefined || (status >= 200 && status < 300)
237
- // A transport decodes a JSON answer, so a success that is not a stream would otherwise end as an empty stream.
238
- // A 204 or empty answer has no body, `undefined` or `null`, and still ends as an empty stream.
239
- if (ok && body != null && !isStreamBody(body)) {
240
- // `cause` is assigned, since the constructor option needs the ES2022 library a generated client cannot assume.
241
- throw Object.assign(new TypeError(`Expected a text/event-stream body, got ${contentType ?? 'a body without a content type'}`), { cause: body })
242
- }
243
-
244
- const validateEvent = async (event: ServerSentEvent): Promise<ServerSentEvent> => {
245
- if (!validator) return event
246
- const value = meta.shape === 'event' ? event : event.data
247
- const validated = await runValidator({ validator, value, context: { direction: 'response', source: 'body' }, onValidationError })
248
- // The schema may not declare `id` and `retry`, so the parsed value is merged over the event instead of replacing it.
249
- return meta.shape === 'event' ? { ...event, ...(validated as object) } : { ...event, data: validated }
250
- }
158
+ export const eventStream: MediaType = {
159
+ decode: (response) => response.text(),
160
+ stream: (body, { config, cursor }) => {
161
+ const events = parseEventStream({ stream: body, maxEventSize: config.maxEventSize, terminator: config.terminator, rawData: config.rawData, cursor })
162
+ return config.transform ? mapEvents(events, config.transform) : events
163
+ },
164
+ }
251
165
 
252
- async function* events(): AsyncGenerator<StreamEvent<TData>> {
253
- if (!isStreamBody(body)) return
254
- // A consumer `break` or a failed event closes this loop, which cancels the reader.
255
- for await (const event of parseEventStream({ stream: body, maxEventSize: options.maxEventSize, terminator: options.terminator, cursor })) {
256
- yield (await validateEvent(options.transform ? options.transform(event) : event)) as StreamEvent<TData>
257
- }
258
- }
166
+ /**
167
+ * The JSON Lines handler. A whole body, such as an error, is read as text.
168
+ */
169
+ export const jsonLines: MediaType = {
170
+ decode: (response) => response.text(),
171
+ stream: (body, { config }) => parseJsonLines({ stream: body, maxEventSize: config.maxEventSize, terminator: config.terminator }),
172
+ }
259
173
 
260
- const stream = events()
261
- return {
262
- stream,
263
- data: ok ? stream : undefined,
264
- status,
265
- error,
266
- contentType,
267
- request,
268
- response,
269
- get lastEventId() {
270
- return cursor.lastEventId
271
- },
272
- [Symbol.asyncIterator]: () => stream,
273
- }
174
+ /**
175
+ * The Server-Sent Events and JSON Lines handlers, registered when the document streams.
176
+ */
177
+ export const streamMediaTypes: MediaTypes = {
178
+ 'text/event-stream': eventStream,
179
+ 'application/jsonl': jsonLines,
180
+ 'application/x-ndjson': jsonLines,
181
+ 'application/json-seq': jsonLines,
182
+ 'application/*+json-seq': jsonLines,
274
183
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * A Standard Schema-compatible validator: a minimal duck-type covering Zod v3/v4, valibot, and
3
- * arktype schemas. Only the `~standard.validate` method is required at runtime.
2
+ * A Standard Schema validator, such as a Zod, Valibot, or ArkType schema.
4
3
  */
5
4
  export type StandardSchemaValidator<TOutput = unknown> = {
6
5
  readonly '~standard': {
@@ -8,42 +7,31 @@ export type StandardSchemaValidator<TOutput = unknown> = {
8
7
  }
9
8
  }
10
9
 
11
- /**
12
- * The two possible outcomes of a Standard Schema `validate` call. A successful result carries
13
- * `value`; a failed result carries `issues`.
14
- */
15
10
  export type StandardSchemaResult<TOutput> = { readonly value: TOutput; readonly issues?: undefined } | { readonly issues: ReadonlyArray<StandardSchemaIssue> }
16
11
 
17
- /**
18
- * One validation issue from a Standard Schema `validate` call.
19
- */
20
12
  export type StandardSchemaIssue = {
21
13
  readonly message?: string
22
- readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>
14
+ readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }> | undefined
23
15
  }
24
16
 
25
17
  /**
26
- * Thrown by `validateStandardSchema` when validation fails. Carries the raw `issues` array from
27
- * the schema's `validate` result so callers receive a uniform error shape regardless of which
28
- * schema library is in use.
18
+ * Thrown when validation fails, with the schema's `issues`.
29
19
  */
30
20
  export class ParseError extends Error {
31
21
  readonly issues: ReadonlyArray<StandardSchemaIssue>
22
+ // Declared here and set below, since the `cause` constructor option needs the ES2022 library.
23
+ declare readonly cause?: unknown
32
24
 
33
- constructor({ issues, message }: { issues: ReadonlyArray<StandardSchemaIssue>; message?: string }) {
25
+ constructor({ issues, message, cause }: { issues: ReadonlyArray<StandardSchemaIssue>; message?: string; cause?: unknown }) {
34
26
  super(message ?? 'Validation failed')
35
27
  this.name = 'ParseError'
36
28
  this.issues = issues
29
+ if (cause !== undefined) this.cause = cause
37
30
  }
38
31
  }
39
32
 
40
33
  /**
41
- * Validates `value` against a Standard Schema-compatible `schema`. Returns the parsed output on
42
- * success; throws `ParseError` with the schema's `issues` on failure. Handles both sync and async
43
- * `validate` implementations transparently.
44
- *
45
- * @example
46
- * const pet = await validateStandardSchema(PetSchema, rawData)
34
+ * Returns the parsed value, or throws a `ParseError` with the schema's `issues`.
47
35
  */
48
36
  export async function validateStandardSchema<TOutput>(schema: StandardSchemaValidator<TOutput>, value: unknown): Promise<TOutput> {
49
37
  const result = await schema['~standard'].validate(value)
@@ -54,13 +42,8 @@ export async function validateStandardSchema<TOutput>(schema: StandardSchemaVali
54
42
  }
55
43
 
56
44
  /**
57
- * Wraps an object schema so its keys match case-insensitively.
58
- * Keys the schema does not declare pass through untouched.
59
- * The declared keys are validated with `schema` under their declared names.
60
- * Every key, and every issue path, keeps the casing the caller used.
45
+ * Wraps an object schema so its keys match in any letter case. `entries` are the keys the schema declares.
61
46
  *
62
- * @param schema Standard Schema-compatible object schema.
63
- * @param entries The record of keys the passed object schema validates.
64
47
  * @example
65
48
  * ```ts
66
49
  * validator: { headers: toHeadersStandardSchema(putEventHeadersSchema, putEventHeadersSchema.shape) }
@@ -109,25 +92,24 @@ export function toHeadersStandardSchema(schema: StandardSchemaValidator, entries
109
92
  }
110
93
 
111
94
  /**
112
- * Identifies the value and request that failed validation in `onValidationError`.
95
+ * Which value and request failed validation.
113
96
  */
114
97
  export type ValidationErrorContext = {
115
98
  value: unknown
116
99
  direction: 'request' | 'response' | 'error'
117
- source: 'body' | 'path' | 'query' | 'headers'
118
- method?: string
119
- url?: string
120
- status?: number
100
+ source: 'body' | 'path' | 'query' | 'headers' | 'cookies'
101
+ method?: string | undefined
102
+ url?: string | undefined
103
+ status?: number | undefined
121
104
  }
122
105
 
123
106
  /**
124
- * Return `{ value }` to substitute a failed value, including `undefined`. Returning nothing rethrows the `ParseError`.
107
+ * Return `{ value }` to use instead of a failed value. Return nothing to rethrow the `ParseError`.
125
108
  */
126
109
  export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => { value: unknown } | void | Promise<{ value: unknown } | void>
127
110
 
128
111
  /**
129
- * Turns generated validation on or off. `false` skips every validator, an object skips only the directions set to `false`:
130
- * `request` is the body, `params` are path, query, and header params, `response` and `error` are the response bodies.
112
+ * Turns generated validation on or off, for every direction or per direction.
131
113
  * @default true
132
114
  */
133
115
  export type ValidateOption = boolean | { request?: boolean; params?: boolean; response?: boolean; error?: boolean }
@@ -136,9 +118,6 @@ export function isValidationEnabled(validate: ValidateOption | undefined, direct
136
118
  return typeof validate === 'object' ? validate[direction] !== false : validate !== false
137
119
  }
138
120
 
139
- /**
140
- * Runs a validator when there is one, handing a `ParseError` to `onValidationError` first, which may substitute the value.
141
- */
142
121
  export async function runValidator<T>({
143
122
  validator,
144
123
  value,