@nodefony/drizzle 10.0.0-alpha.1

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.
Files changed (143) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +162 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +105 -0
  6. package/dist/nodefony/command/migrateShared.js +247 -0
  7. package/dist/nodefony/command/orm-generate.js +356 -0
  8. package/dist/nodefony/command/orm-migrate-baseline.js +208 -0
  9. package/dist/nodefony/command/orm-migrate-repair.js +114 -0
  10. package/dist/nodefony/command/orm-migrate-status.js +67 -0
  11. package/dist/nodefony/command/orm-migrate.js +141 -0
  12. package/dist/nodefony/command/orm-reset.js +166 -0
  13. package/dist/nodefony/config/config.js +107 -0
  14. package/dist/nodefony/config/defineModuleConfig.js +63 -0
  15. package/dist/nodefony/entity/auditEventEntity.js +93 -0
  16. package/dist/nodefony/entity/colKit.js +260 -0
  17. package/dist/nodefony/entity/idempotencyEntity.js +74 -0
  18. package/dist/nodefony/entity/sessionEntity.js +75 -0
  19. package/dist/nodefony/entity/tokenEntity.js +198 -0
  20. package/dist/nodefony/entity/totpSecretEntity.js +98 -0
  21. package/dist/nodefony/entity/userTable.js +141 -0
  22. package/dist/nodefony/entity/webAuthnCredentialEntity.js +114 -0
  23. package/dist/nodefony/entity/webhookEndpointEntity.js +106 -0
  24. package/dist/nodefony/interfaces/IDrizzleConfig.js +1 -0
  25. package/dist/nodefony/interfaces/index.js +1 -0
  26. package/dist/nodefony/migrations-schema/mysql.js +48 -0
  27. package/dist/nodefony/migrations-schema/postgres.js +48 -0
  28. package/dist/nodefony/migrations-schema/sqlite.js +48 -0
  29. package/dist/nodefony/registerStores.js +218 -0
  30. package/dist/nodefony/service/DrizzleService.js +282 -0
  31. package/dist/nodefony/src/DrizzleAuditStore.js +203 -0
  32. package/dist/nodefony/src/DrizzleIdempotencyStore.js +278 -0
  33. package/dist/nodefony/src/DrizzleTokenStore.js +244 -0
  34. package/dist/nodefony/src/DrizzleTotpSecretStore.js +151 -0
  35. package/dist/nodefony/src/DrizzleUserRepository.js +217 -0
  36. package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +159 -0
  37. package/dist/nodefony/src/DrizzleWebhookStore.js +169 -0
  38. package/dist/nodefony/src/SessionStorage.js +259 -0
  39. package/dist/nodefony/src/connectorTarget.js +59 -0
  40. package/dist/nodefony/src/likeSql.js +50 -0
  41. package/dist/nodefony/src/migrator/DrizzleMigrator.js +775 -0
  42. package/dist/nodefony/src/migrator/adopt.js +553 -0
  43. package/dist/nodefony/src/migrator/appSchema.js +414 -0
  44. package/dist/nodefony/src/migrator/catalog.js +76 -0
  45. package/dist/nodefony/src/migrator/destructive.js +213 -0
  46. package/dist/nodefony/src/migrator/divergence.js +84 -0
  47. package/dist/nodefony/src/migrator/drivers/index.js +39 -0
  48. package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +147 -0
  49. package/dist/nodefony/src/migrator/drivers/postgresDriver.js +151 -0
  50. package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +121 -0
  51. package/dist/nodefony/src/migrator/explain.js +565 -0
  52. package/dist/nodefony/src/migrator/hash.js +47 -0
  53. package/dist/nodefony/src/migrator/history.js +219 -0
  54. package/dist/nodefony/src/migrator/index.js +16 -0
  55. package/dist/nodefony/src/migrator/kit.js +296 -0
  56. package/dist/nodefony/src/migrator/name.js +68 -0
  57. package/dist/nodefony/src/migrator/paths.js +88 -0
  58. package/dist/nodefony/src/migrator/refusals.js +143 -0
  59. package/dist/nodefony/src/migrator/resolve.js +281 -0
  60. package/dist/nodefony/src/migrator/schemaDiff.js +86 -0
  61. package/dist/nodefony/src/migrator/sources.js +419 -0
  62. package/dist/nodefony/src/migrator/status.js +231 -0
  63. package/dist/nodefony/src/migrator/types.js +91 -0
  64. package/dist/nodefony/src/orm-core/DrizzleOrm.js +1154 -0
  65. package/dist/nodefony/src/orm-core/DrizzleRepository.js +610 -0
  66. package/dist/nodefony/src/orm-core/DrizzleTransaction.js +106 -0
  67. package/dist/nodefony/src/orm-core/index.js +4 -0
  68. package/dist/nodefony/src/queryKit.js +318 -0
  69. package/dist/nodefony/src/safeTarget.js +55 -0
  70. package/dist/types/index.d.ts +76 -0
  71. package/dist/types/nodefony/command/migrateShared.d.ts +137 -0
  72. package/dist/types/nodefony/command/orm-generate.d.ts +53 -0
  73. package/dist/types/nodefony/command/orm-migrate-baseline.d.ts +87 -0
  74. package/dist/types/nodefony/command/orm-migrate-repair.d.ts +66 -0
  75. package/dist/types/nodefony/command/orm-migrate-status.d.ts +38 -0
  76. package/dist/types/nodefony/command/orm-migrate.d.ts +65 -0
  77. package/dist/types/nodefony/command/orm-reset.d.ts +55 -0
  78. package/dist/types/nodefony/config/config.d.ts +110 -0
  79. package/dist/types/nodefony/config/defineModuleConfig.d.ts +24 -0
  80. package/dist/types/nodefony/entity/auditEventEntity.d.ts +56 -0
  81. package/dist/types/nodefony/entity/colKit.d.ts +130 -0
  82. package/dist/types/nodefony/entity/idempotencyEntity.d.ts +52 -0
  83. package/dist/types/nodefony/entity/sessionEntity.d.ts +50 -0
  84. package/dist/types/nodefony/entity/tokenEntity.d.ts +53 -0
  85. package/dist/types/nodefony/entity/totpSecretEntity.d.ts +61 -0
  86. package/dist/types/nodefony/entity/userTable.d.ts +65 -0
  87. package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +57 -0
  88. package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +58 -0
  89. package/dist/types/nodefony/interfaces/IDrizzleConfig.d.ts +17 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  91. package/dist/types/nodefony/migrations-schema/mysql.d.ts +9 -0
  92. package/dist/types/nodefony/migrations-schema/postgres.d.ts +9 -0
  93. package/dist/types/nodefony/migrations-schema/sqlite.d.ts +9 -0
  94. package/dist/types/nodefony/registerStores.d.ts +52 -0
  95. package/dist/types/nodefony/service/DrizzleService.d.ts +27 -0
  96. package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +65 -0
  97. package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +129 -0
  98. package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +146 -0
  99. package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +60 -0
  100. package/dist/types/nodefony/src/DrizzleUserRepository.d.ts +67 -0
  101. package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +62 -0
  102. package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +79 -0
  103. package/dist/types/nodefony/src/SessionStorage.d.ts +72 -0
  104. package/dist/types/nodefony/src/connectorTarget.d.ts +48 -0
  105. package/dist/types/nodefony/src/likeSql.d.ts +29 -0
  106. package/dist/types/nodefony/src/migrator/DrizzleMigrator.d.ts +94 -0
  107. package/dist/types/nodefony/src/migrator/adopt.d.ts +280 -0
  108. package/dist/types/nodefony/src/migrator/appSchema.d.ts +223 -0
  109. package/dist/types/nodefony/src/migrator/catalog.d.ts +100 -0
  110. package/dist/types/nodefony/src/migrator/destructive.d.ts +123 -0
  111. package/dist/types/nodefony/src/migrator/divergence.d.ts +61 -0
  112. package/dist/types/nodefony/src/migrator/drivers/index.d.ts +26 -0
  113. package/dist/types/nodefony/src/migrator/drivers/mysqlDriver.d.ts +83 -0
  114. package/dist/types/nodefony/src/migrator/drivers/postgresDriver.d.ts +81 -0
  115. package/dist/types/nodefony/src/migrator/drivers/sqliteDriver.d.ts +53 -0
  116. package/dist/types/nodefony/src/migrator/explain.d.ts +424 -0
  117. package/dist/types/nodefony/src/migrator/hash.d.ts +39 -0
  118. package/dist/types/nodefony/src/migrator/history.d.ts +139 -0
  119. package/dist/types/nodefony/src/migrator/index.d.ts +20 -0
  120. package/dist/types/nodefony/src/migrator/kit.d.ts +141 -0
  121. package/dist/types/nodefony/src/migrator/name.d.ts +52 -0
  122. package/dist/types/nodefony/src/migrator/paths.d.ts +54 -0
  123. package/dist/types/nodefony/src/migrator/refusals.d.ts +170 -0
  124. package/dist/types/nodefony/src/migrator/resolve.d.ts +201 -0
  125. package/dist/types/nodefony/src/migrator/schemaDiff.d.ts +112 -0
  126. package/dist/types/nodefony/src/migrator/sources.d.ts +119 -0
  127. package/dist/types/nodefony/src/migrator/status.d.ts +132 -0
  128. package/dist/types/nodefony/src/migrator/types.d.ts +273 -0
  129. package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +241 -0
  130. package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +98 -0
  131. package/dist/types/nodefony/src/orm-core/DrizzleTransaction.d.ts +71 -0
  132. package/dist/types/nodefony/src/orm-core/index.d.ts +11 -0
  133. package/dist/types/nodefony/src/queryKit.d.ts +136 -0
  134. package/dist/types/nodefony/src/safeTarget.d.ts +42 -0
  135. package/docs/index.md +954 -0
  136. package/docs/migrations.md +691 -0
  137. package/migrations/mysql/0000_framework_init.sql +137 -0
  138. package/migrations/mysql/meta/_journal.json +13 -0
  139. package/migrations/postgres/0000_framework_init.sql +128 -0
  140. package/migrations/postgres/meta/_journal.json +13 -0
  141. package/migrations/sqlite/0000_framework_init.sql +127 -0
  142. package/migrations/sqlite/meta/_journal.json +13 -0
  143. package/package.json +126 -0
