@nodefony/drizzle 10.0.0-alpha.1 → 10.0.0-alpha.3

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.
@@ -4,7 +4,7 @@ import { missingHistoryColumn } from "../src/migrator/history.js";
4
4
  import { buildMigrator, readMigrationEnv, resolveConnector } from "../src/migrator/resolve.js";
5
5
  import { MigrationToolError, describeResolutionRefusal, moduleAbsent } from "../src/migrator/refusals.js";
6
6
  import { composeReport } from "../src/migrator/status.js";
7
- import { Command, resolveColorEnabled } from "nodefony";
7
+ import { CONSOLE_DATA_RUN_PROFILE, Command, resolveColorEnabled } from "nodefony";
8
8
  //#region nodefony/command/migrateShared.ts
9
9
  /**
10
10
  * Socle commun des commandes `orm:*` — ce qui garantit qu'aucune d'elles ne
@@ -42,6 +42,20 @@ const MODULE_NAME = "drizzle";
42
42
  * différence entre un outil qu'on sait utiliser et un outil qu'on subit.
43
43
  */
44
44
  var OrmMigrateCommand = class extends Command {
45
+ /**
46
+ * Toutes les commandes `orm:*` parlent à la base — c'est leur définition.
47
+ *
48
+ * Déclaré ICI et pas dans le littéral d'options de chacune : la déclaration
49
+ * appartient à la FAMILLE, et une famille qui se déclare six fois finit par
50
+ * oublier la septième — l'oubli serait alors muet (la commande démarrerait
51
+ * sans connexion et ne trouverait pas de quoi migrer). Posé après `super()`,
52
+ * qui a déjà lu `options.runProfile` : une commande qui veut un profil autre
53
+ * peut donc encore le passer dans ses options, et il gagne.
54
+ */
55
+ constructor(...args) {
56
+ super(...args);
57
+ this.runProfile ??= { ...CONSOLE_DATA_RUN_PROFILE };
58
+ }
45
59
  /**
46
60
  * Faut-il colorer la sortie ?
47
61
  *
@@ -7,8 +7,8 @@ import { describeDivergence } from "../src/migrator/divergence.js";
7
7
  import { DrizzleOrm } from "../src/orm-core/DrizzleOrm.js";
8
8
  import "../src/orm-core/index.js";
9
9
  import { dataLoss, renderDestructive, scanDestructive, summarizeDestructive } from "../src/migrator/destructive.js";
10
- import { BootConfigurationError, Service } from "nodefony";
11
- import { queryFlowMonitor, resolveOrmFlowEnabled } from "@nodefony/orm-core";
10
+ import { BootConfigurationError, Service, runNeedsExternalServices } from "nodefony";
11
+ import { diagnoseConnectionFailure, parseConnectionTarget, queryFlowMonitor, resolveOrmFlowEnabled } from "@nodefony/orm-core";
12
12
  import fs from "node:fs";
13
13
  import path from "node:path";
14
14
  //#region nodefony/service/DrizzleService.ts
@@ -84,7 +84,7 @@ var DrizzleService = class extends Service {
84
84
  this.module = module;
85
85
  this.module.hookKernel("onBoot", async () => {
86
86
  queryFlowMonitor.setEnabled(resolveOrmFlowEnabled(this.kernel));
87
- await this.connectAll().catch((e) => {
87
+ await this.connectAll(runNeedsExternalServices(this.kernel)).catch((e) => {
88
88
  this.log(e, "ERROR");
89
89
  throw e;
90
90
  });
@@ -98,10 +98,17 @@ var DrizzleService = class extends Service {
98
98
  #config() {
99
99
  return this.module.config;
100
100
  }
101
- /** Connecte tous les connecteurs déclarés en config (validée Zod). */
102
- async connectAll() {
101
+ /**
102
+ * Connecte tous les connecteurs déclarés en config (validée Zod).
103
+ *
104
+ * @param connect - `false` pour ENREGISTRER les ORM sans ouvrir de connexion.
105
+ * Réservé au boot d'un run qui n'a pas déclaré `externalServices` : l'ORM
106
+ * doit exister (c'est lui qui publie le plan d'administration) sans qu'un
107
+ * socket ne s'ouvre. Le défaut `true` fait de l'appel direct un ordre.
108
+ */
109
+ async connectAll(connect = true) {
103
110
  const connectors = this.#config()?.connectors ?? {};
104
- for (const [name, cfg] of Object.entries(connectors)) await this.#connectOne(name, cfg);
111
+ for (const [name, cfg] of Object.entries(connectors)) await this.#connectOne(name, cfg, connect);
105
112
  }
106
113
  /**
107
114
  * Chemin SQLite par défaut d'un connecteur, résolu AU BOOT (kernel présent —
@@ -116,7 +123,7 @@ var DrizzleService = class extends Service {
116
123
  return defaultConnectorFilename(this.kernel, name);
117
124
  }
118
125
  /** Connecte un connecteur (crée le dossier de la base SQLite si nécessaire). */
119
- async #connectOne(name, cfg) {
126
+ async #connectOne(name, cfg, connect = true) {
120
127
  const dialect = cfg.dialect ?? "sqlite";
121
128
  const ddl = resolveDdlMode(cfg.ddl, readMigrationEnv(this.kernel));
122
129
  let filename;
@@ -146,11 +153,20 @@ var DrizzleService = class extends Service {
146
153
  }
147
154
  });
