From 5c99622fa86b7b0721384f089862a8405a27a61e Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 15 Aug 2025 20:15:58 +0200 Subject: [PATCH] fix(deps): upgrade zod to v4 and fix breaking changes (#3461) * fix(deps): update dependency drizzle-zod to ^0.8.2 * chore: update zod to v4 import * fix: path is no longer available in transform context * fix: AnyZodObject does no longer exist * fix: auth env.ts using wrong createEnv and remove unused file env-validation.ts * fix: required_error no longer exists on z.string * fix: zod error map is deprecated and replaced with config * fix: default requires callback now * fix: migrate zod resolver for mantine * fix: remove unused form translation file * fix: wrong enum type * fix: record now requires two arguments * fix: add-confirm-password-refinement type issues * fix: add missing first record argument for entityStateSchema * fix: migrate superrefine to check * fix(deps): upgrade zod-form-data to v3 * fix: migrate superRefine to check for mediaUploadSchema * fix: authProvidersSchema default is array * fix: use stringbool instead of custom implementation * fix: record requires first argument * fix: migrate superRefine to check for certificate router * fix: confirm pasword refinement is overwriting types * fix: email optional not working * fix: migrate intersection to object converter * fix: safe parse return value rename * fix: easier access for min and max number value * fix: migrate superRefine to check for oldmarr import file * fix: inference of enum shape for old-import board-size wrong * fix: errors renamed to issues * chore: address pull request feedback * fix: zod form requires object * fix: inference for use-zod-form not working * fix: remove unnecessary convertion * fix(deps): upgrade trpc-to-openapi to v3 * fix: build error * fix: migrate missing zod imports to v4 * fix: migrate zod records to v4 * fix: missing core package dependency in api module * fix: unable to convert custom zod schema to openapi schema * fix(deps): upgrade zod to v4 * chore(renovate): enable zod dependency updates * test: add simple unit test for convertIntersectionToZodObject --------- Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- .github/renovate.json5 | 4 - apps/nextjs/package.json | 2 +- .../auth/invite/[id]/_registration-form.tsx | 2 +- .../app/[locale]/auth/login/_login-form.tsx | 2 +- .../[locale]/init/_steps/group/init-group.tsx | 2 +- .../init/_steps/settings/init-settings.tsx | 2 +- .../init/_steps/user/init-user-form.tsx | 2 +- .../manage/apps/edit/[id]/_app-edit-form.tsx | 2 +- .../src/app/[locale]/manage/apps/page.tsx | 2 +- .../edit/[id]/_integration-edit-form.tsx | 2 +- .../new/_integration-new-form.tsx | 2 +- .../[locale]/manage/integrations/new/page.tsx | 2 +- .../src/app/[locale]/manage/medias/page.tsx | 2 +- .../[locale]/manage/search-engines/_form.tsx | 2 +- .../edit/[id]/_search-engine-edit-form.tsx | 2 +- .../new/_search-engine-new-form.tsx | 2 +- .../[locale]/manage/search-engines/page.tsx | 2 +- .../_components/_change-home-board.tsx | 2 +- .../_change-search-preferences.tsx | 2 +- .../_components/_first-day-of-week.tsx | 2 +- .../_components/_ping-icons-enabled.tsx | 2 +- .../_components/create-user-stepper.tsx | 2 +- .../board/items/item-move-modal.tsx | 2 +- .../board/modals/board-rename-modal.tsx | 2 +- .../sections/category/category-edit-modal.tsx | 2 +- .../board/sections/dynamic/dynamic-actions.ts | 2 +- .../sections/dynamic/dynamic-edit-modal.tsx | 2 +- package.json | 8 +- packages/api/package.json | 5 +- packages/api/src/env.ts | 7 +- packages/api/src/middlewares/integration.ts | 2 +- packages/api/src/middlewares/item.ts | 2 +- packages/api/src/router/apiKeys.ts | 2 +- packages/api/src/router/app.ts | 2 +- packages/api/src/router/board.ts | 4 +- .../router/certificates/certificate-router.ts | 6 +- .../api/src/router/docker/docker-router.ts | 2 +- packages/api/src/router/group.ts | 2 +- .../api/src/router/import/import-router.ts | 2 +- packages/api/src/router/info.ts | 2 +- .../router/integration/integration-router.ts | 2 +- packages/api/src/router/invite.ts | 2 +- packages/api/src/router/location.ts | 2 +- packages/api/src/router/log.ts | 2 +- .../api/src/router/medias/media-router.ts | 2 +- .../api/src/router/onboard/onboard-router.ts | 2 +- .../search-engine/search-engine-router.ts | 2 +- .../api/src/router/section/section-router.ts | 2 +- packages/api/src/router/serverSettings.ts | 2 +- packages/api/src/router/user.ts | 2 +- .../router/user/change-search-preferences.ts | 2 +- packages/api/src/router/widgets/app.ts | 2 +- packages/api/src/router/widgets/calendar.ts | 2 +- packages/api/src/router/widgets/dns-hole.ts | 2 +- packages/api/src/router/widgets/downloads.ts | 2 +- .../api/src/router/widgets/media-requests.ts | 2 +- .../api/src/router/widgets/media-server.ts | 2 +- packages/api/src/router/widgets/minecraft.ts | 2 +- packages/api/src/router/widgets/notebook.ts | 2 +- packages/api/src/router/widgets/releases.ts | 2 +- packages/api/src/router/widgets/rssFeed.ts | 2 +- packages/api/src/router/widgets/smart-home.ts | 2 +- packages/api/src/router/widgets/stocks.ts | 2 +- packages/api/src/router/widgets/weather.ts | 2 +- packages/api/src/schema-merger.ts | 19 +- packages/api/src/test/schema-merger.spec.ts | 23 ++ packages/api/src/trpc.ts | 2 +- packages/auth/env.ts | 4 +- packages/auth/package.json | 2 +- .../authorization/basic-authorization.ts | 2 +- .../authorization/ldap-authorization.ts | 2 +- packages/auth/test/session.spec.ts | 2 +- packages/cli/src/commands/recreate-admin.ts | 2 +- packages/common/env.ts | 4 +- packages/common/package.json | 5 +- packages/common/src/env-validation.ts | 42 ---- .../parse/handlers/zod-parse-error-handler.ts | 2 +- packages/common/src/types.ts | 4 +- packages/core/package.json | 2 +- .../core/src/infrastructure/env/schemas.ts | 17 +- packages/cron-job-api/package.json | 2 +- packages/db/env.ts | 4 +- packages/db/package.json | 2 +- packages/definitions/package.json | 2 +- packages/definitions/src/docs/codegen.ts | 2 +- packages/docker/src/env.ts | 2 +- packages/form/package.json | 3 +- packages/form/src/index.ts | 30 ++- packages/form/src/messages.ts | 103 --------- packages/forms-collection/package.json | 2 +- .../src/new-app/_app-new-form.tsx | 2 +- .../forms-collection/src/new-app/_form.tsx | 2 +- packages/integrations/package.json | 2 +- .../src/adguard-home/adguard-home-types.ts | 2 +- .../src/codeberg/codeberg-schemas.ts | 2 +- .../src/dashdot/dashdot-integration.ts | 2 +- .../src/docker-hub/docker-hub-schemas.ts | 2 +- .../download-client/sabnzbd/sabnzbd-schema.ts | 2 +- .../integrations/src/emby/emby-integration.ts | 2 +- .../src/homeassistant/homeassistant-types.ts | 3 +- .../downloads/download-client-items.ts | 2 +- .../linuxserverio/linuxserverio-schemas.ts | 2 +- .../lidarr/lidarr-integration.ts | 2 +- .../radarr/radarr-integration.ts | 2 +- .../readarr/readarr-integration.ts | 2 +- .../sonarr/sonarr-integration.ts | 2 +- .../tdarr-validation-schemas.ts | 2 +- packages/integrations/src/npm/npm-schemas.ts | 21 +- packages/integrations/src/ntfy/ntfy-schema.ts | 2 +- .../openmediavault/openmediavault-types.ts | 2 +- .../src/opnsense/opnsense-types.ts | 4 +- .../src/overseerr/overseerr-integration.ts | 2 +- .../src/pi-hole/v5/pi-hole-schemas-v5.ts | 2 +- .../src/pi-hole/v6/pi-hole-integration-v6.ts | 2 +- .../src/pi-hole/v6/pi-hole-schemas-v6.ts | 2 +- .../integrations/src/plex/plex-integration.ts | 2 +- .../src/prowlarr/prowlarr-integration.ts | 2 +- .../src/prowlarr/prowlarr-types.ts | 2 +- .../integrations/src/quay/quay-schemas.ts | 3 +- packages/log/package.json | 2 +- packages/log/src/env.ts | 2 +- packages/modals-collection/package.json | 2 +- .../quick-add-app/quick-add-app-modal.tsx | 2 +- .../src/boards/add-board-modal.tsx | 11 +- .../src/boards/import-board-modal.tsx | 14 +- .../certificates/add-certificate-modal.tsx | 9 +- .../src/docker/add-docker-app-to-homarr.tsx | 2 +- packages/old-import/package.json | 4 +- .../src/analyse/analyse-oldmarr-import.ts | 2 +- .../src/components/initial/token-modal.tsx | 2 +- .../src/import/import-initial-oldmarr.ts | 2 +- packages/old-import/src/import/input.ts | 2 +- packages/old-import/src/settings.ts | 28 +-- packages/old-import/src/shared.ts | 2 +- packages/old-import/src/user-schema.ts | 2 +- packages/old-schema/package.json | 2 +- packages/old-schema/src/app.ts | 2 +- packages/old-schema/src/config.ts | 2 +- packages/old-schema/src/setting.ts | 2 +- packages/old-schema/src/tile.ts | 6 +- packages/old-schema/src/widget.ts | 4 +- .../src/minecraft-server-status.ts | 2 +- packages/request-handler/src/releases.ts | 2 +- packages/request-handler/src/rss-feeds.ts | 2 +- packages/request-handler/src/stock-price.ts | 2 +- packages/validation/package.json | 4 +- packages/validation/src/app.ts | 2 +- packages/validation/src/board.ts | 2 +- packages/validation/src/certificates.ts | 32 ++- packages/validation/src/common.ts | 2 +- packages/validation/src/enums.ts | 4 +- packages/validation/src/form/i18n.spec.ts | 114 ++++++++++ packages/validation/src/form/i18n.ts | 207 +++++++++--------- packages/validation/src/group.ts | 2 +- packages/validation/src/icons.ts | 2 +- packages/validation/src/integration.ts | 2 +- packages/validation/src/media.ts | 25 +-- packages/validation/src/permissions.ts | 4 +- packages/validation/src/search-engine.ts | 4 +- packages/validation/src/settings.ts | 2 +- packages/validation/src/shared.ts | 4 +- packages/validation/src/user.ts | 21 +- .../validation/src/widgets/media-request.ts | 2 +- packages/widgets/package.json | 3 +- packages/widgets/src/calendar/index.ts | 2 +- packages/widgets/src/downloads/index.ts | 2 +- .../widgets/src/media-transcoding/index.ts | 2 +- .../src/minecraft/server-status/index.ts | 2 +- .../widgets/src/modals/widget-edit-modal.tsx | 10 +- packages/widgets/src/options.ts | 4 +- packages/widgets/src/releases/index.ts | 2 +- packages/widgets/src/rssFeed/index.ts | 2 +- packages/widgets/src/weather/index.ts | 2 +- pnpm-lock.yaml | 187 +++++++++------- 174 files changed, 653 insertions(+), 631 deletions(-) create mode 100644 packages/api/src/test/schema-merger.spec.ts delete mode 100644 packages/common/src/env-validation.ts delete mode 100644 packages/form/src/messages.ts create mode 100644 packages/validation/src/form/i18n.spec.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 572827714..377002605 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -6,10 +6,6 @@ matchPackagePatterns: ["^@homarr/"], enabled: false, }, - { - matchPackagePatterns: ["^zod$", "^drizzle-zod$", "^zod-form-data$"], - enabled: false, - }, { matchUpdateTypes: ["minor", "patch", "pin", "digest"], automerge: true, diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index f3f9a175c..c689679eb 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -87,7 +87,7 @@ "superjson": "2.2.2", "swagger-ui-react": "^5.27.1", "use-deep-compare-effect": "^1.8.1", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx index 034001c28..f8d559ead 100644 --- a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation"; import { Button, PasswordInput, Stack, TextInput } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { useZodForm } from "@homarr/form"; diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index 8523eb043..ed86ea0f4 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; -import { z } from "zod"; +import { z } from "zod/v4"; import { signIn } from "@homarr/auth/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/apps/nextjs/src/app/[locale]/init/_steps/group/init-group.tsx b/apps/nextjs/src/app/[locale]/init/_steps/group/init-group.tsx index 976a8848a..d423c2fce 100644 --- a/apps/nextjs/src/app/[locale]/init/_steps/group/init-group.tsx +++ b/apps/nextjs/src/app/[locale]/init/_steps/group/init-group.tsx @@ -2,7 +2,7 @@ import { Button, Card, Stack, TextInput } from "@mantine/core"; import { IconArrowRight } from "@tabler/icons-react"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/apps/nextjs/src/app/[locale]/init/_steps/settings/init-settings.tsx b/apps/nextjs/src/app/[locale]/init/_steps/settings/init-settings.tsx index f83612f1a..b5839b743 100644 --- a/apps/nextjs/src/app/[locale]/init/_steps/settings/init-settings.tsx +++ b/apps/nextjs/src/app/[locale]/init/_steps/settings/init-settings.tsx @@ -3,7 +3,7 @@ import { startTransition } from "react"; import { Button, Card, Group, Stack, Switch, Text } from "@mantine/core"; import { IconArrowRight } from "@tabler/icons-react"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/apps/nextjs/src/app/[locale]/init/_steps/user/init-user-form.tsx b/apps/nextjs/src/app/[locale]/init/_steps/user/init-user-form.tsx index 5e43cbc32..daf73f7b3 100644 --- a/apps/nextjs/src/app/[locale]/init/_steps/user/init-user-form.tsx +++ b/apps/nextjs/src/app/[locale]/init/_steps/user/init-user-form.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, PasswordInput, Stack, TextInput } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { signIn } from "@homarr/auth/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx index 2dc13dc27..0f14be6b4 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx index b3dc291fc..761fd4495 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { redirect } from "next/navigation"; import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core"; import { IconBox, IconPencil } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx index 7427c7633..3b97e65f1 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Button, Fieldset, Group, Stack, TextInput } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx index 06c66619a..3bb768e5e 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-form.tsx @@ -16,7 +16,7 @@ import { TextInput, } from "@mantine/core"; import { IconInfoCircle } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/new/page.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/new/page.tsx index deb544491..1938a569e 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/new/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/new/page.tsx @@ -1,6 +1,6 @@ import { notFound } from "next/navigation"; import { Container, Group, Stack, Title } from "@mantine/core"; -import { z } from "zod"; +import { z } from "zod/v4"; import { auth } from "@homarr/auth/next"; import type { IntegrationKind } from "@homarr/definitions"; diff --git a/apps/nextjs/src/app/[locale]/manage/medias/page.tsx b/apps/nextjs/src/app/[locale]/manage/medias/page.tsx index 569168cab..9bdbf22c7 100644 --- a/apps/nextjs/src/app/[locale]/manage/medias/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/medias/page.tsx @@ -16,7 +16,7 @@ import { Tooltip, } from "@mantine/core"; import { IconExternalLink } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx index e34f78dc5..c41b653ed 100644 --- a/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import type { SegmentedControlItem } from "@mantine/core"; import { Button, Fieldset, Grid, Group, SegmentedControl, Stack, Textarea, TextInput } from "@mantine/core"; import { WidgetIntegrationSelect } from "node_modules/@homarr/widgets/src/widget-integration-select"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { searchEngineTypes } from "@homarr/definitions"; diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx index dbd2b949d..419a9b40a 100644 --- a/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/edit/[id]/_search-engine-edit-form.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx index 5bd5a6ae1..a8ff77056 100644 --- a/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/new/_search-engine-new-form.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx index 7634d78be..a3faa2811 100644 --- a/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { redirect } from "next/navigation"; import { ActionIcon, ActionIconGroup, Anchor, Avatar, Card, Group, Stack, Text, Title } from "@mantine/core"; import { IconPencil, IconSearch } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx index 52854e744..bdad9cfea 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-home-board.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Group, Stack } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx index 739f6da2b..5bf23a5fb 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Group, Select, Stack, Switch } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx index 5492023ee..e2b1a859d 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx @@ -4,7 +4,7 @@ import { Button, Group, Radio, Stack } from "@mantine/core"; import type { DayOfWeek } from "@mantine/dates"; import dayjs from "dayjs"; import localeData from "dayjs/plugin/localeData"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx index 5ddcc09f0..d218ac39a 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Group, Stack, Switch } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/apps/nextjs/src/app/[locale]/manage/users/create/_components/create-user-stepper.tsx b/apps/nextjs/src/app/[locale]/manage/users/create/_components/create-user-stepper.tsx index 97eeb9ff0..dc4f1a7c3 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/create/_components/create-user-stepper.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/create/_components/create-user-stepper.tsx @@ -17,7 +17,7 @@ import { } from "@mantine/core"; import { useListState } from "@mantine/hooks"; import { IconPlus, IconUserCheck } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { everyoneGroup, groupPermissions } from "@homarr/definitions"; diff --git a/apps/nextjs/src/components/board/items/item-move-modal.tsx b/apps/nextjs/src/components/board/items/item-move-modal.tsx index 7257fcb49..c1e03ba7c 100644 --- a/apps/nextjs/src/components/board/items/item-move-modal.tsx +++ b/apps/nextjs/src/components/board/items/item-move-modal.tsx @@ -1,6 +1,6 @@ import { useCallback, useRef } from "react"; import { Button, Grid, Group, NumberInput, Stack } from "@mantine/core"; -import { z } from "zod"; +import { z } from "zod/v4"; import { useZodForm } from "@homarr/form"; import type { GridStack } from "@homarr/gridstack"; diff --git a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx index c2f2e08e0..69dd12543 100644 --- a/apps/nextjs/src/components/board/modals/board-rename-modal.tsx +++ b/apps/nextjs/src/components/board/modals/board-rename-modal.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Group, Stack, TextInput } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { useZodForm } from "@homarr/form"; diff --git a/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx b/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx index 55e4780e9..1f990eec0 100644 --- a/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx +++ b/apps/nextjs/src/components/board/sections/category/category-edit-modal.tsx @@ -1,5 +1,5 @@ import { Button, Group, Stack, TextInput } from "@mantine/core"; -import { z } from "zod"; +import { z } from "zod/v4"; import { useZodForm } from "@homarr/form"; import { createModal } from "@homarr/modals"; diff --git a/apps/nextjs/src/components/board/sections/dynamic/dynamic-actions.ts b/apps/nextjs/src/components/board/sections/dynamic/dynamic-actions.ts index 553172905..230e28334 100644 --- a/apps/nextjs/src/components/board/sections/dynamic/dynamic-actions.ts +++ b/apps/nextjs/src/components/board/sections/dynamic/dynamic-actions.ts @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { useUpdateBoard } from "@homarr/boards/updater"; import type { dynamicSectionOptionsSchema } from "@homarr/validation/shared"; diff --git a/apps/nextjs/src/components/board/sections/dynamic/dynamic-edit-modal.tsx b/apps/nextjs/src/components/board/sections/dynamic/dynamic-edit-modal.tsx index e01052b36..b15c700a9 100644 --- a/apps/nextjs/src/components/board/sections/dynamic/dynamic-edit-modal.tsx +++ b/apps/nextjs/src/components/board/sections/dynamic/dynamic-edit-modal.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, CloseButton, ColorInput, Group, Stack, TextInput, useMantineTheme } from "@mantine/core"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { useZodForm } from "@homarr/form"; import { createModal } from "@homarr/modals"; diff --git a/package.json b/package.json index c0b3b82e5..6161c7230 100644 --- a/package.json +++ b/package.json @@ -74,14 +74,14 @@ "overrides": { "proxmox-api>undici": "7.13.0" }, + "patchedDependencies": { + "@types/node-unifi": "patches/@types__node-unifi.patch" + }, "allowUnusedPatches": true, "ignoredBuiltDependencies": [ "@scarf/scarf", "core-js-pure", "protobufjs" - ], - "patchedDependencies": { - "@types/node-unifi": "patches/@types__node-unifi.patch" - } + ] } } diff --git a/packages/api/package.json b/packages/api/package.json index 1b3ac2a88..419622ea8 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -24,6 +24,7 @@ "@homarr/auth": "workspace:^0.1.0", "@homarr/certificates": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0", + "@homarr/core": "workspace:^0.1.0", "@homarr/cron-job-api": "workspace:^0.1.0", "@homarr/cron-job-status": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0", @@ -51,8 +52,8 @@ "react": "19.1.1", "react-dom": "19.1.1", "superjson": "2.2.2", - "trpc-to-openapi": "^2.4.0", - "zod": "^3.25.76" + "trpc-to-openapi": "^3.0.0", + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/api/src/env.ts b/packages/api/src/env.ts index 61512165d..7cc81cc57 100644 --- a/packages/api/src/env.ts +++ b/packages/api/src/env.ts @@ -1,7 +1,6 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { z } from "zod"; +import { z } from "zod/v4"; -import { shouldSkipEnvValidation } from "@homarr/common/env-validation"; +import { createEnv } from "@homarr/core/infrastructure/env"; export const env = createEnv({ server: { @@ -10,6 +9,4 @@ export const env = createEnv({ runtimeEnv: { KUBERNETES_SERVICE_ACCOUNT_NAME: process.env.KUBERNETES_SERVICE_ACCOUNT_NAME, }, - skipValidation: shouldSkipEnvValidation(), - emptyStringAsUndefined: true, }); diff --git a/packages/api/src/middlewares/integration.ts b/packages/api/src/middlewares/integration.ts index 07f1f9407..3e58553c8 100644 --- a/packages/api/src/middlewares/integration.ts +++ b/packages/api/src/middlewares/integration.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Session } from "@homarr/auth"; import { hasQueryAccessToIntegrationsAsync } from "@homarr/auth/server"; diff --git a/packages/api/src/middlewares/item.ts b/packages/api/src/middlewares/item.ts index 173d6000f..375e21607 100644 --- a/packages/api/src/middlewares/item.ts +++ b/packages/api/src/middlewares/item.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { and, eq } from "@homarr/db"; import { items } from "@homarr/db/schema"; diff --git a/packages/api/src/router/apiKeys.ts b/packages/api/src/router/apiKeys.ts index 86a820c31..cdf8b5bea 100644 --- a/packages/api/src/router/apiKeys.ts +++ b/packages/api/src/router/apiKeys.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts index 3f832689e..4fda819a4 100644 --- a/packages/api/src/router/app.ts +++ b/packages/api/src/router/app.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId } from "@homarr/common"; import { asc, eq, inArray, like } from "@homarr/db"; diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index 3891a62cf..8d118018b 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -1,6 +1,6 @@ import { TRPCError } from "@trpc/server"; import superjson from "superjson"; -import { z } from "zod"; +import { z } from "zod/v4"; import { constructBoardPermissions } from "@homarr/auth/shared"; import { createId } from "@homarr/common"; @@ -1623,7 +1623,7 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL, use const forKind = (kind: T) => z.object({ kind: z.literal(kind), - options: z.record(z.unknown()), + options: z.record(z.string(), z.unknown()), }); const outputItemSchema = zodUnionFromArray(widgetKinds.map((kind) => forKind(kind))).and(sharedItemSchema); diff --git a/packages/api/src/router/certificates/certificate-router.ts b/packages/api/src/router/certificates/certificate-router.ts index f69076f35..4c7cfc862 100644 --- a/packages/api/src/router/certificates/certificate-router.ts +++ b/packages/api/src/router/certificates/certificate-router.ts @@ -1,13 +1,13 @@ import { X509Certificate } from "node:crypto"; import { TRPCError } from "@trpc/server"; -import { z } from "zod"; import { zfd } from "zod-form-data"; +import { z } from "zod/v4"; import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server"; import { and, eq } from "@homarr/db"; import { trustedCertificateHostnames } from "@homarr/db/schema"; import { logger } from "@homarr/log"; -import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates"; +import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates"; import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc"; @@ -16,7 +16,7 @@ export const certificateRouter = createTRPCRouter({ .requiresPermission("admin") .input( zfd.formData({ - file: zfd.file().superRefine(superRefineCertificateFile), + file: zfd.file().check(checkCertificateFile), }), ) .mutation(async ({ input }) => { diff --git a/packages/api/src/router/docker/docker-router.ts b/packages/api/src/router/docker/docker-router.ts index abbcebe22..6a0ea49a0 100644 --- a/packages/api/src/router/docker/docker-router.ts +++ b/packages/api/src/router/docker/docker-router.ts @@ -1,6 +1,6 @@ import { TRPCError } from "@trpc/server"; import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Container, ContainerState, Docker, Port } from "@homarr/docker"; import { DockerSingleton } from "@homarr/docker"; diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts index e717fca6f..13fb4fdd3 100644 --- a/packages/api/src/router/group.ts +++ b/packages/api/src/router/group.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId } from "@homarr/common"; import type { Database } from "@homarr/db"; diff --git a/packages/api/src/router/import/import-router.ts b/packages/api/src/router/import/import-router.ts index 4ffc5ca1a..96c5fd039 100644 --- a/packages/api/src/router/import/import-router.ts +++ b/packages/api/src/router/import/import-router.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { analyseOldmarrImportForRouterAsync, analyseOldmarrImportInputSchema } from "@homarr/old-import/analyse"; import { diff --git a/packages/api/src/router/info.ts b/packages/api/src/router/info.ts index cedf7a435..3c205963c 100644 --- a/packages/api/src/router/info.ts +++ b/packages/api/src/router/info.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import z from "zod/v4"; import packageJson from "../../../../package.json"; import { createTRPCRouter, protectedProcedure } from "../trpc"; diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 9cfc7cdb1..4546eeb16 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId, objectEntries } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; diff --git a/packages/api/src/router/invite.ts b/packages/api/src/router/invite.ts index a88e7fb10..1b767748d 100644 --- a/packages/api/src/router/invite.ts +++ b/packages/api/src/router/invite.ts @@ -1,6 +1,6 @@ import { randomBytes } from "crypto"; import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId } from "@homarr/common"; import { asc, eq } from "@homarr/db"; diff --git a/packages/api/src/router/location.ts b/packages/api/src/router/location.ts index 5ecae7db6..7379078ed 100644 --- a/packages/api/src/router/location.ts +++ b/packages/api/src/router/location.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTimeout } from "@homarr/common"; diff --git a/packages/api/src/router/log.ts b/packages/api/src/router/log.ts index 8377972ea..315f51de4 100644 --- a/packages/api/src/router/log.ts +++ b/packages/api/src/router/log.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import z from "zod"; +import z from "zod/v4"; import { logger } from "@homarr/log"; import { logLevels } from "@homarr/log/constants"; diff --git a/packages/api/src/router/medias/media-router.ts b/packages/api/src/router/medias/media-router.ts index 47823b312..ee7cfe3c4 100644 --- a/packages/api/src/router/medias/media-router.ts +++ b/packages/api/src/router/medias/media-router.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId } from "@homarr/common"; import type { InferInsertModel } from "@homarr/db"; diff --git a/packages/api/src/router/onboard/onboard-router.ts b/packages/api/src/router/onboard/onboard-router.ts index b68fbd44f..14909b5b3 100644 --- a/packages/api/src/router/onboard/onboard-router.ts +++ b/packages/api/src/router/onboard/onboard-router.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { onboarding } from "@homarr/db/schema"; import { onboardingSteps } from "@homarr/definitions"; diff --git a/packages/api/src/router/search-engine/search-engine-router.ts b/packages/api/src/router/search-engine/search-engine-router.ts index 2a330c103..6e983d38e 100644 --- a/packages/api/src/router/search-engine/search-engine-router.ts +++ b/packages/api/src/router/search-engine/search-engine-router.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId } from "@homarr/common"; import { asc, eq, like } from "@homarr/db"; diff --git a/packages/api/src/router/section/section-router.ts b/packages/api/src/router/section/section-router.ts index c903efde2..81613f581 100644 --- a/packages/api/src/router/section/section-router.ts +++ b/packages/api/src/router/section/section-router.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { and, eq } from "@homarr/db"; import { sectionCollapseStates, sections } from "@homarr/db/schema"; diff --git a/packages/api/src/router/serverSettings.ts b/packages/api/src/router/serverSettings.ts index c7035aa1f..3a31a51b0 100644 --- a/packages/api/src/router/serverSettings.ts +++ b/packages/api/src/router/serverSettings.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries"; import type { ServerSettings } from "@homarr/server-settings"; diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index 7cbe40a82..8ee07b62e 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createId } from "@homarr/common"; diff --git a/packages/api/src/router/user/change-search-preferences.ts b/packages/api/src/router/user/change-search-preferences.ts index 927418331..7f2793b9e 100644 --- a/packages/api/src/router/user/change-search-preferences.ts +++ b/packages/api/src/router/user/change-search-preferences.ts @@ -1,5 +1,5 @@ import { TRPCError } from "@trpc/server"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Session } from "@homarr/auth"; import type { Modify } from "@homarr/common/types"; diff --git a/packages/api/src/router/widgets/app.ts b/packages/api/src/router/widgets/app.ts index fa2ed05b4..7a65225a6 100644 --- a/packages/api/src/router/widgets/app.ts +++ b/packages/api/src/router/widgets/app.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import { sendPingRequestAsync } from "@homarr/ping"; import { pingChannel, pingUrlChannel } from "@homarr/redis"; diff --git a/packages/api/src/router/widgets/calendar.ts b/packages/api/src/router/widgets/calendar.ts index a4a7b993a..871ec6239 100644 --- a/packages/api/src/router/widgets/calendar.ts +++ b/packages/api/src/router/widgets/calendar.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { radarrReleaseTypes } from "@homarr/integrations/types"; diff --git a/packages/api/src/router/widgets/dns-hole.ts b/packages/api/src/router/widgets/dns-hole.ts index 2fa585a31..46b868435 100644 --- a/packages/api/src/router/widgets/dns-hole.ts +++ b/packages/api/src/router/widgets/dns-hole.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Modify } from "@homarr/common/types"; import type { Integration } from "@homarr/db/schema"; diff --git a/packages/api/src/router/widgets/downloads.ts b/packages/api/src/router/widgets/downloads.ts index 4a3b0dab5..f5ed6fdb9 100644 --- a/packages/api/src/router/widgets/downloads.ts +++ b/packages/api/src/router/widgets/downloads.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Modify } from "@homarr/common/types"; import type { Integration } from "@homarr/db/schema"; diff --git a/packages/api/src/router/widgets/media-requests.ts b/packages/api/src/router/widgets/media-requests.ts index 69fe09fce..d13854e88 100644 --- a/packages/api/src/router/widgets/media-requests.ts +++ b/packages/api/src/router/widgets/media-requests.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; diff --git a/packages/api/src/router/widgets/media-server.ts b/packages/api/src/router/widgets/media-server.ts index 9ab3025c3..0e497c8ff 100644 --- a/packages/api/src/router/widgets/media-server.ts +++ b/packages/api/src/router/widgets/media-server.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import type { StreamSession } from "@homarr/integrations"; diff --git a/packages/api/src/router/widgets/minecraft.ts b/packages/api/src/router/widgets/minecraft.ts index ab1d4c7fb..7f646c3bc 100644 --- a/packages/api/src/router/widgets/minecraft.ts +++ b/packages/api/src/router/widgets/minecraft.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { MinecraftServerStatus } from "@homarr/request-handler/minecraft-server-status"; import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status"; diff --git a/packages/api/src/router/widgets/notebook.ts b/packages/api/src/router/widgets/notebook.ts index 86bf61e3a..2f7229601 100644 --- a/packages/api/src/router/widgets/notebook.ts +++ b/packages/api/src/router/widgets/notebook.ts @@ -1,6 +1,6 @@ import { TRPCError } from "@trpc/server"; import SuperJSON from "superjson"; -import { z } from "zod"; +import { z } from "zod/v4"; import { eq } from "@homarr/db"; import { boards, items } from "@homarr/db/schema"; diff --git a/packages/api/src/router/widgets/releases.ts b/packages/api/src/router/widgets/releases.ts index 6402586c6..1cd1a4f89 100644 --- a/packages/api/src/router/widgets/releases.ts +++ b/packages/api/src/router/widgets/releases.ts @@ -1,5 +1,5 @@ import { escapeForRegEx } from "@tiptap/react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { releasesRequestHandler } from "@homarr/request-handler/releases"; diff --git a/packages/api/src/router/widgets/rssFeed.ts b/packages/api/src/router/widgets/rssFeed.ts index 415319318..b66662c2e 100644 --- a/packages/api/src/router/widgets/rssFeed.ts +++ b/packages/api/src/router/widgets/rssFeed.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; diff --git a/packages/api/src/router/widgets/smart-home.ts b/packages/api/src/router/widgets/smart-home.ts index 65b44535b..2dc9add89 100644 --- a/packages/api/src/router/widgets/smart-home.ts +++ b/packages/api/src/router/widgets/smart-home.ts @@ -1,5 +1,5 @@ import { observable } from "@trpc/server/observable"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { createIntegrationAsync } from "@homarr/integrations"; diff --git a/packages/api/src/router/widgets/stocks.ts b/packages/api/src/router/widgets/stocks.ts index f9b583570..d7b4ed13a 100644 --- a/packages/api/src/router/widgets/stocks.ts +++ b/packages/api/src/router/widgets/stocks.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchStockPriceHandler } from "@homarr/request-handler/stock-price"; diff --git a/packages/api/src/router/widgets/weather.ts b/packages/api/src/router/widgets/weather.ts index 94555c666..35a1b1b63 100644 --- a/packages/api/src/router/widgets/weather.ts +++ b/packages/api/src/router/widgets/weather.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTimeout } from "@homarr/common"; diff --git a/packages/api/src/schema-merger.ts b/packages/api/src/schema-merger.ts index eae12cf89..1385bf656 100644 --- a/packages/api/src/schema-merger.ts +++ b/packages/api/src/schema-merger.ts @@ -1,21 +1,20 @@ -import { z } from "zod"; -import type { AnyZodObject, ZodIntersection, ZodObject } from "zod"; +import { z } from "zod/v4"; +import type { ZodIntersection, ZodObject } from "zod/v4"; -export function convertIntersectionToZodObject>( +export function convertIntersectionToZodObject>( intersection: TIntersection, ) { - const { _def } = intersection; + const left = intersection.def.left; + const right = intersection.def.right; // Merge the shapes - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const mergedShape = { ..._def.left.shape, ..._def.right.shape }; + const mergedShape = { ...left.def.shape, ...right.def.shape }; // Return a new ZodObject return z.object(mergedShape) as unknown as TIntersection extends ZodIntersection - ? TLeft extends AnyZodObject - ? TRight extends AnyZodObject - ? // eslint-disable-next-line @typescript-eslint/no-explicit-any - ZodObject & z.infer> + ? TLeft extends ZodObject + ? TRight extends ZodObject + ? ZodObject : never : never : never; diff --git a/packages/api/src/test/schema-merger.spec.ts b/packages/api/src/test/schema-merger.spec.ts new file mode 100644 index 000000000..28bbf8f12 --- /dev/null +++ b/packages/api/src/test/schema-merger.spec.ts @@ -0,0 +1,23 @@ +import { describe, expect, test } from "vitest"; +import z from "zod/v4"; + +import { convertIntersectionToZodObject } from "../schema-merger"; + +describe("convertIntersectionToZodObject should convert zod intersection to zod object", () => { + test("should merge two ZodObjects with different properties", () => { + const objectA = z.object({ + id: z.string(), + }); + const objectB = z.object({ + name: z.string(), + }); + + const intersection = objectA.and(objectB); + + const result = convertIntersectionToZodObject(intersection); + + expect(result.def.type).toBe("object"); + expect(result.shape).toHaveProperty("id"); + expect(result.shape).toHaveProperty("name"); + }); +}); diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 93c2b57be..fd5f47442 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -9,7 +9,7 @@ import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import type { OpenApiMeta } from "trpc-to-openapi"; -import { ZodError } from "zod"; +import { ZodError } from "zod/v4"; import type { Session } from "@homarr/auth"; import { FlattenError } from "@homarr/common"; diff --git a/packages/auth/env.ts b/packages/auth/env.ts index 12089d9c8..4a41e8594 100644 --- a/packages/auth/env.ts +++ b/packages/auth/env.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { createBooleanSchema, createDurationSchema, createEnv } from "@homarr/core/infrastructure/env"; import { supportedAuthProviders } from "@homarr/definitions"; @@ -19,7 +19,7 @@ const authProvidersSchema = z return false; }), ) - .default("credentials"); + .default(["credentials"]); const authProviders = authProvidersSchema.safeParse(process.env.AUTH_PROVIDERS).data ?? []; diff --git a/packages/auth/package.json b/packages/auth/package.json index a9810be0e..58509d831 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -39,7 +39,7 @@ "next-auth": "5.0.0-beta.29", "react": "19.1.1", "react-dom": "19.1.1", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/auth/providers/credentials/authorization/basic-authorization.ts b/packages/auth/providers/credentials/authorization/basic-authorization.ts index 596716c1b..544441a9d 100644 --- a/packages/auth/providers/credentials/authorization/basic-authorization.ts +++ b/packages/auth/providers/credentials/authorization/basic-authorization.ts @@ -1,5 +1,5 @@ import bcrypt from "bcrypt"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import type { Database } from "@homarr/db"; import { and, eq } from "@homarr/db"; diff --git a/packages/auth/providers/credentials/authorization/ldap-authorization.ts b/packages/auth/providers/credentials/authorization/ldap-authorization.ts index c0d153c88..9f441aa9a 100644 --- a/packages/auth/providers/credentials/authorization/ldap-authorization.ts +++ b/packages/auth/providers/credentials/authorization/ldap-authorization.ts @@ -1,5 +1,5 @@ import { CredentialsSignin } from "@auth/core/errors"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createId, extractErrorMessage } from "@homarr/common"; import type { Database, InferInsertModel } from "@homarr/db"; diff --git a/packages/auth/test/session.spec.ts b/packages/auth/test/session.spec.ts index e15703ea4..cc0efa96a 100644 --- a/packages/auth/test/session.spec.ts +++ b/packages/auth/test/session.spec.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { z } from "zod"; +import { z } from "zod/v4"; import { expireDateAfter, generateSessionToken } from "../session"; diff --git a/packages/cli/src/commands/recreate-admin.ts b/packages/cli/src/commands/recreate-admin.ts index a00bad351..8c2b9fd8d 100644 --- a/packages/cli/src/commands/recreate-admin.ts +++ b/packages/cli/src/commands/recreate-admin.ts @@ -25,7 +25,7 @@ export const recreateAdmin = command({ if (!result.success) { console.error("Invalid username:"); - console.error(result.error.errors.map((error) => `- ${error.message}`).join("\n")); + console.error(result.error.issues.map((error) => `- ${error.message}`).join("\n")); return; } diff --git a/packages/common/env.ts b/packages/common/env.ts index f9e734e0a..2a7cd43d5 100644 --- a/packages/common/env.ts +++ b/packages/common/env.ts @@ -1,5 +1,5 @@ import { randomBytes } from "crypto"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createEnv } from "@homarr/core/infrastructure/env"; @@ -12,7 +12,7 @@ export const env = createEnv({ server: { SECRET_ENCRYPTION_KEY: z .string({ - required_error: `SECRET_ENCRYPTION_KEY is required${errorSuffix}`, + error: `SECRET_ENCRYPTION_KEY is required${errorSuffix}`, }) .min(64, { message: `SECRET_ENCRYPTION_KEY has to be 64 characters${errorSuffix}`, diff --git a/packages/common/package.json b/packages/common/package.json index b3adfe9a9..45e9aa1f6 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -9,8 +9,7 @@ "./types": "./src/types.ts", "./server": "./src/server.ts", "./client": "./src/client.ts", - "./env": "./env.ts", - "./env-validation": "./src/env-validation.ts" + "./env": "./env.ts" }, "typesVersions": { "*": { @@ -35,7 +34,7 @@ "react": "19.1.1", "react-dom": "19.1.1", "undici": "7.13.0", - "zod": "^3.25.76", + "zod": "^4.0.14", "zod-validation-error": "^3.5.3" }, "devDependencies": { diff --git a/packages/common/src/env-validation.ts b/packages/common/src/env-validation.ts deleted file mode 100644 index ff884bc0a..000000000 --- a/packages/common/src/env-validation.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { z } from "zod"; - -const trueStrings = ["1", "yes", "t", "true"]; -const falseStrings = ["0", "no", "f", "false"]; - -export const createBooleanSchema = (defaultValue: boolean) => - z - .string() - .default(defaultValue.toString()) - .transform((value, ctx) => { - const normalized = value.trim().toLowerCase(); - if (trueStrings.includes(normalized)) return true; - if (falseStrings.includes(normalized)) return false; - - throw new Error(`Invalid boolean value for ${ctx.path.join(".")}`); - }); - -export const createDurationSchema = (defaultValue: `${number}${"s" | "m" | "h" | "d"}`) => - z - .string() - .regex(/^\d+[smhd]?$/) - .default(defaultValue) - .transform((duration) => { - const lastChar = duration[duration.length - 1] as "s" | "m" | "h" | "d"; - if (!isNaN(Number(lastChar))) { - return Number(defaultValue); - } - - const multipliers = { - s: 1, - m: 60, - h: 60 * 60, - d: 60 * 60 * 24, - }; - const numberDuration = Number(duration.slice(0, -1)); - const multiplier = multipliers[lastChar]; - - return numberDuration * multiplier; - }); - -export const shouldSkipEnvValidation = () => - Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION) || process.env.npm_lifecycle_event === "lint"; diff --git a/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts b/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts index c695f1809..9f581ba99 100644 --- a/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts +++ b/packages/common/src/errors/parse/handlers/zod-parse-error-handler.ts @@ -1,5 +1,5 @@ -import { ZodError } from "zod"; import { fromError } from "zod-validation-error"; +import { ZodError } from "zod/v4"; import { logger } from "@homarr/log"; diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index e6b0621ff..8784e190f 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type { z } from "zod/v4"; export type MaybePromise = T | Promise; @@ -19,7 +19,7 @@ export type Inverse = { type Invertible = Record; -export type inferSearchParamsFromSchema = inferSearchParamsFromSchemaInner< +export type inferSearchParamsFromSchema = inferSearchParamsFromSchemaInner< z.infer >; diff --git a/packages/core/package.json b/packages/core/package.json index 9cadce5d7..f947211c6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,7 @@ "dependencies": { "@t3-oss/env-nextjs": "^0.13.8", "ioredis": "5.7.0", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/core/src/infrastructure/env/schemas.ts b/packages/core/src/infrastructure/env/schemas.ts index da700f6ad..595dd37ca 100644 --- a/packages/core/src/infrastructure/env/schemas.ts +++ b/packages/core/src/infrastructure/env/schemas.ts @@ -1,19 +1,16 @@ -import { z } from "zod"; +import { z } from "zod/v4"; const trueStrings = ["1", "yes", "t", "true"]; const falseStrings = ["0", "no", "f", "false"]; export const createBooleanSchema = (defaultValue: boolean) => z - .string() - .default(defaultValue.toString()) - .transform((value, ctx) => { - const normalized = value.trim().toLowerCase(); - if (trueStrings.includes(normalized)) return true; - if (falseStrings.includes(normalized)) return false; - - throw new Error(`Invalid boolean value for ${ctx.path.join(".")}`); - }); + .stringbool({ + truthy: trueStrings, + falsy: falseStrings, + case: "insensitive", + }) + .default(defaultValue); export const createDurationSchema = (defaultValue: `${number}${"s" | "m" | "h" | "d"}`) => z diff --git a/packages/cron-job-api/package.json b/packages/cron-job-api/package.json index 71e00579e..74106452a 100644 --- a/packages/cron-job-api/package.json +++ b/packages/cron-job-api/package.json @@ -35,7 +35,7 @@ "@trpc/tanstack-react-query": "^11.4.4", "node-cron": "^4.2.1", "react": "19.1.1", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/db/env.ts b/packages/db/env.ts index bb7ad0388..f02c35b8e 100644 --- a/packages/db/env.ts +++ b/packages/db/env.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { env as commonEnv } from "@homarr/common/env"; import { createEnv } from "@homarr/core/infrastructure/env"; @@ -42,7 +42,7 @@ export const env = createEnv({ .regex(/\d+/) .transform(Number) .refine((number) => number >= 1) - .default("3306"), + .default(3306), DB_USER: z.string(), DB_PASSWORD: z.string(), DB_NAME: z.string(), diff --git a/packages/db/package.json b/packages/db/package.json index 87ed5c0ab..9988dc725 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -51,7 +51,7 @@ "dotenv": "^17.2.1", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.4", - "drizzle-zod": "^0.7.1", + "drizzle-zod": "^0.8.2", "mysql2": "3.14.3", "superjson": "2.2.2" }, diff --git a/packages/definitions/package.json b/packages/definitions/package.json index 0291a64da..c9f22c028 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -25,7 +25,7 @@ "dependencies": { "@homarr/common": "workspace:^0.1.0", "fast-xml-parser": "^5.2.5", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/definitions/src/docs/codegen.ts b/packages/definitions/src/docs/codegen.ts index e8087efb8..f39cf3f28 100644 --- a/packages/definitions/src/docs/codegen.ts +++ b/packages/definitions/src/docs/codegen.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path, { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { XMLParser } from "fast-xml-parser"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createDocumentationLink } from "./index"; diff --git a/packages/docker/src/env.ts b/packages/docker/src/env.ts index a0ba6c5e2..e858d3e69 100644 --- a/packages/docker/src/env.ts +++ b/packages/docker/src/env.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { createBooleanSchema, createEnv } from "@homarr/core/infrastructure/env"; diff --git a/packages/form/package.json b/packages/form/package.json index 91f061fea..77241981a 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -27,7 +27,8 @@ "@homarr/translation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@mantine/form": "^8.2.4", - "zod": "^3.25.76" + "mantine-form-zod-resolver": "^1.2.1", + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/form/src/index.ts b/packages/form/src/index.ts index 59192a2a6..f1d34f193 100644 --- a/packages/form/src/index.ts +++ b/packages/form/src/index.ts @@ -1,29 +1,39 @@ -import { useForm, zodResolver } from "@mantine/form"; -import { z } from "zod"; -import type { AnyZodObject, ZodDiscriminatedUnion, ZodEffects, ZodIntersection } from "zod"; +import { useForm } from "@mantine/form"; +import { zod4Resolver } from "mantine-form-zod-resolver"; +import type { ZodDiscriminatedUnion, ZodIntersection, ZodObject, ZodPipe } from "zod/v4"; +import { z } from "zod/v4"; import { useI18n } from "@homarr/translation/client"; import { zodErrorMap } from "@homarr/validation/form/i18n"; +type inferPossibleSchema< + TSchema extends + | ZodObject + | ZodPipe + | ZodIntersection, ZodObject>, +> = z.infer extends Record ? z.infer : never; + export const useZodForm = < TSchema extends - | AnyZodObject - | ZodEffects - | ZodIntersection, AnyZodObject>, + | ZodObject + | ZodPipe + | ZodIntersection, ZodObject>, >( schema: TSchema, options: Omit< - Exclude>>[0], undefined>, + Exclude>>[0], undefined>, "validate" | "validateInputOnBlur" | "validateInputOnChange" >, ) => { const t = useI18n(); - z.setErrorMap(zodErrorMap(t)); - return useForm>({ + z.config({ + customError: zodErrorMap(t), + }); + return useForm>({ ...options, validateInputOnBlur: true, validateInputOnChange: true, - validate: zodResolver(schema), + validate: zod4Resolver(schema), }); }; diff --git a/packages/form/src/messages.ts b/packages/form/src/messages.ts deleted file mode 100644 index bfbbc4c4f..000000000 --- a/packages/form/src/messages.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { ErrorMapCtx, z, ZodTooBigIssue, ZodTooSmallIssue } from "zod"; -import { ZodIssueCode } from "zod"; - -import type { TranslationObject } from "@homarr/translation"; - -const handleStringError = (issue: z.ZodInvalidStringIssue) => { - if (typeof issue.validation === "object") { - // Check if object contains startsWith / endsWith key to determine the error type. If not, it's an includes error. (see type of issue.validation) - if ("startsWith" in issue.validation) { - return { - key: "errors.string.startsWith", - params: { - startsWith: issue.validation.startsWith, - }, - } as const; - } else if ("endsWith" in issue.validation) { - return { - key: "errors.string.endsWith", - params: { - endsWith: issue.validation.endsWith, - }, - } as const; - } - - return { - key: "errors.invalid_string.includes", - params: { - includes: issue.validation.includes, - }, - } as const; - } - - return { - message: issue.message, - }; -}; - -const handleTooSmallError = (issue: ZodTooSmallIssue) => { - if (issue.type !== "string" && issue.type !== "number") { - return { - message: issue.message, - }; - } - - return { - key: `errors.tooSmall.${issue.type}`, - params: { - minimum: issue.minimum, - count: issue.minimum, - }, - } as const; -}; - -const handleTooBigError = (issue: ZodTooBigIssue) => { - if (issue.type !== "string" && issue.type !== "number") { - return { - message: issue.message, - }; - } - - return { - key: `errors.tooBig.${issue.type}`, - params: { - maximum: issue.maximum, - count: issue.maximum, - }, - } as const; -}; - -export const handleZodError = (issue: z.ZodIssueOptionalMessage, ctx: ErrorMapCtx) => { - if (ctx.defaultError === "Required") { - return { - key: "errors.required", - params: {}, - } as const; - } - if (issue.code === ZodIssueCode.invalid_string) { - return handleStringError(issue); - } - if (issue.code === ZodIssueCode.too_small) { - return handleTooSmallError(issue); - } - if (issue.code === ZodIssueCode.too_big) { - return handleTooBigError(issue); - } - if (issue.code === ZodIssueCode.custom && issue.params?.i18n) { - const { i18n } = issue.params as CustomErrorParams; - return { - key: `errors.custom.${i18n.key}`, - } as const; - } - - return { - message: issue.message, - }; -}; - -export interface CustomErrorParams { - i18n: { - key: keyof TranslationObject["common"]["zod"]["errors"]["custom"]; - params?: Record; - }; -} diff --git a/packages/forms-collection/package.json b/packages/forms-collection/package.json index 8b4ebd41d..403020796 100644 --- a/packages/forms-collection/package.json +++ b/packages/forms-collection/package.json @@ -31,7 +31,7 @@ "@homarr/validation": "workspace:^0.1.0", "@mantine/core": "^8.2.4", "react": "19.1.1", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/forms-collection/src/new-app/_app-new-form.tsx b/packages/forms-collection/src/new-app/_app-new-form.tsx index a8ef5d70d..06e96ada1 100644 --- a/packages/forms-collection/src/new-app/_app-new-form.tsx +++ b/packages/forms-collection/src/new-app/_app-new-form.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; diff --git a/packages/forms-collection/src/new-app/_form.tsx b/packages/forms-collection/src/new-app/_form.tsx index 559b2160a..a23f8c5e6 100644 --- a/packages/forms-collection/src/new-app/_form.tsx +++ b/packages/forms-collection/src/new-app/_form.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef } from "react"; import Link from "next/link"; import { Button, Checkbox, Collapse, Group, Stack, Textarea, TextInput } from "@mantine/core"; import { useDebouncedValue, useDisclosure } from "@mantine/hooks"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { useZodForm } from "@homarr/form"; diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 606f291a4..7df7c9230 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -47,7 +47,7 @@ "tsdav": "^2.1.5", "undici": "7.13.0", "xml2js": "^0.6.2", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/integrations/src/adguard-home/adguard-home-types.ts b/packages/integrations/src/adguard-home/adguard-home-types.ts index bcd7abd29..493bc2099 100644 --- a/packages/integrations/src/adguard-home/adguard-home-types.ts +++ b/packages/integrations/src/adguard-home/adguard-home-types.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const statsResponseSchema = z.object({ time_units: z.enum(["hours", "days"]), diff --git a/packages/integrations/src/codeberg/codeberg-schemas.ts b/packages/integrations/src/codeberg/codeberg-schemas.ts index 1a6dafb39..ff6e62a01 100644 --- a/packages/integrations/src/codeberg/codeberg-schemas.ts +++ b/packages/integrations/src/codeberg/codeberg-schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const releasesResponseSchema = z.array( z.object({ diff --git a/packages/integrations/src/dashdot/dashdot-integration.ts b/packages/integrations/src/dashdot/dashdot-integration.ts index d6ba49ab1..bf58feb40 100644 --- a/packages/integrations/src/dashdot/dashdot-integration.ts +++ b/packages/integrations/src/dashdot/dashdot-integration.ts @@ -3,7 +3,7 @@ import { humanFileSize } from "@homarr/common"; import "@homarr/redis"; import dayjs from "dayjs"; -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; diff --git a/packages/integrations/src/docker-hub/docker-hub-schemas.ts b/packages/integrations/src/docker-hub/docker-hub-schemas.ts index 02b184383..674ae5532 100644 --- a/packages/integrations/src/docker-hub/docker-hub-schemas.ts +++ b/packages/integrations/src/docker-hub/docker-hub-schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const accessTokenResponseSchema = z.object({ access_token: z.string(), diff --git a/packages/integrations/src/download-client/sabnzbd/sabnzbd-schema.ts b/packages/integrations/src/download-client/sabnzbd/sabnzbd-schema.ts index b1a327d09..c1bc9a249 100644 --- a/packages/integrations/src/download-client/sabnzbd/sabnzbd-schema.ts +++ b/packages/integrations/src/download-client/sabnzbd/sabnzbd-schema.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const queueSchema = z.object({ queue: z.object({ diff --git a/packages/integrations/src/emby/emby-integration.ts b/packages/integrations/src/emby/emby-integration.ts index 2b354cbe1..dff55513e 100644 --- a/packages/integrations/src/emby/emby-integration.ts +++ b/packages/integrations/src/emby/emby-integration.ts @@ -1,5 +1,5 @@ import { BaseItemKind } from "@jellyfin/sdk/lib/generated-client/models"; -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; diff --git a/packages/integrations/src/homeassistant/homeassistant-types.ts b/packages/integrations/src/homeassistant/homeassistant-types.ts index e4276159a..da1043b91 100644 --- a/packages/integrations/src/homeassistant/homeassistant-types.ts +++ b/packages/integrations/src/homeassistant/homeassistant-types.ts @@ -1,7 +1,8 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const entityStateSchema = z.object({ attributes: z.record( + z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(z.union([z.string(), z.number()]))]), ), entity_id: z.string(), diff --git a/packages/integrations/src/interfaces/downloads/download-client-items.ts b/packages/integrations/src/interfaces/downloads/download-client-items.ts index 69ebfe88b..2fed74538 100644 --- a/packages/integrations/src/interfaces/downloads/download-client-items.ts +++ b/packages/integrations/src/interfaces/downloads/download-client-items.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import type { Integration } from "@homarr/db/schema"; diff --git a/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts b/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts index bf9842a50..3e3653697 100644 --- a/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts +++ b/packages/integrations/src/linuxserverio/linuxserverio-schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const releasesResponseSchema = z.object({ data: z.object({ diff --git a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts index bec3fde58..6348f9ec1 100644 --- a/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts +++ b/packages/integrations/src/media-organizer/lidarr/lidarr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { logger } from "@homarr/log"; diff --git a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts index 681ade5a0..8dc6aff87 100644 --- a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts +++ b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import type { AtLeastOneOf } from "@homarr/common/types"; diff --git a/packages/integrations/src/media-organizer/readarr/readarr-integration.ts b/packages/integrations/src/media-organizer/readarr/readarr-integration.ts index bd288db61..a9d8154cd 100644 --- a/packages/integrations/src/media-organizer/readarr/readarr-integration.ts +++ b/packages/integrations/src/media-organizer/readarr/readarr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { logger } from "@homarr/log"; diff --git a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts index e82ff9094..a3f623881 100644 --- a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts +++ b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { logger } from "@homarr/log"; diff --git a/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts b/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts index 43729623a..4f43e60de 100644 --- a/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts +++ b/packages/integrations/src/media-transcoding/tdarr-validation-schemas.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const getStatisticsSchema = z.object({ pieStats: z.object({ diff --git a/packages/integrations/src/npm/npm-schemas.ts b/packages/integrations/src/npm/npm-schemas.ts index 290cea8ed..5d21734e2 100644 --- a/packages/integrations/src/npm/npm-schemas.ts +++ b/packages/integrations/src/npm/npm-schemas.ts @@ -1,12 +1,17 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const releasesResponseSchema = z.object({ - time: z.record(z.string().transform((value) => new Date(value))).transform((version) => - Object.entries(version).map(([key, value]) => ({ - latestRelease: key, - latestReleaseAt: value, - })), - ), - versions: z.record(z.object({ description: z.string() })), + time: z + .record( + z.string(), + z.string().transform((value) => new Date(value)), + ) + .transform((version) => + Object.entries(version).map(([key, value]) => ({ + latestRelease: key, + latestReleaseAt: value, + })), + ), + versions: z.record(z.string(), z.object({ description: z.string() })), name: z.string(), }); diff --git a/packages/integrations/src/ntfy/ntfy-schema.ts b/packages/integrations/src/ntfy/ntfy-schema.ts index 2c00557b4..d516e8a79 100644 --- a/packages/integrations/src/ntfy/ntfy-schema.ts +++ b/packages/integrations/src/ntfy/ntfy-schema.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; // There are more properties, see: https://docs.ntfy.sh/subscribe/api/#json-message-format // Not all properties are required for this use case. diff --git a/packages/integrations/src/openmediavault/openmediavault-types.ts b/packages/integrations/src/openmediavault/openmediavault-types.ts index dd1b1b5f4..d2396d08e 100644 --- a/packages/integrations/src/openmediavault/openmediavault-types.ts +++ b/packages/integrations/src/openmediavault/openmediavault-types.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; // Schema for system information export const systemInformationSchema = z.object({ diff --git a/packages/integrations/src/opnsense/opnsense-types.ts b/packages/integrations/src/opnsense/opnsense-types.ts index 24d112302..a91493886 100644 --- a/packages/integrations/src/opnsense/opnsense-types.ts +++ b/packages/integrations/src/opnsense/opnsense-types.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; // API documentation : https://docs.opnsense.org/development/api.html#core-api @@ -21,7 +21,7 @@ const interfaceSchema = z.object({ }); export const opnsenseInterfacesSchema = z.object({ - interfaces: z.record(interfaceSchema), + interfaces: z.record(z.string(), interfaceSchema), time: z.number(), }); diff --git a/packages/integrations/src/overseerr/overseerr-integration.ts b/packages/integrations/src/overseerr/overseerr-integration.ts index 66c1841fc..6a881a155 100644 --- a/packages/integrations/src/overseerr/overseerr-integration.ts +++ b/packages/integrations/src/overseerr/overseerr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { logger } from "@homarr/log"; diff --git a/packages/integrations/src/pi-hole/v5/pi-hole-schemas-v5.ts b/packages/integrations/src/pi-hole/v5/pi-hole-schemas-v5.ts index 35c204f9e..dee8b7b41 100644 --- a/packages/integrations/src/pi-hole/v5/pi-hole-schemas-v5.ts +++ b/packages/integrations/src/pi-hole/v5/pi-hole-schemas-v5.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const summaryResponseSchema = z.object({ status: z.enum(["enabled", "disabled"]), diff --git a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts index 5e38648f1..2332d47bc 100644 --- a/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts +++ b/packages/integrations/src/pi-hole/v6/pi-hole-integration-v6.ts @@ -1,5 +1,5 @@ import type { fetch as undiciFetch, Response as UndiciResponse } from "undici"; -import type { z } from "zod"; +import type { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ResponseError } from "@homarr/common/server"; diff --git a/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts b/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts index 283a8a71f..3be133aa9 100644 --- a/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts +++ b/packages/integrations/src/pi-hole/v6/pi-hole-schemas-v6.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const sessionResponseSchema = z.object({ session: z.object({ diff --git a/packages/integrations/src/plex/plex-integration.ts b/packages/integrations/src/plex/plex-integration.ts index 553216c4f..1c842c02b 100644 --- a/packages/integrations/src/plex/plex-integration.ts +++ b/packages/integrations/src/plex/plex-integration.ts @@ -1,5 +1,5 @@ import { parseStringPromise } from "xml2js"; -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; import { ParseError } from "@homarr/common/server"; diff --git a/packages/integrations/src/prowlarr/prowlarr-integration.ts b/packages/integrations/src/prowlarr/prowlarr-integration.ts index 729d41f41..22b3bbab2 100644 --- a/packages/integrations/src/prowlarr/prowlarr-integration.ts +++ b/packages/integrations/src/prowlarr/prowlarr-integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server"; diff --git a/packages/integrations/src/prowlarr/prowlarr-types.ts b/packages/integrations/src/prowlarr/prowlarr-types.ts index dc10feca8..0bc4785c5 100644 --- a/packages/integrations/src/prowlarr/prowlarr-types.ts +++ b/packages/integrations/src/prowlarr/prowlarr-types.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const indexerResponseSchema = z.object({ id: z.number(), diff --git a/packages/integrations/src/quay/quay-schemas.ts b/packages/integrations/src/quay/quay-schemas.ts index 2de28c018..e24fe2d83 100644 --- a/packages/integrations/src/quay/quay-schemas.ts +++ b/packages/integrations/src/quay/quay-schemas.ts @@ -1,8 +1,9 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const releasesResponseSchema = z.object({ description: z.string().optional(), tags: z.record( + z.string(), z.object({ name: z.string(), last_modified: z.string(), diff --git a/packages/log/package.json b/packages/log/package.json index 5e577ab09..c19ddf0dd 100644 --- a/packages/log/package.json +++ b/packages/log/package.json @@ -27,7 +27,7 @@ "@homarr/core": "workspace:^0.1.0", "superjson": "2.2.2", "winston": "3.17.0", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/log/src/env.ts b/packages/log/src/env.ts index cd6ea02d4..28d463bd6 100644 --- a/packages/log/src/env.ts +++ b/packages/log/src/env.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { createEnv } from "@homarr/core/infrastructure/env"; diff --git a/packages/modals-collection/package.json b/packages/modals-collection/package.json index e494339d7..3799ff445 100644 --- a/packages/modals-collection/package.json +++ b/packages/modals-collection/package.json @@ -39,7 +39,7 @@ "next": "15.4.6", "react": "19.1.1", "react-dom": "19.1.1", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/modals-collection/src/apps/quick-add-app/quick-add-app-modal.tsx b/packages/modals-collection/src/apps/quick-add-app/quick-add-app-modal.tsx index 547840ccd..1acac44ea 100644 --- a/packages/modals-collection/src/apps/quick-add-app/quick-add-app-modal.tsx +++ b/packages/modals-collection/src/apps/quick-add-app/quick-add-app-modal.tsx @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import type { MaybePromise } from "@homarr/common/types"; diff --git a/packages/modals-collection/src/boards/add-board-modal.tsx b/packages/modals-collection/src/boards/add-board-modal.tsx index 2b6c70cf4..919335b9b 100644 --- a/packages/modals-collection/src/boards/add-board-modal.tsx +++ b/packages/modals-collection/src/boards/add-board-modal.tsx @@ -28,10 +28,6 @@ export const AddBoardModal = createModal(({ actions }) => { const boardNameStatus = useBoardNameStatus(form.values.name); - const columnCountChecks = boardColumnCountSchema._def.checks; - const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value; - const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value; - return (
{ @@ -69,7 +65,12 @@ export const AddBoardModal = createModal(({ actions }) => { } /> - + { const [fileValid, setFileValid] = useState(true); const form = useZodForm( z.object({ - file: z.instanceof(File).nullable().superRefine(superRefineJsonImportFile), + file: z.file().check(checkJsonImportFile), configuration: oldmarrImportConfigurationSchema, }), { @@ -43,6 +43,8 @@ export const ImportBoardModal = createModal(({ actions }) => { return; } + // Before validation it can still be null + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!values.file) { return; } @@ -51,7 +53,7 @@ export const ImportBoardModal = createModal(({ actions }) => { const result = oldmarrConfigSchema.safeParse(JSON.parse(content)); if (!result.success) { - console.error(result.error.errors); + console.error(result.error.issues); setFileValid(false); return; } @@ -97,9 +99,7 @@ export const ImportBoardModal = createModal(({ actions }) => { } void handleSubmitAsync({ - // It's checked for null in the superrefine - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - file: values.file!, + file: values.file, configuration: values.configuration, }); })} diff --git a/packages/modals-collection/src/certificates/add-certificate-modal.tsx b/packages/modals-collection/src/certificates/add-certificate-modal.tsx index 9ec4e4533..100ee13cd 100644 --- a/packages/modals-collection/src/certificates/add-certificate-modal.tsx +++ b/packages/modals-collection/src/certificates/add-certificate-modal.tsx @@ -1,6 +1,6 @@ import { Button, FileInput, Group, Stack } from "@mantine/core"; import { IconCertificate } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import type { MaybePromise } from "@homarr/common/types"; @@ -8,7 +8,7 @@ import { useZodForm } from "@homarr/form"; import { createModal } from "@homarr/modals"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n } from "@homarr/translation/client"; -import { superRefineCertificateFile } from "@homarr/validation/certificates"; +import { checkCertificateFile } from "@homarr/validation/certificates"; interface InnerProps { onSuccess?: () => MaybePromise; @@ -18,7 +18,7 @@ export const AddCertificateModal = createModal(({ actions, innerProp const t = useI18n(); const form = useZodForm( z.object({ - file: z.instanceof(File).nullable().superRefine(superRefineCertificateFile), + file: z.file().check(checkCertificateFile), }), { initialValues: { @@ -33,8 +33,7 @@ export const AddCertificateModal = createModal(({ actions, innerProp { const formData = new FormData(); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - formData.set("file", values.file!); + formData.set("file", values.file); await mutateAsync(formData, { async onSuccess() { showSuccessNotification({ diff --git a/packages/modals-collection/src/docker/add-docker-app-to-homarr.tsx b/packages/modals-collection/src/docker/add-docker-app-to-homarr.tsx index 6748f7979..fc8567ccb 100644 --- a/packages/modals-collection/src/docker/add-docker-app-to-homarr.tsx +++ b/packages/modals-collection/src/docker/add-docker-app-to-homarr.tsx @@ -1,5 +1,5 @@ import { Avatar, Button, Group, List, LoadingOverlay, Stack, Text, TextInput } from "@mantine/core"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; diff --git a/packages/old-import/package.json b/packages/old-import/package.json index add57704f..89e991fe8 100644 --- a/packages/old-import/package.json +++ b/packages/old-import/package.json @@ -44,8 +44,8 @@ "react": "19.1.1", "react-dom": "19.1.1", "superjson": "2.2.2", - "zod": "^3.25.76", - "zod-form-data": "^2.0.7" + "zod": "^4.0.14", + "zod-form-data": "^3.0.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/old-import/src/analyse/analyse-oldmarr-import.ts b/packages/old-import/src/analyse/analyse-oldmarr-import.ts index 1edaf11f1..5888d07f2 100644 --- a/packages/old-import/src/analyse/analyse-oldmarr-import.ts +++ b/packages/old-import/src/analyse/analyse-oldmarr-import.ts @@ -1,5 +1,5 @@ import AdmZip from "adm-zip"; -import { z } from "zod"; +import { z } from "zod/v4"; import { logger } from "@homarr/log"; import { oldmarrConfigSchema } from "@homarr/old-schema"; diff --git a/packages/old-import/src/components/initial/token-modal.tsx b/packages/old-import/src/components/initial/token-modal.tsx index a612f029c..d519d312f 100644 --- a/packages/old-import/src/components/initial/token-modal.tsx +++ b/packages/old-import/src/components/initial/token-modal.tsx @@ -1,5 +1,5 @@ import { Button, Group, PasswordInput, Stack } from "@mantine/core"; -import { z } from "zod"; +import { z } from "zod/v4"; import { useZodForm } from "@homarr/form"; import { createModal } from "@homarr/modals"; diff --git a/packages/old-import/src/import/import-initial-oldmarr.ts b/packages/old-import/src/import/import-initial-oldmarr.ts index fdf1a79da..a42bbba83 100644 --- a/packages/old-import/src/import/import-initial-oldmarr.ts +++ b/packages/old-import/src/import/import-initial-oldmarr.ts @@ -1,4 +1,4 @@ -import type { z } from "zod"; +import type { z } from "zod/v4"; import { Stopwatch } from "@homarr/common"; import { handleTransactionsAsync } from "@homarr/db"; diff --git a/packages/old-import/src/import/input.ts b/packages/old-import/src/import/input.ts index f60cdd0f7..681227dbb 100644 --- a/packages/old-import/src/import/input.ts +++ b/packages/old-import/src/import/input.ts @@ -1,6 +1,6 @@ import SuperJSON from "superjson"; -import { z } from "zod"; import { zfd } from "zod-form-data"; +import { z } from "zod/v4"; import { initialOldmarrImportSettings } from "../settings"; diff --git a/packages/old-import/src/settings.ts b/packages/old-import/src/settings.ts index a4944562b..e1bbae154 100644 --- a/packages/old-import/src/settings.ts +++ b/packages/old-import/src/settings.ts @@ -1,5 +1,5 @@ -import { z } from "zod"; import { zfd } from "zod-form-data"; +import { z } from "zod/v4"; import { boardNameSchema } from "@homarr/validation/board"; import { createCustomErrorParams } from "@homarr/validation/form/i18n"; @@ -23,39 +23,33 @@ export const initialOldmarrImportSettings = oldmarrImportConfigurationSchema.pic export type InitialOldmarrImportSettings = z.infer; -export const superRefineJsonImportFile = (value: File | null, context: z.RefinementCtx) => { - if (!value) { - return context.addIssue({ - code: "invalid_type", - expected: "object", - received: "null", - }); - } - - if (value.type !== "application/json") { - return context.addIssue({ +export const checkJsonImportFile: z.core.CheckFn = (context) => { + if (context.value.type !== "application/json") { + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "invalidFileType", params: { expected: "JSON" }, }), + input: context.value.type, }); + return; } - if (value.size > 1024 * 1024) { - return context.addIssue({ + if (context.value.size > 1024 * 1024) { + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "fileTooLarge", params: { maxSize: "1 MB" }, }), + input: context.value.size, }); + return; } - - return null; }; export const importJsonFileSchema = zfd.formData({ - file: zfd.file().superRefine(superRefineJsonImportFile), + file: zfd.file().check(checkJsonImportFile), configuration: zfd.json(oldmarrImportConfigurationSchema), }); diff --git a/packages/old-import/src/shared.ts b/packages/old-import/src/shared.ts index 4276a510c..e752b57f8 100644 --- a/packages/old-import/src/shared.ts +++ b/packages/old-import/src/shared.ts @@ -1,2 +1,2 @@ -export { importJsonFileSchema, superRefineJsonImportFile, oldmarrImportConfigurationSchema } from "./settings"; +export { importJsonFileSchema, checkJsonImportFile, oldmarrImportConfigurationSchema } from "./settings"; export type { OldmarrImportConfiguration } from "./settings"; diff --git a/packages/old-import/src/user-schema.ts b/packages/old-import/src/user-schema.ts index f7a84c917..84e544ea3 100644 --- a/packages/old-import/src/user-schema.ts +++ b/packages/old-import/src/user-schema.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; const regexEncryptedSchema = z.string().regex(/^[a-f0-9]+\.[a-f0-9]+$/g); diff --git a/packages/old-schema/package.json b/packages/old-schema/package.json index 9ee6e5111..bd8bb5543 100644 --- a/packages/old-schema/package.json +++ b/packages/old-schema/package.json @@ -23,7 +23,7 @@ "prettier": "@homarr/prettier-config", "dependencies": { "@homarr/common": "workspace:^0.1.0", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/old-schema/src/app.ts b/packages/old-schema/src/app.ts index 87bedef25..088ccca0a 100644 --- a/packages/old-schema/src/app.ts +++ b/packages/old-schema/src/app.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { tileBaseSchema } from "./tile"; diff --git a/packages/old-schema/src/config.ts b/packages/old-schema/src/config.ts index d120d31c7..f9fec28a8 100644 --- a/packages/old-schema/src/config.ts +++ b/packages/old-schema/src/config.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { oldmarrAppSchema } from "./app"; import { settingsSchema } from "./setting"; diff --git a/packages/old-schema/src/setting.ts b/packages/old-schema/src/setting.ts index f9374a6f2..171ea3b43 100644 --- a/packages/old-schema/src/setting.ts +++ b/packages/old-schema/src/setting.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; const baseSearchEngineSchema = z.object({ properties: z.object({ diff --git a/packages/old-schema/src/tile.ts b/packages/old-schema/src/tile.ts index fc0740765..0d835c3ac 100644 --- a/packages/old-schema/src/tile.ts +++ b/packages/old-schema/src/tile.ts @@ -1,8 +1,8 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { objectKeys } from "@homarr/common"; -const createAreaSchema = ( +const createAreaSchema = ( type: TType, propertiesSchema: TPropertiesSchema, ) => @@ -58,7 +58,7 @@ export const tileBaseSchema = z.object({ export type SizedShape = z.infer; -export const boardSizes = objectKeys(shapeSchema._def.shape()); +export const boardSizes = objectKeys(shapeSchema.def.shape); export type BoardSize = (typeof boardSizes)[number]; export const getBoardSizeName = (size: BoardSize) => { diff --git a/packages/old-schema/src/widget.ts b/packages/old-schema/src/widget.ts index 96a7396ea..dfff00751 100644 --- a/packages/old-schema/src/widget.ts +++ b/packages/old-schema/src/widget.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { tileBaseSchema } from "./tile"; @@ -33,7 +33,7 @@ export const oldmarrWidgetSchema = z .object({ id: z.string(), type: z.enum(oldmarrWidgetKinds), - properties: z.record(z.unknown()), + properties: z.record(z.string(), z.unknown()), }) .and(tileBaseSchema); diff --git a/packages/request-handler/src/minecraft-server-status.ts b/packages/request-handler/src/minecraft-server-status.ts index 22eaf7a38..4cff54ed8 100644 --- a/packages/request-handler/src/minecraft-server-status.ts +++ b/packages/request-handler/src/minecraft-server-status.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTimeout } from "@homarr/common"; diff --git a/packages/request-handler/src/releases.ts b/packages/request-handler/src/releases.ts index 8eeee1b0e..74158c05f 100644 --- a/packages/request-handler/src/releases.ts +++ b/packages/request-handler/src/releases.ts @@ -2,8 +2,8 @@ import dayjs from "dayjs"; import type { IntegrationKindByCategory } from "@homarr/definitions"; import { getIconUrl } from "@homarr/definitions"; -import { createIntegrationAsync } from "@homarr/integrations"; import type { ReleasesResponse } from "@homarr/integrations"; +import { createIntegrationAsync } from "@homarr/integrations"; import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler"; diff --git a/packages/request-handler/src/rss-feeds.ts b/packages/request-handler/src/rss-feeds.ts index 7a565cb5f..1efdcc151 100644 --- a/packages/request-handler/src/rss-feeds.ts +++ b/packages/request-handler/src/rss-feeds.ts @@ -1,7 +1,7 @@ import type { FeedData, FeedEntry } from "@extractus/feed-extractor"; import { extract } from "@extractus/feed-extractor"; import dayjs from "dayjs"; -import { z } from "zod"; +import { z } from "zod/v4"; import type { Modify } from "@homarr/common/types"; import { logger } from "@homarr/log"; diff --git a/packages/request-handler/src/stock-price.ts b/packages/request-handler/src/stock-price.ts index 8219881d9..335e1a78e 100644 --- a/packages/request-handler/src/stock-price.ts +++ b/packages/request-handler/src/stock-price.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { z } from "zod"; +import { z } from "zod/v4"; import { fetchWithTimeout } from "@homarr/common"; diff --git a/packages/validation/package.json b/packages/validation/package.json index 63fcbc424..213a75d81 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -24,8 +24,8 @@ "dependencies": { "@homarr/definitions": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", - "zod": "^3.25.76", - "zod-form-data": "^2.0.7" + "zod": "^4.0.14", + "zod-form-data": "^3.0.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/validation/src/app.ts b/packages/validation/src/app.ts index c50b17fe0..21a4335be 100644 --- a/packages/validation/src/app.ts +++ b/packages/validation/src/app.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const appHrefSchema = z .string() diff --git a/packages/validation/src/board.ts b/packages/validation/src/board.ts index 87facae3e..b538a7076 100644 --- a/packages/validation/src/board.ts +++ b/packages/validation/src/board.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { backgroundImageAttachments, diff --git a/packages/validation/src/certificates.ts b/packages/validation/src/certificates.ts index 283d03a69..9dab3321c 100644 --- a/packages/validation/src/certificates.ts +++ b/packages/validation/src/certificates.ts @@ -1,48 +1,44 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { createCustomErrorParams } from "./form/i18n"; export const certificateValidFileNameSchema = z.string().regex(/^[\w\-. ]+$/); -export const superRefineCertificateFile = (value: File | null, context: z.RefinementCtx) => { - if (!value) { - return context.addIssue({ - code: "invalid_type", - expected: "object", - received: "null", - }); - } - - const result = certificateValidFileNameSchema.safeParse(value.name); +export const checkCertificateFile: z.core.CheckFn = (context) => { + const result = certificateValidFileNameSchema.safeParse(context.value.name); if (!result.success) { - return context.addIssue({ + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "invalidFileName", params: {}, }), + input: context.value.name, }); + return; } - if (!value.name.endsWith(".crt") && !value.name.endsWith(".pem")) { - return context.addIssue({ + if (!context.value.name.endsWith(".crt") && !context.value.name.endsWith(".pem")) { + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "invalidFileType", params: { expected: ".crt" }, }), + input: context.value.name, }); + return; } - if (value.size > 1024 * 1024) { - return context.addIssue({ + if (context.value.size > 1024 * 1024) { + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "fileTooLarge", params: { maxSize: "1 MB" }, }), + input: context.value.size, }); + return; } - - return null; }; diff --git a/packages/validation/src/common.ts b/packages/validation/src/common.ts index 20c7a175b..10cfb995c 100644 --- a/packages/validation/src/common.ts +++ b/packages/validation/src/common.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const paginatedSchema = z.object({ search: z.string().optional(), diff --git a/packages/validation/src/enums.ts b/packages/validation/src/enums.ts index 988198adc..423c28855 100644 --- a/packages/validation/src/enums.ts +++ b/packages/validation/src/enums.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; type CouldBeReadonlyArray = T[] | readonly T[]; @@ -6,6 +6,6 @@ export const zodEnumFromArray = (array: CouldBeReadonlyArray(array: CouldBeReadonlyArray) => +export const zodUnionFromArray = (array: CouldBeReadonlyArray) => // eslint-disable-next-line @typescript-eslint/no-non-null-assertion z.union([array[0]!, array[1]!, ...array.slice(2)]); diff --git a/packages/validation/src/form/i18n.spec.ts b/packages/validation/src/form/i18n.spec.ts new file mode 100644 index 000000000..3ff2cba94 --- /dev/null +++ b/packages/validation/src/form/i18n.spec.ts @@ -0,0 +1,114 @@ +import { describe, expect, test } from "vitest"; +import { z } from "zod/v4"; + +import type { TranslationFunction } from "@homarr/translation"; + +import { createCustomErrorParams, zodErrorMap } from "./i18n"; + +const expectError = (error: z.core.$ZodIssue, key: string) => { + expect(error.message).toContain(key); +}; + +describe("i18n", () => { + const t = ((key: string) => { + return `${key}`; + }) as TranslationFunction; + z.config({ + customError: zodErrorMap(t), + }); + + test("should return required error for string when passing null", () => { + const schema = z.string(); + const result = schema.safeParse(null); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "required"); + }); + test("should return required error for empty string", () => { + const schema = z.string().nonempty(); + const result = schema.safeParse(""); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "required"); + }); + test("should return invalid email error", () => { + const schema = z.string().email(); + const result = schema.safeParse("invalid-email"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "invalidEmail"); + }); + test("should return startsWith error", () => { + const schema = z.string().startsWith("test"); + const result = schema.safeParse("invalid"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "startsWith"); + }); + test("should return endsWith error", () => { + const schema = z.string().endsWith("test"); + const result = schema.safeParse("invalid"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "endsWith"); + }); + test("should return includes error", () => { + const schema = z.string().includes("test"); + const result = schema.safeParse("invalid"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "includes"); + }); + test("should return tooSmall error for string", () => { + const schema = z.string().min(5); + const result = schema.safeParse("test"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "tooSmall.string"); + }); + test("should return tooSmall error for number", () => { + const schema = z.number().min(5); + const result = schema.safeParse(3); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "tooSmall.number"); + }); + test("should return tooBig error for string", () => { + const schema = z.string().max(5); + const result = schema.safeParse("too long"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "tooBig.string"); + }); + test("should return tooBig error for number", () => { + const schema = z.number().max(5); + const result = schema.safeParse(10); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "tooBig.number"); + }); + test("should return custom error", () => { + const schema = z.string().refine((val) => val === "valid", { + params: createCustomErrorParams({ + key: "boardAlreadyExists", + params: {}, + }), + }); + + const result = schema.safeParse("invalid"); + expect(result.success).toBe(false); + if (result.success) return; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expectError(result.error.issues[0]!, "boardAlreadyExists"); + }); +}); diff --git a/packages/validation/src/form/i18n.ts b/packages/validation/src/form/i18n.ts index e1f01041a..dc64f412b 100644 --- a/packages/validation/src/form/i18n.ts +++ b/packages/validation/src/form/i18n.ts @@ -1,132 +1,139 @@ -import type { ErrorMapCtx, z, ZodTooBigIssue, ZodTooSmallIssue } from "zod"; -import { ZodIssueCode } from "zod"; +import type { z } from "zod/v4"; -import type { TranslationFunction, TranslationObject } from "@homarr/translation"; +import type { ScopedTranslationFunction, TranslationFunction, TranslationObject } from "@homarr/translation"; -export const zodErrorMap = (t: TFunction) => { - return (issue: z.ZodIssueOptionalMessage, ctx: ErrorMapCtx) => { - const error = handleZodError(issue, ctx); - if ("message" in error && error.message) { - return { - message: error.message, - }; +export const zodErrorMap = (t: TranslationFunction): z.core.$ZodErrorMap => { + return (issue) => { + const error = handleError(issue); + if (typeof error === "string") { + return error; } - return { - // use never to make ts happy - message: t(error.key ? `common.zod.${error.key}` : "common.zod.errors.default", (error.params ?? {}) as never), - }; + return t(`common.zod.errors.${error.key}`, (error.params ?? {}) as never); }; }; -const handleStringError = (issue: z.ZodInvalidStringIssue) => { - if (issue.validation === "email") { +type ValidTranslationKeys = Parameters>[0]; + +type HandlerReturnValue = + | string + | { + key: ValidTranslationKeys; + params?: Record; + }; + +const handleError = (issue: z.core.$ZodRawIssue): HandlerReturnValue => { + if (issue.code === "too_big") return handleTooBigError(issue); + if (issue.code === "too_small") return handleTooSmallError(issue); + if (issue.code === "invalid_format") return handleInvalidFormatError(issue); + if (issue.code === "invalid_type" && issue.expected === "string" && issue.input === null) { return { - key: "errors.string.invalidEmail", - } as const; + key: "required", + }; + } + if (issue.code === "custom" && issue.params?.i18n) { + const i18n = issue.params.i18n as { key: CustomErrorKey; params?: Record }; + return { + key: `custom.${i18n.key}`, + params: i18n.params, + }; } - if (typeof issue.validation === "object") { - if ("startsWith" in issue.validation) { - return { - key: "errors.string.startsWith", - params: { - startsWith: issue.validation.startsWith, - }, - } as const; - } else if ("endsWith" in issue.validation) { - return { - key: "errors.string.endsWith", - params: { - endsWith: issue.validation.endsWith, - }, - } as const; + return ( + issue.message ?? { + key: "default", } + ); +}; +const handleTooBigError = ( + issue: Pick & { message?: string }, +): HandlerReturnValue => { + if (issue.origin !== "string" && issue.origin !== "number") { + return ( + issue.message ?? { + key: "default", + } + ); + } + + const origin = issue.origin as "string" | "number"; + + return { + key: `tooBig.${origin}`, + params: { + maximum: issue.maximum.toString(), + count: issue.maximum.toString(), + }, + } as const; +}; + +const handleTooSmallError = ( + issue: Pick & { message?: string }, +): HandlerReturnValue => { + if (issue.origin !== "string" && issue.origin !== "number") { + return ( + issue.message ?? { + key: "default", + } + ); + } + + const origin = issue.origin as "string" | "number"; + if (origin === "string" && issue.minimum === 1) { return { - key: "errors.string.includes", + key: "required", + } as const; + } + return { + key: `tooSmall.${origin}`, + params: { + minimum: issue.minimum.toString(), + count: issue.minimum.toString(), + }, + } as const; +}; + +const handleInvalidFormatError = ( + issue: Pick & { message?: string }, +): HandlerReturnValue => { + if (issue.format === "includes" && "includes" in issue && typeof issue.includes === "string") { + return { + key: "string.includes", params: { - includes: issue.validation.includes, + includes: issue.includes, }, } as const; } - return { - message: issue.message, - }; -}; - -const handleTooSmallError = (issue: ZodTooSmallIssue) => { - if (issue.type !== "string" && issue.type !== "number") { + if (issue.format === "ends_with" && "suffix" in issue && typeof issue.suffix === "string") { return { - message: issue.message, - }; - } - - if (issue.type === "string" && issue.minimum === 1) { - return { - key: "errors.required", + key: "string.endsWith", + params: { + endsWith: issue.suffix, + }, } as const; } - return { - key: `errors.tooSmall.${issue.type}`, - params: { - minimum: issue.minimum, - count: issue.minimum, - }, - } as const; -}; - -const handleTooBigError = (issue: ZodTooBigIssue) => { - if (issue.type !== "string" && issue.type !== "number") { + if (issue.format === "starts_with" && "prefix" in issue && typeof issue.prefix === "string") { return { - message: issue.message, - }; - } - - return { - key: `errors.tooBig.${issue.type}`, - params: { - maximum: issue.maximum, - count: issue.maximum, - }, - } as const; -}; - -const handleZodError = (issue: z.ZodIssueOptionalMessage, ctx: ErrorMapCtx) => { - if (ctx.defaultError === "Required") { - return { - key: "errors.required", - params: {}, + key: "string.startsWith", + params: { + startsWith: issue.prefix, + }, } as const; } - if (issue.code === ZodIssueCode.invalid_string) { - return handleStringError(issue); - } - if (issue.code === ZodIssueCode.too_small) { - return handleTooSmallError(issue); - } - if (issue.code === ZodIssueCode.too_big) { - return handleTooBigError(issue); - } - if (issue.code === ZodIssueCode.invalid_type && (ctx.data === "" || issue.received === "null")) { + if (issue.format === "email") { return { - key: "errors.required", - params: {}, - } as const; - } - if (issue.code === ZodIssueCode.custom && issue.params?.i18n) { - const { i18n } = issue.params as CustomErrorParams; - return { - key: `errors.custom.${i18n.key}`, - params: i18n.params, + key: "string.invalidEmail", } as const; } - return { - message: issue.message, - }; + return ( + issue.message ?? { + key: "default", + } + ); }; type CustomErrorKey = keyof TranslationObject["common"]["zod"]["errors"]["custom"]; diff --git a/packages/validation/src/group.ts b/packages/validation/src/group.ts index d621a93d4..cc58b95b1 100644 --- a/packages/validation/src/group.ts +++ b/packages/validation/src/group.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { everyoneGroup, groupPermissionKeys } from "@homarr/definitions"; diff --git a/packages/validation/src/icons.ts b/packages/validation/src/icons.ts index 6d134097f..a577c39b6 100644 --- a/packages/validation/src/icons.ts +++ b/packages/validation/src/icons.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const iconsFindSchema = z.object({ searchText: z.string().optional(), diff --git a/packages/validation/src/integration.ts b/packages/validation/src/integration.ts index 00b3783fa..80dd1c979 100644 --- a/packages/validation/src/integration.ts +++ b/packages/validation/src/integration.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { integrationKinds, integrationPermissions, integrationSecretKinds } from "@homarr/definitions"; diff --git a/packages/validation/src/media.ts b/packages/validation/src/media.ts index 98f186c3d..cece4a19f 100644 --- a/packages/validation/src/media.ts +++ b/packages/validation/src/media.ts @@ -1,4 +1,3 @@ -import type { z } from "zod"; import { zfd } from "zod-form-data"; import { createCustomErrorParams } from "./form/i18n"; @@ -6,36 +5,30 @@ import { createCustomErrorParams } from "./form/i18n"; export const supportedMediaUploadFormats = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"]; export const mediaUploadSchema = zfd.formData({ - file: zfd.file().superRefine((value: File | null, context: z.RefinementCtx) => { - if (!value) { - return context.addIssue({ - code: "invalid_type", - expected: "object", - received: "null", - }); - } - - if (!supportedMediaUploadFormats.includes(value.type)) { - return context.addIssue({ + file: zfd.file().check((context) => { + if (!supportedMediaUploadFormats.includes(context.value.type)) { + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "invalidFileType", params: { expected: `one of ${supportedMediaUploadFormats.join(", ")}` }, }), + input: context.value.type, }); + return; } - if (value.size > 1024 * 1024 * 32) { + if (context.value.size > 1024 * 1024 * 32) { // Don't forget to update the limit in nginx.conf (client_max_body_size) - return context.addIssue({ + context.issues.push({ code: "custom", params: createCustomErrorParams({ key: "fileTooLarge", params: { maxSize: "32 MB" }, }), + input: context.value.size, }); + return; } - - return null; }), }); diff --git a/packages/validation/src/permissions.ts b/packages/validation/src/permissions.ts index 35ebe9c86..eafcd46b8 100644 --- a/packages/validation/src/permissions.ts +++ b/packages/validation/src/permissions.ts @@ -1,6 +1,6 @@ -import { z } from "zod"; +import { z } from "zod/v4"; -export const createSavePermissionsSchema = >( +export const createSavePermissionsSchema = ( permissionSchema: TPermissionSchema, ) => { return z.object({ diff --git a/packages/validation/src/search-engine.ts b/packages/validation/src/search-engine.ts index c84c77d9e..02314d6dc 100644 --- a/packages/validation/src/search-engine.ts +++ b/packages/validation/src/search-engine.ts @@ -1,5 +1,5 @@ -import type { ZodTypeAny } from "zod"; -import { z } from "zod"; +import type { ZodTypeAny } from "zod/v4"; +import { z } from "zod/v4"; import type { SearchEngineType } from "@homarr/definitions"; diff --git a/packages/validation/src/settings.ts b/packages/validation/src/settings.ts index 7110dfb3e..928f2861e 100644 --- a/packages/validation/src/settings.ts +++ b/packages/validation/src/settings.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const settingsInitSchema = z.object({ analytics: z.object({ diff --git a/packages/validation/src/shared.ts b/packages/validation/src/shared.ts index c6da17448..87bb3b41f 100644 --- a/packages/validation/src/shared.ts +++ b/packages/validation/src/shared.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { integrationKinds, widgetKinds } from "@homarr/definitions"; @@ -40,7 +40,7 @@ export const sharedItemSchema = z.object({ export const commonItemSchema = z .object({ kind: zodEnumFromArray(widgetKinds), - options: z.record(z.unknown()), + options: z.record(z.string(), z.unknown()), }) .and(sharedItemSchema); diff --git a/packages/validation/src/user.ts b/packages/validation/src/user.ts index 16bc593a0..788eb2750 100644 --- a/packages/validation/src/user.ts +++ b/packages/validation/src/user.ts @@ -1,5 +1,5 @@ import type { DayOfWeek } from "@mantine/dates"; -import { z } from "zod"; +import { z } from "zod/v4"; import { colorSchemes } from "@homarr/definitions"; import type { TranslationObject } from "@homarr/translation"; @@ -38,9 +38,10 @@ export const userPasswordSchema = z }, ); -const addConfirmPasswordRefinement = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - schema: z.ZodObject, +const addConfirmPasswordRefinement = < + TSchema extends z.ZodObject<{ password: z.core.$ZodString; confirmPassword: z.core.$ZodString }, z.core.$strip>, +>( + schema: TSchema, ) => { return schema.refine((data) => data.password === data.confirmPassword, { path: ["confirmPassword"], @@ -55,7 +56,7 @@ export const userBaseCreateSchema = z.object({ username: usernameSchema, password: userPasswordSchema, confirmPassword: z.string(), - email: z.string().email().or(z.string().length(0).optional()), + email: z.string().email().or(z.string().length(0)).optional(), groupIds: z.array(z.string()), }); @@ -121,7 +122,15 @@ export const userChangeColorSchemeSchema = z.object({ }); export const userFirstDayOfWeekSchema = z.object({ - firstDayOfWeek: z.custom((value) => z.number().min(0).max(6).safeParse(value).success), + firstDayOfWeek: z + .custom((value) => z.number().min(0).max(6).safeParse(value).success) + .meta({ + override: { + type: "integer", + minimum: 0, + maximum: 6, + }, + }), }); export const userPingIconsEnabledSchema = z.object({ diff --git a/packages/validation/src/widgets/media-request.ts b/packages/validation/src/widgets/media-request.ts index aa0f85c6e..65cad88f3 100644 --- a/packages/validation/src/widgets/media-request.ts +++ b/packages/validation/src/widgets/media-request.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const mediaRequestOptionsSchema = z.object({ mediaId: z.number(), diff --git a/packages/widgets/package.json b/packages/widgets/package.json index c420c6558..1aba6cc2c 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -70,6 +70,7 @@ "@tiptap/starter-kit": "^2.26.1", "clsx": "^2.1.1", "dayjs": "^1.11.13", + "mantine-form-zod-resolver": "^1.2.1", "mantine-react-table": "2.0.0-beta.9", "next": "15.4.6", "react": "19.1.1", @@ -77,7 +78,7 @@ "react-markdown": "^10.1.0", "recharts": "^2.15.4", "video.js": "^8.23.4", - "zod": "^3.25.76" + "zod": "^4.0.14" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", diff --git a/packages/widgets/src/calendar/index.ts b/packages/widgets/src/calendar/index.ts index 088bdf6ef..d03b2f610 100644 --- a/packages/widgets/src/calendar/index.ts +++ b/packages/widgets/src/calendar/index.ts @@ -1,5 +1,5 @@ import { IconCalendar } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { radarrReleaseTypes } from "@homarr/integrations/types"; diff --git a/packages/widgets/src/downloads/index.ts b/packages/widgets/src/downloads/index.ts index edd4506e0..c00044aa9 100644 --- a/packages/widgets/src/downloads/index.ts +++ b/packages/widgets/src/downloads/index.ts @@ -1,5 +1,5 @@ import { IconDownload } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; import type { ExtendedDownloadClientItem } from "@homarr/integrations"; diff --git a/packages/widgets/src/media-transcoding/index.ts b/packages/widgets/src/media-transcoding/index.ts index 0103f480a..2753925d8 100644 --- a/packages/widgets/src/media-transcoding/index.ts +++ b/packages/widgets/src/media-transcoding/index.ts @@ -1,5 +1,5 @@ import { IconTransform } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { capitalize } from "@homarr/common"; import { getIntegrationKindsByCategory } from "@homarr/definitions"; diff --git a/packages/widgets/src/minecraft/server-status/index.ts b/packages/widgets/src/minecraft/server-status/index.ts index 1f258e75c..c5cd0e068 100644 --- a/packages/widgets/src/minecraft/server-status/index.ts +++ b/packages/widgets/src/minecraft/server-status/index.ts @@ -1,5 +1,5 @@ import { IconBrandMinecraft } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createWidgetDefinition } from "../../definition"; import { optionsBuilder } from "../../options"; diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx index 8e6c56c8a..d05ac95c1 100644 --- a/packages/widgets/src/modals/widget-edit-modal.tsx +++ b/packages/widgets/src/modals/widget-edit-modal.tsx @@ -2,11 +2,11 @@ import { useState } from "react"; import { Button, Group, Stack } from "@mantine/core"; -import { z } from "zod"; +import { zod4Resolver } from "mantine-form-zod-resolver"; +import { z } from "zod/v4"; import { objectEntries } from "@homarr/common"; import type { WidgetKind } from "@homarr/definitions"; -import { zodResolver } from "@homarr/form"; import { createModal, useModalAction } from "@homarr/modals"; import type { SettingsContextProps } from "@homarr/settings/creator"; import { useI18n } from "@homarr/translation/client"; @@ -41,14 +41,16 @@ export const WidgetEditModal = createModal>(({ actions, i const [advancedOptions, setAdvancedOptions] = useState(innerProps.value.advancedOptions); // Translate the error messages - z.setErrorMap(zodErrorMap(t)); + z.config({ + customError: zodErrorMap(t), + }); const { definition } = widgetImports[innerProps.kind]; const options = definition.createOptions(innerProps.settings) as Record; const form = useForm({ mode: "controlled", initialValues: innerProps.value, - validate: zodResolver( + validate: zod4Resolver( z.object({ options: z.object( objectEntries(options).reduce( diff --git a/packages/widgets/src/options.ts b/packages/widgets/src/options.ts index 194759396..d0fe2cdfb 100644 --- a/packages/widgets/src/options.ts +++ b/packages/widgets/src/options.ts @@ -1,8 +1,8 @@ import type React from "react"; import type { DraggableAttributes, UniqueIdentifier } from "@dnd-kit/core"; import type { ActionIconProps } from "@mantine/core"; -import { z } from "zod"; -import type { ZodType } from "zod"; +import { z } from "zod/v4"; +import type { ZodType } from "zod/v4"; import type { IntegrationKind } from "@homarr/definitions"; diff --git a/packages/widgets/src/releases/index.ts b/packages/widgets/src/releases/index.ts index 42081a9c2..84c4f9998 100644 --- a/packages/widgets/src/releases/index.ts +++ b/packages/widgets/src/releases/index.ts @@ -1,5 +1,5 @@ import { IconRocket } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; diff --git a/packages/widgets/src/rssFeed/index.ts b/packages/widgets/src/rssFeed/index.ts index cd6a66c06..058f1fe51 100644 --- a/packages/widgets/src/rssFeed/index.ts +++ b/packages/widgets/src/rssFeed/index.ts @@ -1,5 +1,5 @@ import { IconRss } from "@tabler/icons-react"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; diff --git a/packages/widgets/src/weather/index.ts b/packages/widgets/src/weather/index.ts index e120c5783..c8eb3f108 100644 --- a/packages/widgets/src/weather/index.ts +++ b/packages/widgets/src/weather/index.ts @@ -1,6 +1,6 @@ import { IconCloud } from "@tabler/icons-react"; import dayjs from "dayjs"; -import { z } from "zod"; +import { z } from "zod/v4"; import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0335615aa..802c623fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,8 +302,8 @@ importers: specifier: ^1.8.1 version: 1.8.1(react@19.1.1) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -548,6 +548,9 @@ importers: '@homarr/common': specifier: workspace:^0.1.0 version: link:../common + '@homarr/core': + specifier: workspace:^0.1.0 + version: link:../core '@homarr/cron-job-api': specifier: workspace:^0.1.0 version: link:../cron-job-api @@ -630,11 +633,11 @@ importers: specifier: 2.2.2 version: 2.2.2 trpc-to-openapi: - specifier: ^2.4.0 - version: 2.4.0(@trpc/server@11.4.4(typescript@5.9.2))(zod-openapi@2.19.0(zod@3.25.76))(zod@3.25.76) + specifier: ^3.0.0 + version: 3.0.0(@trpc/server@11.4.4(typescript@5.9.2))(zod-openapi@5.3.0(zod@4.0.14))(zod@4.0.14) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -706,8 +709,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -857,11 +860,11 @@ importers: specifier: 7.13.0 version: 7.13.0 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 zod-validation-error: specifier: ^3.5.3 - version: 3.5.3(zod@3.25.76) + version: 3.5.3(zod@4.0.14) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -883,13 +886,13 @@ importers: dependencies: '@t3-oss/env-nextjs': specifier: ^0.13.8 - version: 0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@3.25.76) + version: 0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@4.0.14) ioredis: specifier: 5.7.0 version: 5.7.0 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -940,8 +943,8 @@ importers: specifier: 19.1.1 version: 19.1.1 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1130,8 +1133,8 @@ importers: specifier: ^0.44.4 version: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3) drizzle-zod: - specifier: ^0.7.1 - version: 0.7.1(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@3.25.76) + specifier: ^0.8.2 + version: 0.8.2(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@4.0.14) mysql2: specifier: 3.14.3 version: 3.14.3 @@ -1179,8 +1182,8 @@ importers: specifier: ^5.2.5 version: 5.2.5 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1246,9 +1249,12 @@ importers: '@mantine/form': specifier: ^8.2.4 version: 8.2.4(react@19.1.1) + mantine-form-zod-resolver: + specifier: ^1.2.1 + version: 1.2.1(@mantine/form@8.2.4(react@19.1.1))(zod@4.0.14) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1296,8 +1302,8 @@ importers: specifier: 19.1.1 version: 19.1.1 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1449,8 +1455,8 @@ importers: specifier: ^0.6.2 version: 0.6.2 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1486,8 +1492,8 @@ importers: specifier: 3.17.0 version: 3.17.0 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1593,8 +1599,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1697,11 +1703,11 @@ importers: specifier: 2.2.2 version: 2.2.2 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 zod-form-data: - specifier: ^2.0.7 - version: 2.0.7(zod@3.25.76) + specifier: ^3.0.0 + version: 3.0.0(zod@4.0.14) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1728,8 +1734,8 @@ importers: specifier: workspace:^0.1.0 version: link:../common zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2127,11 +2133,11 @@ importers: specifier: workspace:^0.1.0 version: link:../translation zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 zod-form-data: - specifier: ^2.0.7 - version: 2.0.7(zod@3.25.76) + specifier: ^3.0.0 + version: 3.0.0(zod@4.0.14) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -2286,6 +2292,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + mantine-form-zod-resolver: + specifier: ^1.2.1 + version: 1.2.1(@mantine/form@8.2.4(react@19.1.1))(zod@4.0.14) mantine-react-table: specifier: 2.0.0-beta.9 version: 2.0.0-beta.9(@mantine/core@8.2.4(@mantine/hooks@8.2.4(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/dates@8.2.4(@mantine/core@8.2.4(@mantine/hooks@8.2.4(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/hooks@8.2.4(react@19.1.1))(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/hooks@8.2.4(react@19.1.1))(@tabler/icons-react@3.34.1(react@19.1.1))(clsx@2.1.1)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -2308,8 +2317,8 @@ importers: specifier: ^8.23.4 version: 8.23.4 zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.0.14 + version: 4.0.14 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -5967,8 +5976,8 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crossws@0.3.4: - resolution: {integrity: sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==} + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} @@ -6374,11 +6383,11 @@ packages: sqlite3: optional: true - drizzle-zod@0.7.1: - resolution: {integrity: sha512-nZzALOdz44/AL2U005UlmMqaQ1qe5JfanvLujiTHiiT8+vZJTBFhj3pY4Vk+L6UWyKFfNmLhk602Hn4kCTynKQ==} + drizzle-zod@0.8.2: + resolution: {integrity: sha512-9Do/16OjFFNrQDZgvMtxtDDwKWbFOxUAIwNPKX98SfxrP8H18vhN1BvNXbhelLcdgCE7GEaXDJqBjMExSkhpkA==} peerDependencies: drizzle-orm: '>=0.36.0' - zod: '>=3.0.0' + zod: ^3.25.1 dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -8012,6 +8021,13 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + mantine-form-zod-resolver@1.2.1: + resolution: {integrity: sha512-8Y7jX3ybZEhQeLzkbrzCS9gulnCX7sgjM5bCkRDxQHbmal8V5lLM9yZq583UEme8DuM77x/BJNTrBkYZo+JUFw==} + engines: {node: '>=16.6.0'} + peerDependencies: + '@mantine/form': '>=7.0.0' + zod: '>=3.25.0' + mantine-react-table@2.0.0-beta.9: resolution: {integrity: sha512-ZdfcwebWaPERoDvAuk43VYcBCzamohARVclnbuepT0PHZ0wRcDPMBR+zgaocL+pFy8EXUGwvWTOKNh25ITpjNQ==} engines: {node: '>=16'} @@ -8428,8 +8444,8 @@ packages: peerDependencies: webpack: ^5.0.0 - node-mock-http@1.0.0: - resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} + node-mock-http@1.0.2: + resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==} node-plop@0.26.3: resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==} @@ -10213,12 +10229,12 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - trpc-to-openapi@2.4.0: - resolution: {integrity: sha512-B6xrwOC3Ab0q1BWD/QbJzK4OUpCLoT02hAzshSUXEuIZGcJZkMG/OJ4/3gd20dyr8aI+CrFirpWKRIo7JmHbMQ==} + trpc-to-openapi@3.0.0: + resolution: {integrity: sha512-iyG1rmEGzsQeoFn+AcP0fGhDuzh/A7u2MHDUYMAUdaSt72koSG7RJxO9K/7YW9e8vHQcJtVG/Zy+8DvYOr/jWw==} peerDependencies: '@trpc/server': ^11.1.0 - zod: ^3.23.8 - zod-openapi: 4.2.4 + zod: ^4.0.0 + zod-openapi: ^5.0.1 ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} @@ -10990,16 +11006,16 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-form-data@2.0.7: - resolution: {integrity: sha512-O27uzKMx7qc7z51KXER326Fp966jqHGvZX3i18CbvElF/QqVsQQN6Q7BnzepkzeBzTJnU3golibVSagf4dp7RQ==} + zod-form-data@3.0.0: + resolution: {integrity: sha512-jTQbmXQU5wMfI+ERZH6EuATzeD9FEOviPasLL2hCUP+FcLagXLtboUM95PHsSOFseL8Nv91YKlwln3zo1sAS2g==} peerDependencies: - zod: '>= 3.11.0' + zod: '>= 3.25.0' - zod-openapi@2.19.0: - resolution: {integrity: sha512-OUAAyBDPPwZ9u61i4k/LieXUzP2re8kFjqdNh2AvHjsyi/aRNz9leDAtMGcSoSzUT5xUeQoACJufBI6FzzZyxA==} - engines: {node: '>=16.11'} + zod-openapi@5.3.0: + resolution: {integrity: sha512-hv3DU37kV39v0MEiQ0vcL06NLS9bTHTb7FNtuFk2zhuATsi+UNcNN6y03z7qbI9NdC6LJS+Knk5shOSCUMwsDQ==} + engines: {node: '>=20'} peerDependencies: - zod: ^3.21.4 + zod: ^3.25.74 || ^4.0.0 zod-validation-error@3.5.3: resolution: {integrity: sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==} @@ -11007,8 +11023,8 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.0.14: + resolution: {integrity: sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -13107,19 +13123,19 @@ snapshots: dependencies: tslib: 2.8.1 - '@t3-oss/env-core@0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@3.25.76)': + '@t3-oss/env-core@0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@4.0.14)': optionalDependencies: arktype: 2.1.20 typescript: 5.9.2 - zod: 3.25.76 + zod: 4.0.14 - '@t3-oss/env-nextjs@0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@3.25.76)': + '@t3-oss/env-nextjs@0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@4.0.14)': dependencies: - '@t3-oss/env-core': 0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@3.25.76) + '@t3-oss/env-core': 0.13.8(arktype@2.1.20)(typescript@5.9.2)(zod@4.0.14) optionalDependencies: arktype: 2.1.20 typescript: 5.9.2 - zod: 3.25.76 + zod: 4.0.14 '@tabler/icons-react@3.34.1(react@19.1.1)': dependencies: @@ -14921,7 +14937,7 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crossws@0.3.4: + crossws@0.3.5: dependencies: uncrypto: 0.1.3 @@ -15223,10 +15239,10 @@ snapshots: gel: 2.0.0 mysql2: 3.14.3 - drizzle-zod@0.7.1(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@3.25.76): + drizzle-zod@0.8.2(drizzle-orm@0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3))(zod@4.0.14): dependencies: drizzle-orm: 0.44.4(@libsql/client-wasm@0.14.0)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.2.0)(gel@2.0.0)(mysql2@3.14.3) - zod: 3.25.76 + zod: 4.0.14 dunder-proto@1.0.1: dependencies: @@ -16366,11 +16382,11 @@ snapshots: h3@1.15.1: dependencies: cookie-es: 1.2.2 - crossws: 0.3.4 + crossws: 0.3.5 defu: 6.1.4 destr: 2.0.3 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.0 + node-mock-http: 1.0.2 radix3: 1.1.2 ufo: 1.6.1 uncrypto: 0.1.3 @@ -17276,6 +17292,11 @@ snapshots: make-error@1.3.6: {} + mantine-form-zod-resolver@1.2.1(@mantine/form@8.2.4(react@19.1.1))(zod@4.0.14): + dependencies: + '@mantine/form': 8.2.4(react@19.1.1) + zod: 4.0.14 + mantine-react-table@2.0.0-beta.9(@mantine/core@8.2.4(@mantine/hooks@8.2.4(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/dates@8.2.4(@mantine/core@8.2.4(@mantine/hooks@8.2.4(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/hooks@8.2.4(react@19.1.1))(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@mantine/hooks@8.2.4(react@19.1.1))(@tabler/icons-react@3.34.1(react@19.1.1))(clsx@2.1.1)(dayjs@1.11.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@mantine/core': 8.2.4(@mantine/hooks@8.2.4(react@19.1.1))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -17809,7 +17830,7 @@ snapshots: loader-utils: 2.0.4 webpack: 5.94.0 - node-mock-http@1.0.0: {} + node-mock-http@1.0.2: {} node-plop@0.26.3: dependencies: @@ -19912,14 +19933,14 @@ snapshots: trough@2.2.0: {} - trpc-to-openapi@2.4.0(@trpc/server@11.4.4(typescript@5.9.2))(zod-openapi@2.19.0(zod@3.25.76))(zod@3.25.76): + trpc-to-openapi@3.0.0(@trpc/server@11.4.4(typescript@5.9.2))(zod-openapi@5.3.0(zod@4.0.14))(zod@4.0.14): dependencies: '@trpc/server': 11.4.4(typescript@5.9.2) co-body: 6.2.0 h3: 1.15.1 openapi3-ts: 4.4.0 - zod: 3.25.76 - zod-openapi: 2.19.0(zod@3.25.76) + zod: 4.0.14 + zod-openapi: 5.3.0(zod@4.0.14) optionalDependencies: '@rollup/rollup-linux-x64-gnu': 4.6.1 @@ -20781,19 +20802,19 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.5.2 - zod-form-data@2.0.7(zod@3.25.76): + zod-form-data@3.0.0(zod@4.0.14): dependencies: '@rvf/set-get': 7.0.1 - zod: 3.25.76 + zod: 4.0.14 - zod-openapi@2.19.0(zod@3.25.76): + zod-openapi@5.3.0(zod@4.0.14): dependencies: - zod: 3.25.76 + zod: 4.0.14 - zod-validation-error@3.5.3(zod@3.25.76): + zod-validation-error@3.5.3(zod@4.0.14): dependencies: - zod: 3.25.76 + zod: 4.0.14 - zod@3.25.76: {} + zod@4.0.14: {} zwitch@2.0.4: {}