@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.
- package/LICENSE +544 -0
- package/README.md +131 -0
- package/dist/index.js +22 -0
- package/dist/nodefony/interfaces/IEntity.js +1 -0
- package/dist/nodefony/interfaces/IOrm.js +1 -0
- package/dist/nodefony/interfaces/IOrmFlow.js +1 -0
- package/dist/nodefony/interfaces/IOrmGraph.js +1 -0
- package/dist/nodefony/interfaces/IOrmMigrations.js +12 -0
- package/dist/nodefony/interfaces/IOrmProbe.js +1 -0
- package/dist/nodefony/interfaces/IPage.js +1 -0
- package/dist/nodefony/interfaces/IRepository.js +1 -0
- package/dist/nodefony/interfaces/ITransaction.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/src/AbstractCrudService.js +199 -0
- package/dist/nodefony/src/ConnectionMonitor.js +181 -0
- package/dist/nodefony/src/Entity.js +42 -0
- package/dist/nodefony/src/EntityRegistry.js +109 -0
- package/dist/nodefony/src/Orm.js +297 -0
- package/dist/nodefony/src/OrmAdminApi.js +491 -0
- package/dist/nodefony/src/OrmRegistry.js +75 -0
- package/dist/nodefony/src/QueryFlowMonitor.js +128 -0
- package/dist/nodefony/src/buildOrmLeanHealth.js +54 -0
- package/dist/nodefony/src/criteria.js +176 -0
- package/dist/nodefony/src/decorators/entitiesDecorator.js +67 -0
- package/dist/nodefony/src/decorators/entityDecorator.js +51 -0
- package/dist/nodefony/src/decorators/index.js +5 -0
- package/dist/nodefony/src/decorators/metadataStore.js +38 -0
- package/dist/nodefony/src/decorators/repositoryDecorator.js +35 -0
- package/dist/nodefony/src/defineEntity.js +27 -0
- package/dist/nodefony/src/errors.js +76 -0
- package/dist/nodefony/src/ormWiring.js +78 -0
- package/dist/nodefony/src/paginate.js +55 -0
- package/dist/nodefony/src/readOptions.js +52 -0
- package/dist/nodefony/src/serviceWiring.js +1 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IEntity.d.ts +65 -0
- package/dist/types/nodefony/interfaces/IOrm.d.ts +136 -0
- package/dist/types/nodefony/interfaces/IOrmFlow.d.ts +71 -0
- package/dist/types/nodefony/interfaces/IOrmGraph.d.ts +165 -0
- package/dist/types/nodefony/interfaces/IOrmMigrations.d.ts +145 -0
- package/dist/types/nodefony/interfaces/IOrmProbe.d.ts +70 -0
- package/dist/types/nodefony/interfaces/IPage.d.ts +24 -0
- package/dist/types/nodefony/interfaces/IRepository.d.ts +369 -0
- package/dist/types/nodefony/interfaces/ITransaction.d.ts +31 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/src/AbstractCrudService.d.ts +158 -0
- package/dist/types/nodefony/src/ConnectionMonitor.d.ts +86 -0
- package/dist/types/nodefony/src/Entity.d.ts +44 -0
- package/dist/types/nodefony/src/EntityRegistry.d.ts +60 -0
- package/dist/types/nodefony/src/Orm.d.ts +197 -0
- package/dist/types/nodefony/src/OrmAdminApi.d.ts +84 -0
- package/dist/types/nodefony/src/OrmRegistry.d.ts +58 -0
- package/dist/types/nodefony/src/QueryFlowMonitor.d.ts +53 -0
- package/dist/types/nodefony/src/buildOrmLeanHealth.d.ts +15 -0
- package/dist/types/nodefony/src/criteria.d.ts +131 -0
- package/dist/types/nodefony/src/decorators/entitiesDecorator.d.ts +48 -0
- package/dist/types/nodefony/src/decorators/entityDecorator.d.ts +49 -0
- package/dist/types/nodefony/src/decorators/index.d.ts +10 -0
- package/dist/types/nodefony/src/decorators/metadataStore.d.ts +54 -0
- package/dist/types/nodefony/src/decorators/repositoryDecorator.d.ts +30 -0
- package/dist/types/nodefony/src/defineEntity.d.ts +43 -0
- package/dist/types/nodefony/src/errors.d.ts +62 -0
- package/dist/types/nodefony/src/ormWiring.d.ts +48 -0
- package/dist/types/nodefony/src/paginate.d.ts +43 -0
- package/dist/types/nodefony/src/readOptions.d.ts +21 -0
- package/dist/types/nodefony/src/serviceWiring.d.ts +24 -0
- package/docs/index.md +791 -0
- package/docs/tutorial-entity.md +577 -0
- package/package.json +73 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { IOrm } from "../interfaces/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registre process-wide des instances ORM enregistrées sous un nom unique.
|
|
4
|
+
*
|
|
5
|
+
* Support multi-ORM natif : chaque driver (`@nodefony/mongoose`,
|
|
6
|
+
* `@nodefony/drizzle`...) s'enregistre à son boot via
|
|
7
|
+
* {@link OrmRegistry.register} sous une clé logique (`"db_principale"`,
|
|
8
|
+
* `"db_logs"`...). Les consommateurs (repositories, session storage, security)
|
|
9
|
+
* résolvent l'ORM voulu par cette clé, jamais par référence directe au driver.
|
|
10
|
+
*
|
|
11
|
+
* Pas de coupling à `nodefony` : structure pure et lazy (la `Map` interne n'est
|
|
12
|
+
* allouée qu'au premier `register`), donc trivialement testable en isolation.
|
|
13
|
+
*/
|
|
14
|
+
export declare class OrmRegistry {
|
|
15
|
+
#private;
|
|
16
|
+
/**
|
|
17
|
+
* Enregistre une instance ORM sous un nom unique.
|
|
18
|
+
*
|
|
19
|
+
* @param name - clé logique de l'ORM (ex. `"db_principale"`).
|
|
20
|
+
* @param orm - instance implémentant {@link IOrm}.
|
|
21
|
+
* @throws si un ORM du même nom est déjà enregistré (erreur de configuration).
|
|
22
|
+
*/
|
|
23
|
+
register(name: string, orm: IOrm): void;
|
|
24
|
+
/**
|
|
25
|
+
* Résout l'ORM enregistré sous un nom.
|
|
26
|
+
*
|
|
27
|
+
* @param name - clé logique de l'ORM.
|
|
28
|
+
* @returns l'instance {@link IOrm} correspondante.
|
|
29
|
+
* @throws si aucun ORM n'est enregistré sous ce nom.
|
|
30
|
+
*/
|
|
31
|
+
get(name: string): IOrm;
|
|
32
|
+
/**
|
|
33
|
+
* Indique si un ORM est enregistré sous ce nom.
|
|
34
|
+
*
|
|
35
|
+
* @param name - clé logique de l'ORM.
|
|
36
|
+
*/
|
|
37
|
+
has(name: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Liste les noms des ORM enregistrés.
|
|
40
|
+
*
|
|
41
|
+
* @returns tableau des clés (vide si aucun ORM enregistré).
|
|
42
|
+
*/
|
|
43
|
+
list(): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Retire un ORM du registre (utile au teardown des tests / hot-reload).
|
|
46
|
+
*
|
|
47
|
+
* @param name - clé logique de l'ORM.
|
|
48
|
+
* @returns `true` si un ORM a été retiré, `false` sinon.
|
|
49
|
+
*/
|
|
50
|
+
unregister(name: string): boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Singleton process-wide partagé par tous les drivers et consommateurs ORM.
|
|
54
|
+
*
|
|
55
|
+
* Les modules ORM s'enregistrent ici ; la classe {@link OrmRegistry} reste
|
|
56
|
+
* instanciable séparément pour des registres isolés (tests).
|
|
57
|
+
*/
|
|
58
|
+
export declare const ormRegistry: OrmRegistry;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IQueryFlow } from "../interfaces/IOrmFlow.js";
|
|
2
|
+
/**
|
|
3
|
+
* **QueryFlowMonitor** — sonde de **débit ORM** per-instance (process-local),
|
|
4
|
+
* agrégée et **indépendante de l'ALS** : compte les requêtes, suit leur latence
|
|
5
|
+
* (moyenne + EWMA) et capture les plus lentes, pour le panneau Supervision
|
|
6
|
+
* (« contrôle total » des ORM, patron sondes+hub).
|
|
7
|
+
*
|
|
8
|
+
* Distinct du **profiler par-requête** (debug bar, buffer de scope ALS, dev-only,
|
|
9
|
+
* coût nul hors requête tracée) : ce moniteur observe le débit **global** et doit
|
|
10
|
+
* donc compter en continu → il est **gaté** par {@link enabled} (OFF par défaut)
|
|
11
|
+
* pour rester **coût nul en production** et ne pas pénaliser les bancs de charge
|
|
12
|
+
* (qui instancient les adapters hors kernel → `enabled` reste à `false`). Le
|
|
13
|
+
* module driver l'active au boot en environnement non-prod.
|
|
14
|
+
*
|
|
15
|
+
* Perf (règle ABSOLUE) :
|
|
16
|
+
* - structure lazy (`Map` allouée au 1ᵉʳ enregistrement, ring `slow` au 1ᵉʳ lent) ;
|
|
17
|
+
* - le hot path n'alloue rien hors cas lent et n'appelle **jamais** `toSQL()`
|
|
18
|
+
* (l'appelant ne capture le SQL que sur le chemin lent, rare) ;
|
|
19
|
+
* - le débit/s n'est pas calculé ici (dérivé du delta de `total` à la lecture)
|
|
20
|
+
* → 0 état mutable côté lecture, 0 ring de timestamps sous charge.
|
|
21
|
+
*
|
|
22
|
+
* Cloud-native : per-instance ; la vue multi-pod relève de l'agrégation
|
|
23
|
+
* (Prometheus / fan-out Redis P13), pas de cette classe. Reset au restart.
|
|
24
|
+
*/
|
|
25
|
+
declare class QueryFlowMonitor {
|
|
26
|
+
#private;
|
|
27
|
+
/** Sonde active ? OFF par défaut → coût nul tant que le driver ne l'active pas. */
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
/** Seuil « lent » (ms) — modifiable par le driver/config. */
|
|
30
|
+
slowMs: number;
|
|
31
|
+
/** Active/désactive la sonde (appelé au boot du module driver selon l'env). */
|
|
32
|
+
setEnabled(on: boolean): void;
|
|
33
|
+
/**
|
|
34
|
+
* Enregistre une requête mesurée. À n'appeler **que** si {@link enabled} (le
|
|
35
|
+
* tap appelant teste le drapeau pour éviter tout coût quand la sonde est OFF).
|
|
36
|
+
*
|
|
37
|
+
* @param connector - clé du connecteur ORM (registre).
|
|
38
|
+
* @param durationMs - durée de la requête (ms).
|
|
39
|
+
* @param sql - SQL paramétré+redacté, fourni **uniquement** si la requête est
|
|
40
|
+
* lente (l'appelant n'extrait le texte que sur le chemin lent — rare).
|
|
41
|
+
*/
|
|
42
|
+
record(connector: string, durationMs: number, sql?: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Vue figée du flux d'un connecteur (ou flux neutre si jamais observé).
|
|
45
|
+
*
|
|
46
|
+
* @param connector - clé du connecteur ORM.
|
|
47
|
+
* @param vendor - vendor de l'adapter (dérivé par l'appelant).
|
|
48
|
+
*/
|
|
49
|
+
snapshot(connector: string, vendor: string): IQueryFlow;
|
|
50
|
+
}
|
|
51
|
+
/** Singleton process-wide de la sonde de flux ORM. */
|
|
52
|
+
export declare const queryFlowMonitor: QueryFlowMonitor;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IOrmLeanHealth } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* **Santé ORM lean per-instance** — somme process-wide de tous les connecteurs enregistrés,
|
|
4
|
+
* destinée à voyager dans le report de sonde cluster (« ORM par worker »). C'est la fonction
|
|
5
|
+
* branchée par le driver via `setOrmHealthProvider` (core) ; le framework la lit dans le report.
|
|
6
|
+
*
|
|
7
|
+
* Perf (règle ABSOLUE) : lecture PURE des singletons déjà alimentés (`queryFlowMonitor` +
|
|
8
|
+
* `connectionMonitor`) → **0 ping**, **0 `toSQL()`**, O(N connecteurs, N petit). `isConnected()`
|
|
9
|
+
* est un simple test d'état (pas une requête). `queryTotal` reste à 0 si le flux est OFF
|
|
10
|
+
* (prod) — c'est voulu : la sonde ne crée aucun coût, elle agrège ce qui existe déjà.
|
|
11
|
+
*
|
|
12
|
+
* @returns la santé ORM agrégée du process ({@link IOrmLeanHealth}).
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildOrmLeanHealth(): IOrmLeanHealth;
|
|
15
|
+
export default buildOrmLeanHealth;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { FieldOperators, UpdateOperators } from "../interfaces/IRepository.js";
|
|
2
|
+
/**
|
|
3
|
+
* Liste figée des opérateurs riches reconnus dans un critère portable.
|
|
4
|
+
*
|
|
5
|
+
* Source de vérité unique partagée par tous les adapters : un objet de valeur de
|
|
6
|
+
* champ n'est traité comme {@link FieldOperators} que si **toutes** ses clés
|
|
7
|
+
* appartiennent à cette liste (cf {@link isFieldOperators}).
|
|
8
|
+
*/
|
|
9
|
+
export declare const OPERATOR_KEYS: readonly ["$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$nin", "$like", "$null"];
|
|
10
|
+
/** Clé d'opérateur riche reconnue. */
|
|
11
|
+
export type OperatorKey = (typeof OPERATOR_KEYS)[number];
|
|
12
|
+
/**
|
|
13
|
+
* Indique si une valeur de champ est un objet d'{@link FieldOperators}.
|
|
14
|
+
*
|
|
15
|
+
* Heuristique : objet simple, non-`null`, non-tableau, dont **toutes** les clés
|
|
16
|
+
* propres sont des opérateurs reconnus (et au moins une). Une valeur objet
|
|
17
|
+
* « ordinaire » (colonne JSON, sous-document) n'a pas que des clés `$`-préfixées
|
|
18
|
+
* reconnues → elle est traitée comme une égalité, jamais comme un filtre riche.
|
|
19
|
+
*
|
|
20
|
+
* @param value - valeur de critère associée à un champ.
|
|
21
|
+
* @returns `true` si `value` doit être interprétée comme des opérateurs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isFieldOperators(value: unknown): value is FieldOperators<unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Liste figée des opérateurs d'**écriture** reconnus dans le `update` d'un
|
|
26
|
+
* `upsert` (cf {@link UpdateOperators}).
|
|
27
|
+
*
|
|
28
|
+
* Source de vérité unique partagée par tous les adapters — pendant, côté
|
|
29
|
+
* écriture, de {@link OPERATOR_KEYS}.
|
|
30
|
+
*/
|
|
31
|
+
export declare const UPDATE_OPERATOR_KEYS: readonly ["$max", "$min"];
|
|
32
|
+
/** Clé d'opérateur d'écriture reconnue. */
|
|
33
|
+
export type UpdateOperatorKey = (typeof UPDATE_OPERATOR_KEYS)[number];
|
|
34
|
+
/**
|
|
35
|
+
* Indique si une valeur d'écriture est un objet d'{@link UpdateOperators}.
|
|
36
|
+
*
|
|
37
|
+
* Même heuristique que {@link isFieldOperators} : objet simple, non-`null`,
|
|
38
|
+
* non-tableau, dont **toutes** les clés propres sont des opérateurs d'écriture
|
|
39
|
+
* reconnus (et au moins une). Une valeur objet « ordinaire » (colonne JSON,
|
|
40
|
+
* sous-document) est donc écrite telle quelle, jamais interprétée.
|
|
41
|
+
*
|
|
42
|
+
* @param value - valeur d'écriture associée à un champ.
|
|
43
|
+
* @returns `true` si `value` doit être interprétée comme des opérateurs.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isUpdateOperators(value: unknown): value is UpdateOperators<unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Le caractère d'échappement des motifs `$like` du contrat portable.
|
|
48
|
+
*
|
|
49
|
+
* Il ne se choisit pas librement : **PostgreSQL et MySQL appliquent déjà `\`**
|
|
50
|
+
* quand aucune clause `ESCAPE` n'est écrite, si bien qu'un motif portant un
|
|
51
|
+
* antislash se comportait DÉJÀ différemment selon le moteur (SQLite, lui, n'a
|
|
52
|
+
* aucun échappement par défaut : il cherchait l'antislash littéral). Fixer `\`
|
|
53
|
+
* et l'émettre explicitement ne change donc pas la sémantique du contrat — cela
|
|
54
|
+
* la fait exister, en alignant les trois moteurs sur celui des deux
|
|
55
|
+
* comportements qui était déjà majoritaire.
|
|
56
|
+
*
|
|
57
|
+
* @see {@link escapeLikeTerm} pour construire un motif, {@link likePatternToRegExp}
|
|
58
|
+
* pour l'interpréter là où il n'y a pas de SQL (Mongo, mémoire).
|
|
59
|
+
*/
|
|
60
|
+
export declare const LIKE_ESCAPE_CHAR = "\\";
|
|
61
|
+
/**
|
|
62
|
+
* Neutralise les métacaractères d'un **texte** pour l'insérer dans un motif
|
|
63
|
+
* `$like` — `%`, `_` et l'antislash lui-même.
|
|
64
|
+
*
|
|
65
|
+
* À utiliser dès qu'un fragment de motif vient d'un humain ou d'une donnée :
|
|
66
|
+
* sans elle, chercher `50%` demande « 50 suivi de n'importe quoi », et chercher
|
|
67
|
+
* `a_b` ramène `axb`. L'utilisateur ne lit pas ça comme une imprécision, il le
|
|
68
|
+
* lit comme un résultat.
|
|
69
|
+
*
|
|
70
|
+
* L'échappement n'a de valeur que si la clause `ESCAPE` correspondante est
|
|
71
|
+
* ÉMISE : un motif échappé sans elle est cherché littéralement, antislash
|
|
72
|
+
* compris, et ne rend plus rien — en silence. C'est pourquoi les deux vont
|
|
73
|
+
* ensemble et vivent ici, et non chez chaque appelant.
|
|
74
|
+
*
|
|
75
|
+
* @param text - le fragment littéral à insérer dans un motif.
|
|
76
|
+
* @returns le même texte, ses métacaractères neutralisés.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* { discount: { $like: `${escapeLikeTerm("50%")}%` } } // → "50\%%"
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function escapeLikeTerm(text: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* Traduit un motif `$like` en expression régulière **ancrée** — pour les stores
|
|
86
|
+
* qui n'ont pas de `LIKE` (MongoDB, implémentations en mémoire).
|
|
87
|
+
*
|
|
88
|
+
* C'est la contrepartie exacte de ce qu'un moteur SQL fait avec
|
|
89
|
+
* `LIKE … ESCAPE '\'` : `%` vaut « n'importe quelle suite », `_` « un
|
|
90
|
+
* caractère », et un caractère précédé de {@link LIKE_ESCAPE_CHAR} vaut
|
|
91
|
+
* lui-même. Sans cette lecture, un adapter documentaire rendrait des résultats
|
|
92
|
+
* différents d'un adapter SQL pour le même critère portable — la divergence la
|
|
93
|
+
* plus coûteuse qui soit, puisqu'elle ne se voit qu'en changeant de backend.
|
|
94
|
+
*
|
|
95
|
+
* Un antislash final sans caractère à échapper est traité comme un antislash
|
|
96
|
+
* littéral, comme le font les moteurs SQL.
|
|
97
|
+
*
|
|
98
|
+
* @param pattern - le motif du contrat (`préfixe%`, `a\_b`…).
|
|
99
|
+
* @returns une `RegExp` ancrée aux deux bouts.
|
|
100
|
+
*/
|
|
101
|
+
export declare function likePatternToRegExp(pattern: string): RegExp;
|
|
102
|
+
/**
|
|
103
|
+
* Traduit un terme de recherche `?q=` en critère portable — **la** règle de
|
|
104
|
+
* recherche d'orm-core, en un seul exemplaire.
|
|
105
|
+
*
|
|
106
|
+
* Elle existe parce qu'elle était écrite plusieurs fois, presque à l'identique,
|
|
107
|
+
* dans des stores qui n'avaient aucun test dessus.
|
|
108
|
+
*
|
|
109
|
+
* **Le motif est ANCRÉ À GAUCHE** (`préfixe%`), donc **indexable**. Une
|
|
110
|
+
* recherche `%terme%` interdit tout usage d'index et impose un balayage
|
|
111
|
+
* complet : plus « pratique » sur dix lignes, intenable sur un million. C'est un
|
|
112
|
+
* choix de conception — un besoin de sous-chaîne relève d'un index plein-texte,
|
|
113
|
+
* pas de `LIKE`.
|
|
114
|
+
*
|
|
115
|
+
* Plusieurs champs deviennent un `$or` ; un seul reste un critère plat (les
|
|
116
|
+
* deux formes sont équivalentes pour les adapters, la seconde est plus lisible
|
|
117
|
+
* dans les journaux de requêtes).
|
|
118
|
+
*
|
|
119
|
+
* Le terme est **échappé** ({@link escapeLikeTerm}) : chercher `50%` cherche
|
|
120
|
+
* « 50% », et `a_b` ne ramène pas `axb`. Ça n'a été possible qu'une fois la
|
|
121
|
+
* clause `ESCAPE` émise par la traduction de `$like` — auparavant un terme
|
|
122
|
+
* échappé était cherché littéralement, antislash compris, et ne rendait plus
|
|
123
|
+
* rien du tout, en silence. Les deux gestes sont indissociables : c'est pourquoi
|
|
124
|
+
* ils vivent dans le même fichier.
|
|
125
|
+
*
|
|
126
|
+
* @param q - le terme saisi, déjà trimé par `parsePageQuery`.
|
|
127
|
+
* @param fields - les champs sur lesquels chercher, en noms de propriétés.
|
|
128
|
+
* @returns le critère à fusionner, ou `null` si le terme est vide ou qu'aucun
|
|
129
|
+
* champ n'est déclaré — l'appelant décide alors quoi faire de `q`.
|
|
130
|
+
*/
|
|
131
|
+
export declare function searchCriteria<T>(q: string | undefined, fields: ReadonlyArray<keyof T & string>): Record<string, unknown> | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Module } from "nodefony";
|
|
2
|
+
import type { IEntityDefinition } from "../defineEntity.js";
|
|
3
|
+
type Constructor<T = object> = new (...args: any[]) => T;
|
|
4
|
+
/** Connecteur retenu quand ni l'entité ni le décorateur n'en nomment un. */
|
|
5
|
+
export declare const DEFAULT_CONNECTOR = "default";
|
|
6
|
+
/** Options du décorateur {@link entities}. */
|
|
7
|
+
export interface EntitiesOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Connexion nommée pour toutes les entités de la liste (défaut : `"default"`).
|
|
10
|
+
* Une entité qui porte son propre `connector` garde le sien — utile pour une base
|
|
11
|
+
* secondaire (ex. un entrepôt d'analyse) déclarée dans le même module.
|
|
12
|
+
*/
|
|
13
|
+
connector?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Déclare les entités qu'un module apporte — l'équivalent de `@controllers([...])`
|
|
17
|
+
* pour la couche données.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* @entities([PostEntity, CommentEntity])
|
|
21
|
+
* @controllers([PostController])
|
|
22
|
+
* class Blog extends Module { … }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* **Pourquoi ce décorateur existe** : l'inscription d'une entité est impérative
|
|
26
|
+
* (`entityRegistry.register()`), et il n'y a aucune découverte automatique. Sans lui,
|
|
27
|
+
* une application n'a **aucun endroit** où déclarer ses entités : elle dépend d'un
|
|
28
|
+
* import à effet de bord, où un fichier simplement oublié donne une entité
|
|
29
|
+
* silencieusement absente — l'erreur n'apparaît qu'au premier `getRepository()`, loin
|
|
30
|
+
* de sa cause. Une liste, elle, se lit.
|
|
31
|
+
*
|
|
32
|
+
* **Phase `onRegister`, jamais `onBoot`** (piège) : les connecteurs se branchent à
|
|
33
|
+
* `onBoot` et créent les tables à ce moment-là. Enregistrer les entités à `onBoot`,
|
|
34
|
+
* comme le fait `@controllers`, en ferait une **course** avec le `connect()` : selon
|
|
35
|
+
* l'ordre des écouteurs, la table n'existerait pas. `onRegister` est strictement
|
|
36
|
+
* antérieur — sûr par construction.
|
|
37
|
+
*
|
|
38
|
+
* **Idempotent** : une entité déjà inscrite pour le même connecteur est ignorée (un
|
|
39
|
+
* module peut être instancié deux fois dans un même processus — tests, rechargement).
|
|
40
|
+
* Une **collision réelle** (deux entités différentes, même nom, même connecteur) reste
|
|
41
|
+
* une erreur levée par le registre : c'est un conflit de modèle, pas un doublon bénin.
|
|
42
|
+
*
|
|
43
|
+
* @param list - descripteurs produits par `defineEntity()` (un seul ou un tableau).
|
|
44
|
+
* @param options - connecteur cible commun.
|
|
45
|
+
* @returns le décorateur de classe `Module`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function entities(list: IEntityDefinition[] | IEntityDefinition, options?: EntitiesOptions): <T extends Constructor<Module>>(constructor: T) => T;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { IEntityRelation } from "../../interfaces/index.js";
|
|
2
|
+
import { type DecoratedClass } from "./metadataStore.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options du décorateur {@link entity}.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam S - type du schéma natif du driver.
|
|
7
|
+
*/
|
|
8
|
+
export interface EntityOptions<S = unknown> {
|
|
9
|
+
/** Connexion nommée cible, telle que déclarée en config (ex. `"db_principale"`). */
|
|
10
|
+
connector: string;
|
|
11
|
+
/** Nom logique ; par défaut le nom de la classe décorée. */
|
|
12
|
+
name?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Module Nodefony propriétaire (ex. `"user"`, `"test"`) — regroupe l'entité
|
|
15
|
+
* dans le graphe canonique / ERD Studio. Optionnel.
|
|
16
|
+
*/
|
|
17
|
+
module?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Classification (domaine fonctionnel) — axe de regroupement ERD distinct du
|
|
20
|
+
* `module`. Optionnel. Voir {@link IEntity.domain}.
|
|
21
|
+
*/
|
|
22
|
+
domain?: string;
|
|
23
|
+
/** Schéma natif du driver (schéma Mongoose, schéma Drizzle...). */
|
|
24
|
+
schema?: S;
|
|
25
|
+
/** Relations déclaratives vers d'autres entités. */
|
|
26
|
+
relations?: ReadonlyArray<IEntityRelation>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Décore une classe comme entité multi-ORM et l'enregistre au chargement.
|
|
30
|
+
*
|
|
31
|
+
* Résout le piège d'ordre d'initialisation TS (le constructeur de base
|
|
32
|
+
* s'exécute avant les initialiseurs de champs de la sous-classe) : le décorateur
|
|
33
|
+
* s'exécute sur la **classe** au chargement du module, donc `name`/`connector`
|
|
34
|
+
* sont connus sans instance. Il construit un **descripteur {@link IEntity} depuis
|
|
35
|
+
* les options** (aucune instanciation au boot), l'enregistre dans le
|
|
36
|
+
* `entityRegistry` process-wide, et stocke la métadonnée via un `WeakMap`
|
|
37
|
+
* (cf. `metadataStore`, sans `reflect-metadata`).
|
|
38
|
+
*
|
|
39
|
+
* @param options - connecteur cible + nom/schéma/relations optionnels.
|
|
40
|
+
* @returns le décorateur de classe (renvoie la classe inchangée).
|
|
41
|
+
* @throws si une entité de même `name` est déjà enregistrée sur le même `connector`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* \@entity({ connector: "db_principale", schema: { id: { type: "uuid" } } })
|
|
46
|
+
* class User extends Entity { ... }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function entity<S = unknown>(options: EntityOptions<S>): <T extends DecoratedClass>(target: T) => T;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel des décorateurs orm-core (P5.3).
|
|
3
|
+
*
|
|
4
|
+
* `@entity` + `@repository` + helpers d'introspection des métadonnées
|
|
5
|
+
* (stockées via `WeakMap`, sans `reflect-metadata`).
|
|
6
|
+
*/
|
|
7
|
+
export { entity, type EntityOptions } from "./entityDecorator.js";
|
|
8
|
+
export { entities, DEFAULT_CONNECTOR, type EntitiesOptions, } from "./entitiesDecorator.js";
|
|
9
|
+
export { repository, type RepositoryOptions } from "./repositoryDecorator.js";
|
|
10
|
+
export { getEntityMeta, hasEntityMeta, getRepositoryMeta, hasRepositoryMeta, type EntityMetadata, type RepositoryMetadata, type DecoratedClass, } from "./metadataStore.js";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IEntityRelation } from "../../interfaces/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Constructeur de classe ciblé par un décorateur orm-core (paramètres libres).
|
|
4
|
+
*
|
|
5
|
+
* `never[]` en rest accepte n'importe quelle signature de constructeur concrète
|
|
6
|
+
* tout en restant assignable comme clé `WeakMap`. Évite `any` (règle projet).
|
|
7
|
+
*/
|
|
8
|
+
export type DecoratedClass = new (...args: never[]) => object;
|
|
9
|
+
/**
|
|
10
|
+
* Métadonnée attachée à une classe par {@link entity}.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam S - type du schéma natif du driver.
|
|
13
|
+
*/
|
|
14
|
+
export interface EntityMetadata<S = unknown> {
|
|
15
|
+
/** Nom logique de l'entité (clé de lookup). */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** Connexion nommée cible (clé du `ormRegistry`). */
|
|
18
|
+
readonly connector: string;
|
|
19
|
+
/** Module Nodefony propriétaire (regroupement graphe/ERD), si fourni. */
|
|
20
|
+
readonly module?: string;
|
|
21
|
+
/** Classification (domaine fonctionnel), axe de regroupement distinct du module. */
|
|
22
|
+
readonly domain?: string;
|
|
23
|
+
/** Schéma natif du driver (forme libre), si fourni au décorateur. */
|
|
24
|
+
readonly schema?: S;
|
|
25
|
+
/** Relations déclaratives vers d'autres entités. */
|
|
26
|
+
readonly relations?: ReadonlyArray<IEntityRelation>;
|
|
27
|
+
/** Constructeur décoré (pour introspection / résolution lazy par le driver). */
|
|
28
|
+
readonly target: DecoratedClass;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Métadonnée attachée à une classe par {@link repository}.
|
|
32
|
+
*/
|
|
33
|
+
export interface RepositoryMetadata {
|
|
34
|
+
/** Nom logique du repository (clé DI, ex. `"repository.user"`). */
|
|
35
|
+
readonly name: string;
|
|
36
|
+
/** Nom logique de l'entité gérée par ce repository. */
|
|
37
|
+
readonly entity: string;
|
|
38
|
+
/** Connecteur cible (lève l'ambiguïté si l'entité existe sur plusieurs connexions). */
|
|
39
|
+
readonly connector?: string;
|
|
40
|
+
/** Constructeur décoré. */
|
|
41
|
+
readonly target: DecoratedClass;
|
|
42
|
+
}
|
|
43
|
+
/** Enregistre la métadonnée `@entity` d'une classe. */
|
|
44
|
+
export declare function setEntityMeta(target: DecoratedClass, meta: EntityMetadata): void;
|
|
45
|
+
/** Récupère la métadonnée `@entity` d'une classe, ou `undefined`. */
|
|
46
|
+
export declare function getEntityMeta(target: DecoratedClass): EntityMetadata | undefined;
|
|
47
|
+
/** Indique si une classe porte une métadonnée `@entity`. */
|
|
48
|
+
export declare function hasEntityMeta(target: DecoratedClass): boolean;
|
|
49
|
+
/** Enregistre la métadonnée `@repository` d'une classe. */
|
|
50
|
+
export declare function setRepositoryMeta(target: DecoratedClass, meta: RepositoryMetadata): void;
|
|
51
|
+
/** Récupère la métadonnée `@repository` d'une classe, ou `undefined`. */
|
|
52
|
+
export declare function getRepositoryMeta(target: DecoratedClass): RepositoryMetadata | undefined;
|
|
53
|
+
/** Indique si une classe porte une métadonnée `@repository`. */
|
|
54
|
+
export declare function hasRepositoryMeta(target: DecoratedClass): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type DecoratedClass } from "./metadataStore.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options du décorateur {@link repository}.
|
|
4
|
+
*/
|
|
5
|
+
export interface RepositoryOptions {
|
|
6
|
+
/** Nom logique de l'entité gérée par ce repository. */
|
|
7
|
+
entity: string;
|
|
8
|
+
/** Connecteur cible — lève l'ambiguïté si l'entité existe sur plusieurs connexions. */
|
|
9
|
+
connector?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Décore une classe comme repository d'une entité (tag métadonnée pur).
|
|
13
|
+
*
|
|
14
|
+
* Stocke le lien repo↔entity dans un `WeakMap` (cf. `metadataStore`, sans
|
|
15
|
+
* `reflect-metadata`). N'enregistre RIEN dans un registre en P5.3 : le binding
|
|
16
|
+
* DI (`@Inject('repository.user.db_principale')`) est câblé par l'adapter ORM
|
|
17
|
+
* concret (P5.4+), qui scanne ces métadonnées. Coût uniquement au chargement
|
|
18
|
+
* du module — jamais par requête.
|
|
19
|
+
*
|
|
20
|
+
* @param name - nom logique du repository (clé DI, ex. `"repository.user"`).
|
|
21
|
+
* @param options - entité gérée + connecteur cible optionnel.
|
|
22
|
+
* @returns le décorateur de classe (renvoie la classe inchangée).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* \@repository("repository.user", { entity: "User", connector: "db_principale" })
|
|
27
|
+
* class UserRepository implements IRepository<User> { ... }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function repository(name: string, options: RepositoryOptions): <T extends DecoratedClass>(target: T) => T;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IEntity } from "../interfaces/IEntity.js";
|
|
2
|
+
/**
|
|
3
|
+
* Descripteur d'entité **sans connecteur figé** — la forme sous laquelle une
|
|
4
|
+
* application déclare ses entités.
|
|
5
|
+
*
|
|
6
|
+
* Pourquoi le connecteur manque : dans {@link IEntity}, `connector` est le **nom
|
|
7
|
+
* d'une connexion** (`"default"`, `"analytics"`…). C'est une donnée de
|
|
8
|
+
* **configuration**, pas de code : la même table peut être servie par une connexion
|
|
9
|
+
* différente selon l'environnement. La figer à l'import interdirait de la réutiliser
|
|
10
|
+
* — c'est exactement ce qui condamne le décorateur de classe
|
|
11
|
+
* `@entity({connector, schema})` à n'être jamais employé en production. Ici, le
|
|
12
|
+
* connecteur est résolu **au boot**, par le décorateur `entities`.
|
|
13
|
+
*/
|
|
14
|
+
export interface IEntityDefinition<S = unknown, M = unknown> extends Omit<IEntity<S, M>, "connector"> {
|
|
15
|
+
/**
|
|
16
|
+
* Connecteur cible — à ne renseigner que pour **forcer** une entité sur une
|
|
17
|
+
* connexion précise (base secondaire). Sinon, c'est `entities(…, { connector })`
|
|
18
|
+
* qui tranche, et à défaut le connecteur `"default"`.
|
|
19
|
+
*/
|
|
20
|
+
readonly connector?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Déclare une entité applicative. Fonction d'**identité typée** : elle ne fait
|
|
24
|
+
* qu'attacher le type — aucun effet de bord, aucun enregistrement.
|
|
25
|
+
*
|
|
26
|
+
* L'enregistrement est le rôle du décorateur `entities([...])` posé sur le Module,
|
|
27
|
+
* qui s'exécute à la phase `onRegister` (avant que l'ORM ne se connecte). Séparer
|
|
28
|
+
* les deux permet d'importer une entité (dans un test, un script, un autre module)
|
|
29
|
+
* sans déclencher son inscription dans un registre global.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* export const PostEntity = defineEntity({
|
|
34
|
+
* name: "Post",
|
|
35
|
+
* module: "blog",
|
|
36
|
+
* schema: postTable, // table Drizzle native (ou SchemaDefinition Mongoose)
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param definition - descripteur (nom logique, schéma natif, module, relations…).
|
|
41
|
+
* @returns le descripteur, tel quel.
|
|
42
|
+
*/
|
|
43
|
+
export declare function defineEntity<S, M = unknown>(definition: IEntityDefinition<S, M>): IEntityDefinition<S, M>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erreurs typées du socle ORM — **data-level uniquement** (aucun couplage à une
|
|
3
|
+
* couche API/transport : ces erreurs décrivent des fautes sur les données, pas
|
|
4
|
+
* sur une requête HTTP/GraphQL). Une surface API qui les rencontre décide
|
|
5
|
+
* elle-même comment les projeter (400, erreur GraphQL…), l'ORM n'en sait rien.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Levée quand un critère de repository référence un **champ inconnu** de l'entité
|
|
9
|
+
* ciblée.
|
|
10
|
+
*
|
|
11
|
+
* Garde-fou « ultra solide » + **portabilité** : sans elle, un champ inconnu est
|
|
12
|
+
* traité différemment selon l'adapter — Drizzle l'**ignore** (la condition
|
|
13
|
+
* disparaît → la requête peut renvoyer **toute** la table), Mongoose le **garde**
|
|
14
|
+
* (→ **0 résultat**). Le même critère mal typé donnerait donc des résultats
|
|
15
|
+
* opposés selon l'ORM (rupture de la promesse « swap d'ORM ») et une faute de
|
|
16
|
+
* frappe (`emial`) passerait silencieusement. On échoue **tôt et pareil** sur les
|
|
17
|
+
* deux drivers.
|
|
18
|
+
*
|
|
19
|
+
* Les requêtes natives/calculées (opérateurs logiques `$or`, sous-requêtes…) ne
|
|
20
|
+
* relèvent **pas** du critère portable : passer par `IOrm.getNativeConnection()`.
|
|
21
|
+
*/
|
|
22
|
+
export declare class UnknownCriteriaField extends Error {
|
|
23
|
+
/** Champ fautif (clé du critère non résolue sur l'entité). */
|
|
24
|
+
readonly field: string;
|
|
25
|
+
/** Nom logique de l'entité ciblée. */
|
|
26
|
+
readonly entity: string;
|
|
27
|
+
/** Champs connus de l'entité (aide au diagnostic / faute de frappe). */
|
|
28
|
+
readonly known: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* @param field - champ inconnu rencontré dans le critère.
|
|
31
|
+
* @param entity - entité ciblée (nom logique).
|
|
32
|
+
* @param known - champs connus de l'entité.
|
|
33
|
+
*/
|
|
34
|
+
constructor(field: string, entity: string, known: readonly string[]);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Levée quand l'option de lecture `order` n'est pas un tableau de couples
|
|
38
|
+
* `[champ, "ASC" | "DESC"]`.
|
|
39
|
+
*
|
|
40
|
+
* Symétrique de {@link UnknownCriteriaField}, sous la même règle « jamais un skip
|
|
41
|
+
* silencieux » : avant cette garde, une forme voisine mais fausse — `{ age: "asc" }`
|
|
42
|
+
* au lieu de `[["age", "ASC"]]` — faisait partir la requête **sans `ORDER BY`**, et
|
|
43
|
+
* l'appelant recevait des lignes non triées qu'il croyait triées. Le sens est vérifié
|
|
44
|
+
* en casse exacte : un `"desc"` minuscule aurait produit un tri **ASC**, soit
|
|
45
|
+
* l'inverse de l'intention.
|
|
46
|
+
*
|
|
47
|
+
* TypeScript attrape déjà le cas au typage ; la surface réelle est l'appel non typé
|
|
48
|
+
* ou construit dynamiquement — exactement là où un tri faux ne se voit pas. La
|
|
49
|
+
* normalisation d'une entrée utilisateur (casse, `champ:sens`) appartient à la
|
|
50
|
+
* frontière qui la reçoit (`parsePageQuery`), jamais au repository.
|
|
51
|
+
*/
|
|
52
|
+
export declare class InvalidOrderOption extends Error {
|
|
53
|
+
/** Nom logique de l'entité ciblée. */
|
|
54
|
+
readonly entity: string;
|
|
55
|
+
/** Ce qui a été reçu, décrit par sa FORME (jamais la valeur : pas de fuite de données). */
|
|
56
|
+
readonly received: string;
|
|
57
|
+
/**
|
|
58
|
+
* @param entity - entité ciblée (nom logique).
|
|
59
|
+
* @param received - description de la forme reçue (ex. `an object`, `pair #0 is not an array`).
|
|
60
|
+
*/
|
|
61
|
+
constructor(entity: string, received: string);
|
|
62
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IKernel } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* Branche le « plan d'administration » ORM dans le kernel — montage **idempotent**
|
|
4
|
+
* appelé par CHAQUE driver à son `onKernelBoot`. Factorise la dette C5 : le bloc
|
|
5
|
+
* était jusqu'ici recopié à l'identique dans chaque module driver (Drizzle,
|
|
6
|
+
* Mongoose), donc voué à diverger ; il vit désormais à un seul endroit.
|
|
7
|
+
*
|
|
8
|
+
* Trois branchements, tous **GLOBAUX** (ils itèrent `ormRegistry`) → couvrent
|
|
9
|
+
* TOUS les ORM enregistrés, peu importe le driver appelant ; idempotents
|
|
10
|
+
* (« dernier gagne ») → sûr d'être invoqué par N drivers :
|
|
11
|
+
* 1. `registerOrmAdminApi` (si un broker admin est présent) — monte les routes
|
|
12
|
+
* data plane `/nodefony/orm/api/*` ;
|
|
13
|
+
* 2. `setOrmHealthProvider` — santé ORM lean dans la sonde cluster (par worker) ;
|
|
14
|
+
* 3. `setOrmRichProvider` — diagnostic riche (connexion + flux) pour le drill Studio.
|
|
15
|
+
*
|
|
16
|
+
* Les seams `setOrm*Provider` matérialisent l'inversion de dépendance : le core
|
|
17
|
+
* expose la prise, orm-core fournit l'implémentation agnostique
|
|
18
|
+
* (0 dépendance `framework` → `orm-core`).
|
|
19
|
+
*
|
|
20
|
+
* @param kernel - kernel courant (`this.kernel` du module driver), ou nullish.
|
|
21
|
+
*/
|
|
22
|
+
export declare function wireOrmAdminPlane(kernel: IKernel | null | undefined): void;
|
|
23
|
+
/**
|
|
24
|
+
* Pousse dans le BootReporter une ligne par ORM enregistré (« nom → driver
|
|
25
|
+
* (cible) ») pour que la phase de boot « Services & ORM » RACONTE les connexions
|
|
26
|
+
* mises en place, au lieu d'une phase muette. Idempotent : reconstruit la liste
|
|
27
|
+
* complète depuis le registre et REMPLACE (sûr d'être appelé par N drivers).
|
|
28
|
+
*
|
|
29
|
+
* `describeConnection()` ne révèle JAMAIS de credential (redaction côté adapter).
|
|
30
|
+
* Le libellé « Services & ORM » doit matcher une phase du `BootReporter` (core).
|
|
31
|
+
*
|
|
32
|
+
* @param kernel - kernel courant (`this.kernel` du module driver), ou nullish.
|
|
33
|
+
*/
|
|
34
|
+
export declare function reportOrmBootLines(kernel: IKernel | null | undefined): void;
|
|
35
|
+
/**
|
|
36
|
+
* Détermine si la sonde de flux ORM (`queryFlowMonitor`) doit être active :
|
|
37
|
+
* **OFF en production** (coût nul sur le hot path des requêtes), **ON sinon**
|
|
38
|
+
* (observabilité dev / Supervision). Override explicite par la variable
|
|
39
|
+
* d'environnement `NF_ORM_FLOW` (`1`/`true` = forcer ON).
|
|
40
|
+
*
|
|
41
|
+
* Factorise le calcul recopié à l'identique dans chaque `*Service.onBoot` (le
|
|
42
|
+
* pendant « flux » de {@link wireOrmAdminPlane}, gardé distinct car il s'exécute
|
|
43
|
+
* dans le Service, pas le Module, et porte une responsabilité différente).
|
|
44
|
+
*
|
|
45
|
+
* @param kernel - kernel courant (pour lire l'environnement d'exécution).
|
|
46
|
+
* @returns `true` si la sonde de flux doit être activée.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveOrmFlowEnabled(kernel: IKernel | null | undefined): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IRepository } from "../interfaces/IRepository.js";
|
|
2
|
+
import type { IPage, PageQuery } from "../interfaces/IPage.js";
|
|
3
|
+
/** Réglages d'un `paginate` — ce que ce point d'entrée sait faire en plus de découper. */
|
|
4
|
+
export interface IPaginateOptions<T> {
|
|
5
|
+
/**
|
|
6
|
+
* Champs sur lesquels `?q=` cherche. **Absent ou vide ⇒ tout `q` reçu est
|
|
7
|
+
* REFUSÉ**, jamais ignoré.
|
|
8
|
+
*
|
|
9
|
+
* Le défaut est le refus pour la même raison que pour le tri et les filtres :
|
|
10
|
+
* un `q` accepté puis jeté rend la collection ENTIÈRE à qui croit lire un
|
|
11
|
+
* résultat de recherche — la façon la plus discrète de mentir, puisque la
|
|
12
|
+
* réponse est un `200` bien formé. Et c'est exactement ce que ce helper
|
|
13
|
+
* faisait : `q` traversait son type sans être lu une seule fois.
|
|
14
|
+
*
|
|
15
|
+
* Comme le tri, la recherche est une capacité — elle se constate et se
|
|
16
|
+
* déclare, jamais elle ne se suppose.
|
|
17
|
+
*/
|
|
18
|
+
searchable?: ReadonlyArray<keyof T & string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pagine un {@link IRepository} de façon **portable** — au-dessus des primitives
|
|
22
|
+
* natives `find(criteria, { limit, offset, order })` et `count(criteria)` que tout
|
|
23
|
+
* adapter implémente déjà (SQL `LIMIT/OFFSET`+`COUNT`, Mongo `skip/limit`+
|
|
24
|
+
* `countDocuments`…). Aucun adapter n'a à changer : un seul aller vers la page,
|
|
25
|
+
* jamais de matérialisation de toute la collection.
|
|
26
|
+
*
|
|
27
|
+
* `hasNext` est obtenu **sans `COUNT`** par l'astuce `limit + 1` : on demande une
|
|
28
|
+
* ligne de plus que la page ; si elle arrive, il y a une suite (on la retire du
|
|
29
|
+
* résultat). Le `COUNT(*)` — coûteux sur les grosses tables — n'est payé que si
|
|
30
|
+
* `withTotal` n'est pas `false` (mode « Page » vs « Slice », cf Spring Data).
|
|
31
|
+
*
|
|
32
|
+
* La **recherche** `?q=` n'est honorée que si l'appelant déclare où chercher
|
|
33
|
+
* ({@link IPaginateOptions.searchable}) ; sans cela elle est refusée en `400`.
|
|
34
|
+
*
|
|
35
|
+
* @typeParam T - type de l'entité paginée.
|
|
36
|
+
* @param repo - le repository à paginer.
|
|
37
|
+
* @param page - la requête de page ({@link PageQuery}).
|
|
38
|
+
* @param options - capacités de ce point d'entrée (recherche).
|
|
39
|
+
* @returns une {@link Page} : au plus `limit` items, `hasNext`, et `total` si demandé.
|
|
40
|
+
* @throws {@link PageQueryError} (`400`) si un `q` est reçu sans champ
|
|
41
|
+
* cherchable déclaré, ou si le critère porte déjà un `$or`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function paginate<T>(repo: IRepository<T>, page: PageQuery<T>, options?: IPaginateOptions<T>): Promise<IPage<T>>;
|