@nodefony/framework 10.0.0-beta.2 → 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.
Files changed (76) hide show
  1. package/README.md +4 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +15 -20
  4. package/dist/nodefony/config/config.js +1 -2
  5. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  6. package/dist/nodefony/controller/AdminApiController.js +45 -11
  7. package/dist/nodefony/controller/ApiKeyController.js +18 -10
  8. package/dist/nodefony/controller/BenchController.js +1 -2
  9. package/dist/nodefony/controller/IssuerMetadataController.js +1 -2
  10. package/dist/nodefony/controller/LoginPageController.js +117 -0
  11. package/dist/nodefony/controller/OAuth2Controller.js +133 -17
  12. package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
  13. package/dist/nodefony/controller/SessionAuthController.js +50 -13
  14. package/dist/nodefony/controller/TokenAuthController.js +14 -8
  15. package/dist/nodefony/controller/TotpController.js +7 -8
  16. package/dist/nodefony/controller/WebAuthnController.js +28 -12
  17. package/dist/nodefony/controller/oauthAuthority.js +1 -2
  18. package/dist/nodefony/decorators/routerDecorators.js +268 -65
  19. package/dist/nodefony/service/AdminBroker.js +4 -6
  20. package/dist/nodefony/service/Eta.js +1 -2
  21. package/dist/nodefony/service/IdempotencyStore.js +4 -6
  22. package/dist/nodefony/service/router.js +97 -34
  23. package/dist/nodefony/src/Controller.js +71 -43
  24. package/dist/nodefony/src/FrameworkAdminApi.js +33 -11
  25. package/dist/nodefony/src/KernelAdminApi.js +82 -18
  26. package/dist/nodefony/src/PlaygroundAdminApi.js +72 -3
  27. package/dist/nodefony/src/RedisIdempotencyStore.js +46 -4
  28. package/dist/nodefony/src/Resolver.js +105 -36
  29. package/dist/nodefony/src/ResourceController.js +2 -3
  30. package/dist/nodefony/src/Route.js +54 -8
  31. package/dist/nodefony/src/SecurityApiController.js +23 -0
  32. package/dist/nodefony/src/SyslogAdminApi.js +1 -2
  33. package/dist/nodefony/src/Template.js +1 -2
  34. package/dist/nodefony/src/configMutation.js +1 -2
  35. package/dist/nodefony/src/docsReader.js +39 -23
  36. package/dist/nodefony/src/idempotency.js +14 -22
  37. package/dist/nodefony/src/idempotencyGc.js +1 -2
  38. package/dist/nodefony/src/idempotencyStoreRegistry.js +1 -2
  39. package/dist/nodefony/src/loginPage.js +430 -0
  40. package/dist/nodefony/src/requireAuthentication.js +35 -0
  41. package/dist/nodefony/src/routeSchema.js +81 -0
  42. package/dist/nodefony/src/scopeCatalog.js +1 -2
  43. package/dist/nodefony/src/singletonGuard.js +1 -2
  44. package/dist/nodefony/src/syslogFilters.js +1 -2
  45. package/dist/types/index.d.ts +8 -12
  46. package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
  47. package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -2
  48. package/dist/types/nodefony/controller/LoginPageController.d.ts +32 -0
  49. package/dist/types/nodefony/controller/OAuth2Controller.d.ts +67 -8
  50. package/dist/types/nodefony/controller/SessionAuthController.d.ts +46 -8
  51. package/dist/types/nodefony/controller/TokenAuthController.d.ts +6 -4
  52. package/dist/types/nodefony/controller/TotpController.d.ts +2 -2
  53. package/dist/types/nodefony/controller/WebAuthnController.d.ts +12 -4
  54. package/dist/types/nodefony/decorators/routerDecorators.d.ts +221 -35
  55. package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
  56. package/dist/types/nodefony/interfaces/IRoute.d.ts +1 -2
  57. package/dist/types/nodefony/service/router.d.ts +40 -1
  58. package/dist/types/nodefony/src/Controller.d.ts +54 -17
  59. package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
  60. package/dist/types/nodefony/src/Resolver.d.ts +44 -1
  61. package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
  62. package/dist/types/nodefony/src/Route.d.ts +21 -1
  63. package/dist/types/nodefony/src/SecurityApiController.d.ts +17 -0
  64. package/dist/types/nodefony/src/docsReader.d.ts +10 -6
  65. package/dist/types/nodefony/src/idempotency.d.ts +12 -10
  66. package/dist/types/nodefony/src/loginPage.d.ts +144 -0
  67. package/dist/types/nodefony/src/requireAuthentication.d.ts +29 -0
  68. package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
  69. package/docs/admin.md +83 -78
  70. package/docs/controller.md +112 -111
  71. package/docs/decorateurs.md +212 -163
  72. package/docs/idempotence.md +137 -135
  73. package/docs/routing.md +96 -95
  74. package/docs/templates.md +55 -55
  75. package/package.json +10 -13
  76. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
