@nodefony/orm-core 10.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +131 -0
  3. package/dist/index.js +22 -0
  4. package/dist/nodefony/interfaces/IEntity.js +1 -0
  5. package/dist/nodefony/interfaces/IOrm.js +1 -0
  6. package/dist/nodefony/interfaces/IOrmFlow.js +1 -0
  7. package/dist/nodefony/interfaces/IOrmGraph.js +1 -0
  8. package/dist/nodefony/interfaces/IOrmMigrations.js +12 -0
  9. package/dist/nodefony/interfaces/IOrmProbe.js +1 -0
  10. package/dist/nodefony/interfaces/IPage.js +1 -0
  11. package/dist/nodefony/interfaces/IRepository.js +1 -0
  12. package/dist/nodefony/interfaces/ITransaction.js +1 -0
  13. package/dist/nodefony/interfaces/index.js +1 -0
  14. package/dist/nodefony/src/AbstractCrudService.js +199 -0
  15. package/dist/nodefony/src/ConnectionMonitor.js +181 -0
  16. package/dist/nodefony/src/Entity.js +42 -0
  17. package/dist/nodefony/src/EntityRegistry.js +109 -0
  18. package/dist/nodefony/src/Orm.js +297 -0
  19. package/dist/nodefony/src/OrmAdminApi.js +491 -0
  20. package/dist/nodefony/src/OrmRegistry.js +75 -0
  21. package/dist/nodefony/src/QueryFlowMonitor.js +128 -0
  22. package/dist/nodefony/src/buildOrmLeanHealth.js +54 -0
  23. package/dist/nodefony/src/criteria.js +176 -0
  24. package/dist/nodefony/src/decorators/entitiesDecorator.js +67 -0
  25. package/dist/nodefony/src/decorators/entityDecorator.js +51 -0
  26. package/dist/nodefony/src/decorators/index.js +5 -0
  27. package/dist/nodefony/src/decorators/metadataStore.js +38 -0
  28. package/dist/nodefony/src/decorators/repositoryDecorator.js +35 -0
  29. package/dist/nodefony/src/defineEntity.js +27 -0
  30. package/dist/nodefony/src/errors.js +76 -0
  31. package/dist/nodefony/src/ormWiring.js +78 -0
  32. package/dist/nodefony/src/paginate.js +55 -0
  33. package/dist/nodefony/src/readOptions.js +52 -0
  34. package/dist/nodefony/src/serviceWiring.js +1 -0
  35. package/dist/types/index.d.ts +39 -0
  36. package/dist/types/nodefony/interfaces/IEntity.d.ts +65 -0
  37. package/dist/types/nodefony/interfaces/IOrm.d.ts +136 -0
  38. package/dist/types/nodefony/interfaces/IOrmFlow.d.ts +71 -0
  39. package/dist/types/nodefony/interfaces/IOrmGraph.d.ts +165 -0
  40. package/dist/types/nodefony/interfaces/IOrmMigrations.d.ts +145 -0
  41. package/dist/types/nodefony/interfaces/IOrmProbe.d.ts +70 -0
  42. package/dist/types/nodefony/interfaces/IPage.d.ts +24 -0
  43. package/dist/types/nodefony/interfaces/IRepository.d.ts +369 -0
  44. package/dist/types/nodefony/interfaces/ITransaction.d.ts +31 -0
  45. package/dist/types/nodefony/interfaces/index.d.ts +7 -0
  46. package/dist/types/nodefony/src/AbstractCrudService.d.ts +158 -0
  47. package/dist/types/nodefony/src/ConnectionMonitor.d.ts +86 -0
  48. package/dist/types/nodefony/src/Entity.d.ts +44 -0
  49. package/dist/types/nodefony/src/EntityRegistry.d.ts +60 -0
  50. package/dist/types/nodefony/src/Orm.d.ts +197 -0
  51. package/dist/types/nodefony/src/OrmAdminApi.d.ts +84 -0
  52. package/dist/types/nodefony/src/OrmRegistry.d.ts +58 -0
  53. package/dist/types/nodefony/src/QueryFlowMonitor.d.ts +53 -0
  54. package/dist/types/nodefony/src/buildOrmLeanHealth.d.ts +15 -0
  55. package/dist/types/nodefony/src/criteria.d.ts +131 -0
  56. package/dist/types/nodefony/src/decorators/entitiesDecorator.d.ts +48 -0
  57. package/dist/types/nodefony/src/decorators/entityDecorator.d.ts +49 -0
  58. package/dist/types/nodefony/src/decorators/index.d.ts +10 -0
  59. package/dist/types/nodefony/src/decorators/metadataStore.d.ts +54 -0
  60. package/dist/types/nodefony/src/decorators/repositoryDecorator.d.ts +30 -0
  61. package/dist/types/nodefony/src/defineEntity.d.ts +43 -0
  62. package/dist/types/nodefony/src/errors.d.ts +62 -0
  63. package/dist/types/nodefony/src/ormWiring.d.ts +48 -0
  64. package/dist/types/nodefony/src/paginate.d.ts +43 -0
  65. package/dist/types/nodefony/src/readOptions.d.ts +21 -0
  66. package/dist/types/nodefony/src/serviceWiring.d.ts +24 -0
  67. package/docs/index.md +791 -0
  68. package/docs/tutorial-entity.md +577 -0
  69. package/package.json +73 -0
