@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +13 -10
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +3 -3
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  12. package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
  13. package/dist/nodefony/service/audit-logger.js +23 -30
  14. package/dist/nodefony/service/certificates.js +97 -13
  15. package/dist/nodefony/service/error-renderer.js +29 -12
  16. package/dist/nodefony/service/http-kernel.js +198 -50
  17. package/dist/nodefony/service/pretty-request-logger.js +15 -13
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +3 -3
  20. package/dist/nodefony/service/servers/clientError.js +22 -3
  21. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  22. package/dist/nodefony/service/servers/server-http.js +5 -8
  23. package/dist/nodefony/service/servers/server-https.js +12 -24
  24. package/dist/nodefony/service/servers/server-static.js +3 -3
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  29. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  30. package/dist/nodefony/service/trace.js +1 -2
  31. package/dist/nodefony/service/upload/upload-service.js +5 -8
  32. package/dist/nodefony/service/x509.js +1 -2
  33. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  34. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  35. package/dist/nodefony/src/context/Context.js +78 -43
  36. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  37. package/dist/nodefony/src/context/forwarded.js +1 -2
  38. package/dist/nodefony/src/context/http/HttpContext.js +28 -6
  39. package/dist/nodefony/src/context/http/Request.js +33 -29
  40. package/dist/nodefony/src/context/http/Response.js +23 -6
  41. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  42. package/dist/nodefony/src/context/http/parser.js +36 -10
  43. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  44. package/dist/nodefony/src/context/http2/Request.js +4 -8
  45. package/dist/nodefony/src/context/http2/Response.js +41 -9
  46. package/dist/nodefony/src/context/metaData.js +1 -2
  47. package/dist/nodefony/src/context/requestId.js +1 -2
  48. package/dist/nodefony/src/context/requestTrace.js +104 -0
  49. package/dist/nodefony/src/context/responseEnded.js +1 -2
  50. package/dist/nodefony/src/context/timingMode.js +21 -0
  51. package/dist/nodefony/src/context/transport.js +45 -0
  52. package/dist/nodefony/src/context/trustProxy.js +1 -2
  53. package/dist/nodefony/src/context/unsentResponse.js +30 -0
  54. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  55. package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
  56. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  57. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  58. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  59. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  60. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  61. package/dist/nodefony/src/cookies/cookie.js +3 -7
  62. package/dist/nodefony/src/errors/httpError.js +1 -2
  63. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  64. package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
  65. package/dist/nodefony/src/profiler/Profiler.js +104 -32
  66. package/dist/nodefony/src/profiler/aggregate.js +442 -0
  67. package/dist/nodefony/src/proxy/forward.js +29 -5
  68. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  69. package/dist/nodefony/src/proxy/rules.js +12 -6
  70. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  71. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  72. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  73. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  74. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  75. package/dist/nodefony/src/servers/portBinder.js +41 -10
  76. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  77. package/dist/nodefony/src/session/session.js +2 -3
  78. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  79. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  80. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  81. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  82. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  83. package/dist/types/index.d.ts +9 -3
  84. package/dist/types/nodefony/config/config.d.ts +4 -0
  85. package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
  86. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  87. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
  88. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
  89. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  91. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
  92. package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
  93. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  94. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  95. package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
  96. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  97. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  98. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  99. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  100. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  101. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  102. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  103. package/dist/types/nodefony/src/context/Context.d.ts +38 -5
  104. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
  105. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  106. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  107. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  108. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  109. package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
  110. package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
  111. package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
  112. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  113. package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
  114. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  115. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
  118. package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
  119. package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
  120. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  121. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  122. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  123. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  124. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  125. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  126. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  127. package/docs/cookies.md +85 -78
  128. package/docs/diagnostics-channel.md +303 -0
  129. package/docs/index.md +6 -1
  130. package/docs/observabilite.md +159 -81
  131. package/docs/rate-limit.md +83 -83
  132. package/docs/reverse-proxy.md +72 -70
  133. package/docs/servers.md +338 -224
  134. package/docs/session.md +179 -179
  135. package/docs/sse.md +24 -20
  136. package/docs/upload.md +120 -117
  137. package/package.json +8 -9
  138. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -1,5 +1,6 @@
1
1
  import type { IProfilerQuery } from "nodefony";
