feat(deps): bump zod 3→4 + @hookform/resolvers 3→5
Resolved 65 type errors across the codebase via these v4 migration
patterns:
- `ZodError.errors` renamed to `ZodError.issues` (4 call sites in auth
routes + central error handler).
- `z.record(value)` now requires explicit key type: `z.record(z.string(),
value)`. Updated 7 sites across templates / forms / saved-views /
website-inquiries.
- `.refine(check, msgFn)` second-arg shape changed — now requires an
`{ error: (issue) => ... }` object form. Updated
`mergeFieldsSchema` in document-templates validator.
- `.transform(...).default(...)` chains: v4 enforces default value type
matches transform OUTPUT. Reordered to `.default(...).transform(...)`
in list-query / company-memberships handlers.
- `z.coerce.*()` INPUT type widened to `unknown` in v4. Service signatures
using `z.input<typeof schema>` (kept for caller flexibility around
defaults) now re-parse via `schema.parse(data)` to recover the
post-coercion shape Drizzle needs. Done in berth-reservations service.
Invoice service narrows `lineItems` locally with a typed cast since
re-parsing would double-validate.
- `.optional().transform(...)` no longer propagates the optional marker
through v4's new ZodPipe. Moved `.optional()` to the END of chain in
`optionalDesiredDimSchema` (interests) and documents list query
(folderId, signatureOnly).
- ZodIssue subtype shapes simplified: `received` removed from
invalid_type, `type` renamed to `origin` on too_small. Test fixtures
updated.
- @hookform/resolvers v5 splits Resolver into 3-generic form (Input,
Context, Output). useForm calls in 6 forms (client, yacht, berth,
interest, expense, invoices-new-page) now pass explicit generics:
`useForm<z.input<typeof schema>, unknown, z.infer<typeof schema>>`.
Verified: tsc clean (0 errors), vitest 1293/1293 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
const parsed = bodySchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
||||
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||
}
|
||||
|
||||
const result = await consumeCrmInvite({
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
const parsed = bodySchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
||||
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||
}
|
||||
|
||||
await activateAccount(parsed.data.token, parsed.data.password);
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
const parsed = bodySchema.safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new ValidationError(parsed.error.errors[0]?.message ?? 'Invalid input');
|
||||
throw new ValidationError(parsed.error.issues[0]?.message ?? 'Invalid input');
|
||||
}
|
||||
|
||||
await resetPassword(parsed.data.token, parsed.data.password);
|
||||
|
||||
@@ -38,7 +38,7 @@ import { checkRateLimit, rateLimiters } from '@/lib/rate-limit';
|
||||
const SubmissionSchema = z.object({
|
||||
submission_id: z.string().uuid(),
|
||||
kind: z.enum(['berth_inquiry', 'residence_inquiry', 'contact_form']),
|
||||
payload: z.record(z.unknown()),
|
||||
payload: z.record(z.string(), z.unknown()),
|
||||
legacy_nocodb_id: z.string().optional(),
|
||||
/** Defaults to port-nimara since that's currently the only port with a
|
||||
* public marketing site. Future ports can override per-submission. */
|
||||
|
||||
@@ -10,8 +10,8 @@ import { addMembershipSchema } from '@/lib/validators/company-memberships';
|
||||
const listQuerySchema = z.object({
|
||||
activeOnly: z
|
||||
.enum(['true', 'false'])
|
||||
.transform((v) => v === 'true')
|
||||
.default('true'),
|
||||
.default('true')
|
||||
.transform((v) => v === 'true'),
|
||||
});
|
||||
|
||||
export const listHandler: RouteHandler = async (req, ctx, params) => {
|
||||
|
||||
Reference in New Issue
Block a user