package/docs/admin.md CHANGED
@@ -61,9 +61,9 @@ flowchart TD
61
61
  Trois idées à retenir :
62
62
 
63
63
  1. **Le producteur ne connaît pas le transport** — un handler lit un `IAdminRequest`
64
- (`IAdminApi.ts:33`) et rend du JSON. Il ne touche jamais au socket ni à la `Response`.
64
+ (`IAdminApi.ts#IAdminRequest`) et rend du JSON. Il ne touche jamais au socket ni à la `Response`.
65
65
  2. **Un seul controller pont** — toutes les routes admin pointent vers
66
- `AdminApiController.dispatch()` (`AdminApiController.ts:60`). Pas de génération dynamique de
66
+ `AdminApiController.dispatch()` (`AdminApiController.ts#AdminApiController.dispatch`). Pas de génération dynamique de
67
67
  classes ; chaque route reste une vraie `Route` (404/405 du Router intacts).
68
68
  3. **Le broker possède le Router, pas le kernel** — c'est pourquoi il vit dans `@nodefony/framework`,
69
69
  niveau qui monte les routes, alors que le contrat producteur vit dans le core.
@@ -110,19 +110,19 @@ le broker la monte.
110
110
 
111
111
  Nodefony sépare **deux rôles** par inversion de dépendance :
112
112
 
113
- - **Producteur** (`IAdminApi`, dans le core `IAdminApi.ts:212`) : un module dit _quoi_ il expose —
113
+ - **Producteur** (`IAdminApi`, dans le core `IAdminApi.ts#IAdminApi`) : un module dit _quoi_ il expose —
114
114
  son `adminNamespace` et ses `adminEndpoints()` — **sans importer `@nodefony/framework`**. Le
115
115
  contrat vit au plus bas niveau commun pour qu'un adapter ORM, un service IA ou le kernel lui-même
116
116
  puissent l'implémenter.
117
- - **Transporteur** (`AdminBroker`, dans le framework `AdminBroker.ts:21`) : lui seul possède le
117
+ - **Transporteur** (`AdminBroker`, dans le framework `AdminBroker.ts#AdminBroker`) : lui seul possède le
118
118
  Router. Il collecte les producteurs et monte `/nodefony/<ns>/api/*`.
119
119
 
120
120
  Pour s'enregistrer sans dépendre du framework, un producteur récupère le broker via sa **vue
121
- minimale** `IAdminRegistry` (`IAdminApi.ts:243`) — juste `register()` — depuis le container. Le
121
+ minimale** `IAdminRegistry` (`IAdminApi.ts#IAdminRegistry`) — juste `register()` — depuis le container. Le
122
122
  kernel n'étant **pas** un `Module`, c'est le framework qui construit et enregistre l'`IAdminApi` du
123
123
  kernel à sa place (`createKernelAdminApi()`, cité plus bas).
124
124
 
125
- Le compromis assumé : **un seul controller pont** (`AdminApiController.ts:31`) sert les N endpoints.
125
+ Le compromis assumé : **un seul controller pont** (`AdminApiController.ts#AdminApiController`) sert les N endpoints.
126
126
  On y gagne zéro génération de classe, un dispatch O(1), et une garde RBAC + idempotence appliquée au
127
127
  même endroit pour tout le monde.
128
128
 
@@ -237,7 +237,7 @@ curl -s -b /tmp/jar http://localhost:5151/nodefony/framework/api/admin | head -c
237
237
  ```
238
238
 
239
239
  > [!NOTE]
240
- > Chaque réponse HTTP porte un en-tête `x-nodefony-instance` (`AdminApiController.ts:76`) : en
240
+ > Chaque réponse HTTP porte un en-tête `x-nodefony-instance` (`AdminApiController.ts#AdminApiController.dispatch`) : en
241
241
  > multi-pod, il dit **quel process** a répondu (le data plane est per-instance).
