@nodefony/framework 10.0.0-alpha.9 → 10.0.0-beta.2

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 (53) hide show
  1. package/README.md +5 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/index.js +4 -4
  5. package/dist/nodefony/controller/AdminApiController.js +4 -3
  6. package/dist/nodefony/controller/ApiKeyController.js +3 -2
  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 +4 -4
  10. package/dist/nodefony/controller/TotpController.js +7 -5
  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 +78 -26
  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 +49 -49
  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 +221 -65
  22. package/dist/nodefony/src/Route.js +62 -39
  23. package/dist/nodefony/src/SyslogAdminApi.js +4 -4
  24. package/dist/nodefony/src/docsReader.js +49 -39
  25. package/dist/nodefony/src/scopeCatalog.js +1 -1
  26. package/dist/nodefony/src/singletonGuard.js +76 -0
  27. package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -1
  28. package/dist/types/nodefony/controller/BenchController.d.ts +2 -2
  29. package/dist/types/nodefony/controller/OAuth2Controller.d.ts +3 -3
  30. package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +1 -1
  31. package/dist/types/nodefony/controller/SessionAuthController.d.ts +2 -1
  32. package/dist/types/nodefony/controller/TokenAuthController.d.ts +2 -2
  33. package/dist/types/nodefony/controller/TotpController.d.ts +2 -1
  34. package/dist/types/nodefony/decorators/routerDecorators.d.ts +49 -43
  35. package/dist/types/nodefony/interfaces/IController.d.ts +9 -7
  36. package/dist/types/nodefony/interfaces/IResolver.d.ts +10 -10
  37. package/dist/types/nodefony/interfaces/IRoute.d.ts +13 -13
  38. package/dist/types/nodefony/service/AdminBroker.d.ts +3 -3
  39. package/dist/types/nodefony/service/router.d.ts +33 -7
  40. package/dist/types/nodefony/src/Controller.d.ts +97 -24
  41. package/dist/types/nodefony/src/Resolver.d.ts +155 -16
  42. package/dist/types/nodefony/src/ResourceController.d.ts +6 -6
  43. package/dist/types/nodefony/src/Route.d.ts +39 -23
  44. package/dist/types/nodefony/src/docsReader.d.ts +19 -9
  45. package/dist/types/nodefony/src/singletonGuard.d.ts +53 -0
  46. package/docs/admin.md +1 -1
  47. package/docs/controller.md +139 -121
  48. package/docs/decorateurs.md +102 -98
  49. package/docs/idempotence.md +23 -21
  50. package/docs/index.md +1 -1
  51. package/docs/routing.md +60 -53
  52. package/docs/templates.md +14 -14
  53. package/package.json +10 -11
@@ -4,6 +4,8 @@ import type { RouteActionMeta } from "../decorators/routerDecorators.js";
4
4
  import Controller from "./Controller.js";
5
5
  export interface ControllerConstructor {
6
6
  new (...args: any[]): Controller;
7
+ /** Prototype de la classe — porte les métadonnées des actions (`@Param`…). */
8
+ readonly prototype: Controller;
7
9
  }
8
10
  /**
9
11
  * Options pour la configuration d'une route.
@@ -62,21 +64,21 @@ export interface RouteOptions {
62
64
  areaRoleExempt?: boolean;
63
65
  }
64
66
  export interface RouteRequirements {
65
- domain?: string | string[];
66
- scheme?: SchemeType;
67
- methods?: HTTPMethod[] | HTTPMethod;
68
- protocol?: string;
67
+ domain?: string | string[] | undefined;
68
+ scheme?: SchemeType | undefined;
69
+ methods?: HTTPMethod[] | HTTPMethod | undefined;
70
+ protocol?: string | undefined;
69
71
  }
70
72
  declare class Route implements IRoute {
71
73
  #private;
72
74
  name: string;
73
- path?: string;
74
- controller?: ControllerConstructor;
75
- classMethod?: string;
76
- prefix?: string;
77
- method?: HTTPMethod;
78
- schemes?: SchemeType;
79
- pattern?: RegExp;
75
+ path?: string | undefined;
76
+ controller?: ControllerConstructor | undefined;
77
+ classMethod?: string | undefined;
78
+ prefix?: string | undefined;
79
+ method?: HTTPMethod | undefined;
80
+ schemes?: SchemeType | undefined;
81
+ pattern?: RegExp | undefined;
80
82
  variables: string[];
81
83
  /**
82
84
  * Caractères du chemin déclaré qu'une requête ne peut PAS porter — la route
@@ -85,18 +87,18 @@ declare class Route implements IRoute {
85
87
  *
86
88
  * @see {@link UNREACHABLE_IN_PATHNAME}
87
89
  */
