@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.
- package/README.md +4 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/index.js +15 -20
- package/dist/nodefony/config/config.js +1 -2
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/controller/AdminApiController.js +45 -11
- package/dist/nodefony/controller/ApiKeyController.js +18 -10
- package/dist/nodefony/controller/BenchController.js +1 -2
- package/dist/nodefony/controller/IssuerMetadataController.js +1 -2
- package/dist/nodefony/controller/LoginPageController.js +117 -0
- package/dist/nodefony/controller/OAuth2Controller.js +133 -17
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +1 -2
- package/dist/nodefony/controller/SessionAuthController.js +50 -13
- package/dist/nodefony/controller/TokenAuthController.js +14 -8
- package/dist/nodefony/controller/TotpController.js +7 -8
- package/dist/nodefony/controller/WebAuthnController.js +28 -12
- package/dist/nodefony/controller/oauthAuthority.js +1 -2
- package/dist/nodefony/decorators/routerDecorators.js +268 -65
- package/dist/nodefony/service/AdminBroker.js +4 -6
- package/dist/nodefony/service/Eta.js +1 -2
- package/dist/nodefony/service/IdempotencyStore.js +4 -6
- package/dist/nodefony/service/router.js +97 -34
- package/dist/nodefony/src/Controller.js +71 -43
- package/dist/nodefony/src/FrameworkAdminApi.js +33 -11
- package/dist/nodefony/src/KernelAdminApi.js +82 -18
- package/dist/nodefony/src/PlaygroundAdminApi.js +72 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +46 -4
- package/dist/nodefony/src/Resolver.js +105 -36
- package/dist/nodefony/src/ResourceController.js +2 -3
- package/dist/nodefony/src/Route.js +54 -8
- package/dist/nodefony/src/SecurityApiController.js +23 -0
- package/dist/nodefony/src/SyslogAdminApi.js +1 -2
- package/dist/nodefony/src/Template.js +1 -2
- package/dist/nodefony/src/configMutation.js +1 -2
- package/dist/nodefony/src/docsReader.js +39 -23
- package/dist/nodefony/src/idempotency.js +14 -22
- package/dist/nodefony/src/idempotencyGc.js +1 -2
- package/dist/nodefony/src/idempotencyStoreRegistry.js +1 -2
- package/dist/nodefony/src/loginPage.js +430 -0
- package/dist/nodefony/src/requireAuthentication.js +35 -0
- package/dist/nodefony/src/routeSchema.js +81 -0
- package/dist/nodefony/src/scopeCatalog.js +1 -2
- package/dist/nodefony/src/singletonGuard.js +1 -2
- package/dist/nodefony/src/syslogFilters.js +1 -2
- package/dist/types/index.d.ts +8 -12
- package/dist/types/nodefony/controller/AdminApiController.d.ts +12 -0
- package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -2
- package/dist/types/nodefony/controller/LoginPageController.d.ts +32 -0
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +67 -8
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +46 -8
- package/dist/types/nodefony/controller/TokenAuthController.d.ts +6 -4
- package/dist/types/nodefony/controller/TotpController.d.ts +2 -2
- package/dist/types/nodefony/controller/WebAuthnController.d.ts +12 -4
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +221 -35
- package/dist/types/nodefony/interfaces/IController.d.ts +2 -3
- package/dist/types/nodefony/interfaces/IRoute.d.ts +1 -2
- package/dist/types/nodefony/service/router.d.ts +40 -1
- package/dist/types/nodefony/src/Controller.d.ts +54 -17
- package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +12 -0
- package/dist/types/nodefony/src/Resolver.d.ts +44 -1
- package/dist/types/nodefony/src/ResourceController.d.ts +1 -1
- package/dist/types/nodefony/src/Route.d.ts +21 -1
- package/dist/types/nodefony/src/SecurityApiController.d.ts +17 -0
- package/dist/types/nodefony/src/docsReader.d.ts +10 -6
- package/dist/types/nodefony/src/idempotency.d.ts +12 -10
- package/dist/types/nodefony/src/loginPage.d.ts +144 -0
- package/dist/types/nodefony/src/requireAuthentication.d.ts +29 -0
- package/dist/types/nodefony/src/routeSchema.d.ts +112 -0
- package/docs/admin.md +83 -78
- package/docs/controller.md +112 -111
- package/docs/decorateurs.md +212 -163
- package/docs/idempotence.md +137 -135
- package/docs/routing.md +96 -95
- package/docs/templates.md +55 -55
- package/package.json +10 -13
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
268
|
-
| 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts
|
|
269
|
-
| 3 | Une route par endpoint (nom déterministe) | `Router.createRoute()` (`AdminBroker.ts
|
|
270
|
-
| 4 | Le controller pont estampillé une fois | `Router.setController()` idempotent (`AdminBroker.ts
|
|
271
|
-
| 5 | Dispatch : lookup de la route | `AdminBroker.resolve()` (`AdminApiController.ts
|
|
272
|
-
| 6 | Projection du contexte en requête admin | `buildRequest()` (`AdminApiController.ts
|
|
273
|
-
| 7 | Normalisation du retour | `normalizeAdminResult()` (`executeAdmin.ts
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
300
|
-
`^/nodefony/[^/]+/api(/|$)` (`config.ts
|
|
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
|
|
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
|
|
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
|
|
314
|
-
- **Endpoint public** : `public: true` (`IAdminApi.ts
|
|
315
|
-
(`role === ""`, `adminRbac.ts
|
|
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
|
|
318
|
-
publique `nodefony-liveness` (`config.ts
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
351
|
-
2. **S'enregistrer au `onKernelBoot`** via `IAdminRegistry.register()` (`IAdminApi.ts
|
|
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
|
|
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
|
|
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
|
|
366
|
-
| ----------- |
|
|
367
|
-
| `kernel` | `createKernelAdminApi` (`KernelAdminApi.ts
|
|
368
|
-
| `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts
|
|
369
|
-
| `syslog` | `createSyslogAdminApi` (`SyslogAdminApi.ts
|
|
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
|
|
376
|
-
| `security` | `@nodefony/security` | `registerSecurityAdminApi` (`security/index.ts
|
|
377
|
-
| `user` | `@nodefony/user` | `adminNamespace` (`UserAdminApi.ts
|
|
378
|
-
| `orm` | `@nodefony/orm-core` | `adminNamespace` (`OrmAdminApi.ts
|
|
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
|
|
393
|
-
| `unregister(ns)` | Retire un producteur (et ses routes si montées) | `AdminBroker.ts
|
|
394
|
-
| `has(ns)` / `getApi(ns)` | Interrogation du registre | `AdminBroker.ts
|
|
395
|
-
| `list()` | Producteurs enregistrés (immuable) | `AdminBroker.ts
|
|
396
|
-
| `resolvePath(ns, path)` | Chemin absolu d'un endpoint sans le monter | `AdminBroker.ts
|
|
397
|
-
| `mountAll()` | Monte toutes les routes (idempotent) | `AdminBroker.ts
|
|
398
|
-
| `resolve(routeName)` | Lookup O(1) d'une route montée (utilisé par le pont) | `AdminBroker.ts
|
|
399
|
-
| `routes()` | Introspection des routes montées (source du catalogue) | `AdminBroker.ts
|
|
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
|
|
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
|
|
406
|
-
de `nodefony router:dump` ; une variante paginée serveur `routes/page` (`FrameworkAdminApi.ts
|
|
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
|
|
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
|
|
411
|
-
(`PlaygroundAdminApi.ts
|
|
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)
|
|
418
|
-
| ------------------------------------------------ |
|
|
419
|
-
| `register()` throw « routes figées » | Appel **après** `mountAll()` (`AdminBroker.ts
|
|
420
|
-
| `register()` throw « namespace déjà enregistré » | Deux producteurs sur le même `adminNamespace` (`AdminBroker.ts
|
|
421
|
-
| 401 sur toute route `/nodefony/<ns>/api/*` | Zone `nodefony-admin` : pas de session BFF (`config.ts
|
|
422
|
-
| 403 alors qu'on est connecté | Rôle manquant, `isAdminGranted` fail-closed (`adminRbac.ts
|
|
423
|
-
| WS : mutation refusée `400` clé requise | Idempotence : clé obligatoire par socket (`AdminApiController.ts
|
|
424
|
-
| Route admin injoignable / collision Studio | Endpoint mono-segment `/nodefony/<module>` (`IAdminBroker.ts
|
|
425
|
-
| 500 « Admin endpoint not registered » | `adminRoute` absent du registre — incohérence interne (`AdminApiController.ts
|
|
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
|
|