@nodefony/orm-core 10.0.0-beta.5 → 10.0.0-beta.6
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 +21 -6
- package/dist/index.js +3 -3
- package/dist/nodefony/src/OrmAdminApi.js +52 -14
- package/dist/nodefony/src/decorators/index.js +1 -1
- package/dist/nodefony/src/ormWiring.js +1 -1
- package/dist/nodefony/src/orphanEntities.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IEntity.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IOrmGraph.d.ts +20 -1
- package/dist/types/nodefony/interfaces/index.d.ts +1 -1
- package/dist/types/nodefony/src/OrmAdminApi.d.ts +8 -0
- package/docs/index.md +5 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -99,20 +99,35 @@ du module**, sur la classe et non sur une instance : il connaît donc son nom et
|
|
|
99
99
|
instancier, et enregistre l'entité directement.
|
|
100
100
|
|
|
101
101
|
```typescript
|
|
102
|
+
import { randomUUID } from "node:crypto";
|
|
102
103
|
import { entity, repository } from "@nodefony/orm-core";
|
|
103
104
|
import type { IRepository } from "@nodefony/orm-core";
|
|
105
|
+
import { defineTable } from "@nodefony/drizzle";
|
|
106
|
+
|
|
107
|
+
// Le `schema` est la forme de l'ADAPTATEUR : ici la description portable du kit
|
|
108
|
+
// Drizzle, qui suit le dialecte du connecteur.
|
|
109
|
+
const memberTable = defineTable({
|
|
110
|
+
name: "Member",
|
|
111
|
+
columns: {
|
|
112
|
+
id: { kind: "uuid", primaryKey: true, defaultFn: () => randomUUID() },
|
|
113
|
+
email: { kind: "string", length: 255, notNull: true },
|
|
114
|
+
},
|
|
115
|
+
});
|
|
104
116
|
|
|
105
|
-
// `name` vaut par défaut le nom de la classe ; ici forcé à "
|
|
117
|
+
// `name` vaut par défaut le nom de la classe ; ici forcé à "Member".
|
|
106
118
|
@entity({
|
|
107
119
|
connector: "db_principale",
|
|
108
|
-
name: "
|
|
109
|
-
schema:
|
|
120
|
+
name: "Member",
|
|
121
|
+
schema: memberTable,
|
|
110
122
|
relations: [{ type: "one-to-many", target: "Room", field: "rooms" }],
|
|
111
123
|
})
|
|
112
|
-
class
|
|
124
|
+
class MemberEntity {}
|
|
113
125
|
|
|
114
|
-
@repository("repository.
|
|
115
|
-
|
|
126
|
+
@repository("repository.member", {
|
|
127
|
+
entity: "Member",
|
|
128
|
+
connector: "db_principale",
|
|
129
|
+
})
|
|
130
|
+
class MemberRepository implements IRepository<MemberEntity> {
|
|
116
131
|
/* find, findOne, create… sont fournis par l'adaptateur ORM */
|
|
117
132
|
}
|
|
118
133
|
```
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,13 @@ 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
6
|
import { reportOrphanEntities } from "./nodefony/src/orphanEntities.js";
|
|
7
|
-
import { connectionMonitor } from "./nodefony/src/ConnectionMonitor.js";
|
|
8
7
|
import { Orm } from "./nodefony/src/Orm.js";
|
|
9
8
|
import { Entity } from "./nodefony/src/Entity.js";
|
|
10
|
-
import { paginate } from "./nodefony/src/paginate.js";
|
|
11
9
|
import { AbstractCrudService } from "./nodefony/src/AbstractCrudService.js";
|
|
12
|
-
import {
|
|
10
|
+
import { paginate } from "./nodefony/src/paginate.js";
|
|
13
11
|
import { buildConnectionHealth, buildOrmFlow, buildOrmGraph, createOrmAdminApi, registerOrmAdminApi, toDbml, toJsonSchema } from "./nodefony/src/OrmAdminApi.js";
|
|
12
|
+
import { queryFlowMonitor } from "./nodefony/src/QueryFlowMonitor.js";
|
|
13
|
+
import { connectionMonitor } from "./nodefony/src/ConnectionMonitor.js";
|
|
14
14
|
import { buildOrmLeanHealth } from "./nodefony/src/buildOrmLeanHealth.js";
|
|
15
15
|
import { REJECTED_MARKER, describeConnectFailure, diagnoseConnectionFailure, parseConnectionTarget } from "./nodefony/src/connectionDiagnosis.js";
|
|
16
16
|
import { CONNECT_TIMEOUT_MS, withConnectDeadline } from "./nodefony/src/connectDeadline.js";
|
|
@@ -131,8 +131,51 @@ function buildOrmSummaries() {
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* Clé étrangère d'une relation qui n'en nomme aucune : `<entité>Id`, l'entité
|
|
135
|
+
* en minuscule initiale — la convention des adapters.
|
|
136
|
+
*
|
|
137
|
+
* @param entity - nom logique de l'entité qui donne son nom à la clé.
|
|
138
|
+
* @returns le nom de colonne conventionnel.
|
|
139
|
+
*/
|
|
140
|
+
function conventionalForeignKey(entity) {
|
|
141
|
+
return `${entity.charAt(0).toLowerCase()}${entity.slice(1)}Id`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Relations d'un nœud : celles que l'entité DÉCLARE, puis une « plusieurs vers
|
|
145
|
+
* un » par clé étrangère que la base porte et qu'aucune déclaration ne couvre.
|
|
146
|
+
*
|
|
147
|
+
* Sans la seconde moitié, une entité qui référence par ses colonnes (`ref` du
|
|
148
|
+
* kit, table écrite à la main) sans répéter le lien dans `relations` n'avait
|
|
149
|
+
* AUCUNE arête dans le graphe, alors que la contrainte existe en base.
|
|
150
|
+
* Une déclaration couvre une colonne quand sa clé — explicite, ou
|
|
151
|
+
* conventionnelle — est cette colonne.
|
|
152
|
+
*/
|
|
153
|
+
function nodeRelations(declared, columns) {
|
|
154
|
+
const relations = declared.map((r) => ({
|
|
155
|
+
type: r.type,
|
|
156
|
+
target: r.target,
|
|
157
|
+
field: r.field,
|
|
158
|
+
foreignKey: r.foreignKey
|
|
159
|
+
}));
|
|
160
|
+
let covered = null;
|
|
161
|
+
for (const col of columns) {
|
|
162
|
+
if (col.references === void 0) continue;
|
|
163
|
+
covered ??= new Set(declared.filter((r) => r.type === "many-to-one" || r.type === "one-to-one").map((r) => r.foreignKey ?? conventionalForeignKey(r.target)));
|
|
164
|
+
if (covered.has(col.name)) continue;
|
|
165
|
+
relations.push({
|
|
166
|
+
type: "many-to-one",
|
|
167
|
+
target: col.references.entity,
|
|
168
|
+
field: col.name,
|
|
169
|
+
foreignKey: col.name,
|
|
170
|
+
targetKey: col.references.column
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return relations;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Construit un nœud de graphe pour une entité : colonnes (via
|
|
177
|
+
* `orm.describeEntity` si l'adapter l'implémente et si l'ORM est connecté) et
|
|
178
|
+
* relations, déclarées puis imposées par les clés étrangères.
|
|
136
179
|
*/
|
|
137
180
|
function buildEntityNode(connector, name) {
|
|
138
181
|
const entity = entityRegistry.get(name, connector);
|
|
@@ -148,12 +191,7 @@ function buildEntityNode(connector, name) {
|
|
|
148
191
|
module: entity.module ?? "",
|
|
149
192
|
domain: entity.domain ?? "",
|
|
150
193
|
columns,
|
|
151
|
-
relations: (entity.relations ?? [])
|
|
152
|
-
type: r.type,
|
|
153
|
-
target: r.target,
|
|
154
|
-
field: r.field,
|
|
155
|
-
foreignKey: r.foreignKey
|
|
156
|
-
}))
|
|
194
|
+
relations: nodeRelations(entity.relations ?? [], columns)
|
|
157
195
|
};
|
|
158
196
|
}
|
|
159
197
|
/** Construit le graphe canonique complet (optionnellement filtré par connecteur). */
|
|
@@ -327,13 +365,13 @@ function toDbml(graph) {
|
|
|
327
365
|
refs.add(`// many-to-many ${node.name}.${rel.field} <> ${rel.target} (table de jonction)`);
|
|
328
366
|
continue;
|
|
329
367
|
}
|
|
330
|
-
const
|
|
368
|
+
const key = dbmlId(rel.targetKey ?? "id");
|
|
331
369
|
if (rel.type === "one-to-many") {
|
|
332
|
-
const fk = rel.foreignKey ??
|
|
333
|
-
refs.add(`Ref: ${dbmlId(rel.target)}.${dbmlId(fk)} > ${dbmlId(node.name)}
|
|
370
|
+
const fk = rel.foreignKey ?? conventionalForeignKey(node.name);
|
|
371
|
+
refs.add(`Ref: ${dbmlId(rel.target)}.${dbmlId(fk)} > ${dbmlId(node.name)}.${key}`);
|
|
334
372
|
} else {
|
|
335
|
-
const fk = rel.foreignKey ??
|
|
336
|
-
refs.add(`Ref: ${dbmlId(node.name)}.${dbmlId(fk)} > ${dbmlId(rel.target)}
|
|
373
|
+
const fk = rel.foreignKey ?? conventionalForeignKey(rel.target);
|
|
374
|
+
refs.add(`Ref: ${dbmlId(node.name)}.${dbmlId(fk)} > ${dbmlId(rel.target)}.${key}`);
|
|
337
375
|
}
|
|
338
376
|
}
|
|
339
377
|
lines.push(...refs);
|
|
@@ -529,4 +567,4 @@ function registerOrmAdminApi(registry) {
|
|
|
529
567
|
if (registry.has("orm")) return;
|
|
530
568
|
registry.register(createOrmAdminApi());
|
|
531
569
|
}
|
|
532
|
-
export { buildConnectionHealth, buildOrmFlow, buildOrmGraph, createOrmAdminApi, registerOrmAdminApi, storeCarrier, toDbml, toJsonSchema };
|
|
570
|
+
export { buildConnectionHealth, buildOrmFlow, buildOrmGraph, conventionalForeignKey, createOrmAdminApi, registerOrmAdminApi, storeCarrier, toDbml, toJsonSchema };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getEntityMeta, getRepositoryMeta, hasEntityMeta, hasRepositoryMeta } from "./metadataStore.js";
|
|
2
1
|
import { entity } from "./entityDecorator.js";
|
|
3
2
|
import { DEFAULT_CONNECTOR, entities } from "./entitiesDecorator.js";
|
|
4
3
|
import { repository } from "./repositoryDecorator.js";
|
|
4
|
+
import { getEntityMeta, getRepositoryMeta, hasEntityMeta, hasRepositoryMeta } from "./metadataStore.js";
|
|
5
5
|
export { DEFAULT_CONNECTOR, entities, entity, getEntityMeta, getRepositoryMeta, hasEntityMeta, hasRepositoryMeta, repository };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ormRegistry } from "./OrmRegistry.js";
|
|
2
1
|
import { buildConnectionHealth, buildOrmFlow, registerOrmAdminApi } from "./OrmAdminApi.js";
|
|
3
2
|
import { buildOrmLeanHealth } from "./buildOrmLeanHealth.js";
|
|
3
|
+
import { ormRegistry } from "./OrmRegistry.js";
|
|
4
4
|
import { setOrmHealthProvider, setOrmRichProvider } from "nodefony";
|
|
5
5
|
// nodefony/src/ormWiring.ts
|
|
6
6
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type { ConnectionVerdict, IConnectionDiagnosis, IConnectionTarget, } from
|
|
|
31
31
|
export { CONNECT_TIMEOUT_MS, withConnectDeadline, } from "./nodefony/src/connectDeadline.js";
|
|
32
32
|
export { wireOrmAdminPlane, resolveOrmFlowEnabled, reportOrmBootLines, } from "./nodefony/src/ormWiring.js";
|
|
33
33
|
export type { ISlowQuery, IQueryFlow, IOrmFlowReport, } from "./nodefony/interfaces/IOrmFlow.js";
|
|
34
|
-
export type { IColumnInfo, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./nodefony/interfaces/IOrmGraph.js";
|
|
34
|
+
export type { IColumnInfo, IColumnReference, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./nodefony/interfaces/IOrmGraph.js";
|
|
35
35
|
export type { ILatencyWindow, IOrmStorageProbe, IOrmPoolProbe, IOrmProbe, } from "./nodefony/interfaces/IOrmProbe.js";
|
|
36
36
|
export type { IOrmMigrationAction, IOrmMigrationEntry, IOrmMigrationSource, IOrmMigrationStatus, IOrmMigrationFailure, IOrmMigrationReply, IOrmPendingMigration, IOrmMigrationPlan, IOrmMigrationPlanReply, IOrmMigrationApplied, IOrmMigrationApplyReply, } from "./nodefony/interfaces/IOrmMigrations.js";
|
|
37
37
|
export { isMigrationFailure } from "./nodefony/interfaces/IOrmMigrations.js";
|
|
@@ -81,4 +81,16 @@ export interface IEntity<S = unknown, M = unknown> {
|
|
|
81
81
|
* dans leur table → ce champ y est sans effet. **Optionnel**.
|
|
82
82
|
*/
|
|
83
83
|
readonly indexes?: ReadonlyArray<IEntityIndex> | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Champs qui ne sortent JAMAIS par une relation : quand une autre entité
|
|
86
|
+
* charge celle-ci (`relations: ["owner"]`, `?include=owner`), l'adapter ne les
|
|
87
|
+
* lit pas. Le dépôt de l'entité elle-même les rend toujours — c'est la porte
|
|
88
|
+
* de qui en a besoin (l'authentification lit `password` par là).
|
|
89
|
+
*
|
|
90
|
+
* Sans cette déclaration, référencer un `User` exposait son empreinte de mot
|
|
91
|
+
* de passe à quiconque lisait la ressource qui le référence. Un nom qui ne
|
|
92
|
+
* désigne aucun champ fait échouer la connexion en le nommant : une faute de
|
|
93
|
+
* frappe ici rouvrirait la fuite sans un mot. **Optionnel**.
|
|
94
|
+
*/
|
|
95
|
+
readonly privateFields?: ReadonlyArray<string> | undefined;
|
|
84
96
|
}
|
|
@@ -22,6 +22,20 @@ export interface IColumnInfo {
|
|
|
22
22
|
nullable: boolean;
|
|
23
23
|
/** `true` si contrainte d'unicité. */
|
|
24
24
|
unique: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Cible de la clé étrangère que la BASE porte sur cette colonne — absente si
|
|
27
|
+
* la colonne n'en porte pas, ou si l'adapter ne sait pas la lire. Le graphe
|
|
28
|
+
* en dérive une relation « plusieurs vers un » quand aucune relation déclarée
|
|
29
|
+
* ne couvre déjà la colonne.
|
|
30
|
+
*/
|
|
31
|
+
references?: IColumnReference | undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Cible d'une clé étrangère : l'entité visée et la colonne qu'elle y désigne. */
|
|
34
|
+
export interface IColumnReference {
|
|
35
|
+
/** Nom logique de l'entité visée (nom de table si aucune entité ne la porte). */
|
|
36
|
+
entity: string;
|
|
37
|
+
/** Colonne visée (en général la clé primaire). */
|
|
38
|
+
column: string;
|
|
25
39
|
}
|
|
26
40
|
/** Relation projetée pour le graphe (sous-ensemble sérialisable de `IEntityRelation`). */
|
|
27
41
|
export interface IRelationInfo {
|
|
@@ -33,6 +47,8 @@ export interface IRelationInfo {
|
|
|
33
47
|
field: string;
|
|
34
48
|
/** Clé étrangère (explicite ou dérivée déterministe). */
|
|
35
49
|
foreignKey?: string | undefined;
|
|
50
|
+
/** Colonne visée sur la cible — `id` par convention quand elle est absente. */
|
|
51
|
+
targetKey?: string | undefined;
|
|
36
52
|
}
|
|
37
53
|
/** Nœud du graphe : une entité avec ses colonnes et ses relations. */
|
|
38
54
|
export interface IEntityGraphNode {
|
|
@@ -51,7 +67,10 @@ export interface IEntityGraphNode {
|
|
|
51
67
|
domain: string;
|
|
52
68
|
/** Colonnes normalisées (vide si l'adapter n'implémente pas `describeEntity`). */
|
|
53
69
|
columns: IColumnInfo[];
|
|
54
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Relations : celles que l'entité DÉCLARE, puis celles que ses clés
|
|
72
|
+
* étrangères imposent et qu'aucune déclaration ne couvre.
|
|
73
|
+
*/
|
|
55
74
|
relations: IRelationInfo[];
|
|
56
75
|
}
|
|
57
76
|
/** Connexion sous-jacente d'un ORM (extrait via {@link IOrm.describeConnection}). */
|
|
@@ -3,5 +3,5 @@ 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, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./IOrmGraph.js";
|
|
6
|
+
export type { IColumnInfo, IColumnReference, IConnectionInfo, IRelationInfo, IEntityGraphNode, IOrmSummary, IOrmGraph, IConnectionError, IConnectionEvent, IOrmResilience, IConnectionHealth, } from "./IOrmGraph.js";
|
|
7
7
|
export type { ILatencyWindow, IOrmStorageProbe, IOrmPoolProbe, IOrmProbe, } from "./IOrmProbe.js";
|
|
@@ -17,6 +17,14 @@ import type { IOrmFlowReport } from "../interfaces/IOrmFlow.js";
|
|
|
17
17
|
* un ORM (le repli sur le nom `default` s'applique alors).
|
|
18
18
|
*/
|
|
19
19
|
export declare function storeCarrier(resolutions: readonly IStoreResolution[], names: readonly string[]): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Clé étrangère d'une relation qui n'en nomme aucune : `<entité>Id`, l'entité
|
|
22
|
+
* en minuscule initiale — la convention des adapters.
|
|
23
|
+
*
|
|
24
|
+
* @param entity - nom logique de l'entité qui donne son nom à la clé.
|
|
25
|
+
* @returns le nom de colonne conventionnel.
|
|
26
|
+
*/
|
|
27
|
+
export declare function conventionalForeignKey(entity: string): string;
|
|
20
28
|
/** Construit le graphe canonique complet (optionnellement filtré par connecteur). */
|
|
21
29
|
export declare function buildOrmGraph(connectorFilter?: string): IOrmGraph;
|
|
22
30
|
/**
|
package/docs/index.md
CHANGED
|
@@ -478,7 +478,11 @@ atomique sur les quatre backends.
|
|
|
478
478
|
d'un moteur), un `schema` natif du driver, et des `relations` déclaratives (`IEntityRelation`,
|
|
479
479
|
`IEntity.ts#IEntityRelation`). Deux champs facultatifs servent la lisibilité d'un gros modèle : `module` (qui
|
|
480
480
|
apporte l'entité) et `domain` (`IEntity.ts#IEntity.domain`, la classification métier — l'axe qui rend navigable
|
|
481
|
-
une base de plusieurs centaines de tables
|
|
481
|
+
une base de plusieurs centaines de tables ; `create entity --domain` le pose). Le graphe d'entités ne
|
|
482
|
+
se limite pas aux `relations` déclarées : une colonne qui porte une clé étrangère en base en rend la
|
|
483
|
+
cible (`IOrmGraph.ts#IColumnReference`, lue par `describeEntity`), et le graphe en tire une
|
|
484
|
+
relation « plusieurs vers un » quand aucune déclaration ne couvre déjà la colonne
|
|
485
|
+
(`OrmAdminApi.ts#conventionalForeignKey` dit quelle colonne une déclaration couvre). Un troisième, `indexes` (`IEntityIndex`), déclare les
|
|
482
486
|
index COMPOSITES qu'un adaptateur ne sait pas exprimer dans son schéma : Mongoose les applique
|
|
483
487
|
(`schema.index()`), Drizzle les ignore, puisqu'il les déclare dans sa table.
|
|
484
488
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodefony/orm-core",
|
|
3
|
-
"version": "10.0.0-beta.
|
|
3
|
+
"version": "10.0.0-beta.6",
|
|
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",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"esm"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"nodefony": "^10.0.0-beta.
|
|
40
|
+
"nodefony": "^10.0.0-beta.6"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "26.6.
|
|
44
|
-
"nodefony": "^10.0.0-beta.
|
|
45
|
-
"vitest": "5.0
|
|
43
|
+
"@types/node": "26.6.6",
|
|
44
|
+
"nodefony": "^10.0.0-beta.6",
|
|
45
|
+
"vitest": "5.1.0"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|
|
48
48
|
"type": "git",
|