elysia 2.0.0-beta.30 → 2.0.0-beta.32

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/base.js CHANGED
@@ -661,7 +661,8 @@ var Elysia = class Elysia {
661
661
  throw err;
662
662
  }));
663
663
  }
664
- return this;
664
+ if (!result || result === this) return this;
665
+ return this.use(result);
665
666
  }
666
667
  #use(app) {
667
668
  let addedByThisCall;
@@ -969,7 +970,7 @@ var Elysia = class Elysia {
969
970
  this.fetchFn = void 0;
970
971
  this.#routerBuilt = false;
971
972
  require_context.clearContextCache(this);
972
- try {
973
+ if (this["~map"]) try {
973
974
  this.#buildRouter(false);
974
975
  } catch (error) {
975
976
  this.compiled = previousCompiled;
@@ -1498,7 +1499,7 @@ var Elysia = class Elysia {
1498
1499
  let eager = false;
1499
1500
  if (length) {
1500
1501
  const programId = this["~programId"];
1501
- if (!require_compile_aot.Compiled.claim(programId, this["~aotFingerprint"] = require_compile_aot.createAotFingerprint())) require_compile_aot.Compiled.assertUncontested(programId);
1502
+ require_compile_aot.Compiled.claim(programId, this["~aotFingerprint"] = require_compile_aot.createAotFingerprint(), this["~config"]?.name || "");
1502
1503
  markDuplicates = require_compile_aot.Compiled.hasProgram(programId) || precompile !== true && require_universal_is_production.isProduction() && !require_compile_aot.Capture.isAotBuildEnv();
1503
1504
  if (hasTypeBoxSchema) try {
1504
1505
  require_type_bridge.warmTypebox();
package/dist/base.mjs CHANGED
@@ -659,7 +659,8 @@ var Elysia = class Elysia {
659
659
  throw err;
660
660
  }));
661
661
  }
662
- return this;
662
+ if (!result || result === this) return this;
663
+ return this.use(result);
663
664
  }
664
665
  #use(app) {
665
666
  let addedByThisCall;
@@ -967,7 +968,7 @@ var Elysia = class Elysia {
967
968
  this.fetchFn = void 0;
968
969
  this.#routerBuilt = false;
969
970
  clearContextCache(this);
970
- try {
971
+ if (this["~map"]) try {
971
972
  this.#buildRouter(false);
972
973
  } catch (error) {
973
974
  this.compiled = previousCompiled;
@@ -1496,7 +1497,7 @@ var Elysia = class Elysia {
1496
1497
  let eager = false;
1497
1498
  if (length) {
1498
1499
  const programId = this["~programId"];
1499
- if (!Compiled.claim(programId, this["~aotFingerprint"] = createAotFingerprint())) Compiled.assertUncontested(programId);
1500
+ Compiled.claim(programId, this["~aotFingerprint"] = createAotFingerprint(), this["~config"]?.name || "");
1500
1501
  markDuplicates = Compiled.hasProgram(programId) || precompile !== true && isProduction() && !Capture.isAotBuildEnv();
1501
1502
  if (hasTypeBoxSchema) try {
1502
1503
  warmTypebox();
@@ -89,8 +89,8 @@ interface HandlerManifest {
89
89
  };
90
90
  }
91
91
  interface CompiledSnapshot {
92
- registered: CompiledProgramRegistration | undefined;
93
- claimed: WeakRef<ProgramId> | undefined;
92
+ registered: Record<string, CompiledProgramRegistration | undefined> | undefined;
93
+ claimed: Record<string, WeakRef<ProgramId> | undefined> | undefined;
94
94
  programs: WeakMap<ProgramId, CompiledProgram>;
95
95
  }
96
96
  interface CompiledProgramRegistration {
@@ -129,9 +129,8 @@ declare function reconstruct(): ReconstructImpl;
129
129
  declare const aotDriftMessage: (method: string, path: string) => string;
130
130
  declare function warnAotDrift(method: string, path: string): void;
131
131
  declare abstract class Compiled {
132
- static register(manifest: CompiledProgramRegistration): void;
133
- static claim(id: ProgramId, fingerprint: AotFingerprint): boolean;
134
- static assertUncontested(id: ProgramId): void;
132
+ static register(manifest: CompiledProgramRegistration, key?: string): void;
133
+ static claim(id: ProgramId, fingerprint: AotFingerprint, key?: string): boolean;
135
134
  static set reconstruct(impl: ReconstructImpl | undefined);
136
135
  static getHandler(id: ProgramId | undefined, method: string, path: string): FrozenHandler | undefined;
137
136
  static hasProgram(id: ProgramId | undefined): boolean;
@@ -51,26 +51,25 @@ let claimed;
51
51
  let programs = /* @__PURE__ */ new WeakMap();
52
52
  const programFor = (id) => id ? programs.get(id) : void 0;
53
53
  var Compiled = class {
54
- static register(manifest) {
55
- registered = manifest;
56
- claimed = void 0;
54
+ static register(manifest, key = "") {
55
+ (registered ??= require_utils.nullObject())[key] = manifest;
56
+ claimed ??= require_utils.nullObject();
57
57
  }
58
- static claim(id, fingerprint) {
59
- if (!registered || claimed) return false;
60
- const manifest = registered;
58
+ static claim(id, fingerprint, key = "") {
59
+ const manifest = registered?.[key];
60
+ if (!manifest) {
61
+ if (claimed && claimed[key]?.deref() !== id && !isAotBuildEnv()) throw new Error(`[elysia-aot] App "${key}" has no AOT manifest. Name and export it, .mount() ones too`);
62
+ return false;
63
+ }
61
64
  if (manifest.fingerprint.abi !== fingerprint.abi) throw new Error(`[elysia-aot] Mismatch fingerprint`);
62
- claimed = new WeakRef(id);
63
- registered = void 0;
65
+ claimed[key] = new WeakRef(id);
66
+ registered[key] = void 0;
64
67
  programs.set(id, {
65
68
  ...manifest,
66
69
  builtGroups: /* @__PURE__ */ new Set()
67
70
  });
68
71
  return true;
69
72
  }
70
- static assertUncontested(id) {
71
- if (claimed === void 0 || claimed.deref() === id || isAotBuildEnv()) return;
72
- throw new Error("[elysia-aot] A second Elysia app built a router in this process. An AOT build seals one app per process.");
73
- }
74
73
  static set reconstruct(impl) {
75
74
  reconstructImpl = impl;
76
75
  }
@@ -51,26 +51,25 @@ let claimed;
51
51
  let programs = /* @__PURE__ */ new WeakMap();
52
52
  const programFor = (id) => id ? programs.get(id) : void 0;
53
53
  var Compiled = class {
54
- static register(manifest) {
55
- registered = manifest;
56
- claimed = void 0;
54
+ static register(manifest, key = "") {
55
+ (registered ??= nullObject())[key] = manifest;
56
+ claimed ??= nullObject();
57
57
  }
58
- static claim(id, fingerprint) {
59
- if (!registered || claimed) return false;
60
- const manifest = registered;
58
+ static claim(id, fingerprint, key = "") {
59
+ const manifest = registered?.[key];
60
+ if (!manifest) {
61
+ if (claimed && claimed[key]?.deref() !== id && !isAotBuildEnv()) throw new Error(`[elysia-aot] App "${key}" has no AOT manifest. Name and export it, .mount() ones too`);
62
+ return false;
63
+ }
61
64
  if (manifest.fingerprint.abi !== fingerprint.abi) throw new Error(`[elysia-aot] Mismatch fingerprint`);
62
- claimed = new WeakRef(id);
63
- registered = void 0;
65
+ claimed[key] = new WeakRef(id);
66
+ registered[key] = void 0;
64
67
  programs.set(id, {
65
68
  ...manifest,
66
69
  builtGroups: /* @__PURE__ */ new Set()
67
70
  });
68
71
  return true;
69
72
  }
70
- static assertUncontested(id) {
71
- if (claimed === void 0 || claimed.deref() === id || isAotBuildEnv()) return;
72
- throw new Error("[elysia-aot] A second Elysia app built a router in this process. An AOT build seals one app per process.");
73
- }
74
73
  static set reconstruct(impl) {
75
74
  reconstructImpl = impl;
76
75
  }
@@ -414,7 +414,7 @@ function compileHandlerJit(options, tailPass) {
414
414
  link(hook.beforeHandle, "bf");
415
415
  const deriveEntries = hook["~deriveEntries"];
416
416
  const chainGuard = abortChainGuard();
417
- const mapped = require_compile_handler_utils.mapBeforeHandle(hook.beforeHandle, deriveEntries, link, asyncMode, buildReport("beforeHandle"), chainGuard, arm);
417
+ const mapped = require_compile_handler_utils.mapBeforeHandle(hook.beforeHandle, deriveEntries, link, asyncMode, buildReport("beforeHandle"), chainGuard, arm, errorClasses);
418
418
  code += beforeHandlePrefix ? `if(${chainGuard ? `!${chainGuard}&&` : ""}_r===undefined){\n${mapped}}\n` : mapped;
419
419
  }
420
420
  }
@@ -463,7 +463,7 @@ function compileHandlerJit(options, tailPass) {
463
463
  code += require_compile_handler_utils.mapChainHook(hook.afterHandle, "af", asyncMode, buildReport("afterHandle"), abortChainGuard(), arm);
464
464
  }
465
465
  code += endTrace("afterHandle");
466
- if (hasAfterHandle) code += abortCheck();
466
+ if (hasAfterHandle) code += abortCheck() + plain(rethrow("_r"));
467
467
  }
468
468
  if (hasMapResponse || hasTrace) {
469
469
  const mrLen = hook?.mapResponse?.length ?? 0;
@@ -531,15 +531,17 @@ function compileHandlerJit(options, tailPass) {
531
531
  asyncMode.r = false;
532
532
  asyncMode.e = true;
533
533
  }
534
+ const declined = endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule);
534
535
  body += errorHead + `let _r${hasMapResponse ? ",tmp" : ""}\n` + require_compile_handler_utils.mapError(hook.error, [
535
536
  map,
536
537
  link,
537
538
  responseMap,
538
- (hasMapResponse ? `c.responseValue=_r\n` + require_compile_handler_utils.mapChainHook(hook.mapResponse, "mr", asyncMode, void 0, abortChainGuard(), arm) : "") + endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule),
539
+ (hasMapResponse ? `c.responseValue=_r\n` + require_compile_handler_utils.mapChainHook(hook.mapResponse, "mr", asyncMode, void 0, abortChainGuard(), arm) : "") + declined,
539
540
  signPrefix,
540
541
  asyncMode,
541
- arm
542
- ], abortChainGuard(), require_compile_handler_utils.asyncTail(asyncMode)) + endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule) + `return _efb(e,c)\n`;
542
+ arm,
543
+ declined
544
+ ], abortChainGuard(), require_compile_handler_utils.asyncTail(asyncMode)) + declined + `return _efb(e,c)\n`;
543
545
  } else body += endTrace("error") + freThenSchedule("e");
544
546
  const hookThrew = `catch(_ee){${freThenSchedule("_ee").trimEnd()}}}\n`;
545
547
  const syncFirst = typeof asyncMode === "object" && !asyncMode.async;
@@ -414,7 +414,7 @@ function compileHandlerJit(options, tailPass) {
414
414
  link(hook.beforeHandle, "bf");
415
415
  const deriveEntries = hook["~deriveEntries"];
416
416
  const chainGuard = abortChainGuard();
417
- const mapped = mapBeforeHandle(hook.beforeHandle, deriveEntries, link, asyncMode, buildReport("beforeHandle"), chainGuard, arm);
417
+ const mapped = mapBeforeHandle(hook.beforeHandle, deriveEntries, link, asyncMode, buildReport("beforeHandle"), chainGuard, arm, errorClasses);
418
418
  code += beforeHandlePrefix ? `if(${chainGuard ? `!${chainGuard}&&` : ""}_r===undefined){\n${mapped}}\n` : mapped;
419
419
  }
420
420
  }
@@ -463,7 +463,7 @@ function compileHandlerJit(options, tailPass) {
463
463
  code += mapChainHook(hook.afterHandle, "af", asyncMode, buildReport("afterHandle"), abortChainGuard(), arm);
464
464
  }
465
465
  code += endTrace("afterHandle");
466
- if (hasAfterHandle) code += abortCheck();
466
+ if (hasAfterHandle) code += abortCheck() + plain(rethrow("_r"));
467
467
  }
468
468
  if (hasMapResponse || hasTrace) {
469
469
  const mrLen = hook?.mapResponse?.length ?? 0;
@@ -531,15 +531,17 @@ function compileHandlerJit(options, tailPass) {
531
531
  asyncMode.r = false;
532
532
  asyncMode.e = true;
533
533
  }
534
+ const declined = endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule);
534
535
  body += errorHead + `let _r${hasMapResponse ? ",tmp" : ""}\n` + mapError(hook.error, [
535
536
  map,
536
537
  link,
537
538
  responseMap,
538
- (hasMapResponse ? `c.responseValue=_r\n` + mapChainHook(hook.mapResponse, "mr", asyncMode, void 0, abortChainGuard(), arm) : "") + endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule),
539
+ (hasMapResponse ? `c.responseValue=_r\n` + mapChainHook(hook.mapResponse, "mr", asyncMode, void 0, abortChainGuard(), arm) : "") + declined,
539
540
  signPrefix,
540
541
  asyncMode,
541
- arm
542
- ], abortChainGuard(), asyncTail(asyncMode)) + endTrace("error") + (hasDeriveDispose ? schedule + abortCatch : abortCatch + schedule) + `return _efb(e,c)\n`;
542
+ arm,
543
+ declined
544
+ ], abortChainGuard(), asyncTail(asyncMode)) + declined + `return _efb(e,c)\n`;
543
545
  } else body += endTrace("error") + freThenSchedule("e");
544
546
  const hookThrew = `catch(_ee){${freThenSchedule("_ee").trimEnd()}}}\n`;
545
547
  const syncFirst = typeof asyncMode === "object" && !asyncMode.async;
@@ -54,6 +54,7 @@ const handlerParams = () => _handlerParams ??= {
54
54
  const classes = require_handler_utils.returnedErrorClasses(c.hook);
55
55
  return classes ? require_handler_utils.forwardErrorOf(classes) : require_handler_utils.forwardError;
56
56
  },
57
+ ec: (c) => require_handler_utils.returnedErrorClasses(c.hook),
57
58
  fre: (c) => require_handler_utils.finalizeRouteErrorOf(c.hook),
58
59
  fbr: () => require_handler_error.fallbackResponse,
59
60
  aet: () => require_handler_error.adoptErrorType,
@@ -54,6 +54,7 @@ const handlerParams = () => _handlerParams ??= {
54
54
  const classes = returnedErrorClasses(c.hook);
55
55
  return classes ? forwardErrorOf(classes) : forwardError;
56
56
  },
57
+ ec: (c) => returnedErrorClasses(c.hook),
57
58
  fre: (c) => finalizeRouteErrorOf(c.hook),
58
59
  fbr: () => fallbackResponse,
59
60
  aet: () => adoptErrorType,
@@ -30,7 +30,7 @@ declare function deriveModes(hooks: Function[], entries?: readonly DeriveEntry[]
30
30
  * their own entries joined already do
31
31
  */
32
32
  declare function joinDeriveEntries(parts: readonly (Partial<AppHook> | undefined)[]): DeriveEntry[] | undefined;
33
- declare function mapBeforeHandle(_hooks: AppHook['beforeHandle'] | AppHook['beforeHandle'][0], derive: readonly DeriveEntry[] | undefined, link: Link, mode: AsyncMode, report?: TraceReporter, abortGuard?: string, arm?: string): string;
33
+ declare function mapBeforeHandle(_hooks: AppHook['beforeHandle'] | AppHook['beforeHandle'][0], derive: readonly DeriveEntry[] | undefined, link: Link, mode: AsyncMode, report?: TraceReporter, abortGuard?: string, arm?: string, errorClasses?: Function[]): string;
34
34
  type BeforeHandleContext = {
35
35
  request: Request;
36
36
  '~sig'?: AbortSignal;
@@ -39,7 +39,7 @@ declare function runBeforeHandlePrefix(prefix: CompactBeforeHandlePrefix, contex
39
39
  declare function runBeforeHandlePrefixAsync(prefix: CompactBeforeHandlePrefix, context: BeforeHandleContext, abort?: 1): Promise<any>;
40
40
  declare function mapChainHook(_hooks: Function | Function[], prefix: string, mode: AsyncMode, report?: TraceReporter, abortGuard?: string, arm?: string): string;
41
41
  declare const mapAfterResponse: (event: MaybeArray<AfterResponseHandler<any, any, undefined, "local">>, rest?: [report?: TraceReporter | undefined] | undefined, abortGuard?: string, tail?: TailMode) => string;
42
- declare const mapError: (event: MaybeArray<ErrorHandler<any, any, any>>, rest?: [map: string, link: Link, mapResponse: (response: unknown, set: Context["set"], ...params: unknown[]) => unknown, schedule: string, sign: string, mode: AsyncMode, arm?: string | undefined] | undefined, abortGuard?: string, tail?: TailMode) => string;
42
+ declare const mapError: (event: MaybeArray<ErrorHandler<any, any, any>>, rest?: [map: string, link: Link, mapResponse: (response: unknown, set: Context["set"], ...params: unknown[]) => unknown, schedule: string, sign: string, mode: AsyncMode, arm: string | undefined, declined: string] | undefined, abortGuard?: string, tail?: TailMode) => string;
43
43
  interface QueryWalkState {
44
44
  array: Record<string, 1> | undefined;
45
45
  object: Record<string, 1> | undefined;
@@ -203,12 +203,13 @@ function joinDeriveEntries(parts) {
203
203
  }
204
204
  if (at) return require_utils.occurrenceDeriveEntries(hooks, at);
205
205
  }
206
- function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm) {
206
+ function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm, errorClasses) {
207
207
  const hooks = toArray(_hooks);
208
208
  const modes = deriveModes(hooks, derive);
209
209
  const tail = asyncTail(mode);
210
210
  let code = "";
211
211
  let needsEs = false;
212
+ const early = "if(tmp instanceof es||tmp instanceof Error" + (errorClasses ? "||ec.some((C)=>tmp instanceof C)" : "") + ")_r=tmp\n";
212
213
  for (let i = 0; i < hooks.length; i++) {
213
214
  const fn = hooks[i];
214
215
  const guard = i > 0 ? `${abortGuard ? `!${abortGuard}&&` : ""}_r===undefined` : void 0;
@@ -222,18 +223,20 @@ function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm) {
222
223
  link(require_handler_utils.registerDeriveDisposable, "dsp");
223
224
  if (modes[i]) {
224
225
  link(replaceDeriveContext, "rdc");
225
- code += "if(tmp instanceof es)_r=tmp\nelse if(tmp){if(typeof tmp==='object'||typeof tmp==='function'){const _pc=c;c=rdc(c,tmp);for(const _k of Reflect.ownKeys(tmp))dsp(c,c[_k],_pc)}tmp=undefined}\n";
226
+ code += early + "else if(tmp){if(typeof tmp==='object'||typeof tmp==='function'){const _pc=c;c=rdc(c,tmp);for(const _k of Reflect.ownKeys(tmp))dsp(c,c[_k],_pc)}tmp=undefined}\n";
226
227
  } else {
227
228
  const keys = extractDeriveKeys(fn);
228
229
  const merge = !!keys && !!keys.length && !keys.includes("__proto__") ? keys.map((k) => `_v=tmp[${JSON.stringify(k)}];dsp(c,_v);c[${JSON.stringify(k)}]=_v`).join(";") : "if(Object.hasOwn(tmp,'__proto__')){for(const _k of Object.keys(tmp))dsp(c,tmp[_k]);Object.defineProperties(c,Object.getOwnPropertyDescriptors(tmp))}else{_v=Object.assign({},tmp);for(const _k of Reflect.ownKeys(_v)){dsp(c,_v[_k]);c[_k]=_v[_k]}}";
229
- code += `if(tmp instanceof es)_r=tmp
230
- else if(tmp){${merge};tmp=undefined}\n`;
230
+ code += early + `else if(tmp){${merge};tmp=undefined}\n`;
231
231
  }
232
232
  } else code += "if(tmp!==undefined)_r=tmp\n";
233
233
  code += t.end("tmp");
234
234
  if (tail || guard) code += "}\n";
235
235
  }
236
- if (needsEs) link(require_error.ElysiaStatus, "es");
236
+ if (needsEs) {
237
+ link(require_error.ElysiaStatus, "es");
238
+ if (errorClasses) link(errorClasses, "ec");
239
+ }
237
240
  return code;
238
241
  }
239
242
  const compactBeforeHandleChunks = (prefix) => {
@@ -294,11 +297,11 @@ const mapAfterResponse = /* @__PURE__ */ map((i, fn, [report]) => {
294
297
  const call = require_compile_utils.isAsyncFunction(fn) ? `await ar${at(i)}(c)\n` : `let _ar=ar${at(i)}(c)\nif(typeof _ar?.then==='function')await _ar\n`;
295
298
  return `try{${t.begin}${call}${t.end()}}catch(_e){${t.end("_e")}console.error(_e)}\n`;
296
299
  });
297
- const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, mode, arm]) => {
300
+ const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, mode, arm, declined]) => {
298
301
  link(mapResponse, "rm");
299
302
  link(require_handler_error.adoptErrorType, "aet");
300
303
  link(require_error.ElysiaStatus, "es");
301
- return awaitSite(`er${at(i)}(c)`, fn, mode, "_r", arm) + "if(_r!==undefined){\nif(_r instanceof es||_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`;
304
+ return awaitSite(`er${at(i)}(c)`, fn, mode, "_r", arm) + `if(_r!==undefined){\nif(_r instanceof Error){c.set.status=_r.status||500\n${declined}return _efb(_r,c)}\nif(_r instanceof es||_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`;
302
305
  });