88
- unreachableChars?: string[];
90
+ unreachableChars?: string[] | undefined;
89
91
  defaults: Partial<Record<string, unknown>>;
90
92
  requirements: Partial<RouteRequirements>;
91
- hash?: string;
92
- host?: string | string[];
93
+ hash?: string | undefined;
94
+ host?: string | string[] | undefined;
93
95
  /**
94
96
  * Patterns de domaine pré-compilés (host + `requirements.domain`), RegExp
95
97
  * ancrées/wildcard. Compilé UNE fois dans {@link compile} ; testé par requête
96
98
  * via {@link isDomainAllowed} (zéro alloc hot-path). `undefined` = route servie
97
99
  * sur tous les vhosts.
98
100
  */
99
- hostRegexp?: RegExp[];
101
+ hostRegexp?: RegExp[] | undefined;
100
102
  /**
101
103
  * P3a — requirements pré-compilés au boot (0 alloc par match) :
102
104
  * `methodsSet` = méthodes autorisées normalisées UPPERCASE (lookup O(1)) ;
@@ -104,9 +106,9 @@ declare class Route implements IRoute {
104
106
  * `varRegexp` = requirements de variables de route (string → RegExp).
105
107
  * `this.requirements` reste la config BRUTE (hash de route + introspection).
106
108
  */
107
- methodsSet?: Set<string>;
108
- methodsAllow?: string;
109
- varRegexp?: Record<string, RegExp>;
109
+ methodsSet?: Set<string> | undefined;
110
+ methodsAllow?: string | undefined;
111
+ varRegexp?: Record<string, RegExp> | undefined;
110
112
  bypassFirewall: boolean;
111
113
  /**
112
114
  * La route décide seule de son autorisation → la zone du firewall n'applique
@@ -119,7 +121,7 @@ declare class Route implements IRoute {
119
121
  * `routeExpectsBodyStream(route)` via lecture `Reflect` des `ParamMeta`, O(1)
120
122
  * ensuite). Lu en amont par `handleHttp` pour sauter le parse.
121
123
  */
122
- bodyStream?: boolean;
124
+ bodyStream?: boolean | undefined;
123
125
  /**
124
126
  * P5 — Metadata d'action figées (`@HttpCode`/`@Header`/`@Redirect`/params/
125
127
  * session), memo au 1er hit comme {@link bodyStream} : `undefined` = pas
@@ -127,8 +129,8 @@ declare class Route implements IRoute {
127
129
  * ensuite → plus aucun `Reflect.getMetadata` par requête). Objet PARTAGÉ
128
130
  * entre requêtes — ne jamais muter. Posé après `generateId()` → hash stable.
129
131
  */
130
- actionMeta?: RouteActionMeta;
131
- filePath?: string;
132
+ actionMeta?: RouteActionMeta | undefined;
133
+ filePath?: string | undefined;
132
134
  /**
133
135
  * Module propriétaire de la route — set par `Router.setController()` à
134
136
  * `onBoot`, donc PAS disponible à la création de la route (via `@controller`)
@@ -136,7 +138,7 @@ declare class Route implements IRoute {
136
138
  */
137
139
  module?: {
138
140
  name: string;
139
- };
141
+ } | undefined;
140
142
  constructor(name: string, obj?: RouteOptions);
141
143
  /**
142
144
  * Normalise le pathname de la requête pour le matching : retire le(s)
@@ -149,7 +151,21 @@ declare class Route implements IRoute {
149
151
  * @returns le pathname sans slash final, ou `undefined` si la requête n'a pas d'URL.
150
152
  */
151
153
  static cleanPathname(context: ContextType): string | undefined;
152
- match(context: ContextType, cleanPath?: string, methodOverride?: string): ((string | null)[] & Record<string, unknown>) | null | undefined;
154
+ /**
155
+ * Confronte la requête à cette route : chemin, hôte, puis exigences.
156
+ *
157
+ * @param context - contexte HTTP/WS courant.
158
+ * @param cleanPath - pathname déjà normalisé par l'appelant (sinon recalculé).
159
+ * @param methodOverride - méthode logique du pont WS-RPC (cf `Resolver`).
160
+ * @param literal - l'appelant GARANTIT que le chemin de la route ne porte
161
+ * aucune syntaxe de motif (`{}`, `*`…) — c'est le cas d'une route trouvée
162
+ * par l'index des littérales du `Router`. Le motif n'est alors pas exécuté
163
+ * quand le chemin est identique à la lettre.
164
+ * @returns les valeurs capturées (tableau + accès par nom), ou `null`/`undefined`
165
+ * quand le chemin ne correspond pas.
166
+ * @throws 403/405/1002 quand le chemin correspond mais pas l'hôte ou les exigences.
167
+ */
168
+ match(context: ContextType, cleanPath?: string, methodOverride?: string, literal?: boolean): ((string | null)[] & Record<string, unknown>) | null | undefined;
153
169
  /**
154
170
  * Compile the route into a regular expression pattern.
155
171
  * @returns The compiled regular expression pattern.
@@ -1,3 +1,4 @@
1
+ import type { IPackageManagerResolution } from "nodefony";
1
2
  /**
2
3
  * Frontmatter d'un fichier de doc module. Tous les champs sont optionnels —
3
4
  * un `.md` sans frontmatter reste lisible (titre dérivé du premier `# H1`).
@@ -12,7 +13,7 @@ export interface DocFrontmatter {
12
13
  since?: string;
13
14
  updated?: string;
14
15
  status?: string;
15
- order?: number;
16
+ order?: number | undefined;
16
17
  [key: string]: unknown;
17
18
  }
18
19
  /** Entrée du sommaire docs d'un module (sans le corps markdown). */
