@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
@@ -0,0 +1,151 @@
1
+ //#region src/schema.ts
2
+ function createZetaSchema(name, validate, toJsonSchema, meta = {}) {
3
+ const parentMeta = meta;
4
+ return {
5
+ "~zeta": {
6
+ type: name,
7
+ meta
8
+ },
9
+ "~standard": {
10
+ vendor: "@aklinker/zeta",
11
+ version: 1,
12
+ validate
13
+ },
14
+ meta(meta) {
15
+ return createZetaSchema(name, validate, void 0, {
16
+ ...parentMeta,
17
+ ...meta
18
+ });
19
+ },
20
+ toJsonSchema
21
+ };
22
+ }
23
+ /**
24
+ * A schema for an error response. Use when defining additional status codes
25
+ * that an operation might return with:
26
+ *
27
+ * ```ts
28
+ * import { ErrorResponse } from '@aklinker/zeta';
29
+ *
30
+ * app.get(
31
+ * "/api/item/:itemId",
32
+ * {
33
+ * responses: {
34
+ * 200: Item.optional(),
35
+ * 404: ErrorResponse,
36
+ * }
37
+ * },
38
+ * () => {
39
+ * // ...
40
+ * }
41
+ * );
42
+ * ```
43
+ */
44
+ const ErrorResponse = createZetaSchema("ErrorResponse", (value) => {
45
+ if (value == null) return { issues: [{ message: `Expected an object, received ${value}` }] };
46
+ if (typeof value !== "object") return { issues: [{ message: `Expected an object, received ${typeof value}` }] };
47
+ const issues = [];
48
+ if (typeof value.name !== "string") issues.push({
49
+ message: `Expected a string, received ${typeof value.name}`,
50
+ path: ["name"]
51
+ });
52
+ if (typeof value.message !== "string") issues.push({
53
+ message: `Expected a string, received ${typeof value.message}`,
54
+ path: ["message"]
55
+ });
56
+ if (typeof value.status !== "number") issues.push({
57
+ message: `Expected a number, received ${typeof value.status}`,
58
+ path: ["status"]
59
+ });
60
+ if (issues.length > 0) return { issues };
61
+ return { value };
62
+ });
63
+ function isZetaSchema(schema) {
64
+ return schema?.["~standard"]?.vendor === "@aklinker/zeta";
65
+ }
66
+ const ErrorResponseJsonSchema = {
67
+ type: "object",
68
+ properties: {
69
+ status: {
70
+ type: "number",
71
+ description: "HTTP status code",
72
+ example: 400
73
+ },
74
+ name: {
75
+ type: "string",
76
+ description: "The error's name",
77
+ example: "Bad Request"
78
+ },
79
+ message: {
80
+ type: "string",
81
+ description: "User-facing error message",
82
+ example: "Property 'name' is required"
83
+ }
84
+ },
85
+ required: [
86
+ "status",
87
+ "name",
88
+ "message"
89
+ ]
90
+ };
91
+ /**
92
+ * A schema for when you want to not return a response. Use when defining
93
+ * additional status codes that an operation might return with:
94
+ *
95
+ * ```ts
96
+ * import { NoResponse } from '@aklinker/zeta';
97
+ *
98
+ * app.get(
99
+ * "/api/item/:itemId",
100
+ * {
101
+ * responses: {
102
+ * [HttpStatus.Accepted]: NoResponse,
103
+ * }
104
+ * },
105
+ * () => {
106
+ * // ...
107
+ * }
108
+ * );
109
+ * ```
110
+ */
111
+ const NoResponse = createZetaSchema("NoResponse", (value) => {
112
+ return value != null ? { issues: [{ message: `Expected undefined or null, got ${typeof value}` }] } : { value: void 0 };
113
+ });
114
+ const FormDataBody = createZetaSchema("FormDataBody", (value) => {
115
+ return value instanceof FormData ? { value } : { issues: [{ message: `Expected FormData, got ${typeof value}` }] };
116
+ }, () => ({
117
+ type: "object",
118
+ additionalProperties: true
119
+ }), { contentType: "multipart/form-data" });
120
+ const UploadFileBody = createZetaSchema("UploadFileBody", (value) => {
121
+ if (!(value instanceof FormData)) return { issues: [{ message: `Expected FormData, got ${typeof value}` }] };
122
+ const file = value.get("file");
123
+ if (!(file instanceof File)) return { issues: [{ message: `Expected File, got ${typeof file}` }] };
124
+ return { value: file };
125
+ }, () => ({
126
+ type: "object",
127
+ properties: { file: {
128
+ type: "string",
129
+ format: "binary"
130
+ } }
131
+ }), { contentType: "multipart/form-data" });
132
+ const UploadFilesBody = createZetaSchema("UploadFilesBody", (value) => {
133
+ if (!(value instanceof FormData)) return { issues: [{ message: `Expected FormData, got ${typeof value}` }] };
134
+ const files = value.getAll("files");
135
+ if (!Array.isArray(files)) return { issues: [{ message: `Expected array of Files, got ${typeof files}` }] };
136
+ const issues = [];
137
+ for (const file of files) if (!(file instanceof File)) issues.push(`Expected File, got ${typeof file}`);
138
+ if (issues.length > 0) return { issues: issues.map((message) => ({ message })) };
139
+ return { value: files };
140
+ }, () => ({
141
+ type: "object",
142
+ properties: { files: {
143
+ type: "array",
144
+ items: {
145
+ type: "string",
146
+ format: "binary"
147
+ }
148
+ } }
149
+ }), { contentType: "multipart/form-data" });
150
+ //#endregion
151
+ export { ErrorResponse, ErrorResponseJsonSchema, FormDataBody, NoResponse, UploadFileBody, UploadFilesBody, isZetaSchema };
@@ -0,0 +1,56 @@
1
+ //#region src/internal/serialization.ts
2
+ function smartSerialize(value) {
3
+ if (value == null) return void 0;
4
+ switch (typeof value) {
5
+ case "string": return {
6
+ contentType: "text/plain",
7
+ value
8
+ };
9
+ case "number":
10
+ case "boolean":
11
+ case "bigint": return {
12
+ contentType: "text/plain",
13
+ value: String(value)
14
+ };
15
+ }
16
+ if (value instanceof FormData) return {
17
+ contentType: void 0,
18
+ value
19
+ };
20
+ if (value instanceof File) {
21
+ const form = new FormData();
22
+ form.append("file", value);
23
+ return {
24
+ contentType: void 0,
25
+ value: form
26
+ };
27
+ }
28
+ if (typeof FileList !== "undefined" && value instanceof FileList) {
29
+ const form = new FormData();
30
+ for (let i = 0; i < value.length; i++) form.append("files", value.item(i));
31
+ return {
32
+ contentType: void 0,
33
+ value: form
34
+ };
35
+ }
36
+ if (value instanceof Blob) return {
37
+ contentType: value.type,
38
+ value
39
+ };
40
+ return {
41
+ contentType: "application/json",
42
+ value: JSON.stringify(value)
43
+ };
44
+ }
45
+ function smartDeserialize(arg) {
46
+ if (arg instanceof Request && arg.method === "GET") return;
47
+ const contentType = arg.headers.get("content-type");
48
+ if (contentType == null) return;
49
+ if (contentType.startsWith("application/json")) return arg.json();
50
+ if (contentType.startsWith("application/x-www-form-urlencoded") || contentType.startsWith("multipart/form-data")) return arg.formData();
51
+ if (contentType.startsWith("text/")) return arg.text();
52
+ if (contentType.startsWith("application/octet-stream")) return arg.arrayBuffer();
53
+ throw Error(`Unknown content type: "${contentType}"`);
54
+ }
55
+ //#endregion
56
+ export { smartSerialize as n, smartDeserialize as t };
@@ -0,0 +1,26 @@
1
+ import { a as App } from "./types-BvjPE9EM.mjs";
2
+ import { AppClient, CreateAppClientOptions, GetClientRoutes } from "./client.mjs";
3
+
4
+ //#region src/testing.d.ts
5
+ /**
6
+ * Create a client for testing your server-side application. When `fetch` is
7
+ * called, the app's `fetch` function is called instead of using the global
8
+ * `fetch`.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const app = createApp()
13
+ * .get("/example", () => "Hello, world!")
14
+ *
15
+ * const client = createTestAppClient(app);
16
+ *
17
+ * expect(await client.get("/example")).toEqual("Hello, world!");
18
+ * ```
19
+ *
20
+ * @param app
21
+ * @param options
22
+ * @returns An app client used to test your server-side application.
23
+ */
24
+ declare function createTestAppClient<TApp extends App>(app: TApp, options?: Omit<CreateAppClientOptions, "fetch">): AppClient<GetClientRoutes<TApp>>;
25
+ //#endregion
26
+ export { createTestAppClient };
@@ -0,0 +1,52 @@
1
+ import { createAppClient } from "./client.mjs";
2
+ //#region src/testing.ts
3
+ /**
4
+ * Utilities for testing your server-side application.
5
+ *
6
+ * You don't need to use these utils, you can call the app's `fetch` function directly.
7
+ *
8
+ * ```ts
9
+ * const app = createApp()
10
+ * .get("/example", () => "Hello, world!")
11
+ * const fetch = app.build();
12
+ *
13
+ * const res = await fetch("http://test/example");
14
+ * expect(res.status).toEqual(200);
15
+ * expect(await res.text()).toEqual("Hello, world!");
16
+ * ```
17
+ *
18
+ * If you just care about the response body or error thrown, you can use the `createTestAppClient`.
19
+ *
20
+ * @see {@link createTestAppClient}
21
+ * @module
22
+ */
23
+ /**
24
+ * Create a client for testing your server-side application. When `fetch` is
25
+ * called, the app's `fetch` function is called instead of using the global
26
+ * `fetch`.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const app = createApp()
31
+ * .get("/example", () => "Hello, world!")
32
+ *
33
+ * const client = createTestAppClient(app);
34
+ *
35
+ * expect(await client.get("/example")).toEqual("Hello, world!");
36
+ * ```
37
+ *
38
+ * @param app
39
+ * @param options
40
+ * @returns An app client used to test your server-side application.
41
+ */
42
+ function createTestAppClient(app, options) {
43
+ const { baseUrl = "http://localhost" } = options ?? {};
44
+ const fetch = app.build();
45
+ return createAppClient({
46
+ baseUrl,
47
+ ...options,
48
+ fetch: (...args) => fetch(new Request(...args))
49
+ });
50
+ }
51
+ //#endregion
52
+ export { createTestAppClient };
@@ -0,0 +1,47 @@
1
+ import { Z as RequestContext, a as App, st as Transport } from "../types-BvjPE9EM.mjs";
2
+
3
+ //#region src/transports/bun-transport.d.ts
4
+ type BunTransport = Transport<[request: Request, server: Bun.Server]>;
5
+ declare function createBunTransport(options?: Omit<Bun.ServeFunctionOptions<any, any>, "fetch" | "port">): BunTransport;
6
+ /**
7
+ * Given the request context, return Bun's `server` object. Throws an error if the bun transport is not provided on the top-level app.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const app = createApp({
12
+ * transport: createBunTransport(),
13
+ * }).get("/", (ctx) => {
14
+ * const server = getBunServer(ctx);
15
+ * })
16
+ * ```
17
+ *
18
+ * @see `bunServerPlugin` to add the `server` object to request context directly.
19
+ */
20
+ declare function getBunServer(ctx: RequestContext): Bun.Server;
21
+ /**
22
+ * Plugin that decorates Bun's `server` object in the request context.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const app = createApp({
27
+ * transport: createBunTransport(),
28
+ * })
29
+ * .use(bunServerPlugin)
30
+ * .get("/", ({ server }) => {
31
+ * // ...
32
+ * })
33
+ * ```
34
+ *
35
+ * @see `getBunServer` for a simple function to return the server
36
+ */
37
+ declare const bunServerPlugin: App<{
38
+ prefix: "";
39
+ ctx: {
40
+ server: Bun.Server;
41
+ };
42
+ exported: true;
43
+ routes: {};
44
+ transport: BunTransport;
45
+ }>;
46
+ //#endregion
47
+ export { BunTransport, bunServerPlugin, createBunTransport, getBunServer };
@@ -0,0 +1,58 @@
1
+ import { t as createApp } from "../app-Bc9Kn3KA.mjs";
2
+ //#region src/transports/bun-transport.ts
3
+ const SERVER_KEY = Symbol("bun-transport.server");
4
+ function createBunTransport(options) {
5
+ const listen = (port, fetch, cb) => {
6
+ Bun.serve({
7
+ ...options,
8
+ port,
9
+ fetch
10
+ });
11
+ if (cb) setTimeout(cb, 0);
12
+ };
13
+ const decorate = (ctx, _request, server) => {
14
+ ctx[SERVER_KEY] = server;
15
+ };
16
+ return {
17
+ listen,
18
+ decorate
19
+ };
20
+ }
21
+ /**
22
+ * Given the request context, return Bun's `server` object. Throws an error if the bun transport is not provided on the top-level app.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const app = createApp({
27
+ * transport: createBunTransport(),
28
+ * }).get("/", (ctx) => {
29
+ * const server = getBunServer(ctx);
30
+ * })
31
+ * ```
32
+ *
33
+ * @see `bunServerPlugin` to add the `server` object to request context directly.
34
+ */
35
+ function getBunServer(ctx) {
36
+ const server = ctx[SERVER_KEY];
37
+ if (!server) throw Error("Bun server not found. Did you forget to provide the bun transport?");
38
+ return server;
39
+ }
40
+ /**
41
+ * Plugin that decorates Bun's `server` object in the request context.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const app = createApp({
46
+ * transport: createBunTransport(),
47
+ * })
48
+ * .use(bunServerPlugin)
49
+ * .get("/", ({ server }) => {
50
+ * // ...
51
+ * })
52
+ * ```
53
+ *
54
+ * @see `getBunServer` for a simple function to return the server
55
+ */
56
+ const bunServerPlugin = createApp().onTransform((ctx) => ({ server: getBunServer(ctx) })).export();
57
+ //#endregion
58
+ export { bunServerPlugin, createBunTransport, getBunServer };
@@ -0,0 +1,48 @@
1
+ import { Z as RequestContext, a as App, st as Transport } from "../types-BvjPE9EM.mjs";
2
+
3
+ //#region src/transports/deno-transport.d.ts
4
+ type DenoTransport = Transport<[request: Request, server: Deno.HttpServer]>;
5
+ type ServeOptions = Parameters<typeof Deno.serve>[0];
6
+ declare function createDenoTransport(options?: Omit<ServeOptions, "port">): DenoTransport;
7
+ /**
8
+ * Given the request context, return Deno's `server` object. Throws an error if the Deno transport is not provided on the top-level app.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const app = createApp({
13
+ * transport: createDenoTransport(),
14
+ * }).get("/", (ctx) => {
15
+ * const server = getDenoServer(ctx);
16
+ * })
17
+ * ```
18
+ *
19
+ * @see `denoServerPlugin` to add the `server` object to request context directly.
20
+ */
21
+ declare function getDenoServer(ctx: RequestContext): Deno.HttpServer;
22
+ /**
23
+ * Plugin that decorates Deno's `server` object in the request context.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const app = createApp({
28
+ * transport: createDenoTransport(),
29
+ * })
30
+ * .use(denoServerPlugin)
31
+ * .get("/", ({ server }) => {
32
+ * // ...
33
+ * })
34
+ * ```
35
+ *
36
+ * @see `getDenoServer` for a simple function to return the server
37
+ */
38
+ declare const denoServerPlugin: App<{
39
+ prefix: "";
40
+ ctx: {
41
+ server: Deno.HttpServer;
42
+ };
43
+ exported: true;
44
+ routes: {};
45
+ transport: DenoTransport;
46
+ }>;
47
+ //#endregion
48
+ export { DenoTransport, createDenoTransport, denoServerPlugin, getDenoServer };
@@ -0,0 +1,57 @@
1
+ import { t as createApp } from "../app-Bc9Kn3KA.mjs";
2
+ //#region src/transports/deno-transport.ts
3
+ const SERVER_KEY = Symbol("deno-transport.server");
4
+ function createDenoTransport(options) {
5
+ const listen = (port, fetch, cb) => {
6
+ Deno.serve({
7
+ ...options,
8
+ port
9
+ }, fetch);
10
+ if (cb) setTimeout(cb, 0);
11
+ };
12
+ const decorate = (ctx, _request, server) => {
13
+ ctx[SERVER_KEY] = server;
14
+ };
15
+ return {
16
+ listen,
17
+ decorate
18
+ };
19
+ }
20
+ /**
21
+ * Given the request context, return Deno's `server` object. Throws an error if the Deno transport is not provided on the top-level app.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const app = createApp({
26
+ * transport: createDenoTransport(),
27
+ * }).get("/", (ctx) => {
28
+ * const server = getDenoServer(ctx);
29
+ * })
30
+ * ```
31
+ *
32
+ * @see `denoServerPlugin` to add the `server` object to request context directly.
33
+ */
34
+ function getDenoServer(ctx) {
35
+ const server = ctx[SERVER_KEY];
36
+ if (!server) throw Error("Deno server not found. Did you forget to provide the deno transport?");
37
+ return server;
38
+ }
39
+ /**
40
+ * Plugin that decorates Deno's `server` object in the request context.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const app = createApp({
45
+ * transport: createDenoTransport(),
46
+ * })
47
+ * .use(denoServerPlugin)
48
+ * .get("/", ({ server }) => {
49
+ * // ...
50
+ * })
51
+ * ```
52
+ *
53
+ * @see `getDenoServer` for a simple function to return the server
54
+ */
55
+ const denoServerPlugin = createApp().onTransform((ctx) => ({ server: getDenoServer(ctx) })).export();
56
+ //#endregion
57
+ export { createDenoTransport, denoServerPlugin, getDenoServer };
@@ -0,0 +1,6 @@
1
+ import { st as Transport } from "../types-BvjPE9EM.mjs";
2
+
3
+ //#region src/transports/fetch-transport.d.ts
4
+ declare function createFetchTransport(): Transport;
5
+ //#endregion
6
+ export { createFetchTransport };
@@ -0,0 +1,25 @@
1
+ //#region src/transports/fetch-transport.ts
2
+ function createFetchTransport() {
3
+ const listen = (port, fetch, cb) => {
4
+ if (globalThis.Bun) Bun.serve({
5
+ port,
6
+ fetch
7
+ });
8
+ else if (globalThis.Deno) Deno.serve({ port }, fetch);
9
+ else throw Error(`Cannot automatically detect which transport to use. You must specify a transport in your top-level app:
10
+
11
+ ---
12
+ import { createBunTransport } from '@aklinker1/zeta/transports/bun-transport';
13
+
14
+ const app = createApp({
15
+ transport: createBunTransport(),
16
+ })
17
+
18
+ app.listen();
19
+ ---`);
20
+ if (cb) setTimeout(cb, 0);
21
+ };
22
+ return { listen };
23
+ }
24
+ //#endregion
25
+ export { createFetchTransport };