@nodefony/drizzle 10.0.0-alpha.4 → 10.0.0-alpha.6

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.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Répare la recopie d'une recréation de table SQLite.
3
+ *
4
+ * 🔴 Le défaut que ce module ferme, mesuré sur deux applications fraîches :
5
+ * quand une migration doit à la fois MODIFIER une colonne et en AJOUTER une,
6
+ * SQLite ne sait pas altérer sur place — l'outil de génération produit alors la
7
+ * ronde connue (créer `__new_x`, y recopier les lignes de `x`, supprimer `x`,
8
+ * renommer). Sa recopie liste les colonnes de la table d'ARRIVÉE, la colonne
9
+ * neuve comprise, et va donc la LIRE dans la table de départ, où elle n'existe
10
+ * pas encore. La migration échoue sur `no such column` et laisse un marqueur
11
+ * qui bloque tous les passages suivants — l'utilisateur se retrouve devant
12
+ * trois commandes qui refusent, et la seule issue apparente est de détruire la
13
+ * base.
14
+ *
15
+ * La réparation est déterministe et sans jugement : une colonne que la table de
16
+ * départ ne porte pas ne peut pas être recopiée, donc elle sort des DEUX
17
+ * listes. Elle recevra ce que son `CREATE TABLE` lui donne — un défaut, ou
18
+ * `NULL`. C'est exactement ce qu'aurait fait un `ADD COLUMN`.
19
+ *
20
+ * ⚠️ On ne touche à RIEN dès qu'un élément de la recopie n'est pas une simple
21
+ * colonne citée — une expression, un appel de fonction, un alias — ni quand les
22
+ * colonnes de la table de départ sont inconnues. Réécrire ce qu'on n'a pas
23
+ * compris coûterait plus cher que le défaut qu'on répare.
24
+ */
25
+ /** Ce que la réparation a changé, pour pouvoir le DIRE plutôt que le taire. */
26
+ export interface IRebuildCopyRepair {
27
+ /** Le SQL, réparé si besoin ; identique à l'entrée sinon. */
28
+ readonly sql: string;
29
+ /** Les colonnes retirées de la recopie, en `table.colonne`. */
30
+ readonly dropped: readonly string[];
31
+ }
32
+ /**
33
+ * Retire de la recopie les colonnes que la table de départ ne porte pas.
34
+ *
35
+ * @param sql - le SQL de la migration, tel que l'outil vient de l'écrire.
36
+ * @param columnsOf - les colonnes d'une table AVANT cette migration ; `null`
37
+ * quand on ne les connaît pas — auquel cas rien n'est touché, car on ne
38
+ * réécrit jamais sur une supposition.
39
+ * @returns le SQL réparé et la liste de ce qui a été retiré.
40
+ */
41
+ export declare function repairRebuildCopy(sql: string, columnsOf: (table: string) => readonly string[] | null): IRebuildCopyRepair;
@@ -17,6 +17,8 @@ export type CommandFailureCode =
17
17
  | "NF_MIGRATE_UNAVAILABLE"
18
18
  /** Confirmation requise et non donnée. */
19
19
  | "NF_MIGRATE_CONFIRM_REQUIRED"
20
+ /** La base porte des comptes : `--yes` ne suffit pas à les effacer. */
21
+ | "NF_MIGRATE_RESET_HAS_ACCOUNTS"
20
22
  /** Des migrations en attente SUPPRIMENT des données, hors développement. */
21
23
  | "NF_MIGRATE_DESTRUCTIVE"
22
24
  /** Adopter TOUT graverait une affirmation fausse : la base ne suit pas. */
@@ -33,6 +35,12 @@ export type CommandFailureCode =
33
35
  | "NF_GENERATE_DATABASE_BEHIND"
34
36
  /** L'outil qui ÉCRIT les migrations n'est pas installé. */
35
37
  | "NF_GENERATE_TOOL_MISSING"
38
+ /** L'outil de génération pose une question, et il n'y a pas de terminal. */
39
+ | "NF_GENERATE_NEEDS_ANSWER"
40
+ /** L'outil de génération s'est arrêté ; ce qu'il a dit est remonté tel quel. */
41
+ | "NF_GENERATE_TOOL_FAILED"
42
+ /** La lecture du schéma d'une base existante a échoué. */
43
+ | "NF_INTROSPECT_FAILED"
36
44
  /** Le schéma initial serait écrit sur une base qui porte DÉJÀ ces tables. */
