@nodefony/drizzle 10.0.0-alpha.8 → 10.0.0-beta.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/README.md +19 -19
- 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 +9 -6
- package/dist/nodefony/command/migrateShared.js +17 -1
- package/dist/nodefony/command/orm-generate.js +14 -10
- 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 +23 -7
- package/dist/nodefony/entity/userTable.js +4 -1
- package/dist/nodefony/registerStores.js +4 -28
- package/dist/nodefony/service/DrizzleService.js +42 -16
- package/dist/nodefony/src/DrizzleAuditStore.js +14 -2
- 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/connectorTarget.js +1 -1
- package/dist/nodefony/src/frameworkConnector.js +29 -0
- package/dist/nodefony/src/memoryDatabase.js +13 -0
- package/dist/nodefony/src/migrator/DrizzleMigrator.js +3 -3
- package/dist/nodefony/src/migrator/adopt.js +1 -1
- package/dist/nodefony/src/migrator/appSchema.js +44 -1
- package/dist/nodefony/src/migrator/destructive.js +2 -3
- package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +1 -1
- package/dist/nodefony/src/migrator/drivers/postgresDriver.js +1 -1
- package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +1 -0
- package/dist/nodefony/src/migrator/explain.js +7 -10
- package/dist/nodefony/src/migrator/history.js +25 -2
- package/dist/nodefony/src/migrator/kit.js +41 -6
- package/dist/nodefony/src/migrator/paths.js +55 -1
- package/dist/nodefony/src/migrator/rebuildCopy.js +6 -6
- package/dist/nodefony/src/migrator/refusals.js +50 -1
- package/dist/nodefony/src/migrator/resolve.js +59 -9
- package/dist/nodefony/src/migrator/sources.js +13 -8
- package/dist/nodefony/src/orm-core/DrizzleOrm.js +51 -30
- package/dist/nodefony/src/orm-core/DrizzleRepository.js +38 -26
- package/dist/nodefony/src/queryKit.js +5 -5
- package/dist/nodefony/src/safeTarget.js +1 -0
- package/dist/types/nodefony/command/migrateShared.d.ts +10 -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/DrizzleUserRepository.d.ts +1 -1
- 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/connectorTarget.d.ts +1 -1
- 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/DrizzleMigrator.d.ts +4 -4
- package/dist/types/nodefony/src/migrator/appSchema.d.ts +25 -0
- package/dist/types/nodefony/src/migrator/destructive.d.ts +1 -1
- package/dist/types/nodefony/src/migrator/drivers/index.d.ts +2 -2
- package/dist/types/nodefony/src/migrator/explain.d.ts +9 -9
- package/dist/types/nodefony/src/migrator/kit.d.ts +15 -0
- package/dist/types/nodefony/src/migrator/name.d.ts +1 -1
- 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 +44 -1
- package/dist/types/nodefony/src/migrator/types.d.ts +2 -2
- package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +13 -2
- package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +1 -1
- package/dist/types/nodefony/src/orm-core/ddlPlan.d.ts +2 -2
- package/dist/types/nodefony/src/queryKit.d.ts +9 -9
- package/dist/types/nodefony/src/safeTarget.d.ts +2 -2
- package/docs/index.md +31 -28
- package/docs/migrations.md +39 -3
- package/package.json +19 -20
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { MIGRATE_URL_ENV } from "./types.js";
|
|
2
2
|
import { action } from "./explain.js";
|
|
3
|
+
import { FRAMEWORK_CONNECTOR } from "../frameworkConnector.js";
|
|
4
|
+
import { RESERVED_MIGRATION_DIRS } from "./paths.js";
|
|
3
5
|
import { knownConnectors } from "./resolve.js";
|
|
4
6
|
//#region nodefony/src/migrator/refusals.ts
|
|
5
7
|
/**
|
|
@@ -79,6 +81,30 @@ function generationNeedsAnswer(label, replay) {
|
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
84
|
+
* Un fichier lu par l'outil de génération porte un `await` de premier niveau.
|
|
85
|
+
*
|
|
86
|
+
* Nodefony est ESM, et l'application démarre très bien avec ce fichier. Mais
|
|
87
|
+
* l'outil qui écrit les migrations (`drizzle-kit`) relit les fichiers de schéma
|
|
88
|
+
* HORS de la chaîne de l'application, en les transpilant en CommonJS — où un
|
|
89
|
+
* `await` de premier niveau n'existe pas. Sans ce refus, l'utilisateur recevait
|
|
90
|
+
* une pile d'appels d'esbuild qui ne nomme ni l'outil ni le remède.
|
|
91
|
+
*
|
|
92
|
+
* @param label - ce qui était généré, tel qu'on le cite à l'utilisateur.
|
|
93
|
+
* @param file - fichier fautif, tel que l'outil l'a nommé.
|
|
94
|
+
* @param line - ligne de l'`await`.
|
|
95
|
+
* @param replay - la commande à relancer une fois le fichier corrigé.
|
|
96
|
+
* @returns le refus, prêt pour la ligne de commande comme pour l'écran.
|
|
97
|
+
*/
|
|
98
|
+
function generationTopLevelAwait(label, file, line, replay) {
|
|
99
|
+
return {
|
|
100
|
+
code: "NF_GENERATE_TOP_LEVEL_AWAIT",
|
|
101
|
+
summary: `${file}:${line} — un \`await\` de premier niveau empêche de générer ${label} ; rien n'a été écrit.`,
|
|
102
|
+
meaning: "L'application démarre avec ce fichier : elle est ESM. Mais l'outil qui écrit les migrations (drizzle-kit) relit les fichiers de schéma de son côté, en CommonJS, où un `await` de premier niveau n'existe pas. Un fichier d'entité DÉCLARE un schéma : ce qui se décide à l'exécution (choisir une entité selon l'infrastructure, importer un module à la demande) se place dans l'`index.ts` du module — `@entities([...])` accepte une liste calculée. La base n'est pas en cause : elle n'a pas été interrogée.",
|
|
103
|
+
nextActions: [action(replay)],
|
|
104
|
+
exitCode: 2
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
82
108
|
* L'outil de génération s'est arrêté, et ce qu'il a dit est remonté TEL QUEL.
|
|
83
109
|
*
|
|
84
110
|
* 🔴 Ne JAMAIS remplacer sa sortie par une hypothèse. Le fourre-tout des
|
|
@@ -176,6 +202,29 @@ function notConfigured(connector, driver) {
|
|
|
176
202
|
};
|
|
177
203
|
}
|
|
178
204
|
/**
|
|
205
|
+
* Écrire une migration pour un connecteur secondaire dont le NOM est celui d'un
|
|
206
|
+
* dossier de dialecte — refusé.
|
|
207
|
+
*
|
|
208
|
+
* Un connecteur secondaire range ses migrations sous `migrations/<connecteur>/`,
|
|
209
|
+
* à côté des dossiers de dialecte du connecteur du framework. Nommé `postgres`,
|
|
210
|
+
* son dossier SERAIT celui des migrations PostgreSQL du framework : il les
|
|
211
|
+
* lirait comme les siennes, et les siennes seraient appliquées à la base du
|
|
212
|
+
* framework. On le refuse en nommant la collision, plutôt que de le découvrir
|
|
213
|
+
* au premier déploiement.
|
|
214
|
+
*
|
|
215
|
+
* @param connector - connecteur demandé.
|
|
216
|
+
* @returns le refus, prêt pour la ligne de commande comme pour l'écran.
|
|
217
|
+
*/
|
|
218
|
+
function reservedConnectorName(connector) {
|
|
219
|
+
return {
|
|
220
|
+
code: "NF_MIGRATE_SECONDARY_CONNECTOR",
|
|
221
|
+
summary: `Le connecteur « ${connector} » porte le nom d'un dossier réservé (${RESERVED_MIGRATION_DIRS.join(", ")}) : ses migrations ne peuvent pas avoir de dossier propre. Rien n'a été écrit.`,
|
|
222
|
+
meaning: `Les migrations d'un connecteur secondaire vivent sous \`migrations/<connecteur>/<dialecte>\`, à côté des dossiers de dialecte du connecteur « ${FRAMEWORK_CONNECTOR} ». Le dossier \`migrations/${connector}\` est déjà l'un d'eux : les deux connecteurs y liraient les migrations de l'autre, et chacune serait appliquée à la mauvaise base. Renomme le connecteur dans \`connectors\` (par exemple « ${connector}_data ») : son nom ne sert qu'à le désigner.`,
|
|
223
|
+
nextActions: [action("nodefony inspect config --json")],
|
|
224
|
+
exitCode: 2
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
179
228
|
* Traduit une résolution qui n'est PAS `ready` en refus lisible.
|
|
180
229
|
*
|
|
181
230
|
* @param wanted - nom demandé par l'appelant.
|
|
@@ -222,4 +271,4 @@ function describeResolutionRefusal(wanted, resolution, config) {
|
|
|
222
271
|
};
|
|
223
272
|
}
|
|
224
273
|
//#endregion
|
|
225
|
-
export { MigrationToolError, describeResolutionRefusal, formatToolOutput, generationFailed, generationNeedsAnswer, generationToolMissing, introspectFailed, moduleAbsent, notConfigured };
|
|
274
|
+
export { MigrationToolError, describeResolutionRefusal, formatToolOutput, generationFailed, generationNeedsAnswer, generationToolMissing, generationTopLevelAwait, introspectFailed, moduleAbsent, notConfigured, reservedConnectorName };
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
@@ -103,11 +104,20 @@ function generateApplyAllowed(env) {
|
|
|
103
104
|
*
|
|
104
105
|
* @param explicit - valeur écrite dans la configuration du connecteur, si elle l'est.
|
|
105
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
|
+
*
|
|
106
114
|
* @param appHasMigrations - l'application versionne au moins une migration.
|
|
115
|
+
* @param volatile - la base est en mémoire (`:memory:`).
|
|
107
116
|
* @returns le mode effectif.
|
|
108
117
|
*/
|
|
109
|
-
function resolveDdlMode(explicit, env, appHasMigrations = false) {
|
|
118
|
+
function resolveDdlMode(explicit, env, appHasMigrations = false, volatile = false) {
|
|
110
119
|
if (explicit) return explicit;
|
|
120
|
+
if (volatile) return "auto";
|
|
111
121
|
if (env.runtime === "development" || env.nodeEnv === "test") return appHasMigrations && env.nodeEnv !== "test" ? "migrate" : "auto";
|
|
112
122
|
return "none";
|
|
113
123
|
}
|
|
@@ -142,6 +152,23 @@ function appVersionsMigrations(dir) {
|
|
|
142
152
|
return false;
|
|
143
153
|
}
|
|
144
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));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
145
172
|
* La conduite de la sonde de disponibilité face à un schéma en retard.
|
|
146
173
|
*
|
|
147
174
|
* En production, un exemplaire dont le schéma est en retard ne doit pas
|
|
@@ -158,6 +185,26 @@ function resolveCheckMode(explicit, env) {
|
|
|
158
185
|
return env.runtime === "production" ? "fail" : "warn";
|
|
159
186
|
}
|
|
160
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
|
+
/**
|
|
161
208
|
* Décrit ce qui porte un connecteur, pour le nommer dans un refus.
|
|
162
209
|
*
|
|
163
210
|
* Un message qui dit seulement « ce connecteur ne porte pas de migrations »
|
|
@@ -177,10 +224,10 @@ function describeOwner(name) {
|
|
|
177
224
|
driver: ""
|
|
178
225
|
};
|
|
179
226
|
}
|
|
180
|
-
const klass = orm
|
|
227
|
+
const klass = orm.constructor?.name;
|
|
181
228
|
let driver = "";
|
|
182
229
|
try {
|
|
183
|
-
driver = orm
|
|
230
|
+
driver = orm.describeConnection?.().driver ?? "";
|
|
184
231
|
} catch {
|
|
185
232
|
driver = "";
|
|
186
233
|
}
|
|
@@ -221,7 +268,7 @@ const SQL_DRIVERS = /* @__PURE__ */ new Set([
|
|
|
221
268
|
*/
|
|
222
269
|
function knownConnectors(config) {
|
|
223
270
|
const names = new Set(ormRegistry.list());
|
|
224
|
-
for (const name of Object.keys(config.connectors
|
|
271
|
+
for (const name of Object.keys(config.connectors)) names.add(name);
|
|
225
272
|
return [...names].sort();
|
|
226
273
|
}
|
|
227
274
|
/**
|
|
@@ -245,7 +292,7 @@ function knownConnectors(config) {
|
|
|
245
292
|
* @returns la réponse, discriminée par `kind`.
|
|
246
293
|
*/
|
|
247
294
|
function resolveConnector(connector, config, env, kernel, options = {}) {
|
|
248
|
-
const declared = config.connectors
|
|
295
|
+
const declared = config.connectors[connector];
|
|
249
296
|
if (!declared) {
|
|
250
297
|
if (ormRegistry.has(connector)) {
|
|
251
298
|
const owner = describeOwner(connector);
|
|
@@ -287,7 +334,7 @@ function resolveConnector(connector, config, env, kernel, options = {}) {
|
|
|
287
334
|
url: fromMigrateUrl && dialect !== "sqlite" ? parsedUrl.url : base.url
|
|
288
335
|
},
|
|
289
336
|
fromMigrateUrl,
|
|
290
|
-
ddl: resolveDdlMode(declared.ddl, env,
|
|
337
|
+
ddl: resolveDdlMode(declared.ddl, env, connectorVersionsMigrations(connector, appMigrationsDir(kernel, config.migrations.dir)), declared.filename === MEMORY_DATABASE)
|
|
291
338
|
};
|
|
292
339
|
}
|
|
293
340
|
/**
|
|
@@ -335,7 +382,10 @@ function appMigrationsDir(kernel, dir) {
|
|
|
335
382
|
*/
|
|
336
383
|
async function buildMigrator(resolution, config, kernel) {
|
|
337
384
|
const appDir = appMigrationsDir(kernel, config.migrations.dir);
|
|
338
|
-
const sources = await defaultMigrationSources(appDir, {
|
|
385
|
+
const sources = await defaultMigrationSources(appDir, {
|
|
386
|
+
framework: config.frameworkEntities !== false,
|
|
387
|
+
connector: resolution.connector
|
|
388
|
+
});
|
|
339
389
|
return new DrizzleMigrator({
|
|
340
390
|
connector: resolution.connector,
|
|
341
391
|
...resolution.target,
|
|
@@ -348,4 +398,4 @@ function resolveDivergenceMode(config) {
|
|
|
348
398
|
return config.migrations.divergence;
|
|
349
399
|
}
|
|
350
400
|
//#endregion
|
|
351
|
-
export { MIGRATE_URL_ENV, appMigrationsDir, appVersionsMigrations, buildMigrator, generateApplyAllowed, 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 };
|
|
@@ -71,7 +71,8 @@ async function readJournal(dir, source) {
|
|
|
71
71
|
}
|
|
72
72
|
let journal;
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
journal = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
75
76
|
} catch (e) {
|
|
76
77
|
throw new MigrationVerdictError({
|
|
77
78
|
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
@@ -91,21 +92,25 @@ async function readJournal(dir, source) {
|
|
|
91
92
|
facts: { file },
|
|
92
93
|
nextActions: []
|
|
93
94
|
}, `Le journal des migrations « ${file} » ne porte pas de liste « entries » : ce n'est pas un journal que ce format sait lire.`);
|
|
94
|
-
|
|
95
|
+
const version = String(journal.version);
|
|
96
|
+
if (!SUPPORTED_JOURNAL_VERSIONS.includes(version)) throw new MigrationVerdictError({
|
|
95
97
|
code: "NF_MIGRATE_UNKNOWN_FORMAT",
|
|
96
98
|
connector: "",
|
|
97
99
|
source,
|
|
98
100
|
facts: {
|
|
99
101
|
file,
|
|
100
|
-
journalVersion:
|
|
102
|
+
journalVersion: version,
|
|
101
103
|
supported: SUPPORTED_JOURNAL_VERSIONS
|
|
102
104
|
},
|
|
103
105
|
nextActions: [{
|
|
104
106
|
command: "npm update @nodefony/drizzle",
|
|
105
107
|
args: ["update", "@nodefony/drizzle"]
|
|
106
108
|
}]
|
|
107
|
-
}, `Le journal de migrations de la source « ${source} » est en version ${
|
|
108
|
-
return
|
|
109
|
+
}, `Le journal de migrations de la source « ${source} » est en version ${version}, que cette version du framework ne sait pas lire (reconnue : ${SUPPORTED_JOURNAL_VERSIONS.join(", ")}).`);
|
|
110
|
+
return {
|
|
111
|
+
version,
|
|
112
|
+
entries: journal.entries
|
|
113
|
+
};
|
|
109
114
|
}
|
|
110
115
|
/**
|
|
111
116
|
* Lit un fichier de migration, valide son format et découpe ses statements.
|
|
@@ -370,8 +375,8 @@ function scanLine(line, needle, grammar, curseur) {
|
|
|
370
375
|
*/
|
|
371
376
|
function delimiteurDollar(line, debut) {
|
|
372
377
|
let i = debut + 1;
|
|
373
|
-
while (i < line.length && /[A-Za-z0-9_]/.test(line
|
|
374
|
-
if (i === debut + 1 && /[0-9]/.test(line
|
|
378
|
+
while (i < line.length && /[A-Za-z0-9_]/.test(line.charAt(i))) {
|
|
379
|
+
if (i === debut + 1 && /[0-9]/.test(line.charAt(i))) return null;
|
|
375
380
|
i += 1;
|
|
376
381
|
}
|
|
377
382
|
return line[i] === "$" ? line.slice(debut, i + 1) : null;
|
|
@@ -390,7 +395,7 @@ function delimiteurDollar(line, debut) {
|
|
|
390
395
|
function createdTables(files) {
|
|
391
396
|
const found = /* @__PURE__ */ new Set();
|
|
392
397
|
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
|
|
398
|
+
for (const file of files) for (const statement of file.statements) for (const [, table] of statement.matchAll(pattern)) if (table !== void 0) found.add(table);
|
|
394
399
|
return [...found];
|
|
395
400
|
}
|
|
396
401
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -182,7 +183,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
182
183
|
* @param options - options de connexion (`dialect`, `filename` sqlite, `url` pg).
|
|
183
184
|
*/
|
|
184
185
|
constructor(name, options = {}) {
|
|
185
|
-
super(name);
|
|
186
|
+
super(name, options.container);
|
|
186
187
|
this.#dialect = options.dialect ?? "sqlite";
|
|
187
188
|
this.#deriveSchema = options.deriveSchema !== false;
|
|
188
189
|
this.#filename = options.filename ?? ":memory:";
|
|
@@ -430,17 +431,18 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
430
431
|
}
|
|
431
432
|
async onConnect() {
|
|
432
433
|
await this.#releasePrevious();
|
|
433
|
-
|
|
434
|
+
const tables = Object.create(null);
|
|
435
|
+
this.#tables = tables;
|
|
434
436
|
this.#relations = Object.create(null);
|
|
435
437
|
const entities = this.#ownEntities();
|
|
436
438
|
switch (this.#dialect) {
|
|
437
439
|
case "sqlite":
|
|
438
|
-
await this.#connectSqlite(entities);
|
|
440
|
+
await this.#connectSqlite(entities, tables);
|
|
439
441
|
break;
|
|
440
442
|
case "postgres":
|
|
441
|
-
await this.#connectPostgres(entities);
|
|
443
|
+
await this.#connectPostgres(entities, tables);
|
|
442
444
|
break;
|
|
443
|
-
case "mysql": await this.#connectMysql(entities);
|
|
445
|
+
case "mysql": await this.#connectMysql(entities, tables);
|
|
444
446
|
}
|
|
445
447
|
if (this.#deriveSchema) {
|
|
446
448
|
await this.#reconcileSchema();
|
|
@@ -460,7 +462,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
460
462
|
* La méthode devient asynchrone pour cette seule raison ; le pilote, lui,
|
|
461
463
|
* reste synchrone une fois chargé (`client.exec`, `client.pragma`).
|
|
462
464
|
*/
|
|
463
|
-
async #connectSqlite(entities) {
|
|
465
|
+
async #connectSqlite(entities, tables) {
|
|
464
466
|
let Sqlite;
|
|
465
467
|
let sqliteDrizzle;
|
|
466
468
|
try {
|
|
@@ -470,7 +472,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
470
472
|
Sqlite = resolved;
|
|
471
473
|
sqliteDrizzle = (await import("drizzle-orm/better-sqlite3")).drizzle;
|
|
472
474
|
} catch (e) {
|
|
473
|
-
throw new Error(`DrizzleOrm "${this.name}": the sqlite dialect needs the optional driver \`better-sqlite3\` (run \`npm i better-sqlite3\`).
|
|
475
|
+
throw new Error(`DrizzleOrm "${this.name}": the sqlite dialect needs the optional driver \`better-sqlite3\` (run \`npm i better-sqlite3\`). ` + e.message, { cause: e });
|
|
474
476
|
}
|
|
475
477
|
const client = new Sqlite(this.#filename);
|
|
476
478
|
if (this.#filename !== ":memory:") {
|
|
@@ -505,7 +507,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
505
507
|
for (const entity of entities) {
|
|
506
508
|
this.#assertDialectTable(entity, SQLiteTable, "sqlite");
|
|
507
509
|
const table = entity.schema;
|
|
508
|
-
|
|
510
|
+
tables[entity.name] = table;
|
|
509
511
|
entity.model = table;
|
|
510
512
|
}
|
|
511
513
|
if (!this.#deriveSchema) return;
|
|
@@ -536,12 +538,12 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
536
538
|
#rawQuery = async (sql, params = []) => {
|
|
537
539
|
switch (this.#dialect) {
|
|
538
540
|
case "sqlite": {
|
|
539
|
-
const statement = this.#
|
|
541
|
+
const statement = this.#sqliteClient().prepare(sql);
|
|
540
542
|
return statement.reader ? statement.all(...params) : [];
|
|
541
543
|
}
|
|
542
|
-
case "postgres": return (await this.#
|
|
544
|
+
case "postgres": return (await this.#pgClient().query(toDollarParams(sql), params)).rows;
|
|
543
545
|
case "mysql": {
|
|
544
|
-
const [rows] = await this.#
|
|
546
|
+
const [rows] = await this.#mysqlClient().query(sql, params);
|
|
545
547
|
return Array.isArray(rows) ? rows : [];
|
|
546
548
|
}
|
|
547
549
|
}
|
|
@@ -554,13 +556,13 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
554
556
|
async #rawExec(sql) {
|
|
555
557
|
switch (this.#dialect) {
|
|
556
558
|
case "sqlite":
|
|
557
|
-
this.#
|
|
559
|
+
this.#sqliteClient().exec(sql);
|
|
558
560
|
return;
|
|
559
561
|
case "postgres":
|
|
560
|
-
await this.#
|
|
562
|
+
await this.#pgClient().query(sql);
|
|
561
563
|
return;
|
|
562
564
|
case "mysql":
|
|
563
|
-
await this.#
|
|
565
|
+
await this.#mysqlClient().query(sql);
|
|
564
566
|
return;
|
|
565
567
|
}
|
|
566
568
|
}
|
|
@@ -639,20 +641,20 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
639
641
|
case "sqlite": {
|
|
640
642
|
const t = table;
|
|
641
643
|
if (empechees.has(getTableConfig(t).name)) continue;
|
|
642
|
-
for (const statement of this.#createIndexesSQL(t)) this.#
|
|
644
|
+
for (const statement of this.#createIndexesSQL(t)) this.#sqliteClient().exec(statement);
|
|
643
645
|
break;
|
|
644
646
|
}
|
|
645
647
|
case "postgres": {
|
|
646
648
|
const t = table;
|
|
647
649
|
if (empechees.has(getTableConfig$1(t).name)) continue;
|
|
648
|
-
for (const statement of this.#createIndexesPgSQL(t)) await this.#
|
|
650
|
+
for (const statement of this.#createIndexesPgSQL(t)) await this.#pgClient().query(statement);
|
|
649
651
|
break;
|
|
650
652
|
}
|
|
651
653
|
case "mysql": {
|
|
652
654
|
const t = table;
|
|
653
655
|
if (empechees.has(getTableConfig$2(t).name)) continue;
|
|
654
656
|
for (const statement of this.#createIndexesMysqlSQL(t)) try {
|
|
655
|
-
await this.#
|
|
657
|
+
await this.#mysqlClient().query(statement);
|
|
656
658
|
} catch (error) {
|
|
657
659
|
if (error.code !== "ER_DUP_KEYNAME") throw error;
|
|
658
660
|
}
|
|
@@ -676,7 +678,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
676
678
|
* déploiement SQLite ne les tire jamais. Pool partagé ; DDL dérivé pour dev/test
|
|
677
679
|
* (prod = drizzle-kit). Échec d'import → message actionnable (`npm i pg`).
|
|
678
680
|
*/
|
|
679
|
-
async #connectPostgres(entities) {
|
|
681
|
+
async #connectPostgres(entities, tables) {
|
|
680
682
|
if (!this.#url) throw new Error(`DrizzleOrm "${this.name}": dialect "postgres" requires a connection \`url\`.`);
|
|
681
683
|
let PoolCtor;
|
|
682
684
|
let pgDrizzle;
|
|
@@ -707,7 +709,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
707
709
|
this.#pgPool = pool;
|
|
708
710
|
this.#db = pgDrizzle(pool);
|
|
709
711
|
try {
|
|
710
|
-
await this.#finishPostgres(entities, pool, pgDrizzle);
|
|
712
|
+
await this.#finishPostgres(entities, tables, pool, pgDrizzle);
|
|
711
713
|
} catch (e) {
|
|
712
714
|
this.#unwireAll();
|
|
713
715
|
await pool.end().catch(() => void 0);
|
|
@@ -718,11 +720,11 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
718
720
|
}
|
|
719
721
|
}
|
|
720
722
|
/** Suite de la connexion postgres — isolée pour rendre `connect()` atomique. */
|
|
721
|
-
async #finishPostgres(entities, pool, pgDrizzle) {
|
|
723
|
+
async #finishPostgres(entities, tables, pool, pgDrizzle) {
|
|
722
724
|
this.#beginTx = async () => {
|
|
723
725
|
const cx = await pool.connect();
|
|
724
726
|
const puitsTx = (err) => {
|
|
725
|
-
this.connectionLost(`pg (transaction) : ${err
|
|
727
|
+
this.connectionLost(`pg (transaction) : ${err instanceof Error ? err.message : String(err)}`);
|
|
726
728
|
};
|
|
727
729
|
cx.on("error", puitsTx);
|
|
728
730
|
/** Rend la connexion, en retirant d'abord NOTRE puits. */
|
|
@@ -747,7 +749,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
747
749
|
for (const entity of entities) {
|
|
748
750
|
this.#assertDialectTable(entity, PgTable, "postgres");
|
|
749
751
|
const table = entity.schema;
|
|
750
|
-
|
|
752
|
+
tables[entity.name] = table;
|
|
751
753
|
entity.model = table;
|
|
752
754
|
}
|
|
753
755
|
if (!this.#deriveSchema) return;
|
|
@@ -808,7 +810,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
808
810
|
*/
|
|
809
811
|
#wirePgLifecycle(pool) {
|
|
810
812
|
const onError = (err) => {
|
|
811
|
-
this.connectionLost(`pg: ${err
|
|
813
|
+
this.connectionLost(`pg: ${err instanceof Error ? err.message : String(err)}`);
|
|
812
814
|
};
|
|
813
815
|
const onConnect = () => {
|
|
814
816
|
this.connectionRestored();
|
|
@@ -852,7 +854,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
852
854
|
this.connectionRestored();
|
|
853
855
|
cx.on("error", (err) => {
|
|
854
856
|
if (this.#mysqlPool !== pool) return;
|
|
855
|
-
this.connectionLost(`mysql: ${err
|
|
857
|
+
this.connectionLost(`mysql: ${err instanceof Error ? err.message : String(err)}`);
|
|
856
858
|
});
|
|
857
859
|
cx.stream?.once("close", () => {
|
|
858
860
|
if (this.#mysqlPool !== pool) return;
|
|
@@ -872,7 +874,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
872
874
|
* `datetime(3)` (kind `dateMs` du colKit) sont écrites/relues en UTC — mêmes
|
|
873
875
|
* instants que `timestamptz` PG, sans dépendre de la timezone du serveur.
|
|
874
876
|
*/
|
|
875
|
-
async #connectMysql(entities) {
|
|
877
|
+
async #connectMysql(entities, tables) {
|
|
876
878
|
if (!this.#url) throw new Error(`DrizzleOrm "${this.name}": dialect "mysql" requires a connection \`url\`.`);
|
|
877
879
|
let pool;
|
|
878
880
|
let mysqlDrizzle;
|
|
@@ -902,7 +904,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
902
904
|
this.#mysqlPool = pool;
|
|
903
905
|
this.#db = mysqlDrizzle(pool);
|
|
904
906
|
try {
|
|
905
|
-
await this.#finishMysql(entities, pool, mysqlDrizzle);
|
|
907
|
+
await this.#finishMysql(entities, tables, pool, mysqlDrizzle);
|
|
906
908
|
} catch (e) {
|
|
907
909
|
this.#unwireAll();
|
|
908
910
|
await pool.end().catch(() => void 0);
|
|
@@ -913,7 +915,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
913
915
|
}
|
|
914
916
|
}
|
|
915
917
|
/** Suite de la connexion mysql — isolée pour rendre `connect()` atomique. */
|
|
916
|
-
async #finishMysql(entities, pool, mysqlDrizzle) {
|
|
918
|
+
async #finishMysql(entities, tables, pool, mysqlDrizzle) {
|
|
917
919
|
this.#beginTx = async () => {
|
|
918
920
|
const cx = await pool.getConnection();
|
|
919
921
|
try {
|
|
@@ -936,7 +938,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
936
938
|
for (const entity of entities) {
|
|
937
939
|
this.#assertDialectTable(entity, MySqlTable, "mysql");
|
|
938
940
|
const table = entity.schema;
|
|
939
|
-
|
|
941
|
+
tables[entity.name] = table;
|
|
940
942
|
entity.model = table;
|
|
941
943
|
}
|
|
942
944
|
if (!this.#deriveSchema) return;
|
|
@@ -976,7 +978,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
976
978
|
const table = this.#tables?.[name];
|
|
977
979
|
if (!this.#db) throw new Error(`DrizzleOrm "${this.name}": non connecté — impossible de rendre un dépôt pour "${name}". Les tables ne sont résolues qu'à la connexion. Si ce run est une commande en ligne, elle n'a pas déclaré \`externalServices\` (CONSOLE_DATA_RUN_PROFILE).`);
|
|
978
980
|
if (!table) throw new Error(`DrizzleOrm "${this.name}": no entity table registered under "${name}".`);
|
|
979
|
-
|
|
981
|
+
this.#repositories ??= Object.create(null);
|
|
980
982
|
let repository = this.#repositories[name];
|
|
981
983
|
if (repository === void 0) {
|
|
982
984
|
repository = new DrizzleRepository(this.#db, table, this.#relations?.[name] ?? {}, this.name, this.#dialect);
|
|
@@ -1009,6 +1011,25 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
1009
1011
|
throw error;
|
|
1010
1012
|
}
|
|
1011
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Connexion SQLite courante — lève si le connecteur n'est pas ouvert. Les
|
|
1016
|
+
* appelants ne passent ici qu'une fois `onConnect` engagé : la levée ne sert
|
|
1017
|
+
* qu'à typer, et à dire la cause si l'ordre était un jour rompu.
|
|
1018
|
+
*/
|
|
1019
|
+
#sqliteClient() {
|
|
1020
|
+
if (this.#client === null) throw new Error(`DrizzleOrm "${this.name}": not connected.`);
|
|
1021
|
+
return this.#client;
|
|
1022
|
+
}
|
|
1023
|
+
/** Pool PostgreSQL courant — même contrat que {@link #sqliteClient}. */
|
|
1024
|
+
#pgClient() {
|
|
1025
|
+
if (this.#pgPool === null) throw new Error(`DrizzleOrm "${this.name}": not connected.`);
|
|
1026
|
+
return this.#pgPool;
|
|
1027
|
+
}
|
|
1028
|
+
/** Pool MySQL courant — même contrat que {@link #sqliteClient}. */
|
|
1029
|
+
#mysqlClient() {
|
|
1030
|
+
if (this.#mysqlPool === null) throw new Error(`DrizzleOrm "${this.name}": not connected.`);
|
|
1031
|
+
return this.#mysqlPool;
|
|
1032
|
+
}
|
|
1012
1033
|
getNativeConnection() {
|
|
1013
1034
|
if (!this.#db) throw new Error(`DrizzleOrm "${this.name}": not connected.`);
|
|
1014
1035
|
return this.#db;
|
|
@@ -1122,7 +1143,7 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
1122
1143
|
* @returns sonde `pool` (taille max, idle, empruntées, en attente).
|
|
1123
1144
|
*/
|
|
1124
1145
|
#probePgPool(pool) {
|
|
1125
|
-
const max = pool.options
|
|
1146
|
+
const max = pool.options.max;
|
|
1126
1147
|
return { pool: {
|
|
1127
1148
|
size: typeof max === "number" ? max : 10,
|
|
1128
1149
|
available: pool.idleCount,
|