@nodefony/framework 10.0.0-alpha.8 → 10.0.0-beta.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 (49) hide show
  1. package/README.md +4 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/index.js +7 -6
  5. package/dist/nodefony/controller/AdminApiController.js +4 -3
  6. package/dist/nodefony/controller/ApiKeyController.js +1 -1
  7. package/dist/nodefony/controller/OAuth2Controller.js +11 -6
  8. package/dist/nodefony/controller/SessionAuthController.js +3 -3
  9. package/dist/nodefony/controller/TokenAuthController.js +2 -2
  10. package/dist/nodefony/controller/TotpController.js +5 -4
  11. package/dist/nodefony/controller/WebAuthnController.js +4 -4
  12. package/dist/nodefony/decorators/routerDecorators.js +41 -35
  13. package/dist/nodefony/service/AdminBroker.js +2 -2
  14. package/dist/nodefony/service/IdempotencyStore.js +4 -5
  15. package/dist/nodefony/service/router.js +75 -25
  16. package/dist/nodefony/src/Controller.js +184 -78
  17. package/dist/nodefony/src/FrameworkAdminApi.js +19 -6
  18. package/dist/nodefony/src/KernelAdminApi.js +60 -55
  19. package/dist/nodefony/src/PlaygroundAdminApi.js +3 -3
  20. package/dist/nodefony/src/RedisIdempotencyStore.js +2 -2
  21. package/dist/nodefony/src/Resolver.js +219 -63
  22. package/dist/nodefony/src/Route.js +38 -38
  23. package/dist/nodefony/src/SyslogAdminApi.js +4 -4
  24. package/dist/nodefony/src/docsReader.js +22 -18
  25. package/dist/nodefony/src/scopeCatalog.js +1 -1
  26. package/dist/nodefony/src/singletonGuard.js +76 -0
  27. package/dist/types/nodefony/controller/BenchController.d.ts +2 -2
  28. package/dist/types/nodefony/controller/OAuth2Controller.d.ts +3 -3
  29. package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +1 -1
  30. package/dist/types/nodefony/controller/SessionAuthController.d.ts +2 -1
  31. package/dist/types/nodefony/decorators/routerDecorators.d.ts +49 -43
  32. package/dist/types/nodefony/interfaces/IController.d.ts +9 -7
  33. package/dist/types/nodefony/interfaces/IResolver.d.ts +10 -10
  34. package/dist/types/nodefony/interfaces/IRoute.d.ts +13 -13
  35. package/dist/types/nodefony/service/AdminBroker.d.ts +3 -3
  36. package/dist/types/nodefony/service/router.d.ts +33 -7
  37. package/dist/types/nodefony/src/Controller.d.ts +97 -24
  38. package/dist/types/nodefony/src/Resolver.d.ts +154 -15
  39. package/dist/types/nodefony/src/ResourceController.d.ts +6 -6
  40. package/dist/types/nodefony/src/Route.d.ts +24 -22
  41. package/dist/types/nodefony/src/docsReader.d.ts +3 -3
  42. package/dist/types/nodefony/src/singletonGuard.d.ts +53 -0
  43. package/docs/admin.md +26 -26
  44. package/docs/controller.md +139 -121
  45. package/docs/decorateurs.md +108 -105
  46. package/docs/idempotence.md +78 -71
  47. package/docs/routing.md +57 -51
  48. package/docs/templates.md +17 -17
  49. package/package.json +12 -12
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Filets d'un contrôleur **singleton** — le défaut : UNE instance par classe,
3
+ * partagée par toutes les requêtes, concurrentes comprises.
4
+ *
5
+ * Un singleton ne peut porter aucun état de requête sur `this` : la valeur
6
+ * écrite par une requête serait lue par la suivante — une fuite de données
7
+ * entre utilisateurs, sans la moindre erreur. Deux filets la rendent visible :
8
+ *
9
+ * - les accesseurs d'état par requête de `Controller` (`query`, `request`,
10
+ * `context`…) refusent l'écriture sur un singleton, dans tous les
11
+ * environnements (coût nul : ils ne sont jamais écrits sur le chemin chaud) ;
12
+ * - en développement seulement, {@link guardSingletonState} fait lever toute
13
+ * écriture sur un champ de l'instance, une fois sa construction et son
14
+ * `initialize()` terminés.
15
+ *
16
+ * @module
17
+ */
18
+ /**
19
+ * `true` si l'environnement du kernel est celui du développement.
20
+ *
21
+ * Une seule définition pour tout le paquet : la sonde CSP de `Controller` et le
22
+ * filet des singletons s'allument sur le même critère.
23
+ *
24
+ * @param environment - `kernel.environment`, tel quel.
25
+ * @returns `true` en développement (`"development"` ou son alias `"dev"`).
26
+ */
27
+ export declare function isDevelopment(environment: unknown): boolean;
28
+ /**
29
+ * Message d'une écriture refusée sur un contrôleur singleton : il nomme la
30
+ * classe, le membre, la raison du refus et les deux remèdes.
31
+ *
32
+ * @param controller - nom de la classe du contrôleur.
33
+ * @param member - membre écrit (`count`, `query`…).
34
+ * @returns le message, en une phrase par idée.
35
+ */
36
+ export declare function singletonWriteMessage(controller: string, member: string): string;
37
+ /**
38
+ * En développement, fait lever toute écriture sur un champ d'un contrôleur
39
+ * singleton, avec un message qui nomme le contrôleur, le champ et le remède.
40
+ *
41
+ * Chaque propriété PROPRE inscriptible devient un accesseur : la lecture rend
42
+ * la valeur posée à la construction ou dans `initialize()`, l'écriture lève.
43
+ * L'instance est ensuite rendue non extensible, pour qu'un champ non déclaré
44
+ * ne puisse pas être ajouté en cours de requête.
45
+ *
46
+ * Ce que le filet n'attrape PAS, et que seule la relecture garde : les champs
47
+ * `#privés` (ce ne sont pas des propriétés), et les mutations internes d'un
48
+ * objet tenu par un champ (`this.cache.set(…)`, `this.list.push(…)`).
49
+ *
50
+ * @param controller - l'instance, construite et initialisée.
51
+ * @param name - nom de sa classe, pour le message.
52
+ */
53
+ export declare function guardSingletonState(controller: object, name: string): void;
package/docs/admin.md CHANGED
@@ -264,8 +264,8 @@ sequenceDiagram
264
264
 