37
45
  | "NF_GENERATE_DATABASE_NOT_ADOPTED"
38
46
  /** L'adoption par lecture de la base, demandée alors qu'il existe déjà des migrations. */
@@ -124,6 +132,59 @@ export interface IResolutionRefusal {
124
132
  * @returns le refus, avec le geste qui répare.
125
133
  */
126
134
  export declare function generationToolMissing(): IResolutionRefusal;
135
+ /**
136
+ * Sortie d'un outil tiers, prête à être citée dans une explication.
137
+ *
138
+ * Elle est indentée pour se distinguer de la prose qui l'entoure, et BORNÉE par
139
+ * la fin : une pile d'appels se termine par ce qui a cassé, jamais par ce qui a
140
+ * démarré. La troncature s'ANNONCE — une sortie coupée en silence fait chercher
141
+ * une cause dans la moitié qu'on n'a pas montrée.
142
+ *
143
+ * @param output - sortie complète de l'outil (standard puis erreur).
144
+ * @param maxLines - nombre de lignes conservées, depuis la fin.
145
+ * @returns le bloc cité, ou une phrase disant qu'il n'y avait rien.
146
+ */
147
+ export declare function formatToolOutput(output: string, maxLines?: number): string;
148
+ /**
149
+ * L'outil de génération pose une QUESTION, et aucun terminal n'y répond.
150
+ *
151
+ * C'est le cas d'une colonne qui disparaît pendant qu'une autre apparaît :
152
+ * renommage (les données suivent) ou suppression puis ajout (les données sont
153
+ * perdues) — l'outil ne peut pas le deviner, et il a raison de demander.
154
+ *
155
+ * @param label - ce qui était généré, tel qu'on le cite à l'utilisateur.
156
+ * @param replay - la commande à rejouer dans un terminal interactif.
157
+ * @returns le refus, prêt pour la ligne de commande comme pour l'écran.
158
+ */
159
+ export declare function generationNeedsAnswer(label: string, replay: string): IResolutionRefusal;
160
+ /**
161
+ * L'outil de génération s'est arrêté, et ce qu'il a dit est remonté TEL QUEL.
162
+ *
163
+ * 🔴 Ne JAMAIS remplacer sa sortie par une hypothèse. Le fourre-tout des
164
+ * commandes de migration explique tout par une base injoignable ou des droits
165
+ * manquants : sur un schéma qui retire une colonne, les deux sont FAUX, et ils
166
+ * envoient vérifier une base qui répond très bien pendant que la cause est dans
167
+ * le fichier d'entité qu'on vient d'éditer. Un message d'erreur est cru PARCE
168
+ * QU'il est précis.
169
+ *
170
+ * @param label - ce qui était généré, tel qu'on le cite à l'utilisateur.
171
+ * @param status - code de sortie observé (il vaut `0` même en échec).
172
+ * @param output - sortie complète de l'outil.
173
+ * @returns le refus, prêt pour la ligne de commande comme pour l'écran.
174
+ */
175
+ export declare function generationFailed(label: string, status: number | null, output: string): IResolutionRefusal;
176
+ /**
177
+ * La lecture du schéma d'une base existante a échoué.
178
+ *
179
+ * Contrairement à la génération, celle-ci INTERROGE la base : une base muette
180
+ * ou des droits insuffisants sont ici des explications légitimes.
181
+ *
182
+ * @param label - ce qui était lu, tel qu'on le cite à l'utilisateur.
183
+ * @param status - code de sortie observé.
184
+ * @param output - sortie complète de l'outil.
185
+ * @returns le refus, prêt pour la ligne de commande comme pour l'écran.
186
+ */
187
+ export declare function introspectFailed(label: string, status: number | null, output: string): IResolutionRefusal;
127
188
  /**
128
189
  * Erreur portant un {@link IResolutionRefusal} déjà composé.
129
190
  *
package/docs/index.md CHANGED
@@ -113,7 +113,7 @@ loin, et ces deux pas expliquent la taille de cette page.
113
113
 
114
114
  **1. Il porte le framework, pas seulement ton métier.** Les huit briques durables (session, users,
115
115
  jetons, passkeys, TOTP, audit, webhooks, idempotence) ont leurs tables **dans le module**, déclarées
116
- au démarrage par `registerDrizzleFrameworkStores()` (`registerStores.ts:149`). Aucune application
116
+ au démarrage par `registerDrizzleFrameworkStores()` (`registerStores.ts:184`). Aucune application
117
117
  n'écrit de `registerXStore(...)`.
118
118
 
119
119
  **2. Il reconstruit la portabilité que Drizzle n'offre pas.** Drizzle est schema-as-code
@@ -417,7 +417,7 @@ la piste à vérifier.
417
417
 
418
418
  Pour les dialectes réseau, la connexion fait un **ping réel** au démarrage : les pools `pg` et `mysql2`
419
419
  sont paresseux, sans ce `SELECT 1` une base morte « se connecterait » et n'échouerait qu'à la première
420
- requête métier (`#connectPostgres()`, `DrizzleOrm.ts:597` · `#connectMysql()`, `DrizzleOrm.ts:1200`).
420
+ requête métier (`#connectPostgres()`, `DrizzleOrm.ts:998` · `#connectMysql()`, `DrizzleOrm.ts:1296`).
421
421
 
422
422
  ## Dialectes — une base par déploiement, un seul code
423
423
 
@@ -510,7 +510,7 @@ connexion**, donc leurs tables sont créées au moment où l'ORM s'ouvre.
510
510
  | `DrizzleTransaction` | `BEGIN`/`COMMIT`/`ROLLBACK` pilotés à la main, sur les trois dialectes | `DrizzleTransaction.ts:70` |
511
511
  | `buildFrameworkTable` | une spécification logique → la table du dialecte demandé | `colKit.ts:543` |
512
512
  | `queryKit` (interne) | le SQL brut des entités framework, émis **et exécuté** par dialecte | `findUserIdBySocialProvider()` (`queryKit.ts:76`) |
513
- | `registerStores` | l'auto-enregistrement des huit briques | `registerDrizzleFrameworkStores()` (`registerStores.ts:149`) |
513
+ | `registerStores` | l'auto-enregistrement des huit briques | `registerDrizzleFrameworkStores()` (`registerStores.ts:184`) |
514
514
 
515
515
  ### Le DDL dérivé — comment les tables apparaissent
516
516
 
@@ -624,7 +624,7 @@ const rows = await db.all(sql`
624
624
  `);
625
625
  ```
626
626
 
627
- C'est l'**anti-blocage** du modèle Repository (`getNativeConnection()`, `DrizzleOrm.ts:1405`) : CTE,
627
+ C'est l'**anti-blocage** du modèle Repository (`getNativeConnection()`, `DrizzleOrm.ts:1524`) : CTE,
628
628
  fonctions de fenêtre, sous-requêtes corrélées, jointures arbitraires. Deux contreparties assumées :
629
629
  ce SQL n'est plus portable entre dialectes, et il **ne passe pas** par la sonde de profilage des
630
630
  requêtes.
@@ -678,12 +678,12 @@ use("@nodefony/security", {
678
678
  > **Les TSDoc de deux fichiers du module décrivent une « approche B » où l'application câblerait
679
679
  > elle-même la fabrique et l'entité d'idempotence** (`DrizzleIdempotencyStore.ts:97` ·
680
680
  > `idempotencyEntity.ts:32`). Ce n'est plus le comportement : le module inscrit lui-même la fabrique
681
- > via `registerIdempotencyStore()` (`registerStores.ts:316`). **Le code exécuté fait autorité** —
681
+ > via `registerIdempotencyStore()` (`registerStores.ts:356`). **Le code exécuté fait autorité** —
682
682
  > ces commentaires sont périmés.
683
683
 
684
684
  ### Le mécanisme, et comment garder la main
685
685
 
686
- `registerDrizzleFrameworkStores()` (`registerStores.ts:149`) est appelé à l'enregistrement du module,
686
+ `registerDrizzleFrameworkStores()` (`registerStores.ts:184`) est appelé à l'enregistrement du module,
687
687
  avec le dialecte du connecteur `default`. Pour chaque brique, il déclare l'entité puis inscrit la
688
688
  fabrique du store dans le registre de son propriétaire (`http`, `security` ou `framework`). Deux
689
689
  garde-fous préservent ta liberté :
@@ -696,7 +696,7 @@ Et deux garde-fous protègent de l'incohérence :
696
696
  - une brique **non portée** sur le dialecte configuré n'est ni déclarée ni fabricable — la
697
697
  sélectionner échoue franchement au démarrage plutôt que de produire une table fantôme ;
698
698
  - la fabrique **capture le dialecte** de son enregistrement : elle refuse un ORM d'un autre dialecte
699
- (`resolveConnectedOrm()`, `registerStores.ts:112`).
699
+ (`resolveConnectedOrm()`, `registerStores.ts:143`).
700
700
 
701
701
  Pour tout couper — module « données seulement », aucune entité ni fabrique framework :
702
702
 
@@ -831,7 +831,7 @@ faire lui-même).
831
831
  Côté écrans : **Database**, **ORM (vue d'ensemble et par entité)** et **Stores** — ce dernier répond à
832
832
  la question « où sont écrites mes données ? » pour chaque brique.
833
833
 
834
- La sonde d'un connecteur s'adapte au dialecte (`probe()`, `DrizzleOrm.ts:1495`) :
834
+ La sonde d'un connecteur s'adapte au dialecte (`probe()`, `DrizzleOrm.ts:1614`) :
835
835
 
836
836
  - **SQLite** → `storage` : taille du fichier, mode de journal, pages libres (lus par `PRAGMA`) ;
837
837
  - **PostgreSQL / MySQL** → `pool` : taille, connexions libres, empruntées, en attente — **compteurs en
@@ -843,7 +843,7 @@ que promettre en silence — c'est le principe « superviser sans peser sur la p
843
843
 
844
844
  Chaque store expose aussi son **emplacement physique** pour l'écran Stores : le chemin du fichier
845
845
  SQLite, relativisé (anti-fuite d'information), et `undefined` pour un backend réseau — dont
846
- l'emplacement **est** l'infra déclarée, déjà affichée ailleurs (`location`, `DrizzleOrm.ts:372`).
846
+ l'emplacement **est** l'infra déclarée, déjà affichée ailleurs (`location`, `DrizzleOrm.ts:415`).
847
847
 
848
848
  ## ⚡ Performance & mémoire
849
849
 
@@ -106,6 +106,9 @@ nodefony orm:migrate:repair
106
106
 
107
107
  # Développement seulement : supprime et recrée la base du connecteur.
108
108
  nodefony orm:reset
109
+
110
+ # … et si la base porte des comptes, des passkeys ou des seconds facteurs :
111
+ nodefony orm:reset --yes --drop-accounts
109
112
  ```
110
113
 
111
114
  Toutes acceptent `--connector <nom>` (défaut : `default`) et `--json`. Le flux `--json` est **pur** :
@@ -358,7 +361,7 @@ Trois propriétés en découlent, et elles valent d'être nommées :
358
361
  - **Rien n'est appliqué deux fois.** L'historique (`nodefony_migrations`, `types.ts:23`) est écrit
359
362
  dans la même transaction que le DDL, là où le moteur le permet.
360
363
  - **Un pod en retard ne reçoit pas de trafic.** En `none` comme en `migrate`, l'état du schéma est
361
- publié à la sonde de disponibilité (`#publishReadiness()`, `DrizzleService.ts:345`) : `/readyz`
364
+ publié à la sonde de disponibilité (`#publishReadiness()`, `DrizzleService.ts:445`) : `/readyz`
362
365
  répond 503, l'orchestrateur sort l'exemplaire du répartiteur de charge, et l'ancien continue de
363
366
  servir. `/livez` n'est jamais touché — un schéma en retard n'est pas un processus malade, et le
364
367
  redémarrer ne réparerait rien. La vérification est rejouée toutes les 15 secondes : dès que le
@@ -508,6 +511,23 @@ conforme, la clé est ABSENTE** (jamais un objet vide) : `.divergence == null` s
508
511
  L'écran lisible en dit autant : le résumé nomme les trois premières entrées de chaque famille, et la
509
512
  liste complète ne se déroule que lorsqu'elle ne tient plus dans la phrase.
510
513
 
514
+ ### Ce que `--yes` ne suffit pas à effacer
515
+
516
+ `orm:reset` **refuse** sur une base qui porte de l'irremplaçable, et nomme ce qu'elle allait
517
+ supprimer avec le nombre de lignes : des comptes au-delà de celui que votre semis repose, des
518
+ passkeys (liées à l'appareil — personne ne peut les réémettre), des seconds facteurs. Il faut alors
519
+ `--drop-accounts` en plus de `--yes` : le drapeau dit que vous avez vu la liste.
520
+
521
+ Ce qui se reconstitue ne déclenche rien — une session et un jeton se refont par un login, une trace
522
+ d'audit est une trace. Et **une application fraîche n'est pas gênée** : le compte d'administration
523
+ que son semis repose à chaque démarrage ne compte pas comme une perte. Une garde qui se lève sur le
524
+ cas normal s'apprend à être contournée avant le jour où elle a raison.
525
+
526
+ Quand une migration a échoué et que son marqueur est posé, le refus nomme d'abord
527
+ `orm:migrate:repair` — qui lève le marqueur **sans rien effacer**. C'est le seul moment où l'on sait
528
+ que la voie non destructrice s'applique, et c'est exactement la situation où l'on est tenté de tout
529
+ remettre à zéro.
530
+
511
531
  Les gestes proposés suivent **l'environnement** : `orm:reset` efface, elle n'est acceptée qu'en
512
532
  développement, et elle n'est donc proposée que là. Ailleurs, la sortie renvoie vers l'écriture d'une
513
533
  migration correctrice (`orm:generate --custom`) puis son application.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/drizzle",
3
- "version": "10.0.0-alpha.4",
3
+ "version": "10.0.0-alpha.6",
4
4
  "description": "Moteur SQL de Nodefony sur Drizzle ORM (SQLite, PostgreSQL, MySQL) : dépôts typés, migrations de schéma et adoption d'une base existante",
5
5
  "nodefony": {
6
6
  "storeKind": "durable",
@@ -57,33 +57,33 @@
57
57
  "dependencies": {
58
58
  "drizzle-orm": "0.45.2",
59
59
  "tslib": "2.8.1",
60
- "zod": "^4.4.3"
60
+ "zod": "^4.6.1"
61
61
  },
62
62
  "devDependencies": {
63
- "@nodefony/framework": "^10.0.0-alpha.4",
64
- "@nodefony/http": "^10.0.0-alpha.4",
65
- "@nodefony/orm-core": "^10.0.0-alpha.4",
66
- "@nodefony/security": "^10.0.0-alpha.4",
67
- "@nodefony/user": "^10.0.0-alpha.4",
63
+ "@nodefony/framework": "^10.0.0-alpha.6",
64
+ "@nodefony/http": "^10.0.0-alpha.6",
65
+ "@nodefony/orm-core": "^10.0.0-alpha.6",
66
+ "@nodefony/security": "^10.0.0-alpha.6",
67
+ "@nodefony/user": "^10.0.0-alpha.6",
68
68
  "@types/better-sqlite3": "9.6.0",
69
- "@types/node": "26.4.1",
69
+ "@types/node": "26.5.1",
70
70
  "@types/pg": "8.23.1",
71
71
  "better-sqlite3": "13.0.3",
72
72
  "drizzle-kit": "0.31.10",
73
73
  "mysql2": "3.24.4",
74
- "nodefony": "^10.0.0-alpha.4",
74
+ "nodefony": "^10.0.0-alpha.6",
75
75
  "pg": "8.23.0",
76
76
  "rimraf": "6.1.3"
77
77
  },
78
78
  "peerDependencies": {
79
- "@nodefony/framework": "^10.0.0-alpha.4",
80
- "@nodefony/http": "^10.0.0-alpha.4",
81
- "@nodefony/orm-core": "^10.0.0-alpha.4",
82
- "@nodefony/security": "^10.0.0-alpha.4",
83
- "@nodefony/user": "^10.0.0-alpha.4",
79
+ "@nodefony/framework": "^10.0.0-alpha.6",
80
+ "@nodefony/http": "^10.0.0-alpha.6",
81
+ "@nodefony/orm-core": "^10.0.0-alpha.6",
82
+ "@nodefony/security": "^10.0.0-alpha.6",
83
+ "@nodefony/user": "^10.0.0-alpha.6",
84
84
  "better-sqlite3": "^13.0.0",
85
85
  "mysql2": "^3.24.0",
86
- "nodefony": "^10.0.0-alpha.4",
86
+ "nodefony": "^10.0.0-alpha.6",
87
87
  "pg": "^8.23.0"
88
88
  },
89
89
  "repository": {
@@ -91,11 +91,11 @@
91
91
  "url": "git+https://github.com/nodefony/nodefony-core.git",
92
92
  "directory": "src/packages/@nodefony/drizzle"
93
93
  },
94
- "license": "CECILL-B",
94
+ "license": "Apache-2.0",
95
95
  "licenses": [
96
96
  {
97
- "type": "CECILL-B",
98
- "url": "http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html"
97
+ "type": "Apache-2.0",
98
+ "url": "https://www.apache.org/licenses/LICENSE-2.0"
99
99
  }
100
100
  ],
101
101
  "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",