@@ -138,7 +139,7 @@ export interface DocSearchResult {
138
139
  * est rendu : annoncer une coupe qui n'a pas eu lieu ferait chercher un
139
140
  * reste inexistant.
140
141
  */
141
- note?: string;
142
+ note?: string | undefined;
142
143
  }
143
144
  /**
144
145
  * Cherche un texte dans les docs colocalisées des modules donnés.
@@ -164,7 +165,7 @@ export interface DocSearchResult {
164
165
  * @returns les docs retenues, et ce que la recherche a réellement balayé
165
166
  */
166
167
  export declare function searchModuleDocs(targets: readonly DocSearchTarget[], query: string, options?: {
167
- limit?: number;
168
+ limit?: number | undefined;
168
169
  perDoc?: number;
169
170
  }): Promise<DocSearchResult>;
170
171
  /**
@@ -297,21 +298,30 @@ export interface TestRunCommand {
297
298
  * config du module (la liste `reporter` dupliquée par module divergeait).
298
299
  * - sinon (cœur : monocart, pas de `vitest.config.ts`) → `npm run coverage`.
299
300
  *
301
+ * Le binaire et le script passent par le gestionnaire de paquets du PROJET :
302
+ * `npx` n'existe pas chez bun, et sous pnpm il chercherait hors de l'arbre que
303
+ * le verrou décrit.
304
+ *
300
305
  * @param modulePath - racine du module (où vit `vitest.config.ts`)
301
306
  * @param file - chemin relatif d'UN fichier de test, déjà validé par l'appelant
307
+ * @param manager - gestionnaire du projet ; défaut : résolu depuis la racine
308
+ * de l'application qui contient le module
302
309
  * @returns commande, arguments en tableau (spawn sans shell) et libellé du mode
303
310
  */
304
- export declare function testRunCommand(modulePath: string, file?: string): TestRunCommand;
311
+ export declare function testRunCommand(modulePath: string, file?: string, manager?: IPackageManagerResolution["name"]): TestRunCommand;
305
312
  /**
306
313
  * Lance les tests d'un module et renvoie un résumé (pass/fail/durée + tail).
307
314
  *
308
- * - 1 fichier (module vitest) → `npx vitest run <file>` (rapide, pass/fail).
309
- * - sinon → `npm run coverage` (suite complète + refresh coverage ; marche
310
- * pour vitest comme pour monocart/core).
315
+ * - 1 fichier (module vitest) → `vitest run <file>` (rapide, pass/fail).
316
+ * - sinon → le script `coverage` (suite complète + refresh coverage ; marche
317
+ * pour vitest comme pour le cœur).
318
+ * Les deux par le gestionnaire du projet (cf {@link testRunCommand}).
311
319
  *
312
320
  * ⚠️ EXÉCUTE un process — appelé UNIQUEMENT derrière le garde dev-only de
313
- * l'endpoint (cf KernelAdminApi). spawn sans shell + args en tableau (pas
314
- * d'injection shell). `file` validé en amont (suffixe .test.ts, pas de `..`).
321
+ * l'endpoint (cf KernelAdminApi). `portableSpawn` : jamais de `shell`, args
322
+ * en tableau (pas d'injection), et le `.cmd` du gestionnaire se lance sous
323
+ * Windows — un `spawn("npx")` nu y rendait `ENOENT`. `file` validé en amont
324
+ * (suffixe .test.ts, pas de `..`).
315
325
  */
316
326
  export declare function runModuleTests(modulePath: string, file?: string): Promise<TestRunResult>;
317
327
  /**
@@ -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
@@ -336,7 +336,7 @@ montée avec `[method, "WEBSOCKET"]`. Elle devient donc invocable par le pont WS
336
336
  Les **mutations** sont pontables par socket. La sécurité d'écriture repose alors sur l'**idempotence**
337
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