303
306
  function map(map) {
304
307
  return function(event, rest, abortGuard, tail) {
@@ -203,12 +203,13 @@ function joinDeriveEntries(parts) {
203
203
  }
204
204
  if (at) return occurrenceDeriveEntries(hooks, at);
205
205
  }
206
- function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm) {
206
+ function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm, errorClasses) {
207
207
  const hooks = toArray(_hooks);
208
208
  const modes = deriveModes(hooks, derive);
209
209
  const tail = asyncTail(mode);
210
210
  let code = "";
211
211
  let needsEs = false;
212
+ const early = "if(tmp instanceof es||tmp instanceof Error" + (errorClasses ? "||ec.some((C)=>tmp instanceof C)" : "") + ")_r=tmp\n";
212
213
  for (let i = 0; i < hooks.length; i++) {
213
214
  const fn = hooks[i];
214
215
  const guard = i > 0 ? `${abortGuard ? `!${abortGuard}&&` : ""}_r===undefined` : void 0;
@@ -222,18 +223,20 @@ function mapBeforeHandle(_hooks, derive, link, mode, report, abortGuard, arm) {
222
223
  link(registerDeriveDisposable, "dsp");
223
224
  if (modes[i]) {
224
225
  link(replaceDeriveContext, "rdc");
225
- code += "if(tmp instanceof es)_r=tmp\nelse if(tmp){if(typeof tmp==='object'||typeof tmp==='function'){const _pc=c;c=rdc(c,tmp);for(const _k of Reflect.ownKeys(tmp))dsp(c,c[_k],_pc)}tmp=undefined}\n";
226
+ code += early + "else if(tmp){if(typeof tmp==='object'||typeof tmp==='function'){const _pc=c;c=rdc(c,tmp);for(const _k of Reflect.ownKeys(tmp))dsp(c,c[_k],_pc)}tmp=undefined}\n";
226
227
  } else {
227
228
  const keys = extractDeriveKeys(fn);
228
229
  const merge = !!keys && !!keys.length && !keys.includes("__proto__") ? keys.map((k) => `_v=tmp[${JSON.stringify(k)}];dsp(c,_v);c[${JSON.stringify(k)}]=_v`).join(";") : "if(Object.hasOwn(tmp,'__proto__')){for(const _k of Object.keys(tmp))dsp(c,tmp[_k]);Object.defineProperties(c,Object.getOwnPropertyDescriptors(tmp))}else{_v=Object.assign({},tmp);for(const _k of Reflect.ownKeys(_v)){dsp(c,_v[_k]);c[_k]=_v[_k]}}";
229
- code += `if(tmp instanceof es)_r=tmp
230
- else if(tmp){${merge};tmp=undefined}\n`;
230
+ code += early + `else if(tmp){${merge};tmp=undefined}\n`;
231
231
  }
232
232
  } else code += "if(tmp!==undefined)_r=tmp\n";
233
233
  code += t.end("tmp");
234
234
  if (tail || guard) code += "}\n";
235
235
  }
236
- if (needsEs) link(ElysiaStatus, "es");
236
+ if (needsEs) {
237
+ link(ElysiaStatus, "es");
238
+ if (errorClasses) link(errorClasses, "ec");
239
+ }
237
240
  return code;
238
241
  }
