@nodefony/orm-core 10.0.0-alpha.7 → 10.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -3
- package/dist/nodefony/src/ConnectionMonitor.js +33 -2
- package/dist/nodefony/src/Orm.js +17 -1
- package/dist/nodefony/src/OrmAdminApi.js +51 -9
- package/dist/nodefony/src/connectionDiagnosis.js +66 -6
- package/dist/nodefony/src/errors.js +59 -1
- package/dist/nodefony/src/orphanEntities.js +63 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/nodefony/interfaces/IEntity.d.ts +19 -0
- package/dist/types/nodefony/interfaces/IOrm.d.ts +10 -1
- package/dist/types/nodefony/interfaces/IOrmGraph.d.ts +47 -2
- package/dist/types/nodefony/interfaces/ITransaction.d.ts +3 -1
- package/dist/types/nodefony/interfaces/index.d.ts +2 -2
- package/dist/types/nodefony/src/ConnectionMonitor.d.ts +9 -2
- package/dist/types/nodefony/src/Orm.d.ts +9 -1
- package/dist/types/nodefony/src/OrmAdminApi.d.ts +17 -1
- package/dist/types/nodefony/src/connectionDiagnosis.d.ts +29 -0
- package/dist/types/nodefony/src/errors.d.ts +48 -0
- package/dist/types/nodefony/src/orphanEntities.d.ts +38 -0
- package/docs/index.md +36 -33
- package/docs/tutorial-entity.md +14 -13
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { LIKE_ESCAPE_CHAR, OPERATOR_KEYS, UPDATE_OPERATOR_KEYS, escapeLikeTerm, isFieldOperators, isUpdateOperators, likePatternToRegExp, searchCriteria } from "./nodefony/src/criteria.js";
|
|
2
|
-
import { InvalidOrderOption, UnknownCriteriaField } from "./nodefony/src/errors.js";
|
|
2
|
+
import { InvalidOrderOption, ReferencedEntityError, SavepointNotSupportedError, UnknownCriteriaField } from "./nodefony/src/errors.js";
|
|
3
3
|
import { assertOrderOption } from "./nodefony/src/readOptions.js";
|
|
4
4
|
import { OrmRegistry, ormRegistry } from "./nodefony/src/OrmRegistry.js";
|
|
5
5
|
import { EntityRegistry, entityRegistry } from "./nodefony/src/EntityRegistry.js";
|
|
6
|
+
import { reportOrphanEntities } from "./nodefony/src/orphanEntities.js";
|
|
6
7
|
import { connectionMonitor } from "./nodefony/src/ConnectionMonitor.js";
|
|
7
8
|
import { Orm } from "./nodefony/src/Orm.js";
|
|
8
9
|
import { Entity } from "./nodefony/src/Entity.js";
|
|
@@ -11,7 +12,7 @@ import { AbstractCrudService } from "./nodefony/src/AbstractCrudService.js";
|
|
|
11
12
|
import { queryFlowMonitor } from "./nodefony/src/QueryFlowMonitor.js";
|
|
12
13
|
import { buildConnectionHealth, buildOrmFlow, buildOrmGraph, createOrmAdminApi, registerOrmAdminApi, toDbml, toJsonSchema } from "./nodefony/src/OrmAdminApi.js";
|
|
13
14
|
import { buildOrmLeanHealth } from "./nodefony/src/buildOrmLeanHealth.js";
|
|
14
|
-
import { diagnoseConnectionFailure, parseConnectionTarget } from "./nodefony/src/connectionDiagnosis.js";
|
|
15
|
+
import { REJECTED_MARKER, describeConnectFailure, diagnoseConnectionFailure, parseConnectionTarget } from "./nodefony/src/connectionDiagnosis.js";
|
|
15
16
|
import { CONNECT_TIMEOUT_MS, withConnectDeadline } from "./nodefony/src/connectDeadline.js";
|
|
16
17
|
import { reportOrmBootLines, resolveOrmFlowEnabled, wireOrmAdminPlane } from "./nodefony/src/ormWiring.js";
|
|
17
18
|
import { isMigrationFailure } from "./nodefony/interfaces/IOrmMigrations.js";
|
|
@@ -21,4 +22,4 @@ import { DEFAULT_CONNECTOR, entities } from "./nodefony/src/decorators/entitiesD
|
|
|
21
22
|
import { repository } from "./nodefony/src/decorators/repositoryDecorator.js";
|
|
22
23
|
import "./nodefony/src/decorators/index.js";
|
|
23
24
|
import { defineEntity } from "./nodefony/src/defineEntity.js";
|
|
24
|
-
export { AbstractCrudService, CONNECT_TIMEOUT_MS, DEFAULT_CONNECTOR, Entity, EntityRegistry, InvalidOrderOption, LIKE_ESCAPE_CHAR, OPERATOR_KEYS, Orm, OrmRegistry, UPDATE_OPERATOR_KEYS, UnknownCriteriaField, assertOrderOption, buildConnectionHealth, buildOrmFlow, buildOrmGraph, buildOrmLeanHealth, connectionMonitor, createOrmAdminApi, defineEntity, diagnoseConnectionFailure, entities, entity, entityRegistry, escapeLikeTerm, getEntityMeta, getRepositoryMeta, hasEntityMeta, hasRepositoryMeta, isFieldOperators, isMigrationFailure, isUpdateOperators, likePatternToRegExp, ormRegistry, paginate, parseConnectionTarget, queryFlowMonitor, registerOrmAdminApi, reportOrmBootLines, repository, resolveOrmFlowEnabled, searchCriteria, toDbml, toJsonSchema, wireOrmAdminPlane, withConnectDeadline };
|
|
25
|
+
export { AbstractCrudService, CONNECT_TIMEOUT_MS, DEFAULT_CONNECTOR, Entity, EntityRegistry, InvalidOrderOption, LIKE_ESCAPE_CHAR, OPERATOR_KEYS, Orm, OrmRegistry, REJECTED_MARKER, ReferencedEntityError, SavepointNotSupportedError, UPDATE_OPERATOR_KEYS, UnknownCriteriaField, assertOrderOption, buildConnectionHealth, buildOrmFlow, buildOrmGraph, buildOrmLeanHealth, connectionMonitor, createOrmAdminApi, defineEntity, describeConnectFailure, diagnoseConnectionFailure, entities, entity, entityRegistry, escapeLikeTerm, getEntityMeta, getRepositoryMeta, hasEntityMeta, hasRepositoryMeta, isFieldOperators, isMigrationFailure, isUpdateOperators, likePatternToRegExp, ormRegistry, paginate, parseConnectionTarget, queryFlowMonitor, registerOrmAdminApi, reportOrmBootLines, reportOrphanEntities, repository, resolveOrmFlowEnabled, searchCriteria, toDbml, toJsonSchema, wireOrmAdminPlane, withConnectDeadline };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
//#region nodefony/src/ConnectionMonitor.ts
|
|
2
2
|
/** Taille max du ring d'erreurs récentes (borne mémoire). */
|
|
3
3
|
const MAX_RECENT_ERRORS = 12;
|
|
4
|
+
/** Taille max de la chronologie pertes/reprises (borne mémoire). */
|
|
5
|
+
const MAX_EVENTS = 20;
|
|
4
6
|
/** Taille de la fenêtre glissante de latence (min/moy/max). */
|
|
5
7
|
const MAX_LATENCY_SAMPLES = 30;
|
|
6
8
|
/** Fenêtre de latence vide. */
|
|
@@ -18,6 +20,9 @@ const EMPTY_CORE = {
|
|
|
18
20
|
connectCount: 0,
|
|
19
21
|
reconnectCount: 0,
|
|
20
22
|
lostCount: 0,
|
|
23
|
+
lastLostAt: null,
|
|
24
|
+
lastRestoredAt: null,
|
|
25
|
+
events: [],
|
|
21
26
|
errorCount: 0,
|
|
22
27
|
lastConnectMs: null,
|
|
23
28
|
lastError: null,
|
|
@@ -54,6 +59,9 @@ var ConnectionMonitor = class {
|
|
|
54
59
|
connectCount: 0,
|
|
55
60
|
reconnectCount: 0,
|
|
56
61
|
lostCount: 0,
|
|
62
|
+
lastLostAt: null,
|
|
63
|
+
lastRestoredAt: null,
|
|
64
|
+
events: null,
|
|
57
65
|
errorCount: 0,
|
|
58
66
|
lastConnectMs: null,
|
|
59
67
|
lastError: null,
|
|
@@ -99,11 +107,19 @@ var ConnectionMonitor = class {
|
|
|
99
107
|
* preuve de bonne santé.
|
|
100
108
|
*
|
|
101
109
|
* @param name - clé du connecteur.
|
|
110
|
+
* @param reason - cause lisible (credential déjà retiré), si connue.
|
|
102
111
|
*/
|
|
103
|
-
recordLost(name) {
|
|
112
|
+
recordLost(name, reason) {
|
|
104
113
|
const s = this.#ensure(name);
|
|
114
|
+
const ts = Date.now();
|
|
105
115
|
s.lostCount += 1;
|
|
106
116
|
s.connectedSince = null;
|
|
117
|
+
s.lastLostAt = ts;
|
|
118
|
+
this.#pushEvent(s, {
|
|
119
|
+
kind: "lost",
|
|
120
|
+
ts,
|
|
121
|
+
reason
|
|
122
|
+
});
|
|
107
123
|
}
|
|
108
124
|
/**
|
|
109
125
|
* Enregistre une **reprise** de connexion constatée par l'adapter.
|
|
@@ -112,8 +128,20 @@ var ConnectionMonitor = class {
|
|
|
112
128
|
*/
|
|
113
129
|
recordReconnect(name) {
|
|
114
130
|
const s = this.#ensure(name);
|
|
131
|
+
const ts = Date.now();
|
|
115
132
|
s.reconnectCount += 1;
|
|
116
|
-
s.connectedSince =
|
|
133
|
+
s.connectedSince = ts;
|
|
134
|
+
s.lastRestoredAt = ts;
|
|
135
|
+
this.#pushEvent(s, {
|
|
136
|
+
kind: "restored",
|
|
137
|
+
ts
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Ajoute un événement en tête de la chronologie (allouée au 1ᵉʳ, bornée). */
|
|
141
|
+
#pushEvent(s, e) {
|
|
142
|
+
if (s.events === null) s.events = [];
|
|
143
|
+
s.events.unshift(e);
|
|
144
|
+
if (s.events.length > MAX_EVENTS) s.events.length = MAX_EVENTS;
|
|
117
145
|
}
|
|
118
146
|
/**
|
|
119
147
|
* Enregistre une erreur (connexion échouée ou ping en échec).
|
|
@@ -167,6 +195,9 @@ var ConnectionMonitor = class {
|
|
|
167
195
|
connectCount: s.connectCount,
|
|
168
196
|
reconnectCount: s.reconnectCount,
|
|
169
197
|
lostCount: s.lostCount,
|
|
198
|
+
lastLostAt: s.lastLostAt,
|
|
199
|
+
lastRestoredAt: s.lastRestoredAt,
|
|
200
|
+
events: s.events ?? [],
|
|
170
201
|
errorCount: s.errorCount,
|
|
171
202
|
lastConnectMs: s.lastConnectMs,
|
|
172
203
|
lastError: s.lastError,
|
package/dist/nodefony/src/Orm.js
CHANGED
|
@@ -126,7 +126,7 @@ var Orm = class extends Service {
|
|
|
126
126
|
if (!this.alive) return;
|
|
127
127
|
this.alive = false;
|
|
128
128
|
this.#lostPending = true;
|
|
129
|
-
connectionMonitor.recordLost(this.name);
|
|
129
|
+
connectionMonitor.recordLost(this.name, reason);
|
|
130
130
|
this.log(`connexion perdue : ${reason}`, "WARNING");
|
|
131
131
|
this.fire("onOrmLost", this, reason);
|
|
132
132
|
}
|
|
@@ -197,6 +197,22 @@ var Orm = class extends Service {
|
|
|
197
197
|
timer.unref?.();
|
|
198
198
|
this.#heartbeat = timer;
|
|
199
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Décrit le mécanisme de résilience TEL QU'IL TOURNE — lecture pure, pour
|
|
202
|
+
* le data plane (`connection/health`) : période et montre du battement,
|
|
203
|
+
* battement actif ou non, perte en souffrance.
|
|
204
|
+
*
|
|
205
|
+
* @returns l'état du mécanisme dans ce process.
|
|
206
|
+
*/
|
|
207
|
+
describeResilience() {
|
|
208
|
+
return {
|
|
209
|
+
heartbeatMs: this.heartbeatMs,
|
|
210
|
+
heartbeatTimeoutMs: this.heartbeatTimeoutMs,
|
|
211
|
+
heartbeatActive: this.#heartbeat !== null,
|
|
212
|
+
pingable: typeof this.ping === "function",
|
|
213
|
+
lostPending: this.#lostPending
|
|
214
|
+
};
|
|
215
|
+
}
|
|
200
216
|
/** Arrête le battement et libère la minuterie. Idempotent. */
|
|
201
217
|
stopHeartbeat() {
|
|
202
218
|
if (this.#heartbeat !== null) {
|
|
@@ -2,6 +2,7 @@ import { ormRegistry } from "./OrmRegistry.js";
|
|
|
2
2
|
import { entityRegistry } from "./EntityRegistry.js";
|
|
3
3
|
import { connectionMonitor } from "./ConnectionMonitor.js";
|
|
4
4
|
import { queryFlowMonitor } from "./QueryFlowMonitor.js";
|
|
5
|
+
import { Nodefony } from "nodefony";
|
|
5
6
|
import { performance } from "node:perf_hooks";
|
|
6
7
|
//#region nodefony/src/OrmAdminApi.ts
|
|
7
8
|
/**
|
|
@@ -76,9 +77,38 @@ async function migrationCapability(request, capability, missingMessage) {
|
|
|
76
77
|
return fn.call(orm);
|
|
77
78
|
}
|
|
78
79
|
/** Résumé des ORM enregistrés (statut connexion + nombre d'entités). */
|
|
80
|
+
/**
|
|
81
|
+
* Connecteur qui porte les stores de l'application — le connecteur PAR DÉFAUT.
|
|
82
|
+
*
|
|
83
|
+
* « Défaut » est un RÔLE, pas un nom : sur une infra MongoDB les briques
|
|
84
|
+
* durables vivent sur `nodefony` (Mongoose), et marquer `default` (Drizzle)
|
|
85
|
+
* parce qu'il s'appelle ainsi désignait une base que personne n'utilise. Le
|
|
86
|
+
* rôle se CONSTATE dans le registre des stores (#474) : le connecteur qui porte
|
|
87
|
+
* le plus de briques durables, puis de briques tout court.
|
|
88
|
+
*
|
|
89
|
+
* @param resolutions - registre des stores du kernel (`storeResolutions`).
|
|
90
|
+
* @param names - connecteurs enregistrés ; une brique sur un autre nom est ignorée.
|
|
91
|
+
* @returns le nom du connecteur, ou `null` si aucune brique n'est portée par
|
|
92
|
+
* un ORM (le repli sur le nom `default` s'applique alors).
|
|
93
|
+
*/
|
|
94
|
+
function storeCarrier(resolutions, names) {
|
|
95
|
+
const score = /* @__PURE__ */ new Map();
|
|
96
|
+
for (const r of resolutions) {
|
|
97
|
+
if (!r.connector || !names.includes(r.connector)) continue;
|
|
98
|
+
score.set(r.connector, (score.get(r.connector) ?? 0) + (r.nature === "durable" ? 1e3 : 1));
|
|
99
|
+
}
|
|
100
|
+
let best = null;
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
const s = score.get(name) ?? 0;
|
|
103
|
+
if (s > 0 && (best === null || s > (score.get(best) ?? 0))) best = name;
|
|
104
|
+
}
|
|
105
|
+
return best;
|
|
106
|
+
}
|
|
79
107
|
function buildOrmSummaries() {
|
|
80
108
|
const entities = entityRegistry.list();
|
|
81
|
-
|
|
109
|
+
const names = ormRegistry.list();
|
|
110
|
+
const carrier = storeCarrier(Nodefony.getKernel()?.storeResolutions ?? [], names);
|
|
111
|
+
return names.map((name) => {
|
|
82
112
|
let connected = false;
|
|
83
113
|
let vendor = "";
|
|
84
114
|
let connection;
|
|
@@ -93,7 +123,7 @@ function buildOrmSummaries() {
|
|
|
93
123
|
return {
|
|
94
124
|
name,
|
|
95
125
|
vendor,
|
|
96
|
-
default: name === "default",
|
|
126
|
+
default: carrier === null ? name === "default" : name === carrier,
|
|
97
127
|
connected,
|
|
98
128
|
entityCount: entities.filter((e) => e.connector === name).length,
|
|
99
129
|
connection
|
|
@@ -162,8 +192,10 @@ async function buildConnectionHealth(filter) {
|
|
|
162
192
|
let storage;
|
|
163
193
|
let pool;
|
|
164
194
|
let extra;
|
|
195
|
+
let resilience;
|
|
165
196
|
try {
|
|
166
197
|
const inst = ormRegistry.get(name);
|
|
198
|
+
resilience = inst.describeResilience?.();
|
|
167
199
|
connected = inst.isConnected();
|
|
168
200
|
vendor = vendorOf(inst);
|
|
169
201
|
const c = inst.describeConnection?.();
|
|
@@ -211,6 +243,11 @@ async function buildConnectionHealth(filter) {
|
|
|
211
243
|
uptimeMs: core.uptimeMs,
|
|
212
244
|
connectCount: core.connectCount,
|
|
213
245
|
reconnectCount: core.reconnectCount,
|
|
246
|
+
lostCount: core.lostCount,
|
|
247
|
+
lastLostAt: core.lastLostAt,
|
|
248
|
+
lastRestoredAt: core.lastRestoredAt,
|
|
249
|
+
events: core.events,
|
|
250
|
+
resilience,
|
|
214
251
|
errorCount: core.errorCount,
|
|
215
252
|
lastError: core.lastError,
|
|
216
253
|
recentErrors: core.recentErrors,
|
|
@@ -409,16 +446,21 @@ function createOrmAdminApi() {
|
|
|
409
446
|
},
|
|
410
447
|
{
|
|
411
448
|
path: "counts",
|
|
412
|
-
summary: "Nombre de lignes par entité (COUNT(*)) — ?connector= pour filtrer. Lazy : 1 COUNT par table.",
|
|
449
|
+
summary: "Nombre de lignes par entité (COUNT(*)) — ?connector= pour filtrer. Lazy : 1 COUNT par table. Clé = nom de l'entité, qualifiée `connecteur:nom` quand plusieurs connecteurs portent ce nom.",
|
|
413
450
|
handler: async (request) => {
|
|
414
451
|
const connector = oneParam(request, "connector");
|
|
415
452
|
const counts = {};
|
|
416
453
|
const entities = entityRegistry.list().filter((e) => !connector || e.connector === connector);
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
454
|
+
const homonyms = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const e of entities) homonyms.set(e.name, (homonyms.get(e.name) ?? 0) + 1);
|
|
456
|
+
for (const e of entities) {
|
|
457
|
+
const key = (homonyms.get(e.name) ?? 0) > 1 ? `${e.connector}:${e.name}` : e.name;
|
|
458
|
+
try {
|
|
459
|
+
const inst = ormRegistry.get(e.connector);
|
|
460
|
+
counts[key] = inst.isConnected() ? await inst.getRepository(e.name).count() : -1;
|
|
461
|
+
} catch {
|
|
462
|
+
counts[key] = -1;
|
|
463
|
+
}
|
|
422
464
|
}
|
|
423
465
|
return counts;
|
|
424
466
|
}
|
|
@@ -488,4 +530,4 @@ function registerOrmAdminApi(registry) {
|
|
|
488
530
|
registry.register(createOrmAdminApi());
|
|
489
531
|
}
|
|
490
532
|
//#endregion
|
|
491
|
-
export { buildConnectionHealth, buildOrmFlow, buildOrmGraph, createOrmAdminApi, registerOrmAdminApi, toDbml, toJsonSchema };
|
|
533
|
+
export { buildConnectionHealth, buildOrmFlow, buildOrmGraph, createOrmAdminApi, registerOrmAdminApi, storeCarrier, toDbml, toJsonSchema };
|
|
@@ -31,15 +31,41 @@ const ANSWERED_CODES = /* @__PURE__ */ new Set([
|
|
|
31
31
|
"AuthenticationFailed",
|
|
32
32
|
"Unauthorized"
|
|
33
33
|
]);
|
|
34
|
+
/**
|
|
35
|
+
* Classes SQLSTATE (PostgreSQL) qui décrivent la CONNEXION elle-même, pas une
|
|
36
|
+
* instruction : `08` exception de connexion, `57` intervention de l'opérateur
|
|
37
|
+
* (arrêt, base qui démarre). Tout autre SQLSTATE est rendu par un serveur à qui
|
|
38
|
+
* l'on parlait déjà.
|
|
39
|
+
*/
|
|
40
|
+
const CONNECTION_SQLSTATE_CLASSES = /* @__PURE__ */ new Set(["08", "57"]);
|
|
41
|
+
/**
|
|
42
|
+
* Refus MySQL/MariaDB émis PENDANT l'établissement : le serveur n'a encore
|
|
43
|
+
* exécuté aucune instruction. Tout autre `ER_*` vient d'un serveur connecté.
|
|
44
|
+
*/
|
|
45
|
+
const MYSQL_CONNECTION_PHASE = /* @__PURE__ */ new Set([
|
|
46
|
+
"ER_CON_COUNT_ERROR",
|
|
47
|
+
"ER_TOO_MANY_USER_CONNECTIONS",
|
|
48
|
+
"ER_HOST_IS_BLOCKED",
|
|
49
|
+
"ER_SERVER_SHUTDOWN"
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* Le code prouve-t-il que la connexion avait ABOUTI quand le refus est tombé ?
|
|
53
|
+
*
|
|
54
|
+
* Ce n'est vrai que d'un code produit par le SERVEUR en réponse à une
|
|
55
|
+
* instruction : un SQLSTATE PostgreSQL hors des classes de connexion, ou un
|
|
56
|
+
* `ER_*` MySQL hors de la phase d'établissement. Les codes de refus connus
|
|
57
|
+
* ({@link ANSWERED_CODES}) sont tranchés AVANT.
|
|
58
|
+
*/
|
|
59
|
+
function provesEstablished(code) {
|
|
60
|
+
if (/^[0-9A-Z]{5}$/.test(code) && /[0-9]/.test(code)) return !CONNECTION_SQLSTATE_CLASSES.has(code.slice(0, 2));
|
|
61
|
+
return code.startsWith("ER_") && !MYSQL_CONNECTION_PHASE.has(code);
|
|
62
|
+
}
|
|
34
63
|
/** Lit le code que le driver a posé sur l'erreur, sans rien supposer de sa forme. */
|
|
35
64
|
function readCode(error) {
|
|
36
65
|
if (typeof error !== "object" || error === null) return null;
|
|
37
66
|
const carrier = error;
|
|
38
|
-
for (const raw of [carrier.code, carrier.codeName])
|
|
39
|
-
|
|
40
|
-
if (typeof raw === "number") return String(raw);
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
67
|
+
for (const raw of [carrier.code, carrier.codeName]) if (typeof raw === "string" && raw.length > 0) return raw;
|
|
68
|
+
return typeof carrier.code === "number" ? String(carrier.code) : null;
|
|
43
69
|
}
|
|
44
70
|
/**
|
|
45
71
|
* Extrait l'hôte et le port d'une URL de connexion, sans son secret.
|
|
@@ -102,11 +128,45 @@ function diagnoseConnectionFailure(error, target = {
|
|
|
102
128
|
code,
|
|
103
129
|
explanation: `un serveur a RÉPONDU sur ${where} puis a refusé (${code}) — donc quelque chose tient bien ce port. Ce peut être la base attendue avec de mauvais identifiants, mais aussi UN AUTRE SERVEUR (le conteneur d'un autre projet, par exemple) : le refus vient alors d'une base qui n'est pas la vôtre, et vérifier les identifiants ne mène nulle part.${target.port !== null ? ` Pour savoir qui tient ce port : « docker ps --filter publish=${target.port} », ou « ${whoHoldsPort(target.port, platform)} ».` : ""}`
|
|
104
130
|
};
|
|
131
|
+
if (code !== null && provesEstablished(code)) return {
|
|
132
|
+
verdict: "rejected",
|
|
133
|
+
code,
|
|
134
|
+
explanation: `la connexion à ${where} a ABOUTI, puis le serveur a refusé une instruction (${code}) — ce n'est ni l'adresse ni l'état de la base : c'est le schéma envoyé (une table, une contrainte, un type) qu'il faut corriger.`
|
|
135
|
+
};
|
|
105
136
|
return {
|
|
106
137
|
verdict: "unknown",
|
|
107
138
|
code,
|
|
108
139
|
explanation: `échec de connexion à ${where}${code ? ` (${code})` : ""} — vérifier que l'infrastructure déclarée (NF_DATABASE_URL / connectors) désigne bien la base attendue, et qu'elle est démarrée.`
|
|
109
140
|
};
|
|
110
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Marqueur de la phrase « connecté, puis refusé » — lu par `create app`
|
|
144
|
+
* (`migrationFailureCause`, cœur) pour NE PAS conclure à une base injoignable.
|
|
145
|
+
* Le cœur ne peut pas importer ce paquet : sa copie est éprouvée contre
|
|
146
|
+
* celle-ci par un test de parité.
|
|
147
|
+
*/
|
|
148
|
+
const REJECTED_MARKER = "s'est connecté, mais le serveur a refusé une instruction — ";
|
|
149
|
+
/**
|
|
150
|
+
* Compose le message d'échec au démarrage d'un connecteur — UNE rédaction pour
|
|
151
|
+
* tous les adapters.
|
|
152
|
+
*
|
|
153
|
+
* 🔴 Un refus d'instruction (verdict `rejected`) ne commence JAMAIS par « n'a
|
|
154
|
+
* pas pu se connecter » : c'était faux, et la phrase envoyait vérifier
|
|
155
|
+
* l'adresse et l'état d'une base qui répondait très bien. La cause du pilote
|
|
156
|
+
* passe alors EN TÊTE — c'est elle qui nomme la table ou la contrainte.
|
|
157
|
+
*
|
|
158
|
+
* @param subject - sujet de la phrase (`Drizzle : le connecteur "x" (…)`).
|
|
159
|
+
* @param diagnosis - verdict de {@link diagnoseConnectionFailure}.
|
|
160
|
+
* @param cause - message brut du pilote.
|
|
161
|
+
* @param advice - conseil propre à l'adapter, pour un échec de CONNEXION.
|
|
162
|
+
* @returns le message complet.
|
|
163
|
+
*/
|
|
164
|
+
function describeConnectFailure(subject, diagnosis, cause, advice = "") {
|
|
165
|
+
if (diagnosis.verdict === "rejected") {
|
|
166
|
+
const explanation = diagnosis.explanation.charAt(0).toUpperCase() + diagnosis.explanation.slice(1);
|
|
167
|
+
return `${subject} ${REJECTED_MARKER}${cause}${diagnosis.code ? ` (${diagnosis.code})` : ""}. ${explanation}`;
|
|
168
|
+
}
|
|
169
|
+
return `${subject} n'a pas pu se connecter — ${diagnosis.explanation}${advice} Cause : ${cause}`;
|
|
170
|
+
}
|
|
111
171
|
//#endregion
|
|
112
|
-
export { diagnoseConnectionFailure, parseConnectionTarget };
|
|
172
|
+
export { REJECTED_MARKER, describeConnectFailure, diagnoseConnectionFailure, parseConnectionTarget };
|
|
@@ -72,5 +72,63 @@ var InvalidOrderOption = class extends Error {
|
|
|
72
72
|
this.received = received;
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Levée quand une suppression viserait une entité encore RÉFÉRENCÉE par une
|
|
77
|
+
* relation obligatoire d'une autre entité.
|
|
78
|
+
*
|
|
79
|
+
* C'est le pendant, pour un moteur sans clé étrangère (MongoDB), du `restrict`
|
|
80
|
+
* que le SQL généré pose sur une colonne de référence obligatoire : le parent
|
|
81
|
+
* ne part pas tant qu'un enfant le désigne. Sans cette garde, la suppression
|
|
82
|
+
* passait et `?include=` rendait ensuite `null` à la place du parent — une
|
|
83
|
+
* application passée du SQL à MongoDB perdait en silence une garantie qu'elle
|
|
84
|
+
* croyait avoir.
|
|
85
|
+
*
|
|
86
|
+
* Erreur de DONNÉES : c'est la surface API qui la projette (le rendu d'erreur
|
|
87
|
+
* HTTP la reconnaît par son `name` et répond 409).
|
|
88
|
+
*/
|
|
89
|
+
var ReferencedEntityError = class extends Error {
|
|
90
|
+
/** Entité dont la suppression est refusée (nom logique). */
|
|
91
|
+
entity;
|
|
92
|
+
/** Entité qui la référence encore (nom logique). */
|
|
93
|
+
referencedBy;
|
|
94
|
+
/** Champ de référence de {@link ReferencedEntityError.referencedBy}. */
|
|
95
|
+
field;
|
|
96
|
+
/**
|
|
97
|
+
* @param entity - entité dont la suppression est refusée.
|
|
98
|
+
* @param referencedBy - entité qui la référence encore.
|
|
99
|
+
* @param field - champ de référence obligatoire qui la désigne.
|
|
100
|
+
*/
|
|
101
|
+
constructor(entity, referencedBy, field) {
|
|
102
|
+
super(`Cannot delete "${entity}": still referenced by "${referencedBy}.${field}" (required reference). Delete or reassign the "${referencedBy}" entries first.`);
|
|
103
|
+
this.name = "ReferencedEntityError";
|
|
104
|
+
this.entity = entity;
|
|
105
|
+
this.referencedBy = referencedBy;
|
|
106
|
+
this.field = field;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Levée par `ITransaction.savepoint()` / `rollbackTo()` sur un moteur qui ne porte
|
|
111
|
+
* PAS de savepoints (MongoDB).
|
|
112
|
+
*
|
|
113
|
+
* Refuser vaut mieux que se taire : un `rollbackTo()` rendu en no-op laissait
|
|
114
|
+
* l'appelant croire ses écritures annulées alors qu'elles restaient en base. Le
|
|
115
|
+
* refus tombe dès `savepoint()`, pas au `rollbackTo()` : le chemin d'erreur, où
|
|
116
|
+
* l'on annule, est celui qu'on teste le moins — la faute doit se voir au premier
|
|
117
|
+
* essai. Pour annuler, terminer la transaction entière (`rollback()`, ou laisser
|
|
118
|
+
* le callback de `IOrm.transaction()` lever).
|
|
119
|
+
*/
|
|
120
|
+
var SavepointNotSupportedError = class extends Error {
|
|
121
|
+
/** Moteur qui refuse (valeur de `describeConnection().driver`). */
|
|
122
|
+
driver;
|
|
123
|
+
/**
|
|
124
|
+
* @param driver - moteur qui ne porte pas de savepoints.
|
|
125
|
+
* @param operation - opération refusée (`savepoint` ou `rollbackTo`).
|
|
126
|
+
*/
|
|
127
|
+
constructor(driver, operation) {
|
|
128
|
+
super(`${operation}() is not supported by the "${driver}" driver: it has no savepoints. Roll back the whole transaction instead (rollback(), or throw from the transaction callback).`);
|
|
129
|
+
this.name = "SavepointNotSupportedError";
|
|
130
|
+
this.driver = driver;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
75
133
|
//#endregion
|
|
76
|
-
export { InvalidOrderOption, UnknownCriteriaField };
|
|
134
|
+
export { InvalidOrderOption, ReferencedEntityError, SavepointNotSupportedError, UnknownCriteriaField };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ormRegistry } from "./OrmRegistry.js";
|
|
2
|
+
import { entityRegistry } from "./EntityRegistry.js";
|
|
3
|
+
//#region nodefony/src/orphanEntities.ts
|
|
4
|
+
/**
|
|
5
|
+
* Entités inscrites sur un connecteur qu'aucun ORM n'a ouvert.
|
|
6
|
+
*
|
|
7
|
+
* Une telle entité ne sera servie par rien : son dépôt lèvera au premier
|
|
8
|
+
* appel, loin de sa cause. Vécu : sur une infra MongoDB, le `User` d'une
|
|
9
|
+
* application restait écrit en table SQL sur `default` — un connecteur que
|
|
10
|
+
* Drizzle n'ouvre pas sur cette infra — et le boot n'en disait RIEN.
|
|
11
|
+
*
|
|
12
|
+
* @param entities - entités inscrites (registre des entités).
|
|
13
|
+
* @param connectors - connecteurs ouverts (registre des ORM).
|
|
14
|
+
* @returns les orphelines, dans l'ordre d'inscription.
|
|
15
|
+
*/
|
|
16
|
+
function findOrphanEntities(entities, connectors) {
|
|
17
|
+
return entities.filter((e) => !connectors.includes(e.connector));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* La phrase qui NOMME les orphelines et le remède — `null` s'il n'y en a pas.
|
|
21
|
+
*
|
|
22
|
+
* @param orphans - entités sans connecteur ouvert.
|
|
23
|
+
* @param connectors - connecteurs ouverts, cités pour situer la faute.
|
|
24
|
+
* @param declared - connecteurs DÉCLARÉS par les modules ORM (leur configuration).
|
|
25
|
+
*/
|
|
26
|
+
function describeOrphanEntities(orphans, connectors, declared = []) {
|
|
27
|
+
if (orphans.length === 0) return null;
|
|
28
|
+
const name = (e) => `${e.name}${e.module ? `@${e.module}` : ""} → « ${e.connector} »`;
|
|
29
|
+
const open = `Connecteurs ouverts : ${connectors.length > 0 ? connectors.join(", ") : "aucun"}.`;
|
|
30
|
+
const skipped = orphans.filter((e) => declared.includes(e.connector));
|
|
31
|
+
const unknown = orphans.filter((e) => !declared.includes(e.connector));
|
|
32
|
+
const parts = [];
|
|
33
|
+
if (unknown.length > 0) parts.push(`${unknown.length} entité(s) inscrite(s) sur un connecteur qu'aucun ORM n'a ouvert — rien ne les servira, leur dépôt lèvera au premier appel : ${unknown.map(name).join(", ")}. ${open} Une entité doit suivre la base déclarée (NF_DATABASE_URL) : table SQL sur une infra SQL, document sur MongoDB. État réel : npx nodefony inspect entities.`);
|
|
34
|
+
if (skipped.length > 0) parts.push(`${skipped.length} entité(s) sur un connecteur DÉCLARÉ mais jamais ouvert : ${skipped.map(name).join(", ")}. Ce n'est pas leur déclaration qui est en cause : un connecteur a échoué au démarrage (voir l'erreur de connexion plus haut), et ceux qui le suivent n'ont pas été ouverts. ${open}`);
|
|
35
|
+
return parts.join(" ");
|
|
36
|
+
}
|
|
37
|
+
/** Les démarrages déjà rapportés — un Kernel ne le dit qu'une fois. */
|
|
38
|
+
const reported = /* @__PURE__ */ new WeakSet();
|
|
39
|
+
/**
|
|
40
|
+
* Signale UNE fois par démarrage les entités orphelines, au moment où tous les
|
|
41
|
+
* ORM sont ouverts.
|
|
42
|
+
*
|
|
43
|
+
* Appelée par chaque module ORM (Drizzle, Mongoose) à `onReady` : la règle vit
|
|
44
|
+
* ici, une fois, et le premier appel la rend — le second se tait, sans quoi une
|
|
45
|
+
* application qui charge les deux ORM lirait deux fois le même avertissement.
|
|
46
|
+
* La garde porte sur le KERNEL et non sur le process : un process qui démarre
|
|
47
|
+
* plusieurs noyaux (une suite de tests) doit être prévenu à chacun.
|
|
48
|
+
*
|
|
49
|
+
* @param log - journal du module appelant.
|
|
50
|
+
* @param owner - le démarrage concerné (le Kernel), clé de la garde.
|
|
51
|
+
* @param declared - connecteurs que l'appelant a DÉCLARÉS (sa configuration).
|
|
52
|
+
* @returns le message rendu, ou `null` (rien à dire, ou déjà dit).
|
|
53
|
+
*/
|
|
54
|
+
function reportOrphanEntities(log, owner, declared = []) {
|
|
55
|
+
if (reported.has(owner)) return null;
|
|
56
|
+
reported.add(owner);
|
|
57
|
+
const connectors = ormRegistry.list();
|
|
58
|
+
const message = describeOrphanEntities(findOrphanEntities(entityRegistry.list(), connectors), connectors, declared);
|
|
59
|
+
if (message !== null) log(message, "WARNING");
|
|
60
|
+
return message;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { describeOrphanEntities, findOrphanEntities, reportOrphanEntities };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* Module, pas d'enregistrement dans `@modules()`. Les drivers concrets sont les
|
|
8
8
|
* Modules ; ils s'enregistrent eux-mêmes dans le `OrmRegistry` à leur boot.
|
|
9
9
|
*/
|
|
10
|
-
export type { IOrm, IEntity, IEntityRelation, IRepository, OrmCriteria, Criteria, FieldCriteria, FieldOperators, UpdateData, UpdateOperators, RepositoryReadOptions, ITransaction, IPage, IPageQuery, PageQuery, } from "./nodefony/interfaces/index.js";
|
|
10
|
+
export type { IOrm, IEntity, IEntityIndex, IEntityRelation, IRepository, OrmCriteria, Criteria, FieldCriteria, FieldOperators, UpdateData, UpdateOperators, RepositoryReadOptions, ITransaction, IPage, IPageQuery, PageQuery, } from "./nodefony/interfaces/index.js";
|
|
11
11
|
export { OPERATOR_KEYS, isFieldOperators, searchCriteria, LIKE_ESCAPE_CHAR, escapeLikeTerm, likePatternToRegExp, } from "./nodefony/src/criteria.js";
|
|
12
12
|
export type { OperatorKey } from "./nodefony/src/criteria.js";
|
|
13
13
|
export { UPDATE_OPERATOR_KEYS, isUpdateOperators, } from "./nodefony/src/criteria.js";
|
|
14
14
|
export type { UpdateOperatorKey } from "./nodefony/src/criteria.js";
|
|
15
|
-
export { UnknownCriteriaField, InvalidOrderOption, } from "./nodefony/src/errors.js";
|
|
15
|
+
export { UnknownCriteriaField, InvalidOrderOption, ReferencedEntityError, SavepointNotSupportedError, } from "./nodefony/src/errors.js";
|
|
16
16
|
export { assertOrderOption } from "./nodefony/src/readOptions.js";
|
|
17
17
|
export { OrmRegistry, ormRegistry } from "./nodefony/src/OrmRegistry.js";
|
|
18
18
|
export { EntityRegistry, entityRegistry } from "./nodefony/src/EntityRegistry.js";
|
|
19
|
+
export { reportOrphanEntities } from "./nodefony/src/orphanEntities.js";
|
|
19
20
|
export { Orm } from "./nodefony/src/Orm.js";
|
|
20
21
|
export { Entity } from "./nodefony/src/Entity.js";
|
|
21
22
|
export { AbstractCrudService } from "./nodefony/src/AbstractCrudService.js";
|
|
@@ -25,12 +26,12 @@ export { buildOrmGraph, buildConnectionHealth, buildOrmFlow, toDbml, toJsonSchem
|
|
|
25
26
|
export { queryFlowMonitor } from "./nodefony/src/QueryFlowMonitor.js";
|
|
26
27
|
export { connectionMonitor } from "./nodefony/src/ConnectionMonitor.js";
|
|
27
28
|
export { buildOrmLeanHealth } from "./nodefony/src/buildOrmLeanHealth.js";
|
|
28
|
-
export { diagnoseConnectionFailure, parseConnectionTarget, } from "./nodefony/src/connectionDiagnosis.js";
|
|
29
|
+
export { diagnoseConnectionFailure, describeConnectFailure, parseConnectionTarget, REJECTED_MARKER, } from "./nodefony/src/connectionDiagnosis.js";
|
|
29
30
|
export type { ConnectionVerdict, IConnectionDiagnosis, IConnectionTarget, } from "./nodefony/src/connectionDiagnosis.js";
|
|
30
31
|
export { CONNECT_TIMEOUT_MS, withConnectDeadline, } from "./nodefony/src/connectDeadline.js";
|
|
31
32
|
export { wireOrmAdminPlane, resolveOrmFlowEnabled, reportOrmBootLines, } from "./nodefony/src/ormWiring.js";
|
|
32
33
|
export type { ISlowQuery, IQueryFlow, IOrmFlowReport, } from "./nodefony/interfaces/IOrmFlow.js";
|
|
33
|
-
export type { IColumnInfo, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionHealth, } from "./nodefony/interfaces/IOrmGraph.js";
|
|
34
|
+
export type { IColumnInfo, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./nodefony/interfaces/IOrmGraph.js";
|
|
34
35
|
export type { ILatencyWindow, IOrmStorageProbe, IOrmPoolProbe, IOrmProbe, } from "./nodefony/interfaces/IOrmProbe.js";
|
|
35
36
|
export type { IOrmMigrationAction, IOrmMigrationEntry, IOrmMigrationSource, IOrmMigrationStatus, IOrmMigrationFailure, IOrmMigrationReply, IOrmPendingMigration, IOrmMigrationPlan, IOrmMigrationPlanReply, IOrmMigrationApplied, IOrmMigrationApplyReply, } from "./nodefony/interfaces/IOrmMigrations.js";
|
|
36
37
|
export { isMigrationFailure } from "./nodefony/interfaces/IOrmMigrations.js";
|
|
@@ -30,6 +30,19 @@ export interface IEntityRelation {
|
|
|
30
30
|
* @typeParam S - type du schéma natif du driver.
|
|
31
31
|
* @typeParam M - type du modèle compilé natif du driver.
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* Index COMPOSITE déclaré par une entité, pour l'adapter qui ne sait pas
|
|
35
|
+
* l'exprimer dans son schéma (Mongoose : un `SchemaDefinition` plat n'a d'index
|
|
36
|
+
* que par champ).
|
|
37
|
+
*/
|
|
38
|
+
export interface IEntityIndex {
|
|
39
|
+
/** Champs et sens, dans l'ORDRE de l'index (`1` croissant, `-1` décroissant). */
|
|
40
|
+
readonly fields: Readonly<Record<string, 1 | -1>>;
|
|
41
|
+
/** Contrainte d'unicité sur la combinaison. */
|
|
42
|
+
readonly unique?: boolean;
|
|
43
|
+
/** Nom de l'index en base (sinon celui que le driver dérive des champs). */
|
|
44
|
+
readonly name?: string;
|
|
45
|
+
}
|
|
33
46
|
export interface IEntity<S = unknown, M = unknown> {
|
|
34
47
|
/** Nom logique de l'entité (clé de lookup, ex. `"User"`). */
|
|
35
48
|
readonly name: string;
|
|
@@ -62,4 +75,10 @@ export interface IEntity<S = unknown, M = unknown> {
|
|
|
62
75
|
readonly timestamps?: boolean;
|
|
63
76
|
/** Relations déclarées vers d'autres entités (par nom logique). */
|
|
64
77
|
readonly relations?: ReadonlyArray<IEntityRelation>;
|
|
78
|
+
/**
|
|
79
|
+
* Index composites, appliqués par l'adapter qui les exprime hors schéma
|
|
80
|
+
* (Mongoose `schema.index()`). Les ORM schema-as-code (Drizzle) les déclarent
|
|
81
|
+
* dans leur table → ce champ y est sans effet. **Optionnel**.
|
|
82
|
+
*/
|
|
83
|
+
readonly indexes?: ReadonlyArray<IEntityIndex>;
|
|
65
84
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IRepository } from "./IRepository.js";
|
|
2
2
|
import type { ITransaction } from "./ITransaction.js";
|
|
3
|
-
import type { IColumnInfo, IConnectionInfo } from "./IOrmGraph.js";
|
|
3
|
+
import type { IOrmResilience, IColumnInfo, IConnectionInfo } from "./IOrmGraph.js";
|
|
4
4
|
import type { IOrmProbe } from "./IOrmProbe.js";
|
|
5
5
|
import type { IOrmMigrationApplyReply, IOrmMigrationPlanReply, IOrmMigrationReply } from "./IOrmMigrations.js";
|
|
6
6
|
/**
|
|
@@ -73,6 +73,15 @@ export interface IOrm {
|
|
|
73
73
|
* @returns infos de connexion, ou `undefined` si non implémenté.
|
|
74
74
|
*/
|
|
75
75
|
describeConnection?(): IConnectionInfo;
|
|
76
|
+
/**
|
|
77
|
+
* Décrit le mécanisme de résilience tel qu'il tourne (battement, perte en
|
|
78
|
+
* souffrance) pour le data plane. **Optionnel** : la classe `Orm` l'implémente
|
|
79
|
+
* pour tous ses adapters ; un ORM qui ne dérive pas d'elle laisse le
|
|
80
|
+
* diagnostic sans cette section.
|
|
81
|
+
*
|
|
82
|
+
* @returns l'état du mécanisme.
|
|
83
|
+
*/
|
|
84
|
+
describeResilience?(): IOrmResilience;
|
|
76
85
|
/**
|
|
77
86
|
* Ping bas-coût de la connexion (round-trip réel vers la base) pour le
|
|
78
87
|
* diagnostic du data plane. **Optionnel** : un adapter qui ne l'implémente pas
|
|
@@ -82,7 +82,11 @@ export interface IOrmSummary {
|
|
|
82
82
|
* `IOrm.vendor` déclaré par chaque adapter (P7.1 industrialisation ORM).
|
|
83
83
|
*/
|
|
84
84
|
vendor: string;
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* `true` pour le connecteur par défaut : celui qui porte les stores de
|
|
87
|
+
* l'application (un RÔLE constaté, pas le nom `"default"` — repli sur ce nom
|
|
88
|
+
* seulement quand aucun store n'est porté par un ORM).
|
|
89
|
+
*/
|
|
86
90
|
default: boolean;
|
|
87
91
|
/** État de la connexion. */
|
|
88
92
|
connected: boolean;
|
|
@@ -98,6 +102,37 @@ export interface IConnectionError {
|
|
|
98
102
|
/** Horodatage epoch ms. */
|
|
99
103
|
ts: number;
|
|
100
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Un événement de vie de la connexion : une PERTE ou une REPRISE, constatée
|
|
107
|
+
* par l'adapter (événement du driver) ou par le battement de cœur.
|
|
108
|
+
*/
|
|
109
|
+
export interface IConnectionEvent {
|
|
110
|
+
/** `lost` : la base a cessé de répondre ; `restored` : elle répond à nouveau. */
|
|
111
|
+
kind: "lost" | "restored";
|
|
112
|
+
/** Horodatage epoch ms. */
|
|
113
|
+
ts: number;
|
|
114
|
+
/** Cause de la perte (credential déjà retiré) — absente pour une reprise. */
|
|
115
|
+
reason?: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Le mécanisme de résilience d'un connecteur, TEL QU'IL TOURNE dans ce process.
|
|
119
|
+
*
|
|
120
|
+
* Rend lisible ce qui décide de « connecté / perdu / rétabli » : un pilote qui
|
|
121
|
+
* surveille ses serveurs (MongoDB) le sait seul ; les autres ne l'apprennent
|
|
122
|
+
* que par une requête, d'où le battement de cœur que porte la classe `Orm`.
|
|
123
|
+
*/
|
|
124
|
+
export interface IOrmResilience {
|
|
125
|
+
/** Période du battement (ms) ; `0` = désactivé (`NF_ORM_HEARTBEAT_MS=0`). */
|
|
126
|
+
heartbeatMs: number;
|
|
127
|
+
/** Délai au-delà duquel un battement sans réponse vaut une perte (ms). */
|
|
128
|
+
heartbeatTimeoutMs: number;
|
|
129
|
+
/** Le battement tourne-t-il en ce moment ? */
|
|
130
|
+
heartbeatActive: boolean;
|
|
131
|
+
/** L'adapter sait-il répondre à `ping()` (condition du battement) ? */
|
|
132
|
+
pingable: boolean;
|
|
133
|
+
/** Une perte est-elle constatée et PAS ENCORE réparée ? */
|
|
134
|
+
lostPending: boolean;
|
|
135
|
+
}
|
|
101
136
|
/**
|
|
102
137
|
* Diagnostic d'un connecteur — réponse de `/nodefony/orm/api/connection/health`.
|
|
103
138
|
* Combine l'état figé ({@link IConnectionInfo}), les compteurs de cycle de vie
|
|
@@ -131,8 +166,18 @@ export interface IConnectionHealth {
|
|
|
131
166
|
uptimeMs: number | null;
|
|
132
167
|
/** Nombre total de connexions réussies. */
|
|
133
168
|
connectCount: number;
|
|
134
|
-
/**
|
|
169
|
+
/** Reprises CONSTATÉES après une perte (jamais déduites des connexions). */
|
|
135
170
|
reconnectCount: number;
|
|
171
|
+
/** Pertes constatées depuis le démarrage du process. */
|
|
172
|
+
lostCount: number;
|
|
173
|
+
/** Dernière perte (epoch ms), `null` si aucune. */
|
|
174
|
+
lastLostAt: number | null;
|
|
175
|
+
/** Dernière reprise (epoch ms), `null` si aucune. */
|
|
176
|
+
lastRestoredAt: number | null;
|
|
177
|
+
/** Pertes et reprises récentes (ring borné, plus récentes d'abord). */
|
|
178
|
+
events: IConnectionEvent[];
|
|
179
|
+
/** Mécanisme de détection et de reprise, si l'ORM le décrit. */
|
|
180
|
+
resilience?: IOrmResilience;
|
|
136
181
|
/** Nombre total d'erreurs enregistrées (connexion + ping). */
|
|
137
182
|
errorCount: number;
|
|
138
183
|
/** Dernière erreur, `null` si aucune. */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Obtenue dans le callback de `IOrm.transaction()`. Les transactions cross-ORM
|
|
5
5
|
* (2PC) ne sont PAS garanties : une transaction porte sur un seul ORM/connexion.
|
|
6
|
-
*
|
|
6
|
+
* Un moteur sans savepoints (MongoDB) les REFUSE : `SavepointNotSupportedError`.
|
|
7
7
|
*/
|
|
8
8
|
export interface ITransaction {
|
|
9
9
|
/** Valide définitivement les opérations de la transaction. */
|
|
@@ -14,12 +14,14 @@ export interface ITransaction {
|
|
|
14
14
|
* Crée un point de sauvegarde nommé pour un rollback partiel ultérieur.
|
|
15
15
|
*
|
|
16
16
|
* @param name - identifiant du savepoint.
|
|
17
|
+
* @throws SavepointNotSupportedError si le moteur n'en porte pas (MongoDB).
|
|
17
18
|
*/
|
|
18
19
|
savepoint(name: string): Promise<void>;
|
|
19
20
|
/**
|
|
20
21
|
* Annule jusqu'à un savepoint sans terminer la transaction.
|
|
21
22
|
*
|
|
22
23
|
* @param name - savepoint cible.
|
|
24
|
+
* @throws SavepointNotSupportedError si le moteur n'en porte pas (MongoDB).
|
|
23
25
|
*/
|
|
24
26
|
rollbackTo(name: string): Promise<void>;
|
|
25
27
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { IOrm } from "./IOrm.js";
|
|
2
|
-
export type { IEntity, IEntityRelation } from "./IEntity.js";
|
|
2
|
+
export type { IEntity, IEntityIndex, IEntityRelation } from "./IEntity.js";
|
|
3
3
|
export type { IRepository, OrmCriteria, Criteria, FieldCriteria, FieldOperators, UpdateData, UpdateOperators, RepositoryReadOptions, } from "./IRepository.js";
|
|
4
4
|
export type { ITransaction } from "./ITransaction.js";
|
|
5
5
|
export type { IPage, IPageQuery, PageQuery } from "./IPage.js";
|
|
6
|
-
export type { IColumnInfo, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionHealth, } from "./IOrmGraph.js";
|
|
6
|
+
export type { IColumnInfo, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./IOrmGraph.js";
|
|
7
7
|
export type { ILatencyWindow, IOrmStorageProbe, IOrmPoolProbe, IOrmProbe, } from "./IOrmProbe.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IConnectionError } from "../interfaces/IOrmGraph.js";
|
|
1
|
+
import type { IConnectionError, IConnectionEvent } from "../interfaces/IOrmGraph.js";
|
|
2
2
|
import type { ILatencyWindow } from "../interfaces/IOrmProbe.js";
|
|
3
3
|
/** Vue figée des compteurs du moniteur pour un connecteur. */
|
|
4
4
|
export interface IConnectionMonitorCore {
|
|
@@ -8,6 +8,12 @@ export interface IConnectionMonitorCore {
|
|
|
8
8
|
reconnectCount: number;
|
|
9
9
|
/** Pertes de connexion constatées depuis le démarrage du process. */
|
|
10
10
|
lostCount: number;
|
|
11
|
+
/** Dernière perte (epoch ms), `null` si aucune. */
|
|
12
|
+
lastLostAt: number | null;
|
|
13
|
+
/** Dernière reprise (epoch ms), `null` si aucune. */
|
|
14
|
+
lastRestoredAt: number | null;
|
|
15
|
+
/** Pertes et reprises récentes, plus récentes d'abord. */
|
|
16
|
+
events: IConnectionEvent[];
|
|
11
17
|
errorCount: number;
|
|
12
18
|
lastConnectMs: number | null;
|
|
13
19
|
lastError: IConnectionError | null;
|
|
@@ -58,8 +64,9 @@ declare class ConnectionMonitor {
|
|
|
58
64
|
* preuve de bonne santé.
|
|
59
65
|
*
|
|
60
66
|
* @param name - clé du connecteur.
|
|
67
|
+
* @param reason - cause lisible (credential déjà retiré), si connue.
|
|
61
68
|
*/
|
|
62
|
-
recordLost(name: string): void;
|
|
69
|
+
recordLost(name: string, reason?: string): void;
|
|
63
70
|
/**
|
|
64
71
|
* Enregistre une **reprise** de connexion constatée par l'adapter.
|
|
65
72
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Service } from "nodefony";
|
|
2
2
|
import type { Container, Event, DefaultOptionsService } from "nodefony";
|
|
3
|
-
import type { IColumnInfo, IConnectionInfo, IOrm, IRepository, ITransaction } from "../interfaces/index.js";
|
|
3
|
+
import type { IColumnInfo, IConnectionInfo, IOrm, IOrmResilience, IRepository, ITransaction } from "../interfaces/index.js";
|
|
4
4
|
/**
|
|
5
5
|
* Classe de base abstraite de tout ORM Nodefony — câble {@link Service} (DI,
|
|
6
6
|
* Syslog, bus d'événements) et le contrat {@link IOrm}, et s'auto-enregistre
|
|
@@ -121,6 +121,14 @@ export declare abstract class Orm extends Service implements IOrm {
|
|
|
121
121
|
* prochain battement pour rendre la main.
|
|
122
122
|
*/
|
|
123
123
|
protected startHeartbeat(): void;
|
|
124
|
+
/**
|
|
125
|
+
* Décrit le mécanisme de résilience TEL QU'IL TOURNE — lecture pure, pour
|
|
126
|
+
* le data plane (`connection/health`) : période et montre du battement,
|
|
127
|
+
* battement actif ou non, perte en souffrance.
|
|
128
|
+
*
|
|
129
|
+
* @returns l'état du mécanisme dans ce process.
|
|
130
|
+
*/
|
|
131
|
+
describeResilience(): IOrmResilience;
|
|
124
132
|
/** Arrête le battement et libère la minuterie. Idempotent. */
|
|
125
133
|
protected stopHeartbeat(): void;
|
|
126
134
|
/**
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import type { IAdminApi, IAdminRegistry } from "nodefony";
|
|
1
|
+
import type { IAdminApi, IAdminRegistry, IStoreResolution } from "nodefony";
|
|
2
2
|
import type { IConnectionHealth, IOrmGraph } from "../interfaces/IOrmGraph.js";
|
|
3
3
|
import type { IOrmFlowReport } from "../interfaces/IOrmFlow.js";
|
|
4
|
+
/** Résumé des ORM enregistrés (statut connexion + nombre d'entités). */
|
|
5
|
+
/**
|
|
6
|
+
* Connecteur qui porte les stores de l'application — le connecteur PAR DÉFAUT.
|
|
7
|
+
*
|
|
8
|
+
* « Défaut » est un RÔLE, pas un nom : sur une infra MongoDB les briques
|
|
9
|
+
* durables vivent sur `nodefony` (Mongoose), et marquer `default` (Drizzle)
|
|
10
|
+
* parce qu'il s'appelle ainsi désignait une base que personne n'utilise. Le
|
|
11
|
+
* rôle se CONSTATE dans le registre des stores (#474) : le connecteur qui porte
|
|
12
|
+
* le plus de briques durables, puis de briques tout court.
|
|
13
|
+
*
|
|
14
|
+
* @param resolutions - registre des stores du kernel (`storeResolutions`).
|
|
15
|
+
* @param names - connecteurs enregistrés ; une brique sur un autre nom est ignorée.
|
|
16
|
+
* @returns le nom du connecteur, ou `null` si aucune brique n'est portée par
|
|
17
|
+
* un ORM (le repli sur le nom `default` s'applique alors).
|
|
18
|
+
*/
|
|
19
|
+
export declare function storeCarrier(resolutions: readonly IStoreResolution[], names: readonly string[]): string | null;
|
|
4
20
|
/** Construit le graphe canonique complet (optionnellement filtré par connecteur). */
|
|
5
21
|
export declare function buildOrmGraph(connectorFilter?: string): IOrmGraph;
|
|
6
22
|
/**
|
|
@@ -24,6 +24,12 @@ export type ConnectionVerdict =
|
|
|
24
24
|
"unreachable"
|
|
25
25
|
/** Un serveur a répondu, et il refuse — le port est bien tenu par quelqu'un. */
|
|
26
26
|
| "answered"
|
|
27
|
+
/**
|
|
28
|
+
* La connexion a ABOUTI, puis le serveur a refusé une INSTRUCTION — une
|
|
29
|
+
* table, une contrainte, un type. Ni l'adresse ni l'état de la base ne sont
|
|
30
|
+
* en cause : c'est le schéma envoyé qu'il faut corriger.
|
|
31
|
+
*/
|
|
32
|
+
| "rejected"
|
|
27
33
|
/** L'erreur ne permet de trancher ni dans un sens ni dans l'autre. */
|
|
28
34
|
| "unknown";
|
|
29
35
|
/** Diagnostic rendu à l'appelant, qui compose le message final. */
|
|
@@ -56,3 +62,26 @@ export declare function parseConnectionTarget(url?: string | null): IConnectionT
|
|
|
56
62
|
* @returns le verdict, le code constaté et une explication déjà rédigée.
|
|
57
63
|
*/
|
|
58
64
|
export declare function diagnoseConnectionFailure(error: unknown, target?: IConnectionTarget, platform?: string): IConnectionDiagnosis;
|
|
65
|
+
/**
|
|
66
|
+
* Marqueur de la phrase « connecté, puis refusé » — lu par `create app`
|
|
67
|
+
* (`migrationFailureCause`, cœur) pour NE PAS conclure à une base injoignable.
|
|
68
|
+
* Le cœur ne peut pas importer ce paquet : sa copie est éprouvée contre
|
|
69
|
+
* celle-ci par un test de parité.
|
|
70
|
+
*/
|
|
71
|
+
export declare const REJECTED_MARKER = "s'est connect\u00E9, mais le serveur a refus\u00E9 une instruction \u2014 ";
|
|
72
|
+
/**
|
|
73
|
+
* Compose le message d'échec au démarrage d'un connecteur — UNE rédaction pour
|
|
74
|
+
* tous les adapters.
|
|
75
|
+
*
|
|
76
|
+
* 🔴 Un refus d'instruction (verdict `rejected`) ne commence JAMAIS par « n'a
|
|
77
|
+
* pas pu se connecter » : c'était faux, et la phrase envoyait vérifier
|
|
78
|
+
* l'adresse et l'état d'une base qui répondait très bien. La cause du pilote
|
|
79
|
+
* passe alors EN TÊTE — c'est elle qui nomme la table ou la contrainte.
|
|
80
|
+
*
|
|
81
|
+
* @param subject - sujet de la phrase (`Drizzle : le connecteur "x" (…)`).
|
|
82
|
+
* @param diagnosis - verdict de {@link diagnoseConnectionFailure}.
|
|
83
|
+
* @param cause - message brut du pilote.
|
|
84
|
+
* @param advice - conseil propre à l'adapter, pour un échec de CONNEXION.
|
|
85
|
+
* @returns le message complet.
|
|
86
|
+
*/
|
|
87
|
+
export declare function describeConnectFailure(subject: string, diagnosis: IConnectionDiagnosis, cause: string, advice?: string): string;
|
|
@@ -60,3 +60,51 @@ export declare class InvalidOrderOption extends Error {
|
|
|
60
60
|
*/
|
|
61
61
|
constructor(entity: string, received: string);
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Levée quand une suppression viserait une entité encore RÉFÉRENCÉE par une
|
|
65
|
+
* relation obligatoire d'une autre entité.
|
|
66
|
+
*
|
|
67
|
+
* C'est le pendant, pour un moteur sans clé étrangère (MongoDB), du `restrict`
|
|
68
|
+
* que le SQL généré pose sur une colonne de référence obligatoire : le parent
|
|
69
|
+
* ne part pas tant qu'un enfant le désigne. Sans cette garde, la suppression
|
|
70
|
+
* passait et `?include=` rendait ensuite `null` à la place du parent — une
|
|
71
|
+
* application passée du SQL à MongoDB perdait en silence une garantie qu'elle
|
|
72
|
+
* croyait avoir.
|
|
73
|
+
*
|
|
74
|
+
* Erreur de DONNÉES : c'est la surface API qui la projette (le rendu d'erreur
|
|
75
|
+
* HTTP la reconnaît par son `name` et répond 409).
|
|
76
|
+
*/
|
|
77
|
+
export declare class ReferencedEntityError extends Error {
|
|
78
|
+
/** Entité dont la suppression est refusée (nom logique). */
|
|
79
|
+
readonly entity: string;
|
|
80
|
+
/** Entité qui la référence encore (nom logique). */
|
|
81
|
+
readonly referencedBy: string;
|
|
82
|
+
/** Champ de référence de {@link ReferencedEntityError.referencedBy}. */
|
|
83
|
+
readonly field: string;
|
|
84
|
+
/**
|
|
85
|
+
* @param entity - entité dont la suppression est refusée.
|
|
86
|
+
* @param referencedBy - entité qui la référence encore.
|
|
87
|
+
* @param field - champ de référence obligatoire qui la désigne.
|
|
88
|
+
*/
|
|
89
|
+
constructor(entity: string, referencedBy: string, field: string);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Levée par `ITransaction.savepoint()` / `rollbackTo()` sur un moteur qui ne porte
|
|
93
|
+
* PAS de savepoints (MongoDB).
|
|
94
|
+
*
|
|
95
|
+
* Refuser vaut mieux que se taire : un `rollbackTo()` rendu en no-op laissait
|
|
96
|
+
* l'appelant croire ses écritures annulées alors qu'elles restaient en base. Le
|
|
97
|
+
* refus tombe dès `savepoint()`, pas au `rollbackTo()` : le chemin d'erreur, où
|
|
98
|
+
* l'on annule, est celui qu'on teste le moins — la faute doit se voir au premier
|
|
99
|
+
* essai. Pour annuler, terminer la transaction entière (`rollback()`, ou laisser
|
|
100
|
+
* le callback de `IOrm.transaction()` lever).
|
|
101
|
+
*/
|
|
102
|
+
export declare class SavepointNotSupportedError extends Error {
|
|
103
|
+
/** Moteur qui refuse (valeur de `describeConnection().driver`). */
|
|
104
|
+
readonly driver: string;
|
|
105
|
+
/**
|
|
106
|
+
* @param driver - moteur qui ne porte pas de savepoints.
|
|
107
|
+
* @param operation - opération refusée (`savepoint` ou `rollbackTo`).
|
|
108
|
+
*/
|
|
109
|
+
constructor(driver: string, operation: "savepoint" | "rollbackTo");
|
|
110
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { IEntity } from "../interfaces/IEntity.js";
|
|
2
|
+
/**
|
|
3
|
+
* Entités inscrites sur un connecteur qu'aucun ORM n'a ouvert.
|
|
4
|
+
*
|
|
5
|
+
* Une telle entité ne sera servie par rien : son dépôt lèvera au premier
|
|
6
|
+
* appel, loin de sa cause. Vécu : sur une infra MongoDB, le `User` d'une
|
|
7
|
+
* application restait écrit en table SQL sur `default` — un connecteur que
|
|
8
|
+
* Drizzle n'ouvre pas sur cette infra — et le boot n'en disait RIEN.
|
|
9
|
+
*
|
|
10
|
+
* @param entities - entités inscrites (registre des entités).
|
|
11
|
+
* @param connectors - connecteurs ouverts (registre des ORM).
|
|
12
|
+
* @returns les orphelines, dans l'ordre d'inscription.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findOrphanEntities(entities: readonly IEntity[], connectors: readonly string[]): IEntity[];
|
|
15
|
+
/**
|
|
16
|
+
* La phrase qui NOMME les orphelines et le remède — `null` s'il n'y en a pas.
|
|
17
|
+
*
|
|
18
|
+
* @param orphans - entités sans connecteur ouvert.
|
|
19
|
+
* @param connectors - connecteurs ouverts, cités pour situer la faute.
|
|
20
|
+
* @param declared - connecteurs DÉCLARÉS par les modules ORM (leur configuration).
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeOrphanEntities(orphans: readonly IEntity[], connectors: readonly string[], declared?: readonly string[]): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Signale UNE fois par démarrage les entités orphelines, au moment où tous les
|
|
25
|
+
* ORM sont ouverts.
|
|
26
|
+
*
|
|
27
|
+
* Appelée par chaque module ORM (Drizzle, Mongoose) à `onReady` : la règle vit
|
|
28
|
+
* ici, une fois, et le premier appel la rend — le second se tait, sans quoi une
|
|
29
|
+
* application qui charge les deux ORM lirait deux fois le même avertissement.
|
|
30
|
+
* La garde porte sur le KERNEL et non sur le process : un process qui démarre
|
|
31
|
+
* plusieurs noyaux (une suite de tests) doit être prévenu à chacun.
|
|
32
|
+
*
|
|
33
|
+
* @param log - journal du module appelant.
|
|
34
|
+
* @param owner - le démarrage concerné (le Kernel), clé de la garde.
|
|
35
|
+
* @param declared - connecteurs que l'appelant a DÉCLARÉS (sa configuration).
|
|
36
|
+
* @returns le message rendu, ou `null` (rien à dire, ou déjà dit).
|
|
37
|
+
*/
|
|
38
|
+
export declare function reportOrphanEntities(log: (message: string, severity: "WARNING") => void, owner: object, declared?: readonly string[]): string | null;
|
package/docs/index.md
CHANGED
|
@@ -393,7 +393,7 @@ l'ordre des écouteurs, la table existe ou non. `entities()` s'accroche à `onRe
|
|
|
393
393
|
(`entitiesDecorator.ts:66`), strictement antérieur — sûr par construction. C'est la différence de
|
|
394
394
|
comportement avec `@controllers`, qui lui reste à `onBoot`.
|
|
395
395
|
|
|
396
|
-
**`Orm.connect()`** (`Orm.ts:
|
|
396
|
+
**`Orm.connect()`** (`Orm.ts:118`) est une **template method** : elle mesure la latence, alimente le
|
|
397
397
|
moniteur de connexion, puis émet `onOrmReady`. Un adapter surcharge `onConnect()` (`Orm.ts:74`), et
|
|
398
398
|
**jamais** `connect()` — sinon l'événement et l'instrumentation disparaissent.
|
|
399
399
|
|
|
@@ -412,8 +412,8 @@ transaction (`IOrm.transaction()`, `IOrm.ts:41`) et expose la trappe native
|
|
|
412
412
|
|
|
413
413
|
Quatre méthodes sont **optionnelles** — un adapter qui ne les implémente pas dégrade proprement au
|
|
414
414
|
lieu d'échouer : `describeEntity()` (`IOrm.ts:78`, colonnes pour l'ERD), `describeConnection()`
|
|
415
|
-
(`IOrm.ts:
|
|
416
|
-
`probe()` (`IOrm.ts:
|
|
415
|
+
(`IOrm.ts:88`, driver et cible **sans credential**), `ping()` (`IOrm.ts:109`, aller-retour réel) et
|
|
416
|
+
`probe()` (`IOrm.ts:119`, métriques driver, qui ne doit **jamais** lever).
|
|
417
417
|
|
|
418
418
|
### [`IRepository`](tutorial-entity.md) — les quinze verbes
|
|
419
419
|
|
|
@@ -422,16 +422,16 @@ verbes se choisissent sur **la garantie** qu'ils apportent, pas sur leur nom.
|
|
|
422
422
|
|
|
423
423
|
| Verbe | Ce qu'il garantit | Ancre |
|
|
424
424
|
| --------------------- | -------------------------------------------------------------------- | ---------------------------- |
|
|
425
|
-
| `find` / `findOne` | lecture filtrée + eager-load + tri + bornes | `IRepository.ts:
|
|
426
|
-
| `count` / `exists` | compter, ou juste savoir s'il y en a un (sans charger de colonne) | `IRepository.ts:
|
|
425
|
+
| `find` / `findOne` | lecture filtrée + eager-load + tri + bornes | `IRepository.ts:230` |
|
|
426
|
+
| `count` / `exists` | compter, ou juste savoir s'il y en a un (sans charger de colonne) | `IRepository.ts:395`, `:335` |
|
|
427
427
|
| `create` | insertion d'une ligne, rend la version persistée (id, défauts) | `IRepository.ts:240` |
|
|
428
428
|
| `createMany` | N lignes en **une** requête — seed, import, ingestion par lots | `IRepository.ts:252` |
|
|
429
429
|
| `updateOne` | met à jour **au plus une** ligne, **atomiquement**, et la rend | `IRepository.ts:269` |
|
|
430
430
|
| `updateMany` | met à jour toutes les lignes du critère, rend le **nombre** | `IRepository.ts:312` |
|
|
431
431
|
| `upsert` | insère **ou** met à jour sur conflit de clé, en **une** instruction | `IRepository.ts:296` |
|
|
432
432
|
| `increment` | `SET f = f + ?` atomique — compteurs, quotas, rate-limit | `IRepository.ts:287` |
|
|
433
|
-
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts:
|
|
434
|
-
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts:
|
|
433
|
+
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts:336` |
|
|
434
|
+
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts:345` |
|
|
435
435
|
| `findOneAndDelete` | supprime **et rend** la ligne — file de jobs, outbox, `pop` atomique | `IRepository.ts:355` |
|
|
436
436
|
| `withTransaction(tx)` | une **vue** du repository liée à une transaction | `IRepository.ts:406` |
|
|
437
437
|
|
|
@@ -440,7 +440,7 @@ verbes se choisissent sur **la garantie** qu'ils apportent, pas sur leur nom.
|
|
|
440
440
|
> `findOneAndUpdate` en Mongo), jamais un `UPDATE` suivi d'une relecture. La différence n'est pas
|
|
441
441
|
> cosmétique : la relecture rendrait `null` **à tort** dès que le critère porte sur un champ qu'on
|
|
442
442
|
> vient de modifier — `updateOne({ status: "pending" }, { status: "done" })` ne retrouve plus rien
|
|
443
|
-
> après coup (`IRepository.ts:
|
|
443
|
+
> après coup (`IRepository.ts:269`).
|
|
444
444
|
|
|
445
445
|
Quelques usages, un par garantie :
|
|
446
446
|
|
|
@@ -472,11 +472,13 @@ atomique sur les quatre backends.
|
|
|
472
472
|
|
|
473
473
|
### [`IEntity`](tutorial-entity.md) — la description d'une table
|
|
474
474
|
|
|
475
|
-
`IEntity` (`IEntity.ts:
|
|
475
|
+
`IEntity` (`IEntity.ts:51`) porte un nom logique, un `connector` (le nom d'une **connexion**, jamais
|
|
476
476
|
d'un moteur), un `schema` natif du driver, et des `relations` déclaratives (`IEntityRelation`,
|
|
477
477
|
`IEntity.ts:4`). Deux champs facultatifs servent la lisibilité d'un gros modèle : `module` (qui
|
|
478
|
-
apporte l'entité) et `domain` (`IEntity.ts:
|
|
479
|
-
une base de plusieurs centaines de tables).
|
|
478
|
+
apporte l'entité) et `domain` (`IEntity.ts:72`, la classification métier — l'axe qui rend navigable
|
|
479
|
+
une base de plusieurs centaines de tables). Un troisième, `indexes` (`IEntityIndex`), déclare les
|
|
480
|
+
index COMPOSITES qu'un adaptateur ne sait pas exprimer dans son schéma : Mongoose les applique
|
|
481
|
+
(`schema.index()`), Drizzle les ignore, puisqu'il les déclare dans sa table.
|
|
480
482
|
|
|
481
483
|
Dans une application, on ne construit pas un `IEntity` à la main : on écrit un `IEntityDefinition`
|
|
482
484
|
(`defineEntity.ts:15`) — le même objet **sans** `connector`, justement parce que le connecteur est
|
|
@@ -555,7 +557,7 @@ Deux décisions le rendent utilisable sur une grosse table :
|
|
|
555
557
|
`false`. C'est la distinction « Page » (avec total) et « Slice » (sans), reprise de Spring Data.
|
|
556
558
|
|
|
557
559
|
Les bornes sont normalisées plutôt que propagées : un `limit` de `0` ou un `offset` négatif est
|
|
558
|
-
ramené dans le domaine valide (`paginate.ts:
|
|
560
|
+
ramené dans le domaine valide (`paginate.ts:56`) — un `find({ limit: 0 })` a un comportement qui
|
|
559
561
|
dépend du dialecte, donc on ne le laisse pas sortir.
|
|
560
562
|
|
|
561
563
|
Le contrat de page lui-même (`IPage`, `IPageQuery`) vit dans le **cœur**
|
|
@@ -588,7 +590,7 @@ export class ArticleService extends AbstractCrudService<ArticleRow> {
|
|
|
588
590
|
- **Lectures** (`find`, `findOne`, `findById`, `count`, `findPage`) — **délégation pure**. Aucun
|
|
589
591
|
hook, aucun événement : c'est le chemin chaud, il ne doit rien payer.
|
|
590
592
|
- **Mutations** (`create`, `updateOne`, `delete`) — encadrées par des hooks _template method_
|
|
591
|
-
(`beforeCreate`, `AbstractCrudService.ts:
|
|
593
|
+
(`beforeCreate`, `AbstractCrudService.ts:212`, et ses six frères) puis un événement de cycle de vie
|
|
592
594
|
`onCreated` / `onUpdated` / `onDeleted`, **émis seulement si la mutation a eu lieu**
|
|
593
595
|
(`AbstractCrudService.ts:167`). L'audit, l'invalidation de cache ou une notification Studio s'y
|
|
594
596
|
abonnent sans toucher au service.
|
|
@@ -606,18 +608,19 @@ d'administration : elle ne charge qu'une page, quelle que soit la taille de la t
|
|
|
606
608
|
|
|
607
609
|
Deux drivers implémentent les contrats. Le contrat `IRepository` est tenu **en entier** par les
|
|
608
610
|
deux : les quinze verbes existent des deux côtés — par exemple l'upsert, avec
|
|
609
|
-
`DrizzleRepository.upsert()` (`DrizzleRepository.ts:
|
|
610
|
-
(`MongooseRepository.ts:
|
|
611
|
-
|
|
612
|
-
| Capacité | `@nodefony/drizzle` | `@nodefony/mongoose`
|
|
613
|
-
| -------------------------------------- | ----------------------------------- |
|
|
614
|
-
| Moteurs | SQLite, PostgreSQL, MySQL / MariaDB | MongoDB
|
|
615
|
-
| Contrat `IRepository` (15 verbes) | complet | complet
|
|
616
|
-
| Eager-load `{ relations }` | oui | oui (`populate`)
|
|
617
|
-
| Transactions
|
|
618
|
-
|
|
|
619
|
-
|
|
|
620
|
-
| Sonde
|
|
611
|
+
`DrizzleRepository.upsert()` (`DrizzleRepository.ts:879`) et `MongooseRepository.upsert()`
|
|
612
|
+
(`MongooseRepository.ts:405`).
|
|
613
|
+
|
|
614
|
+
| Capacité | `@nodefony/drizzle` | `@nodefony/mongoose` |
|
|
615
|
+
| -------------------------------------- | ----------------------------------- | -------------------------------------------- |
|
|
616
|
+
| Moteurs | SQLite, PostgreSQL, MySQL / MariaDB | MongoDB |
|
|
617
|
+
| Contrat `IRepository` (15 verbes) | complet | complet |
|
|
618
|
+
| Eager-load `{ relations }` | oui | oui (`populate`) |
|
|
619
|
+
| Transactions | oui | oui (replica set requis par Mongo) |
|
|
620
|
+
| Savepoints (rollback partiel) | oui | non — refusés (`SavepointNotSupportedError`) |
|
|
621
|
+
| Colonnes pour l'ERD (`describeEntity`) | oui (`DrizzleOrm.ts:1933`) | oui (`MongooseOrm.ts:649`) |
|
|
622
|
+
| Sonde de flux (requêtes/s, lentes) | oui — alimente `queryFlowMonitor` | non câblée |
|
|
623
|
+
| Sonde profonde (`probe`) | oui (`DrizzleOrm.ts:1835`) | oui (`MongooseOrm.ts:617`) |
|
|
621
624
|
|
|
622
625
|
**Les « stores » du framework, eux, ne sont pas alignés — et c'est un choix.** Un adapter déclare ce
|
|
623
626
|
qu'il porte dans son `package.json`, clé `nodefony.stores` :
|
|
@@ -644,7 +647,7 @@ qu'il porte dans son `package.json`, clé `nodefony.stores` :
|
|
|
644
647
|
|
|
645
648
|
Le contrat minimal tient en peu de choses, parce que `orm-core` fournit déjà la plomberie.
|
|
646
649
|
|
|
647
|
-
1. **Étendre `Orm`** (`Orm.ts:
|
|
650
|
+
1. **Étendre `Orm`** (`Orm.ts:46`) : implémenter `onConnect()`, `disconnect()`, `isConnected()`,
|
|
648
651
|
`getRepository()`, `transaction()`, `getNativeConnection()`. L'enregistrement dans `ormRegistry`
|
|
649
652
|
est fait par le constructeur de base — il n'y a rien à écrire. **Ne jamais surcharger
|
|
650
653
|
`connect()`** : c'est la template method qui émet `onOrmReady` et instrumente la latence.
|
|
@@ -672,7 +675,7 @@ la santé ne doit pas coûter le débit, et observer le débit ne doit pas réve
|
|
|
672
675
|
|
|
673
676
|
**`connectionMonitor`** (`ConnectionMonitor.ts:197`) suit le **cycle de vie** d'une connexion :
|
|
674
677
|
première connexion, reconnexions, erreurs récentes, et une fenêtre de latence de ping
|
|
675
|
-
(`ConnectionMonitor.recordPing()`, `ConnectionMonitor.ts:
|
|
678
|
+
(`ConnectionMonitor.recordPing()`, `ConnectionMonitor.ts:136`). Il est alimenté par `Orm.connect()`
|
|
676
679
|
sans que l'adapter ait à y penser.
|
|
677
680
|
|
|
678
681
|
**`queryFlowMonitor`** (`QueryFlowMonitor.ts:146`) suit le **débit** : total de requêtes, latence
|
|
@@ -687,7 +690,7 @@ rendent sûr en production :
|
|
|
687
690
|
observe, et le débit par seconde est **dérivé côté lecteur** (delta entre deux relevés), donc rien
|
|
688
691
|
n'est muté à la lecture.
|
|
689
692
|
|
|
690
|
-
Le data plane `/nodefony/orm/api/*` (`createOrmAdminApi()`, `OrmAdminApi.ts:
|
|
693
|
+
Le data plane `/nodefony/orm/api/*` (`createOrmAdminApi()`, `OrmAdminApi.ts:541`) expose huit points
|
|
691
694
|
d'entrée, tous filtrables par `?connector=` :
|
|
692
695
|
|
|
693
696
|
| Point d'entrée | Ce qu'il rend |
|
|
@@ -695,11 +698,11 @@ d'entrée, tous filtrables par `?connector=` :
|
|
|
695
698
|
| `orms` | les connecteurs, leur état, leur nombre d'entités |
|
|
696
699
|
| `entities` | le modèle complet : colonnes + relations |
|
|
697
700
|
| `entity/{name}` | une entité (404 si inconnue) |
|
|
698
|
-
| `graph` | le graphe canonique (`buildOrmGraph()`, `OrmAdminApi.ts:
|
|
701
|
+
| `graph` | le graphe canonique (`buildOrmGraph()`, `OrmAdminApi.ts:227`) |
|
|
699
702
|
| `counts` | le nombre de lignes par entité — un `COUNT(*)` par table |
|
|
700
703
|
| `connection/health` | état, latence, erreurs, reconnexions, sondes |
|
|
701
|
-
| `flow` | débit et requêtes lentes (`buildOrmFlow()`, `OrmAdminApi.ts:
|
|
702
|
-
| `export/{format}` | `dbml` (`toDbml()`, `OrmAdminApi.ts:
|
|
704
|
+
| `flow` | débit et requêtes lentes (`buildOrmFlow()`, `OrmAdminApi.ts:360`) |
|
|
705
|
+
| `export/{format}` | `dbml` (`toDbml()`, `OrmAdminApi.ts:395`) ou `jsonschema` |
|
|
703
706
|
|
|
704
707
|
Ce graphe canonique est **la pièce maîtresse**, pas le diagramme : c'est une donnée sérialisable qui
|
|
705
708
|
sert à la fois l'ERD de Studio, un export vers un outil tiers, et le contexte d'un agent IA
|
|
@@ -730,11 +733,11 @@ deux, et pas de course).
|
|
|
730
733
|
| Symptôme | Cause | Correction |
|
|
731
734
|
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
732
735
|
| « no entity registered under "X" » au premier appel | l'entité n'a jamais été inscrite (fichier importé mais `defineEntity` est sans effet de bord) | l'ajouter à `@entities([...])` sur le module (`entitiesDecorator.ts:56`) |
|
|
733
|
-
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec `connect()` | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts:
|
|
736
|
+
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec `connect()` | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts:56`) |
|
|
734
737
|
| « entity "User" exists on multiple connectors … specify one » | la même entité vit sur plusieurs connecteurs | préciser le connecteur : `entityRegistry.get("User", "analytics")` (`EntityRegistry.ts:54`) |
|
|
735
738
|
| Un filtre « champ vide » ne remonte jamais rien | `colonne = NULL` est toujours faux en SQL | `{ champ: { $null: true } }` ou la valeur nue `{ champ: null }` (`IRepository.ts:65`) |
|
|
736
739
|
| `UnknownCriteriaField` sur un champ qui existe « pourtant » | faute de frappe, ou champ calculé absent du schéma | lire les champs connus dans le message ; pour du natif, passer par `getNativeConnection()` |
|
|
737
|
-
| `updateOne` rend `null` alors que la ligne a bien changé | ancien réflexe `UPDATE` + relecture (le critère porte sur le champ modifié) | utiliser `updateOne`, atomique par construction (`IRepository.ts:
|
|
740
|
+
| `updateOne` rend `null` alors que la ligne a bien changé | ancien réflexe `UPDATE` + relecture (le critère porte sur le champ modifié) | utiliser `updateOne`, atomique par construction (`IRepository.ts:269`) |
|
|
738
741
|
| Un `upsert` écrase une valeur qui devait progresser | le `DO UPDATE` est inconditionnel (contrainte MySQL) | poser la condition **dans** la valeur : `{ seuil: { $max: v } }` (`IRepository.ts:94`) |
|
|
739
742
|
| Un objet de critère est pris pour une égalité (colonne JSON) | comportement **voulu** : une valeur n'est un filtre que si **toutes** ses clés sont des opérateurs | c'est la protection ; pour filtrer dedans, passer au natif (`criteria.ts:42`) |
|
|
740
743
|
| `onOrmReady` ne part plus après un ajout dans l'adapter | `connect()` a été surchargé | surcharger `onConnect()` (`Orm.ts:74`), jamais `connect()` |
|
package/docs/tutorial-entity.md
CHANGED
|
@@ -88,7 +88,7 @@ battre contre le cadre.
|
|
|
88
88
|
|
|
89
89
|
**1. Le connecteur est une donnée de configuration, jamais de code.** C'est pourquoi le
|
|
90
90
|
descripteur qu'on écrit (`IEntityDefinition`, `defineEntity.ts:15`) est un `IEntity`
|
|
91
|
-
(`IEntity.ts:
|
|
91
|
+
(`IEntity.ts:51`) **privé de son `connector`** : celui-ci est résolu au démarrage, avec `"default"`
|
|
92
92
|
pour valeur de repli (`DEFAULT_CONNECTOR`, `entitiesDecorator.ts:12`). Figer la connexion dans le
|
|
93
93
|
fichier d'entité interdirait de servir la même table depuis une base différente selon
|
|
94
94
|
l'environnement.
|
|
@@ -303,7 +303,7 @@ Cette absence d'effet de bord est délibérée : elle permet d'importer une enti
|
|
|
303
303
|
un script ou un autre module **sans** déclencher son inscription dans un singleton global.
|
|
304
304
|
|
|
305
305
|
Les champs facultatifs qui servent tôt : `module` (qui apporte l'entité) et `domain`
|
|
306
|
-
(`IEntity.ts:
|
|
306
|
+
(`IEntity.ts:72`), l'axe de classification qui rend navigable une base de plusieurs centaines de
|
|
307
307
|
tables dans l'ERD Studio.
|
|
308
308
|
|
|
309
309
|
### Étape 4 — brancher l'entité au module
|
|
@@ -318,7 +318,7 @@ d'être connues :
|
|
|
318
318
|
être instancié deux fois : tests, rechargement). Une vraie collision, elle, lève
|
|
319
319
|
(`EntityRegistry.register()`, `EntityRegistry.ts:24`).
|
|
320
320
|
3. **Connecteur commun** — `entities([...], { connector: "analytics" })` pose toute la liste sur
|
|
321
|
-
une autre base ; une entité qui porte son propre `connector` (`IEntity.ts:
|
|
321
|
+
une autre base ; une entité qui porte son propre `connector` (`IEntity.ts:56`) garde le sien.
|
|
322
322
|
|
|
323
323
|
### Le raccourci — `nodefony create entity`
|
|
324
324
|
|
|
@@ -339,7 +339,8 @@ que l'import plus la ligne du décorateur.
|
|
|
339
339
|
> [!WARNING]
|
|
340
340
|
> La commande refuse de générer si aucun module ORM n'est déclaré dans l'application. C'est
|
|
341
341
|
> volontaire : une entité sans driver produit du code mort qui ne compile même pas. Ajoute
|
|
342
|
-
> `
|
|
342
|
+
> `"@nodefony/drizzle"` (SQL) ou `"@nodefony/mongoose"` (MongoDB) au manifeste `modules`, puis
|
|
343
|
+
> relance. Sur MongoDB, la même commande écrit un schéma Mongoose au lieu d'une table.
|
|
343
344
|
|
|
344
345
|
## 🧰 Lire et écrire — le contrat complet
|
|
345
346
|
|
|
@@ -348,23 +349,23 @@ apportent, jamais sur leur nom.
|
|
|
348
349
|
|
|
349
350
|
| Verbe | Ce qu'il garantit | Ancre |
|
|
350
351
|
| --------------------- | ------------------------------------------------------------------ | ---------------------------- |
|
|
351
|
-
| `find` / `findOne` | lecture filtrée, avec tri, bornes et eager-load | `IRepository.ts:
|
|
352
|
+
| `find` / `findOne` | lecture filtrée, avec tri, bornes et eager-load | `IRepository.ts:230`, `:192` |
|
|
352
353
|
| `create` | insère une ligne et rend sa version persistée (id, défauts) | `IRepository.ts:240` |
|
|
353
354
|
| `createMany` | N lignes en **une** requête — seed, import, ingestion par lots | `IRepository.ts:252` |
|
|
354
355
|
| `updateOne` | modifie **au plus une** ligne, **atomiquement**, et la rend | `IRepository.ts:269` |
|
|
355
356
|
| `updateMany` | modifie toutes les lignes du critère, rend le **nombre** | `IRepository.ts:312` |
|
|
356
357
|
| `upsert` | insère **ou** met à jour sur conflit de clé, en une instruction | `IRepository.ts:296` |
|
|
357
358
|
| `increment` | `SET f = f + ?` atomique — compteurs, quotas, limitation de débit | `IRepository.ts:287` |
|
|
358
|
-
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts:
|
|
359
|
-
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts:
|
|
359
|
+
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts:336` |
|
|
360
|
+
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts:345` |
|
|
360
361
|
| `findOneAndDelete` | supprime **et rend** la ligne — file de jobs, `pop` atomique | `IRepository.ts:355` |
|
|
361
|
-
| `count` / `exists` | compter, ou juste savoir s'il y en a une (sans charger de colonne) | `IRepository.ts:
|
|
362
|
+
| `count` / `exists` | compter, ou juste savoir s'il y en a une (sans charger de colonne) | `IRepository.ts:395`, `:335` |
|
|
362
363
|
| `withTransaction(tx)` | une **vue** du repository liée à une transaction | `IRepository.ts:406` |
|
|
363
364
|
|
|
364
365
|
> [!IMPORTANT]
|
|
365
366
|
> **Il n'existe pas de méthode `update()`.** Le choix est explicite et il est intentionnel :
|
|
366
367
|
> `IRepository.updateOne()` (`IRepository.ts:269`) pour une ligne — atomique, et elle **rend** la
|
|
367
|
-
> ligne modifiée —, `IRepository.updateMany()` (`IRepository.ts:
|
|
368
|
+
> ligne modifiée —, `IRepository.updateMany()` (`IRepository.ts:312`) pour un lot — qui rend le
|
|
368
369
|
> **nombre** de lignes touchées. Un verbe unique masquerait cette différence de garantie, qui est
|
|
369
370
|
> précisément ce qu'on veut choisir en connaissance de cause.
|
|
370
371
|
|
|
@@ -393,7 +394,7 @@ await orm.transaction(async (tx) => {
|
|
|
393
394
|
```
|
|
394
395
|
|
|
395
396
|
Deux réflexes de performance, dès le premier jour : préférer `IRepository.exists()`
|
|
396
|
-
(`IRepository.ts:
|
|
397
|
+
(`IRepository.ts:395`) à `findOne(...) !== null` — aucune colonne n'est chargée —, et
|
|
397
398
|
`IRepository.increment()` (`IRepository.ts:325`) à une lecture suivie d'une écriture : une requête
|
|
398
399
|
au lieu de deux, et pas de course.
|
|
399
400
|
|
|
@@ -471,7 +472,7 @@ champs connus — le diagnostic d'une faute de frappe est immédiat.
|
|
|
471
472
|
|
|
472
473
|
## 📄 Pager les résultats
|
|
473
474
|
|
|
474
|
-
`find()` avec `limit`/`offset`/`order` (`RepositoryReadOptions`, `IRepository.ts:
|
|
475
|
+
`find()` avec `limit`/`offset`/`order` (`RepositoryReadOptions`, `IRepository.ts:174`) suffit pour
|
|
475
476
|
une tranche. Pour une vraie page — celle qui sait s'il y a une suite — utilise `paginate()`
|
|
476
477
|
(`paginate.ts:47`) :
|
|
477
478
|
|
|
@@ -524,9 +525,9 @@ d'administration, la primitive est `AbstractCrudService.findPage()` (`AbstractCr
|
|
|
524
525
|
|
|
525
526
|
| Symptôme | Cause | Correction |
|
|
526
527
|
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
527
|
-
| `posts.update is not a function` | la méthode `update()` n'existe pas dans le contrat | `updateOne` pour une ligne (`IRepository.ts:269`), `updateMany` pour un lot (`IRepository.ts:
|
|
528
|
+
| `posts.update is not a function` | la méthode `update()` n'existe pas dans le contrat | `updateOne` pour une ligne (`IRepository.ts:269`), `updateMany` pour un lot (`IRepository.ts:312`) |
|
|
528
529
|
| « no entity registered under "Post" » au premier appel | le fichier d'entité est importé, mais `defineEntity()` est **sans effet de bord** | ajouter l'entité à `@entities([...])` sur le module (`entitiesDecorator.ts:56`) |
|
|
529
|
-
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec l'ouverture du connecteur | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts:
|
|
530
|
+
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec l'ouverture du connecteur | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts:56`) |
|
|
530
531
|
| Une colonne ajoutée au schéma reste absente de la table | le DDL du boot est un `CREATE TABLE IF NOT EXISTS` : aucun `ALTER` n'est émis | supprimer la base de développement et redémarrer, ou passer par une migration |
|
|
531
532
|
| Un `DEFAULT` SQL ou un index déclaré n'apparaît pas | le DDL dérivé ne les émet pas | poser le défaut côté JavaScript (`$defaultFn`) ; créer l'index par migration |
|
|
532
533
|
| Un filtre « champ vide » ne remonte jamais rien | `colonne = NULL` est toujours faux en SQL | `{ champ: { $null: true } }` ou la valeur nue `{ champ: null }` (`IRepository.ts:65`) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodefony/orm-core",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.9",
|
|
4
4
|
"description": "Un contrat de dépôt de données pour Nodefony, plusieurs moteurs : interfaces, registre et classes de base pour le support multi-ORM",
|
|
5
5
|
"contributors": [],
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"types": "./dist/types/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"nodefony-source": "./index.ts",
|
|
11
12
|
"types": "./dist/types/index.d.ts",
|
|
12
13
|
"import": "./dist/index.js",
|
|
13
14
|
"default": "./dist/index.js"
|
|
@@ -15,9 +16,9 @@
|
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
18
|
"start": "node dist/index.js",
|
|
18
|
-
"build": "
|
|
19
|
+
"build": "rolldown -c rolldown.config.ts && tsgo -p tsconfig.declarations.json",
|
|
19
20
|
"dev": "rolldown -c rolldown.config.ts --watch",
|
|
20
|
-
"clean": "
|
|
21
|
+
"clean": "node --eval \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
21
22
|
"test": "vitest run",
|
|
22
23
|
"coverage": "vitest run --coverage",
|
|
23
24
|
"typecheck": "tsgo --noEmit -p tsconfig.json && tsgo --noEmit -p tsconfig.tests.json"
|
|
@@ -36,12 +37,11 @@
|
|
|
36
37
|
"esm"
|
|
37
38
|
],
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"nodefony": "^10.0.0-alpha.
|
|
40
|
+
"nodefony": "^10.0.0-alpha.9"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@types/node": "26.6.
|
|
43
|
-
"nodefony": "^10.0.0-alpha.
|
|
44
|
-
"rimraf": "6.1.3",
|
|
43
|
+
"@types/node": "26.6.2",
|
|
44
|
+
"nodefony": "^10.0.0-alpha.9",
|
|
45
45
|
"vitest": "5.0.1"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|