@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
|
@@ -70,8 +70,8 @@ function parseFrontmatter(raw) {
|
|
|
70
70
|
}
|
|
71
71
|
/** Premier titre `# H1` du corps markdown, ou `null`. */
|
|
72
72
|
function firstHeading(body) {
|
|
73
|
-
const
|
|
74
|
-
return
|
|
73
|
+
const title = body.match(/^#\s+(.+)$/m)?.[1];
|
|
74
|
+
return title === void 0 ? null : title.trim();
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Date ISO du dernier commit git d'un fichier (détecte la dérive doc↔code).
|
|
@@ -297,19 +297,22 @@ async function searchModuleDocs(targets, query, options = {}) {
|
|
|
297
297
|
}
|
|
298
298
|
score += occurrences / Math.max(1, foldedBody.length / DOC_LENGTH_PIVOT);
|
|
299
299
|
const candidates = [];
|
|
300
|
-
for (
|
|
301
|
-
const carried = terms.filter((t) =>
|
|
302
|
-
|
|
300
|
+
for (const [i, folded] of foldedLines.entries()) {
|
|
301
|
+
const carried = terms.filter((t) => folded.includes(t));
|
|
302
|
+
const [matchedTerm] = carried;
|
|
303
|
+
if (matchedTerm === void 0) continue;
|
|
303
304
|
candidates.push({
|
|
304
305
|
lineNumber: i,
|
|
306
|
+
line: lines[i] ?? "",
|
|
307
|
+
folded,
|
|
305
308
|
coverage: carried.length,
|
|
306
|
-
matchedTerm
|
|
309
|
+
matchedTerm
|
|
307
310
|
});
|
|
308
311
|
}
|
|
309
312
|
candidates.sort((a, b) => b.coverage - a.coverage || a.lineNumber - b.lineNumber);
|
|
310
313
|
const matches = candidates.slice(0, perDoc).sort((a, b) => a.lineNumber - b.lineNumber).map((c) => ({
|
|
311
314
|
line: c.lineNumber + 1,
|
|
312
|
-
text: snippet(
|
|
315
|
+
text: snippet(c.line, c.folded, c.matchedTerm)
|
|
313
316
|
}));
|
|
314
317
|
hits.push({
|
|
315
318
|
module: target.key,
|
|
@@ -414,10 +417,10 @@ async function typeFiles(modulePath) {
|
|
|
414
417
|
*/
|
|
415
418
|
function declarationStart(lines, at) {
|
|
416
419
|
let start = at;
|
|
417
|
-
if (
|
|
420
|
+
if (lines[start - 1]?.trim().endsWith("*/")) {
|
|
418
421
|
let i = start - 1;
|
|
419
|
-
while (i > 0 && !lines[i]
|
|
420
|
-
if (lines[i]
|
|
422
|
+
while (i > 0 && !lines[i]?.trim().startsWith("/**")) i -= 1;
|
|
423
|
+
if (lines[i]?.trim().startsWith("/**")) start = i;
|
|
421
424
|
}
|
|
422
425
|
return start;
|
|
423
426
|
}
|
|
@@ -457,14 +460,15 @@ async function readSymbolDeclaration(modulePath, symbolName) {
|
|
|
457
460
|
let depth = 0;
|
|
458
461
|
let opened = false;
|
|
459
462
|
let end = at;
|
|
460
|
-
for (
|
|
461
|
-
|
|
463
|
+
for (const [offset, line] of lines.slice(at).entries()) {
|
|
464
|
+
const i = at + offset;
|
|
465
|
+
for (const ch of line) if (ch === "{") {
|
|
462
466
|
depth += 1;
|
|
463
467
|
opened = true;
|
|
464
468
|
} else if (ch === "}") depth -= 1;
|
|
465
469
|
end = i;
|
|
466
470
|
if (opened && depth <= 0) break;
|
|
467
|
-
if (!opened &&
|
|
471
|
+
if (!opened && line.trimEnd().endsWith(";")) break;
|
|
468
472
|
}
|
|
469
473
|
const declaration = lines.slice(start, end + 1).join("\n");
|
|
470
474
|
return declaration.length > DECLARATION_MAX_CHARS ? {
|
|
@@ -720,16 +724,16 @@ function runModuleTests(modulePath, file) {
|
|
|
720
724
|
mode
|
|
721
725
|
});
|
|
722
726
|
}
|
|
723
|
-
child.stdout
|
|
724
|
-
child.stderr
|
|
727
|
+
child.stdout.on("data", cap);
|
|
728
|
+
child.stderr.on("data", cap);
|
|
725
729
|
const timer = setTimeout(() => child.kill("SIGKILL"), 18e4);
|
|
726
730
|
let settled = false;
|
|
727
731
|
const settle = (result) => {
|
|
728
732
|
if (settled) return;
|
|
729
733
|
settled = true;
|
|
730
734
|
clearTimeout(timer);
|
|
731
|
-
child.stdout
|
|
732
|
-
child.stderr
|
|
735
|
+
child.stdout.off("data", cap);
|
|
736
|
+
child.stderr.off("data", cap);
|
|
733
737
|
resolve(result);
|
|
734
738
|
};
|
|
735
739
|
child.on("error", (e) => {
|
|
@@ -894,7 +898,7 @@ async function readCoverage(modulePath) {
|
|
|
894
898
|
} catch {}
|
|
895
899
|
}
|
|
896
900
|
if (!report) return { available: false };
|
|
897
|
-
report.files
|
|
901
|
+
report.files?.sort((a, b) => a.file.localeCompare(b.file));
|
|
898
902
|
try {
|
|
899
903
|
report.generated = usedFile ? (await stat(usedFile)).mtime.toISOString() : null;
|
|
900
904
|
} catch {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region nodefony/src/singletonGuard.ts
|
|
2
|
+
/**
|
|
3
|
+
* Filets d'un contrôleur **singleton** — le défaut : UNE instance par classe,
|
|
4
|
+
* partagée par toutes les requêtes, concurrentes comprises.
|
|
5
|
+
*
|
|
6
|
+
* Un singleton ne peut porter aucun état de requête sur `this` : la valeur
|
|
7
|
+
* écrite par une requête serait lue par la suivante — une fuite de données
|
|
8
|
+
* entre utilisateurs, sans la moindre erreur. Deux filets la rendent visible :
|
|
9
|
+
*
|
|
10
|
+
* - les accesseurs d'état par requête de `Controller` (`query`, `request`,
|
|
11
|
+
* `context`…) refusent l'écriture sur un singleton, dans tous les
|
|
12
|
+
* environnements (coût nul : ils ne sont jamais écrits sur le chemin chaud) ;
|
|
13
|
+
* - en développement seulement, {@link guardSingletonState} fait lever toute
|
|
14
|
+
* écriture sur un champ de l'instance, une fois sa construction et son
|
|
15
|
+
* `initialize()` terminés.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* `true` si l'environnement du kernel est celui du développement.
|
|
21
|
+
*
|
|
22
|
+
* Une seule définition pour tout le paquet : la sonde CSP de `Controller` et le
|
|
23
|
+
* filet des singletons s'allument sur le même critère.
|
|
24
|
+
*
|
|
25
|
+
* @param environment - `kernel.environment`, tel quel.
|
|
26
|
+
* @returns `true` en développement (`"development"` ou son alias `"dev"`).
|
|
27
|
+
*/
|
|
28
|
+
function isDevelopment(environment) {
|
|
29
|
+
return environment === "development" || environment === "dev";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Message d'une écriture refusée sur un contrôleur singleton : il nomme la
|
|
33
|
+
* classe, le membre, la raison du refus et les deux remèdes.
|
|
34
|
+
*
|
|
35
|
+
* @param controller - nom de la classe du contrôleur.
|
|
36
|
+
* @param member - membre écrit (`count`, `query`…).
|
|
37
|
+
* @returns le message, en une phrase par idée.
|
|
38
|
+
*/
|
|
39
|
+
function singletonWriteMessage(controller, member) {
|
|
40
|
+
return `Contrôleur « ${controller} » : écriture de « this.${member} » refusée. Ce contrôleur est un singleton (le défaut) : une seule instance sert toutes les requêtes, concurrentes comprises, et cette valeur fuirait de l'une à l'autre. Remèdes : déclarer la classe @Scope("request") — une instance par requête, une par connexion en WebSocket — ou porter cet état par la requête : un argument décoré (@Param, @Query, @Body…), un service de portée request, ou le contexte.`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* En développement, fait lever toute écriture sur un champ d'un contrôleur
|
|
44
|
+
* singleton, avec un message qui nomme le contrôleur, le champ et le remède.
|
|
45
|
+
*
|
|
46
|
+
* Chaque propriété PROPRE inscriptible devient un accesseur : la lecture rend
|
|
47
|
+
* la valeur posée à la construction ou dans `initialize()`, l'écriture lève.
|
|
48
|
+
* L'instance est ensuite rendue non extensible, pour qu'un champ non déclaré
|
|
49
|
+
* ne puisse pas être ajouté en cours de requête.
|
|
50
|
+
*
|
|
51
|
+
* Ce que le filet n'attrape PAS, et que seule la relecture garde : les champs
|
|
52
|
+
* `#privés` (ce ne sont pas des propriétés), et les mutations internes d'un
|
|
53
|
+
* objet tenu par un champ (`this.cache.set(…)`, `this.list.push(…)`).
|
|
54
|
+
*
|
|
55
|
+
* @param controller - l'instance, construite et initialisée.
|
|
56
|
+
* @param name - nom de sa classe, pour le message.
|
|
57
|
+
*/
|
|
58
|
+
function guardSingletonState(controller, name) {
|
|
59
|
+
for (const key of Reflect.ownKeys(controller)) {
|
|
60
|
+
const descriptor = Object.getOwnPropertyDescriptor(controller, key);
|
|
61
|
+
if (descriptor?.writable !== true) continue;
|
|
62
|
+
const value = descriptor.value;
|
|
63
|
+
const member = String(key);
|
|
64
|
+
Object.defineProperty(controller, key, {
|
|
65
|
+
configurable: false,
|
|
66
|
+
enumerable: descriptor.enumerable ?? false,
|
|
67
|
+
get: () => value,
|
|
68
|
+
set: () => {
|
|
69
|
+
throw new TypeError(singletonWriteMessage(name, member));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
Object.preventExtensions(controller);
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { guardSingletonState, isDevelopment, singletonWriteMessage };
|
|
@@ -26,7 +26,7 @@ import Controller from "../src/Controller.js";
|
|
|
26
26
|
declare class BenchController extends Controller {
|
|
27
27
|
constructor(context: ContextType);
|
|
28
28
|
/** Rend le corps figé. Aucune lecture de kernel, aucun I/O, aucune allocation. */
|
|
29
|
-
index():
|
|
29
|
+
index(): import("nodefony").MaybePromise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
30
30
|
/**
|
|
31
31
|
* Dump de la sonde perf in-situ du http-kernel (`NF_PERF_PROBE=1`) : µs
|
|
32
32
|
* moyens par requête des postes enterScope / new HttpContext / leaveScope.
|
|
@@ -35,7 +35,7 @@ declare class BenchController extends Controller {
|
|
|
35
35
|
* pas. `?reset=1` remet les compteurs à zéro (à faire après le warmup, pour
|
|
36
36
|
* ne pas diluer la mesure avec le code froid).
|
|
37
37
|
*/
|
|
38
|
-
probe():
|
|
38
|
+
probe(): import("nodefony").MaybePromise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Monte la route de banc — appelée par le module framework, uniquement si
|
|
@@ -82,11 +82,11 @@ declare class OAuth2Controller extends Controller {
|
|
|
82
82
|
* fournisseur déclaré `hidden` n'y figure pas, mais reste autorisable :
|
|
83
83
|
* masquer n'est pas désactiver. Aucun secret n'est exposé.
|
|
84
84
|
*/
|
|
85
|
-
providers():
|
|
85
|
+
providers(): import("nodefony").MaybePromise<import("@nodefony/http").Http2Response | import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse>;
|
|
86
86
|
/** Démarre le flux : URL d'autorisation + état anti-replay en session, 302. */
|
|
87
|
-
authorize(provider: string): Promise<
|
|
87
|
+
authorize(provider: string): Promise<import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse | undefined>;
|
|
88
88
|
/** Valide `state`, échange le `code`, ouvre la session BFF (302). */
|
|
89
|
-
callback(provider: string): Promise<
|
|
89
|
+
callback(provider: string): Promise<import("@nodefony/http").HttpResponse | import("@nodefony/http").WebsocketResponse | undefined>;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Monte les routes du social login OAuth — appelé par le module framework à
|
|
@@ -89,7 +89,7 @@ declare class ProtectedResourceMetadataController extends Controller {
|
|
|
89
89
|
*/
|
|
90
90
|
export interface IServiceScan {
|
|
91
91
|
keys(): string[];
|
|
92
|
-
get
|
|
92
|
+
get(name: string): unknown;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
95
|
* Rassemble ce que TOUS les services du conteneur déclarent protéger.
|
|
@@ -20,7 +20,8 @@ export interface ISessionAuthFlow {
|
|
|
20
20
|
login(context: ContextType, identifier: unknown, password: unknown): Promise<ILoginOutcome>;
|
|
21
21
|
completeMfaLogin(context: ContextType, code: unknown): Promise<unknown>;
|
|
22
22
|
logout(context: ContextType): Promise<boolean>;
|
|
23
|
-
|
|
23
|
+
/** Identité courante, ou `null` sans session authentifiée. */
|
|
24
|
+
me(context: ContextType): Promise<unknown>;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Endpoints HTTP du flux de session BFF — adaptateurs MINCES au-dessus du
|
|
@@ -80,7 +80,7 @@ export declare const USE_SESSION_METHOD_METADATA = "session:useMethod";
|
|
|
80
80
|
export type ParamSource = "param" | "body" | "query" | "headers" | "cookie" | "session" | "req" | "res" | "file" | "files" | "user";
|
|
81
81
|
export interface ParamMeta {
|
|
82
82
|
source: ParamSource;
|
|
83
|
-
key?: string;
|
|
83
|
+
key?: string | undefined;
|
|
84
84
|
index: number;
|
|
85
85
|
/**
|
|
86
86
|
* P2.9 — `@Body({ stream: true })` : injecte le **flux brut** de la requête
|
|
@@ -89,7 +89,7 @@ export interface ParamMeta {
|
|
|
89
89
|
* pipeline saute le parse busboy/JSON pour la route concernée (cf
|
|
90
90
|
* `routeExpectsBodyStream` + `handleHttp`).
|
|
91
91
|
*/
|
|
92
|
-
stream?: boolean;
|
|
92
|
+
stream?: boolean | undefined;
|
|
93
93
|
}
|
|
94
94
|
export interface RedirectMeta {
|
|
95
95
|
url: string;
|
|
@@ -106,7 +106,7 @@ export interface SecurityClause {
|
|
|
106
106
|
/** Attributs en OR — un seul accordé suffit pour valider la clause. */
|
|
107
107
|
readonly anyOf: readonly string[];
|
|
108
108
|
/** Nom du paramètre de route passé comme `subject` au voter (optionnel). */
|
|
109
|
-
readonly subjectParam?: string;
|
|
109
|
+
readonly subjectParam?: string | undefined;
|
|
110
110
|
}
|
|
111
111
|
/**
|
|
112
112
|
* Exigence d'autorisation **figée** d'une action — calculée UNE fois par route
|
|
@@ -258,7 +258,7 @@ declare function Redirect(url: string, statusCode?: number): (target: object, pr
|
|
|
258
258
|
* \@Get("/") home() {} // marseille.fr/ → OK ; nodefony.com/ → 403
|
|
259
259
|
* }
|
|
260
260
|
*/
|
|
261
|
-
declare function Domain(patterns: string | string[]): (target:
|
|
261
|
+
declare function Domain(patterns: string | string[]): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
262
262
|
/**
|
|
263
263
|
* Déclare une route (décorateur de **méthode**) ou tout un contrôleur
|
|
264
264
|
* (décorateur de **classe**) comme **PUBLIQUE** : le firewall ne s'exécute pas
|
|
@@ -288,31 +288,37 @@ declare function Domain(patterns: string | string[]): (target: any, propertyKey?
|
|
|
288
288
|
* \@Get("/studio/api/stats") stats() {} // gaté par l'aire data plane
|
|
289
289
|
* }
|
|
290
290
|
*/
|
|
291
|
-
declare function BypassFirewall(target:
|
|
291
|
+
declare function BypassFirewall(target: object, propertyKey?: string, descriptor?: PropertyDescriptor): any;
|
|
292
292
|
/**
|
|
293
|
-
* Déclare
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* `@Scope("
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
293
|
+
* Déclare la portée d'instanciation d'un contrôleur — pose le statique `scope`
|
|
294
|
+
* de la classe (hérité de `Controller`, défaut `"singleton"`). Lu par le
|
|
295
|
+
* constructeur de `Controller` (`new.target`) et par le `Resolver` : 0 Reflect.
|
|
296
|
+
*
|
|
297
|
+
* `@Scope("request")` : une instance par requête — par CONNEXION en
|
|
298
|
+
* WebSocket. À déclarer quand le contrôleur injecte un service de portée
|
|
299
|
+
* `request` à son constructeur (un singleton qui le tente est refusé au
|
|
300
|
+
* démarrage), prépare chaque requête dans `initialize()`, ou garde un état
|
|
301
|
+
* sur `this`.
|
|
302
|
+
*
|
|
303
|
+
* `@Scope("singleton")` redit le défaut : UNE instance partagée par toutes les
|
|
304
|
+
* requêtes (cache du Router, `initialize()` appelé une fois, à la création).
|
|
305
|
+
* **Contrat sans état** : l'action ne lit ni n'écrit aucun état de requête sur
|
|
306
|
+
* `this` — tout passe par les arguments décorés (`@Param`/`@Body`…) et les
|
|
307
|
+
* helpers, qui retrouvent la requête courante dans l'ALS. Les accesseurs
|
|
308
|
+
* d'état refusent l'écriture, et le développement refuse toute écriture sur
|
|
309
|
+
* un champ.
|
|
304
310
|
*
|
|
305
311
|
* ⚠️ Homonyme : le core `nodefony` exporte aussi `Scope` (le scope DI du
|
|
306
312
|
* `Container`) — celui-ci s'importe depuis `@nodefony/framework`.
|
|
307
313
|
*
|
|
308
314
|
* @example
|
|
309
|
-
* \@Scope("
|
|
310
|
-
* \@controller("/
|
|
311
|
-
* class
|
|
315
|
+
* \@Scope("request")
|
|
316
|
+
* \@controller("/me")
|
|
317
|
+
* class MeController extends Controller { ... }
|
|
312
318
|
*/
|
|
313
|
-
declare function Scope(scope: ControllerScope):
|
|
319
|
+
declare function Scope(scope: ControllerScope): (target: {
|
|
314
320
|
scope?: ControllerScope;
|
|
315
|
-
}
|
|
321
|
+
}) => void;
|
|
316
322
|
/** Options déclaratives de `@UseSession` (= forme de l'intent runtime). */
|
|
317
323
|
export type UseSessionOptions = SessionIntent;
|
|
318
324
|
/**
|
|
@@ -336,7 +342,7 @@ export type UseSessionOptions = SessionIntent;
|
|
|
336
342
|
* \@Get("/me") @UseSession({ readOnly: true }) me() {} // lecture seule → 0 write
|
|
337
343
|
* }
|
|
338
344
|
*/
|
|
339
|
-
declare function UseSession(options?: UseSessionOptions): (target:
|
|
345
|
+
declare function UseSession(options?: UseSessionOptions): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
340
346
|
/**
|
|
341
347
|
* Résout l'intent de session effectif d'une action — lu par le `Resolver` au
|
|
342
348
|
* match, posé sur `context.sessionIntent`, consommé au point d'activation unique
|
|
@@ -364,7 +370,7 @@ declare function resolveSessionIntent(ctor: ControllerConstructor, actionName: s
|
|
|
364
370
|
*/
|
|
365
371
|
declare function IsGranted(attribute: string | readonly string[], options?: {
|
|
366
372
|
subject?: string;
|
|
367
|
-
}): (target:
|
|
373
|
+
}): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
368
374
|
/**
|
|
369
375
|
* Déclare une action (méthode) ou un contrôleur (classe) **publique** : skip
|
|
370
376
|
* l'autorisation (override un `@IsGranted` de classe sur cette méthode) ET skip
|
|
@@ -373,7 +379,7 @@ declare function IsGranted(attribute: string | readonly string[], options?: {
|
|
|
373
379
|
* login, une sonde de liveness, une page publique d'un contrôleur par ailleurs
|
|
374
380
|
* protégé.
|
|
375
381
|
*/
|
|
376
|
-
declare function Anonymous(): (target:
|
|
382
|
+
declare function Anonymous(): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
377
383
|
/**
|
|
378
384
|
* Exige un **scope** (`api:action`) pour l'action (décorateur de **méthode**) ou
|
|
379
385
|
* tout le contrôleur (décorateur de **classe**). Axe d'autorisation **distinct des
|
|
@@ -395,7 +401,7 @@ declare function Anonymous(): (target: any, propertyKey?: string, descriptor?: P
|
|
|
395
401
|
* metadata est **dédiée** (≠ `@IsGranted`) pour que la découverte au boot puisse
|
|
396
402
|
* lister les scopes déclarés par route sans les confondre avec les rôles.
|
|
397
403
|
*/
|
|
398
|
-
declare function RequireScope(scope: string | readonly string[]): (target:
|
|
404
|
+
declare function RequireScope(scope: string | readonly string[]): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
399
405
|
/**
|
|
400
406
|
* `@Csp({ "frame-src": [...] })` — déclare des directives CSP **additionnelles**
|
|
401
407
|
* pour l'action (méthode) ou tout le contrôleur (classe). Distinct de
|
|
@@ -407,7 +413,7 @@ declare function RequireScope(scope: string | readonly string[]): (target: any,
|
|
|
407
413
|
* dans le CSP de la réponse est fait par le firewall, hors hot-path, UNIQUEMENT
|
|
408
414
|
* sur les routes décorées. Calque `@IsGranted` (0 import `@nodefony/security`).
|
|
409
415
|
*/
|
|
410
|
-
declare function Csp(directives: CspDirectives): (target:
|
|
416
|
+
declare function Csp(directives: CspDirectives): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
411
417
|
/**
|
|
412
418
|
* `@CsrfProtect()` — opt-IN à la défense CSRF **synchronizer token** (double-submit
|
|
413
419
|
* signé HMAC) EN PLUS de la défense globale Fetch Metadata/Origin (étape 1, toujours
|
|
@@ -416,7 +422,7 @@ declare function Csp(directives: CspDirectives): (target: any, propertyKey?: str
|
|
|
416
422
|
* DOIT rejouer ce token dans l'en-tête `x-csrf-token` (sinon 403). Classe = toutes
|
|
417
423
|
* les actions. N'écrit qu'un marqueur (0 import `@nodefony/security`, 0 cycle).
|
|
418
424
|
*/
|
|
419
|
-
declare const CsrfProtect: () => (target:
|
|
425
|
+
declare const CsrfProtect: () => (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
420
426
|
/**
|
|
421
427
|
* `@CsrfExempt()` — opt-OUT de la défense CSRF pour une route, **en conservant
|
|
422
428
|
* l'authentification et l'autorisation** (≠ `@Anonymous`/`@BypassFirewall` qui
|
|
@@ -424,7 +430,7 @@ declare const CsrfProtect: () => (target: any, propertyKey?: string, descriptor?
|
|
|
424
430
|
* légitime, dont la requête est authentifiée autrement (signature HMAC du provider,
|
|
425
431
|
* clé API). Classe = toutes les actions. Marqueur seul (0 logique sécu ici).
|
|
426
432
|
*/
|
|
427
|
-
declare const CsrfExempt: () => (target:
|
|
433
|
+
declare const CsrfExempt: () => (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
428
434
|
/**
|
|
429
435
|
* `@Idempotent()` — protège une **mutation** (POST/PUT/PATCH/DELETE) d'un
|
|
430
436
|
* controller userland contre le double-effet d'un rejeu (double-clic, reconnexion
|
|
@@ -452,7 +458,7 @@ declare const CsrfExempt: () => (target: any, propertyKey?: string, descriptor?:
|
|
|
452
458
|
*/
|
|
453
459
|
declare function Idempotent(options?: {
|
|
454
460
|
required?: boolean;
|
|
455
|
-
}): (target:
|
|
461
|
+
}): (target: object, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
456
462
|
declare const Param: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
457
463
|
declare const Query: (key?: string) => (target: object, propertyKey: string, parameterIndex: number) => void;
|
|
458
464
|
/**
|
|
@@ -520,23 +526,23 @@ export interface IParamArgContext {
|
|
|
520
526
|
* transport — pont WS-RPC `api.request` (`Resolver.queryOverride`). Prime
|
|
521
527
|
* sur `request.queryGet` pour `@Query` uniquement (`@Req` reste le brut).
|
|
522
528
|
*/
|
|
523
|
-
queryOverride?: Record<string, unknown
|
|
529
|
+
queryOverride?: Record<string, unknown> | undefined;
|
|
524
530
|
request?: {
|
|
525
|
-
queryGet?: Record<string, unknown
|
|
526
|
-
queryPost?: Record<string, unknown
|
|
527
|
-
queryFile?: unknown[];
|
|
528
|
-
headers?: Record<string, unknown
|
|
531
|
+
queryGet?: Record<string, unknown> | undefined;
|
|
532
|
+
queryPost?: Record<string, unknown> | undefined;
|
|
533
|
+
queryFile?: unknown[] | undefined;
|
|
534
|
+
headers?: Record<string, unknown> | undefined;
|
|
529
535
|
/**
|
|
530
536
|
* P2.9 — `IncomingMessage` brut (un `Readable`) sous-jacent au wrapper
|
|
531
537
|
* `HttpRequest`. Injecté tel quel par `@Body({ stream: true })` (le pipeline
|
|
532
538
|
* n'a pas consommé/parsé ce flux). `undefined` pour les contextes WS.
|
|
533
539
|
*/
|
|
534
|
-
request?: NodeJS.ReadableStream;
|
|
535
|
-
} | null;
|
|
540
|
+
request?: NodeJS.ReadableStream | undefined;
|
|
541
|
+
} | null | undefined;
|
|
536
542
|
response?: unknown;
|
|
537
543
|
session?: {
|
|
538
544
|
get(key: string): unknown;
|
|
539
|
-
} | null;
|
|
545
|
+
} | null | undefined;
|
|
540
546
|
getRequestCookies(name?: string): unknown;
|
|
541
547
|
}
|
|
542
548
|
/**
|
|
@@ -570,9 +576,9 @@ declare function buildParamArgs(metas: ParamMeta[], ctx: IParamArgContext): unkn
|
|
|
570
576
|
declare function routeExpectsBodyStream(routeDef: {
|
|
571
577
|
controller?: {
|
|
572
578
|
prototype: object;
|
|
573
|
-
} | null;
|
|
574
|
-
classMethod?: string;
|
|
575
|
-
bodyStream?: boolean;
|
|
579
|
+
} | null | undefined;
|
|
580
|
+
classMethod?: string | undefined;
|
|
581
|
+
bodyStream?: boolean | undefined;
|
|
576
582
|
}): boolean;
|
|
577
583
|
/**
|
|
578
584
|
* Snapshot des métadonnées d'action d'une route (`@HttpCode`/`@Header`/
|
|
@@ -639,8 +645,8 @@ declare function computeActionMeta(ctor?: {
|
|
|
639
645
|
declare function resolveActionMeta(routeDef: {
|
|
640
646
|
controller?: {
|
|
641
647
|
prototype: object;
|
|
642
|
-
} | null;
|
|
643
|
-
classMethod?: string;
|
|
644
|
-
actionMeta?: RouteActionMeta;
|
|
648
|
+
} | null | undefined;
|
|
649
|
+
classMethod?: string | undefined;
|
|
650
|
+
actionMeta?: RouteActionMeta | undefined;
|
|
645
651
|
}): RouteActionMeta;
|
|
646
652
|
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, };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { contextRequest, HTTPMethod, HttpResponse, Http2Response, WebsocketResponse, Session, ContextType } from "@nodefony/http";
|
|
2
|
-
import type { Module, FileClass } from "nodefony";
|
|
2
|
+
import type { Module, FileClass, MaybePromise } from "nodefony";
|
|
3
3
|
import type { OutgoingHttpHeaders } from "node:http";
|
|
4
4
|
import type { ReadStream } from "node:fs";
|
|
5
5
|
import type { IRoute } from "./IRoute.js";
|
|
@@ -7,21 +7,23 @@ export interface IController {
|
|
|
7
7
|
readonly route?: IRoute | null;
|
|
8
8
|
request: contextRequest;
|
|
9
9
|
response: HttpResponse | Http2Response | WebsocketResponse | null;
|
|
10
|
-
context?: ContextType;
|
|
10
|
+
context?: ContextType | undefined;
|
|
11
11
|
session?: Session | null;
|
|
12
|
-
method?: HTTPMethod;
|
|
12
|
+
method?: HTTPMethod | undefined;
|
|
13
13
|
queryGet: Record<string, unknown>;
|
|
14
14
|
query: Record<string, unknown>;
|
|
15
15
|
queryFile: unknown[];
|
|
16
16
|
queryPost: Record<string, unknown>;
|
|
17
|
-
module?: Module;
|
|
17
|
+
module?: Module | undefined;
|
|
18
18
|
setContext(context: ContextType): void;
|
|
19
19
|
setContextJson(encoding?: BufferEncoding): unknown;
|
|
20
20
|
setContextHtml(encoding?: BufferEncoding): unknown;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/** Rend une donnée — la réponse, ou sa promesse quand l'envoi attend. */
|
|
22
|
+
render(data: unknown, encoding?: BufferEncoding, status?: string | number, headers?: Record<string, string | number>): MaybePromise<unknown>;
|
|
23
|
+
/** Envoie une donnée sérialisée — la réponse, ou sa promesse quand l'envoi attend. */
|
|
24
|
+
renderResponse(data: unknown, encoding?: BufferEncoding, status?: string | number, headers?: OutgoingHttpHeaders): MaybePromise<HttpResponse | Http2Response | WebsocketResponse>;
|
|
23
25
|
renderView(path: string, param?: Record<string, unknown>, status?: string | number, headers?: Record<string, string | number>): Promise<HttpResponse | Http2Response | WebsocketResponse>;
|
|
24
|
-
renderJson(obj: unknown, status?: string | number, headers?: OutgoingHttpHeaders):
|
|
26
|
+
renderJson(obj: unknown, status?: string | number, headers?: OutgoingHttpHeaders): MaybePromise<unknown>;
|
|
25
27
|
setRoute(route: IRoute): IRoute;
|
|
26
28
|
getSession(): Session | undefined | null;
|
|
27
29
|
redirect(url: string, status?: string | number, headers?: Record<string, string | number>): void;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { ContextType, HttpError } from "@nodefony/http";
|
|
2
|
-
import type { Injector } from "nodefony";
|
|
1
|
+
import type { ContextType, HttpError, IRouteResolver } from "@nodefony/http";
|
|
2
|
+
import type { Injector, MaybePromise } from "nodefony";
|
|
3
3
|
import type { ControllerConstructor } from "../src/Route.js";
|
|
4
4
|
import type { IRoute } from "./IRoute.js";
|
|
5
5
|
import type { IController } from "./IController.js";
|
|
6
|
-
export interface IResolver {
|
|
7
|
-
injector?: Injector | null;
|
|
6
|
+
export interface IResolver extends IRouteResolver {
|
|
7
|
+
injector?: Injector | null | undefined;
|
|
8
8
|
controller: ControllerConstructor | null;
|
|
9
|
-
actionName?: string;
|
|
10
|
-
action?: (...args: unknown[]) => unknown;
|
|
9
|
+
actionName?: string | undefined;
|
|
10
|
+
action?: ((...args: unknown[]) => unknown) | undefined;
|
|
11
11
|
context: ContextType;
|
|
12
12
|
readonly route: IRoute | null;
|
|
13
13
|
resolve: boolean;
|
|
14
14
|
variables: unknown[];
|
|
15
|
-
exception?: HttpError | Error | null;
|
|
15
|
+
exception?: HttpError | Error | null | undefined;
|
|
16
16
|
acceptedProtocol: string | null;
|
|
17
17
|
bypassFirewall: boolean;
|
|
18
18
|
match(route: IRoute, context: ContextType, cleanPath?: string): unknown;
|
|
19
19
|
getMatchedParams(): Record<string, unknown>;
|
|
20
20
|
parsePathernController(name: string): void;
|
|
21
21
|
getAction(name: string): ((...args: unknown[]) => unknown) | null;
|
|
22
|
-
newController(context?: ContextType):
|
|
23
|
-
callController(data?: unknown[], reload?: boolean):
|
|
24
|
-
returnController(result: unknown):
|
|
22
|
+
newController(context?: ContextType): MaybePromise<IController | object>;
|
|
23
|
+
callController(data?: unknown[], reload?: boolean): MaybePromise<unknown>;
|
|
24
|
+
returnController(result: unknown): MaybePromise<unknown>;
|
|
25
25
|
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type { HTTPMethod, SchemeType, ContextType } from "@nodefony/http";
|
|
1
|
+
import type { HTTPMethod, SchemeType, ContextType, IResolvedRoute } from "@nodefony/http";
|
|
2
2
|
import type { ControllerConstructor, RouteRequirements } from "../src/Route.js";
|
|
3
|
-
export interface IRoute {
|
|
3
|
+
export interface IRoute extends IResolvedRoute {
|
|
4
4
|
name: string;
|
|
5
|
-
path?: string;
|
|
6
|
-
controller?: ControllerConstructor;
|
|
7
|
-
classMethod?: string;
|
|
8
|
-
prefix?: string;
|
|
9
|
-
method?: HTTPMethod;
|
|
10
|
-
schemes?: SchemeType;
|
|
11
|
-
pattern?: RegExp;
|
|
5
|
+
path?: string | undefined;
|
|
6
|
+
controller?: ControllerConstructor | undefined;
|
|
7
|
+
classMethod?: string | undefined;
|
|
8
|
+
prefix?: string | undefined;
|
|
9
|
+
method?: HTTPMethod | undefined;
|
|
10
|
+
schemes?: SchemeType | undefined;
|
|
11
|
+
pattern?: RegExp | undefined;
|
|
12
12
|
variables: unknown[];
|
|
13
13
|
defaults: Partial<Record<string, unknown>>;
|
|
14
14
|
requirements: Partial<RouteRequirements>;
|
|
15
|
-
hash?: string;
|
|
16
|
-
host?: string | string[];
|
|
17
|
-
hostRegexp?: RegExp[];
|
|
15
|
+
hash?: string | undefined;
|
|
16
|
+
host?: string | string[] | undefined;
|
|
17
|
+
hostRegexp?: RegExp[] | undefined;
|
|
18
18
|
bypassFirewall: boolean;
|
|
19
|
-
filePath?: string;
|
|
19
|
+
filePath?: string | undefined;
|
|
20
20
|
match(context: ContextType, cleanPath?: string): unknown[] | null | undefined;
|
|
21
21
|
compile(): RegExp;
|
|
22
22
|
toString(): string;
|
|
@@ -15,11 +15,11 @@ declare class AdminBroker extends Service implements IAdminBroker {
|
|
|
15
15
|
readonly apiSegment = "api";
|
|
16
16
|
readonly defaultRole = "ROLE_NODEFONY_ADMIN";
|
|
17
17
|
/** Producteurs enregistrés, indexés par namespace. */
|
|
18
|
-
private producers;
|
|
18
|
+
private readonly producers;
|
|
19
19
|
/** Routes résolues, indexées par nom de route (dispatch O(1)). */
|
|
20
|
-
private byRouteName;
|
|
20
|
+
private readonly byRouteName;
|
|
21
21
|
/** Module framework propriétaire — requis pour `Router.setController`. */
|
|
22
|
-
private frameworkModule;
|
|
22
|
+
private readonly frameworkModule;
|
|
23
23
|
/** Vrai une fois `mountAll()` exécuté (idempotence + verrou de register). */
|
|
24
24
|
private mounted;
|
|
25
25
|
constructor(module: Module);
|