@nodefony/drizzle 10.0.0-beta.1 → 10.0.0-beta.2
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 +9 -7
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +2 -2
- package/dist/nodefony/src/migrator/drivers/sqliteDriver.js +6 -3
- package/dist/nodefony/src/orm-core/DrizzleOrm.js +6 -3
- package/dist/nodefony/src/orm-core/DrizzleRepository.js +106 -16
- package/dist/nodefony/src/sqliteJournal.js +54 -0
- package/dist/types/nodefony/src/sqliteJournal.d.ts +53 -0
- package/docs/index.md +43 -32
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -4,14 +4,15 @@ Adapter [Drizzle ORM](https://orm.drizzle.team/) pour Nodefony, branché sur
|
|
|
4
4
|
[`@nodefony/orm-core`](https://www.npmjs.com/package/@nodefony/orm-core). driver concret du socle multi-ORM (avec
|
|
5
5
|
`@nodefony/mongoose`), **type-safe-first**.
|
|
6
6
|
|
|
7
|
-
> Driver
|
|
7
|
+
> Driver SQLite : `better-sqlite3` (dépendance de pair optionnelle, à installer avec le dialecte
|
|
8
|
+
> `sqlite`). Pour Postgres/MySQL,
|
|
8
9
|
> changer le client et le constructeur de table (`pgTable`/`mysqlTable`) — le
|
|
9
10
|
> contrat `IRepository` reste identique.
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
|
-
npm install @nodefony/drizzle@
|
|
15
|
+
npm install @nodefony/drizzle@beta drizzle-orm better-sqlite3
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
## Utilisation comme module Nodefony (bootable)
|
|
@@ -43,8 +44,8 @@ l'override générique `NF__DRIZZLE__<CHEMIN>`. Sans `filename`, un connecteur S
|
|
|
43
44
|
Au runtime, l'ORM est récupérable via le registre :
|
|
44
45
|
|
|
45
46
|
```typescript
|
|
46
|
-
import {
|
|
47
|
-
const orm =
|
|
47
|
+
import { ormRegistry } from "@nodefony/orm-core";
|
|
48
|
+
const orm = ormRegistry.get("default");
|
|
48
49
|
const users = orm.getRepository("User");
|
|
49
50
|
```
|
|
50
51
|
|
|
@@ -83,7 +84,7 @@ const users = orm.getRepository<{ id: string; email: string; age: number }>(
|
|
|
83
84
|
const u = await users.create({ email: "a@b.c", age: 30 });
|
|
84
85
|
await users.findOne({ id: u.id });
|
|
85
86
|
await users.find(); // tous
|
|
86
|
-
await users.
|
|
87
|
+
await users.updateOne({ id: u.id }, { age: 31 });
|
|
87
88
|
await users.delete({ id: u.id });
|
|
88
89
|
await users.count();
|
|
89
90
|
```
|
|
@@ -101,7 +102,8 @@ await users.find({ age: { $ne: 30 } });
|
|
|
101
102
|
await users.find({ email: { $like: "u2%" } }); // sémantique SQL (`%`, `_`)
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
Opérateurs supportés : `$eq $ne $gt $gte $lt $lte $in $nin $like
|
|
105
|
+
Opérateurs supportés : `$eq $ne $gt $gte $lt $lte $in $nin $like $null`, et `$or` pour une
|
|
106
|
+
disjonction. Une valeur nue
|
|
105
107
|
(`{ email: "a@b.c" }`) = égalité.
|
|
106
108
|
|
|
107
109
|
## Relations & eager-load
|
|
@@ -153,7 +155,7 @@ const rows = db.all(sql`SELECT ... JOIN ...`);
|
|
|
153
155
|
## Notes
|
|
154
156
|
|
|
155
157
|
- **Dev/test** : le schéma SQLite est créé automatiquement (DDL dérivé des tables).
|
|
156
|
-
**Production** :
|
|
158
|
+
**Production** : `nodefony orm:generate` puis `nodefony orm:migrate` (voir `docs/migrations.md`).
|
|
157
159
|
- `better-sqlite3` est **synchrone** : les transactions sont pilotées manuellement
|
|
158
160
|
(`BEGIN`/`COMMIT`/`ROLLBACK`) pour rester compatibles avec le contrat async.
|
|
159
161
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorate.js
|
|
2
2
|
function __decorate(decorators, target, key, desc) {
|
|
3
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorateMetadata.js
|
|
2
2
|
function __decorateMetadata(k, v) {
|
|
3
3
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
4
4
|
}
|
package/dist/index.js
CHANGED
|
@@ -38,8 +38,8 @@ import OrmMigrateStatus from "./nodefony/command/orm-migrate-status.js";
|
|
|
38
38
|
import OrmMigrateBaseline from "./nodefony/command/orm-migrate-baseline.js";
|
|
39
39
|
import OrmMigrateRepair from "./nodefony/command/orm-migrate-repair.js";
|
|
40
40
|
import OrmReset from "./nodefony/command/orm-reset.js";
|
|
41
|
-
import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.
|
|
42
|
-
import __decorate from "./_virtual/_@oxc-project_runtime@0.
|
|
41
|
+
import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
|
|
42
|
+
import __decorate from "./_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
|
|
43
43
|
import SessionStorage from "./nodefony/src/SessionStorage.js";
|
|
44
44
|
import "./nodefony/src/migrator/index.js";
|
|
45
45
|
import { DrizzleUserRepository } from "./nodefony/src/DrizzleUserRepository.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HISTORY_TABLE } from "../types.js";
|
|
2
2
|
import { schemaReader } from "../catalog.js";
|
|
3
3
|
import "../../memoryDatabase.js";
|
|
4
|
+
import { enableWriteAheadLog } from "../../sqliteJournal.js";
|
|
4
5
|
//#region nodefony/src/migrator/drivers/sqliteDriver.ts
|
|
5
6
|
/**
|
|
6
7
|
* Pilote SQLite de l'applicateur — connexion `better-sqlite3` dédiée.
|
|
@@ -53,9 +54,11 @@ var SqliteMigrationDriver = class {
|
|
|
53
54
|
throw new Error(`Le dialecte sqlite exige le pilote optionnel \`better-sqlite3\` (\`npm i better-sqlite3\`). ${e.message}`, { cause: e });
|
|
54
55
|
}
|
|
55
56
|
const db = new Sqlite(this.#filename);
|
|
56
|
-
if (this.#filename !== ":memory:") {
|
|
57
|
-
db
|
|
58
|
-
|
|
57
|
+
if (this.#filename !== ":memory:") try {
|
|
58
|
+
await enableWriteAheadLog(db);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
db.close();
|
|
61
|
+
throw e;
|
|
59
62
|
}
|
|
60
63
|
this.#database = db;
|
|
61
64
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { schemaReader, toDollarParams } from "../migrator/catalog.js";
|
|
2
2
|
import "../memoryDatabase.js";
|
|
3
|
+
import { enableWriteAheadLog } from "../sqliteJournal.js";
|
|
3
4
|
import { notConfigured } from "../migrator/refusals.js";
|
|
4
5
|
import { additiveSql, compareSchema, hasGap } from "../migrator/schemaDiff.js";
|
|
5
6
|
import { describeTargetSafely } from "../safeTarget.js";
|
|
@@ -475,9 +476,11 @@ var DrizzleOrm = class DrizzleOrm extends Orm {
|
|
|
475
476
|
throw new Error(`DrizzleOrm "${this.name}": the sqlite dialect needs the optional driver \`better-sqlite3\` (run \`npm i better-sqlite3\`). ` + e.message, { cause: e });
|
|
476
477
|
}
|
|
477
478
|
const client = new Sqlite(this.#filename);
|
|
478
|
-
if (this.#filename !== ":memory:") {
|
|
479
|
-
client
|
|
480
|
-
|
|
479
|
+
if (this.#filename !== ":memory:") try {
|
|
480
|
+
await enableWriteAheadLog(client);
|
|
481
|
+
} catch (e) {
|
|
482
|
+
client.close();
|
|
483
|
+
throw e;
|
|
481
484
|
}
|
|
482
485
|
client.pragma("foreign_keys = ON");
|
|
483
486
|
this.#client = client;
|
|
@@ -24,6 +24,25 @@ const PREPARED_CACHE_MAX = 128;
|
|
|
24
24
|
/** Séquence des noms de prepared statements (PG les exige uniques par process). */
|
|
25
25
|
let preparedNameSeq = 0;
|
|
26
26
|
/**
|
|
27
|
+
* Les comparaisons scalaires, dans l'ordre où elles entrent dans le `WHERE`.
|
|
28
|
+
*
|
|
29
|
+
* UNE table pour le chemin direct (`#pushOperators`) ET le chemin préparé
|
|
30
|
+
* (`#buildPreparedSelect`) : deux listes recopiées finiraient par traduire le
|
|
31
|
+
* même critère en deux SQL différents, selon que la forme est en cache ou non.
|
|
32
|
+
* Leur texte SQL ne dépend que de l'opérateur, jamais de la valeur — c'est ce
|
|
33
|
+
* qui les rend préparables (≠ `$in`/`$nin`, dont le SQL suit la cardinalité).
|
|
34
|
+
*/
|
|
35
|
+
const COMPARISONS = [
|
|
36
|
+
["$eq", eq],
|
|
37
|
+
["$ne", ne],
|
|
38
|
+
["$gt", gt],
|
|
39
|
+
["$gte", gte],
|
|
40
|
+
["$lt", lt],
|
|
41
|
+
["$lte", lte]
|
|
42
|
+
];
|
|
43
|
+
/** Opérateurs qu'une forme préparée sait rendre : les comparaisons et `$null`. */
|
|
44
|
+
const PREPARABLE_OPERATORS = /* @__PURE__ */ new Set([...COMPARISONS.map(([op]) => op), "$null"]);
|
|
45
|
+
/**
|
|
27
46
|
* Repository portable (contrat {@link IRepository}) au-dessus d'une table Drizzle
|
|
28
47
|
* + driver `better-sqlite3`.
|
|
29
48
|
*
|
|
@@ -83,9 +102,18 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
83
102
|
this.#dialect = dialect;
|
|
84
103
|
this.#transactional = transactional;
|
|
85
104
|
}
|
|
86
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Colonne Drizzle d'une table par nom logique ; `undefined` si elle n'existe pas.
|
|
107
|
+
*
|
|
108
|
+
* 🔴 `Object.hasOwn` sur l'objet des COLONNES, jamais `table[name]` : le nom
|
|
109
|
+
* vient souvent de la requête (`?sort=`), et une clé héritée du prototype
|
|
110
|
+
* (`constructor`, `__proto__`, `toString`) ne vaut pas `undefined` — elle
|
|
111
|
+
* franchissait la liste blanche et cassait plus loin dans le pilote.
|
|
112
|
+
* `getTableColumns` rend l'objet tel quel, sans copie.
|
|
113
|
+
*/
|
|
87
114
|
#col(table, name) {
|
|
88
|
-
|
|
115
|
+
const columns = getTableColumns(table);
|
|
116
|
+
return Object.hasOwn(columns, name) ? columns[name] : void 0;
|
|
89
117
|
}
|
|
90
118
|
/**
|
|
91
119
|
* Colonne EXIGÉE : un nom inconnu lève au lieu de partir dans le SQL.
|
|
@@ -138,9 +166,21 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
138
166
|
*
|
|
139
167
|
* Fallback sans PK déclarée : `rowid` (SQLite-only — toutes les entités
|
|
140
168
|
* framework ont une PK ; une table d'app sans PK est un cas sqlite assumé).
|
|
169
|
+
*
|
|
170
|
+
* Court-circuit : un critère qui fixe TOUTES les colonnes de la PK par une
|
|
171
|
+
* égalité désigne déjà au plus une ligne — le `WHERE` nu suffit, et la
|
|
172
|
+
* sous-requête n'est plus payée. Elle coûtait ~60 µs par UPDATE en SQLite
|
|
173
|
+
* (recompilation d'un SQL plus long + table dérivée matérialisée, mesuré par
|
|
174
|
+
* `scripts/micro/micro-update-pick.mjs` du skill `nodefony-load-test`), soit
|
|
175
|
+
* l'essentiel de l'écart du banc ORM face à NestJS (#510).
|
|
176
|
+
*
|
|
177
|
+
* @param criteria - critère portable de la mutation.
|
|
178
|
+
* @returns le prédicat qui borne la mutation à au plus une ligne.
|
|
141
179
|
*/
|
|
142
|
-
#pickOne(
|
|
180
|
+
#pickOne(criteria) {
|
|
181
|
+
const where = this.#where(criteria);
|
|
143
182
|
const pk = this.#pkColumns();
|
|
183
|
+
if (where && pk && this.#pinsPrimaryKey(pk, criteria)) return where;
|
|
144
184
|
if (!pk) return where ? sql`rowid in (select rowid from ${this.#table} where ${where} limit 1)` : sql`rowid in (select rowid from ${this.#table} limit 1)`;
|
|
145
185
|
const qualified = sql.join(pk, sql.raw(", "));
|
|
146
186
|
const inner = where ? sql`select ${qualified} from ${this.#table} where ${where} limit 1` : sql`select ${qualified} from ${this.#table} limit 1`;
|
|
@@ -150,6 +190,25 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
150
190
|
return sql`${target} in (select ${output} from (${inner}) as picked)`;
|
|
151
191
|
}
|
|
152
192
|
/**
|
|
193
|
+
* Vrai si le critère fixe chaque colonne de la PK par une égalité à une
|
|
194
|
+
* valeur (ni `null`, ni opérateur, ni branche de `$or`) — l'unicité de la
|
|
195
|
+
* PK garantit alors « au plus une ligne » sans sous-requête.
|
|
196
|
+
*
|
|
197
|
+
* @param pk - colonnes de la PK (au plus 31 : masque de bits).
|
|
198
|
+
* @param criteria - critère portable de la mutation.
|
|
199
|
+
*/
|
|
200
|
+
#pinsPrimaryKey(pk, criteria) {
|
|
201
|
+
if (criteria === void 0 || pk.length > 31) return false;
|
|
202
|
+
let pinned = 0;
|
|
203
|
+
for (const [field, value] of Object.entries(criteria)) {
|
|
204
|
+
if (field === "$or" || value === null || value === void 0 || isFieldOperators(value)) continue;
|
|
205
|
+
const col = this.#col(this.#table, field);
|
|
206
|
+
const i = col === void 0 ? -1 : pk.indexOf(col);
|
|
207
|
+
if (i !== -1) pinned |= 1 << i;
|
|
208
|
+
}
|
|
209
|
+
return pinned === (1 << pk.length) - 1;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
153
212
|
* Lignes affectées, normalisé par driver : better-sqlite3 `{changes}` /
|
|
154
213
|
* pg `{rowCount}` / mysql2 tuple `[ResultSetHeader{affectedRows}, fields]`.
|
|
155
214
|
*/
|
|
@@ -228,12 +287,10 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
228
287
|
}
|
|
229
288
|
/** Empile les conditions d'un objet d'opérateurs riches sur une colonne. */
|
|
230
289
|
#pushOperators(conds, col, ops) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (ops.$lt !== void 0) conds.push(lt(col, ops.$lt));
|
|
236
|
-
if (ops.$lte !== void 0) conds.push(lte(col, ops.$lte));
|
|
290
|
+
for (const [op, compare] of COMPARISONS) {
|
|
291
|
+
const value = ops[op];
|
|
292
|
+
if (value !== void 0) conds.push(compare(col, value));
|
|
293
|
+
}
|
|
237
294
|
if (ops.$in !== void 0) conds.push(inArray(col, [...ops.$in]));
|
|
238
295
|
if (ops.$nin !== void 0) conds.push(notInArray(col, [...ops.$nin]));
|
|
239
296
|
if (ops.$like !== void 0) conds.push(likeCond(this.#dialect, sql`${col}`, ops.$like));
|
|
@@ -246,7 +303,13 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
246
303
|
for (const [field, value] of Object.entries(criteria)) {
|
|
247
304
|
if (field === "$or") {
|
|
248
305
|
if (!Array.isArray(value)) throw new Error(`DrizzleRepository(${getTableName(this.#table)}): $or attend un tableau de critères.`);
|
|
249
|
-
|
|
306
|
+
if (value.length === 0) {
|
|
307
|
+
conds.push(sql`1 = 0`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const branches = value.map((branch) => this.#where(branch));
|
|
311
|
+
if (branches.some((branch) => branch === void 0)) continue;
|
|
312
|
+
const [first, ...rest] = branches;
|
|
250
313
|
if (first !== void 0) conds.push(rest.length === 0 ? first : or(first, ...rest));
|
|
251
314
|
continue;
|
|
252
315
|
}
|
|
@@ -267,8 +330,14 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
267
330
|
* présence de limit/offset. Les VALEURS n'en font jamais partie : elles
|
|
268
331
|
* deviennent des placeholders (`p<i>`, `lim`, `off`) bindés à l'exécution.
|
|
269
332
|
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
333
|
+
* Les comparaisons scalaires ({@link COMPARISONS}) et `$null` entrent dans la
|
|
334
|
+
* forme : la COMBINAISON d'opérateurs d'un champ fait partie de la clé, leurs
|
|
335
|
+
* valeurs deviennent des placeholders `p<i>$<op>`. C'est le chemin des
|
|
336
|
+
* lectures bornées dans le temps (`expiresAt: { $gt: now }` — la révocation
|
|
337
|
+
* des jetons, lue à chaque requête authentifiée).
|
|
338
|
+
*
|
|
339
|
+
* Repli (`null`) par construction : `$or`, `$in`/`$nin` (le SQL suit la
|
|
340
|
+
* cardinalité), `$like` (clause d'échappement propre au dialecte), valeur
|
|
272
341
|
* `undefined` (le chemin actuel la rejette, même contrat), transaction
|
|
273
342
|
* (cf ctor), cache plein ({@link PREPARED_CACHE_MAX}).
|
|
274
343
|
*/
|
|
@@ -278,7 +347,22 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
278
347
|
if (criteria) {
|
|
279
348
|
let i = 0;
|
|
280
349
|
for (const [field, value] of Object.entries(criteria)) {
|
|
281
|
-
if (field === "$or" || value === void 0
|
|
350
|
+
if (field === "$or" || value === void 0) return null;
|
|
351
|
+
if (isFieldOperators(value)) {
|
|
352
|
+
let ops = "";
|
|
353
|
+
for (const op in value) if (!PREPARABLE_OPERATORS.has(op)) return null;
|
|
354
|
+
for (const [op] of COMPARISONS) {
|
|
355
|
+
const operand = value[op];
|
|
356
|
+
if (operand !== void 0) {
|
|
357
|
+
ops += op;
|
|
358
|
+
params[`p${i}${op}`] = operand;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (value.$null !== void 0) ops += value.$null ? "$null1" : "$null0";
|
|
362
|
+
key += `${field}\x04${ops}\x00`;
|
|
363
|
+
i++;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
282
366
|
if (value === null) key += `${field}\x01\x00`;
|
|
283
367
|
else {
|
|
284
368
|
key += `${field}\x00`;
|
|
@@ -322,6 +406,12 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
322
406
|
let i = 0;
|
|
323
407
|
for (const [field, value] of Object.entries(criteria)) {
|
|
324
408
|
const col = this.#requireCol(field);
|
|
409
|
+
if (isFieldOperators(value)) {
|
|
410
|
+
for (const [op, compare] of COMPARISONS) if (value[op] !== void 0) conds.push(compare(col, sql.param(sql.placeholder(`p${i}${op}`), col)));
|
|
411
|
+
if (value.$null !== void 0) conds.push(value.$null ? isNull(col) : isNotNull(col));
|
|
412
|
+
i++;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
325
415
|
conds.push(value === null ? isNull(col) : eq(col, sql.param(sql.placeholder(`p${i}`), col)));
|
|
326
416
|
i++;
|
|
327
417
|
}
|
|
@@ -435,7 +525,7 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
435
525
|
}
|
|
436
526
|
async updateOne(criteria, data) {
|
|
437
527
|
if (this.#dialect === "mysql") return this.#mysqlUpdateOneReturning(data, criteria);
|
|
438
|
-
const pick = this.#pickOne(
|
|
528
|
+
const pick = this.#pickOne(criteria);
|
|
439
529
|
return (await this.#prof(this.#db.update(execTable(this.#table)).set(data).where(pick).returning()))[0] ?? null;
|
|
440
530
|
}
|
|
441
531
|
/**
|
|
@@ -502,7 +592,7 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
502
592
|
setObj[field] = sql`${col} + ${delta}`;
|
|
503
593
|
}
|
|
504
594
|
if (this.#dialect === "mysql") return this.#mysqlUpdateOneReturning(setObj, criteria);
|
|
505
|
-
const pick = this.#pickOne(
|
|
595
|
+
const pick = this.#pickOne(criteria);
|
|
506
596
|
return (await this.#prof(this.#db.update(execTable(this.#table)).set(setObj).where(pick).returning()))[0] ?? null;
|
|
507
597
|
}
|
|
508
598
|
async delete(criteria) {
|
|
@@ -521,7 +611,7 @@ var DrizzleRepository = class DrizzleRepository {
|
|
|
521
611
|
}
|
|
522
612
|
/** DELETE atomique d'AU PLUS une ligne (PK via `#pickOne`), RETURNING la ligne. */
|
|
523
613
|
async #deleteOneReturning(criteria) {
|
|
524
|
-
const pick = this.#pickOne(
|
|
614
|
+
const pick = this.#pickOne(criteria);
|
|
525
615
|
return this.#prof(this.#db.delete(execTable(this.#table)).where(pick).returning());
|
|
526
616
|
}
|
|
527
617
|
/** PK obligatoire en mysql (les verbes re-SELECTent par PK). Fail-loud sinon. */
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region nodefony/src/sqliteJournal.ts
|
|
2
|
+
/** Échéance par défaut : au-delà, une base occupée l'est pour de bon. */
|
|
3
|
+
const WAL_DEADLINE_MS = 1e4;
|
|
4
|
+
const sleepFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
/**
|
|
6
|
+
* L'erreur est-elle un conflit de verrou, qui se règle en réessayant ?
|
|
7
|
+
*
|
|
8
|
+
* @param e - erreur levée par `better-sqlite3`
|
|
9
|
+
* @returns vrai pour `SQLITE_BUSY*` et `SQLITE_LOCKED*`
|
|
10
|
+
*/
|
|
11
|
+
function isSqliteBusy(e) {
|
|
12
|
+
const code = e?.code;
|
|
13
|
+
return typeof code === "string" && (code.startsWith("SQLITE_BUSY") || code.startsWith("SQLITE_LOCKED"));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Passe la base en WAL (`synchronous = NORMAL`), en tolérant les ouvertures
|
|
17
|
+
* CONCURRENTES d'une base neuve.
|
|
18
|
+
*
|
|
19
|
+
* Basculer en WAL exige un verrou exclusif. Deux processus qui ouvrent la même
|
|
20
|
+
* base neuve au même instant — l'application et `orm:migrate` lancé par
|
|
21
|
+
* `docker exec`, ou les workers de `nodefony cluster` — tiennent chacun un
|
|
22
|
+
* verrou partagé et veulent l'exclusif : SQLite tranche l'impasse par un
|
|
23
|
+
* `SQLITE_BUSY` IMMÉDIAT, sans passer par l'attente (`timeout`) de la
|
|
24
|
+
* connexion. Mesuré : 23 ouvertures sur 40 échouaient avec trois processus
|
|
25
|
+
* synchronisés, et la forge a vu un `orm:migrate` refusé au démarrage.
|
|
26
|
+
*
|
|
27
|
+
* Le mode WAL est PERSISTANT (écrit dans le fichier) : on le lit d'abord, et
|
|
28
|
+
* seul le premier ouvreur bascule. Un conflit se réessaie, à délai croissant,
|
|
29
|
+
* jusqu'à {@link WAL_DEADLINE_MS} ; toute autre erreur remonte telle quelle.
|
|
30
|
+
*
|
|
31
|
+
* @param db - connexion ouverte sur un FICHIER (sans objet sur `:memory:`)
|
|
32
|
+
* @param options - échéance, attente et horloge (injectées en test)
|
|
33
|
+
* @throws La dernière erreur `SQLITE_BUSY` si l'échéance est dépassée, ou
|
|
34
|
+
* toute autre erreur au premier essai
|
|
35
|
+
*/
|
|
36
|
+
async function enableWriteAheadLog(db, options = {}) {
|
|
37
|
+
const deadlineMs = options.deadlineMs ?? 1e4;
|
|
38
|
+
const sleep = options.sleep ?? sleepFor;
|
|
39
|
+
const now = options.now ?? Date.now;
|
|
40
|
+
const start = now();
|
|
41
|
+
for (let attempt = 1;; attempt++) try {
|
|
42
|
+
if (db.pragma("journal_mode", { simple: true }) !== "wal") {
|
|
43
|
+
const mode = db.pragma("journal_mode = WAL", { simple: true });
|
|
44
|
+
if (mode !== "wal") throw Object.assign(/* @__PURE__ */ new Error(`journal_mode resté « ${String(mode)} »`), { code: "SQLITE_BUSY" });
|
|
45
|
+
}
|
|
46
|
+
db.pragma("synchronous = NORMAL");
|
|
47
|
+
return;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (!isSqliteBusy(e) || now() - start >= deadlineMs) throw e;
|
|
50
|
+
await sleep(Math.min(20 * attempt, 250));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { WAL_DEADLINE_MS, enableWriteAheadLog, isSqliteBusy };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mise en WAL d'une base SQLite — UNE implémentation, appelée par les deux
|
|
3
|
+
* ouvreurs de fichier du paquet : l'adapter ORM (`DrizzleOrm`) et le pilote de
|
|
4
|
+
* migration (`sqliteDriver`). Une base migrée puis ouverte par l'application ne
|
|
5
|
+
* doit pas changer de mode en route, et les deux doivent survivre à la même
|
|
6
|
+
* concurrence.
|
|
7
|
+
*/
|
|
8
|
+
/** Ce que la mise en WAL lit d'une connexion `better-sqlite3`. */
|
|
9
|
+
export interface ISqlitePragmaClient {
|
|
10
|
+
pragma(source: string, options?: {
|
|
11
|
+
simple?: boolean;
|
|
12
|
+
}): unknown;
|
|
13
|
+
}
|
|
14
|
+
/** Réglages de la mise en WAL — injectables pour l'éprouver sans attendre. */
|
|
15
|
+
export interface IWriteAheadLogOptions {
|
|
16
|
+
/** Durée maximale des réessais, en millisecondes. */
|
|
17
|
+
deadlineMs?: number;
|
|
18
|
+
/** Attente entre deux essais. */
|
|
19
|
+
sleep?: (ms: number) => Promise<void>;
|
|
20
|
+
/** Horloge (millisecondes). */
|
|
21
|
+
now?: () => number;
|
|
22
|
+
}
|
|
23
|
+
/** Échéance par défaut : au-delà, une base occupée l'est pour de bon. */
|
|
24
|
+
export declare const WAL_DEADLINE_MS = 10000;
|
|
25
|
+
/**
|
|
26
|
+
* L'erreur est-elle un conflit de verrou, qui se règle en réessayant ?
|
|
27
|
+
*
|
|
28
|
+
* @param e - erreur levée par `better-sqlite3`
|
|
29
|
+
* @returns vrai pour `SQLITE_BUSY*` et `SQLITE_LOCKED*`
|
|
30
|
+
*/
|
|
31
|
+
export declare function isSqliteBusy(e: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Passe la base en WAL (`synchronous = NORMAL`), en tolérant les ouvertures
|
|
34
|
+
* CONCURRENTES d'une base neuve.
|
|
35
|
+
*
|
|
36
|
+
* Basculer en WAL exige un verrou exclusif. Deux processus qui ouvrent la même
|
|
37
|
+
* base neuve au même instant — l'application et `orm:migrate` lancé par
|
|
38
|
+
* `docker exec`, ou les workers de `nodefony cluster` — tiennent chacun un
|
|
39
|
+
* verrou partagé et veulent l'exclusif : SQLite tranche l'impasse par un
|
|
40
|
+
* `SQLITE_BUSY` IMMÉDIAT, sans passer par l'attente (`timeout`) de la
|
|
41
|
+
* connexion. Mesuré : 23 ouvertures sur 40 échouaient avec trois processus
|
|
42
|
+
* synchronisés, et la forge a vu un `orm:migrate` refusé au démarrage.
|
|
43
|
+
*
|
|
44
|
+
* Le mode WAL est PERSISTANT (écrit dans le fichier) : on le lit d'abord, et
|
|
45
|
+
* seul le premier ouvreur bascule. Un conflit se réessaie, à délai croissant,
|
|
46
|
+
* jusqu'à {@link WAL_DEADLINE_MS} ; toute autre erreur remonte telle quelle.
|
|
47
|
+
*
|
|
48
|
+
* @param db - connexion ouverte sur un FICHIER (sans objet sur `:memory:`)
|
|
49
|
+
* @param options - échéance, attente et horloge (injectées en test)
|
|
50
|
+
* @throws La dernière erreur `SQLITE_BUSY` si l'échéance est dépassée, ou
|
|
51
|
+
* toute autre erreur au premier essai
|
|
52
|
+
*/
|
|
53
|
+
export declare function enableWriteAheadLog(db: ISqlitePragmaClient, options?: IWriteAheadLogOptions): Promise<void>;
|
package/docs/index.md
CHANGED
|
@@ -200,7 +200,7 @@ Le tableau pour situer en cinq secondes ; les cards en dessous pour savoir où l
|
|
|
200
200
|
"desc": "Ce qui reste identique (les noms de colonnes, le contrat du repository) et ce qui diverge, avec la raison : types epoch/date/JSON, absence de `RETURNING` en MySQL, `OFFSET` sans `LIMIT`.",
|
|
201
201
|
"meta": "à lire avant de changer de base, pas après le premier incident" },
|
|
202
202
|
{ "icon": "🚧", "title": "migrations", "href": "#migrations--deux-chemins-un-seul-schéma",
|
|
203
|
-
"desc": "En développement, les tables sont créées au démarrage par un DDL dérivé de tes schémas
|
|
203
|
+
"desc": "En développement, les tables sont créées au démarrage par un DDL dérivé de tes schémas : index et clés étrangères compris, aucun `DEFAULT` SQL, et seules les colonnes facultatives ajoutées sont rattrapées. La section dit ce que ça implique en production.",
|
|
204
204
|
"meta": "le point à ne pas rater avant le premier déploiement" }
|
|
205
205
|
]
|
|
206
206
|
```
|
|
@@ -353,13 +353,18 @@ importable et testable sans serveur.
|
|
|
353
353
|
|
|
354
354
|
### Les options
|
|
355
355
|
|
|
356
|
-
| Option
|
|
357
|
-
|
|
|
358
|
-
| `connectors`
|
|
359
|
-
| `connectors.<n>.dialect`
|
|
360
|
-
| `connectors.<n>.filename`
|
|
361
|
-
| `connectors.<n>.url`
|
|
362
|
-
| `
|
|
356
|
+
| Option | Type | Défaut | Effet |
|
|
357
|
+
| -------------------------- | ----------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
358
|
+
| `connectors` | `Record<string, Connector>` | `{ default: {…} }` | Les connexions, indexées par nom (= clé du registre des ORM). |
|
|
359
|
+
| `connectors.<n>.dialect` | `"sqlite" \| "postgres" \| "mysql"` | `"sqlite"` | Choisit le driver (`better-sqlite3` / `pg` / `mysql2`). |
|
|
360
|
+
| `connectors.<n>.filename` | `string` | _résolu au boot_ | Fichier SQLite. `:memory:` = base éphémère. Ignoré hors `sqlite`. |
|
|
361
|
+
| `connectors.<n>.url` | `string` | — | Chaîne de connexion `postgres://…` / `mysql://…`. **Porte un secret.** |
|
|
362
|
+
| `connectors.<n>.ddl` | `"auto" \| "migrate" \| "none"` | _par environnement_ | `auto` en développement et en test (`migrate` dès que l'app versionne une migration), `none` ailleurs. |
|
|
363
|
+
| `migrations.dir` | `string` | `"migrations"` | Dossier des migrations versionnées. |
|
|
364
|
+
| `migrations.check` | `"fail" \| "warn" \| "off"` | _par environnement_ | Schéma en retard : `fail` retient `/readyz` (production), `warn` ailleurs. |
|
|
365
|
+
| `migrations.lockTimeoutMs` | `number` | `30000` | Attente du verrou de migration entre exemplaires. |
|
|
366
|
+
| `migrations.divergence` | `"report" \| "fail" \| "off"` | `"report"` | Conduite face à une migration appliquée qui a changé depuis. |
|
|
367
|
+
| `frameworkEntities` | `boolean` | `true` | Déclare (ou non) le schéma des huit briques durables sur `default`. |
|
|
363
368
|
|
|
364
369
|
Table dérivée de `drizzleConfigSchema` (`config.ts:136`) et de `SQL_DIALECTS` (`config.ts:37`).
|
|
365
370
|
|
|
@@ -420,20 +425,21 @@ la piste à vérifier.
|
|
|
420
425
|
|
|
421
426
|
Pour les dialectes réseau, la connexion fait un **ping réel** au démarrage : les pools `pg` et `mysql2`
|
|
422
427
|
sont paresseux, sans ce `SELECT 1` une base morte « se connecterait » et n'échouerait qu'à la première
|
|
423
|
-
requête métier (`#connectPostgres()`, `DrizzleOrm.ts:
|
|
428
|
+
requête métier (`#connectPostgres()`, `DrizzleOrm.ts:1206` · `#connectMysql()`, `DrizzleOrm.ts:1521`).
|
|
424
429
|
|
|
425
430
|
## Dialectes — une base par déploiement, un seul code
|
|
426
431
|
|
|
427
432
|
Trois dialectes au même contrat. Le tableau dit ce qu'il faut installer et ce qu'on obtient.
|
|
428
433
|
|
|
429
|
-
| Dialecte | Driver | Statut du paquet
|
|
430
|
-
| ---------- | ---------------- |
|
|
431
|
-
| `sqlite` | `better-sqlite3` |
|
|
432
|
-
| `postgres` | `pg` | dépendance **optionnelle** | production multi-pod (recommandé) | `url` |
|
|
433
|
-
| `mysql` | `mysql2` | dépendance **optionnelle** | production — **MySQL 8.4 et MariaDB 11.4** | `url` |
|
|
434
|
+
| Dialecte | Driver | Statut du paquet | Cible | Configuration |
|
|
435
|
+
| ---------- | ---------------- | ---------------------------------- | ------------------------------------------ | ------------- |
|
|
436
|
+
| `sqlite` | `better-sqlite3` | dépendance de pair **optionnelle** | développement, tests, production mono-nœud | `filename` |
|
|
437
|
+
| `postgres` | `pg` | dépendance de pair **optionnelle** | production multi-pod (recommandé) | `url` |
|
|
438
|
+
| `mysql` | `mysql2` | dépendance de pair **optionnelle** | production — **MySQL 8.4 et MariaDB 11.4** | `url` |
|
|
434
439
|
|
|
435
|
-
Les drivers
|
|
436
|
-
|
|
440
|
+
Les trois drivers sont des dépendances de pair optionnelles, posées par le scaffold pour le dialecte
|
|
441
|
+
choisi et chargées **paresseusement** à la connexion : une application n'installe, et ne charge, que
|
|
442
|
+
le pilote de son dialecte.
|
|
437
443
|
|
|
438
444
|
### Ce qui ne change pas
|
|
439
445
|
|
|
@@ -467,7 +473,7 @@ En MySQL, les verbes « qui rendent la ligne écrite » (`create`, `updateOne`,
|
|
|
467
473
|
`findOneAndDelete`) se décomposent en sélection de la cible → mutation bornée par la clé primaire **avec
|
|
468
474
|
le critère revérifié dans le `WHERE`** → relecture. Deux à trois allers-retours au lieu d'un : c'est le
|
|
469
475
|
prix du dialecte, payé **uniquement** en MySQL. Une course perdue rend `null`, jamais une mutation hors
|
|
470
|
-
critère (`#mysqlInsertReturning()`, `DrizzleRepository.ts:
|
|
476
|
+
critère (`#mysqlInsertReturning()`, `DrizzleRepository.ts:1228`).
|
|
471
477
|
|
|
472
478
|
Le SQL brut nécessaire aux entités du framework est lui aussi routé par dialecte, dans un seul fichier
|
|
473
479
|
(`queryKit.ts`) : recherche dans une colonne JSON (`findUserIdBySocialProvider()`, `queryKit.ts:76`),
|
|
@@ -518,12 +524,17 @@ connexion**, donc leurs tables sont créées au moment où l'ORM s'ouvre.
|
|
|
518
524
|
### Le DDL dérivé — comment les tables apparaissent
|
|
519
525
|
|
|
520
526
|
Drizzle ne « synchronise » pas un schéma. L'adapter dérive lui-même un `CREATE TABLE IF NOT EXISTS`
|
|
521
|
-
depuis chaque table déclarée (`#buildCreateTable()`, `DrizzleOrm.ts:509`) et l'exécute à la connexion
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
depuis chaque table déclarée (`#buildCreateTable()`, `DrizzleOrm.ts:509`) et l'exécute à la connexion
|
|
528
|
+
**quand le connecteur est en `ddl: "auto"`** — le défaut en développement et en test ; en production
|
|
529
|
+
le défaut est `none`, et un travail extérieur lance `orm:migrate`. Trois conséquences à connaître
|
|
530
|
+
**avant** de dépendre de ce mécanisme :
|
|
531
|
+
|
|
532
|
+
1. il **crée** les tables, leurs index et leurs clés étrangères ; il ne rattrape ensuite que les
|
|
533
|
+
colonnes ajoutées qui **acceptent le vide** — jamais une colonne obligatoire, jamais une
|
|
534
|
+
suppression ;
|
|
535
|
+
2. il n'émet **aucun `DEFAULT` SQL** — d'où la règle des défauts en `$defaultFn` ;
|
|
536
|
+
3. pour tout le reste (renommer, rendre obligatoire, supprimer), c'est une migration
|
|
537
|
+
(`orm:generate`, `orm:migrate`).
|
|
527
538
|
|
|
528
539
|
C'est un confort de développement, pas un outil de migration. La suite est dans
|
|
529
540
|
[Migrations](#migrations--deux-chemins-un-seul-schéma).
|
|
@@ -559,7 +570,7 @@ await posts.count({ views: { $gte: 10 } });
|
|
|
559
570
|
|
|
560
571
|
Les opérateurs (`$eq $ne $gt $gte $lt $lte $in $nin $like`) sont **ceux d'orm-core**, identiques sur
|
|
561
572
|
tous les drivers ; la traduction en `eq()`/`inArray()` se fait dans `#where()`
|
|
562
|
-
(`DrizzleRepository.ts:
|
|
573
|
+
(`DrizzleRepository.ts:521`). Leur référence complète est dans
|
|
563
574
|
[la page d'orm-core](../../orm-core/docs/index.md).
|
|
564
575
|
|
|
565
576
|
`$like` est émis avec sa clause `ESCAPE '\'` (`likeSql.ts`), ce qui rend un `%` ou un `_` **littéral**
|
|
@@ -570,11 +581,12 @@ lui-même en SQLite : le même critère ne rendait pas les mêmes lignes selon l
|
|
|
570
581
|
Deux points de comportement qui évitent des surprises :
|
|
571
582
|
|
|
572
583
|
- **« au plus une ligne »** est garanti par construction pour `updateOne`/`deleteOne`/`increment` :
|
|
573
|
-
la mutation est bornée
|
|
574
|
-
`
|
|
584
|
+
la mutation est bornée à une ligne : `WHERE <pk> = ?` quand le critère fixe toute la clé primaire,
|
|
585
|
+
sinon `WHERE pk IN (SELECT pk FROM (SELECT pk … LIMIT 1) AS picked)` — jamais un `LIMIT` sur
|
|
586
|
+
l'`UPDATE` lui-même (`#pickOne()`, `DrizzleRepository.ts:324`). C'est ce qui rend ces verbes portables — MySQL
|
|
575
587
|
interdit la forme naïve.
|
|
576
588
|
- **l'eager-load est manuel** : une requête `IN (…)` par relation déclarée, puis regroupement en
|
|
577
|
-
mémoire (`#populate()`, `DrizzleRepository.ts:
|
|
589
|
+
mémoire (`#populate()`, `DrizzleRepository.ts:817`). Choix assumé — pas de couche de relations à
|
|
578
590
|
déclarer une seconde fois, et le comportement est le même sur les trois dialectes.
|
|
579
591
|
|
|
580
592
|
### Transactions — une connexion dédiée, jamais le pool
|
|
@@ -627,7 +639,7 @@ const rows = await db.all(sql`
|
|
|
627
639
|
`);
|
|
628
640
|
```
|
|
629
641
|
|
|
630
|
-
C'est l'**anti-blocage** du modèle Repository (`getNativeConnection()`, `DrizzleOrm.ts:
|
|
642
|
+
C'est l'**anti-blocage** du modèle Repository (`getNativeConnection()`, `DrizzleOrm.ts:1792`) : CTE,
|
|
631
643
|
fonctions de fenêtre, sous-requêtes corrélées, jointures arbitraires. Deux contreparties assumées :
|
|
632
644
|
ce SQL n'est plus portable entre dialectes, et il **ne passe pas** par la sonde de profilage des
|
|
633
645
|
requêtes.
|
|
@@ -851,7 +863,7 @@ l'emplacement **est** l'infra déclarée, déjà affichée ailleurs (`location`,
|
|
|
851
863
|
## ⚡ Performance & mémoire
|
|
852
864
|
|
|
853
865
|
**La sonde de requêtes ne coûte rien quand elle est éteinte.** Chaque exécution passe par un point de
|
|
854
|
-
mesure unique (`#prof()`, `DrizzleRepository.ts:
|
|
866
|
+
mesure unique (`#prof()`, `DrizzleRepository.ts:433`) qui alimente deux consommateurs — le profileur
|
|
855
867
|
par requête (barre de debug) et l'agrégat de flux. Les deux sont gardés par un drapeau : si aucun n'est
|
|
856
868
|
actif, la fonction rend le constructeur de requête tel quel, sans allocation. Le flux est **désactivé
|
|
857
869
|
en production** par défaut.
|
|
@@ -875,8 +887,7 @@ connexions. Les chiffres vivent dans la sortie du banc, pas ici — ils dépende
|
|
|
875
887
|
| -------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
876
888
|
| Une écriture dans `transaction()` n'est pas annulée | repository obtenu par `getRepository()` → passe par le **pool**, hors tx | `repo.withTransaction(tx)` — le seul moyen d'entrer dans la transaction |
|
|
877
889
|
| `NOT NULL constraint failed` sur une colonne pourtant « par défaut » | `.default()` SQL : le DDL dérivé ne l'émet pas | poser le défaut côté JS : `$defaultFn(() => …)` |
|
|
878
|
-
| Colonne ajoutée, table inchangée
|
|
879
|
-
| Index déclaré, absent en base | les index ne sortent que via `drizzle-kit` | migration `drizzle-kit` en production |
|
|
890
|
+
| Colonne **obligatoire** ajoutée, table inchangée | le rattrapage de développement n'ajoute que les colonnes facultatives | `nodefony orm:reset` (dev), ou `orm:generate` puis `orm:migrate` |
|
|
880
891
|
| Le démarrage échoue sur le connecteur | infra déclarée injoignable → `BootConfigurationError` (voulu) | corriger l'URL / démarrer la base / retirer le connecteur |
|
|
881
892
|
| `cannot start a transaction within a transaction` (SQLite) | deux `BEGIN` concurrents sur la connexion unique | rien à faire : la file d'attente interne les sérialise |
|
|
882
893
|
| Une entité fonctionne en SQLite, échoue en PostgreSQL | table Drizzle **figée** sur un dialecte, posée sur le connecteur `default` | fixer l'entité sur son propre connecteur SQLite, ou la porter au dialecte |
|
|
@@ -941,7 +952,7 @@ npm run test:load # charge, limites, mémoire
|
|
|
941
952
|
- 🧭 **L'abstraction au-dessus** : [`@nodefony/orm-core`](../../orm-core/docs/index.md) — contrats
|
|
942
953
|
`IOrm`/`IRepository`/`ITransaction`, `Criteria` et opérateurs riches, registres. À lire pour tout ce
|
|
943
954
|
qui est **portable** ; cette page-ci ne documente que le driver SQL.
|
|
944
|
-
- 🗄️ **Déployer un schéma** : [Migrations de schéma](migrations.md) — les
|
|
955
|
+
- 🗄️ **Déployer un schéma** : [Migrations de schéma](migrations.md) — les six commandes, le
|
|
945
956
|
rattrapage de développement, le travail d'orchestrateur et l'expansion/contraction.
|
|
946
957
|
- 📗 **Tutoriel** : [créer une entité pas à pas](../../orm-core/docs/tutorial-entity.md)
|
|
947
958
|
- 🧩 **L'autre driver** : [`@nodefony/mongoose`](../../mongoose/docs/index.md) — même contrat, MongoDB.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodefony/drizzle",
|
|
3
|
-
"version": "10.0.0-beta.
|
|
3
|
+
"version": "10.0.0-beta.2",
|
|
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,29 +60,29 @@
|
|
|
60
60
|
"zod": "^4.6.5"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@nodefony/framework": "^10.0.0-beta.
|
|
64
|
-
"@nodefony/http": "^10.0.0-beta.
|
|
65
|
-
"@nodefony/orm-core": "^10.0.0-beta.
|
|
66
|
-
"@nodefony/security": "^10.0.0-beta.
|
|
67
|
-
"@nodefony/user": "^10.0.0-beta.
|
|
63
|
+
"@nodefony/framework": "^10.0.0-beta.2",
|
|
64
|
+
"@nodefony/http": "^10.0.0-beta.2",
|
|
65
|
+
"@nodefony/orm-core": "^10.0.0-beta.2",
|
|
66
|
+
"@nodefony/security": "^10.0.0-beta.2",
|
|
67
|
+
"@nodefony/user": "^10.0.0-beta.2",
|
|
68
68
|
"@types/better-sqlite3": "9.6.0",
|
|
69
69
|
"@types/node": "26.6.3",
|
|
70
70
|
"@types/pg": "8.23.1",
|
|
71
71
|
"better-sqlite3": "13.0.3",
|
|
72
72
|
"drizzle-kit": "0.31.11",
|
|
73
|
-
"mysql2": "3.24.
|
|
74
|
-
"nodefony": "^10.0.0-beta.
|
|
75
|
-
"pg": "8.23.
|
|
73
|
+
"mysql2": "3.24.5",
|
|
74
|
+
"nodefony": "^10.0.0-beta.2",
|
|
75
|
+
"pg": "8.23.1"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@nodefony/framework": "^10.0.0-beta.
|
|
79
|
-
"@nodefony/http": "^10.0.0-beta.
|
|
80
|
-
"@nodefony/orm-core": "^10.0.0-beta.
|
|
81
|
-
"@nodefony/security": "^10.0.0-beta.
|
|
82
|
-
"@nodefony/user": "^10.0.0-beta.
|
|
78
|
+
"@nodefony/framework": "^10.0.0-beta.2",
|
|
79
|
+
"@nodefony/http": "^10.0.0-beta.2",
|
|
80
|
+
"@nodefony/orm-core": "^10.0.0-beta.2",
|
|
81
|
+
"@nodefony/security": "^10.0.0-beta.2",
|
|
82
|
+
"@nodefony/user": "^10.0.0-beta.2",
|
|
83
83
|
"better-sqlite3": "^13.0.0",
|
|
84
84
|
"mysql2": "^3.24.0",
|
|
85
|
-
"nodefony": "^10.0.0-beta.
|
|
85
|
+
"nodefony": "^10.0.0-beta.2",
|
|
86
86
|
"pg": "^8.23.0"
|
|
87
87
|
},
|
|
88
88
|
"repository": {
|