Repository navigation
💡 [REQUEST] - Migrer apps/server-nestjs vers Zod 4 (catalog isolé, RC @ts-rest 3.53) #2558
Description
Description
Migrer le dépôt cloud-pi-native/console vers Zod 4 (actuellement zod@^3.25.76 via le catalog runtime), cible prioritaire apps/server-nestjs.
Problème : ce n'est pas un correctif de sécurité (Zod 3 reste maintenu, séries 3.25.x). Modernisation optionnelle, basse urgence. Le vrai verrou est la topologie des dépendances : server-nestjs compose des schémas Zod issus de @cpn-console/shared (zod 3) et @cpn-console/hooks (zod 3) — pas seulement il les valide.
Spécifications techniques
Constats vérifiés (commentaires de l'issue + expérimentation en workspace isolé zod4) :
zod= catalog partagé (catalogs.runtime,pnpm-workspace.yaml:118,catalogMode: strict).apps/server-nestjscompose des schémas Zod de@cpn-console/shared(ex.ProjectSchemaV2.omit().extend()dansproject.utils.ts) et de@cpn-console/hooks(ex.editStrippersGeneratordansproject-services.utils.ts), pas seulement il les passe auZodValidationPipe.ZodValidationPipe(zod-validation.pipe.ts) n'importe zod qu'enimport type { ZodSchema }(effacé au runtime) et appelleschema.safeParse(...). → Le pipe seul est agnostique de version. Mais ce n'est pas le seul point de contact : les sites de composition (.extend(),.merge()) croisent réellement les deux majorités.@ts-rest:core/fastifyen RC3.53.0-rc.1ont supprimé le peerzod, mais@ts-rest/open-api@3.53.0-rc.1gardepeerDependencies.zod: "^3.22.3".open-apiimporté uniquement parapps/server/src(figé). →@ts-resta été retiré deserver-nestjs(0 import source) lors de l'expérimentation ; ce n'était pas le vrai verrou.
Résultat de l'expérimentation (workspace zod4, catalog zod4: { zod: ^4.4.3 } isolé sur server-nestjs)
- Build
pnpm build: VERT (0 erreur) après corrections mécaniques (catalog isolé, drop@ts-rest, pipe version-agnostique, retrait deZodTypeDef,base.config.default(0),csven.transform()). - Tests
pnpm test: 572 passés / 2 échoués → en isolation 1 fail :project-services.service.spec.ts > servicesService > update stores project configuration through the real utility. - Cause racine (runtime) :
@cpn-console/hooksest en zod 3.25.76.buildProjectEditStrippers(project-services.utils.ts:39-41) faitglobal = global.merge(z.object({ [service.name]: editZod.global.default({}) }))oùeditZodprovient deeditStrippersGenerator.parse(service.config)(hooks, zod-3). Zod 4normalizeDefrejette le schéma zod-3 embarqué →Error: Invalid element at key "...": expected a Zod schema.
→ Path A REFUTÉ empiriquement : le build compile (l'alias ZodType de zod 4 est assez laxiste pour le typecheck), mais la frontière dual-major se manifeste AU RUNTIME. Isoler zod sur server-nestjs ne suffit pas : les schémas de shared/hooks (zod 3) y sont composés, pas seulement validés.
Chemins évalués
- Path A — catalog isolé
server-nestjs(zod 4) :❌ NON viable. Build vert mais crash runtime viabuildProjectEditStrippers/editStrippersGenerator(@cpn-console/hookszod 3). Le pipe étant version-agnostique ne suffit pas ; les sites de composition croisent les deux majorités. - Path B — bump workspace-wide (shared + hooks + server-nestjs, figer
apps/server) : seul chemin viable si migration, mais blast-radius maximal (doit migrershared+hooks+server-nestjsensemble, et résoudre le peerzod@^3de@ts-rest/open-apiconsommé parapps/serverfigé). - Path C — rester sur Zod 3 : aucun coût (série
3.25.xmaintenue). Recommandé tant que la migration workspace-wide n'est pas planifiée.
Définition du fini
- Recenser la douleur (commentaire PAIN) : basse urgence, contrat client +
zod-validation-error@5(si partagé) + audit enum - Recenser le chemin (commentaire PATH) + rectification initiale : Path A cru viable (pipe version-agnostique)
- REFUTATION Path A (empirique) : build vert + 1 fail runtime — dual-major au runtime via
buildProjectEditStrippers/editStrippersGenerator(@cpn-console/hookszod 3.25.76) - Décider : Path B (migration workspace-wide
shared+hooks+server-nestjs, coordonnée) OU Path C (rester Zod 3) - Si Path B : planifier la migration coordonnée
shared→zod4,hooks→zod4, puisserver-nestjs; résoudre le peer@ts-rest/open-api@^3(figerapps/serverou attendre@ts-reststable sans peer zod-3) - Si Path B :
tsc/pnpm buildverts +pnpm testverts sur l'ensemble des paquets migrés
Activity
shikanime commentedon Aug 24, 2026
Recherche — chemin de migration (Path A écarté)
Fait vérifié décisif (non couvert par l'RC @ts-rest)
apps/server-nestjsimporte des schémas zod depuis@cpn-console/sharedet les valide via son propreZodValidationPipe:CreateRepositorySchemaest donc compilé en zod 3 (danspackages/shared) puis validé par un pipe zod 4 (dansapps/server-nestjs).Conséquence : un catalog isolé
apps/server-nestjs(Path A) mettrait deux versions majeures de zod dans un même runtime → incompatibilité de type à la compilation (leZodSchemazod-4 n'accepte pas un export zod-3) et frontière d'instances zod dupliquées. Path A est écarté.pnpm multi-catalog : confirmé mais insuffisant ici
pnpm-workspace.yamlsupporte bien plusieurs catalogs nommés (catalog:name), et la doc cite explicitement le cas « repo multi-package migrant une dépendance par morceaux ».Mais comme
sharedetserver-nestjspartagent des schémas zod à l'exécution, ils doivent monter ensemble en zod 4. Un catalog isolé ne résout pas la dépendance croisée.Chemin retenu : workspace-wide (Path B), bloqué sur @ts-rest/open-api
zod->^4etzod-validation-error->^5.0.0dans le catalogruntime(les deux ensemble, carpackages/sharedutilisefromZodError).@ts-rest/core+@ts-rest/fastify->3.53.0-rc.1(peerzodsupprimé, Standard Schema).@ts-rest/open-api@3.53.0-rc.1gardepeerDependencies.zod: "^3.22.3". Il est importé uniquement parapps/server/src(serveur Fastify historique, figé) et déclaré-mais-inutilisé dansapps/server-nestjs. Un bump workspace-wide viole ce peer tant queapps/serverreste sur la branche zod-3.Sources officielles
catalog:namesupporté)zod@^3.22.3: registry npm (vérifié)^3.25.0 || ^4.0.0: registry npm (vérifié)Décision proposée
La migration n'est faisable proprement que quand
@ts-rest/open-api(ou la stable 3.53) supprime son peer zod-3, OU siapps/serverest retiré du workspace zod-4. Jusque-là : blocker documenté, pas de bump.shikanime commentedon Aug 24, 2026
Recherche — la douleur (pain) du passage Zod 3 → 4
1. Coût de RESTER en Zod 3 : aucun (point crucial)
Zod 3 est toujours maintenu (séries
3.25.xactives). Le passage en 4 n'est pas un correctif de sécurité : c'est une modernisation optionnelle (parsing ~15x plus rapide, ~100x moins d'instantiationstsc, bundle réduit). Aucune urgence à imposer au backlog.2. Breaking changes qui touchent CE repo (si on migre)
ZodError.messagea changé : avant =JSON.stringify(issues), maintenant = message lisible. Impact direct surpackages/shared/src/utils/schemas.ts(parseZodError=fromZodError(zodError).toString()). Nécessitezod-validation-error@5(compatible zod 4) — à bump synchrone avec zod.error.flatten(): forme de sortie modifiée (zod-validation.pipe.ts:12→throw new BadRequestException(result.error.flatten())). Le contrat d'erreur de validation envoyé au client change ; le client (apps/client) doit être vérifié pour ne pas casser l'affichage des erreurs de champ.transform()renvoie une instanceZodPipe(et non plusZodEffects).apps/server-nestjs/src/config/config.utils.ts(flag(),csv()) chaîne.transform/.pipe— fonctionnellement OK, mais toutinstanceof ZodEffects(absent ici) casserait.superRefine+z.ZodIssueCode.custom: conservé (config.utils.ts:62,66). Pas de changement requis sur nosctx.addIssue.errorMapretiré au profit d'un callbackerror;invalid_type_error/required_errorsupprimés. À vérifier (non utilisé directement a priori).z.nativeEnumdéprécié → préférerz.enum. À auditer danspackages/shared(schémas d'enums).3. Effort de migration (réel)
zod@^4+zod-validation-error@^5(sinonfromZodErrorcasse).z.nativeEnum/instanceof ZodEffects/errorMap.packages/shared/src/utils/schemas.spec.ts:39(safeParse(undefined).error instanceof ZodError) et:222(parseZodError) doivent être revalidés sous zod 4.Conclusion douleur
La migration est basse-urgence, coût moyen, confinement maîtrisable (essentiellement
zod-validation-error+ contrat client + audit enum). Le vrai verrou n'est pas la douleur zod, mais le peer@ts-rest/open-api@zod@^3(voir commentaire précédent) qui impose un bump workspace-wide.Sources officielles
@ts-restStandard Schema / Zod4 (RC) : https://github.com/ts-rest/ts-rest/releases/tag/v3.53.0-rc.1zod-validation-errorcompat zod 4 (v5.0.0, peer^3.25.0 || ^4.0.0) : registry npmshikanime commentedon Aug 24, 2026
Rectification — Path A n'est PAS mort (correction du commentaire précédent)
Erreur dans mon commentaire PATH : j'écrivais qu'un catalog isolé
server-nestjscréerait une « frontière d'instances zod dupliquées » et casserait à l'exécution. Faux. Réanalyse deapps/server-nestjs/src/modules/infrastructure/pipe/zod-validation.pipe.ts:CreateRepositorySchema(zod 3, compilé danspackages/shared) est passé tel quel au pipe. Le pipe invoque le.safeParsepropre au schéma → exécution 100% zod 3, sans jamais charger le zod 4 deserver-nestjs(qui n'est qu'unimport type).Risque résiduel réel (beaucoup plus étroit)
ZodSchemaimporté en zod 4 (server-nestjs) vs schéma exporté typé zod 3 (shared). Si structurellement incompatible,tscéchoue à la compilation du pipe. À vérifier par un build, pas un crash runtime.3.53.0-rc.1est prérelease ;catalogMode: strict+minimumReleaseAge: 1440peuvent gêner — pin explicite ouoverrides.Précision sur Standard Schema
Le point du commentaire PATH (« @ts-rest/core@3.53 valide les schémas des deux majors via Standard Schema ») est vrai mais orthogonal : il concerne la validation des contrats FAITE PAR @ts-rest, pas notre
ZodValidationPipe(qui appelle.safeParsedirectement). Ne pas le citer comme justification du pipe.Verdict corrigé
server-nestjs) = viable, risque réel = compat type TS + pin RC. C'est le chemin de migration à plus faible blast-radius (laissesharedetapps/serverintacts).shared+apps/serverfigé qui consomme@ts-rest/open-apizod-3).Action de vérification restante
Build
server-nestjsavec zod 4 catalog isolé + schémas zod-3 desharedpassés au pipe → confirmer quetscaccepte le typeZodSchemainter-major. C'est le seul point non résolu.[-]💡 [REQUEST] - Migrer vers Zod 4 (blocage @ts-rest peer zod@^3)[/-][+]💡 [REQUEST] - Migrer apps/server-nestjs vers Zod 4 (catalog isolé, RC @ts-rest 3.53)[/+]shikanime commentedon Aug 24, 2026
🔁 Rectification — Path A REFUTÉ (preuve empirique)
L'affirmation précédente (« Path A viable, risque = type TS + pin RC ») est infirmée par l'expérimentation en workspace isolé
zod4.Ce qui a été fait
zod4: { zod: ^4.4.3 }appliqué uniquement àapps/server-nestjs(@cpn-console/shared+apps/serverrestent zod 3.25.76).@ts-rest/*deserver-nestjs(0 import source).ZodTypeDef,base.config.default(0),csven.transform().Résultat
pnpm build→ VERT (0 erreur). L'aliasZodTypede zod 4 est assez laxiste pour compiler.pnpm test→ 572 passés / 1 fail en isolation :project-services.service.spec.ts > servicesService > update stores project configuration through the real utility.Cause racine (runtime, pas typecheck)
@cpn-console/hooksest en zod 3.25.76.buildProjectEditStrippers(project-services.utils.ts:39-41) composeeditStrippersGenerator.parse(service.config)(zod-3) dansz.object({ [service.name]: ... }).merge(...)local zod-4. Zod 4 rejette le schéma zod-3 embarqué au runtime.Conclusion
Isoler
zodsurserver-nestjsne suffit pas : le pipe est version-agnostique, mais les sites de composition (.extend(),.merge()) croisent réellement les deux majorités. La frontière dual-major se manifeste au runtime, pas au build.Path A ❌ NON viable. Seuls restent :
shared+hooks+server-nestjs→ zod 4), blast-radius maximal ;3.25.xmaintenue, aucun coût). Recommandé.Le travail d'expérimentation (catalog isolé + corrections mécaniques) est conservé en workspace
zod4mais ne doit pas être mergé tant que la décision Path B / C n'est pas prise.fix(server-nestjs,hooks): isolate zod 4 migration to avoid leaking in…
feat(server-nestjs): migrate apps/server-nestjs to Zod 4 in isolation
feat(server-nestjs): migrate apps/server-nestjs to Zod 4 in isolation