@nodefony/drizzle 10.0.0-alpha.8 → 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.
Files changed (50) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
  3. package/dist/index.js +9 -6
  4. package/dist/nodefony/command/migrateShared.js +17 -1
  5. package/dist/nodefony/command/orm-generate.js +11 -7
  6. package/dist/nodefony/command/orm-migrate-baseline.js +5 -2
  7. package/dist/nodefony/command/orm-reset.js +1 -0
  8. package/dist/nodefony/config/defineModuleConfig.js +21 -5
  9. package/dist/nodefony/entity/userTable.js +3 -0
  10. package/dist/nodefony/registerStores.js +2 -26
  11. package/dist/nodefony/service/DrizzleService.js +19 -7
  12. package/dist/nodefony/src/DrizzleAuditStore.js +13 -1
  13. package/dist/nodefony/src/DrizzleIdempotencyStore.js +14 -2
  14. package/dist/nodefony/src/DrizzleTokenStore.js +13 -1
  15. package/dist/nodefony/src/DrizzleTotpSecretStore.js +13 -1
  16. package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +13 -1
  17. package/dist/nodefony/src/DrizzleWebhookStore.js +13 -1
  18. package/dist/nodefony/src/SessionStorage.js +8 -0
  19. package/dist/nodefony/src/frameworkConnector.js +29 -0
  20. package/dist/nodefony/src/memoryDatabase.js +13 -0
  21. package/dist/nodefony/src/migrator/appSchema.js +44 -1
  22. package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +1 -0
  23. package/dist/nodefony/src/migrator/kit.js +23 -2
  24. package/dist/nodefony/src/migrator/paths.js +55 -1
  25. package/dist/nodefony/src/migrator/refusals.js +50 -1
  26. package/dist/nodefony/src/migrator/resolve.js +55 -5
  27. package/dist/nodefony/src/orm-core/DrizzleOrm.js +2 -1
  28. package/dist/nodefony/src/safeTarget.js +1 -0
  29. package/dist/types/nodefony/command/migrateShared.d.ts +10 -0
  30. package/dist/types/nodefony/config/defineModuleConfig.d.ts +5 -1
  31. package/dist/types/nodefony/entity/userTable.d.ts +46 -3
  32. package/dist/types/nodefony/registerStores.d.ts +4 -3
  33. package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +6 -0
  34. package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +8 -1
  35. package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +6 -0
  36. package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +6 -0
  37. package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +6 -0
  38. package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +6 -0
  39. package/dist/types/nodefony/src/SessionStorage.d.ts +6 -0
  40. package/dist/types/nodefony/src/frameworkConnector.d.ts +24 -0
  41. package/dist/types/nodefony/src/memoryDatabase.d.ts +10 -0
  42. package/dist/types/nodefony/src/migrator/appSchema.d.ts +25 -0
  43. package/dist/types/nodefony/src/migrator/kit.d.ts +15 -0
  44. package/dist/types/nodefony/src/migrator/paths.d.ts +35 -0
  45. package/dist/types/nodefony/src/migrator/refusals.d.ts +36 -1
  46. package/dist/types/nodefony/src/migrator/resolve.d.ts +44 -1
  47. package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +11 -0
  48. package/docs/index.md +28 -25
  49. package/docs/migrations.md +39 -3
  50. package/package.json +18 -19
@@ -1,4 +1,5 @@
1
1
  import { FORMAT_MARKER } from "./types.js";
2
+ import { ownsSharedMigrations } from "../frameworkConnector.js";
2
3
  import "./kit.js";
3
4
  import { registerHooks } from "node:module";
4
5
  import { entityRegistry } from "@nodefony/orm-core";
@@ -410,5 +411,47 @@ function registeredTables(connector) {
410
411
  }
411
412
  return out;
412
413
  }