@@ -0,0 +1,158 @@
1
+ import { Service } from "nodefony";
2
+ import type { IRepository, Criteria, RepositoryReadOptions, IPage, PageQuery } from "../interfaces/index.js";
3
+ import type { ServiceWiring } from "./serviceWiring.js";
4
+ /**
5
+ * Service CRUD **générique** — socle réutilisable au-dessus d'un {@link IRepository}.
6
+ *
7
+ * Toute entité (User, Article, Room...) expose son CRUD « toujours de la même
8
+ * manière » en étendant cette classe : `class UserService extends
9
+ * AbstractCrudService<IUser, IUserRepository>`. Le service est la **source de
10
+ * vérité métier**, transport-agnostique : les adaptateurs (REST controller, WS
11
+ * RPC, resolver GraphQL, commande CLI) l'appellent sans dupliquer la logique.
12
+ *
13
+ * Étend {@link Service} (DI, Syslog, bus d'événements) → instancié **une seule
14
+ * fois** (singleton DI). C'est légitime car la classe est **stateless** : son
15
+ * état d'instance ne porte que des invariants partagés (le `repository` injecté).
16
+ * L'état par requête (utilisateur courant, tenant, transaction) vit dans le
17
+ * `Context`/ALS, **jamais** dans un champ du service.
18
+ *
19
+ * **Perf** : les lectures (`find`/`findOne`/`findById`/`count`) délèguent
20
+ * directement au repository — aucun hook ni event sur le chemin chaud. Les
21
+ * mutations (`create`/`update`/`delete`) passent par les hooks template-method et
22
+ * émettent un événement de cycle de vie (`onCreated`/`onUpdated`/`onDeleted`)
23
+ * consommable pour l'audit, le cache, ou Studio.
24
+ *
25
+ * @typeParam T - type de l'entité gérée.
26
+ * @typeParam R - type concret du repository (conserve les finders métier dans la
27
+ * sous-classe, ex. `IUserRepository`). Défaut : `IRepository<T>`.
28
+ */
29
+ export declare abstract class AbstractCrudService<T, R extends IRepository<T> = IRepository<T>> extends Service {
30
+ protected readonly repository: R;
31
+ /**
32
+ * @param name - identifiant logique du service (msgid des logs, clé d'abonnement events).
33
+ * @param repository - source de persistance injectée (DI).
34
+ * @param wiring - câblage Service ({@link ServiceWiring} : `container?`,
35
+ * `notificationsCenter?`, `options?`) — quasi toujours omis (fourni par le container DI).
36
+ */
37
+ constructor(name: string, repository: R, ...wiring: ServiceWiring);
38
+ /**
39
+ * Liste les entités correspondant au critère (toutes si omis).
40
+ *
41
+ * @param criteria - filtre typé optionnel.
42
+ * @param options - eager-load / pagination / tri portables.
43
+ */
44
+ find(criteria?: Criteria<T>, options?: RepositoryReadOptions): Promise<T[]>;
45
+ /**
46
+ * Première entité correspondant au critère, ou `null`.
47
+ *
48
+ * @param criteria - filtre de sélection.
49
+ * @param options - eager-load portable.
50
+ */
51
+ findOne(criteria: Criteria<T>, options?: RepositoryReadOptions): Promise<T | null>;
52
+ /**
53
+ * Entité par clé primaire `id`, ou `null`.
54
+ *
55
+ * @remarks Suppose une PK nommée `id` (convention Nodefony : UUID `string`).
56
+ * Override dans la sous-classe si la PK diffère.
57
+ * @param id - identifiant primaire.
58
+ * @param options - eager-load portable.
59
+ */
60
+ findById(id: string, options?: RepositoryReadOptions): Promise<T | null>;
61
+ /**
62
+ * Compte les entités correspondant au critère (toutes si omis).
63
+ *
64
+ * @param criteria - filtre optionnel.
65
+ */
66
+ count(criteria?: Criteria<T>): Promise<number>;
67
+ /**
68
+ * Liste **une page** d'entités — pagination **native** (jamais matérialiser
69
+ * toute la collection). C'est la primitive à utiliser pour toute liste admin /
70
+ * data plane : elle ne charge qu'une page en mémoire, quelle que soit la taille
71
+ * de la table.
72
+ *
73
+ * @param page - requête de page ({@link PageQuery} : `limit` obligatoire,
74
+ * `offset`/`order`/`criteria`/`withTotal` optionnels).
75
+ * @returns une {@link Page} : au plus `limit` items, `hasNext`, et `total` si demandé.
76
+ * @throws `PageQueryError` (`400`) si un `?q=` est reçu alors que
77
+ * {@link AbstractCrudService.searchableFields} est vide.
78
+ */
79
+ findPage(page: PageQuery<T>): Promise<IPage<T>>;
80
+ /**
81
+ * Champs sur lesquels `?q=` cherche — **vide par défaut, donc `q` est refusé**.
82
+ *
83
+ * Un service CRUD générique ne peut pas deviner ce qui, dans une entité, se
84
+ * cherche : un titre oui, une clé étrangère ou un horodatage non. Le défaut
85
+ * vide n'est donc pas une lacune mais la seule réponse honnête — et il refuse
86
+ * plutôt qu'il n'ignore, sans quoi une recherche non honorée rendrait toute la
87
+ * table sous un `200`.
88
+ *
89
+ * L'activer tient en une ligne dans le service concret, et la recherche
90
+ * devient alors réelle de bout en bout (`?q=` → `LIKE` ancré, indexable) :
91
+ *
92
+ * ```ts
93
+ * protected override readonly searchableFields = ["title", "slug"] as const;
94
+ * ```
95
+ *
96
+ * ⚠️ Le déclarer ici ne suffit PAS à ouvrir la route : le contrat de page
97
+ * refuse `q` en amont tant que le point d'entrée HTTP ne passe pas
98
+ * `searchable: true` à `parsePageQuery`. Les deux se déclarent, pour la même
99
+ * raison — une capacité se constate à chaque étage qu'elle traverse.
100
+ */
101
+ protected readonly searchableFields: ReadonlyArray<keyof T & string>;
102
+ /**
103
+ * Crée une entité : `beforeCreate` → persistance → `afterCreate` → `onCreated`.
104
+ *
105
+ * @param data - champs de l'entité à créer.
106
+ * @returns l'entité persistée. Émet `onCreated`.
107
+ */
108
+ create(data: Partial<T>): Promise<T>;
109
+ /**
110
+ * Met à jour **au plus une** entité : `beforeUpdate` → persistance atomique
111
+ * → `afterUpdate` → `onUpdated`.
112
+ *
113
+ * S'appuie sur {@link IRepository.updateOne} (atomique, `RETURNING` /
114
+ * `findOneAndUpdate`) : l'entité retournée reflète l'écriture, donc l'event
115
+ * `onUpdated` part de façon fiable même quand le critère porte sur un champ
116
+ * modifié (corrige le faux `null` de l'ancien `update` + relecture).
117
+ *
118
+ * `updateMany` (mise à jour en masse) n'est volontairement **pas** exposée
119
+ * ici : c'est une primitive de niveau repository (`IRepository.updateMany`) —
120
+ * on la remontera dans un service le jour où un usage métier la réclame.
121
+ *
122
+ * @param criteria - filtre de sélection (champ inconnu → `UnknownCriteriaField`).
123
+ * @param data - champs à modifier.
124
+ * @returns l'entité mise à jour, ou `null` si aucune ne correspond (pas d'event).
125
+ */
126
+ updateOne(criteria: Criteria<T>, data: Partial<T>): Promise<T | null>;
127
+ /**
128
+ * Supprime : `beforeDelete` → persistance → `afterDelete` → `onDeleted`.
129
+ *
130
+ * @param criteria - filtre de sélection.
131
+ * @returns le nombre d'entités supprimées. Émet `onDeleted` (criteria, count)
132
+ * uniquement si au moins une ligne a été supprimée.
133
+ */
134
+ delete(criteria: Criteria<T>): Promise<number>;
135
+ /**
136
+ * Transforme/valide les données avant création (ex. hacher un mot de passe).
137
+ *
138
+ * @param data - données entrantes.
139
+ * @returns les données préparées à persister.
140
+ */
141
+ protected beforeCreate(data: Partial<T>): Partial<T> | Promise<Partial<T>>;
142
+ /** Effet de bord après création (ex. provisionner une ressource liée). */
143
+ protected afterCreate(_entity: T): void | Promise<void>;
144
+ /**
145
+ * Transforme/valide les données avant mise à jour.
146
+ *
147
+ * @param criteria - cible de la mise à jour.
148
+ * @param data - données entrantes.
149
+ * @returns les données préparées à persister.
150
+ */
151
+ protected beforeUpdate(_criteria: Criteria<T>, data: Partial<T>): Partial<T> | Promise<Partial<T>>;
152
+ /** Effet de bord après mise à jour. */
153
+ protected afterUpdate(_entity: T): void | Promise<void>;
154
+ /** Garde/validation avant suppression (ex. interdire la suppression du dernier admin). */
155
+ protected beforeDelete(_criteria: Criteria<T>): void | Promise<void>;
156
+ /** Effet de bord après suppression (ex. nettoyer un cache). */
157
+ protected afterDelete(_criteria: Criteria<T>, _removed: number): void | Promise<void>;
158
+ }
@@ -0,0 +1,86 @@
1
+ import type { IConnectionError } from "../interfaces/IOrmGraph.js";
2
+ import type { ILatencyWindow } from "../interfaces/IOrmProbe.js";
3
+ /** Vue figée des compteurs du moniteur pour un connecteur. */
4
+ export interface IConnectionMonitorCore {
5
+ connectedSince: number | null;
6
+ uptimeMs: number | null;
7
+ connectCount: number;
8
+ reconnectCount: number;
9
+ /** Pertes de connexion constatées depuis le démarrage du process. */
10
+ lostCount: number;
11
+ errorCount: number;
12
+ lastConnectMs: number | null;
13
+ lastError: IConnectionError | null;
14
+ recentErrors: IConnectionError[];
15
+ latency: ILatencyWindow;
16
+ }
17
+ /**
18
+ * **ConnectionMonitor** — observabilité **per-instance** (process-local) du
19
+ * cycle de vie des connexions ORM : connexions, **reconnexions**, **erreurs**
20
+ * (connexion + ping).
21
+ *
22
+ * Alimenté par la template method {@link Orm.connect} (capture latence + erreur
23
+ * de connexion) et par le ping live de l'endpoint `connection/health`. Lecture
24
+ * via {@link snapshot} (data plane ORM / dashboard Studio).
25
+ *
26
+ * **Cloud-native** : l'état d'une connexion DB est local au process (pool par
27
+ * pod) → le moniteur est volontairement per-instance. La vue multi-pod relève
28
+ * de l'agrégation (Prometheus par pod / fan-out Redis P13), pas de cette classe.
29
+ *
30
+ * **Perf** : structure lazy (`Map` allouée au 1ᵉʳ enregistrement) ; ring
31
+ * d'erreurs alloué seulement au 1ᵉʳ incident ; aucun coût tant qu'aucun ORM ne
32
+ * se connecte ou n'échoue. Reset au restart (diagnostic, pas d'état durable).
33
+ */
34
+ declare class ConnectionMonitor {
35
+ #private;
36
+ /**
37
+ * Enregistre une latence de ping live dans la fenêtre glissante (ring borné).
38
+ *
39
+ * @param name - clé du connecteur.
40
+ * @param ms - latence du ping (ms).
41
+ */
42
+ recordPing(name: string, ms: number): void;
43
+ /**
44
+ * Enregistre une connexion réussie + sa latence.
45
+ *
46
+ * Ne compte PAS une reconnexion : une reprise du driver ne repasse jamais
47
+ * par `Orm.connect()`, elle se signale par {@link ConnectionMonitor.recordReconnect}.
48
+ *
49
+ * @param name - clé du connecteur.
50
+ * @param latencyMs - durée de l'établissement (ms).
51
+ */
52
+ recordConnect(name: string, latencyMs: number): void;
53
+ /**
54
+ * Enregistre une **perte** de connexion constatée par l'adapter.
55
+ *
56
+ * `connectedSince` retombe à `null` : un uptime qui continue de croître
57
+ * pendant que le serveur est tombé est pire qu'absent — il se lit comme une
58
+ * preuve de bonne santé.
59
+ *
60
+ * @param name - clé du connecteur.
61
+ */
62
+ recordLost(name: string): void;
63
+ /**
64
+ * Enregistre une **reprise** de connexion constatée par l'adapter.
65
+ *
66
+ * @param name - clé du connecteur.
67
+ */
68
+ recordReconnect(name: string): void;
69
+ /**
70
+ * Enregistre une erreur (connexion échouée ou ping en échec).
71
+ *
72
+ * @param name - clé du connecteur.
73
+ * @param message - message d'erreur (credential déjà retiré par l'appelant).
74
+ */
75
+ recordError(name: string, message: string): void;
76
+ /**
77
+ * Vue figée des compteurs d'un connecteur (ou compteurs neutres si jamais
78
+ * observé).
79
+ *
80
+ * @param name - clé du connecteur.
81
+ */
82
+ snapshot(name: string): IConnectionMonitorCore;
83
+ }
84
+ /** Singleton process-wide du moniteur de connexions. */
85
+ export declare const connectionMonitor: ConnectionMonitor;
86
+ export {};
@@ -0,0 +1,44 @@
1
+ import type { IEntity, IEntityRelation } from "../interfaces/index.js";
2
+ /**
3
+ * Classe de base abstraite d'une entité, indépendante du driver ORM.
4
+ *
5
+ * Porte le contrat {@link IEntity} : nom logique, connecteur cible, schéma natif
6
+ * (calculé via {@link Entity.getSchema}), modèle compilé (post-connexion) et
7
+ * relations déclaratives. {@link Entity.register} insère l'entité dans le
8
+ * {@link entityRegistry} process-wide.
9
+ *
10
+ * **Pas d'auto-enregistrement dans le constructeur** : en TypeScript, le
11
+ * constructeur de la classe de base s'exécute AVANT les initialiseurs de champs
12
+ * de la sous-classe — `name`/`connector` seraient encore `undefined`. L'enregistrement
13
+ * automatique est donc porté par le décorateur `@entity` (P5.3), qui lit les
14
+ * métadonnées de classe ; en attendant, appeler explicitement
15
+ * {@link Entity.register} après instanciation.
16
+ *
17
+ * @typeParam S - type du schéma natif du driver.
18
+ * @typeParam M - type du modèle compilé natif du driver.
19
+ */
20
+ export declare abstract class Entity<S = unknown, M = unknown> implements IEntity<S, M> {
21
+ /** Nom logique de l'entité (clé de lookup, ex. `"User"`). */
22
+ abstract readonly name: string;
23
+ /** Connexion nommée cible, telle que déclarée en config (clé du `ormRegistry`). */
24
+ abstract readonly connector: string;
25
+ /** Modèle compilé natif, renseigné après connexion de l'ORM. */
26
+ model?: M;
27
+ /** Relations déclarées vers d'autres entités (par nom logique). */
28
+ readonly relations?: ReadonlyArray<IEntityRelation>;
29
+ /**
30
+ * Construit la définition de schéma propre au driver.
31
+ *
32
+ * @returns le schéma natif (forme libre selon l'ORM).
33
+ */
34
+ abstract getSchema(): S;
35
+ /** Définition de schéma propre au driver (délègue à {@link Entity.getSchema}). */
36
+ get schema(): S;
37
+ /**
38
+ * Enregistre cette entité dans le {@link entityRegistry} process-wide.
39
+ *
40
+ * @returns `this` (chaînable).
41
+ * @throws si l'entité est déjà enregistrée sur le même connecteur.
42
+ */
43
+ register(): this;
44
+ }
@@ -0,0 +1,60 @@
1
+ import type { IEntity } from "../interfaces/index.js";
2
+ /**
3
+ * Registre cross-ORM des entités, indexé par nom logique puis par connecteur.
4
+ *
5
+ * Une même entité logique (ex. `User`) peut exister sur plusieurs connexions :
6
+ * l'index interne est `entities[entityName][connectorName] = IEntity`. Le lookup
7
+ * {@link EntityRegistry.get} accepte un nom seul (résolu si l'entité n'existe
8
+ * que sur un connecteur) ou un couple nom + connecteur pour lever l'ambiguïté.
9
+ *
10
+ * Structure pure (`Object.create(null)`) et lazy : rien n'est alloué tant
11
+ * qu'aucune entité n'est enregistrée. Aucun coupling à `nodefony`.
12
+ */
13
+ export declare class EntityRegistry {
14
+ #private;
15
+ /**
16
+ * Enregistre une entité pour son connecteur cible.
17
+ *
18
+ * @param entity - entité implémentant {@link IEntity} (utilise `name` + `connector`).
19
+ * @throws si cette entité est déjà enregistrée sur le même connecteur.
20
+ */
21
+ register(entity: IEntity): void;
22
+ /**
23
+ * Résout une entité par nom (et connecteur si plusieurs candidats).
24
+ *
25
+ * @param name - nom logique de l'entité (ex. `"User"`).
26
+ * @param connector - connecteur cible ; obligatoire si l'entité existe sur plusieurs.
27
+ * @returns l'entité correspondante.
28
+ * @throws si l'entité est inconnue, absente du connecteur demandé, ou ambiguë
29
+ * (plusieurs connecteurs) sans `connector` précisé.
30
+ */
31
+ get(name: string, connector?: string): IEntity;
32
+ /**
33
+ * Indique si une entité (optionnellement sur un connecteur précis) est enregistrée.
34
+ *
35
+ * @param name - nom logique de l'entité.
36
+ * @param connector - connecteur cible facultatif.
37
+ */
38
+ has(name: string, connector?: string): boolean;
39
+ /**
40
+ * Liste toutes les entités enregistrées, tous connecteurs confondus.
41
+ *
42
+ * @returns tableau d'entités (vide si registre vide).
43
+ */
44
+ list(): IEntity[];
45
+ /**
46
+ * Retire une entité du registre (teardown tests / hot-reload).
47
+ *
48
+ * @param name - nom logique de l'entité.
49
+ * @param connector - si fourni, retire seulement cette variante ; sinon toutes.
50
+ * @returns `true` si quelque chose a été retiré, `false` sinon.
51
+ */
52
+ unregister(name: string, connector?: string): boolean;
53
+ }
54
+ /**
55
+ * Singleton process-wide partagé. Les entités s'y enregistrent (via le
56
+ * décorateur `entities([...])` posé sur le Module, ou {@link Entity.register}).
57
+ * La classe {@link EntityRegistry} reste instanciable pour des registres isolés
58
+ * (tests).
59
+ */
60
+ export declare const entityRegistry: EntityRegistry;
@@ -0,0 +1,197 @@
1
+ import { Service } from "nodefony";
2
+ import type { Container, Event, DefaultOptionsService } from "nodefony";
3
+ import type { IColumnInfo, IConnectionInfo, IOrm, IRepository, ITransaction } from "../interfaces/index.js";
4
+ /**
5
+ * Classe de base abstraite de tout ORM Nodefony — câble {@link Service} (DI,
6
+ * Syslog, bus d'événements) et le contrat {@link IOrm}, et s'auto-enregistre
7
+ * dans le {@link ormRegistry} process-wide à la construction.
8
+ *
9
+ * Les drivers concrets (`@nodefony/mongoose`, `@nodefony/drizzle`...)
10
+ * implémentent les opérations bas niveau ({@link Orm.onConnect}, `disconnect`,
11
+ * `getRepository`, `transaction`, `getNativeConnection`). La connexion passe par
12
+ * la template method {@link Orm.connect} qui émet l'événement `onOrmReady` une
13
+ * fois le driver connecté — garantissant que tous les ORM signalent leur
14
+ * disponibilité de façon homogène (avant le `onReady` du Kernel).
15
+ *
16
+ * @typeParam — aucun ; les types natifs du driver transitent via les génériques
17
+ * des méthodes ({@link IRepository}, {@link ITransaction}, native connection).
18
+ */
19
+ export declare abstract class Orm extends Service implements IOrm {
20
+ #private;
21
+ /**
22
+ * État de vie de la connexion — source UNIQUE de `isConnected()`.
23
+ *
24
+ * `protected` et non `#privé` : `disconnect()` est implémenté par chaque
25
+ * adapter et doit pouvoir le remettre à `false`.
26
+ */
27
+ protected alive: boolean;
28
+ /**
29
+ * **Ce que cet adapter sait VRAIMENT de l'état de sa connexion** — déclaré,
30
+ * jamais supposé.
31
+ *
32
+ * `"events"` : le driver signale les pertes et les reprises, l'adapter les
33
+ * traduit ; `isConnected()` est un CONSTAT.
34
+ * `"assumed"` : rien ne signale quoi que ce soit (base embarquée, driver
35
+ * muet) ; `isConnected()` dit seulement « la connexion a été établie et
36
+ * n'a pas été fermée » — une supposition.
37
+ *
38
+ * **Le défaut est `"assumed"`, et il n'est pas abstrait — délibérément.**
39
+ * Le rendre obligatoire forcerait chaque adapter à répondre, mais casserait
40
+ * la compilation de tout adapter existant : ajouter un ORM deviendrait une
41
+ * rupture. Le défaut prudent protège aussi bien de l'oubli, parce qu'il dit
42
+ * la VÉRITÉ sur un adapter qui n'a rien câblé — il ne sait pas. Ce qu'il
43
+ * faut empêcher, ce n'est pas le silence : c'est qu'un silence se fasse
44
+ * passer pour un constat.
45
+ */
46
+ get liveness(): "events" | "assumed";
47
+ /**
48
+ * @param name - clé unique de l'ORM dans le {@link ormRegistry}.
49
+ * @param container - container DI hérité (Kernel) ou nouveau si omis.
50
+ * @param notificationsCenter - bus d'événements partagé, `false` pour aucun.
51
+ * @param options - options de service.
52
+ * @throws si un ORM du même `name` est déjà enregistré.
53
+ */
54
+ constructor(name: string, container?: Container, notificationsCenter?: Event | false | null, options?: DefaultOptionsService);
55
+ /**
56
+ * Connecte le driver puis émet `onOrmReady`.
57
+ *
58
+ * Template method : ne pas surcharger — implémenter {@link Orm.onConnect}.
59
+ * Instrumente le {@link connectionMonitor} (latence + reconnexion en cas de
60
+ * succès, erreur de connexion en cas d'échec).
61
+ */
62
+ connect(): Promise<void>;
63
+ /**
64
+ * **Le driver a PERDU la connexion** — à appeler par l'adapter depuis
65
+ * l'événement natif de son driver (`pool.on("error")` côté `pg`,
66
+ * `connection.on("disconnected")` côté Mongoose…).
67
+ *
68
+ * Idempotent : un driver émet souvent plusieurs erreurs pour une seule
69
+ * coupure (une par connexion du pool). Seule la PREMIÈRE bascule l'état,
70
+ * compte l'incident et émet `onOrmLost` — sinon un pool de 10 connexions
71
+ * ferait dix fois le tour du framework pour un seul serveur tombé.
72
+ *
73
+ * @param reason - cause lisible, sans credential (elle est journalisée).
74
+ */
75
+ protected connectionLost(reason: string): void;
76
+ /**
77
+ * **Le driver a RÉTABLI la connexion** — à appeler par l'adapter depuis
78
+ * l'événement natif correspondant (`pool.on("connect")`, `reconnected`…).
79
+ *
80
+ * **Une reprise n'existe que s'il y a eu une perte.** `pg` émet `connect` et
81
+ * `acquire` à chaque client pris au pool, y compris quand rien n'est tombé,
82
+ * et y compris pendant l'établissement initial : sans cette condition, un
83
+ * pool qui grandit sous la charge — ou simplement une application qui
84
+ * démarre — compterait des reconnexions imaginaires.
85
+ */
86
+ protected connectionRestored(): void;
87
+ /**
88
+ * Période du battement de cœur, en millisecondes. `0` le désactive.
89
+ *
90
+ * **Pourquoi le framework doit fournir ça** : le driver MongoDB surveille ses
91
+ * serveurs en permanence (SDAM) et sait donc qu'une base est tombée même sans
92
+ * le moindre trafic. `pg` et `mysql2` n'ont RIEN de tel — ils n'apprennent
93
+ * l'état du serveur que par leurs requêtes. Mesuré : sur ces deux dialectes,
94
+ * une coupure survenue pendant qu'une requête était en vol, ou un serveur
95
+ * simplement GELÉ (qui ne ferme rien), n'émettent aucun événement : l'état
96
+ * restait « connecté » indéfiniment. Un battement comble cette asymétrie —
97
+ * il ne réinvente rien, il donne aux drivers muets ce que Mongo a déjà.
98
+ *
99
+ * Pourquoi pas une instrumentation des requêtes : son coût croît avec le
100
+ * trafic, pour une information qui ne change qu'aux rares instants de panne ;
101
+ * et il faudrait distinguer une erreur de connexion d'une contrainte violée.
102
+ * Ici le coût est CONSTANT et connu d'avance : une requête légère par période.
103
+ */
104
+ protected heartbeatMs: number;
105
+ /**
106
+ * Délai au-delà duquel un battement sans réponse vaut une PERTE.
107
+ *
108
+ * Sans lui, le battement ne sert à rien contre le cas qui l'a justifié :
109
+ * une base GELÉE ne ferme rien et ne répond pas, donc `ping()` PEND — et
110
+ * le battement pendait avec elle, indéfiniment, sans jamais conclure
111
+ * (mesuré : 30 s de gel, aucune détection). Une sonde doit avoir sa
112
+ * propre montre, sinon elle hérite de la panne qu'elle est censée voir.
113
+ */
114
+ protected heartbeatTimeoutMs: number;
115
+ /**
116
+ * Démarre le battement si l'adapter sait répondre à un `ping()` et que la
117
+ * période n'est pas nulle. Idempotent.
118
+ *
119
+ * La minuterie est `unref()` : elle ne doit JAMAIS retenir le process en vie
120
+ * — un banc, un script CLI ou un test qui se termine ne doit pas attendre le
121
+ * prochain battement pour rendre la main.
122
+ */
123
+ protected startHeartbeat(): void;
124
+ /** Arrête le battement et libère la minuterie. Idempotent. */
125
+ protected stopHeartbeat(): void;
126
+ /**
127
+ * **Bat MAINTENANT**, sans attendre la fin de la période.
128
+ *
129
+ * À appeler par un adapter dont le driver émet un signal SUSPECT mais pas
130
+ * concluant — typiquement la fermeture du socket d'une connexion du pool :
131
+ * elle arrive aussi bien pour un serveur tombé que pour une connexion
132
+ * inactive recyclée, et l'adapter ne peut pas les distinguer. Plutôt que de
133
+ * trancher à sa place — ce qui inscrirait de faux incidents à chaque
134
+ * recyclage — il délègue ici : le battement fait la seule chose qui tranche,
135
+ * une requête, et met l'état à jour selon la réponse.
136
+ *
137
+ * Sans cette porte, un dialecte muet reste marqué connecté jusqu'au battement
138
+ * suivant : 30 s par défaut, là où `pg` bascule en millisecondes. C'est une
139
+ * asymétrie de détection entre deux dialectes de production, pas un réglage.
140
+ *
141
+ * Ne coûte rien quand rien ne va mal : la garde `#beating` écarte les
142
+ * battements concurrents, donc un pool dont dix connexions tombent d'un coup
143
+ * ne sonde qu'une fois. Ne rétablit ni ne perd quoi que ce soit après un
144
+ * `disconnect()` — `#beat` y voit un arrêt volontaire et s'abstient.
145
+ */
146
+ protected beatNow(): void;
147
+ /**
148
+ * Indique si la connexion est active — **implémentation UNIQUE**, portée par
149
+ * la classe de base et non par chaque adapter.
150
+ *
151
+ * C'est délibéré : tant que chaque adapter portait son propre booléen posé à
152
+ * la connexion, aucun ne le remettait à `false` quand le serveur tombait —
153
+ * la santé ORM répondait « connecté » en pleine coupure. L'état vit ici, et
154
+ * il n'a que trois sources : `connect()`, `disconnect()`, et les deux
155
+ * signaux que l'adapter traduit depuis son driver.
156
+ */
157
+ isConnected(): boolean;
158
+ /**
159
+ * Établit la connexion native du driver (compilation des entités, pool...).
160
+ *
161
+ * Appelé par {@link Orm.connect} ; ne pas émettre `onOrmReady` ici.
162
+ */
163
+ protected abstract onConnect(): Promise<void>;
164
+ /** Ferme la connexion et libère le pool. */
165
+ abstract disconnect(): Promise<void>;
166
+ /**
167
+ * Repository typé d'une entité enregistrée.
168
+ *
169
+ * @param name - nom logique de l'entité.
170
+ */
171
+ abstract getRepository<T = unknown>(name: string): IRepository<T>;
172
+ /**
173
+ * Exécute un travail transactionnel (commit si résolu, rollback si rejeté).
174
+ *
175
+ * @param work - callback recevant la transaction active.
176
+ */
177
+ abstract transaction<R>(work: (tx: ITransaction) => Promise<R>): Promise<R>;
178
+ /** Expose la connexion native du driver (trappe SQL/commandes brutes). */
179
+ abstract getNativeConnection<C = unknown>(): C;
180
+ /**
181
+ * Décrit les colonnes d'une entité pour le graphe canonique. Défaut : `[]`
182
+ * (relations seules dans l'ERD). Les adapters surchargent avec l'introspection
183
+ * native (Drizzle `getTableConfig`, Mongoose paths).
184
+ *
185
+ * @param _name - nom logique de l'entité.
186
+ * @returns colonnes normalisées.
187
+ */
188
+ describeEntity(_name: string): IColumnInfo[];
189
+ /**
190
+ * Décrit la connexion sous-jacente (driver + cible). Défaut : driver vide
191
+ * (inconnu). Les adapters surchargent (Drizzle → `sqlite` + fichier, etc.).
192
+ * Ne DOIT jamais exposer de credential.
193
+ *
194
+ * @returns infos de connexion (driver vide si non renseigné).
195
+ */
196
+ describeConnection(): IConnectionInfo;
197
+ }
@@ -0,0 +1,84 @@
1
+ import type { IAdminApi, IAdminRegistry } from "nodefony";
2
+ import type { IConnectionHealth, IOrmGraph } from "../interfaces/IOrmGraph.js";
3
+ import type { IOrmFlowReport } from "../interfaces/IOrmFlow.js";
4
+ /** Construit le graphe canonique complet (optionnellement filtré par connecteur). */
5
+ export declare function buildOrmGraph(connectorFilter?: string): IOrmGraph;
6
+ /**
7
+ * Construit le **diagnostic complet des connexions** (per-instance) : ping live
8
+ * (latence enregistrée dans la fenêtre glissante), sonde profonde driver
9
+ * ({@link IOrm.probe} — stockage/pool), et compteurs de cycle de vie du
10
+ * {@link connectionMonitor}. Réutilisé par l'endpoint `connection/health` ET par
11
+ * le ticker hub realtime de Studio (« contrôle total des ORM »).
12
+ *
13
+ * @param filter - nom de connecteur (optionnel) pour ne sonder que celui-ci.
14
+ * @returns un {@link IConnectionHealth} par connecteur.
15
+ */
16
+ export declare function buildConnectionHealth(filter?: string): Promise<IConnectionHealth[]>;
17
+ /**
18
+ * Construit le rapport de **flux ORM** (per-instance) : débit (via `total`),
19
+ * latence (moyenne + EWMA), pire latence et requêtes lentes récentes, par
20
+ * connecteur enregistré. Lecture pure du {@link queryFlowMonitor} (aucune
21
+ * requête émise — contrairement à `buildConnectionHealth` qui ping) → bon marché.
22
+ * Réutilisé par l'endpoint `flow` ET le ticker hub realtime de Studio.
23
+ *
24
+ * @param filter - nom de connecteur (optionnel) pour ne rapporter que celui-ci.
25
+ * @returns le rapport (`enabled=false` en prod → connecteurs à 0).
26
+ */
27
+ export declare function buildOrmFlow(filter?: string): IOrmFlowReport;
28
+ /**
29
+ * Sérialise un graphe en **DBML** (Database Markup Language) — format pivot lu
30
+ * par dbdiagram.io, les outils IA et convertible en SQL. Les `Ref:` sont dérivés
31
+ * des relations selon la convention FK des adapters (`<source>Id` sur la cible
32
+ * pour 1-N ; `<target>Id` sur la source pour N-1/1-1). Le many-to-many est
33
+ * annoté en commentaire (table de jonction non portable).
34
+ *
35
+ * @param graph - graphe canonique.
36
+ * @returns texte DBML.
37
+ */
38
+ export declare function toDbml(graph: IOrmGraph): string;
39
+ /** Propriété d'un schéma JSON (scalaire, réf relation, ou tableau de réfs). */
40
+ interface IJsonSchemaProperty {
41
+ type?: string;
42
+ format?: string;
43
+ $ref?: string;
44
+ items?: {
45
+ $ref: string;
46
+ };
47
+ }
48
+ /** Définition d'objet JSON Schema pour une entité. */
49
+ interface IJsonSchemaObject {
50
+ type: "object";
51
+ title: string;
52
+ properties: Record<string, IJsonSchemaProperty>;
53
+ required?: string[];
54
+ additionalProperties: false;
55
+ }
56
+ /**
57
+ * Sérialise un graphe en **JSON Schema** (draft 2020-12) — un `$defs` par
58
+ * entité. Format « IA-first » : un agent (text-to-SQL, génération de formulaire,
59
+ * validation de payload) consomme directement ce schéma. Les colonnes
60
+ * non-nullables alimentent `required` ; les relations deviennent des `$ref`
61
+ * (N→1/1→1) ou des tableaux de `$ref` (1→N/N→N) vers les autres `$defs`.
62
+ *
63
+ * @param graph - graphe canonique.
64
+ * @returns document JSON Schema ({@link IJsonSchemaObject} par entité).
65
+ */
66
+ export declare function toJsonSchema(graph: IOrmGraph): {
67
+ $schema: string;
68
+ $defs: Record<string, IJsonSchemaObject>;
69
+ };
70
+ /**
71
+ * Construit le producteur `IAdminApi` du data plane ORM.
72
+ *
73
+ * @returns le `IAdminApi` (namespace `"orm"`).
74
+ */
75
+ export declare function createOrmAdminApi(): IAdminApi;
76
+ /**
77
+ * Enregistre l'`OrmAdminApi` sur le broker admin, **idempotent** (no-op si déjà
78
+ * monté). Appelé par un module driver à son `onKernelBoot` (orm-core est une lib
79
+ * pure et ne peut pas s'auto-monter).
80
+ *
81
+ * @param registry - broker admin (`container.get("adminBroker")`).
82
+ */
83
+ export declare function registerOrmAdminApi(registry: IAdminRegistry): void;
84
+ export {};