@@ -0,0 +1,223 @@
1
+ import type { SqlDialect } from "../../interfaces/IDrizzleConfig.js";
2
+ /** Une table trouvée dans un fichier d'entité de l'application. */
3
+ export interface IDiscoveredTable {
4
+ /** Chemin absolu du fichier qui l'exporte. */
5
+ file: string;
6
+ /** Nom sous lequel le fichier l'exporte (`postTable`, `PostEntity`…). */
7
+ exportName: string;
8
+ /** Nom de la table en base — c'est lui qui compte, pas l'identifiant JS. */
9
+ tableName: string;
10
+ /** Moteur pour lequel elle est écrite — `null` si aucun des trois. */
11
+ dialect: SqlDialect | null;
12
+ }
13
+ /** Ce qu'un fichier d'entité a refusé de livrer. */
14
+ export interface IUnreadableEntityFile {
15
+ /** Chemin absolu du fichier. */
16
+ file: string;
17
+ /** La cause, telle que l'import l'a rendue. */
18
+ cause: string;
19
+ }
20
+ /** Résultat d'une découverte : ce qui a été lu, et ce qui a résisté. */
21
+ export interface IDiscoveredSchema {
22
+ tables: IDiscoveredTable[];
23
+ unreadable: IUnreadableEntityFile[];
24
+ }
25
+ /**
26
+ * Fichiers d'entités d'une cible (l'application, ou l'un de ses modules).
27
+ *
28
+ * Les `*.schema.ts` sont écartés : c'est la convention du générateur pour les
29
+ * contrats d'entrée (Zod), qui n'ont rien à faire dans un schéma de base.
30
+ *
31
+ * @param targetDir - dossier d'une cible (contient `index.ts` + `nodefony/`).
32
+ * @returns les chemins absolus, triés — l'ordre doit être le même partout.
33
+ */
34
+ export declare function entityFilesOf(targetDir: string): Promise<string[]>;
35
+ /**
36
+ * Importe des fichiers d'entités et relève les tables qu'ils exportent.
37
+ *
38
+ * L'import est la seule façon HONNÊTE de savoir ce qu'un fichier fournit : lire
39
+ * un nom d'export au motif qu'il finit par `Table` marcherait sur le code que
40
+ * le générateur écrit, et sur rien d'autre — or ces fichiers sont faits pour
41
+ * être modifiés à la main, c'est même écrit dans leur en-tête.
42
+ *
43
+ * Un fichier qui refuse de s'importer n'est PAS ignoré : il est rendu à
44
+ * l'appelant avec sa cause. C'est presque toujours le même défaut — un accès au
45
+ * kernel à l'évaluation du module — et le taire produirait une migration
46
+ * amputée là où il faut une phrase.
47
+ *
48
+ * @param files - chemins absolus des fichiers d'entités.
49
+ * @returns les tables trouvées et les fichiers illisibles.
50
+ */
51
+ export declare function collectTables(files: readonly string[]): Promise<IDiscoveredSchema>;
52
+ /**
53
+ * Moteur pour lequel une table est écrite.
54
+ *
55
+ * Une entité d'application est du Drizzle **natif** : `sqliteTable`, `pgTable` et
56
+ * `mysqlTable` produisent trois objets différents, et une table écrite pour un
57
+ * moteur est simplement IGNORÉE par l'outil quand il en génère un autre — sans
58
+ * un mot, comme d'habitude. Constaté sur une application témoin : six tables
59
+ * découvertes, quatre écrites, et un message qui annonçait six.
60
+ *
61
+ * @param table - table relevée dans un fichier d'entité.
62
+ * @returns le dialecte, ou `null` si ce n'est aucun des trois.
63
+ */
64
+ export declare function dialectOf(table: unknown): SqlDialect | null;
65
+ /**
66
+ * Spécificateur d'import de `from` vers `to`, écrit pour VOYAGER.
67
+ *
68
+ * Un spécificateur s'écrit en `/` sur les trois systèmes — c'est du texte que
69
+ * lit un outil, pas un accès disque. L'extension est explicite : le module
70
+ * temporaire est compilé par l'outil, qui doit savoir quoi ouvrir sans deviner.
71
+ *
72
+ * @param fromFile - fichier qui contiendra l'import.
73
+ * @param toFile - fichier visé.
74
+ * @returns un spécificateur relatif, toujours préfixé `./` ou `../`.
75
+ */
76
+ export declare function importSpecifier(fromFile: string, toFile: string): string;
77
+ /**
78
+ * Écrit le module temporaire que `drizzle-kit` lira comme « le schéma ».
79
+ *
80
+ * Chaque table reçoit un alias unique et neutre : deux fichiers peuvent très
81
+ * bien exporter deux tables sous le même identifiant JS, et un ré-export en
82
+ * étoile les rendrait AMBIGUËS — l'ambiguïté n'est pas une erreur en ESM, c'est
83
+ * une absence silencieuse. Le nom de la table en base, lui, n'est pas touché :
84
+ * il vit dans l'appel `…Table("nom")`, pas dans l'identifiant.
85
+ *
86
+ * @param file - chemin du module à écrire.
87
+ * @param tables - tables à ré-exporter, dans l'ordre de découverte.
88
+ * @returns le contenu écrit (rendu pour les bancs).
89
+ */
90
+ export declare function writeSchemaModule(file: string, tables: readonly IDiscoveredTable[]): Promise<string>;
91
+ /**
92
+ * Schéma PostgreSQL réellement visé par une URL de connexion.
93
+ *
94
+ * 🔴 L'outil d'introspection ne suit PAS le `search_path` porté par l'URL : il
95
+ * lit `public`, quoi qu'on lui donne. Sans cette dérivation, adopter une
96
+ * application logée dans un schéma dédié — le montage habituel d'une base
97
+ * mutualisée — écrivait la référence des tables de `public`, c'est-à-dire
98
+ * celles de quelqu'un d'autre, et déclarait absentes les siennes. Constaté sur
99
+ * un serveur réel : la référence décrivait trois tables étrangères au projet.
100
+ *
101
+ * Deux formes sont reconnues, parce que les deux circulent : le `search_path`
102
+ * passé dans `options`, et le paramètre `schema` que posent certains outils.
103
+ * Une URL sans rien rend `null` — l'appelant laisse alors le défaut de l'outil,
104
+ * qui est le bon.
105
+ *
106
+ * @param url - URL de connexion, telle que le connecteur la porte.
107
+ * @returns le premier schéma du chemin de recherche, ou `null`.
108
+ */
109
+ export declare function postgresSchemaOf(url: string): string | null;
110
+ /**
111
+ * Écrit la configuration `drizzle-kit` d'une génération d'application.
112
+ *
113
+ * 🔴 **Les chemins y sont RELATIFS, et ce n'est pas un style.** L'outil préfixe
114
+ * son dossier de sortie par `./` : un chemin absolu devient `.//Users/…`, la
115
+ * lecture échoue — et l'échec se présente comme un succès, puisque l'outil rend
116
+ * 0 quand il rate. La configuration est donc écrite pour être lue depuis la
117
+ * racine de l'application, qui est le dossier d'exécution.
118
+ *
119
+ * `tablesFilter` exclut ce que l'application ne possède pas : les tables du
120
+ * framework et la table d'historique. Sans lui, une entité d'application qui
121
+ * référence une table du framework la ferait entrer dans le diff, et la
122
+ * migration porterait un second `CREATE TABLE` de cette table — qui échoue en
123
+ * production, sur toute base déjà migrée.
124
+ *
125
+ * @param options - où écrire, quoi lire, où sortir, quoi exclure.
126
+ * @returns le contenu écrit (rendu pour les bancs).
127
+ */
128
+ export declare function writeKitConfig({ file, projectRoot, schemaFile, outDir, dialect, excludedTables, dbUrl, }: {
129
+ file: string;
130
+ projectRoot: string;
131
+ schemaFile: string;
132
+ outDir: string;
133
+ dialect: SqlDialect;
134
+ excludedTables: readonly string[];
135
+ /**
136
+ * Coordonnées de connexion — posées UNIQUEMENT pour l'introspection.
137
+ *
138
+ * La génération, elle, ne touche aucune base : son diff se calcule entre les
139
+ * instantanés du dossier et les entités. Écrire des coordonnées dans sa
140
+ * configuration laisserait croire le contraire à qui relit le fichier, et
141
+ * ferait porter à une commande de lecture pure le risque d'une connexion.
142
+ */
143
+ dbUrl?: string;
144
+ }): Promise<string>;
145
+ /**
146
+ * Écrit une migration LIBRE et son entrée de journal, sans `drizzle-kit`.
147
+ *
148
+ * C'est la porte de sortie du modèle déclaratif : une vue, un déclencheur, une
149
+ * clé étrangère réelle, un remplissage de données ne se DÉDUISENT d'aucun
150
+ * schéma. Sans elle, on n'aurait le choix qu'entre renoncer et écrire un
151
+ * fichier à la main dans un journal dont le format n'est pas documenté — deux
152
+ * façons de casser l'historique.
153
+ *
154
+ * Le fichier est vide de toute instruction : un squelette qui « propose » du
155
+ * SQL est un squelette qu'on applique sans le lire.
156
+ *
157
+ * @param options - dossier de sortie, dialecte, nom de la migration.
158
+ * @returns le tag attribué et le chemin du fichier écrit.
159
+ * @throws Error si le journal existant est illisible — on n'en réécrit JAMAIS
160
+ * un par-dessus : il porte l'historique déjà appliqué en production.
161
+ */
162
+ export declare function writeCustomMigration({ outDir, dialect, name, now, }: {
163
+ outDir: string;
164
+ dialect: SqlDialect;
165
+ name: string;
166
+ now?: number;
167
+ }): Promise<{
168
+ tag: string;
169
+ file: string;
170
+ }>;
171
+ /** Une entité que le registre connaît, et la table qu'elle vise. */
172
+ export interface IExpectedEntity {
173
+ /** Nom logique de l'entité, tel qu'il est enregistré. */
174
+ entity: string;
175
+ /** Nom de la table en base. */
176
+ table: string;
177
+ }
178
+ /**
179
+ * Ce que le registre attend et que les fichiers ne fournissent PAS.
180
+ *
181
+ * C'est le seul contrôle qu'un registre puisse rendre et qu'aucun outil de
182
+ * génération ne rendra jamais : `drizzle-kit` ne sait pas ce qu'une application
183
+ * a déclaré, il ne voit que ce qu'on lui donne à lire. Sans cette confrontation,
184
+ * une entité dont le fichier a été déplacé, renommé, ou rendu illisible
185
+ * disparaît de la migration **sans un mot** — et une migration ne se corrige
186
+ * pas : elle est immuable dès qu'une base l'a reçue.
187
+ *
188
+ * Les tables du framework sont écartées des deux côtés : elles sont fournies par
189
+ * une autre source, appliquée avant.
190
+ *
191
+ * @param expected - entités du registre, sur le connecteur visé.
192
+ * @param providedTables - noms des tables que les fichiers découverts fournissent.
193
+ * @param frameworkTables - noms des tables construites par le framework.
194
+ * @returns les entités sans fournisseur, dans l'ordre reçu.
195
+ */
196
+ export declare function missingProviders(expected: readonly IExpectedEntity[], providedTables: ReadonlySet<string>, frameworkTables: ReadonlySet<string>): IExpectedEntity[];
197
+ /**
198
+ * Les tables de l'application qui usurpent une table du framework.
199
+ *
200
+ * Deux `CREATE TABLE` pour un même nom : la migration passe sur une base vierge
201
+ * et échoue sur toute base déjà migrée — c'est-à-dire en production, et nulle
202
+ * part ailleurs. C'est le pire endroit pour découvrir la faute, donc on la dit
203
+ * avant d'écrire quoi que ce soit.
204
+ *
205
+ * @param tables - tables fournies par les fichiers de l'application.
206
+ * @param frameworkTables - noms des tables construites par le framework.
207
+ * @returns les tables en conflit, avec le fichier qui les exporte.
208
+ */
209
+ export declare function usurpedTables(tables: readonly IDiscoveredTable[], frameworkTables: ReadonlySet<string>): IDiscoveredTable[];
210
+ /**
211
+ * Tables attendues sur un connecteur, telles que le REGISTRE les connaît.
212
+ *
213
+ * Le registre est la seule source qui sache ce que l'application DÉCLARE :
214
+ * les fichiers disent ce qu'elle fournit, la base ce qu'elle porte, et c'est
215
+ * le croisement des trois qui fait les verdicts. Deux commandes en dépendent —
216
+ * la génération pour repérer une entité sans fichier, l'adoption pour ne lire
217
+ * QUE les tables de l'application — et une seconde copie divergerait en
218
+ * silence.
219
+ *
220
+ * @param connector - connecteur visé.
221
+ * @returns les entités et leur table, dans l'ordre du registre.
222
+ */
223
+ export declare function registeredTables(connector: string): IExpectedEntity[];
@@ -0,0 +1,100 @@
1
+ import type { SqlDialect } from "../../config/config.js";
2
+ /**
3
+ * Lecture du catalogue du serveur — quelles tables existent, quelles colonnes
4
+ * elles portent —, écrite UNE fois par dialecte et partagée par ses deux
5
+ * porteurs.
6
+ *
7
+ * ## Pourquoi ce fichier existe
8
+ *
9
+ * Deux composants ont besoin de la même réponse, depuis deux connexions
10
+ * différentes : le pilote de l'applicateur de migrations, qui tient sa propre
11
+ * connexion, et l'ORM, qui compare son schéma déclaré à la base **sur la
12
+ * connexion qu'il a déjà**. Recopier les trois requêtes chez le second les
13
+ * ferait diverger en silence — chacune passant ses propres tests — et la
14
+ * divergence ne se verrait qu'à travers un verdict FAUX rendu à un exploitant.
15
+ *
16
+ * ## Pourquoi l'ORM n'ouvre pas simplement un pilote de migration
17
+ *
18
+ * Parce qu'une seconde connexion sur `:memory:` désigne une base **différente
19
+ * et vide**. Le diff y annoncerait toutes les tables manquantes et un
20
+ * rattrapage y écrirait dans une base que personne ne lit. Le lecteur est donc
21
+ * paramétré par l'exécuteur de requêtes de son porteur, jamais par une
22
+ * connexion à lui.
23
+ */
24
+ /**
25
+ * Exécute une requête paramétrée et rend ses lignes.
26
+ *
27
+ * Les paramètres s'écrivent `?` dans tous les dialectes ; c'est au porteur de
28
+ * traduire s'il le faut (PostgreSQL attend `$1`).
29
+ */
30
+ export type SqlQuery = <T extends Record<string, unknown>>(sql: string, params?: readonly unknown[]) => Promise<T[]>;
31
+ /** Ce dont la comparaison de schéma a besoin, et rien de plus. */
32
+ export interface ISchemaReader {
33
+ /**
34
+ * Deux noms de COLONNE désignent-ils la même, pour ce moteur ?
35
+ *
36
+ * 🔴 **La sémantique est celle du MOTEUR** : si le serveur sait résoudre ce
37
+ * nom dans un `SELECT`, la colonne existe pour l'application ; sinon la
38
+ * requête du code échouerait vraiment, et l'annoncer manquante est JUSTE.
39
+ *
40
+ * - **SQLite** — sans distinction de casse, comme sa résolution de noms.
41
+ * - **MySQL** — sans distinction de casse, sur **toutes** les plateformes :
42
+ * contrairement aux tables, les noms de colonnes ne dépendent pas de
43
+ * `lower_case_table_names`.
44
+ * - **PostgreSQL** — EXACT : les identifiants sont cités (Drizzle cite tout),
45
+ * donc `SELECT "createdAt"` sur une colonne `createdat` échoue pour de bon.
46
+ *
47
+ * ⚠️ **Il n'y a délibérément PAS d'équivalent synchrone pour les TABLES.**
48
+ * Leur sensibilité à la casse dépend de la MACHINE — `lower_case_table_names`
49
+ * vaut `0` sur Linux (sensible, constaté sur MySQL 8.4) et `1` ou `2`
50
+ * ailleurs. Une règle déduite du seul dialecte serait donc fausse une fois
51
+ * sur deux, sans que rien ne le dise. Elle se **CONSTATE** par
52
+ * {@link ISchemaReader.tableExists}, qui interroge le catalogue du serveur et
53
+ * hérite de sa collation — jamais elle ne se déduit.
54
+ *
55
+ * @param declared - nom tel que le code le déclare.
56
+ * @param actual - nom tel que la base le rend.
57
+ * @returns `true` si le moteur les résoudrait vers la même colonne.
58
+ */
59
+ sameColumnName(declared: string, actual: string): boolean;
60
+ /** La table existe-t-elle dans le schéma courant de la connexion ? */
61
+ tableExists(table: string): Promise<boolean>;
62
+ /** Colonnes de la table, telles que la base les déclare. */
63
+ columnsOf(table: string): Promise<string[]>;
64
+ }
65
+ /**
66
+ * Compare deux noms de COLONNE selon la résolution du moteur.
67
+ *
68
+ * Exportée parce qu'elle porte une RÈGLE : la recopier ailleurs la ferait
69
+ * diverger, et une divergence de casse ne se voit que sur une base adoptée,
70
+ * c'est-à-dire chez l'utilisateur.
71
+ *
72
+ * Elle ne vaut PAS pour les tables — voir {@link ISchemaReader.sameColumnName},
73
+ * qui dit pourquoi leur sensibilité se constate au lieu de se déduire.
74
+ *
75
+ * @param dialect - moteur qui résoudrait le nom.
76
+ * @param declared - nom tel que le code le déclare.
77
+ * @param actual - nom tel que la base le rend.
78
+ * @returns `true` si le moteur les résoudrait vers la même colonne.
79
+ */
80
+ export declare function sameColumnName(dialect: SqlDialect, declared: string, actual: string): boolean;
81
+ /**
82
+ * Compose un lecteur de catalogue au-dessus d'un exécuteur de requêtes.
83
+ *
84
+ * @param dialect - dialecte du serveur interrogé.
85
+ * @param query - exécuteur du porteur (pilote de migration, ou ORM connecté).
86
+ * @returns le lecteur, sans état ni connexion propre.
87
+ */
88
+ export declare function schemaReader(dialect: SqlDialect, query: SqlQuery): ISchemaReader;
89
+ /**
90
+ * Traduit les paramètres `?` du dialecte commun en `$n` PostgreSQL.
91
+ *
92
+ * L'applicateur écrit ses requêtes une seule fois, avec la forme la plus
93
+ * répandue ; chaque pilote l'adapte. Aucune des requêtes de l'applicateur ne
94
+ * contient de littéral `?` — les valeurs, elles, sont bindées, jamais
95
+ * concaténées.
96
+ *
97
+ * @param sql - requête écrite avec des `?`.
98
+ * @returns la même requête, paramètres numérotés.
99
+ */
100
+ export declare function toDollarParams(sql: string): string;
@@ -0,0 +1,123 @@
1
+ import type { IMigrationFile } from "./types.js";
2
+ /**
3
+ * Ce qu'une migration s'apprête à DÉTRUIRE — constaté avant d'appliquer.
4
+ *
5
+ * ## Pourquoi ce garde existe, alors qu'aucun outil de migration n'en a
6
+ *
7
+ * La question posée était : « faut-il sauvegarder la base avant une
8
+ * migration ? ». La réponse honnête est **non, et personne ne le fait** —
9
+ * Flyway, Liquibase, Rails, Django, Alembic, Prisma : aucun ne sauvegarde. Les
10
+ * raisons tiennent :
11
+ *
12
+ * - l'outil n'a **ni les droits, ni la place, ni le temps**. Le compte qui migre
13
+ * a le droit de modifier le schéma, pas d'exporter toutes les données — et
14
+ * c'est exactement ce qu'on veut ;
15
+ * - la sauvegarde est un **métier d'exploitation** (instantané de volume,
16
+ * restauration à un instant donné, réplica), déjà outillé et déjà froid ;
17
+ * - un instantané pris par l'outil donnerait une **fausse assurance**.
18
+ * Restaurer une base de production est une décision et une interruption de
19
+ * service, jamais un drapeau qu'on tape par réflexe. Le pire des scénarios
20
+ * est celui où quelqu'un migre sans précaution *parce que « l'outil
21
+ * sauvegarde »*.
22
+ *
23
+ * Mais il restait un vrai trou, et c'est lui qu'on ferme ici : **appliquer un
24
+ * `DROP COLUMN` en production sans un mot**. L'outil ne sauvegarde pas — il
25
+ * **empêche d'appliquer sans savoir**. C'est ce que fait l'analyse de Atlas, et
26
+ * ce qu'aucun applicateur de l'écosystème Node ne propose.
27
+ *
28
+ * ## Ce que ce scan N'EST PAS
29
+ *
30
+ * Ce n'est pas un analyseur syntaxique SQL, et il ne prétend pas à
31
+ * l'exhaustivité : il reconnaît des formes. Une instruction destructive écrite
32
+ * d'une façon qu'il ne connaît pas passera. **Il ne remplace donc jamais la
33
+ * lecture du SQL** (`--dry-run`), et surtout pas la règle qui protège vraiment :
34
+ * ne jamais faire un changement destructif dans la même version que le code qui
35
+ * s'en sert (étendre, déployer, migrer les données, retirer une version plus
36
+ * tard). Le retour arrière porte alors sur le CODE, jamais sur la base.
37
+ *
38
+ * Un faux positif coûte une lecture et un drapeau ; un faux négatif coûte des
39
+ * données. Le scan penche donc toujours du côté du signalement.
40
+ */
41
+ /** Gravité d'une trouvaille — elles ne se traitent pas pareil. */
42
+ export type DestructiveSeverity =
43
+ /** Des données existantes disparaissent. Refusé hors développement. */
44
+ "data-loss"
45
+ /**
46
+ * Rien ne disparaît, mais l'instruction peut échouer sur des données
47
+ * existantes, ou casser le code de la version précédente. Signalé, jamais
48
+ * bloquant : c'est le lot normal d'une migration qui fait évoluer un schéma.
49
+ */
50
+ | "breaking";
51
+ /** Une instruction qui mérite d'être vue avant d'être exécutée. */
52
+ export interface IDestructiveFinding {
53
+ source: string;
54
+ tag: string;
55
+ /** Chemin du fichier — pour aller le lire. */
56
+ path: string;
57
+ severity: DestructiveSeverity;
58
+ /** Étiquette courte et stable, lisible par une machine. */
59
+ kind: string;
60
+ /** Ce qui est perdu ou risqué, en français. */
61
+ what: string;
62
+ /** L'instruction elle-même, bornée pour rester lisible. */
63
+ statement: string;
64
+ }
65
+ /**
66
+ * Cherche, dans les migrations en attente, ce qui détruit ou casse.
67
+ *
68
+ * @param files - migrations qui vont être appliquées.
69
+ * @returns une trouvaille par instruction reconnue, dans l'ordre d'application.
70
+ */
71
+ export declare function scanDestructive(files: readonly IMigrationFile[]): IDestructiveFinding[];
72
+ /** Les trouvailles qui font vraiment disparaître des données. */
73
+ export declare function dataLoss(findings: readonly IDestructiveFinding[]): IDestructiveFinding[];
74
+ /**
75
+ * Rend le bilan lisible par un humain — le fait, puis ce qu'il faut faire.
76
+ *
77
+ * @param findings - trouvailles à présenter.
78
+ * @param bloquant - la commande refuse-t-elle d'appliquer ?
79
+ * @returns le texte, sans mise en forme (l'appelant colore s'il le veut).
80
+ */
81
+ export declare function renderDestructive(findings: readonly IDestructiveFinding[], bloquant: boolean): string;
82
+ /** Les gestes proposés face à un refus destructif. */
83
+ export declare function destructiveActions(connector: string): string[];
84
+ /** Résumé d'une ligne, pour un message ou un journal. */
85
+ export declare function summarizeDestructive(findings: readonly IDestructiveFinding[], connector: string): string;
86
+ /**
87
+ * Ce lot de migrations touche-t-il des lignes qui existaient déjà ?
88
+ *
89
+ * @param files - migrations sur le point d'être appliquées.
90
+ * @returns vrai dès qu'une instruction modifie une table existante.
91
+ */
92
+ export declare function touchesExistingRows(files: readonly {
93
+ statements: readonly string[];
94
+ }[]): boolean;
95
+ /**
96
+ * Comment vérifier qu'une migration a préservé les données — **en UN geste**.
97
+ *
98
+ * Cette phrase existe parce que la sortie de SUCCÈS ne disait rien. Le produit
99
+ * annonçait « ✓ 1 migration appliquée » et s'arrêtait là ; l'agent à qui l'on
100
+ * demandait de prouver que les données avaient suivi n'avait aucun moyen sous
101
+ * les yeux, et celui qu'il inventait — repartir d'une base vide — détruit
102
+ * précisément ce qu'il fallait observer.
103
+ *
104
+ * 🔴 Elle NOMME la base, et c'est ce qui manquait au premier jet. Dire « une
105
+ * copie de la base » sans dire laquelle laisse deviner un chemin : mesuré au
106
+ * banc, l'agent a suivi le conseil, visé le mauvais fichier, vu son `cp`
107
+ * échouer en silence, puis FABRIQUÉ une base au client SQL — avec une table
108
+ * d'historique inventée. La migration a été refusée sur cette base bancale, et
109
+ * c'est ce refus qui l'a renvoyé détruire la vraie. Le produit connaissait
110
+ * pourtant l'emplacement : il le publie dans sa propre sortie.
111
+ *
112
+ * Elle nomme aussi les deux interpréteurs : « VAR=x commande » est de la
113
+ * syntaxe POSIX, que celui de Windows refuse.
114
+ *
115
+ * @param connector - connecteur visé, pour composer la commande.
116
+ * @param target - la base telle que le rapport la publie (`driver.target`),
117
+ * et son dialecte : on copie un FICHIER en sqlite, on exporte ailleurs.
118
+ * @returns la phrase à rendre après une application réussie.
119
+ */
120
+ export declare function checkDataAdvice(connector: string, target?: {
121
+ dialect: string;
122
+ target?: string;
123
+ }): string;
@@ -0,0 +1,61 @@
1
+ import type { IMigrationPlan } from "./types.js";
2
+ import { type ISchemaComparison } from "./schemaDiff.js";
3
+ /**
4
+ * Ce que la base porte face à ce que le code DÉCLARE, sans condition de verdict.
5
+ *
6
+ * Séparé de {@link describeDivergence} parce que les deux répondent à des
7
+ * questions différentes, et que la seconde REFUSE de répondre avant que le plan
8
+ * soit à jour — ce qui est juste pour un rapport d'état (un écart n'a de sens
9
+ * qu'une fois tout appliqué) et faux pour qui doit décider AVANT d'agir.
10
+ *
11
+ * Le cas qui a exigé cette séparation : l'adoption d'une base existante. Elle
12
+ * doit constater l'état RÉEL avant d'écrire quoi que ce soit dans l'historique
13
+ * — après, il est trop tard, l'affirmation est déjà gravée.
14
+ *
15
+ * Ne modifie jamais rien, et ne jette jamais : une base muette n'est pas une
16
+ * divergence, c'est une panne, qui a sa propre voie de signalement.
17
+ *
18
+ * @param connector - nom du connecteur à interroger.
19
+ * @returns les écarts nommés, ou `null` (rien à dire, ou rien d'interrogeable).
20
+ */
21
+ export declare function gapAgainstDeclared(connector: string): Promise<ISchemaComparison | null>;
22
+ /**
23
+ * La comparaison BRUTE — ce que la base porte face au code, écart ou non.
24
+ *
25
+ * Séparée de {@link gapAgainstDeclared}, qui ne rend que les écarts : il existe
26
+ * une question à laquelle « aucun écart » est une réponse pleine, et non un
27
+ * silence. Celle-ci : *la base porte-t-elle DÉJÀ les tables que je m'apprête à
28
+ * créer ?* Une base parfaitement conforme y répond « oui », et c'est justement
29
+ * le cas où il ne faut pas écrire un `CREATE TABLE`.
30
+ *
31
+ * Ne modifie jamais rien, et ne jette jamais : une base muette n'est pas une
32
+ * conformité, c'est une absence de réponse — rendue `null` pour que personne
33
+ * ne conclue à sa place.
34
+ *
35
+ * @param connector - nom du connecteur à interroger.
36
+ * @returns la comparaison, ou `null` si rien n'était interrogeable.
37
+ */
38
+ export declare function comparisonAgainstDeclared(connector: string): Promise<ISchemaComparison | null>;
39
+ /**
40
+ * Ce qui diverge, NOMMÉ — ou `null` quand il n'y a rien à dire.
41
+ *
42
+ * Producteur UNIQUE de la troisième source : le verdict, la phrase française,
43
+ * la charge utile `--json` et la sonde de disponibilité lisent tous ce même
44
+ * retour. Rendre un booléen ici et recalculer le détail ailleurs ferait deux
45
+ * lectures de la base pour une seule question, et deux réponses qui finiraient
46
+ * par se contredire.
47
+ *
48
+ * Répond `null` sans rien interroger dans tous les cas où la réponse ne
49
+ * changerait rien : plan déjà porteur d'un verdict, connecteur absent du
50
+ * registre, connecteur non connecté, ORM d'une autre nature (mongoose n'a pas
51
+ * de schéma déclaré à comparer). Répond `null` aussi quand la comparaison a eu
52
+ * lieu et n'a rien trouvé — l'absence d'écart ne garde pas d'objet vide en
53
+ * mémoire, et l'appelant n'a qu'un test à écrire.
54
+ *
55
+ * **Ne modifie jamais rien** — le rattrapage additif est le travail du mode de
56
+ * schéma dérivé, au démarrage, et de lui seul.
57
+ *
58
+ * @param plan - plan calculé par l'applicateur, en lecture seule.
59
+ * @returns les écarts nommés, ou `null` s'il n'y en a pas à publier.
60
+ */
61
+ export declare function describeDivergence(plan: IMigrationPlan): Promise<ISchemaComparison | null>;
@@ -0,0 +1,26 @@
1
+ import type { SqlDialect } from "../../../config/config.js";
2
+ import type { IMigrationDriver } from "../types.js";
3
+ export { SqliteMigrationDriver } from "./sqliteDriver.js";
4
+ export { PostgresMigrationDriver, PG_LOCK_KEY } from "./postgresDriver.js";
5
+ export { MysqlMigrationDriver, MYSQL_LOCK_NAME_SQL, MYSQL_LOCK_PREFIX, } from "./mysqlDriver.js";
6
+ /** Cible de connexion de l'applicateur. */
7
+ export interface IMigrationTarget {
8
+ /** Dialecte du connecteur. */
9
+ dialect: SqlDialect;
10
+ /** Fichier SQLite (dialecte `sqlite`). */
11
+ filename?: string;
12
+ /** URL de connexion DIRECTE (dialectes `postgres` et `mysql`). */
13
+ url?: string;
14
+ }
15
+ /**
16
+ * Ouvre le pilote à connexion unique du dialecte demandé.
17
+ *
18
+ * L'URL doit être une connexion **directe** au serveur : un répartiteur de
19
+ * connexions en mode transaction casse les verrous consultatifs de session, et
20
+ * le verrou de l'applicateur en est un.
21
+ *
22
+ * @param target - dialecte et coordonnées de la base.
23
+ * @returns le pilote, déjà connecté.
24
+ * @throws Error si les coordonnées manquent pour ce dialecte.
25
+ */
26
+ export declare function openMigrationDriver(target: IMigrationTarget): Promise<IMigrationDriver>;
@@ -0,0 +1,83 @@
1
+ import type { IMigrationDriver } from "../types.js";
2
+ /**
3
+ * Préfixe de l'identité du verrou MySQL — **contrat inter-versions**.
4
+ *
5
+ * Au même titre que le nom de la table d'historique : deux versions du
6
+ * framework qui ne s'excluent plus, c'est pendant un déploiement que ça se
7
+ * paie.
8
+ */
9
+ export declare const MYSQL_LOCK_PREFIX = "nodefony:migrations:";
10
+ /**
11
+ * Expression SQL de l'identité du verrou — évaluée par le SERVEUR.
12
+ *
13
+ * **Pourquoi qualifier par `DATABASE()`** : `GET_LOCK` est global au SERVEUR,
14
+ * pas à la base. Sans cette qualification, deux applications sans aucun rapport
15
+ * hébergées sur la même instance se sérialiseraient — en silence, ce qui est le
16
+ * pire des symptômes.
17
+ *
18
+ * **Pourquoi un repli haché** : MySQL borne le nom d'un verrou à 64 caractères
19
+ * (MariaDB est plus permissif, mais c'est la contrainte la plus stricte qui
20
+ * fait règle). Le préfixe en consomme 20 ; au-delà de 44 caractères de nom de
21
+ * base, l'appel échouerait — et l'échec d'un verrou est un blocage total, avec
22
+ * un message que rien ne rattache au nom de la base. Le repli reste
23
+ * DÉTERMINISTE (fonction du seul nom de base), donc deux versions du framework
24
+ * calculent toujours la même identité : le contrat tient.
25
+ */
26
+ export declare const MYSQL_LOCK_NAME_SQL: string;
27
+ /**
28
+ * Pilote MySQL / MariaDB de l'applicateur — **une seule connexion**.
29
+ *
30
+ * `GET_LOCK` est un verrou de SESSION : un pool le rendrait inopérant. Il
31
+ * s'auto-libère à la mort de la connexion — aucun verrou zombie.
32
+ *
33
+ * 🔴 **Le DDL n'est PAS transactionnel ici** : un `CREATE TABLE` valide
34
+ * implicitement la transaction en cours. Un échec à mi-course laisse donc la
35
+ * base dans un état partiel, avec un marqueur d'échec persistant — et c'est
36
+ * exactement pourquoi il ne doit JAMAIS y avoir de reprise aveugle : c'est la
37
+ * réparation, après inspection humaine, qui tranche.
38
+ */
39
+ export declare class MysqlMigrationDriver implements IMigrationDriver {
40
+ #private;
41
+ readonly dialect: "mysql";
42
+ readonly transactionalDdl = false;
43
+ /**
44
+ * @param url - URL de connexion DIRECTE au serveur.
45
+ */
46
+ constructor(url: string);
47
+ /**
48
+ * Ouvre la connexion dédiée.
49
+ *
50
+ * @throws Error si le pilote `mysql2` n'est pas installé.
51
+ */
52
+ connect(): Promise<void>;
53
+ /** Connexion ouverte, ou une erreur qui dit quoi faire. */
54
+ /** Motif de la perte de connexion, si elle a été constatée. */
55
+ get lostReason(): string | null;
56
+ /** @inheritdoc */
57
+ exec(sql: string): Promise<void>;
58
+ /** @inheritdoc */
59
+ query<T extends Record<string, unknown>>(sql: string, params?: readonly unknown[]): Promise<T[]>;
60
+ /** @inheritdoc */
61
+ sameColumnName(declared: string, actual: string): boolean;
62
+ /** @inheritdoc */
63
+ tableExists(table: string): Promise<boolean>;
64
+ /** @inheritdoc */
65
+ columnsOf(table: string): Promise<string[]>;
66
+ /** @inheritdoc */
67
+ begin(): Promise<void>;
68
+ /** @inheritdoc */
69
+ commit(): Promise<void>;
70
+ /** @inheritdoc */
71
+ rollback(): Promise<void>;
72
+ /**
73
+ * Prend le verrou nommé, avec le délai natif de `GET_LOCK`.
74
+ *
75
+ * @param timeoutMs - délai maximal d'attente.
76
+ * @throws Error si le verrou n'est pas obtenu dans le délai.
77
+ */
78
+ lock(timeoutMs: number): Promise<void>;
79
+ /** @inheritdoc */
80
+ unlock(): Promise<void>;
81
+ /** @inheritdoc */
82
+ close(): Promise<void>;
83
+ }