265
265
  | # | Étape | Où |
266
266
  | --- | ----------------------------------------- | ---------------------------------------------------------- |
267
- | 1 | Le producteur s'enregistre | `AdminBroker.register()` (`AdminBroker.ts:45`) |
268
- | 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts:104`) |
267
+ | 1 | Le producteur s'enregistre | `AdminBroker.register()` (`AdminBroker.ts:53`) |
268
+ | 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts:112`) |
269
269
  | 3 | Une route par endpoint (nom déterministe) | `Router.createRoute()` (`AdminBroker.ts:124`) |
270
270
  | 4 | Le controller pont estampillé une fois | `Router.setController()` idempotent (`AdminBroker.ts:146`) |
271
271
  | 5 | Dispatch : lookup de la route | `AdminBroker.resolve()` (`AdminApiController.ts:94`) |
@@ -275,15 +275,15 @@ sequenceDiagram
275
275
  Points de conception saillants :
276
276
 
277
277
  - **Le montage FIGE les routes.** Après `mountAll()`, tout `register()` lève
278
- (`AdminBroker.ts:46`) : on ne monte pas une route à chaud (Zero surprise en prod). Le broker garde
278
+ (`AdminBroker.ts:53`) : on ne monte pas une route à chaud (Zero surprise en prod). Le broker garde
279
279
  la trace via son drapeau `mounted`.
280
- - **Le nom de route est déterministe** : `admin.<ns>.<method>.<path>` (`AdminBroker.ts:114`) — c'est
280
+ - **Le nom de route est déterministe** : `admin.<ns>.<method>.<path>` (`AdminBroker.ts:123`) — c'est
281
281
  la clé du lookup O(1) que le pont refait à chaque requête.
282
282
  - **`Router.setController` n'est appelé qu'une fois** par process (`AdminBroker.ts:146`) : il pose
283
283
  une propriété non réinscriptible sur le prototype ; une garde `hasOwnProperty` rend l'appel
284
284
  idempotent (multi-broker en test, re-boot).
