@aklinker1/zeta 2.1.2 → 2.2.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/dist/adapters/zod-schema-adapter.d.mts +17 -0
- package/dist/adapters/zod-schema-adapter.mjs +726 -0
- package/dist/app-Bc9Kn3KA.mjs +1225 -0
- package/dist/client.d.mts +71 -0
- package/dist/client.mjs +73 -0
- package/dist/index.d.mts +317 -0
- package/dist/index.mjs +3 -0
- package/dist/schema-DKqL09oQ.d.mts +168 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +151 -0
- package/dist/serialization-0dai2wUm.mjs +56 -0
- package/dist/testing.d.mts +26 -0
- package/dist/testing.mjs +52 -0
- package/dist/transports/bun-transport.d.mts +47 -0
- package/dist/transports/bun-transport.mjs +58 -0
- package/dist/transports/deno-transport.d.mts +48 -0
- package/dist/transports/deno-transport.mjs +57 -0
- package/dist/transports/fetch-transport.d.mts +6 -0
- package/dist/transports/fetch-transport.mjs +25 -0
- package/dist/types-BvjPE9EM.d.mts +712 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/package.json +51 -19
- package/src/adapters/zod-schema-adapter.ts +0 -29
- package/src/app.ts +0 -479
- package/src/client.ts +0 -184
- package/src/errors.ts +0 -529
- package/src/index.ts +0 -5
- package/src/internal/compile-fetch-function.ts +0 -166
- package/src/internal/compile-route-handler.ts +0 -194
- package/src/internal/context.ts +0 -65
- package/src/internal/serialization.ts +0 -91
- package/src/internal/utils.ts +0 -191
- package/src/meta.ts +0 -14
- package/src/open-api.ts +0 -273
- package/src/schema.ts +0 -271
- package/src/status.ts +0 -143
- package/src/testing.ts +0 -62
- package/src/transports/bun-transport.ts +0 -17
- package/src/transports/deno-transport.ts +0 -13
- package/src/types.ts +0 -1102
package/src/index.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import type { MatchedRoute } from "rou3";
|
|
2
|
-
import { HttpError, NotFoundHttpError } from "../errors";
|
|
3
|
-
import { HttpStatus } from "../status";
|
|
4
|
-
import type { LifeCycleHooks, RouterData, ServerSideFetch } from "../types";
|
|
5
|
-
import { Context } from "./context";
|
|
6
|
-
import {
|
|
7
|
-
cleanupCompiledWhitespace,
|
|
8
|
-
getRawPathname,
|
|
9
|
-
serializeErrorResponse,
|
|
10
|
-
} from "./utils";
|
|
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
|
-
${onGlobalAfterResponseCount ? "let handlerReturnedPromise = false;" : ""}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
${onGlobalRequestCount ? compileOnGlobalRequestHook(onGlobalRequestCount) : ""}
|
|
26
|
-
|
|
27
|
-
const matchedRoute = utils.getRoute(request.method, path);
|
|
28
|
-
if (matchedRoute == null) {
|
|
29
|
-
throw new utils.NotFoundHttpError(undefined, {
|
|
30
|
-
method: request.method,
|
|
31
|
-
path,
|
|
32
|
-
});
|
|
33
|
-
} else {
|
|
34
|
-
ctx.matchedRoute = matchedRoute;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
ctx.response = matchedRoute.data.compiledHandler(request, ctx);
|
|
38
|
-
if (typeof ctx.response.then !== utils.FUNCTION) return ctx.response;
|
|
39
|
-
|
|
40
|
-
${onGlobalAfterResponseCount ? "handlerReturnedPromise = true;" : ""}
|
|
41
|
-
return ctx.response.catch(error => {
|
|
42
|
-
${onGlobalErrorCount ? compileOnGlobalErrorHook(onGlobalErrorCount, 3) : ""}
|
|
43
|
-
|
|
44
|
-
${compileErrorResponse(3)}
|
|
45
|
-
})${onGlobalAfterResponseCount ? compileOnGlobalAfterResponsePromiseFinally(onGlobalAfterResponseCount, 2) : ""};
|
|
46
|
-
} catch (error) {
|
|
47
|
-
${onGlobalErrorCount ? compileOnGlobalErrorHook(onGlobalErrorCount, 2) : ""}
|
|
48
|
-
|
|
49
|
-
${compileErrorResponse(2)}
|
|
50
|
-
} ${onGlobalAfterResponseCount ? compileOnGlobalAfterResponseFinally(onGlobalAfterResponseCount, 1) : ""}
|
|
51
|
-
}
|
|
52
|
-
//#sourceURL=zeta-jit-generated://zeta-fetch-fn.js
|
|
53
|
-
`;
|
|
54
|
-
return new Function("utils", cleanupCompiledWhitespace(js))({
|
|
55
|
-
FUNCTION: "function",
|
|
56
|
-
getRawPathname,
|
|
57
|
-
hooks: options.hooks,
|
|
58
|
-
Context,
|
|
59
|
-
getRoute: options.getRoute,
|
|
60
|
-
NotFoundHttpError,
|
|
61
|
-
origin: options.origin,
|
|
62
|
-
HttpError,
|
|
63
|
-
HttpStatus,
|
|
64
|
-
serializeErrorResponse,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function compileOnGlobalRequestHook(hookCount: number): string {
|
|
69
|
-
const lines: string[] = [];
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < hookCount; i++) {
|
|
72
|
-
const resultVar = `onGlobalRequestRes${i}`;
|
|
73
|
-
lines.push(
|
|
74
|
-
` const ${resultVar} = utils.hooks.onGlobalRequest[${i}].callback(ctx);`,
|
|
75
|
-
...(process.env.NODE_ENV !== "production"
|
|
76
|
-
? [
|
|
77
|
-
` if (${resultVar} instanceof Promise)`,
|
|
78
|
-
` console.warn("Warning: Promise returned from onGlobalRequest hook. Promises returned from onGlobalRequest are not awaited, ignoring the return value.");`,
|
|
79
|
-
]
|
|
80
|
-
: []),
|
|
81
|
-
` if (${resultVar})`,
|
|
82
|
-
` if (typeof ${resultVar}.body?.bytes === utils.FUNCTION)`,
|
|
83
|
-
` return ${resultVar};`,
|
|
84
|
-
` else`,
|
|
85
|
-
` for (const key of Object.keys(${resultVar}))`,
|
|
86
|
-
` ctx[key] = ${resultVar}[key];`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return lines.join("\n");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function compileOnGlobalErrorHook(hookCount: number, tabs: number): string {
|
|
94
|
-
const indent = " ".repeat(tabs);
|
|
95
|
-
const lines: string[] = [`${indent}ctx.error = error;`];
|
|
96
|
-
|
|
97
|
-
for (let i = 0; i < hookCount; i++) {
|
|
98
|
-
lines.push(`${indent}utils.hooks.onGlobalError[${i}].callback(ctx);`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return lines.join("\n");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function compileOnGlobalAfterResponseFinally(
|
|
105
|
-
hookCount: number,
|
|
106
|
-
tabs: number,
|
|
107
|
-
): string {
|
|
108
|
-
const indent = " ".repeat(tabs);
|
|
109
|
-
return `finally {
|
|
110
|
-
${indent} if (!handlerReturnedPromise) {
|
|
111
|
-
${compileOnGlobalAfterResponseHook(hookCount, tabs + 2)}
|
|
112
|
-
${indent} }
|
|
113
|
-
${indent}}
|
|
114
|
-
`;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function compileOnGlobalAfterResponsePromiseFinally(
|
|
118
|
-
hookCount: number,
|
|
119
|
-
tabs: number,
|
|
120
|
-
): string {
|
|
121
|
-
const indent = " ".repeat(tabs);
|
|
122
|
-
return `.finally(() => {
|
|
123
|
-
${compileOnGlobalAfterResponseHook(hookCount, tabs + 1)}
|
|
124
|
-
${indent}})`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function compileOnGlobalAfterResponseHook(
|
|
128
|
-
hookCount: number,
|
|
129
|
-
tabs: number,
|
|
130
|
-
): string {
|
|
131
|
-
const indent = " ".repeat(tabs);
|
|
132
|
-
const lines: string[] = [`${indent}setTimeout(() => {`];
|
|
133
|
-
|
|
134
|
-
for (let i = 0; i < hookCount; i++) {
|
|
135
|
-
lines.push(
|
|
136
|
-
`${indent} utils.hooks.onGlobalAfterResponse[${i}].callback(ctx);`,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
lines.push(`${indent}})`);
|
|
141
|
-
|
|
142
|
-
return lines.join("\n");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function compileErrorResponse(tabs: number): string {
|
|
146
|
-
const indent = " ".repeat(tabs);
|
|
147
|
-
return `${indent}const status =
|
|
148
|
-
${indent} error instanceof utils.HttpError
|
|
149
|
-
${indent} ? error.status
|
|
150
|
-
${indent} : utils.HttpStatus.InternalServerError;
|
|
151
|
-
${indent}return (
|
|
152
|
-
${indent} ctx.response = Response.json(
|
|
153
|
-
${indent} utils.serializeErrorResponse(error),
|
|
154
|
-
${indent} { status, headers: ctx.set.headers },
|
|
155
|
-
${indent} )
|
|
156
|
-
${indent});`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
type CompileOptions = {
|
|
160
|
-
hooks: LifeCycleHooks;
|
|
161
|
-
getRoute: (
|
|
162
|
-
method: string,
|
|
163
|
-
path: string,
|
|
164
|
-
) => MatchedRoute<RouterData> | undefined;
|
|
165
|
-
origin: string;
|
|
166
|
-
};
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { getMeta } from "../meta";
|
|
2
|
-
import type {
|
|
3
|
-
CompiledRouteHandler,
|
|
4
|
-
LifeCycleHookName,
|
|
5
|
-
LifeCycleHooks,
|
|
6
|
-
MaybePromise,
|
|
7
|
-
OnBeforeHandleContext,
|
|
8
|
-
RouteDef,
|
|
9
|
-
SchemaAdapter,
|
|
10
|
-
ServerSideFetch,
|
|
11
|
-
} from "../types";
|
|
12
|
-
import { smartDeserialize, smartSerialize } from "./serialization";
|
|
13
|
-
import {
|
|
14
|
-
cleanupCompiledWhitespace,
|
|
15
|
-
IsStatusResult,
|
|
16
|
-
validateInputSchema,
|
|
17
|
-
validateOutputSchema,
|
|
18
|
-
} from "./utils";
|
|
19
|
-
|
|
20
|
-
export function compileRouteHandler(
|
|
21
|
-
options: CompileOptions,
|
|
22
|
-
): CompiledRouteHandler {
|
|
23
|
-
if (options.fetch) {
|
|
24
|
-
return new Function(`
|
|
25
|
-
return (request, ctx) => ctx.matchedRoute.data.fetch(request)
|
|
26
|
-
//#sourceURL=${getSourceUrl(options)}
|
|
27
|
-
`)();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const responseContentTypeMap = getResponseContentTypeMap(options);
|
|
31
|
-
|
|
32
|
-
const js = `
|
|
33
|
-
return async (request, ctx) => {
|
|
34
|
-
${options.method === "GET" ? "" : ADD_CTX_BODY}
|
|
35
|
-
|
|
36
|
-
${options.hooks.onTransform?.length ? compileCtxModifierHookCall("onTransform", options.hooks.onTransform.length) : ""}
|
|
37
|
-
|
|
38
|
-
${options.def?.body ? "ctx.body = utils.validateInputSchema(ctx.matchedRoute.data.def.body, ctx.body);" : ""}
|
|
39
|
-
${options.def?.params ? "ctx.params = utils.validateInputSchema(ctx.matchedRoute.data.def.params, ctx.params);" : ""}
|
|
40
|
-
${options.def?.query ? "ctx.query = utils.validateInputSchema(ctx.matchedRoute.data.def.query, ctx.query);" : ""}
|
|
41
|
-
|
|
42
|
-
${options.hooks.onBeforeHandle?.length ? compileCtxModifierHookCall("onBeforeHandle", options.hooks.onBeforeHandle.length) : ""}
|
|
43
|
-
|
|
44
|
-
ctx.response = await ctx.matchedRoute.data.handler(ctx);
|
|
45
|
-
if (ctx.response) {
|
|
46
|
-
if (ctx.response[utils.IsStatusResult]) {
|
|
47
|
-
ctx.set.status = ctx.response.status;
|
|
48
|
-
ctx.response = ctx.response.body;
|
|
49
|
-
}
|
|
50
|
-
if (typeof ctx.response?.body?.bytes === utils.FUNCTION) return ctx.response;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
${compileValidateResponse(options)}
|
|
54
|
-
|
|
55
|
-
${options.hooks.onAfterHandle?.length ? compileResponseModifierHookCall("onAfterHandle", options.hooks.onAfterHandle.length) : ""}
|
|
56
|
-
|
|
57
|
-
${options.hooks.onMapResponse?.length ? compileResponseModifierHookCall("onMapResponse", options.hooks.onMapResponse.length) : ""}
|
|
58
|
-
|
|
59
|
-
if (ctx.response == null) {
|
|
60
|
-
return (
|
|
61
|
-
ctx.response = new Response(undefined, {
|
|
62
|
-
status: ctx.set.status,
|
|
63
|
-
headers: ctx.set.headers,
|
|
64
|
-
})
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const serialized = utils.smartSerialize(ctx.response);
|
|
69
|
-
if (!ctx.set.headers["Content-Type"]) ctx.set.headers["Content-Type"] = ${responseContentTypeMap ? "responseContentTypeMap[ctx.set.status] ??" : ""} serialized.contentType
|
|
70
|
-
return (
|
|
71
|
-
ctx.response = new Response(serialized.value, {
|
|
72
|
-
status: ctx.set.status,
|
|
73
|
-
headers: ctx.set.headers,
|
|
74
|
-
})
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
//#sourceURL=${getSourceUrl(options)}
|
|
78
|
-
`;
|
|
79
|
-
return new Function(
|
|
80
|
-
"utils",
|
|
81
|
-
"responseContentTypeMap",
|
|
82
|
-
cleanupCompiledWhitespace(js),
|
|
83
|
-
)(UTILS, responseContentTypeMap);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// These functions are available in the generated code via the "utils" object.
|
|
87
|
-
const UTILS = {
|
|
88
|
-
smartDeserialize,
|
|
89
|
-
smartSerialize,
|
|
90
|
-
FUNCTION: "function",
|
|
91
|
-
IsStatusResult,
|
|
92
|
-
validateInputSchema,
|
|
93
|
-
validateOutputSchema,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
type CompileOptions = {
|
|
97
|
-
schemaAdapter: SchemaAdapter | undefined;
|
|
98
|
-
def: RouteDef | undefined;
|
|
99
|
-
method: string;
|
|
100
|
-
route: string;
|
|
101
|
-
hooks: LifeCycleHooks;
|
|
102
|
-
fetch?: ServerSideFetch;
|
|
103
|
-
handler?: (ctx: OnBeforeHandleContext) => MaybePromise<any>;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
function getSourceUrl(options: CompileOptions) {
|
|
107
|
-
return `zeta-jit-generated://${options.method.toLowerCase()}-${options.route.replace(/\s/gm, "").replaceAll("/", "-")}.js`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const ADD_CTX_BODY = `
|
|
111
|
-
ctx.body = utils.smartDeserialize(request);
|
|
112
|
-
if (ctx.body) ctx.body = await ctx.body;
|
|
113
|
-
`;
|
|
114
|
-
|
|
115
|
-
function compileCtxModifierHookCall(
|
|
116
|
-
hook: LifeCycleHookName,
|
|
117
|
-
hookCount: number,
|
|
118
|
-
): string {
|
|
119
|
-
const lines: string[] = [];
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < hookCount; i++) {
|
|
122
|
-
const resultVar = `${hook}Res${i}`;
|
|
123
|
-
lines.push(
|
|
124
|
-
` const ${resultVar} = await ctx.matchedRoute.data.hooks.${hook}[${i}].callback(ctx);`,
|
|
125
|
-
` if (${resultVar})`,
|
|
126
|
-
` if (typeof ${resultVar}.body?.bytes === utils.FUNCTION)`,
|
|
127
|
-
` return ${resultVar};`,
|
|
128
|
-
` else`,
|
|
129
|
-
` for (const key of Object.keys(${resultVar}))`,
|
|
130
|
-
` ctx[key] = ${resultVar}[key];`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return lines.join("\n");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function compileResponseModifierHookCall(
|
|
138
|
-
hook: LifeCycleHookName,
|
|
139
|
-
hookCount: number,
|
|
140
|
-
): string {
|
|
141
|
-
const lines: string[] = [];
|
|
142
|
-
|
|
143
|
-
for (let i = 0; i < hookCount; i++) {
|
|
144
|
-
const resultVar = `${hook}Res${i}`;
|
|
145
|
-
lines.push(
|
|
146
|
-
` const ${resultVar} = await ctx.matchedRoute.data.hooks.${hook}[${i}].callback(ctx);`,
|
|
147
|
-
` if (${resultVar}) ctx.response = ${resultVar};`,
|
|
148
|
-
` if (typeof ${resultVar}.body?.bytes === utils.FUNCTION)`,
|
|
149
|
-
` return ${resultVar};`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return lines.join("\n");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function compileValidateResponse(options: CompileOptions): string {
|
|
157
|
-
// No schemas defined
|
|
158
|
-
if (!options.def?.responses) return "";
|
|
159
|
-
|
|
160
|
-
// One schema defined
|
|
161
|
-
if ("~standard" in options.def.responses)
|
|
162
|
-
return "ctx.response = utils.validateOutputSchema(ctx.matchedRoute.data.def.responses, ctx.response);";
|
|
163
|
-
|
|
164
|
-
// Multiple schemas based on the status code
|
|
165
|
-
return "ctx.response = utils.validateOutputSchema(ctx.matchedRoute.data.def.responses[ctx.set.status], ctx.response);";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function getResponseContentTypeMap(
|
|
169
|
-
options: CompileOptions,
|
|
170
|
-
): Record<number, string> | undefined {
|
|
171
|
-
// No schemas defined
|
|
172
|
-
if (!options.def?.responses) return;
|
|
173
|
-
|
|
174
|
-
// One schema defined
|
|
175
|
-
if ("~standard" in options.def.responses) {
|
|
176
|
-
const { contentType } = getMeta(
|
|
177
|
-
options.schemaAdapter,
|
|
178
|
-
options.def.responses,
|
|
179
|
-
);
|
|
180
|
-
if (!contentType) return;
|
|
181
|
-
|
|
182
|
-
return { [200]: contentType };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Multiple schemas based on the status code
|
|
186
|
-
const map: Record<number, string> = {};
|
|
187
|
-
for (const [status, schema] of Object.entries(options.def.responses)) {
|
|
188
|
-
const { contentType } = getMeta(options.schemaAdapter, schema);
|
|
189
|
-
map[Number(status)] = contentType;
|
|
190
|
-
}
|
|
191
|
-
if (Object.keys(map).length === 0) return;
|
|
192
|
-
|
|
193
|
-
return map;
|
|
194
|
-
}
|
package/src/internal/context.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { MatchedRoute } from "rou3";
|
|
2
|
-
import { HttpStatus } from "../status";
|
|
3
|
-
import type { RouterData, StatusResult } from "../types";
|
|
4
|
-
import { getRawParams, getRawQuery, IsStatusResult } from "./utils";
|
|
5
|
-
|
|
6
|
-
export class Context {
|
|
7
|
-
set = {
|
|
8
|
-
status: HttpStatus.Ok,
|
|
9
|
-
headers: {},
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
matchedRoute: MatchedRoute<RouterData> | undefined;
|
|
13
|
-
|
|
14
|
-
// Private storage for overwritten values
|
|
15
|
-
#params: Record<string, any> | undefined;
|
|
16
|
-
#query: Record<string, any> | undefined;
|
|
17
|
-
|
|
18
|
-
constructor(
|
|
19
|
-
public request: Request,
|
|
20
|
-
public path: string,
|
|
21
|
-
public origin: string,
|
|
22
|
-
) {}
|
|
23
|
-
|
|
24
|
-
get url(): URL {
|
|
25
|
-
return new URL(this.request.url, this.origin);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get params(): Record<string, any> {
|
|
29
|
-
if (this.#params !== undefined) {
|
|
30
|
-
return this.#params;
|
|
31
|
-
}
|
|
32
|
-
return this.matchedRoute?.params ? getRawParams(this.matchedRoute) : {};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
set params(value: Record<string, any>) {
|
|
36
|
-
this.#params = value;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get query(): Record<string, any> {
|
|
40
|
-
if (this.#query !== undefined) {
|
|
41
|
-
return this.#query;
|
|
42
|
-
}
|
|
43
|
-
return this.request.url.includes("?") ? getRawQuery(this.request) : {};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
set query(value: Record<string, any>) {
|
|
47
|
-
this.#query = value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get route(): string | undefined {
|
|
51
|
-
return this.matchedRoute?.data.route;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get method(): string {
|
|
55
|
-
return this.request.method;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
status(status: number, body?: unknown): StatusResult {
|
|
59
|
-
return {
|
|
60
|
-
[IsStatusResult]: true,
|
|
61
|
-
status,
|
|
62
|
-
body,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
export function smartSerialize(value: unknown):
|
|
2
|
-
| {
|
|
3
|
-
contentType: string | undefined;
|
|
4
|
-
value: BodyInit;
|
|
5
|
-
}
|
|
6
|
-
| undefined {
|
|
7
|
-
if (value == null) return undefined;
|
|
8
|
-
|
|
9
|
-
switch (typeof value) {
|
|
10
|
-
case "string":
|
|
11
|
-
return { contentType: "text/plain", value };
|
|
12
|
-
case "number":
|
|
13
|
-
case "boolean":
|
|
14
|
-
case "bigint":
|
|
15
|
-
return { contentType: "text/plain", value: String(value) };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (value instanceof FormData) {
|
|
19
|
-
return {
|
|
20
|
-
contentType: undefined, // Let fetch set the content type with a boundary
|
|
21
|
-
value,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (value instanceof File) {
|
|
26
|
-
const form = new FormData();
|
|
27
|
-
form.append("file", value);
|
|
28
|
-
return {
|
|
29
|
-
contentType: undefined,
|
|
30
|
-
value: form,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (typeof FileList !== "undefined" && value instanceof FileList) {
|
|
35
|
-
const form = new FormData();
|
|
36
|
-
for (let i = 0; i < value.length; i++) {
|
|
37
|
-
form.append("files", value.item(i)!);
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
contentType: undefined,
|
|
41
|
-
value: form,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (value instanceof Blob) {
|
|
46
|
-
return {
|
|
47
|
-
contentType: value.type,
|
|
48
|
-
value,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
contentType: "application/json",
|
|
54
|
-
value: JSON.stringify(value),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function smartDeserialize(
|
|
59
|
-
arg: Response | Request,
|
|
60
|
-
): Promise<unknown> | undefined {
|
|
61
|
-
if (arg instanceof Request && arg.method === "GET") return;
|
|
62
|
-
|
|
63
|
-
const contentType = arg.headers.get("content-type");
|
|
64
|
-
if (contentType == null) return;
|
|
65
|
-
|
|
66
|
-
// JSON
|
|
67
|
-
if (contentType.startsWith("application/json")) {
|
|
68
|
-
return arg.json();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Forms
|
|
72
|
-
if (
|
|
73
|
-
contentType.startsWith("application/x-www-form-urlencoded") ||
|
|
74
|
-
contentType.startsWith("multipart/form-data")
|
|
75
|
-
) {
|
|
76
|
-
return arg.formData();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Text
|
|
80
|
-
if (contentType.startsWith("text/")) {
|
|
81
|
-
return arg.text();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Binary
|
|
85
|
-
if (contentType.startsWith("application/octet-stream")) {
|
|
86
|
-
return arg.arrayBuffer();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Unknown
|
|
90
|
-
throw Error(`Unknown content type: "${contentType}"`);
|
|
91
|
-
}
|
package/src/internal/utils.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type { MatchedRoute } from "rou3";
|
|
3
|
-
import { HttpError } from "../errors";
|
|
4
|
-
import type { ErrorResponse } from "../schema";
|
|
5
|
-
import { HttpStatus } from "../status";
|
|
6
|
-
import { createBunTransport } from "../transports/bun-transport";
|
|
7
|
-
import { createDenoTransport } from "../transports/deno-transport";
|
|
8
|
-
import type {
|
|
9
|
-
App,
|
|
10
|
-
LifeCycleHook,
|
|
11
|
-
MaybePromise,
|
|
12
|
-
RouterData,
|
|
13
|
-
StatusResult,
|
|
14
|
-
Transport,
|
|
15
|
-
} from "../types";
|
|
16
|
-
|
|
17
|
-
export function validateSchema<T>(
|
|
18
|
-
schema: StandardSchemaV1<T, T>,
|
|
19
|
-
input: unknown,
|
|
20
|
-
status: number,
|
|
21
|
-
message: string,
|
|
22
|
-
): T {
|
|
23
|
-
const res = schema["~standard"].validate(input);
|
|
24
|
-
if (res instanceof Promise) throw Error("Async validation not supported");
|
|
25
|
-
|
|
26
|
-
if (res.issues)
|
|
27
|
-
throw new HttpError(status, message, {
|
|
28
|
-
issues: res.issues,
|
|
29
|
-
input: input,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return res.value;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function createHttpSchemaValidator(status: number, message: string) {
|
|
36
|
-
return <T>(schema: StandardSchemaV1<T, T>, input: unknown): T =>
|
|
37
|
-
validateSchema<T>(schema, input, status, message);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const validateInputSchema = createHttpSchemaValidator(
|
|
41
|
-
HttpStatus.BadRequest,
|
|
42
|
-
"Input validation failed",
|
|
43
|
-
);
|
|
44
|
-
export const validateOutputSchema = createHttpSchemaValidator(
|
|
45
|
-
HttpStatus.UnprocessableEntity,
|
|
46
|
-
"Output validation failed",
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
export function isApp(obj: unknown): obj is App<any> {
|
|
50
|
-
return (obj as any)[Symbol.toStringTag] === "ZetaApp";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function getRawPathname(request: Request): string {
|
|
54
|
-
// Fast path for common case: http://host/path
|
|
55
|
-
const start = request.url.indexOf("/", 8); // Skip 'http://' or 'https://'
|
|
56
|
-
if (start === -1) return "/";
|
|
57
|
-
|
|
58
|
-
// Find end of pathname (before ? or #)
|
|
59
|
-
for (let i = start + 1; i < request.url.length; i++) {
|
|
60
|
-
if (request.url[i] === "?" || request.url[i] === "#") {
|
|
61
|
-
return request.url.slice(start, i);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return request.url.slice(start);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getRawQuery(request: Request): Record<string, string> {
|
|
68
|
-
let index = request.url.indexOf("?");
|
|
69
|
-
if (index === -1) return {};
|
|
70
|
-
|
|
71
|
-
const res: Record<string, string> = {};
|
|
72
|
-
const str = request.url;
|
|
73
|
-
const len = str.length;
|
|
74
|
-
let start = index + 1;
|
|
75
|
-
|
|
76
|
-
for (let i = start; i < len; i++) {
|
|
77
|
-
if (str[i] === "&" || i === len - 1) {
|
|
78
|
-
const end = i === len - 1 ? len : i;
|
|
79
|
-
const eqIndex = str.indexOf("=", start);
|
|
80
|
-
if (eqIndex !== -1 && eqIndex < end) {
|
|
81
|
-
res[str.slice(start, eqIndex)] = str.slice(eqIndex + 1, end);
|
|
82
|
-
}
|
|
83
|
-
start = i + 1;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return res;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getRawParams(
|
|
90
|
-
route: MatchedRoute<RouterData>,
|
|
91
|
-
): Record<string, string> {
|
|
92
|
-
const params = route.params;
|
|
93
|
-
if (!params) return {};
|
|
94
|
-
|
|
95
|
-
const res: Record<string, string> = {};
|
|
96
|
-
for (const key in params) {
|
|
97
|
-
// Rename rou3's _ to ** to match type-system
|
|
98
|
-
const outKey = key === "_" ? "**" : key;
|
|
99
|
-
res[outKey] = decodeURIComponent(params[key]!);
|
|
100
|
-
}
|
|
101
|
-
return res;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function getErrorStack(err: Error): string[] | undefined {
|
|
105
|
-
if (process.env.NODE_ENV === "production") return;
|
|
106
|
-
return err.stack
|
|
107
|
-
?.split("\n")
|
|
108
|
-
.map((line) => line.trim())
|
|
109
|
-
.slice(1);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function serializeErrorResponse(err: unknown): ErrorResponse {
|
|
113
|
-
if (err instanceof HttpError)
|
|
114
|
-
return {
|
|
115
|
-
status: err.status,
|
|
116
|
-
name: err.name,
|
|
117
|
-
message: err.message,
|
|
118
|
-
...err.additionalInfo,
|
|
119
|
-
stack: getErrorStack(err),
|
|
120
|
-
cause: err.cause != null ? serializeErrorResponse(err.cause) : undefined,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
if (err instanceof Error)
|
|
124
|
-
return {
|
|
125
|
-
status: HttpStatus.InternalServerError,
|
|
126
|
-
name: err.name,
|
|
127
|
-
message: err.message,
|
|
128
|
-
stack: getErrorStack(err),
|
|
129
|
-
cause: err.cause != null ? serializeErrorResponse(err.cause) : undefined,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
name: "Unknown Error",
|
|
134
|
-
message: "An unknown error occurred",
|
|
135
|
-
status: HttpStatus.InternalServerError,
|
|
136
|
-
stack: getErrorStack(err as Error),
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export async function callCtxModifierHooks(
|
|
141
|
-
ctx: any,
|
|
142
|
-
hooks:
|
|
143
|
-
| LifeCycleHook<(ctx: any) => MaybePromise<Record<string, any> | void>>[]
|
|
144
|
-
| undefined,
|
|
145
|
-
): Promise<Response | undefined> {
|
|
146
|
-
if (!hooks) return;
|
|
147
|
-
|
|
148
|
-
for (const hook of hooks) {
|
|
149
|
-
let res = hook.callback(ctx);
|
|
150
|
-
if (res instanceof Promise) res = await res;
|
|
151
|
-
if (res instanceof Response) return res;
|
|
152
|
-
if (res) Object.assign(ctx, res); // TODO: Replace with manual property setting for performance?
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export const IsStatusResult = Symbol("IsStatusResult");
|
|
157
|
-
|
|
158
|
-
export function isStatusResult(result: any): result is StatusResult {
|
|
159
|
-
return IsStatusResult in result;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function detectTransport(): Transport {
|
|
163
|
-
// @ts-ignore: Bun types may not be available
|
|
164
|
-
if (typeof Bun !== "undefined") return createBunTransport();
|
|
165
|
-
// @ts-ignore: Deno types may not be available
|
|
166
|
-
if (typeof Deno !== "undefined") return createDenoTransport();
|
|
167
|
-
|
|
168
|
-
throw Error(`Cannot automatically detect which transport to use. You must specify a transport in your top-level app:
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
import { createBunTransport } from '@aklinker1/zeta/transports/bun-transport';
|
|
172
|
-
|
|
173
|
-
const app = createApp({
|
|
174
|
-
transport: createBunTransport(),
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
app.listen();
|
|
178
|
-
---`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function cleanupCompiledWhitespace(code: string): string {
|
|
182
|
-
return (
|
|
183
|
-
code
|
|
184
|
-
// Remove lines only containing spaces
|
|
185
|
-
.replace(/^ +$/gm, "")
|
|
186
|
-
// Reduce multiple newlines to one
|
|
187
|
-
.replace(/\n\n+/gm, "\n\n")
|
|
188
|
-
// Remove blank lines after curly braces
|
|
189
|
-
.replaceAll("{\n\n", "{\n")
|
|
190
|
-
);
|
|
191
|
-
}
|