242
242
 
243
243
  ## 🏗️ Architecture interne — register → mountAll → dispatch
@@ -262,60 +262,65 @@ sequenceDiagram
262
262
  AC-->>RT: handler(IAdminRequest) → JSON / RpcError
263
263
  ```
264
264
 
265
- | # | Étape | Où |
266
- | --- | ----------------------------------------- | ---------------------------------------------------------- |
267
- | 1 | Le producteur s'enregistre | `AdminBroker.register()` (`AdminBroker.ts:53`) |
268
- | 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts:112`) |
269
- | 3 | Une route par endpoint (nom déterministe) | `Router.createRoute()` (`AdminBroker.ts:124`) |
270
- | 4 | Le controller pont estampillé une fois | `Router.setController()` idempotent (`AdminBroker.ts:146`) |
271
- | 5 | Dispatch : lookup de la route | `AdminBroker.resolve()` (`AdminApiController.ts:94`) |
272
- | 6 | Projection du contexte en requête admin | `buildRequest()` (`AdminApiController.ts:175`) |
273
- | 7 | Normalisation du retour | `normalizeAdminResult()` (`executeAdmin.ts:90`) |
265
+ | # | Étape | Où |
266
+ | --- | ----------------------------------------- | ----------------------------------------------------------------------------- |
267
+ | 1 | Le producteur s'enregistre | `AdminBroker.register()` (`AdminBroker.ts#AdminBroker.register`) |
268
+ | 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts#AdminBroker.mountAll`) |
269
+ | 3 | Une route par endpoint (nom déterministe) | `Router.createRoute()` (`AdminBroker.ts#AdminBroker.mountAll`) |
270
+ | 4 | Le controller pont estampillé une fois | `Router.setController()` idempotent (`AdminBroker.ts#AdminBroker.mountAll`) |
271
+ | 5 | Dispatch : lookup de la route | `AdminBroker.resolve()` (`AdminApiController.ts#AdminApiController.runAdmin`) |
272
+ | 6 | Projection du contexte en requête admin | `buildRequest()` (`AdminApiController.ts#AdminApiController.buildRequest`) |
273
+ | 7 | Normalisation du retour | `normalizeAdminResult()` (`executeAdmin.ts#normalizeAdminResult`) |
274
274
 
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:53`) : on ne monte pas une route à chaud (Zero surprise en prod). Le broker garde
278
+ (`AdminBroker.ts#AdminBroker.register`) : 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:123`) — c'est
280
+ - **Le nom de route est déterministe** : `admin.<ns>.<method>.<path>` (`AdminBroker.ts#AdminBroker.mountAll`) — c'est
281
281
  la clé du lookup O(1) que le pont refait à chaque requête.
282
- - **`Router.setController` n'est appelé qu'une fois** par process (`AdminBroker.ts:146`) : il pose
282
+ - **`Router.setController` n'est appelé qu'une fois** par process (`AdminBroker.ts#AdminBroker.mountAll`) : 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:108`) — jamais un état retenu.
286
+ (`AdminBroker.ts#AdminBroker.routes`) — jamais un état retenu.
287
287
 
288
288
  > [!IMPORTANT]
289
289
  > Convention de route **figée** : le data plane est toujours en **≥ 3 segments**
290
- > `/nodefony/<module>/api/*` (`IAdminBroker.ts:42`). Jamais une route admin mono-segment
290
+ > `/nodefony/<module>/api/*` (`IAdminBroker.ts#IAdminBroker`). Jamais une route admin mono-segment
291
291
  > `/nodefony/<module>` — elle entrerait en collision avec le fallback SPA de Studio. Le chemin
292
- > relatif d'un endpoint a **≥ 1 segment** (`types/IAdminApi.ts:155`) : la racine `/nodefony/<ns>/api` est
292
+ > relatif d'un endpoint a **≥ 1 segment** (`types/IAdminApi.ts#IAdminEndpoint.path`) : la racine `/nodefony/<ns>/api` est
293
293
  > réservée.
294
294
 
295
295
  ## 🔐 RBAC — autorisation du data plane
296
296
 
297
297
  Deux gardes se succèdent, dans cet ordre :
298
298
 