2
- import type { ISecurityTrace, PhaseName, PhaseTiming } from "../../interfaces/IContext.js";
2
+ import type { ISecurityTrace, PhaseName, PhaseTiming, ProtocolName } from "../../interfaces/IContext.js";
3
+ import type { IResolvedRoute, IRouteResolver } from "../../interfaces/IRouting.js";
3
4
  /**
4
5
  * Zone firewall capturée sur le contexte (`SecuredArea`, lue en structurel :
5
6
  * `@nodefony/http` ne peut pas importer `@nodefony/security`).
@@ -10,22 +11,22 @@ export interface ProfiledArea {
10
11
  mode?: string;
11
12
  authenticators?: readonly string[];
12
13
  }
13
- /** Ce que le Profiler lit d'un Resolver (route/controller/action). */
14
+ /**
15
+ * Ce que le Profiler lit d'un Resolver (route/controller/action) — dérivé de
16
+ * {@link IRouteResolver}, en lecture seule : tout résolveur du contrat s'y range
17
+ * sans conversion, et les deux types ne peuvent plus diverger.
18
+ */
14
19
  export interface ProfiledResolver {
15
- route?: {
16
- name?: string;
17
- } | null;
18
- controller?: {
19
- name?: string;
20
- } | null;
21
- actionName?: string;
20
+ readonly route?: Pick<IResolvedRoute, "name"> | null | undefined;
21
+ readonly controller?: IRouteResolver["controller"];
22
+ readonly actionName?: string | undefined;
22
23
  }
23
24
  /** Descripteur d'ouverture d'une invocation (ce que le pont connaît d'entrée). */
