elysia 2.0.0-beta.1 → 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.
Files changed (65) hide show
  1. package/dist/adapter/utils.d.ts +2 -2
  2. package/dist/adapter/web-standard/handler.js +11 -7
  3. package/dist/adapter/web-standard/handler.mjs +12 -8
  4. package/dist/base.d.ts +18 -3
  5. package/dist/base.js +31 -15
  6. package/dist/base.mjs +31 -15
  7. package/dist/compile/aot-capture.js +1 -1
  8. package/dist/compile/aot-capture.mjs +2 -2
  9. package/dist/compile/handler/descriptor.d.ts +1 -1
  10. package/dist/compile/handler/frozen-validator.d.ts +2 -1
  11. package/dist/compile/handler/frozen-validator.js +33 -3
  12. package/dist/compile/handler/frozen-validator.mjs +33 -4
  13. package/dist/compile/handler/index.d.ts +1 -1
  14. package/dist/compile/handler/jit.js +5 -8
  15. package/dist/compile/handler/jit.mjs +6 -9
  16. package/dist/compile/handler/params.js +3 -3
  17. package/dist/compile/handler/params.mjs +4 -4
  18. package/dist/compile/handler/utils.d.ts +1 -1
  19. package/dist/compile/handler/utils.js +5 -2
  20. package/dist/compile/handler/utils.mjs +5 -2
  21. package/dist/context.d.ts +2 -2
  22. package/dist/cookie/cookie.js +1 -0
  23. package/dist/cookie/cookie.mjs +1 -0
  24. package/dist/error.d.ts +139 -6
  25. package/dist/error.js +73 -0
  26. package/dist/error.mjs +73 -1
  27. package/dist/handler/error.d.ts +20 -1
  28. package/dist/handler/error.js +120 -8
  29. package/dist/handler/error.mjs +119 -9
  30. package/dist/handler/fetch.js +9 -5
  31. package/dist/handler/fetch.mjs +9 -5
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.js +1 -0
  34. package/dist/index.mjs +2 -2
  35. package/dist/package.js +1 -1
  36. package/dist/package.mjs +1 -1
  37. package/dist/plugin/aot/core.d.ts +4 -3
  38. package/dist/plugin/aot/core.js +1 -1
  39. package/dist/plugin/aot/core.mjs +1 -1
  40. package/dist/route-table.d.ts +1 -1
  41. package/dist/sucrose.js +118 -16
  42. package/dist/sucrose.mjs +118 -16
  43. package/dist/type/bridge.d.ts +6 -6
  44. package/dist/type/coerce.js +9 -4
  45. package/dist/type/coerce.mjs +9 -4
  46. package/dist/type/elysia/array-string.js +29 -2
  47. package/dist/type/elysia/array-string.mjs +30 -3
  48. package/dist/type/elysia/file-type.js +11 -1
  49. package/dist/type/elysia/file-type.mjs +11 -1
  50. package/dist/type/elysia/form.js +2 -1
  51. package/dist/type/elysia/form.mjs +2 -1
  52. package/dist/type/elysia/object-string.js +26 -2
  53. package/dist/type/elysia/object-string.mjs +27 -3
  54. package/dist/type/validator/index.js +11 -13
  55. package/dist/type/validator/index.mjs +12 -14
  56. package/dist/types.d.ts +104 -18
  57. package/dist/universal/file.js +1 -0
  58. package/dist/universal/file.mjs +1 -0
  59. package/dist/universal/index.d.ts +1 -1
  60. package/dist/utils.d.ts +3 -5
  61. package/dist/utils.js +7 -9
  62. package/dist/utils.mjs +6 -10
  63. package/dist/ws/route.js +1 -1
  64. package/dist/ws/route.mjs +1 -1
  65. package/package.json +2 -2
@@ -10,17 +10,41 @@ const require_type_elysia_object = require('./object.js');
10
10
  let typebox_type = require("typebox/type");
11
11
 
12
12
  //#region src/type/elysia/object-string.ts
13
+ const MEMO_LIMIT = 8192;
13
14
  function ObjectString(property, _options) {
14
15
  const [{ properties, ...constraints }, meta] = require_type_elysia_utils.getMeta(_options ?? require_utils.nullObject());
15
16
  const object = require_type_elysia_object.ObjectType(property, constraints);
17
+ let check;
18
+ let raw;
19
+ let parsed;
16
20
  const objectString = (0, typebox_type.Decode)((0, typebox_type.Refine)(require_type_elysia_string.StringType(), (value) => {
17
21
  if (value.charCodeAt(0) !== 123) return false;
22
+ if (value === raw) return true;
18
23
  try {
19
- return require_type_bridge.Check(object, JSON.parse(value));
24
+ const next = JSON.parse(value);
25
+ if (!check) try {
26
+ const compiled = require_type_bridge.Compile(object);
27
+ if (compiled.evaluateResult) compiled.evaluateResult.code = void 0;
28
+ if (compiled.buildResult) compiled.buildResult.functions = void 0;
29
+ check = (v) => compiled.Check(v);
30
+ } catch {
31
+ check = (v) => require_type_bridge.Check(object, v);
32
+ }
33
+ if (!check(next)) return false;
34
+ if (value.length <= MEMO_LIMIT) {
35
+ raw = value;
36
+ parsed = next;
37
+ }
38
+ return true;
20
39
  } catch {
21
40
  return false;
22
41
  }
23
- }, () => "must be an object"), (value) => require_type_bridge.Decode(object, JSON.parse(value)));
42
+ }, () => "must be an object"), (value) => {
43
+ const decoded = value === raw ? parsed : JSON.parse(value);
44
+ raw = void 0;
45
+ parsed = void 0;
46
+ return require_type_bridge.Decode(object, decoded);
47
+ });
24
48
  return require_type_elysia_utils.elyType(require_type_constants.ELYSIA_TYPES.ObjectString, require_type_elysia_union.Union([object, objectString], meta));
25
49
  }
26
50
 
