refactor: ⬆️ Upgrade to Zod v4 and hono-openapi 0.5.0

This commit is contained in:
Jesse Wierzbinski 2025-07-07 03:42:35 +02:00
parent add2429606
commit 24d4150da4
No known key found for this signature in database
209 changed files with 1331 additions and 1622 deletions

View file

@ -1,8 +1,8 @@
import type { ContentfulStatusCode } from "hono/utils/http-status";
import type { JSONObject } from "hono/utils/types";
import type { DescribeRouteOptions } from "hono-openapi";
import { resolver } from "hono-openapi/zod";
import { z } from "zod";
import { resolver } from "hono-openapi";
import { z } from "zod/v4";
/**
* API Error

View file

@ -1,4 +1,4 @@
import type { Hook } from "@hono/zod-validator";
import type { Hook } from "@hono/standard-validator";
import type { RolePermission } from "@versia/client/schemas";
import { config } from "@versia-server/config";
import { serverLogger } from "@versia-server/logging";
@ -8,9 +8,9 @@ import { eq, type SQL } from "drizzle-orm";
import type { Context, Hono, MiddlewareHandler, ValidationTargets } from "hono";
import { every } from "hono/combine";
import { createMiddleware } from "hono/factory";
import { validator } from "hono-openapi/zod";
import { validator } from "hono-openapi";
import { type ParsedQs, parse } from "qs";
import { type ZodAny, type ZodError, z } from "zod";
import { type ZodAny, ZodError, z } from "zod/v4";
import { fromZodError } from "zod-validation-error";
import type { AuthData, HonoEnv } from "~/types/api";
import { ApiError } from "./api-error.ts";
@ -31,9 +31,11 @@ export const handleZodError: Hook<
keyof ValidationTargets
> = (result, context): Response | undefined => {
if (!result.success) {
const issues = result.error as z.core.$ZodIssue[];
return context.json(
{
error: fromZodError(result.error as ZodError).message,
error: fromZodError(new ZodError(issues)).message,
},
422,
);
@ -204,7 +206,7 @@ type WithIdParam = {
* @returns MiddlewareHandler
*/
export const withNoteParam = every(
validator("param", z.object({ id: z.string().uuid() }), handleZodError),
validator("param", z.object({ id: z.uuid() }), handleZodError),
createMiddleware<
HonoEnv & {
Variables: {
@ -242,7 +244,7 @@ export const withNoteParam = every(
* @returns MiddlewareHandler
*/
export const withUserParam = every(
validator("param", z.object({ id: z.string().uuid() }), handleZodError),
validator("param", z.object({ id: z.uuid() }), handleZodError),
createMiddleware<
HonoEnv & {
Variables: {
@ -278,7 +280,7 @@ export const withUserParam = every(
* @returns
*/
export const withEmojiParam = every(
validator("param", z.object({ id: z.string().uuid() }), handleZodError),
validator("param", z.object({ id: z.uuid() }), handleZodError),
createMiddleware<
HonoEnv & {
Variables: {

View file

@ -10,7 +10,7 @@ import {
inArray,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Applications } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";

View file

@ -16,7 +16,7 @@ import {
isNull,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Emojis, type Instances, type Medias } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";
@ -194,7 +194,7 @@ export class Emoji extends BaseInterface<typeof Emojis, EmojiType> {
global: this.data.ownerId === null,
description:
this.media.data.content[this.media.getPreferredMimeType()]
.description ?? null,
?.description ?? null,
};
}

View file

@ -16,7 +16,7 @@ import {
type SQL,
} from "drizzle-orm";
import sharp from "sharp";
import type { z } from "zod";
import type { z } from "zod/v4";
import { mimeLookup } from "@/content_types.ts";
import { getMediaHash } from "../../../classes/media/media-hasher.ts";
import { ApiError } from "../api-error.ts";
@ -297,7 +297,7 @@ export class Media extends BaseInterface<typeof Medias> {
const content = await Media.fileToContentFormat(file, url, {
description:
this.data.content[Object.keys(this.data.content)[0]]
.description || undefined,
?.description || undefined,
});
await this.update({
@ -319,7 +319,7 @@ export class Media extends BaseInterface<typeof Medias> {
remote: true,
description:
this.data.content[Object.keys(this.data.content)[0]]
.description || undefined,
?.description || undefined,
},
};
@ -363,7 +363,7 @@ export class Media extends BaseInterface<typeof Medias> {
content[type] = {
...content[type],
...metadata,
};
} as (typeof content)[keyof typeof content];
}
await this.update({
@ -490,6 +490,14 @@ export class Media extends BaseInterface<typeof Medias> {
public toApiMeta(): z.infer<typeof AttachmentSchema.shape.meta> {
const type = this.getPreferredMimeType();
const data = this.data.content[type];
if (!data) {
throw new ApiError(
500,
`No content for type ${type} in attachment ${this.id}`,
);
}
const size =
data.width && data.height
? `${data.width}x${data.height}`
@ -533,7 +541,7 @@ export class Media extends BaseInterface<typeof Medias> {
? new ProxiableUrl(thumbnailData.content).proxied
: null,
meta: this.toApiMeta(),
description: data.description || null,
description: data?.description || null,
blurhash: this.data.blurhash,
};
}

View file

@ -20,7 +20,7 @@ import {
} from "drizzle-orm";
import { htmlToText } from "html-to-text";
import { createRegExp, exactly, global } from "magic-regexp";
import type { z } from "zod";
import type { z } from "zod/v4";
import { mergeAndDeduplicate } from "@/lib.ts";
import { sanitizedHtmlStrip } from "@/sanitization";
import { versiaTextToHtml } from "../parsers.ts";

View file

@ -7,7 +7,7 @@ import {
inArray,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Notifications } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";
@ -189,6 +189,7 @@ export class Notification extends BaseInterface<
created_at: new Date(this.data.createdAt).toISOString(),
id: this.data.id,
type: this.data.type,
event: undefined,
status: this.data.status
? await new Note(this.data.status).toApi(account)
: undefined,

View file

@ -7,7 +7,7 @@ import {
inArray,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { PushSubscriptions, Tokens } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";

View file

@ -10,7 +10,7 @@ import {
type SQL,
sql,
} from "drizzle-orm";
import { z } from "zod";
import { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Relationships, Users } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";

View file

@ -12,7 +12,7 @@ import {
inArray,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Roles, RoleToUsers } from "../tables/schema.ts";
import { BaseInterface } from "./base.ts";

View file

@ -7,7 +7,7 @@ import {
inArray,
type SQL,
} from "drizzle-orm";
import type { z } from "zod";
import type { z } from "zod/v4";
import { db } from "../tables/db.ts";
import { Tokens } from "../tables/schema.ts";
import type { Application } from "./application.ts";

View file

@ -30,7 +30,7 @@ import {
sql,
} from "drizzle-orm";
import { htmlToText } from "html-to-text";
import type { z } from "zod";
import type { z } from "zod/v4";
import { getBestContentType } from "@/content_types";
import { randomString } from "@/math";
import type { HttpVerb, KnownEntity } from "~/types/api.ts";

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
import { Hooks } from "./hooks.ts";
import { Plugin } from "./plugin.ts";

View file

@ -245,7 +245,8 @@ export class InboxProcessor {
// If note has a blocked word
if (
Object.values(note.content?.data ?? {})
.flatMap((c) => c.content)
.flatMap((c) => c?.content)
.filter((content) => content !== undefined)
.some((content) =>
config.validation.filters.note_content.some((filter) =>
filter.test(content),
@ -281,7 +282,8 @@ export class InboxProcessor {
if (
Object.values(user.bio?.data ?? {})
.flatMap((c) => c.content)
.flatMap((c) => c?.content)
.filter((content) => content !== undefined)
.some((content) =>
config.validation.filters.bio.some((filter) =>
filter.test(content),

View file

@ -1,6 +1,6 @@
import { zodToJsonSchema } from "zod-to-json-schema";
import * as z from "zod/v4";
import { manifestSchema } from "./schema.ts";
const jsonSchema = zodToJsonSchema(manifestSchema);
const jsonSchema = z.toJSONSchema(manifestSchema);
console.write(`${JSON.stringify(jsonSchema, null, 4)}\n`);

View file

@ -39,7 +39,6 @@
"hono": "catalog:",
"mitt": "catalog:",
"zod": "catalog:",
"zod-to-json-schema": "catalog:",
"zod-validation-error": "catalog:",
"chalk": "catalog:",
"@versia/client": "workspace:*",
@ -52,7 +51,7 @@
"altcha-lib": "catalog:",
"hono-openapi": "catalog:",
"qs": "catalog:",
"@hono/zod-validator": "catalog:",
"@hono/standard-validator": "catalog:",
"ioredis": "catalog:",
"linkify-html": "catalog:",
"markdown-it": "catalog:",

View file

@ -1,6 +1,6 @@
import type { Hono, MiddlewareHandler } from "hono";
import { createMiddleware } from "hono/factory";
import type { z } from "zod";
import type { z } from "zod/v4";
import { fromZodError, type ZodError } from "zod-validation-error";
import type { HonoEnv } from "~/types/api";
import type { ServerHooks } from "./hooks.ts";

View file

@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from "zod/v4";
export const manifestSchema = z.object({
// biome-ignore lint/style/useNamingConvention: JSON schema requires this to be $schema
@ -15,8 +15,8 @@ export const manifestSchema = z.object({
.array(
z.object({
name: z.string().min(1).max(100),
email: z.string().email().optional(),
url: z.string().url().optional(),
email: z.email().optional(),
url: z.url().optional(),
}),
)
.optional(),
@ -47,7 +47,7 @@ export const manifestSchema = z.object({
"other",
])
.optional(),
url: z.string().url().optional(),
url: z.url().optional(),
})
.optional(),
});

View file

@ -25,7 +25,7 @@ import {
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core";
import type { z } from "zod";
import type { z } from "zod/v4";
const createdAt = () =>
timestamp("created_at", { precision: 3, mode: "string" })