@kubb/plugin-client 5.12.0-canary.20261109T134631 → 6.0.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 +1 -1
- package/dist/client-Dq0z61hu.d.ts +554 -0
- package/dist/index.cjs +1396 -802
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -74
- package/dist/index.d.ts +126 -74
- package/dist/index.js +1394 -804
- package/dist/index.js.map +1 -1
- package/dist/serializers-BRgfPGr1.d.ts +119 -0
- package/dist/{standardSchema-BShJWz2i.d.ts → standardSchema-CGuiSK7F.d.ts} +15 -37
- package/dist/templates/client.d.ts +3 -2
- package/dist/templates/core.d.ts +3 -3
- package/dist/templates/mediaTypes/formData.d.ts +5 -0
- package/dist/templates/mediaTypes/urlencoded.d.ts +5 -0
- package/dist/templates/mediaTypes.d.ts +2 -0
- package/dist/templates/serializers/cookies.d.ts +5 -0
- package/dist/templates/serializers/headers.d.ts +5 -0
- package/dist/templates/serializers/path.d.ts +5 -0
- package/dist/templates/serializers/pathStyles.d.ts +5 -0
- package/dist/templates/serializers/query.d.ts +5 -0
- package/dist/templates/serializers/queryStyles.d.ts +14 -0
- package/dist/templates/serializers.d.ts +2 -2
- package/dist/templates/sse.d.ts +20 -113
- package/dist/templates/standardSchema.d.ts +1 -1
- package/docs/guides/authentication.md +9 -1
- package/docs/guides/migrate-to-v6.md +139 -0
- package/docs/guides/validate-requests-and-responses.md +8 -3
- package/docs/guides/write-your-client.md +117 -11
- package/docs/options.md +24 -0
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +8 -7
- package/src/builders/callConfig.ts +80 -0
- package/src/builders/contentType.ts +35 -0
- package/src/builders/generics.ts +29 -0
- package/src/builders/operationBody.ts +43 -0
- package/src/builders/operationData.ts +64 -0
- package/src/builders/returnStatement.ts +103 -0
- package/src/builders/security.ts +125 -0
- package/src/builders/signature.ts +71 -0
- package/src/builders/styles.ts +94 -0
- package/src/builders/validator.ts +84 -0
- package/src/builders/validatorOptions.ts +53 -0
- package/src/clientTypes.ts +195 -0
- package/src/components/ClientRuntime.tsx +97 -0
- package/src/components/Operation.tsx +72 -0
- package/src/components/SdkClient.tsx +100 -0
- package/src/components/SdkFacade.tsx +35 -0
- package/src/generators/clientGenerator.tsx +91 -6
- package/src/generators/sdkGenerator.tsx +209 -0
- package/src/index.ts +3 -1
- package/src/macros.ts +9 -0
- package/src/plugin.ts +9 -9
- package/src/resolver.ts +28 -0
- package/src/setup.ts +90 -0
- package/src/types.ts +16 -2
- package/templates/client.ts +2 -1
- package/templates/core.ts +735 -286
- package/templates/mediaTypes/formData.ts +59 -0
- package/templates/mediaTypes/urlencoded.ts +33 -0
- package/templates/mediaTypes.ts +147 -0
- package/templates/serializers/cookies.ts +58 -0
- package/templates/serializers/headers.ts +35 -0
- package/templates/serializers/path.ts +19 -0
- package/templates/serializers/pathStyles.ts +27 -0
- package/templates/serializers/query.ts +21 -0
- package/templates/serializers/queryStyles.ts +107 -0
- package/templates/serializers.ts +48 -364
- package/templates/sse.ts +87 -178
- package/templates/standardSchema.ts +16 -37
- package/dist/client-BxO6Dygu.d.ts +0 -355
- package/dist/serializers-Ds8zZwOO.d.ts +0 -190
package/templates/sse.ts
CHANGED
|
@@ -1,86 +1,5 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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
|
-
|
|
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
|
|
155
|
-
const
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
config
|
|
213
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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,
|