@nodefony/orm-core 10.0.0-beta.4 → 10.0.0-beta.5

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/docs/index.md CHANGED
@@ -734,19 +734,20 @@ deux, et pas de course).
734
734
 
735
735
  ## ⚠️ Pièges
736
736
 
737
- | Symptôme | Cause | Correction |
738
- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
739
- | « no entity registered under "X" » au premier appel | l'entité n'a jamais été inscrite (fichier importé mais `defineEntity` est sans effet de bord) | l'ajouter à `@entities([...])` sur le module (`entitiesDecorator.ts#entities`) |
740
- | La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec `connect()` | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts#entities`) |
741
- | « entity "User" exists on multiple connectors … specify one » | la même entité vit sur plusieurs connecteurs | préciser le connecteur : `entityRegistry.get("User", "analytics")` (`EntityRegistry.ts#EntityRegistry.get`) |
742
- | 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`) |
743
- | `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()` |
744
- | `updateOne` rend `null` alors que la ligne a bien changé | ancien réflexe `UPDATE` + relecture (le critère porte sur le champ modifié) | utiliser `updateOne`, atomique par construction (`IRepository.ts#IRepository.updateOne`) |
745
- | Un `upsert` écrase une valeur qui devait progresser | le `DO UPDATE` est inconditionnel (contrainte MySQL) | poser la condition **dans** la valeur : `{ seuil: { $max: v } }` (`IRepository.ts#UpdateOperators`) |
746
- | Un objet de critère est pris pour une égalité (colonne JSON) | comportement **voulu** : une valeur n'est un filtre que si **toutes** ses clés sont des opérateurs | c'est la protection ; pour filtrer dedans, passer au natif (`criteria.ts#isFieldOperators`) |
747
- | `onOrmReady` ne part plus après un ajout dans l'adapter | `connect()` a été surchargé | surcharger `onConnect()` (`Orm.ts#Orm.onConnect`), jamais `connect()` |
748
- | 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 |
749
- | Rien dans `flow` alors que la base travaille | la sonde est éteinte hors développement, ou le driver n'a pas de tap | `NF_ORM_FLOW=1` (`ormWiring.ts#resolveOrmFlowEnabled`) ; les deux drivers officiels ont un tap |
737
+ | Symptôme | Cause | Correction |
738
+ | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
739
+ | « no entity registered under "X" » au premier appel | l'entité n'a jamais été inscrite (fichier importé mais `defineEntity` est sans effet de bord) | l'ajouter à `@entities([...])` sur le module (`entitiesDecorator.ts#entities`) |
740
+ | La table n'existe pas alors que l'entité est déclarée | inscription faite à `onBoot` → course avec `connect()` | inscrire à `onRegister` — c'est ce que fait `entities()` (`entitiesDecorator.ts#entities`) |
741
+ | « entity "User" exists on multiple connectors … specify one » | la même entité vit sur plusieurs connecteurs | préciser le connecteur : `entityRegistry.get("User", "analytics")` (`EntityRegistry.ts#EntityRegistry.get`) |
742
+ | 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`) |
743
+ | `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()` |
744
+ | `updateOne` rend `null` alors que la ligne a bien changé | ancien réflexe `UPDATE` + relecture (le critère porte sur le champ modifié) | utiliser `updateOne`, atomique par construction (`IRepository.ts#IRepository.updateOne`) |
745
+ | Un `upsert` écrase une valeur qui devait progresser | le `DO UPDATE` est inconditionnel (contrainte MySQL) | poser la condition **dans** la valeur : `{ seuil: { $max: v } }` (`IRepository.ts#UpdateOperators`) |
746
+ | Un objet de critère est pris pour une égalité (colonne JSON) | comportement **voulu** : une valeur n'est un filtre que si **toutes** ses clés sont des opérateurs | c'est la protection ; pour filtrer dedans, passer au natif (`criteria.ts#isFieldOperators`) |
747
+ | `onOrmReady` ne part plus après un ajout dans l'adapter | `connect()` a été surchargé | surcharger `onConnect()` (`Orm.ts#Orm.onConnect`), jamais `connect()` |
748
+ | 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 |
749
+ | Rien dans `flow` alors que la base travaille | la sonde est éteinte hors développement, ou le driver n'a pas de tap | `NF_ORM_FLOW=1` (`ormWiring.ts#resolveOrmFlowEnabled`) ; les deux drivers officiels ont un tap |
750
+ | Un champ vaut `null` dans la réponse alors que la colonne est remplie | repository maison : la fonction qui transforme une ligne en entité ne recopie pas cette colonne — rien ne lève, le champ optionnel reste vide | faire porter au mapping **chaque** champ exposé, et un test qui écrit une ligne complète puis compare tous les champs relus (`IRepository.ts#IRepository`) |
750
751
 