239
242
  const compactBeforeHandleChunks = (prefix) => {
@@ -294,11 +297,11 @@ const mapAfterResponse = /* @__PURE__ */ map((i, fn, [report]) => {
294
297
  const call = isAsyncFunction(fn) ? `await ar${at(i)}(c)\n` : `let _ar=ar${at(i)}(c)\nif(typeof _ar?.then==='function')await _ar\n`;
295
298
  return `try{${t.begin}${call}${t.end()}}catch(_e){${t.end("_e")}console.error(_e)}\n`;
296
299
  });
297
- const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, mode, arm]) => {
300
+ const mapError = /* @__PURE__ */ map((i, fn, [map, link, mapResponse, schedule, sign, mode, arm, declined]) => {
298
301
  link(mapResponse, "rm");
299
302
  link(adoptErrorType, "aet");
300
303
  link(ElysiaStatus, "es");
301
- return awaitSite(`er${at(i)}(c)`, fn, mode, "_r", arm) + "if(_r!==undefined){\nif(_r instanceof es||_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`;
304
+ return awaitSite(`er${at(i)}(c)`, fn, mode, "_r", arm) + `if(_r!==undefined){\nif(_r instanceof Error){c.set.status=_r.status||500\n${declined}return _efb(_r,c)}\nif(_r instanceof es||_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`;
302
305
  });
303
306
  function map(map) {
304
307
  return function(event, rest, abortGuard, tail) {
package/dist/error.d.ts CHANGED
@@ -232,54 +232,36 @@ type TaggedHTTPError<Type extends string, Annotation = {}> = {
232
232
  /**
233
233
  * Self-describing HTTP error
234
234
  *
235
- * Annotate `status`, `headers` and `value` on the subclass and Elysia maps
236
- * the instance to a response on its own, no `.onError(Class, handler)` needed
235
+ * Set `status`, `headers`, `detail`, `extension` and `value` to map to response
237
236
  *
238
- * Everything an owned `HTTPError` serves is an RFC 9457 problem document:
239
- * `type` is the problem type carried on the wire, and the body (or the error
240
- * message, when there is none) becomes `detail`, an object nested verbatim
241
- * and never merged into the envelope
237
+ * Send RFC 9457 problem document by default that can be overridden by `value()`
242
238
  *
243
- * - `detail()` - the common case. Whatever it returns becomes the `detail`
244
- * member of the problem document, verbatim, objects included. The envelope
245
- * still carries `type`, `status` and an auto-filled `title`
246
- * - `value()` - the escape hatch. Whatever it returns replaces the *whole*
247
- * response: no envelope, no `application/problem+json`. The annotated
248
- * `status` and `headers` still apply, only the content is yours
239
+ * - `detail` - add `detail` field to the problem document
240
+ * - `extension()` - add extension members to the problem document
241
+ * - `value()` - override the whole response, return `problem()` to keep the shape
249
242
  *
250
- * `value` wins over `detail`, `detail` over the error message. A knob
251
- * returning `undefined` falls through to the next one.
243
+ * `value` wins over `detail`, `detail` over an `extension()` string `detail`,
244
+ * that over message. A `value` returning `undefined` falls
245
+ * through to the next one. A response that depends on the request belongs in
246
+ * `.onError(Class, handler)`
252
247
  *
253
248
  * @example
254
249
  * ```ts
255
- * class OutOfCredit extends HTTPError<'OUT_OF_CREDIT'> {
256
- * type = 'OUT_OF_CREDIT' as const
257
- * override readonly status = 402
258
- *
259
- * detail() {
260
- * return { balance: 0, currency: 'usd' }
261
- * }
250
+ * class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT') {
251
+ * readonly status = 402
252
+ * readonly detail = 'Top up to continue'
262
253
  * }
263
- * // → { type: 'OUT_OF_CREDIT', title: 'Payment Required', status: 402,
264
- * // detail: { balance: 0, currency: 'usd' } }
265
254
  *
266
- * class Overdrawn extends HTTPError<'OVERDRAWN'> {
267
- * type = 'OVERDRAWN' as const
268
- * override readonly status = 402
255
+ * class Overdrawn extends Error implements HTTPError {
256
+ * readonly type = 'OVERDRAWN'
257
+ * readonly status = 402
269
258
  *
270
- * async detail() {
271
- * return await describeBalance()
272
- * }
273
- * }
274
- *
275
- * class Legacy extends HTTPError<'LEGACY'> {
276
- * type = 'LEGACY' as const
277
- * override readonly status = 402
278
- *
279
- * // full control, served as-is
280
- * value() {
281
- * return { code: 'OUT_OF_CREDIT', ok: false }
282
- * }
259
+ * value() {
260
+ * return problem(402, {
261
+ * type: 'OVERDRAWN',
262
+ * detail: await describeBalance()
263
+ * })
264
+ * }
283
265
  * }
284
266
  * ```
285
267
  *
@@ -328,18 +310,44 @@ declare abstract class HTTPError<const in out T extends string = string> extends
328
310
  /** Headers merged into `set.headers` when the error is served */
329
311
  readonly headers?: Record<string, string>;
330
312
  /**
331
- * Return a value to serve as the whole response
313
+ * Return a value to serve as the whole response, may be `async`
332
314
  *
333
315
  * Supports `status()`/`problem()` for status-based Eden response
334
316
  */
335
317
  value?(): MaybePromise<unknown>;
336
318
  /**
337
- * Return the `detail` member of the RFC 9457 problem document,
338
- * similar to `problem()`
319
+ * `detail` member of the RFC 9457 problem document, served in place of
320
+ * the error message
339
321
  *
340
- * Use `value()` to override the entire response
322
+ * Use `value()` for anything computed asynchronously
341
323
  */
342
- detail?(): MaybePromise<unknown>;
324
+ readonly detail?: string;
325
+ /**
326
+ * Return RFC 9457 extension members added to the problem document,
327
+ * synchronously
328
+ *
329
+ * `type`, `code` and `status` stay the error's own. A string `detail` is
330
+ * served when the error has no `detail` of its own, in place of the
331
+ * message. `title` and `instance` may be overridden. Not served when
332
+ * `value()` answers
333
+ *
334
+ * Only a plain object's own data members are served, which types can't
335
+ * check: return an object literal, not a class instance
336
+ *
337
+ * @example
338
+ * ```ts
339
+ * class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {
340
+ * constructor(readonly balance: number) {
341
+ * super()
342
+ * }
343
+ *
344
+ * extension() {
345
+ * return { balance: this.balance, accounts: ['/account/12345'] }
346
+ * }
347
+ * }
348
+ * ```
349
+ */
350
+ extension?(): Record<string, unknown>;
343
351
  /**
344
352
  * Create a concrete `HTTPError` subclass carrying `code`, mirrored into
345
353
  * `type` unless {@link HTTPError.typeBase} is set
@@ -362,16 +370,14 @@ declare abstract class HTTPError<const in out T extends string = string> extends
362
370
  * Create a concrete `HTTPError` subclass carrying `code`, annotated with
363
371
  * `status` as a number or a status name
364
372
  *
365
- * Annotate what is served with `detail()` (problem `detail` member) or
366
- * `value()` (whole-response override), either of which may be `async`
373
+ * Annotate what is served with `detail` (problem `detail` member) or
374
+ * `value()` (whole-response override, may be `async`)
367
375
  *
368
376
  * @example
369
377
  * ```ts
370
378
  * class OutOfCredit extends HTTPError.id('OUT_OF_CREDIT', 402) {}
371
379
  * class Denied extends HTTPError.id('DENIED', 'Payment Required') {
372
- * async detail() {
373
- * return await describeBalance()
374
- * }
380
+ * override readonly detail = 'Top up to continue'
375
381
  * }
376
382
  * ```
377
383
  */
@@ -22,13 +22,15 @@ declare const resolveStatus: (status: unknown) => unknown;
22
22
  * its declared `response`, otherwise its message, or `mask` in its place
23
23
  * on a production 5xx
24
24
  */
25
- declare function statusFallbackBody(error: any, status: unknown, mask?: string): any;
25
+ declare function statusFallbackBody(error: any, status: unknown, mask?: string, asDetail?: boolean): any;
26
26
  /**
27
- * Read one annotation knob, a method (may be `async`) or a plain value.
28
- * A method may have side effects, so only an error claiming a problem type
29
- * gets it invoked; a plain value is read from any error
27
+ * RFC 9457 extension members of an error claiming a problem type. Sync
28
+ * only: anything but a plain object, a Promise included, adds nothing.
29
+ * `type`, `code` and `status` stay the error's own, `detail` only when a
30
+ * string
30
31
  */
31
- declare const readAnnotation: (self: any, key: "value" | "detail", claimsProblem: boolean) => any;
32
+ declare function extensionOf(self: any): any;
33
+ declare function readAnnotation(self: any, key: 'value' | 'detail', claimsProblem: boolean): any;
32
34
  declare function createErrorHandler(onErrors: AppHook['error'] | undefined, mapResponse: (response: unknown, set: Context['set'], ...any: unknown[]) => unknown, allowUnsafe?: boolean): (context: Context, error: Error, sign?: ((set: Context["set"]) => unknown) | undefined) => unknown;
33
35
  //#endregion
34
- export { adoptErrorType, claimsProblemType, createErrorHandler, fallbackResponse, readAnnotation, resolveStatus, statusFallbackBody };
36
+ export { adoptErrorType, claimsProblemType, createErrorHandler, extensionOf, fallbackResponse, readAnnotation, resolveStatus, statusFallbackBody };