@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,414 @@
1
+ import { FORMAT_MARKER } from "./types.js";
2
+ import "./kit.js";
3
+ import { registerHooks } from "node:module";
4
+ import { entityRegistry } from "@nodefony/orm-core";
5
+ import path from "node:path";
6
+ import { Table, getTableName, is } from "drizzle-orm";
7
+ import { SQLiteTable } from "drizzle-orm/sqlite-core";
8
+ import { PgTable } from "drizzle-orm/pg-core";
9
+ import { MySqlTable } from "drizzle-orm/mysql-core";
10
+ import fs from "node:fs/promises";
11
+ import { pathToFileURL } from "node:url";
12
+ //#region nodefony/src/migrator/appSchema.ts
13
+ /**
14
+ * Ce qu'une APPLICATION donne à lire à `drizzle-kit` — et comment on s'assure
15
+ * qu'elle lui donne bien TOUT.
16
+ *
17
+ * ## Le mécanisme, et pourquoi c'est celui-là
18
+ *
19
+ * `drizzle-kit` est un process séparé qui lit des **fichiers** : il ne sait rien
20
+ * d'un registre vivant. Et une entité enregistrée ne porte **aucune provenance
21
+ * de fichier** — `IEntity.schema` est un objet d'exécution. On ne peut donc pas
22
+ * « matérialiser le schéma depuis le registre » : cette voie a été explorée et
23
+ * écartée, elle n'existe pas.
24
+ *
25
+ * Le sens est l'inverse : **les fichiers fournissent, le registre valide.**
26
+ *
27
+ * 1. On découvre les fichiers d'entités par la convention du générateur —
28
+ * `nodefony/entity/*.ts`, dans l'application et dans chacun de ses modules.
29
+ * 2. On les IMPORTE pour voir ce qu'ils exportent vraiment : une table Drizzle
30
+ * se reconnaît (`is(value, Table)`), elle ne se devine pas à un nom.
31
+ * 3. On écrit un module temporaire qui les **ré-exporte à plat**.
32
+ * 4. Le registre sert de CONTRÔLE : une entité enregistrée que plus aucun
33
+ * fichier ne fournit est un refus qui la NOMME, jamais une migration
34
+ * silencieusement amputée — laquelle se graverait à vie dans le journal.
35
+ *
36
+ * ## Le ré-export doit être PLAT, sans exception
37
+ *
38
+ * `drizzle-kit` ne collecte que les tables exportées directement. Une table
39
+ * nichée dans un objet exporté est ignorée **sans un mot** (mesuré : un fichier
40
+ * exportant une table plate et une table nichée rend « 1 tables »). C'est
41
+ * pourquoi le module temporaire ré-exporte chaque table sous un nom propre, et
42
+ * jamais l'objet qui la contient.
43
+ */
44
+ /** Dossier des entités, relatif à une cible de scaffold. */
45
+ const ENTITY_DIR = ["nodefony", "entity"];
46
+ /**
47
+ * Dialecte tel que `drizzle-kit` le nomme dans sa configuration.
48
+ *
49
+ * Il ne s'écrit pas comme le nôtre — `postgresql` chez lui, `postgres` chez
50
+ * nous — et cette table est le seul endroit où l'écart existe.
51
+ */
52
+ const KIT_DIALECT = {
53
+ sqlite: "sqlite",
54
+ postgres: "postgresql",
55
+ mysql: "mysql"
56
+ };
57
+ /**
58
+ * Fichiers d'entités d'une cible (l'application, ou l'un de ses modules).
59
+ *
60
+ * Les `*.schema.ts` sont écartés : c'est la convention du générateur pour les
61
+ * contrats d'entrée (Zod), qui n'ont rien à faire dans un schéma de base.
62
+ *
63
+ * @param targetDir - dossier d'une cible (contient `index.ts` + `nodefony/`).
64
+ * @returns les chemins absolus, triés — l'ordre doit être le même partout.
65
+ */
66
+ async function entityFilesOf(targetDir) {
67
+ const dir = path.join(targetDir, ...ENTITY_DIR);
68
+ let names;
69
+ try {
70
+ names = await fs.readdir(dir);
71
+ } catch (e) {
72
+ if (e.code !== "ENOENT") throw e;
73
+ return [];
74
+ }
75
+ return names.filter((n) => n.endsWith(".ts") && !n.endsWith(".schema.ts")).sort().map((n) => path.join(dir, n));
76
+ }
77
+ /**
78
+ * Importe des fichiers d'entités et relève les tables qu'ils exportent.
79
+ *
80
+ * L'import est la seule façon HONNÊTE de savoir ce qu'un fichier fournit : lire
81
+ * un nom d'export au motif qu'il finit par `Table` marcherait sur le code que
82
+ * le générateur écrit, et sur rien d'autre — or ces fichiers sont faits pour
83
+ * être modifiés à la main, c'est même écrit dans leur en-tête.
84
+ *
85
+ * Un fichier qui refuse de s'importer n'est PAS ignoré : il est rendu à
86
+ * l'appelant avec sa cause. C'est presque toujours le même défaut — un accès au
87
+ * kernel à l'évaluation du module — et le taire produirait une migration
88
+ * amputée là où il faut une phrase.
89
+ *
90
+ * @param files - chemins absolus des fichiers d'entités.
91
+ * @returns les tables trouvées et les fichiers illisibles.
92
+ */
93
+ async function collectTables(files) {
94
+ const tables = [];
95
+ const unreadable = [];
96
+ const hooks = registerExtensionlessResolution();
97
+ try {
98
+ for (const file of files) {
99
+ let mod;
100
+ try {
101
+ mod = await import(pathToFileURL(file).href);
102
+ } catch (e) {
103
+ unreadable.push({
104
+ file,
105
+ cause: e instanceof Error ? e.message : String(e)
106
+ });
107
+ continue;
108
+ }
109
+ for (const [exportName, value] of Object.entries(mod)) {
110
+ if (!is(value, Table)) continue;
111
+ tables.push({
112
+ file,
113
+ exportName,
114
+ tableName: getTableName(value),
115
+ dialect: dialectOf(value)
116
+ });
117
+ }
118
+ }
119
+ } finally {
120
+ hooks.deregister();
121
+ }
122
+ return {
123
+ tables,
124
+ unreadable
125
+ };
126
+ }
127
+ /**
128
+ * Moteur pour lequel une table est écrite.
129
+ *
130
+ * Une entité d'application est du Drizzle **natif** : `sqliteTable`, `pgTable` et
131
+ * `mysqlTable` produisent trois objets différents, et une table écrite pour un
132
+ * moteur est simplement IGNORÉE par l'outil quand il en génère un autre — sans
133
+ * un mot, comme d'habitude. Constaté sur une application témoin : six tables
134
+ * découvertes, quatre écrites, et un message qui annonçait six.
135
+ *
136
+ * @param table - table relevée dans un fichier d'entité.
137
+ * @returns le dialecte, ou `null` si ce n'est aucun des trois.
138
+ */
139
+ function dialectOf(table) {
140
+ if (is(table, SQLiteTable)) return "sqlite";
141
+ if (is(table, PgTable)) return "postgres";
142
+ if (is(table, MySqlTable)) return "mysql";
143
+ return null;
144
+ }
145
+ /**
146
+ * Fait résoudre `./voisin` comme le ferait un bundler — le temps de la lecture.
147
+ *
148
+ * Un fichier d'entité qui factorise ses colonnes dans un voisin l'importe en
149
+ * TypeScript, donc **sans extension** : c'est ce que tout le monde écrit, ce que
150
+ * les éditeurs proposent, et ce que `moduleResolution: "Bundler"` autorise. Node,
151
+ * lui, applique la règle ESM et exige l'extension — un tel fichier est donc
152
+ * illisible pour lui, alors qu'il se compile parfaitement.
153
+ *
154
+ * Constaté sur les entités du framework lui-même : neuf fichiers sur neuf
155
+ * refusaient de s'importer, tous pour la même raison. Sans cette résolution, la
156
+ * découverte n'aurait marché que sur les fichiers qui n'importent AUCUN voisin
157
+ * — c'est-à-dire sur ce que le générateur écrit, et sur rien de ce qu'on écrit
158
+ * ensuite.
159
+ *
160
+ * La portée est bornée au plus court : posée pour la lecture, retirée juste
161
+ * après, y compris en cas d'échec.
162
+ *
163
+ * @returns le jeton de retrait des hooks.
164
+ */
165
+ function registerExtensionlessResolution() {
166
+ return registerHooks({ resolve(specifier, context, nextResolve) {
167
+ try {
168
+ return nextResolve(specifier, context);
169
+ } catch (e) {
170
+ if (specifier.startsWith(".") && path.extname(specifier) === "") return nextResolve(`${specifier}.ts`, context);
171
+ throw e;
172
+ }
173
+ } });
174
+ }
175
+ /**
176
+ * Spécificateur d'import de `from` vers `to`, écrit pour VOYAGER.
177
+ *
178
+ * Un spécificateur s'écrit en `/` sur les trois systèmes — c'est du texte que
179
+ * lit un outil, pas un accès disque. L'extension est explicite : le module
180
+ * temporaire est compilé par l'outil, qui doit savoir quoi ouvrir sans deviner.
181
+ *
182
+ * @param fromFile - fichier qui contiendra l'import.
183
+ * @param toFile - fichier visé.
184
+ * @returns un spécificateur relatif, toujours préfixé `./` ou `../`.
185
+ */
186
+ function importSpecifier(fromFile, toFile) {
187
+ const posix = path.relative(path.dirname(fromFile), toFile).split(path.sep).join("/");
188
+ return posix.startsWith(".") ? posix : `./${posix}`;
189
+ }
190
+ /**
191
+ * Écrit le module temporaire que `drizzle-kit` lira comme « le schéma ».
192
+ *
193
+ * Chaque table reçoit un alias unique et neutre : deux fichiers peuvent très
194
+ * bien exporter deux tables sous le même identifiant JS, et un ré-export en
195
+ * étoile les rendrait AMBIGUËS — l'ambiguïté n'est pas une erreur en ESM, c'est
196
+ * une absence silencieuse. Le nom de la table en base, lui, n'est pas touché :
197
+ * il vit dans l'appel `…Table("nom")`, pas dans l'identifiant.
198
+ *
199
+ * @param file - chemin du module à écrire.
200
+ * @param tables - tables à ré-exporter, dans l'ordre de découverte.
201
+ * @returns le contenu écrit (rendu pour les bancs).
202
+ */
203
+ async function writeSchemaModule(file, tables) {
204
+ const lines = [
205
+ "// Fichier ENGENDRÉ par `nodefony orm:generate` — il est réécrit à chaque",
206
+ "// exécution et effacé à la fin. Ne rien y mettre à la main.",
207
+ "//",
208
+ "// Les ré-exports sont PLATS : drizzle-kit ne collecte que les tables",
209
+ "// exportées directement, et ignore sans un mot celles qui sont nichées.",
210
+ ""
211
+ ];
212
+ tables.forEach((t, i) => {
213
+ lines.push(`export { ${t.exportName} as nf_${i}_${t.tableName.replace(/[^A-Za-z0-9_]/g, "_")} } from "${importSpecifier(file, t.file)}";`);
214
+ });
215
+ const body = `${lines.join("\n")}\n`;
216
+ await fs.mkdir(path.dirname(file), { recursive: true });
217
+ await fs.writeFile(file, body, "utf8");
218
+ return body;
219
+ }
220
+ /**
221
+ * Schéma PostgreSQL réellement visé par une URL de connexion.
222
+ *
223
+ * 🔴 L'outil d'introspection ne suit PAS le `search_path` porté par l'URL : il
224
+ * lit `public`, quoi qu'on lui donne. Sans cette dérivation, adopter une
225
+ * application logée dans un schéma dédié — le montage habituel d'une base
226
+ * mutualisée — écrivait la référence des tables de `public`, c'est-à-dire
227
+ * celles de quelqu'un d'autre, et déclarait absentes les siennes. Constaté sur
228
+ * un serveur réel : la référence décrivait trois tables étrangères au projet.
229
+ *
230
+ * Deux formes sont reconnues, parce que les deux circulent : le `search_path`
231
+ * passé dans `options`, et le paramètre `schema` que posent certains outils.
232
+ * Une URL sans rien rend `null` — l'appelant laisse alors le défaut de l'outil,
233
+ * qui est le bon.
234
+ *
235
+ * @param url - URL de connexion, telle que le connecteur la porte.
236
+ * @returns le premier schéma du chemin de recherche, ou `null`.
237
+ */
238
+ function postgresSchemaOf(url) {
239
+ let parsed;
240
+ try {
241
+ parsed = new URL(url);
242
+ } catch {
243
+ return null;
244
+ }
245
+ const direct = parsed.searchParams.get("schema");
246
+ if (direct !== null && direct.trim() !== "") return direct.trim();
247
+ const options = parsed.searchParams.get("options");
248
+ if (options === null) return null;
249
+ const premier = /(?:^|\s)-c\s*search_path=([^\s]+)/u.exec(options)?.[1]?.split(",")[0]?.trim();
250
+ return premier !== void 0 && premier !== "" ? premier : null;
251
+ }
252
+ /**
253
+ * Écrit la configuration `drizzle-kit` d'une génération d'application.
254
+ *
255
+ * 🔴 **Les chemins y sont RELATIFS, et ce n'est pas un style.** L'outil préfixe
256
+ * son dossier de sortie par `./` : un chemin absolu devient `.//Users/…`, la
257
+ * lecture échoue — et l'échec se présente comme un succès, puisque l'outil rend
258
+ * 0 quand il rate. La configuration est donc écrite pour être lue depuis la
259
+ * racine de l'application, qui est le dossier d'exécution.
260
+ *
261
+ * `tablesFilter` exclut ce que l'application ne possède pas : les tables du
262
+ * framework et la table d'historique. Sans lui, une entité d'application qui
263
+ * référence une table du framework la ferait entrer dans le diff, et la
264
+ * migration porterait un second `CREATE TABLE` de cette table — qui échoue en
265
+ * production, sur toute base déjà migrée.
266
+ *
267
+ * @param options - où écrire, quoi lire, où sortir, quoi exclure.
268
+ * @returns le contenu écrit (rendu pour les bancs).
269
+ */
270
+ async function writeKitConfig({ file, projectRoot, schemaFile, outDir, dialect, excludedTables, dbUrl }) {
271
+ const rel = (target) => `./${path.relative(projectRoot, target).split(path.sep).join("/")}`;
272
+ const filters = ["*", ...excludedTables.map((t) => `!${t}`)];
273
+ const schema = dbUrl !== void 0 && dialect === "postgres" ? postgresSchemaOf(dbUrl) : null;
274
+ const body = `// Fichier ENGENDRÉ par \`nodefony orm:generate\` — réécrit puis effacé.
275
+ import { defineConfig } from "drizzle-kit";
276
+
277
+ export default defineConfig({
278
+ dialect: ${JSON.stringify(KIT_DIALECT[dialect])},\n schema: ${JSON.stringify(rel(schemaFile))},\n out: ${JSON.stringify(rel(outDir))},\n migrations: { prefix: "index" },\n tablesFilter: ${JSON.stringify(filters)},\n` + (schema === null ? "" : ` schemaFilter: ${JSON.stringify([schema])},\n`) + (dbUrl === void 0 ? "" : ` dbCredentials: { url: ${JSON.stringify(dbUrl)} },\n`) + `});\n`;
279
+ await fs.mkdir(path.dirname(file), { recursive: true });
280
+ await fs.writeFile(file, body, "utf8");
281
+ return body;
282
+ }
283
+ /**
284
+ * Version d'ENTRÉE écrite par `drizzle-kit` selon le dialecte.
285
+ *
286
+ * Elle n'est pas la version du journal (« 7 » partout) : c'est celle du format
287
+ * d'instantané, et elle diffère par moteur. Les valeurs sont RELEVÉES sur les
288
+ * journaux que l'outil a produits pour le framework, jamais devinées — une
289
+ * entrée écrite à la main doit être indistinguable des siennes, sans quoi la
290
+ * génération suivante repartirait de travers.
291
+ */
292
+ const ENTRY_VERSION = {
293
+ sqlite: "6",
294
+ postgres: "7",
295
+ mysql: "5"
296
+ };
297
+ /**
298
+ * Écrit une migration LIBRE et son entrée de journal, sans `drizzle-kit`.
299
+ *
300
+ * C'est la porte de sortie du modèle déclaratif : une vue, un déclencheur, une
301
+ * clé étrangère réelle, un remplissage de données ne se DÉDUISENT d'aucun
302
+ * schéma. Sans elle, on n'aurait le choix qu'entre renoncer et écrire un
303
+ * fichier à la main dans un journal dont le format n'est pas documenté — deux
304
+ * façons de casser l'historique.
305
+ *
306
+ * Le fichier est vide de toute instruction : un squelette qui « propose » du
307
+ * SQL est un squelette qu'on applique sans le lire.
308
+ *
309
+ * @param options - dossier de sortie, dialecte, nom de la migration.
310
+ * @returns le tag attribué et le chemin du fichier écrit.
311
+ * @throws Error si le journal existant est illisible — on n'en réécrit JAMAIS
312
+ * un par-dessus : il porte l'historique déjà appliqué en production.
313
+ */
314
+ async function writeCustomMigration({ outDir, dialect, name, now = Date.now() }) {
315
+ const journalFile = path.join(outDir, "meta", "_journal.json");
316
+ let journal = {
317
+ version: "7",
318
+ dialect: KIT_DIALECT[dialect],
319
+ entries: []
320
+ };
321
+ let raw = null;
322
+ try {
323
+ raw = await fs.readFile(journalFile, "utf8");
324
+ } catch {}
325
+ if (raw !== null) try {
326
+ journal = JSON.parse(raw);
327
+ } catch (e) {
328
+ throw new Error(`Le journal « ${journalFile} » est illisible (${e instanceof Error ? e.message : String(e)}). Rien n'a été écrit : ce fichier porte la liste de ce qui a DÉJÀ été appliqué en production, et en réécrire un neuf ferait rejouer toutes les migrations sur une base qui les a déjà reçues.`, { cause: e });
329
+ }
330
+ const entries = [...journal.entries ?? []];
331
+ const idx = entries.reduce((max, e) => Math.max(max, e.idx + 1), 0);
332
+ const tag = `${String(idx).padStart(4, "0")}_${name}`;
333
+ const file = path.join(outDir, `${tag}.sql`);
334
+ await fs.mkdir(path.join(outDir, "meta"), { recursive: true });
335
+ await fs.writeFile(file, `${FORMAT_MARKER}\n-- Migration LIBRE « ${name} » — à écrire à la main.\n--\n-- Elle est déjà inscrite au journal : elle sera appliquée telle quelle,\n-- une seule fois, dans l'ordre, et son empreinte sera gravée. La modifier\n-- après application sera REFUSÉ — écrire une migration de plus, alors.\n--\n-- Séparer les instructions par « --> statement-breakpoint ».\n`, "utf8");
336
+ entries.push({
337
+ idx,
338
+ version: ENTRY_VERSION[dialect],
339
+ when: now,
340
+ tag,
341
+ breakpoints: true
342
+ });
343
+ await fs.writeFile(journalFile, `${JSON.stringify({
344
+ ...journal,
345
+ entries
346
+ }, null, 2)}\n`, "utf8");
347
+ return {
348
+ tag,
349
+ file
350
+ };
351
+ }
352
+ /**
353
+ * Ce que le registre attend et que les fichiers ne fournissent PAS.
354
+ *
355
+ * C'est le seul contrôle qu'un registre puisse rendre et qu'aucun outil de
356
+ * génération ne rendra jamais : `drizzle-kit` ne sait pas ce qu'une application
357
+ * a déclaré, il ne voit que ce qu'on lui donne à lire. Sans cette confrontation,
358
+ * une entité dont le fichier a été déplacé, renommé, ou rendu illisible
359
+ * disparaît de la migration **sans un mot** — et une migration ne se corrige
360
+ * pas : elle est immuable dès qu'une base l'a reçue.
361
+ *
362
+ * Les tables du framework sont écartées des deux côtés : elles sont fournies par
363
+ * une autre source, appliquée avant.
364
+ *
365
+ * @param expected - entités du registre, sur le connecteur visé.
366
+ * @param providedTables - noms des tables que les fichiers découverts fournissent.
367
+ * @param frameworkTables - noms des tables construites par le framework.
368
+ * @returns les entités sans fournisseur, dans l'ordre reçu.
369
+ */
370
+ function missingProviders(expected, providedTables, frameworkTables) {
371
+ return expected.filter(({ table }) => !providedTables.has(table) && !frameworkTables.has(table));
372
+ }
373
+ /**
374
+ * Les tables de l'application qui usurpent une table du framework.
375
+ *
376
+ * Deux `CREATE TABLE` pour un même nom : la migration passe sur une base vierge
377
+ * et échoue sur toute base déjà migrée — c'est-à-dire en production, et nulle
378
+ * part ailleurs. C'est le pire endroit pour découvrir la faute, donc on la dit
379
+ * avant d'écrire quoi que ce soit.
380
+ *
381
+ * @param tables - tables fournies par les fichiers de l'application.
382
+ * @param frameworkTables - noms des tables construites par le framework.
383
+ * @returns les tables en conflit, avec le fichier qui les exporte.
384
+ */
385
+ function usurpedTables(tables, frameworkTables) {
386
+ return tables.filter((t) => frameworkTables.has(t.tableName));
387
+ }
388
+ /**
389
+ * Tables attendues sur un connecteur, telles que le REGISTRE les connaît.
390
+ *
391
+ * Le registre est la seule source qui sache ce que l'application DÉCLARE :
392
+ * les fichiers disent ce qu'elle fournit, la base ce qu'elle porte, et c'est
393
+ * le croisement des trois qui fait les verdicts. Deux commandes en dépendent —
394
+ * la génération pour repérer une entité sans fichier, l'adoption pour ne lire
395
+ * QUE les tables de l'application — et une seconde copie divergerait en
396
+ * silence.
397
+ *
398
+ * @param connector - connecteur visé.
399
+ * @returns les entités et leur table, dans l'ordre du registre.
400
+ */
401
+ function registeredTables(connector) {
402
+ const out = [];
403
+ for (const entity of entityRegistry.list()) {
404
+ if (entity.connector !== connector) continue;
405
+ const schema = entity.schema;
406
+ if (is(schema, Table)) out.push({
407
+ entity: entity.name,
408
+ table: getTableName(schema)
409
+ });
410
+ }
411
+ return out;
412
+ }
413
+ //#endregion
414
+ export { collectTables, dialectOf, entityFilesOf, importSpecifier, missingProviders, postgresSchemaOf, registeredTables, usurpedTables, writeCustomMigration, writeKitConfig, writeSchemaModule };
@@ -0,0 +1,76 @@
1
+ //#region nodefony/src/migrator/catalog.ts
2
+ /**
3
+ * Les moteurs dont la résolution des noms de COLONNES ignore la casse.
4
+ *
5
+ * PostgreSQL n'y est pas, et ce n'est pas un oubli : il stocke la casse d'un
6
+ * identifiant cité et la compare exactement.
7
+ */
8
+ const CASE_INSENSITIVE_COLUMN_DIALECTS = /* @__PURE__ */ new Set(["sqlite", "mysql"]);
9
+ /**
10
+ * Compare deux noms de COLONNE selon la résolution du moteur.
11
+ *
12
+ * Exportée parce qu'elle porte une RÈGLE : la recopier ailleurs la ferait
13
+ * diverger, et une divergence de casse ne se voit que sur une base adoptée,
14
+ * c'est-à-dire chez l'utilisateur.
15
+ *
16
+ * Elle ne vaut PAS pour les tables — voir {@link ISchemaReader.sameColumnName},
17
+ * qui dit pourquoi leur sensibilité se constate au lieu de se déduire.
18
+ *
19
+ * @param dialect - moteur qui résoudrait le nom.
20
+ * @param declared - nom tel que le code le déclare.
21
+ * @param actual - nom tel que la base le rend.
22
+ * @returns `true` si le moteur les résoudrait vers la même colonne.
23
+ */
24
+ function sameColumnName(dialect, declared, actual) {
25
+ return CASE_INSENSITIVE_COLUMN_DIALECTS.has(dialect) ? declared.toLowerCase() === actual.toLowerCase() : declared === actual;
26
+ }
27
+ /**
28
+ * Compose un lecteur de catalogue au-dessus d'un exécuteur de requêtes.
29
+ *
30
+ * @param dialect - dialecte du serveur interrogé.
31
+ * @param query - exécuteur du porteur (pilote de migration, ou ORM connecté).
32
+ * @returns le lecteur, sans état ni connexion propre.
33
+ */
34
+ function schemaReader(dialect, query) {
35
+ return {
36
+ sameColumnName(declared, actual) {
37
+ return sameColumnName(dialect, declared, actual);
38
+ },
39
+ async tableExists(table) {
40
+ switch (dialect) {
41
+ case "sqlite": return (await query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? COLLATE NOCASE", [table])).length > 0;
42
+ case "postgres": return (await query("SELECT 1 AS found FROM information_schema.tables WHERE table_name = ? AND table_schema = ANY(current_schemas(false))", [table])).length > 0;
43
+ case "mysql": {
44
+ const rows = await query("SELECT COUNT(*) AS n FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?", [table]);
45
+ return Number(rows[0]?.n ?? 0) > 0;
46
+ }
47
+ }
48
+ },
49
+ async columnsOf(table) {
50
+ switch (dialect) {
51
+ case "sqlite":
52
+ if (!await this.tableExists(table)) return [];
53
+ return (await query(`PRAGMA table_info("${table.replace(/"/g, "\"\"")}")`)).map((row) => row.name);
54
+ case "postgres": return (await query("SELECT column_name FROM information_schema.columns WHERE table_name = ? AND table_schema = ANY(current_schemas(false))", [table])).map((row) => row.column_name);
55
+ case "mysql": return (await query("SELECT column_name AS name FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ?", [table])).map((row) => String(row.name));
56
+ }
57
+ }
58
+ };
59
+ }
60
+ /**
61
+ * Traduit les paramètres `?` du dialecte commun en `$n` PostgreSQL.
62
+ *
63
+ * L'applicateur écrit ses requêtes une seule fois, avec la forme la plus
64
+ * répandue ; chaque pilote l'adapte. Aucune des requêtes de l'applicateur ne
65
+ * contient de littéral `?` — les valeurs, elles, sont bindées, jamais
66
+ * concaténées.
67
+ *
68
+ * @param sql - requête écrite avec des `?`.
69
+ * @returns la même requête, paramètres numérotés.
70
+ */
71
+ function toDollarParams(sql) {
72
+ let index = 0;
73
+ return sql.replace(/\?/g, () => `$${++index}`);
74
+ }
75
+ //#endregion
76
+ export { sameColumnName, schemaReader, toDollarParams };