@nodefony/devkit 10.0.0-beta.5 → 10.0.0-beta.7
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.
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +3 -3
- package/dist/nodefony/controllers/DevkitController.js +1 -1
- package/dist/nodefony/controllers/McpController.js +2 -2
- package/dist/nodefony/controllers/ProfileSessionController.js +1 -1
- package/dist/nodefony/mcp/profileRoute.js +28 -9
- package/dist/nodefony/service/DevkitService.js +6 -6
- package/docs/index.md +1 -1
- package/package.json +13 -13
- package/skills/nodefony-add-crud/SKILL.md +38 -23
- package/skills/nodefony-add-grpc-service/SKILL.md +179 -0
- package/skills/nodefony-add-grpc-service/scripts/call.mjs +218 -0
- package/skills/nodefony-add-realtime-binary/SKILL.md +301 -0
- package/skills/nodefony-add-realtime-binary/references/messages.md +20 -0
- package/skills/nodefony-add-realtime-binary/references/mesures.md +59 -0
- package/skills/nodefony-add-realtime-channel/SKILL.md +452 -105
- package/skills/nodefony-add-realtime-channel/references/contrat.md +22 -0
- package/skills/nodefony-add-realtime-channel/references/production.md +140 -0
- package/skills/nodefony-add-realtime-presence/SKILL.md +328 -0
- package/skills/nodefony-authentication/SKILL.md +2 -2
- package/skills/nodefony-browser/SKILL.md +37 -2
- package/skills/nodefony-browser/references/sondes.md +19 -0
- package/skills/nodefony-browser/scripts/audit.mjs +17 -2
- package/skills/nodefony-browser/scripts/inspect.mjs +108 -87
- package/skills/nodefony-browser/scripts/lib/actions.mjs +164 -0
- package/skills/nodefony-browser/scripts/lib/browser.mjs +67 -11
- package/skills/nodefony-browser/scripts/lib/probes.mjs +108 -3
- package/skills/nodefony-browser/scripts/watch.mjs +27 -11
- package/skills/nodefony-dev/SKILL.md +31 -15
- package/skills/nodefony-dev/references/rapport-de-build.md +19 -0
- package/skills/nodefony-devops/SKILL.md +28 -21
- package/skills/nodefony-devops/references/dimensionnement.md +1 -1
- package/skills/nodefony-devops/references/frontal.md +5 -2
- package/skills/nodefony-devops/references/image.md +23 -15
- package/skills/nodefony-migrate-schema/SKILL.md +110 -20
- package/skills/nodefony-migrate-schema/references/verdicts.md +23 -20
- package/skills/nodefony-protect-route/SKILL.md +27 -32
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nodefony-migrate-schema
|
|
3
3
|
description: >
|
|
4
|
-
Fait évoluer le schéma d'une base Nodefony et le porte en production, par
|
|
5
|
-
`orm:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
exemplaires. À charger AVANT de modifier une entité déjà en base, ou avant de déployer un schéma
|
|
10
|
-
changé.
|
|
4
|
+
Fait évoluer le schéma d'une base Nodefony et le porte en production, par `orm:generate` et
|
|
5
|
+
`orm:migrate` — jamais par un `ALTER` à la main ni en supprimant la base. Porte la lecture de
|
|
6
|
+
l'état, le plan avant le geste, chaque code de refus et son geste, le renommage déclaré, les trois
|
|
7
|
+
interdits qui cassent un historique, et le déploiement où les migrations passent AVANT les
|
|
8
|
+
exemplaires. À charger AVANT de modifier une entité déjà en base, ou de déployer un schéma changé.
|
|
11
9
|
Déclencheurs : "j'ai ajouté un champ à une entité", "la colonne n'existe pas en base",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
10
|
+
"migrer le schéma", "orm:migrate", "orm:generate", "déployer un changement de schéma",
|
|
11
|
+
"no such column",
|
|
14
12
|
"column does not exist", "ma base ne correspond plus au code", "adopter une base existante",
|
|
15
13
|
"réparer une migration en échec", "éprouver une migration sans casser ma base",
|
|
16
|
-
"rejouer les migrations depuis zéro"
|
|
14
|
+
"rejouer les migrations depuis zéro", "renommer une colonne ou une table",
|
|
15
|
+
"changer le type ou la longueur d'une colonne", "changer de base", "passer à PostgreSQL ou
|
|
16
|
+
MySQL".
|
|
17
17
|
metadata:
|
|
18
|
-
version:
|
|
18
|
+
version: 4
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# Faire évoluer un schéma, et le porter en production
|
|
@@ -29,18 +29,27 @@ metadata:
|
|
|
29
29
|
> la ligne exacte :
|
|
30
30
|
> `node node_modules/@nodefony/devkit/skills/nodefony-dev/scripts/docs.mjs <termes>`.
|
|
31
31
|
|
|
32
|
-
## 1. La seule chose à savoir avant tout le reste
|
|
32
|
+
## 1. La seule chose à savoir avant tout le reste — QUI fabrique le schéma ici ?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
La clé `ddl` d'un connecteur répond à une seule question : qui fabrique les tables. Le mode n'est
|
|
35
|
+
presque jamais écrit, il se DÉDUIT — et c'est lui qui décide du geste.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
| Situation | Mode | Qui fabrique les tables |
|
|
38
|
+
| -------------------------------------------------------- | --------- | ----------------------------------------------------------- |
|
|
39
|
+
| `ddl` écrit dans la configuration | lui | ce que dit la configuration — il gagne toujours |
|
|
40
|
+
| développement, l'application versionne **≥ 1** migration | `migrate` | les migrations, appliquées au démarrage |
|
|
41
|
+
| développement, **aucune** migration ; ou `NODE_ENV=test` | `auto` | le démarrage, d'après les entités (création + ajouts vides) |
|
|
42
|
+
| **tout le reste**, production comprise | `none` | personne au démarrage : un job lance `orm:migrate` AVANT |
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
🔴 **Une application créée par `nodefony create app` avec un ORM SQL est en `migrate` dès
|
|
45
|
+
l'installation** : le générateur écrit ET applique sa migration initiale. Le cas courant, en
|
|
46
|
+
développement, est donc : **une entité change → une migration s'écrit** (§ 3). Ne crois pas
|
|
47
|
+
qu'« en développement la base suit le code » sans avoir vérifié qu'aucune migration n'existe
|
|
48
|
+
(`npx nodefony orm:migrate:status --json`).
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
En `auto` seulement, un champ ajouté **qui accepte le vide** est posé au démarrage suivant — rien à
|
|
51
|
+
taper. Tout le reste (renommage, type, longueur, suppression, champ obligatoire) n'y est JAMAIS
|
|
52
|
+
rattrapé : l'ancienne colonne reste en base, la nouvelle naît vide.
|
|
44
53
|
|
|
45
54
|
## 2. Lire l'état — deux voies, selon que l'application tourne
|
|
46
55
|
|
|
@@ -115,6 +124,62 @@ relue et versionnée en amont, et c'est `orm:migrate` seul qui l'applique.
|
|
|
115
124
|
> second échoue sur un objet qui n'a jamais existé. Un `ddl` écrit dans la configuration gagne
|
|
116
125
|
> toujours ; sous `NODE_ENV=test`, rien ne change.
|
|
117
126
|
|
|
127
|
+
### 🔴 Un RENOMMAGE — le DÉCLARER, jamais le laisser deviner
|
|
128
|
+
|
|
129
|
+
Une colonne qui disparaît pendant qu'une autre apparaît, c'est un renommage (les données suivent)
|
|
130
|
+
OU une suppression puis un ajout (les données sont perdues). Aucun diff ne voit une intention. Ne
|
|
131
|
+
lance donc JAMAIS `orm:generate` nu après avoir renommé un champ : il s'arrête
|
|
132
|
+
(`NF_GENERATE_NEEDS_ANSWER`), et **il n'y a pas de question à laquelle répondre** — l'outil tourne
|
|
133
|
+
sans terminal, humain devant l'écran compris. La réponse se DÉCLARE :
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# une colonne : la table sous le nom que porte son entité
|
|
137
|
+
npx nodefony orm:generate --name titre_en_anglais --rename article.titre=title
|
|
138
|
+
# une table, puis une colonne DANS la table renommée (son NOUVEAU nom)
|
|
139
|
+
npx nodefony orm:generate --name lignes --rename invoice_line=invoice_det --rename invoice_det.fk_parent_line=fk_parent
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- La migration porte un `RENAME`, **changement de type compris** (`varchar(8)` → `varchar(16)`
|
|
143
|
+
renommé ET élargi s'écrit en entier).
|
|
144
|
+
- Le rapport `--json` le confirme : `renamed: ["article.titre → title"]`. Relis le fichier quand même.
|
|
145
|
+
- Un nom mal écrit, une ancienne colonne absente du dernier instantané, un nouveau nom qu'aucune
|
|
146
|
+
entité ne déclare : refus `NF_GENERATE_RENAME`, **rien n'est écrit**.
|
|
147
|
+
- Une VRAIE suppression ET un ajout sans rapport dans la même table : **deux générations** — d'abord
|
|
148
|
+
l'entité sans la colonne retirée (la perte est nommée par le garde destructif), puis avec
|
|
149
|
+
l'ajout. Ne déclare pas `--rename` pour faire taire l'outil : ce serait mentir sur les données.
|
|
150
|
+
|
|
151
|
+
Allonger une colonne (`varchar(16)` → `varchar(32)`) ou monter un entier de rang (`integer` →
|
|
152
|
+
`bigint`) n'est PAS destructif : le générateur le constate sur l'état de départ et n'exige rien. Le
|
|
153
|
+
raccourcir, ou rendre la colonne obligatoire, reste refusé sans `--allow-destructive`.
|
|
154
|
+
|
|
155
|
+
### Les pièges d'un schéma qui ÉVOLUE — constatés en faisant migrer un vrai progiciel
|
|
156
|
+
|
|
157
|
+
Chacun a été rencontré en portant un schéma réel d'une version à la suivante, sur SQLite,
|
|
158
|
+
PostgreSQL, MariaDB et MySQL. Le geste est le seul qui ait tenu sur les quatre.
|
|
159
|
+
|
|
160
|
+
1. **Renommer la PROPRIÉTÉ sans renommer la colonne ne demande AUCUNE migration.** Le modificateur
|
|
161
|
+
`column` garde le nom en base : `title: { kind: "string", length: 180, column: "titre" }`. Le
|
|
162
|
+
code lit `title`, la base garde `titre`, `orm:generate` répond « rien à écrire ». Choisis-le
|
|
163
|
+
quand seul le nom vu par le code doit changer — c'est le renommage qui ne coûte rien.
|
|
164
|
+
2. **Une table renommée fait apparaître des `DROP CONSTRAINT` / `DROP INDEX` puis leurs `ADD`** :
|
|
165
|
+
les noms de contraintes que le kit dérive du nom de table changent, la base garde les anciens.
|
|
166
|
+
En SQLite, la table est même RECRÉÉE (`__new_<table>`, recopie, `DROP TABLE`, `RENAME`). C'est
|
|
167
|
+
la mécanique, pas une perte, et le garde ne la signale pas. Ce qui se vérifie : la ligne
|
|
168
|
+
`renamed` du rapport, et **aucun `DROP COLUMN`** dans le fichier.
|
|
169
|
+
3. **Une référence posée sur des lignes DÉJÀ remplies échoue si une ligne pointe vers rien** —
|
|
170
|
+
sur les quatre moteurs : PostgreSQL et MySQL refusent l'`ADD CONSTRAINT`, SQLite refuse la
|
|
171
|
+
validation (« référence(s) orpheline(s) », table et ligne nommées). La migration est annulée et
|
|
172
|
+
un marqueur d'échec posé. Geste : corriger d'ABORD les lignes dans une migration libre
|
|
173
|
+
(`--custom` : `UPDATE … SET fk = NULL WHERE …`), PUIS déclarer la référence ; après un échec,
|
|
174
|
+
`orm:migrate:repair` lève le marqueur une fois la cause traitée.
|
|
175
|
+
4. **Retirer un champ de l'entité, c'est `DROP COLUMN`** : refusé sans `--allow-destructive`.
|
|
176
|
+
Un progiciel qui abandonne une colonne la laisse souvent en base sur les installations
|
|
177
|
+
existantes (le script de passage réel ne la supprime pas). Si ses données comptent encore,
|
|
178
|
+
garde le champ, facultatif, plutôt que de le retirer.
|
|
179
|
+
5. **Une évolution se prouve sur une base PEUPLÉE, jamais vide** (§ 4 b). Une table vide ne
|
|
180
|
+
déclenche ni la recréation SQLite d'une table référencée, ni une référence orpheline : un défaut
|
|
181
|
+
de l'applicateur SQLite n'apparaissait QUE sur des lignes réelles. Une ligne par table suffit.
|
|
182
|
+
|
|
118
183
|
### 🔴 Un champ OBLIGATOIRE sur une table PEUPLÉE — ton moteur ne fait pas ce que tu crois
|
|
119
184
|
|
|
120
185
|
Ajouter une colonne `NOT NULL` **sans valeur par défaut** à une table qui porte déjà des lignes n'a
|
|
@@ -218,6 +283,29 @@ Deux choses qu'il rapporte et qu'il faut lire :
|
|
|
218
283
|
> Cela ne concerne QUE cette commande de reprise : la création des tables, leur migration et le
|
|
219
284
|
> fonctionnement de l'application sont inchangés sur MariaDB.
|
|
220
285
|
|
|
286
|
+
### Changer de BASE — de SQLite à PostgreSQL, MySQL ou MariaDB
|
|
287
|
+
|
|
288
|
+
Une entité écrite par `create entity` est une description PORTABLE (`defineTable`) : elle suit la
|
|
289
|
+
base du connecteur, **sans être réécrite**. Changer de moteur tient en quatre gestes :
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
npm install pg # ou mysql2 — le pilote de la NOUVELLE base (absent : le démarrage le nomme)
|
|
293
|
+
# .env : NF_DATABASE_URL=postgres://user:secret@hote:5432/app (le dialecte se déduit de l'URL)
|
|
294
|
+
npx nodefony orm:generate --name initiale # migration INITIALE de ce dialecte
|
|
295
|
+
npx nodefony orm:migrate
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- **Les migrations se rangent par dialecte** (`migrations/<dialecte>/`) : celles de SQLite ne
|
|
299
|
+
s'appliquent jamais ailleurs, d'où la migration initiale du nouveau moteur.
|
|
300
|
+
- 🔴 **Les DONNÉES ne suivent pas.** Le schéma change de base, les lignes restent dans l'ancienne :
|
|
301
|
+
aucune commande ne les transfère — export puis import, à ta charge.
|
|
302
|
+
- **Une table NATIVE ne suit pas** (`sqliteTable`, ou `create entity --dialect <moteur>`) : pointée
|
|
303
|
+
vers un autre moteur, l'application refuse de démarrer en nommant l'entité
|
|
304
|
+
(`EntityNotPortedError`). Régénère-la sans `--dialect` (`create entity <Nom> … --force`).
|
|
305
|
+
- **Certains écarts appartiennent au moteur**, pas au framework : l'ordre de tri des chaînes et
|
|
306
|
+
l'égalité sans casse suivent la collation de la base, la place des `NULL` dans un tri diffère, le
|
|
307
|
+
type `vector` n'existe qu'en PostgreSQL. Le SQL natif que tu écris reste propre à ton dialecte.
|
|
308
|
+
|
|
221
309
|
## 4. Éprouver une migration — sur une base d'ESSAI, jamais sur la tienne
|
|
222
310
|
|
|
223
311
|
Quand il faut **prouver** qu'une migration fait ce qu'elle annonce, la réponse n'est jamais de
|
|
@@ -320,9 +408,11 @@ Le `code` est stable — **lis-le, il désigne le geste**.
|
|
|
320
408
|
| `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 |
|
|
321
409
|
| `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 |
|
|
322
410
|
| `NF_MIGRATE_MISSING_FILE` | une migration appliquée n'a plus de fichier | rétablir le fichier — il fait partie de l'historique |
|
|
323
|
-
| `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, et pourtant la base ne porte pas le schéma
|
|
411
|
+
| `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, RIEN n'attend, 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` |
|
|
324
412
|
| `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 |
|
|
325
413
|
| `NF_MIGRATE_DESTRUCTIVE` | les migrations en attente SUPPRIMENT des données | lire le SQL (`--dry-run`), puis assumer avec `--allow-destructive` |
|
|
414
|
+
| `NF_GENERATE_NEEDS_ANSWER` | une colonne disparaît et une autre apparaît : renommage ou perte ? | renommage → `--rename table.ancienne=nouvelle` ; vraie suppression + ajout → deux générations |
|
|
415
|
+
| `NF_GENERATE_RENAME` | un `--rename` mal écrit, ou que le dernier instantané ne permet pas | corriger la déclaration — la table sous le nom de son entité ; rien n'a été écrit |
|
|
326
416
|
|
|
327
417
|
Les codes exhaustifs, avec un exemple de charge utile pour chacun :
|
|
328
418
|
[`references/verdicts.md`](references/verdicts.md).
|
|
@@ -113,26 +113,29 @@ 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é
|
|
117
|
-
| -------------------------------- |
|
|
118
|
-
| `NF_MIGRATE_UNKNOWN_CONNECTOR` | aucun connecteur de ce nom
|
|
119
|
-
| `NF_MIGRATE_UNKNOWN_TAG` | `--up-to` désigne une migration inconnue
|
|
120
|
-
| `NF_MIGRATE_UNKNOWN_SOURCE` | `--source` n'est pas déclarée par cette application
|
|
121
|
-
| `NF_MIGRATE_URL_MISMATCH` | la variable de migration désigne une base d'un AUTRE dialecte
|
|
122
|
-
| `NF_MIGRATE_NOT_CONFIGURED` | connecteur SQL non déclaré dans la configuration
|
|
123
|
-
| `NF_MIGRATE_SECONDARY_CONNECTOR` | un connecteur secondaire porte le nom d'un dossier de dialecte (`sqlite`, `postgres`, `mysql`, `meta`)
|
|
124
|
-
| `NF_MIGRATE_NO_MIGRATIONS` | ce connecteur est porté par une base qui ne se migre pas ainsi
|
|
125
|
-
| `NF_MIGRATE_NOT_DEVELOPMENT` | geste réservé au développement, demandé ailleurs
|
|
126
|
-
| `NF_MIGRATE_DESTRUCTIVE` | des migrations en attente SUPPRIMENT des données
|
|
127
|
-
| `NF_MIGRATE_UNAVAILABLE` | la commande s'est arrêtée sans pouvoir nommer la cause
|
|
128
|
-
| `NF_MIGRATE_CONFIRM_REQUIRED` | `orm:reset` demandé hors terminal, ou en sortie machine
|
|
129
|
-
| `NF_GENERATE_NAME` | le nom de migration demandé n'est pas utilisable
|
|
130
|
-
| `NF_GENERATE_MISSING_ENTITY` | une entité est enregistrée sans fichier qui la fournisse
|
|
131
|
-
| `NF_GENERATE_UNREADABLE_ENTITY` | rien d'écrit, et un fichier d'entités ne s'importe pas : sa table n'est vue par personne
|
|
132
|
-
| `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
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_UNREADABLE_ENTITY` | rien d'écrit, et un fichier d'entités ne s'importe pas : sa table n'est vue par personne | corriger la cause nommée (souvent un paquet absent des dépendances), ou sortir le fichier de `nodefony/entity/` |
|
|
132
|
+
| `NF_GENERATE_FRAMEWORK_TABLE` | une entité de l'application usurpe une table du framework | la renommer, ou écrire une migration libre (`--custom`) |
|
|
133
|
+
| `NF_GENERATE_DEV_MODULE_TABLE` | un module `policy: "dev"` lie ses tables au connecteur généré : elles iraient en base de production | lui donner un connecteur à lui, sans migration ; ou retirer sa politique `dev` |
|
|
134
|
+
| `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 |
|
|
135
|
+
| `NF_GENERATE_DATABASE_BEHIND` | rien à écrire, RIEN n'attend, et pourtant la base ne porte pas le schéma | `repair --forget <source>/<tag>` : l'historique affirme une migration jamais exécutée |
|
|
136
|
+
| `NF_GENERATE_TOOL_MISSING` | l'outil de génération n'est pas installé | l'installer en dépendance de développement |
|
|
137
|
+
| `NF_GENERATE_NEEDS_ANSWER` | une colonne disparaît et une autre apparaît — l'outil demande « renommage ? », sans terminal possible | renommage → `--rename table.ancienne=nouvelle` ; vraie suppression + ajout → deux générations |
|
|
138
|
+
| `NF_GENERATE_RENAME` | `--rename` mal écrit, ancien nom absent du dernier instantané, ou nouveau nom qu'aucune entité ne porte | corriger la déclaration (la table sous le nom de son entité) — rien n'a été écrit |
|
|
136
139
|
|
|
137
140
|
## Un contrat qui ne bougera pas
|
|
138
141
|
|
|
@@ -245,10 +245,10 @@ Un service partenaire, un script, un agent ne stockent aucun cookie. Le
|
|
|
245
245
|
`nodefony/config/security.ts` généré (preset `complete`) pose **deux zones, une par sorte de
|
|
246
246
|
preuve** — choisis selon ce que l'appelant détient, n'en ajoute pas une troisième :
|
|
247
247
|
|
|
248
|
-
| L'appelant présente
|
|
249
|
-
|
|
|
250
|
-
| une **clé d'API** opaque (`nf_…`)
|
|
251
|
-
| un **jeton d'accès**
|
|
248
|
+
| L'appelant présente | Zone | Préfixe | Obtenir la preuve |
|
|
249
|
+
| ------------------------------------ | --------- | -------------- | ---------------------------------- |
|
|
250
|
+
| une **clé d'API** opaque (`nf_…`) | `machine` | `/api/machine` | `POST /nodefony/security/api/keys` |
|
|
251
|
+
| un **jeton d'accès** signé par l'app | `service` | `/api/service` | émis par l'app (voir la démo) |
|
|
252
252
|
|
|
253
253
|
### Clé d'API : la zone `machine`
|
|
254
254
|
|
|
@@ -275,27 +275,30 @@ qui ne stocke pas de cookie ne doit rien recevoir qu'il faille stocker.**
|
|
|
275
275
|
|
|
276
276
|
Les clés s'émettent par `POST /nodefony/security/api/keys`.
|
|
277
277
|
|
|
278
|
-
### Jeton d'accès : la zone `
|
|
278
|
+
### Jeton d'accès : la zone `service`
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
npx nodefony app:partner:call # jeton signé par l'app pour `admin` (--as <compte>)
|
|
287
|
-
npx nodefony app:partner:call --issuer keycloak # client <app>-machine, flux client_credentials
|
|
280
|
+
```ts
|
|
281
|
+
service: {
|
|
282
|
+
pattern: "^/api/service",
|
|
283
|
+
authenticators: ["jwt"], // jeton signé par CETTE application
|
|
284
|
+
stateless: true,
|
|
285
|
+
}
|
|
288
286
|
```
|
|
289
287
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
288
|
+
Une route qui tombe sous `/api/service` est protégée sans rien ajouter. L'application générée
|
|
289
|
+
n'y met AUCUN controller : l'exemple complet — émission du jeton, appel, et la variante Keycloak
|
|
290
|
+
(`client_credentials`) — vit dans la démo, qu'on copie dans l'application pour la lire.
|
|
291
|
+
|
|
292
|
+
**Accepter aussi les jetons d'un fournisseur** (Keycloak) : ajouter `"external-jwt"` à la zone ;
|
|
293
|
+
`jwt` et `external-jwt` s'aiguillent sur l'émetteur du jeton, leur ordre ne décide de rien. Un jeton
|
|
294
|
+
Keycloak s'obtient par `requestClientCredentialsToken` de `@nodefony/security` — découverte du point
|
|
295
|
+
de jeton comprise —, jamais par un `fetch` recopié.
|
|
294
296
|
|
|
295
297
|
🔴 **L'audience vit en UN endroit.** Un jeton n'ouvre que la zone dont il porte la ressource
|
|
296
|
-
(RFC 8707).
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
(RFC 8707). Écris-la UNE fois (une fonction de `nodefony/config/security.ts`, sur le modèle de
|
|
299
|
+
`publicBaseUrl`), lue par la zone (`resource`) et par `jwt.audiences`. Une ressource recopiée à la
|
|
300
|
+
main finit par différer, et le seul symptôme est un refus sans explication. Ouvrir une ressource
|
|
301
|
+
à des jetons, c'est trois gestes, pas un :
|
|
299
302
|
|
|
300
303
|
| Oublié | Symptôme |
|
|
301
304
|
| ----------------------------------------- | ------------------------------------------------------------- |
|
|
@@ -303,19 +306,11 @@ sans explication. Ouvrir une AUTRE ressource à des jetons, c'est trois gestes,
|
|
|
303
306
|
| `security:keycloak:realm --write` relancé | **401** du jeton Keycloak : le realm n'inscrit pas l'audience |
|
|
304
307
|
| la `resource` sur la zone | l'application **refuse de démarrer** en nommant la zone |
|
|
305
308
|
|
|
306
|
-
🔴 **`subjectPolicy: "ephemeral"` s'écrit SUR LA ZONE, jamais dans `resourceServer`.**
|
|
309
|
+
🔴 **`subjectPolicy: "ephemeral"` s'écrit SUR LA ZONE, jamais dans `resourceServer`.** Un client
|
|
307
310
|
machine de Keycloak n'a pas de compte ici : `ephemeral` l'accepte le temps de la requête, avec les
|
|
308
|
-
seuls `ephemeralRoles` de la zone
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
`whoami` n'exige pas `ROLE_PARTNER` — un compte de l'app ne l'a pas. Pour réserver une action aux
|
|
313
|
-
partenaires : `@IsGranted("ROLE_PARTNER")`.
|
|
314
|
-
|
|
315
|
-
**Prouver** : `--issuer nodefony` rend 200 sans Keycloak ; `--issuer keycloak` rend 200 avec le
|
|
316
|
-
profil `keycloak` du compose allumé ; et le MÊME jeton machine est refusé (401) sur une zone qui
|
|
317
|
-
garde `subjectPolicy` par défaut (`require`). Chaque échec de la commande sort en code 1 avec le
|
|
318
|
-
geste qui répare — lis-le avant de chercher ailleurs.
|
|
311
|
+
seuls `ephemeralRoles` de la zone. Posé dans `resourceServer.subjectPolicy` (global), il ferait
|
|
312
|
+
accepter un appelant sans compte par **toutes** les zones `external-jwt`. Et un rôle fort dans
|
|
313
|
+
`ephemeralRoles` est accordé à **quiconque** détient un jeton valide du realm.
|
|
319
314
|
|
|
320
315
|
## Les gestes qui affaiblissent en silence
|
|
321
316
|
|