@nodefony/drizzle 10.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +162 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +105 -0
  6. package/dist/nodefony/command/migrateShared.js +247 -0
  7. package/dist/nodefony/command/orm-generate.js +356 -0
  8. package/dist/nodefony/command/orm-migrate-baseline.js +208 -0
  9. package/dist/nodefony/command/orm-migrate-repair.js +114 -0
  10. package/dist/nodefony/command/orm-migrate-status.js +67 -0
  11. package/dist/nodefony/command/orm-migrate.js +141 -0
  12. package/dist/nodefony/command/orm-reset.js +166 -0
  13. package/dist/nodefony/config/config.js +107 -0
  14. package/dist/nodefony/config/defineModuleConfig.js +63 -0
  15. package/dist/nodefony/entity/auditEventEntity.js +93 -0
  16. package/dist/nodefony/entity/colKit.js +260 -0
  17. package/dist/nodefony/entity/idempotencyEntity.js +74 -0
  18. package/dist/nodefony/entity/sessionEntity.js +75 -0
  19. package/dist/nodefony/entity/tokenEntity.js +198 -0
  20. package/dist/nodefony/entity/totpSecretEntity.js +98 -0
  21. package/dist/nodefony/entity/userTable.js +141 -0
  22. package/dist/nodefony/entity/webAuthnCredentialEntity.js +114 -0
  23. package/dist/nodefony/entity/webhookEndpointEntity.js +106 -0
  24. package/dist/nodefony/interfaces/IDrizzleConfig.js +1 -0
  25. package/dist/nodefony/interfaces/index.js +1 -0
  26. package/dist/nodefony/migrations-schema/mysql.js +48 -0
  27. package/dist/nodefony/migrations-schema/postgres.js +48 -0
  28. package/dist/nodefony/migrations-schema/sqlite.js +48 -0
  29. package/dist/nodefony/registerStores.js +218 -0
  30. package/dist/nodefony/service/DrizzleService.js +282 -0
  31. package/dist/nodefony/src/DrizzleAuditStore.js +203 -0
  32. package/dist/nodefony/src/DrizzleIdempotencyStore.js +278 -0
  33. package/dist/nodefony/src/DrizzleTokenStore.js +244 -0
  34. package/dist/nodefony/src/DrizzleTotpSecretStore.js +151 -0
  35. package/dist/nodefony/src/DrizzleUserRepository.js +217 -0
  36. package/dist/nodefony/src/DrizzleWebAuthnCredentialStore.js +159 -0
  37. package/dist/nodefony/src/DrizzleWebhookStore.js +169 -0
  38. package/dist/nodefony/src/SessionStorage.js +259 -0
  39. package/dist/nodefony/src/connectorTarget.js +59 -0
  40. package/dist/nodefony/src/likeSql.js +50 -0
  41. package/dist/nodefony/src/migrator/DrizzleMigrator.js +775 -0
  42. package/dist/nodefony/src/migrator/adopt.js +553 -0
  43. package/dist/nodefony/src/migrator/appSchema.js +414 -0
  44. package/dist/nodefony/src/migrator/catalog.js +76 -0
  45. package/dist/nodefony/src/migrator/destructive.js +213 -0
  46. package/dist/nodefony/src/migrator/divergence.js +84 -0
  47. package/dist/nodefony/src/migrator/drivers/index.js +39 -0
  48. package/dist/nodefony/src/migrator/drivers/mysqlDriver.js +147 -0
  49. package/dist/nodefony/src/migrator/drivers/postgresDriver.js +151 -0
  50. package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +121 -0
  51. package/dist/nodefony/src/migrator/explain.js +565 -0
  52. package/dist/nodefony/src/migrator/hash.js +47 -0
  53. package/dist/nodefony/src/migrator/history.js +219 -0
  54. package/dist/nodefony/src/migrator/index.js +16 -0
  55. package/dist/nodefony/src/migrator/kit.js +296 -0
  56. package/dist/nodefony/src/migrator/name.js +68 -0
  57. package/dist/nodefony/src/migrator/paths.js +88 -0
  58. package/dist/nodefony/src/migrator/refusals.js +143 -0
  59. package/dist/nodefony/src/migrator/resolve.js +281 -0
  60. package/dist/nodefony/src/migrator/schemaDiff.js +86 -0
  61. package/dist/nodefony/src/migrator/sources.js +419 -0
  62. package/dist/nodefony/src/migrator/status.js +231 -0
  63. package/dist/nodefony/src/migrator/types.js +91 -0
  64. package/dist/nodefony/src/orm-core/DrizzleOrm.js +1154 -0
  65. package/dist/nodefony/src/orm-core/DrizzleRepository.js +610 -0
  66. package/dist/nodefony/src/orm-core/DrizzleTransaction.js +106 -0
  67. package/dist/nodefony/src/orm-core/index.js +4 -0
  68. package/dist/nodefony/src/queryKit.js +318 -0
  69. package/dist/nodefony/src/safeTarget.js +55 -0
  70. package/dist/types/index.d.ts +76 -0
  71. package/dist/types/nodefony/command/migrateShared.d.ts +137 -0
  72. package/dist/types/nodefony/command/orm-generate.d.ts +53 -0
  73. package/dist/types/nodefony/command/orm-migrate-baseline.d.ts +87 -0
  74. package/dist/types/nodefony/command/orm-migrate-repair.d.ts +66 -0
  75. package/dist/types/nodefony/command/orm-migrate-status.d.ts +38 -0
  76. package/dist/types/nodefony/command/orm-migrate.d.ts +65 -0
  77. package/dist/types/nodefony/command/orm-reset.d.ts +55 -0
  78. package/dist/types/nodefony/config/config.d.ts +110 -0
  79. package/dist/types/nodefony/config/defineModuleConfig.d.ts +24 -0
  80. package/dist/types/nodefony/entity/auditEventEntity.d.ts +56 -0
  81. package/dist/types/nodefony/entity/colKit.d.ts +130 -0
  82. package/dist/types/nodefony/entity/idempotencyEntity.d.ts +52 -0
  83. package/dist/types/nodefony/entity/sessionEntity.d.ts +50 -0
  84. package/dist/types/nodefony/entity/tokenEntity.d.ts +53 -0
  85. package/dist/types/nodefony/entity/totpSecretEntity.d.ts +61 -0
  86. package/dist/types/nodefony/entity/userTable.d.ts +65 -0
  87. package/dist/types/nodefony/entity/webAuthnCredentialEntity.d.ts +57 -0
  88. package/dist/types/nodefony/entity/webhookEndpointEntity.d.ts +58 -0
  89. package/dist/types/nodefony/interfaces/IDrizzleConfig.d.ts +17 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  91. package/dist/types/nodefony/migrations-schema/mysql.d.ts +9 -0
  92. package/dist/types/nodefony/migrations-schema/postgres.d.ts +9 -0
  93. package/dist/types/nodefony/migrations-schema/sqlite.d.ts +9 -0
  94. package/dist/types/nodefony/registerStores.d.ts +52 -0
  95. package/dist/types/nodefony/service/DrizzleService.d.ts +27 -0
  96. package/dist/types/nodefony/src/DrizzleAuditStore.d.ts +65 -0
  97. package/dist/types/nodefony/src/DrizzleIdempotencyStore.d.ts +129 -0
  98. package/dist/types/nodefony/src/DrizzleTokenStore.d.ts +146 -0
  99. package/dist/types/nodefony/src/DrizzleTotpSecretStore.d.ts +60 -0
  100. package/dist/types/nodefony/src/DrizzleUserRepository.d.ts +67 -0
  101. package/dist/types/nodefony/src/DrizzleWebAuthnCredentialStore.d.ts +62 -0
  102. package/dist/types/nodefony/src/DrizzleWebhookStore.d.ts +79 -0
  103. package/dist/types/nodefony/src/SessionStorage.d.ts +72 -0
  104. package/dist/types/nodefony/src/connectorTarget.d.ts +48 -0
  105. package/dist/types/nodefony/src/likeSql.d.ts +29 -0
  106. package/dist/types/nodefony/src/migrator/DrizzleMigrator.d.ts +94 -0
  107. package/dist/types/nodefony/src/migrator/adopt.d.ts +280 -0
  108. package/dist/types/nodefony/src/migrator/appSchema.d.ts +223 -0
  109. package/dist/types/nodefony/src/migrator/catalog.d.ts +100 -0
  110. package/dist/types/nodefony/src/migrator/destructive.d.ts +123 -0
  111. package/dist/types/nodefony/src/migrator/divergence.d.ts +61 -0
  112. package/dist/types/nodefony/src/migrator/drivers/index.d.ts +26 -0
  113. package/dist/types/nodefony/src/migrator/drivers/mysqlDriver.d.ts +83 -0
  114. package/dist/types/nodefony/src/migrator/drivers/postgresDriver.d.ts +81 -0
  115. package/dist/types/nodefony/src/migrator/drivers/sqliteDriver.d.ts +53 -0
  116. package/dist/types/nodefony/src/migrator/explain.d.ts +424 -0
  117. package/dist/types/nodefony/src/migrator/hash.d.ts +39 -0
  118. package/dist/types/nodefony/src/migrator/history.d.ts +139 -0
  119. package/dist/types/nodefony/src/migrator/index.d.ts +20 -0
  120. package/dist/types/nodefony/src/migrator/kit.d.ts +141 -0
  121. package/dist/types/nodefony/src/migrator/name.d.ts +52 -0
  122. package/dist/types/nodefony/src/migrator/paths.d.ts +54 -0
  123. package/dist/types/nodefony/src/migrator/refusals.d.ts +170 -0
  124. package/dist/types/nodefony/src/migrator/resolve.d.ts +201 -0
  125. package/dist/types/nodefony/src/migrator/schemaDiff.d.ts +112 -0
  126. package/dist/types/nodefony/src/migrator/sources.d.ts +119 -0
  127. package/dist/types/nodefony/src/migrator/status.d.ts +132 -0
  128. package/dist/types/nodefony/src/migrator/types.d.ts +273 -0
  129. package/dist/types/nodefony/src/orm-core/DrizzleOrm.d.ts +241 -0
  130. package/dist/types/nodefony/src/orm-core/DrizzleRepository.d.ts +98 -0
  131. package/dist/types/nodefony/src/orm-core/DrizzleTransaction.d.ts +71 -0
  132. package/dist/types/nodefony/src/orm-core/index.d.ts +11 -0
  133. package/dist/types/nodefony/src/queryKit.d.ts +136 -0
  134. package/dist/types/nodefony/src/safeTarget.d.ts +42 -0
  135. package/docs/index.md +954 -0
  136. package/docs/migrations.md +691 -0
  137. package/migrations/mysql/0000_framework_init.sql +137 -0
  138. package/migrations/mysql/meta/_journal.json +13 -0
  139. package/migrations/postgres/0000_framework_init.sql +128 -0
  140. package/migrations/postgres/meta/_journal.json +13 -0
  141. package/migrations/sqlite/0000_framework_init.sql +127 -0
  142. package/migrations/sqlite/meta/_journal.json +13 -0
  143. package/package.json +126 -0
