@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,219 @@
1
+ import { HISTORY_TABLE } from "./types.js";
2
+ //#region nodefony/src/migrator/history.ts
3
+ /**
4
+ * Étapes d'amorçage postérieures au format d'origine.
5
+ *
6
+ * **Vide dans cette version** — l'étape 0 est le `CREATE` lui-même. Ce tableau
7
+ * est le point d'extension : ajouter une colonne à l'historique, aujourd'hui ou
8
+ * dans cinq versions, se fait ICI et nulle part ailleurs.
9
+ */
10
+ const HISTORY_STEPS = [];
11
+ /** Colonnes du format d'origine, dans l'ordre du `CREATE`. */
12
+ const BASE_COLUMNS = [
13
+ "source",
14
+ "tag",
15
+ "hash",
16
+ "run_id",
17
+ "started_at",
18
+ "finished_at",
19
+ "execution_ms",
20
+ "success",
21
+ "error",
22
+ "applied_by"
23
+ ];
24
+ /**
25
+ * Le moteur se plaint-il d'une COLONNE d'historique qu'il ne trouve pas ?
26
+ *
27
+ * Reconnaît la table d'historique d'une AUTRE provenance : la table porte le
28
+ * bon nom, elle n'a pas les bonnes colonnes. `CREATE TABLE IF NOT EXISTS` ne
29
+ * la répare pas — elle existe —, et la première lecture échoue sur un message
30
+ * de moteur brut, que le fourre-tout des pannes habille alors de deux causes
31
+ * FAUSSES : « la base n'a pas répondu » et « les droits manquent ». Mesuré au
32
+ * banc : c'est ce message qui a renvoyé un agent détruire une base de
33
+ * production après qu'il eut pourtant suivi le conseil de travailler sur une
34
+ * copie — copie qu'il avait dû fabriquer à la main, avec un historique inventé.
35
+ *
36
+ * PURE, et une grammaire par moteur : les trois formulent la même panne dans
37
+ * trois langues, et un motif écrit pour l'une est muet pour les deux autres.
38
+ * Bornée aux colonnes que ce fichier déclare : une colonne APPLICATIVE
39
+ * manquante est un tout autre incident, qui a déjà sa voie.
40
+ *
41
+ * @param message - message d'erreur rendu par le pilote.
42
+ * @returns la colonne d'historique introuvable, ou `null`.
43
+ */
44
+ function missingHistoryColumn(message) {
45
+ for (const pattern of [
46
+ /no such column:\s*(?:[\w."`]*\.)?[`"']?(\w+)[`"']?/i,
47
+ /column\s+[`"']?(?:[\w.]*\.)?[`"']?(\w+)[`"']?\s+does not exist/i,
48
+ /unknown column\s+[`"']?(?:[\w.]*\.)?[`"']?(\w+)[`"']?/i
49
+ ]) {
50
+ const name = pattern.exec(message)?.[1]?.toLowerCase();
51
+ if (name !== void 0 && BASE_COLUMNS.includes(name)) return name;
52
+ }
53
+ return null;
54
+ }
55
+ /** DDL de création par dialecte, au format d'origine. */
56
+ const CREATE_SQL = {
57
+ sqlite: `CREATE TABLE IF NOT EXISTS ${HISTORY_TABLE} (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n source TEXT NOT NULL,\n tag TEXT NOT NULL,\n hash TEXT NOT NULL,\n run_id TEXT NOT NULL,\n started_at INTEGER NOT NULL,\n finished_at INTEGER,\n execution_ms INTEGER,\n success INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n applied_by TEXT,\n UNIQUE (source, tag)\n)`,
58
+ postgres: `CREATE TABLE IF NOT EXISTS ${HISTORY_TABLE} (\n id BIGSERIAL PRIMARY KEY,\n source TEXT NOT NULL,\n tag TEXT NOT NULL,\n hash TEXT NOT NULL,\n run_id TEXT NOT NULL,\n started_at BIGINT NOT NULL,\n finished_at BIGINT,\n execution_ms INTEGER,\n success BOOLEAN NOT NULL DEFAULT FALSE,\n error TEXT,\n applied_by TEXT,\n UNIQUE (source, tag)\n)`,
59
+ mysql: `CREATE TABLE IF NOT EXISTS ${HISTORY_TABLE} (\n id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,\n source VARCHAR(190) NOT NULL,\n tag VARCHAR(190) NOT NULL,\n hash VARCHAR(255) NOT NULL,\n run_id VARCHAR(64) NOT NULL,\n started_at BIGINT NOT NULL,\n finished_at BIGINT NULL,\n execution_ms INT NULL,\n success TINYINT(1) NOT NULL DEFAULT 0,\n error TEXT NULL,\n applied_by VARCHAR(255) NULL,\n UNIQUE KEY uq_${HISTORY_TABLE} (source, tag)\n)`
60
+ };
61
+ /**
62
+ * Crée la table d'historique si besoin, puis l'amène au format courant.
63
+ *
64
+ * À appeler **juste après le verrou et AVANT la moindre lecture** : c'est
65
+ * l'ordre qui rend l'évolution de la table possible sans outil de conversion
66
+ * chez l'utilisateur.
67
+ *
68
+ * @param driver - pilote à connexion unique, verrou déjà tenu.
69
+ * @returns les colonnes ajoutées par l'amorçage (vide dans le cas courant).
70
+ */
71
+ async function ensureHistorySchema(driver) {
72
+ await driver.exec(CREATE_SQL[driver.dialect]);
73
+ const present = new Set((await driver.columnsOf(HISTORY_TABLE)).map((c) => c.toLowerCase()));
74
+ const added = [];
75
+ for (const step of HISTORY_STEPS) {
76
+ if (present.has(step.column.toLowerCase())) continue;
77
+ await driver.exec(step.ddl[driver.dialect]);
78
+ added.push(step.column);
79
+ }
80
+ return added;
81
+ }
82
+ /**
83
+ * Lit l'historique complet, **colonnes nommées une par une**.
84
+ *
85
+ * Jamais `SELECT *` : c'est ce qui rend l'ajout d'une colonne inoffensif pour
86
+ * un applicateur plus ancien, qui continue de lire exactement ce qu'il connaît.
87
+ *
88
+ * @param driver - pilote à connexion unique.
89
+ * @returns les lignes, dans leur ordre d'insertion.
90
+ */
91
+ async function readHistory(driver) {
92
+ return (await driver.query(`SELECT ${BASE_COLUMNS.join(", ")} FROM ${HISTORY_TABLE} ORDER BY id`)).map((row) => ({
93
+ source: String(row.source),
94
+ tag: String(row.tag),
95
+ hash: String(row.hash),
96
+ runId: String(row.run_id),
97
+ startedAt: Number(row.started_at),
98
+ finishedAt: row.finished_at === null ? null : Number(row.finished_at),
99
+ executionMs: row.execution_ms === null ? null : Number(row.execution_ms),
100
+ success: toBoolean(row.success),
101
+ error: row.error === null ? null : String(row.error),
102
+ appliedBy: row.applied_by === null ? null : String(row.applied_by)
103
+ }));
104
+ }
105
+ /**
106
+ * `INSERT` d'une ligne d'historique — **jamais positionnel**.
107
+ *
108
+ * @param driver - pilote à connexion unique.
109
+ * @param row - ligne à écrire.
110
+ */
111
+ async function insertHistory(driver, row) {
112
+ await driver.query(`INSERT INTO ${HISTORY_TABLE} (${BASE_COLUMNS.join(", ")}) VALUES (${BASE_COLUMNS.map(() => "?").join(", ")})`, [
113
+ row.source,
114
+ row.tag,
115
+ row.hash,
116
+ row.runId,
117
+ row.startedAt,
118
+ row.finishedAt,
119
+ row.executionMs,
120
+ fromBoolean(row.success, driver.dialect),
121
+ row.error,
122
+ row.appliedBy
123
+ ]);
124
+ }
125
+ /**
126
+ * Marque une ligne d'historique terminée (chemin MySQL, DDL non transactionnel).
127
+ *
128
+ * @param driver - pilote à connexion unique.
129
+ * @param row - ligne dont le résultat est connu.
130
+ */
131
+ async function finishHistory(driver, row) {
132
+ await driver.query(`UPDATE ${HISTORY_TABLE} SET finished_at = ?, execution_ms = ?, success = ?, error = ?, hash = ? WHERE source = ? AND tag = ?`, [
133
+ row.finishedAt,
134
+ row.executionMs,
135
+ fromBoolean(row.success, driver.dialect),
136
+ row.error,
137
+ row.hash,
138
+ row.source,
139
+ row.tag
140
+ ]);
141
+ }
142
+ /**
143
+ * Supprime les marqueurs d'échec d'une source, ou de toutes.
144
+ *
145
+ * @param driver - pilote à connexion unique.
146
+ * @param source - source à réparer ; toutes si omise.
147
+ * @returns les migrations dont le marqueur a été levé.
148
+ */
149
+ async function deleteFailed(driver, source) {
150
+ const failed = (await readHistory(driver)).filter((row) => (!row.success || row.finishedAt === null) && (source === void 0 || row.source === source));
151
+ for (const row of failed) await driver.query(`DELETE FROM ${HISTORY_TABLE} WHERE source = ? AND tag = ?`, [row.source, row.tag]);
152
+ return failed.map(({ source: s, tag }) => ({
153
+ source: s,
154
+ tag
155
+ }));
156
+ }
157
+ /**
158
+ * Désinscrit UNE entrée nommée de l'historique, quel que soit son état.
159
+ *
160
+ * ## Pourquoi ce geste existe, alors qu'un interdit dit de ne pas y toucher
161
+ *
162
+ * L'interdit porte sur la modification À LA MAIN, dans un client SQL, sans
163
+ * trace. Il existait pourtant un état dont AUCUNE commande ne sortait : une
164
+ * migration inscrite `success` que personne n'a jamais exécutée — une adoption
165
+ * mal bornée, une base héritée d'une version antérieure aux gardes. Le
166
+ * générateur disait « c'est l'historique qu'il faut reprendre » et renvoyait
167
+ * vers la réparation, qui ne sait lever que des marqueurs d'ÉCHEC : elle
168
+ * répondait « rien à réparer », et l'on revenait au point de départ. Trois
169
+ * messages vrais, aucun geste — et le seul chemin restant était de détruire la
170
+ * base.
171
+ *
172
+ * Le geste est donc rendu au produit, où il laisse une trace et où il est
173
+ * BORNÉ : une entrée précisément nommée, jamais un lot, jamais un motif.
174
+ *
175
+ * ⚠️ Ne touche pas la base : après cet oubli, la migration sera REJOUÉE au
176
+ * prochain passage. Si elle avait réellement été appliquée, ce rejeu échouera
177
+ * — bruyamment, ce qui est le comportement voulu.
178
+ *
179
+ * @param driver - pilote sous verrou.
180
+ * @param entries - entrées à désinscrire, chacune nommée `source` et `tag`.
181
+ * @returns celles qui existaient et ont été retirées.
182
+ */
183
+ async function forgetEntries(driver, entries) {
184
+ const existing = await readHistory(driver);
185
+ const removed = [];
186
+ for (const target of entries) {
187
+ if (!existing.some((row) => row.source === target.source && row.tag === target.tag)) continue;
188
+ await driver.query(`DELETE FROM ${HISTORY_TABLE} WHERE source = ? AND tag = ?`, [target.source, target.tag]);
189
+ removed.push({
190
+ source: target.source,
191
+ tag: target.tag
192
+ });
193
+ }
194
+ return removed;
195
+ }
196
+ /**
197
+ * Normalise un booléen tel que le rend la base.
198
+ *
199
+ * PostgreSQL rend `true`, SQLite et MySQL rendent `1` — et un `1` textuel
200
+ * traverse certains pilotes. Les trois se lisent ici, une fois.
201
+ *
202
+ * @param value - valeur brute lue en base.
203
+ * @returns le booléen correspondant.
204
+ */
205
+ function toBoolean(value) {
206
+ return value === true || value === 1 || value === "1";
207
+ }
208
+ /**
209
+ * Encode un booléen pour la base.
210
+ *
211
+ * @param value - booléen applicatif.
212
+ * @param dialect - dialecte cible.
213
+ * @returns la valeur à binder.
214
+ */
215
+ function fromBoolean(value, dialect) {
216
+ return dialect === "postgres" ? value : value ? 1 : 0;
217
+ }
218
+ //#endregion
219
+ export { HISTORY_STEPS, deleteFailed, ensureHistorySchema, finishHistory, forgetEntries, insertHistory, missingHistoryColumn, readHistory };
@@ -0,0 +1,16 @@
1
+ import { FORMAT_MARKER, HISTORY_TABLE, MigrationVerdictError, STATEMENT_BREAKPOINT } from "./types.js";
2
+ import { sameColumnName, schemaReader } from "./catalog.js";
3
+ import { MYSQL_LOCK_NAME_SQL, MYSQL_LOCK_PREFIX, MysqlMigrationDriver } from "./drivers/mysqlDriver.js";
4
+ import { PG_LOCK_KEY, PostgresMigrationDriver } from "./drivers/postgresDriver.js";
5
+ import { SqliteMigrationDriver } from "./drivers/sqliteDriver.js";
6
+ import { openMigrationDriver } from "./drivers/index.js";
7
+ import { HISTORY_STEPS, ensureHistorySchema, readHistory } from "./history.js";
8
+ import { APP_RANK, APP_SOURCE, FRAMEWORK_RANK, FRAMEWORK_SOURCE, defaultMigrationSources, frameworkMigrationsDir } from "./paths.js";
9
+ import { migrationHash, normalizeSql } from "./hash.js";
10
+ import { SUPPORTED_JOURNAL_VERSIONS, createdTables, frameworkTables, loadSources, orderSources, splitStatements } from "./sources.js";
11
+ import { DEFAULT_LOCK_TIMEOUT_MS, DrizzleMigrator } from "./DrizzleMigrator.js";
12
+ import { additiveSql, compareSchema, hasGap } from "./schemaDiff.js";
13
+ import { comparisonAgainstDeclared, describeDivergence, gapAgainstDeclared } from "./divergence.js";
14
+ import { MIGRATION_NAME_MAX, checkMigrationName, suggestMigrationName } from "./name.js";
15
+ import { adoptFromDatabase, introspectionUrl, readJournal, snapshotTables, tablesPresentIn, uncommentIntrospection } from "./adopt.js";
16
+ export { APP_RANK, APP_SOURCE, DEFAULT_LOCK_TIMEOUT_MS, DrizzleMigrator, FORMAT_MARKER, FRAMEWORK_RANK, FRAMEWORK_SOURCE, HISTORY_STEPS, HISTORY_TABLE, MIGRATION_NAME_MAX, MYSQL_LOCK_NAME_SQL, MYSQL_LOCK_PREFIX, MigrationVerdictError, MysqlMigrationDriver, PG_LOCK_KEY, PostgresMigrationDriver, STATEMENT_BREAKPOINT, SUPPORTED_JOURNAL_VERSIONS, SqliteMigrationDriver, additiveSql, adoptFromDatabase, checkMigrationName, compareSchema, comparisonAgainstDeclared, createdTables, defaultMigrationSources, describeDivergence, ensureHistorySchema, frameworkMigrationsDir, frameworkTables, gapAgainstDeclared, hasGap, introspectionUrl, loadSources, migrationHash, normalizeSql, openMigrationDriver, orderSources, readHistory, readJournal, sameColumnName, schemaReader, snapshotTables, splitStatements, suggestMigrationName, tablesPresentIn, uncommentIntrospection };
@@ -0,0 +1,296 @@
1
+ import { FORMAT_MARKER } from "./types.js";
2
+ import { MigrationToolError, generationToolMissing } from "./refusals.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ //#region nodefony/src/migrator/kit.ts
7
+ /**
8
+ * Plomberie `drizzle-kit` — la partie qui ne connaît NI le framework NI
9
+ * l'application, et que les deux exécutent donc à l'identique.
10
+ *
11
+ * Elle vivait dans `scripts/`, qui n'est pas publié (`files` ne porte que
12
+ * `dist`, `docs` et `migrations`). La commande `orm:generate`, elle, tourne chez
13
+ * l'utilisateur : lui laisser dépendre de `scripts/` aurait été livrer un verbe
14
+ * dont la moitié manque au paquet. Le socle a donc rejoint le code publié, et
15
+ * `scripts/` le CONSOMME — une seule implémentation, celle qu'exécutent le dépôt
16
+ * du framework et toute application.
17
+ *
18
+ * 🔴 **Le piège qui justifie ce fichier** : `drizzle-kit` **rend le code 0 quand
19
+ * il échoue**. Une exception non rattrapée part sur la sortie d'erreur et le
20
+ * process sort quand même à zéro. Tout appelant doit donc exiger une **preuve
21
+ * positive** que la génération a eu lieu — c'est le rôle de {@link runGenerate}.
22
+ * Un second piège en découle : le dossier de sortie ne peut pas être ABSOLU
23
+ * (l'outil le préfixe par `./`, fabriquant `.//Users/…`), et l'échec de lecture
24
+ * qui s'ensuit se présente lui aussi comme un succès.
25
+ */
26
+ /**
27
+ * Résout le binaire de `drizzle-kit` sans passer par un lanceur de shell.
28
+ *
29
+ * `npx` est un `.cmd` sous Windows, inexécutable sans `shell: true` — qui
30
+ * rouvrirait une injection par le nom de migration. Le paquet n'exporte pas son
31
+ * binaire (`exports` ne couvre que `.` et `./api`), donc on remonte les dossiers
32
+ * `node_modules` comme le ferait Node.
33
+ *
34
+ * @param from - dossier de départ de la remontée (racine du paquet qui génère,
35
+ * ou racine de l'application).
36
+ * @returns chemin absolu de `bin.cjs`.
37
+ * @throws Error si `drizzle-kit` n'est pas installé au-dessus de `from`.
38
+ */
39
+ function resolveDrizzleKitBin(from) {
40
+ let dir = from;
41
+ for (;;) {
42
+ const candidate = path.join(dir, "node_modules", "drizzle-kit", "bin.cjs");
43
+ if (fs.existsSync(candidate)) return candidate;
44
+ const parent = path.dirname(dir);
45
+ if (parent === dir) throw new MigrationToolError(generationToolMissing());
46
+ dir = parent;
47
+ }
48
+ }
49
+ /**
50
+ * Lance `drizzle-kit generate` et EXIGE la preuve qu'il a tourné.
51
+ *
52
+ * @param options - `cwd` (dossier depuis lequel l'outil est lancé — les chemins
53
+ * de la configuration lui sont relatifs), `configRel` (configuration, chemin
54
+ * relatif à `cwd`), `name` (nom imposé de la migration), `label` (ce qui est
55
+ * cité dans l'erreur).
56
+ * @returns la sortie complète de l'outil (sortie standard puis sortie d'erreur).
57
+ * @throws Error si le code est non nul, ou si rien ne prouve que la génération a
58
+ * eu lieu — l'absence de preuve n'est JAMAIS lue comme « rien à faire ».
59
+ */
60
+ function runGenerate({ cwd, configRel, name, label, regenerateCommand }) {
61
+ const result = spawnSync(process.execPath, [
62
+ resolveDrizzleKitBin(cwd),
63
+ "generate",
64
+ `--config=${configRel}`,
65
+ `--name=${name}`
66
+ ], {
67
+ cwd,
68
+ encoding: "utf8"
69
+ });
70
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
71
+ if (result.status !== 0 || !generationHappened(output)) {
72
+ if (isInteractivePromptFailure(output)) {
73
+ const replay = regenerateCommand ?? `nodefony orm:generate --name ${name}`;
74
+ throw new Error(`Un RENOMMAGE probable a été détecté sur ${label}, et il faut trancher.\n\n drizzle-kit ne peut pas deviner votre intention : une colonne qui\n disparaît et une autre qui apparaît, c'est soit un renommage — les\n données SUIVENT —, soit une suppression puis un ajout — les données\n sont PERDUES. Il pose donc la question, et il n'y a pas de terminal\n ici pour y répondre.\n\n Rejouer la commande dans un terminal interactif :\n ${replay}\n\n ⚠️ Après avoir répondu « renamed », RELIRE le fichier produit : quand\n une colonne est renommée ET que son type change, l'outil n'écrit que\n le renommage et OUBLIE le changement de type (drizzle-orm#3826).`);
75
+ }
76
+ throw new Error(`La génération n'a pas eu lieu sur ${label} (code ${result.status}). Ne rien conclure de ce silence : l'outil rend 0 même en échec.\n` + output.trim());
77
+ }
78
+ return output;
79
+ }
80
+ /**
81
+ * Lance `drizzle-kit introspect` et EXIGE la preuve qu'il a travaillé.
82
+ *
83
+ * C'est la seule commande de la chaîne qui LIT la base pour en tirer des
84
+ * fichiers. Elle sert l'adoption d'une base qui existait avant les migrations :
85
+ * l'instantané qu'elle dépose décrit l'état RÉEL, celui à partir duquel la
86
+ * génération suivante produira un `ALTER` au lieu d'un `CREATE TABLE`.
87
+ *
88
+ * La preuve n'est pas cherchée dans le texte de l'outil mais dans ce qu'il
89
+ * LAISSE : l'appelant relit le journal des fichiers. Ici on ne garde que le
90
+ * refus le plus grossier — un code de sortie non nul —, parce que l'outil rend
91
+ * `0` même en échec et qu'un marqueur de texte a déjà menti une fois (il change
92
+ * avec la couleur du terminal).
93
+ *
94
+ * @param options - `cwd` (dossier depuis lequel l'outil est lancé), `configRel`
95
+ * (configuration, chemin relatif à `cwd`), `label` (ce qui est cité en cas
96
+ * d'échec).
97
+ * @returns la sortie complète de l'outil.
98
+ * @throws Error si le code est non nul.
99
+ */
100
+ function runIntrospect({ cwd, configRel, label }) {
101
+ const result = spawnSync(process.execPath, [
102
+ resolveDrizzleKitBin(cwd),
103
+ "introspect",
104
+ `--config=${configRel}`
105
+ ], {
106
+ cwd,
107
+ encoding: "utf8"
108
+ });
109
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
110
+ if (result.status !== 0) throw new Error(`La lecture du schéma de ${label} a échoué (code ${result.status}).\n` + output.trim());
111
+ return output;
112
+ }
113
+ /**
114
+ * La génération a-t-elle EU LIEU ? — lue sur une sortie DÉCOLORÉE.
115
+ *
116
+ * L'outil ne rend pas de code d'échec (il sort 0 même quand il rate) : la seule
117
+ * preuve disponible est un marqueur dans son texte. Encore faut-il le chercher
118
+ * dans le texte, et non dans sa mise en forme.
119
+ *
120
+ * 🔴 **Le piège, payé en intégration continue** : la forge pose `FORCE_COLOR`,
121
+ * l'outil colore alors sa coche — `[`, une séquence d'échappement, `✓`, une
122
+ * autre séquence, `]` — et `"[✓]"` n'est plus une sous-chaîne. La génération
123
+ * réussissait, le fichier était écrit, et l'appelant annonçait qu'elle n'avait
124
+ * pas eu lieu. Vert sur un poste sans terminal, rouge à la forge : la sonde
125
+ * mesurait la présentation.
126
+ *
127
+ * Fonction PURE, pour qu'elle s'éprouve sans lancer un process — une règle qui
128
+ * exige un sous-processus pour être vue rouge n'est jamais vue rouge.
129
+ *
130
+ * @param output - sortie complète de l'outil, telle qu'elle a été capturée.
131
+ * @returns `true` si l'outil dit avoir écrit, ou n'avoir rien eu à écrire.
132
+ */
133
+ function generationHappened(output) {
134
+ const plain = output.replace(/\u001B\[[0-9;]*m/g, "");
135
+ return plain.includes("No schema changes") || plain.includes("[✓]");
136
+ }
137
+ /**
138
+ * Instructions qui DÉTRUISENT des données, par dialecte.
139
+ *
140
+ * Sources : la documentation PostgreSQL sur les verrous d'`ALTER TABLE`, le
141
+ * comportement constaté de `drizzle-kit`, et le fait — vérifié — que l'outil se
142
+ * décrit lui-même comme une aide à la productivité, pas comme un dispositif de
143
+ * sûreté de déploiement. Aucun générateur de diff ne peut distinguer seul un
144
+ * renommage (les données suivent) d'une suppression suivie d'un ajout (les
145
+ * données disparaissent) : c'est une intention, pas une différence de schéma.
146
+ *
147
+ * Chaque entrée porte le motif, ce qui se passe, et ce qu'il faut faire — le
148
+ * message d'un refus doit dire quoi faire, pas seulement ce qui est refusé.
149
+ */
150
+ const DESTRUCTIVE_PATTERNS = [
151
+ {
152
+ id: "drop-table",
153
+ pattern: /\bDROP\s+TABLE\b/i,
154
+ what: "supprime une table ET toutes ses lignes",
155
+ todo: "sauvegarder, puis appliquer en deux temps (cf expand/contract)"
156
+ },
157
+ {
158
+ id: "drop-column",
159
+ pattern: /\bDROP\s+COLUMN\b/i,
160
+ what: "supprime une colonne ET son contenu, sans retour possible",
161
+ todo: "s'il s'agissait d'un RENOMMAGE, regénérer dans un terminal interactif et répondre « renamed » : l'outil produit alors un RENAME, qui conserve les données"
162
+ },
163
+ {
164
+ id: "alter-column-type",
165
+ pattern: /\bALTER\s+(?:COLUMN\s+)?[`"\w]+\s+(?:SET\s+DATA\s+)?TYPE\b/i,
166
+ what: "convertit une colonne : les valeurs qui n'entrent pas dans le nouveau type sont perdues ou font échouer la migration à mi-parcours",
167
+ todo: "vérifier la conversion sur une copie des données de production avant d'appliquer"
168
+ },
169
+ {
170
+ id: "modify-column",
171
+ pattern: /\bMODIFY\s+COLUMN\b/i,
172
+ what: "réécrit une colonne MySQL (type, nullabilité) — mêmes risques",
173
+ todo: "vérifier la conversion sur une copie des données avant d'appliquer"
174
+ },
175
+ {
176
+ id: "truncate",
177
+ pattern: /\bTRUNCATE\b/i,
178
+ what: "vide une table entière",
179
+ todo: "ne jamais laisser une instruction de ce genre dans une migration"
180
+ }
181
+ ];
182
+ /**
183
+ * Ce qui ne détruit rien, mais doit être VU avant d'appliquer.
184
+ *
185
+ * Deux natures, et elles ne justifient ni l'une ni l'autre un refus — le
186
+ * générateur ne lit pas la base, il ne peut donc pas trancher à la place de
187
+ * celui qui la connaît.
188
+ *
189
+ * **Ce qui VERROUILLE.** Le scénario type, largement documenté : un
190
+ * `ALTER TABLE` prend un verrou exclusif, se met en file derrière une requête
191
+ * longue, et toutes les requêtes suivantes s'empilent derrière lui ; le parc de
192
+ * connexions se vide en quelques dizaines de secondes, et l'application rend des
193
+ * 503 alors que la migration, elle, n'a rien de lent.
194
+ *
195
+ * **Ce qui ÉCHOUE sur une table peuplée.** L'inapplicabilité est une propriété
196
+ * du SQL écrit, pas de la donnée : une colonne obligatoire sans défaut, un index
197
+ * unique posé sur une colonne qu'on vient d'ajouter. Elle se voit donc sans se
198
+ * connecter — et ne pas la dire a déjà conduit un agent à supprimer une base
199
+ * pour sortir de l'impasse (tâche 33 du banc de découvrabilité).
200
+ */
201
+ const BLOCKING_PATTERNS = [
202
+ {
203
+ id: "create-index-not-concurrent",
204
+ dialects: ["postgres"],
205
+ pattern: /\bCREATE\s+(?:UNIQUE\s+)?INDEX\b(?!\s+CONCURRENTLY)/i,
206
+ what: "bloque les écritures de la table pendant toute la construction de l'index (minutes sur une grande table)",
207
+ todo: "sur une table déjà volumineuse en production, appliquer l'index à part, en `CREATE INDEX CONCURRENTLY` (hors transaction)"
208
+ },
209
+ {
210
+ id: "set-not-null",
211
+ dialects: ["postgres"],
212
+ pattern: /\bSET\s+NOT\s+NULL\b/i,
213
+ what: "scanne la table entière sous verrou exclusif pour valider chaque ligne",
214
+ todo: "ajouter d'abord une contrainte `CHECK … NOT VALID`, la valider à part, puis poser le `NOT NULL`"
215
+ },
216
+ {
217
+ id: "add-not-null-sans-defaut",
218
+ pattern: /\bADD\s+(?:COLUMN\s+)?[`"']?\w+[`"']?[^;\n]*?\bNOT\s+NULL\b(?![^;\n]*\bDEFAULT\b)/i,
219
+ what: "ajoute une colonne OBLIGATOIRE sans valeur par défaut : sur une table qui porte déjà des lignes, sqlite et PostgreSQL REFUSENT la migration, et MySQL/MariaDB la remplit de chaînes vides sans un avertissement",
220
+ todo: "donner un défaut à la déclaration du champ (`role:string=membre`), ou le déclarer facultatif (`department:string?`) ; s'il faut les deux, c'est en trois temps — ajouter avec défaut, remplir (`--custom`), retirer le défaut"
221
+ },
222
+ {
223
+ id: "colonne-neuve-puis-index-unique",
224
+ pattern: /\bCREATE\s+UNIQUE\s+INDEX\b/i,
225
+ detect: (code) => {
226
+ const added = [...code.matchAll(/\bADD\s+(?:COLUMN\s+)?[`"']?(\w+)[`"']?/gi)].map((m) => m[1].toLowerCase());
227
+ if (added.length === 0) return false;
228
+ return [...code.matchAll(/\bCREATE\s+UNIQUE\s+INDEX\b[^;\n]*?\(([^)]*)\)/gi)].some((m) => m[1].split(",").map((c) => c.trim().replace(/[`"']/g, "").toLowerCase()).some((c) => added.includes(c)));
229
+ },
230
+ what: "ajoute une colonne ET pose son index UNIQUE dans la même migration : toutes les lignes déjà présentes reçoivent la même valeur, et l'index échoue sur la deuxième — cet enchaînement ne réussit que sur une table vide",
231
+ todo: "séparer : ajouter la colonne sans contrainte d'unicité, remplir chaque ligne d'une valeur DISTINCTE (`nodefony orm:generate --custom`), puis poser l'index unique"
232
+ }
233
+ ];
234
+ /**
235
+ * Analyse le SQL d'une migration et rend ce qui mérite un refus ou un regard.
236
+ *
237
+ * Volontairement **textuelle** : il ne s'agit pas d'analyser du SQL, mais de
238
+ * refuser de laisser passer sans un mot ce qui détruit des données. Un motif de
239
+ * trop fait poser une question ; un motif de moins fait perdre une table.
240
+ *
241
+ * @param sql - contenu d'un fichier de migration.
242
+ * @param dialect - dialecte concerné (certains risques lui sont propres).
243
+ * @returns `{ destructive, blocking }`, chacun décrivant ce qui a été reconnu.
244
+ */
245
+ function auditMigrationSql(sql, dialect) {
246
+ const code = sql.split("\n").filter((line) => !line.trimStart().startsWith("--")).join("\n");
247
+ const match = (list) => list.filter((rule) => !rule.dialects || rule.dialects.includes(dialect)).filter((rule) => rule.pattern.test(code)).filter((rule) => rule.detect === void 0 || rule.detect(code)).map(({ id, what, todo }) => ({
248
+ id,
249
+ what,
250
+ todo
251
+ }));
252
+ return {
253
+ destructive: match(DESTRUCTIVE_PATTERNS),
254
+ blocking: match(BLOCKING_PATTERNS)
255
+ };
256
+ }
257
+ /**
258
+ * Reconnaît l'échec de `drizzle-kit` faute de terminal interactif.
259
+ *
260
+ * L'outil pose une question — « cette colonne a-t-elle été renommée, ou
261
+ * supprimée puis ajoutée ? » — à laquelle lui seul ne peut pas répondre. Sans
262
+ * terminal, il échoue, et **rend 0**. Sans reconnaissance explicite, l'utilisateur
263
+ * reçoit une pile d'appels de l'outil au lieu de la seule chose qui compte : la
264
+ * question qu'on lui pose, et où y répondre.
265
+ *
266
+ * @param output - sortie complète de l'outil.
267
+ * @returns `true` si l'échec vient d'une question restée sans terminal.
268
+ */
269
+ function isInteractivePromptFailure(output) {
270
+ return /Interactive prompts require a TTY/i.test(output);
271
+ }
272
+ /**
273
+ * Pose le marqueur de format en tête des `.sql` d'un dossier qui ne l'ont pas.
274
+ *
275
+ * Écrit en fins de ligne `\n` quel que soit le système : le dépôt et le gabarit
276
+ * d'application déclarent `* text=auto eol=lf`, sans quoi une copie de travail
277
+ * Windows produirait une fausse dérive à chaque lecture.
278
+ *
279
+ * @param dir - dossier `<sortie>/<dialecte>` dont on marque les fichiers.
280
+ * @returns le nombre de fichiers marqués.
281
+ */
282
+ function stampFormatMarker(dir) {
283
+ if (!fs.existsSync(dir)) return 0;
284
+ let stamped = 0;
285
+ for (const name of fs.readdirSync(dir)) {
286
+ if (!name.endsWith(".sql")) continue;
287
+ const file = path.join(dir, name);
288
+ const body = fs.readFileSync(file, "utf8");
289
+ if (body.startsWith("-- nodefony:migration format=1")) continue;
290
+ fs.writeFileSync(file, `${FORMAT_MARKER}\n${body.replace(/\r\n/g, "\n")}`);
291
+ stamped++;
292
+ }
293
+ return stamped;
294
+ }
295
+ //#endregion
296
+ export { FORMAT_MARKER, auditMigrationSql, generationHappened, isInteractivePromptFailure, resolveDrizzleKitBin, runGenerate, runIntrospect, stampFormatMarker };
@@ -0,0 +1,68 @@
1
+ //#region nodefony/src/migrator/name.ts
2
+ /**
3
+ * Ce qu'un nom de migration a le droit d'être — et ce qu'on propose sinon.
4
+ *
5
+ * **Pourquoi une fonction pure, à part de la commande** : ce nom entre dans le
6
+ * tag du fichier, et un tag ne se renomme plus une fois la migration appliquée
7
+ * quelque part — c'est lui qui dit à chaque base ce qu'elle a déjà reçu. La
8
+ * règle qui le garde mérite donc d'être exerçable sans démarrer une
9
+ * application : un contrôle qui coûte trois minutes est un contrôle qu'on
10
+ * saute.
11
+ */
12
+ /**
13
+ * Longueur maximale d'un nom de migration.
14
+ *
15
+ * Le tag complet vaut `NNNN_<nom>` et le fichier `<tag>.sql` : à 120
16
+ * caractères, on reste très en deçà des 255 octets qu'un système de fichiers
17
+ * accepte pour un nom, et loin des 260 caractères qu'un chemin Windows tolère
18
+ * par défaut. La borne n'est pas là pour économiser des octets — elle est là
19
+ * pour qu'un nom trop long échoue AVANT d'avoir écrit un fichier, avec une
20
+ * phrase, plutôt qu'au moment de l'écriture avec un code d'erreur système.
21
+ */
22
+ const MIGRATION_NAME_MAX = 120;
23
+ /** Ce qu'un nom peut contenir : minuscules, chiffres et le trait bas. */
24
+ const FORME = /^[a-z0-9_]+$/;
25
+ /** Un nom doit porter au moins une lettre ou un chiffre — `___` n'en est pas un. */
26
+ const SUBSTANCE = /[a-z0-9]/;
27
+ /**
28
+ * Dérive un nom acceptable d'une saisie qui ne l'est pas.
29
+ *
30
+ * @param input - ce que l'utilisateur a tapé.
31
+ * @returns un nom conforme, ou `undefined` s'il n'en reste rien de sensé.
32
+ */
33
+ function suggestMigrationName(input) {
34
+ const derive = input.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "").slice(0, 120);
35
+ return SUBSTANCE.test(derive) ? derive : void 0;
36
+ }
37
+ /**
38
+ * Vérifie un nom de migration, et dit ce qu'il faudrait taper à la place.
39
+ *
40
+ * @param input - nom reçu de la ligne de commande, éventuellement absent.
41
+ * @returns le verdict, avec sa raison et sa suggestion.
42
+ */
43
+ function checkMigrationName(input) {
44
+ if (!input) return {
45
+ ok: false,
46
+ reason: "Il manque le nom de la migration."
47
+ };
48
+ if (input.length > 120) return {
49
+ ok: false,
50
+ reason: `Le nom fait ${input.length} caractères, la limite est 120 : il devient un nom de fichier, et un chemin trop long échoue à l'écriture sur certains systèmes.`,
51
+ suggestion: suggestMigrationName(input)
52
+ };
53
+ if (!FORME.test(input)) return {
54
+ ok: false,
55
+ reason: `Le nom « ${input} » ne convient pas : minuscules, chiffres et « _ » seulement.`,
56
+ suggestion: suggestMigrationName(input)
57
+ };
58
+ if (!SUBSTANCE.test(input)) return {
59
+ ok: false,
60
+ reason: `Le nom « ${input} » ne porte aucune lettre ni chiffre : il produirait un tag que personne ne saura relire.`
61
+ };
62
+ return {
63
+ ok: true,
64
+ name: input
65
+ };
66
+ }
67
+ //#endregion
68
+ export { MIGRATION_NAME_MAX, checkMigrationName, suggestMigrationName };