299
- 1. **Le firewall AUTHENTIFIE en amont.** La zone `nodefony-admin` (`config.ts:137`) couvre
300
- `^/nodefony/[^/]+/api(/|$)` (`config.ts:141`) avec l'authenticator `session` (cookie BFF) : sans
299
+ 1. **Le firewall AUTHENTIFIE en amont.** La zone `nodefony-admin` (`config.ts`) couvre
300
+ `^/nodefony/[^/]+/api(/|$)` (`config.ts`) avec l'authenticator `session` (cookie BFF) : sans
301
301
  session, c'est **401** avant même le controller.
302
302
  2. **Le broker tranche le RÔLE.** À l'exécution, le pont compare le rôle exigé aux rôles de
303
- l'appelant via la fonction pure `isAdminGranted()` (`adminRbac.ts:24`). Rôle absent → **403**.
303
+ l'appelant via la fonction pure `isAdminGranted()` (`adminRbac.ts#isAdminGranted`). Rôle absent → **403**.
304
304
  Le refus est prononcé au CŒUR (`executeAdmin.ts`), pas dans le controller HTTP : c'est ce
305
305
  qui fait que les deux chemins d'appel — la route et le pont MCP — refusent à l'identique.
306
306
 
307
307
  La décision est **fail-closed** : un authentifié **sans** le rôle requis — y compris `roles=[]`