414
+ /**
415
+ * Tables d'un connecteur parmi celles que les fichiers fournissent — le
416
+ * partage qui fait qu'une migration ne décrit que SA base.
417
+ *
418
+ * Une table appartient au connecteur de l'entité qui la déclare (le REGISTRE
419
+ * le sait, un fichier non). Une table qu'aucune entité ne réclame reste au
420
+ * connecteur du framework, comme avant que les connecteurs secondaires aient
421
+ * leurs migrations. Sans ce partage, la table d'une entité secondaire entrait
422
+ * AUSSI dans les migrations du framework : créée dans une base qui ne s'en sert
423
+ * jamais, et proposée à la suppression le jour où elle n'y figure plus.
424
+ *
425
+ * @param tables - tables fournies par les fichiers de l'application.
426
+ * @param connector - connecteur visé.
427
+ * @param owners - `table → connecteurs` qui la déclarent ; par défaut, le registre.
428
+ * @returns les tables de ce connecteur, dans l'ordre reçu.
429
+ */
430
+ function tablesOfConnector(tables, connector, owners = registeredOwners()) {
431
+ return tables.filter((t) => {
432
+ const claimed = owners.get(t.tableName);
433
+ return claimed === void 0 ? ownsSharedMigrations(connector) : claimed.has(connector);
434
+ });
435
+ }
436
+ /**
437
+ * Pour chaque table enregistrée, les connecteurs dont une entité la déclare.
438
+ *
439
+ * @returns la table de correspondance, lue dans le registre des entités.
440
+ */
441
+ function registeredOwners() {
442
+ const owners = /* @__PURE__ */ new Map();
443
+ for (const entity of entityRegistry.list()) {
444
+ const schema = entity.schema;
445
+ if (!is(schema, Table)) continue;
446
+ const table = getTableName(schema);
447
+ let set = owners.get(table);
448
+ if (set === void 0) {
449
+ set = /* @__PURE__ */ new Set();
450
+ owners.set(table, set);
451
+ }
452
+ set.add(entity.connector);
453
+ }
454
+ return owners;
455
+ }
413
456
  //#endregion
414
- export { collectTables, dialectOf, entityFilesOf, importSpecifier, missingProviders, postgresSchemaOf, registeredTables, usurpedTables, writeCustomMigration, writeKitConfig, writeSchemaModule };
457
+ export { collectTables, dialectOf, entityFilesOf, importSpecifier, missingProviders, postgresSchemaOf, registeredOwners, registeredTables, tablesOfConnector, usurpedTables, writeCustomMigration, writeKitConfig, writeSchemaModule };
@@ -1,5 +1,6 @@
1
1
  import { HISTORY_TABLE } from "../types.js";
2
2
  import { schemaReader } from "../catalog.js";
3
+ import "../../memoryDatabase.js";
3
4
  //#region nodefony/src/migrator/drivers/sqliteDriver.ts
