@aklinker1/zeta 1.3.3 → 2.0.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 +8 -7
- package/src/adapters/zod-schema-adapter.ts +1 -18
- package/src/app.ts +63 -115
- package/src/client.ts +5 -4
- package/src/errors.ts +0 -14
- package/src/internal/compile-fetch-function.ts +157 -0
- package/src/internal/compile-route-handler.ts +190 -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 +22 -24
- package/src/internal/call-handler.ts +0 -139
package/src/types.ts
CHANGED
|
@@ -434,11 +434,20 @@ export type RouterData = {
|
|
|
434
434
|
def?: RouteDef;
|
|
435
435
|
route: string;
|
|
436
436
|
hooks: LifeCycleHooks;
|
|
437
|
+
compiledHandler: CompiledRouteHandler;
|
|
437
438
|
} & (
|
|
438
439
|
| { fetch: ServerSideFetch }
|
|
439
440
|
| { handler: (ctx: OnBeforeHandleContext) => Promise<any> }
|
|
440
441
|
);
|
|
441
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Function type called internally once a route is matched for a request.
|
|
445
|
+
*/
|
|
446
|
+
export type CompiledRouteHandler = (
|
|
447
|
+
request: Request,
|
|
448
|
+
ctx: any,
|
|
449
|
+
) => MaybePromise<Response>;
|
|
450
|
+
|
|
442
451
|
//
|
|
443
452
|
// HANDLERS
|
|
444
453
|
//
|
|
@@ -512,9 +521,7 @@ export type LifeCycleHook<TCallback extends Function> = {
|
|
|
512
521
|
* into the handler context.
|
|
513
522
|
*/
|
|
514
523
|
export type OnGlobalRequestHook = LifeCycleHook<
|
|
515
|
-
(
|
|
516
|
-
ctx: Simplify<OnGlobalRequestContext>,
|
|
517
|
-
) => MaybePromise<Record<string, any> | void>
|
|
524
|
+
(ctx: Simplify<OnGlobalRequestContext>) => Record<string, any> | void
|
|
518
525
|
>;
|
|
519
526
|
|
|
520
527
|
/**
|
|
@@ -560,27 +567,29 @@ export type OnMapResponseHook = LifeCycleHook<
|
|
|
560
567
|
* Zeta will handle any `HttpError`s thrown, but you can handle your own errors
|
|
561
568
|
* here.
|
|
562
569
|
*/
|
|
563
|
-
export type
|
|
564
|
-
(ctx: Simplify<OnGlobalErrorContext>) =>
|
|
570
|
+
export type OnGlobalErrorHook = LifeCycleHook<
|
|
571
|
+
(ctx: Simplify<OnGlobalErrorContext>) => void
|
|
565
572
|
>;
|
|
566
573
|
|
|
567
574
|
/**
|
|
568
575
|
* Called after the response is sent back to the client.
|
|
569
576
|
*/
|
|
570
577
|
export type OnGlobalAfterResponseHook = LifeCycleHook<
|
|
571
|
-
(ctx: Simplify<AfterResponseContext>) =>
|
|
578
|
+
(ctx: Simplify<AfterResponseContext>) => void
|
|
572
579
|
>;
|
|
573
580
|
|
|
574
581
|
export type LifeCycleHooks = {
|
|
575
|
-
onGlobalRequest
|
|
576
|
-
onTransform
|
|
577
|
-
onBeforeHandle
|
|
578
|
-
onAfterHandle
|
|
579
|
-
onMapResponse
|
|
580
|
-
onGlobalError
|
|
581
|
-
onGlobalAfterResponse
|
|
582
|
+
onGlobalRequest?: OnGlobalRequestHook[];
|
|
583
|
+
onTransform?: OnTransformHook[];
|
|
584
|
+
onBeforeHandle?: OnBeforeHandleHook[];
|
|
585
|
+
onAfterHandle?: OnAfterHandleHook[];
|
|
586
|
+
onMapResponse?: OnMapResponseHook[];
|
|
587
|
+
onGlobalError?: OnGlobalErrorHook[];
|
|
588
|
+
onGlobalAfterResponse?: OnGlobalAfterResponseHook[];
|
|
582
589
|
};
|
|
583
590
|
|
|
591
|
+
export type LifeCycleHookName = keyof LifeCycleHooks;
|
|
592
|
+
|
|
584
593
|
//
|
|
585
594
|
// BASE TYPES
|
|
586
595
|
//
|
|
@@ -1028,17 +1037,6 @@ export interface SchemaAdapter {
|
|
|
1028
1037
|
* @returns The JSON schema.
|
|
1029
1038
|
*/
|
|
1030
1039
|
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
1040
|
getMeta: (schema: StandardSchemaV1) => Record<string, any> | undefined;
|
|
1043
1041
|
}
|
|
1044
1042
|
|
|
@@ -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
|
-
}
|