@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.
- package/README.md +4 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.150.0 → _@oxc-project_runtime@0.151.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +7 -6
- package/dist/nodefony/controller/AdminApiController.js +4 -3
- package/dist/nodefony/controller/ApiKeyController.js +1 -1
- package/dist/nodefony/controller/OAuth2Controller.js +11 -6
- package/dist/nodefony/controller/SessionAuthController.js +3 -3
- package/dist/nodefony/controller/TokenAuthController.js +2 -2
- package/dist/nodefony/controller/TotpController.js +5 -4
- package/dist/nodefony/controller/WebAuthnController.js +4 -4
- package/dist/nodefony/decorators/routerDecorators.js +41 -35
- package/dist/nodefony/service/AdminBroker.js +2 -2
- package/dist/nodefony/service/IdempotencyStore.js +4 -5
- package/dist/nodefony/service/router.js +75 -25
- package/dist/nodefony/src/Controller.js +184 -78
- package/dist/nodefony/src/FrameworkAdminApi.js +19 -6
- package/dist/nodefony/src/KernelAdminApi.js +60 -55
- package/dist/nodefony/src/PlaygroundAdminApi.js +3 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +2 -2
- package/dist/nodefony/src/Resolver.js +219 -63
- package/dist/nodefony/src/Route.js +38 -38
- package/dist/nodefony/src/SyslogAdminApi.js +4 -4
- package/dist/nodefony/src/docsReader.js +22 -18
- package/dist/nodefony/src/scopeCatalog.js +1 -1
- package/dist/nodefony/src/singletonGuard.js +76 -0
- package/dist/types/nodefony/controller/BenchController.d.ts +2 -2
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +3 -3
- package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +1 -1
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +2 -1
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +49 -43
- package/dist/types/nodefony/interfaces/IController.d.ts +9 -7
- package/dist/types/nodefony/interfaces/IResolver.d.ts +10 -10
- package/dist/types/nodefony/interfaces/IRoute.d.ts +13 -13
- package/dist/types/nodefony/service/AdminBroker.d.ts +3 -3
- package/dist/types/nodefony/service/router.d.ts +33 -7
- package/dist/types/nodefony/src/Controller.d.ts +97 -24
- package/dist/types/nodefony/src/Resolver.d.ts +154 -15
- package/dist/types/nodefony/src/ResourceController.d.ts +6 -6
- package/dist/types/nodefony/src/Route.d.ts +24 -22
- package/dist/types/nodefony/src/docsReader.d.ts +3 -3
- package/dist/types/nodefony/src/singletonGuard.d.ts +53 -0
- package/docs/admin.md +26 -26
- package/docs/controller.md +139 -121
- package/docs/decorateurs.md +108 -105
- package/docs/idempotence.md +78 -71
- package/docs/routing.md +57 -51
- package/docs/templates.md +17 -17
- 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:
|
|
268
|
-
| 2 | Le framework monte tout | `AdminBroker.mountAll()` (`AdminBroker.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
314
|
-
- **Endpoint public** : `public: true` (`IAdminApi.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
368
|
-
| `framework` | `createFrameworkAdminApi` (`FrameworkAdminApi.ts:
|
|
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:
|
|
377
|
-
| `user` | `@nodefony/user` | `adminNamespace` (`UserAdminApi.ts:
|
|
378
|
-
| `orm` | `@nodefony/orm-core` | `adminNamespace` (`OrmAdminApi.ts:
|
|
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:
|
|
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:
|
|
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:
|
|
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`) |
|