@supacloud/elysia 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/dist/fixtures/webhook-generated/application.d.ts +4 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +16 -5
- package/dist/schema_contract.d.ts +30 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -630,6 +630,16 @@ transaction and persist with a row lock or expected-version check.
|
|
|
630
630
|
Lightweight error class carrying HTTP `status`, machine-readable `code`, and
|
|
631
631
|
optional structured `details`.
|
|
632
632
|
|
|
633
|
+
The adapter registers `ApplicationError` and `SchemaContractError` in Elysia
|
|
634
|
+
2.0's native error dictionary (`app.error(ErrorClass, handler)`). Typed entries
|
|
635
|
+
keep these classes out of the generic catch-all, so route response inference
|
|
636
|
+
stays precise while Elysia (and Eden Treaty) knows the errors are handled. The
|
|
637
|
+
dictionary is exported as `applicationErrorDictionary`, and
|
|
638
|
+
`frameworkErrorCode(error)` exposes the same code normalization (`ParseError` →
|
|
639
|
+
`PARSE`, `ValidationError` → `VALIDATION`, otherwise the error's own `code`)
|
|
640
|
+
used by the generic lane. A configured `errorMapper` remains authoritative for
|
|
641
|
+
every lane and still receives the request context.
|
|
642
|
+
|
|
633
643
|
### `createMemorySandbox(options): MemorySandbox`
|
|
634
644
|
|
|
635
645
|
Creates an in-process application harness with `request()`, `db`, `storage`,
|
|
@@ -693,9 +703,18 @@ const app = registerElysiaRoute(new Elysia(), route);
|
|
|
693
703
|
|
|
694
704
|
The callback is typed from the contract (including decoded transforms and
|
|
695
705
|
declared response statuses). Cookie values retain Elysia's native shape, so a
|
|
696
|
-
declared `session: t.String()` is read as `cookie.session.value`.
|
|
697
|
-
|
|
698
|
-
|
|
706
|
+
declared `session: t.String()` is read as `cookie.session.value`.
|
|
707
|
+
`registerElysiaRoute` returns the same instance and preserves its existing type.
|
|
708
|
+
Chaining registers routes at runtime but does not add those routes to Eden's
|
|
709
|
+
static route tree. Use the contract's typed client or the compiler-generated
|
|
710
|
+
client for transport types.
|
|
711
|
+
|
|
712
|
+
When no schema declares `query` or `headers`, the handler context falls back to
|
|
713
|
+
Elysia's native `Record<string, string | undefined>` (not a narrower `string`),
|
|
714
|
+
and `params` resolves from the path exactly as Elysia does. TypeBox transforms
|
|
715
|
+
(`t.Numeric`, `t.Transform`, `Type.Codec`) are decoded before the handler runs,
|
|
716
|
+
so `query.page` from `t.Object({ page: t.Numeric() })` is a `number`, not a
|
|
717
|
+
string.
|
|
699
718
|
|
|
700
719
|
Response maps may use concrete statuses, `1XX`-`5XX` families, and `default`.
|
|
701
720
|
The adapter expands family/default entries to concrete validators before
|
|
@@ -92,6 +92,10 @@ export interface CompiledController {
|
|
|
92
92
|
export interface CompiledModule {
|
|
93
93
|
name: string;
|
|
94
94
|
createServices(deps: Record<string, unknown>, imported: Record<string, Record<string, unknown>>): Record<string, unknown>;
|
|
95
|
+
/** Initializes application-scoped instances owned by this module. */
|
|
96
|
+
initializeServices?(services: Record<string, unknown>): Promise<void>;
|
|
97
|
+
/** Destroys application-scoped instances owned by this module. */
|
|
98
|
+
destroyServices?(services: Record<string, unknown>): Promise<void>;
|
|
95
99
|
createRequestScope?(services: Record<string, unknown>, ctx: unknown, imported?: Record<string, Record<string, unknown>>): Promise<Record<string, unknown>>;
|
|
96
100
|
destroyRequestScope?(scope: Record<string, unknown>): Promise<void>;
|
|
97
101
|
createJobScope?(services: Record<string, unknown>, ctx: unknown, imported?: Record<string, Record<string, unknown>>): Promise<Record<string, unknown>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { HttpPolicyDatabase } from "./http-policy-postgres";
|
|
|
14
14
|
export { createHttpTelemetry } from "./http-telemetry";
|
|
15
15
|
export type { HttpTelemetryEvent, HttpTelemetryObserver } from "./http-telemetry";
|
|
16
16
|
import { type ExecutionObserver } from "./execution";
|
|
17
|
+
import { SchemaContractError } from "./schema_contract";
|
|
17
18
|
import { type ApplicationDocumentationOptions } from "./documentation";
|
|
18
19
|
export type { ExecutionEvent, ExecutionObserver } from "./execution";
|
|
19
20
|
export { bindCompiledCommand } from "./command-binding";
|
|
@@ -242,6 +243,28 @@ export interface ErrorContext {
|
|
|
242
243
|
requestContext: unknown;
|
|
243
244
|
frameworkCode: string | number | undefined;
|
|
244
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Elysia 2.0 native error dictionary registered by the adapter.
|
|
248
|
+
*
|
|
249
|
+
* Declaring the runtime classes here lets Elysia's type system (and therefore
|
|
250
|
+
* Eden Treaty / generated clients) know these errors are *handled*: the generic
|
|
251
|
+
* `.error(handler)` catch-all no longer widens every route's response schema
|
|
252
|
+
* with an untyped fallback. Each entry also documents the public `code` served
|
|
253
|
+
* by {@link defaultErrorResponse} when a caller does not install an
|
|
254
|
+
* `errorMapper`.
|
|
255
|
+
*/
|
|
256
|
+
export declare const applicationErrorDictionary: {
|
|
257
|
+
readonly ApplicationError: typeof ApplicationError;
|
|
258
|
+
readonly SchemaContractError: typeof SchemaContractError;
|
|
259
|
+
};
|
|
260
|
+
export type ApplicationErrorDictionary = typeof applicationErrorDictionary;
|
|
261
|
+
export type ApplicationErrorClass = ApplicationErrorDictionary[keyof ApplicationErrorDictionary];
|
|
262
|
+
/**
|
|
263
|
+
* Normalize the framework's runtime error code the same way the generic error
|
|
264
|
+
* hook always has: Elysia's built-in `ParseError`/`ValidationError` become
|
|
265
|
+
* `PARSE`/`VALIDATION`, while domain errors keep their own `code`.
|
|
266
|
+
*/
|
|
267
|
+
export declare function frameworkErrorCode(error: unknown): string;
|
|
245
268
|
export type ErrorMapper = (error: unknown, context: ErrorContext) => Response | undefined | Promise<Response | undefined>;
|
|
246
269
|
export type ApplicationHttpContext<Http extends AnyElysia = Elysia> = Omit<Context<{}, {
|
|
247
270
|
decorator: Http["~Singleton"]["decorator"];
|
package/dist/index.js
CHANGED
|
@@ -1978,6 +1978,13 @@ function validatedJsonResponse(validate, value, init) {
|
|
|
1978
1978
|
headers.set("content-type", "application/json");
|
|
1979
1979
|
return new Response(body, { ...init, headers });
|
|
1980
1980
|
}
|
|
1981
|
+
var applicationErrorDictionary = {
|
|
1982
|
+
ApplicationError,
|
|
1983
|
+
SchemaContractError
|
|
1984
|
+
};
|
|
1985
|
+
function frameworkErrorCode(error) {
|
|
1986
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code.toUpperCase().replaceAll("-", "_") : "UNKNOWN";
|
|
1987
|
+
}
|
|
1981
1988
|
var httpPluginIds = new WeakMap;
|
|
1982
1989
|
var nextHttpPluginId = 0;
|
|
1983
1990
|
function contextPlugin(http) {
|
|
@@ -2409,16 +2416,18 @@ function createModulePlugin(compiled, services, ctxFactory = defaultRequestConte
|
|
|
2409
2416
|
requestCompletions.delete(request);
|
|
2410
2417
|
}
|
|
2411
2418
|
});
|
|
2412
|
-
|
|
2413
|
-
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code.toUpperCase().replaceAll("-", "_") : "UNKNOWN";
|
|
2419
|
+
const mapError = async (error, frameworkCode, request) => {
|
|
2414
2420
|
const context = {
|
|
2415
2421
|
request,
|
|
2416
2422
|
requestContext: requestContexts.get(request),
|
|
2417
|
-
frameworkCode
|
|
2423
|
+
frameworkCode
|
|
2418
2424
|
};
|
|
2419
2425
|
const mapped = await options.errorMapper?.(error, context);
|
|
2420
|
-
return mapped ?? defaultErrorResponse(error,
|
|
2421
|
-
}
|
|
2426
|
+
return mapped ?? defaultErrorResponse(error, frameworkCode);
|
|
2427
|
+
};
|
|
2428
|
+
plugin.error(ApplicationError, ({ error, request }) => mapError(error, frameworkErrorCode(error), request));
|
|
2429
|
+
plugin.error(SchemaContractError, ({ error, request }) => mapError(error, frameworkErrorCode(error), request));
|
|
2430
|
+
plugin.error(({ error, request }) => mapError(error, frameworkErrorCode(error), request));
|
|
2422
2431
|
const routeRegistrar = plugin;
|
|
2423
2432
|
for (const controller of compiled.controllers) {
|
|
2424
2433
|
for (const route of controller.routes) {
|
|
@@ -2828,6 +2837,7 @@ export {
|
|
|
2828
2837
|
VERIFIED_JWT_SUBJECT_HEADER,
|
|
2829
2838
|
WorkerReceiptUnconfirmedError,
|
|
2830
2839
|
WorkerRegistrationError,
|
|
2840
|
+
applicationErrorDictionary,
|
|
2831
2841
|
assertFeatureTransition,
|
|
2832
2842
|
assertResponseStatusDeclared,
|
|
2833
2843
|
bindCompiledCommand,
|
|
@@ -2859,6 +2869,7 @@ export {
|
|
|
2859
2869
|
defineRouteContract,
|
|
2860
2870
|
executeCompiledCommand,
|
|
2861
2871
|
executeJob,
|
|
2872
|
+
frameworkErrorCode,
|
|
2862
2873
|
previewCompiledCommand,
|
|
2863
2874
|
registerElysiaRoute,
|
|
2864
2875
|
requireIdempotencyKey,
|
|
@@ -70,9 +70,29 @@ type PathParameterNames<Path extends string> = Path extends `${string}:${infer N
|
|
|
70
70
|
type PathParameters<Path extends string> = [PathParameterNames<Path>] extends [never] ? Record<string, string> : {
|
|
71
71
|
[Name in PathParameterNames<Path>]: string;
|
|
72
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Resolve one declared schema field to the value Elysia hands to the handler.
|
|
75
|
+
*
|
|
76
|
+
* Elysia registers the route with the same schema instance, so the handler sees
|
|
77
|
+
* the *decoded* output of TypeBox transforms (`t.Numeric`, `t.Transform`,
|
|
78
|
+
* `Type.Codec`). `StaticDecode` mirrors that pipeline exactly, which is why the
|
|
79
|
+
* decoded type is used here instead of the raw `Static` input type.
|
|
80
|
+
*/
|
|
73
81
|
type DecodedField<Schemas extends RouteContractSchemas, Key extends keyof RouteContractSchemas> = [
|
|
74
82
|
Extract<Schemas[Key], TSchema>
|
|
75
83
|
] extends [never] ? unknown : UnwrapSchema<Extract<Schemas[Key], TSchema>>;
|
|
84
|
+
/** True when the contract declared a schema for `Key`. */
|
|
85
|
+
type HasField<Schemas extends RouteContractSchemas, Key extends keyof RouteContractSchemas> = [
|
|
86
|
+
Extract<Schemas[Key], TSchema>
|
|
87
|
+
] extends [never] ? false : true;
|
|
88
|
+
/**
|
|
89
|
+
* Elysia's native fallback for an undeclared route field.
|
|
90
|
+
*
|
|
91
|
+
* The values are copied from Elysia's own `Context` so a contract without a
|
|
92
|
+
* schema behaves exactly like a hand-written Elysia route (query and headers
|
|
93
|
+
* stay `string | undefined`, params resolve from the path).
|
|
94
|
+
*/
|
|
95
|
+
type FieldFallback<Schemas extends RouteContractSchemas, Key extends keyof RouteContractSchemas, Path extends string> = HasField<Schemas, Key> extends true ? DecodedField<Schemas, Key> : Key extends "params" ? PathParameters<Path> : Key extends "query" | "headers" ? Record<string, string | undefined> : Record<string, unknown>;
|
|
76
96
|
type DecodedCookie<Schemas extends RouteContractSchemas> = DecodedField<Schemas, "cookie"> extends infer Value ? Value extends Record<string, unknown> ? Record<string, Cookie<unknown>> & {
|
|
77
97
|
[Key in keyof Value]-?: Cookie<Value[Key]>;
|
|
78
98
|
} : Record<string, Cookie<unknown>> : Record<string, Cookie<unknown>>;
|
|
@@ -84,9 +104,9 @@ type RouteStatus<Schemas extends RouteContractSchemas> = [StatusCode<Schemas>] e
|
|
|
84
104
|
/** Elysia-compatible decoded context inferred from one route contract and path. */
|
|
85
105
|
export type ElysiaRouteContext<Schemas extends RouteContractSchemas, Path extends string = ""> = {
|
|
86
106
|
body: DecodedField<Schemas, "body">;
|
|
87
|
-
params:
|
|
88
|
-
query:
|
|
89
|
-
headers:
|
|
107
|
+
params: FieldFallback<Schemas, "params", Path>;
|
|
108
|
+
query: FieldFallback<Schemas, "query", Path>;
|
|
109
|
+
headers: FieldFallback<Schemas, "headers", Path>;
|
|
90
110
|
cookie: DecodedCookie<Schemas>;
|
|
91
111
|
request: Request;
|
|
92
112
|
path: string;
|
|
@@ -94,7 +114,7 @@ export type ElysiaRouteContext<Schemas extends RouteContractSchemas, Path extend
|
|
|
94
114
|
server: unknown;
|
|
95
115
|
store: Record<string, unknown>;
|
|
96
116
|
set: {
|
|
97
|
-
headers: Record<string, string>;
|
|
117
|
+
headers: Record<string, string | number>;
|
|
98
118
|
status?: number | keyof StatusMap;
|
|
99
119
|
redirect?: string;
|
|
100
120
|
cookie?: Record<string, unknown>;
|
|
@@ -127,7 +147,12 @@ export declare function toElysiaRouteSchema<const Schemas extends RouteContractS
|
|
|
127
147
|
* contextually typed from the same schemas that will be registered at runtime.
|
|
128
148
|
*/
|
|
129
149
|
export declare function defineElysiaRoute<const Method extends HTTPMethod, const Path extends string, const Schemas extends RouteContractSchemas>(method: Method, path: Path, contract: Schemas, handler: ElysiaRouteHandler<NoInfer<Schemas>, Path>): ElysiaRouteDefinition<Method, Path, Schemas>;
|
|
130
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Register a contract-bound route while preserving Elysia's fluent app API.
|
|
152
|
+
*
|
|
153
|
+
* Runtime registration preserves the caller's existing instance type. It does
|
|
154
|
+
* not add the contract to Eden's static route tree.
|
|
155
|
+
*/
|
|
131
156
|
export declare function registerElysiaRoute<const App extends AnyElysia, const Method extends HTTPMethod, const Path extends string, const Schemas extends RouteContractSchemas>(app: App, route: ElysiaRouteDefinition<Method, Path, Schemas>): App;
|
|
132
157
|
export declare class SchemaContractError extends Error {
|
|
133
158
|
readonly code = "SCHEMA_CONTRACT_INVALID";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/elysia",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Elysia runtime adapter for SupaCloud compiled modules: application/request scopes, route registration and validation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"elysia": "2.0.0-beta.21"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@supacloud/compiler": "0.
|
|
68
|
+
"@supacloud/compiler": "0.39.0",
|
|
69
69
|
"@supacloud/delivery": "0.8.0",
|
|
70
70
|
"@supacloud/commands": "0.9.1",
|
|
71
71
|
"@supacloud/db": "0.13.1",
|