148
155
  const target = dialect === "sqlite" ? filename : redactUrl(cfg.url);
156
+ if (!connect) {
157
+ this.#orms.set(name, orm);
158
+ this.log(`Drizzle « ${name} » enregistré SANS connexion (${dialect}: ${target}) — ce run n'a pas déclaré \`externalServices\`. Une commande qui lit ou écrit des données le déclare via son \`runProfile\` (CONSOLE_DATA_RUN_PROFILE).`, "DEBUG");
159
+ return;
160
+ }
149
161
  try {
150
162
  await orm.connect();
151
163
  } catch (e) {
152
164
  const cause = e instanceof Error ? e.message : String(e);
153
- throw new BootConfigurationError(`Drizzle : le connecteur "${name}" (${dialect}: ${target}) n'a pas pu se connecter — corriger la configuration (infra déclarée NF_DATABASE_URL/connectors, base démarrée ?, entités portées sur ce dialecte ?) ou la retirer. Cause : ${cause}`, { cause: e });
165
+ const diagnosis = diagnoseConnectionFailure(e, dialect === "sqlite" ? {
166
+ host: null,
167
+ port: null
168
+ } : parseConnectionTarget(cfg.url));
169
+ throw new BootConfigurationError(`Drizzle : le connecteur "${name}" (${dialect}: ${target}) n'a pas pu se connecter — ${diagnosis.explanation} Si l'adresse est la bonne, vérifier l'infrastructure déclarée (NF_DATABASE_URL / connectors) et que les entités sont portées sur ce dialecte, ou retirer le connecteur. Cause : ${cause}`, { cause: e });
154
170
  }
155
171
  this.#orms.set(name, orm);
156
172
  this.log(`Drizzle « ${name} » connecté (${dialect}: ${target}) — schéma : ${ddl} ${DDL_EXPLAINED[ddl]}`, "INFO");
@@ -6,7 +6,7 @@ import { failureFrom } from "../migrator/status.js";
6
6
  import { DrizzleRepository } from "./DrizzleRepository.js";
7
7
  import { DrizzleTransaction } from "./DrizzleTransaction.js";
8
8
  import { createRequire } from "node:module";
9
- import { Orm, entityRegistry } from "@nodefony/orm-core";
9
+ import { CONNECT_TIMEOUT_MS, Orm, entityRegistry, parseConnectionTarget, withConnectDeadline } from "@nodefony/orm-core";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
12
12
  import { is } from "drizzle-orm";
