@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.
Files changed (41) hide show
  1. package/dist/adapters/zod-schema-adapter.d.mts +17 -0
  2. package/dist/adapters/zod-schema-adapter.mjs +726 -0
  3. package/dist/app-Bc9Kn3KA.mjs +1225 -0
  4. package/dist/client.d.mts +71 -0
  5. package/dist/client.mjs +73 -0
  6. package/dist/index.d.mts +317 -0
  7. package/dist/index.mjs +3 -0
  8. package/dist/schema-DKqL09oQ.d.mts +168 -0
  9. package/dist/schema.d.mts +2 -0
  10. package/dist/schema.mjs +151 -0
  11. package/dist/serialization-0dai2wUm.mjs +56 -0
  12. package/dist/testing.d.mts +26 -0
  13. package/dist/testing.mjs +52 -0
  14. package/dist/transports/bun-transport.d.mts +47 -0
  15. package/dist/transports/bun-transport.mjs +58 -0
  16. package/dist/transports/deno-transport.d.mts +48 -0
  17. package/dist/transports/deno-transport.mjs +57 -0
  18. package/dist/transports/fetch-transport.d.mts +6 -0
  19. package/dist/transports/fetch-transport.mjs +25 -0
  20. package/dist/types-BvjPE9EM.d.mts +712 -0
  21. package/dist/types.d.mts +2 -0
  22. package/dist/types.mjs +1 -0
  23. package/package.json +51 -19
  24. package/src/adapters/zod-schema-adapter.ts +0 -29
  25. package/src/app.ts +0 -479
  26. package/src/client.ts +0 -184
  27. package/src/errors.ts +0 -529
  28. package/src/index.ts +0 -5
  29. package/src/internal/compile-fetch-function.ts +0 -166
  30. package/src/internal/compile-route-handler.ts +0 -194
  31. package/src/internal/context.ts +0 -65
  32. package/src/internal/serialization.ts +0 -91
  33. package/src/internal/utils.ts +0 -191
  34. package/src/meta.ts +0 -14
  35. package/src/open-api.ts +0 -273
  36. package/src/schema.ts +0 -271
  37. package/src/status.ts +0 -143
  38. package/src/testing.ts +0 -62
  39. package/src/transports/bun-transport.ts +0 -17
  40. package/src/transports/deno-transport.ts +0 -13
  41. package/src/types.ts +0 -1102
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from "./app";
2
- export type { App } from "./types";
3
- export { ErrorResponse, NoResponse, type ZetaSchema } from "./schema";
4
- export * from "./status";
5
- export * from "./errors";
@@ -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
- }
@@ -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
- }
@@ -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
- }