Skip to content

Commit f3bdffa

Browse files
committed
feat: FormData, File, and FileList body types
1 parent 8654eba commit f3bdffa

12 files changed

Lines changed: 187 additions & 11 deletions

File tree

example.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { version } from "./package.json" with { type: "json" };
99
import { z } from "zod/v4";
1010
import { zodSchemaAdapter } from "./src/adapters/zod-schema-adapter";
11+
import { UploadFileBody, FormDataBody, UploadFilesBody } from "./src/schema";
1112

1213
const EntryId = z.coerce.number().int().min(0).meta({ ref: "EntryId" });
1314
type EntryId = z.infer<typeof EntryId>;
@@ -148,6 +149,48 @@ const app = createApp({
148149
responses: z.string().meta({ contentType: "text/csv" }),
149150
},
150151
() => "test",
152+
)
153+
.post(
154+
"/api/upload-form",
155+
{
156+
summary: "Upload Form",
157+
tags: ["Uploads"],
158+
body: FormDataBody,
159+
responses: {
160+
[HttpStatus.NoContent]: NoResponse,
161+
},
162+
},
163+
({ status, body }) => {
164+
return status(HttpStatus.NoContent, undefined);
165+
},
166+
)
167+
.post(
168+
"/api/upload-file",
169+
{
170+
summary: "Upload File",
171+
tags: ["Uploads"],
172+
body: UploadFileBody,
173+
responses: {
174+
[HttpStatus.NoContent]: NoResponse,
175+
},
176+
},
177+
({ status, body }) => {
178+
return status(HttpStatus.NoContent, undefined);
179+
},
180+
)
181+
.post(
182+
"/api/upload-files",
183+
{
184+
summary: "Upload Files",
185+
tags: ["Uploads"],
186+
body: UploadFilesBody,
187+
responses: {
188+
[HttpStatus.NoContent]: NoResponse,
189+
},
190+
},
191+
({ status, body }) => {
192+
return status(HttpStatus.NoContent, undefined);
193+
},
151194
);
152195

153196
console.log(

jsr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"./types": "./src/types.ts",
1313
"./client": "./src/client.ts",
1414
"./testing": "./src/testing.ts",
15+
"./schema": "./src/schema.ts",
1516
"./adapters/zod-schema-adapter": "./src/adapters/zod-schema-adapter.ts",
1617
"./transports/bun-transport": "./src/transports/bun-transport.ts",
1718
"./transports/deno-transport": "./src/transports/deno-transport.ts"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"./types": "./src/types.ts",
1313
"./client": "./src/client.ts",
1414
"./testing": "./src/testing.ts",
15+
"./schema": "./src/schema.ts",
1516
"./adapters/zod-schema-adapter": "./src/adapters/zod-schema-adapter.ts",
1617
"./transports/bun-transport": "./src/transports/bun-transport.ts",
1718
"./transports/deno-transport": "./src/transports/deno-transport.ts"

src/__tests__/client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { GetClientRoutes } from "../client";
66
import { expectTypeOf } from "expect-type";
77
import { z } from "zod/v4";
88
import { HttpStatus } from "../status";
9-
import { ErrorResponse, NoResponse } from "../custom-responses";
9+
import { ErrorResponse, NoResponse } from "../schema";
1010

1111
// Silence console.error logs
1212
globalThis.console.error = mock();

src/__tests__/types.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expectTypeOf } from "expect-type";
33
import type * as t from "../types";
44
import { z } from "zod/v4";
55
import type { StandardSchemaV1 } from "@standard-schema/spec";
6-
import { ErrorResponse } from "../custom-responses";
6+
import { ErrorResponse } from "../schema";
77
import type { HttpStatus } from "../status";
88

99
describe("Types", () => {

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
GetRequestParamsInput,
99
GetResponseOutput,
1010
} from "./types";
11-
import type { ErrorResponse } from "./custom-responses";
11+
import type { ErrorResponse } from "./schema";
1212
import { smartDeserialize, smartSerialize } from "./internal/serialization";
1313
import type {
1414
GetAppRoutes,

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export * from "./app";
22
export type { App } from "./types";
3-
export { ErrorResponse, NoResponse, type ZetaSchema } from "./custom-responses";
3+
export { ErrorResponse, NoResponse, type ZetaSchema } from "./schema";
44
export * from "./status";
55
export * from "./errors";

src/internal/serialization.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ export function smartSerialize(value: unknown):
1313
};
1414
}
1515

16+
if (value instanceof File) {
17+
const serialized = new FormData();
18+
serialized.append("file", value);
19+
return {
20+
contentType: undefined,
21+
serialized,
22+
};
23+
}
24+
25+
if (value instanceof FileList) {
26+
const serialized = new FormData();
27+
for (let i = 0; i < value.length; i++) {
28+
serialized.append("files", value.item(i)!);
29+
}
30+
return {
31+
contentType: undefined,
32+
serialized,
33+
};
34+
}
35+
1636
if (value instanceof Blob) {
1737
return {
1838
contentType: value.type,

src/internal/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
Transport,
1111
} from "../types";
1212
import type { MatchedRoute } from "rou3";
13-
import type { ErrorResponse } from "../custom-responses";
13+
import type { ErrorResponse } from "../schema";
1414
import { createBunTransport } from "../transports/bun-transport";
1515
import { createDenoTransport } from "../transports/deno-transport";
1616

src/meta.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { StandardSchemaV1 } from "@standard-schema/spec";
22
import type { SchemaAdapter } from "./types";
3-
import type { ZetaSchema } from "./custom-responses";
3+
import { isZetaSchema, type ZetaSchema } from "./schema";
44

55
/** Get metadata for either a ZetaSchema or a StandardSchemaV1. */
66
export function getMeta(
77
adapter: SchemaAdapter | undefined,
88
schema: StandardSchemaV1 | ZetaSchema,
99
): Record<string, any> {
10-
if ("~zeta" in schema) return schema.meta;
10+
if (isZetaSchema(schema)) return schema["~zeta"].meta;
1111

1212
if (!adapter) return {};
1313
return adapter.getMeta(schema) ?? {};

0 commit comments

Comments
 (0)