285
285
  - **Le catalogue se construit à la volée** depuis `AdminBroker.list()` + `AdminBroker.routes()`
286
- (`AdminBroker.ts:100`) — jamais un état retenu.
286
+ (`AdminBroker.ts:108`) — jamais un état retenu.
287
287
 
288
288
  > [!IMPORTANT]
289
289
  > Convention de route **figée** : le data plane est toujours en **≥ 3 segments**
@@ -310,8 +310,8 @@ La décision est **fail-closed** : un authentifié **sans** le rôle requis —
310
310
  laissez-passer.
311
311
 
312
312
  - **Rôle par défaut** : sans `role` explicite, un endpoint exige `ROLE_NODEFONY_ADMIN`
313
- (`AdminBroker.ts:112` ; défaut du champ `IAdminEndpoint.role`, `IAdminApi.ts:152`).
314
- - **Endpoint public** : `public: true` (`IAdminApi.ts:152`) → le RBAC du broker est court-circuité
313
+ (`AdminBroker.ts:112` ; défaut du champ `IAdminEndpoint.role`, `IAdminApi.ts:166`).
314
+ - **Endpoint public** : `public: true` (`IAdminApi.ts:175`) → le RBAC du broker est court-circuité
315
315
  (`role === ""`, `adminRbac.ts:26`). À réserver aux sondes cloud-native (liveness/readiness) et à
316
316
  placer hors d'une zone fermée — sinon le firewall verrouille en amont. Exemple réel :
317
317
  `GET /nodefony/kernel/api/livez` (`KernelAdminApi.ts:615`), sorti de `nodefony-admin` par la zone
@@ -334,9 +334,9 @@ montée avec `[method, "WEBSOCKET"]`. Elle devient donc invocable par le pont WS
334
334
  expose son statut.
335
335
 
336
336
  Les **mutations** sont pontables par socket. La sécurité d'écriture repose alors sur l'**idempotence**
337
- (`idempotencyGate()`, `AdminApiController.ts:158`) : la clé `Idempotency-Key` est **obligatoire en
337
+ (`idempotencyGate()`, `AdminApiController.ts:131`) : la clé `Idempotency-Key` est **obligatoire en
338
338
  WS** (une socket reconnecte et rejoue), **optionnelle en HTTP** (`required: false`,
339
- `AdminApiController.ts:184`). Un `GET` n'est jamais idempotenté (`AdminApiController.ts:149`) ; la
339
+ `AdminApiController.ts:149`). Un `GET` n'est jamais idempotenté (`AdminApiController.ts:135`) ; la
340
340
  porte est évaluée **après** le RBAC (un 403 ne consomme aucune entrée). Le helper est le **même** que
341
341
  le seam `@Idempotent` des controllers userland — voir [Idempotence](idempotence.md).
342
342
 
@@ -364,18 +364,18 @@ sont enregistrés au `onKernelReady` du framework :
364
364
 
365
365
  | Namespace | Producteur | Rôle |
366
366
  | ----------- | ----------------------------------------------------- | ------------------------------------------------- |
367
- | `kernel` | `createKernelAdminApi` (`KernelAdminApi.ts:468`) | modules, process, uptime, `livez` |
368
- | `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts:40`) | dump du Router + **catalogue** + Playground (dev) |
367
+ | `kernel` | `createKernelAdminApi` (`KernelAdminApi.ts:582`) | modules, process, uptime, `livez` |
368
+ | `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts:86`) | dump du Router + **catalogue** + Playground (dev) |
369
369
  | `syslog` | `createSyslogAdminApi` (`SyslogAdminApi.ts:95`) | viewer de logs (dev) |
370
370
 
371
371
  Les modules externes s'enregistrent depuis leur propre `onKernelBoot` :
372
372
 