24
25
  export interface FrameProfileInit {
25
26
  /** Identifiant du profil — `<requestId de la connexion>.<n° de frame>`. */
26
27
  requestId: string;
27
- /** Type de transport du contexte porteur (`websocket` / `websocket-secure`). */
28
- type: string;
28
+ /** Famille du contexte porteur (`context.type` : `websocket`). */
29
+ type: ProtocolName;
29
30
  scheme: string;
30
31
  /** Méthode LOGIQUE de l'invocation (`GET`, `POST`…), pas le transport. */
31
32
  method: string;
@@ -63,7 +64,7 @@ export interface FrameProfileInit {
63
64
  */
64
65
  export declare class FrameProfile {
65
66
  readonly requestId: string;
66
- readonly type: string;
67
+ readonly type: ProtocolName;
67
68
  readonly scheme: string;
68
69
  readonly method: string;
69
70
  readonly url: string;
@@ -79,6 +80,12 @@ export declare class FrameProfile {
79
80
  * invocation, il redevient exact, et le SQL se replace dans le waterfall.
80
81
  */
81
82
  readonly profilerQueries: IProfilerQuery[] | null;
83
+ /**
84
+ * Ce profil sera-t-il gardé par le Profiler ? Faux quand seul le timing
85
+ * l'a ouvert (chemin d'outillage) : son id ne désigne alors aucun profil
86
+ * lisible, et ne doit pas être annoncé au client.
87
+ */
88
+ get profiled(): boolean;
82
89
  /** Resolver de la frame — porte route / controller / action. */
83
90
  resolver: ProfiledResolver | null;
84
91
  /** Statut HTTP-équivalent de l'invocation (200, 403, 404…). */
@@ -89,7 +96,16 @@ export declare class FrameProfile {
89
96
  message: string;
90
97
  } | null;
91
98
  private readonly timing;
99
+ private readonly clockStartMs;
100
+ private durationMs;
92
101
  constructor(init: FrameProfileInit);
102
+ /**
103
+ * Arrête l'horloge de la frame (au premier appel) et rend sa durée en ms ;
104
+ * `null` si le timing est éteint. Contrat de `IContext.stopClock`.
105
+ *
106
+ * @returns la durée de l'invocation, ou `null`
107
+ */
108
+ stopClock(): number | null;
93
109
  /**
94
110
  * Ouvre une phase. Contrairement au `Context`, l'index est un **scan arrière**
95
111
  * (pas de `Map<nom, index>`) : une frame porte une poignée de phases, et une
@@ -18,106 +18,19 @@
18
18
  * Multi-process : un profil vit sur le worker qui a traité l'appel (fetch même
19
19
  * origine par requestId). Agrégat cluster = Redis (P13).
20
20
  */
21
- /** Une phase du pipeline, projetée pour le transport (durée résolue). */
22
- export interface ProfilePhase {
23
- name: string;
24
- /** Début relatif `performance.now()` (ms) — sert au calcul du waterfall. */
25
- startMs: number;
26
- /** Durée en ms, `null` si la phase ne s'est pas terminée (erreur). */
27
- durationMs: number | null;
28
- }
29
- /**
30
- * Une requête SQL/NoSQL exécutée pendant la requête HTTP (SEAM ORM — futur).
31
- *
32
- * Non collecté aujourd'hui : quand `@nodefony/orm-core` arrivera, les adapters
33
- * pousseront leurs requêtes dans un buffer per-request dev-only (via l'ALS
34
- * `RequestContext.getRequestId()`, déjà en place) que `collect()` lira ici.
35
- * Le champ `queries` reste donc `undefined` tant qu'aucun ORM ne pushe → 0 coût.
36
- */
37
- export interface ProfileQuery {
38
- /** Requête (SQL ou commande NoSQL), tronquée si volumineuse. */
39
- sql: string;
40
- /** Début relatif (`performance.now()`) — même horloge que les phases. */
41
- startMs?: number | undefined;
42
- /** Durée d'exécution en ms. */
43
- durationMs: number;
44
- /** Lignes affectées/retournées, si connu. */
45
- rows?: number | undefined;
46
- /** Connecteur émetteur (`drizzle`, `mongoose`…). */
47
- connector?: string | undefined;
48
- }
49
- /**
50
- * Traversée du firewall par cette requête — **ce qui était possible** (la zone :
51
- * son nom, si elle est protégée, quels authenticators elle accepte) croisé avec
52
- * **ce qui s'est passé** (quel maillon a résolu l'identité, l'issue, le motif
53
- * d'un refus).
54
- *
55
- * Sans cela, une requête qui PASSE est invisible côté sécurité : le chemin de
56
- * succès n'émet aucun événement d'audit (choix délibéré — le volume nominal
57
- * n'est pas un signal). `undefined` hors zone firewall.
58
- */
59
- export interface ProfileSecurity {
60
- /** Nom de la zone traversée. */
61
- zone: string | null;
62
- /** La zone exige-t-elle une identité (`security: true`) ? */
63
- protected: boolean;
64
- /** Chaîne d'authenticators : `first` = le premier qui supporte, `all` = MFA. */
65
- mode: string | null;
66
- /** Authenticators que la zone accepte (ce qui était POSSIBLE). */
67
- candidates: string[];
68
- /** Authenticator qui a RÉELLEMENT résolu l'identité. */
69
- authenticator: string | null;
70
- /** `granted` · `anonymous` · `denied` · `failure` · `throttled` · `bypass` · `public`. */
71
- outcome: string | null;
72
- /** Motif du refus (`no_credentials`, `invalid_credentials`…). */
73
- reason: string | null;
74
- /** Rôles du token résolu (l'axe autorisation). */
75
- roles: string[] | null;
76
- }
77
- /** Profil complet d'une requête (HTTP ou message WS), exposé par `get`. */
78
- export interface ProfileEntry {
79
- requestId: string;
80
- /** Horodatage de la collecte (`Date.now()`), pour le tri client. */
81
- ts: number;
82
- kind: "http" | "ws";
83
- method: string | null;
84
- url: string;
85
- scheme: string;
86
- status: number | null;
87
- /** Durée totale serveur (ms), dérivée des phases. */
88
- durationMs: number | null;
89
- route: string | null;
90
- controller: string | null;
91
- action: string | null;
92
- remoteAddress: string | null;
93
- /** Identité (username) si le firewall l'a injectée, sinon `null`. */
94
- user: string | null;
95
- /** `traceparent` W3C Trace Context (P2.7) — corrélation distribuée RFC-propre. */
96
- traceparent: string | null;
97
- error: string | null;
98
- phases: ProfilePhase[];
99
- /** Requêtes ORM (SEAM futur — `undefined` tant qu'aucun adapter ne pushe). */
100
- queries?: ProfileQuery[] | undefined;
101
- /** Traversée du firewall — `undefined` si la requête n'a croisé aucune zone. */
102
- security?: ProfileSecurity | undefined;
103
- }
104
- /** Résumé léger d'un profil pour la liste `recent` (sans les phases). */
105
- export interface ProfileSummary {
106
- requestId: string;
107
- ts: number;
108
- kind: "http" | "ws";
109
- method: string | null;
110
- url: string;
111
- status: number | null;
112
- durationMs: number | null;
113
- route: string | null;
114
- error: string | null;
115
- }
21
+ import type { ProfiledResolver } from "./FrameProfile.js";
22
+ import type { ProtocolName } from "../../interfaces/IContext.js";
23
+ import type { IProfileEntry, IProfilePhase, IProfileQuery, IProfileSecurity, IProfileSummary } from "nodefony";
24
+ export type ProfilePhase = IProfilePhase;
25
+ export type ProfileQuery = IProfileQuery;
26
+ export type ProfileSecurity = IProfileSecurity;
27
+ export type ProfileEntry = IProfileEntry;
28
+ export type ProfileSummary = IProfileSummary;
116
29
  /** Forme structurelle minimale lue sur un `Context` (lecture défensive). */
117
30
  interface ProfilableContext {
118
31
  requestId?: string | undefined;
119
32
  userIdentifier?: string | null | undefined;
120
- type?: string | undefined;
33
+ type?: ProtocolName | undefined;
121
34
  scheme?: string | undefined;
122
35
  method?: string | null | undefined;
123
36
  url?: string | undefined;
@@ -134,15 +47,12 @@ interface ProfilableContext {
134
47
  startMs: number;
135
48
  durationMs?: number | undefined;
136
49
  }> | undefined;
137
- resolver?: {
138
- route?: {
139
- name?: string | undefined;
140
- } | null;
141
- controller?: {
142
- name?: string | undefined;
143
- } | null | undefined;
144
- actionName?: string | undefined;
145
- } | null | undefined;
50
+ resolver?: ProfiledResolver | null | undefined;
51
+ /**
52
+ * Arrête l'horloge de la requête et rend sa durée (ms) — la même que le
53
+ * journal d'accès (`IContext.stopClock`). `null` si `timing.enabled` vaut `false`.
54
+ */
55
+ stopClock(): number | null;
146
56
  /**
147
57
  * Buffer de requêtes ORM rempli pendant la requête (dev-only). Même tableau
148
58
  * que la payload ALS — les adapters ORM y poussent via `RequestContext`.
@@ -168,10 +78,50 @@ interface ProfilableContext {
168
78
  roles: string[] | null;
169
79
  } | null | undefined;
170
80
  }
171
- export declare class Profiler {
172
- private readonly _buf;
173
- private readonly _cap;
174
- constructor(cap?: number);
81
+ /** Les profils collectés depuis un curseur. */
82
+ export interface IProfileBatch {
83
+ /** Profils de numéro supérieur au curseur, du plus ancien au plus récent. */
84
+ entries: ProfileEntry[];
85
+ /** Curseur à repasser au prochain appel : le dernier numéro attribué. */
86
+ cursor: number;
87
+ /**
88
+ * Profils collectés depuis le curseur mais disparus avant d'être lus —
89
+ * évincés par le tampon, ou écrasés par un identifiant réutilisé. Exact.
90
+ */
91
+ lost: number;
92
+ }
93
+ /**
94
+ * Ce qu'un lecteur extérieur (outil de l'agent de code, banc) attend du
95
+ * profileur : suivre les profils au fil de l'eau, ou en retrouver un par son
96
+ * identifiant. Contrat structurel — on le résout par NOM dans le conteneur
97
+ * (`"profiler"`), sans dépendre de la classe.
98
+ */
99
+ export interface IProfilerReader {
100
+ /** Curseur courant : le passer à {@link since} pour ne lire que la suite. */
101
+ readonly cursor: number;
102
+ /** Profils collectés depuis `cursor`, et le compte de ceux qui ont été perdus. */
103
+ since(cursor: number): IProfileBatch;
104
+ /** Profil complet d'une requête, ou `undefined` si évincé/inconnu. */
105
+ get(requestId: string): ProfileEntry | undefined;
106
+ }
107
+ /**
108
+ * Collecteur des profils de requête, en un anneau par provenance
109
+ * ({@link profileLane}) : l'application, l'outillage, les assets relayés.
110
+ * Chacun évince ses propres profils — ouvrir la console ne chasse plus celui
111
+ * qu'on y consulte.
112
+ */
113
+ export declare class Profiler implements IProfilerReader {
114
+ #private;
115
+ private readonly _lanes;
116
+ private readonly _caps;
117
+ /** Dernier numéro d'ordre attribué (0 = aucun profil collecté). */
118
+ private _seq;
119
+ /**
120
+ * @param cap - capacité de l'anneau de l'application.
121
+ * @param sideCap - capacité de chacun des anneaux de l'outillage et des
122
+ * assets relayés.
123
+ */
124
+ constructor(cap?: number, sideCap?: number);
175
125
  /**
176
126
  * Snapshot un `Context` en fin de requête. Tolérant aux champs absents
177
127
  * (handshake WS, erreur précoce). À appeler AVANT `context.clean()`.
@@ -183,11 +133,29 @@ export declare class Profiler {
183
133
  get(requestId: string): ProfileEntry | undefined;
184
134
  /**
185
135
  * Résumés des requêtes les plus récentes (récent → ancien), capés à `limit`.
136
+ *
137
+ * @param limit - nombre maximal de résumés
138
+ * @param connection - le requestId d'une connexion WebSocket : ses SEULES
139
+ * frames du pont (`<connection>.<n>`, règle `frameIndexOf` du cœur)
140
+ */
141
+ recent(limit?: number, connection?: string): ProfileSummary[];
142
+ /** Dernier numéro d'ordre attribué : le point de départ d'une lecture suivie. */
143
+ get cursor(): number;
144
+ /**
145
+ * Profils collectés après `cursor`, tous anneaux confondus, dans l'ordre
146
+ * des numéros.
147
+ *
148
+ * Les pertes se comptent sans estimation : chaque numéro attribué est un
149
+ * profil collecté, donc ce qui manque entre le curseur et le dernier numéro
150
+ * a disparu avant d'être lu. Un curseur venu d'ailleurs (supérieur au
151
+ * dernier numéro) repart de zéro.
152
+ *
153
+ * @param cursor - le `cursor` rendu par l'appel précédent, ou {@link cursor}.
186
154
  */
187
- recent(limit?: number): ProfileSummary[];
155
+ since(cursor: number): IProfileBatch;
188
156
  /** Nombre de profils en mémoire. */
189
157
  get size(): number;
190
- /** Vide le ring buffer. */
158
+ /** Vide les anneaux. */
191
159
  clear(): void;
192
160
  }
193
161
  export default Profiler;
@@ -0,0 +1,132 @@
1
+ import type { ProfileEntry } from "./Profiler.js";
2
+ import type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport } from "nodefony";
3
+ export type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport, };
4
+ /**
5
+ * Agrégation des profils de requêtes — règle PURE, sans horloge ni I/O.
6
+ *
7
+ * Une seule implémentation pour tout ce qui résume le profileur : l'outil MCP
8
+ * qui observe l'application (`nodefony_observe`), celui qui tire lui-même des
9
+ * requêtes sur une route, et demain la console d'administration. Elle est
10
+ * bornée par construction : un échantillon de durées par route, un nombre
11
+ * maximal de formes SQL retenues, quelques exemples d'erreurs — observer une
12
+ * application chargée ne fait jamais grossir la mémoire avec la fenêtre.
13
+ */
14
+ /**
15
+ * Répétitions d'une même forme SQL, dans UNE requête, à partir desquelles on
16
+ * signale un N+1. En dessous, ce sont des boucles courtes légitimes (quelques
17
+ * lectures de configuration, une pagination) qu'on signalerait à tort.
18
+ */
19
+ export declare const N_PLUS_ONE_MIN_REPEATS = 5;
20
+ /** Durées retenues par route pour les percentiles (échantillonnage au-delà). */
21
+ export declare const ROUTE_SAMPLE_CAP = 256;
22
+ /** Formes SQL distinctes retenues ; les suivantes ne sont plus que comptées. */
23
+ export declare const SQL_SHAPE_CAP = 200;
24
+ /** Exemples d'erreurs retenus (statut ≥ 500 ou exception). */
25
+ export declare const ERROR_SAMPLE_CAP = 10;
26
+ /**
27
+ * Préfixe de l'espace réservé au framework.
28
+ *
29
+ * @deprecated Ne dit pas ce qui est de l'outillage : une connexion
30
+ * d'utilisateur (`/nodefony/security/api/auth/…`) ou une route d'application
31
+ * (`/nodefony/test/index`) passent sous ce préfixe. La règle est
32
+ * `isConsolePath` (`nodefony`), que `profileLane` appelle.
33
+ */
34
+ export declare const TOOLING_URL_PREFIX = "/nodefony/";
35
+ /**
36
+ * Provenance d'un profil : l'application, l'outillage (`/nodefony/…` :
37
+ * console, API d'administration, porte MCP), ou un asset relayé sous un
38
+ * préfixe de module (`/_vite/`).
39
+ */
40
+ export type ProfileLane = "app" | "tooling" | "asset";
41
+ /**
42
+ * Range un profil par provenance, d'après son URL. L'outillage est reconnu par
43
+ * la règle du cœur, `isConsolePath` — la même que les Journaux, la barre de
44
+ * débogage et le Studio : une connexion d'utilisateur reste de l'application.
45
+ * Les anneaux du profileur et le filtre de l'agrégation la lisent tous deux.
46
+ *
47
+ * @param url - URL du profil, absolue ou chemin.
48
+ * @returns la provenance du profil.
49
+ */
50
+ export declare function profileLane(url: string): ProfileLane;
51
+ /**
52
+ * Le profil vient-il de l'outillage (routes `/nodefony/…` ou assets relayés)
53
+ * plutôt que de l'application.
54
+ *
55
+ * @param url - URL du profil, absolue ou chemin.
56
+ */
57
+ export declare function isToolingUrl(url: string): boolean;
58
+ /**
59
+ * Une requête de cette URL se profile-t-elle ? L'application toujours ;
60
+ * l'outillage et les assets relayés seulement sur demande (`timing.tooling`)
61
+ * — on ne mesure pas la façon dont on mesure.
62
+ *
63
+ * @param url - URL ou chemin de la requête (ou de la frame du pont)
64
+ * @param tooling - profiler aussi l'outillage
65
+ */
66
+ export declare function isProfiledUrl(url: string, tooling: boolean): boolean;
67
+ /** Groupe des requêtes qu'aucune route n'a servies (404, statique, sondes). */
68
+ export declare const NO_ROUTE = "(aucune route)";
69
+ /** Pseudo-phase : le temps de l'action hors phases imbriquées et hors SQL. */
70
+ export declare const ACTION_SELF_PHASE = "action.self";
71
+ /** Réglages d'une agrégation. */
72
+ export interface IProfileAggregatorOptions {
73
+ /** Répétitions à partir desquelles une forme SQL est un N+1. */
74
+ nPlusOneMinRepeats?: number | undefined;
75
+ /** Agréger aussi les requêtes de l'outillage (`/nodefony/…`, `/_vite/…`). */
76
+ includeTooling?: boolean | undefined;
77
+ /** Source d'aléa de l'échantillonnage — injectée par les tests. */
78
+ random?: (() => number) | undefined;
79
+ }
80
+ /** Accumulateur : on lui donne les profils un à un, il rend le rapport. */
81
+ export interface IProfileAggregator {
82
+ /** Ajoute un profil ; `false` s'il a été écarté (outillage). */
83
+ add(entry: ProfileEntry): boolean;
84
+ /** Le rapport à cet instant ; l'accumulateur continue d'accepter des profils. */
85
+ result(): IProfileReport;
86
+ }
87
+ /**
88
+ * Réduit une requête SQL (ou le descripteur d'une commande NoSQL) à sa FORME :
89
+ * valeurs remplacées par `?`, listes de valeurs repliées, blancs compactés.
90
+ *
91
+ * Deux requêtes qui ne diffèrent que par leurs valeurs ont la même forme — c'est
92
+ * ce qui permet de reconnaître un N+1. Et aucune valeur ne survit : la forme
93
+ * peut sortir dans un rapport sans emporter une donnée personnelle.
94
+ *
95
+ * @param sql - le texte tel que l'adaptateur l'a poussé au profileur.
96
+ * @returns la forme normalisée.
97
+ */
98
+ export declare function sqlShape(sql: string): string;
99
+ /**
100
+ * Une requête en ERREUR du serveur : statut ≥ 500, ou exception levée sans
101
+ * verdict client. Règle unique — le rapport, ses exemples et la lecture de leurs
102
+ * journaux en dépendent.
103
+ *
104
+ * Un statut 4xx n'en est JAMAIS une, même porteur d'une exception : un refus
105
+ * 401/403 (l'authentification lève une `AuthenticationError`) ou une ressource
106
+ * absente est le verdict rendu au client, compté à part — le lister parmi les
107
+ * erreurs ferait chercher une panne là où il manque une identité.
108
+ *
109
+ * @param entry - le profil (seuls statut et erreur sont lus).
110
+ */
111
+ export declare function isFailedProfile(entry: Pick<ProfileEntry, "status" | "error">): boolean;
112
+ /**
113
+ * Le chemin d'une URL de profil. Le profileur enregistre l'URL ABSOLUE
114
+ * (`http://hôte:port/chemin`) : un filtre écrit sur le chemin ne mordrait
115
+ * jamais dessus. Sans `URL` à construire — appelée sur chaque profil lu.
116
+ */
117
+ export declare function urlPath(url: string): string;
118
+ /**
119
+ * Crée un accumulateur de profils.
120
+ *
121
+ * @param options - seuil N+1, prise en compte de l'outillage, aléa injecté.
122
+ * @returns l'accumulateur ; son `result()` peut être appelé à tout moment.
123
+ */
124
+ export declare function createProfileAggregator(options?: IProfileAggregatorOptions): IProfileAggregator;
125
+ /**
126
+ * Agrège une liste de profils en une fois.
127
+ *
128
+ * @param entries - les profils, dans n'importe quel ordre.
129
+ * @param options - mêmes réglages que {@link createProfileAggregator}.
130
+ * @returns le rapport.
131
+ */
132
+ export declare function aggregateProfiles(entries: Iterable<ProfileEntry>, options?: IProfileAggregatorOptions): IProfileReport;
@@ -140,8 +140,20 @@ export declare function forwardRequest(req: ProxiedRequest, res: ProxiedResponse
140
140
  * @returns la tête complète, CRLF final compris
141
141
  */
142
142
  export declare function rawHead(status: string, rawHeaders: readonly string[], skip?: ReadonlySet<string> | null, extra?: readonly string[]): string;
143
- /** Réponse brute minimale sur un socket d'upgrade, puis fermeture. */
144
- export declare function refuseUpgrade(socket: Duplex, status: number, reason: string): void;
143
+ /**
144
+ * Réponse brute minimale sur un socket d'upgrade, puis fermeture.
145
+ *
146
+ * Les en-têtes passent par la validation de `node:http` : un nom ou une valeur
147
+ * invalide — un saut de ligne surtout, qui forgerait un en-tête de plus — est
148
+ * OMIS, jamais écrit. `Connection` et `Content-Length` sont posés ici et ne se
149
+ * remplacent pas.
150
+ *
151
+ * @param socket - socket du client, encore en attente de sa réponse
152
+ * @param status - statut HTTP du refus
153
+ * @param reason - phrase de raison
154
+ * @param headers - en-têtes du refus (`WWW-Authenticate`, `Retry-After`…)
155
+ */
156
+ export declare function refuseUpgrade(socket: Duplex, status: number, reason: string, headers?: Readonly<Record<string, string | number>> | null): void;
145
157
  /**
146
158
  * Relaie un upgrade WebSocket DÉJÀ validé (méthode, `Upgrade`, clé, version,
147
159
  * `Origin` — cf `ReverseProxy.handleUpgrade`) : rejoue le handshake vers
@@ -42,6 +42,12 @@ export interface ProxyIntrospection {
42
42
  httpPort: number;
43
43
  /** Port HTTPS/2 du backend (re-encrypt). */
44
44
  httpsPort: number;
45
+ /**
46
+ * Port h2c du backend (`servers.h2c`), ou `null` s'il n'est pas activé. Le
47
+ * générateur haproxy y envoie le trafic en HTTP/2 (`proto h2`) ; nginx, qui
48
+ * ne parle pas HTTP/2 à ses amonts, l'ignore.
49
+ */
50
+ h2cPort?: number | null;
45
51
  /** Dossiers statiques servis à la racine `/` (ordre = priorité). */
46
52
  staticRoots: string[];
47
53
  /** Montages statiques préfixés (servis tels quels). */
@@ -71,6 +77,16 @@ export interface ProxyIntrospection {
71
77
  * saines.
72
78
  */
73
79
  keepaliveIntervalMs: number;
80
+ /**
81
+ * Chemin de la sonde de disponibilité (`health.readinessPath`), ou `null`
82
+ * si les sondes sont désactivées.
83
+ *
84
+ * HAProxy la sonde (`option httpchk`) au lieu d'un simple contrôle de
85
+ * connexion : un exemplaire qui draine garde son port ouvert, mais `/readyz`
86
+ * répond déjà `503` — c'est ce qui le retire du répartiteur AVANT qu'il
87
+ * cesse d'accepter. Un contrôle TCP le garderait jusqu'à la fermeture du port.
88
+ */
89
+ readinessPath?: string | null;
74
90
  /**
75
91
  * Terminaison TLS au frontal, ou `null` pour n'écouter qu'en clair.
76
92
  *
@@ -78,6 +78,9 @@ export declare function isAmbiguousPath(url: string): boolean;
78
78
  * un tel handshake est relayé : un `Upgrade` quelconque (`h2c`…) raccordé
79
79
  * octet pour octet ouvrirait un tunnel que rien n'inspecte.
80
80
  *
81
+ * Appelé à CHAQUE handshake (proxy inverse et routes de Nodefony) : aucune
82
+ * allocation — des expressions testées, jamais de découpe de chaîne.
83
+ *
81
84
  * @param method - méthode de la requête
82
85
  * @param headers - en-têtes reçus
83
86
  * @returns la raison du refus, ou `null` si le handshake est conforme
@@ -0,0 +1,37 @@
1
+ import type { Http2SecureServer, Http2Server, ServerOptions } from "node:http2";
2
+ import type { IHttpConfig } from "../../config/config.js";
3
+ /**
4
+ * Limites anti-DoS d'un serveur HTTP/2, lues dans la section `http2` du module :
5
+ * `maxSessionMemory` (amplification mémoire) et `maxConcurrentStreams` dans les
6
+ * SETTINGS (CVE-2023-44487, Rapid Reset). Règle UNIQUE des serveurs HTTP/2 — TLS
7
+ * (5152) comme h2c — : une limite oubliée sur l'un d'eux en ferait la porte de
8
+ * l'attaque.
9
+ *
10
+ * Une limite absente garde le défaut de Node (aucune régression).
11
+ *
12
+ * @param limits - `module.options.http2`.
13
+ * @returns les options à fusionner dans celles de `createServer`/`createSecureServer`.
14
+ */
15
+ export declare function http2ServerLimits(limits: Partial<IHttpConfig["http2"]> | undefined): Pick<ServerOptions, "maxSessionMemory" | "settings">;
16
+ /**
17
+ * Borne l'inactivité des sessions d'un serveur HTTP/2 — TLS (5152) comme h2c —
18
+ * et contourne un défaut de Node qui garde chaque session fermée en vie jusqu'à
19
+ * l'échéance de ce délai.
20
+ *
21
+ * Le défaut (Node 24, 26) : `closeSession` (`internal/http2/core`) marque la
22
+ * session détruite PUIS appelle `session.setTimeout(0)` ; or `setStreamTimeout`
23
+ * sort aussitôt sur un objet détruit. Le minuteur n'est jamais désarmé et retient
24
+ * la session (~2,4 Ko) pendant `timeoutMs` : sous un flux de connexions neuves,
25
+ * connexions/s × délai × 2,4 Ko. Signalé à Node : https://github.com/nodejs/node/issues/66885
26
+ *
27
+ * Le contournement : désarmer à la réception du GOAWAY, AVANT la destruction —
28
+ * c'est ainsi que ferment un proxy et un client Node, même par `destroy()`. Rien
29
+ * n'est retiré à la protection : Node n'agit déjà plus à l'échéance sur une
30
+ * session fermée (`sessionOnTimeout` sort sur `closed`). Une coupure TCP sans
31
+ * GOAWAY garde la rétention, bornée au délai.
32
+ *
33
+ * @param server - le serveur HTTP/2.
34
+ * @param timeoutMs - délai d'inactivité ; 0 ou absent le laisse désactivé.
35
+ * @param onTimeout - appelé à l'échéance (événement `timeout` du serveur).
36
+ */
37
+ export declare function setHttp2SessionTimeout(server: Http2Server | Http2SecureServer, timeoutMs: number | undefined, onTimeout: () => void): void;
@@ -10,6 +10,15 @@ import type { ServersConfig } from "nodefony";
10
10
  * @returns la section typée, ou `undefined` si elle est absente ou n'est pas un objet.
11
11
  */
12
12
  export declare function asServersConfig(servers: unknown): ServersConfig | undefined;
13
+ /**
14
+ * Le serveur h2c démarre-t-il ? Seul un `enabled: true` EXPLICITE l'allume : la
15
+ * section existe toujours (défauts), un port seul ne vaut pas activation.
16
+ *
17
+ * @param entry - `servers.h2c`.
18
+ */
19
+ export declare function isH2cEnabled(entry: {
20
+ enabled?: boolean | undefined;
21
+ } | undefined): boolean;
13
22
  /**
14
23
  * Port DEMANDÉ pour un serveur (`servers.<type>.port`), ou `undefined` si le
15
24
  * serveur est désactivé, absent, ou sans port numérique.
@@ -17,4 +26,4 @@ export declare function asServersConfig(servers: unknown): ServersConfig | undef
17
26
  * @param servers - section `servers` déjà rétrécie ({@link asServersConfig}).
18
27
  * @param type - le serveur visé.
19
28
  */
20
- export declare function configuredServerPort(servers: ServersConfig | undefined, type: "http" | "https"): number | undefined;
29
+ export declare function configuredServerPort(servers: ServersConfig | undefined, type: "http" | "https" | "h2c"): number | undefined;
@@ -3,12 +3,19 @@ import { resolvePortPolicy } from "nodefony";
3
3
  import type { PortPolicy } from "nodefony";
4
4
  export { resolvePortPolicy };
5
5
  export type { PortPolicy };
6
- /** Serveur écoutable (surface minimale commune `http`/`https`/`http2`). */
6
+ /**
7
+ * Serveur écoutable (surface minimale commune `http`/`https`/`http2`).
8
+ *
9
+ * Les écouteurs sont typés événement par événement : c'est la forme que les
10
+ * surcharges de Node acceptent telle quelle, sans conversion chez l'appelant.
11
+ */
7
12
  export interface Listenable {
8
13
  listen(port: number, host?: string): unknown;
9
14
  address(): AddressInfo | string | null;
10
- once(event: string, listener: (...args: never[]) => void): unknown;
11
- removeListener(event: string, listener: (...args: never[]) => void): unknown;
15
+ once(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown;
16
+ once(event: "listening", listener: () => void): unknown;
17
+ removeListener(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown;
18
+ removeListener(event: "listening", listener: () => void): unknown;
12
19
  }
13
20
  /** Nombre de ports essayés après le désiré, en `auto`. */
14
21
  export declare const DEFAULT_PORT_RETRY_ATTEMPTS = 20;
@@ -34,18 +41,35 @@ export interface ServersPortConfig {
34
41
  https?: {
35
42
  port?: number | undefined;
36
43
  } | false;
44
+ h2c?: {
45
+ enabled?: boolean | undefined;
46
+ port?: number | undefined;
47
+ };
37
48
  portPolicy?: PortPolicy;
38
49
  portRetryAttempts?: number;
39
50
  }
51
+ /** Serveur dont `buildBindPlan` compose le plan. */
52
+ export type BoundServer = "http" | "https" | "h2c";
53
+ /**
54
+ * Serveur actif qui convoite le MÊME port que h2c, ou `null`.
55
+ *
56
+ * Un port partagé avec h2c n'est pas un conflit à contourner : sur ce port, le
57
+ * client HTTP/1.1 et le proxy h2c se marchent dessus, et le repli `auto` ferait
58
+ * glisser l'un des deux là où personne ne l'attend. Le boot le refuse.
59
+ *
60
+ * @param servers - `kernel.options.servers`.
61
+ * @returns `"http"` ou `"https"` en conflit, sinon `null` (h2c inactif, port 0).
62
+ */
63
+ export declare function h2cPortConflict(servers: ServersPortConfig | undefined): "http" | "https" | null;
40
64
  /**
41
65
  * Compose le plan de bind d'un serveur depuis la config du kernel — source UNIQUE
42
- * (les deux serveurs l'appellent ; la politique n'est décidée qu'ici).
66
+ * (les serveurs l'appellent ; la politique n'est décidée qu'ici).
43
67
  *
44
68
  * @param which - le serveur qu'on borne.
45
69
  * @param servers - `kernel.options.servers`.
46
70
  * @param environment - `kernel.environment` (arbitre le défaut de la politique).
47
71
  */
48
- export declare function buildBindPlan(which: "http" | "https", servers: ServersPortConfig | undefined, environment: string | undefined): BindPlan;
72
+ export declare function buildBindPlan(which: BoundServer, servers: ServersPortConfig | undefined, environment: string | undefined): BindPlan;
49
73
  /**
50
74
  * Sonde « un serveur répond-il déjà ici ? ».
51
75
  *