308
- (compte non doté) — est **rejeté** (`adminRbac.ts:27`). C'était l'ex-fail-open historique (un
308
+ (compte non doté) — est **rejeté** (`adminRbac.ts#isAdminGranted`). C'était l'ex-fail-open historique (un
309
309
  `roles.length > 0 &&` héritait du « mode mock » d'avant l'auth) : l'absence de rôle ne vaut pas
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:166`).
314
- - **Endpoint public** : `public: true` (`IAdminApi.ts:175`) → le RBAC du broker est court-circuité
315
- (`role === ""`, `adminRbac.ts:26`). À réserver aux sondes cloud-native (liveness/readiness) et à
313
+ (`AdminBroker.ts#AdminBroker.mountAll` ; défaut du champ `IAdminEndpoint.role`, `IAdminApi.ts#IAdminEndpoint.role`).
314
+ - **Endpoint public** : `public: true` (`IAdminApi.ts#IAdminEndpoint.public`) → le RBAC du broker est court-circuité
315
+ (`role === ""`, `adminRbac.ts#isAdminGranted`). À 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
- `GET /nodefony/kernel/api/livez` (`KernelAdminApi.ts:615`), sorti de `nodefony-admin` par la zone
318
- publique `nodefony-liveness` (`config.ts:132`).
317
+ `GET /nodefony/kernel/api/livez` (`KernelAdminApi.ts#auditLogLevelChange`), sorti de `nodefony-admin` par la zone
318
+ publique `nodefony-liveness` (`config.ts`).
319
+ - **Endpoint qui reçoit un mot de passe** : `credentials: true` (`IAdminApi.ts#IAdminEndpoint.credentials`). En
320
+ production, une requête arrivée en clair est refusée en **403 avant le rôle et le handler**
321
+ (`executeAdmin.ts#executeAdminEndpoint`) — la décision est celle de security (`credentialTransport`), le
322
+ contrôleur la fournit pour HTTP et pour le pont WS. La CLI n'a pas de transport : rien n'est
323
+ jugé. Exemples : `POST me/password`, `POST users/{id}/password`, `POST users` (module `user`).
319
324
 
320
325
  > [!TIP]
321
326
  > `ROLE_NODEFONY_*` = rôles de la **plateforme** (administrer le framework), distincts des rôles
@@ -324,19 +329,19 @@ laissez-passer.
324
329
 
325
330
  ## 🔌 HTTP et WebSocket — la même action (API souveraine)
326
331
 
327
- Toute action admin déclare **aussi** le transport `WEBSOCKET` (`AdminBroker.ts:123`) : la route est
332
+ Toute action admin déclare **aussi** le transport `WEBSOCKET` (`AdminBroker.ts#AdminBroker.mountAll`) : la route est
328
333
  montée avec `[method, "WEBSOCKET"]`. Elle devient donc invocable par le pont WS-RPC `api.request`
329
- (`WebsocketContext.ts:354`) — même action, même handler, même réponse. Seul l'emballage diffère :
334
+ (`WebsocketContext.ts#WebsocketContext.request`) — même action, même handler, même réponse. Seul l'emballage diffère :
330
335
 
331
- - **HTTP** : `renderJson` + statut + en-tête `x-nodefony-instance` (`AdminApiController.ts:74`).
336
+ - **HTTP** : `renderJson` + statut + en-tête `x-nodefony-instance` (`AdminApiController.ts#AdminApiController.dispatch`).
332
337
  - **WS-RPC** : la valeur **nue** (le pont l'enveloppe `{id, result}`) ; un statut ≥ 400 devient un
333
- `RpcError` avec `data.status`/`data.body` (`AdminApiController.ts:66`), symétrie d'un `fetch` qui
338
+ `RpcError` avec `data.status`/`data.body` (`AdminApiController.ts#AdminApiController.dispatch`), symétrie d'un `fetch` qui
334
339
  expose son statut.
335
340
 
336
341
  Les **mutations** sont pontables par socket. La sécurité d'écriture repose alors sur l'**idempotence**
337
- (`idempotencyGate()`, `AdminApiController.ts:131`) : la clé `Idempotency-Key` est **obligatoire en
342
+ (`idempotencyGate()`, `AdminApiController.ts#AdminApiController.idempotencyGate`) : la clé `Idempotency-Key` est **obligatoire en
338
343
  WS** (une socket reconnecte et rejoue), **optionnelle en HTTP** (`required: false`,
339
- `AdminApiController.ts:149`). Un `GET` n'est jamais idempotenté (`AdminApiController.ts:135`) ; la
344
+ `AdminApiController.ts#AdminApiController.idempotencyGate`). Un `GET` n'est jamais idempotenté (`AdminApiController.ts#AdminApiController.idempotencyGate`) ; la
340
345
  porte est évaluée **après** le RBAC (un 403 ne consomme aucune entrée). Le helper est le **même** que
341
346
  le seam `@Idempotent` des controllers userland — voir [Idempotence](idempotence.md).
342
347
 
@@ -345,37 +350,37 @@ le seam `@Idempotent` des controllers userland — voir [Idempotence](idempotenc
345
350
  Trois pas, du point de vue d'un module :
346
351
 
347
352
  1. **Écrire un `IAdminApi`** : `adminNamespace` (url-safe, stable), `adminDescriptor()` (sidebar
348
- Studio, `IAdminApi.ts:220`) et `adminEndpoints()` (`IAdminApi.ts:222`). Un endpoint peut renvoyer
353
+ Studio, `IAdminApi.ts#IAdminApi.adminDescriptor`) et `adminEndpoints()` (`IAdminApi.ts#IAdminApi.adminEndpoints`). Un endpoint peut renvoyer
349
354
  la donnée brute (assumée `{status:200, body}`) ou une `IAdminResponse` pour piloter statut/en-têtes
350
- (`IAdminApi.ts:67`).
351
- 2. **S'enregistrer au `onKernelBoot`** via `IAdminRegistry.register()` (`IAdminApi.ts:249`), récupéré
355
+ (`IAdminApi.ts#IAdminResponse`).
356
+ 2. **S'enregistrer au `onKernelBoot`** via `IAdminRegistry.register()` (`IAdminApi.ts#IAdminRegistry.register`), récupéré
352
357
  par `container.get("adminBroker")`. Rendre l'appel **idempotent** (`registry.has(ns)` avant
353
358
  `register`) — modèle de tous les producteurs.
354
359
  3. **Laisser le framework monter** : à `onKernelReady`, `Framework.onKernelReady()` enregistre les
355
- producteurs internes puis appelle `broker.mountAll()` (`index.ts:369`).
360
+ producteurs internes puis appelle `broker.mountAll()` (`index.ts#Framework.onKernelReady`).
356
361
 
357
362
  **Handlers lazy** : résous tes services **dans** le handler (à la requête), jamais au montage — un
358
363
  service désactivable renvoie alors `503` proprement au lieu de casser le boot.
359
364
 
360
365
  ### Les producteurs réels (à imiter, sans les redocumenter)
361
366
 
362
- Le broker lui-même est déclaré comme service du framework (`index.ts:144`). Les producteurs internes
367
+ Le broker lui-même est déclaré comme service du framework (`index.ts`). Les producteurs internes
363
368
  sont enregistrés au `onKernelReady` du framework :
364
369
 
