@nodefony/devkit 10.0.0-alpha.8 → 10.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorateParam.js
1
+ //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import defaults, { devkitConfigSchema } from "./nodefony/config/config.js";
2
2
  import { defineDevkitConfig } from "./nodefony/config/defineModuleConfig.js";
3
3
  import { buildCard } from "./nodefony/src/card.js";
4
- import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
5
- import __decorate from "./_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
4
+ import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.js";
5
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.js";
6
6
  import DevkitService_default from "./nodefony/service/DevkitService.js";
7
7
  import DevkitController_default from "./nodefony/controllers/DevkitController.js";
8
8
  import McpController_default from "./nodefony/controllers/McpController.js";
@@ -1,5 +1,5 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.js";
3
3
  import { Controller, controller, route } from "@nodefony/framework";
4
4
  //#region nodefony/controllers/DevkitController.ts
5
5
  /**
@@ -1,6 +1,6 @@
1
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
3
- import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateParam.js";
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.js";
3
+ import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateParam.js";
4
4
  import { ACCESS_TOKEN_VERIFIER, JsonRpcError, Nodefony, authorizeProtectedResource, checkMcpAccess, collectMcpTools, handleMcpMessage, jsonRpcFailure, mcpCallerRoles, protectedResourceMetadataUrl, readBearerHeader } from "nodefony";
5
5
  import { Body, Controller, Headers, controller, route } from "@nodefony/framework";
6
6
  //#region nodefony/controllers/McpController.ts
@@ -1,7 +1,7 @@
1
1
  import defaults from "../config/config.js";
2
2
  import { buildCard } from "../src/card.js";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorateMetadata.js";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.js";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.js";
5
5
  import { Module, Nodefony, Service, extend, injectable, mcpDeclaredScopes } from "nodefony";
6
6
  //#region nodefony/service/DevkitService.ts
7
7
  /**
package/docs/index.md CHANGED
@@ -106,9 +106,13 @@ la rend éprouvable sans serveur, et ce qui l'empêche d'inventer quoi que ce so
106
106
  npx nodefony ai:mcp # écrit .mcp.json ; --dry-run pour voir sans écrire
107
107
  ```
108
108
 
109
- Quatre outils, qui sont les commandes que vous connaissez déjà :
109
+ Sept outils. Quatre sont les commandes que vous connaissez déjà :
110
110
  `nodefony_inspect` (ce qui est monté), `nodefony_check` (ce qui manque),
111
111
  `nodefony_symbols` (ce qu'une API signifie), `nodefony_card` (par où commencer).
112
+ `nodefony_docs` sert la documentation des modules. `nodefony_admin_list` et
113
+ `nodefony_admin_call` ouvrent les lectures du data plane d'administration — le
114
+ catalogue, puis l'appel — et exigent un jeton porteur du scope `admin:read` :
115
+ sans lui, ils n'apparaissent pas dans la liste.
112
116
 
113
117
  **Il n'y a pas de process à lancer.** Depuis la révision `2026-07-28` du
114
118
  transport, un serveur MCP est un endpoint `POST` sans session : c'est donc une
@@ -280,16 +284,20 @@ agent invente : faute d'une marche à suivre, il écrit un CRUD à la main, un
280
284
  service à méthodes `static` que le conteneur ne voit pas, un contrôle de droits
281
285
  dans le corps de l'action.
282
286
 
283
- Le paquet livre donc cinq **skills** au format [Agent Skills](https://agentskills.io),
284
- un par tâche où l'invention coûte cher :
285
-
286
- | Skill | Le besoin qu'il couvre |
287
- | ------------------------------- | ----------------------------------------------------------- |
288
- | `nodefony-add-crud` | exposer une ressource REST complète, entité comprise |
289
- | `nodefony-add-service` | ajouter de la logique métier réutilisable, vue du conteneur |
290
- | `nodefony-protect-route` | réserver une route à qui est habilité |
291
- | `nodefony-add-realtime-channel` | ouvrir un canal temps réel où le serveur pousse |
292
- | `nodefony-browser` | voir **et mesurer** un écran, sans navigateur sur le poste |
287
+ Le paquet livre donc huit **skills** au format [Agent Skills](https://agentskills.io) :
288
+ un qui porte la conduite commune de toute tâche, et un par geste où l'invention
289
+ coûte cher :
290
+
291
+ | Skill | Le besoin qu'il couvre |
292
+ | ------------------------------- | --------------------------------------------------------------- |
293
+ | `nodefony-dev` | conduire une tâche de bout en bout, avant toute modification |
294
+ | `nodefony-add-crud` | exposer une ressource REST complète, entité comprise |
295
+ | `nodefony-add-service` | ajouter de la logique métier réutilisable, vue du conteneur |
296
+ | `nodefony-protect-route` | réserver une route à qui est habilité |
297
+ | `nodefony-add-realtime-channel` | ouvrir un canal temps réel où le serveur pousse |
298
+ | `nodefony-browser` | voir **et mesurer** un écran, sans navigateur sur le poste |
299
+ | `nodefony-migrate-schema` | faire évoluer le schéma d'une base et le porter en production |
300
+ | `nodefony-devops` | exploiter l'application en conteneur, sans réécrire son contrat |
293
301
 
294
302
  Tous portent le préfixe `nodefony-` : leurs pointeurs arrivent dans le dossier où
295
303
  vous écrivez aussi les vôtres, et sans namespace un skill maison du même nom
@@ -330,13 +338,13 @@ absente sans `NODE_ENV`) ; les skills, eux, doivent se mettre à jour par npm.
330
338
 
331
339
  ## Configuration
332
340
 
333
- | Clé | Type | Défaut | Rôle |
334
- | -------------------- | ---------- | -------------------------------------- | ---------------------------------------------- |
335
- | `enabled` | `boolean` | `true` | Interrupteur du module |
336
- | `mcp.enabled` | `boolean` | `true` | Répond-on aux requêtes MCP ? Coupé → `404` |
337
- | `mcp.allowedOrigins` | `string[]` | `[]` | Origines de navigateur admises ; vide = aucune |
338
- | `mcp.allowRemote` | `boolean` | `false` | Accepter un appel d'une adresse non locale |
339
- | `mcp.tools` | `string[]` | `["inspect","check","symbols","card"]` | Allowlist des outils — lecture seule |
341
+ | Clé | Type | Défaut | Rôle |
342
+ | -------------------- | ---------- | -------------------------------------------------- | ---------------------------------------------- |
343
+ | `enabled` | `boolean` | `true` | Interrupteur du module |
344
+ | `mcp.enabled` | `boolean` | `true` | Répond-on aux requêtes MCP ? Coupé → `404` |
345
+ | `mcp.allowedOrigins` | `string[]` | `[]` | Origines de navigateur admises ; vide = aucune |
346
+ | `mcp.allowRemote` | `boolean` | `false` | Accepter un appel d'une adresse non locale |
347
+ | `mcp.tools` | `string[]` | les sept outils intégrés (`BUILTIN_MCP_TOOL_KEYS`) | Allowlist des outils — lecture seule |
340
348
 
341
349
  Les clés, leurs types et leurs défauts viennent du schéma Zod
342
350
  (`nodefony/config/config.ts`) — **source unique** dont dérivent la
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/devkit",
3
- "version": "10.0.0-alpha.8",
3
+ "version": "10.0.0-alpha.9",
4
4
  "type": "module",
5
5
  "description": "Outillage de développement d'une application Nodefony : carte de visite du projet et portes de découverte pour un agent de développement",
6
6
  "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
@@ -25,17 +25,17 @@
25
25
  "coverage": "vitest run --coverage"
26
26
  },
27
27
  "peerDependencies": {
28
- "@nodefony/framework": "^10.0.0-alpha.8",
29
- "@nodefony/http": "^10.0.0-alpha.8",
30
- "nodefony": "^10.0.0-alpha.8",
28
+ "@nodefony/framework": "^10.0.0-alpha.9",
29
+ "@nodefony/http": "^10.0.0-alpha.9",
30
+ "nodefony": "^10.0.0-alpha.9",
31
31
  "zod": "^4.6.5",
32
32
  "playwright": "^1.50.0",
33
33
  "lighthouse": "^13.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@nodefony/framework": "^10.0.0-alpha.8",
37
- "@nodefony/http": "^10.0.0-alpha.8",
38
- "nodefony": "^10.0.0-alpha.8"
36
+ "@nodefony/framework": "^10.0.0-alpha.9",
37
+ "@nodefony/http": "^10.0.0-alpha.9",
38
+ "nodefony": "^10.0.0-alpha.9"
39
39
  },
40
40
  "dependencies": {
41
41
  "axe-core": "4.13.0",
@@ -1,18 +1,18 @@
1
1
  ---
2
2
  name: nodefony-add-crud
3
3
  description: >
4
- Crée une ressource complète dans une application Nodefony — table, schémas de validation,
4
+ Crée une ressource complète dans une application Nodefony — entité, schémas de validation,
5
5
  service CRUD, controller REST+WebSocket et tests — par le générateur `nodefony create entity`,
6
- au lieu de l'écrire à la main. Porte la grammaire de champs (types, relations, index simples et
7
- composites), les réglages pour épouser une table SQL existante, et les trois vérités qu'on
8
- découvre autrement en production : la table naît au démarrage, un champ ajouté n'est rattrapé que
9
- s'il accepte le vide, et la production s'applique par des migrations — dont le cycle complet vit
10
- dans le skill `nodefony-migrate-schema`. À charger AVANT d'écrire une entité, un repository ou un
11
- controller de ressource.
6
+ sur SQL comme sur MongoDB. Porte la grammaire de champs, la
7
+ table des types moteur par moteur, les relations et clés étrangères,
8
+ les réglages pour épouser une table SQL existante, et ce qu'on découvre autrement en
9
+ production : la table naît au démarrage, un champ ajouté n'est rattrapé que s'il accepte le
10
+ vide, et la production s'applique par des migrations (skill `nodefony-migrate-schema`). À
11
+ charger AVANT d'écrire une entité, un repository ou un controller de ressource.
12
12
  Déclencheurs : "ajoute une entité", "crée un CRUD", "nouvelle table", "modèle de données",
13
- "ressource REST", "endpoint CRUD", "je veux stocker des articles/commandes/utilisateurs",
14
- "comment définir un champ", "une relation entre deux entités", "index composite",
15
- "épouser une table existante", "renommer les colonnes en snake_case".
13
+ "ressource REST", "je veux stocker des articles/commandes", "comment définir un champ", "quels types de champ ?", "une relation entre
14
+ deux entités", "clé étrangère", "index composite", "épouser une table existante",
15
+ "entité MongoDB", "schéma Mongoose".
16
16
  ---
17
17
 
18
18
  # add-crud — une ressource complète, générée
@@ -44,29 +44,48 @@ se connecte qu'au démarrage), la pagination bornée **et son tri déclaré**, l
44
44
 
45
45
  ## La grammaire de champs
46
46
 
47
- `nom:type[?][:index|:unique]` — **non-null par défaut**, `?` rend facultatif, `:unique` pose une contrainte
48
- d'unicité, `:index` un index simple.
49
-
50
- | Type | Ce que ça produit |
51
- | -------- | ----------------------- |
52
- | `string` | texte court (une ligne) |
53
- | `text` | texte long |
54
- | `int` | entier |
55
- | `float` | décimal |
56
- | `bool` | booléen |
57
- | `json` | document |
58
- | `date` | horodatage |
59
- | `uuid` | identifiant |
60
-
61
- **Une relation** s'écrit `ref:<Entité>` :
47
+ `nom:type[?][=défaut][:index|:unique]` — **non-null par défaut**, `?` rend facultatif, `=valeur`
48
+ fixe un défaut LITTÉRAL, `:unique` pose une contrainte d'unicité, `:index` un index simple.
49
+ 🔴 **`!` est REFUSÉ** : il ne veut pas dire « obligatoire » ici — un champ l'est déjà.
50
+
51
+ **Casse** : l'entité et la cible d'une relation en PascalCase (`Post`, `ref:User`), le champ en
52
+ camelCase (`publishedAt`). Une faute de casse ou un type d'un autre outil (`boolean`, `integer`)
53
+ est refusé avec la forme juste proposée.
54
+
55
+ | Type | Ce que ça produit | SQLite | PostgreSQL | MySQL / MariaDB | MongoDB |
56
+ | -------------- | ----------------------------- | --------------- | ---------------- | --------------- | ---------------------- |
57
+ | `string(n)` | texte court, borné (255) | `text` ¹ | `varchar(n)` | `varchar(n)` | `String` + `maxlength` |
58
+ | `text` | texte long | `text` | `text` | `text` | `String` |
59
+ | `int` | entier | `integer` | `integer` | `int` | `Number` |
60
+ | `float` | nombre à virgule | `real` | `double` | `double` | `Number` |
61
+ | `decimal(p,s)` | décimal EXACT (voyage chaîne) | `numeric` | `numeric(p,s)` | `decimal(p,s)` | `String` |
62
+ | `bool` | booléen | `integer` (0/1) | `boolean` | `boolean` | `Boolean` |
63
+ | `json` | document libre | `text` (JSON) | `jsonb` | `json` | `Mixed` |
64
+ | `date` | horodatage (ms) | `integer` (ms) | `timestamptz(3)` | `datetime(3)` | `Date` |
65
+ | `uuid` | identifiant | `text` | `uuid` | `varchar(36)` | `String` |
66
+ | `char(n)` | longueur EXACTE | `text` ¹ | `char(n)` | `char(n)` | `String`, `n` exact |
67
+ | `enum(a,b)` | valeurs admises ² | `text` | `varchar(255)` | `varchar(255)` | `String` + `enum` |
68
+ | `ref:<Entité>` | relation ³ | clé étrangère | clé étrangère | clé étrangère | `ObjectId` + `ref` |
69
+
70
+ ¹ SQLite n'applique aucune longueur : c'est le schéma d'entrée (Zod) qui borne, sur TOUS les
71
+ transports. ² Même colonne partout, sans type SQL nommé (qui exigerait une migration) : le type
72
+ TypeScript et le schéma Zod bornent les valeurs. ³ En SQL, la colonne prend le type de la clé
73
+ visée (`uuid`, entier pour `--id serial`, texte pour `User`) ; en MongoDB, aucune clé étrangère.
74
+
75
+ La table exacte, par moteur, telle que CETTE version l'écrit :
76
+ `npx nodefony create entity --describe-json` (champ `context.columnTypes`).
77
+
78
+ **Une relation** s'écrit `<champ>:ref:<Entité>` — le champ d'abord, toujours :
62
79
 
63
80
  ```bash
64
- npx nodefony create entity Comment body:text ref:Article
81
+ npx nodefony create entity Comment body:text article:ref:Article
65
82
  ```
66
83
 
67
- La colonne de jointure est **indexée d'office** — c'est elle que traverse un `?include=`.
68
- Les clés étrangères ne sont pas émises : une contrainte déclarée dans le `CREATE TABLE`
69
- n'atteindrait jamais une base déjà en place. C'est le domaine des migrations.
84
+ La colonne de jointure est **indexée d'office** — c'est elle que traverse un `?include=`. En SQL,
85
+ la **clé étrangère est émise** (`.references()`), du type de la clé visée, et son effacement se
86
+ déduit de la nullabilité : `article:ref:Article` (obligatoire) → `restrict`, le parent ne peut pas
87
+ partir ; `article:ref:Article?` → `set null`. `cascade` n'est jamais un défaut : il s'écrit à la
88
+ main dans la table générée.
70
89
 
71
90
  **Un index de table** porte plusieurs colonnes, et c'est le seul à le pouvoir :
72
91
 
@@ -77,6 +96,26 @@ npx nodefony create entity Visit siteId:uuid path:string at:date --index "siteId
77
96
  Les deux options sont **répétables** — un couple par index. Sur un schéma réel, la majorité des
78
97
  index utiles sont composites : c'est ainsi qu'une table est réellement interrogée.
79
98
 
99
+ ## Sur une application MongoDB
100
+
101
+ La même commande écrit une entité **document** : schéma Mongoose, service CRUD, controller et
102
+ tests — sans table ni migration (la collection naît à la première écriture). Une ligne, toutes
103
+ les options qui ont un sens ici :
104
+
105
+ ```bash
106
+ npx nodefony create entity Post title:string(120) body:text? views:int=0 status:enum(draft,published)=draft slug:string:unique tags:json? author:ref:User --soft-delete --route /api/posts --dry-run
107
+ ```
108
+
109
+ - La clé est l'`_id` natif, servie en `id` ; `ref:<Entité>` est un `ObjectId` indexé, chargé
110
+ par `?include=`, que le schéma d'entrée exige bien formé (24 caractères hexadécimaux → 422).
111
+ - **MongoDB ne tient aucune clé étrangère, l'ORM tient l'effacement** : supprimer un parent
112
+ encore désigné par une référence obligatoire est refusé (409), une facultative est remise à
113
+ `null` — la politique du SQL. Hors transaction, ce contrôle n'est pas atomique ; et
114
+ l'INSERTION n'est pas gardée (un identifiant inexistant est accepté, `?include=` rend `null`).
115
+ - Refusées en le disant (options SQL) : `--table`, `--column-case`, `--id-name`, `--dialect`,
116
+ `--id`, `--index`, `--unique`. Un index composite s'écrit à la main dans le schéma.
117
+ - `User` ne se régénère pas : il s'étend dans `nodefony/entity/User.ts` (son TSDoc donne le geste).
118
+
80
119
  ## Épouser une table qui existe déjà
81
120
 
82
121
  Trois réglages, et ils ne touchent **que** le SQL — la propriété TypeScript reste `id`, `siteId` :
@@ -149,7 +188,7 @@ Ce contrat vaut aussi quand tu écris une liste **à la main** (un endpoint d'ad
149
188
  listing filtré) : le côté serveur déclare ce qu'il sait trier, le point d'entrée le demande, et le
150
189
  refus tombe tout seul.
151
190
 
152
- ## Les trois vérités à savoir avant de livrer
191
+ ## Les trois vérités à savoir avant de livrer (SQL)
153
192
 
154
193
  1. **La table naît au prochain démarrage en développement** (`CREATE TABLE IF NOT EXISTS`).
155
194
  2. **La modifier n'altère rien** — aucun `ALTER` n'est émis. Une colonne ajoutée à une entité déjà
@@ -164,7 +203,8 @@ Le générateur s'arrête plutôt que de produire un fichier bancal — lis le m
164
203
  geste :
165
204
 
166
205
  - **hors projet** (aucun `nodefony.config.ts` au-dessus) ;
167
- - **`@nodefony/drizzle` absent** de la cible ;
206
+ - **aucun ORM** dans l'application (ni `@nodefony/drizzle`, ni `@nodefony/mongoose`) ;
207
+ - **une option SQL sur une application MongoDB** (liste ci-dessus) ;
168
208
  - **entité déjà déclarée** ;
169
209
  - **nom réservé par un module du framework** (`session`, `access_token`, `audit_event`…) — un
170
210
  homonyme dépossède le module, et l'application ne démarre plus sur un message parlant d'une
@@ -307,18 +307,19 @@ nodefony orm:migrate:status
307
307
  Un refus n'est pas une panne : c'est le produit qui s'arrête devant une décision qui t'appartient.
308
308
  Le `code` est stable — **lis-le, il désigne le geste**.
309
309
 
310
- | Code | Ce qui s'est passé | Le geste |
311
- | ---------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
312
- | `NF_MIGRATE_BASELINE_REQUIRED` | la base porte déjà les tables, sans aucun historique | `orm:migrate:baseline` (l'adopter) |
313
- | `NF_GENERATE_DATABASE_NOT_ADOPTED` | aucune migration n'existe, et la base porte déjà ces tables | `orm:migrate:baseline --from-database`, PUIS regénérer |
314
- | `NF_MIGRATE_BASELINE_NOT_EMPTY` | `--from-database` demandé alors que des migrations existent | `orm:migrate:baseline` sans option |
315
- | `NF_MIGRATE_FAILED_MARKER` | une migration a échoué, ou n'a jamais fini | LIRE l'erreur, puis `orm:migrate:repair` |
316
- | `NF_MIGRATE_HASH_MISMATCH` | un fichier déjà appliqué a été modifié | RÉTABLIR le fichier (1er geste) ; `--update-hashes` seulement si la modification était sans effet |
317
- | `NF_MIGRATE_OUT_OF_ORDER` | une migration en attente se range avant la dernière posée | renommer la nouvelle après la dernière appliquée |
318
- | `NF_MIGRATE_MISSING_FILE` | une migration appliquée n'a plus de fichier | rétablir le fichier — il fait partie de l'historique |
319
- | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | l'historique affirme une migration jamais exécutée : `orm:migrate:repair --forget <source>/<tag>` puis `orm:migrate` |
320
- | `NF_MIGRATE_LOCK_TIMEOUT` | un autre travail de migration tient le verrou | ATTENDRE puis rejouer — ce n'est pas une panne, et le verbe est idempotent |
321
- | `NF_MIGRATE_DESTRUCTIVE` | les migrations en attente SUPPRIMENT des données | lire le SQL (`--dry-run`), puis assumer avec `--allow-destructive` |
310
+ | Code | Ce qui s'est passé | Le geste |
311
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
312
+ | `NF_MIGRATE_BASELINE_REQUIRED` | la base porte déjà les tables, sans aucun historique | `orm:migrate:baseline` (l'adopter) |
313
+ | `NF_GENERATE_DATABASE_NOT_ADOPTED` | aucune migration n'existe, et la base porte déjà ces tables | `orm:migrate:baseline --from-database`, PUIS regénérer |
314
+ | `NF_MIGRATE_BASELINE_NOT_EMPTY` | `--from-database` demandé alors que des migrations existent | `orm:migrate:baseline` sans option |
315
+ | `NF_MIGRATE_SECONDARY_CONNECTOR` | un connecteur secondaire porte le nom d'un dossier de dialecte (`sqlite`, `postgres`, `mysql`, `meta`) | renommer le connecteur dans `connectors` — un secondaire range ses migrations sous `migrations/<connecteur>/` |
316
+ | `NF_MIGRATE_FAILED_MARKER` | une migration a échoué, ou n'a jamais fini | LIRE l'erreur, puis `orm:migrate:repair` |
317
+ | `NF_MIGRATE_HASH_MISMATCH` | un fichier déjà appliqué a été modifié | RÉTABLIR le fichier (1er geste) ; `--update-hashes` seulement si la modification était sans effet |
318
+ | `NF_MIGRATE_OUT_OF_ORDER` | une migration en attente se range avant la dernière posée | renommer la nouvelle après la dernière appliquée |
319
+ | `NF_MIGRATE_MISSING_FILE` | une migration appliquée n'a plus de fichier | rétablir le fichier — il fait partie de l'historique |
320
+ | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | l'historique affirme une migration jamais exécutée : `orm:migrate:repair --forget <source>/<tag>` puis `orm:migrate` |
321
+ | `NF_MIGRATE_LOCK_TIMEOUT` | un autre travail de migration tient le verrou | ATTENDRE puis rejouer — ce n'est pas une panne, et le verbe est idempotent |
322
+ | `NF_MIGRATE_DESTRUCTIVE` | les migrations en attente SUPPRIMENT des données | lire le SQL (`--dry-run`), puis assumer avec `--allow-destructive` |
322
323
 
323
324
  Les codes exhaustifs, avec un exemple de charge utile pour chacun :
324
325
  [`references/verdicts.md`](references/verdicts.md).
@@ -113,24 +113,25 @@ n'a jamais les deux — c'est le discriminant à tester.
113
113
 
114
114
  ### Refus d'usage — la demande elle-même
115
115
 
116
- | Code | Ce qui s'est passé | Le geste |
117
- | ------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
118
- | `NF_MIGRATE_UNKNOWN_CONNECTOR` | aucun connecteur de ce nom | le message liste ceux que l'application déclare |
119
- | `NF_MIGRATE_UNKNOWN_TAG` | `--up-to` désigne une migration inconnue | relire `sources[].pendingTags` |
120
- | `NF_MIGRATE_UNKNOWN_SOURCE` | `--source` n'est pas déclarée par cette application | relire `sources[].name` |
121
- | `NF_MIGRATE_URL_MISMATCH` | la variable de migration désigne une base d'un AUTRE dialecte | corriger la variable, ou choisir le bon connecteur |
122
- | `NF_MIGRATE_NOT_CONFIGURED` | connecteur SQL non déclaré dans la configuration | le déclarer pour pouvoir le suivre |
123
- | `NF_MIGRATE_NO_MIGRATIONS` | ce connecteur est porté par une base qui ne se migre pas ainsi | rien à migrer ici — ce n'est pas une panne |
124
- | `NF_MIGRATE_NOT_DEVELOPMENT` | geste réservé au développement, demandé ailleurs | passer par le travail de déploiement |
125
- | `NF_MIGRATE_DESTRUCTIVE` | des migrations en attente SUPPRIMENT des données | relire le fichier produit, puis assumer explicitement |
126
- | `NF_MIGRATE_UNAVAILABLE` | la commande s'est arrêtée sans pouvoir nommer la cause | constater l'état (`orm:migrate:status`) avant de reprendre |
127
- | `NF_MIGRATE_CONFIRM_REQUIRED` | `orm:reset` demandé hors terminal, ou en sortie machine | relancer avec `--yes` si l'effacement est voulu |
128
- | `NF_GENERATE_NAME` | le nom de migration demandé n'est pas utilisable | reprendre la suggestion que le message donne |
129
- | `NF_GENERATE_MISSING_ENTITY` | une entité est enregistrée sans fichier qui la fournisse | `nodefony inspect entities --json` pour la situer |
130
- | `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework | la renommer, ou écrire une migration libre (`--custom`) |
131
- | `NF_GENERATE_DESTRUCTIVE` | la migration ÉCRITE supprime des données | relire le fichier, puis annuler (`git checkout`) ou appliquer — la mise en service a sa propre garde |
132
- | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | `repair --forget <source>/<tag>` : l'historique affirme une migration jamais exécutée |
133
- | `NF_GENERATE_TOOL_MISSING` | l'outil de génération n'est pas installé | l'installer en dépendance de développement |
116
+ | Code | Ce qui s'est passé | Le geste |
117
+ | -------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
118
+ | `NF_MIGRATE_UNKNOWN_CONNECTOR` | aucun connecteur de ce nom | le message liste ceux que l'application déclare |
119
+ | `NF_MIGRATE_UNKNOWN_TAG` | `--up-to` désigne une migration inconnue | relire `sources[].pendingTags` |
120
+ | `NF_MIGRATE_UNKNOWN_SOURCE` | `--source` n'est pas déclarée par cette application | relire `sources[].name` |
121
+ | `NF_MIGRATE_URL_MISMATCH` | la variable de migration désigne une base d'un AUTRE dialecte | corriger la variable, ou choisir le bon connecteur |
122
+ | `NF_MIGRATE_NOT_CONFIGURED` | connecteur SQL non déclaré dans la configuration | le déclarer pour pouvoir le suivre |
123
+ | `NF_MIGRATE_SECONDARY_CONNECTOR` | un connecteur secondaire porte le nom d'un dossier de dialecte (`sqlite`, `postgres`, `mysql`, `meta`) | renommer le connecteur dans `connectors` — un secondaire range ses migrations sous `migrations/<connecteur>/` |
124
+ | `NF_MIGRATE_NO_MIGRATIONS` | ce connecteur est porté par une base qui ne se migre pas ainsi | rien à migrer ici — ce n'est pas une panne |
125
+ | `NF_MIGRATE_NOT_DEVELOPMENT` | geste réservé au développement, demandé ailleurs | passer par le travail de déploiement |
126
+ | `NF_MIGRATE_DESTRUCTIVE` | des migrations en attente SUPPRIMENT des données | relire le fichier produit, puis assumer explicitement |
127
+ | `NF_MIGRATE_UNAVAILABLE` | la commande s'est arrêtée sans pouvoir nommer la cause | constater l'état (`orm:migrate:status`) avant de reprendre |
128
+ | `NF_MIGRATE_CONFIRM_REQUIRED` | `orm:reset` demandé hors terminal, ou en sortie machine | relancer avec `--yes` si l'effacement est voulu |
129
+ | `NF_GENERATE_NAME` | le nom de migration demandé n'est pas utilisable | reprendre la suggestion que le message donne |
130
+ | `NF_GENERATE_MISSING_ENTITY` | une entité est enregistrée sans fichier qui la fournisse | `nodefony inspect entities --json` pour la situer |
131
+ | `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework | la renommer, ou écrire une migration libre (`--custom`) |
132
+ | `NF_GENERATE_DESTRUCTIVE` | la migration ÉCRITE supprime des données | relire le fichier, puis annuler (`git checkout`) ou appliquer — la mise en service a sa propre garde |
133
+ | `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma | `repair --forget <source>/<tag>` : l'historique affirme une migration jamais exécutée |
134
+ | `NF_GENERATE_TOOL_MISSING` | l'outil de génération n'est pas installé | l'installer en dépendance de développement |
134
135
 
135
136
  ## Un contrat qui ne bougera pas
136
137