diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6e6f015..36062a0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
- version: 7
+ version: 9
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
diff --git a/apps/example/package.json b/apps/example/package.json
index 885e8be..72ef027 100644
--- a/apps/example/package.json
+++ b/apps/example/package.json
@@ -16,7 +16,7 @@
"jsdom": "24.0.0",
"next-rest-framework": "workspace:*",
"tsx": "4.7.2",
- "zod-form-data": "2.0.2"
+ "zod-form-data": "3.0.1"
},
"devDependencies": {
"@types/jsdom": "^21.1.6",
diff --git a/apps/example/public/openapi.json b/apps/example/public/openapi.json
index da37333..6fa2105 100644
--- a/apps/example/public/openapi.json
+++ b/apps/example/public/openapi.json
@@ -998,19 +998,18 @@
"components": {
"schemas": {
"CreateTodo201ResponseBody": {
- "type": "string",
- "description": "New TODO created message."
+ "description": "New TODO created message.",
+ "type": "string"
},
"CreateTodo401ResponseBody": {
- "type": "string",
- "description": "Unauthorized."
+ "description": "Unauthorized.",
+ "type": "string"
},
"CreateTodoRequestBody": {
+ "description": "New TODO's name.",
"type": "object",
"properties": { "name": { "type": "string" } },
- "required": ["name"],
- "additionalProperties": false,
- "description": "New TODO's name."
+ "required": ["name"]
},
"CreateTodoResponseBody": {
"type": "object",
@@ -1026,18 +1025,18 @@
"DeleteTodo404ResponseBody": { "type": "string" },
"DeleteTodoRequestBody": { "type": "string" },
"DeleteTodoResponseBody": {
+ "description": "TODO not found.",
"type": "object",
"properties": { "error": { "type": "string" } },
"required": ["error"],
- "additionalProperties": false,
- "description": "TODO not found."
+ "additionalProperties": false
},
"DeleteTodoResponseBody2": {
+ "description": "TODO deleted message.",
"type": "object",
"properties": { "message": { "type": "string" } },
"required": ["message"],
- "additionalProperties": false,
- "description": "TODO deleted message."
+ "additionalProperties": false
},
"ErrorMessage": {
"type": "object",
@@ -1058,40 +1057,40 @@
"format": "binary"
},
"FormDataUrlEncodedRequestBody": {
+ "description": "Test form description.",
"type": "object",
"properties": { "text": { "type": "string" } },
- "required": ["text"],
- "additionalProperties": false,
- "description": "Test form description."
+ "required": ["text"]
},
"FormDataUrlEncodedResponseBody": {
+ "description": "Test form response.",
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
- "additionalProperties": false,
- "description": "Test form response."
+ "additionalProperties": false
},
"GetParams200ResponseBody": {
+ "description": "Parameters response.",
"type": "object",
"properties": {
"slug": { "type": "string", "enum": ["foo", "bar", "baz"] },
"total": { "type": "string" }
},
"required": ["slug", "total"],
- "additionalProperties": false,
- "description": "Parameters response."
+ "additionalProperties": false
},
"GetPathParams200ResponseBody": {
+ "description": "Parameters response.",
"type": "object",
"properties": {
"slug": { "type": "string", "enum": ["foo", "bar", "baz"] },
"total": { "type": "string" }
},
"required": ["slug", "total"],
- "additionalProperties": false,
- "description": "Parameters response."
+ "additionalProperties": false
},
"GetTodoById200ResponseBody": {
+ "description": "TODO response.",
"type": "object",
"properties": {
"id": { "type": "number" },
@@ -1099,25 +1098,25 @@
"completed": { "type": "boolean" }
},
"required": ["id", "name", "completed"],
- "additionalProperties": false,
- "description": "TODO response."
+ "additionalProperties": false
},
"GetTodoById404ResponseBody": {
- "type": "string",
- "description": "TODO not found."
+ "description": "TODO not found.",
+ "type": "string"
},
"GetTodoByIdRequestBody": {
- "type": "string",
- "description": "TODO name."
+ "description": "TODO name.",
+ "type": "string"
},
"GetTodoByIdResponseBody": {
+ "description": "TODO not found.",
"type": "object",
"properties": { "error": { "type": "string" } },
"required": ["error"],
- "additionalProperties": false,
- "description": "TODO not found."
+ "additionalProperties": false
},
"GetTodoByIdResponseBody2": {
+ "description": "TODO response.",
"type": "object",
"properties": {
"id": { "type": "number" },
@@ -1125,10 +1124,10 @@
"completed": { "type": "boolean" }
},
"required": ["id", "name", "completed"],
- "additionalProperties": false,
- "description": "TODO response."
+ "additionalProperties": false
},
"GetTodos200ResponseBody": {
+ "description": "List of TODOs.",
"type": "array",
"items": {
"type": "object",
@@ -1139,10 +1138,10 @@
},
"required": ["id", "name", "completed"],
"additionalProperties": false
- },
- "description": "List of TODOs."
+ }
},
"GetTodosResponseBody": {
+ "description": "List of TODOs.",
"type": "array",
"items": {
"type": "object",
@@ -1153,8 +1152,7 @@
},
"required": ["id", "name", "completed"],
"additionalProperties": false
- },
- "description": "List of TODOs."
+ }
},
"MessageWithErrors": {
"type": "object",
@@ -1204,18 +1202,17 @@
},
"RouteWithExternalDep200ResponseBody": { "type": "string" },
"UrlEncodedFormData200ResponseBody": {
+ "description": "Test form response.",
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
- "additionalProperties": false,
- "description": "Test form response."
+ "additionalProperties": false
},
"UrlEncodedFormDataRequestBody": {
+ "description": "Test form description.",
"type": "object",
"properties": { "text": { "type": "string" } },
- "required": ["text"],
- "additionalProperties": false,
- "description": "Test form description."
+ "required": ["text"]
}
}
}
diff --git a/package.json b/package.json
index cd9f425..9c37115 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,7 @@
"next": "15.1.6",
"prettier": "3.0.2",
"typescript": "5.2.2",
- "zod": "3.22.2"
+ "zod": "^4.1.13"
},
"prettier": {
"semi": true,
diff --git a/packages/next-rest-framework/package.json b/packages/next-rest-framework/package.json
index 1d579a8..1f1134c 100644
--- a/packages/next-rest-framework/package.json
+++ b/packages/next-rest-framework/package.json
@@ -41,8 +41,7 @@
"formidable": "^3.5.1",
"lodash": "4.17.21",
"prettier": "3.0.2",
- "qs": "6.11.2",
- "zod-to-json-schema": "3.21.4"
+ "qs": "6.11.2"
},
"devDependencies": {
"@types/formidable": "^3.4.5",
@@ -58,7 +57,7 @@
"ts-node": "10.9.1",
"tsup": "8.0.1",
"typescript": "*",
- "zod": "*",
- "zod-form-data": "*"
+ "zod": "^4.1.13",
+ "zod-form-data": "3.0.1"
}
}
diff --git a/packages/next-rest-framework/src/app-router/route-operation.ts b/packages/next-rest-framework/src/app-router/route-operation.ts
index 5c4013f..83fee48 100644
--- a/packages/next-rest-framework/src/app-router/route-operation.ts
+++ b/packages/next-rest-framework/src/app-router/route-operation.ts
@@ -17,7 +17,7 @@ import {
type ContentTypesThatSupportInputValidation
} from '../types';
import { NextResponse, type NextRequest } from 'next/server';
-import { type ZodSchema, type z } from 'zod';
+import { type ZodType, type z } from 'zod';
import { type ValidMethod } from '../constants';
import { type I18NConfig } from 'next/dist/server/config-shared';
import { type NextURL } from 'next/dist/server/web/next-url';
@@ -200,14 +200,14 @@ interface InputObject<
body?: ContentType extends ContentTypesThatSupportInputValidation
? ContentType extends FormDataContentType
? ZodFormSchema
- : ZodSchema
+ : ZodType
: never;
/*! If defined, this will override the body schema for the OpenAPI spec. */
bodySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
- query?: ZodSchema;
+ query?: ZodType;
/*! If defined, this will override the query schema for the OpenAPI spec. */
querySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
- params?: ZodSchema;
+ params?: ZodType;
/*! If defined, this will override the params schema for the OpenAPI spec. */
paramsSchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
}
diff --git a/packages/next-rest-framework/src/app-router/route.ts b/packages/next-rest-framework/src/app-router/route.ts
index 8427aa3..20b7669 100644
--- a/packages/next-rest-framework/src/app-router/route.ts
+++ b/packages/next-rest-framework/src/app-router/route.ts
@@ -65,7 +65,11 @@ export const route = >(
let middlewareOptions: BaseOptions = {};
if (middleware1) {
- const res = await middleware1(reqClone, {...context, params: await context.params}, middlewareOptions);
+ const res = await middleware1(
+ reqClone,
+ { ...context, params: await context.params },
+ middlewareOptions
+ );
const isOptionsResponse = (res: unknown): res is BaseOptions =>
typeof res === 'object';
@@ -77,7 +81,11 @@ export const route = >(
}
if (middleware2) {
- const res2 = await middleware2(reqClone, {...context, params: await context.params}, middlewareOptions);
+ const res2 = await middleware2(
+ reqClone,
+ { ...context, params: await context.params },
+ middlewareOptions
+ );
if (res2 instanceof Response) {
return res2;
@@ -88,7 +96,7 @@ export const route = >(
if (middleware3) {
const res3 = await middleware3(
reqClone,
- {...context, params: await context.params},
+ { ...context, params: await context.params },
middlewareOptions
);
@@ -165,16 +173,16 @@ export const route = >(
try {
const formData = await reqClone.formData();
- const { valid, errors, data } = validateSchema({
+ const result = validateSchema({
schema: bodySchema,
obj: formData
});
- if (!valid) {
+ if (!result.valid) {
return NextResponse.json(
{
message: DEFAULT_ERRORS.invalidRequestBody,
- errors
+ errors: result.errors
},
{
status: 400
@@ -186,14 +194,16 @@ export const route = >(
reqClone = new NextRequest(reqClone.url, {
method: reqClone.method,
headers: reqClone.headers,
- body: JSON.stringify(data)
+ body: JSON.stringify(result.data)
});
// Return parsed form data.
reqClone.formData = async () => {
const formData = new FormData();
- for (const [key, value] of Object.entries(data)) {
+ for (const [key, value] of Object.entries(
+ result.data as Record
+ )) {
formData.append(key, value as string | Blob);
}
@@ -239,7 +249,7 @@ export const route = >(
url.searchParams.delete(key);
if (data[key]) {
- url.searchParams.append(key, data[key]);
+ url.searchParams.append(key, data[key] as string);
}
});
@@ -268,13 +278,13 @@ export const route = >(
);
}
- context.params = data;
+ context.params = Promise.resolve(data);
}
}
const res = await handler?.(
reqClone as TypedNextRequest,
- {...context, params: await context.params},
+ { ...context, params: await context.params },
middlewareOptions
);
diff --git a/packages/next-rest-framework/src/app-router/rpc-route.ts b/packages/next-rest-framework/src/app-router/rpc-route.ts
index b3d40e5..5229bd5 100644
--- a/packages/next-rest-framework/src/app-router/rpc-route.ts
+++ b/packages/next-rest-framework/src/app-router/rpc-route.ts
@@ -88,13 +88,19 @@ export const rpcRoute = <
let middlewareOptions: BaseOptions = {};
if (middleware1) {
- middlewareOptions = await middleware1(body, middlewareOptions);
+ const res1 = await middleware1(body, middlewareOptions);
+ if (res1 && typeof res1 === 'object')
+ middlewareOptions = res1 as BaseOptions;
if (middleware2) {
- middlewareOptions = await middleware2(body, middlewareOptions);
+ const res2 = await middleware2(body, middlewareOptions);
+ if (res2 && typeof res2 === 'object')
+ middlewareOptions = res2 as BaseOptions;
if (middleware3) {
- middlewareOptions = await middleware3(body, middlewareOptions);
+ const res3 = await middleware3(body, middlewareOptions);
+ if (res3 && typeof res3 === 'object')
+ middlewareOptions = res3 as BaseOptions;
}
}
}
@@ -147,16 +153,16 @@ export const rpcRoute = <
)
) {
try {
- const { valid, errors, data } = validateSchema({
+ const result = validateSchema({
schema: bodySchema,
obj: body
});
- if (!valid) {
+ if (!result.valid) {
return NextResponse.json(
{
message: DEFAULT_ERRORS.invalidRequestBody,
- errors
+ errors: result.errors
},
{
status: 400
@@ -166,7 +172,9 @@ export const rpcRoute = <
const formData = new FormData();
- for (const [key, value] of Object.entries(data)) {
+ for (const [key, value] of Object.entries(
+ result.data as Record
+ )) {
formData.append(key, value as string | Blob);
}
diff --git a/packages/next-rest-framework/src/pages-router/api-route-operation.ts b/packages/next-rest-framework/src/pages-router/api-route-operation.ts
index 5c403d9..7f16a60 100644
--- a/packages/next-rest-framework/src/pages-router/api-route-operation.ts
+++ b/packages/next-rest-framework/src/pages-router/api-route-operation.ts
@@ -18,7 +18,7 @@ import {
type BaseParams
} from '../types';
import { type NextApiRequest, type NextApiResponse } from 'next/types';
-import { type ZodSchema, type z } from 'zod';
+import { type ZodType, type z } from 'zod';
export type TypedNextApiRequest<
Method = keyof typeof ValidMethod,
@@ -135,14 +135,14 @@ interface InputObject<
body?: ContentType extends ContentTypesThatSupportInputValidation
? ContentType extends FormDataContentType
? ZodFormSchema
- : ZodSchema
+ : ZodType
: never;
/*! If defined, this will override the body schema for the OpenAPI spec. */
bodySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
- query?: ZodSchema;
+ query?: ZodType;
/*! If defined, this will override the query schema for the OpenAPI spec. */
querySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
- params?: ZodSchema;
+ params?: ZodType;
/*! If defined, this will override the params schema for the OpenAPI spec. */
paramsSchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
}
diff --git a/packages/next-rest-framework/src/pages-router/api-route.ts b/packages/next-rest-framework/src/pages-router/api-route.ts
index 59d6368..4825ac7 100644
--- a/packages/next-rest-framework/src/pages-router/api-route.ts
+++ b/packages/next-rest-framework/src/pages-router/api-route.ts
@@ -176,9 +176,11 @@ export const apiRoute = >(
const formData = new FormData();
- Object.entries(data).forEach(([key, value]) => {
- formData.append(key, value as string | Blob);
- });
+ Object.entries(data as Record).forEach(
+ ([key, value]) => {
+ formData.append(key, value as string | Blob);
+ }
+ );
req.body = formData;
} catch {
diff --git a/packages/next-rest-framework/src/pages-router/rpc-api-route.ts b/packages/next-rest-framework/src/pages-router/rpc-api-route.ts
index b9e3bf0..8cc9326 100644
--- a/packages/next-rest-framework/src/pages-router/rpc-api-route.ts
+++ b/packages/next-rest-framework/src/pages-router/rpc-api-route.ts
@@ -62,13 +62,19 @@ export const rpcApiRoute = <
let middlewareOptions: BaseOptions = {};
if (middleware1) {
- middlewareOptions = await middleware1(req.body, {});
+ const res1 = await middleware1(req.body, {});
+ if (res1 && typeof res1 === 'object')
+ middlewareOptions = res1 as BaseOptions;
if (middleware2) {
- middlewareOptions = await middleware2(req.body, middlewareOptions);
+ const res2 = await middleware2(req.body, middlewareOptions);
+ if (res2 && typeof res2 === 'object')
+ middlewareOptions = res2 as BaseOptions;
if (middleware3) {
- middlewareOptions = await middleware3(req.body, middlewareOptions);
+ const res3 = await middleware3(req.body, middlewareOptions);
+ if (res3 && typeof res3 === 'object')
+ middlewareOptions = res3 as BaseOptions;
}
}
}
@@ -128,15 +134,15 @@ export const rpcApiRoute = <
}
try {
- const { valid, errors, data } = validateSchema({
+ const result = validateSchema({
schema: bodySchema,
obj: req.body
});
- if (!valid) {
+ if (!result.valid) {
res.status(400).json({
message: DEFAULT_ERRORS.invalidRequestBody,
- errors
+ errors: result.errors
});
return;
@@ -144,9 +150,11 @@ export const rpcApiRoute = <
const formData = new FormData();
- Object.entries(data).forEach(([key, value]) => {
- formData.append(key, value as string | Blob);
- });
+ Object.entries(result.data as Record).forEach(
+ ([key, value]) => {
+ formData.append(key, value as string | Blob);
+ }
+ );
req.body = formData;
} catch {
diff --git a/packages/next-rest-framework/src/shared/paths.ts b/packages/next-rest-framework/src/shared/paths.ts
index 232f03f..36e6228 100644
--- a/packages/next-rest-framework/src/shared/paths.ts
+++ b/packages/next-rest-framework/src/shared/paths.ts
@@ -12,7 +12,7 @@ import {
import { merge } from 'lodash';
import { getJsonSchema } from './schemas';
-import { type ZodObject, type ZodSchema, type ZodRawShape } from 'zod';
+import { type ZodObject, type ZodType, type ZodRawShape } from 'zod';
import { type ApiRouteOperationDefinition } from '../pages-router';
import { type RouteOperationDefinition } from '../app-router';
import { type RpcOperationDefinition } from './rpc-operation';
@@ -112,7 +112,7 @@ export const getPathsFromRoute = ({
};
const description =
- input.bodySchema?.description ?? input.body._def.description;
+ input.bodySchema?.description ?? input.body.description;
if (description) {
generatedOperationObject.requestBody.description = description;
@@ -185,7 +185,7 @@ export const getPathsFromRoute = ({
const description =
bodySchema?.description ??
- body._def.description ??
+ body.description ??
`Response for status ${status}`;
return Object.assign(obj, {
@@ -219,7 +219,7 @@ export const getPathsFromRoute = ({
pathParameters = Object.entries(schema).map(([name, schema]) => {
const _schema = (input.params as ZodObject).shape[
name
- ] as ZodSchema;
+ ] as ZodType;
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
@@ -269,7 +269,7 @@ export const getPathsFromRoute = ({
...Object.entries(schema).map(([name, schema]) => {
const _schema = (input.query as ZodObject).shape[
name
- ] as ZodSchema;
+ ] as ZodType;
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
diff --git a/packages/next-rest-framework/src/shared/rpc-operation.ts b/packages/next-rest-framework/src/shared/rpc-operation.ts
index e04d1c2..6baf4c7 100644
--- a/packages/next-rest-framework/src/shared/rpc-operation.ts
+++ b/packages/next-rest-framework/src/shared/rpc-operation.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-invalid-void-type */
-import { type z, type ZodSchema } from 'zod';
+import { type z, type ZodType } from 'zod';
import { validateSchema } from './schemas';
import { DEFAULT_ERRORS } from '../constants';
import {
@@ -21,12 +21,12 @@ interface InputObject {
contentType?: ContentType;
body?: ContentType extends FormDataContentType
? ZodFormSchema
- : ZodSchema;
+ : ZodType;
/*! If defined, this will override the body schema for the OpenAPI spec. */
bodySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
}
interface OutputObject {
- body: ZodSchema;
+ body: ZodType;
/*! If defined, this will override the body schema for the OpenAPI spec. */
bodySchema?: OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject;
contentType: BaseContentType;
@@ -49,7 +49,7 @@ type RpcOperationHandler<
> = (
params: ContentType extends FormDataContentType
? TypedFormData>>
- : z.infer>,
+ : z.infer>,
options: Options
) =>
| Promise>
@@ -75,7 +75,7 @@ export type RpcOperationDefinition<
? (
body: ContentType extends FormDataContentType
? FormData
- : z.infer>
+ : z.infer>
) => TypedResponse
: () => TypedResponse) & {
_meta: OperationDefinitionMeta;
@@ -185,7 +185,7 @@ export const rpcOperation = (openApiOperation?: OpenApiOperation) => {
const operation = async (
body: ContentType extends FormDataContentType
? FormData
- : z.infer>
+ : z.infer>
) => await callOperation(body);
operation._meta = meta;
diff --git a/packages/next-rest-framework/src/shared/schemas.ts b/packages/next-rest-framework/src/shared/schemas.ts
index c521fc8..7e091eb 100644
--- a/packages/next-rest-framework/src/shared/schemas.ts
+++ b/packages/next-rest-framework/src/shared/schemas.ts
@@ -1,39 +1,53 @@
import { type OpenAPIV3_1 } from 'openapi-types';
-import { zodToJsonSchema } from 'zod-to-json-schema';
-import { type AnyZodObject, type ZodSchema } from 'zod';
+import { z, type ZodType, type ZodTypeAny } from 'zod';
import { type zfd } from 'zod-form-data';
import chalk from 'chalk';
-const isZodSchema = (schema: unknown): schema is ZodSchema =>
+const isZodSchema = (schema: unknown): schema is ZodTypeAny =>
!!schema && typeof schema === 'object' && '_def' in schema;
-const isZodObjectSchema = (schema: unknown): schema is AnyZodObject =>
- isZodSchema(schema) && 'shape' in schema;
+const isZodObjectSchema = (schema: unknown): schema is z.ZodObject =>
+ schema instanceof z.ZodObject;
-const zodSchemaValidator = ({
+const isZodArraySchema = (
+ schema: ZodTypeAny
+): schema is z.ZodArray => schema instanceof z.ZodArray;
+
+const zodSchemaValidator = ({
schema,
obj
}: {
- schema: ZodSchema;
+ schema: ZodType;
obj: unknown;
-}) => {
- const data = schema.safeParse(obj);
- const errors = !data.success ? data.error.issues : null;
+}):
+ | { valid: true; errors: null; data: T }
+ | { valid: false; errors: z.ZodIssue[]; data: null } => {
+ const result = schema.safeParse(obj);
+
+ if (result.success) {
+ return {
+ valid: true,
+ errors: null,
+ data: result.data
+ };
+ }
return {
- valid: data.success,
- errors,
- data: data.success ? data.data : null
+ valid: false,
+ errors: result.error.issues,
+ data: null
};
};
-export const validateSchema = ({
+export const validateSchema = ({
schema,
obj
}: {
- schema: ZodSchema | typeof zfd.formData;
+ schema: ZodType | typeof zfd.formData;
obj: unknown;
-}) => {
+}):
+ | { valid: true; errors: null; data: T }
+ | { valid: false; errors: z.ZodIssue[]; data: null } => {
if (isZodSchema(schema)) {
return zodSchemaValidator({ schema, obj });
}
@@ -43,21 +57,58 @@ export const validateSchema = ({
type SchemaType = 'input-params' | 'input-query' | 'input-body' | 'output-body';
+// Helper function to register descriptions in a local Zod registry
+const registerDescriptions = (
+ schema: ZodTypeAny,
+ registry: z.core.$ZodRegistry<{ description?: string }>
+): void => {
+ // Register description for the schema itself
+ if (schema.description) {
+ registry.add(schema, { description: schema.description });
+ }
+
+ // For object schemas, register descriptions for each property
+ if (isZodObjectSchema(schema)) {
+ Object.values(schema.shape as Record).forEach(
+ (propSchema) => {
+ registerDescriptions(propSchema, registry);
+ }
+ );
+ }
+
+ // For array schemas, register description for the element type
+ if (isZodArraySchema(schema)) {
+ registerDescriptions(schema.element, registry);
+ }
+};
+
export const getJsonSchema = ({
schema,
operationId,
type
}: {
- schema: ZodSchema;
+ schema: ZodType;
operationId: string;
type: SchemaType;
}): OpenAPIV3_1.SchemaObject => {
if (isZodSchema(schema)) {
try {
- return zodToJsonSchema(schema, {
- $refStrategy: 'none',
- target: 'openApi3'
- });
+ // Create a local registry to avoid global registry conflicts (see: https://github.com/colinhacks/zod/issues/4145)
+ const localRegistry = z.core.registry<{ description?: string }>();
+
+ // Register descriptions in the local registry so toJSONSchema can access them
+ registerDescriptions(schema, localRegistry);
+
+ // For input schemas, use 'input' to get what the API accepts (before transformations)
+ // For output schemas, use 'output' to get what the API returns (after transformations)
+ const io = type === 'output-body' ? 'output' : 'input';
+
+ return z.toJSONSchema(schema, {
+ target: 'openapi-3.0',
+ unrepresentable: 'any', // Allow unrepresentable types (date, bigint, etc.) to be converted to {}
+ io,
+ metadata: localRegistry
+ }) as OpenAPIV3_1.SchemaObject;
} catch (error) {
const solutions: Record = {
'input-params': 'paramsSchema',
@@ -70,8 +121,11 @@ export const getJsonSchema = ({
chalk.yellowBright(
`
Warning: ${type} schema for operation ${operationId} could not be converted to a JSON schema. The OpenAPI spec may not be accurate.
-This is most likely related to an issue with the \`zod-to-json-schema\`: https://github.com/StefanTerdell/zod-to-json-schema?tab=readme-ov-file#known-issues
-Please consider using the ${solutions[type]} property in addition to the Zod schema.`
+Error: ${error instanceof Error ? error.message : String(error)}
+This is most likely related to Zod v4's toJSONSchema() limitations or an issue with the schema structure.
+Please consider using the ${
+ solutions[type]
+ } property in addition to the Zod schema.`
)
);
@@ -82,9 +136,9 @@ Please consider using the ${solutions[type]} property in addition to the Zod sch
throw Error('Invalid schema.');
};
-export const getSchemaKeys = ({ schema }: { schema: ZodSchema }) => {
+export const getSchemaKeys = ({ schema }: { schema: ZodType }) => {
if (isZodObjectSchema(schema)) {
- return Object.keys(schema._def.shape());
+ return Object.keys(schema.shape);
}
throw Error('Invalid schema.');
diff --git a/packages/next-rest-framework/src/types.ts b/packages/next-rest-framework/src/types.ts
index f4801ad..f443cdd 100644
--- a/packages/next-rest-framework/src/types.ts
+++ b/packages/next-rest-framework/src/types.ts
@@ -1,5 +1,5 @@
import { type OpenAPIV3_1 } from 'openapi-types';
-import { type ZodEffects, type z, type ZodSchema } from 'zod';
+import { type ZodType, type ZodTransform, type ZodPipe } from 'zod';
export type DocsProvider = 'redoc' | 'swagger-ui';
@@ -86,7 +86,7 @@ export interface OutputObject<
ContentType extends
AnyContentTypeWithAutocompleteForMostCommonOnes = AnyContentTypeWithAutocompleteForMostCommonOnes
> {
- body: ZodSchema;
+ body: ZodType;
bodySchema?:
| OpenAPIV3_1.SchemaObject
| OpenAPIV3_1.ReferenceObject /*! If defined, this will override the body schema for the OpenAPI spec. */;
@@ -203,8 +203,6 @@ interface FormDataLikeInput {
entries: () => IterableIterator<[string, FormDataEntryValue]>;
}
-export type ZodFormSchema = ZodEffects<
- ZodSchema,
- z.output>,
- FormData | FormDataLikeInput
->;
+export type ZodFormSchema =
+ | ZodTransform
+ | ZodPipe>;
diff --git a/packages/next-rest-framework/tests/app-router/route.test.ts b/packages/next-rest-framework/tests/app-router/route.test.ts
index bd91270..003b002 100644
--- a/packages/next-rest-framework/tests/app-router/route.test.ts
+++ b/packages/next-rest-framework/tests/app-router/route.test.ts
@@ -3,6 +3,7 @@ import { TypedNextResponse, route, routeOperation } from '../../src/app-router';
import { DEFAULT_ERRORS, ValidMethod } from '../../src/constants';
import { createMockRouteRequest } from '../utils';
import { validateSchema } from '../../src/shared';
+import { getPathsFromRoute } from '../../src/shared/paths';
import { zfd } from 'zod-form-data';
describe('route', () => {
@@ -675,4 +676,55 @@ describe('route', () => {
expect(console.log).toHaveBeenCalledWith('baz');
expect(console.log).toHaveBeenCalledWith('handler');
});
+
+ it('preserves schema descriptions in OpenAPI spec', async () => {
+ const { req, context } = createMockRouteRequest({
+ method: ValidMethod.GET
+ });
+
+ const schema = z.object({
+ foo: z.string().describe('A test field'),
+ items: z
+ .array(
+ z.object({
+ id: z.string().describe('Item ID'),
+ name: z.string().describe('Item name')
+ })
+ )
+ .describe('List of items')
+ });
+
+ const operations = {
+ test: routeOperation({ method: 'GET' })
+ .outputs([
+ {
+ status: 200,
+ contentType: 'application/json',
+ body: schema
+ }
+ ])
+ .handler(() =>
+ TypedNextResponse.json({
+ foo: 'bar',
+ items: [{ id: '1', name: 'x' }]
+ })
+ )
+ } as const;
+
+ await route(operations).GET(req, context);
+
+ const { schemas } = getPathsFromRoute({
+ operations,
+ route: '/api/test'
+ });
+
+ const responseSchema = schemas?.Test200ResponseBody as any;
+ expect(responseSchema?.properties?.foo?.description).toBe('A test field');
+ expect(responseSchema?.properties?.items?.description).toBe(
+ 'List of items'
+ );
+ const itemsSchema = responseSchema?.properties?.items;
+ expect(itemsSchema?.items?.properties?.id?.description).toBe('Item ID');
+ expect(itemsSchema?.items?.properties?.name?.description).toBe('Item name');
+ });
});
diff --git a/packages/next-rest-framework/tests/app-router/rpc-route.test.ts b/packages/next-rest-framework/tests/app-router/rpc-route.test.ts
index fe488da..6043090 100644
--- a/packages/next-rest-framework/tests/app-router/rpc-route.test.ts
+++ b/packages/next-rest-framework/tests/app-router/rpc-route.test.ts
@@ -441,4 +441,26 @@ describe('rpcRoute', () => {
expect(console.log).toHaveBeenCalledWith('baz');
expect(console.log).toHaveBeenCalledWith('handler');
});
+
+ it('ignores non-object middleware return values when building options', async () => {
+ const { req, context } = createMockRpcRouteRequest({
+ body: { foo: 'bar' },
+ headers: { 'content-type': 'application/json' }
+ });
+
+ const res = await rpcRoute({
+ test: rpcOperation()
+ .input({
+ contentType: 'application/json',
+ body: z.object({ foo: z.string() })
+ })
+ .middleware(() => 'noop' as any)
+ .middleware(() => undefined)
+ .middleware(() => 42 as any)
+ .handler((_input, options) => options)
+ }).POST(req, context);
+
+ const json = await res?.json();
+ expect(json).toEqual({});
+ });
});
diff --git a/packages/next-rest-framework/tests/pages-router/api-route.test.ts b/packages/next-rest-framework/tests/pages-router/api-route.test.ts
index 6a1a53d..de62701 100644
--- a/packages/next-rest-framework/tests/pages-router/api-route.test.ts
+++ b/packages/next-rest-framework/tests/pages-router/api-route.test.ts
@@ -4,6 +4,7 @@ import { validateSchema } from '../../src/shared';
import { createMockApiRouteRequest } from '../utils';
import { apiRoute, apiRouteOperation } from '../../src/pages-router';
import { zfd } from 'zod-form-data';
+import { getPathsFromRoute } from '../../src/shared/paths';
describe('apiRoute', () => {
it.each(Object.values(ValidMethod))(
@@ -655,4 +656,52 @@ describe('apiRoute', () => {
expect(console.log).toHaveBeenCalledWith('baz');
expect(console.log).toHaveBeenCalledWith('handler');
});
+
+ it('preserves schema descriptions in OpenAPI spec', async () => {
+ const { req, res } = createMockApiRouteRequest({
+ method: ValidMethod.GET
+ });
+
+ const schema = z.object({
+ foo: z.string().describe('A test field'),
+ items: z
+ .array(
+ z.object({
+ id: z.string().describe('Item ID'),
+ name: z.string().describe('Item name')
+ })
+ )
+ .describe('List of items')
+ });
+
+ const operations = {
+ test: apiRouteOperation({ method: 'GET' })
+ .outputs([
+ {
+ status: 200,
+ contentType: 'application/json',
+ body: schema
+ }
+ ])
+ .handler((_req, res) => {
+ res.json({ foo: 'bar', items: [{ id: '1', name: 'Test' }] });
+ })
+ };
+
+ await apiRoute(operations)(req, res);
+
+ const { schemas } = getPathsFromRoute({
+ operations,
+ route: '/api/test'
+ });
+
+ const responseSchema = schemas?.Test200ResponseBody;
+ expect(responseSchema?.properties?.foo?.description).toBe('A test field');
+ expect(responseSchema?.properties?.items?.description).toBe(
+ 'List of items'
+ );
+ const itemsSchema = responseSchema?.properties?.items as any;
+ expect(itemsSchema?.items?.properties?.id?.description).toBe('Item ID');
+ expect(itemsSchema?.items?.properties?.name?.description).toBe('Item name');
+ });
});
diff --git a/packages/next-rest-framework/tests/pages-router/rpc-api-route.test.ts b/packages/next-rest-framework/tests/pages-router/rpc-api-route.test.ts
index ff427ec..0386ff5 100644
--- a/packages/next-rest-framework/tests/pages-router/rpc-api-route.test.ts
+++ b/packages/next-rest-framework/tests/pages-router/rpc-api-route.test.ts
@@ -442,4 +442,27 @@ describe('rpcApiRoute', () => {
expect(console.log).toHaveBeenCalledWith('baz');
expect(console.log).toHaveBeenCalledWith('handler');
});
+
+ it('ignores non-object middleware return values when building options', async () => {
+ const { req, res } = createMockRpcApiRouteRequest({
+ body: { foo: 'bar' },
+ headers: {
+ 'content-type': 'application/json'
+ }
+ });
+
+ await rpcApiRoute({
+ test: rpcOperation()
+ .input({
+ contentType: 'application/json',
+ body: z.object({ foo: z.string() })
+ })
+ .middleware(() => 'noop' as any)
+ .middleware(() => undefined)
+ .middleware(() => null as any)
+ .handler((_input, options) => options)
+ })(req, res);
+
+ expect(res._getJSONData()).toEqual({});
+ });
});
diff --git a/packages/next-rest-framework/tests/shared/schemas.test.ts b/packages/next-rest-framework/tests/shared/schemas.test.ts
new file mode 100644
index 0000000..7002360
--- /dev/null
+++ b/packages/next-rest-framework/tests/shared/schemas.test.ts
@@ -0,0 +1,83 @@
+import { z } from 'zod';
+import { getJsonSchema, validateSchema } from '../../src/shared';
+
+describe('shared/schemas', () => {
+ describe('validateSchema', () => {
+ it('returns parsed data for valid objects', () => {
+ const schema = z.object({
+ foo: z.string(),
+ count: z.number().int()
+ });
+
+ const result = validateSchema({
+ schema,
+ obj: { foo: 'bar', count: 3 }
+ });
+
+ expect(result).toEqual({
+ valid: true,
+ errors: null,
+ data: { foo: 'bar', count: 3 }
+ });
+ });
+
+ it('collects issues for invalid data', () => {
+ const schema = z.object({
+ foo: z.string(),
+ count: z.number()
+ });
+
+ const result = validateSchema({
+ schema,
+ obj: { foo: 123 }
+ });
+
+ expect(result.valid).toBe(false);
+ expect(result.data).toBeNull();
+ expect(result.errors).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ path: ['foo'] }),
+ expect.objectContaining({ path: ['count'] })
+ ])
+ );
+ });
+ });
+
+ describe('getJsonSchema', () => {
+ it('preserves nested descriptions for objects and arrays', () => {
+ const schema = z.object({
+ foo: z.string().describe('Foo description'),
+ items: z
+ .array(
+ z
+ .object({
+ id: z.string().describe('Item id'),
+ tags: z
+ .array(z.string().describe('Tag value'))
+ .describe('Tag list')
+ })
+ .describe('Item object')
+ )
+ .describe('Items list')
+ });
+
+ const jsonSchema = getJsonSchema({
+ schema,
+ operationId: 'TestOperation',
+ type: 'output-body'
+ });
+
+ const properties = jsonSchema.properties as any;
+ expect(properties.foo.description).toBe('Foo description');
+
+ const itemsSchema = properties.items;
+ expect(itemsSchema.description).toBe('Items list');
+ expect(itemsSchema.items.description).toBe('Item object');
+ expect(itemsSchema.items.properties.id.description).toBe('Item id');
+ expect(itemsSchema.items.properties.tags.description).toBe('Tag list');
+ expect(itemsSchema.items.properties.tags.items.description).toBe(
+ 'Tag value'
+ );
+ });
+ });
+});
diff --git a/packages/next-rest-framework/tests/utils.ts b/packages/next-rest-framework/tests/utils.ts
index 50eafa4..39f5b28 100644
--- a/packages/next-rest-framework/tests/utils.ts
+++ b/packages/next-rest-framework/tests/utils.ts
@@ -1,4 +1,4 @@
-import { type ZodSchema } from 'zod';
+import { type ZodTypeAny } from 'zod';
import { NextRequest } from 'next/server';
import {
DEFAULT_DESCRIPTION,
@@ -38,7 +38,7 @@ export const createMockRouteRequest = ({
headers?: Record;
}): {
req: NextRequest;
- context: { params: typeof params };
+ context: { params: Promise };
} => ({
req: new NextRequest(`http://localhost:3000${path}?${qs.stringify(query)}`, {
method,
@@ -52,7 +52,7 @@ export const createMockRouteRequest = ({
...headers
}
}),
- context: { params }
+ context: { params: Promise.resolve(params) }
});
export const createMockRpcRouteRequest = ({
@@ -69,7 +69,7 @@ export const createMockRpcRouteRequest = ({
headers?: Record;
} = {}): {
req: NextRequest;
- context: { params: { operationId: typeof operation } };
+ context: { params: Promise<{ operationId: typeof operation }> };
} => {
const { req } = createMockRouteRequest({
path,
@@ -80,7 +80,10 @@ export const createMockRpcRouteRequest = ({
}
});
- return { req, context: { params: { operationId: operation } } };
+ return {
+ req,
+ context: { params: Promise.resolve({ operationId: operation }) }
+ };
};
export const createMockApiRouteRequest = <
@@ -148,7 +151,7 @@ export const getExpectedSpec = ({
allowedPaths,
deniedPaths
}: {
- zodSchema: ZodSchema;
+ zodSchema: ZodTypeAny;
allowedPaths: string[];
deniedPaths: string[];
}) => {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b5d902b..389d943 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -58,8 +58,8 @@ importers:
specifier: 5.2.2
version: 5.2.2
zod:
- specifier: 3.22.2
- version: 3.22.2
+ specifier: ^4.1.13
+ version: 4.1.13
apps/example:
dependencies:
@@ -73,8 +73,8 @@ importers:
specifier: 4.7.2
version: 4.7.2
zod-form-data:
- specifier: 2.0.2
- version: 2.0.2(zod@3.22.2)
+ specifier: 3.0.1
+ version: 3.0.1(zod@4.1.13)
devDependencies:
'@types/jsdom':
specifier: ^21.1.6
@@ -143,9 +143,6 @@ importers:
qs:
specifier: 6.11.2
version: 6.11.2
- zod-to-json-schema:
- specifier: 3.21.4
- version: 3.21.4(zod@3.22.2)
devDependencies:
'@types/formidable':
specifier: ^3.4.5
@@ -187,11 +184,11 @@ importers:
specifier: '*'
version: 5.2.2
zod:
- specifier: '*'
- version: 3.22.2
+ specifier: ^4.1.13
+ version: 4.1.13
zod-form-data:
- specifier: '*'
- version: 2.0.2(zod@3.22.2)
+ specifier: 3.0.1
+ version: 3.0.1(zod@4.1.13)
packages:
@@ -442,6 +439,7 @@ packages:
'@babel/plugin-proposal-object-rest-spread@7.12.1':
resolution: {integrity: sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==}
+ deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1295,6 +1293,7 @@ packages:
'@humanwhocodes/config-array@0.11.10':
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'}
+ deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -1302,6 +1301,7 @@ packages:
'@humanwhocodes/object-schema@1.2.1':
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
+ deprecated: Use @eslint/object-schema instead
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
@@ -1725,6 +1725,9 @@ packages:
'@rushstack/eslint-patch@1.6.1':
resolution: {integrity: sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==}
+ '@rvf/set-get@7.0.1':
+ resolution: {integrity: sha512-GkTSn9K1GrTYoTUqlUs36k6nJnzjQaFBTTEIqUYmzBcsGsoJM8xG7EAx2WLHWAA4QzFjcwWUSHQ3vM3Fbw50Tg==}
+
'@sideway/address@4.1.4':
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
@@ -2156,6 +2159,7 @@ packages:
acorn-import-assertions@1.9.0:
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
+ deprecated: package has been renamed to acorn-import-attributes
peerDependencies:
acorn: ^8
@@ -3226,6 +3230,7 @@ packages:
eslint-config-standard-with-typescript@38.0.0:
resolution: {integrity: sha512-G7JR6I8tmWEQjzbESo/9gVq4AQctbVO4J8PINQj8l2lgbJF/W9KCJ4uDLiKmLMjWszW/F5SsucoA/5VpHvfwOQ==}
+ deprecated: Please use eslint-config-love, instead.
peerDependencies:
'@typescript-eslint/eslint-plugin': ^6.1.0
eslint: ^8.0.1
@@ -3335,6 +3340,7 @@ packages:
eslint@8.47.0:
resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
espree@9.6.1:
@@ -3639,9 +3645,11 @@ packages:
glob@7.1.7:
resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==}
+ deprecated: Glob versions prior to v9 are no longer supported
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
global-dirs@3.0.1:
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
@@ -3901,6 +3909,7 @@ packages:
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.3:
resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
@@ -5589,6 +5598,7 @@ packages:
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rollup@4.9.4:
@@ -6096,6 +6106,7 @@ packages:
trim@0.0.1:
resolution: {integrity: sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==}
+ deprecated: Use String.prototype.trim() instead
trough@1.0.5:
resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}
@@ -6657,18 +6668,13 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- zod-form-data@2.0.2:
- resolution: {integrity: sha512-sKTi+k0fvkxdakD0V5rq+9WVJA3cuTQUfEmNqvHrTzPLvjfLmkkBLfR0ed3qOi9MScJXTHIDH/jUNnEJ3CBX4g==}
- peerDependencies:
- zod: '>= 3.11.0'
-
- zod-to-json-schema@3.21.4:
- resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==}
+ zod-form-data@3.0.1:
+ resolution: {integrity: sha512-uwSrDzpLDoXeAxePjPHrjjMelE5pk5zL5JcwLFISvqidGjtPl7hcheH584xGcS76c9IRHq6tqdGkf+A4eKO6Cw==}
peerDependencies:
- zod: ^3.21.4
+ zod: '>= 3.25.0'
- zod@3.22.2:
- resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
+ zod@4.1.13:
+ resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
zwitch@1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
@@ -8823,6 +8829,8 @@ snapshots:
'@rushstack/eslint-patch@1.6.1': {}
+ '@rvf/set-get@7.0.1': {}
+
'@sideway/address@4.1.4':
dependencies:
'@hapi/hoek': 9.3.0
@@ -10623,7 +10631,7 @@ snapshots:
debug: 4.3.4
enhanced-resolve: 5.15.0
eslint: 8.47.0
- eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.47.0))(eslint@8.47.0)
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.47.0)
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.47.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.2
@@ -10635,7 +10643,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.47.0))(eslint@8.47.0):
+ eslint-module-utils@2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.47.0):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -10662,7 +10670,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.47.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1)(eslint@8.47.0))(eslint@8.47.0)
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.1(eslint@8.47.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.47.0)
has: 1.0.3
is-core-module: 2.13.0
is-glob: 4.0.3
@@ -14616,14 +14624,11 @@ snapshots:
yocto-queue@0.1.0: {}
- zod-form-data@2.0.2(zod@3.22.2):
- dependencies:
- zod: 3.22.2
-
- zod-to-json-schema@3.21.4(zod@3.22.2):
+ zod-form-data@3.0.1(zod@4.1.13):
dependencies:
- zod: 3.22.2
+ '@rvf/set-get': 7.0.1
+ zod: 4.1.13
- zod@3.22.2: {}
+ zod@4.1.13: {}
zwitch@1.0.5: {}