@@ -50,6 +50,22 @@ function renderCheck(check, dialect) {
50
50
  * Trappe SQL brut : {@link DrizzleOrm.getNativeConnection} expose le db Drizzle
51
51
  * (tag `sql`) pour les jointures arbitraires (ADR-0003 risque #1).
52
52
  */
53
+ /**
54
+ * L'adresse visée, écrite pour un humain et SANS son secret.
55
+ *
56
+ * Une URL de connexion porte le mot de passe : la recopier telle quelle dans
57
+ * un message d'erreur le publie dans les journaux. `parseConnectionTarget` ne
58
+ * rend que l'hôte et le port — c'est tout ce dont on a besoin pour aller voir
59
+ * qui écoute.
60
+ *
61
+ * @param url - l'URL du connecteur, si elle existe.
62
+ * @returns `hôte:port`, ou une désignation neutre quand l'URL ne dit rien.
63
+ */
64
+ function describeTarget(url) {
65
+ const { host, port } = parseConnectionTarget(url);
66
+ if (host && port !== null) return `${host}:${port}`;
67
+ return host ?? "configuré";
68
+ }
53
69
  var DrizzleOrm = class DrizzleOrm extends Orm {
54
70
  #client = null;
55
71
  /**
@@ -598,11 +614,13 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
598
614
  const pool = new PoolCtor({
599
615
  connectionString: this.#url,
600
616
  keepAlive: true,
601
- keepAliveInitialDelayMillis: 1e4
617
+ keepAliveInitialDelayMillis: 1e4,
618
+ connectionTimeoutMillis: CONNECT_TIMEOUT_MS,
619
+ query_timeout: CONNECT_TIMEOUT_MS
602
620
  });
603
621
  this.#wirePgLifecycle(pool);
604
622
  try {
605
- await pool.query("SELECT 1");
623
+ await withConnectDeadline(pool.query("SELECT 1"), `la réponse de postgres ${describeTarget(this.#url)}`);
606
624
  } catch (e) {
607
625
  this.#unwireAll();
608
626
  await pool.end().catch(() => void 0);
@@ -778,14 +796,15 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
778
796
  uri: this.#url,
779
797
  timezone: "Z",
780
798
  enableKeepAlive: true,
781
- keepAliveInitialDelay: 1e4
799
+ keepAliveInitialDelay: 1e4,
800
+ connectTimeout: CONNECT_TIMEOUT_MS
782
801
  });
783
802
  this.#wireMysqlLifecycle(pool);
784
803
  } catch (e) {
785
804
  throw new Error(`DrizzleOrm "${this.name}": the mysql dialect needs the optional driver \`mysql2\` (run \`npm i mysql2\`). ${e.message}`, { cause: e });
786
805
  }
787
806
  try {
788
- await pool.query("SELECT 1");
807
+ await withConnectDeadline(pool.query("SELECT 1"), `la réponse de mysql ${describeTarget(this.#url)}`);
789
808
  } catch (e) {
790
809
  this.#unwireAll();
791
810
  await pool.end().catch(() => void 0);
@@ -21,6 +21,17 @@ export interface IMigrateSharedOptions {
21
21
  * différence entre un outil qu'on sait utiliser et un outil qu'on subit.
22
22
  */
23
23
  export declare abstract class OrmMigrateCommand extends Command {
24
+ /**
25
+ * Toutes les commandes `orm:*` parlent à la base — c'est leur définition.
26
+ *
27
+ * Déclaré ICI et pas dans le littéral d'options de chacune : la déclaration
28
+ * appartient à la FAMILLE, et une famille qui se déclare six fois finit par
29
+ * oublier la septième — l'oubli serait alors muet (la commande démarrerait
30
+ * sans connexion et ne trouverait pas de quoi migrer). Posé après `super()`,
31
+ * qui a déjà lu `options.runProfile` : une commande qui veut un profil autre
32
+ * peut donc encore le passer dans ses options, et il gagne.
33
+ */
34
+ constructor(...args: ConstructorParameters<typeof Command>);
24
35
  /**
25
36
  * Faut-il colorer la sortie ?
26
37
  *
@@ -17,8 +17,15 @@ declare class DrizzleService extends Service {
17
17
  #private;
18
18
  module: Module;
19
19
  constructor(module: Module);
20
- /** Connecte tous les connecteurs déclarés en config (validée Zod). */
21
- connectAll(): Promise<void>;
20
+ /**
21
+ * Connecte tous les connecteurs déclarés en config (validée Zod).
22
+ *
23
+ * @param connect - `false` pour ENREGISTRER les ORM sans ouvrir de connexion.
24
+ * Réservé au boot d'un run qui n'a pas déclaré `externalServices` : l'ORM
25
+ * doit exister (c'est lui qui publie le plan d'administration) sans qu'un
26
+ * socket ne s'ouvre. Le défaut `true` fait de l'appel direct un ordre.
27
+ */
28
+ connectAll(connect?: boolean): Promise<void>;
22
29
  /** Ferme toutes les connexions. */
23
30
  disconnectAll(): Promise<void>;
24
31
  /** Retourne l'ORM Drizzle d'un connecteur (défaut : `"default"`). */
@@ -48,24 +48,6 @@ export interface DrizzleOrmOptions {
48
48
  /** Qui sait APPLIQUER — refuse hors développement, en le disant. */
49
49
  applyMigrations?: () => Promise<IOrmMigrationApplyReply>;
50
50
  }
51
- /**
52
- * Adapter Drizzle (driver `better-sqlite3`) **branché sur `@nodefony/orm-core`**
53
- * — 3ᵉ adapter du banc multi-ORM (P7.4), choix SQL #1 moderne.
54
- *
55
- * Particularité vs les autres ORM : Drizzle est **schema-as-code** — il n'y
56
- * a pas de « compilation » de modèle. `entity.schema` *est* déjà une table
57
- * Drizzle (`sqliteTable(...)`). L'adapter :
58
- * - dérive le DDL de chaque table via `getTableConfig()` et le crée (dev/test ;
59
- * la prod passera par `drizzle-kit`) — pas de `sync()` natif côté Drizzle ;
60
- * - résout les relations déclaratives ({@link IEntityRelation}) en métadonnées
61
- * d'**eager-load manuel** (cf {@link DrizzleRepository}), pour rester générique
62
- * sans imposer la couche `relations()` de Drizzle ;
63
- * - pilote les transactions à la main (`BEGIN`/`COMMIT`/`ROLLBACK`) car
64
- * `better-sqlite3` est synchrone (cf {@link DrizzleTransaction}).
65
- *
66
- * Trappe SQL brut : {@link DrizzleOrm.getNativeConnection} expose le db Drizzle
67
- * (tag `sql`) pour les jointures arbitraires (ADR-0003 risque #1).
68
- */
69
51
  export declare class DrizzleOrm extends Orm {
70
52
  #private;
71
53
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/drizzle",
3
- "version": "10.0.0-alpha.1",
3
+ "version": "10.0.0-alpha.3",
4
4
  "description": "Moteur SQL de Nodefony sur Drizzle ORM (SQLite, PostgreSQL, MySQL) : dépôts typés, migrations de schéma et adoption d'une base existante",
5
5
  "nodefony": {
6
6
  "storeKind": "durable",
@@ -60,30 +60,30 @@
60
60
  "zod": "^4.4.3"
61
61
  },
62
62
  "devDependencies": {
63
- "@nodefony/framework": "*",
64
- "@nodefony/http": "*",
65
- "@nodefony/orm-core": "*",
66
- "@nodefony/security": "*",
67
- "@nodefony/user": "*",
63
+ "@nodefony/framework": "^10.0.0-alpha.3",
64
+ "@nodefony/http": "^10.0.0-alpha.3",
65
+ "@nodefony/orm-core": "^10.0.0-alpha.3",
66
+ "@nodefony/security": "^10.0.0-alpha.3",
67
+ "@nodefony/user": "^10.0.0-alpha.3",
68
68
  "@types/better-sqlite3": "9.6.0",
69
69
  "@types/node": "26.4.1",
70
70
  "@types/pg": "8.23.1",
71
71
  "better-sqlite3": "13.0.3",
72
72
  "drizzle-kit": "0.31.10",
73
73
  "mysql2": "3.24.3",
74
- "nodefony": "*",
74
+ "nodefony": "^10.0.0-alpha.3",
75
75
  "pg": "8.23.0",
76
76
  "rimraf": "6.1.3"
77
77
  },
78
78
  "peerDependencies": {
79
- "@nodefony/framework": "*",
80
- "@nodefony/http": "*",
81
- "@nodefony/orm-core": "*",
82
- "@nodefony/security": "*",
83
- "@nodefony/user": "*",
79
+ "@nodefony/framework": "^10.0.0-alpha.3",
80
+ "@nodefony/http": "^10.0.0-alpha.3",
81
+ "@nodefony/orm-core": "^10.0.0-alpha.3",
82
+ "@nodefony/security": "^10.0.0-alpha.3",
83
+ "@nodefony/user": "^10.0.0-alpha.3",
84
84
  "better-sqlite3": "^13.0.0",
85
85
  "mysql2": "^3.24.0",
86
- "nodefony": "*",
86
+ "nodefony": "^10.0.0-alpha.3",
87
87
  "pg": "^8.23.0"
88
88
  },
89
89
  "repository": {