@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.
- package/README.md +5 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +4 -4
- package/dist/nodefony/controller/AdminApiController.js +4 -3
- package/dist/nodefony/controller/ApiKeyController.js +3 -2
- package/dist/nodefony/controller/OAuth2Controller.js +11 -6
- package/dist/nodefony/controller/SessionAuthController.js +3 -3
- package/dist/nodefony/controller/TokenAuthController.js +4 -4
- package/dist/nodefony/controller/TotpController.js +7 -5
- 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 +78 -26
- package/dist/nodefony/src/Controller.js +184 -78
- package/dist/nodefony/src/FrameworkAdminApi.js +19 -6
- package/dist/nodefony/src/KernelAdminApi.js +49 -49
- package/dist/nodefony/src/PlaygroundAdminApi.js +3 -3
- package/dist/nodefony/src/RedisIdempotencyStore.js +2 -2
- package/dist/nodefony/src/Resolver.js +221 -65
- package/dist/nodefony/src/Route.js +62 -39
- package/dist/nodefony/src/SyslogAdminApi.js +4 -4
- package/dist/nodefony/src/docsReader.js +49 -39
- package/dist/nodefony/src/scopeCatalog.js +1 -1
- package/dist/nodefony/src/singletonGuard.js +76 -0
- package/dist/types/nodefony/controller/ApiKeyController.d.ts +2 -1
- 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/controller/TokenAuthController.d.ts +2 -2
- package/dist/types/nodefony/controller/TotpController.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 +155 -16
- package/dist/types/nodefony/src/ResourceController.d.ts +6 -6
- package/dist/types/nodefony/src/Route.d.ts +39 -23
- package/dist/types/nodefony/src/docsReader.d.ts +19 -9
- package/dist/types/nodefony/src/singletonGuard.d.ts +53 -0
- package/docs/admin.md +1 -1
- package/docs/controller.md +139 -121
- package/docs/decorateurs.md +102 -98
- package/docs/idempotence.md +23 -21
- package/docs/index.md +1 -1
- package/docs/routing.md +60 -53
- package/docs/templates.md +14 -14
- 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
|
-
|
|
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) → `
|
|
309
|
-
* - sinon →
|
|
310
|
-
* pour vitest comme pour
|
|
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).
|
|
314
|
-
* d'injection
|
|
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:
|
|
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
|
|