365
- | Namespace | Producteur | Rôle |
366
- | ----------- | ----------------------------------------------------- | ------------------------------------------------- |
367
- | `kernel` | `createKernelAdminApi` (`KernelAdminApi.ts:582`) | modules, process, uptime, `livez` |
368
- | `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts:86`) | dump du Router + **catalogue** + Playground (dev) |
369
- | `syslog` | `createSyslogAdminApi` (`SyslogAdminApi.ts:95`) | viewer de logs (dev) |
370
+ | Namespace | Producteur | Rôle |
371
+ | ----------- | -------------------------------------------------------------------------- | ------------------------------------------------- |
372
+ | `kernel` | `createKernelAdminApi` (`KernelAdminApi.ts#createKernelAdminApi`) | modules, process, uptime, `livez` |
373
+ | `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts#createFrameworkAdminApi`) | dump du Router + **catalogue** + Playground (dev) |
374
+ | `syslog` | `createSyslogAdminApi` (`SyslogAdminApi.ts#createSyslogAdminApi`) | viewer de logs (dev) |
370
375
 
371
376
  Les modules externes s'enregistrent depuis leur propre `onKernelBoot` :
372
377
 
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`) |
378
+ | Namespace | Module | Enregistrement |
379
+ | ---------- | -------------------- | ---------------------------------------------------------------------- |
380
+ | `http` | `@nodefony/http` | `createHttpAdminApi` (`http/index.ts#Http.onKernelRegister`) |
381
+ | `security` | `@nodefony/security` | `registerSecurityAdminApi` (`security/index.ts#Security.onKernelBoot`) |
382
+ | `user` | `@nodefony/user` | `adminNamespace` (`UserAdminApi.ts#createUserAdminApi`) |
383
+ | `orm` | `@nodefony/orm-core` | `adminNamespace` (`OrmAdminApi.ts#createOrmAdminApi`) |
379
384
 
380
385
  Pour le détail de chacun, se reporter à la doc de son module — le broker reste agnostique de leur
381
386
  contenu.
@@ -387,42 +392,42 @@ Depuis une app : `AdminBroker` (le service) et les types `IAdminApi`, `IAdminEnd
387
392
  `@nodefony/framework` et `nodefony`. Les signatures exactes vivent dans `.ai/symbols.json` — jamais
388
393
  recopiées ici (elles s'y périmeraient).
389
394
 
390
- | Membre (`IAdminBroker`) | Rôle | Ancre |
391
- | ------------------------ | -------------------------------------------------------- | -------------------- |
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` |
395
+ | Membre (`IAdminBroker`) | Rôle | Ancre |
396
+ | ------------------------ | -------------------------------------------------------- | ---------------------------------------- |
397
+ | `register(api)` | Enregistre un producteur (throw si namespace pris/monté) | `AdminBroker.ts#AdminBroker.register` |
398
+ | `unregister(ns)` | Retire un producteur (et ses routes si montées) | `AdminBroker.ts#AdminBroker.unregister` |
399
+ | `has(ns)` / `getApi(ns)` | Interrogation du registre | `AdminBroker.ts#AdminBroker.getApi` |
400
+ | `list()` | Producteurs enregistrés (immuable) | `AdminBroker.ts#AdminBroker.list` |
401
+ | `resolvePath(ns, path)` | Chemin absolu d'un endpoint sans le monter | `AdminBroker.ts#AdminBroker.resolvePath` |
402
+ | `mountAll()` | Monte toutes les routes (idempotent) | `AdminBroker.ts#AdminBroker.mountAll` |
403
+ | `resolve(routeName)` | Lookup O(1) d'une route montée (utilisé par le pont) | `AdminBroker.ts#AdminBroker.resolve` |
404
+ | `routes()` | Introspection des routes montées (source du catalogue) | `AdminBroker.ts#AdminBroker.routes` |
400
405
 
401
406
  ## 📡 Observabilité — Studio
402
407
 
403
- - **Catalogue / discovery** : `GET /nodefony/framework/api/admin` (`FrameworkAdminApi.ts:203`) —
408
+ - **Catalogue / discovery** : `GET /nodefony/framework/api/admin` (`FrameworkAdminApi.ts#createFrameworkAdminApi`) —
404
409
  producteurs + descriptors + endpoints. C'est ce que Studio lit pour générer sa navigation admin.
