@nodefony/drizzle 10.0.0-alpha.7 → 10.0.0-alpha.9
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/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +11 -7
- package/dist/nodefony/command/migrateShared.js +17 -1
- package/dist/nodefony/command/orm-generate.js +81 -13
- package/dist/nodefony/command/orm-migrate-baseline.js +5 -2
- package/dist/nodefony/command/orm-reset.js +1 -0
- package/dist/nodefony/config/defineModuleConfig.js +21 -5
- package/dist/nodefony/entity/userTable.js +3 -0
- package/dist/nodefony/registerStores.js +2 -26
- package/dist/nodefony/service/DrizzleService.js +19 -7
- package/dist/nodefony/src/DrizzleAuditStore.js +13 -1
- package/dist/nodefony/src/DrizzleIdempotencyStore.js +14 -2
- package/dist/nodefony/src/DrizzleTokenStore.js +13 -1
- package/dist/nodefony/src/DrizzleTotpSecretStore.js +13 -1
- package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +13 -1
- package/dist/nodefony/src/DrizzleWebhookStore.js +13 -1
- package/dist/nodefony/src/SessionStorage.js +8 -0
- package/dist/nodefony/src/frameworkConnector.js +29 -0
- package/dist/nodefony/src/memoryDatabase.js +13 -0
- package/dist/nodefony/src/migrator/DrizzleMigrator.js +17 -1
- package/dist/nodefony/src/migrator/appSchema.js +44 -1
- package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +1 -0
- package/dist/nodefony/src/migrator/explain.js +42 -1
- package/dist/nodefony/src/migrator/kit.js +23 -2
- package/dist/nodefony/src/migrator/paths.js +55 -1
- package/dist/nodefony/src/migrator/refusals.js +50 -1
- package/dist/nodefony/src/migrator/resolve.js +126 -6
- package/dist/nodefony/src/orm-core/DrizzleOrm.js +123 -22
- package/dist/nodefony/src/orm-core/ddlPlan.js +95 -0
- package/dist/nodefony/src/orm-core/index.js +2 -1
- package/dist/nodefony/src/orm-core/seedRow.js +65 -0
- package/dist/nodefony/src/safeTarget.js +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/nodefony/command/migrateShared.d.ts +10 -0
- package/dist/types/nodefony/command/orm-generate.d.ts +12 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +5 -1
- package/dist/types/nodefony/entity/userTable.d.ts +46 -3
- package/dist/types/nodefony/registerStores.d.ts +4 -3
- package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +6 -0
- package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +8 -1
- package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +6 -0
- package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +6 -0
- package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +6 -0
- package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +6 -0
- package/dist/types/nodefony/src/SessionStorage.d.ts +6 -0
- package/dist/types/nodefony/src/frameworkConnector.d.ts +24 -0
- package/dist/types/nodefony/src/memoryDatabase.d.ts +10 -0
- package/dist/types/nodefony/src/migrator/appSchema.d.ts +25 -0
- package/dist/types/nodefony/src/migrator/explain.d.ts +19 -0
- package/dist/types/nodefony/src/migrator/kit.d.ts +15 -0
- package/dist/types/nodefony/src/migrator/paths.d.ts +35 -0
- package/dist/types/nodefony/src/migrator/refusals.d.ts +36 -1
- package/dist/types/nodefony/src/migrator/resolve.d.ts +97 -1
- package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +11 -0
- package/dist/types/nodefony/src/orm-core/ddlPlan.d.ts +96 -0
- package/dist/types/nodefony/src/orm-core/index.d.ts +1 -0
- package/dist/types/nodefony/src/orm-core/seedRow.d.ts +23 -0
- package/docs/index.md +28 -25
- package/docs/migrations.md +69 -4
- package/package.json +19 -20
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { MIGRATE_URL_ENV } from "./types.js";
|
|
2
2
|
import { resolveConnectorTarget } from "../connectorTarget.js";
|
|
3
|
-
import {
|
|
3
|
+
import { MEMORY_DATABASE } from "../memoryDatabase.js";
|
|
4
|
+
import { connectorMigrationsDir, defaultMigrationSources } from "./paths.js";
|
|
4
5
|
import { DrizzleMigrator } from "./DrizzleMigrator.js";
|
|
5
6
|
import { parseDatabaseUrl, sqliteFilenameFromUrl } from "nodefony";
|
|
6
7
|
import { ormRegistry } from "@nodefony/orm-core";
|
|
8
|
+
import fs from "node:fs";
|
|
7
9
|
import path from "node:path";
|
|
8
10
|
//#region nodefony/src/migrator/resolve.ts
|
|
9
11
|
/**
|
|
@@ -52,6 +54,25 @@ function resetAllowed(env) {
|
|
|
52
54
|
return env.runtime === "development" && env.nodeEnv !== "test";
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
57
|
+
* `orm:generate --apply` est-il recevable dans cet environnement ?
|
|
58
|
+
*
|
|
59
|
+
* Écrire une migration et l'appliquer dans le même souffle suppose qu'on relit
|
|
60
|
+
* le SQL produit entre les deux — ce que personne ne fait sur un serveur.
|
|
61
|
+
* Ailleurs qu'en développement, la migration a été écrite, relue et versionnée
|
|
62
|
+
* en amont : `orm:migrate` l'applique, et lui seul. Aucun drapeau ne lève ce
|
|
63
|
+
* refus, pour la même raison que {@link resetAllowed} n'en a pas.
|
|
64
|
+
*
|
|
65
|
+
* Même forme que `resetAllowed`, et c'est voulu : les deux disent « geste de
|
|
66
|
+
* développement », et deux rédactions de la même condition finiraient par
|
|
67
|
+
* diverger sans que rien ne le signale.
|
|
68
|
+
*
|
|
69
|
+
* @param env - environnement constaté.
|
|
70
|
+
* @returns `true` si l'enchaînement est recevable.
|
|
71
|
+
*/
|
|
72
|
+
function generateApplyAllowed(env) {
|
|
73
|
+
return env.runtime === "development" && env.nodeEnv !== "test";
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
55
76
|
* Le mode de schéma qui s'applique à un connecteur.
|
|
56
77
|
*
|
|
57
78
|
* Règle des défauts, et son pourquoi : **appliquer des migrations au démarrage
|
|
@@ -63,13 +84,89 @@ function resetAllowed(env) {
|
|
|
63
84
|
* - tout le reste → `none` : personne ne touche au schéma au démarrage, un
|
|
64
85
|
* travail externe applique les migrations avant que le trafic n'arrive.
|
|
65
86
|
*
|
|
87
|
+
* 🔴 **Une application qui VERSIONNE des migrations bascule en `migrate`, même
|
|
88
|
+
* en développement.** Deux fabricants du même schéma ne s'accordent pas, et
|
|
89
|
+
* rien ne le signale tant qu'on ne les fait pas se rencontrer : le DDL dérivé
|
|
90
|
+
* pose une contrainte d'unicité INLINE et anonyme (`content text NOT NULL
|
|
91
|
+
* UNIQUE`), là où le générateur de migrations raisonne sur un index NOMMÉ
|
|
92
|
+
* (`messages_content_unique`). La migration suivante émet alors un
|
|
93
|
+
* `DROP INDEX` d'un index qui n'a jamais existé, échoue sur `no such index`,
|
|
94
|
+
* inscrit son marqueur — et la réparation ne peut rien y faire, puisque le
|
|
95
|
+
* fichier est inapplicable. Vécu sur un agent tiers : trente-cinq minutes de
|
|
96
|
+
* `repair`/`baseline`/`reset`, deux bases détruites, la boucle brisée à la main.
|
|
97
|
+
* La condition est délibérément « l'application EN A », pas « elle en a en
|
|
98
|
+
* attente » : dès la première migration écrite, le schéma leur appartient.
|
|
99
|
+
* Elle est bornée au développement RÉEL, jamais à `NODE_ENV=test` : une suite
|
|
100
|
+
* lance un exemplaire par worker, et appliquer des migrations depuis plusieurs
|
|
101
|
+
* processus au démarrage est précisément ce que la règle ci-dessus refuse.
|
|
102
|
+
* Un `ddl` écrit dans la configuration gagne toujours — c'est un choix, pas une
|
|
103
|
+
* déduction.
|
|
104
|
+
*
|
|
66
105
|
* @param explicit - valeur écrite dans la configuration du connecteur, si elle l'est.
|
|
67
106
|
* @param env - environnement constaté.
|
|
107
|
+
* 🔴 **Une base VOLATILE (`:memory:`) dérive toujours son schéma**, en
|
|
108
|
+
* production comprise. Rien d'autre ne peut la peupler : elle repart vide à
|
|
109
|
+
* chaque démarrage, et une migration appliquée ne survit pas au processus. En
|
|
110
|
+
* `none`, elle restait vide, le verdict de schéma tombait à `divergent` et la
|
|
111
|
+
* sonde de disponibilité retenait la mise en service (vécu en CI : le connecteur
|
|
112
|
+
* d'un module de banc, ouvert en production sous dérogation).
|
|
113
|
+
*
|
|
114
|
+
* @param appHasMigrations - l'application versionne au moins une migration.
|
|
115
|
+
* @param volatile - la base est en mémoire (`:memory:`).
|
|
68
116
|
* @returns le mode effectif.
|
|
69
117
|
*/
|
|
70
|
-
function resolveDdlMode(explicit, env) {
|
|
118
|
+
function resolveDdlMode(explicit, env, appHasMigrations = false, volatile = false) {
|
|
71
119
|
if (explicit) return explicit;
|
|
72
|
-
|
|
120
|
+
if (volatile) return "auto";
|
|
121
|
+
if (env.runtime === "development" || env.nodeEnv === "test") return appHasMigrations && env.nodeEnv !== "test" ? "migrate" : "auto";
|
|
122
|
+
return "none";
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* L'application versionne-t-elle au moins une migration ?
|
|
126
|
+
*
|
|
127
|
+
* Lecture SYNCHRONE et bornée à un `readdirSync` par connecteur au démarrage —
|
|
128
|
+
* hors de tout chemin de requête. Elle doit l'être : le mode de schéma se
|
|
129
|
+
* décide avant la connexion, et le rendre asynchrone propagerait un `await`
|
|
130
|
+
* dans toute la chaîne de résolution pour une réponse qui ne change jamais
|
|
131
|
+
* pendant la vie du processus.
|
|
132
|
+
*
|
|
133
|
+
* Un dossier absent, illisible ou vide répond `false` — l'application n'a alors
|
|
134
|
+
* rien à appliquer, et `auto` reste le bon défaut de démarrage rapide.
|
|
135
|
+
*
|
|
136
|
+
* @param dir - dossier de migrations de l'application ({@link appMigrationsDir}).
|
|
137
|
+
* @returns `true` si au moins un fichier `.sql` y est versionné, à un niveau
|
|
138
|
+
* ou sous un sous-dossier de dialecte (`migrations/postgres/0001_x.sql`).
|
|
139
|
+
*/
|
|
140
|
+
function appVersionsMigrations(dir) {
|
|
141
|
+
if (!dir) return false;
|
|
142
|
+
try {
|
|
143
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
144
|
+
if (entry.isFile() && entry.name.endsWith(".sql")) return true;
|
|
145
|
+
if (entry.isDirectory() && entry.name !== "meta") {
|
|
146
|
+
if (fs.readdirSync(path.join(dir, entry.name)).some((name) => name.endsWith(".sql"))) return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Des migrations versionnées CONCERNENT-elles ce connecteur ?
|
|
156
|
+
*
|
|
157
|
+
* C'est la question que pose la bascule `auto` → `migrate`, et elle n'est pas
|
|
158
|
+
* « l'application versionne-t-elle des migrations ? » : celles de la racine
|
|
159
|
+
* décrivent la base du framework. Posée telle quelle, elle faisait basculer en
|
|
160
|
+
* `migrate` un connecteur secondaire qui n'en recevra jamais une — sa base
|
|
161
|
+
* restait alors sans schéma, ni dérivé ni migré. On regarde donc le dossier
|
|
162
|
+
* DU connecteur ({@link connectorMigrationsDir}).
|
|
163
|
+
*
|
|
164
|
+
* @param connector - nom du connecteur.
|
|
165
|
+
* @param dir - dossier de migrations de l'application ({@link appMigrationsDir}).
|
|
166
|
+
* @returns `true` si le dossier propre au connecteur porte au moins un `.sql`.
|
|
167
|
+
*/
|
|
168
|
+
function connectorVersionsMigrations(connector, dir) {
|
|
169
|
+
return appVersionsMigrations(connectorMigrationsDir(dir, connector));
|
|
73
170
|
}
|
|
74
171
|
/**
|
|
75
172
|
* La conduite de la sonde de disponibilité face à un schéma en retard.
|
|
@@ -88,6 +185,26 @@ function resolveCheckMode(explicit, env) {
|
|
|
88
185
|
return env.runtime === "production" ? "fail" : "warn";
|
|
89
186
|
}
|
|
90
187
|
/**
|
|
188
|
+
* En schéma `auto`, faut-il ÉNONCER au démarrage les migrations enregistrées
|
|
189
|
+
* que personne n'a appliquées ?
|
|
190
|
+
*
|
|
191
|
+
* Oui en développement, sauf conduite `off` — c'est ce conseil qui évite de
|
|
192
|
+
* conclure à une base incohérente devant une colonne inconnue. Jamais sur une
|
|
193
|
+
* base `:memory:` : elle repart VIDE à chaque démarrage, son historique aussi,
|
|
194
|
+
* si bien que tout y paraît « à appliquer » à chaque fois — un avertissement
|
|
195
|
+
* permanent qui n'apprend rien (vécu : le connecteur de banc `mediasoup`,
|
|
196
|
+
* une fois déclaré, réclamait à chaque démarrage les migrations du framework
|
|
197
|
+
* et de l'application, qui ne le concernent pas).
|
|
198
|
+
*
|
|
199
|
+
* @param check - conduite résolue ({@link resolveCheckMode}).
|
|
200
|
+
* @param env - environnement constaté.
|
|
201
|
+
* @param filename - fichier sqlite du connecteur, s'il en a un.
|
|
202
|
+
* @returns `true` si le conseil doit être rendu.
|
|
203
|
+
*/
|
|
204
|
+
function adviseMigrations(check, env, filename) {
|
|
205
|
+
return check !== "off" && env.runtime === "development" && filename !== ":memory:";
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
91
208
|
* Décrit ce qui porte un connecteur, pour le nommer dans un refus.
|
|
92
209
|
*
|
|
93
210
|
* Un message qui dit seulement « ce connecteur ne porte pas de migrations »
|
|
@@ -217,7 +334,7 @@ function resolveConnector(connector, config, env, kernel, options = {}) {
|
|
|
217
334
|
url: fromMigrateUrl && dialect !== "sqlite" ? parsedUrl.url : base.url
|
|
218
335
|
},
|
|
219
336
|
fromMigrateUrl,
|
|
220
|
-
ddl: resolveDdlMode(declared.ddl, env)
|
|
337
|
+
ddl: resolveDdlMode(declared.ddl, env, connectorVersionsMigrations(connector, appMigrationsDir(kernel, config.migrations.dir)), declared.filename === MEMORY_DATABASE)
|
|
221
338
|
};
|
|
222
339
|
}
|
|
223
340
|
/**
|
|
@@ -265,7 +382,10 @@ function appMigrationsDir(kernel, dir) {
|
|
|
265
382
|
*/
|
|
266
383
|
async function buildMigrator(resolution, config, kernel) {
|
|
267
384
|
const appDir = appMigrationsDir(kernel, config.migrations.dir);
|
|
268
|
-
const sources = await defaultMigrationSources(appDir, {
|
|
385
|
+
const sources = await defaultMigrationSources(appDir, {
|
|
386
|
+
framework: config.frameworkEntities !== false,
|
|
387
|
+
connector: resolution.connector
|
|
388
|
+
});
|
|
269
389
|
return new DrizzleMigrator({
|
|
270
390
|
connector: resolution.connector,
|
|
271
391
|
...resolution.target,
|
|
@@ -278,4 +398,4 @@ function resolveDivergenceMode(config) {
|
|
|
278
398
|
return config.migrations.divergence;
|
|
279
399
|
}
|
|
280
400
|
//#endregion
|
|
281
|
-
export { MIGRATE_URL_ENV, appMigrationsDir, buildMigrator, knownConnectors, readMigrationEnv, resetAllowed, resolveCheckMode, resolveConnector, resolveDdlMode, resolveDivergenceMode };
|
|
401
|
+
export { MEMORY_DATABASE, MIGRATE_URL_ENV, adviseMigrations, appMigrationsDir, appVersionsMigrations, buildMigrator, connectorVersionsMigrations, generateApplyAllowed, knownConnectors, readMigrationEnv, resetAllowed, resolveCheckMode, resolveConnector, resolveDdlMode, resolveDivergenceMode };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { schemaReader, toDollarParams } from "../migrator/catalog.js";
|
|
2
|
+
import "../memoryDatabase.js";
|
|
2
3
|
import { notConfigured } from "../migrator/refusals.js";
|
|
3
4
|
import { additiveSql, compareSchema, hasGap } from "../migrator/schemaDiff.js";
|
|
4
5
|
import { describeTargetSafely } from "../safeTarget.js";
|
|
5
6
|
import { failureFrom } from "../migrator/status.js";
|
|
6
7
|
import { DrizzleRepository } from "./DrizzleRepository.js";
|
|
7
8
|
import { DrizzleTransaction } from "./DrizzleTransaction.js";
|
|
9
|
+
import { explainOmittedForeignKeys, planTableCreation } from "./ddlPlan.js";
|
|
8
10
|
import { createRequire } from "node:module";
|
|
9
11
|
import { CONNECT_TIMEOUT_MS, Orm, entityRegistry, parseConnectionTarget, withConnectDeadline } from "@nodefony/orm-core";
|
|
10
12
|
import fs from "node:fs";
|
|
@@ -15,6 +17,32 @@ import { PgDialect, PgTable, getTableConfig as getTableConfig$1 } from "drizzle-
|
|
|
15
17
|
import { MySqlDialect, MySqlTable, getTableConfig as getTableConfig$2 } from "drizzle-orm/mysql-core";
|
|
16
18
|
//#region nodefony/src/orm-core/DrizzleOrm.ts
|
|
17
19
|
/**
|
|
20
|
+
* Traduit les clés étrangères d'une table Drizzle en noms SQL émettables.
|
|
21
|
+
*
|
|
22
|
+
* @param foreignKeys - contraintes lues sur la table (`getTableConfig`).
|
|
23
|
+
* @param nameOf - comment lire le nom SQL d'une table du dialecte courant.
|
|
24
|
+
* @param skip - tables visées dont la contrainte doit être ABANDONNÉE (cycle ou
|
|
25
|
+
* cible absente de cette base) ; l'appelant les a déjà journalisées.
|
|
26
|
+
* @returns les contraintes à écrire dans le `CREATE TABLE`.
|
|
27
|
+
*/
|
|
28
|
+
function resolveForeignKeys(foreignKeys, nameOf, skip) {
|
|
29
|
+
const resolved = [];
|
|
30
|
+
for (const fk of foreignKeys) {
|
|
31
|
+
const reference = fk.reference();
|
|
32
|
+
const foreignTable = nameOf(reference.foreignTable);
|
|
33
|
+
if (skip?.has(foreignTable)) continue;
|
|
34
|
+
resolved.push({
|
|
35
|
+
name: fk.getName(),
|
|
36
|
+
columns: reference.columns.map((column) => column.name),
|
|
37
|
+
foreignTable,
|
|
38
|
+
foreignColumns: reference.foreignColumns.map((column) => column.name),
|
|
39
|
+
onDelete: fk.onDelete,
|
|
40
|
+
onUpdate: fk.onUpdate
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
18
46
|
* Rend le prédicat d'une contrainte `CHECK` en SQL littéral, avec la grammaire
|
|
19
47
|
* du dialecte demandé.
|
|
20
48
|
*
|
|
@@ -155,7 +183,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
155
183
|
* @param options - options de connexion (`dialect`, `filename` sqlite, `url` pg).
|
|
156
184
|
*/
|
|
157
185
|
constructor(name, options = {}) {
|
|
158
|
-
super(name);
|
|
186
|
+
super(name, options.container);
|
|
159
187
|
this.#dialect = options.dialect ?? "sqlite";
|
|
160
188
|
this.#deriveSchema = options.deriveSchema !== false;
|
|
161
189
|
this.#filename = options.filename ?? ":memory:";
|
|
@@ -245,7 +273,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
245
273
|
* `"…"` (SQL standard, SQLite/PG) vs backtick MySQL (qui ne lit `"…"` qu'en
|
|
246
274
|
* mode ANSI_QUOTES, jamais garanti). La prod reste pilotée par drizzle-kit.
|
|
247
275
|
*/
|
|
248
|
-
#buildCreateTable(name, columns, quote = "\"", checks = [], dialect = "sqlite") {
|
|
276
|
+
#buildCreateTable(name, columns, quote = "\"", checks = [], dialect = "sqlite", foreignKeys = []) {
|
|
249
277
|
const defs = columns.map((col) => {
|
|
250
278
|
const parts = [`${quote}${col.name}${quote}`, col.getSQLType()];
|
|
251
279
|
if (col.primary) parts.push("PRIMARY KEY");
|
|
@@ -258,6 +286,12 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
258
286
|
if (predicate === null) continue;
|
|
259
287
|
defs.push(`CONSTRAINT ${quote}${check.name}${quote} CHECK (${predicate})`);
|
|
260
288
|
}
|
|
289
|
+
for (const fk of foreignKeys) {
|
|
290
|
+
const source = fk.columns.map((c) => `${quote}${c}${quote}`).join(", ");
|
|
291
|
+
const target = fk.foreignColumns.map((c) => `${quote}${c}${quote}`).join(", ");
|
|
292
|
+
const actions = [fk.onDelete ? ` ON DELETE ${fk.onDelete.toUpperCase()}` : "", fk.onUpdate ? ` ON UPDATE ${fk.onUpdate.toUpperCase()}` : ""].join("");
|
|
293
|
+
defs.push(`CONSTRAINT ${quote}${fk.name}${quote} FOREIGN KEY (${source}) REFERENCES ${quote}${fk.foreignTable}${quote} (${target})${actions}`);
|
|
294
|
+
}
|
|
261
295
|
return `CREATE TABLE IF NOT EXISTS ${quote}${name}${quote} (${defs.join(", ")})`;
|
|
262
296
|
}
|
|
263
297
|
/**
|
|
@@ -273,11 +307,11 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
273
307
|
* pas. Émis à part, il arrive AUSSI sur une base de développement déjà créée —
|
|
274
308
|
* ce qu'aucune clause de table ne permettrait.
|
|
275
309
|
*
|
|
276
|
-
* Les clés étrangères, elles, ne
|
|
277
|
-
* DANS le `CREATE TABLE
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
310
|
+
* Les clés étrangères, elles, ne peuvent PAS être émises à part : elles se
|
|
311
|
+
* déclarent DANS le `CREATE TABLE` (cf `#buildCreateTable`), donc elles
|
|
312
|
+
* n'atteignent jamais une base déjà créée — c'est le prix assumé, le même que
|
|
313
|
+
* pour les `CHECK`. Et elles imposent de créer les tables dans l'ordre de
|
|
314
|
+
* leurs dépendances : cf `#planCreation`, qui porte aussi le cas du cycle.
|
|
281
315
|
*
|
|
282
316
|
* @param table - nom de la table portant les index.
|
|
283
317
|
* @param indexes - index déclarés (`getTableConfig(...).indexes`).
|
|
@@ -295,10 +329,43 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
295
329
|
}
|
|
296
330
|
return statements;
|
|
297
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Ordonne les tables pour que chaque cible naisse avant qui la désigne, et dit
|
|
334
|
+
* ce qu'il a fallu abandonner.
|
|
335
|
+
*
|
|
336
|
+
* Le tri lui-même vit à part ({@link planTableCreation}, module pur) ; cette
|
|
337
|
+
* méthode ne fait que lire les tables du dialecte courant et JOURNALISER.
|
|
338
|
+
* L'avertissement compte autant que le tri : une contrainte d'intégrité qui
|
|
339
|
+
* n'est pas posée est une garantie qu'on croit avoir.
|
|
340
|
+
*
|
|
341
|
+
* @param entities - les entités de ce connecteur.
|
|
342
|
+
* @param describe - comment lire le nom d'une table et ses cibles.
|
|
343
|
+
* @returns les entités dans l'ordre de création, et les cibles à ignorer par table.
|
|
344
|
+
*/
|
|
345
|
+
#planCreation(entities, describe) {
|
|
346
|
+
const described = entities.map((entity) => ({
|
|
347
|
+
entity,
|
|
348
|
+
...describe(entity)
|
|
349
|
+
}));
|
|
350
|
+
const plan = planTableCreation(described);
|
|
351
|
+
const skip = /* @__PURE__ */ new Map();
|
|
352
|
+
for (const entry of plan.omitted) {
|
|
353
|
+
const targets = skip.get(entry.table) ?? /* @__PURE__ */ new Set();
|
|
354
|
+
targets.add(entry.target);
|
|
355
|
+
skip.set(entry.table, targets);
|
|
356
|
+
}
|
|
357
|
+
const message = explainOmittedForeignKeys(plan.omitted);
|
|
358
|
+
if (message) this.log(message, "WARNING");
|
|
359
|
+
const rank = new Map(plan.order.map((name, index) => [name, index]));
|
|
360
|
+
return {
|
|
361
|
+
ordered: [...described].sort((a, b) => (rank.get(a.name) ?? 0) - (rank.get(b.name) ?? 0)).map((entry) => entry.entity),
|
|
362
|
+
skip
|
|
363
|
+
};
|
|
364
|
+
}
|
|
298
365
|
/** Dérive le `CREATE TABLE` SQLite depuis la table Drizzle (dev/test). */
|
|
299
|
-
#createTableSQL(table) {
|
|
300
|
-
const { name, columns, checks } = getTableConfig(table);
|
|
301
|
-
return this.#buildCreateTable(name, columns, "\"", checks, "sqlite");
|
|
366
|
+
#createTableSQL(table, skip) {
|
|
367
|
+
const { name, columns, checks, foreignKeys } = getTableConfig(table);
|
|
368
|
+
return this.#buildCreateTable(name, columns, "\"", checks, "sqlite", resolveForeignKeys(foreignKeys, (target) => getTableConfig(target).name, skip));
|
|
302
369
|
}
|
|
303
370
|
/** Dérive les `CREATE INDEX` SQLite depuis la table Drizzle (dev/test). */
|
|
304
371
|
#createIndexesSQL(table) {
|
|
@@ -306,9 +373,9 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
306
373
|
return this.#buildCreateIndexes(name, indexes);
|
|
307
374
|
}
|
|
308
375
|
/** Dérive le `CREATE TABLE` Postgres depuis la table Drizzle (dev/test). */
|
|
309
|
-
#createTablePgSQL(table) {
|
|
310
|
-
const { name, columns, checks } = getTableConfig$1(table);
|
|
311
|
-
return this.#buildCreateTable(name, columns, "\"", checks, "postgres");
|
|
376
|
+
#createTablePgSQL(table, skip) {
|
|
377
|
+
const { name, columns, checks, foreignKeys } = getTableConfig$1(table);
|
|
378
|
+
return this.#buildCreateTable(name, columns, "\"", checks, "postgres", resolveForeignKeys(foreignKeys, (target) => getTableConfig$1(target).name, skip));
|
|
312
379
|
}
|
|
313
380
|
/** Dérive les `CREATE INDEX` Postgres depuis la table Drizzle (dev/test). */
|
|
314
381
|
#createIndexesPgSQL(table) {
|
|
@@ -316,9 +383,9 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
316
383
|
return this.#buildCreateIndexes(name, indexes);
|
|
317
384
|
}
|
|
318
385
|
/** Dérive le `CREATE TABLE` MySQL depuis la table Drizzle (dev/test). */
|
|
319
|
-
#createTableMysqlSQL(table) {
|
|
320
|
-
const { name, columns, checks } = getTableConfig$2(table);
|
|
321
|
-
return this.#buildCreateTable(name, columns, "`", checks, "mysql");
|
|
386
|
+
#createTableMysqlSQL(table, skip) {
|
|
387
|
+
const { name, columns, checks, foreignKeys } = getTableConfig$2(table);
|
|
388
|
+
return this.#buildCreateTable(name, columns, "`", checks, "mysql", resolveForeignKeys(foreignKeys, (target) => getTableConfig$2(target).name, skip));
|
|
322
389
|
}
|
|
323
390
|
/**
|
|
324
391
|
* Dérive les `CREATE INDEX` MySQL depuis la table Drizzle (dev/test).
|
|
@@ -411,6 +478,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
411
478
|
client.pragma("journal_mode = WAL");
|
|
412
479
|
client.pragma("synchronous = NORMAL");
|
|
413
480
|
}
|
|
481
|
+
client.pragma("foreign_keys = ON");
|
|
414
482
|
this.#client = client;
|
|
415
483
|
const db = sqliteDrizzle(client);
|
|
416
484
|
this.#db = db;
|
|
@@ -440,8 +508,19 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
440
508
|
const table = entity.schema;
|
|
441
509
|
this.#tables[entity.name] = table;
|
|
442
510
|
entity.model = table;
|
|
443
|
-
|
|
444
|
-
|
|
511
|
+
}
|
|
512
|
+
if (!this.#deriveSchema) return;
|
|
513
|
+
const plan = this.#planCreation(entities, (entity) => {
|
|
514
|
+
const config = getTableConfig(entity.schema);
|
|
515
|
+
return {
|
|
516
|
+
name: config.name,
|
|
517
|
+
references: config.foreignKeys.map((fk) => getTableConfig(fk.reference().foreignTable).name)
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
for (const entity of plan.ordered) {
|
|
521
|
+
const table = entity.schema;
|
|
522
|
+
const name = getTableConfig(table).name;
|
|
523
|
+
client.exec(this.#createTableSQL(table, plan.skip.get(name)));
|
|
445
524
|
}
|
|
446
525
|
}
|
|
447
526
|
/**
|
|
@@ -671,8 +750,19 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
671
750
|
const table = entity.schema;
|
|
672
751
|
this.#tables[entity.name] = table;
|
|
673
752
|
entity.model = table;
|
|
674
|
-
|
|
675
|
-
|
|
753
|
+
}
|
|
754
|
+
if (!this.#deriveSchema) return;
|
|
755
|
+
const plan = this.#planCreation(entities, (entity) => {
|
|
756
|
+
const config = getTableConfig$1(entity.schema);
|
|
757
|
+
return {
|
|
758
|
+
name: config.name,
|
|
759
|
+
references: config.foreignKeys.map((fk) => getTableConfig$1(fk.reference().foreignTable).name)
|
|
760
|
+
};
|
|
761
|
+
});
|
|
762
|
+
for (const entity of plan.ordered) {
|
|
763
|
+
const table = entity.schema;
|
|
764
|
+
const name = getTableConfig$1(table).name;
|
|
765
|
+
await pool.query(this.#createTablePgSQL(table, plan.skip.get(name)));
|
|
676
766
|
}
|
|
677
767
|
}
|
|
678
768
|
/**
|
|
@@ -849,8 +939,19 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
849
939
|
const table = entity.schema;
|
|
850
940
|
this.#tables[entity.name] = table;
|
|
851
941
|
entity.model = table;
|
|
852
|
-
|
|
853
|
-
|
|
942
|
+
}
|
|
943
|
+
if (!this.#deriveSchema) return;
|
|
944
|
+
const plan = this.#planCreation(entities, (entity) => {
|
|
945
|
+
const config = getTableConfig$2(entity.schema);
|
|
946
|
+
return {
|
|
947
|
+
name: config.name,
|
|
948
|
+
references: config.foreignKeys.map((fk) => getTableConfig$2(fk.reference().foreignTable).name)
|
|
949
|
+
};
|
|
950
|
+
});
|
|
951
|
+
for (const entity of plan.ordered) {
|
|
952
|
+
const table = entity.schema;
|
|
953
|
+
const name = getTableConfig$2(table).name;
|
|
954
|
+
await pool.query(this.#createTableMysqlSQL(table, plan.skip.get(name)));
|
|
854
955
|
}
|
|
855
956
|
}
|
|
856
957
|
async disconnect() {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region nodefony/src/orm-core/ddlPlan.ts
|
|
2
|
+
/**
|
|
3
|
+
* Trie les tables pour que chaque cible naisse avant qui la désigne.
|
|
4
|
+
*
|
|
5
|
+
* Tri topologique (Kahn) sur le graphe des dépendances, avec deux écarts
|
|
6
|
+
* assumés au manuel :
|
|
7
|
+
*
|
|
8
|
+
* - **l'auto-référence ne compte pas** (`parent:ref:Category` dans `Category`) :
|
|
9
|
+
* les trois moteurs acceptent une contrainte qui vise la table en cours de
|
|
10
|
+
* création, dans le même `CREATE TABLE` ;
|
|
11
|
+
* - **un cycle ne fait pas échouer le démarrage**. Il n'y a aucun ordre correct,
|
|
12
|
+
* mais refuser de démarrer pour ça serait hors de proportion : on crée les
|
|
13
|
+
* tables du cycle dans un ordre stable, et on OMET les contraintes qui
|
|
14
|
+
* regardent en arrière. Elles sont rendues à l'appelant pour qu'il les dise —
|
|
15
|
+
* une garantie qui disparaît sans un mot est pire que son absence.
|
|
16
|
+
*
|
|
17
|
+
* À ordre de sortie stable : les tables prêtes sont prises par ordre
|
|
18
|
+
* alphabétique, jamais dans l'ordre d'arrivée. Deux démarrages de la même
|
|
19
|
+
* application produisent donc le même DDL, ce qui rend un journal comparable.
|
|
20
|
+
*
|
|
21
|
+
* @param tables - les tables du connecteur et leurs cibles.
|
|
22
|
+
* @returns l'ordre de création et les contraintes abandonnées.
|
|
23
|
+
*/
|
|
24
|
+
function planTableCreation(tables) {
|
|
25
|
+
const known = new Set(tables.map((table) => table.name));
|
|
26
|
+
const omitted = [];
|
|
27
|
+
/** Dépendances RÉELLES : cible connue, et différente de la table elle-même. */
|
|
28
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const table of tables) {
|
|
30
|
+
const deps = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const target of table.references) {
|
|
32
|
+
if (target === table.name) continue;
|
|
33
|
+
if (!known.has(target)) {
|
|
34
|
+
omitted.push({
|
|
35
|
+
table: table.name,
|
|
36
|
+
target,
|
|
37
|
+
reason: "absent"
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
deps.add(target);
|
|
42
|
+
}
|
|
43
|
+
dependencies.set(table.name, deps);
|
|
44
|
+
}
|
|
45
|
+
const order = [];
|
|
46
|
+
const placed = /* @__PURE__ */ new Set();
|
|
47
|
+
const remaining = new Set(known);
|
|
48
|
+
for (;;) {
|
|
49
|
+
const ready = [...remaining].filter((name) => {
|
|
50
|
+
const deps = dependencies.get(name);
|
|
51
|
+
return !deps || [...deps].every((dep) => placed.has(dep));
|
|
52
|
+
}).sort();
|
|
53
|
+
if (ready.length === 0) break;
|
|
54
|
+
for (const name of ready) {
|
|
55
|
+
order.push(name);
|
|
56
|
+
placed.add(name);
|
|
57
|
+
remaining.delete(name);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const name of [...remaining].sort()) {
|
|
61
|
+
order.push(name);
|
|
62
|
+
placed.add(name);
|
|
63
|
+
}
|
|
64
|
+
const rank = new Map(order.map((name, index) => [name, index]));
|
|
65
|
+
for (const name of order) for (const target of dependencies.get(name) ?? []) if ((rank.get(target) ?? 0) > (rank.get(name) ?? 0)) omitted.push({
|
|
66
|
+
table: name,
|
|
67
|
+
target,
|
|
68
|
+
reason: "cycle"
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
order,
|
|
72
|
+
omitted
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Met en mots les contraintes abandonnées — le message que l'appelant journalise.
|
|
77
|
+
*
|
|
78
|
+
* Écrit pour être lu par celui qui découvre le problème dans ses journaux : il
|
|
79
|
+
* nomme les tables, la cause, et ce qu'il reste à faire. Un avertissement qui ne
|
|
80
|
+
* dit pas quoi faire se relit deux fois puis s'ignore.
|
|
81
|
+
*
|
|
82
|
+
* @param omitted - les contraintes non émises.
|
|
83
|
+
* @returns le texte à journaliser, ou `null` s'il n'y a rien à dire.
|
|
84
|
+
*/
|
|
85
|
+
function explainOmittedForeignKeys(omitted) {
|
|
86
|
+
if (omitted.length === 0) return null;
|
|
87
|
+
const cycles = omitted.filter((entry) => entry.reason === "cycle");
|
|
88
|
+
const absent = omitted.filter((entry) => entry.reason === "absent");
|
|
89
|
+
const parts = [];
|
|
90
|
+
if (cycles.length > 0) parts.push(`cycle entre entités — contrainte(s) NON posée(s) : ${cycles.map((c) => `${c.table} → ${c.target}`).join(", ")}. Aucun ordre de création ne satisfait les deux sens ; une migration (\`orm:generate\`) les ajoute après coup par \`ALTER TABLE\`.`);
|
|
91
|
+
if (absent.length > 0) parts.push(`cible hors de cette base — contrainte(s) NON posée(s) : ${absent.map((a) => `${a.table} → ${a.target}`).join(", ")}. L'entité visée est déclarée sur un autre connecteur : l'intégrité référentielle ne traverse pas deux bases.`);
|
|
92
|
+
return parts.join(" ");
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { explainOmittedForeignKeys, planTableCreation };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DrizzleRepository } from "./DrizzleRepository.js";
|
|
2
2
|
import { DrizzleTransaction } from "./DrizzleTransaction.js";
|
|
3
3
|
import { DrizzleOrm } from "./DrizzleOrm.js";
|
|
4
|
-
|
|
4
|
+
import { seedEntityRow } from "./seedRow.js";
|
|
5
|
+
export { DrizzleOrm, DrizzleRepository, DrizzleTransaction, seedEntityRow };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getTableColumns } from "drizzle-orm";
|
|
2
|
+
//#region nodefony/src/orm-core/seedRow.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compteur de semis — il rend chaque ligne semée DISTINCTE.
|
|
5
|
+
*
|
|
6
|
+
* Une entité cible porte souvent une colonne unique (un courriel, une
|
|
7
|
+
* référence). Semer deux fois la même valeur violerait sa contrainte, et le test
|
|
8
|
+
* échouerait sur son propre décor plutôt que sur ce qu'il éprouve.
|
|
9
|
+
*/
|
|
10
|
+
let seedCounter = 0;
|
|
11
|
+
/**
|
|
12
|
+
* Valeur plausible pour une colonne, dérivée de ce que Drizzle en dit.
|
|
13
|
+
*
|
|
14
|
+
* On lit `dataType`, la classification **générique** de Drizzle (commune aux
|
|
15
|
+
* trois dialectes), et non `columnType`, qui nomme l'implémentation (`SQLiteText`,
|
|
16
|
+
* `PgVarchar`) et obligerait à connaître chaque moteur.
|
|
17
|
+
*/
|
|
18
|
+
function sampleForColumn(column, n) {
|
|
19
|
+
if (column.enumValues?.length) return column.enumValues[0];
|
|
20
|
+
switch (column.dataType) {
|
|
21
|
+
case "number": return n;
|
|
22
|
+
case "bigint": return BigInt(n);
|
|
23
|
+
case "boolean": return true;
|
|
24
|
+
case "date": return /* @__PURE__ */ new Date();
|
|
25
|
+
case "json": return {};
|
|
26
|
+
case "buffer": return Buffer.from(`seed-${n}`);
|
|
27
|
+
default: return `seed-${n}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Insère une ligne minimale dans la table d'une entité, et rend son identifiant.
|
|
32
|
+
*
|
|
33
|
+
* Destiné aux **tests** : dès qu'une entité porte une relation (`author:ref:Author`),
|
|
34
|
+
* sa colonne est une vraie clé étrangère, et la base REFUSE un identifiant
|
|
35
|
+
* inventé. La ligne parente doit donc exister avant toute insertion — et c'est
|
|
36
|
+
* la seule façon d'éprouver la contrainte au lieu de la contourner (débrancher
|
|
37
|
+
* `foreign_keys` rendrait le test vert sans rien prouver).
|
|
38
|
+
*
|
|
39
|
+
* Ce qui est rempli : les colonnes **obligatoires sans valeur par défaut**, avec
|
|
40
|
+
* une valeur du type que Drizzle déclare. Le reste est laissé au schéma — une
|
|
41
|
+
* clé primaire auto-générée, un horodatage, un défaut applicatif se posent seuls.
|
|
42
|
+
*
|
|
43
|
+
* @param orm - la connexion sur laquelle semer.
|
|
44
|
+
* @param entity - l'entité cible ; son `schema` est la table Drizzle.
|
|
45
|
+
* @returns l'identifiant de la ligne créée, à poser dans la colonne qui la désigne.
|
|
46
|
+
* @throws Si la cible porte elle-même une clé étrangère obligatoire — le semis à
|
|
47
|
+
* un seul étage ne sait pas la satisfaire, et le message le dit plutôt que de
|
|
48
|
+
* laisser remonter un « FOREIGN KEY constraint failed » sans contexte.
|
|
49
|
+
*/
|
|
50
|
+
async function seedEntityRow(orm, entity) {
|
|
51
|
+
const n = ++seedCounter;
|
|
52
|
+
const columns = getTableColumns(entity.schema);
|
|
53
|
+
const row = {};
|
|
54
|
+
for (const [name, column] of Object.entries(columns)) {
|
|
55
|
+
if (!column.notNull || column.hasDefault || column.primary) continue;
|
|
56
|
+
row[name] = sampleForColumn(column, n);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return (await orm.getRepository(entity.name).create(row)).id;
|
|
60
|
+
} catch (cause) {
|
|
61
|
+
throw new Error(`seedEntityRow : impossible de semer une ligne de « ${entity.name} ». Si cette entité porte elle-même une relation obligatoire, sème d'abord sa cible. Cause : ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { seedEntityRow };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export { DrizzleMigrator, DEFAULT_LOCK_TIMEOUT_MS, openMigrationDriver, framewor
|
|
|
48
48
|
export { MIGRATION_FORMAT_VERSION, EXIT, buildReport, verdictOf, exitCodeOf, meaningOf, renderStatus, renderRefusal, styleFor, action, type IMigrationReport, type IMigrationSourceReport, type IReportContext, type MigrationVerdictName, type IStyle, } from "./nodefony/src/migrator/explain.js";
|
|
49
49
|
export { MIGRATE_URL_ENV, readMigrationEnv, resolveDdlMode, resolveCheckMode, resetAllowed, resolveConnector, resolveDivergenceMode, knownConnectors, appMigrationsDir, buildMigrator, type IMigrationEnv, type IConnectorResolution, } from "./nodefony/src/migrator/resolve.js";
|
|
50
50
|
export { DDL_MODES, MIGRATION_CHECK_MODES, DIVERGENCE_MODES, type DdlMode, type MigrationCheckMode, type DivergenceMode, } from "./nodefony/config/config.js";
|
|
51
|
-
export { DrizzleOrm, DrizzleRepository, DrizzleTransaction, } from "./nodefony/src/orm-core/index.js";
|
|
51
|
+
export { DrizzleOrm, DrizzleRepository, DrizzleTransaction, seedEntityRow, } from "./nodefony/src/orm-core/index.js";
|
|
52
52
|
export type { DrizzleOrmOptions, DrizzleDb, DrizzleResolvedRelation, } from "./nodefony/src/orm-core/index.js";
|
|
53
53
|
export { userTable, createUserTable, createUserEntity, registerUserEntity, } from "./nodefony/entity/userTable.js";
|
|
54
54
|
export type { UserRow } from "./nodefony/entity/userTable.js";
|
|
@@ -107,6 +107,16 @@ export declare abstract class OrmMigrateCommand extends Command {
|
|
|
107
107
|
}>;
|
|
108
108
|
config: IDrizzleConfig;
|
|
109
109
|
} | null;
|
|
110
|
+
/**
|
|
111
|
+
* Arrête une commande qui ÉCRIRAIT une migration pour un connecteur
|
|
112
|
+
* secondaire dont le nom est celui d'un dossier de dialecte — son dossier
|
|
113
|
+
* propre se confondrait avec celui des migrations du framework.
|
|
114
|
+
*
|
|
115
|
+
* @param connector - connecteur résolu.
|
|
116
|
+
* @param json - mode machine.
|
|
117
|
+
* @returns `true` si la commande est arrêtée.
|
|
118
|
+
*/
|
|
119
|
+
protected refuseReservedConnector(connector: string, json?: boolean): boolean;
|
|
110
120
|
/**
|
|
111
121
|
* Construit l'applicateur d'un connecteur résolu.
|
|
112
122
|
*
|
|
@@ -5,6 +5,7 @@ interface IGenerateOptions extends IMigrateSharedOptions {
|
|
|
5
5
|
name?: string;
|
|
6
6
|
custom?: boolean;
|
|
7
7
|
allowDestructive?: boolean;
|
|
8
|
+
apply?: boolean;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* `nodefony orm:generate` — écrit la migration qui fera passer la base au
|
|
@@ -39,6 +40,17 @@ interface IGenerateOptions extends IMigrateSharedOptions {
|
|
|
39
40
|
* nodefony orm:migrate # ou : le travail de déploiement
|
|
40
41
|
* ```
|
|
41
42
|
*
|
|
43
|
+
* @example En développement — écrire ET appliquer en un seul geste
|
|
44
|
+
* ```bash
|
|
45
|
+
* nodefony orm:generate --name ajout_du_titre --apply
|
|
46
|
+
* ```
|
|
47
|
+
* `--apply` n'est rien d'autre qu'un `orm:migrate` enchaîné : la commande
|
|
48
|
+
* DÉLÈGUE, elle ne rejoue pas l'application pour son compte. Toutes les gardes
|
|
49
|
+
* du verbe d'application valent donc à l'identique — refus du destructif sans
|
|
50
|
+
* `--allow-destructive`, verrou, historique. Refusé hors développement : un
|
|
51
|
+
* déploiement n'écrit pas ses migrations au moment de les appliquer, il
|
|
52
|
+
* applique celles que la revue de code a validées.
|
|
53
|
+
*
|
|
42
54
|
* @example Ce que le modèle déclaratif ne peut pas déduire
|
|
43
55
|
* ```bash
|
|
44
56
|
* nodefony orm:generate --custom --name vue_des_ventes
|
|
@@ -13,10 +13,14 @@ import type { IDrizzleConfig, IDrizzleConfigInput } from "../interfaces/IDrizzle
|
|
|
13
13
|
* au boot — cf audit config ORM 2026-06 §3.2.
|
|
14
14
|
*
|
|
15
15
|
* @param config - configuration brute (sections omises = défauts sûrs).
|
|
16
|
+
* @param declared - ce que l'APPLICATION a écrit elle-même. Au boot, `config`
|
|
17
|
+
* arrive déjà fusionné avec les défauts du module (`config.ts`) : on ne peut
|
|
18
|
+
* plus y distinguer un `default` demandé d'un `default` posé par défaut. Omis,
|
|
19
|
+
* `config` fait foi (appel direct, tests).
|
|
16
20
|
* @returns config validée, surchargée par l'env, et gelée.
|
|
17
21
|
* @throws ZodError si la config est invalide.
|
|
18
22
|
*/
|
|
19
|
-
export declare function defineDrizzleConfig(config?: IDrizzleConfigInput): IDrizzleConfig;
|
|
23
|
+
export declare function defineDrizzleConfig(config?: IDrizzleConfigInput, declared?: IDrizzleConfigInput): IDrizzleConfig;
|
|
20
24
|
/**
|
|
21
25
|
* JSON Schema introspectable de la config Drizzle — destiné au formulaire
|
|
22
26
|
* d'édition Studio (futur) et à la documentation générée.
|