@@ -0,0 +1,88 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region nodefony/src/migrator/paths.ts
5
+ /** Nom logique RÉSERVÉ de la source livrée par le framework. */
6
+ const FRAMEWORK_SOURCE = "framework";
7
+ /** Nom logique RÉSERVÉ de la source livrée par l'application. */
8
+ const APP_SOURCE = "app";
9
+ /** Rang de la source framework : première appliquée, toujours. */
10
+ const FRAMEWORK_RANK = 0;
11
+ /**
12
+ * Rang de la source application : dernière appliquée, toujours.
13
+ *
14
+ * Les entités d'une application peuvent référencer les tables du framework et
15
+ * celles des modules ; l'inverse n'arrive jamais.
16
+ */
17
+ const APP_RANK = 1e6;
18
+ /** Dossier de migrations, mémoïsé — la remontée ne se fait qu'une fois. */
19
+ let cachedDir = null;
20
+ /**
21
+ * Dossier des migrations livrées par ce paquet.
22
+ *
23
+ * **Trouvé en remontant jusqu'au `package.json` du paquet**, jamais par un
24
+ * nombre de niveaux codé en dur : la profondeur diffère entre les sources
25
+ * (`nodefony/src/migrator/`) et le paquet bâti (`dist/nodefony/src/migrator/`),
26
+ * et un compte figé serait juste d'un côté, faux de l'autre — sans que rien ne
27
+ * le signale avant l'exécution chez un utilisateur.
28
+ *
29
+ * @returns le chemin absolu du dossier `migrations` du paquet.
30
+ * @throws Error si la racine du paquet est introuvable.
31
+ */
32
+ async function frameworkMigrationsDir() {
33
+ if (cachedDir !== null) return cachedDir;
34
+ let dir = path.dirname(fileURLToPath(import.meta.url));
35
+ for (;;) {
36
+ const manifest = path.join(dir, "package.json");
37
+ try {
38
+ const raw = await fs.readFile(manifest, "utf8");
39
+ if (JSON.parse(raw).name === "@nodefony/drizzle") {
40
+ cachedDir = path.join(dir, "migrations");
41
+ return cachedDir;
42
+ }
43
+ } catch {}
44
+ const parent = path.dirname(dir);
45
+ if (parent === dir) throw new Error("Migrations : racine du paquet `@nodefony/drizzle` introuvable — le dossier `migrations` livré ne peut pas être résolu.");
46
+ dir = parent;
47
+ }
48
+ }
49
+ /**
50
+ * Registre de sources standard : le framework, puis l'application.
51
+ *
52
+ * L'espace de noms reste OUVERT — un module tiers ajoute la sienne au même
53
+ * registre, avec son propre rang. `framework` et `app` sont deux valeurs
54
+ * réservées, pas une énumération.
55
+ *
56
+ * **Le framework ne fournit ses migrations que s'il déclare ses entités.** Un
57
+ * module réglé `frameworkEntities: false` est data-only : il n'enregistre ni
58
+ * entité ni fabrique, et le démarrage en mode dérivé ne crée donc aucune table
59
+ * de session, de jeton, d'audit ni de webhook. Les inclure ici quand même
60
+ * faisait fabriquer DEUX bases différentes à la même application selon qu'elle
61
+ * démarrait en développement ou qu'on la migrait en production — et rien ne le
62
+ * disait, le verdict de divergence ignorant par construction ce que la base a
63
+ * en TROP.
64
+ *
65
+ * La règle vit ICI, à l'endroit unique où les sources se composent : ses deux
66
+ * appelants (le service au démarrage, les commandes) la recopieraient sinon, et
67
+ * deux copies divergent en silence.
68
+ *
69
+ * @param appDir - dossier de migrations de l'application, s'il y en a un.
70
+ * @param options.framework - `false` quand le module est data-only.
71
+ * @returns le registre, prêt pour l'applicateur.
72
+ */
73
+ async function defaultMigrationSources(appDir, options = {}) {
74
+ const sources = [];
75
+ if (options.framework !== false) sources.push({
76
+ name: FRAMEWORK_SOURCE,
77
+ dir: await frameworkMigrationsDir(),
78
+ rank: 0
79
+ });
80
+ if (appDir) sources.push({
81
+ name: "app",
82
+ dir: appDir,
83
+ rank: APP_RANK
84
+ });
85
+ return sources;
86
+ }
87
+ //#endregion
88
+ export { APP_RANK, APP_SOURCE, FRAMEWORK_RANK, FRAMEWORK_SOURCE, defaultMigrationSources, frameworkMigrationsDir };
@@ -0,0 +1,143 @@
1
+ import { MIGRATE_URL_ENV } from "./types.js";
2
+ import { action } from "./explain.js";
3
+ import { knownConnectors } from "./resolve.js";
4
+ //#region nodefony/src/migrator/refusals.ts
5
+ /**
6
+ * Les refus de RÉSOLUTION, rendus en VALEUR — la prose qu'un connecteur non
7
+ * migrable mérite, sans supposer qui la lira.
8
+ *
9
+ * **Pourquoi ce fichier existe** : ces quatre messages étaient écrits dans la
10
+ * commande, mêlés à l'écriture sur la sortie et au code de sortie. Ils ont
11
+ * pourtant DEUX lecteurs — la ligne de commande, et le plan d'administration
12
+ * qui alimente l'écran de la console. Les recopier de l'autre côté aurait posé
13
+ * deux vérités sur la même question ; les laisser dans la commande aurait
14
+ * obligé l'écran à réinventer les siennes, plus courtes, donc plus fausses.
15
+ *
16
+ * Aucune entrée-sortie ici, aucun style, aucun code de sortie : une fonction
17
+ * reçoit une résolution et rend ce qu'il y a à en dire. C'est ce qui la rend
18
+ * éprouvable sans base, sans kernel et sans terminal.
19
+ */
20
+ /** Nom du module qui porte la configuration des connecteurs SQL. */
21
+ const MODULE_NAME = "drizzle";
22
+ /**
23
+ * L'outil qui ÉCRIT les migrations n'est pas installé.
24
+ *
25
+ * Refus À PART, et c'est tout son intérêt : sans lui, cette cause tombait dans
26
+ * le fourre-tout des commandes de migration, qui habille toute exception non
27
+ * typée d'un `meaning` écrit pour la base injoignable. La charge utile portait
28
+ * alors DEUX explications qui se contredisent — le fait disait « l'outil
29
+ * manque », l'explication disait « vérifie que la base est démarrée » — et ses
30
+ * deux gestes interrogeaient une base qui n'y était pour rien.
31
+ *
32
+ * @returns le refus, avec le geste qui répare.
33
+ */
34
+ function generationToolMissing() {
35
+ return {
36
+ code: "NF_GENERATE_TOOL_MISSING",
37
+ summary: "L'outil qui écrit les migrations (`drizzle-kit`) n'est pas installé : rien n'a été écrit.",
38
+ meaning: "Écrire une migration demande un outil de DÉVELOPPEMENT, que l'application déclare mais qui n'est pas dans « node_modules » — un `npm install` manque, ou l'installation s'est faite sans les dépendances de développement (`--omit=dev`). La base n'est pas en cause : appliquer des migrations, lui, ne réclame aucun outil tiers.",
39
+ nextActions: [action("npm install"), action("npm install --save-dev drizzle-kit")],
40
+ exitCode: 2
41
+ };
42
+ }
43
+ /**
44
+ * Erreur portant un {@link IResolutionRefusal} déjà composé.
45
+ *
46
+ * Elle existe pour que la CAUSE porte son propre remède jusqu'à la sortie de la
47
+ * commande : reconnaître une cause au texte de son message serait une garde qui
48
+ * se casse au premier reformulage.
49
+ */
50
+ var MigrationToolError = class extends Error {
51
+ refusal;
52
+ /**
53
+ * @param refusal - refus complet, seule source de la décision.
54
+ */
55
+ constructor(refusal) {
56
+ super(refusal.summary);
57
+ this.refusal = refusal;
58
+ this.name = "MigrationToolError";
59
+ }
60
+ };
61
+ /**
62
+ * Le module qui porte les connecteurs n'est pas chargé par l'application.
63
+ *
64
+ * @returns le refus, prêt pour la ligne de commande comme pour l'écran.
65
+ */
66
+ function moduleAbsent() {
67
+ return {
68
+ code: "NF_MIGRATE_UNAVAILABLE",
69
+ summary: `Le module « @nodefony/${MODULE_NAME} » n'est pas chargé par cette application : il n'y a aucun connecteur SQL à migrer.`,
70
+ meaning: "Les migrations sont portées par le module qui déclare les connecteurs. Sans lui, la commande n'a ni base, ni fichiers, ni historique à consulter.",
71
+ nextActions: [action("nodefony inspect modules")],
72
+ exitCode: 2
73
+ };
74
+ }
75
+ /**
76
+ * Le connecteur est une base SQL, mais la configuration du module ne le déclare
77
+ * pas — cas d'un ORM construit directement dans du code.
78
+ *
79
+ * ⚠️ Ne JAMAIS lui répondre « ne porte pas de migrations » : c'est faux d'un
80
+ * connecteur SQL, et un message faux publié est appris par les scripts qui le
81
+ * lisent.
82
+ *
83
+ * @param connector - nom du connecteur.
84
+ * @param driver - base sous-jacente, telle que l'ORM la nomme.
85
+ * @returns le refus, prêt pour la ligne de commande comme pour l'écran.
86
+ */
87
+ function notConfigured(connector, driver) {
88
+ return {
89
+ code: "NF_MIGRATE_NOT_CONFIGURED",
90
+ summary: `Le connecteur « ${connector} » est bien une base SQL (${driver}), mais il n'est pas déclaré dans la configuration de « @nodefony/${MODULE_NAME} » : il n'y a ni fichiers ni coordonnées pour lire son état.`,
91
+ meaning: "Un connecteur créé directement dans du code (un banc de test, un module qui instancie son ORM lui-même) est enregistré au moment où il se connecte, mais l'état des migrations se lit dans la configuration — c'est elle qui porte le dossier des fichiers et le mode de schéma. Déclare-le dans `connectors` pour pouvoir le suivre.",
92
+ nextActions: [action("nodefony inspect config --json")],
93
+ exitCode: 2
94
+ };
95
+ }
96
+ /**
97
+ * Traduit une résolution qui n'est PAS `ready` en refus lisible.
98
+ *
99
+ * @param wanted - nom demandé par l'appelant.
100
+ * @param resolution - ce que la résolution a rendu.
101
+ * @param config - configuration validée du module (nomme les connecteurs réels).
102
+ * @returns le refus correspondant, jamais `null` : chaque cas a sa prose.
103
+ */
104
+ function describeResolutionRefusal(wanted, resolution, config) {
105
+ const premier = knownConnectors(config)[0] ?? "default";
106
+ if (resolution.kind === "unknown") return {
107
+ code: "NF_MIGRATE_UNKNOWN_CONNECTOR",
108
+ summary: `Aucun connecteur ne s'appelle « ${wanted} ». Ceux que cette application déclare : ${resolution.known.length > 0 ? resolution.known.map((n) => `« ${n} »`).join(", ") : "aucun"}.`,
109
+ meaning: "Le nom attendu est celui d'une clé de `connectors` dans la configuration, pas un nom de base ni un dialecte. Sans `--connector`, la commande travaille sur « default ».",
110
+ nextActions: [action("nodefony orm:migrate:status"), action("nodefony inspect config --json")],
111
+ exitCode: 2
112
+ };
113
+ if (resolution.kind === "url-mismatch") {
114
+ const vise = resolution.urlDialect === null ? "une base que cette commande ne sait pas lire" : `une base ${resolution.urlDialect}`;
115
+ return {
116
+ code: "NF_MIGRATE_URL_MISMATCH",
117
+ summary: `${MIGRATE_URL_ENV} désigne ${vise}, alors que le connecteur « ${wanted} » est déclaré en ${resolution.dialect}. Rien n'a été appliqué.`,
118
+ meaning: "Les deux ne peuvent pas être vraies en même temps : le SQL d'un dialecte ne s'applique pas avec le pilote d'un autre, et deviner laquelle des deux bases tu vises reviendrait à migrer la mauvaise en annonçant un succès. Soit la variable pointe la base du connecteur, soit c'est le connecteur qu'il faut choisir — la variable ne sert qu'à changer le COMPTE et l'hôte, jamais la nature de la base. Sous PowerShell, retirer la variable s'écrit `Remove-Item Env:" + MIGRATE_URL_ENV + "`.",
119
+ nextActions: [
120
+ action(`unset ${MIGRATE_URL_ENV}`),
121
+ action(`nodefony orm:migrate:status --connector ${wanted}`),
122
+ action("nodefony inspect config --json")
123
+ ],
124
+ exitCode: 2
125
+ };
126
+ }
127
+ if (resolution.sqlLike) return {
128
+ code: "NF_MIGRATE_NOT_CONFIGURED",
129
+ summary: `Le connecteur « ${wanted} » est bien une base SQL (${resolution.driver}), mais il n'est pas déclaré dans la configuration de « @nodefony/${MODULE_NAME} » : la commande n'a pas ses coordonnées de connexion.`,
130
+ meaning: "Un connecteur créé directement dans du code (un banc de test, un module qui instancie son ORM lui-même) est enregistré au moment où il se connecte, mais la commande, elle, lit la configuration — c'est elle qui porte le fichier ou l'URL, et un secret ne se lit pas dans un objet déjà connecté. Déclare-le dans `connectors` pour pouvoir le migrer.",
131
+ nextActions: [action("nodefony inspect config --json"), action(`nodefony orm:migrate:status --connector ${premier}`)],
132
+ exitCode: 2
133
+ };
134
+ return {
135
+ code: "NF_MIGRATE_NO_MIGRATIONS",
136
+ summary: `Le connecteur « ${wanted} » est porté par ${resolution.owner}, dont la base ne se met pas à jour par des migrations de schéma.`,
137
+ meaning: "Les migrations par fichiers versionnés sont une mécanique SQL. Les autres bases résorbent l'écart entre le code et le schéma autrement — la question est la même, la réponse n'est pas la même. Aucune commande ne peut migrer ce connecteur aujourd'hui.",
138
+ nextActions: [action(`nodefony orm:migrate:status --connector ${premier}`)],
139
+ exitCode: 2
140
+ };
141
+ }
142
+ //#endregion
143
+ export { MigrationToolError, describeResolutionRefusal, generationToolMissing, moduleAbsent, notConfigured };
@@ -0,0 +1,281 @@
1
+ import { MIGRATE_URL_ENV } from "./types.js";
2
+ import { resolveConnectorTarget } from "../connectorTarget.js";
3
+ import { defaultMigrationSources } from "./paths.js";
4
+ import { DrizzleMigrator } from "./DrizzleMigrator.js";
5
+ import { parseDatabaseUrl, sqliteFilenameFromUrl } from "nodefony";
6
+ import { ormRegistry } from "@nodefony/orm-core";
7
+ import path from "node:path";
8
+ //#region nodefony/src/migrator/resolve.ts
9
+ /**
10
+ * Résolution du décor d'une commande de migration : QUI possède le connecteur,
11
+ * OÙ se trouve sa base, QUEL mode de schéma s'applique.
12
+ *
13
+ * **Pourquoi un fichier à part, et pourquoi il ne fait aucune entrée-sortie** :
14
+ * ces règles sont lues par cinq commandes, par le démarrage du module et par la
15
+ * sonde de disponibilité. Une seule d'entre elles recopiée ailleurs
16
+ * divergerait — et une divergence de mode `ddl` entre le démarrage et la ligne
17
+ * de commande, c'est une base que l'un croit à jour et que l'autre migre.
18
+ *
19
+ * Les fonctions de décision sont **pures** : on leur INJECTE l'environnement
20
+ * constaté au lieu de le lire ici. C'est ce qui les rend éprouvables sans
21
+ * kernel, sans base et sans variable d'environnement à poser.
22
+ */
23
+ /**
24
+ * Constate l'environnement depuis le kernel et le processus.
25
+ *
26
+ * @param kernel - kernel courant, ou `null` hors application.
27
+ * @returns l'environnement constaté, prêt à être injecté aux règles pures.
28
+ */
29
+ function readMigrationEnv(kernel) {
30
+ return {
31
+ runtime: kernel?.resolveRuntimeEnv() ?? "production",
32
+ nodeEnv: process.env.NODE_ENV
33
+ };
34
+ }
35
+ /**
36
+ * L'effacement d'une base est-il ACCEPTÉ dans cet environnement ?
37
+ *
38
+ * **Liste blanche, jamais liste noire** : seul `development` passe. Un
39
+ * `staging`, un `test`, un environnement que personne n'a pensé à nommer sont
40
+ * refusés — c'est exactement ce qu'une garde écrite « si production » laisserait
41
+ * passer, et c'est là que l'accident se produit.
42
+ *
43
+ * Écrite ici parce qu'elle a DEUX lecteurs qui doivent dire la même chose :
44
+ * `orm:reset`, qui refuse ; et le rendu des migrations, qui ne doit pas proposer
45
+ * un geste que l'autre va rejeter. Deux copies de cette règle divergeraient, et
46
+ * la sortie promettrait alors une commande impossible.
47
+ *
48
+ * @param env - environnement constaté.
49
+ * @returns `true` si `orm:reset` est recevable.
50
+ */
51
+ function resetAllowed(env) {
52
+ return env.runtime === "development" && env.nodeEnv !== "test";
53
+ }
54
+ /**
55
+ * Le mode de schéma qui s'applique à un connecteur.
56
+ *
57
+ * Règle des défauts, et son pourquoi : **appliquer des migrations au démarrage
58
+ * n'est jamais un défaut**. C'est la norme unanime des outils de migration, et
59
+ * elle tient à un fait simple — au démarrage, plusieurs exemplaires partent en
60
+ * même temps. `migrate` est donc toujours un choix écrit, jamais une déduction.
61
+ *
62
+ * - développement et test → `auto` : le schéma suit le code, sans rien taper ;
63
+ * - tout le reste → `none` : personne ne touche au schéma au démarrage, un
64
+ * travail externe applique les migrations avant que le trafic n'arrive.
65
+ *
66
+ * @param explicit - valeur écrite dans la configuration du connecteur, si elle l'est.
67
+ * @param env - environnement constaté.
68
+ * @returns le mode effectif.
69
+ */
70
+ function resolveDdlMode(explicit, env) {
71
+ if (explicit) return explicit;
72
+ return env.runtime === "development" || env.nodeEnv === "test" ? "auto" : "none";
73
+ }
74
+ /**
75
+ * La conduite de la sonde de disponibilité face à un schéma en retard.
76
+ *
77
+ * En production, un exemplaire dont le schéma est en retard ne doit pas
78
+ * recevoir de trafic : il répondrait des erreurs de colonne inconnue à des
79
+ * utilisateurs réels. Ailleurs, le retenir gênerait plus qu'il n'aiderait — un
80
+ * avertissement suffit.
81
+ *
82
+ * @param explicit - valeur écrite dans `migrations.check`, si elle l'est.
83
+ * @param env - environnement constaté.
84
+ * @returns la conduite effective.
85
+ */
86
+ function resolveCheckMode(explicit, env) {
87
+ if (explicit) return explicit;
88
+ return env.runtime === "production" ? "fail" : "warn";
89
+ }
90
+ /**
91
+ * Décrit ce qui porte un connecteur, pour le nommer dans un refus.
92
+ *
93
+ * Un message qui dit seulement « ce connecteur ne porte pas de migrations »
94
+ * laisse l'utilisateur sans recours : il ne sait ni ce que c'est, ni où
95
+ * regarder. On nomme donc la classe qui le sert et la base qu'elle adresse.
96
+ *
97
+ * @param name - nom du connecteur dans le registre.
98
+ * @returns une description courte (`mongoose (mongodb)`), ou le nom de classe seul.
99
+ */
100
+ function describeOwner(name) {
101
+ let orm;
102
+ try {
103
+ orm = ormRegistry.get(name);
104
+ } catch {
105
+ return {
106
+ label: "un ORM inconnu",
107
+ driver: ""
108
+ };
109
+ }
110
+ const klass = orm?.constructor?.name;
111
+ let driver = "";
112
+ try {
113
+ driver = orm?.describeConnection?.().driver ?? "";
114
+ } catch {
115
+ driver = "";
116
+ }
117
+ return {
118
+ label: klass && driver ? `${klass} (${driver})` : klass || driver || "un ORM inconnu",
119
+ driver
120
+ };
121
+ }
122
+ /**
123
+ * Bases SQL connues — celles dont le schéma se fait par migrations versionnées.
124
+ *
125
+ * La liste est CONSTATÉE sur ce que l'ORM dit de lui-même (`describeConnection`),
126
+ * jamais déduite d'un test d'instance : à la frontière npm, deux copies du même
127
+ * paquet font échouer `instanceof` sans un mot, et le repli serait ici un
128
+ * message FAUX. Un nom absent de cette liste ne provoque aucune casse : il fait
129
+ * seulement dire « pas de migrations pour cette base », ce qui reste vrai tant
130
+ * qu'aucun applicateur ne la sert.
131
+ */
132
+ const SQL_DRIVERS = /* @__PURE__ */ new Set([
133
+ "sqlite",
134
+ "sqlite3",
135
+ "postgres",
136
+ "postgresql",
137
+ "mysql",
138
+ "mariadb"
139
+ ]);
140
+ /**
141
+ * Tous les noms de connecteurs qui existent, tous ORM confondus.
142
+ *
143
+ * L'union du registre et de la configuration : le registre porte ce qui est
144
+ * connecté, la configuration ce qui est déclaré. Un connecteur déclaré mais non
145
+ * connecté doit apparaître dans la liste — sinon un utilisateur qui a fait une
146
+ * faute de frappe se voit répondre que son connecteur n'existe pas ET ne le
147
+ * voit pas dans la liste, alors qu'il est bien écrit dans son fichier.
148
+ *
149
+ * @param config - configuration validée du module drizzle.
150
+ * @returns les noms, triés, sans doublon.
151
+ */
152
+ function knownConnectors(config) {
153
+ const names = new Set(ormRegistry.list());
154
+ for (const name of Object.keys(config.connectors ?? {})) names.add(name);
155
+ return [...names].sort();
156
+ }
157
+ /**
158
+ * Résout un nom de connecteur en l'une des **trois** réponses possibles.
159
+ *
160
+ * Ces trois réponses sont un contrat, pas un détail d'implémentation : le jour
161
+ * où un second ORM apporte ses propres migrations, c'est `unsupported` qui doit
162
+ * cesser de sortir pour ses connecteurs — et rien d'autre ne bouge. Répondre
163
+ * « ne porte pas de migrations » à un connecteur qui en porterait serait un
164
+ * message FAUX, appris par les scripts qui le lisent.
165
+ *
166
+ * La propriété se **constate** sur la configuration du module, jamais par un
167
+ * test d'instance : à la frontière npm, deux copies du même paquet font échouer
168
+ * `instanceof` sans un mot.
169
+ *
170
+ * @param connector - nom demandé.
171
+ * @param config - configuration validée du module drizzle.
172
+ * @param env - environnement constaté.
173
+ * @param kernel - kernel courant, indispensable au chemin SQLite par défaut.
174
+ * @param options - `allowMigrateUrl` autorise {@link MIGRATE_URL_ENV} à primer.
175
+ * @returns la réponse, discriminée par `kind`.
176
+ */
177
+ function resolveConnector(connector, config, env, kernel, options = {}) {
178
+ const declared = config.connectors?.[connector];
179
+ if (!declared) {
180
+ if (ormRegistry.has(connector)) {
181
+ const owner = describeOwner(connector);
182
+ return {
183
+ kind: "unsupported",
184
+ connector,
185
+ owner: owner.label,
186
+ sqlLike: SQL_DRIVERS.has(owner.driver.toLowerCase()),
187
+ driver: owner.driver
188
+ };
189
+ }
190
+ return {
191
+ kind: "unknown",
192
+ connector,
193
+ known: knownConnectors(config)
194
+ };
195
+ }
196
+ const base = resolveConnectorTarget(kernel, connector, declared);
197
+ const dialect = base.dialect;
198
+ const migrateUrl = options.allowMigrateUrl ? process.env[MIGRATE_URL_ENV] : void 0;
199
+ if (migrateUrl) {
200
+ const vise = describeMigrateUrl(migrateUrl);
201
+ if (vise === null || vise !== dialect) return {
202
+ kind: "url-mismatch",
203
+ connector,
204
+ dialect,
205
+ urlDialect: vise
206
+ };
207
+ }
208
+ const parsedUrl = migrateUrl ? parseDatabaseUrl(migrateUrl) : null;
209
+ const fromMigrateUrl = parsedUrl !== null;
210
+ return {
211
+ kind: "ready",
212
+ connector,
213
+ dialect,
214
+ target: {
215
+ dialect,
216
+ filename: fromMigrateUrl && dialect === "sqlite" ? sqliteFilenameFromUrl(parsedUrl.url) : base.filename,
217
+ url: fromMigrateUrl && dialect !== "sqlite" ? parsedUrl.url : base.url
218
+ },
219
+ fromMigrateUrl,
220
+ ddl: resolveDdlMode(declared.ddl, env)
221
+ };
222
+ }
223
+ /**
224
+ * Dialecte que désigne une URL de migration, ou `null` si elle n'en désigne aucun.
225
+ *
226
+ * Ne jette jamais : une URL illisible est un cas d'usage à REFUSER avec une
227
+ * phrase, pas une exception qui remonte en pile d'appels au milieu d'un
228
+ * déploiement.
229
+ *
230
+ * @param url - valeur brute de la variable.
231
+ * @returns le dialecte SQL visé, ou `null` (URL invalide, ou base non SQL).
232
+ */
233
+ function describeMigrateUrl(url) {
234
+ try {
235
+ const vue = parseDatabaseUrl(url);
236
+ return vue.family === "sql" ? vue.dialect : null;
237
+ } catch {
238
+ return null;
239
+ }
240
+ }
241
+ /**
242
+ * Dossier de migrations de l'application, résolu depuis la racine que le kernel
243
+ * connaît.
244
+ *
245
+ * Jamais depuis le répertoire courant du processus : un espace de travail en a
246
+ * plusieurs, et la commande serait juste ou fausse selon l'endroit d'où on la
247
+ * tape — le pire des comportements, parce qu'il marche une fois sur deux.
248
+ *
249
+ * @param kernel - kernel courant.
250
+ * @param dir - valeur de `migrations.dir` (relative, ou absolue si l'app le veut).
251
+ * @returns le chemin absolu, ou `undefined` sans kernel.
252
+ */
253
+ function appMigrationsDir(kernel, dir) {
254
+ const root = typeof kernel?.path === "string" ? kernel.path : void 0;
255
+ if (!root) return;
256
+ return path.isAbsolute(dir) ? dir : path.resolve(root, dir);
257
+ }
258
+ /**
259
+ * Construit l'applicateur d'un connecteur résolu.
260
+ *
261
+ * @param resolution - réponse `ready` de {@link resolveConnector}.
262
+ * @param config - configuration validée du module drizzle.
263
+ * @param kernel - kernel courant (racine de l'application).
264
+ * @returns l'applicateur, sources du framework et de l'application chargées.
265
+ */
266
+ async function buildMigrator(resolution, config, kernel) {
267
+ const appDir = appMigrationsDir(kernel, config.migrations.dir);
268
+ const sources = await defaultMigrationSources(appDir, { framework: config.frameworkEntities !== false });
269
+ return new DrizzleMigrator({
270
+ connector: resolution.connector,
271
+ ...resolution.target,
272
+ sources,
273
+ lockTimeoutMs: config.migrations.lockTimeoutMs
274
+ });
275
+ }
276
+ /** Conduite face à la divergence, telle que la configuration la déclare. */
277
+ function resolveDivergenceMode(config) {
278
+ return config.migrations.divergence;
279
+ }
280
+ //#endregion
281
+ export { MIGRATE_URL_ENV, appMigrationsDir, buildMigrator, knownConnectors, readMigrationEnv, resetAllowed, resolveCheckMode, resolveConnector, resolveDdlMode, resolveDivergenceMode };
@@ -0,0 +1,86 @@
1
+ //#region nodefony/src/migrator/schemaDiff.ts
2
+ /**
3
+ * Nomme ce qui manque, en trois mots plutôt qu'en trois lignes.
4
+ *
5
+ * Un refus qui dit « la base diverge » sans dire OÙ oblige à rouvrir un client
6
+ * SQL — c'est-à-dire exactement le geste que ces commandes existent pour éviter.
7
+ *
8
+ * Écrite ici parce qu'elle a DEUX lecteurs, et qu'ils doivent nommer l'écart de
9
+ * la même façon : l'adoption d'une base existante, et le générateur quand il
10
+ * n'a rien à écrire. Deux formulations pour un même fait apprendraient à leurs
11
+ * lecteurs qu'il s'agit de deux problèmes.
12
+ *
13
+ * @param c - les écarts, tels que la comparaison les rend.
14
+ * @returns une énumération courte, prête à entrer dans une phrase.
15
+ */
16
+ function summarizeGap(c) {
17
+ const bouts = [];
18
+ if (c.missingTables.length > 0) bouts.push(`table(s) absente(s) : ${c.missingTables.join(", ")}`);
19
+ const columns = [...c.blocking, ...c.additive].map((g) => `${g.table}.${g.column}`);
20
+ if (columns.length > 0) bouts.push(`colonne(s) absente(s) : ${columns.join(", ")}`);
21
+ return bouts.join(" · ");
22
+ }
23
+ /** La base s'écarte-t-elle du code ? */
24
+ function hasGap(c) {
25
+ return c.additive.length > 0 || c.blocking.length > 0 || c.missingTables.length > 0;
26
+ }
27
+ /**
28
+ * Compare le schéma déclaré au schéma réel, table par table.
29
+ *
30
+ * Une requête par table, et **au démarrage uniquement** : rien de ceci n'existe
31
+ * dans le chemin d'une requête.
32
+ *
33
+ * @param reader - lecteur de catalogue du porteur (ORM connecté, ou pilote).
34
+ * @param expected - schéma attendu (cf `DrizzleOrm.describeTables`).
35
+ * @returns les écarts, séparés selon qu'ils se rattrapent ou non.
36
+ */
37
+ async function compareSchema(reader, expected) {
38
+ const additive = [];
39
+ const blocking = [];
40
+ const missingTables = [];
41
+ for (const expectedTable of expected) {
42
+ if (!await reader.tableExists(expectedTable.table)) {
43
+ missingTables.push(expectedTable.table);
44
+ continue;
45
+ }
46
+ const actualColumns = await reader.columnsOf(expectedTable.table);
47
+ for (const col of expectedTable.columns) {
48
+ if (actualColumns.some((actualColumn) => reader.sameColumnName(col.name, actualColumn))) continue;
49
+ const gap = {
50
+ table: expectedTable.table,
51
+ column: col.name,
52
+ type: col.type,
53
+ nullable: col.nullable
54
+ };
55
+ if (col.nullable && !col.primaryKey) additive.push(gap);
56
+ else blocking.push(gap);
57
+ }
58
+ }
59
+ return {
60
+ additive,
61
+ blocking,
62
+ missingTables
63
+ };
64
+ }
65
+ /** Échappe un identifiant dans le dialecte visé. */
66
+ function ident(name, dialect) {
67
+ return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, "\"\"")}"`;
68
+ }
69
+ /**
70
+ * Le `ALTER TABLE` qui pose une colonne manquante.
71
+ *
72
+ * Aucune valeur par défaut n'est émise, et aucune contrainte : la colonne est
73
+ * ajoutée telle que le code la déclare, nullable. Ajouter un défaut ici
74
+ * reviendrait à inventer la donnée des lignes existantes.
75
+ *
76
+ * @param gap - la colonne manquante (elle DOIT accepter le vide).
77
+ * @param dialect - dialecte du connecteur.
78
+ * @returns le SQL, prêt à exécuter.
79
+ * @throws Error si l'on tente de rattraper une colonne obligatoire.
80
+ */
81
+ function additiveSql(gap, dialect) {
82
+ if (!gap.nullable) throw new Error(`Rattrapage refusé : la colonne « ${gap.table}.${gap.column} » est obligatoire. La poser exigerait d'inventer une valeur pour les lignes déjà présentes — c'est une décision métier, pas une décision d'outil.`);
83
+ return `ALTER TABLE ${ident(gap.table, dialect)} ADD COLUMN ${ident(gap.column, dialect)} ${gap.type}`;
84
+ }
85
+ //#endregion
86
+ export { additiveSql, compareSchema, hasGap, summarizeGap };