@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.
- package/LICENSE +544 -0
- package/README.md +162 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +105 -0
- package/dist/nodefony/command/migrateShared.js +247 -0
- package/dist/nodefony/command/orm-generate.js +356 -0
- package/dist/nodefony/command/orm-migrate-baseline.js +208 -0
- package/dist/nodefony/command/orm-migrate-repair.js +114 -0
- package/dist/nodefony/command/orm-migrate-status.js +67 -0
- package/dist/nodefony/command/orm-migrate.js +141 -0
- package/dist/nodefony/command/orm-reset.js +166 -0
- package/dist/nodefony/config/config.js +107 -0
- package/dist/nodefony/config/defineModuleConfig.js +63 -0
- package/dist/nodefony/entity/auditEventEntity.js +93 -0
- package/dist/nodefony/entity/colKit.js +260 -0
- package/dist/nodefony/entity/idempotencyEntity.js +74 -0
- package/dist/nodefony/entity/sessionEntity.js +75 -0
- package/dist/nodefony/entity/tokenEntity.js +198 -0
- package/dist/nodefony/entity/totpSecretEntity.js +98 -0
- package/dist/nodefony/entity/userTable.js +141 -0
- package/dist/nodefony/entity/webAuthnCredentialEntity.js +114 -0
- package/dist/nodefony/entity/webhookEndpointEntity.js +106 -0
- package/dist/nodefony/interfaces/IDrizzleConfig.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/migrations-schema/mysql.js +48 -0
- package/dist/nodefony/migrations-schema/postgres.js +48 -0
- package/dist/nodefony/migrations-schema/sqlite.js +48 -0
- package/dist/nodefony/registerStores.js +218 -0
- package/dist/nodefony/service/DrizzleService.js +282 -0
- package/dist/nodefony/src/DrizzleAuditStore.js +203 -0
- package/dist/nodefony/src/DrizzleIdempotencyStore.js +278 -0
- package/dist/nodefony/src/DrizzleTokenStore.js +244 -0
- package/dist/nodefony/src/DrizzleTotpSecretStore.js +151 -0
- package/dist/nodefony/src/DrizzleUserRepository.js +217 -0
- package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +159 -0
- package/dist/nodefony/src/DrizzleWebhookStore.js +169 -0
- package/dist/nodefony/src/SessionStorage.js +259 -0
- package/dist/nodefony/src/connectorTarget.js +59 -0
- package/dist/nodefony/src/likeSql.js +50 -0
- package/dist/nodefony/src/migrator/DrizzleMigrator.js +775 -0
- package/dist/nodefony/src/migrator/adopt.js +553 -0
- package/dist/nodefony/src/migrator/appSchema.js +414 -0
- package/dist/nodefony/src/migrator/catalog.js +76 -0
- package/dist/nodefony/src/migrator/destructive.js +213 -0
- package/dist/nodefony/src/migrator/divergence.js +84 -0
- package/dist/nodefony/src/migrator/drivers/index.js +39 -0
- package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +147 -0
- package/dist/nodefony/src/migrator/drivers/postgresDriver.js +151 -0
- package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +121 -0
- package/dist/nodefony/src/migrator/explain.js +565 -0
- package/dist/nodefony/src/migrator/hash.js +47 -0
- package/dist/nodefony/src/migrator/history.js +219 -0
- package/dist/nodefony/src/migrator/index.js +16 -0
- package/dist/nodefony/src/migrator/kit.js +296 -0
- package/dist/nodefony/src/migrator/name.js +68 -0
- package/dist/nodefony/src/migrator/paths.js +88 -0
- package/dist/nodefony/src/migrator/refusals.js +143 -0
- package/dist/nodefony/src/migrator/resolve.js +281 -0
- package/dist/nodefony/src/migrator/schemaDiff.js +86 -0
- package/dist/nodefony/src/migrator/sources.js +419 -0
- package/dist/nodefony/src/migrator/status.js +231 -0
- package/dist/nodefony/src/migrator/types.js +91 -0
- package/dist/nodefony/src/orm-core/DrizzleOrm.js +1154 -0
- package/dist/nodefony/src/orm-core/DrizzleRepository.js +610 -0
- package/dist/nodefony/src/orm-core/DrizzleTransaction.js +106 -0
- package/dist/nodefony/src/orm-core/index.js +4 -0
- package/dist/nodefony/src/queryKit.js +318 -0
- package/dist/nodefony/src/safeTarget.js +55 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/nodefony/command/migrateShared.d.ts +137 -0
- package/dist/types/nodefony/command/orm-generate.d.ts +53 -0
- package/dist/types/nodefony/command/orm-migrate-baseline.d.ts +87 -0
- package/dist/types/nodefony/command/orm-migrate-repair.d.ts +66 -0
- package/dist/types/nodefony/command/orm-migrate-status.d.ts +38 -0
- package/dist/types/nodefony/command/orm-migrate.d.ts +65 -0
- package/dist/types/nodefony/command/orm-reset.d.ts +55 -0
- package/dist/types/nodefony/config/config.d.ts +110 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +24 -0
- package/dist/types/nodefony/entity/auditEventEntity.d.ts +56 -0
- package/dist/types/nodefony/entity/colKit.d.ts +130 -0
- package/dist/types/nodefony/entity/idempotencyEntity.d.ts +52 -0
- package/dist/types/nodefony/entity/sessionEntity.d.ts +50 -0
- package/dist/types/nodefony/entity/tokenEntity.d.ts +53 -0
- package/dist/types/nodefony/entity/totpSecretEntity.d.ts +61 -0
- package/dist/types/nodefony/entity/userTable.d.ts +65 -0
- package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +57 -0
- package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +58 -0
- package/dist/types/nodefony/interfaces/IDrizzleConfig.d.ts +17 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- package/dist/types/nodefony/migrations-schema/mysql.d.ts +9 -0
- package/dist/types/nodefony/migrations-schema/postgres.d.ts +9 -0
- package/dist/types/nodefony/migrations-schema/sqlite.d.ts +9 -0
- package/dist/types/nodefony/registerStores.d.ts +52 -0
- package/dist/types/nodefony/service/DrizzleService.d.ts +27 -0
- package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +65 -0
- package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +129 -0
- package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +146 -0
- package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +60 -0
- package/dist/types/nodefony/src/DrizzleUserRepository.d.ts +67 -0
- package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +62 -0
- package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +79 -0
- package/dist/types/nodefony/src/SessionStorage.d.ts +72 -0
- package/dist/types/nodefony/src/connectorTarget.d.ts +48 -0
- package/dist/types/nodefony/src/likeSql.d.ts +29 -0
- package/dist/types/nodefony/src/migrator/DrizzleMigrator.d.ts +94 -0
- package/dist/types/nodefony/src/migrator/adopt.d.ts +280 -0
- package/dist/types/nodefony/src/migrator/appSchema.d.ts +223 -0
- package/dist/types/nodefony/src/migrator/catalog.d.ts +100 -0
- package/dist/types/nodefony/src/migrator/destructive.d.ts +123 -0
- package/dist/types/nodefony/src/migrator/divergence.d.ts +61 -0
- package/dist/types/nodefony/src/migrator/drivers/index.d.ts +26 -0
- package/dist/types/nodefony/src/migrator/drivers/mysqlDriver.d.ts +83 -0
- package/dist/types/nodefony/src/migrator/drivers/postgresDriver.d.ts +81 -0
- package/dist/types/nodefony/src/migrator/drivers/sqliteDriver.d.ts +53 -0
- package/dist/types/nodefony/src/migrator/explain.d.ts +424 -0
- package/dist/types/nodefony/src/migrator/hash.d.ts +39 -0
- package/dist/types/nodefony/src/migrator/history.d.ts +139 -0
- package/dist/types/nodefony/src/migrator/index.d.ts +20 -0
- package/dist/types/nodefony/src/migrator/kit.d.ts +141 -0
- package/dist/types/nodefony/src/migrator/name.d.ts +52 -0
- package/dist/types/nodefony/src/migrator/paths.d.ts +54 -0
- package/dist/types/nodefony/src/migrator/refusals.d.ts +170 -0
- package/dist/types/nodefony/src/migrator/resolve.d.ts +201 -0
- package/dist/types/nodefony/src/migrator/schemaDiff.d.ts +112 -0
- package/dist/types/nodefony/src/migrator/sources.d.ts +119 -0
- package/dist/types/nodefony/src/migrator/status.d.ts +132 -0
- package/dist/types/nodefony/src/migrator/types.d.ts +273 -0
- package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +241 -0
- package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +98 -0
- package/dist/types/nodefony/src/orm-core/DrizzleTransaction.d.ts +71 -0
- package/dist/types/nodefony/src/orm-core/index.d.ts +11 -0
- package/dist/types/nodefony/src/queryKit.d.ts +136 -0
- package/dist/types/nodefony/src/safeTarget.d.ts +42 -0
- package/docs/index.md +954 -0
- package/docs/migrations.md +691 -0
- package/migrations/mysql/0000_framework_init.sql +137 -0
- package/migrations/mysql/meta/_journal.json +13 -0
- package/migrations/postgres/0000_framework_init.sql +128 -0
- package/migrations/postgres/meta/_journal.json +13 -0
- package/migrations/sqlite/0000_framework_init.sql +127 -0
- package/migrations/sqlite/meta/_journal.json +13 -0
- package/package.json +126 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { FORMAT_MARKER, MigrationVerdictError, STATEMENT_BREAKPOINT } from "./types.js";
|
|
2
|
+
import { frameworkMigrationsDir } from "./paths.js";
|
|
3
|
+
import { migrationHash, normalizeSql } from "./hash.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
//#region nodefony/src/migrator/sources.ts
|
|
7
|
+
/**
|
|
8
|
+
* Version du journal drizzle-kit que cet applicateur sait lire.
|
|
9
|
+
*
|
|
10
|
+
* On adopte le format d'un outil tiers : le lire « au mieux » reviendrait à
|
|
11
|
+
* découvrir un défaut de découpe APRÈS publication, quand le corriger
|
|
12
|
+
* changerait le sens de fichiers déjà livrés chez des utilisateurs.
|
|
13
|
+
*/
|
|
14
|
+
const SUPPORTED_JOURNAL_VERSIONS = ["7"];
|
|
15
|
+
/**
|
|
16
|
+
* Ordonne un registre de sources : rang croissant, nom en départage.
|
|
17
|
+
*
|
|
18
|
+
* `framework` porte le rang 0 (les entités d'application peuvent référencer ses
|
|
19
|
+
* tables), `app` le rang le plus élevé. Entre les deux, les modules à leur
|
|
20
|
+
* ordre de chargement. Le nom départage à rang égal pour que l'ordre soit
|
|
21
|
+
* **déterministe** : deux pods qui liraient le même registre dans deux ordres
|
|
22
|
+
* différents appliqueraient deux plans différents.
|
|
23
|
+
*
|
|
24
|
+
* @param sources - registre, dans n'importe quel ordre.
|
|
25
|
+
* @returns le même registre, trié.
|
|
26
|
+
*/
|
|
27
|
+
function orderSources(sources) {
|
|
28
|
+
return [...sources].sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Charge toutes les sources d'un registre pour un dialecte donné.
|
|
32
|
+
*
|
|
33
|
+
* @param sources - registre de sources (espace de noms ouvert).
|
|
34
|
+
* @param dialect - dialecte du connecteur ; sélectionne le sous-dossier.
|
|
35
|
+
* @returns les fichiers ordonnés et les sources absentes, nommées.
|
|
36
|
+
* @throws MigrationVerdictError si un fichier ne porte pas le format attendu.
|
|
37
|
+
*/
|
|
38
|
+
async function loadSources(sources, dialect) {
|
|
39
|
+
const files = [];
|
|
40
|
+
const absent = [];
|
|
41
|
+
for (const source of orderSources(sources)) {
|
|
42
|
+
const dir = path.join(source.dir, dialect);
|
|
43
|
+
const journal = await readJournal(dir, source.name);
|
|
44
|
+
if (journal === null) {
|
|
45
|
+
absent.push(source.name);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const entry of [...journal.entries].sort((a, b) => a.idx - b.idx)) files.push(await readMigrationFile(dir, source.name, entry, dialect));
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
files,
|
|
52
|
+
absent
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Lit le journal d'une source, ou `null` si la source n'est pas installée.
|
|
57
|
+
*
|
|
58
|
+
* @param dir - dossier `<source>/<dialecte>`.
|
|
59
|
+
* @param source - nom logique de la source, pour nommer un refus.
|
|
60
|
+
* @returns le journal validé, ou `null` si le dossier n'existe pas.
|
|
61
|
+
* @throws MigrationVerdictError si la version du journal n'est pas reconnue.
|
|
62
|
+
*/
|
|
63
|
+
async function readJournal(dir, source) {
|
|
64
|
+
const file = path.join(dir, "meta", "_journal.json");
|
|
65
|
+
let raw;
|
|
66
|
+
try {
|
|
67
|
+
raw = await fs.readFile(file, "utf8");
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e.code === "ENOENT") return null;
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
let journal;
|
|
73
|
+
try {
|
|
74
|
+
journal = JSON.parse(raw);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
throw new MigrationVerdictError({
|
|
77
|
+
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
78
|
+
connector: "",
|
|
79
|
+
source,
|
|
80
|
+
facts: {
|
|
81
|
+
file,
|
|
82
|
+
cause: e instanceof Error ? e.message : String(e)
|
|
83
|
+
},
|
|
84
|
+
nextActions: []
|
|
85
|
+
}, `Le journal des migrations « ${file} » n'est pas lisible : ${e instanceof Error ? e.message : String(e)}. La cause la plus fréquente est un conflit de fusion non résolu — deux branches ayant généré chacune une migration. Ouvrir le fichier, résoudre le conflit, et vérifier que chaque migration du dossier y figure une fois. La base n'est pour rien dans cette erreur.`);
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(journal.entries)) throw new MigrationVerdictError({
|
|
88
|
+
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
89
|
+
connector: "",
|
|
90
|
+
source,
|
|
91
|
+
facts: { file },
|
|
92
|
+
nextActions: []
|
|
93
|
+
}, `Le journal des migrations « ${file} » ne porte pas de liste « entries » : ce n'est pas un journal que ce format sait lire.`);
|
|
94
|
+
if (!SUPPORTED_JOURNAL_VERSIONS.includes(String(journal.version))) throw new MigrationVerdictError({
|
|
95
|
+
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
96
|
+
connector: "",
|
|
97
|
+
source,
|
|
98
|
+
facts: {
|
|
99
|
+
file,
|
|
100
|
+
journalVersion: String(journal.version),
|
|
101
|
+
supported: SUPPORTED_JOURNAL_VERSIONS
|
|
102
|
+
},
|
|
103
|
+
nextActions: [{
|
|
104
|
+
command: "npm update @nodefony/drizzle",
|
|
105
|
+
args: ["update", "@nodefony/drizzle"]
|
|
106
|
+
}]
|
|
107
|
+
}, `Le journal de migrations de la source « ${source} » est en version ${String(journal.version)}, que cette version du framework ne sait pas lire (reconnue : ${SUPPORTED_JOURNAL_VERSIONS.join(", ")}).`);
|
|
108
|
+
return journal;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Lit un fichier de migration, valide son format et découpe ses statements.
|
|
112
|
+
*
|
|
113
|
+
* @param dir - dossier `<source>/<dialecte>`.
|
|
114
|
+
* @param source - nom logique de la source.
|
|
115
|
+
* @param entry - entrée du journal.
|
|
116
|
+
* @param dialect - dialecte du connecteur ; choisit la grammaire de découpe.
|
|
117
|
+
* @returns le fichier chargé, empreinte comprise.
|
|
118
|
+
* @throws MigrationVerdictError si le marqueur de format est absent ou autre.
|
|
119
|
+
*/
|
|
120
|
+
async function readMigrationFile(dir, source, entry, dialect) {
|
|
121
|
+
const file = path.join(dir, `${entry.tag}.sql`);
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
content = await fs.readFile(file, "utf8");
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
throw new MigrationVerdictError({
|
|
127
|
+
code: "NF_MIGRATE_JOURNAL_MISMATCH",
|
|
128
|
+
connector: "",
|
|
129
|
+
source,
|
|
130
|
+
tag: entry.tag,
|
|
131
|
+
facts: {
|
|
132
|
+
file,
|
|
133
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
134
|
+
},
|
|
135
|
+
nextActions: [{
|
|
136
|
+
command: "nodefony orm:migrate:status --json",
|
|
137
|
+
args: ["orm:migrate:status", "--json"]
|
|
138
|
+
}]
|
|
139
|
+
}, `Le journal de la source « ${source} » annonce la migration « ${entry.tag} », mais son fichier « ${file} » est introuvable. Cette source est incohérente avec elle-même : rien n'a été appliqué, et l'applicateur ne devine jamais le contenu d'un fichier absent. Deux issues, et le statut ci-dessus dit laquelle : si « ${entry.tag} » figure dans l'historique, la migration a DÉJÀ été appliquée et c'est le fichier qu'il faut rendre (contrôle de version) ; si elle n'y figure pas, retirer son entrée de « ${path.join(dir, "meta", "_journal.json")} ». Ne PAS refaire la base : elle porte des données, et rien ici ne les concerne.`);
|
|
140
|
+
}
|
|
141
|
+
const normalized = normalizeSql(content);
|
|
142
|
+
const marker = normalized.split("\n", 1)[0]?.trim() ?? "";
|
|
143
|
+
if (marker !== "-- nodefony:migration format=1") throw new MigrationVerdictError({
|
|
144
|
+
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
145
|
+
connector: "",
|
|
146
|
+
source,
|
|
147
|
+
tag: entry.tag,
|
|
148
|
+
facts: {
|
|
149
|
+
file,
|
|
150
|
+
found: marker,
|
|
151
|
+
expected: FORMAT_MARKER
|
|
152
|
+
},
|
|
153
|
+
nextActions: [{
|
|
154
|
+
command: `nodefony orm:generate --custom --name ${entry.tag}`,
|
|
155
|
+
args: [
|
|
156
|
+
"orm:generate",
|
|
157
|
+
"--custom",
|
|
158
|
+
"--name",
|
|
159
|
+
entry.tag
|
|
160
|
+
]
|
|
161
|
+
}]
|
|
162
|
+
}, `Le fichier « ${file} » ne porte pas le format de migration attendu (« ${FORMAT_MARKER} » ; lu : « ${marker} »). Il n'a PAS été appliqué. Un fichier de migration ne s'écrit pas à la main : « --custom » dépose le gabarit, son marqueur et son entrée de journal, et c'est dedans que le SQL libre se met. Sinon, poser « ${FORMAT_MARKER} » en première ligne du fichier.`);
|
|
163
|
+
return {
|
|
164
|
+
source,
|
|
165
|
+
tag: entry.tag,
|
|
166
|
+
idx: entry.idx,
|
|
167
|
+
hash: migrationHash(content),
|
|
168
|
+
statements: splitStatements(normalized, dialect),
|
|
169
|
+
path: file
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Découpe un fichier de migration en statements exécutables.
|
|
174
|
+
*
|
|
175
|
+
* ## Le séparateur EST un commentaire SQL — et c'est tout le problème
|
|
176
|
+
*
|
|
177
|
+
* `--> statement-breakpoint` commence par deux tirets : pour le moteur, c'est
|
|
178
|
+
* un commentaire, et rien ne le distingue d'un autre à l'œil nu. Deux ordres
|
|
179
|
+
* naïfs échouent donc, chacun pour sa raison, et les deux ont été constatés :
|
|
180
|
+
*
|
|
181
|
+
* - **découper puis retirer les commentaires** fait d'un séparateur écrit DANS
|
|
182
|
+
* un commentaire un vrai séparateur. La ligne est coupée en deux, et le
|
|
183
|
+
* fragment de droite — qui ne commence plus par deux tirets — part au pilote
|
|
184
|
+
* comme une instruction. Le produit se le faisait à lui-même : le gabarit
|
|
185
|
+
* qu'écrit `orm:generate --custom` porte la phrase qui NOMME le séparateur,
|
|
186
|
+
* si bien que toute migration libre écrite en suivant son aide échouait sur
|
|
187
|
+
* une erreur de syntaxe, en laissant dans l'historique une migration `failed`
|
|
188
|
+
* — c'est-à-dire une base bloquée, à réparer à la main ;
|
|
189
|
+
* - **retirer les commentaires puis découper** emporte les séparateurs
|
|
190
|
+
* eux-mêmes, et fond toutes les instructions du fichier en une seule.
|
|
191
|
+
*
|
|
192
|
+
* Il n'y a donc pas d'ordre à trouver : les deux décisions se prennent au MÊME
|
|
193
|
+
* moment, ligne par ligne, en sachant si l'on est dans une chaîne littérale.
|
|
194
|
+
* Un commentaire ne peut pas ouvrir un commentaire : dès que la ligne commence
|
|
195
|
+
* par deux tirets sans être le séparateur, tout ce qu'elle porte est du texte.
|
|
196
|
+
*
|
|
197
|
+
* Le séparateur n'est pas reconnu « ligne entière » pour autant : drizzle-kit
|
|
198
|
+
* le colle en fin d'instruction (`CREATE INDEX …;--> statement-breakpoint`), et
|
|
199
|
+
* l'exiger seul sur sa ligne ferait fusionner toutes les instructions d'une
|
|
200
|
+
* migration du framework.
|
|
201
|
+
*
|
|
202
|
+
* ## La grammaire de chaîne est celle du MOTEUR, pas une moyenne des trois
|
|
203
|
+
*
|
|
204
|
+
* Savoir si l'on est dans une chaîne n'a pas la même réponse partout : MySQL
|
|
205
|
+
* échappe l'apostrophe par une contre-oblique, PostgreSQL délimite les corps
|
|
206
|
+
* par `$tag$`. Une grammaire fausse ne lève AUCUNE erreur — le scanner croit
|
|
207
|
+
* sortir d'une chaîne où il est encore, la ligne suivante commençant par deux
|
|
208
|
+
* tirets est retirée comme un commentaire alors qu'elle est de la DONNÉE, ou
|
|
209
|
+
* un séparateur porté par un texte coupe l'instruction en deux. La migration
|
|
210
|
+
* s'inscrit ensuite en succès avec l'empreinte du fichier entier : plus aucun
|
|
211
|
+
* verdict ne peut le voir. Le dialecte est donc un paramètre REQUIS, jamais un
|
|
212
|
+
* défaut — le fichier vit déjà sous `<source>/<dialecte>/`, l'appelant l'a.
|
|
213
|
+
*
|
|
214
|
+
* @param normalized - contenu normalisé (fins de ligne en LF).
|
|
215
|
+
* @param dialect - dialecte du connecteur ; choisit la grammaire de chaîne.
|
|
216
|
+
* @returns les statements non vides, dans l'ordre du fichier.
|
|
217
|
+
*/
|
|
218
|
+
function splitStatements(normalized, dialect) {
|
|
219
|
+
const grammar = STRING_GRAMMARS[dialect];
|
|
220
|
+
const statements = [];
|
|
221
|
+
let current = [];
|
|
222
|
+
const curseur = {
|
|
223
|
+
open: "",
|
|
224
|
+
coupe: -1
|
|
225
|
+
};
|
|
226
|
+
/** Clôt le statement en cours — vide, il ne compte pas. */
|
|
227
|
+
const clore = () => {
|
|
228
|
+
const statement = current.join("\n").trim();
|
|
229
|
+
if (statement.length > 0) statements.push(statement);
|
|
230
|
+
current = [];
|
|
231
|
+
curseur.open = "";
|
|
232
|
+
};
|
|
233
|
+
for (const line of normalized.split("\n")) {
|
|
234
|
+
if (curseur.open === "") {
|
|
235
|
+
const tete = line.trimStart();
|
|
236
|
+
if (tete.startsWith("--> statement-breakpoint")) {
|
|
237
|
+
clore();
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (tete.startsWith("--")) continue;
|
|
241
|
+
scanLine(line, STATEMENT_BREAKPOINT, grammar, curseur);
|
|
242
|
+
const coupe = curseur.coupe;
|
|
243
|
+
if (coupe >= 0) {
|
|
244
|
+
current.push(line.slice(0, coupe));
|
|
245
|
+
clore();
|
|
246
|
+
const remainder = line.slice(coupe + STATEMENT_BREAKPOINT.length);
|
|
247
|
+
if (remainder.trim() !== "") {
|
|
248
|
+
current.push(remainder);
|
|
249
|
+
scanLine(remainder, "", grammar, curseur);
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
current.push(line);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
current.push(line);
|
|
257
|
+
scanLine(line, "", grammar, curseur);
|
|
258
|
+
}
|
|
259
|
+
clore();
|
|
260
|
+
return statements;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* La grammaire de chaîne de chaque moteur supporté.
|
|
264
|
+
*
|
|
265
|
+
* SQLite suit le standard et rien de plus : une contre-oblique y est un
|
|
266
|
+
* caractère ordinaire, et `$$` n'est pas un délimiteur. Lui appliquer la
|
|
267
|
+
* grammaire de MySQL serait aussi faux que l'inverse — c'est pourquoi le
|
|
268
|
+
* routage se fait dans les DEUX sens, et pas seulement pour ajouter des cas.
|
|
269
|
+
*/
|
|
270
|
+
const STRING_GRAMMARS = {
|
|
271
|
+
sqlite: {
|
|
272
|
+
backslashInSingleQuote: false,
|
|
273
|
+
apostropheEchappeePrefixee: false,
|
|
274
|
+
dollarQuote: false
|
|
275
|
+
},
|
|
276
|
+
postgres: {
|
|
277
|
+
backslashInSingleQuote: false,
|
|
278
|
+
apostropheEchappeePrefixee: true,
|
|
279
|
+
dollarQuote: true
|
|
280
|
+
},
|
|
281
|
+
mysql: {
|
|
282
|
+
backslashInSingleQuote: true,
|
|
283
|
+
apostropheEchappeePrefixee: false,
|
|
284
|
+
dollarQuote: false
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
/** Vrai si `c` peut faire partie d'un identifiant SQL non quoté. */
|
|
288
|
+
function isIdentifierChar(c) {
|
|
289
|
+
return c !== void 0 && /[A-Za-z0-9_$]/.test(c);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Balaye une ligne : met à jour l'état de chaîne, et repère le motif.
|
|
293
|
+
*
|
|
294
|
+
* UNE seule implémentation pour les deux questions que pose la découpe — « où
|
|
295
|
+
* couper ? » et « la ligne suivante peut-elle porter un commentaire ? ». Elles
|
|
296
|
+
* se répondaient auparavant dans deux fonctions jumelles, chacune avec sa copie
|
|
297
|
+
* de la grammaire : deux copies divergent, et chacune reste verte dans son
|
|
298
|
+
* propre test.
|
|
299
|
+
*
|
|
300
|
+
* Le balayage s'ARRÊTE sur le motif : l'appelant repart alors du reste de la
|
|
301
|
+
* ligne, avec une chaîne close par construction (un statement neuf commence).
|
|
302
|
+
* Passer un motif vide balaye donc la ligne entière.
|
|
303
|
+
*
|
|
304
|
+
* @param line - ligne à balayer.
|
|
305
|
+
* @param needle - texte cherché hors chaîne ; vide pour ne rien chercher.
|
|
306
|
+
* @param grammar - grammaire de chaîne du moteur visé.
|
|
307
|
+
* @param curseur - état lu ET écrit : chaîne ouverte, indice du motif.
|
|
308
|
+
*/
|
|
309
|
+
function scanLine(line, needle, grammar, curseur) {
|
|
310
|
+
curseur.coupe = -1;
|
|
311
|
+
let open = curseur.open;
|
|
312
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
313
|
+
const c = line[i];
|
|
314
|
+
if (open.startsWith("$")) {
|
|
315
|
+
if (line.startsWith(open, i)) {
|
|
316
|
+
i += open.length - 1;
|
|
317
|
+
open = "";
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (open !== "") {
|
|
322
|
+
if (c === "\\" && (grammar.backslashInSingleQuote || open === "e'")) {
|
|
323
|
+
i += 1;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (c === "'") {
|
|
327
|
+
if (line[i + 1] === "'") {
|
|
328
|
+
i += 1;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
open = "";
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (c === "'") {
|
|
336
|
+
open = "'";
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (grammar.apostropheEchappeePrefixee && (c === "E" || c === "e") && line[i + 1] === "'" && !isIdentifierChar(line[i - 1])) {
|
|
340
|
+
open = "e'";
|
|
341
|
+
i += 1;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (grammar.dollarQuote && c === "$") {
|
|
345
|
+
const tag = delimiteurDollar(line, i);
|
|
346
|
+
if (tag !== null) {
|
|
347
|
+
open = tag;
|
|
348
|
+
i += tag.length - 1;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (needle !== "" && line.startsWith(needle, i)) {
|
|
353
|
+
curseur.coupe = i;
|
|
354
|
+
curseur.open = open;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
curseur.open = open;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Le délimiteur `$tag$` qui commence à `debut`, ou `null`.
|
|
362
|
+
*
|
|
363
|
+
* Le tag est optionnel (`$$`) et suit les règles d'un identifiant : ce qui
|
|
364
|
+
* exclut `$1`, un paramètre de requête, qu'il ne faut surtout pas lire comme
|
|
365
|
+
* l'ouverture d'une chaîne.
|
|
366
|
+
*
|
|
367
|
+
* @param line - ligne balayée.
|
|
368
|
+
* @param debut - indice du `$` candidat.
|
|
369
|
+
* @returns le délimiteur complet, ou `null` si ce n'en est pas un.
|
|
370
|
+
*/
|
|
371
|
+
function delimiteurDollar(line, debut) {
|
|
372
|
+
let i = debut + 1;
|
|
373
|
+
while (i < line.length && /[A-Za-z0-9_]/.test(line[i])) {
|
|
374
|
+
if (i === debut + 1 && /[0-9]/.test(line[i])) return null;
|
|
375
|
+
i += 1;
|
|
376
|
+
}
|
|
377
|
+
return line[i] === "$" ? line.slice(debut, i + 1) : null;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Noms des tables qu'un lot de migrations CRÉE.
|
|
381
|
+
*
|
|
382
|
+
* Sert la garde d'adoption : un historique vide alors que ces tables existent
|
|
383
|
+
* déjà signale une base antérieure aux migrations, pas une base neuve. La
|
|
384
|
+
* liste est DÉRIVÉE des fichiers — une liste codée en dur mentirait dès la
|
|
385
|
+
* première migration livrée par un module tiers.
|
|
386
|
+
*
|
|
387
|
+
* @param files - fichiers de migration chargés.
|
|
388
|
+
* @returns les noms de tables, sans doublon.
|
|
389
|
+
*/
|
|
390
|
+
function createdTables(files) {
|
|
391
|
+
const found = /* @__PURE__ */ new Set();
|
|
392
|
+
const pattern = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?([A-Za-z0-9_$]+)[`"']?/gi;
|
|
393
|
+
for (const file of files) for (const statement of file.statements) for (const match of statement.matchAll(pattern)) found.add(match[1]);
|
|
394
|
+
return [...found];
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Tables que le FRAMEWORK construit — dérivées de ses fichiers de migration.
|
|
398
|
+
*
|
|
399
|
+
* Dérivées, jamais listées à la main : une liste codée en dur mentirait dès la
|
|
400
|
+
* première migration livrée par une version suivante.
|
|
401
|
+
*
|
|
402
|
+
* Une seule implémentation, parce que deux appelants en dépendent pour la même
|
|
403
|
+
* décision — le générateur les exclut de ce qu'il écrit, l'adoption les exclut
|
|
404
|
+
* de ce qu'elle lit. Deux copies divergeraient en silence, chacune verte dans
|
|
405
|
+
* son propre test.
|
|
406
|
+
*
|
|
407
|
+
* @param dialect - dialecte du connecteur visé.
|
|
408
|
+
* @returns les noms de tables du framework.
|
|
409
|
+
*/
|
|
410
|
+
async function frameworkTables(dialect) {
|
|
411
|
+
const { files } = await loadSources([{
|
|
412
|
+
name: "framework",
|
|
413
|
+
dir: await frameworkMigrationsDir(),
|
|
414
|
+
rank: 0
|
|
415
|
+
}], dialect);
|
|
416
|
+
return createdTables(files);
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
export { SUPPORTED_JOURNAL_VERSIONS, createdTables, frameworkTables, loadSources, orderSources, splitStatements };
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { MigrationVerdictError } from "./types.js";
|
|
2
|
+
import { action, buildReport } from "./explain.js";
|
|
3
|
+
import { buildMigrator, readMigrationEnv, resetAllowed, resolveConnector } from "./resolve.js";
|
|
4
|
+
import { describeResolutionRefusal, moduleAbsent } from "./refusals.js";
|
|
5
|
+
import { describeDivergence } from "./divergence.js";
|
|
6
|
+
import { describeTargetSafely } from "../safeTarget.js";
|
|
7
|
+
//#region nodefony/src/migrator/status.ts
|
|
8
|
+
/**
|
|
9
|
+
* Habille un refus en charge utile publiée — la MÊME que celle de `--json`.
|
|
10
|
+
*
|
|
11
|
+
* @param connector - connecteur demandé.
|
|
12
|
+
* @param refusal - ce qu'il y a à en dire.
|
|
13
|
+
* @returns la charge utile d'arrêt.
|
|
14
|
+
*/
|
|
15
|
+
function failureFrom(connector, refusal) {
|
|
16
|
+
return {
|
|
17
|
+
formatVersion: 1,
|
|
18
|
+
connector,
|
|
19
|
+
exitCode: refusal.exitCode,
|
|
20
|
+
error: {
|
|
21
|
+
code: refusal.code,
|
|
22
|
+
summary: refusal.summary,
|
|
23
|
+
meaning: refusal.meaning,
|
|
24
|
+
nextActions: refusal.nextActions
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compose la charge utile d'un état — le SEUL endroit où le contexte du rendu
|
|
30
|
+
* est assemblé.
|
|
31
|
+
*
|
|
32
|
+
* Les quatre commandes de migration ET le plan d'administration publient le
|
|
33
|
+
* même objet ; recopier son assemblage les faisait déjà diverger d'un champ à
|
|
34
|
+
* l'autre, et le prochain consommateur à naître aurait oublié celui du jour.
|
|
35
|
+
* La troisième source ne se paie qu'ici, une fois : `describeDivergence`
|
|
36
|
+
* s'abstient toute seule quand le verdict est déjà décidé.
|
|
37
|
+
*
|
|
38
|
+
* @param plan - plan calculé par l'applicateur, en lecture seule.
|
|
39
|
+
* @param resolution - connecteur prêt (porte le mode de schéma effectif).
|
|
40
|
+
* @param config - configuration validée du module.
|
|
41
|
+
* @param kernel - kernel courant, pour constater l'environnement.
|
|
42
|
+
* @returns la charge utile, prête pour `--json` comme pour l'écran.
|
|
43
|
+
*/
|
|
44
|
+
async function composeReport(plan, resolution, config, kernel) {
|
|
45
|
+
const mode = config.migrations.divergence;
|
|
46
|
+
return buildReport(plan, {
|
|
47
|
+
ddl: resolution.ddl,
|
|
48
|
+
divergence: mode === "off" ? null : await describeDivergence(plan),
|
|
49
|
+
divergenceMode: mode,
|
|
50
|
+
canReset: resetAllowed(readMigrationEnv(kernel)),
|
|
51
|
+
target: describeTargetSafely(resolution.target),
|
|
52
|
+
fromMigrateUrl: resolution.fromMigrateUrl
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Résout le connecteur pour les trois verbes — ou rend le refus.
|
|
57
|
+
*
|
|
58
|
+
* Les trois posent la même question dans le même ordre : le module est-il
|
|
59
|
+
* chargé, le connecteur existe-t-il, est-il migrable. Trois copies auraient
|
|
60
|
+
* fini par répondre trois choses différentes au même cas.
|
|
61
|
+
*
|
|
62
|
+
* @param wanted - connecteur demandé.
|
|
63
|
+
* @param config - configuration validée, `null` si le module n'est pas chargé.
|
|
64
|
+
* @param kernel - kernel courant.
|
|
65
|
+
* @returns le connecteur prêt et sa configuration, ou le refus.
|
|
66
|
+
*/
|
|
67
|
+
function prepare(wanted, config, kernel) {
|
|
68
|
+
if (!config) return {
|
|
69
|
+
ok: false,
|
|
70
|
+
failure: failureFrom(wanted, moduleAbsent())
|
|
71
|
+
};
|
|
72
|
+
const resolution = resolveConnector(wanted, config, readMigrationEnv(kernel), kernel, { allowMigrateUrl: false });
|
|
73
|
+
if (resolution.kind !== "ready") return {
|
|
74
|
+
ok: false,
|
|
75
|
+
failure: failureFrom(wanted, describeResolutionRefusal(wanted, resolution, config))
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
resolution,
|
|
80
|
+
config
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Traduit n'importe quel échec d'exécution en charge utile publiée.
|
|
85
|
+
*
|
|
86
|
+
* Un refus de l'applicateur porte DÉJÀ son verdict et ses gestes : on les rend
|
|
87
|
+
* tels quels. Tout le reste — base injoignable, droits manquants, verrou
|
|
88
|
+
* impossible — est une panne, et l'écran doit la MONTRER : un tableau vide qui
|
|
89
|
+
* ressemble à « tout va bien » est pire qu'une erreur.
|
|
90
|
+
*
|
|
91
|
+
* @param connector - connecteur concerné.
|
|
92
|
+
* @param e - ce qui a été levé.
|
|
93
|
+
* @returns la charge utile d'arrêt.
|
|
94
|
+
*/
|
|
95
|
+
function failure(connector, e) {
|
|
96
|
+
if (e instanceof MigrationVerdictError) return {
|
|
97
|
+
formatVersion: 1,
|
|
98
|
+
connector,
|
|
99
|
+
exitCode: e.verdict.code === "NF_MIGRATE_LOCK_TIMEOUT" ? 2 : 1,
|
|
100
|
+
error: {
|
|
101
|
+
code: e.verdict.code,
|
|
102
|
+
summary: e.message,
|
|
103
|
+
meaning: "",
|
|
104
|
+
nextActions: [...e.verdict.nextActions]
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
formatVersion: 1,
|
|
109
|
+
connector,
|
|
110
|
+
exitCode: 2,
|
|
111
|
+
error: {
|
|
112
|
+
code: "NF_MIGRATE_UNAVAILABLE",
|
|
113
|
+
summary: `Le connecteur « ${connector} » n'a pas pu être lu : ${e instanceof Error ? e.message : String(e)}`,
|
|
114
|
+
meaning: "Toucher une base échoue quand celle-ci est injoignable, quand le compte n'a pas le droit de lire l'historique, ou quand le verrou est tenu par un autre travail.",
|
|
115
|
+
nextActions: [action(`nodefony orm:migrate:status --connector ${connector}`)]
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Lit l'état des migrations d'un connecteur, sans rien appliquer.
|
|
121
|
+
*
|
|
122
|
+
* @param wanted - connecteur demandé (`default` quand rien n'est précisé).
|
|
123
|
+
* @param config - configuration validée du module, `null` s'il n'est pas chargé.
|
|
124
|
+
* @param kernel - kernel courant.
|
|
125
|
+
* @returns l'état, ou le refus qui explique pourquoi il n'y en a pas.
|
|
126
|
+
*/
|
|
127
|
+
async function migrationStatusFor(wanted, config, kernel) {
|
|
128
|
+
const prepared = prepare(wanted, config, kernel);
|
|
129
|
+
if (!prepared.ok) return prepared;
|
|
130
|
+
try {
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
report: await composeReport(await (await buildMigrator(prepared.resolution, prepared.config, kernel)).status(), prepared.resolution, prepared.config, kernel)
|
|
134
|
+
};
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
failure: failure(prepared.resolution.connector, e)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Ce qui S'APPLIQUERAIT, avec son SQL — lecture seule.
|
|
144
|
+
*
|
|
145
|
+
* Sert la confirmation d'un geste d'application : une modification de schéma
|
|
146
|
+
* ne se confirme pas sur une promesse, elle se confirme sur les instructions
|
|
147
|
+
* qui vont être exécutées.
|
|
148
|
+
*
|
|
149
|
+
* @param wanted - connecteur demandé.
|
|
150
|
+
* @param config - configuration validée du module, `null` s'il n'est pas chargé.
|
|
151
|
+
* @param kernel - kernel courant.
|
|
152
|
+
* @returns le plan, ou le refus.
|
|
153
|
+
*/
|
|
154
|
+
async function migrationPlanFor(wanted, config, kernel) {
|
|
155
|
+
const prepared = prepare(wanted, config, kernel);
|
|
156
|
+
if (!prepared.ok) return prepared;
|
|
157
|
+
try {
|
|
158
|
+
const plan = await (await buildMigrator(prepared.resolution, prepared.config, kernel)).status();
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
plan: {
|
|
162
|
+
formatVersion: 1,
|
|
163
|
+
connector: plan.connector,
|
|
164
|
+
pending: plan.pending.map((f) => ({
|
|
165
|
+
source: f.source,
|
|
166
|
+
tag: f.tag,
|
|
167
|
+
statements: [...f.statements]
|
|
168
|
+
}))
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
failure: failure(prepared.resolution.connector, e)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Applique les migrations en attente — **DÉVELOPPEMENT seulement**.
|
|
180
|
+
*
|
|
181
|
+
* 🔴 Le refus hors développement n'est pas une précaution d'interface, c'est la
|
|
182
|
+
* doctrine : en production, les migrations s'appliquent dans un travail
|
|
183
|
+
* d'orchestrateur qui se termine AVANT que le premier nouvel exemplaire ne
|
|
184
|
+
* démarre. Les appliquer au clic de quelqu'un qui regarde une console pendant
|
|
185
|
+
* que le trafic passe, c'est modifier un schéma sous les pieds des exemplaires
|
|
186
|
+
* en service. La garde vit ICI, dans le produit — jamais dans l'écran, qui ne
|
|
187
|
+
* protège que celui qui le regarde.
|
|
188
|
+
*
|
|
189
|
+
* @param wanted - connecteur demandé.
|
|
190
|
+
* @param config - configuration validée du module, `null` s'il n'est pas chargé.
|
|
191
|
+
* @param kernel - kernel courant.
|
|
192
|
+
* @returns ce qui a été appliqué, ou le refus.
|
|
193
|
+
*/
|
|
194
|
+
async function applyMigrationsFor(wanted, config, kernel) {
|
|
195
|
+
const env = readMigrationEnv(kernel);
|
|
196
|
+
if (!resetAllowed(env)) return {
|
|
197
|
+
ok: false,
|
|
198
|
+
failure: failureFrom(wanted, {
|
|
199
|
+
code: "NF_MIGRATE_NOT_DEVELOPMENT",
|
|
200
|
+
summary: "Appliquer les migrations depuis la console d'administration est réservé au développement. Rien n'a été appliqué.",
|
|
201
|
+
meaning: "En production, les migrations s'appliquent dans un travail dédié qui se termine AVANT que le premier nouvel exemplaire ne démarre — le fichier `deploy/migrate-job.yaml` d'une application générée en est la recette. Les appliquer depuis un serveur qui sert le trafic reviendrait à changer le schéma sous les pieds des exemplaires en service.",
|
|
202
|
+
nextActions: [action(`nodefony orm:migrate --connector ${wanted}`), action(`nodefony orm:migrate:status --connector ${wanted}`)],
|
|
203
|
+
exitCode: 1
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
const prepared = prepare(wanted, config, kernel);
|
|
207
|
+
if (!prepared.ok) return prepared;
|
|
208
|
+
try {
|
|
209
|
+
const run = await (await buildMigrator(prepared.resolution, prepared.config, kernel)).migrate();
|
|
210
|
+
return {
|
|
211
|
+
ok: true,
|
|
212
|
+
run: {
|
|
213
|
+
formatVersion: 1,
|
|
214
|
+
connector: prepared.resolution.connector,
|
|
215
|
+
runId: run.runId,
|
|
216
|
+
applied: run.applied.map((a) => ({
|
|
217
|
+
source: a.source,
|
|
218
|
+
tag: a.tag,
|
|
219
|
+
executionMs: a.executionMs
|
|
220
|
+
}))
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
} catch (e) {
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
failure: failure(prepared.resolution.connector, e)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
export { applyMigrationsFor, composeReport, failureFrom, migrationPlanFor, migrationStatusFor };
|