@nodefony/http 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.
Files changed (155) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +77 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
  6. package/dist/index.js +108 -0
  7. package/dist/nodefony/command/assetsPublishCommand.js +102 -0
  8. package/dist/nodefony/command/certificatesCommand.js +47 -0
  9. package/dist/nodefony/command/networkCommand.js +27 -0
  10. package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
  11. package/dist/nodefony/config/config.js +335 -0
  12. package/dist/nodefony/config/defineModuleConfig.js +93 -0
  13. package/dist/nodefony/interfaces/IContext.js +1 -0
  14. package/dist/nodefony/interfaces/ICookie.js +1 -0
  15. package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
  16. package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
  17. package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
  18. package/dist/nodefony/interfaces/IRequest.js +1 -0
  19. package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
  20. package/dist/nodefony/interfaces/IResponse.js +1 -0
  21. package/dist/nodefony/interfaces/ISession.js +1 -0
  22. package/dist/nodefony/interfaces/IUpload.js +1 -0
  23. package/dist/nodefony/interfaces/index.js +1 -0
  24. package/dist/nodefony/service/HttpAdminApi.js +376 -0
  25. package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
  26. package/dist/nodefony/service/audit-logger.js +159 -0
  27. package/dist/nodefony/service/certificates.js +545 -0
  28. package/dist/nodefony/service/error-renderer.js +320 -0
  29. package/dist/nodefony/service/http-kernel.js +948 -0
  30. package/dist/nodefony/service/pretty-request-logger.js +72 -0
  31. package/dist/nodefony/service/request-logger.js +54 -0
  32. package/dist/nodefony/service/servers/clientError.js +20 -0
  33. package/dist/nodefony/service/servers/server-http.js +135 -0
  34. package/dist/nodefony/service/servers/server-https.js +204 -0
  35. package/dist/nodefony/service/servers/server-static.js +192 -0
  36. package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
  37. package/dist/nodefony/service/servers/server-websocket.js +104 -0
  38. package/dist/nodefony/service/servers/serverShutdown.js +31 -0
  39. package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
  40. package/dist/nodefony/service/sessions/sessions-service.js +580 -0
  41. package/dist/nodefony/service/trace.js +72 -0
  42. package/dist/nodefony/service/upload/upload-service.js +171 -0
  43. package/dist/nodefony/src/assets/collectAssets.js +34 -0
  44. package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
  45. package/dist/nodefony/src/context/Context.js +415 -0
  46. package/dist/nodefony/src/context/domainMatcher.js +88 -0
  47. package/dist/nodefony/src/context/forwarded.js +185 -0
  48. package/dist/nodefony/src/context/http/HttpContext.js +309 -0
  49. package/dist/nodefony/src/context/http/Request.js +543 -0
  50. package/dist/nodefony/src/context/http/Response.js +368 -0
  51. package/dist/nodefony/src/context/http/parser.js +188 -0
  52. package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
  53. package/dist/nodefony/src/context/http2/Request.js +29 -0
  54. package/dist/nodefony/src/context/http2/Response.js +97 -0
  55. package/dist/nodefony/src/context/metaData.js +47 -0
  56. package/dist/nodefony/src/context/requestId.js +41 -0
  57. package/dist/nodefony/src/context/trustProxy.js +167 -0
  58. package/dist/nodefony/src/context/websocket/Response.js +181 -0
  59. package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
  60. package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
  61. package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
  62. package/dist/nodefony/src/cookies/cookie.js +258 -0
  63. package/dist/nodefony/src/errors/httpError.js +69 -0
  64. package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
  65. package/dist/nodefony/src/profiler/Profiler.js +139 -0
  66. package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
  67. package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
  68. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
  69. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
  70. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
  71. package/dist/nodefony/src/servers/portBinder.js +114 -0
  72. package/dist/nodefony/src/session/session.js +390 -0
  73. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
  74. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
  75. package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
  76. package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
  77. package/dist/types/index.d.ts +83 -0
  78. package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
  79. package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
  80. package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
  81. package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
  82. package/dist/types/nodefony/config/config.d.ts +197 -0
  83. package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
  84. package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
  85. package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
  86. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
  87. package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
  88. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
  89. package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
  90. package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
  91. package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
  92. package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
  93. package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
  94. package/dist/types/nodefony/interfaces/index.d.ts +7 -0
  95. package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
  96. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
  97. package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
  98. package/dist/types/nodefony/service/certificates.d.ts +246 -0
  99. package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
  100. package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
  101. package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
  102. package/dist/types/nodefony/service/request-logger.d.ts +18 -0
  103. package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
  104. package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
  105. package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
  106. package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
  107. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
  108. package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
  109. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
  110. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
  111. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
  112. package/dist/types/nodefony/service/trace.d.ts +39 -0
  113. package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
  114. package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
  115. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
  116. package/dist/types/nodefony/src/context/Context.d.ts +195 -0
  117. package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
  118. package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
  119. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
  120. package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
  121. package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
  122. package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
  123. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
  124. package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
  125. package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
  126. package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
  127. package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
  128. package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
  129. package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
  130. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
  131. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
  132. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
  133. package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
  134. package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
  135. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
  136. package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
  137. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
  138. package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
  139. package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
  140. package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
  141. package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
  142. package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
  143. package/dist/types/nodefony/src/session/session.d.ts +171 -0
  144. package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
  145. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
  146. package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
  147. package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
  148. package/docs/cookies.md +365 -0
  149. package/docs/index.md +163 -0
  150. package/docs/observabilite.md +460 -0
  151. package/docs/rate-limit.md +372 -0
  152. package/docs/servers.md +935 -0
  153. package/docs/session.md +768 -0
  154. package/docs/upload.md +460 -0
  155. package/package.json +101 -0