751
752
  ## 🧪 Tests & couverture
752
753
 
@@ -23,6 +23,26 @@ source: "src/packages/@nodefony/orm-core/docs/tutorial-entity.md"
23
23
 
24
24
  📍 [Documentation](../../../../../docs/index.md) › [ORM — le socle](index.md) › **Créer une entité**
25
25
 
26
+ ## ⚡ Le geste — une commande
27
+
28
+ Dans une application, une entité ne s'écrit pas à la main : `create entity` pose tout ce que cette
29
+ page construit pas à pas, câblé et testé.
30
+
31
+ ```bash
32
+ npx nodefony create entity Post title:string content:text views:int --route /api/posts
33
+ ```
34
+
35
+ Elle écrit la table (`nodefony/entity/Post.ts`), ses schémas de validation, un service CRUD, un
36
+ contrôleur REST dont les lectures répondent aussi par la socket, un test unitaire et un test e2e,
37
+ puis inscrit l'entité et le contrôleur dans `index.ts`. Si l'application versionne ses migrations
38
+ (toute application complète), elle écrit puis applique celle de la table à la base de
39
+ développement ; sinon, la table naît au prochain démarrage. Si la base ne répond pas, sa sortie donne la commande à
40
+ relancer (`npx nodefony orm:generate --name posts --apply`). Les options :
41
+ [le raccourci](#le-raccourci--nodefony-create-entity).
42
+
43
+ La suite de la page construit la même entité à la main : c'est ce qui permet de comprendre ce que
44
+ la commande écrit, et de le retoucher.
45
+
26
46
  ## 🧠 Schéma général
27
47
 
28
48
  Trois objets s'emboîtent, toujours dans le même ordre. Le tutoriel les parcourt de gauche à
@@ -337,11 +357,14 @@ champs se déclarent en positionnels, façon Rails :
337
357
  nodefony create entity Post title:string content:text views:int
338
358
  # --id uuid7|uuid4|serial --soft-delete --no-timestamps
339
359
  # --module <nom> --no-controller --connector <nom>
360
+ # --no-migrate (n'écrit pas la migration : la sortie la conseille)
340
361
  ```
341
362
 
342
363
  Elle écrit le fichier d'entité (`nodefony/entity/Post.ts`), les schémas de validation, un service
343
364
  CRUD, un controller REST + WebSocket et les tests — puis **câble elle-même** `@entities([...])` et
344
- `@controllers([...])`. Le schéma vit alors dans son propre fichier, et le point d'entrée ne garde
365
+ `@controllers([...])`, et migre la table neuve quand l'application versionne son schéma — mais seulement si la migration ne touche
366
+ qu'elle : une autre entité modifiée en attente laisse le fichier écrit, NON appliqué, et la sortie
367
+ nomme les tables en trop. Le schéma vit alors dans son propre fichier, et le point d'entrée ne garde
345
368
  que l'import plus la ligne du décorateur.
346
369
 
347
370
  > [!WARNING]
@@ -379,6 +402,12 @@ apportent, jamais sur leur nom.
379
402
  > **nombre** de lignes touchées. Un verbe unique masquerait cette différence de garantie, qui est
380
403
  > précisément ce qu'on veut choisir en connaissance de cause.
381
404
 
405
+ **Une clé qui vaut `undefined` vaut une clé absente.** Les écritures du service CRUD acceptent
406
+ `PartialInput<T>` (`types/PartialInput.ts#PartialInput`) et retirent ces clés avant la persistance
407
+ (`AbstractCrudService.updateOne()`, `AbstractCrudService.ts#AbstractCrudService.updateOne`) : un champ
408
+ facultatif d'un schéma Zod (`.optional()`, `.partial()`) qui arrive `undefined` ne touche pas la
409
+ colonne. Pour la **vider**, écrire `null`.
410
+
382
411
  Quelques usages, un par garantie :
383
412
 
384
413
  ```ts ignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/orm-core",
3
- "version": "10.0.0-beta.4",
3
+ "version": "10.0.0-beta.5",
4
4
  "description": "Un contrat de dépôt de données pour Nodefony, plusieurs moteurs : interfaces, registre et classes de base pour le support multi-ORM",
5
5
  "contributors": [],
6
6
  "type": "module",
@@ -37,11 +37,11 @@
37
37
  "esm"
38
38
  ],
39
39
  "peerDependencies": {
40
- "nodefony": "^10.0.0-beta.4"
40
+ "nodefony": "^10.0.0-beta.5"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "26.6.5",
44
- "nodefony": "^10.0.0-beta.4",
44
+ "nodefony": "^10.0.0-beta.5",
45
45
  "vitest": "5.0.5"
46
46
  },
47
47
  "repository": {