elysia 2.0.0-beta.10 → 2.0.0-beta.11

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.
@@ -46,7 +46,7 @@ function mapResponseWithSet(response, set, request, owned) {
46
46
  case "File":
47
47
  case "Blob": return require_adapter_utils.handleFile(response, set, request);
48
48
  case "ElysiaStatus":
49
- set.status = response.code;
49
+ set.status = response.status;
50
50
  if (response.headers) Object.assign(require_adapter_utils.materializeSetHeaders(set), response.headers);
51
51
  return mapResponseWithSet(response.response, set, request, owned);
52
52
  case void 0: return response ? Response.json(response, set) : new Response(null, set);
@@ -62,7 +62,7 @@ function mapResponse(response, set, request, owned) {
62
62
  const headers = set.headers;
63
63
  if (set.status !== void 0 || set.cookie || headers["\0"] === headers || require_utils.isNotEmpty(headers)) return mapResponseWithSet(response, set, request, owned);
64
64
  if (response instanceof require_error.ElysiaStatus) {
65
- set.status = response.code;
65
+ set.status = response.status;
66
66
  if (response.headers) Object.assign(set.headers, response.headers);
67
67
  return mapResponse(response.response, set, request, owned);
68
68
  }
@@ -84,7 +84,7 @@ function mapCompactResponse(response, request, owned) {
84
84
  case "File":
85
85
  case "Blob": return require_adapter_utils.handleFile(response, void 0, request);
86
86
  case "ElysiaStatus": return mapResponse(response.response, {
87
- status: response.code,
87
+ status: response.status,
88
88
  headers: response.headers ? Object.assign(require_utils.nullObject(), response.headers) : require_utils.nullObject()
89
89
  }, request, owned);
90
90
  case void 0: return response ? Response.json(response) : new Response("");
@@ -121,14 +121,14 @@ function mapFallback(response, set, request, owned = false) {
121
121
  if (response instanceof Promise) return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
122
122
  if (response instanceof Error) return errorToResponse(response, set, request, owned);
123
123
  if (response instanceof require_error.ElysiaStatus) if (set) {
124
- set.status = response.code;
124
+ set.status = response.status;
125
125
  if (response.headers) set.headers = {
126
126
  ...set.headers,
127
127
  ...response.headers
128
128
  };
129
129
  return mapResponse(response.response, set, request, owned);
130
130
  } else return mapResponse(response.response, {
131
- status: response.code,
131
+ status: response.status,
132
132
  headers: response.headers ? { ...response.headers } : {}
133
133
  }, request, owned);
134
134
  if (response instanceof require_universal_file.ElysiaFile) return handleElysiaFile(response, void 0, request);
@@ -45,7 +45,7 @@ function mapResponseWithSet(response, set, request, owned) {
45
45
  case "File":
46
46
  case "Blob": return handleFile(response, set, request);
47
47
  case "ElysiaStatus":
48
- set.status = response.code;
48
+ set.status = response.status;
49
49
  if (response.headers) Object.assign(materializeSetHeaders(set), response.headers);
50
50
  return mapResponseWithSet(response.response, set, request, owned);
51
51
  case void 0: return response ? Response.json(response, set) : new Response(null, set);
@@ -61,7 +61,7 @@ function mapResponse(response, set, request, owned) {
61
61
  const headers = set.headers;
62
62
  if (set.status !== void 0 || set.cookie || headers["\0"] === headers || isNotEmpty(headers)) return mapResponseWithSet(response, set, request, owned);
63
63
  if (response instanceof ElysiaStatus) {
64
- set.status = response.code;
64
+ set.status = response.status;
65
65
  if (response.headers) Object.assign(set.headers, response.headers);
66
66
  return mapResponse(response.response, set, request, owned);
67
67
  }
@@ -83,7 +83,7 @@ function mapCompactResponse(response, request, owned) {
83
83
  case "File":
84
84
  case "Blob": return handleFile(response, void 0, request);
85
85
  case "ElysiaStatus": return mapResponse(response.response, {
86
- status: response.code,
86
+ status: response.status,
87
87
  headers: response.headers ? Object.assign(nullObject(), response.headers) : nullObject()
88
88
  }, request, owned);
89
89
  case void 0: return response ? Response.json(response) : new Response("");
@@ -120,14 +120,14 @@ function mapFallback(response, set, request, owned = false) {
120
120
  if (response instanceof Promise) return response.then((x) => set ? mapResponse(x, set, request, owned) : mapCompactResponse(x, request, owned));
121
121
  if (response instanceof Error) return errorToResponse(response, set, request, owned);
122
122
  if (response instanceof ElysiaStatus) if (set) {
123
- set.status = response.code;
123
+ set.status = response.status;
124
124
  if (response.headers) set.headers = {
125
125
  ...set.headers,
126
126
  ...response.headers
127
127
  };
128
128
  return mapResponse(response.response, set, request, owned);
129
129
  } else return mapResponse(response.response, {
130
- status: response.code,
130
+ status: response.status,
131
131
  headers: response.headers ? { ...response.headers } : {}
132
132
  }, request, owned);
133
133
  if (response instanceof ElysiaFile) return handleElysiaFile(response, void 0, request);
@@ -458,7 +458,7 @@ const _s=tee(_r,${teeCount})\nreturn _fin2(c,_s[0],_s[1])\n}\nreturn _fin2(c,_r,
458
458
  link(require_error.ElysiaStatus, "es");
459
459
  const awaitStr = responseValiAsync ? "await " : "";
460
460
  code += `if(_r instanceof es){
461
- const _vr=va.response[_r.code]
461
+ const _vr=va.response[_r.status]
462
462
  if(_vr)_r.response=${awaitStr}${responseValiAsync ? `(_vr.mayReturnPromise?_vr.From(_r.response,'response',true):_vr.EncodeFrom(_r.response,'response'))` : `_vr.EncodeFrom(_r.response,'response')`}\n}else if(!(_r instanceof Response)&&!(_r instanceof ReadableStream)&&typeof _r?.next!=='function'){\nconst _vr=va.response[c.set.status??200]\nif(_vr)_r=${awaitStr}${responseValiAsync ? `(_vr.mayReturnPromise?_vr.From(_r,'response',true):_vr.EncodeFrom(_r,'response'))` : `_vr.EncodeFrom(_r,'response')`}\n}\n`;
463
463
  code += abortCheck();
464
464
  }
@@ -457,7 +457,7 @@ const _s=tee(_r,${teeCount})\nreturn _fin2(c,_s[0],_s[1])\n}\nreturn _fin2(c,_r,
457
457
  link(ElysiaStatus, "es");
458
458
  const awaitStr = responseValiAsync ? "await " : "";
459
459
  code += `if(_r instanceof es){
460
- const _vr=va.response[_r.code]
460
+ const _vr=va.response[_r.status]
461
461
  if(_vr)_r.response=${awaitStr}${responseValiAsync ? `(_vr.mayReturnPromise?_vr.From(_r.response,'response',true):_vr.EncodeFrom(_r.response,'response'))` : `_vr.EncodeFrom(_r.response,'response')`}\n}else if(!(_r instanceof Response)&&!(_r instanceof ReadableStream)&&typeof _r?.next!=='function'){\nconst _vr=va.response[c.set.status??200]\nif(_vr)_r=${awaitStr}${responseValiAsync ? `(_vr.mayReturnPromise?_vr.From(_r,'response',true):_vr.EncodeFrom(_r,'response'))` : `_vr.EncodeFrom(_r,'response')`}\n}\n`;
462
462
  code += abortCheck();
463
463
  }
@@ -5,8 +5,7 @@ declare class InvalidCookie extends ElysiaError {
5
5
  key?: string | undefined;
6
6
  response: string;
7
7
  status: 400;
8
- problemType: string;
9
- problemTitle: string;
8
+ readonly code = "invalid-cookie";
10
9
  constructor(key?: string | undefined, response?: string, status?: number);
11
10
  static signature(key: string): InvalidCookie;
12
11
  static secret(key?: string): Omit<InvalidCookie, 'status'> & {
@@ -1,4 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_universal_is_production = require('../universal/is-production.js');
2
3
  const require_error = require('../error.js');
3
4
 
4
5
  //#region src/cookie/error.ts
@@ -8,15 +9,17 @@ var InvalidCookie = class InvalidCookie extends require_error.ElysiaError {
8
9
  this.key = key;
9
10
  this.response = response;
10
11
  this.status = 400;
11
- this.problemType = "invalid-cookie";
12
- this.problemTitle = "Invalid Cookie";
12
+ this.code = "invalid-cookie";
13
13
  if (status) this.status = status;
14
14
  }
15
15
  static signature(key) {
16
16
  return new InvalidCookie(key, `"${key}" has invalid cookie signature`);
17
17
  }
18
18
  static secret(key) {
19
- return new InvalidCookie(key, `${key ? `Cookie "${key}"` : "A cookie"} is signed but \`cookie.secrets\` is missing or empty. Set it to a non-empty string: an empty secret signs with a zero-length key, which anyone can forge.`, 500);
19
+ const advice = `${key ? `Cookie "${key}"` : "A cookie"} is signed but \`cookie.secrets\` is missing or empty`;
20
+ const error = new InvalidCookie(key, require_universal_is_production.isProduction() ? "Internal Server Error" : advice, 500);
21
+ error.message = advice;
22
+ return error;
20
23
  }
21
24
  };
22
25
 
@@ -1,3 +1,4 @@
1
+ import { isProduction } from "../universal/is-production.mjs";
1
2
  import { ElysiaError } from "../error.mjs";
2
3
 
3
4
  //#region src/cookie/error.ts
@@ -7,15 +8,17 @@ var InvalidCookie = class InvalidCookie extends ElysiaError {
7
8
  this.key = key;
8
9
  this.response = response;
9
10
  this.status = 400;
10
- this.problemType = "invalid-cookie";
11
- this.problemTitle = "Invalid Cookie";
11
+ this.code = "invalid-cookie";
12
12
  if (status) this.status = status;
13
13
  }
14
14
  static signature(key) {
15
15
  return new InvalidCookie(key, `"${key}" has invalid cookie signature`);
16
16
  }
17
17
  static secret(key) {
18
- return new InvalidCookie(key, `${key ? `Cookie "${key}"` : "A cookie"} is signed but \`cookie.secrets\` is missing or empty. Set it to a non-empty string: an empty secret signs with a zero-length key, which anyone can forge.`, 500);
18
+ const advice = `${key ? `Cookie "${key}"` : "A cookie"} is signed but \`cookie.secrets\` is missing or empty`;
19
+ const error = new InvalidCookie(key, isProduction() ? "Internal Server Error" : advice, 500);
20
+ error.message = advice;
21
+ return error;
19
22
  }
20
23
  };
21
24
 
package/dist/error.d.ts CHANGED
@@ -6,13 +6,26 @@ import { MaybePromise } from "./types.js";
6
6
  declare class ElysiaError<Status extends number = number, Response extends string = string> extends Error {
7
7
  status?: Status;
8
8
  response?: Response;
9
- /** RFC 9457 problem `type` slug (overridden per subclass) */
10
- problemType: string;
11
- /** RFC 9457 problem `title` (overridden per subclass) */
12
- problemTitle?: string;
9
+ /**
10
+ * Stable machine-readable token served in the `code` member, the same
11
+ * contract a {@link HTTPError.id} class speaks (set per subclass)
12
+ */
13
+ readonly code?: string;
14
+ /**
15
+ * RFC 9457 problem type served in the `type` member. Mirrors `code`, or
16
+ * resolves to `<typeBase>/<code>` once {@link HTTPError.typeBase} is set.
17
+ * Defined on the prototype below
18
+ */
19
+ type?: string;
13
20
  constructor(message: string, cause?: Error);
14
21
  toResponse(headers?: Record<string, any>): globalThis.Response;
15
22
  }
23
+ /**
24
+ * Problem members an `ElysiaError` serves. `title` is left to `problemBody`,
25
+ * which derives it from the status — so `response` is only worth serving as
26
+ * `detail` when it says something the title doesn't
27
+ */
28
+ declare function elysiaErrorProblem(error: ElysiaError): Problem;
16
29
  /**
17
30
  * Wrap a string into a TypeBox `error` callback that overrides the default
18
31
  * validation message. Use as `t.Number({ error: validationDetail('x must be a number') })`.
@@ -21,22 +34,19 @@ declare const validationDetail: <T>(message: T) => (error: any) => any;
21
34
  declare class InternalServerError extends ElysiaError {
22
35
  response: string;
23
36
  status: 500;
24
- problemType: string;
25
- problemTitle: string;
37
+ readonly code = "internal-server-error";
26
38
  constructor(response?: string, cause?: Error);
27
39
  }
28
40
  declare class NotFound extends ElysiaError {
29
41
  response: string;
30
42
  status: 404;
31
- problemType: string;
32
- problemTitle: string;
43
+ readonly code = "not-found";
33
44
  constructor(response?: string);
34
45
  }
35
46
  declare class ParseError extends ElysiaError {
36
47
  status: 400;
37
48
  response: string;
38
- problemType: string;
39
- problemTitle: string;
49
+ readonly code = "parse";
40
50
  constructor(cause?: Error);
41
51
  toResponse(headers?: Record<string, any>): Response;
42
52
  }
@@ -109,11 +119,18 @@ declare class ValidationError extends ElysiaError {
109
119
  toResponse(headers?: Record<string, any>): Response;
110
120
  }
111
121
  declare class ElysiaStatus<const in out Code extends number | keyof StatusMap, T = (Code extends keyof StatusMapBack ? StatusMapBack[Code] : Code), const in out Status extends (Code extends keyof StatusMap ? StatusMap[Code] : Code) = (Code extends keyof StatusMap ? StatusMap[Code] : Code)> {
112
- code: Status;
122
+ /**
123
+ * Type-only brand, erased at runtime.
124
+ *
125
+ * `status` + `response` is a shape a handler may write by hand, so without
126
+ * a nominal marker a plain `{ status, response }` literal would be
127
+ * absorbed as a status box by response inference
128
+ */
129
+ readonly ['~status']: true;
130
+ status: Status;
113
131
  response: T;
114
132
  headers?: Record<string, string>;
115
133
  constructor(code: Code, res: T, headers?: Record<string, string>);
116
- get status(): number;
117
134
  }
118
135
  declare const status: <const Code extends number | keyof StatusMap, const T = (Code extends keyof StatusMapBack ? StatusMapBack[Code] : Code)>(code: Code, response?: T) => ElysiaStatus<Code, T, Code extends "Continue" | "Switching Protocols" | "Processing" | "Early Hints" | "OK" | "Created" | "Accepted" | "Non-Authoritative Information" | "No Content" | "Reset Content" | "Partial Content" | "Multi-Status" | "Already Reported" | "Multiple Choices" | "Moved Permanently" | "Found" | "See Other" | "Not Modified" | "Temporary Redirect" | "Permanent Redirect" | "Bad Request" | "Unauthorized" | "Payment Required" | "Forbidden" | "Not Found" | "Method Not Allowed" | "Not Acceptable" | "Proxy Authentication Required" | "Request Timeout" | "Conflict" | "Gone" | "Length Required" | "Precondition Failed" | "Payload Too Large" | "URI Too Long" | "Unsupported Media Type" | "Range Not Satisfiable" | "Expectation Failed" | "I'm a teapot" | "Enhance Your Calm" | "Misdirected Request" | "Unprocessable Content" | "Locked" | "Failed Dependency" | "Too Early" | "Upgrade Required" | "Precondition Required" | "Too Many Requests" | "Request Header Fields Too Large" | "Unavailable For Legal Reasons" | "Internal Server Error" | "Not Implemented" | "Bad Gateway" | "Service Unavailable" | "Gateway Timeout" | "HTTP Version Not Supported" | "Variant Also Negotiates" | "Insufficient Storage" | "Loop Detected" | "Not Extended" | "Network Authentication Required" ? {
119
136
  readonly Continue: 100;
@@ -185,7 +202,14 @@ declare const PROBLEM_JSON = "application/problem+json";
185
202
  * @see https://www.rfc-editor.org/info/rfc9457/
186
203
  */
187
204
  type Problem<Code extends number | keyof StatusMap = number | keyof StatusMap, Extension extends Record<string, unknown> = {}> = {
188
- /** URI (or slug) identifying the problem type. @default 'about:blank' */type?: string; /** Short, human-readable summary of the problem type */
205
+ /** URI (or slug) identifying the problem type. @default 'about:blank' */type?: string;
206
+ /**
207
+ * Extension member carrying a stable machine-readable token.
208
+ *
209
+ * `type` may be widened to a URI by {@link HTTPError.typeBase}, `code`
210
+ * never is
211
+ */
212
+ code?: string; /** Short, human-readable summary of the problem type */
189
213
  title?: string; /** HTTP status. A number or a `StatusMap` name (e.g. `'Conflict'`) */
190
214
  status?: Code; /** Human-readable explanation specific to this occurrence */
191
215
  detail?: string; /** URI identifying the specific occurrence of the problem */
@@ -242,6 +266,7 @@ type SelectiveStatus<in out Res> = <const Code extends keyof Res | StatusMapBack
242
266
  type TaggedHTTPError<Type extends string, Annotation = {}> = {
243
267
  new (message?: string, options?: ErrorOptions): HTTPError<Type> & {
244
268
  type: Type;
269
+ readonly code: Type;
245
270
  } & Annotation;
246
271
  prototype: HTTPError<Type>;
247
272
  };
@@ -306,8 +331,38 @@ declare abstract class HTTPError<const in out T extends string = string> extends
306
331
  /**
307
332
  * RFC 9457 problem type served in the `type` member, a URI or a slug.
308
333
  * Doubles as the tag that discriminates one error class from another
334
+ *
335
+ * On a {@link HTTPError.id} class this mirrors `code`, or resolves to
336
+ * `<typeBase>/<code>` once {@link HTTPError.typeBase} is set
309
337
  */
310
338
  abstract type: T;
339
+ /**
340
+ * Stable machine-readable token served in the `code` member, set by
341
+ * {@link HTTPError.id} to its first argument
342
+ *
343
+ * Unlike `type` it is never widened to a URI, so it stays the value to
344
+ * dispatch on
345
+ */
346
+ readonly code?: string;
347
+ /**
348
+ * Absolute base for the RFC 9457 `type` URI, applied to every class made
349
+ * by {@link HTTPError.id} and to every built-in `ElysiaError`
350
+ * (`NotFound`, `ParseError`, …) — both derive `type` from the same slug
351
+ *
352
+ * Unset, `type` mirrors `code` verbatim. Set, `type` resolves to
353
+ * `<typeBase>/<code>` with the code carried through as written — no
354
+ * casing or separator rewrite — while `code` keeps serving the bare token
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * HTTPError.typeBase = 'https://example.com/errors'
359
+ *
360
+ * class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {}
361
+ * // → { type: 'https://example.com/errors/OUT_OF_CREDIT',
362
+ * // code: 'OUT_OF_CREDIT', … }
363
+ * ```
364
+ */
365
+ static typeBase?: string;
311
366
  /**
312
367
  * HTTP status served when the error reaches the error pipeline,
313
368
  * as a number or a status name (`'Payment Required'`)
@@ -329,9 +384,10 @@ declare abstract class HTTPError<const in out T extends string = string> extends
329
384
  */
330
385
  detail?(): MaybePromise<unknown>;
331
386
  /**
332
- * Create a concrete `HTTPError` subclass carrying `type`
387
+ * Create a concrete `HTTPError` subclass carrying `code`, mirrored into
388
+ * `type` unless {@link HTTPError.typeBase} is set
333
389
  *
334
- * `type` carries no status annotation, annotate `status` in the subclass
390
+ * `code` carries no status annotation, annotate `status` in the subclass
335
391
  * body as a number or a status name
336
392
  *
337
393
  * @example
@@ -343,9 +399,9 @@ declare abstract class HTTPError<const in out T extends string = string> extends
343
399
  * }
344
400
  * ```
345
401
  */
346
- static id<const Type extends string>(type: Type): TaggedHTTPError<Type>;
402
+ static id<const Type extends string>(code: Type): TaggedHTTPError<Type>;
347
403
  /**
348
- * Create a concrete `HTTPError` subclass carrying `type`, annotated with
404
+ * Create a concrete `HTTPError` subclass carrying `code`, annotated with
349
405
  * `status` as a number or a status name
350
406
  *
351
407
  * Annotate what is served with `detail()` (problem `detail` member) or
@@ -354,16 +410,16 @@ declare abstract class HTTPError<const in out T extends string = string> extends
354
410
  * @example
355
411
  * ```ts
356
412
  * class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {}
357
- * class Denied extends HTTPError.id('https://example.com/denied', 'Payment Required') {
413
+ * class Denied extends HTTPError.id('DENIED', 'Payment Required') {
358
414
  * async detail() {
359
415
  * return await describeBalance()
360
416
  * }
361
417
  * }
362
418
  * ```
363
419
  */
364
- static id<const Type extends string, const S extends number | keyof StatusMap>(type: Type, status: S): TaggedHTTPError<Type, {
420
+ static id<const Type extends string, const S extends number | keyof StatusMap>(code: Type, status: S): TaggedHTTPError<Type, {
365
421
  readonly status: S extends keyof StatusMap ? StatusMap[S] : S;
366
422
  }>;
367
423
  }
368
424
  //#endregion
369
- export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, MAX_ERRORS, NotFound, PROBLEM_JSON, ParseError, Problem, ProblemResponseBody, SelectiveStatus, TaggedHTTPError, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
425
+ export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, MAX_ERRORS, NotFound, PROBLEM_JSON, ParseError, Problem, ProblemResponseBody, SelectiveStatus, TaggedHTTPError, ValidationError, elysiaErrorProblem, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
package/dist/error.js CHANGED
@@ -29,20 +29,63 @@ function isCacheableExpected(value, visited = /* @__PURE__ */ new Set()) {
29
29
  var ElysiaError = class extends Error {
30
30
  constructor(message, cause) {
31
31
  super(message);
32
- this.problemType = "about:blank";
33
32
  this.name = this.constructor.name;
34
33
  if (cause) this.cause = cause;
35
34
  }
36
35
  toResponse(headers) {
37
- return problemResponse({
38
- type: this.problemType,
39
- title: this.problemTitle,
40
- status: this.status ?? 500,
41
- detail: this.response !== void 0 && this.response !== this.problemTitle ? this.response : void 0
42
- }, headers);
36
+ return problemResponse(elysiaErrorProblem(this), headers);
43
37
  }
44
38
  };
45
39
  /**
40
+ * `type` an error slug resolves to: the slug verbatim, or `<typeBase>/<slug>`
41
+ * once {@link HTTPError.typeBase} is set. Shared by `ElysiaError` and every
42
+ * class {@link HTTPError.id} makes, so both lanes derive `type` identically
43
+ */
44
+ function problemTypeOf(code) {
45
+ if (code === void 0) return;
46
+ const base = HTTPError.typeBase;
47
+ if (!base) return code;
48
+ return (base.endsWith("/") ? base : base + "/") + code;
49
+ }
50
+ /**
51
+ * Problem members an `ElysiaError` serves. `title` is left to `problemBody`,
52
+ * which derives it from the status — so `response` is only worth serving as
53
+ * `detail` when it says something the title doesn't
54
+ */
55
+ function elysiaErrorProblem(error) {
56
+ const status = error.status ?? 500;
57
+ return {
58
+ type: error.type ?? "about:blank",
59
+ code: error.code,
60
+ status,
61
+ detail: error.response !== void 0 && error.response !== require_constants.StatusMapBack[status] ? error.response : void 0
62
+ };
63
+ }
64
+ /**
65
+ * Install the `type` mirror on an error prototype. Reads `code` off the
66
+ * instance so a subclass that renames its own `code` retags with it, and keeps
67
+ * `type` assignable — a value written onto an instance lands as an own
68
+ * property instead of throwing against a getter-only slot
69
+ */
70
+ function defineProblemType(prototype) {
71
+ Object.defineProperty(prototype, "type", {
72
+ get() {
73
+ return problemTypeOf(this.code);
74
+ },
75
+ set(value) {
76
+ Object.defineProperty(this, "type", {
77
+ value,
78
+ writable: true,
79
+ enumerable: true,
80
+ configurable: true
81
+ });
82
+ },
83
+ enumerable: true,
84
+ configurable: true
85
+ });
86
+ }
87
+ defineProblemType(ElysiaError.prototype);
88
+ /**
46
89
  * Wrap a string into a TypeBox `error` callback that overrides the default
47
90
  * validation message. Use as `t.Number({ error: validationDetail('x must be a number') })`.
48
91
  */
@@ -55,8 +98,7 @@ var InternalServerError = class extends ElysiaError {
55
98
  super(response, cause);
56
99
  this.response = response;
57
100
  this.status = 500;
58
- this.problemType = "internal-server-error";
59
- this.problemTitle = "Internal Server Error";
101
+ this.code = "internal-server-error";
60
102
  }
61
103
  };
62
104
  var NotFound = class extends ElysiaError {
@@ -64,8 +106,7 @@ var NotFound = class extends ElysiaError {
64
106
  super(response);
65
107
  this.response = response;
66
108
  this.status = 404;
67
- this.problemType = "not-found";
68
- this.problemTitle = "Not Found";
109
+ this.code = "not-found";
69
110
  }
70
111
  };
71
112
  var ParseError = class extends ElysiaError {
@@ -73,15 +114,12 @@ var ParseError = class extends ElysiaError {
73
114
  super("Bad Request", cause);
74
115
  this.status = 400;
75
116
  this.response = "Bad Request";
76
- this.problemType = "parse";
77
- this.problemTitle = "Bad Request";
117
+ this.code = "parse";
78
118
  }
79
119
  toResponse(headers) {
80
120
  const cause = this.cause;
81
121
  return problemResponse({
82
- type: this.problemType,
83
- title: this.problemTitle,
84
- status: 400,
122
+ ...elysiaErrorProblem(this),
85
123
  detail: !require_universal_is_production.isProduction() && cause?.message != null ? String(cause.message) : void 0
86
124
  }, headers);
87
125
  }
@@ -487,13 +525,10 @@ const emptyHttpStatus = new Set([
487
525
  var ElysiaStatus = class {
488
526
  constructor(code, res, headers) {
489
527
  const response = res ?? (code in require_constants.StatusMapBack ? require_constants.StatusMapBack[code] : code);
490
- this.code = require_constants.StatusMap[code] ?? code;
491
- if (!emptyHttpStatus.has(this.code)) this.response = response;
528
+ this.status = require_constants.StatusMap[code] ?? code;
529
+ if (!emptyHttpStatus.has(this.status)) this.response = response;
492
530
  this.headers = headers;
493
531
  }
494
- get status() {
495
- return this.code;
496
- }
497
532
  };
498
533
  Object.defineProperty(ElysiaStatus, "name", { value: "ElysiaStatus" });
499
534
  const status = (code, response) => new ElysiaStatus(code, response);
@@ -624,12 +659,17 @@ function problem(statusOrDetail, detail) {
624
659
  * @since 2.0.0
625
660
  */
626
661
  var HTTPError = class HTTPError extends Error {
627
- static id(type, status) {
628
- const Tagged = class extends HTTPError {};
629
- Tagged.prototype.type = type;
630
- Tagged.prototype.name = type;
662
+ static id(code, status) {
663
+ const Tagged = class extends HTTPError {
664
+ constructor(..._args) {
665
+ super(..._args);
666
+ this.code = code;
667
+ }
668
+ };
669
+ defineProblemType(Tagged.prototype);
670
+ Tagged.prototype.name = code;
631
671
  Object.defineProperty(Tagged, "name", {
632
- value: type,
672
+ value: code,
633
673
  configurable: true
634
674
  });
635
675
  const resolved = typeof status === "string" ? require_constants.StatusMap[status] : status;
@@ -648,6 +688,7 @@ exports.NotFound = NotFound;
648
688
  exports.PROBLEM_JSON = PROBLEM_JSON;
649
689
  exports.ParseError = ParseError;
650
690
  exports.ValidationError = ValidationError;
691
+ exports.elysiaErrorProblem = elysiaErrorProblem;
651
692
  exports.internalServerErrorBody = internalServerErrorBody;
652
693
  exports.internalServerErrorBodyString = internalServerErrorBodyString;
653
694
  exports.internalServerErrorResponse = internalServerErrorResponse;
package/dist/error.mjs CHANGED
@@ -28,20 +28,63 @@ function isCacheableExpected(value, visited = /* @__PURE__ */ new Set()) {
28
28
  var ElysiaError = class extends Error {
29
29
  constructor(message, cause) {
30
30
  super(message);
31
- this.problemType = "about:blank";
32
31
  this.name = this.constructor.name;
33
32
  if (cause) this.cause = cause;
34
33
  }
35
34
  toResponse(headers) {
36
- return problemResponse({
37
- type: this.problemType,
38
- title: this.problemTitle,
39
- status: this.status ?? 500,
40
- detail: this.response !== void 0 && this.response !== this.problemTitle ? this.response : void 0
41
- }, headers);
35
+ return problemResponse(elysiaErrorProblem(this), headers);
42
36
  }
43
37
  };
44
38
  /**
39
+ * `type` an error slug resolves to: the slug verbatim, or `<typeBase>/<slug>`
40
+ * once {@link HTTPError.typeBase} is set. Shared by `ElysiaError` and every
41
+ * class {@link HTTPError.id} makes, so both lanes derive `type` identically
42
+ */
43
+ function problemTypeOf(code) {
44
+ if (code === void 0) return;
45
+ const base = HTTPError.typeBase;
46
+ if (!base) return code;
47
+ return (base.endsWith("/") ? base : base + "/") + code;
48
+ }
49
+ /**
50
+ * Problem members an `ElysiaError` serves. `title` is left to `problemBody`,
51
+ * which derives it from the status — so `response` is only worth serving as
52
+ * `detail` when it says something the title doesn't
53
+ */
54
+ function elysiaErrorProblem(error) {
55
+ const status = error.status ?? 500;
56
+ return {
57
+ type: error.type ?? "about:blank",
58
+ code: error.code,
59
+ status,
60
+ detail: error.response !== void 0 && error.response !== StatusMapBack[status] ? error.response : void 0
61
+ };
62
+ }
63
+ /**
64
+ * Install the `type` mirror on an error prototype. Reads `code` off the
65
+ * instance so a subclass that renames its own `code` retags with it, and keeps
66
+ * `type` assignable — a value written onto an instance lands as an own
67
+ * property instead of throwing against a getter-only slot
68
+ */
69
+ function defineProblemType(prototype) {
70
+ Object.defineProperty(prototype, "type", {
71
+ get() {
72
+ return problemTypeOf(this.code);
73
+ },
74
+ set(value) {
75
+ Object.defineProperty(this, "type", {
76
+ value,
77
+ writable: true,
78
+ enumerable: true,
79
+ configurable: true
80
+ });
81
+ },
82
+ enumerable: true,
83
+ configurable: true
84
+ });
85
+ }
86
+ defineProblemType(ElysiaError.prototype);
87
+ /**
45
88
  * Wrap a string into a TypeBox `error` callback that overrides the default
46
89
  * validation message. Use as `t.Number({ error: validationDetail('x must be a number') })`.
47
90
  */
@@ -54,8 +97,7 @@ var InternalServerError = class extends ElysiaError {
54
97
  super(response, cause);
55
98
  this.response = response;
56
99
  this.status = 500;
57
- this.problemType = "internal-server-error";
58
- this.problemTitle = "Internal Server Error";
100
+ this.code = "internal-server-error";
59
101
  }
60
102
  };
61
103
  var NotFound = class extends ElysiaError {
@@ -63,8 +105,7 @@ var NotFound = class extends ElysiaError {
63
105
  super(response);
64
106
  this.response = response;
65
107
  this.status = 404;
66
- this.problemType = "not-found";
67
- this.problemTitle = "Not Found";
108
+ this.code = "not-found";
68
109
  }
69
110
  };
70
111
  var ParseError = class extends ElysiaError {
@@ -72,15 +113,12 @@ var ParseError = class extends ElysiaError {
72
113
  super("Bad Request", cause);
73
114
  this.status = 400;
74
115
  this.response = "Bad Request";
75
- this.problemType = "parse";
76
- this.problemTitle = "Bad Request";
116
+ this.code = "parse";
77
117
  }
78
118
  toResponse(headers) {
79
119
  const cause = this.cause;
80
120
  return problemResponse({
81
- type: this.problemType,
82
- title: this.problemTitle,
83
- status: 400,
121
+ ...elysiaErrorProblem(this),
84
122
  detail: !isProduction() && cause?.message != null ? String(cause.message) : void 0
85
123
  }, headers);
86
124
  }
@@ -486,13 +524,10 @@ const emptyHttpStatus = new Set([
486
524
  var ElysiaStatus = class {
487
525
  constructor(code, res, headers) {
488
526
  const response = res ?? (code in StatusMapBack ? StatusMapBack[code] : code);
489
- this.code = StatusMap[code] ?? code;
490
- if (!emptyHttpStatus.has(this.code)) this.response = response;
527
+ this.status = StatusMap[code] ?? code;
528
+ if (!emptyHttpStatus.has(this.status)) this.response = response;
491
529
  this.headers = headers;
492
530
  }
493
- get status() {
494
- return this.code;
495
- }
496
531
  };
497
532
  Object.defineProperty(ElysiaStatus, "name", { value: "ElysiaStatus" });
498
533
  const status = (code, response) => new ElysiaStatus(code, response);
@@ -623,12 +658,17 @@ function problem(statusOrDetail, detail) {
623
658
  * @since 2.0.0
624
659
  */
625
660
  var HTTPError = class HTTPError extends Error {
626
- static id(type, status) {
627
- const Tagged = class extends HTTPError {};
628
- Tagged.prototype.type = type;
629
- Tagged.prototype.name = type;
661
+ static id(code, status) {
662
+ const Tagged = class extends HTTPError {
663
+ constructor(..._args) {
664
+ super(..._args);
665
+ this.code = code;
666
+ }
667
+ };
668
+ defineProblemType(Tagged.prototype);
669
+ Tagged.prototype.name = code;
630
670
  Object.defineProperty(Tagged, "name", {
631
- value: type,
671
+ value: code,
632
672
  configurable: true
633
673
  });
634
674
  const resolved = typeof status === "string" ? StatusMap[status] : status;
@@ -638,4 +678,4 @@ var HTTPError = class HTTPError extends Error {
638
678
  };
639
679
 
640
680
  //#endregion
641
- export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, MAX_ERRORS, NotFound, PROBLEM_JSON, ParseError, ValidationError, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
681
+ export { ElysiaError, ElysiaStatus, HTTPError, InternalServerError, MAX_ERRORS, NotFound, PROBLEM_JSON, ParseError, ValidationError, elysiaErrorProblem, internalServerErrorBody, internalServerErrorBodyString, internalServerErrorResponse, isProduction, problem, problemBody, problemResponse, status, subValueAt, validationDetail };
@@ -39,9 +39,10 @@ const claimsProblemType = (error) => typeof error?.type === "string" ? !(error i
39
39
  function adoptErrorType(result, error) {
40
40
  const body = result?.response;
41
41
  if (typeof error?.type !== "string" || !claimsProblemType(error) || body?.type !== "about:blank" || result.headers?.["content-type"] !== "application/problem+json") return result;
42
- return new require_error.ElysiaStatus(result.code, {
42
+ return new require_error.ElysiaStatus(result.status, {
43
43
  ...body,
44
- type: error.type
44
+ type: error.type,
45
+ ...typeof error.code === "string" ? { code: error.code } : {}
45
46
  }, result.headers);
46
47
  }
47
48
  /**
@@ -76,8 +77,9 @@ function statusFallbackBody(error, status) {
76
77
  /**
77
78
  * RFC 9457 problem document carrying `detail` verbatim, mirroring `problem()`.
78
79
  */
79
- const problemOf = (self, detail, status) => new require_error.ElysiaStatus(status, require_error.problemBody({
80
+ const problemOf = (self, detail, status, claimsProblem) => new require_error.ElysiaStatus(status, require_error.problemBody({
80
81
  type: self.type ?? "about:blank",
82
+ ...claimsProblem && typeof self.code === "string" ? { code: self.code } : {},
81
83
  detail,
82
84
  status
83
85
  }), { "content-type": require_error.PROBLEM_JSON });
@@ -119,7 +121,7 @@ function fallbackErrorResponse(context, error, mapResponse, defaultError) {
119
121
  const serveMessage = () => {
120
122
  if (!claimsProblem) return legacy();
121
123
  mergeHeaders();
122
- return mapResponse(problemOf(self, statusFallbackBody(error, served), served), context.set, context);
124
+ return mapResponse(problemOf(self, statusFallbackBody(error, served), served, claimsProblem), context.set, context);
123
125
  };
124
126
  const serveDetail = () => {
125
127
  let detail;
@@ -132,10 +134,10 @@ function fallbackErrorResponse(context, error, mapResponse, defaultError) {
132
134
  if (detail instanceof Promise) return detail.then((resolved) => {
133
135
  if (resolved === void 0) return serveMessage();
134
136
  mergeHeaders();
135
- return mapResponse(problemOf(self, resolved, served), context.set, context);
137
+ return mapResponse(problemOf(self, resolved, served, claimsProblem), context.set, context);
136
138
  }, failed);
137
139
  mergeHeaders();
138
- return mapResponse(problemOf(self, detail, served), context.set, context);
140
+ return mapResponse(problemOf(self, detail, served, claimsProblem), context.set, context);
139
141
  };
140
142
  if (error instanceof require_error.ValidationError) return mapResponse(new require_error.ElysiaStatus(422, require_error.problemBody({
141
143
  type: "validation",
@@ -38,9 +38,10 @@ const claimsProblemType = (error) => typeof error?.type === "string" ? !(error i
38
38
  function adoptErrorType(result, error) {
39
39
  const body = result?.response;
40
40
  if (typeof error?.type !== "string" || !claimsProblemType(error) || body?.type !== "about:blank" || result.headers?.["content-type"] !== "application/problem+json") return result;
41
- return new ElysiaStatus(result.code, {
41
+ return new ElysiaStatus(result.status, {
42
42
  ...body,
43
- type: error.type
43
+ type: error.type,
44
+ ...typeof error.code === "string" ? { code: error.code } : {}
44
45
  }, result.headers);
45
46
  }
46
47
  /**
@@ -75,8 +76,9 @@ function statusFallbackBody(error, status) {
75
76
  /**
76
77
  * RFC 9457 problem document carrying `detail` verbatim, mirroring `problem()`.
77
78
  */
78
- const problemOf = (self, detail, status) => new ElysiaStatus(status, problemBody({
79
+ const problemOf = (self, detail, status, claimsProblem) => new ElysiaStatus(status, problemBody({
79
80
  type: self.type ?? "about:blank",
81
+ ...claimsProblem && typeof self.code === "string" ? { code: self.code } : {},
80
82
  detail,
81
83
  status
82
84
  }), { "content-type": PROBLEM_JSON });
@@ -118,7 +120,7 @@ function fallbackErrorResponse(context, error, mapResponse, defaultError) {
118
120
  const serveMessage = () => {
119
121
  if (!claimsProblem) return legacy();
120
122
  mergeHeaders();
121
- return mapResponse(problemOf(self, statusFallbackBody(error, served), served), context.set, context);
123
+ return mapResponse(problemOf(self, statusFallbackBody(error, served), served, claimsProblem), context.set, context);
122
124
  };
123
125
  const serveDetail = () => {
124
126
  let detail;
@@ -131,10 +133,10 @@ function fallbackErrorResponse(context, error, mapResponse, defaultError) {
131
133
  if (detail instanceof Promise) return detail.then((resolved) => {
132
134
  if (resolved === void 0) return serveMessage();
133
135
  mergeHeaders();
134
- return mapResponse(problemOf(self, resolved, served), context.set, context);
136
+ return mapResponse(problemOf(self, resolved, served, claimsProblem), context.set, context);
135
137
  }, failed);
136
138
  mergeHeaders();
137
- return mapResponse(problemOf(self, detail, served), context.set, context);
139
+ return mapResponse(problemOf(self, detail, served, claimsProblem), context.set, context);
138
140
  };
139
141
  if (error instanceof ValidationError) return mapResponse(new ElysiaStatus(422, problemBody({
140
142
  type: "validation",
@@ -19,8 +19,9 @@ function cachedResponse(body, status, headers) {
19
19
  }
20
20
  const NOT_FOUND_BODY = JSON.stringify({
21
21
  type: "not-found",
22
- title: "Not Found",
23
- status: 404
22
+ code: "not-found",
23
+ status: 404,
24
+ title: "Not Found"
24
25
  });
25
26
  const getNotFound = cachedResponse(NOT_FOUND_BODY, 404, { "content-type": require_error.PROBLEM_JSON });
26
27
  function forwardError(value) {
@@ -18,8 +18,9 @@ function cachedResponse(body, status, headers) {
18
18
  }
19
19
  const NOT_FOUND_BODY = JSON.stringify({
20
20
  type: "not-found",
21
- title: "Not Found",
22
- status: 404
21
+ code: "not-found",
22
+ status: 404,
23
+ title: "Not Found"
23
24
  });
24
25
  const getNotFound = cachedResponse(NOT_FOUND_BODY, 404, { "content-type": PROBLEM_JSON });
25
26
  function forwardError(value) {
package/dist/package.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  //#region package.json
3
- var version = "2.0.0-beta.10";
3
+ var version = "2.0.0-beta.11";
4
4
 
5
5
  //#endregion
6
6
  Object.defineProperty(exports, 'version', {
package/dist/package.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "2.0.0-beta.10";
2
+ var version = "2.0.0-beta.11";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/dist/types.d.ts CHANGED
@@ -685,8 +685,8 @@ type ExtractResolveFromMacro<A> = IsNever<A> extends true ? {} : A extends AnyEl
685
685
  type ExtractOnlyResponseFromMacro<A> = IsNever<A> extends true ? {} : Extract<A, AnyElysiaStatus> extends infer A ? IsNever<A> extends true ? {} : {
686
686
  return: MergeResponseStatus<A>;
687
687
  } : {};
688
- type MergeResponseStatus<A> = { [status in keyof UnionToIntersect<A extends ElysiaStatus<any, any, infer Status> ? { [A in Status]: 1 } : never>]: Extract<A, {
689
- code: status;
688
+ type MergeResponseStatus<A> = { [status in keyof UnionToIntersect<A extends ElysiaStatus<any, any, infer Status> ? { [A in Status]: 1 } : never>]: Extract<A, AnyElysiaStatus & {
689
+ status: status;
690
690
  }>['response'] extends infer Value ? IsAny<Value> extends true ? StatusMapBack[status] : Value : never };
691
691
  type ExtractAllResponseFromMacro<A> = IsNever<A> extends true ? {} : {
692
692
  return: MergeResponseStatus<A> & (Exclude<A, AnyElysiaStatus> extends infer A ? IsAny<A> extends true ? {} : IsNever<A> extends true ? {} : NonNullable<void> extends A ? {} : undefined extends A ? {} : {
@@ -808,6 +808,16 @@ type OwnedError<E> = E extends {
808
808
  type ErrorProblemType<E> = OwnedError<E> extends true ? E extends {
809
809
  type: infer T extends string;
810
810
  } ? T : 'about:blank' : 'about:blank';
811
+ /**
812
+ * `code` extension member an error contributes a class made by
813
+ * `HTTPError.id`, or a built-in `ElysiaError`. `type` may be widened to a URI
814
+ * by `HTTPError.typeBase`, `code` never is
815
+ */
816
+ type ErrorProblemCode<E> = E extends {
817
+ code: infer C extends string;
818
+ } ? string extends C ? {} : {
819
+ code: C;
820
+ } : {};
811
821
  /**
812
822
  * Problem document served for a `detail` annotation. `detail` is carried
813
823
  * verbatim, objects included — it is never spread into the envelope
@@ -815,7 +825,7 @@ type ErrorProblemType<E> = OwnedError<E> extends true ? E extends {
815
825
  type ProblemOf<E, Detail> = ProblemResponseBody<ErrorFallbackStatus<E>, {
816
826
  type: ErrorProblemType<E>;
817
827
  detail: Detail;
818
- }>;
828
+ } & ErrorProblemCode<E>>;
819
829
  /**
820
830
  * Tier 3 — nothing annotated. An error that claimed a problem serves its
821
831
  * message as `detail`, anything else keeps the legacy raw lane
@@ -902,7 +912,7 @@ type ComposeElysiaResponse<Schema extends RouteSchema, Handle, Possibility exten
902
912
  expected?: string;
903
913
  };
904
914
  })>>;
905
- type ExtractErrorFromHandle<in out Handle> = { [ErrorResponse in Extract<Handle, AnyElysiaStatus> as ErrorResponse extends AnyElysiaStatus ? ErrorResponse['code'] : never]: Prettify<ErrorResponse['response']> };
915
+ type ExtractErrorFromHandle<in out Handle> = { [ErrorResponse in Extract<Handle, AnyElysiaStatus> as ErrorResponse extends AnyElysiaStatus ? ErrorResponse['status'] : never]: Prettify<ErrorResponse['response']> };
906
916
  /**
907
917
  * Status used when an error handler returns a plain value (or falls through):
908
918
  * the error's declared literal `status`, otherwise 500
package/dist/ws/route.js CHANGED
@@ -170,12 +170,7 @@ function wsErrorFrame(error) {
170
170
  } catch {}
171
171
  if (typeof error?.toResponse === "function") {
172
172
  if (error instanceof require_error.ElysiaError && error.toResponse === require_error.ElysiaError.prototype.toResponse) try {
173
- return JSON.stringify(require_error.problemBody({
174
- type: error.problemType,
175
- title: error.problemTitle,
176
- status: error.status ?? 500,
177
- detail: error.response !== void 0 && error.response !== error.problemTitle ? error.response : void 0
178
- }));
173
+ return JSON.stringify(require_error.problemBody(require_error.elysiaErrorProblem(error)));
179
174
  } catch {}
180
175
  try {
181
176
  const r = error.toResponse();
@@ -198,6 +193,7 @@ function wsProblemFrame(error) {
198
193
  const stringify = (value) => typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
199
194
  const problemFrame = (detail) => JSON.stringify(require_error.problemBody({
200
195
  type: error.type ?? "about:blank",
196
+ ...typeof error.code === "string" ? { code: error.code } : {},
201
197
  detail,
202
198
  status: served
203
199
  }));
package/dist/ws/route.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isBun } from "../universal/constants.mjs";
2
2
  import { assignOwn, deriveEntryFn, isNotEmpty, isSocketQuiet, nullObject } from "../utils.mjs";
3
3
  import { isBridgeLive } from "../type/bridge.mjs";
4
- import { ElysiaError, ElysiaStatus, ValidationError, internalServerErrorBodyString, problemBody, problemResponse } from "../error.mjs";
4
+ import { ElysiaError, ElysiaStatus, ValidationError, elysiaErrorProblem, internalServerErrorBodyString, problemBody, problemResponse } from "../error.mjs";
5
5
  import { hasSyncHmac } from "../cookie/crypto.mjs";
6
6
  import { mapResponse } from "../adapter/web-standard/handler.mjs";
7
7
  import { parseQueryFromURL } from "../parse-query.mjs";
@@ -169,12 +169,7 @@ function wsErrorFrame(error) {
169
169
  } catch {}
170
170
  if (typeof error?.toResponse === "function") {
171
171
  if (error instanceof ElysiaError && error.toResponse === ElysiaError.prototype.toResponse) try {
172
- return JSON.stringify(problemBody({
173
- type: error.problemType,
174
- title: error.problemTitle,
175
- status: error.status ?? 500,
176
- detail: error.response !== void 0 && error.response !== error.problemTitle ? error.response : void 0
177
- }));
172
+ return JSON.stringify(problemBody(elysiaErrorProblem(error)));
178
173
  } catch {}
179
174
  try {
180
175
  const r = error.toResponse();
@@ -197,6 +192,7 @@ function wsProblemFrame(error) {
197
192
  const stringify = (value) => typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
198
193
  const problemFrame = (detail) => JSON.stringify(problemBody({
199
194
  type: error.type ?? "about:blank",
195
+ ...typeof error.code === "string" ? { code: error.code } : {},
200
196
  detail,
201
197
  status: served
202
198
  }));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "elysia",
3
3
  "description": "Ergonomic Framework for Human",
4
- "version": "2.0.0-beta.10",
4
+ "version": "2.0.0-beta.11",
5
5
  "author": {
6
6
  "name": "saltyAom",
7
7
  "url": "https://github.com/SaltyAom",
@@ -287,9 +287,6 @@
287
287
  "@types/bun": {
288
288
  "optional": true
289
289
  },
290
- "openapi-types": {
291
- "optional": true
292
- },
293
290
  "typescript": {
294
291
  "optional": true
295
292
  }