@@ -0,0 +1,68 @@
1
+ import http, { OutgoingHttpHeaders } from "node:http";
2
+ import http2 from "node:http2";
3
+ import HttpContext from "../http/HttpContext.js";
4
+ import { Pci, Pdu, Message, Severity, Msgid } from "nodefony";
5
+ import Cookie from "../../cookies/cookie.js";
6
+ declare class HttpResponse {
7
+ #private;
8
+ context: HttpContext;
9
+ response: http.ServerResponse | http2.Http2ServerResponse | null;
10
+ statusCode: number;
11
+ statusMessage: string;
12
+ flushing: boolean;
13
+ encoding: BufferEncoding;
14
+ body: Buffer | null;
15
+ contentType: string;
16
+ headers: http.OutgoingHttpHeaders;
17
+ timeout?: number;
18
+ cookies: Record<string, Cookie>;
19
+ constructor(response: http.ServerResponse | http2.Http2ServerResponse, context: HttpContext);
20
+ /**
21
+ * Filet posé juste avant l'émission des en-têtes : si aucun `Content-Type`
22
+ * n'a été choisi (ni négociation, ni `render`, ni controller), émet le défaut
23
+ * `this.contentType` (application/octet-stream) — comportement identique à
24
+ * l'ancienne pose au constructeur, sans le ping-pong set/remove/re-set.
25
+ */
26
+ protected ensureContentTypeHeader(): void;
27
+ clean(): void;
28
+ isHeaderSent(): boolean;
29
+ isHtml(): boolean;
30
+ setTimeout(ms: number): void;
31
+ addCookie(cookie: Cookie): Cookie;
32
+ deleteCookie(cookie: Cookie): boolean;
33
+ deleteCookieByName(name: string): boolean;
34
+ setCookies(): void | http.ServerResponse<http.IncomingMessage>;
35
+ setCookie(cookie: Cookie): void | http.ServerResponse<http.IncomingMessage>;
36
+ setHeader(name: string, value: number | string | readonly string[]): void | http.ServerResponse<http.IncomingMessage>;
37
+ setHeaders(obj: OutgoingHttpHeaders): OutgoingHttpHeaders;
38
+ setContentType(type?: string, encoding?: BufferEncoding): void | http.ServerResponse<http.IncomingMessage>;
39
+ setFileMimeType(type: string, encoding?: BufferEncoding): void | http.ServerResponse<http.IncomingMessage>;
40
+ setContentTypeByExtension(extention: string): void | http.ServerResponse<http.IncomingMessage>;
41
+ getMimeType(filenameOrExt: string): string | false;
42
+ setEncoding(encoding: BufferEncoding): BufferEncoding;
43
+ setStatusCode(status: number | string, message?: string): {
44
+ code: number;
45
+ message: string;
46
+ };
47
+ getStatus(): {
48
+ code: number;
49
+ message: string;
50
+ };
51
+ getStatusCode(): number;
52
+ getStatusMessage(code?: number | string): string;
53
+ setBody(ele: unknown, encoding?: BufferEncoding | undefined): Buffer;
54
+ setLength(body?: string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
55
+ writeHead(statusCode?: number, headers?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): void;
56
+ addTrailers(headers: http.OutgoingHttpHeaders): void;
57
+ flush(chunk: unknown, encoding: BufferEncoding): Promise<HttpResponse>;
58
+ send(chunk?: unknown, encoding?: BufferEncoding, _flush?: boolean): Promise<HttpResponse>;
59
+ write(chunk?: unknown, encoding?: BufferEncoding): Promise<HttpResponse>;
60
+ writeContinue(): void | undefined;
61
+ end(chunk?: string | Buffer, encoding?: BufferEncoding): Promise<http.ServerResponse | http2.ServerHttp2Stream>;
62
+ getHeader(name: string): string | number | string[] | undefined;
63
+ hasHeader(name: string): boolean;
64
+ getHeaders(): http.OutgoingHttpHeaders;
65
+ redirect(url: string, status?: number | string, headers?: Record<string, string | number>): this;
66
+ log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
67
+ }
68
+ export default HttpResponse;
@@ -0,0 +1,65 @@
1
+ import HttpRequest from "./Request.js";
2
+ import Http2Request from "../http2/Request.js";
3
+ import QS from "qs";
4
+ import xml2js from "xml2js";
5
+ declare class Parser {
6
+ request: HttpRequest | Http2Request;
7
+ chunks: Buffer[];
8
+ private received;
9
+ private aborted;
10
+ private overflow;
11
+ private onOverflow;
12
+ constructor(request: HttpRequest | Http2Request);
13
+ write(buffer: Buffer): Buffer;
14
+ /**
15
+ * Résout quand le flux requête est **entièrement reçu** (`end`). No-op si déjà
16
+ * terminé (corps vide / déjà drainé) → pas de hang d'un `once("end")` tardif.
17
+ * Indispensable avant de concaténer les chunks pour un corps à décoder
18
+ * (JSON…) : sinon on lit un buffer encore incomplet. (formidable drainait via
19
+ * son `await form.parse()` ; ses remplaçants doivent drainer explicitement.)
20
+ * Rejette en 413 si le corps dépasse `maxBodySize` (avant ou pendant l'attente).
21
+ */
22
+ protected ended(): Promise<void>;
23
+ parse(): Promise<this>;
24
+ }
25
+ declare class ParserQs extends Parser {
26
+ parserOptions: QS.IParseOptions;
27
+ charset: BufferEncoding;
28
+ constructor(request: HttpRequest | Http2Request);
29
+ parse(): Promise<this>;
30
+ }
31
+ declare class ParserXml extends Parser {
32
+ xmlParser: xml2js.Parser;
33
+ charset: BufferEncoding;
34
+ constructor(request: HttpRequest | Http2Request, settingsXml?: xml2js.ParserOptions);
35
+ parse(): Promise<this>;
36
+ }
37
+ /**
38
+ * Parse un corps `application/json` (ou `*+json`) en objet → `queryPost`
39
+ * (source lue par le décorateur `@Body`). Reprend le rôle de l'ancien plugin
40
+ * `json` de formidable, supprimé avec le passage à busboy (multipart seul).
41
+ * Lenient : un corps JSON malformé est ignoré (pas de throw — `parse()` est
42
+ * invoqué non-awaité par `initialize`, un rejet serait non géré) ; le brut
43
+ * reste disponible dans `request.data`.
44
+ */
45
+ declare class ParserJson extends Parser {
46
+ charset: BufferEncoding;
47
+ constructor(request: HttpRequest | Http2Request);
48
+ parse(): Promise<this>;
49
+ }
50
+ /** Media-range « accepte tout » (wildcard type + sous-type) — repli sûr, jamais throw. */
51
+ /**
52
+ * Une entrée d'en-tête `Accept` analysée : le type et le sous-type compilés en
53
+ * expressions régulières, plus les paramètres bruts du segment (`q`, `charset`,
54
+ * `level`…). L'index est ouvert parce que le client choisit ces noms — mais ses
55
+ * valeurs, elles, sont connues : une regex pour le type, un nombre pour `q`, une
56
+ * chaîne pour le reste.
57
+ */
58
+ type AcceptEntry = {
59
+ type: RegExp;
60
+ subtype: RegExp;
61
+ q?: number;
62
+ [key: string]: RegExp | number | string | undefined;
63
+ };
64
+ declare const acceptParser: (acc?: string) => AcceptEntry[];
65
+ export { Parser, ParserXml, ParserQs, ParserJson, acceptParser };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Fast-path d'analyse du request-target — évite le `new URL` (parse WHATWG
3
+ * complet) sur le chemin nominal, où le target est déjà sous sa forme
4
+ * canonique et où pathname/search s'extraient par simple découpe.
5
+ *
6
+ * ⚠️ SÉCURITÉ — le contrat qui rend la découpe légale : la normalisation
7
+ * WHATWG (dot-segments — y compris leurs formes percent-encodées `%2e` —,
8
+ * conversion `\` → `/`, percent-encoding, punycode/lowercase du host,
9
+ * normalisation des hosts « IPv4-like » type `127.1`/`0x7f.1`) PROTÈGE le
10
+ * routing et le matching de zones du firewall. La découpe n'est donc admise
11
+ * QUE si elle est prouvablement l'IDENTITÉ : tout caractère ou motif que le
12
+ * parseur WHATWG transformerait — ou dont l'innocuité n'est pas certaine —
13
+ * déclenche le bail-out (`null`) et le vrai `new URL` est construit, comme
14
+ * avant. Whitelist stricte : le doute coûte un parse, jamais un contournement.
15
+ *
16
+ * La preuve est portée par `tests/unit/urlFastPath.test.ts` : balayage
17
+ * exhaustif par caractère (0x00-0x7F, plus témoins > 0x7F) comparant chaque
18
+ * target ACCEPTÉ au résultat du vrai `new URL` — pathname, search et href
19
+ * doivent être STRICTEMENT identiques à la découpe.
20
+ */
21
+ /**
22
+ * Résultat d'une découpe acceptée : les deux composants, dans la même forme
23
+ * que `URL.pathname` / `URL.search` (le `?` inclus, `""` si query absente ou
24
+ * vide — `/x?` donne `search === ""` comme WHATWG).
25
+ */
26
+ export interface ISplitTarget {
27
+ pathname: string;
28
+ search: string;
29
+ }
30
+ /**
31
+ * Découpe un request-target origin-form (`/path[?query]`) en
32
+ * pathname/search SI — et seulement si — le parse WHATWG serait l'identité.
33
+ *
34
+ * @param target - le request-target brut (`IncomingMessage.url` en HTTP/1,
35
+ * pseudo-header `:path` en HTTP/2).
36
+ * @returns les composants découpés, ou `null` (bail-out → vrai `new URL`).
37
+ */
38
+ export declare function splitTarget(target: unknown): ISplitTarget | null;
39
+ /**
40
+ * L'autorité (`host[:port]` brut de l'en-tête `Host` / `:authority`) est-elle
41
+ * déjà sous la forme exacte que le parseur WHATWG rendrait ?
42
+ *
43
+ * Refusé (→ bail-out) : majuscules (lowercase WHATWG), IPv6 (`[`), toute
44
+ * forme « IPv4-like » ambiguë (`127.1`, `0x7f.1`, `2130706433` — WHATWG les
45
+ * NORMALISE en dotted-quad, un matcher les lisant brut serait contournable),
46
+ * label vide (`a..b`, `.a`, `a.`), port par défaut du scheme (élidé par
47
+ * WHATWG) ou avec zéro de tête, percent-encoding, non-ASCII (punycode).
48
+ *
49
+ * @param host - autorité brute (peut contenir `:port`).
50
+ * @param scheme - scheme effectif (`http` | `https`) — décide du port par défaut.
51
+ */
52
+ export declare function isCanonicalAuthority(host: unknown, scheme: string): host is string;
@@ -0,0 +1,14 @@
1
+ import http2 from "node:http2";
2
+ import HttpContext from "../http/HttpContext.js";
3
+ import HttpRequest from "../http/Request.js";
4
+ import { HTTPMethod } from "../Context.js";
5
+ declare class Http2Request extends HttpRequest {
6
+ request: http2.Http2ServerRequest;
7
+ constructor(request: http2.Http2ServerRequest, context: HttpContext);
8
+ getHost(): string | undefined;
9
+ getUserAgent(): string | undefined;
10
+ getMethod(): HTTPMethod;
11
+ getRawTarget(): string | undefined;
12
+ protected resolveScheme(): string;
13
+ }
14
+ export default Http2Request;
@@ -0,0 +1,20 @@
1
+ import http2 from "node:http2";
2
+ import http from "node:http";
3
+ import HttpContext from "../http/HttpContext.js";
4
+ import HttpResponse from "../http/Response.js";
5
+ declare class Http2Response extends HttpResponse {
6
+ statusCode: number;
7
+ stream: http2.ServerHttp2Stream | null;
8
+ streamId?: number | undefined;
9
+ constructor(response: http2.Http2ServerResponse, context: HttpContext);
10
+ isHeaderSent(): boolean;
11
+ writeHead(statusCode?: number, headers?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[]): void;
12
+ send(chunk?: unknown, encoding?: BufferEncoding, _flush?: boolean): Promise<Http2Response>;
13
+ end(chunk?: string | Buffer, encoding?: BufferEncoding): Promise<http.ServerResponse | http2.ServerHttp2Stream>;
14
+ getStatusMessage(code?: number | string): string;
15
+ getStatus(): {
16
+ code: number;
17
+ message: string;
18
+ };
19
+ }
20
+ export default Http2Response;
@@ -0,0 +1,58 @@
1
+ import type { Data, MetaData } from "../../service/http-kernel.js";
2
+ /**
3
+ * Forme structurelle minimale d'une route nécessaire pour bâtir l'enveloppe
4
+ * `nodefony.route`. Découplée de la classe `Route` de `@nodefony/framework`
5
+ * (pas d'import, pas de cycle, pas de fuite d'instance partagée).
6
+ */
7
+ export interface IMetaDataRouteSource {
8
+ name: string;
9
+ path?: string;
10
+ }
11
+ /**
12
+ * Contexte structurel minimal nécessaire pour assembler `Context.metaData`.
13
+ * Volontairement découplé de `Context` (typage par forme, comme
14
+ * `IParamArgContext`) → {@link buildMetaData} est une fonction pure, testable en
15
+ * unit avec une fausse source, sans démarrer de serveur. Le vrai `Context`
16
+ * satisfait cette forme.
17
+ */
18
+ export interface IMetaDataSource {
19
+ kernel?: {
20
+ projectName?: string;
21
+ version?: string;
22
+ environment?: MetaData["environment"];
23
+ debug?: MetaData["debug"];
24
+ } | null;
25
+ request?: {
26
+ url?: URL;
27
+ href?: string;
28
+ } | null;
29
+ scheme: MetaData["scheme"];
30
+ requestId: string;
31
+ resolver?: {
32
+ route: IMetaDataRouteSource | null;
33
+ getMatchedParams(): Record<string, unknown>;
34
+ } | null;
35
+ }
36
+ /**
37
+ * Assemble l'enveloppe `metaData` d'une requête EN PLACE (builder monomorphe) :
38
+ * met à jour les champs de `target.nodefony` dans un ordre fixe → V8 garde une
39
+ * hidden class stable et inline les écritures. Remplace l'ancien
40
+ * `extend(true, …)` qui deep-clonait/dispatchait à chaque réponse JSON et chaque
41
+ * frame WS — inutile ici puisque `target` est l'objet metaData per-requête
42
+ * (jamais partagé).
43
+ *
44
+ * `route` est un **snapshot per-requête** `{ name, path, variablesMap }` : on ne
45
+ * diffuse jamais l'instance `Route` partagée (statique), dont les variables
46
+ * matchées seraient écrasées par toute requête/connexion concurrente (bleed).
47
+ *
48
+ * `override` (ex. frame WS `{ nodefony: { websocket } }`) est fusionné *shallow*
49
+ * dans l'enveloppe : le seul cas réel est l'ajout d'un sous-objet `nodefony`
50
+ * peu profond — pas besoin du deep-merge récursif générique. Toute clé top-level
51
+ * hors `nodefony` est préservée telle quelle.
52
+ *
53
+ * @param target - objet metaData per-requête à muter (et retourner)
54
+ * @param src - source structurelle (le `Context` réel, ou un faux en test)
55
+ * @param override - overrides optionnels de l'appelant
56
+ * @returns le même `target`, muté
57
+ */
58
+ export declare function buildMetaData(target: Data, src: IMetaDataSource, override?: Record<string, unknown>): Data;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Validation de l'identifiant de corrélation `X-Request-Id` fourni par le
3
+ * client, avant qu'il ne soit adopté comme `Context.requestId`.
4
+ *
5
+ * Le `requestId` finit (1) réfléchi dans la réponse (`X-Request-Id`),
6
+ * (2) écrit dans les logs (`ID : …`), (3) propagé en ALS à tout le pipeline.
7
+ * Une valeur cliente non assainie ouvre donc : log-injection (CR/LF dans les
8
+ * logs), forging de corrélation, et throw `setHeader` natif (→ 500/DoS) sur
9
+ * caractère de contrôle ou non-ASCII. Cf RFC 9110 §5.5 (field values) + Zero
10
+ * Trust (mémoire `feedback_security_rfc_rigor`).
11
+ */
12
+ /**
13
+ * Longueur maximale acceptée pour un `X-Request-Id` client. Borne anti-abus
14
+ * (log flooding / header oversize). 128 couvre largement un UUID (36), un
15
+ * nanoid, ou un `traceparent` (55).
16
+ */
17
+ export declare const MAX_REQUEST_ID_LENGTH = 128;
18
+ /**
19
+ * Valide un `X-Request-Id` entrant. Retourne la valeur si elle est sûre, sinon
20
+ * `null` (l'appelant conserve alors l'UUID généré côté serveur).
21
+ *
22
+ * On REJETTE (plutôt que tronquer/nettoyer) une valeur invalide : nettoyer
23
+ * donnerait au client un faux contrôle sur l'identifiant et masquerait l'abus.
24
+ *
25
+ * @param raw - valeur brute du header `x-request-id` (ou `undefined`/`null`).
26
+ * @returns la valeur si sûre, sinon `null`.
27
+ */
28
+ export declare function sanitizeRequestId(raw: string | undefined | null): string | null;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Politique de confiance envers les en-têtes `X-Forwarded-*` (et le scheme
3
+ * proxifié), pour décider si la connexion entrante provient d'un reverse-proxy
4
+ * légitime.
5
+ *
6
+ * Sans cette barrière, n'importe quel client peut envoyer
7
+ * `X-Forwarded-For: 1.2.3.4` → IP spoofée (contournement de rate-limit /
8
+ * d'allow-list IP, falsification des logs d'audit) et `X-Forwarded-Proto: https`
9
+ * → faux scheme. Cf RFC 7239 / OWASP « Host header & forwarded headers ».
10
+ */
11
+ /**
12
+ * Valeur de configuration `trustProxy` :
13
+ * - `false` (défaut sûr) : ne JAMAIS faire confiance aux `X-Forwarded-*`.
14
+ * - `true` : confiance totale (déploiement où le LB est l'unique point d'entrée).
15
+ * - `string` / `string[]` : IP, CIDR (`10.0.0.0/8`, `::1/128`) ou presets
16
+ * `"loopback"`, `"linklocal"`, `"uniquelocal"`.
17
+ */
18
+ export type TrustProxyConfig = boolean | string | string[];
19
+ /** Décide si l'adresse distante (socket réel) est un proxy de confiance. */
20
+ export interface TrustProxyChecker {
21
+ isTrusted(remoteAddress: string | undefined | null): boolean;
22
+ }
23
+ /**
24
+ * Compile une config `trustProxy` en un {@link TrustProxyChecker} (une fois, au
25
+ * boot — pas par requête). Les entrées invalides lèvent à la compilation (fail
26
+ * fast) plutôt que silencieusement par requête.
27
+ *
28
+ * @param config - voir {@link TrustProxyConfig}. `undefined` → aucune confiance.
29
+ * @returns un checker `isTrusted(remoteAddress)`.
30
+ */
31
+ export declare function buildTrustProxy(config: TrustProxyConfig | undefined): TrustProxyChecker;
32
+ /**
33
+ * Cœur de la résolution **from-right** (OWASP) partagé par `X-Forwarded-For`
34
+ * (de-facto) et le paramètre `for` du header `Forwarded` (RFC 7239 §5.2).
35
+ *
36
+ * `chain` = liste des maillons forwarded NORMALISÉS (IP nues, gauche→droite :
37
+ * `[client, proxy1, proxy2]`), telle qu'écrite par les proxies par **append à
38
+ * droite**. La partie gauche est forgeable par le client (cf {@link extractClientIp}).
39
+ * La résolution part de la **connexion réelle** (le socket, non forgeable) et
40
+ * remonte de DROITE à GAUCHE : tant que le maillon courant est un proxy de
41
+ * confiance, on passe au précédent ; le **premier maillon non fiable** est l'IP
42
+ * cliente réelle.
43
+ *
44
+ * Un maillon `null` (identifiant obfusqué `_secret`, `unknown`, ou node illisible
45
+ * — RFC 7239 §6.3/§8.3) est une **barrière non franchissable** : on ne peut pas
46
+ * en vérifier la confiance → on s'arrête et on retourne le dernier maillon de
47
+ * confiance connu (une vraie IP de l'infra), jamais `null` ni la valeur obfusquée.
48
+ *
49
+ * @param chain - maillons forwarded normalisés (IP ou `null` si obfusqué).
50
+ * @param socketAddress - `socket.remoteAddress` (connexion TCP réelle, fiable).
51
+ * @param checker - politique de confiance ({@link buildTrustProxy}).
52
+ * @returns l'IP cliente réelle résolue (toujours une valeur fiable).
53
+ */
54
+ export declare function resolveFromRight(chain: ReadonlyArray<string | null>, socketAddress: string, checker: TrustProxyChecker): string;
55
+ /**
56
+ * Résout l'adresse IP cliente RÉELLE à partir de la connexion socket et de la
57
+ * chaîne `X-Forwarded-For`, en dépouillant les proxies de confiance **de droite
58
+ * à gauche** (algorithme OWASP).
59
+ *
60
+ * `X-Forwarded-For` (de-facto) est construit par **append de gauche à droite** :
61
+ * `XFF: client, proxy1, proxy2`. Chaque proxy ajoute À DROITE l'adresse qu'il a
62
+ * vue. La partie **gauche** est donc entièrement **forgeable** par le client
63
+ * (il peut envoyer `X-Forwarded-For: 1.2.3.4` ; le 1ᵉʳ proxy ne fait qu'append
64
+ * l'IP réelle après). Lire `XFF[0]` revient à lire la valeur du client →
65
+ * **IP spoofing** (contournement de ban / rate-limit / allow-list / audit).
66
+ *
67
+ * Hot path préservé : sans `X-Forwarded-For` (cas direct usuel) la fonction
68
+ * retourne immédiatement le socket — 0 allocation (pas de split). Pour le header
69
+ * standard `Forwarded` (RFC 7239), voir `resolveForwarded` (module `forwarded`).
70
+ *
71
+ * @param xff - valeur brute de l'en-tête `X-Forwarded-For` (string, ou string[]
72
+ * si l'en-tête est répété), ou `undefined`.
73
+ * @param socketAddress - `socket.remoteAddress` (la connexion TCP réelle).
74
+ * @param checker - politique de confiance ({@link buildTrustProxy}).
75
+ * @returns l'IP cliente réelle, ou `null` si aucune connexion socket fiable.
76
+ */
77
+ export declare function extractClientIp(xff: string | string[] | undefined, socketAddress: string | undefined | null, checker: TrustProxyChecker): string | null;
@@ -0,0 +1,53 @@
1
+ import Cookie from "../../cookies/cookie.js";
2
+ import { Message, Msgid, Pci, Severity } from "nodefony";
3
+ import WebsocketContext from "./WebsocketContext.js";
4
+ import Ws from "ws";
5
+ export type { IWsCookie } from "../../../interfaces/ICookie.js";
6
+ /**
7
+ * `true` si l'erreur d'écriture signifie seulement que le client s'est
8
+ * déconnecté (frame perdue, connexion en cours de fermeture) — à logger DEBUG,
9
+ * jamais ERROR + stack (vécu : un simple reload de page pendant un ping WS
10
+ * remplissait le journal d'« Error: write EPIPE »).
11
+ */
12
+ export declare function isPeerGoneError(error: unknown): boolean;
13
+ declare class WebsocketResponse {
14
+ private context;
15
+ statusCode: number;
16
+ body: Buffer | null;
17
+ encoding: BufferEncoding;
18
+ connection: Ws | null;
19
+ statusMessage: string;
20
+ webSocketVersion?: number;
21
+ cookies: Record<string, Cookie>;
22
+ constructor(connection: Ws | null, context: WebsocketContext);
23
+ log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): import("nodefony").Pdu | undefined;
24
+ setConnection(connection: Ws): Ws;
25
+ send(data?: Buffer | string | null, encoding?: BufferEncoding): Promise<WebsocketResponse>;
26
+ broadcast(data?: Buffer | string | null, type?: BufferEncoding): void;
27
+ /**
28
+ * Logge UNE seule fois par connexion (au 1er drop) qu'un client subit la
29
+ * backpressure — observabilité opérateur sans bruit dans le hot path.
30
+ */
31
+ private logFirstDrop;
32
+ setBody(ele: string | NodeJS.ArrayBufferView | ArrayBuffer | SharedArrayBuffer, encoding?: BufferEncoding): Buffer<ArrayBufferLike> | null;
33
+ drop(reasonCode: number, description: string): void;
34
+ close(reasonCode: number, description: string): void;
35
+ getStatus(): {
36
+ code: number;
37
+ message: string;
38
+ };
39
+ getStatusCode(): number;
40
+ getStatusMessage(): string;
41
+ setStatusCode(status: number | string, message?: string): {
42
+ code: number;
43
+ message: string;
44
+ };
45
+ clean(): void;
46
+ setEncoding(encoding: BufferEncoding): BufferEncoding;
47
+ setHeader(): boolean;
48
+ setHeaders(): boolean;
49
+ addCookie(cookie: Cookie): void;
50
+ setCookies(): void;
51
+ setCookie(_cookie: Cookie): void;
52
+ }
53
+ export default WebsocketResponse;
@@ -0,0 +1,125 @@
1
+ import Context from "../Context.js";
2
+ import { ServerType, SchemeType } from "../../../service/http-kernel.js";
3
+ import { Severity, Msgid, Message, nodefonyError, Scope } from "nodefony";
4
+ import Ws from "ws";
5
+ import type { IncomingMessage } from "node:http";
6
+ import WebsocketResponse from "./Response.js";
7
+ import { URL } from "node:url";
8
+ import { HTTPMethod } from "../Context.js";
9
+ import HttpError from "../../errors/httpError.js";
10
+ import { FrameProfile } from "../../profiler/FrameProfile.js";
11
+ import type { PhaseName } from "../../../interfaces/IContext.js";
12
+ import { ProxyType } from "../http/HttpContext.js";
13
+ import { type ResolvedProxy } from "../forwarded.js";
14
+ export interface IWsRequestExtension {
15
+ url: URL;
16
+ query: Record<string, string>;
17
+ queryGet: Record<string, string>;
18
+ path: string;
19
+ }
20
+ export type WsIncomingMessage = IncomingMessage & IWsRequestExtension;
21
+ import type { IWebsocketContext as IWebsocketContextInterface } from "../../../interfaces/IContext.js";
22
+ /**
23
+ * Coerce un code (applicatif / HTTP / WS) en code de fermeture WebSocket VALIDE
24
+ * et conforme RFC 6455 §7.4, en PRÉFÉRANT les codes standard §7.4.1 quand le
25
+ * sens existe :
26
+ * - code déjà valide émissible (1000-1003, 1007-1011, 3000-4999) → conservé ;
27
+ * - HTTP 5xx / interne / code absent → **1011** (Internal Error) ;
28
+ * - HTTP 401 / 403 / 421 → **1008** (Policy Violation) ;
29
+ * - autre `< 1000` (ex. 404, sans équivalent RFC) → **4004**, plage privée
30
+ * 4000-4999 (§7.4.2, « undefined by this protocol » → convention applicative).
31
+ *
32
+ * Évite d'émettre un code de la plage 0-999 (« not used », rejeté par `ws`) ou
33
+ * un code réservé non émissible (1004/1005/1006/1015).
34
+ *
35
+ * @param code - code source (number, undefined…).
36
+ * @returns un code de fermeture WS valide.
37
+ */
38
+ export declare function toWsCloseCode(code: number | undefined | null): number;
39
+ export default class WebsocketContext extends Context implements IWebsocketContextInterface {
40
+ #private;
41
+ request: WsIncomingMessage | null;
42
+ response: WebsocketResponse | null;
43
+ acceptedProtocol?: string;
44
+ port: number | string;
45
+ rejected: boolean;
46
+ teardownWired: boolean;
47
+ connection: Ws | null;
48
+ origin: string;
49
+ proxy: ProxyType | null;
50
+ forwarded: ResolvedProxy | null;
51
+ wsUrl: URL | null;
52
+ queryGet: Record<string, string>;
53
+ queryRequest: Record<string, string>;
54
+ wsPath: string;
55
+ constructor(scope: Scope, req: IncomingMessage, ws: Ws, type: ServerType);
56
+ log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message): import("nodefony").Pdu;
57
+ logRequest(httpError?: Error | HttpError | nodefonyError | null, acceptedProtocol?: string | null): import("nodefony").Pdu | undefined;
58
+ connect(): Promise<Ws>;
59
+ handle(data?: unknown[]): Promise<this>;
60
+ render(chunk: unknown, encoding?: BufferEncoding): Promise<WebsocketResponse>;
61
+ send(data?: string | Buffer | null, encoding?: BufferEncoding): Promise<WebsocketResponse>;
62
+ broadcast(data?: string | Buffer | null, encoding?: BufferEncoding): void | null;
63
+ /**
64
+ * Logge le CONTENU d'un message WS (corrélé `requestId` via l'override `log`),
65
+ * gaté hors prod et borné — le Suivi de requête (Studio) le surface alors par
66
+ * direction. Hot path : le gate booléen court-circuite en prod AVANT toute
67
+ * construction de chaîne (0 allocation / 0 concat).
68
+ *
69
+ * @param dir - sens du message du point de vue serveur.
70
+ * @param data - charge utile (string, Buffer binaire, objet, ou null).
71
+ */
72
+ private logMessageContent;
73
+ /**
74
+ * Ouvre le profil d'**une invocation** du pont RPC (une frame `api.request`).
75
+ *
76
+ * Le contexte WS vit pour la CONNEXION : ses `phases` sont cumulatives et son
77
+ * `requestId` est unique pour toute la socket. Une frame reçoit donc son
78
+ * propre profil, identifié `<requestId de la connexion>.<n° de frame>` — le
79
+ * `id` JSON-RPC ne peut pas servir de clé : il est choisi par le client.
80
+ *
81
+ * @param method - méthode LOGIQUE de l'invocation (`GET`, `POST`…).
82
+ * @param framePath - chemin invoqué par la frame (jamais l'URL de la
83
+ * connexion) — nommé ainsi parce que `url` masquerait le module
84
+ * `node:url`, importé ici et utilisé par `connect()`.
85
+ * @returns le profil, ou `null` si profiler ET timing sont éteints (prod) —
86
+ * zéro allocation dans ce cas.
87
+ */
88
+ beginFrame(method: string, framePath: string): FrameProfile | null;
89
+ /**
90
+ * Enregistre le profil d'une invocation terminée dans le ring buffer du
91
+ * Profiler (no-op hors dev, ou si aucun profil n'a été ouvert).
92
+ */
93
+ collectFrame(frame: FrameProfile | null): void;
94
+ /**
95
+ * Les phases émises pendant une invocation du pont (`initialize` par le
96
+ * Resolver, `render` par le Controller…) vont dans le profil de **la frame**,
97
+ * pas du contexte : sans cette redirection, `Context.phases` accumulerait deux
98
+ * entrées par message pour toute la vie de la socket (timeline cumulative
99
+ * ET croissance sans borne). Hors invocation (handshake), comportement de base.
100
+ */
101
+ phaseStart(name: PhaseName): void;
102
+ phaseEnd(name: PhaseName): void;
103
+ handleMessage(data: Buffer | string, isBinary: boolean): Promise<unknown>;
104
+ onClose(code: number, reason: Buffer): void;
105
+ /**
106
+ * Listener `error` de la socket ws (OBLIGATOIRE — sans lui, un 'error' émis
107
+ * sans listener crashe le process via EventEmitter). `ws` émet 'error' PUIS
108
+ * 'close' → le teardown se fait dans {@link onClose} ; ici on logge seulement
109
+ * (pas de double-close). Erreur transport (reset TCP, frame corrompue…).
110
+ *
111
+ * @param error - erreur émise par la socket.
112
+ */
113
+ onConnectionError(error: Error): void;
114
+ setScheme(): SchemeType;
115
+ getRemoteAddress(): string | null;
116
+ getHost(): string | undefined;
117
+ getHostName(): string | undefined;
118
+ getUserAgent(): string;
119
+ getMethod(): HTTPMethod;
120
+ setContextJson(): void;
121
+ clean(): void;
122
+ close(reasonCode: number | undefined | null, description: string): void;
123
+ drop(reasonCode: number, description: string): void;
124
+ reject(code: number | string | undefined, message?: string): void;
125
+ }
@@ -0,0 +1,73 @@
1
+ import Ws, { WebSocketServer } from "ws";
2
+ /** Politique appliquée quand le buffer d'envoi d'un client dépasse le seuil. */
3
+ export type WsBackpressurePolicy = "drop" | "close";
4
+ /**
5
+ * Options de backpressure SORTANTE (serveur → client).
6
+ *
7
+ * Knobs Nodefony (pas des options `ws`) ; `ws` les conserve quand même dans
8
+ * `wss.options` car son constructeur fait `{ ...defaults, ...options }` → on les
9
+ * relit ici via {@link readBackpressureOptions}.
10
+ */
11
+ export interface IWsBackpressureOptions {
12
+ /** Seuil (octets) de `ws.bufferedAmount` ; `0`/absent = désactivé. */
13
+ maxBackpressure?: number;
14
+ /** Action au dépassement du seuil. Défaut `"drop"`. */
15
+ backpressurePolicy?: WsBackpressurePolicy;
16
+ /** Refus consécutifs au-delà desquels on ferme (1013) ; `0`/absent = jamais. */
17
+ backpressureCloseAfterDrops?: number;
18
+ }
19
+ /**
20
+ * Ce dont la règle a besoin, et rien de plus : la taille de la file d'envoi et
21
+ * de quoi fermer. Typé STRUCTURELLEMENT pour que `@nodefony/realtime` applique
22
+ * la même règle sans importer `ws` (son transport type déjà sa connexion ainsi).
23
+ */
24
+ export interface IBackpressureTarget {
25
+ readonly bufferedAmount?: number;
26
+ close(code?: number, reason?: string): void;
27
+ }
28
+ /** Socket augmentée d'un compteur de drops (number lazy — lu par la sonde socket). */
29
+ export interface IBackpressureSocket extends Ws {
30
+ /** Nombre cumulé de frames droppées/refusées pour backpressure sur cette socket. */
31
+ _nfDrops?: number;
32
+ /**
33
+ * Frames refusées **d'affilée**, remis à zéro dès qu'une frame repart. C'est
34
+ * le signal d'une file qui ne se draine plus — le seul qui distingue un pic
35
+ * passager d'un client mort. Cf le palier 2 de {@link decideSend}.
36
+ */
37
+ _nfDropStreak?: number;
38
+ }
39
+ /** Décision pour une frame : l'émettre, la sauter, ou (socket fermée) ne rien faire. */
40
+ export type WsSendDecision = "send" | "drop" | "close";
41
+ /**
42
+ * Relit le seuil + la politique depuis les options du `WebSocketServer`.
43
+ *
44
+ * `ws` préserve nos clés custom (`maxBackpressure`/`backpressurePolicy`) dans
45
+ * `wss.options` via le spread de son constructeur. Appelé UNE fois hors de la boucle
46
+ * `broadcast()` (pas par frame).
47
+ *
48
+ * @param wss - le serveur WebSocket (ou null/undefined → backpressure désactivée)
49
+ * @returns seuil résolu (`max`, 0 = off) + politique (`policy`, défaut `"drop"`)
50
+ */
51
+ export declare const readBackpressureOptions: (wss: WebSocketServer | null | undefined) => {
52
+ max: number;
53
+ policy: WsBackpressurePolicy;
54
+ closeAfterDrops: number;
55
+ };
56
+ /**
57
+ * Décide si une frame doit partir vers `ws` selon la backpressure SORTANTE
58
+ * (serveur → client). Protège la RAM d'envoi du serveur quand le client est lent à
59
+ * RECEVOIR (ne lit pas assez vite → `ws.bufferedAmount` gonfle → OOM). `broadcast()`
60
+ * amplifie : un seul client lent peut, sans borne, faire tomber la diffusion entière.
61
+ *
62
+ * PERF (hot path WS) : lecture O(1) de `ws.bufferedAmount`, **0 allocation sous le
63
+ * seuil** (chemin nominal = comportement inchangé). Au-delà : incrémente `_nfDrops`
64
+ * (lazy) et, si la politique est `close`, ferme la socket (RFC 6455 close 1013
65
+ * « Try Again Later » → le client peut back-off + reconnecter). NE FAIT PAS le `send()`
66
+ * — le caller émet uniquement si le retour vaut `"send"`.
67
+ *
68
+ * @param ws - la socket destinataire
69
+ * @param max - seuil d'octets (`<= 0` → désactivé)
70
+ * @param policy - action au dépassement
71
+ * @returns `"send"` (émettre) · `"drop"` (sauter cette frame) · `"close"` (socket fermée)
72
+ */
73
+ export declare const decideSend: (ws: IBackpressureTarget, max: number, policy: WsBackpressurePolicy, closeAfterDrops?: number) => WsSendDecision;