@aklinker1/zeta 1.3.3 → 2.1.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 +21 -638
- package/package.json +15 -7
- package/src/adapters/zod-schema-adapter.ts +1 -18
- package/src/app.ts +133 -125
- package/src/client.ts +5 -4
- package/src/errors.ts +1 -15
- package/src/internal/compile-fetch-function.ts +166 -0
- package/src/internal/compile-route-handler.ts +194 -0
- package/src/internal/context.ts +47 -0
- package/src/internal/serialization.ts +30 -31
- package/src/internal/utils.ts +77 -46
- package/src/open-api.ts +33 -18
- package/src/types.ts +25 -42
- package/src/internal/call-handler.ts +0 -139
package/src/types.ts
CHANGED
|
@@ -130,12 +130,7 @@ export interface App<TAppData extends AppData = AppData> {
|
|
|
130
130
|
onGlobalRequest(
|
|
131
131
|
callback: (
|
|
132
132
|
ctx: OnGlobalRequestContext<GetAppDataCtx<TAppData>>,
|
|
133
|
-
) => MaybePromise<void>,
|
|
134
|
-
): this;
|
|
135
|
-
onGlobalRequest(
|
|
136
|
-
callback: (
|
|
137
|
-
ctx: OnGlobalRequestContext<GetAppDataCtx<TAppData>>,
|
|
138
|
-
) => MaybePromise<Response>,
|
|
133
|
+
) => MaybePromise<Response | void>,
|
|
139
134
|
): this;
|
|
140
135
|
onGlobalRequest<TNewCtx extends Record<string, any>>(
|
|
141
136
|
callback: (
|
|
@@ -154,12 +149,7 @@ export interface App<TAppData extends AppData = AppData> {
|
|
|
154
149
|
onTransform(
|
|
155
150
|
callback: (
|
|
156
151
|
ctx: OnTransformContext<GetAppDataCtx<TAppData>>,
|
|
157
|
-
) => MaybePromise<void>,
|
|
158
|
-
): this;
|
|
159
|
-
onTransform(
|
|
160
|
-
callback: (
|
|
161
|
-
ctx: OnTransformContext<GetAppDataCtx<TAppData>>,
|
|
162
|
-
) => MaybePromise<Response>,
|
|
152
|
+
) => MaybePromise<Response | void>,
|
|
163
153
|
): this;
|
|
164
154
|
onTransform<TNewCtx extends Record<string, any>>(
|
|
165
155
|
callback: (
|
|
@@ -178,12 +168,7 @@ export interface App<TAppData extends AppData = AppData> {
|
|
|
178
168
|
onBeforeHandle(
|
|
179
169
|
callback: (
|
|
180
170
|
ctx: OnBeforeHandleContext<GetAppDataCtx<TAppData>>,
|
|
181
|
-
) => MaybePromise<void>,
|
|
182
|
-
): this;
|
|
183
|
-
onBeforeHandle(
|
|
184
|
-
callback: (
|
|
185
|
-
ctx: OnBeforeHandleContext<GetAppDataCtx<TAppData>>,
|
|
186
|
-
) => MaybePromise<Response>,
|
|
171
|
+
) => MaybePromise<Response | void>,
|
|
187
172
|
): this;
|
|
188
173
|
onBeforeHandle<TNewCtx extends Record<string, any>>(
|
|
189
174
|
callback: (
|
|
@@ -434,11 +419,20 @@ export type RouterData = {
|
|
|
434
419
|
def?: RouteDef;
|
|
435
420
|
route: string;
|
|
436
421
|
hooks: LifeCycleHooks;
|
|
422
|
+
compiledHandler: CompiledRouteHandler;
|
|
437
423
|
} & (
|
|
438
424
|
| { fetch: ServerSideFetch }
|
|
439
425
|
| { handler: (ctx: OnBeforeHandleContext) => Promise<any> }
|
|
440
426
|
);
|
|
441
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Function type called internally once a route is matched for a request.
|
|
430
|
+
*/
|
|
431
|
+
export type CompiledRouteHandler = (
|
|
432
|
+
request: Request,
|
|
433
|
+
ctx: any,
|
|
434
|
+
) => MaybePromise<Response>;
|
|
435
|
+
|
|
442
436
|
//
|
|
443
437
|
// HANDLERS
|
|
444
438
|
//
|
|
@@ -512,9 +506,7 @@ export type LifeCycleHook<TCallback extends Function> = {
|
|
|
512
506
|
* into the handler context.
|
|
513
507
|
*/
|
|
514
508
|
export type OnGlobalRequestHook = LifeCycleHook<
|
|
515
|
-
(
|
|
516
|
-
ctx: Simplify<OnGlobalRequestContext>,
|
|
517
|
-
) => MaybePromise<Record<string, any> | void>
|
|
509
|
+
(ctx: Simplify<OnGlobalRequestContext>) => Record<string, any> | void
|
|
518
510
|
>;
|
|
519
511
|
|
|
520
512
|
/**
|
|
@@ -560,27 +552,29 @@ export type OnMapResponseHook = LifeCycleHook<
|
|
|
560
552
|
* Zeta will handle any `HttpError`s thrown, but you can handle your own errors
|
|
561
553
|
* here.
|
|
562
554
|
*/
|
|
563
|
-
export type
|
|
564
|
-
(ctx: Simplify<OnGlobalErrorContext>) =>
|
|
555
|
+
export type OnGlobalErrorHook = LifeCycleHook<
|
|
556
|
+
(ctx: Simplify<OnGlobalErrorContext>) => void
|
|
565
557
|
>;
|
|
566
558
|
|
|
567
559
|
/**
|
|
568
560
|
* Called after the response is sent back to the client.
|
|
569
561
|
*/
|
|
570
562
|
export type OnGlobalAfterResponseHook = LifeCycleHook<
|
|
571
|
-
(ctx: Simplify<AfterResponseContext>) =>
|
|
563
|
+
(ctx: Simplify<AfterResponseContext>) => void
|
|
572
564
|
>;
|
|
573
565
|
|
|
574
566
|
export type LifeCycleHooks = {
|
|
575
|
-
onGlobalRequest
|
|
576
|
-
onTransform
|
|
577
|
-
onBeforeHandle
|
|
578
|
-
onAfterHandle
|
|
579
|
-
onMapResponse
|
|
580
|
-
onGlobalError
|
|
581
|
-
onGlobalAfterResponse
|
|
567
|
+
onGlobalRequest?: OnGlobalRequestHook[];
|
|
568
|
+
onTransform?: OnTransformHook[];
|
|
569
|
+
onBeforeHandle?: OnBeforeHandleHook[];
|
|
570
|
+
onAfterHandle?: OnAfterHandleHook[];
|
|
571
|
+
onMapResponse?: OnMapResponseHook[];
|
|
572
|
+
onGlobalError?: OnGlobalErrorHook[];
|
|
573
|
+
onGlobalAfterResponse?: OnGlobalAfterResponseHook[];
|
|
582
574
|
};
|
|
583
575
|
|
|
576
|
+
export type LifeCycleHookName = keyof LifeCycleHooks;
|
|
577
|
+
|
|
584
578
|
//
|
|
585
579
|
// BASE TYPES
|
|
586
580
|
//
|
|
@@ -1028,17 +1022,6 @@ export interface SchemaAdapter {
|
|
|
1028
1022
|
* @returns The JSON schema.
|
|
1029
1023
|
*/
|
|
1030
1024
|
toJsonSchema: (schema: any) => any;
|
|
1031
|
-
/**
|
|
1032
|
-
* Used to pull out the openapi parameters from a schema.
|
|
1033
|
-
* @param schema The schema to parse.
|
|
1034
|
-
* @returns An array of objects used to generate the OpenAPI docs.
|
|
1035
|
-
*/
|
|
1036
|
-
parseParamsRecord: (schema: StandardSchemaV1) => Array<{
|
|
1037
|
-
name: string;
|
|
1038
|
-
optional: boolean;
|
|
1039
|
-
schema: StandardSchemaV1;
|
|
1040
|
-
description?: string;
|
|
1041
|
-
}>;
|
|
1042
1025
|
getMeta: (schema: StandardSchemaV1) => Record<string, any> | undefined;
|
|
1043
1026
|
}
|
|
1044
1027
|
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import type { MatchedRoute } from "rou3";
|
|
2
|
-
import type { RouterData, SchemaAdapter } from "../types";
|
|
3
|
-
import { NotFoundHttpError } from "../errors";
|
|
4
|
-
import {
|
|
5
|
-
callCtxModifierHooks,
|
|
6
|
-
getRawParams,
|
|
7
|
-
getRawQuery,
|
|
8
|
-
isStatusResult,
|
|
9
|
-
IsStatusResult,
|
|
10
|
-
validateInputSchema,
|
|
11
|
-
validateOutputSchema,
|
|
12
|
-
} from "./utils";
|
|
13
|
-
import { smartDeserialize, smartSerialize } from "./serialization";
|
|
14
|
-
import { getMeta } from "../meta";
|
|
15
|
-
|
|
16
|
-
export async function callHandler(
|
|
17
|
-
ctx: any,
|
|
18
|
-
getRoute: (
|
|
19
|
-
method: string,
|
|
20
|
-
path: string,
|
|
21
|
-
) => MatchedRoute<RouterData> | undefined,
|
|
22
|
-
schemaAdapter: SchemaAdapter | undefined,
|
|
23
|
-
): Promise<Response> {
|
|
24
|
-
const route = getRoute(ctx.method, ctx.path);
|
|
25
|
-
if (route == null) {
|
|
26
|
-
throw new NotFoundHttpError(undefined, {
|
|
27
|
-
method: ctx.method,
|
|
28
|
-
path: ctx.path,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if ("fetch" in route.data) {
|
|
33
|
-
const res = route.data.fetch(ctx.request);
|
|
34
|
-
return res instanceof Promise ? await res : res;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const rawBody = await smartDeserialize(ctx.request);
|
|
38
|
-
const rawQuery = getRawQuery(ctx.url);
|
|
39
|
-
const rawParams = getRawParams(route);
|
|
40
|
-
ctx.route = route.data.route;
|
|
41
|
-
ctx.params = rawParams;
|
|
42
|
-
ctx.query = rawQuery;
|
|
43
|
-
ctx.body = rawBody;
|
|
44
|
-
|
|
45
|
-
if (route.data.hooks.onTransform.length > 0) {
|
|
46
|
-
const onTransformResponse = await callCtxModifierHooks(
|
|
47
|
-
ctx,
|
|
48
|
-
route.data.hooks.onTransform,
|
|
49
|
-
);
|
|
50
|
-
if (onTransformResponse) return onTransformResponse;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (route.data.def?.body)
|
|
54
|
-
ctx.body = validateInputSchema(route.data.def?.body, rawBody);
|
|
55
|
-
if (route.data.def?.query)
|
|
56
|
-
ctx.query = validateInputSchema(route.data.def?.query, rawQuery);
|
|
57
|
-
if (route.data.def?.params)
|
|
58
|
-
ctx.params = validateInputSchema(route.data.def?.params, rawParams);
|
|
59
|
-
|
|
60
|
-
if (route.data.hooks.onBeforeHandle.length > 0) {
|
|
61
|
-
const res = await callCtxModifierHooks(
|
|
62
|
-
ctx,
|
|
63
|
-
route.data.hooks.onBeforeHandle,
|
|
64
|
-
);
|
|
65
|
-
if (res) return res;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
ctx.status = (status: number, body: any) => ({
|
|
69
|
-
[IsStatusResult]: true,
|
|
70
|
-
status,
|
|
71
|
-
body,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
let response: any = route.data.handler(ctx);
|
|
76
|
-
if (response instanceof Promise) response = await response;
|
|
77
|
-
|
|
78
|
-
ctx.response = response;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const hook of route.data.hooks.onAfterHandle) {
|
|
82
|
-
let res = hook.callback(ctx);
|
|
83
|
-
res = res instanceof Promise ? await res : res;
|
|
84
|
-
if (res instanceof Response) return res;
|
|
85
|
-
ctx.response = res;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let responseMeta: Record<string, any> | undefined;
|
|
89
|
-
if (!(ctx.response instanceof Response)) {
|
|
90
|
-
if (route.data.def?.responses) {
|
|
91
|
-
if ("~standard" in route.data.def.responses) {
|
|
92
|
-
ctx.response = validateOutputSchema(
|
|
93
|
-
route.data.def.responses,
|
|
94
|
-
ctx.response,
|
|
95
|
-
);
|
|
96
|
-
responseMeta = getMeta(schemaAdapter, route.data.def.responses);
|
|
97
|
-
} else {
|
|
98
|
-
if (!ctx.response || !isStatusResult(ctx.response)) {
|
|
99
|
-
throw new Error(
|
|
100
|
-
"When `responses` is a record of schemas, you must return a value from `ctx.status(...)`.",
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
const { status, body } = ctx.response;
|
|
104
|
-
const schema = route.data.def.responses[status];
|
|
105
|
-
if (!schema) {
|
|
106
|
-
// This should be caught by the `status` function's type definition, but it's here as a safeguard.
|
|
107
|
-
throw new Error(`No response schema found for status ${status}.`);
|
|
108
|
-
}
|
|
109
|
-
ctx.set.status = status;
|
|
110
|
-
ctx.response = validateOutputSchema(schema, body);
|
|
111
|
-
responseMeta = getMeta(schemaAdapter, schema);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (ctx.response instanceof Response) return ctx.response;
|
|
117
|
-
|
|
118
|
-
for (const hook of route.data.hooks.onMapResponse) {
|
|
119
|
-
let res = hook.callback(ctx);
|
|
120
|
-
res = res instanceof Promise ? await res : res;
|
|
121
|
-
if (res instanceof Response) return res;
|
|
122
|
-
ctx.response = res;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const resBody = smartSerialize(ctx.response);
|
|
126
|
-
if (!resBody)
|
|
127
|
-
return new Response(undefined, {
|
|
128
|
-
status: ctx.set.status,
|
|
129
|
-
headers: ctx.set.headers,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
return new Response(resBody.serialized, {
|
|
133
|
-
status: ctx.set.status,
|
|
134
|
-
headers: {
|
|
135
|
-
"Content-Type": responseMeta?.contentType ?? resBody.contentType,
|
|
136
|
-
...ctx.set.headers,
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
}
|