@nodefony/framework 10.0.0-alpha.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/LICENSE +544 -0
- package/README.md +50 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +211 -0
- package/dist/nodefony/config/config.js +61 -0
- package/dist/nodefony/config/defineModuleConfig.js +36 -0
- package/dist/nodefony/controller/AdminApiController.js +163 -0
- package/dist/nodefony/controller/ApiKeyController.js +151 -0
- package/dist/nodefony/controller/BenchController.js +132 -0
- package/dist/nodefony/controller/IssuerMetadataController.js +148 -0
- package/dist/nodefony/controller/OAuth2Controller.js +133 -0
- package/dist/nodefony/controller/ProtectedResourceMetadataController.js +221 -0
- package/dist/nodefony/controller/SessionAuthController.js +141 -0
- package/dist/nodefony/controller/TokenAuthController.js +113 -0
- package/dist/nodefony/controller/TotpController.js +129 -0
- package/dist/nodefony/controller/WebAuthnController.js +242 -0
- package/dist/nodefony/controller/oauthAuthority.js +74 -0
- package/dist/nodefony/decorators/routerDecorators.js +967 -0
- package/dist/nodefony/interfaces/IAdminBroker.js +1 -0
- package/dist/nodefony/interfaces/IController.js +1 -0
- package/dist/nodefony/interfaces/IIdempotencyStore.js +1 -0
- package/dist/nodefony/interfaces/IResolver.js +1 -0
- package/dist/nodefony/interfaces/IRoute.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/AdminBroker.js +106 -0
- package/dist/nodefony/service/Eta.js +68 -0
- package/dist/nodefony/service/IdempotencyStore.js +136 -0
- package/dist/nodefony/service/router.js +243 -0
- package/dist/nodefony/src/Controller.js +515 -0
- package/dist/nodefony/src/FrameworkAdminApi.js +268 -0
- package/dist/nodefony/src/KernelAdminApi.js +1243 -0
- package/dist/nodefony/src/PlaygroundAdminApi.js +97 -0
- package/dist/nodefony/src/RedisIdempotencyStore.js +254 -0
- package/dist/nodefony/src/Resolver.js +416 -0
- package/dist/nodefony/src/ResourceController.js +148 -0
- package/dist/nodefony/src/Route.js +476 -0
- package/dist/nodefony/src/SyslogAdminApi.js +466 -0
- package/dist/nodefony/src/Template.js +15 -0
- package/dist/nodefony/src/configMutation.js +186 -0
- package/dist/nodefony/src/docsReader.js +929 -0
- package/dist/nodefony/src/idempotency.js +137 -0
- package/dist/nodefony/src/idempotencyGc.js +32 -0
- package/dist/nodefony/src/idempotencyStoreRegistry.js +36 -0
- package/dist/nodefony/src/scopeCatalog.js +40 -0
- package/dist/nodefony/src/syslogFilters.js +51 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/nodefony/config/config.d.ts +41 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +27 -0
- package/dist/types/nodefony/controller/AdminApiController.d.ts +70 -0
- package/dist/types/nodefony/controller/ApiKeyController.d.ts +49 -0
- package/dist/types/nodefony/controller/BenchController.d.ts +45 -0
- package/dist/types/nodefony/controller/IssuerMetadataController.d.ts +86 -0
- package/dist/types/nodefony/controller/OAuth2Controller.d.ts +81 -0
- package/dist/types/nodefony/controller/ProtectedResourceMetadataController.d.ts +147 -0
- package/dist/types/nodefony/controller/SessionAuthController.d.ts +74 -0
- package/dist/types/nodefony/controller/TokenAuthController.d.ts +56 -0
- package/dist/types/nodefony/controller/TotpController.d.ts +42 -0
- package/dist/types/nodefony/controller/WebAuthnController.d.ts +123 -0
- package/dist/types/nodefony/controller/oauthAuthority.d.ts +54 -0
- package/dist/types/nodefony/decorators/routerDecorators.d.ts +632 -0
- package/dist/types/nodefony/interfaces/IAdminBroker.d.ts +79 -0
- package/dist/types/nodefony/interfaces/IController.d.ts +38 -0
- package/dist/types/nodefony/interfaces/IIdempotencyStore.d.ts +1 -0
- package/dist/types/nodefony/interfaces/IResolver.d.ts +25 -0
- package/dist/types/nodefony/interfaces/IRoute.d.ts +31 -0
- package/dist/types/nodefony/interfaces/index.d.ts +5 -0
- package/dist/types/nodefony/service/AdminBroker.d.ts +37 -0
- package/dist/types/nodefony/service/Eta.d.ts +25 -0
- package/dist/types/nodefony/service/IdempotencyStore.d.ts +45 -0
- package/dist/types/nodefony/service/router.d.ts +53 -0
- package/dist/types/nodefony/src/Controller.d.ts +193 -0
- package/dist/types/nodefony/src/FrameworkAdminApi.d.ts +35 -0
- package/dist/types/nodefony/src/KernelAdminApi.d.ts +71 -0
- package/dist/types/nodefony/src/PlaygroundAdminApi.d.ts +98 -0
- package/dist/types/nodefony/src/RedisIdempotencyStore.d.ts +104 -0
- package/dist/types/nodefony/src/Resolver.d.ts +165 -0
- package/dist/types/nodefony/src/ResourceController.d.ts +171 -0
- package/dist/types/nodefony/src/Route.d.ts +192 -0
- package/dist/types/nodefony/src/SyslogAdminApi.d.ts +38 -0
- package/dist/types/nodefony/src/Template.d.ts +8 -0
- package/dist/types/nodefony/src/configMutation.d.ts +109 -0
- package/dist/types/nodefony/src/docsReader.d.ts +369 -0
- package/dist/types/nodefony/src/idempotency.d.ts +96 -0
- package/dist/types/nodefony/src/idempotencyGc.d.ts +30 -0
- package/dist/types/nodefony/src/idempotencyStoreRegistry.d.ts +59 -0
- package/dist/types/nodefony/src/scopeCatalog.d.ts +26 -0
- package/dist/types/nodefony/src/syslogFilters.d.ts +52 -0
- package/docs/admin.md +451 -0
- package/docs/controller.md +645 -0
- package/docs/decorateurs.md +845 -0
- package/docs/idempotence.md +741 -0
- package/docs/index.md +151 -0
- package/docs/routing.md +648 -0
- package/docs/templates.md +380 -0
- package/package.json +83 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { TypeController } from "../service/router.js";
|
|
3
|
+
import { RouteOptions } from "../src/Route.js";
|
|
4
|
+
import Controller from "../src/Controller.js";
|
|
5
|
+
import type { ControllerScope } from "../src/Controller.js";
|
|
6
|
+
import { Module } from "nodefony";
|
|
7
|
+
import { ControllerConstructor } from "../src/Route.js";
|
|
8
|
+
import type { SessionIntent } from "@nodefony/http";
|
|
9
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
10
|
+
/**
|
|
11
|
+
* Rattache un ou plusieurs contrôleurs à un module, dont ils suivent le cycle de vie.
|
|
12
|
+
*
|
|
13
|
+
* Décorateur de **classe de module**. L'enregistrement n'a pas lieu à
|
|
14
|
+
* l'évaluation du décorateur mais au hook `onBoot` du kernel : tant que le boot
|
|
15
|
+
* n'a pas eu lieu, les routes déclarées par `@route` sur ces classes n'existent
|
|
16
|
+
* pas encore dans le routeur — un test qui interroge le routeur sans booter ne
|
|
17
|
+
* verra rien. L'enregistrement est tagué au nom du module, de sorte qu'un échec
|
|
18
|
+
* désigne le module fautif au lieu d'un contrôleur anonyme.
|
|
19
|
+
*
|
|
20
|
+
* @param controller - Un contrôleur, ou un tableau de contrôleurs, à rattacher.
|
|
21
|
+
* @returns Le décorateur de classe, qui renvoie le module enrichi du hook.
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* @controllers([DefaultController, RestController])
|
|
25
|
+
* class TestModule extends Module {}
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function controllers(targets: TypeController<Controller>[] | TypeController<Controller>): <T extends Constructor<Module>>(constructor: T) => T;
|
|
29
|
+
/**
|
|
30
|
+
* Declaration Controller
|
|
31
|
+
*
|
|
32
|
+
* @param prefix - prefixage du router du controller.
|
|
33
|
+
* @param options - Les options .
|
|
34
|
+
* @returns Un décorateur de méthode qui peut être utilisé pour annoter une méthode de contrôleur.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* \@controller("/openapi")
|
|
38
|
+
* \@UseSession()
|
|
39
|
+
* class OpenApiController extends Controller {
|
|
40
|
+
* constructor(context: Context) {
|
|
41
|
+
* super("OpenApiController", context);
|
|
42
|
+
* }
|
|
43
|
+
* \@route("index-openapi", { path: "" })
|
|
44
|
+
* index() {
|
|
45
|
+
* this.render({});
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
declare function controller(prefix: string): <T extends ControllerConstructor & {
|
|
50
|
+
prefix?: string;
|
|
51
|
+
}>(mycontroller: T) => T;
|
|
52
|
+
/**
|
|
53
|
+
* Crée une route avec le nom et les options spécifiés.
|
|
54
|
+
*
|
|
55
|
+
* @param name - Le nom de la route.
|
|
56
|
+
* @param options - Les options de la route.
|
|
57
|
+
* @returns Un décorateur de méthode qui peut être utilisé pour annoter une méthode de contrôleur.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* \@route("myroute", {
|
|
61
|
+
* path: "/add/{name}",
|
|
62
|
+
* method: ["GET", "POST"],
|
|
63
|
+
* defaults: { name: "john" },
|
|
64
|
+
* })
|
|
65
|
+
* method(name: string) {
|
|
66
|
+
* return this.renderJson({ name });
|
|
67
|
+
* }
|
|
68
|
+
*/
|
|
69
|
+
declare function route(name: string, options: RouteOptions): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
70
|
+
export declare const HTTP_CODE_METADATA = "route:httpCode";
|
|
71
|
+
export declare const HEADERS_METADATA = "route:responseHeaders";
|
|
72
|
+
export declare const REDIRECT_METADATA = "route:redirect";
|
|
73
|
+
export declare const PARAM_ARGS_METADATA = "route:paramArgs";
|
|
74
|
+
export declare const DOMAIN_CLASS_METADATA = "route:domainClass";
|
|
75
|
+
export declare const DOMAIN_METHOD_METADATA = "route:domainMethod";
|
|
76
|
+
export declare const BYPASS_FIREWALL_CLASS_METADATA = "route:bypassFirewallClass";
|
|
77
|
+
export declare const BYPASS_FIREWALL_METHOD_METADATA = "route:bypassFirewallMethod";
|
|
78
|
+
export declare const USE_SESSION_CLASS_METADATA = "session:useClass";
|
|
79
|
+
export declare const USE_SESSION_METHOD_METADATA = "session:useMethod";
|
|
80
|
+
export type ParamSource = "param" | "body" | "query" | "headers" | "cookie" | "session" | "req" | "res" | "file" | "files" | "user";
|
|
81
|
+
export interface ParamMeta {
|
|
82
|
+
source: ParamSource;
|
|
83
|
+
key?: string;
|
|
84
|
+
index: number;
|
|
85
|
+
/**
|
|
86
|
+
* P2.9 — `@Body({ stream: true })` : injecte le **flux brut** de la requête
|
|
87
|
+
* (`IncomingMessage`, un `Readable`) au lieu du body parsé en mémoire. Permet
|
|
88
|
+
* de piper un gros upload (vidéo, backup) vers disque/S3 sans pic RAM. Le
|
|
89
|
+
* pipeline saute le parse busboy/JSON pour la route concernée (cf
|
|
90
|
+
* `routeExpectsBodyStream` + `handleHttp`).
|
|
91
|
+
*/
|
|
92
|
+
stream?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface RedirectMeta {
|
|
95
|
+
url: string;
|
|
96
|
+
statusCode: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Une clause `@IsGranted` : un OU plusieurs attributs (OR interne), sujet
|
|
100
|
+
* optionnel. `@IsGranted("ROLE_ADMIN")` → `{ anyOf: ["ROLE_ADMIN"] }` ;
|
|
101
|
+
* `@IsGranted(["A","B"])` → `{ anyOf: ["A","B"] }` (un seul suffit) ;
|
|
102
|
+
* `@IsGranted("doc.edit", { subject: "id" })` → le param de route `id` est passé
|
|
103
|
+
* au voter.
|
|
104
|
+
*/
|
|
105
|
+
export interface SecurityClause {
|
|
106
|
+
/** Attributs en OR — un seul accordé suffit pour valider la clause. */
|
|
107
|
+
readonly anyOf: readonly string[];
|
|
108
|
+
/** Nom du paramètre de route passé comme `subject` au voter (optionnel). */
|
|
109
|
+
readonly subjectParam?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Exigence d'autorisation **figée** d'une action — calculée UNE fois par route
|
|
113
|
+
* (fusion classe + méthode) puis gelée sur `RouteActionMeta.security`. Objet
|
|
114
|
+
* PARTAGÉ entre toutes les requêtes : ne jamais muter. `null` (hors de ce type)
|
|
115
|
+
* = aucune garde → coût nul sur le hot path.
|
|
116
|
+
*
|
|
117
|
+
* Plusieurs `@IsGranted` empilés = `clauses` multiples en **AND** (toutes doivent
|
|
118
|
+
* passer). `@Anonymous()` ne produit jamais de `SecurityRequirement` (l'action
|
|
119
|
+
* devient `security: null` = publique).
|
|
120
|
+
*/
|
|
121
|
+
export interface SecurityRequirement {
|
|
122
|
+
/** Clauses en AND — toutes doivent être accordées (chacune est un OR interne). */
|
|
123
|
+
readonly clauses: readonly SecurityClause[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Directives CSP additionnelles déclarées par une action (`@Csp`) :
|
|
127
|
+
* `directive → sources` (ex. `{ "frame-src": ["https://youtube.com"] }`).
|
|
128
|
+
* Structurellement compatible avec `CspFragment` (@nodefony/security) — aucun
|
|
129
|
+
* import cross-module (0 cycle). Mergé additivement dans le CSP de la réponse
|
|
130
|
+
* par le firewall, UNIQUEMENT sur les routes qui en déclarent (cold path).
|
|
131
|
+
*/
|
|
132
|
+
export type CspDirectives = Record<string, readonly string[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Configuration d'idempotence figée d'une action (`@Idempotent`) — calculée une
|
|
135
|
+
* fois par route (fusion classe + méthode) puis gelée sur `RouteActionMeta.idempotent`.
|
|
136
|
+
* `null` (hors de ce type) = action non idempotentée → 0 coût hot path.
|
|
137
|
+
*/
|
|
138
|
+
export interface IdempotentMeta {
|
|
139
|
+
/**
|
|
140
|
+
* Mode STRICT : une mutation sans `Idempotency-Key` est rejetée (400). Défaut
|
|
141
|
+
* `true` (`@Idempotent()`). `@Idempotent({ required: false })` = mode souple
|
|
142
|
+
* (honore la clé si présente, exécute sinon) — sauf en WS, toujours strict.
|
|
143
|
+
*/
|
|
144
|
+
readonly required: boolean;
|
|
145
|
+
}
|
|
146
|
+
type MethodDecoratorOptions = Omit<RouteOptions, "path" | "method">;
|
|
147
|
+
declare const Get: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
148
|
+
declare const Post: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
149
|
+
declare const Put: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
150
|
+
declare const Delete: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
151
|
+
declare const Patch: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
152
|
+
declare const Options: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
153
|
+
declare const Head: (path?: string, options?: MethodDecoratorOptions) => (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
154
|
+
/**
|
|
155
|
+
* `@All` — route sans restriction de méthode : matche **toutes** les méthodes
|
|
156
|
+
* HTTP (équivalent NestJS `@All()`). N'émet aucun requirement `methods`, donc
|
|
157
|
+
* `Route.matchRequirements` ne lève jamais 405 sur la méthode.
|
|
158
|
+
*/
|
|
159
|
+
declare function All(path?: string, options?: MethodDecoratorOptions): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
160
|
+
/**
|
|
161
|
+
* Fixe le code de statut HTTP de la réponse d'une action.
|
|
162
|
+
*
|
|
163
|
+
* Décorateur de **méthode**. Le code est posé sur la réponse **avant** que le
|
|
164
|
+
* corps de l'action ne s'exécute (`Resolver._applyResponseMeta`) : l'action
|
|
165
|
+
* garde donc le dernier mot et peut encore le remplacer. Emploie-le pour le
|
|
166
|
+
* statut nominal d'une action — un 201 sur une création — et non pour un statut
|
|
167
|
+
* qui dépend du résultat. La métadonnée n'est lue qu'une fois par route, puis
|
|
168
|
+
* mémorisée : le décorateur ne coûte rien par requête.
|
|
169
|
+
*
|
|
170
|
+
* @param statusCode - Code HTTP appliqué à la réponse (201, 204, 202…).
|
|
171
|
+
* @returns Le décorateur de méthode.
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* @route("item-create", { path: "/items", method: "POST" })
|
|
175
|
+
* @HttpCode(201)
|
|
176
|
+
* async create() {
|
|
177
|
+
* return this.renderJson({ id: 42 });
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
declare function HttpCode(statusCode: number): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
182
|
+
/**
|
|
183
|
+
* Ajoute un en-tête à la réponse d'une action.
|
|
184
|
+
*
|
|
185
|
+
* Décorateur de **méthode**, empilable : chaque application ajoute une entrée,
|
|
186
|
+
* la dernière l'emportant sur un même nom d'en-tête. Les en-têtes sont posés
|
|
187
|
+
* avant l'exécution du corps de l'action, qui peut donc encore les modifier.
|
|
188
|
+
* Réserve-le aux en-têtes constants d'une action ; ce qui dépend de la requête
|
|
189
|
+
* s'écrit dans le corps.
|
|
190
|
+
*
|
|
191
|
+
* @param key - Nom de l'en-tête.
|
|
192
|
+
* @param value - Valeur de l'en-tête.
|
|
193
|
+
* @returns Le décorateur de méthode.
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* @route("feed", { path: "/feed", method: "GET" })
|
|
197
|
+
* @Header("Cache-Control", "public, max-age=3600")
|
|
198
|
+
* async feed() {
|
|
199
|
+
* return this.renderJson(items);
|
|
200
|
+
* }
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
declare function Header(key: string, value: string): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
204
|
+
/**
|
|
205
|
+
* Redirige la réponse d'une action vers une autre URL.
|
|
206
|
+
*
|
|
207
|
+
* Décorateur de **méthode**. ⚠️ Le corps de l'action **est exécuté** : la
|
|
208
|
+
* redirection est portée à côté du résultat et appliquée après coup par le
|
|
209
|
+
* `Resolver`. Ce n'est donc pas un court-circuit — tout effet de bord écrit dans
|
|
210
|
+
* l'action a bien lieu. L'action peut d'ailleurs surcharger la cible ou le code
|
|
211
|
+
* en renvoyant sa propre redirection.
|
|
212
|
+
*
|
|
213
|
+
* @param url - URL cible, absolue ou relative à l'application.
|
|
214
|
+
* @param statusCode - Code HTTP de redirection. Défaut `302` (temporaire) ;
|
|
215
|
+
* `301` pour un déplacement permanent, `307` pour conserver la méthode.
|
|
216
|
+
* @returns Le décorateur de méthode.
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* @route("legacy", { path: "/old-path", method: "GET" })
|
|
220
|
+
* @Redirect("/new-path", 301)
|
|
221
|
+
* async oldPath() {}
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
declare function Redirect(url: string, statusCode?: number): (target: object, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
225
|
+
/**
|
|
226
|
+
* Restreint une route (décorateur de **méthode**) ou tout un contrôleur
|
|
227
|
+
* (décorateur de **classe**) à un ou plusieurs vhosts. Source de vérité du
|
|
228
|
+
* domaine de routing — le `host` posé ici alimente `Route.host`, compilé en
|
|
229
|
+
* RegExp ancrée/wildcard (matcher partagé `@nodefony/http`). Domaine non servi
|
|
230
|
+
* par la route → 403.
|
|
231
|
+
*
|
|
232
|
+
* Précédence : `@route({ host })` > `@Domain` méthode > `@Domain` classe.
|
|
233
|
+
*
|
|
234
|
+
* Pattern : exact (`"marseille.fr"`) ou wildcard un-label (`"*.cdn.nodefony.com"`).
|
|
235
|
+
*
|
|
236
|
+
* ⚠️ En décorateur de **classe**, placer `@Domain` SOUS `@controller` : les
|
|
237
|
+
* décorateurs de classe s'appliquent de bas en haut, et `@controller` construit
|
|
238
|
+
* les routes — il doit voir le domaine de classe déjà posé.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* \@controller("/")
|
|
242
|
+
* \@Domain("marseille.fr")
|
|
243
|
+
* class MarseilleController extends Controller {
|
|
244
|
+
* \@Get("/") home() {} // marseille.fr/ → OK ; nodefony.com/ → 403
|
|
245
|
+
* }
|
|
246
|
+
*/
|
|
247
|
+
declare function Domain(patterns: string | string[]): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
248
|
+
/**
|
|
249
|
+
* Déclare une route (décorateur de **méthode**) ou tout un contrôleur
|
|
250
|
+
* (décorateur de **classe**) comme **PUBLIQUE** : le firewall ne s'exécute pas
|
|
251
|
+
* (`Route.bypassFirewall`). Pour la **liveness** (`/health`, `/info` — sondes
|
|
252
|
+
* k8s/monitoring NON authentifiées, ping pré-login), les **webhooks signés**, ou
|
|
253
|
+
* un endpoint d'auth (login). Sucre déclaratif sur l'option
|
|
254
|
+
* `RouteOptions.bypassFirewall` (les deux coexistent ; l'option l'emporte).
|
|
255
|
+
*
|
|
256
|
+
* Précédence : `@Get({ bypassFirewall })` > `@BypassFirewall` méthode > classe.
|
|
257
|
+
* Lu au montage `@controller` (ordre des décorateurs indifférent). **Fail-closed** :
|
|
258
|
+
* un oubli laisse la route GATÉE (401), jamais ouverte par erreur.
|
|
259
|
+
*
|
|
260
|
+
* ⚠️ En décorateur de **classe**, placer `@BypassFirewall` SOUS `@controller`
|
|
261
|
+
* (décorateurs de classe appliqués de bas en haut). Préfigure `@Public`/
|
|
262
|
+
* `@Anonymous` (P6.8b) — sémantique « pas d'auth », qui s'appuiera sur ce primitif.
|
|
263
|
+
*
|
|
264
|
+
* Décorateur SANS argument → **simple, SANS parenthèses** (`@BypassFirewall`,
|
|
265
|
+
* pas `@BypassFirewall()`) : c'est un DRAPEAU, pas une option paramétrée. Une
|
|
266
|
+
* factory (`()`) ne se justifie que pour passer des arguments (cf `@Get("/x")`,
|
|
267
|
+
* `@Domain("host")`).
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* \@controller("/nodefony")
|
|
271
|
+
* class StudioController extends Controller {
|
|
272
|
+
* \@BypassFirewall
|
|
273
|
+
* \@Get("/studio/api/health") health() {} // public (liveness)
|
|
274
|
+
* \@Get("/studio/api/stats") stats() {} // gaté par l'aire data plane
|
|
275
|
+
* }
|
|
276
|
+
*/
|
|
277
|
+
declare function BypassFirewall(target: any, propertyKey?: string, descriptor?: PropertyDescriptor): any;
|
|
278
|
+
/**
|
|
279
|
+
* Déclare le scope d'instanciation d'un controller (V4.3) — pose le statique
|
|
280
|
+
* `scope` de la classe (hérité de `Controller`, défaut `"request"`). Lu par le
|
|
281
|
+
* constructor de `Controller` (`new.target`) et par le `Resolver` : 0 Reflect.
|
|
282
|
+
*
|
|
283
|
+
* `@Scope("singleton")` : UNE instance partagée par toutes les requêtes
|
|
284
|
+
* (cache kernel-scoped sur le Router, `initialize()` appelé 1× à la création).
|
|
285
|
+
* **Contrat stateless strict** : l'action ne lit/n'écrit AUCUN état par requête
|
|
286
|
+
* sur `this` — tout passe par les arguments décorés (`@Param`/`@Body`…) et les
|
|
287
|
+
* helpers, qui retrouvent la requête courante via l'ALS (V4.1). Un champ muté
|
|
288
|
+
* par requête sur un singleton = data race silencieuse entre deux requêtes
|
|
289
|
+
* concurrentes. Le défaut per-request reste inchangé (0 breaking legacy).
|
|
290
|
+
*
|
|
291
|
+
* ⚠️ Homonyme : le core `nodefony` exporte aussi `Scope` (le scope DI du
|
|
292
|
+
* `Container`) — celui-ci s'importe depuis `@nodefony/framework`.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* \@Scope("singleton")
|
|
296
|
+
* \@controller("/api/books")
|
|
297
|
+
* class BookController extends ResourceController { ... }
|
|
298
|
+
*/
|
|
299
|
+
declare function Scope(scope: ControllerScope): <T extends {
|
|
300
|
+
scope?: ControllerScope;
|
|
301
|
+
}>(target: T) => void;
|
|
302
|
+
/** Options déclaratives de `@UseSession` (= forme de l'intent runtime). */
|
|
303
|
+
export type UseSessionOptions = SessionIntent;
|
|
304
|
+
/**
|
|
305
|
+
* Déclare qu'une route (décorateur de **méthode**) ou tout un contrôleur
|
|
306
|
+
* (décorateur de **classe**) a besoin d'une **session serveur**. C'est l'unique
|
|
307
|
+
* façon d'activer une session (avec la reprise auto d'un cookie existant — L1) :
|
|
308
|
+
* il n'y a plus de `sessionAutoStart` global « démarre partout » (le moteur du
|
|
309
|
+
* ×23). Lazy par défaut — aucune session pour une route qui n'en déclare pas.
|
|
310
|
+
*
|
|
311
|
+
* Précédence : `@UseSession` méthode > `@UseSession` classe. La simple présence
|
|
312
|
+
* d'un paramètre `@Session` sur l'action suffit aussi (intent implicite).
|
|
313
|
+
*
|
|
314
|
+
* - `{ readOnly }` : session lue/reprise mais **jamais persistée** (0 write storage).
|
|
315
|
+
*
|
|
316
|
+
* ⚠️ En décorateur de **classe**, placer `@UseSession` SOUS `@controller`.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* \@controller("/account")
|
|
320
|
+
* \@UseSession()
|
|
321
|
+
* class AccountController extends Controller {
|
|
322
|
+
* \@Get("/me") @UseSession({ readOnly: true }) me() {} // lecture seule → 0 write
|
|
323
|
+
* }
|
|
324
|
+
*/
|
|
325
|
+
declare function UseSession(options?: UseSessionOptions): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
326
|
+
/**
|
|
327
|
+
* Résout l'intent de session effectif d'une action — lu par le `Resolver` au
|
|
328
|
+
* match, posé sur `context.sessionIntent`, consommé au point d'activation unique
|
|
329
|
+
* (`HttpKernel.startSession`). Combine `@UseSession` classe + méthode (méthode
|
|
330
|
+
* prioritaire) ; à défaut, un paramètre `@Session` déclare un intent implicite.
|
|
331
|
+
*
|
|
332
|
+
* @returns l'intent, ou `null` si la route ne requiert aucune session.
|
|
333
|
+
*/
|
|
334
|
+
declare function resolveSessionIntent(ctor: ControllerConstructor, actionName: string): SessionIntent | null;
|
|
335
|
+
/**
|
|
336
|
+
* Exige une autorisation pour l'action (décorateur de **méthode**) ou tout le
|
|
337
|
+
* contrôleur (décorateur de **classe**).
|
|
338
|
+
*
|
|
339
|
+
* - `@IsGranted("ROLE_ADMIN")` — un attribut (rôle `ROLE_*`, permission, ou
|
|
340
|
+
* attribut métier résolu par un voter).
|
|
341
|
+
* - `@IsGranted(["ROLE_ADMIN", "ROLE_AUDITOR"])` — **OR** : un seul suffit.
|
|
342
|
+
* - empiler plusieurs `@IsGranted` — **AND** : toutes les clauses doivent passer.
|
|
343
|
+
* - `@IsGranted("doc.edit", { subject: "id" })` — le paramètre de route `id` est
|
|
344
|
+
* passé comme `subject` au voter (ownership, multi-tenant).
|
|
345
|
+
*
|
|
346
|
+
* Classe + méthode fusionnent en AND. L'évaluation a lieu dans le `Resolver`
|
|
347
|
+
* AVANT l'instanciation du controller (403 court-circuite — Zero Trust). N'écrit
|
|
348
|
+
* QUE des métadonnées (zéro logique sécu ici → 0 import `@nodefony/security`,
|
|
349
|
+
* 0 cycle ; le moteur `authorization` est appelé par nom au runtime).
|
|
350
|
+
*/
|
|
351
|
+
declare function IsGranted(attribute: string | readonly string[], options?: {
|
|
352
|
+
subject?: string;
|
|
353
|
+
}): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
354
|
+
/**
|
|
355
|
+
* Déclare une action (méthode) ou un contrôleur (classe) **publique** : skip
|
|
356
|
+
* l'autorisation (override un `@IsGranted` de classe sur cette méthode) ET skip
|
|
357
|
+
* l'authentification (réutilise le mécanisme `@BypassFirewall` → pas de 401 en
|
|
358
|
+
* zone protégée). L'alias lisible de « permitAll » (mental model Spring). Pour un
|
|
359
|
+
* login, une sonde de liveness, une page publique d'un contrôleur par ailleurs
|
|
360
|
+
* protégé.
|
|
361
|
+
*/
|
|
362
|
+
declare function Anonymous(): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
363
|
+
/**
|
|
364
|
+
* Exige un **scope** (`api:action`) pour l'action (décorateur de **méthode**) ou
|
|
365
|
+
* tout le contrôleur (décorateur de **classe**). Axe d'autorisation **distinct des
|
|
366
|
+
* rôles** (`@IsGranted`) : un scope **downscope** un jeton MACHINE délégué (clé
|
|
367
|
+
* API, JWT d'agent, OAuth) — il est un **no-op** pour une session humaine, dont
|
|
368
|
+
* les droits sont portés par ses rôles (cf {@link ScopeVoter}).
|
|
369
|
+
*
|
|
370
|
+
* - `@RequireScope("orders:read")` — le jeton doit porter ce scope.
|
|
371
|
+
* - `@RequireScope(["orders:read", "orders:admin"])` — **OR** : un seul suffit.
|
|
372
|
+
* - empiler plusieurs `@RequireScope` — **AND** : tous les scopes requis.
|
|
373
|
+
*
|
|
374
|
+
* Convention d'espace **plat** `api:action` (modèle GitHub PAT classic) : le
|
|
375
|
+
* préfixe avant `:` EST l'API → la découverte au boot regroupe les scopes par API
|
|
376
|
+
* sans catalogue séparé. Classe + méthode fusionnent en AND, et fusionnent AUSSI
|
|
377
|
+
* avec les clauses `@IsGranted` dans le même `SecurityRequirement` (rôle ET scope).
|
|
378
|
+
*
|
|
379
|
+
* N'écrit QUE des métadonnées (zéro logique sécu ici → 0 import `@nodefony/security`,
|
|
380
|
+
* 0 cycle) : la décision est rendue par le `ScopeVoter` au runtime (par nom). La
|
|
381
|
+
* metadata est **dédiée** (≠ `@IsGranted`) pour que la découverte au boot puisse
|
|
382
|
+
* lister les scopes déclarés par route sans les confondre avec les rôles.
|
|
383
|
+
*/
|
|
384
|
+
declare function RequireScope(scope: string | readonly string[]): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
385
|
+
/**
|
|
386
|
+
* `@Csp({ "frame-src": [...] })` — déclare des directives CSP **additionnelles**
|
|
387
|
+
* pour l'action (méthode) ou tout le contrôleur (classe). Distinct de
|
|
388
|
+
* `registerCspOrigins` (besoins PERMANENTS d'un module, ex. Vite) : ici c'est le
|
|
389
|
+
* besoin ponctuel d'UNE réponse (embarquer une iframe YouTube, autoriser une CDN).
|
|
390
|
+
*
|
|
391
|
+
* Classe + méthode fusionnent additivement (sources concaténées par directive).
|
|
392
|
+
* Empiler plusieurs `@Csp` fusionne aussi. N'écrit QUE des métadonnées : le merge
|
|
393
|
+
* dans le CSP de la réponse est fait par le firewall, hors hot-path, UNIQUEMENT
|
|
394
|
+
* sur les routes décorées. Calque `@IsGranted` (0 import `@nodefony/security`).
|
|
395
|
+
*/
|
|
396
|
+
declare function Csp(directives: CspDirectives): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
397
|
+
/**
|
|
398
|
+
* `@CsrfProtect()` — opt-IN à la défense CSRF **synchronizer token** (double-submit
|
|
399
|
+
* signé HMAC) EN PLUS de la défense globale Fetch Metadata/Origin (étape 1, toujours
|
|
400
|
+
* active). Pour les mutations à haute valeur (changement de mot de passe, virement) :
|
|
401
|
+
* une requête sûre vers la route SÈME le cookie lisible `csrf-token` ; la mutation
|
|
402
|
+
* DOIT rejouer ce token dans l'en-tête `x-csrf-token` (sinon 403). Classe = toutes
|
|
403
|
+
* les actions. N'écrit qu'un marqueur (0 import `@nodefony/security`, 0 cycle).
|
|
404
|
+
*/
|
|
405
|
+
declare const CsrfProtect: () => (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
406
|
+
/**
|
|
407
|
+
* `@CsrfExempt()` — opt-OUT de la défense CSRF pour une route, **en conservant
|
|
408
|
+
* l'authentification et l'autorisation** (≠ `@Anonymous`/`@BypassFirewall` qui
|
|
409
|
+
* coupent l'auth). Pour un webhook ou une API recevant un POST cross-origin
|
|
410
|
+
* légitime, dont la requête est authentifiée autrement (signature HMAC du provider,
|
|
411
|
+
* clé API). Classe = toutes les actions. Marqueur seul (0 logique sécu ici).
|
|
412
|
+
*/
|
|
413
|
+
declare const CsrfExempt: () => (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
414
|
+
/**
|
|
415
|
+
* `@Idempotent()` — protège une **mutation** (POST/PUT/PATCH/DELETE) d'un
|
|
416
|
+
* controller userland contre le double-effet d'un rejeu (double-clic, reconnexion
|
|
417
|
+
* socket, retry réseau), via une `Idempotency-Key` cliente (modèle Stripe, conforme
|
|
418
|
+
* `draft-ietf-httpapi-idempotency-key-header`). No-op sur les méthodes sûres (GET…).
|
|
419
|
+
*
|
|
420
|
+
* - **STRICT par défaut** : une mutation SANS clé est rejetée **400** (draft §2.7).
|
|
421
|
+
* - `@Idempotent({ required: false })` : mode **souple** — honore la clé si fournie,
|
|
422
|
+
* exécute sinon. (Une mutation par **socket** reste toujours strict : le WS rejoue.)
|
|
423
|
+
* - clé fournie → dédup complète : rejeu complété → réponse **mémorisée** ; rejeu
|
|
424
|
+
* concurrent → **409** ; même clé + payload différent → **422**.
|
|
425
|
+
*
|
|
426
|
+
* Décorateur de **méthode** (une action) ou de **classe** (toutes les mutations du
|
|
427
|
+
* controller). Précédence : méthode > classe (comme `@UseSession`). N'écrit QUE des
|
|
428
|
+
* métadonnées (0 import `@nodefony/security`, 0 cycle) ; la porte est appliquée par
|
|
429
|
+
* le `Resolver` (helper partagé `idempotency.ts`, le MÊME que le data plane admin),
|
|
430
|
+
* sur le `idempotencyStore` DI. Coût nul sur une route non décorée (`security: null`).
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* \@controller("/api/orders")
|
|
434
|
+
* class OrderController extends Controller {
|
|
435
|
+
* \@Post("/") @Idempotent() create(@Body() dto: CreateOrder) { ... } // clé obligatoire
|
|
436
|
+
* \@Patch("/{id}") @Idempotent({ required: false }) update() { ... } // clé optionnelle (HTTP)
|
|
437
|
+
* }
|
|
438
|
+
*/
|
|
439
|
+
declare function Idempotent(options?: {
|
|
440
|
+
required?: boolean;
|
|
441
|
+
}): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
442
|
+
declare const Param: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
443
|
+
declare const Query: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
444
|
+
/**
|
|
445
|
+
* Décorateur de paramètre `@Body` :
|
|
446
|
+
* - `@Body()` → body parsé entier · `@Body("field")` → un champ du body parsé.
|
|
447
|
+
* - `@Body({ stream: true })` → **flux brut** de la requête (`Readable`), sans
|
|
448
|
+
* parse en mémoire (P2.9 — gros uploads sans pic RAM ; le pipeline saute le
|
|
449
|
+
* parse busboy/JSON pour cette route).
|
|
450
|
+
*
|
|
451
|
+
* ## Le corps n'est PAS validé — le type écrit ici ne promet rien
|
|
452
|
+
*
|
|
453
|
+
* `@Body()` injecte le corps **tel qu'il a été parsé** : `@Body() dto: CreateOrder`
|
|
454
|
+
* compile, mais rien ne garantit qu'un `CreateOrder` soit arrivé. C'est un choix
|
|
455
|
+
* assumé, pas un oubli — valider ici ne garderait que la porte HTTP, et devrait
|
|
456
|
+
* rester **synchrone** ({@link resolveParamArg} l'est, et le rendre asynchrone
|
|
457
|
+
* taxerait toute requête à paramètres décorés).
|
|
458
|
+
*
|
|
459
|
+
* Où valider, donc :
|
|
460
|
+
*
|
|
461
|
+
* - **Une entité** → les hooks `beforeCreate` / `beforeUpdate` d'
|
|
462
|
+
* `AbstractCrudService` : ils sont `await`és — donc une règle asynchrone
|
|
463
|
+
* (unicité en base) y est possible — et ils gardent REST, WebSocket **et** la
|
|
464
|
+
* CLI d'un seul geste. C'est ce que génère `nodefony create entity`.
|
|
465
|
+
* - **Un cas isolé** → `schema.parse(body)` en première ligne de l'action, sur le
|
|
466
|
+
* modèle d'`assertPageQuery`. Rien d'autre à écrire : une `ZodError` qui remonte
|
|
467
|
+
* devient un **422** (RFC 9110 §15.5.21) portant `error.fields` — quel champ,
|
|
468
|
+
* quel message, quelle règle.
|
|
469
|
+
*
|
|
470
|
+
* Et typer le paramètre avec `z.infer<typeof createXSchema>` plutôt qu'avec
|
|
471
|
+
* `Partial<XRow>` : le premier décrit le contrat d'**entrée**, le second promet la
|
|
472
|
+
* ligne de **table** (`id`, horodatages) que le schéma effacera de toute façon.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* \@Post("/")
|
|
477
|
+
* async create(\@Body() payload: CreatePost) {
|
|
478
|
+
* return this.createResource(payload); // le service valide dans beforeCreate
|
|
479
|
+
* }
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
declare function Body(keyOrOptions?: string | {
|
|
483
|
+
stream?: boolean;
|
|
484
|
+
}): (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
485
|
+
declare const Headers: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
486
|
+
declare const Cookie: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
487
|
+
declare const Session: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
488
|
+
/** `@CurrentUser() user: IUser` — injecte l'utilisateur de l'ALS (jamais le credential). */
|
|
489
|
+
declare const CurrentUser: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
490
|
+
declare const Req: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
491
|
+
declare const Res: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
492
|
+
declare const UploadedFile: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
493
|
+
declare const UploadedFiles: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
494
|
+
/**
|
|
495
|
+
* Contexte structurel minimal nécessaire pour résoudre les arguments injectés
|
|
496
|
+
* par les décorateurs de paramètre. Volontairement découplé de `HttpContext`
|
|
497
|
+
* (typage par forme) → la résolution est une fonction pure, testable en unit
|
|
498
|
+
* avec un faux contexte, sans démarrer de serveur. Le `Resolver` lui passe le
|
|
499
|
+
* vrai `Context`, qui satisfait cette forme.
|
|
500
|
+
*/
|
|
501
|
+
export interface IParamArgContext {
|
|
502
|
+
/** Variables de route extraites du path (`{name}` → valeur). */
|
|
503
|
+
paramsMap: Record<string, unknown>;
|
|
504
|
+
/**
|
|
505
|
+
* Query de l'invocation courante quand elle ne vient PAS de l'URL du
|
|
506
|
+
* transport — pont WS-RPC `api.request` (`Resolver.queryOverride`). Prime
|
|
507
|
+
* sur `request.queryGet` pour `@Query` uniquement (`@Req` reste le brut).
|
|
508
|
+
*/
|
|
509
|
+
queryOverride?: Record<string, unknown>;
|
|
510
|
+
request?: {
|
|
511
|
+
queryGet?: Record<string, unknown>;
|
|
512
|
+
queryPost?: Record<string, unknown>;
|
|
513
|
+
queryFile?: unknown[];
|
|
514
|
+
headers?: Record<string, unknown>;
|
|
515
|
+
/**
|
|
516
|
+
* P2.9 — `IncomingMessage` brut (un `Readable`) sous-jacent au wrapper
|
|
517
|
+
* `HttpRequest`. Injecté tel quel par `@Body({ stream: true })` (le pipeline
|
|
518
|
+
* n'a pas consommé/parsé ce flux). `undefined` pour les contextes WS.
|
|
519
|
+
*/
|
|
520
|
+
request?: NodeJS.ReadableStream;
|
|
521
|
+
} | null;
|
|
522
|
+
response?: unknown;
|
|
523
|
+
session?: {
|
|
524
|
+
get(key: string): unknown;
|
|
525
|
+
} | null;
|
|
526
|
+
getRequestCookies(name?: string): unknown;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Résout la valeur d'un unique paramètre décoré depuis le contexte de requête.
|
|
530
|
+
*
|
|
531
|
+
* @param meta - métadonnée posée par le décorateur (source + clé optionnelle)
|
|
532
|
+
* @param ctx - contexte de requête (forme structurelle minimale)
|
|
533
|
+
* @returns la valeur à injecter dans l'argument `meta.index` de l'action
|
|
534
|
+
*/
|
|
535
|
+
declare function resolveParamArg(meta: ParamMeta, ctx: IParamArgContext): unknown;
|
|
536
|
+
/**
|
|
537
|
+
* Construit le tableau d'arguments d'une action à partir des métadonnées de
|
|
538
|
+
* paramètres décorés. Chaque valeur est placée à son `index` déclaré (les trous
|
|
539
|
+
* restent `undefined`). Fonction pure — aucun effet de bord, aucune I/O.
|
|
540
|
+
*
|
|
541
|
+
* @param metas - métadonnées de tous les paramètres décorés de l'action
|
|
542
|
+
* @param ctx - contexte de requête (forme structurelle minimale)
|
|
543
|
+
* @returns arguments positionnels à spread dans l'action
|
|
544
|
+
*/
|
|
545
|
+
declare function buildParamArgs(metas: ParamMeta[], ctx: IParamArgContext): unknown[];
|
|
546
|
+
/**
|
|
547
|
+
* P2.9 — Indique si l'action d'une route attend le **flux brut** du body
|
|
548
|
+
* (un paramètre `@Body({ stream:true })`). Le résultat est **mémoïsé** sur
|
|
549
|
+
* `route.bodyStream` : lecture `Reflect` au 1er appel, O(1) ensuite → 0 coût
|
|
550
|
+
* hot-path. Lu **en amont** par `handleHttp` (avant le parse) pour décider de
|
|
551
|
+
* sauter le parse busboy/JSON. Typage structurel (pas d'import `Route` → 0 cycle).
|
|
552
|
+
*
|
|
553
|
+
* @param routeDef - route résolue (porte `controller` + `classMethod` à `onBoot`).
|
|
554
|
+
* @returns `true` si l'action déclare un `@Body({ stream:true })`.
|
|
555
|
+
*/
|
|
556
|
+
declare function routeExpectsBodyStream(routeDef: {
|
|
557
|
+
controller?: {
|
|
558
|
+
prototype: object;
|
|
559
|
+
} | null;
|
|
560
|
+
classMethod?: string;
|
|
561
|
+
bodyStream?: boolean;
|
|
562
|
+
}): boolean;
|
|
563
|
+
/**
|
|
564
|
+
* Snapshot des métadonnées d'action d'une route (`@HttpCode`/`@Header`/
|
|
565
|
+
* `@Redirect`/paramètres décorés/intent de session), résolu UNE fois par route
|
|
566
|
+
* puis figé sur `route.actionMeta` (cf {@link resolveActionMeta}). Objet
|
|
567
|
+
* **PARTAGÉ entre toutes les requêtes** de la route — ne jamais muter.
|
|
568
|
+
*/
|
|
569
|
+
export interface RouteActionMeta {
|
|
570
|
+
/** Paramètres décorés (`@Param`/`@Body`/`@Query`…) — `null` si aucun. */
|
|
571
|
+
paramsMeta: ParamMeta[] | null;
|
|
572
|
+
/** `@Redirect` de l'action — `null` si absent. */
|
|
573
|
+
redirectMeta: RedirectMeta | null;
|
|
574
|
+
/** `@HttpCode` de l'action — `null` si absent. */
|
|
575
|
+
httpCode: number | null;
|
|
576
|
+
/** Entrées `@Header` pré-dépliées (`Object.entries` fait 1×) — `null` si aucune. */
|
|
577
|
+
headerEntries: [string, string][] | null;
|
|
578
|
+
/** Intent `@UseSession`/`@Session` — `null` si la route n'en déclare pas. */
|
|
579
|
+
sessionIntent: SessionIntent | null;
|
|
580
|
+
/**
|
|
581
|
+
* Exigence d'autorisation (`@IsGranted`, fusion classe+méthode) — `null` si
|
|
582
|
+
* l'action n'est pas gardée (ou `@Anonymous`). `null` = **0 coût** sur le hot
|
|
583
|
+
* path (ni résolution de service, ni `decide`, ni await). Frozen, partagé.
|
|
584
|
+
*/
|
|
585
|
+
security: SecurityRequirement | null;
|
|
586
|
+
/**
|
|
587
|
+
* Directives CSP additionnelles (`@Csp`, fusion classe+méthode) — `null` si
|
|
588
|
+
* l'action n'en déclare pas (cas courant → 0 composition CSP). Frozen, partagé.
|
|
589
|
+
*/
|
|
590
|
+
cspDirectives: CspDirectives | null;
|
|
591
|
+
/** `@CsrfProtect` (classe ou méthode) → exige le synchronizer token sur la mutation. */
|
|
592
|
+
csrfProtect: boolean;
|
|
593
|
+
/** `@CsrfExempt` (classe ou méthode) → la route est hors défense CSRF (auth conservée). */
|
|
594
|
+
csrfExempt: boolean;
|
|
595
|
+
/**
|
|
596
|
+
* Idempotence (`@Idempotent`, fusion classe + méthode) — `null` si l'action
|
|
597
|
+
* n'est pas décorée (cas courant → **0 coût** sur le hot path : ni résolution de
|
|
598
|
+
* store, ni `begin`). Frozen, partagé entre requêtes.
|
|
599
|
+
*/
|
|
600
|
+
idempotent: IdempotentMeta | null;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* P6.8 — Liste PLATE des scopes `api:action` déclarés par une action
|
|
604
|
+
* (`@RequireScope`, classe + méthode), **dédupliqués**. Source de la **découverte
|
|
605
|
+
* au boot** : le catalogue de scopes du formulaire de clés API se construit en
|
|
606
|
+
* scannant les routes (cf `collectDeclaredApiScopes`), au lieu d'une config plate
|
|
607
|
+
* qui dérive du code. Lecture `Reflect` directe — **cold path** (introspection à la
|
|
608
|
+
* demande, jamais sur le hot path requête). `[]` si l'action ne déclare aucun scope.
|
|
609
|
+
*/
|
|
610
|
+
declare function extractActionScopes(ctor: {
|
|
611
|
+
prototype: object;
|
|
612
|
+
}, method: string): string[];
|
|
613
|
+
declare function computeActionMeta(ctor?: {
|
|
614
|
+
prototype: object;
|
|
615
|
+
} | null, method?: string): RouteActionMeta;
|
|
616
|
+
/**
|
|
617
|
+
* P5 — Metadata d'action d'une route, **mémoïsées au 1er hit** sur
|
|
618
|
+
* `route.actionMeta` (pattern frère de {@link routeExpectsBodyStream}) :
|
|
619
|
+
* `undefined` = pas encore résolu → 1 lecture `Reflect` par route pour la vie
|
|
620
|
+
* du process, O(1) ensuite. Sort `Reflect.getMetadata` (~6 appels/req) du hot
|
|
621
|
+
* path `match`/`executeAction`. Posé APRÈS `generateId()` (1ʳᵉ requête) → le
|
|
622
|
+
* hash de route et l'introspection Studio restent stables. Typage structurel
|
|
623
|
+
* (pas d'import `Route` → 0 cycle).
|
|
624
|
+
*/
|
|
625
|
+
declare function resolveActionMeta(routeDef: {
|
|
626
|
+
controller?: {
|
|
627
|
+
prototype: object;
|
|
628
|
+
} | null;
|
|
629
|
+
classMethod?: string;
|
|
630
|
+
actionMeta?: RouteActionMeta;
|
|
631
|
+
}): RouteActionMeta;
|
|
632
|
+
export { route, controller, controllers, Get, Post, Put, Delete, Patch, Options, Head, All, Domain, BypassFirewall, IsGranted, RequireScope, Anonymous, Csp, CsrfProtect, CsrfExempt, Idempotent, CurrentUser, Scope, UseSession, resolveSessionIntent, HttpCode, Header, Redirect, Param, Body, Query, Headers, Cookie, Session, Req, Res, UploadedFile, UploadedFiles, resolveParamArg, buildParamArgs, routeExpectsBodyStream, computeActionMeta, resolveActionMeta, extractActionScopes, };
|