@nodefony/orm-core 10.0.0-beta.3 → 10.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nodefony/interfaces/IOrmMigrations.js +1 -2
- package/dist/nodefony/src/AbstractCrudService.js +1 -2
- package/dist/nodefony/src/ConnectionMonitor.js +1 -2
- package/dist/nodefony/src/Entity.js +1 -2
- package/dist/nodefony/src/EntityRegistry.js +1 -2
- package/dist/nodefony/src/Orm.js +1 -2
- package/dist/nodefony/src/OrmAdminApi.js +4 -5
- package/dist/nodefony/src/OrmRegistry.js +1 -2
- package/dist/nodefony/src/QueryFlowMonitor.js +1 -2
- package/dist/nodefony/src/buildOrmLeanHealth.js +1 -2
- package/dist/nodefony/src/connectDeadline.js +1 -2
- package/dist/nodefony/src/connectionDiagnosis.js +72 -15
- package/dist/nodefony/src/criteria.js +1 -2
- package/dist/nodefony/src/decorators/entitiesDecorator.js +1 -2
- package/dist/nodefony/src/decorators/entityDecorator.js +1 -2
- package/dist/nodefony/src/decorators/metadataStore.js +3 -4
- package/dist/nodefony/src/decorators/repositoryDecorator.js +1 -2
- package/dist/nodefony/src/defineEntity.js +1 -2
- package/dist/nodefony/src/errors.js +1 -2
- package/dist/nodefony/src/omitUndefined.js +1 -2
- package/dist/nodefony/src/ormWiring.js +1 -2
- package/dist/nodefony/src/orphanEntities.js +1 -2
- package/dist/nodefony/src/paginate.js +1 -2
- package/dist/nodefony/src/readOptions.js +1 -2
- package/dist/types/nodefony/src/connectionDiagnosis.d.ts +9 -1
- package/docs/index.md +132 -132
- package/docs/tutorial-entity.md +86 -79
- package/package.json +5 -5
package/docs/tutorial-entity.md
CHANGED
|
@@ -74,7 +74,7 @@ changer de moteur ne touche pas ton code.
|
|
|
74
74
|
|
|
75
75
|
Une fois l'entité inscrite, tu ne la manipules plus jamais directement : tu demandes son
|
|
76
76
|
**repository**, et c'est lui qui porte les verbes (`find`, `create`, `updateOne`…). Un repository
|
|
77
|
-
est le contrat `IRepository<T>` (`IRepository.ts
|
|
77
|
+
est le contrat `IRepository<T>` (`IRepository.ts#RepositoryReadOptions.relations`), identique quel que soit le moteur.
|
|
78
78
|
|
|
79
79
|
> [!NOTE]
|
|
80
80
|
> `@nodefony/orm-core` n'est **pas un module à démarrer** : rien à ajouter dans `modules: [...]`.
|
|
@@ -87,16 +87,16 @@ Deux partis pris expliquent la forme exacte des étapes qui suivent. Les connaî
|
|
|
87
87
|
battre contre le cadre.
|
|
88
88
|
|
|
89
89
|
**1. Le connecteur est une donnée de configuration, jamais de code.** C'est pourquoi le
|
|
90
|
-
descripteur qu'on écrit (`IEntityDefinition`, `defineEntity.ts
|
|
91
|
-
(`IEntity.ts
|
|
92
|
-
pour valeur de repli (`DEFAULT_CONNECTOR`, `entitiesDecorator.ts
|
|
90
|
+
descripteur qu'on écrit (`IEntityDefinition`, `defineEntity.ts#IEntityDefinition`) est un `IEntity`
|
|
91
|
+
(`IEntity.ts#IEntity`) **privé de son `connector`** : celui-ci est résolu au démarrage, avec `"default"`
|
|
92
|
+
pour valeur de repli (`DEFAULT_CONNECTOR`, `entitiesDecorator.ts#DEFAULT_CONNECTOR`). Figer la connexion dans le
|
|
93
93
|
fichier d'entité interdirait de servir la même table depuis une base différente selon
|
|
94
94
|
l'environnement.
|
|
95
95
|
|
|
96
96
|
**2. L'inscription est déclarative, et elle a lieu tôt.** `defineEntity()`
|
|
97
|
-
(`defineEntity.ts
|
|
98
|
-
C'est le décorateur `entities()` (`entitiesDecorator.ts
|
|
99
|
-
inscrit la liste — à la phase `onRegister` (`entitiesDecorator.ts
|
|
97
|
+
(`defineEntity.ts#defineEntity`) n'a **aucun effet de bord** : importer un fichier d'entité n'inscrit rien.
|
|
98
|
+
C'est le décorateur `entities()` (`entitiesDecorator.ts#entities`), posé sur la classe du module, qui
|
|
99
|
+
inscrit la liste — à la phase `onRegister` (`entitiesDecorator.ts#entities`), strictement **avant** que
|
|
100
100
|
le connecteur ne s'ouvre et ne crée les tables.
|
|
101
101
|
|
|
102
102
|
Le bénéfice est concret : une entité oubliée se voit **dans une liste**, pas dans un import à
|
|
@@ -114,18 +114,18 @@ Le moteur et sa cible physique vivent ici, et nulle part ailleurs. SQLite ne dem
|
|
|
114
114
|
installation : c'est le bon choix pour ce premier tour.
|
|
115
115
|
|
|
116
116
|
```ts
|
|
117
|
-
// nodefony
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
})
|
|
117
|
+
// nodefony/config/drizzle.ts — le fragment que le générateur a posé, que
|
|
118
|
+
// `nodefony.config.ts` passe à `use("@nodefony/drizzle", drizzleConfig())`.
|
|
119
|
+
// Le connecteur "default" est le NOM de la connexion, pas celui du moteur :
|
|
120
|
+
// c'est `dialect` qui nomme le moteur.
|
|
121
|
+
import type { IDrizzleConfigInput } from "@nodefony/drizzle";
|
|
122
|
+
|
|
123
|
+
export const drizzleConfig = () =>
|
|
124
|
+
({
|
|
125
|
+
connectors: {
|
|
126
|
+
default: { dialect: "sqlite", filename: "var/databases/app.db" },
|
|
127
|
+
},
|
|
128
|
+
}) satisfies IDrizzleConfigInput;
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
### 2. Décrire la table et l'inscrire
|
|
@@ -234,8 +234,8 @@ export async function demo(): Promise<void> {
|
|
|
234
234
|
### Ce qu'on observe
|
|
235
235
|
|
|
236
236
|
Au démarrage, le driver crée la table et le récap de boot affiche la connexion sous
|
|
237
|
-
« Services & ORM » (`reportOrmBootLines()`, `ormWiring.ts
|
|
238
|
-
inscrite laisse une ligne `ADD ENTITY` (`entitiesDecorator.ts
|
|
237
|
+
« Services & ORM » (`reportOrmBootLines()`, `ormWiring.ts#reportOrmBootLines`). En journal `DEBUG`, chaque entité
|
|
238
|
+
inscrite laisse une ligne `ADD ENTITY` (`entitiesDecorator.ts#entities`) — c'est la preuve la plus
|
|
239
239
|
directe que le décorateur a bien travaillé.
|
|
240
240
|
|
|
241
241
|
Le data plane confirme depuis l'extérieur, sans ouvrir la base :
|
|
@@ -266,12 +266,19 @@ c'est simplement celui que les entités visent quand elles n'en nomment pas d'au
|
|
|
266
266
|
Une deuxième base se déclare exactement pareil, sous un autre nom :
|
|
267
267
|
|
|
268
268
|
```ts ignore
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
// nodefony/config/drizzle.ts — `NF_ANALYTICS_URL` se déclare dans `env.ts`
|
|
270
|
+
// (`envString`), puis se lit par `ctx.env` : jamais `process.env` ici.
|
|
271
|
+
import type { ConfigContext } from "nodefony";
|
|
272
|
+
import type { IDrizzleConfigInput } from "@nodefony/drizzle";
|
|
273
|
+
import type { env } from "../../env";
|
|
274
|
+
|
|
275
|
+
export const drizzleConfig = (ctx: ConfigContext<typeof env>) =>
|
|
276
|
+
({
|
|
277
|
+
connectors: {
|
|
278
|
+
default: { dialect: "sqlite", filename: "var/databases/app.db" },
|
|
279
|
+
analytics: { dialect: "postgres", url: ctx.env.NF_ANALYTICS_URL },
|
|
280
|
+
},
|
|
281
|
+
}) satisfies IDrizzleConfigInput;
|
|
275
282
|
```
|
|
276
283
|
|
|
277
284
|
Les dialectes disponibles, leurs options et le choix de la cible sont détaillés dans la page du
|
|
@@ -297,29 +304,29 @@ Deux conséquences du DDL dérivé au boot, à connaître dès maintenant :
|
|
|
297
304
|
|
|
298
305
|
### Étape 3 — déclarer l'entité
|
|
299
306
|
|
|
300
|
-
`defineEntity()` (`defineEntity.ts
|
|
307
|
+
`defineEntity()` (`defineEntity.ts#defineEntity`) est une fonction d'**identité typée** : elle attache le
|
|
301
308
|
type et rend son argument, rien de plus. Aucun registre n'est touché.
|
|
302
309
|
|
|
303
310
|
Cette absence d'effet de bord est délibérée : elle permet d'importer une entité depuis un test,
|
|
304
311
|
un script ou un autre module **sans** déclencher son inscription dans un singleton global.
|
|
305
312
|
|
|
306
313
|
Les champs facultatifs qui servent tôt : `module` (qui apporte l'entité) et `domain`
|
|
307
|
-
(`IEntity.ts
|
|
314
|
+
(`IEntity.ts#IEntity.domain`), l'axe de classification qui rend navigable une base de plusieurs centaines de
|
|
308
315
|
tables dans l'ERD Studio.
|
|
309
316
|
|
|
310
317
|
### Étape 4 — brancher l'entité au module
|
|
311
318
|
|
|
312
|
-
C'est `entities()` (`entitiesDecorator.ts
|
|
319
|
+
C'est `entities()` (`entitiesDecorator.ts#entities`) qui inscrit, et lui seul. Trois propriétés valent
|
|
313
320
|
d'être connues :
|
|
314
321
|
|
|
315
|
-
1. **Phase `onRegister`** (`entitiesDecorator.ts
|
|
322
|
+
1. **Phase `onRegister`** (`entitiesDecorator.ts#entities`) — strictement avant l'ouverture des
|
|
316
323
|
connexions. C'est ce qui rend l'ordre sûr par construction, contrairement à `@controllers`, qui
|
|
317
324
|
travaille à `onBoot`.
|
|
318
325
|
2. **Idempotent** — une entité déjà inscrite sur le même connecteur est ignorée (un module peut
|
|
319
326
|
être instancié deux fois : tests, rechargement). Une vraie collision, elle, lève
|
|
320
|
-
(`EntityRegistry.register()`, `EntityRegistry.ts
|
|
327
|
+
(`EntityRegistry.register()`, `EntityRegistry.ts#EntityRegistry.register`).
|
|
321
328
|
3. **Connecteur commun** — `entities([...], { connector: "analytics" })` pose toute la liste sur
|
|
322
|
-
une autre base ; une entité qui porte son propre `connector` (`IEntity.ts
|
|
329
|
+
une autre base ; une entité qui porte son propre `connector` (`IEntity.ts#IEntity.connector`) garde le sien.
|
|
323
330
|
|
|
324
331
|
### Le raccourci — `nodefony create entity`
|
|
325
332
|
|
|
@@ -348,27 +355,27 @@ que l'import plus la ligne du décorateur.
|
|
|
348
355
|
Le repository porte **quinze verbes**, pas cinq. Ils se choisissent sur la **garantie** qu'ils
|
|
349
356
|
apportent, jamais sur leur nom.
|
|
350
357
|
|
|
351
|
-
| Verbe | Ce qu'il garantit | Ancre
|
|
352
|
-
| --------------------- | ------------------------------------------------------------------------- |
|
|
353
|
-
| `find` / `findOne` | lecture filtrée, avec tri, bornes et eager-load | `IRepository.ts
|
|
354
|
-
| `create` | insère une ligne et rend sa version persistée (id, défauts) | `IRepository.ts
|
|
355
|
-
| `createMany` | N lignes en **une** requête — seed, import, ingestion par lots | `IRepository.ts
|
|
356
|
-
| `updateOne` | modifie **au plus une** ligne, **atomiquement**, et la rend | `IRepository.ts
|
|
357
|
-
| `updateMany` | modifie toutes les lignes du critère, rend le **nombre** | `IRepository.ts
|
|
358
|
-
| `upsert` | insère **ou** met à jour sur conflit de clé, en une instruction | `IRepository.ts
|
|
359
|
-
| `increment` | `SET f = f + ?` atomique — compteurs, quotas, limitation de débit | `IRepository.ts
|
|
360
|
-
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts
|
|
361
|
-
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts
|
|
362
|
-
| `findOneAndDelete` | supprime **et rend** la ligne — file de jobs, `pop` atomique | `IRepository.ts
|
|
363
|
-
| `count` | compter les entités qui correspondent au critère | `IRepository.ts
|
|
364
|
-
| `exists` | savoir s'il y en a au moins une, sans charger de colonne | `IRepository.ts
|
|
365
|
-
| `countDistinct` | compter les valeurs DISTINCTES et non nulles d'un champ, sans les charger | `IRepository.ts
|
|
366
|
-
| `withTransaction(tx)` | une **vue** du repository liée à une transaction | `IRepository.ts
|
|
358
|
+
| Verbe | Ce qu'il garantit | Ancre |
|
|
359
|
+
| --------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
360
|
+
| `find` / `findOne` | lecture filtrée, avec tri, bornes et eager-load | `IRepository.ts#IRepository.findOne`, `IRepository.ts#RepositoryReadOptions.order` |
|
|
361
|
+
| `create` | insère une ligne et rend sa version persistée (id, défauts) | `IRepository.ts#IRepository.create` |
|
|
362
|
+
| `createMany` | N lignes en **une** requête — seed, import, ingestion par lots | `IRepository.ts#IRepository.createMany` |
|
|
363
|
+
| `updateOne` | modifie **au plus une** ligne, **atomiquement**, et la rend | `IRepository.ts#IRepository.updateOne` |
|
|
364
|
+
| `updateMany` | modifie toutes les lignes du critère, rend le **nombre** | `IRepository.ts#IRepository.updateMany` |
|
|
365
|
+
| `upsert` | insère **ou** met à jour sur conflit de clé, en une instruction | `IRepository.ts#IRepository.upsert` |
|
|
366
|
+
| `increment` | `SET f = f + ?` atomique — compteurs, quotas, limitation de débit | `IRepository.ts#IRepository.upsert` |
|
|
367
|
+
| `delete` | supprime tout ce qui matche, rend le nombre | `IRepository.ts#IRepository.delete` |
|
|
368
|
+
| `deleteOne` | supprime **au plus une** ligne, rend un booléen | `IRepository.ts#IRepository.deleteOne` |
|
|
369
|
+
| `findOneAndDelete` | supprime **et rend** la ligne — file de jobs, `pop` atomique | `IRepository.ts#IRepository.findOneAndDelete` |
|
|
370
|
+
| `count` | compter les entités qui correspondent au critère | `IRepository.ts#IRepository.count` |
|
|
371
|
+
| `exists` | savoir s'il y en a au moins une, sans charger de colonne | `IRepository.ts#IRepository.exists` |
|
|
372
|
+
| `countDistinct` | compter les valeurs DISTINCTES et non nulles d'un champ, sans les charger | `IRepository.ts#IRepository.countDistinct` |
|
|
373
|
+
| `withTransaction(tx)` | une **vue** du repository liée à une transaction | `IRepository.ts#IRepository.withTransaction` |
|
|
367
374
|
|
|
368
375
|
> [!IMPORTANT]
|
|
369
376
|
> **Il n'existe pas de méthode `update()`.** Le choix est explicite et il est intentionnel :
|
|
370
|
-
> `IRepository.updateOne()` (`IRepository.ts
|
|
371
|
-
> ligne modifiée —, `IRepository.updateMany()` (`IRepository.ts
|
|
377
|
+
> `IRepository.updateOne()` (`IRepository.ts#IRepository.updateOne`) pour une ligne — atomique, et elle **rend** la
|
|
378
|
+
> ligne modifiée —, `IRepository.updateMany()` (`IRepository.ts#IRepository.updateMany`) pour un lot — qui rend le
|
|
372
379
|
> **nombre** de lignes touchées. Un verbe unique masquerait cette différence de garantie, qui est
|
|
373
380
|
> précisément ce qu'on veut choisir en connaissance de cause.
|
|
374
381
|
|
|
@@ -397,8 +404,8 @@ await orm.transaction(async (tx) => {
|
|
|
397
404
|
```
|
|
398
405
|
|
|
399
406
|
Deux réflexes de performance, dès le premier jour : préférer `IRepository.exists()`
|
|
400
|
-
(`IRepository.ts
|
|
401
|
-
`IRepository.increment()` (`IRepository.ts
|
|
407
|
+
(`IRepository.ts#IRepository.exists`) à `findOne(...) !== null` — aucune colonne n'est chargée —, et
|
|
408
|
+
`IRepository.increment()` (`IRepository.ts#IRepository.increment`) à une lecture suivie d'une écriture : une requête
|
|
402
409
|
au lieu de deux, et pas de course.
|
|
403
410
|
|
|
404
411
|
## 🔎 Filtrer — les opérateurs de critère
|
|
@@ -414,7 +421,7 @@ await posts.find({ title: { $like: "Bon%" } }); // motif SQL
|
|
|
414
421
|
await posts.find({ publishedAt: { $null: false } }); // IS NOT NULL
|
|
415
422
|
```
|
|
416
423
|
|
|
417
|
-
**Dix** opérateurs sont reconnus, figés dans `OPERATOR_KEYS` (`criteria.ts
|
|
424
|
+
**Dix** opérateurs sont reconnus, figés dans `OPERATOR_KEYS` (`criteria.ts#OPERATOR_KEYS`) — source unique
|
|
418
425
|
partagée par tous les drivers :
|
|
419
426
|
|
|
420
427
|
| Opérateur | Effet | Opérateur | Effet |
|
|
@@ -431,21 +438,21 @@ Plusieurs opérateurs sur un même champ se combinent en **ET**.
|
|
|
431
438
|
> **`$null` est celui qu'on oublie, et il coûte cher.** En SQL, `colonne = NULL` est **toujours
|
|
432
439
|
> faux** : un filtre « la colonne est vide » écrit naïvement ne remonte jamais rien — sans la
|
|
433
440
|
> moindre erreur. Deux formes équivalentes le résolvent : la valeur nue `{ publishedAt: null }`
|
|
434
|
-
> et l'opérateur `{ publishedAt: { $null: true } }` (`FieldOperators.$null`, `IRepository.ts
|
|
441
|
+
> et l'opérateur `{ publishedAt: { $null: true } }` (`FieldOperators.$null`, `IRepository.ts#FieldOperators.$null`).
|
|
435
442
|
> La valeur nue n'est ouverte par le typage que si le champ est **nullable** (`FieldCriteria`,
|
|
436
|
-
> `IRepository.ts
|
|
443
|
+
> `IRepository.ts#FieldCriteria`) : chercher `IS NULL` sur une colonne non-nullable est une erreur de
|
|
437
444
|
> raisonnement, et le compilateur la refuse.
|
|
438
445
|
|
|
439
446
|
**Comment un objet est reconnu comme filtre plutôt que comme valeur** : `isFieldOperators()`
|
|
440
|
-
(`criteria.ts
|
|
447
|
+
(`criteria.ts#isFieldOperators`) ne l'interprète que si **toutes** ses clés sont des opérateurs connus. Une
|
|
441
448
|
colonne JSON ou un sous-document (`{ meta: { auteur: "…" } }`) reste donc une égalité — c'est ce
|
|
442
449
|
qui évite qu'une donnée métier soit prise pour une requête.
|
|
443
450
|
|
|
444
451
|
### Les opérateurs d'écriture — `$max` et `$min`
|
|
445
452
|
|
|
446
453
|
Une seconde famille, distincte, s'applique **en écriture** dans un `upsert` :
|
|
447
|
-
`UpdateOperators` (`IRepository.ts
|
|
448
|
-
`UPDATE_OPERATOR_KEYS` (`criteria.ts
|
|
454
|
+
`UpdateOperators` (`IRepository.ts#UpdateOperators`), dont les clés reconnues sont listées par
|
|
455
|
+
`UPDATE_OPERATOR_KEYS` (`criteria.ts#UPDATE_OPERATOR_KEYS`).
|
|
449
456
|
|
|
450
457
|
Ils existent parce qu'un upsert **ne peut pas porter de condition** : son `DO UPDATE` s'applique
|
|
451
458
|
dès qu'il y a conflit de clé (MySQL n'accepte pas de `WHERE` sur `ON DUPLICATE KEY UPDATE`). Pour
|
|
@@ -468,16 +475,16 @@ Sur une ligne dont on sait qu'elle **existe**, ils sont inutiles :
|
|
|
468
475
|
|
|
469
476
|
Les sous-requêtes, les agrégats et les jointures arbitraires n'en font pas partie (la disjonction,
|
|
470
477
|
elle, s'exprime par `$or: [...]`). Ce n'est pas un oubli : c'est la limite de ce qui se porte d'un moteur SQL à MongoDB. La
|
|
471
|
-
sortie est `IOrm.getNativeConnection()` (`IOrm.ts
|
|
478
|
+
sortie est `IOrm.getNativeConnection()` (`IOrm.ts#IOrm.getNativeConnection`), qui rend la connexion brute du driver.
|
|
472
479
|
|
|
473
|
-
Un champ absent de l'entité lève `UnknownCriteriaField` (`errors.ts
|
|
480
|
+
Un champ absent de l'entité lève `UnknownCriteriaField` (`errors.ts#UnknownCriteriaField`), et le message liste les
|
|
474
481
|
champs connus — le diagnostic d'une faute de frappe est immédiat.
|
|
475
482
|
|
|
476
483
|
## 📄 Pager les résultats
|
|
477
484
|
|
|
478
|
-
`find()` avec `limit`/`offset`/`order` (`RepositoryReadOptions`, `IRepository.ts
|
|
485
|
+
`find()` avec `limit`/`offset`/`order` (`RepositoryReadOptions`, `IRepository.ts#RepositoryReadOptions`) suffit pour
|
|
479
486
|
une tranche. Pour une vraie page — celle qui sait s'il y a une suite — utilise `paginate()`
|
|
480
|
-
(`paginate.ts
|
|
487
|
+
(`paginate.ts#paginate`) :
|
|
481
488
|
|
|
482
489
|
```ts ignore
|
|
483
490
|
const page = await paginate(posts, {
|
|
@@ -497,7 +504,7 @@ sur une grosse table.
|
|
|
497
504
|
## ⚙️ Ranger le métier dans un service
|
|
498
505
|
|
|
499
506
|
Dès que la logique dépasse l'appel direct, elle quitte le controller. `AbstractCrudService`
|
|
500
|
-
(`AbstractCrudService.ts
|
|
507
|
+
(`AbstractCrudService.ts#AbstractCrudService`) donne le socle : les lectures sont une **délégation pure** (aucun
|
|
501
508
|
surcoût sur le chemin chaud), les mutations sont encadrées par des points d'extension puis un
|
|
502
509
|
événement de cycle de vie.
|
|
503
510
|
|
|
@@ -516,7 +523,7 @@ export class PostService extends AbstractCrudService<PostRow> {
|
|
|
516
523
|
|
|
517
524
|
L'intérêt est de n'avoir **qu'un seul endroit** à modifier quand la règle change : la même méthode
|
|
518
525
|
sert la route REST, l'appel WebSocket, un résolveur GraphQL et une commande CLI. Pour toute liste
|
|
519
|
-
d'administration, la primitive est `AbstractCrudService.findPage()` (`AbstractCrudService.ts
|
|
526
|
+
d'administration, la primitive est `AbstractCrudService.findPage()` (`AbstractCrudService.ts#AbstractCrudService.findPage`)
|
|
520
527
|
— elle ne charge qu'une page, quelle que soit la taille de la table.
|
|
521
528
|
|
|
522
529
|
> [!WARNING]
|
|
@@ -526,20 +533,20 @@ d'administration, la primitive est `AbstractCrudService.findPage()` (`AbstractCr
|
|
|
526
533
|
|
|
527
534
|
## ⚠️ Pièges
|
|
528
535
|
|
|
529
|
-
| Symptôme | Cause | Correction
|
|
530
|
-
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
531
|
-
| `posts.update is not a function` | la méthode `update()` n'existe pas dans le contrat | `updateOne` pour une ligne (`IRepository.ts
|
|
532
|
-
| « no entity registered under "Post" » au premier appel | le fichier d'entité est importé, mais `defineEntity()` est **sans effet de bord** | ajouter l'entité à `@entities([...])` sur le module (`entitiesDecorator.ts
|
|
533
|
-
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec l'ouverture du connecteur | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts
|
|
534
|
-
| Une colonne ajoutée au schéma reste absente de la table | colonne **obligatoire** : le rattrapage de développement n'ajoute que les colonnes facultatives | `nodefony orm:reset` en développement, ou `orm:generate` puis `orm:migrate`
|
|
535
|
-
| Un `DEFAULT` SQL déclaré n'apparaît pas | le DDL dérivé ne l'émet pas | poser le défaut côté JavaScript (`$defaultFn`)
|
|
536
|
-
| Un filtre « champ vide » ne remonte jamais rien | `colonne = NULL` est toujours faux en SQL | `{ champ: { $null: true } }` ou la valeur nue `{ champ: null }` (`IRepository.ts
|
|
537
|
-
| `UnknownCriteriaField` sur un champ qui « existe pourtant » | faute de frappe, ou champ calculé absent du schéma | lire les champs connus dans le message ; pour du natif, passer par `getNativeConnection()`
|
|
538
|
-
| `connector: "sqlite"` ne trouve aucune connexion | `connector` nomme une **connexion**, pas un moteur | mettre la clé de `connectors` (`"default"`) ; le moteur est le `dialect` de la config
|
|
539
|
-
| « entity "Post" exists on multiple connectors … specify one » | la même entité est inscrite sur deux connexions | préciser laquelle : `entityRegistry.get("Post", "analytics")` (`EntityRegistry.ts
|
|
540
|
-
| « no ORM registered under "default" » au premier appel | le repository est demandé avant l'ouverture du connecteur, ou le driver n'est pas dans `modules` | construire le service **au premier usage**, pas au chargement (`ormRegistry.get()`, `OrmRegistry.ts
|
|
541
|
-
| Une entité déclarée dans un module reste invisible | le module embarque sa **propre copie** du registre (singleton dédoublé) | externaliser `@nodefony/orm-core` dans le `rolldown.config.ts` du module
|
|
542
|
-
| Une transaction ne couvre pas les deux bases | une transaction porte sur **un seul** connecteur (2PC non garanti) | rassembler les écritures atomiques sur une seule connexion
|
|
536
|
+
| Symptôme | Cause | Correction |
|
|
537
|
+
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
538
|
+
| `posts.update is not a function` | la méthode `update()` n'existe pas dans le contrat | `updateOne` pour une ligne (`IRepository.ts#IRepository.updateOne`), `updateMany` pour un lot (`IRepository.ts#IRepository.updateMany`) |
|
|
539
|
+
| « no entity registered under "Post" » au premier appel | le fichier d'entité est importé, mais `defineEntity()` est **sans effet de bord** | ajouter l'entité à `@entities([...])` sur le module (`entitiesDecorator.ts#entities`) |
|
|
540
|
+
| La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec l'ouverture du connecteur | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts#entities`) |
|
|
541
|
+
| Une colonne ajoutée au schéma reste absente de la table | colonne **obligatoire** : le rattrapage de développement n'ajoute que les colonnes facultatives | `nodefony orm:reset` en développement, ou `orm:generate` puis `orm:migrate` |
|
|
542
|
+
| Un `DEFAULT` SQL déclaré n'apparaît pas | le DDL dérivé ne l'émet pas | poser le défaut côté JavaScript (`$defaultFn`) |
|
|
543
|
+
| Un filtre « champ vide » ne remonte jamais rien | `colonne = NULL` est toujours faux en SQL | `{ champ: { $null: true } }` ou la valeur nue `{ champ: null }` (`IRepository.ts#FieldOperators.$null`) |
|
|
544
|
+
| `UnknownCriteriaField` sur un champ qui « existe pourtant » | faute de frappe, ou champ calculé absent du schéma | lire les champs connus dans le message ; pour du natif, passer par `getNativeConnection()` |
|
|
545
|
+
| `connector: "sqlite"` ne trouve aucune connexion | `connector` nomme une **connexion**, pas un moteur | mettre la clé de `connectors` (`"default"`) ; le moteur est le `dialect` de la config |
|
|
546
|
+
| « entity "Post" exists on multiple connectors … specify one » | la même entité est inscrite sur deux connexions | préciser laquelle : `entityRegistry.get("Post", "analytics")` (`EntityRegistry.ts#EntityRegistry.get`) |
|
|
547
|
+
| « no ORM registered under "default" » au premier appel | le repository est demandé avant l'ouverture du connecteur, ou le driver n'est pas dans `modules` | construire le service **au premier usage**, pas au chargement (`ormRegistry.get()`, `OrmRegistry.ts#OrmRegistry.get`) |
|
|
548
|
+
| Une entité déclarée dans un module reste invisible | le module embarque sa **propre copie** du registre (singleton dédoublé) | externaliser `@nodefony/orm-core` dans le `rolldown.config.ts` du module |
|
|
549
|
+
| Une transaction ne couvre pas les deux bases | une transaction porte sur **un seul** connecteur (2PC non garanti) | rassembler les écritures atomiques sur une seule connexion |
|
|
543
550
|
|
|
544
551
|
## 🧪 Tests & couverture
|
|
545
552
|
|
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.4",
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"private": false,
|
|
27
27
|
"engines": {
|
|
28
|
-
"node": ">=24.
|
|
28
|
+
"node": ">=24.14.0"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"nodefony",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"esm"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"nodefony": "^10.0.0-beta.
|
|
40
|
+
"nodefony": "^10.0.0-beta.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "26.6.5",
|
|
44
|
-
"nodefony": "^10.0.0-beta.
|
|
45
|
-
"vitest": "5.0.
|
|
44
|
+
"nodefony": "^10.0.0-beta.4",
|
|
45
|
+
"vitest": "5.0.5"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|
|
48
48
|
"type": "git",
|