405
- - **Routes** : `GET /nodefony/framework/api/routes` (`FrameworkAdminApi.ts:122`) — l'équivalent web
406
- de `nodefony router:dump` ; une variante paginée serveur `routes/page` (`FrameworkAdminApi.ts:129`).
410
+ - **Routes** : `GET /nodefony/framework/api/routes` (`FrameworkAdminApi.ts#createFrameworkAdminApi`) — l'équivalent web
411
+ de `nodefony router:dump` ; une variante paginée serveur `routes/page` (`FrameworkAdminApi.ts#createFrameworkAdminApi`).
407
412
  - **Playground** (`/nodefony/playground`, **dev only**) : `buildPlaygroundSnapshot()`
408
- (`PlaygroundAdminApi.ts:167`) sérialise controllers + actions + transports + params + gardes — la
413
+ (`PlaygroundAdminApi.ts#buildPlaygroundSnapshot`) sérialise controllers + actions + transports + params + gardes — la
409
414
  page Studio bâtit ses formulaires depuis ces métadonnées, **sans code généré**. Le montage est
410
- conditionné à l'environnement (`index.ts:342`). Le pont admin lui-même est **exclu** du snapshot
411
- (`PlaygroundAdminApi.ts:180`) pour ne pas noyer les controllers applicatifs.
415
+ conditionné à l'environnement (`index.ts#Framework.onKernelBoot`). Le pont admin lui-même est **exclu** du snapshot
416
+ (`PlaygroundAdminApi.ts#buildPlaygroundSnapshot`) pour ne pas noyer les controllers applicatifs.
412
417
  - Les écrans admin de Studio (Routes, Sessions, Firewall, Users…) sont des vues qui consomment ces
413
418
  data planes — voir le [module Studio](../../studio/docs/index.md).
414
419
 
415
420
  ## ⚠️ Pièges (symptôme → cause → correction)
416
421
 
417
- | Symptôme | Cause (dans le code) | Correction |
418
- | ------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
419
- | `register()` throw « routes figées » | Appel **après** `mountAll()` (`AdminBroker.ts:112`) | Enregistrer au `onKernelBoot`, pas plus tard |
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
- | 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
- | 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`) |
423
- | WS : mutation refusée `400` clé requise | Idempotence : clé obligatoire par socket (`AdminApiController.ts:184`) | Fournir `Idempotency-Key` sur la mutation WS |
424
- | Route admin injoignable / collision Studio | Endpoint mono-segment `/nodefony/<module>` (`IAdminBroker.ts:42`) | Toujours `≥ 3` segments `/nodefony/<ns>/api/<path>` |
425
- | 500 « Admin endpoint not registered » | `adminRoute` absent du registre — incohérence interne (`AdminApiController.ts:96`) | Vérifier que le producteur a bien été enregistré avant `mountAll()` |
422
+ | Symptôme | Cause (dans le code) | Correction |
423
+ | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
424
+ | `register()` throw « routes figées » | Appel **après** `mountAll()` (`AdminBroker.ts#AdminBroker.mountAll`) | Enregistrer au `onKernelBoot`, pas plus tard |
425
+ | `register()` throw « namespace déjà enregistré » | Deux producteurs sur le même `adminNamespace` (`AdminBroker.ts#AdminBroker.register`) | Namespace unique ; garder `register()` idempotent (`has(ns)` avant) |
426
+ | 401 sur toute route `/nodefony/<ns>/api/*` | Zone `nodefony-admin` : pas de session BFF (`config.ts`) | S'authentifier (login BFF) ; pour une sonde publique → `public: true` + zone anonyme |
427
+ | 403 alors qu'on est connecté | Rôle manquant, `isAdminGranted` fail-closed (`adminRbac.ts#isAdminGranted`) | Doter le compte du rôle requis (défaut `ROLE_NODEFONY_ADMIN`) |
428
+ | WS : mutation refusée `400` clé requise | Idempotence : clé obligatoire par socket (`AdminApiController.ts#AdminApiController.idempotencyGate`) | Fournir `Idempotency-Key` sur la mutation WS |
429
+ | Route admin injoignable / collision Studio | Endpoint mono-segment `/nodefony/<module>` (`IAdminBroker.ts#IAdminBroker`) | Toujours `≥ 3` segments `/nodefony/<ns>/api/<path>` |
430
+ | 500 « Admin endpoint not registered » | `adminRoute` absent du registre — incohérence interne (`AdminApiController.ts#AdminApiController.runAdmin`) | Vérifier que le producteur a bien été enregistré avant `mountAll()` |
426
431
 
427
432
  ## 🧪 Tests & couverture
428
433