@@ -1,5 +1,5 @@
1
1
  import { nullObject } from "../../utils.mjs";
2
- import { Check, Decode as Decode$1 } from "../bridge.mjs";
2
+ import { Check, Compile, Decode as Decode$1 } from "../bridge.mjs";
3
3
  import { ELYSIA_TYPES } from "../constants.mjs";
4
4
  import { StringType } from "./string.mjs";
5
5
  import { Union } from "./union.mjs";
@@ -8,17 +8,41 @@ import { ObjectType } from "./object.mjs";
8
8
  import { Decode, Refine } from "typebox/type";
9
9
 
10
10
  //#region src/type/elysia/object-string.ts
11
+ const MEMO_LIMIT = 8192;
11
12
  function ObjectString(property, _options) {
12
13
  const [{ properties, ...constraints }, meta] = getMeta(_options ?? nullObject());
13
14
  const object = ObjectType(property, constraints);
15
+ let check;
16
+ let raw;
17
+ let parsed;
14
18
  const objectString = Decode(Refine(StringType(), (value) => {
15
19
  if (value.charCodeAt(0) !== 123) return false;
20
+ if (value === raw) return true;
16
21
  try {
17
- return Check(object, JSON.parse(value));
22
+ const next = JSON.parse(value);
23
+ if (!check) try {
24
+ const compiled = Compile(object);
25
+ if (compiled.evaluateResult) compiled.evaluateResult.code = void 0;
26
+ if (compiled.buildResult) compiled.buildResult.functions = void 0;
27
+ check = (v) => compiled.Check(v);
28
+ } catch {
29
+ check = (v) => Check(object, v);
30
+ }
31
+ if (!check(next)) return false;
32
+ if (value.length <= MEMO_LIMIT) {
33
+ raw = value;
34
+ parsed = next;
35
+ }
36
+ return true;
18
37
  } catch {
19
38
  return false;
20
39
  }
21
- }, () => "must be an object"), (value) => Decode$1(object, JSON.parse(value)));
40
+ }, () => "must be an object"), (value) => {
41
+ const decoded = value === raw ? parsed : JSON.parse(value);
42
+ raw = void 0;
43
+ parsed = void 0;
44
+ return Decode$1(object, decoded);
45
+ });
22
46
  return elyType(ELYSIA_TYPES.ObjectString, Union([object, objectString], meta));
23
47
  }
24
48
 
@@ -516,17 +516,15 @@ var TypeBoxValidator = class extends require_validator_index.Validator {
516
516
  }
517
517
  }
518
518
  #markForm(value) {
519
- if (this.#isForm && value !== null && typeof value === "object" && !("~ely-form" in value)) {
520
- Object.defineProperty(value, "~ely-form", {
521
- value: 1,
522
- configurable: true
523
- });
524
- return true;
519
+ if (this.#isForm && value !== null && typeof value === "object") {
520
+ const previous = Object.getPrototypeOf(value);
521
+ if (previous === require_utils.ELYSIA_FORM_PROTOTYPE) return;
522
+ Object.setPrototypeOf(value, require_utils.ELYSIA_FORM_PROTOTYPE);
523
+ return previous;
525
524
  }
526
- return false;
527
525
  }
528
- #unmarkForm(value) {
529
- if (this.#isForm && value !== null && typeof value === "object" && "~ely-form" in value && Object.getOwnPropertyDescriptor(value, "~ely-form")?.configurable) delete value["~ely-form"];
526
+ #unmarkForm(value, previous) {
527
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === require_utils.ELYSIA_FORM_PROTOTYPE) Object.setPrototypeOf(value, previous);
530
528
  }
