elysia 2.0.0-beta.0 → 2.0.0-beta.2
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/dist/adapter/utils.d.ts +1 -1
- package/dist/adapter/web-standard/handler.js +11 -7
- package/dist/adapter/web-standard/handler.mjs +12 -8
- package/dist/base.d.ts +18 -3
- package/dist/base.js +31 -15
- package/dist/base.mjs +31 -15
- package/dist/compile/aot-capture.js +1 -1
- package/dist/compile/aot-capture.mjs +2 -2
- package/dist/compile/handler/descriptor.d.ts +1 -1
- package/dist/compile/handler/frozen-validator.d.ts +2 -1
- package/dist/compile/handler/frozen-validator.js +33 -3
- package/dist/compile/handler/frozen-validator.mjs +33 -4
- package/dist/compile/handler/index.d.ts +1 -1
- package/dist/compile/handler/jit.js +5 -8
- package/dist/compile/handler/jit.mjs +6 -9
- package/dist/compile/handler/params.js +3 -3
- package/dist/compile/handler/params.mjs +4 -4
- package/dist/compile/handler/utils.d.ts +1 -1
- package/dist/compile/handler/utils.js +5 -2
- package/dist/compile/handler/utils.mjs +5 -2
- package/dist/context.d.ts +2 -2
- package/dist/cookie/cookie.js +1 -0
- package/dist/cookie/cookie.mjs +1 -0
- package/dist/error.d.ts +138 -5
- package/dist/error.js +73 -0
- package/dist/error.mjs +73 -1
- package/dist/handler/error.d.ts +20 -1
- package/dist/handler/error.js +120 -8
- package/dist/handler/error.mjs +119 -9
- package/dist/handler/fetch.js +9 -5
- package/dist/handler/fetch.mjs +9 -5
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -0
- package/dist/index.mjs +2 -2
- package/dist/package.js +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugin/aot/core.d.ts +4 -3
- package/dist/plugin/aot/core.js +1 -1
- package/dist/plugin/aot/core.mjs +1 -1
- package/dist/route-table.d.ts +1 -1
- package/dist/sucrose.js +118 -16
- package/dist/sucrose.mjs +118 -16
- package/dist/type/coerce.js +9 -4
- package/dist/type/coerce.mjs +9 -4
- package/dist/type/elysia/array-string.js +29 -2
- package/dist/type/elysia/array-string.mjs +30 -3
- package/dist/type/elysia/file-type.js +11 -1
- package/dist/type/elysia/file-type.mjs +11 -1
- package/dist/type/elysia/form.js +2 -1
- package/dist/type/elysia/form.mjs +2 -1
- package/dist/type/elysia/object-string.js +26 -2
- package/dist/type/elysia/object-string.mjs +27 -3
- package/dist/type/validator/index.js +11 -13
- package/dist/type/validator/index.mjs +12 -14
- package/dist/types.d.ts +104 -18
- package/dist/universal/file.js +1 -0
- package/dist/universal/file.mjs +1 -0
- package/dist/universal/index.d.ts +1 -1
- package/dist/utils.d.ts +3 -5
- package/dist/utils.js +7 -9
- package/dist/utils.mjs +6 -10
- package/dist/ws/route.js +1 -1
- package/dist/ws/route.mjs +1 -1
- package/package.json +2 -8
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_utils = require('../../utils.js');
|
|
3
|
-
const require_universal_is_production = require('../../universal/is-production.js');
|
|
4
3
|
const require_error = require('../../error.js');
|
|
5
4
|
const require_adapter_utils = require('../../adapter/utils.js');
|
|
6
5
|
const require_parse_query = require('../../parse-query.js');
|
|
7
6
|
const require_cookie_utils = require('../../cookie/utils.js');
|
|
8
7
|
const require_handler_utils = require('../../handler/utils.js');
|
|
8
|
+
const require_handler_error = require('../../handler/error.js');
|
|
9
9
|
const require_compile_handler_utils = require('./utils.js');
|
|
10
10
|
|
|
11
11
|
//#region src/compile/handler/params.ts
|
|
@@ -29,10 +29,8 @@ const handlerParams = () => _handlerParams ??= {
|
|
|
29
29
|
pe: () => require_error.ParseError,
|
|
30
30
|
es: () => require_error.ElysiaStatus,
|
|
31
31
|
rdc: () => require_compile_handler_utils.replaceDeriveContext,
|
|
32
|
-
ise: () => require_error.internalServerErrorResponse,
|
|
33
32
|
emp: () => require_handler_utils.emptyResponse,
|
|
34
33
|
ea: () => require_compile_handler_utils.armEntryAbort,
|
|
35
|
-
isprod: () => require_universal_is_production.isProduction,
|
|
36
34
|
verr: () => require_error.ValidationError,
|
|
37
35
|
tee: () => require_adapter_utils.tee,
|
|
38
36
|
msh: () => require_adapter_utils.materializeSetHeaders,
|
|
@@ -47,6 +45,8 @@ const handlerParams = () => _handlerParams ??= {
|
|
|
47
45
|
va: (c) => c.vali,
|
|
48
46
|
fe: () => require_handler_utils.forwardError,
|
|
49
47
|
fre: () => require_handler_utils.finalizeRouteError,
|
|
48
|
+
fbr: () => require_handler_error.fallbackResponse,
|
|
49
|
+
aet: () => require_handler_error.adoptErrorType,
|
|
50
50
|
rt: (c) => c.root,
|
|
51
51
|
ho: (c) => c.hook,
|
|
52
52
|
tf: (c) => c.hook.transform,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { requestId } from "../../utils.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { ElysiaStatus, ParseError, ValidationError, internalServerErrorResponse } from "../../error.mjs";
|
|
2
|
+
import { ElysiaStatus, ParseError, ValidationError } from "../../error.mjs";
|
|
4
3
|
import { materializeSetHeaders, normalizeContentType, tee } from "../../adapter/utils.mjs";
|
|
5
4
|
import { parseQueryFromURL } from "../../parse-query.mjs";
|
|
6
5
|
import { buildCookieJar, parseCookieRaw, parseCookieRawDeferred, parseCookieRawLazy, parseCookieRawSigned, parseCookieRawSync, signCookieValues } from "../../cookie/utils.mjs";
|
|
7
6
|
import { emptyResponse, finalizeRouteError, forwardError } from "../../handler/utils.mjs";
|
|
7
|
+
import { adoptErrorType, fallbackResponse } from "../../handler/error.mjs";
|
|
8
8
|
import { armEntryAbort, cloneResponse, getQueryParseChannels, hasRequestBody, replaceDeriveContext, runBeforeHandlePrefix } from "./utils.mjs";
|
|
9
9
|
|
|
10
10
|
//#region src/compile/handler/params.ts
|
|
@@ -28,10 +28,8 @@ const handlerParams = () => _handlerParams ??= {
|
|
|
28
28
|
pe: () => ParseError,
|
|
29
29
|
es: () => ElysiaStatus,
|
|
30
30
|
rdc: () => replaceDeriveContext,
|
|
31
|
-
ise: () => internalServerErrorResponse,
|
|
32
31
|
emp: () => emptyResponse,
|
|
33
32
|
ea: () => armEntryAbort,
|
|
34
|
-
isprod: () => isProduction,
|
|
35
33
|
verr: () => ValidationError,
|
|
36
34
|
tee: () => tee,
|
|
37
35
|
msh: () => materializeSetHeaders,
|
|
@@ -46,6 +44,8 @@ const handlerParams = () => _handlerParams ??= {
|
|
|
46
44
|
va: (c) => c.vali,
|
|
47
45
|
fe: () => forwardError,
|
|
48
46
|
fre: () => finalizeRouteError,
|
|
47
|
+
fbr: () => fallbackResponse,
|
|
48
|
+
aet: () => adoptErrorType,
|
|
49
49
|
rt: (c) => c.root,
|
|
50
50
|
ho: (c) => c.hook,
|
|
51
51
|
tf: (c) => c.hook.transform,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AfterResponseHandler, AppHook, ErrorHandler, MaybeArray, TransformHandler } from "../../types.js";
|
|
2
1
|
import { CompactBeforeHandlePrefix, DeriveEntry } from "../../utils.js";
|
|
2
|
+
import { AfterResponseHandler, AppHook, ErrorHandler, MaybeArray, TransformHandler } from "../../types.js";
|
|
3
3
|
import { Context } from "../../context.js";
|
|
4
4
|
import { emptyResponse } from "../../handler/utils.js";
|
|
5
5
|
|
|
@@ -5,6 +5,7 @@ const require_adapter_skip_clone = require('../../adapter/skip-clone.js');
|
|
|
5
5
|
const require_error = require('../../error.js');
|
|
6
6
|
const require_compile_utils = require('../utils.js');
|
|
7
7
|
const require_handler_utils = require('../../handler/utils.js');
|
|
8
|
+
const require_handler_error = require('../../handler/error.js');
|
|
8
9
|
const require_adapter_origin = require('../../adapter/origin.js');
|
|
9
10
|
const require_compile_lexer = require('../lexer.js');
|
|
10
11
|
|
|
@@ -402,11 +403,13 @@ const mapAfterHandle = (_hooks, isAsync, report, abortGuard) => mapChainHook(toA
|
|
|
402
403
|
const mapMapResponse = (_hooks, isAsync, report, abortGuard) => mapChainHook(toArray(_hooks), "mr", isAsync, report, abortGuard);
|
|
403
404
|
const mapAfterResponse = /* @__PURE__ */ map((i, fn, [report]) => {
|
|
404
405
|
const t = trace(report, fn);
|
|
405
|
-
|
|
406
|
+
const call = require_compile_utils.isAsyncFunction(fn) ? `await ar${at(i)}(c)\n` : `let _ar=ar${at(i)}(c)\nif(_ar instanceof Promise)await _ar\n`;
|
|
407
|
+
return `try{` + t.begin + call + t.end() + `}catch(_e){` + t.end("_e") + `}\n`;
|
|
406
408
|
});
|
|
407
409
|
const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, isAsync]) => {
|
|
408
410
|
link(mapResponse, "rm");
|
|
409
|
-
|
|
411
|
+
link(require_handler_error.adoptErrorType, "aet");
|
|
412
|
+
return `_r=${Await(fn)}er${at(i)}(c)\n` + awaitGuard(fn, isAsync, "_r") + "if(_r!==undefined){\nif(_r instanceof Response)c.set.status=_r.status\nelse if(c.set.status===undefined||c.set.status===200)c.set.status=500\n" + schedule + sign + `return _em(c,${map}(aet(_r,e),c.set,c.request,true))\n}\n`;
|
|
410
413
|
});
|
|
411
414
|
function map(map) {
|
|
412
415
|
return function(event, rest, abortGuard) {
|
|
@@ -4,6 +4,7 @@ import { skipClone } from "../../adapter/skip-clone.mjs";
|
|
|
4
4
|
import { ElysiaStatus } from "../../error.mjs";
|
|
5
5
|
import { isAsyncFunction } from "../utils.mjs";
|
|
6
6
|
import { emptyResponse } from "../../handler/utils.mjs";
|
|
7
|
+
import { adoptErrorType } from "../../handler/error.mjs";
|
|
7
8
|
import { origin } from "../../adapter/origin.mjs";
|
|
8
9
|
import { isIdentChar, isSpace, skipString } from "../lexer.mjs";
|
|
9
10
|
|
|
@@ -401,11 +402,13 @@ const mapAfterHandle = (_hooks, isAsync, report, abortGuard) => mapChainHook(toA
|
|
|
401
402
|
const mapMapResponse = (_hooks, isAsync, report, abortGuard) => mapChainHook(toArray(_hooks), "mr", isAsync, report, abortGuard);
|
|
402
403
|
const mapAfterResponse = /* @__PURE__ */ map((i, fn, [report]) => {
|
|
403
404
|
const t = trace(report, fn);
|
|
404
|
-
|
|
405
|
+
const call = isAsyncFunction(fn) ? `await ar${at(i)}(c)\n` : `let _ar=ar${at(i)}(c)\nif(_ar instanceof Promise)await _ar\n`;
|
|
406
|
+
return `try{` + t.begin + call + t.end() + `}catch(_e){` + t.end("_e") + `}\n`;
|
|
405
407
|
});
|
|
406
408
|
const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, isAsync]) => {
|
|
407
409
|
link(mapResponse, "rm");
|
|
408
|
-
|
|
410
|
+
link(adoptErrorType, "aet");
|
|
411
|
+
return `_r=${Await(fn)}er${at(i)}(c)\n` + awaitGuard(fn, isAsync, "_r") + "if(_r!==undefined){\nif(_r instanceof Response)c.set.status=_r.status\nelse if(c.set.status===undefined||c.set.status===200)c.set.status=500\n" + schedule + sign + `return _em(c,${map}(aet(_r,e),c.set,c.request,true))\n}\n`;
|
|
409
412
|
});
|
|
410
413
|
function map(map) {
|
|
411
414
|
return function(event, rest, abortGuard) {
|
package/dist/context.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BaseCookie } from "./cookie/types.js";
|
|
2
2
|
import { Cookie } from "./cookie/cookie.js";
|
|
3
3
|
import { StatusMap } from "./constants.js";
|
|
4
|
-
import { SelectiveStatus, status } from "./error.js";
|
|
5
4
|
import { Server } from "./universal/server.js";
|
|
6
|
-
import { AfterResponseHandler, DefaultSingleton, HTTPHeaders, InputSchema, MaybePromise, Prettify, ResolvePath, RouteSchema, SingletonBase } from "./types.js";
|
|
7
5
|
import { redirect } from "./utils.js";
|
|
6
|
+
import { AfterResponseHandler, DefaultSingleton, HTTPHeaders, InputSchema, MaybePromise, Prettify, ResolvePath, RouteSchema, SingletonBase } from "./types.js";
|
|
7
|
+
import { SelectiveStatus, status } from "./error.js";
|
|
8
8
|
import { AnyElysia } from "./base.js";
|
|
9
9
|
|
|
10
10
|
//#region src/context.d.ts
|
package/dist/cookie/cookie.js
CHANGED
|
@@ -90,6 +90,7 @@ var Cookie = class {
|
|
|
90
90
|
return typeof this.value === "object" ? JSON.stringify(this.value) : this.value?.toString() ?? "";
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
|
+
Object.defineProperty(Cookie, "name", { value: "Cookie" });
|
|
93
94
|
for (const key of FORWARDED_KEYS) Object.defineProperty(Cookie.prototype, key, {
|
|
94
95
|
get() {
|
|
95
96
|
return this.cookie[key];
|
package/dist/cookie/cookie.mjs
CHANGED
|
@@ -89,6 +89,7 @@ var Cookie = class {
|
|
|
89
89
|
return typeof this.value === "object" ? JSON.stringify(this.value) : this.value?.toString() ?? "";
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
|
+
Object.defineProperty(Cookie, "name", { value: "Cookie" });
|
|
92
93
|
for (const key of FORWARDED_KEYS) Object.defineProperty(Cookie.prototype, key, {
|
|
93
94
|
get() {
|
|
94
95
|
return this.cookie[key];
|
package/dist/error.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { StatusMap, StatusMapBack } from "./constants.js";
|
|
2
2
|
import { isProduction } from "./universal/is-production.js";
|
|
3
|
+
import { MaybePromise } from "./types.js";
|
|
3
4
|
|
|
4
5
|
//#region src/error.d.ts
|
|
5
6
|
declare class ElysiaError<Status extends number = number, Response extends string = string> extends Error {
|
|
@@ -193,13 +194,14 @@ type NumericStatus<Code extends number | keyof StatusMap> = Code extends keyof S
|
|
|
193
194
|
type ProblemStatus<P> = P extends {
|
|
194
195
|
status: infer S extends number | keyof StatusMap;
|
|
195
196
|
} ? NumericStatus<S> : 500;
|
|
196
|
-
type ProblemResponseBody<Status extends number, P> = {
|
|
197
|
+
type ProblemResponseBody<Status extends number, P> = Omit<{
|
|
197
198
|
type: string;
|
|
198
199
|
title: string;
|
|
199
|
-
status: Status;
|
|
200
200
|
detail?: string;
|
|
201
201
|
instance?: string;
|
|
202
|
-
} & Omit<P,
|
|
202
|
+
}, keyof P> & Omit<P, 'status'> & {
|
|
203
|
+
status: Status;
|
|
204
|
+
};
|
|
203
205
|
declare function problemBody(p: Problem): Record<string, unknown> & {
|
|
204
206
|
status: number;
|
|
205
207
|
};
|
|
@@ -217,7 +219,9 @@ declare function internalServerErrorResponse(error: any): Response;
|
|
|
217
219
|
*
|
|
218
220
|
* @see https://www.rfc-editor.org/info/rfc9457
|
|
219
221
|
*/
|
|
220
|
-
declare function problem<const Code extends number | keyof StatusMap, const P extends Record<string, unknown>
|
|
222
|
+
declare function problem<const Code extends number | keyof StatusMap, const P extends Record<string, unknown> & {
|
|
223
|
+
status?: never;
|
|
224
|
+
} = {}>(status: Code, detail?: P & Omit<Problem<Code>, 'status'>): ElysiaStatus<NumericStatus<Code>, ProblemResponseBody<NumericStatus<Code>, P>>;
|
|
221
225
|
/**
|
|
222
226
|
* RFC 9457 Problem Details function
|
|
223
227
|
*
|
|
@@ -231,5 +235,134 @@ declare function problem<const Code extends number | keyof StatusMap, const P ex
|
|
|
231
235
|
declare function problem<const P extends Problem>(detail: P): ElysiaStatus<ProblemStatus<P>, ProblemResponseBody<ProblemStatus<P>, P>>;
|
|
232
236
|
type CheckExcessProps<T, U> = 0 extends 1 & T ? T : U extends U ? Exclude<keyof T, keyof U> extends never ? T : { [K in keyof U]: U[K] } & { [K in Exclude<keyof T, keyof U>]: never } : never;
|
|
233
237
|
type SelectiveStatus<in out Res> = <const Code extends keyof Res | StatusMapBack[Extract<keyof StatusMapBack, keyof Res>], T extends (Code extends keyof Res ? Res[Code] : Code extends keyof StatusMap ? Res[StatusMap[Code]] : never)>(code: Code, response: CheckExcessProps<T, Code extends keyof Res ? Res[Code] : Code extends keyof StatusMap ? Res[StatusMap[Code]] : never>) => ElysiaStatus<Code, T>;
|
|
238
|
+
/**
|
|
239
|
+
* Class returned by {@link HTTPError.id}
|
|
240
|
+
*/
|
|
241
|
+
type TaggedHTTPError<Type extends string, Annotation = {}> = {
|
|
242
|
+
new (message?: string, options?: ErrorOptions): HTTPError<Type> & {
|
|
243
|
+
type: Type;
|
|
244
|
+
} & Annotation;
|
|
245
|
+
prototype: HTTPError<Type>;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Self-describing HTTP error
|
|
249
|
+
*
|
|
250
|
+
* Annotate `status`, `headers` and `value` on the subclass and Elysia maps
|
|
251
|
+
* the instance to a response on its own, no `.error(Class, handler)` needed
|
|
252
|
+
*
|
|
253
|
+
* Everything an owned `HTTPError` serves is an RFC 9457 problem document:
|
|
254
|
+
* `type` is the problem type carried on the wire, an annotated object body
|
|
255
|
+
* merges into the envelope, and any other body (or the error message, when
|
|
256
|
+
* there is none) becomes `detail`
|
|
257
|
+
*
|
|
258
|
+
* - `detail()` - the common case. Whatever it returns becomes the `detail`
|
|
259
|
+
* member of the problem document, verbatim, objects included. The envelope
|
|
260
|
+
* still carries `type`, `status` and an auto-filled `title`
|
|
261
|
+
* - `value()` - the escape hatch. Whatever it returns replaces the *whole*
|
|
262
|
+
* response: no envelope, no `application/problem+json`. The annotated
|
|
263
|
+
* `status` and `headers` still apply, only the content is yours
|
|
264
|
+
*
|
|
265
|
+
* `value` wins over `detail`, `detail` over the error message. A knob
|
|
266
|
+
* returning `undefined` falls through to the next one.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* class OutOfCredit extends HTTPError<'OUT_OF_CREDIT'> {
|
|
271
|
+
* type = 'OUT_OF_CREDIT' as const
|
|
272
|
+
* override readonly status = 402
|
|
273
|
+
*
|
|
274
|
+
* detail() {
|
|
275
|
+
* return { balance: 0, currency: 'usd' }
|
|
276
|
+
* }
|
|
277
|
+
* }
|
|
278
|
+
* // → { type: 'OUT_OF_CREDIT', title: 'Payment Required', status: 402,
|
|
279
|
+
* // detail: { balance: 0, currency: 'usd' } }
|
|
280
|
+
*
|
|
281
|
+
* class Overdrawn extends HTTPError<'OVERDRAWN'> {
|
|
282
|
+
* type = 'OVERDRAWN' as const
|
|
283
|
+
* override readonly status = 402
|
|
284
|
+
*
|
|
285
|
+
* async detail() {
|
|
286
|
+
* return await describeBalance()
|
|
287
|
+
* }
|
|
288
|
+
* }
|
|
289
|
+
*
|
|
290
|
+
* class Legacy extends HTTPError<'LEGACY'> {
|
|
291
|
+
* type = 'LEGACY' as const
|
|
292
|
+
* override readonly status = 402
|
|
293
|
+
*
|
|
294
|
+
* // full control, served as-is
|
|
295
|
+
* value() {
|
|
296
|
+
* return { code: 'OUT_OF_CREDIT', ok: false }
|
|
297
|
+
* }
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*
|
|
301
|
+
* @see https://www.rfc-editor.org/info/rfc9457
|
|
302
|
+
* @since 2.0.0
|
|
303
|
+
*/
|
|
304
|
+
declare abstract class HTTPError<const in out T extends string = string> extends Error {
|
|
305
|
+
/**
|
|
306
|
+
* RFC 9457 problem type served in the `type` member, a URI or a slug.
|
|
307
|
+
* Doubles as the tag that discriminates one error class from another
|
|
308
|
+
*/
|
|
309
|
+
abstract type: T;
|
|
310
|
+
/**
|
|
311
|
+
* HTTP status served when the error reaches the error pipeline,
|
|
312
|
+
* as a number or a status name (`'Payment Required'`)
|
|
313
|
+
*/
|
|
314
|
+
readonly status?: number | keyof StatusMap;
|
|
315
|
+
/** Headers merged into `set.headers` when the error is served */
|
|
316
|
+
readonly headers?: Record<string, string>;
|
|
317
|
+
/**
|
|
318
|
+
* Return a value to serve as the whole response
|
|
319
|
+
*
|
|
320
|
+
* Supports `status()`/`problem()` for status-based Eden response
|
|
321
|
+
*/
|
|
322
|
+
value?(): MaybePromise<unknown>;
|
|
323
|
+
/**
|
|
324
|
+
* Return the `detail` member of the RFC 9457 problem document,
|
|
325
|
+
* similar to `problem()`
|
|
326
|
+
*
|
|
327
|
+
* Use `value()` to override the entire response
|
|
328
|
+
*/
|
|
329
|
+
detail?(): MaybePromise<unknown>;
|
|
330
|
+
/**
|
|
331
|
+
* Create a concrete `HTTPError` subclass carrying `type`
|
|
332
|
+
*
|
|
333
|
+
* `type` carries no status annotation, annotate `status` in the subclass
|
|
334
|
+
* body as a number or a status name
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {
|
|
339
|
+
* headers = {
|
|
340
|
+
* 'x-powered-by': 'Elysia'
|
|
341
|
+
* }
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
static id<const Type extends string>(type: Type): TaggedHTTPError<Type>;
|
|
346
|
+
/**
|
|
347
|
+
* Create a concrete `HTTPError` subclass carrying `type`, annotated with
|
|
348
|
+
* `status` as a number or a status name
|
|
349
|
+
*
|
|
350
|
+
* Annotate what is served with `detail()` (problem `detail` member) or
|
|
351
|
+
* `value()` (whole-response override), either of which may be `async`
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```ts
|
|
355
|
+
* class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {}
|
|
356
|
+
* class Denied extends HTTPError.id('https://example.com/denied', 'Payment Required') {
|
|
357
|
+
* async detail() {
|
|
358
|
+
* return await describeBalance()
|
|
359
|
+
* }
|
|
360
|
+
* }
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
static id<const Type extends string, const S extends number | keyof StatusMap>(type: Type, status: S): TaggedHTTPError<Type, {
|
|
364
|
+
readonly status: S extends keyof StatusMap ? StatusMap[S] : S;
|
|
365
|
+
}>;
|
|
366
|
+
}
|
|
234
367
|
//#endregion
|
|
235
|
-
export { ElysiaError, ElysiaStatus, InternalServerError, NotFound, PROBLEM_JSON, ParseError, Problem, SelectiveStatus, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
|
|
368
|
+
export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, NotFound, PROBLEM_JSON, ParseError, Problem, ProblemResponseBody, SelectiveStatus, TaggedHTTPError, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
|
package/dist/error.js
CHANGED
|
@@ -437,6 +437,7 @@ var ElysiaStatus = class {
|
|
|
437
437
|
return this.code;
|
|
438
438
|
}
|
|
439
439
|
};
|
|
440
|
+
Object.defineProperty(ElysiaStatus, "name", { value: "ElysiaStatus" });
|
|
440
441
|
const status = (code, response) => new ElysiaStatus(code, response);
|
|
441
442
|
const PROBLEM_JSON = "application/problem+json";
|
|
442
443
|
function problemBody(p) {
|
|
@@ -507,10 +508,82 @@ function problem(statusOrDetail, detail) {
|
|
|
507
508
|
};
|
|
508
509
|
return new ElysiaStatus(body.status ?? 500, problemBody(body), { "content-type": PROBLEM_JSON });
|
|
509
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Self-describing HTTP error
|
|
513
|
+
*
|
|
514
|
+
* Annotate `status`, `headers` and `value` on the subclass and Elysia maps
|
|
515
|
+
* the instance to a response on its own, no `.error(Class, handler)` needed
|
|
516
|
+
*
|
|
517
|
+
* Everything an owned `HTTPError` serves is an RFC 9457 problem document:
|
|
518
|
+
* `type` is the problem type carried on the wire, an annotated object body
|
|
519
|
+
* merges into the envelope, and any other body (or the error message, when
|
|
520
|
+
* there is none) becomes `detail`
|
|
521
|
+
*
|
|
522
|
+
* - `detail()` - the common case. Whatever it returns becomes the `detail`
|
|
523
|
+
* member of the problem document, verbatim, objects included. The envelope
|
|
524
|
+
* still carries `type`, `status` and an auto-filled `title`
|
|
525
|
+
* - `value()` - the escape hatch. Whatever it returns replaces the *whole*
|
|
526
|
+
* response: no envelope, no `application/problem+json`. The annotated
|
|
527
|
+
* `status` and `headers` still apply, only the content is yours
|
|
528
|
+
*
|
|
529
|
+
* `value` wins over `detail`, `detail` over the error message. A knob
|
|
530
|
+
* returning `undefined` falls through to the next one.
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* ```ts
|
|
534
|
+
* class OutOfCredit extends HTTPError<'OUT_OF_CREDIT'> {
|
|
535
|
+
* type = 'OUT_OF_CREDIT' as const
|
|
536
|
+
* override readonly status = 402
|
|
537
|
+
*
|
|
538
|
+
* detail() {
|
|
539
|
+
* return { balance: 0, currency: 'usd' }
|
|
540
|
+
* }
|
|
541
|
+
* }
|
|
542
|
+
* // → { type: 'OUT_OF_CREDIT', title: 'Payment Required', status: 402,
|
|
543
|
+
* // detail: { balance: 0, currency: 'usd' } }
|
|
544
|
+
*
|
|
545
|
+
* class Overdrawn extends HTTPError<'OVERDRAWN'> {
|
|
546
|
+
* type = 'OVERDRAWN' as const
|
|
547
|
+
* override readonly status = 402
|
|
548
|
+
*
|
|
549
|
+
* async detail() {
|
|
550
|
+
* return await describeBalance()
|
|
551
|
+
* }
|
|
552
|
+
* }
|
|
553
|
+
*
|
|
554
|
+
* class Legacy extends HTTPError<'LEGACY'> {
|
|
555
|
+
* type = 'LEGACY' as const
|
|
556
|
+
* override readonly status = 402
|
|
557
|
+
*
|
|
558
|
+
* // full control, served as-is
|
|
559
|
+
* value() {
|
|
560
|
+
* return { code: 'OUT_OF_CREDIT', ok: false }
|
|
561
|
+
* }
|
|
562
|
+
* }
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* @see https://www.rfc-editor.org/info/rfc9457
|
|
566
|
+
* @since 2.0.0
|
|
567
|
+
*/
|
|
568
|
+
var HTTPError = class HTTPError extends Error {
|
|
569
|
+
static id(type, status) {
|
|
570
|
+
const Tagged = class extends HTTPError {};
|
|
571
|
+
Tagged.prototype.type = type;
|
|
572
|
+
Tagged.prototype.name = type;
|
|
573
|
+
Object.defineProperty(Tagged, "name", {
|
|
574
|
+
value: type,
|
|
575
|
+
configurable: true
|
|
576
|
+
});
|
|
577
|
+
const resolved = typeof status === "string" ? require_constants.StatusMap[status] : status;
|
|
578
|
+
if (typeof resolved === "number") Tagged.prototype.status = resolved;
|
|
579
|
+
return Tagged;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
510
582
|
|
|
511
583
|
//#endregion
|
|
512
584
|
exports.ElysiaError = ElysiaError;
|
|
513
585
|
exports.ElysiaStatus = ElysiaStatus;
|
|
586
|
+
exports.HTTPError = HTTPError;
|
|
514
587
|
exports.InternalServerError = InternalServerError;
|
|
515
588
|
exports.NotFound = NotFound;
|
|
516
589
|
exports.PROBLEM_JSON = PROBLEM_JSON;
|
package/dist/error.mjs
CHANGED
|
@@ -436,6 +436,7 @@ var ElysiaStatus = class {
|
|
|
436
436
|
return this.code;
|
|
437
437
|
}
|
|
438
438
|
};
|
|
439
|
+
Object.defineProperty(ElysiaStatus, "name", { value: "ElysiaStatus" });
|
|
439
440
|
const status = (code, response) => new ElysiaStatus(code, response);
|
|
440
441
|
const PROBLEM_JSON = "application/problem+json";
|
|
441
442
|
function problemBody(p) {
|
|
@@ -506,6 +507,77 @@ function problem(statusOrDetail, detail) {
|
|
|
506
507
|
};
|
|
507
508
|
return new ElysiaStatus(body.status ?? 500, problemBody(body), { "content-type": PROBLEM_JSON });
|
|
508
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Self-describing HTTP error
|
|
512
|
+
*
|
|
513
|
+
* Annotate `status`, `headers` and `value` on the subclass and Elysia maps
|
|
514
|
+
* the instance to a response on its own, no `.error(Class, handler)` needed
|
|
515
|
+
*
|
|
516
|
+
* Everything an owned `HTTPError` serves is an RFC 9457 problem document:
|
|
517
|
+
* `type` is the problem type carried on the wire, an annotated object body
|
|
518
|
+
* merges into the envelope, and any other body (or the error message, when
|
|
519
|
+
* there is none) becomes `detail`
|
|
520
|
+
*
|
|
521
|
+
* - `detail()` - the common case. Whatever it returns becomes the `detail`
|
|
522
|
+
* member of the problem document, verbatim, objects included. The envelope
|
|
523
|
+
* still carries `type`, `status` and an auto-filled `title`
|
|
524
|
+
* - `value()` - the escape hatch. Whatever it returns replaces the *whole*
|
|
525
|
+
* response: no envelope, no `application/problem+json`. The annotated
|
|
526
|
+
* `status` and `headers` still apply, only the content is yours
|
|
527
|
+
*
|
|
528
|
+
* `value` wins over `detail`, `detail` over the error message. A knob
|
|
529
|
+
* returning `undefined` falls through to the next one.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```ts
|
|
533
|
+
* class OutOfCredit extends HTTPError<'OUT_OF_CREDIT'> {
|
|
534
|
+
* type = 'OUT_OF_CREDIT' as const
|
|
535
|
+
* override readonly status = 402
|
|
536
|
+
*
|
|
537
|
+
* detail() {
|
|
538
|
+
* return { balance: 0, currency: 'usd' }
|
|
539
|
+
* }
|
|
540
|
+
* }
|
|
541
|
+
* // → { type: 'OUT_OF_CREDIT', title: 'Payment Required', status: 402,
|
|
542
|
+
* // detail: { balance: 0, currency: 'usd' } }
|
|
543
|
+
*
|
|
544
|
+
* class Overdrawn extends HTTPError<'OVERDRAWN'> {
|
|
545
|
+
* type = 'OVERDRAWN' as const
|
|
546
|
+
* override readonly status = 402
|
|
547
|
+
*
|
|
548
|
+
* async detail() {
|
|
549
|
+
* return await describeBalance()
|
|
550
|
+
* }
|
|
551
|
+
* }
|
|
552
|
+
*
|
|
553
|
+
* class Legacy extends HTTPError<'LEGACY'> {
|
|
554
|
+
* type = 'LEGACY' as const
|
|
555
|
+
* override readonly status = 402
|
|
556
|
+
*
|
|
557
|
+
* // full control, served as-is
|
|
558
|
+
* value() {
|
|
559
|
+
* return { code: 'OUT_OF_CREDIT', ok: false }
|
|
560
|
+
* }
|
|
561
|
+
* }
|
|
562
|
+
* ```
|
|
563
|
+
*
|
|
564
|
+
* @see https://www.rfc-editor.org/info/rfc9457
|
|
565
|
+
* @since 2.0.0
|
|
566
|
+
*/
|
|
567
|
+
var HTTPError = class HTTPError extends Error {
|
|
568
|
+
static id(type, status) {
|
|
569
|
+
const Tagged = class extends HTTPError {};
|
|
570
|
+
Tagged.prototype.type = type;
|
|
571
|
+
Tagged.prototype.name = type;
|
|
572
|
+
Object.defineProperty(Tagged, "name", {
|
|
573
|
+
value: type,
|
|
574
|
+
configurable: true
|
|
575
|
+
});
|
|
576
|
+
const resolved = typeof status === "string" ? StatusMap[status] : status;
|
|
577
|
+
if (typeof resolved === "number") Tagged.prototype.status = resolved;
|
|
578
|
+
return Tagged;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
509
581
|
|
|
510
582
|
//#endregion
|
|
511
|
-
export { ElysiaError, ElysiaStatus, InternalServerError, NotFound, PROBLEM_JSON, ParseError, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
|
|
583
|
+
export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, NotFound, PROBLEM_JSON, ParseError, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
|
package/dist/handler/error.d.ts
CHANGED
|
@@ -2,6 +2,25 @@ import { AppHook } from "../types.js";
|
|
|
2
2
|
import { Context } from "../context.js";
|
|
3
3
|
|
|
4
4
|
//#region src/handler/error.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Adopt the intercepted error's `type` into a problem the hook produced
|
|
7
|
+
* without one.
|
|
8
|
+
*
|
|
9
|
+
* `'about:blank'` is RFC 9457's "unspecified" default, so it's the only value
|
|
10
|
+
* worth replacing — an explicit type the handler set always wins.
|
|
11
|
+
*
|
|
12
|
+
* The result is copied rather than mutated: `.error(Class, value)` wraps a
|
|
13
|
+
* non-function into `() => value`, so the very same `ElysiaStatus` is replayed
|
|
14
|
+
* on every request and an in-place write would leak across requests
|
|
15
|
+
*/
|
|
16
|
+
declare function adoptErrorType(result: any, error: any): any;
|
|
17
|
+
/**
|
|
18
|
+
* Response served once every error hook has declined the error.
|
|
19
|
+
*
|
|
20
|
+
* Exported for the JIT: a route with error hooks emits its own catch block,
|
|
21
|
+
* which must land on *this* function rather than re-implementing it
|
|
22
|
+
*/
|
|
23
|
+
declare function fallbackResponse(context: Context, error: any, mapResponse: (response: unknown, set: Context['set'], context?: Context) => unknown, defaultError?: Response): unknown;
|
|
5
24
|
declare function createErrorHandler(onErrors: AppHook['error'] | undefined, mapResponse: (response: unknown, set: Context['set'], ...any: unknown[]) => unknown, defaultError?: Response, allowUnsafe?: boolean): (context: Context, error: Error) => unknown;
|
|
6
25
|
//#endregion
|
|
7
|
-
export { createErrorHandler };
|
|
26
|
+
export { adoptErrorType, createErrorHandler, fallbackResponse };
|