4
5
  /**
5
6
  * Pilote SQLite de l'applicateur — connexion `better-sqlite3` dédiée.
@@ -1,5 +1,5 @@
1
1
  import { FORMAT_MARKER } from "./types.js";
2
- import { MigrationToolError, generationFailed, generationNeedsAnswer, generationToolMissing, introspectFailed } from "./refusals.js";
2
+ import { MigrationToolError, generationFailed, generationNeedsAnswer, generationToolMissing, generationTopLevelAwait, introspectFailed } from "./refusals.js";
3
3
  import { droppedTables, rebuiltTables } from "./destructive.js";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -71,6 +71,8 @@ function runGenerate({ cwd, configRel, name, label, regenerateCommand }) {
71
71
  });
72
72
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
73
73
  if (result.status !== 0 || !generationHappened(output)) {
74
+ const awaited = topLevelAwaitFailure(output);
75
+ if (awaited !== null) throw new MigrationToolError(generationTopLevelAwait(label, awaited.file, awaited.line, regenerateCommand ?? `nodefony orm:generate --name ${name}`));
74
76
  if (isInteractivePromptFailure(output)) throw new MigrationToolError(generationNeedsAnswer(label, regenerateCommand ?? `nodefony orm:generate --name ${name}`));
75
77
  throw new MigrationToolError(generationFailed(label, result.status, output));
76
78
  }
@@ -273,6 +275,25 @@ function isInteractivePromptFailure(output) {
273
275
  return /Interactive prompts require a TTY/i.test(output);
274
276
  }
275
277
  /**
278
+ * Reconnaît le refus de l'outil devant un `await` de premier niveau.
279
+ *
280
+ * C'est l'esbuild de `drizzle-kit` qui le détecte, en transpilant le fichier en
281
+ * CommonJS : sa ligne d'erreur nomme le fichier et la position. On la lit plutôt
282
+ * que d'analyser nous-mêmes les sources — l'outil qui refuse est aussi celui qui
283
+ * sait exactement où, sans faux positif. Fonction PURE, éprouvée sans process.
284
+ *
285
+ * @param output - sortie complète de l'outil.
286
+ * @returns le fichier et la ligne, ou `null` si l'échec est d'une autre nature.
287
+ */
288
+ function topLevelAwaitFailure(output) {
289
+ const plain = output.replace(/\u001B\[[0-9;]*m/g, "");
290
+ const m = /(?:^|\n)[^\S\n]*(\S[^\n]*?\.[cm]?[jt]sx?):(\d+):\d+: ERROR: Top-level await is currently not supported with the \\?"cjs\\?" output format/u.exec(plain);
291
+ return m ? {
292
+ file: m[1],
293
+ line: Number(m[2])
294
+ } : null;
295
+ }
296
+ /**
276
297
  * Pose le marqueur de format en tête des `.sql` d'un dossier qui ne l'ont pas.
277
298
  *
278
299
  * Écrit en fins de ligne `\n` quel que soit le système : le dépôt et le gabarit
@@ -296,4 +317,4 @@ function stampFormatMarker(dir) {
296
317
  return stamped;
297
318
  }
298
319
  //#endregion
299
- export { FORMAT_MARKER, auditMigrationSql, generationHappened, isInteractivePromptFailure, resolveDrizzleKitBin, runGenerate, runIntrospect, stampFormatMarker };
320
+ export { FORMAT_MARKER, auditMigrationSql, generationHappened, isInteractivePromptFailure, resolveDrizzleKitBin, runGenerate, runIntrospect, stampFormatMarker, topLevelAwaitFailure };
@@ -1,3 +1,4 @@
1
+ import { ownsSharedMigrations } from "../frameworkConnector.js";
1
2
  import path from "node:path";
2
3
  import fs from "node:fs/promises";
3
4
  import { fileURLToPath } from "node:url";
@@ -15,6 +16,47 @@ const FRAMEWORK_RANK = 0;
15
16
  * celles des modules ; l'inverse n'arrive jamais.
16
17
  */
17
18
  const APP_RANK = 1e6;
19
+ /**
20
+ * Noms qui ne peuvent PAS désigner le dossier d'un connecteur secondaire : ce
21
+ * sont ceux des dossiers de dialecte (et du journal) que le connecteur du
22
+ * framework range déjà sous `migrations/`. Un connecteur `postgres` y lirait les
23
+ * migrations de `default` comme les siennes.
24
+ */
25
+ const RESERVED_MIGRATION_DIRS = [
26
+ "sqlite",
27
+ "postgres",
28
+ "mysql",
29
+ "meta"
30
+ ];
31
+ /**
32
+ * Le nom de ce connecteur collisionne-t-il avec un dossier de dialecte ?
33
+ *
34
+ * @param connector - nom du connecteur (clé de `connectors`).
35
+ * @returns `true` pour un connecteur SECONDAIRE nommé comme un dossier réservé.
36
+ */
37
+ function isReservedConnectorName(connector) {
38
+ return !ownsSharedMigrations(connector) && RESERVED_MIGRATION_DIRS.includes(connector);
39
+ }
40
+ /**
41
+ * Dossier des migrations PROPRES à un connecteur — la seule règle qui dit où
42
+ * elles vivent.
43
+ *
44
+ * Le connecteur du framework garde la racine (`migrations/<dialecte>`) : ses
45
+ * migrations décrivent la base du framework et des entités de l'application.
46
+ * Un connecteur secondaire reçoit son sous-dossier (`migrations/<connecteur>/
47
+ * <dialecte>`) : sa base ne reçoit que ce qui y est écrit, et celle du
48
+ * framework n'en voit rien — le dossier de dialecte qu'elle lit n'est pas le
49
+ * même.
50
+ *
51
+ * @param appDir - dossier de migrations de l'application (`appMigrationsDir`).
52
+ * @param connector - nom du connecteur.
53
+ * @returns le dossier parent des sous-dossiers de dialecte, ou `undefined` sans
54
+ * racine d'application, ou pour un nom réservé ({@link isReservedConnectorName}).
55
+ */
56
+ function connectorMigrationsDir(appDir, connector) {
57
+ if (appDir === void 0 || isReservedConnectorName(connector)) return;
58
+ return ownsSharedMigrations(connector) ? appDir : path.join(appDir, connector);
59
+ }
18
60
  /** Dossier de migrations, mémoïsé — la remontée ne se fait qu'une fois. */
19
61
  let cachedDir = null;
20
62
  /**
@@ -68,10 +110,22 @@ async function frameworkMigrationsDir() {
68
110
  *
69
111
  * @param appDir - dossier de migrations de l'application, s'il y en a un.
70
112
  * @param options.framework - `false` quand le module est data-only.
113
+ * @param options.connector - connecteur visé ; un connecteur secondaire ne
114
+ * reçoit que SES migrations ({@link connectorMigrationsDir}), jamais celles
115
+ * du framework. Omis : le registre complet (usage direct).
71
116
  * @returns le registre, prêt pour l'applicateur.
72
117
  */
73
118
  async function defaultMigrationSources(appDir, options = {}) {
74
119
  const sources = [];
120
+ if (options.connector !== void 0 && !ownsSharedMigrations(options.connector)) {
121
+ const own = connectorMigrationsDir(appDir, options.connector);
122
+ if (own !== void 0) sources.push({
123
+ name: "app",
124
+ dir: own,
125
+ rank: APP_RANK
126
+ });
127
+ return sources;
128
+ }
75
129
  if (options.framework !== false) sources.push({
76
130
  name: FRAMEWORK_SOURCE,
77
131
  dir: await frameworkMigrationsDir(),
@@ -85,4 +139,4 @@ async function defaultMigrationSources(appDir, options = {}) {
85
139
  return sources;
86
140
  }
87
141
  //#endregion
88
- export { APP_RANK, APP_SOURCE, FRAMEWORK_RANK, FRAMEWORK_SOURCE, defaultMigrationSources, frameworkMigrationsDir };
142
+ export { APP_RANK, APP_SOURCE, FRAMEWORK_RANK, FRAMEWORK_SOURCE, RESERVED_MIGRATION_DIRS, connectorMigrationsDir, defaultMigrationSources, frameworkMigrationsDir, isReservedConnectorName };
@@ -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 { defaultMigrationSources } from "./paths.js";
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 »
@@ -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, appVersionsMigrations(appMigrationsDir(kernel, config.migrations.dir)))
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, { framework: config.frameworkEntities !== false });
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 };
@@ -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:";
@@ -1,3 +1,4 @@
1
+ import "./memoryDatabase.js";
1
2
  import path from "node:path";
2
3
  //#region nodefony/src/safeTarget.ts
3
4
  /**
@@ -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
  *
@@ -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.
@@ -1,16 +1,59 @@
1
- import type { SQLiteTable } from "drizzle-orm/sqlite-core";
1
+ import type { AnySQLiteColumn, SQLiteTable } from "drizzle-orm/sqlite-core";
2
+ import type { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
3
+ import type { AnyMySqlColumn, MySqlTable } from "drizzle-orm/mysql-core";
2
4
  import type { IEntity } from "@nodefony/orm-core";
3
5
  import type { SqlDialect } from "../interfaces/IDrizzleConfig.js";
6
+ /**
7
+ * Factory de la table `User` — comme {@link FrameworkTableFactory}, mais sa clé
8
+ * primaire reste ATTEIGNABLE au type.
9
+ *
10
+ * 🔴 Sans cela, une application ne peut pas RÉFÉRENCER l'utilisateur, et c'est
11
+ * la première relation que quiconque écrit : `create entity Message
12
+ * "author:ref(User)"` rend `.references(() => userTable.id)`, qui ne compilait
13
+ * pas (`TS2339: Property 'id' does not exist on type 'SQLiteTable<TableConfig>'`).
14
+ * La table est bâtie depuis une spec dont les colonnes sont calculées
15
+ * (`Object.fromEntries`) : l'inférence les perd, et le type rendu ne porte plus
16
+ * aucune colonne — alors qu'elles existent toutes à l'exécution.
17
+ *
18
+ * Cette précision reste LOCALE à `User`, et n'est pas remontée dans le colKit :
19
+ * les tables du framework n'ont pas toutes une colonne `id` (`session` et
20
+ * `idempotency_key` portent une autre clé), donc l'y déclarer mentirait sur
21
+ * elles. `tests/unit/userTableReference.test.ts` garde le fait — par sa
22
+ * COMPILATION, qu'un cas d'exécution ne pourrait pas prouver.
23
+ */
24
+ export interface UserTableFactory {
25
+ (dialect: "sqlite"): SQLiteTable & {
26
+ readonly id: AnySQLiteColumn;
27
+ };
28
+ (dialect: "postgres"): PgTable & {
29
+ readonly id: AnyPgColumn;
30
+ };
31
+ (dialect: "mysql"): MySqlTable & {
32
+ readonly id: AnyMySqlColumn;
33
+ };
34
+ (dialect?: SqlDialect): (SQLiteTable & {
35
+ readonly id: AnySQLiteColumn;
36
+ }) | (PgTable & {
37
+ readonly id: AnyPgColumn;
38
+ }) | (MySqlTable & {
39
+ readonly id: AnyMySqlColumn;
40
+ });
41
+ }
4
42
  /**
5
43
  * Factory de la table `User` pour un dialecte donné (mémoïsée — une instance
6
44
  * par dialecte). Les trois dialectes sont portés (`sqlite` par défaut).
7
45
  */
8
- export declare const createUserTable: import("./colKit.js").FrameworkTableFactory;
46
+ export declare const createUserTable: UserTableFactory;
9
47
  /**
10
48
  * Variante SQLite de la table `User` (dialecte par défaut) — export conservé
11
49
  * pour l'usage direct/banc-test (ex. module mediasoup).
50
+ *
51
+ * Son type n'est PAS réécrit en `SQLiteTable` : l'annoter ainsi effaçait la
52
+ * clé primaire que la factory vient d'exposer, et c'était la moitié du défaut.
12
53
  */
13
- export declare const userTable: SQLiteTable;
54
+ export declare const userTable: SQLiteTable<import("drizzle-orm/sqlite-core").TableConfig> & {
55
+ readonly id: AnySQLiteColumn;
56
+ };
14
57
  /**
15
58
  * Forme plate d'une ligne `User` renvoyée par le repository de base — le contrat
16
59
  * `IUserRow` de `@nodefony/user`, ré-exporté sous son nom historique.
@@ -1,3 +1,4 @@
1
+ import { FRAMEWORK_CONNECTOR } from "./src/frameworkConnector.js";
1
2
  import type { SqlDialect } from "./config/config.js";
2
3
  /**
3
4
  * AUTO-ENREGISTREMENT des backends framework portés par Drizzle — « charger le
@@ -20,10 +21,10 @@ import type { SqlDialect } from "./config/config.js";
20
21
  * boot (« store inconnu ») — jamais de table fantôme ni d'erreur SQL différée.
21
22
  */
22
23
  /**
23
- * Connecteur conventionnel qui héberge le schéma framework (même convention que
24
- * `SESSION_CONNECTOR` : `"default"` pour Drizzle, `"nodefony"` pour Mongoose).
24
+ * Connecteur conventionnel qui héberge le schéma framework — défini dans
25
+ * `src/frameworkConnector.ts`, que les migrations lisent aussi.
25
26
  */
26
- export declare const FRAMEWORK_CONNECTOR = "default";
27
+ export { FRAMEWORK_CONNECTOR };
27
28
  /** Bilan de l'auto-enregistrement (loggé par le module — jamais silencieux). */
28
29
  export interface IFrameworkStoresReport {
29
30
  /** Entités déclarées par l'auto-register (tables créées au connect). */
@@ -47,6 +47,12 @@ export declare class DrizzleAuditStore implements IAuditStore {
47
47
  * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
48
48
  */
49
49
  get location(): string | undefined;
50
+ /**
51
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
52
+ * registre des stores : la console rattache la brique à SON connecteur au
53
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
54
+ */
55
+ get connector(): string | undefined;
50
56
  /**
51
57
  * Construit le store depuis un {@link DrizzleOrm}. Le handle est résolu **lazy**
52
58
  * (gardé par `isConnected()` → `null` tant que l'ORM n'est pas/plus connecté).
@@ -73,8 +73,9 @@ export declare class DrizzleIdempotencyStore implements IIdempotencyStore {
73
73
  * base ({@link DrizzleOrm.location}) pour Studio. Lazy (comme `resolveDb`) car
74
74
  * le store est fabriqué AVANT le connect de l'ORM → l'emplacement n'est lisible
75
75
  * qu'une fois l'ORM enregistré (lu au `onReady`, pas à la construction).
76
+ * @param connector - connecteur ORM qui porte la table, publié au registre des stores.
76
77
  */
77
- constructor(resolveDb: () => DrizzleDb | null, now?: () => number, leaseMs?: number, ttlMs?: number, table?: DrizzleTable, resolveLocation?: () => string | undefined);
78
+ constructor(resolveDb: () => DrizzleDb | null, now?: () => number, leaseMs?: number, ttlMs?: number, table?: DrizzleTable, resolveLocation?: () => string | undefined, connector?: string);
78
79
  /**
79
80
  * Emplacement physique de la base (fichier SQLite) pour l'écran Studio « Stores »
80
81
  * — lu par `readStoreLocation`. Résolu **lazy** (l'ORM n'existe pas à la
@@ -82,6 +83,12 @@ export declare class DrizzleIdempotencyStore implements IIdempotencyStore {
82
83
  * ou sur un backend réseau (pg/mysql → voir l'infra déclarée).
83
84
  */
84
85
  get location(): string | undefined;
86
+ /**
87
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
88
+ * registre des stores : la console rattache la brique à SON connecteur au
89
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
90
+ */
91
+ get connector(): string | undefined;
85
92
  /**
86
93
  * Construit le store depuis un {@link DrizzleOrm}. Le handle est résolu **lazy**
87
94
  * (gardé par `isConnected()` → `null` tant que l'ORM n'est pas/plus connecté).
@@ -46,6 +46,12 @@ export declare class DrizzleTokenStore implements ITokenStore {
46
46
  * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
47
47
  */
48
48
  get location(): string | undefined;
49
+ /**
50
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
51
+ * registre des stores : la console rattache la brique à SON connecteur au
52
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
53
+ */
54
+ get connector(): string | undefined;
49
55
  /**
50
56
  * Construit le store depuis un {@link DrizzleOrm} connecté. Les entités
51
57
  * (`registerTokenEntities`) doivent avoir été enregistrées **avant**
@@ -35,6 +35,12 @@ export declare class DrizzleTotpSecretStore implements ITotpSecretStore {
35
35
  * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
36
36
  */
37
37
  get location(): string | undefined;
38
+ /**
39
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
40
+ * registre des stores : la console rattache la brique à SON connecteur au
41
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
42
+ */
43
+ get connector(): string | undefined;
38
44
  /**
39
45
  * Construit le store depuis un {@link DrizzleOrm} connecté. L'entité
40
46
  * (`registerTotpSecretEntity`) doit avoir été enregistrée **avant** `orm.connect()`.
@@ -34,6 +34,12 @@ export declare class DrizzleWebAuthnCredentialStore implements IWebAuthnCredenti
34
34
  * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
35
35
  */
36
36
  get location(): string | undefined;
37
+ /**
38
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
39
+ * registre des stores : la console rattache la brique à SON connecteur au
40
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
41
+ */
42
+ get connector(): string | undefined;
37
43
  /**
38
44
  * Construit le store depuis un {@link DrizzleOrm} connecté. L'entité
39
45
  * (`registerWebAuthnCredentialEntity`) doit avoir été enregistrée **avant**
@@ -51,6 +51,12 @@ export declare class DrizzleWebhookStore implements IWebhookStore {
51
51
  * — lu par `readStoreLocation`. `undefined` = backend réseau ou `:memory:`.
52
52
  */
53
53
  get location(): string | undefined;
54
+ /**
55
+ * Connecteur ORM qui porte ce store, lu par `readStoreConnector` pour le
56
+ * registre des stores : la console rattache la brique à SON connecteur au
57
+ * lieu de le déduire. `undefined` pour un store construit sans ORM (bancs).
58
+ */
59
+ get connector(): string | undefined;
54
60
  /**
55
61
  * Construit le store depuis un {@link DrizzleOrm} connecté. L'entité
56
62
  * (`registerWebhookEndpointEntity`) doit avoir été enregistrée **avant**
@@ -31,6 +31,12 @@ declare class SessionStorage implements ISessionStorage {
31
31
  * DÉFENSIVE (getter `location` optionnel) — l'ORM base `IOrm` ne l'expose pas.
32
32
  */
33
33
  get location(): string | undefined;
34
+ /**
35
+ * Connecteur ORM qui porte les sessions — lu par `readStoreConnector` pour le
36
+ * registre des stores. `undefined` tant que l'ORM de ce connecteur n'est pas
37
+ * enregistré : on ne publie pas un rattachement que rien ne porte.
38
+ */
39
+ get connector(): string | undefined;
34
40
  read(id: string): Promise<ISerializedSession>;
35
41
  start(id: string): Promise<ISerializedSession>;
36
42
  write(id: string, data: ISerializedSession): Promise<ISerializedSession>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Connecteur conventionnel qui héberge le schéma framework (même convention que
3
+ * `SESSION_CONNECTOR` : `"default"` pour Drizzle, `"nodefony"` pour Mongoose).
4
+ *
5
+ * Vit dans ce fichier sans dépendance parce que DEUX familles le lisent : le
6
+ * registre des stores (où résoudre les briques du framework) et les migrations
7
+ * (qui les reçoit). Une copie du littéral de chaque côté dériverait en silence.
8
+ */
9
+ export declare const FRAMEWORK_CONNECTOR = "default";
10
+ /**
11
+ * Ce connecteur possède-t-il les migrations du framework et de l'application ?
12
+ *
13
+ * **Seul le connecteur du framework les possède.** Le dossier des migrations de
14
+ * l'application (`migrations/<dialect>`) ne porte aucune notion de connecteur :
15
+ * il décrit UNE base, celle où vivent les briques du framework et les entités
16
+ * de l'application. Les attribuer à tout connecteur faisait qu'une base
17
+ * secondaire (analyse, base d'un module) en mode `migrate` y créait des tables
18
+ * qui ne lui appartiennent pas, et qu'en mode `auto` elle annonçait à chaque
19
+ * démarrage des migrations qui ne la concernent pas.
20
+ *
21
+ * @param connector - nom du connecteur (clé de `connectors`).
22
+ * @returns `true` pour le connecteur du framework, `false` pour tout autre.
23
+ */
24
+ export declare function ownsSharedMigrations(connector: string): boolean;