@znight/http 0.1.1 → 0.1.3
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/LICENSE +1 -1
- package/README.md +83 -32
- package/dist/{adapter-CGSceVEN.d.mts → adapter-J0UXFnNM.d.mts} +7 -1
- package/dist/compiler.d.mts +1 -1
- package/dist/compiler.mjs +82 -13
- package/dist/compiler.mjs.map +1 -1
- package/dist/express.d.mts +1 -1
- package/dist/express.mjs +3 -2
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.d.mts +1 -1
- package/dist/fastify.mjs +254 -42
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.d.mts +1 -1
- package/dist/hono.mjs +272 -67
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +10 -2
- package/dist/index.mjs +930 -278
- package/dist/index.mjs.map +1 -1
- package/dist/{node-BUnkdSsa.mjs → node-DnYv_AMI.mjs} +4 -3
- package/dist/{node-BUnkdSsa.mjs.map → node-DnYv_AMI.mjs.map} +1 -1
- package/dist/{node-io-BY1ZDLRQ.mjs → node-io-BkdRSQeg.mjs} +151 -86
- package/dist/{node-io-BY1ZDLRQ.mjs.map → node-io-BkdRSQeg.mjs.map} +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.mjs +1 -1
- package/dist/utils-DIWdzVht.mjs +46 -0
- package/dist/utils-DIWdzVht.mjs.map +1 -0
- package/package.json +4 -4
- package/src/adapter.ts +6 -0
- package/src/adapters/express.ts +4 -1
- package/src/adapters/fastify.ts +329 -44
- package/src/adapters/hono.ts +341 -86
- package/src/adapters/node-io.ts +185 -82
- package/src/adapters/node.ts +4 -1
- package/src/body.ts +31 -2
- package/src/coerce.ts +31 -0
- package/src/compiler/generate.ts +127 -22
- package/src/context.ts +151 -78
- package/src/index.ts +1 -1
- package/src/pipeline.ts +809 -208
- package/src/plugin.ts +10 -4
- package/src/query.ts +7 -1
- package/src/response.ts +6 -8
- package/src/serialize.ts +83 -16
- package/src/utils.ts +25 -2
- package/src/validate.ts +28 -12
- package/dist/utils-DCbu3a4w.mjs +0 -27
- package/dist/utils-DCbu3a4w.mjs.map +0 -1
package/src/plugin.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Container, Plugin, PluginContext, PluginOptions, ProviderKey, Token, ZnightMetadata } from '@znight/core'
|
|
2
2
|
import type {
|
|
3
|
-
AdapterReply,
|
|
4
3
|
HttpAdapter,
|
|
5
4
|
HttpServerAddress,
|
|
6
5
|
InjectRequest,
|
|
@@ -49,7 +48,14 @@ import { openInjectSse } from './inject-sse.ts'
|
|
|
49
48
|
import { isMediaTypePattern } from './mime.ts'
|
|
50
49
|
import { invalidOption } from './option-error.ts'
|
|
51
50
|
import { compareRoutePaths, compareStrings, joinPaths, parseRoutePath, type RoutePath, RoutePathError } from './path.ts'
|
|
52
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
DEFAULT_SSE_SETTINGS,
|
|
53
|
+
Pipeline,
|
|
54
|
+
type Resolvable,
|
|
55
|
+
type RoutePlan,
|
|
56
|
+
randomRequestId,
|
|
57
|
+
type SseSettings,
|
|
58
|
+
} from './pipeline.ts'
|
|
53
59
|
import { collectPluginRoutes } from './plugin-routes.ts'
|
|
54
60
|
import { resolveTrustProxy } from './proxy.ts'
|
|
55
61
|
import { RouteTable } from './router.ts'
|
|
@@ -512,7 +518,7 @@ export class HttpPlugin implements Plugin {
|
|
|
512
518
|
adapter.registerRoute({
|
|
513
519
|
method: registration.method,
|
|
514
520
|
path: registration.path,
|
|
515
|
-
handler:
|
|
521
|
+
handler: pipeline.handler(plan),
|
|
516
522
|
})
|
|
517
523
|
routes.push(
|
|
518
524
|
Object.freeze({
|
|
@@ -649,7 +655,7 @@ function resolve(options: HttpPluginOptions): Resolved {
|
|
|
649
655
|
if (requestIdHeader !== false && (typeof requestIdHeader !== 'string' || !isHeaderName(requestIdHeader))) {
|
|
650
656
|
throw invalidOption('requestIdHeader', 'a header name or false', requestIdHeader)
|
|
651
657
|
}
|
|
652
|
-
const generateRequestId = options.generateRequestId ??
|
|
658
|
+
const generateRequestId = options.generateRequestId ?? randomRequestId
|
|
653
659
|
if (typeof generateRequestId !== 'function') throw invalidOption('generateRequestId', 'a function', generateRequestId)
|
|
654
660
|
const adapter = options.adapter ?? nodeAdapter()
|
|
655
661
|
if (
|
package/src/query.ts
CHANGED
|
@@ -23,8 +23,14 @@ export function parseQuery(query: string): QueryRecord {
|
|
|
23
23
|
return result
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** A request target split into its raw path and raw query string (without `?`). */
|
|
27
|
+
export interface RequestTarget {
|
|
28
|
+
readonly path: string
|
|
29
|
+
readonly query: string
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
/** Splits a request target (`/users?page=2`) into its raw path and raw query (without `?`). */
|
|
27
|
-
export function splitUrl(url: string):
|
|
33
|
+
export function splitUrl(url: string): RequestTarget {
|
|
28
34
|
const index = url.indexOf('?')
|
|
29
35
|
return index === -1 ? { path: url, query: '' } : { path: url.slice(0, index), query: url.slice(index + 1) }
|
|
30
36
|
}
|
package/src/response.ts
CHANGED
|
@@ -229,12 +229,10 @@ function withCookie(cookies: readonly ResponseCookie[], cookie: ResponseCookie):
|
|
|
229
229
|
|
|
230
230
|
/** Whether `value` is an {@link HttpResponse} (also across duplicated package copies). */
|
|
231
231
|
export function isHttpResponse(value: unknown): value is HttpResponse {
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'headers' in value)
|
|
239
|
-
)
|
|
232
|
+
if (value instanceof HttpResponse) return true
|
|
233
|
+
if (value === null || typeof value !== 'object') return false
|
|
234
|
+
const type = (value as { constructor?: { name?: unknown } }).constructor
|
|
235
|
+
// object literals and arrays (most handler results): reading a function's name is slow
|
|
236
|
+
if (type === Object || type === Array) return false
|
|
237
|
+
return type?.name === 'HttpResponse' && 'body' in value && 'headers' in value
|
|
240
238
|
}
|
package/src/serialize.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ResolvedFile } from './files.ts'
|
|
|
5
5
|
import type { HttpResponse } from './response.ts'
|
|
6
6
|
import type { SseSession } from './sse.ts'
|
|
7
7
|
|
|
8
|
+
import { Buffer } from 'node:buffer'
|
|
8
9
|
import { Readable } from 'node:stream'
|
|
9
10
|
|
|
10
11
|
import { isFileBody } from './files.ts'
|
|
@@ -12,10 +13,16 @@ import { isHttpResponse } from './response.ts'
|
|
|
12
13
|
import { isSseBody } from './sse.ts'
|
|
13
14
|
import { isBodylessStatus } from './status.ts'
|
|
14
15
|
import { discardStream, isByteStream, toByteStream } from './stream.ts'
|
|
15
|
-
import {
|
|
16
|
+
import { fastRecord, isBun } from './utils.ts'
|
|
16
17
|
|
|
17
18
|
const ENCODER = new TextEncoder()
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* UTF-8 encoding of a text body: a pooled `Buffer` (a `Uint8Array`) is several times cheaper than
|
|
22
|
+
* `TextEncoder` on Node.js, `TextEncoder` is the fastest on Bun.
|
|
23
|
+
*/
|
|
24
|
+
const utf8: (text: string) => Uint8Array = isBun() ? text => ENCODER.encode(text) : text => Buffer.from(text, 'utf8')
|
|
25
|
+
|
|
19
26
|
/** Content types set by the serialiser. */
|
|
20
27
|
export const ContentType: {
|
|
21
28
|
readonly json: string
|
|
@@ -48,6 +55,10 @@ export interface SerializedReply {
|
|
|
48
55
|
readonly status: number
|
|
49
56
|
readonly headers: Record<string, HeaderValue>
|
|
50
57
|
readonly body: ReplyBody
|
|
58
|
+
/** The text `body` encodes, for a body serialised from a string (JSON, text). */
|
|
59
|
+
readonly text?: string
|
|
60
|
+
/** Only the serialiser set the headers (content type and length): they are valid by construction. */
|
|
61
|
+
readonly ownHeaders?: boolean
|
|
51
62
|
/** Cookies of the `HttpResponse` (`withCookie`), applied by the pipeline over those of the context. */
|
|
52
63
|
readonly cookies?: readonly ResponseCookie[]
|
|
53
64
|
/** The event stream of the body (`@Sse`, `HttpResponse.sse()`). */
|
|
@@ -85,12 +96,30 @@ export function serializeResult(result: unknown, options: SerializeOptions): Ser
|
|
|
85
96
|
cookies = cookiesOf(result)
|
|
86
97
|
}
|
|
87
98
|
const finalStatus = options.status ?? status ?? defaultStatus(options.routeMethod, payload)
|
|
88
|
-
const headers =
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
99
|
+
const headers = fastRecord<HeaderValue>()
|
|
100
|
+
const encoded = encode(payload, headers)
|
|
101
|
+
let body: ReplyBody
|
|
102
|
+
let text: string | undefined
|
|
103
|
+
if (typeof encoded === 'string') {
|
|
104
|
+
text = encoded
|
|
105
|
+
body = encodeText(encoded, headers, sendsBody(finalStatus, options.method))
|
|
106
|
+
} else {
|
|
107
|
+
body = encoded
|
|
108
|
+
}
|
|
109
|
+
let own = true
|
|
110
|
+
if (options.headers !== undefined) {
|
|
111
|
+
assignHeaders(headers, options.headers)
|
|
112
|
+
own = false
|
|
113
|
+
}
|
|
114
|
+
if (options.context !== undefined) {
|
|
115
|
+
assignHeaders(headers, options.context)
|
|
116
|
+
own = false
|
|
117
|
+
}
|
|
118
|
+
if (extra !== undefined) {
|
|
119
|
+
assignHeaders(headers, extra)
|
|
120
|
+
own = false
|
|
121
|
+
}
|
|
122
|
+
return withCookies(finish(finalStatus, headers, body, options.method, text, own), cookies)
|
|
94
123
|
}
|
|
95
124
|
|
|
96
125
|
/**
|
|
@@ -106,7 +135,7 @@ export function serializeSseReply(
|
|
|
106
135
|
options: SerializeOptions,
|
|
107
136
|
): SerializedReply {
|
|
108
137
|
const status = options.status ?? response?.status ?? 200
|
|
109
|
-
const headers =
|
|
138
|
+
const headers = fastRecord<HeaderValue>()
|
|
110
139
|
headers['cache-control'] = 'no-cache, no-transform'
|
|
111
140
|
headers['x-accel-buffering'] = 'no'
|
|
112
141
|
if (options.headers !== undefined) assignHeaders(headers, options.headers)
|
|
@@ -140,7 +169,7 @@ export function serializeFileReply(
|
|
|
140
169
|
options: SerializeOptions,
|
|
141
170
|
): SerializedReply {
|
|
142
171
|
const status = file.status === 200 ? (options.status ?? response.status ?? 200) : file.status
|
|
143
|
-
const headers =
|
|
172
|
+
const headers = fastRecord<HeaderValue>()
|
|
144
173
|
assignHeaders(headers, file.headers)
|
|
145
174
|
if (options.headers !== undefined) assignHeaders(headers, options.headers)
|
|
146
175
|
if (options.context !== undefined) assignHeaders(headers, options.context)
|
|
@@ -156,9 +185,10 @@ function defaultStatus(routeMethod: string, payload: unknown): number {
|
|
|
156
185
|
|
|
157
186
|
/** Serialises an error body as JSON. */
|
|
158
187
|
export function serializeError(status: number, body: HttpErrorBody | unknown, method: string): SerializedReply {
|
|
159
|
-
const headers =
|
|
188
|
+
const headers = fastRecord<HeaderValue>()
|
|
160
189
|
const encoded = encode(body, headers)
|
|
161
|
-
return finish(status, headers, encoded, method)
|
|
190
|
+
if (typeof encoded !== 'string') return finish(status, headers, encoded, method)
|
|
191
|
+
return finish(status, headers, encodeText(encoded, headers, sendsBody(status, method)), method, encoded)
|
|
162
192
|
}
|
|
163
193
|
|
|
164
194
|
/** Copies headers with lower-case names (later values win). */
|
|
@@ -184,11 +214,17 @@ export function addMissingHeaders(
|
|
|
184
214
|
}
|
|
185
215
|
}
|
|
186
216
|
|
|
217
|
+
/**
|
|
218
|
+
* The reply of a body: `text` is the string `body` encodes (see {@link encodeText}). `own`: only
|
|
219
|
+
* {@link encode} set `headers`, which are then valid for a text body.
|
|
220
|
+
*/
|
|
187
221
|
function finish(
|
|
188
222
|
status: number,
|
|
189
223
|
headers: Record<string, HeaderValue>,
|
|
190
224
|
body: ReplyBody,
|
|
191
225
|
method: string,
|
|
226
|
+
text?: string,
|
|
227
|
+
own = false,
|
|
192
228
|
): SerializedReply {
|
|
193
229
|
if (isBodylessStatus(status)) {
|
|
194
230
|
discard(body)
|
|
@@ -200,16 +236,42 @@ function finish(
|
|
|
200
236
|
discard(body)
|
|
201
237
|
return { status, headers, body: null }
|
|
202
238
|
}
|
|
203
|
-
return { status, headers, body }
|
|
239
|
+
if (text === undefined) return { status, headers, body }
|
|
240
|
+
return own ? { status, headers, body, text, ownHeaders: true } : { status, headers, body, text }
|
|
204
241
|
}
|
|
205
242
|
|
|
206
243
|
function discard(body: ReplyBody): void {
|
|
207
244
|
if (body instanceof ReadableStream) discardStream(body)
|
|
208
245
|
}
|
|
209
246
|
|
|
210
|
-
|
|
247
|
+
/** Whether a reply with this status, to this method, carries its body (see {@link finish}). */
|
|
248
|
+
function sendsBody(status: number, method: string): boolean {
|
|
249
|
+
return method !== 'HEAD' && !isBodylessStatus(status)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Sets the `content-length` of a text body (right after its `content-type`, before the route
|
|
254
|
+
* headers) and returns its UTF-8 bytes: their length is the header, a lone surrogate counting as
|
|
255
|
+
* U+FFFD. `null` when the reply sends no body (`HEAD`, bodyless status): nothing is encoded then,
|
|
256
|
+
* `Buffer.byteLength` counts the same bytes.
|
|
257
|
+
*/
|
|
258
|
+
function encodeText(text: string, headers: Record<string, HeaderValue>, sent: boolean): Uint8Array | null {
|
|
259
|
+
if (!sent) {
|
|
260
|
+
headers['content-length'] = String(Buffer.byteLength(text, 'utf8'))
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
const bytes = utf8(text)
|
|
264
|
+
headers['content-length'] = String(bytes.byteLength)
|
|
265
|
+
return bytes
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The body of a payload, its `content-type` and, for bytes, its `content-length` set in `headers`.
|
|
270
|
+
* Text (a string, JSON) is returned as a string: {@link encodeText} sets its length and encodes it.
|
|
271
|
+
*/
|
|
272
|
+
function encode(payload: unknown, headers: Record<string, HeaderValue>): ReplyBody | string {
|
|
211
273
|
if (payload === undefined) return null
|
|
212
|
-
if (typeof payload === 'string') return
|
|
274
|
+
if (typeof payload === 'string') return text(payload, headers, ContentType.text)
|
|
213
275
|
if (payload instanceof Uint8Array) return bytes(payload, headers, ContentType.bytes)
|
|
214
276
|
if (payload instanceof ArrayBuffer) return bytes(new Uint8Array(payload), headers, ContentType.bytes)
|
|
215
277
|
if (ArrayBuffer.isView(payload)) {
|
|
@@ -233,7 +295,7 @@ function encode(payload: unknown, headers: Record<string, HeaderValue>): ReplyBo
|
|
|
233
295
|
if (isSseBody(payload)) throw new TypeError('An event stream (HttpResponse.sse()) cannot be serialised here')
|
|
234
296
|
const json = JSON.stringify(payload)
|
|
235
297
|
if (json === undefined) throw new TypeError(`Cannot serialise a ${typeof payload} as a response body`)
|
|
236
|
-
return
|
|
298
|
+
return text(json, headers, ContentType.json)
|
|
237
299
|
}
|
|
238
300
|
|
|
239
301
|
function bytes(data: Uint8Array, headers: Record<string, HeaderValue>, type: string): Uint8Array {
|
|
@@ -242,6 +304,11 @@ function bytes(data: Uint8Array, headers: Record<string, HeaderValue>, type: str
|
|
|
242
304
|
return data
|
|
243
305
|
}
|
|
244
306
|
|
|
307
|
+
function text(data: string, headers: Record<string, HeaderValue>, type: string): string {
|
|
308
|
+
headers['content-type'] = type
|
|
309
|
+
return data
|
|
310
|
+
}
|
|
311
|
+
|
|
245
312
|
function isNodeReadable(value: unknown): value is Readable {
|
|
246
313
|
return (
|
|
247
314
|
value instanceof Readable ||
|
|
@@ -254,7 +321,7 @@ function isNodeReadable(value: unknown): value is Readable {
|
|
|
254
321
|
}
|
|
255
322
|
|
|
256
323
|
function fromWebResponse(response: Response, method: string): SerializedReply {
|
|
257
|
-
const headers =
|
|
324
|
+
const headers = fastRecord<HeaderValue>()
|
|
258
325
|
for (const [name, value] of response.headers) {
|
|
259
326
|
if (name !== 'set-cookie') headers[name] = value
|
|
260
327
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
/** Internal helpers (not part of the public API). */
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Whether `value` is a promise-like object (has a callable `then`). The guard keeps the thenable
|
|
5
|
+
* part of the value's type (`Promise<R>` of `R | Promise<R>`, even when `R` is `object` or `any`), so
|
|
6
|
+
* that generated code awaiting it gets `R`.
|
|
7
|
+
*/
|
|
8
|
+
export function isThenable<T>(
|
|
9
|
+
value: T,
|
|
10
|
+
): value is unknown extends T ? T & PromiseLike<unknown> : Extract<T, PromiseLike<unknown>> {
|
|
5
11
|
return (
|
|
6
12
|
value !== null &&
|
|
7
13
|
(typeof value === 'object' || typeof value === 'function') &&
|
|
@@ -25,6 +31,23 @@ export function emptyRecord<V>(): Record<string, V> {
|
|
|
25
31
|
return Object.create(null) as Record<string, V>
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Constructor of {@link fastRecord}: its prototype is an empty, frozen object without prototype, so
|
|
36
|
+
* nothing is inherited (`__proto__`, `constructor`, `toString`... are plain own keys).
|
|
37
|
+
*/
|
|
38
|
+
const FastRecord = function FastRecord() {} as unknown as new () => object
|
|
39
|
+
FastRecord.prototype = Object.freeze(Object.create(null))
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A `Record` that inherits nothing, like {@link emptyRecord}, but that V8 keeps in fast mode: an
|
|
43
|
+
* `Object.create(null)` object is a dictionary, about twice as slow to fill and enumerate (response
|
|
44
|
+
* headers: built, completed and enumerated on every request). Its prototype is not `null` (an empty
|
|
45
|
+
* frozen object): use {@link emptyRecord} for records handed to user code (query, fields...).
|
|
46
|
+
*/
|
|
47
|
+
export function fastRecord<V>(): Record<string, V> {
|
|
48
|
+
return new FastRecord() as Record<string, V>
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
/** Whether the code runs on Bun. */
|
|
29
52
|
export function isBun(): boolean {
|
|
30
53
|
return typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'
|
package/src/validate.ts
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type StandardSchemaV1Result,
|
|
1
|
+
import type {
|
|
2
|
+
InferSchemaOutput,
|
|
3
|
+
StandardSchemaV1,
|
|
4
|
+
StandardSchemaV1Issue,
|
|
5
|
+
StandardSchemaV1Props,
|
|
6
|
+
StandardSchemaV1Result,
|
|
8
7
|
} from './standard-schema.ts'
|
|
8
|
+
|
|
9
|
+
import { ValidationException } from './exceptions.ts'
|
|
9
10
|
import { isThenable } from './utils.ts'
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Whether `value` is a promise-like object (has a callable `then`). Called by the generated
|
|
14
|
+
* `invoke` functions on the result of {@link validate} (README, "Compiler contract").
|
|
15
|
+
*/
|
|
16
|
+
export { isThenable } from './utils.ts'
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Validates `value` with a [Standard Schema](https://standardschema.dev) (Zod, Valibot, ArkType...)
|
|
13
20
|
* and returns the schema **output** (defaults applied, transforms run, unknown keys stripped when
|
|
14
21
|
* the schema strips them).
|
|
15
22
|
*
|
|
16
23
|
* Returns synchronously when the schema validates synchronously, a promise otherwise (async
|
|
17
|
-
* refinements): generated code
|
|
24
|
+
* refinements): generated code calls the handler directly with the value, and `await`s the value
|
|
25
|
+
* when it is a thenable (`isThenable`).
|
|
18
26
|
*
|
|
19
27
|
* @param location what is validated, used in the error message: `body`, `query`, `query "page"`,
|
|
20
28
|
* `param "id"`, `headers`, `cookie "sid"`, `cookies`...
|
|
@@ -28,12 +36,20 @@ export function validate<S extends StandardSchemaV1>(
|
|
|
28
36
|
value: unknown,
|
|
29
37
|
location: string,
|
|
30
38
|
): InferSchemaOutput<S> | Promise<InferSchemaOutput<S>> {
|
|
31
|
-
|
|
39
|
+
// `~standard` is read once (the same checks as `isStandardSchema`)
|
|
40
|
+
const props =
|
|
41
|
+
schema !== null && (typeof schema === 'object' || typeof schema === 'function')
|
|
42
|
+
? (schema as { readonly '~standard'?: unknown })['~standard']
|
|
43
|
+
: undefined
|
|
44
|
+
if (
|
|
45
|
+
props === null ||
|
|
46
|
+
typeof props !== 'object' ||
|
|
47
|
+
(props as { readonly version?: unknown }).version !== 1 ||
|
|
48
|
+
typeof (props as { readonly validate?: unknown }).validate !== 'function'
|
|
49
|
+
) {
|
|
32
50
|
throw new TypeError(`The schema of ${location} is not a Standard Schema v1 (missing "~standard.validate")`)
|
|
33
51
|
}
|
|
34
|
-
const result =
|
|
35
|
-
| StandardSchemaV1Result<InferSchemaOutput<S>>
|
|
36
|
-
| Promise<StandardSchemaV1Result<InferSchemaOutput<S>>>
|
|
52
|
+
const result = (props as StandardSchemaV1Props<unknown, InferSchemaOutput<S>>).validate(value)
|
|
37
53
|
if (isThenable(result)) return Promise.resolve(result).then(settled => unwrap(settled, location))
|
|
38
54
|
return unwrap(result, location)
|
|
39
55
|
}
|
package/dist/utils-DCbu3a4w.mjs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// src/utils.ts
|
|
2
|
-
/** Internal helpers (not part of the public API). */
|
|
3
|
-
/** Whether `value` is a promise-like object (has a callable `then`). */
|
|
4
|
-
function isThenable(value) {
|
|
5
|
-
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
|
|
6
|
-
}
|
|
7
|
-
/** Message of any thrown value. */
|
|
8
|
-
function errorMessage(error) {
|
|
9
|
-
if (error instanceof Error) return error.message;
|
|
10
|
-
if (typeof error === "string") return error;
|
|
11
|
-
try {
|
|
12
|
-
return String(error);
|
|
13
|
-
} catch {
|
|
14
|
-
return Object.prototype.toString.call(error);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/** A `Record` without prototype (safe for user-controlled keys such as `__proto__`). */
|
|
18
|
-
function emptyRecord() {
|
|
19
|
-
return Object.create(null);
|
|
20
|
-
}
|
|
21
|
-
/** Whether the code runs on Bun. */
|
|
22
|
-
function isBun() {
|
|
23
|
-
return typeof globalThis.Bun !== "undefined";
|
|
24
|
-
}
|
|
25
|
-
export { isThenable as i, errorMessage as n, isBun as r, emptyRecord as t };
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=utils-DCbu3a4w.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils-DCbu3a4w.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["/** Internal helpers (not part of the public API). */\n\n/** Whether `value` is a promise-like object (has a callable `then`). */\nexport function isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n value !== null &&\n (typeof value === 'object' || typeof value === 'function') &&\n typeof (value as { then?: unknown }).then === 'function'\n )\n}\n\n/** Message of any thrown value. */\nexport function errorMessage(error: unknown): string {\n if (error instanceof Error) return error.message\n if (typeof error === 'string') return error\n try {\n return String(error)\n } catch {\n return Object.prototype.toString.call(error)\n }\n}\n\n/** A `Record` without prototype (safe for user-controlled keys such as `__proto__`). */\nexport function emptyRecord<V>(): Record<string, V> {\n return Object.create(null) as Record<string, V>\n}\n\n/** Whether the code runs on Bun. */\nexport function isBun(): boolean {\n return typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'\n}\n"],"mappings":";;;AAGA,SAAgB,WAAW,OAA+C;CACxE,OACE,UAAU,SACT,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;;AAGA,SAAgB,aAAa,OAAwB;CACnD,IAAI,iBAAiB,OAAO,OAAO,MAAM;CACzC,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,OAAO,OAAO,KAAK;CACrB,QAAQ;EACN,OAAO,OAAO,UAAU,SAAS,KAAK,KAAK;CAC7C;AACF;;AAGA,SAAgB,cAAoC;CAClD,OAAO,OAAO,OAAO,IAAI;AAC3B;;AAGA,SAAgB,QAAiB;CAC/B,OAAO,OAAQ,WAAiC,QAAQ;AAC1D"}
|