373
- | Namespace | Module | Enregistrement |
374
- | ---------- | -------------------- | --------------------------------------------------- |
375
- | `http` | `@nodefony/http` | `createHttpAdminApi` (`http/index.ts:121`) |
376
- | `security` | `@nodefony/security` | `registerSecurityAdminApi` (`security/index.ts:94`) |
377
- | `user` | `@nodefony/user` | `adminNamespace` (`UserAdminApi.ts:879`) |
378
- | `orm` | `@nodefony/orm-core` | `adminNamespace` (`OrmAdminApi.ts:537`) |
373
+ | Namespace | Module | Enregistrement |
374
+ | ---------- | -------------------- | ---------------------------------------------------- |
375
+ | `http` | `@nodefony/http` | `createHttpAdminApi` (`http/index.ts:121`) |
376
+ | `security` | `@nodefony/security` | `registerSecurityAdminApi` (`security/index.ts:104`) |
377
+ | `user` | `@nodefony/user` | `adminNamespace` (`UserAdminApi.ts:945`) |
378
+ | `orm` | `@nodefony/orm-core` | `adminNamespace` (`OrmAdminApi.ts:709`) |
379
379
 
380
380
  Pour le détail de chacun, se reporter à la doc de son module — le broker reste agnostique de leur
381
381
  contenu.
@@ -389,14 +389,14 @@ recopiées ici (elles s'y périmeraient).
389
389
 
390
390
  | Membre (`IAdminBroker`) | Rôle | Ancre |
391
391
  | ------------------------ | -------------------------------------------------------- | -------------------- |
392
- | `register(api)` | Enregistre un producteur (throw si namespace pris/monté) | `AdminBroker.ts:45` |
393
- | `unregister(ns)` | Retire un producteur (et ses routes si montées) | `AdminBroker.ts:61` |
394
- | `has(ns)` / `getApi(ns)` | Interrogation du registre | `AdminBroker.ts:79` |
395
- | `list()` | Producteurs enregistrés (immuable) | `AdminBroker.ts:87` |
396
- | `resolvePath(ns, path)` | Chemin absolu d'un endpoint sans le monter | `AdminBroker.ts:91` |
397
- | `mountAll()` | Monte toutes les routes (idempotent) | `AdminBroker.ts:104` |
398
- | `resolve(routeName)` | Lookup O(1) d'une route montée (utilisé par le pont) | `AdminBroker.ts:96` |
399
- | `routes()` | Introspection des routes montées (source du catalogue) | `AdminBroker.ts:100` |
392
+ | `register(api)` | Enregistre un producteur (throw si namespace pris/monté) | `AdminBroker.ts:53` |
393
+ | `unregister(ns)` | Retire un producteur (et ses routes si montées) | `AdminBroker.ts:69` |
394
+ | `has(ns)` / `getApi(ns)` | Interrogation du registre | `AdminBroker.ts:91` |
395
+ | `list()` | Producteurs enregistrés (immuable) | `AdminBroker.ts:95` |
396
+ | `resolvePath(ns, path)` | Chemin absolu d'un endpoint sans le monter | `AdminBroker.ts:99` |
397
+ | `mountAll()` | Monte toutes les routes (idempotent) | `AdminBroker.ts:112` |
398
+ | `resolve(routeName)` | Lookup O(1) d'une route montée (utilisé par le pont) | `AdminBroker.ts:104` |
399
+ | `routes()` | Introspection des routes montées (source du catalogue) | `AdminBroker.ts:108` |
400
400
 
401
401
  ## 📡 Observabilité — Studio
402
402
 
@@ -416,7 +416,7 @@ recopiées ici (elles s'y périmeraient).
416
416
 
417
417
  | Symptôme | Cause (dans le code) | Correction |
418
418
  | ------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
419
- | `register()` throw « routes figées » | Appel **après** `mountAll()` (`AdminBroker.ts:46`) | Enregistrer au `onKernelBoot`, pas plus tard |
419
+ | `register()` throw « routes figées » | Appel **après** `mountAll()` (`AdminBroker.ts:112`) | Enregistrer au `onKernelBoot`, pas plus tard |
420
420
  | `register()` throw « namespace déjà enregistré » | Deux producteurs sur le même `adminNamespace` (`AdminBroker.ts:51`) | Namespace unique ; garder `register()` idempotent (`has(ns)` avant) |
421
421
  | 401 sur toute route `/nodefony/<ns>/api/*` | Zone `nodefony-admin` : pas de session BFF (`config.ts:141`) | S'authentifier (login BFF) ; pour une sonde publique → `public: true` + zone anonyme |
422
422
  | 403 alors qu'on est connecté | Rôle manquant, `isAdminGranted` fail-closed (`adminRbac.ts:24`) | Doter le compte du rôle requis (défaut `ROLE_NODEFONY_ADMIN`) |