@znight/http 0.1.2 → 0.1.4
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 +154 -35
- package/dist/compiler.d.mts +77 -2
- package/dist/compiler.mjs +259 -101
- package/dist/compiler.mjs.map +1 -1
- package/dist/express.mjs +3 -2
- package/dist/express.mjs.map +1 -1
- package/dist/fastify.mjs +200 -23
- package/dist/fastify.mjs.map +1 -1
- package/dist/hono.mjs +174 -43
- package/dist/hono.mjs.map +1 -1
- package/dist/index.d.mts +36 -4
- package/dist/index.mjs +1005 -257
- package/dist/index.mjs.map +1 -1
- package/dist/{node-Clm1Ia7T.mjs → node-DnYv_AMI.mjs} +4 -3
- package/dist/{node-Clm1Ia7T.mjs.map → node-DnYv_AMI.mjs.map} +1 -1
- package/dist/{node-io-CVnqpVNS.mjs → node-io-BkdRSQeg.mjs} +99 -60
- package/dist/{node-io-CVnqpVNS.mjs.map → node-io-BkdRSQeg.mjs.map} +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/adapters/express.ts +4 -1
- package/src/adapters/fastify.ts +265 -25
- package/src/adapters/hono.ts +225 -58
- package/src/adapters/node-io.ts +128 -58
- package/src/adapters/node.ts +4 -1
- package/src/body.ts +31 -2
- package/src/coerce.ts +31 -0
- package/src/compiler/analyze.ts +3 -3
- package/src/compiler/generate.ts +127 -22
- package/src/compiler/index.ts +13 -0
- package/src/compiler/route-decorators.ts +45 -2
- package/src/compiler/route-groups.ts +216 -0
- package/src/compiler/validate.ts +15 -126
- package/src/context.ts +148 -78
- package/src/decorators.ts +19 -3
- package/src/index.ts +2 -1
- package/src/pipeline.ts +936 -177
- package/src/plugin-routes.ts +19 -0
- package/src/plugin.ts +7 -3
- package/src/response.ts +6 -8
- package/src/serialize.ts +48 -21
- 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-routes.ts
CHANGED
|
@@ -8,11 +8,13 @@ import type { HttpContext } from './context.ts'
|
|
|
8
8
|
import type { RouteMetaEntry } from './decorators.ts'
|
|
9
9
|
import type { HttpMethod } from './metadata.ts'
|
|
10
10
|
import type { GuardRef } from './plugin.ts'
|
|
11
|
+
import type { ByteSize } from './size.ts'
|
|
11
12
|
|
|
12
13
|
import { ErrorCode, ZnightError } from '@znight/core'
|
|
13
14
|
|
|
14
15
|
import { isGroupOf } from './decorators.ts'
|
|
15
16
|
import { joinPaths, normalizePath, parseRoutePath, type RoutePath, RoutePathError } from './path.ts'
|
|
17
|
+
import { parseByteSize } from './size.ts'
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* A route served by `HttpPlugin` on behalf of another plugin. It runs through the same pipeline as
|
|
@@ -35,6 +37,13 @@ export interface PluginRoute {
|
|
|
35
37
|
readonly meta?: readonly RouteMetaEntry[]
|
|
36
38
|
/** Guards of the route (provider classes or instances), run after the global guards. */
|
|
37
39
|
readonly guards?: readonly GuardRef[]
|
|
40
|
+
/**
|
|
41
|
+
* Maximum body size of this route (`'100kb'`, `1048576`, `Infinity`), with the grammar of the
|
|
42
|
+
* `HttpPlugin` option `bodyLimit`, which it replaces for this route only (lower or higher, like
|
|
43
|
+
* `@BodyLimit` on a controller route). Past it, reading the body answers 413. Default: the
|
|
44
|
+
* `HttpPlugin` option.
|
|
45
|
+
*/
|
|
46
|
+
readonly bodyLimit?: ByteSize
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
/** What {@link PluginHttpRoutes.httpRoutes} receives. */
|
|
@@ -78,6 +87,8 @@ export interface CollectedPluginRoute {
|
|
|
78
87
|
readonly handler: (ctx: HttpContext) => unknown
|
|
79
88
|
readonly meta: readonly NormalizedMetaEntry[]
|
|
80
89
|
readonly guards: readonly unknown[]
|
|
90
|
+
/** Parsed `bodyLimit` in bytes, `undefined`: the plugin limit. */
|
|
91
|
+
readonly bodyLimit: number | undefined
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
/** A plugin route metadata entry with its group (internal). */
|
|
@@ -147,6 +158,13 @@ function validateRoute(instance: object, plugin: string, value: unknown, prefix:
|
|
|
147
158
|
}
|
|
148
159
|
const guards = route.guards ?? []
|
|
149
160
|
if (!Array.isArray(guards)) throw invalid(plugin, `the guards of the route ${where} must be an array`)
|
|
161
|
+
const bodyLimit = route.bodyLimit === undefined ? undefined : parseByteSize(route.bodyLimit)
|
|
162
|
+
if (route.bodyLimit !== undefined && bodyLimit === undefined) {
|
|
163
|
+
throw invalid(
|
|
164
|
+
plugin,
|
|
165
|
+
`invalid bodyLimit ${describe(route.bodyLimit)} of the route ${where}: expected a size in bytes (1048576, '100kb', Infinity)`,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
150
168
|
return {
|
|
151
169
|
plugin,
|
|
152
170
|
instance,
|
|
@@ -157,6 +175,7 @@ function validateRoute(instance: object, plugin: string, value: unknown, prefix:
|
|
|
157
175
|
handler: route.handler,
|
|
158
176
|
meta: entries,
|
|
159
177
|
guards,
|
|
178
|
+
bodyLimit,
|
|
160
179
|
}
|
|
161
180
|
}
|
|
162
181
|
|
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,
|
|
@@ -519,7 +518,7 @@ export class HttpPlugin implements Plugin {
|
|
|
519
518
|
adapter.registerRoute({
|
|
520
519
|
method: registration.method,
|
|
521
520
|
path: registration.path,
|
|
522
|
-
handler:
|
|
521
|
+
handler: pipeline.handler(plan),
|
|
523
522
|
})
|
|
524
523
|
routes.push(
|
|
525
524
|
Object.freeze({
|
|
@@ -1036,7 +1035,12 @@ function buildRegistrations(
|
|
|
1036
1035
|
invoke: (_plugin: unknown, http: HttpContext): unknown => handler(http),
|
|
1037
1036
|
})
|
|
1038
1037
|
const instance: Resolvable<object> = { instance: route.instance }
|
|
1039
|
-
|
|
1038
|
+
// the route's own limit (`bodyLimit`), otherwise the plugin limit
|
|
1039
|
+
const body = bodyPlan(
|
|
1040
|
+
route.bodyLimit === undefined ? {} : { bodyLimit: route.bodyLimit },
|
|
1041
|
+
options.bodyLimit,
|
|
1042
|
+
options.uploads,
|
|
1043
|
+
)
|
|
1040
1044
|
const chain = guards.map(guard => guard.resolvable)
|
|
1041
1045
|
const path = route.path
|
|
1042
1046
|
declared.push({
|
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
|
@@ -13,7 +13,7 @@ import { isHttpResponse } from './response.ts'
|
|
|
13
13
|
import { isSseBody } from './sse.ts'
|
|
14
14
|
import { isBodylessStatus } from './status.ts'
|
|
15
15
|
import { discardStream, isByteStream, toByteStream } from './stream.ts'
|
|
16
|
-
import {
|
|
16
|
+
import { fastRecord, isBun } from './utils.ts'
|
|
17
17
|
|
|
18
18
|
const ENCODER = new TextEncoder()
|
|
19
19
|
|
|
@@ -96,8 +96,16 @@ export function serializeResult(result: unknown, options: SerializeOptions): Ser
|
|
|
96
96
|
cookies = cookiesOf(result)
|
|
97
97
|
}
|
|
98
98
|
const finalStatus = options.status ?? status ?? defaultStatus(options.routeMethod, payload)
|
|
99
|
-
const headers =
|
|
100
|
-
const
|
|
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
|
+
}
|
|
101
109
|
let own = true
|
|
102
110
|
if (options.headers !== undefined) {
|
|
103
111
|
assignHeaders(headers, options.headers)
|
|
@@ -111,7 +119,7 @@ export function serializeResult(result: unknown, options: SerializeOptions): Ser
|
|
|
111
119
|
assignHeaders(headers, extra)
|
|
112
120
|
own = false
|
|
113
121
|
}
|
|
114
|
-
return withCookies(finish(finalStatus, headers, body, options.method, own), cookies)
|
|
122
|
+
return withCookies(finish(finalStatus, headers, body, options.method, text, own), cookies)
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
/**
|
|
@@ -127,7 +135,7 @@ export function serializeSseReply(
|
|
|
127
135
|
options: SerializeOptions,
|
|
128
136
|
): SerializedReply {
|
|
129
137
|
const status = options.status ?? response?.status ?? 200
|
|
130
|
-
const headers =
|
|
138
|
+
const headers = fastRecord<HeaderValue>()
|
|
131
139
|
headers['cache-control'] = 'no-cache, no-transform'
|
|
132
140
|
headers['x-accel-buffering'] = 'no'
|
|
133
141
|
if (options.headers !== undefined) assignHeaders(headers, options.headers)
|
|
@@ -161,7 +169,7 @@ export function serializeFileReply(
|
|
|
161
169
|
options: SerializeOptions,
|
|
162
170
|
): SerializedReply {
|
|
163
171
|
const status = file.status === 200 ? (options.status ?? response.status ?? 200) : file.status
|
|
164
|
-
const headers =
|
|
172
|
+
const headers = fastRecord<HeaderValue>()
|
|
165
173
|
assignHeaders(headers, file.headers)
|
|
166
174
|
if (options.headers !== undefined) assignHeaders(headers, options.headers)
|
|
167
175
|
if (options.context !== undefined) assignHeaders(headers, options.context)
|
|
@@ -177,9 +185,10 @@ function defaultStatus(routeMethod: string, payload: unknown): number {
|
|
|
177
185
|
|
|
178
186
|
/** Serialises an error body as JSON. */
|
|
179
187
|
export function serializeError(status: number, body: HttpErrorBody | unknown, method: string): SerializedReply {
|
|
180
|
-
const headers =
|
|
188
|
+
const headers = fastRecord<HeaderValue>()
|
|
181
189
|
const encoded = encode(body, headers)
|
|
182
|
-
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)
|
|
183
192
|
}
|
|
184
193
|
|
|
185
194
|
/** Copies headers with lower-case names (later values win). */
|
|
@@ -206,14 +215,15 @@ export function addMissingHeaders(
|
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
/**
|
|
209
|
-
* The reply of a body:
|
|
210
|
-
*
|
|
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.
|
|
211
220
|
*/
|
|
212
221
|
function finish(
|
|
213
222
|
status: number,
|
|
214
223
|
headers: Record<string, HeaderValue>,
|
|
215
|
-
body: ReplyBody
|
|
224
|
+
body: ReplyBody,
|
|
216
225
|
method: string,
|
|
226
|
+
text?: string,
|
|
217
227
|
own = false,
|
|
218
228
|
): SerializedReply {
|
|
219
229
|
if (isBodylessStatus(status)) {
|
|
@@ -226,19 +236,38 @@ function finish(
|
|
|
226
236
|
discard(body)
|
|
227
237
|
return { status, headers, body: null }
|
|
228
238
|
}
|
|
229
|
-
if (
|
|
230
|
-
return own
|
|
231
|
-
? { status, headers, body: utf8(body), text: body, ownHeaders: true }
|
|
232
|
-
: { status, headers, body: utf8(body), text: body }
|
|
239
|
+
if (text === undefined) return { status, headers, body }
|
|
240
|
+
return own ? { status, headers, body, text, ownHeaders: true } : { status, headers, body, text }
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
function discard(body: ReplyBody
|
|
243
|
+
function discard(body: ReplyBody): void {
|
|
236
244
|
if (body instanceof ReadableStream) discardStream(body)
|
|
237
245
|
}
|
|
238
246
|
|
|
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
|
+
|
|
239
268
|
/**
|
|
240
|
-
* The body of a payload, its `content-type` and, for bytes
|
|
241
|
-
*
|
|
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.
|
|
242
271
|
*/
|
|
243
272
|
function encode(payload: unknown, headers: Record<string, HeaderValue>): ReplyBody | string {
|
|
244
273
|
if (payload === undefined) return null
|
|
@@ -277,8 +306,6 @@ function bytes(data: Uint8Array, headers: Record<string, HeaderValue>, type: str
|
|
|
277
306
|
|
|
278
307
|
function text(data: string, headers: Record<string, HeaderValue>, type: string): string {
|
|
279
308
|
headers['content-type'] = type
|
|
280
|
-
// the length of its UTF-8 encoding (a lone surrogate counts as U+FFFD, like the encoders)
|
|
281
|
-
headers['content-length'] = String(Buffer.byteLength(data, 'utf8'))
|
|
282
309
|
return data
|
|
283
310
|
}
|
|
284
311
|
|
|
@@ -294,7 +321,7 @@ function isNodeReadable(value: unknown): value is Readable {
|
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
function fromWebResponse(response: Response, method: string): SerializedReply {
|
|
297
|
-
const headers =
|
|
324
|
+
const headers = fastRecord<HeaderValue>()
|
|
298
325
|
for (const [name, value] of response.headers) {
|
|
299
326
|
if (name !== 'set-cookie') headers[name] = value
|
|
300
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"}
|