531
529
  From(value, type) {
532
530
  return this.isAsync ? this.FromAsync(value, type) : this.FromSync(value, type);
@@ -568,7 +566,7 @@ var TypeBoxValidator = class extends require_validator_index.Validator {
568
566
  if (bypass) return bypass.value;
569
567
  }
570
568
  const markedValue = value;
571
- const marked = this.#isForm ? this.#markForm(value) : false;
569
+ const marked = this.#isForm ? this.#markForm(value) : void 0;
572
570
  try {
573
571
  if (this.hasCodec) {
574
572
  if (!this.#noValidate) {
@@ -607,7 +605,7 @@ var TypeBoxValidator = class extends require_validator_index.Validator {
607
605
  if (this.Clean && !this.#decodeMirror && !this.#cleanRedundant) value = this.Clean(value);
608
606
  return value;
609
607
  } finally {
610
- if (marked) this.#unmarkForm(markedValue);
608
+ if (marked !== void 0) this.#unmarkForm(markedValue, marked);
611
609
  }
612
610
  }
613
611
  FromSync(value, type) {
@@ -623,7 +621,7 @@ var TypeBoxValidator = class extends require_validator_index.Validator {
623
621
  if (bypass) return bypass.value;
624
622
  }
625
623
  const markedValue = value;
626
- const marked = this.#isForm ? this.#markForm(value) : false;
624
+ const marked = this.#isForm ? this.#markForm(value) : void 0;
627
625
  try {
628
626
  if (this.hasCodec) {
629
627
  const errors = this.#noValidate ? void 0 : this.#validate(value);
@@ -644,7 +642,7 @@ var TypeBoxValidator = class extends require_validator_index.Validator {
644
642
  if (this.Clean && !this.#decodeMirror && !this.#cleanRedundant) value = this.Clean(value);
645
643
  return value;
646
644
  } finally {
647
- if (marked) this.#unmarkForm(markedValue);
645
+ if (marked !== void 0) this.#unmarkForm(markedValue, marked);
648
646
  }
649
647
  }
650
648
  };
@@ -1,4 +1,4 @@
1
- import { nullObject } from "../../utils.mjs";
1
+ import { ELYSIA_FORM_PROTOTYPE, nullObject } from "../../utils.mjs";
2
2
  import { ELYSIA_TYPES } from "../constants.mjs";
3
3
  import { ValidationError } from "../../error.mjs";
4
4
  import { Capture, Compiled, EMPTY_EXTERNALS, captureImpl, reconstruct } from "../../compile/aot.mjs";
@@ -514,17 +514,15 @@ var TypeBoxValidator = class extends Validator$1 {
514
514
  }
515
515
  }
516
516
  #markForm(value) {
517
- if (this.#isForm && value !== null && typeof value === "object" && !("~ely-form" in value)) {
518
- Object.defineProperty(value, "~ely-form", {
519
- value: 1,
520
- configurable: true
521
- });
522
- return true;
517
+ if (this.#isForm && value !== null && typeof value === "object") {
518
+ const previous = Object.getPrototypeOf(value);
519
+ if (previous === ELYSIA_FORM_PROTOTYPE) return;
520
+ Object.setPrototypeOf(value, ELYSIA_FORM_PROTOTYPE);
521
+ return previous;
523
522
  }
524
- return false;
525
523
  }
526
- #unmarkForm(value) {
527
- if (this.#isForm && value !== null && typeof value === "object" && "~ely-form" in value && Object.getOwnPropertyDescriptor(value, "~ely-form")?.configurable) delete value["~ely-form"];
524
+ #unmarkForm(value, previous) {
525
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === ELYSIA_FORM_PROTOTYPE) Object.setPrototypeOf(value, previous);
528
526
  }
529
527
  From(value, type) {
530
528
  return this.isAsync ? this.FromAsync(value, type) : this.FromSync(value, type);
@@ -566,7 +564,7 @@ var TypeBoxValidator = class extends Validator$1 {
566
564
  if (bypass) return bypass.value;
567
565
  }
568
566
  const markedValue = value;
569
- const marked = this.#isForm ? this.#markForm(value) : false;
567
+ const marked = this.#isForm ? this.#markForm(value) : void 0;
570
568
  try {
571
569
  if (this.hasCodec) {
572
570
  if (!this.#noValidate) {
@@ -605,7 +603,7 @@ var TypeBoxValidator = class extends Validator$1 {
605
603
  if (this.Clean && !this.#decodeMirror && !this.#cleanRedundant) value = this.Clean(value);
606
604
  return value;
607
605
  } finally {
608
- if (marked) this.#unmarkForm(markedValue);
606
+ if (marked !== void 0) this.#unmarkForm(markedValue, marked);
609
607
  }
610
608
  }
611
609
  FromSync(value, type) {
@@ -621,7 +619,7 @@ var TypeBoxValidator = class extends Validator$1 {
621
619
  if (bypass) return bypass.value;
622
620
  }
623
621
  const markedValue = value;
624
- const marked = this.#isForm ? this.#markForm(value) : false;
622
+ const marked = this.#isForm ? this.#markForm(value) : void 0;
625
623
  try {
626
624
  if (this.hasCodec) {
627
625
  const errors = this.#noValidate ? void 0 : this.#validate(value);
@@ -642,7 +640,7 @@ var TypeBoxValidator = class extends Validator$1 {
642
640
  if (this.Clean && !this.#decodeMirror && !this.#cleanRedundant) value = this.Clean(value);
643
641
  return value;
644
642
  } finally {
645
- if (marked) this.#unmarkForm(markedValue);
643
+ if (marked !== void 0) this.#unmarkForm(markedValue, marked);
646
644
  }
647
645
  }
648
646
  };
package/dist/types.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { CookieOptions } from "./cookie/types.js";
2
- import { StatusMapBack } from "./constants.js";
3
- import { ElysiaError, ElysiaStatus } from "./error.js";
4
2
  import { TraceHandler } from "./trace.js";
5
3
  import { ElysiaFile } from "./universal/file.js";
4
+ import { StatusMap, StatusMapBack } from "./constants.js";
6
5
  import { Serve } from "./universal/server.js";
7
6
  import { ChainNode } from "./utils.js";
7
+ import { ElysiaError, ElysiaStatus, ProblemResponseBody } from "./error.js";
8
8
  import { Context, ErrorContext, LifecycleContext, PreContext } from "./context.js";
9
9
  import { AnySchema, StandardSchemaV1Like, TypeBoxSchema } from "./type/types.js";
10
10
  import { AnyElysia, Elysia } from "./base.js";
@@ -620,9 +620,7 @@ type StaticCyclic<T extends TypeBoxSchema, Definitions extends Record<string, An
620
620
  type UnwrapSchema<Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {}> = Schema extends undefined ? unknown : Schema extends TypeBoxSchema ? Schema extends OptionalField ? Partial<StaticCyclic<Schema, Definitions>> : StaticCyclic<Schema, Definitions> : Schema extends StandardSchemaV1Like ? NonNullable<Schema['~standard']['types']>['output'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TypeBoxSchema ? StaticCyclic<Definitions[Schema], Definitions> : Definitions[Schema] extends StandardSchemaV1Like ? NonNullable<Definitions[Schema]['~standard']['types']>['output'] : unknown : unknown : unknown;
621
621
  type UnwrapBodySchema<Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {}> = undefined extends Schema ? unknown : Schema extends TypeBoxSchema ? Schema extends OptionalField ? Partial<StaticCyclic<Schema, Definitions>> | null : StaticCyclic<Schema, Definitions> : Schema extends StandardSchemaV1Like ? NonNullable<Schema['~standard']['types']>['output'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TypeBoxSchema ? StaticCyclic<Definitions[Schema], Definitions> : Definitions[Schema] extends StandardSchemaV1Like ? NonNullable<Definitions[Schema]['~standard']['types']>['output'] : unknown : unknown : unknown;
622
622
  type FormInnerProperties<Schema> = Extract<Schema extends TIntersect<infer Members> ? Members[number] : never, TObject>['properties'];
623
- type UnwrapResponseSchema<Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {}> = Schema extends TypeBoxSchema ? StaticEncode<Schema> extends {
624
- '~ely-form': any;
625
- } ? ElysiaFormData<{ [K in keyof FormInnerProperties<Schema>]: Static<FormInnerProperties<Schema>[K] & TSchema> }> : UnwrapSchema<Schema, Definitions> : Schema extends StandardSchemaV1Like ? NonNullable<Schema['~standard']['types']>['input'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TypeBoxSchema ? StaticCyclic<Definitions[Schema], Definitions> : Definitions[Schema] extends StandardSchemaV1Like ? NonNullable<Definitions[Schema]['~standard']['types']>['input'] : unknown : unknown : unknown;
623
+ type UnwrapResponseSchema<Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {}> = Schema extends TypeBoxSchema ? StaticEncode<Schema> extends ElysiaFormData<any> ? ElysiaFormData<{ [K in keyof FormInnerProperties<Schema>]: Static<FormInnerProperties<Schema>[K] & TSchema> }> : UnwrapSchema<Schema, Definitions> : Schema extends StandardSchemaV1Like ? NonNullable<Schema['~standard']['types']>['input'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TypeBoxSchema ? StaticCyclic<Definitions[Schema], Definitions> : Definitions[Schema] extends StandardSchemaV1Like ? NonNullable<Definitions[Schema]['~standard']['types']>['input'] : unknown : unknown : unknown;
626
624
  interface UnwrapRoute<in out Schema extends InputSchema<any>, in out Definitions extends DefinitionBase['typebox'] = {}, in out Path extends string = ''> {
627
625
  body: UnwrapBodySchema<Schema['body'], Definitions>;
628
626
  headers: UnwrapSchema<Schema['headers'], Definitions>;
@@ -744,19 +742,102 @@ type ObjectMacroDefs<Body, Headers, Query, Params, Cookie, N, AmbientSchema exte
744
742
  }, Definitions['error']>> } & { [K in keyof N]: N[K] extends ((...a: any[]) => any) ? unknown : string extends keyof N[K] ? unknown : { [P in Exclude<keyof N[K], MacroPropertyKey | InputSchemaKey | keyof MacroNames | keyof N>]: `Unknown macro property '${P & string}'` } } & N;
745
743
  type CreateEden<Path extends string, Property extends Record<string, unknown> = {}> = Path extends `/${infer Rest}` ? _CreateEden<Rest, Property> : Path extends '' | '/' ? Property : _CreateEden<Path, Property>;
746
744
  type _CreateEden<Path extends string, Property extends Record<string, unknown> = {}> = Path extends `${infer Start}/${infer Rest}` ? { [x in Start]: _CreateEden<Rest, Property> } : Path extends '' ? Property : { [x in Path]: Property };
745
+ /**
746
+ * Value an annotation knob resolves to, `never` when it annotates nothing.
747
+ *
748
+ * Both knobs are canonically methods, so what they *return* is the
749
+ * annotation. A value or getter reads as the value itself. An `unknown`
750
+ * declaration claims nothing, and `undefined` is excluded — it falls through
751
+ * to the next tier rather than being served
752
+ */
753
+ type ResolveAnnotation<V> = (V extends ((...args: any) => infer Returned) ? Returned : V) extends infer Value ? Exclude<Awaited<Value>, undefined> extends infer Resolved ? IsNever<Resolved> extends true ? never : unknown extends Resolved ? never : Resolved : never : never;
754
+ /** Whether a knob may resolve `undefined` and fall through to the next tier */
755
+ type AnnotationFallsThrough<V> = undefined extends Awaited<V extends ((...args: any) => infer Returned) ? Returned : V> ? true : false;
756
+ /**
757
+ * Numeric literal `status` an error annotates, written as a number or a
758
+ * status name. `never` when it's absent or widened
759
+ */
760
+ type LiteralErrorStatus<E> = E extends {
761
+ status: infer S;
762
+ } ? number extends S ? never : keyof StatusMap extends S ? never : S extends keyof StatusMap ? StatusMap[S] : S extends number ? S : never : never;
763
+ /**
764
+ * An owned `HTTPError` carries a *literal* `type`. Wild errors often carry a
765
+ * wide-string one (`ErrorEvent.type`, SDK errors), which claims nothing
766
+ */
767
+ type OwnedError<E> = E extends {
768
+ type: infer T extends string;
769
+ } ? string extends T ? false : true : false;
770
+ /**
771
+ * RFC 9457 problem type an error contributes. `problemBody` defaults an
772
+ * absent one to `'about:blank'`
773
+ */
774
+ type ErrorProblemType<E> = OwnedError<E> extends true ? E extends {
775
+ type: infer T extends string;
776
+ } ? T : 'about:blank' : 'about:blank';
777
+ /**
778
+ * Problem document served for a `detail` annotation. `detail` is carried
779
+ * verbatim, objects included — it is never spread into the envelope
780
+ */
781
+ type ProblemOf<E, Detail> = ProblemResponseBody<ErrorFallbackStatus<E>, {
782
+ type: ErrorProblemType<E>;
783
+ detail: Detail;
784
+ }>;
785
+ /**
786
+ * Tier 3 — nothing annotated. An error that claimed a problem serves its
787
+ * message as `detail`, anything else keeps the legacy raw lane
788
+ */
789
+ type MessageTier<E> = OwnedError<E> extends true ? ProblemOf<E, string> : E extends {
790
+ response: infer R;
791
+ } ? unknown extends R ? string : R : string;
792
+ /** Tier 2 — `detail` fills the `detail` member of a problem document */
793
+ type DetailTier<E> = E extends {
794
+ detail: infer V;
795
+ } ? [OwnedError<E>, V] extends [false, (...args: any) => any] ? MessageTier<E> : IsNever<ResolveAnnotation<V>> extends true ? MessageTier<E> : AnnotationFallsThrough<V> extends true ? ProblemOf<E, ResolveAnnotation<V>> | MessageTier<E> : ProblemOf<E, ResolveAnnotation<V>> : MessageTier<E>;
796
+ /**
797
+ * Tier 1 — `value` replaces the whole response, so it is served raw: no
798
+ * envelope, no problem+json
799
+ */
800
+ type ValueTier<E> = E extends {
801
+ value: infer V;
802
+ } ? [OwnedError<E>, V] extends [false, (...args: any) => any] ? DetailTier<E> : IsNever<ResolveAnnotation<V>> extends true ? DetailTier<E> : AnnotationFallsThrough<V> extends true ? ResolveAnnotation<V> | DetailTier<E> : ResolveAnnotation<V> : DetailTier<E>;
803
+ /**
804
+ * Key a served value by the status it actually reaches.
805
+ *
806
+ * `value()` may hand back a `status()` or `problem()`, which the mapResponse
807
+ * lane serves at the status *it* carries, not the one the error annotated —
808
+ * so those members escape to their own keys. Everything else stays under
809
+ * `Fallback`. The unwrapping is `MergeResponseStatus`, the same one a route
810
+ * handler's returned `status()` goes through
811
+ */
812
+ type ServedAtStatus<Served, Fallback extends number> = UnionResponseStatus<IsNever<Extract<Served, AnyElysiaStatus>> extends true ? {} : MergeResponseStatus<Extract<Served, AnyElysiaStatus>>, Exclude<Served, AnyElysiaStatus> extends infer Plain ? IsNever<Plain> extends true ? {} : { [Status in Fallback]: Plain } : {}>;
813
+ /**
814
+ * Response an error describes for itself, resolved through the three tiers:
815
+ * a raw `value`, else a problem document built from `detail`, else its message
816
+ */
817
+ type SelfDescribedResponse<E> = ServedAtStatus<ValueTier<E>, ErrorFallbackStatus<E>>;
818
+ /**
819
+ * Response of an error that reached the error pipeline without a matching
820
+ * `.error(Class, handler)`.
821
+ *
822
+ * A self-describing error maps to its annotated `status` and knobs,
823
+ * anything else is served as an unhandled 500
824
+ */
825
+ type UnhandledErrorResponse<E> = [E] extends [never] ? {} : MergeStatusUnion<E extends unknown ? OwnedError<E> extends true ? SelfDescribedResponse<E> : IsNever<LiteralErrorStatus<E>> extends true ? {
826
+ 500: E;
827
+ } : SelfDescribedResponse<E> : never>;
747
828
  type CreateEdenResponse<Path extends string, Schema extends RouteSchema, MacroContext extends RouteSchema, Res extends PossibleResponse, Err extends Error = never> = RouteSchema extends MacroContext ? {
748
829
  body: Schema['body'];
749
830
  params: IsNever<keyof Schema['params']> extends true ? ResolvePath<Path> : Schema['params'];
750
831
  query: Schema['query'];
751
832
  headers: Schema['headers'];
752
- response: Prettify<Res>;
833
+ response: Prettify<UnionResponseStatus<Res, UnhandledErrorResponse<Err>>>;
753
834
  error: Err;
754
835
  } : {
755
836
  body: Prettify<Schema['body'] & MacroContext['body']>;
756
837
  params: IsNever<keyof (Schema['params'] & MacroContext['params'])> extends true ? ResolvePath<Path> : Prettify<Schema['params'] & MacroContext['params']>;
757
838
  query: Prettify<Schema['query'] & MacroContext['query']>;
758
839
  headers: Prettify<Schema['headers'] & MacroContext['headers']>;
759
- response: Prettify<Res>;
840
+ response: Prettify<UnionResponseStatus<Res, UnhandledErrorResponse<Err>>>;
760
841
  error: Err;
761
842
  };
762
843
  type CreateWSEdenResponse<Path extends string, Schema extends RouteSchema, MacroContext extends RouteSchema, Res extends PossibleResponse> = Omit<CreateEdenResponse<Path, Schema, MacroContext, Res>, 'error'>;
@@ -788,16 +869,13 @@ type ExtractErrorFromHandle<in out Handle> = { [ErrorResponse in Extract<Handle,
788
869
  * Status used when an error handler returns a plain value (or falls through):
789
870
  * the error's declared literal `status`, otherwise 500
790
871
  */
791
- type ErrorFallbackStatus<E> = E extends {
792
- status: infer S extends number;
793
- } ? number extends S ? 500 : S : 500;
872
+ type ErrorFallbackStatus<E> = IsNever<LiteralErrorStatus<E>> extends true ? 500 : LiteralErrorStatus<E>;
794
873
  /**
795
- * Body produced when no error handler returns a value:
796
- * the error's declared `response`, otherwise its message (string)
874
+ * Body produced when no error handler returns a value. Falling through lands
875
+ * on exactly the lane an unhandled error takes, so it resolves the same three
876
+ * tiers — `MessageTier` already carries the foreign `response` fallback
797
877
  */
798
- type ErrorFallbackBody<E> = E extends {
799
- response: infer R;
800
- } ? unknown extends R ? string : R : string;
878
+ type ErrorFallbackBody<E> = ValueTier<E>;
801
879
  /**
802
880
  * `Definitions['error']` / `EphemeralType['error']` entry registered by an
803
881
  * `.error(Class, handler)` call
@@ -806,11 +884,19 @@ type ErrorDefinitionEntry<E extends abstract new (...args: any) => Error, R> = {
806
884
  error: InstanceType<E>;
807
885
  response: ErrorHandlerResponseSchema<Awaited<R>, InstanceType<E>>;
808
886
  };
809
- type ErrorHandlerResponseSchema<R, E> = ExtractErrorFromHandle<Exclude<R, Error>> & (Exclude<R, Extract<Exclude<R, Error>, AnyElysiaStatus> | undefined> | (undefined extends R ? ErrorFallbackBody<E> : never) extends infer Plain ? IsNever<Plain> extends true ? {} : { [S in ErrorFallbackStatus<E>]: Plain } : {});
810
- type MatchRegisteredError<V, Errors extends ErrorDefinition[]> = Errors extends [infer Head extends ErrorDefinition, ...infer Rest extends ErrorDefinition[]] ? [V] extends [Head['error']] ? Head['response'] : MatchRegisteredError<V, Rest> : never;
887
+ type ErrorHandlerResponseSchema<R, E> = ExtractErrorFromHandle<Exclude<R, Error>> & (Exclude<R, Extract<Exclude<R, Error>, AnyElysiaStatus> | undefined> | (undefined extends R ? ErrorFallbackBody<E> : never) extends infer Served ? ServedAtStatus<Served, ErrorFallbackStatus<E>> : {});
888
+ type MatchRegisteredError<V, Errors extends ErrorDefinition[]> = Errors extends [infer Head extends ErrorDefinition, ...infer Rest extends ErrorDefinition[]] ? V extends Head['error'] ? Head['response'] : MatchRegisteredError<V, Rest> : never;
811
889
  type HasErrorMatch<V, Errors extends ErrorDefinition[]> = Errors extends [infer Head extends ErrorDefinition, ...infer Rest extends ErrorDefinition[]] ? [V] extends [Head['error']] ? true : HasErrorMatch<V, Rest> : false;
812
890
  type UnhandledReturnedError<Value, Errors extends ErrorDefinition[]> = 0 extends 1 & Value ? never : Extract<Value, Error> extends infer Es ? Es extends Error ? HasErrorMatch<Es, Errors> extends true ? never : Es : never : never;
813
891
  type UnhandledReturnedErrorOf<T, Errors extends ErrorDefinition[]> = T extends ((...a: any) => MaybePromise<infer R>) ? UnhandledReturnedError<R, Errors> : UnhandledReturnedError<T, Errors>;
892
+ /**
893
+ * Strip what `UnhandledErrorResponse` contributed for `Err` from a route's
894
+ * response, so a now-handled error doesn't leave a stale status behind.
895
+ *
896
+ * A response that merely shares the same status survives, unless it's
897
+ * indistinguishable from the error's own body
898
+ */
899
+ type WithoutUnhandledErrorResponse<Response, Err> = UnhandledErrorResponse<Err> extends infer Contributed ? { [K in keyof Response as K extends keyof Contributed ? IsNever<Exclude<Response[K], Contributed[K]>> extends true ? never : K : K]: K extends keyof Contributed ? Exclude<Response[K], Contributed[K]> : Response[K] } : never;
814
900
  type ResolveRouteErrors<Routes, Errors extends ErrorDefinition[]> = Errors extends [] ? Routes : { [K in keyof Routes]: Routes[K] extends {
815
901
  params: any;
816
902
  query: any;
@@ -818,7 +904,7 @@ type ResolveRouteErrors<Routes, Errors extends ErrorDefinition[]> = Errors exten
818
904
  response: any;
819
905
  error: any;
820
906
  } ? [Routes[K]['error']] extends [never] ? Routes[K] : Omit<Routes[K], 'response' | 'error'> & {
821
- response: Prettify<UnionResponseStatus<Routes[K]['response'], ExtractReturnedError<Routes[K]['error'], Errors>>>;
907
+ response: Prettify<UnionResponseStatus<WithoutUnhandledErrorResponse<Routes[K]['response'], Routes[K]['error']>, UnionResponseStatus<ExtractReturnedError<Routes[K]['error'], Errors>, UnhandledErrorResponse<UnhandledReturnedError<Routes[K]['error'], Errors>>>>>;
822
908
  error: UnhandledReturnedError<Routes[K]['error'], Errors>;
823
909
  } : Routes[K] extends Record<keyof any, any> ? ResolveRouteErrors<Routes[K], Errors> : Routes[K] };
824
910
  type MergeStatusUnion<U> = { [K in U extends unknown ? keyof U : never]: U extends unknown ? K extends keyof U ? U[K] : never : never };
@@ -110,6 +110,7 @@ var ElysiaFile = class {
110
110
  return value ?? 0;
111
111
  }
112
112
  };
113
+ Object.defineProperty(ElysiaFile, "name", { value: "ElysiaFile" });
113
114
 
114
115
  //#endregion
115
116
  exports.ElysiaFile = ElysiaFile;
@@ -109,6 +109,7 @@ var ElysiaFile = class {
109
109
  return value ?? 0;
110
110
  }
111
111
  };
112
+ Object.defineProperty(ElysiaFile, "name", { value: "ElysiaFile" });
112
113
 
113
114
  //#endregion
114
115
  export { ElysiaFile, file, getFileExtension, mime };
@@ -1,5 +1,5 @@
1
- import { isProduction } from "./is-production.js";
2
1
  import { file } from "./file.js";
3
2
  import { env } from "./env.js";
4
3
  import { ErrorLike, GenericServeOptions, ListenCallback, Serve, ServeOptions, Server, ServerWebSocketSendStatus, SocketAddress } from "./server.js";
4
+ import { isProduction } from "./is-production.js";
5
5
  export { type ErrorLike, type GenericServeOptions, type ListenCallback, type Serve, type ServeOptions, type Server, type ServerWebSocketSendStatus, type SocketAddress, env, file, isProduction };
package/dist/utils.d.ts CHANGED
@@ -97,10 +97,8 @@ declare function flattenChainMemoReadonly(root: object, start: ChainNode | undef
97
97
  */
98
98
  declare const redirect: (url: string, status?: 301 | 302 | 303 | 307 | 308) => Response;
99
99
  type redirect = typeof redirect;
100
- /**
101
- * Build `FormData` from a `form()` object, skipping the internal `~ely-form`
102
- * marker that flags the object as a form.
103
- */
100
+ declare const ELYSIA_FORM_PROTOTYPE: object;
101
+ declare const isElysiaForm: (value: unknown) => boolean;
104
102
  declare function formToFormData(value: Record<keyof any, unknown>): FormData;
105
103
  /**
106
104
  * Return a `multipart/form-data` response.
@@ -177,4 +175,4 @@ declare namespace prefix {
177
175
  }
178
176
  declare function serializeMacroSeed(_key: string, value: unknown): unknown;
179
177
  //#endregion
180
- export { ChainNode, CompactBeforeHandleChunk, CompactBeforeHandlePrefix, DeriveEntry, bytes, clearFlattenChainMemo, cloneHook, clonePlainDecorators, clonePlainDeep, coalesceSchemas, compactBeforeHandleConflicts, compactBeforeHandlePrefix, constantTimeEqual, createErrorEventHandler, deriveEntryFn, eventProperties, evictOldestHalf, flattenChain, flattenChainMemo, flattenChainMemoReadonly, fnOrigin, fnv1a, form, formToFormData, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isBlob, isByteStream, isCompactBeforeHandleOnly, isEmpty, isLocalScope, isMapDeriveEntry, isNotEmpty, isRecordNumber, joinPath, macroEpoch, macroOrigin, mapDeriveEntry, mergeArray, mergeDeep, mergeHook, mergeResponse, nullObject, prefix, pushField, redirect, replaceUrlPath, requestId, schemaProperties, serializeMacroSeed, sse };
178
+ export { ChainNode, CompactBeforeHandleChunk, CompactBeforeHandlePrefix, DeriveEntry, ELYSIA_FORM_PROTOTYPE, bytes, clearFlattenChainMemo, cloneHook, clonePlainDecorators, clonePlainDeep, coalesceSchemas, compactBeforeHandleConflicts, compactBeforeHandlePrefix, constantTimeEqual, createErrorEventHandler, deriveEntryFn, eventProperties, evictOldestHalf, flattenChain, flattenChainMemo, flattenChainMemoReadonly, fnOrigin, fnv1a, form, formToFormData, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isBlob, isByteStream, isCompactBeforeHandleOnly, isElysiaForm, isEmpty, isLocalScope, isMapDeriveEntry, isNotEmpty, isRecordNumber, joinPath, macroEpoch, macroOrigin, mapDeriveEntry, mergeArray, mergeDeep, mergeHook, mergeResponse, nullObject, prefix, pushField, redirect, replaceUrlPath, requestId, schemaProperties, serializeMacroSeed, sse };
package/dist/utils.js CHANGED
@@ -251,6 +251,10 @@ const redirect = (url, status = 302) => new Response(null, {
251
251
  status,
252
252
  headers: { location: notLatin.test(url) ? encodeURI(url) : url }
253
253
  });
254
+ var ElysiaForm = class {};
255
+ Object.defineProperty(ElysiaForm, "name", { value: "ElysiaForm" });
256
+ const ELYSIA_FORM_PROTOTYPE = ElysiaForm.prototype;
257
+ const isElysiaForm = (value) => value instanceof ElysiaForm || value != null && Object.getPrototypeOf(value)?.constructor?.name === "ElysiaForm";
254
258
  function appendFormField(formData, key, value) {
255
259
  if (value === void 0 || value === null) return;
256
260
  if (value instanceof Blob) formData.append(key, value);
@@ -258,14 +262,9 @@ function appendFormField(formData, key, value) {
258
262
  else if (typeof value === "object") formData.append(key, JSON.stringify(value));
259
263
  else formData.append(key, "" + value);
260
264
  }
261
- /**
262
- * Build `FormData` from a `form()` object, skipping the internal `~ely-form`
263
- * marker that flags the object as a form.
264
- */
265
265
  function formToFormData(value) {
266
266
  const formData = new FormData();
267
267
  for (const key in value) {
268
- if (key === "~ely-form") continue;
269
268
  const field = value[key];
270
269
  if (Array.isArray(field)) for (const item of field) appendFormField(formData, key, item);
271
270
  else appendFormField(formData, key, field);
@@ -287,10 +286,7 @@ function formToFormData(value) {
287
286
  * )
288
287
  * ```
289
288
  */
290
- const form = (value) => ({
291
- ...value,
292
- "~ely-form": 1
293
- });
289
+ const form = (value) => Object.setPrototypeOf({ ...value }, ELYSIA_FORM_PROTOTYPE);
294
290
  const getLoosePath = (path) => path.charCodeAt(path.length - 1) === 47 ? path.slice(0, -1) : path + "/";
295
291
  const byteStreams = /* @__PURE__ */ new WeakSet();
296
292
  /**
@@ -685,6 +681,7 @@ function serializeMacroSeed(_key, value) {
685
681
  }
686
682
 
687
683
  //#endregion
684
+ exports.ELYSIA_FORM_PROTOTYPE = ELYSIA_FORM_PROTOTYPE;
688
685
  exports.bytes = bytes;
689
686
  exports.clearFlattenChainMemo = clearFlattenChainMemo;
690
687
  exports.cloneHook = cloneHook;
@@ -712,6 +709,7 @@ exports.invalidateMacroEpoch = invalidateMacroEpoch;
712
709
  exports.isBlob = isBlob;
713
710
  exports.isByteStream = isByteStream;
714
711
  exports.isCompactBeforeHandleOnly = isCompactBeforeHandleOnly;
712
+ exports.isElysiaForm = isElysiaForm;
715
713
  exports.isEmpty = isEmpty;
716
714
  exports.isLocalScope = isLocalScope;
717
715
  exports.isMapDeriveEntry = isMapDeriveEntry;
package/dist/utils.mjs CHANGED
@@ -250,6 +250,10 @@ const redirect = (url, status = 302) => new Response(null, {
250
250
  status,
251
251
  headers: { location: notLatin.test(url) ? encodeURI(url) : url }
252
252
  });
253
+ var ElysiaForm = class {};
254
+ Object.defineProperty(ElysiaForm, "name", { value: "ElysiaForm" });
255
+ const ELYSIA_FORM_PROTOTYPE = ElysiaForm.prototype;
256
+ const isElysiaForm = (value) => value instanceof ElysiaForm || value != null && Object.getPrototypeOf(value)?.constructor?.name === "ElysiaForm";
253
257
  function appendFormField(formData, key, value) {
254
258
  if (value === void 0 || value === null) return;
255
259
  if (value instanceof Blob) formData.append(key, value);
@@ -257,14 +261,9 @@ function appendFormField(formData, key, value) {
257
261
  else if (typeof value === "object") formData.append(key, JSON.stringify(value));
258
262
  else formData.append(key, "" + value);
259
263
  }
260
- /**
261
- * Build `FormData` from a `form()` object, skipping the internal `~ely-form`
262
- * marker that flags the object as a form.
263
- */
264
264
  function formToFormData(value) {
265
265
  const formData = new FormData();
266
266
  for (const key in value) {
267
- if (key === "~ely-form") continue;
268
267
  const field = value[key];
269
268
  if (Array.isArray(field)) for (const item of field) appendFormField(formData, key, item);
270
269
  else appendFormField(formData, key, field);
@@ -286,10 +285,7 @@ function formToFormData(value) {
286
285
  * )
287
286
  * ```
288
287
  */
289
- const form = (value) => ({
290
- ...value,
291
- "~ely-form": 1
292
- });
288
+ const form = (value) => Object.setPrototypeOf({ ...value }, ELYSIA_FORM_PROTOTYPE);
293
289
  const getLoosePath = (path) => path.charCodeAt(path.length - 1) === 47 ? path.slice(0, -1) : path + "/";
294
290
  const byteStreams = /* @__PURE__ */ new WeakSet();
295
291
  /**
@@ -684,4 +680,4 @@ function serializeMacroSeed(_key, value) {
684
680
  }
685
681
 
686
682
  //#endregion
687
- export { bytes, clearFlattenChainMemo, cloneHook, clonePlainDecorators, clonePlainDeep, coalesceSchemas, compactBeforeHandleConflicts, compactBeforeHandlePrefix, constantTimeEqual, createErrorEventHandler, deriveEntryFn, eventProperties, evictOldestHalf, flattenChain, flattenChainMemo, flattenChainMemoReadonly, fnOrigin, fnv1a, form, formToFormData, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isBlob, isByteStream, isCompactBeforeHandleOnly, isEmpty, isLocalScope, isMapDeriveEntry, isNotEmpty, isRecordNumber, joinPath, macroEpoch, macroOrigin, mapDeriveEntry, mergeArray, mergeDeep, mergeHook, mergeResponse, nullObject, prefix, pushField, redirect, replaceUrlPath, requestId, schemaProperties, serializeMacroSeed, sse };
683
+ export { ELYSIA_FORM_PROTOTYPE, bytes, clearFlattenChainMemo, cloneHook, clonePlainDecorators, clonePlainDeep, coalesceSchemas, compactBeforeHandleConflicts, compactBeforeHandlePrefix, constantTimeEqual, createErrorEventHandler, deriveEntryFn, eventProperties, evictOldestHalf, flattenChain, flattenChainMemo, flattenChainMemoReadonly, fnOrigin, fnv1a, form, formToFormData, getLoosePath, guardNonPlainLeaves, hookToGuard, invalidateMacroEpoch, isBlob, isByteStream, isCompactBeforeHandleOnly, isElysiaForm, isEmpty, isLocalScope, isMapDeriveEntry, isNotEmpty, isRecordNumber, joinPath, macroEpoch, macroOrigin, mapDeriveEntry, mergeArray, mergeDeep, mergeHook, mergeResponse, nullObject, prefix, pushField, redirect, replaceUrlPath, requestId, schemaProperties, serializeMacroSeed, sse };
package/dist/ws/route.js CHANGED
@@ -7,12 +7,12 @@ const require_error = require('../error.js');
7
7
  const require_adapter_web_standard_handler = require('../adapter/web-standard/handler.js');
8
8
  const require_parse_query = require('../parse-query.js');
9
9
  const require_generation = require('../generation.js');
10
+ const require_handler_error = require('../handler/error.js');
10
11
  const require_compile_handler_utils = require('../compile/handler/utils.js');
11
12
  const require_validator_index = require('../validator/index.js');
12
13
  const require_validator_route = require('../validator/route.js');
13
14
  const require_compile_handler_frozen_validator = require('../compile/handler/frozen-validator.js');
14
15
  const require_compile_handler_index = require('../compile/handler/index.js');
15
- const require_handler_error = require('../handler/error.js');
16
16
  const require_ws_context = require('./context.js');
17
17
  const require_ws_parser = require('./parser.js');
18
18
 
package/dist/ws/route.mjs CHANGED
@@ -6,12 +6,12 @@ import { ElysiaError, ElysiaStatus, ValidationError, internalServerErrorBodyStri
6
6
  import { mapResponse } from "../adapter/web-standard/handler.mjs";
7
7
  import { parseQueryFromURL } from "../parse-query.mjs";
8
8
  import { frozenRootOf } from "../generation.mjs";
9
+ import { createErrorHandler } from "../handler/error.mjs";
9
10
  import { deriveModes, getQueryParseChannels, replaceDeriveContext } from "../compile/handler/utils.mjs";
10
11
  import { StandardValidator } from "../validator/index.mjs";
11
12
  import { RouteValidator } from "../validator/route.mjs";
12
13
  import { buildFrozenRouteValidator, isBridgeNotInitialized } from "../compile/handler/frozen-validator.mjs";
13
14
  import { composeRouteHook, localMacroRoot, resolveWSLocalHook } from "../compile/handler/index.mjs";
14
- import { createErrorHandler } from "../handler/error.mjs";
15
15
  import { ElysiaWS, isGeneratorObject } from "./context.mjs";
16
16
  import { createMessageParser } from "./parser.mjs";
17
17
 
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.1",
4
+ "version": "2.0.0-beta.2",
5
5
  "author": {
6
6
  "name": "saltyAom",
7
7
  "url": "https://github.com/SaltyAom",
@@ -255,7 +255,7 @@
255
255
  "esbuild": "0.25.4",
256
256
  "eslint": "^9.24.0",
257
257
  "eslint-plugin-sonarjs": "^4.0.2",
258
- "exact-mirror": "1.2.2",
258
+ "exact-mirror": "^1.2.4",
259
259
  "expect-type": "^1.2.1",
260
260
  "file-type": "^22.0.0",
261
261
  "globals": "^17.4.0",