chore(server): update zod to 4.4.3 and nestjs-zod to 5.5.0 (#30532)

This commit is contained in:
Timon 2026-08-24 10:59:22 +02:00 committed by GitHub
parent cbf5d83a69
commit 2feb889b9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 39 additions and 26 deletions

View file

@ -4947,7 +4947,6 @@
"in": "query",
"description": "Return edited asset if available",
"schema": {
"default": false,
"type": "boolean"
}
},
@ -5035,7 +5034,6 @@
"in": "query",
"description": "Return edited asset if available",
"schema": {
"default": false,
"type": "boolean"
}
},
@ -25478,7 +25476,6 @@
"type": "string"
},
"expiresAt": {
"default": null,
"description": "Expiration date",
"example": "2024-01-01T00:00:00.000Z",
"format": "date-time",

18
pnpm-lock.yaml generated
View file

@ -575,8 +575,8 @@ importers:
specifier: ^8.0.0
version: 8.1.0(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(rxjs@7.8.2)
nestjs-zod:
specifier: ^5.3.0
version: 5.5.0(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/swagger@11.4.6(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(@typescript/typescript6@6.0.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6)
specifier: ^5.5.0
version: 5.5.0(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/swagger@11.4.6(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(@typescript/typescript6@6.0.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.4.3)
nodemailer:
specifier: ^9.0.0
version: 9.0.5
@ -641,8 +641,8 @@ importers:
specifier: ^13.12.0
version: 13.15.35
zod:
specifier: 4.3.6
version: 4.3.6
specifier: 4.4.3
version: 4.4.3
devDependencies:
'@eslint/js':
specifier: ^10.0.0
@ -13066,8 +13066,8 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zod@4.4.3:
resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
zwitch@1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
@ -23363,12 +23363,12 @@ snapshots:
rxjs: 7.8.2
tslib: 2.8.1
nestjs-zod@5.5.0(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/swagger@11.4.6(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(@typescript/typescript6@6.0.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.3.6):
nestjs-zod@5.5.0(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/swagger@11.4.6(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(@typescript/typescript6@6.0.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.4.3):
dependencies:
'@nestjs/common': 11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1)
deepmerge: 4.3.1
rxjs: 7.8.2
zod: 4.3.6
zod: 4.4.3
optionalDependencies:
'@nestjs/swagger': 11.4.6(@nestjs/common@11.1.29(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/core@11.1.29)(@typescript/typescript6@6.0.2)(reflect-metadata@0.2.2)
@ -27081,7 +27081,7 @@ snapshots:
compress-commons: 6.0.2
readable-stream: 4.7.0
zod@4.3.6: {}
zod@4.4.3: {}
zwitch@1.0.5: {}

View file

@ -94,7 +94,7 @@
"nestjs-cls": "^6.0.0",
"nestjs-kysely": "3.1.2",
"nestjs-otel": "^8.0.0",
"nestjs-zod": "^5.3.0",
"nestjs-zod": "^5.5.0",
"nodemailer": "^9.0.0",
"openid-client": "^6.3.3",
"pg": "^8.11.3",
@ -116,7 +116,7 @@
"ua-parser-js": "^2.0.0",
"uuid": "^14.0.0",
"validator": "^13.12.0",
"zod": "4.3.6"
"zod": "4.4.3"
},
"devDependencies": {
"@eslint/js": "^10.0.0",

View file

@ -1,7 +1,7 @@
import { createZodDto } from 'nestjs-zod';
import { HistoryBuilder } from 'src/decorators';
import { AssetMetadataUpsertItemSchema } from 'src/dtos/asset.dto';
import { AssetVisibilitySchema } from 'src/enum';
import { ApiCustomExtension, AssetVisibilitySchema } from 'src/enum';
import { isoDatetimeToDate, JsonParsed, stringToBool } from 'src/validation';
import z from 'zod';
@ -40,8 +40,15 @@ const AssetMediaBaseSchema = z.object({
fileModifiedAt: isoDatetimeToDate.describe('File modification date'),
duration: z.coerce.number().int().min(0).optional().describe('Duration in milliseconds (for videos)'),
filename: z.string().optional().describe('Filename'),
/** The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled in the controller. */
[UploadFieldName.ASSET_DATA]: z.any().describe('Asset file data').meta({ type: 'string', format: 'binary' }),
/**
* The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled
* in the controller. File parts never reach the request body, so they must be optional here
*/
[UploadFieldName.ASSET_DATA]: z
.any()
.optional()
.describe('Asset file data')
.meta({ type: 'string', format: 'binary', [ApiCustomExtension.Required]: true }),
});
const AssetMediaCreateSchema = AssetMediaBaseSchema.extend({

View file

@ -36,15 +36,12 @@ import z from 'zod';
const { Admin, User, Public } = ConfigVisibility;
const configBool = z
.preprocess((val) => {
if (val === 'true') {
return true;
}
if (val === 'false') {
return false;
}
return val;
}, z.boolean())
.preprocess(
(val) => z.stringbool({ truthy: ['true'], falsy: ['false'], case: 'sensitive' }).safeParse(val).data ?? val,
z.boolean(),
)
.nonoptional()
.meta({ type: 'boolean' });
const cronExpressionSchema = z

View file

@ -31,6 +31,7 @@ const DownloadArchiveSchema = z
// which would limit this DTO to a maximum of 1000 assets.
assetIds: z
.preprocess((val) => (typeof val === 'string' ? val.split(',') : val), z.array(z.uuidv4()))
.nonoptional()
.describe('Asset IDs'),
edited: z
.preprocess((val) => {

View file

@ -603,6 +603,7 @@ export enum ApiCustomExtension {
AdminOnly = 'x-immich-admin-only',
History = 'x-immich-history',
State = 'x-immich-state',
Required = 'x-immich-required',
}
export enum MetadataKey {

View file

@ -211,6 +211,16 @@ const patchOpenAPI = (document: OpenAPIObject) => {
for (const schema of Object.values(schemas)) {
delete (schema as Record<string, unknown>).id;
// documents a property as required even though it is optional during body validation
for (const [key, value] of Object.entries(schema.properties ?? {})) {
if (!(ApiCustomExtension.Required in value)) {
continue;
}
delete (value as Record<string, unknown>)[ApiCustomExtension.Required];
(schema.required ??= []).push(key);
}
}
document.components.schemas = sortKeys(schemas);