@aklinker1/zeta 1.3.2 → 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 -613
- 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/schema.ts +2 -2
- package/src/types.ts +22 -24
- package/src/internal/call-handler.ts +0 -139
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* Contains a schema adapter for app's using [`zod`](https://npmjs.com/package/zod).
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
import type { SchemaAdapter } from "../types";
|
|
6
7
|
|
|
7
|
-
const zod = "zod";
|
|
8
|
-
const { z } = await import(zod);
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* Usage:
|
|
12
10
|
*
|
|
@@ -25,21 +23,6 @@ export const zodSchemaAdapter: SchemaAdapter = {
|
|
|
25
23
|
delete res.$schema;
|
|
26
24
|
return res;
|
|
27
25
|
},
|
|
28
|
-
parseParamsRecord: (params: any) => {
|
|
29
|
-
if (params?._zod?.def?.type !== "object")
|
|
30
|
-
throw Error(
|
|
31
|
-
"Query, params, and header schemas must be simple Zode objects defined with z.object({ ... })",
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
return Object.entries(params._zod.def.shape).map(
|
|
35
|
-
([key, schema]: [string, any]) => ({
|
|
36
|
-
name: key,
|
|
37
|
-
schema,
|
|
38
|
-
optional: schema.safeParse(undefined).success,
|
|
39
|
-
...z.globalRegistry.get(schema),
|
|
40
|
-
}),
|
|
41
|
-
);
|
|
42
|
-
},
|
|
43
26
|
getMeta: (schema: any) => {
|
|
44
27
|
return z.globalRegistry.get(schema);
|
|
45
28
|
},
|
package/src/app.ts
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import { callHandler } from "./internal/call-handler";
|
|
2
|
-
import { HttpError } from "./errors";
|
|
3
|
-
import { HttpStatus } from "./status";
|
|
4
1
|
import type {
|
|
5
2
|
App,
|
|
6
3
|
RouterData,
|
|
7
4
|
RouteDef,
|
|
8
5
|
BasePath,
|
|
9
6
|
ServerSideFetch,
|
|
10
|
-
OnGlobalRequestContext,
|
|
11
|
-
LifeCycleHooks,
|
|
12
7
|
LifeCycleHook,
|
|
13
8
|
DefaultAppData,
|
|
14
9
|
BasePrefix,
|
|
15
10
|
SchemaAdapter,
|
|
16
11
|
Transport,
|
|
12
|
+
LifeCycleHookName,
|
|
17
13
|
} from "./types";
|
|
18
|
-
import { addRoute, createRouter
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
detectTransport,
|
|
22
|
-
serializeErrorResponse,
|
|
23
|
-
} from "./internal/utils";
|
|
14
|
+
import { addRoute, createRouter } from "rou3";
|
|
15
|
+
import { compileRouter } from "rou3/compiler";
|
|
16
|
+
import { detectTransport } from "./internal/utils";
|
|
24
17
|
import type { OpenAPIV3_1 } from "openapi-types";
|
|
25
18
|
import { buildOpenApiDocs, buildScalarHtml } from "./open-api";
|
|
19
|
+
import { compileRouteHandler } from "./internal/compile-route-handler";
|
|
20
|
+
import { compileFetchFunction } from "./internal/compile-fetch-function";
|
|
26
21
|
|
|
27
22
|
let appIdInc = 0;
|
|
28
23
|
const nextAppId = () => `app-${appIdInc++}`;
|
|
@@ -68,15 +63,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
68
63
|
const appId = nextAppId();
|
|
69
64
|
|
|
70
65
|
const { origin = "http://localhost", prefix = "" } = options ?? {};
|
|
71
|
-
const hooks: App["~zeta"]["hooks"] = {
|
|
72
|
-
onAfterHandle: [],
|
|
73
|
-
onGlobalAfterResponse: [],
|
|
74
|
-
onBeforeHandle: [],
|
|
75
|
-
onMapResponse: [],
|
|
76
|
-
onGlobalError: [],
|
|
77
|
-
onGlobalRequest: [],
|
|
78
|
-
onTransform: [],
|
|
79
|
-
};
|
|
66
|
+
const hooks: App["~zeta"]["hooks"] = {};
|
|
80
67
|
const routes: App["~zeta"]["routes"] = {};
|
|
81
68
|
|
|
82
69
|
const addRoutesEntry = (method: string, route: string, data: RouterData) => {
|
|
@@ -87,15 +74,13 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
87
74
|
routes[method][route] = data;
|
|
88
75
|
};
|
|
89
76
|
|
|
90
|
-
const cloneHooks = () =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
onTransform: [...hooks.onTransform],
|
|
98
|
-
});
|
|
77
|
+
const cloneHooks = (): App["~zeta"]["hooks"] => {
|
|
78
|
+
const cloned: App["~zeta"]["hooks"] = {};
|
|
79
|
+
for (const key of Object.keys(hooks) as LifeCycleHookName[]) {
|
|
80
|
+
if (hooks[key]) cloned[key] = [...hooks[key]] as any;
|
|
81
|
+
}
|
|
82
|
+
return cloned;
|
|
83
|
+
};
|
|
99
84
|
|
|
100
85
|
const app: App<DefaultAppData> = {
|
|
101
86
|
// @ts-expect-error
|
|
@@ -146,77 +131,8 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
146
131
|
}
|
|
147
132
|
}
|
|
148
133
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
findRoute(router, method, path);
|
|
152
|
-
|
|
153
|
-
return async (request) => {
|
|
154
|
-
const url = new URL(request.url, origin);
|
|
155
|
-
const ctx: any = {
|
|
156
|
-
path: url.pathname,
|
|
157
|
-
url,
|
|
158
|
-
request,
|
|
159
|
-
method: request.method,
|
|
160
|
-
set: {
|
|
161
|
-
status: HttpStatus.Ok,
|
|
162
|
-
headers: {},
|
|
163
|
-
},
|
|
164
|
-
} satisfies OnGlobalRequestContext;
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const onGlobalRequestResponse = await callCtxModifierHooks(
|
|
168
|
-
ctx,
|
|
169
|
-
hooks.onGlobalRequest,
|
|
170
|
-
);
|
|
171
|
-
if (onGlobalRequestResponse) {
|
|
172
|
-
ctx.response = onGlobalRequestResponse;
|
|
173
|
-
return onGlobalRequestResponse;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const response = await callHandler(
|
|
177
|
-
ctx,
|
|
178
|
-
getRoute,
|
|
179
|
-
options?.schemaAdapter,
|
|
180
|
-
);
|
|
181
|
-
ctx.response = response;
|
|
182
|
-
|
|
183
|
-
return response;
|
|
184
|
-
} catch (err) {
|
|
185
|
-
ctx.error = err;
|
|
186
|
-
|
|
187
|
-
for (const hook of hooks.onGlobalError) {
|
|
188
|
-
const res = hook.callback(ctx);
|
|
189
|
-
res instanceof Promise ? await res : res;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const status =
|
|
193
|
-
err instanceof HttpError
|
|
194
|
-
? err.status
|
|
195
|
-
: HttpStatus.InternalServerError;
|
|
196
|
-
const res = Response.json(serializeErrorResponse(err), { status });
|
|
197
|
-
ctx.response = res;
|
|
198
|
-
return res;
|
|
199
|
-
} finally {
|
|
200
|
-
// Defer calls to the `onGlobalAfterResponse` hooks until after the response is sent
|
|
201
|
-
if (hooks.onGlobalAfterResponse.length > 0) {
|
|
202
|
-
setTimeout(async () => {
|
|
203
|
-
try {
|
|
204
|
-
for (const hook of hooks.onGlobalAfterResponse) {
|
|
205
|
-
let res = hook.callback(ctx);
|
|
206
|
-
if (res instanceof Promise) await res;
|
|
207
|
-
}
|
|
208
|
-
} catch (err) {
|
|
209
|
-
ctx.error = err;
|
|
210
|
-
|
|
211
|
-
for (const hook of hooks.onGlobalError) {
|
|
212
|
-
const res = hook.callback(ctx);
|
|
213
|
-
res instanceof Promise ? await res : res;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}, 0);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
};
|
|
134
|
+
const getRoute = compileRouter(router);
|
|
135
|
+
return compileFetchFunction({ getRoute, hooks, origin });
|
|
220
136
|
},
|
|
221
137
|
|
|
222
138
|
getOpenApiSpec: () => {
|
|
@@ -241,6 +157,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
241
157
|
const obj: Record<string, any> =
|
|
242
158
|
args.length === 2 ? { [args[0]]: args[1] } : args[0];
|
|
243
159
|
|
|
160
|
+
hooks.onTransform ??= [];
|
|
244
161
|
hooks.onTransform.push({
|
|
245
162
|
id: nextHookId(appId),
|
|
246
163
|
applyTo: "local",
|
|
@@ -251,6 +168,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
251
168
|
},
|
|
252
169
|
|
|
253
170
|
onGlobalRequest(callback: any) {
|
|
171
|
+
hooks.onGlobalRequest ??= [];
|
|
254
172
|
hooks.onGlobalRequest.push({
|
|
255
173
|
id: nextHookId(appId),
|
|
256
174
|
applyTo: "global",
|
|
@@ -259,6 +177,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
259
177
|
return app;
|
|
260
178
|
},
|
|
261
179
|
onTransform(callback: any) {
|
|
180
|
+
hooks.onTransform ??= [];
|
|
262
181
|
hooks.onTransform.push({
|
|
263
182
|
id: nextHookId(appId),
|
|
264
183
|
applyTo: "local",
|
|
@@ -267,6 +186,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
267
186
|
return app;
|
|
268
187
|
},
|
|
269
188
|
onBeforeHandle(callback: any) {
|
|
189
|
+
hooks.onBeforeHandle ??= [];
|
|
270
190
|
hooks.onBeforeHandle.push({
|
|
271
191
|
id: nextHookId(appId),
|
|
272
192
|
applyTo: "local",
|
|
@@ -275,6 +195,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
275
195
|
return app;
|
|
276
196
|
},
|
|
277
197
|
onAfterHandle(callback: any) {
|
|
198
|
+
hooks.onAfterHandle ??= [];
|
|
278
199
|
hooks.onAfterHandle.push({
|
|
279
200
|
id: nextHookId(appId),
|
|
280
201
|
applyTo: "local",
|
|
@@ -283,6 +204,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
283
204
|
return app;
|
|
284
205
|
},
|
|
285
206
|
onMapResponse(callback: any) {
|
|
207
|
+
hooks.onMapResponse ??= [];
|
|
286
208
|
hooks.onMapResponse.push({
|
|
287
209
|
id: nextHookId(appId),
|
|
288
210
|
applyTo: "local",
|
|
@@ -291,6 +213,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
291
213
|
return app;
|
|
292
214
|
},
|
|
293
215
|
onGlobalError(callback: any) {
|
|
216
|
+
hooks.onGlobalError ??= [];
|
|
294
217
|
hooks.onGlobalError.push({
|
|
295
218
|
id: nextHookId(appId),
|
|
296
219
|
applyTo: "global",
|
|
@@ -299,6 +222,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
299
222
|
return app;
|
|
300
223
|
},
|
|
301
224
|
onGlobalAfterResponse(callback: any) {
|
|
225
|
+
hooks.onGlobalAfterResponse ??= [];
|
|
302
226
|
hooks.onGlobalAfterResponse.push({
|
|
303
227
|
id: nextHookId(appId),
|
|
304
228
|
applyTo: "global",
|
|
@@ -322,11 +246,21 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
322
246
|
const def: RouteDef = args.length === 2 ? args[0] : undefined;
|
|
323
247
|
const handler = args[1] ?? args[0];
|
|
324
248
|
const route = `${prefix}${path}`;
|
|
325
|
-
|
|
249
|
+
const hooks = cloneHooks();
|
|
250
|
+
const compiledHandler = compileRouteHandler({
|
|
251
|
+
schemaAdapter: options?.schemaAdapter,
|
|
326
252
|
def,
|
|
253
|
+
hooks,
|
|
254
|
+
method,
|
|
255
|
+
route,
|
|
327
256
|
handler,
|
|
257
|
+
});
|
|
258
|
+
addRoutesEntry(method, route, {
|
|
259
|
+
def,
|
|
328
260
|
route,
|
|
329
|
-
hooks
|
|
261
|
+
hooks,
|
|
262
|
+
compiledHandler,
|
|
263
|
+
handler,
|
|
330
264
|
});
|
|
331
265
|
return app;
|
|
332
266
|
},
|
|
@@ -348,11 +282,22 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
348
282
|
}
|
|
349
283
|
|
|
350
284
|
const route = `${prefix}${path}/**`;
|
|
285
|
+
const hooks = cloneHooks();
|
|
286
|
+
const compiledHandler = compileRouteHandler({
|
|
287
|
+
schemaAdapter: options?.schemaAdapter,
|
|
288
|
+
hooks,
|
|
289
|
+
method: "ANY",
|
|
290
|
+
route,
|
|
291
|
+
fetch,
|
|
292
|
+
def,
|
|
293
|
+
});
|
|
294
|
+
|
|
351
295
|
addRoutesEntry(Method.Any, route, {
|
|
352
296
|
def,
|
|
353
297
|
fetch,
|
|
354
298
|
route,
|
|
355
|
-
hooks
|
|
299
|
+
hooks,
|
|
300
|
+
compiledHandler,
|
|
356
301
|
});
|
|
357
302
|
|
|
358
303
|
return app as any;
|
|
@@ -369,20 +314,23 @@ export function createApp<TPrefix extends BasePrefix = "">(
|
|
|
369
314
|
}
|
|
370
315
|
}
|
|
371
316
|
|
|
372
|
-
// Add global hooks to parent
|
|
373
|
-
for (const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
317
|
+
// Add the child's global hooks to parent
|
|
318
|
+
for (const hookName of Object.keys(
|
|
319
|
+
childApp["~zeta"].hooks,
|
|
320
|
+
) as LifeCycleHookName[]) {
|
|
321
|
+
for (const hook of childApp["~zeta"].hooks[
|
|
322
|
+
hookName
|
|
323
|
+
]! as LifeCycleHook<any>[]) {
|
|
324
|
+
if (hook.applyTo !== "global" && !childApp["~zeta"].exported)
|
|
325
|
+
continue;
|
|
326
|
+
|
|
327
|
+
if (hooks[hookName]) {
|
|
328
|
+
// Don't add a hook if it's already there
|
|
329
|
+
if (!hooks[hookName].includes(hook)) hooks[hookName].push(hook);
|
|
330
|
+
} else {
|
|
331
|
+
hooks[hookName] = [hook];
|
|
378
332
|
}
|
|
379
333
|
}
|
|
380
|
-
let seen = new Set<string>();
|
|
381
|
-
hooks[name] = hooks[name].filter((hook) => {
|
|
382
|
-
if (seen.has(hook.id)) return false;
|
|
383
|
-
seen.add(hook.id);
|
|
384
|
-
return true;
|
|
385
|
-
}) as LifeCycleHook<any>[];
|
|
386
334
|
}
|
|
387
335
|
|
|
388
336
|
return app as any;
|
package/src/client.ts
CHANGED
|
@@ -99,11 +99,12 @@ export function createAppClient<TApp extends App>(
|
|
|
99
99
|
headers: { ...headers } as Record<string, string>,
|
|
100
100
|
} satisfies RequestInit;
|
|
101
101
|
|
|
102
|
-
const
|
|
102
|
+
const serializedBody =
|
|
103
103
|
inputs.body == null ? undefined : smartSerialize(inputs.body);
|
|
104
|
-
if (
|
|
105
|
-
init.body =
|
|
106
|
-
if (
|
|
104
|
+
if (serializedBody) {
|
|
105
|
+
init.body = serializedBody.value;
|
|
106
|
+
if (serializedBody.contentType)
|
|
107
|
+
init.headers["Content-Type"] = serializedBody.contentType;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
try {
|
package/src/errors.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
1
|
import { HttpStatus } from "./status";
|
|
3
2
|
|
|
4
|
-
/**
|
|
5
|
-
* Thrown when validation fails. Refer to `.issues` for details on what went wrong.
|
|
6
|
-
*/
|
|
7
|
-
export class ValidationGlobalError extends Error {
|
|
8
|
-
constructor(
|
|
9
|
-
readonly input: any,
|
|
10
|
-
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>,
|
|
11
|
-
) {
|
|
12
|
-
super("Validation error");
|
|
13
|
-
this.name = "ValidationGlobalError";
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
3
|
/**
|
|
18
4
|
* Base class of all HTTP errors. You can throw this error or throw any of the
|
|
19
5
|
* subclasses. Zeta will automatically detect and handle these errors, setting
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { MatchedRoute } from "rou3";
|
|
2
|
+
import type { LifeCycleHooks, RouterData, ServerSideFetch } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
cleanupCompiledWhitespace,
|
|
5
|
+
getRawPathname,
|
|
6
|
+
serializeErrorResponse,
|
|
7
|
+
} from "./utils";
|
|
8
|
+
import { Context } from "./context";
|
|
9
|
+
import { HttpError, NotFoundHttpError } from "../errors";
|
|
10
|
+
import { HttpStatus } from "../status";
|
|
11
|
+
|
|
12
|
+
export function compileFetchFunction(options: CompileOptions): ServerSideFetch {
|
|
13
|
+
const onGlobalRequestCount = options.hooks.onGlobalRequest?.length;
|
|
14
|
+
const onGlobalAfterResponseCount =
|
|
15
|
+
options.hooks.onGlobalAfterResponse?.length;
|
|
16
|
+
const onGlobalErrorCount = options.hooks.onGlobalError?.length;
|
|
17
|
+
|
|
18
|
+
const js = `
|
|
19
|
+
return (request) => {
|
|
20
|
+
const path = utils.getRawPathname(request);
|
|
21
|
+
const ctx = new utils.Context(request, path, utils.origin);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
${onGlobalRequestCount ? compileOnGlobalRequestHook(onGlobalRequestCount) : ""}
|
|
25
|
+
|
|
26
|
+
const matchedRoute = utils.getRoute(request.method, path);
|
|
27
|
+
if (matchedRoute == null) {
|
|
28
|
+
throw new utils.NotFoundHttpError(undefined, {
|
|
29
|
+
method: request.method,
|
|
30
|
+
path,
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
ctx.matchedRoute = matchedRoute;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ctx.response = matchedRoute.data.compiledHandler(request, ctx);
|
|
37
|
+
if (typeof ctx.response.then !== utils.FUNCTION) return ctx.response;
|
|
38
|
+
|
|
39
|
+
return ctx.response.catch(error => {
|
|
40
|
+
${onGlobalErrorCount ? compileOnGlobalErrorHook(onGlobalErrorCount, 3) : ""}
|
|
41
|
+
|
|
42
|
+
${compileErrorResponse(3)}
|
|
43
|
+
})${onGlobalAfterResponseCount ? compileOnGlobalAfterResponsePromiseFinally(onGlobalAfterResponseCount, 2) : ""};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
${onGlobalErrorCount ? compileOnGlobalErrorHook(onGlobalErrorCount, 2) : ""}
|
|
46
|
+
|
|
47
|
+
${compileErrorResponse(2)}
|
|
48
|
+
} ${onGlobalAfterResponseCount ? compileOnGlobalAfterResponseFinally(onGlobalAfterResponseCount, 1) : ""}
|
|
49
|
+
}
|
|
50
|
+
//#sourceURL=zeta-jit-generated://zeta-fetch-fn.js
|
|
51
|
+
`;
|
|
52
|
+
return new Function("utils", cleanupCompiledWhitespace(js))({
|
|
53
|
+
FUNCTION: "function",
|
|
54
|
+
getRawPathname,
|
|
55
|
+
hooks: options.hooks,
|
|
56
|
+
Context,
|
|
57
|
+
getRoute: options.getRoute,
|
|
58
|
+
NotFoundHttpError,
|
|
59
|
+
origin: options.origin,
|
|
60
|
+
HttpError,
|
|
61
|
+
HttpStatus,
|
|
62
|
+
serializeErrorResponse,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function compileOnGlobalRequestHook(hookCount: number): string {
|
|
67
|
+
const lines: string[] = [];
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < hookCount; i++) {
|
|
70
|
+
const resultVar = `onGlobalRequestRes${i}`;
|
|
71
|
+
lines.push(
|
|
72
|
+
` const ${resultVar} = utils.hooks.onGlobalRequest[${i}].callback(ctx);`,
|
|
73
|
+
...(process.env.NODE_ENV !== "production"
|
|
74
|
+
? [
|
|
75
|
+
` if (${resultVar} instanceof Promise)`,
|
|
76
|
+
` console.warn("Warning: Promise returned from onGlobalRequest hook. Promises returned from onGlobalRequest are not awaited, ignoring the return value.");`,
|
|
77
|
+
]
|
|
78
|
+
: []),
|
|
79
|
+
` if (${resultVar})`,
|
|
80
|
+
` if (typeof ${resultVar}.body?.bytes === utils.FUNCTION)`,
|
|
81
|
+
` return ${resultVar};`,
|
|
82
|
+
` else`,
|
|
83
|
+
` for (const key of Object.keys(${resultVar}))`,
|
|
84
|
+
` ctx[key] = ${resultVar}[key];`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function compileOnGlobalErrorHook(hookCount: number, tabs: number): string {
|
|
92
|
+
const indent = " ".repeat(tabs);
|
|
93
|
+
const lines: string[] = [`${indent}ctx.error = error;`];
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < hookCount; i++) {
|
|
96
|
+
lines.push(`${indent}utils.hooks.onGlobalError[${i}].callback(ctx);`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function compileOnGlobalAfterResponseFinally(
|
|
103
|
+
hookCount: number,
|
|
104
|
+
tabs: number,
|
|
105
|
+
): string {
|
|
106
|
+
const indent = " ".repeat(tabs);
|
|
107
|
+
return `finally {
|
|
108
|
+
${compileOnGlobalAfterResponseHook(hookCount, tabs + 1)}
|
|
109
|
+
${indent}}
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function compileOnGlobalAfterResponsePromiseFinally(
|
|
114
|
+
hookCount: number,
|
|
115
|
+
tabs: number,
|
|
116
|
+
): string {
|
|
117
|
+
const indent = " ".repeat(tabs);
|
|
118
|
+
return `.finally(() => {
|
|
119
|
+
${compileOnGlobalAfterResponseHook(hookCount, tabs + 1)}
|
|
120
|
+
${indent}})`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function compileOnGlobalAfterResponseHook(
|
|
124
|
+
hookCount: number,
|
|
125
|
+
tabs: number,
|
|
126
|
+
): string {
|
|
127
|
+
const indent = " ".repeat(tabs);
|
|
128
|
+
const lines: string[] = [`${indent}setTimeout(() => {`];
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < hookCount; i++) {
|
|
131
|
+
lines.push(
|
|
132
|
+
`${indent} utils.hooks.onGlobalAfterResponse[${i}].callback(ctx);`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
lines.push(`${indent}})`);
|
|
137
|
+
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function compileErrorResponse(tabs: number): string {
|
|
142
|
+
const indent = " ".repeat(tabs);
|
|
143
|
+
return `${indent}const status =
|
|
144
|
+
${indent} error instanceof utils.HttpError
|
|
145
|
+
${indent} ? error.status
|
|
146
|
+
${indent} : utils.HttpStatus.InternalServerError;
|
|
147
|
+
${indent}return (ctx.response = Response.json(utils.serializeErrorResponse(error), { status }));`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type CompileOptions = {
|
|
151
|
+
hooks: LifeCycleHooks;
|
|
152
|
+
getRoute: (
|
|
153
|
+
method: string,
|
|
154
|
+
path: string,
|
|
155
|
+
) => MatchedRoute<RouterData> | undefined;
|
|
156
|
+
origin: string;
|
|
157
|
+
};
|