@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,258 @@
1
+ import { extend } from "nodefony";
2
+ import crypto from "node:crypto";
3
+ import { parseCookie } from "cookie";
4
+ import MS from "ms";
5
+ //#region nodefony/src/cookies/cookie.ts
6
+ const encode = encodeURIComponent;
7
+ const decode = decodeURIComponent;
8
+ const cookieDefaultSettings = {
9
+ maxAge: 0,
10
+ path: "/",
11
+ domain: void 0,
12
+ secure: true,
13
+ sameSite: "Lax",
14
+ httpOnly: true,
15
+ signed: false,
16
+ secret: "!nodefony.secret!"
17
+ };
18
+ function parser(strToParse) {
19
+ return parseCookie(strToParse);
20
+ }
21
+ function cookiesParser(context) {
22
+ let cookies = null;
23
+ let co = null;
24
+ switch (context.type) {
25
+ case "http":
26
+ case "https":
27
+ case "http2":
28
+ if (context.request.request && context.request.request.headers.cookie) cookies = context.request.request.headers.cookie;
29
+ if (cookies) {
30
+ const obj = parser(cookies);
31
+ for (const cookie in obj) {
32
+ co = new Cookie(cookie, obj[cookie]);
33
+ context.addRequestCookie(co);
34
+ }
35
+ if (context.response) context.request.request.cookies = context.response.cookies;
36
+ }
37
+ break;
38
+ case "websocket":
39
+ case "websocket-secure": {
40
+ const cookieHeader = context.request?.headers?.cookie;
41
+ if (cookieHeader) {
42
+ const parsed = parser(cookieHeader);
43
+ for (const name in parsed) {
44
+ co = new Cookie(name, parsed[name]);
45
+ context.addRequestCookie(co);
46
+ }
47
+ }
48
+ break;
49
+ }
50
+ default: throw new Error("cookiesParser Bad Type");
51
+ }
52
+ }
53
+ var Cookie = class Cookie {
54
+ options = {};
55
+ name;
56
+ signed;
57
+ value;
58
+ originalMaxAge;
59
+ expires;
60
+ maxAge;
61
+ path;
62
+ domain;
63
+ httpOnly;
64
+ secure;
65
+ sameSite;
66
+ priority;
67
+ constructor(cookiesOrName, value, options) {
68
+ this.options = extend({}, cookieDefaultSettings, options || {});
69
+ if (!cookiesOrName) throw new Error("cookie must have name");
70
+ this.name = cookiesOrName;
71
+ if (typeof cookiesOrName === "string") {
72
+ if (!cookiesOrName) throw new Error("cookie must have name");
73
+ this.name = cookiesOrName;
74
+ this.signed = this.options.signed;
75
+ this.value = this.setValue(value);
76
+ if (this.options.maxAge) this.originalMaxAge = this.setOriginalMaxAge(this.options.maxAge);
77
+ else this.originalMaxAge = void 0;
78
+ this.expires = this.setExpires(this.options.expires);
79
+ this.path = this.setPath(this.options.path);
80
+ this.domain = this.setDomain();
81
+ this.httpOnly = this.setHttpOnly(this.options.httpOnly);
82
+ this.secure = this.setSecure(this.options.secure);
83
+ this.sameSite = this.setSameSite(this.options.sameSite);
84
+ this.priority = this.setPriority(this.options.priority);
85
+ }
86
+ if (cookiesOrName instanceof Cookie) {
87
+ this.options = cookiesOrName.options;
88
+ this.name = cookiesOrName.name;
89
+ this.signed = cookiesOrName.signed;
90
+ this.value = cookiesOrName.value;
91
+ this.originalMaxAge = cookiesOrName.originalMaxAge;
92
+ this.expires = cookiesOrName.expires;
93
+ this.path = cookiesOrName.path;
94
+ this.domain = cookiesOrName.domain;
95
+ this.httpOnly = cookiesOrName.httpOnly;
96
+ this.secure = cookiesOrName.secure;
97
+ this.maxAge = cookiesOrName.maxAge;
98
+ this.sameSite = cookiesOrName.sameSite;
99
+ this.priority = cookiesOrName.priority;
100
+ }
101
+ }
102
+ clearCookie() {
103
+ this.setExpires(1);
104
+ this.path = "/";
105
+ }
106
+ setValue(value) {
107
+ if (value) value = decode(String(value));
108
+ if (this.signed) {
109
+ const secret = this.options.secret;
110
+ if (!secret || secret === cookieDefaultSettings.secret) throw new Error("signed cookie requires a configured secret (refusing the default/predictable secret)");
111
+ this.value = `s:${this.sign(value, secret)}`;
112
+ } else this.value = value;
113
+ return this.value;
114
+ }
115
+ setSecure(val) {
116
+ return !!val;
117
+ }
118
+ setDomain() {
119
+ return this.options.domain;
120
+ }
121
+ setHttpOnly(val) {
122
+ return !!val;
123
+ }
124
+ setPath(val) {
125
+ return val;
126
+ }
127
+ /**
128
+ * Normalise l'attribut `SameSite` vers une valeur canonique RFC 6265bis.
129
+ *
130
+ * Fallback **`Lax`** (jamais `None`) : `None` désactive la protection CSRF et
131
+ * impose `Secure` — ce n'est jamais un défaut sûr. Toute entrée inconnue
132
+ * (ancien `boolean`, casse libre) retombe sur `Lax` (fail-safe).
133
+ *
134
+ * @param val - valeur souhaitée (`Strict` / `Lax` / `None`, casse libre).
135
+ * @returns la forme canonique title-case.
136
+ */
137
+ setSameSite(val) {
138
+ if (!val) return "Lax";
139
+ switch (String(val).toLowerCase()) {
140
+ case "strict": return "Strict";
141
+ case "none": return "None";
142
+ default: return "Lax";
143
+ }
144
+ }
145
+ setExpires(date) {
146
+ if (date) try {
147
+ if (date instanceof Date) this.expires = date;
148
+ else this.expires = new Date(date);
149
+ } catch (e) {
150
+ this.expires = void 0;
151
+ }
152
+ else {
153
+ const maxage = this.getMaxAge();
154
+ if (!maxage) this.expires = void 0;
155
+ else {
156
+ const res = (/* @__PURE__ */ new Date()).getTime() + maxage * 1e3;
157
+ this.expires = new Date(res);
158
+ }
159
+ return this.expires;
160
+ }
161
+ this.getMaxAge();
162
+ return this.expires;
163
+ }
164
+ setOriginalMaxAge(ms) {
165
+ if (typeof ms === "number") return ms;
166
+ return MS(ms) / 1e3;
167
+ }
168
+ setPriority(val) {
169
+ return val;
170
+ }
171
+ getMaxAge() {
172
+ if (this.expires && this.expires instanceof Date) {
173
+ const s = (this.expires.getTime() - (/* @__PURE__ */ new Date()).getTime()) / 1e3;
174
+ if (s > 0) this.maxAge = s;
175
+ else this.maxAge = void 0;
176
+ } else this.maxAge = this.originalMaxAge;
177
+ return this.maxAge;
178
+ }
179
+ toString() {
180
+ return `${this.name}=${encode(String(this.value))}`;
181
+ }
182
+ /**
183
+ * Signe une valeur de cookie : `HMAC-SHA256(value)` clé = `secret`, encodé en
184
+ * base64url (sûr en cookie, pas de `=`/`+`/`/`). Retourne `value.signature`
185
+ * → la valeur d'origine est PRÉSERVÉE (récupérable via {@link unsign}).
186
+ *
187
+ * @param val - valeur en clair à signer.
188
+ * @param secret - clé HMAC (le secret, JAMAIS la valeur).
189
+ * @returns `value.base64url(hmac)`.
190
+ * @throws {TypeError} si `val` n'est pas une string ou `secret` est vide.
191
+ */
192
+ sign(val, secret) {
193
+ if (typeof val !== "string") throw new TypeError("cookie value required (string)");
194
+ if (typeof secret !== "string" || !secret) throw new TypeError("cookie secret required (non-empty string)");
195
+ return `${val}.${crypto.createHmac("sha256", secret).update(val).digest("base64url")}`;
196
+ }
197
+ /**
198
+ * Vérifie et déballe une valeur signée par {@link sign} (tolère le préfixe
199
+ * `s:`). Comparaison **timing-safe** (`crypto.timingSafeEqual`).
200
+ *
201
+ * @param val - valeur signée (`value.signature`, éventuellement préfixée `s:`).
202
+ * Défaut : `this.value`.
203
+ * @param secret - clé HMAC. Défaut : `this.options.secret`.
204
+ * @returns la valeur en clair si la signature est valide, sinon `false`.
205
+ * @throws {TypeError} si les types sont invalides ou le secret est vide.
206
+ */
207
+ unsign(val, secret) {
208
+ let input = val ?? this.value;
209
+ const key = secret ?? this.options.secret;
210
+ if (typeof input !== "string") throw new TypeError("unsign cookie value bad type");
211
+ if (typeof key !== "string" || !key) throw new TypeError("unsign cookie secret required (non-empty string)");
212
+ if (input.startsWith("s:")) input = input.slice(2);
213
+ const idx = input.lastIndexOf(".");
214
+ if (idx <= 0) return false;
215
+ const plain = input.slice(0, idx);
216
+ const expected = this.sign(plain, key);
217
+ const a = Buffer.from(input);
218
+ const b = Buffer.from(expected);
219
+ if (a.length !== b.length) return false;
220
+ return crypto.timingSafeEqual(a, b) ? plain : false;
221
+ }
222
+ serialize() {
223
+ const tab = [];
224
+ tab.push(this.toString());
225
+ const hostPrefix = this.name.startsWith("__Host-");
226
+ const securePrefix = this.name.startsWith("__Secure-");
227
+ const secure = this.secure || this.sameSite === "None" || hostPrefix || securePrefix;
228
+ if (this.maxAge) tab.push(`Max-Age=${this.maxAge}`);
229
+ if (this.domain && !hostPrefix) tab.push(`Domain=${this.domain}`);
230
+ const path = hostPrefix ? "/" : this.path;
231
+ if (path) tab.push(`Path=${path}`);
232
+ if (this.sameSite) tab.push(`SameSite=${this.sameSite}`);
233
+ if (this.expires) tab.push(`Expires=${this.expires.toUTCString()}`);
234
+ if (this.httpOnly) tab.push("HttpOnly");
235
+ if (secure) tab.push("Secure");
236
+ if (this.priority) tab.push(`Priority=${this.priority}`);
237
+ return tab.join("; ");
238
+ }
239
+ serializeWebSocket() {
240
+ const hostPrefix = this.name.startsWith("__Host-");
241
+ const securePrefix = this.name.startsWith("__Secure-");
242
+ const secure = this.secure || this.sameSite === "None" || hostPrefix || securePrefix;
243
+ const obj = {
244
+ name: this.name,
245
+ value: this.value
246
+ };
247
+ if (this.maxAge) obj.maxage = this.maxAge;
248
+ if (this.domain && !hostPrefix) obj.domain = this.domain;
249
+ const path = hostPrefix ? "/" : this.path;
250
+ if (path) obj.path = path;
251
+ if (this.expires) obj.expires = this.expires;
252
+ if (this.httpOnly) obj.httponly = true;
253
+ if (secure) obj.secure = true;
254
+ return obj;
255
+ }
256
+ };
257
+ //#endregion
258
+ export { cookiesParser, Cookie as default, parser };
@@ -0,0 +1,69 @@
1
+ import { clc, nodefonyError } from "nodefony";
2
+ //#region nodefony/src/errors/httpError.ts
3
+ const exclude = {
4
+ context: true,
5
+ resolver: true,
6
+ container: true,
7
+ secure: true,
8
+ request: true,
9
+ response: true
10
+ };
11
+ const jsonHttpError = {
12
+ configurable: true,
13
+ writable: true,
14
+ value() {
15
+ const alt = {};
16
+ const storeKey = function(key) {
17
+ if (key in exclude) return;
18
+ alt[key] = this[key];
19
+ };
20
+ Object.getOwnPropertyNames(this).forEach(storeKey, this);
21
+ return alt;
22
+ }
23
+ };
24
+ var HttpError = class extends nodefonyError {
25
+ context;
26
+ response;
27
+ request;
28
+ url;
29
+ controller;
30
+ action;
31
+ jsonResponse;
32
+ constructor(message, code, context) {
33
+ super(message, code);
34
+ this.context = context;
35
+ this.response = context?.response;
36
+ this.request = context?.request;
37
+ this.url = this.context?.url;
38
+ if (this.response && code) this.response.statusCode = code;
39
+ if (!this.message) this.message = (context?.response)?.body?.toString() || (context?.response)?.statusMessage || (context?.response)?.getStatusMessage();
40
+ const resolver = context?.resolver;
41
+ this.controller = resolver?.controller?.name ?? void 0;
42
+ this.action = resolver?.actionName ?? void 0;
43
+ const res = context?.response;
44
+ if (res) this.jsonResponse = `${res.statusCode} ${res.statusMessage ?? ""}`.trim();
45
+ }
46
+ toString() {
47
+ if (this.context) return `${clc.red(this.message)}
48
+ ${clc.blue("Name :")} ${this.name}
49
+ ${clc.blue("Type :")} ${this.errorType}
50
+ ${clc.green("Controller :")} ${this.controller}
51
+ ${clc.green("Action :")} ${this.action}
52
+ ${clc.blue("Url :")} ${this.url}
53
+ ${clc.red("Code :")} ${this.code}
54
+ ${clc.red("Message :")} ${this.message}
55
+ ${clc.red("Response :")} ${this.jsonResponse}
56
+ ${clc.green("Stack :")} ${this.stack}`;
57
+ return `${clc.red(this.message)}
58
+ ${clc.blue("Name :")} ${this.name}
59
+ ${clc.blue("Type :")} ${this.errorType}
60
+
61
+ ${clc.red("Code :")} ${this.code}
62
+ ${clc.red("Message :")} ${this.message}
63
+ ${clc.red("Response :")} ${this.jsonResponse}
64
+ ${clc.green("Stack :")} ${this.stack}`;
65
+ }
66
+ };
67
+ Object.defineProperty(HttpError.prototype, "toJSON", jsonHttpError);
68
+ //#endregion
69
+ export { HttpError as default };
@@ -0,0 +1,95 @@
1
+ //#region nodefony/src/profiler/FrameProfile.ts
2
+ /**
3
+ * Profil d'**une invocation** du pont RPC (une frame `api.request`), et non de
4
+ * la connexion qui la porte.
5
+ *
6
+ * Pourquoi un objet séparé du `Context` : un `WebsocketContext` vit pour toute
7
+ * la **connexion**, alors qu'une socket peut porter des centaines d'invocations,
8
+ * concurrentes de surcroît. Empiler leurs phases sur le contexte produirait une
9
+ * timeline **cumulative** (donc fausse) et ferait croître `Context.phases` sans
10
+ * borne. Le profil naît et meurt donc avec la frame ; il voyage dans l'ALS
11
+ * (`RequestContext.payload.invocation`), seul canal déjà per-invocation traversé
12
+ * par le Resolver, le controller et les adapters ORM.
13
+ *
14
+ * Il satisfait **structurellement** ce que `Profiler.collect()` lit d'un
15
+ * contexte : aucune connaissance du transport n'est requise côté Profiler, un
16
+ * profil de frame s'y collecte comme une requête HTTP (`kind: "ws"`).
17
+ *
18
+ * Coût : **rien n'est alloué** quand le profiler dev est absent ET le timing
19
+ * éteint (production) — le pont n'ouvre alors aucun profil.
20
+ */
21
+ var FrameProfile = class {
22
+ requestId;
23
+ type;
24
+ scheme;
25
+ method;
26
+ url;
27
+ remoteAddress;
28
+ traceparent;
29
+ security;
30
+ securityTrace;
31
+ /** Phases de CETTE frame (vide si le timing est éteint). */
32
+ phases = [];
33
+ /**
34
+ * Buffer ORM de CETTE frame — `null` hors profiling. C'est ce buffer que le
35
+ * kernel refusait d'allouer au handshake (il aurait cumulé N messages) : par
36
+ * invocation, il redevient exact, et le SQL se replace dans le waterfall.
37
+ */
38
+ profilerQueries;
39
+ /** Resolver de la frame — porte route / controller / action. */
40
+ resolver = null;
41
+ /** Statut HTTP-équivalent de l'invocation (200, 403, 404…). */
42
+ response = null;
43
+ error = null;
44
+ timing;
45
+ constructor(init) {
46
+ this.requestId = init.requestId;
47
+ this.type = init.type;
48
+ this.scheme = init.scheme;
49
+ this.method = init.method;
50
+ this.url = init.url;
51
+ this.remoteAddress = init.remoteAddress;
52
+ this.traceparent = init.traceparent;
53
+ this.security = init.security;
54
+ this.securityTrace = init.securityTrace;
55
+ this.timing = init.timing;
56
+ this.profilerQueries = init.queries ? [] : null;
57
+ }
58
+ /**
59
+ * Ouvre une phase. Contrairement au `Context`, l'index est un **scan arrière**
60
+ * (pas de `Map<nom, index>`) : une frame porte une poignée de phases, et une
61
+ * phase peut être ré-entrante (une action qui en déclenche une autre) — la
62
+ * table par nom, elle, écraserait la première occurrence.
63
+ */
64
+ phaseStart(name) {
65
+ if (!this.timing) return;
66
+ this.phases.push({
67
+ name,
68
+ startMs: performance.now()
69
+ });
70
+ }
71
+ /** Ferme la DERNIÈRE phase ouverte de ce nom (idempotent). */
72
+ phaseEnd(name) {
73
+ if (!this.timing) return;
74
+ for (let i = this.phases.length - 1; i >= 0; i--) {
75
+ const p = this.phases[i];
76
+ if (p.name === name && p.endMs === void 0) {
77
+ p.endMs = performance.now();
78
+ p.durationMs = p.endMs - p.startMs;
79
+ return;
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * Fige l'issue de l'invocation (statut + erreur éventuelle) avant collecte.
85
+ *
86
+ * @param status - statut HTTP-équivalent (200 ; 403/404/409… pour un refus).
87
+ * @param error - l'erreur qui a interrompu la frame, le cas échéant.
88
+ */
89
+ finish(status, error) {
90
+ this.response = { statusCode: status };
91
+ if (error !== void 0 && error !== null) this.error = { message: error instanceof Error ? error.message : String(error) };
92
+ }
93
+ };
94
+ //#endregion
95
+ export { FrameProfile, FrameProfile as default };
@@ -0,0 +1,139 @@
1
+ //#region nodefony/src/profiler/Profiler.ts
2
+ /** Durée totale = (fin de la dernière phase terminée) − (début de la 1ère). */
3
+ function totalDuration(phases) {
4
+ if (phases.length === 0) return null;
5
+ const first = phases[0];
6
+ if (typeof first.startMs !== "number") return null;
7
+ let end = first.startMs;
8
+ for (const p of phases) {
9
+ const e = p.startMs + (p.durationMs ?? 0);
10
+ if (e > end) end = e;
11
+ }
12
+ const d = end - first.startMs;
13
+ return d >= 0 ? Math.round(d * 1e3) / 1e3 : null;
14
+ }
15
+ /** Capacité par défaut du ring buffer (assez pour une session de debug). */
16
+ const DEFAULT_CAP = 500;
17
+ var Profiler = class {
18
+ _buf = /* @__PURE__ */ new Map();
19
+ _cap;
20
+ constructor(cap = DEFAULT_CAP) {
21
+ this._cap = cap > 0 ? cap : DEFAULT_CAP;
22
+ }
23
+ /**
24
+ * Snapshot un `Context` en fin de requête. Tolérant aux champs absents
25
+ * (handshake WS, erreur précoce). À appeler AVANT `context.clean()`.
26
+ *
27
+ * @param ctx - le Context HTTP/WS terminé (lu en structurel, jamais muté).
28
+ */
29
+ collect(ctx) {
30
+ const requestId = ctx.requestId;
31
+ if (!requestId) return;
32
+ const phases = ctx.phases ?? [];
33
+ const entry = {
34
+ requestId,
35
+ ts: Date.now(),
36
+ kind: ctx.type === "websocket" || ctx.type === "websocket-secure" ? "ws" : "http",
37
+ method: ctx.method ?? null,
38
+ url: ctx.url ?? "",
39
+ scheme: ctx.scheme ?? "",
40
+ status: ctx.response?.statusCode ?? (ctx.error ? 500 : null),
41
+ durationMs: totalDuration(phases),
42
+ route: ctx.resolver?.route?.name ?? null,
43
+ controller: ctx.resolver?.controller?.name ?? null,
44
+ action: ctx.resolver?.actionName ?? null,
45
+ remoteAddress: ctx.remoteAddress ?? null,
46
+ user: readUser(ctx),
47
+ traceparent: ctx.traceparent ?? null,
48
+ error: ctx.error?.message ?? null,
49
+ phases: phases.map((p) => ({
50
+ name: p.name,
51
+ startMs: p.startMs,
52
+ durationMs: p.durationMs ?? null
53
+ })),
54
+ queries: ctx.profilerQueries && ctx.profilerQueries.length > 0 ? ctx.profilerQueries : void 0,
55
+ security: readSecurity(ctx)
56
+ };
57
+ this._buf.delete(requestId);
58
+ this._buf.set(requestId, entry);
59
+ if (this._buf.size > this._cap) {
60
+ const oldest = this._buf.keys().next().value;
61
+ if (oldest !== void 0) this._buf.delete(oldest);
62
+ }
63
+ }
64
+ /** Profil complet d'une requête, ou `undefined` si évincé/inconnu. */
65
+ get(requestId) {
66
+ return this._buf.get(requestId);
67
+ }
68
+ /**
69
+ * Résumés des requêtes les plus récentes (récent → ancien), capés à `limit`.
70
+ */
71
+ recent(limit = 60) {
72
+ const out = [];
73
+ const entries = [...this._buf.values()];
74
+ for (let i = entries.length - 1; i >= 0 && out.length < limit; i--) {
75
+ const e = entries[i];
76
+ out.push({
77
+ requestId: e.requestId,
78
+ ts: e.ts,
79
+ kind: e.kind,
80
+ method: e.method,
81
+ url: e.url,
82
+ status: e.status,
83
+ durationMs: e.durationMs,
84
+ route: e.route,
85
+ error: e.error
86
+ });
87
+ }
88
+ return out;
89
+ }
90
+ /** Nombre de profils en mémoire. */
91
+ get size() {
92
+ return this._buf.size;
93
+ }
94
+ /** Vide le ring buffer. */
95
+ clear() {
96
+ this._buf.clear();
97
+ }
98
+ };
99
+ /**
100
+ * Croise la ZONE (ce qui était possible) et la TRACE (ce qui s'est passé).
101
+ *
102
+ * Hors zone firewall → `undefined` (pas de section « Sécurité » vide côté
103
+ * client). Zone publique traversée sans trace → `outcome: "public"` : la
104
+ * requête a bien croisé une zone, qui n'exigeait simplement rien.
105
+ */
106
+ function readSecurity(ctx) {
107
+ const area = ctx.security;
108
+ if (!area) return void 0;
109
+ const t = ctx.securityTrace ?? null;
110
+ const isProtected = area.security === true;
111
+ return {
112
+ zone: area.name ?? null,
113
+ protected: isProtected,
114
+ mode: area.mode ?? null,
115
+ candidates: area.authenticators ? [...area.authenticators] : [],
116
+ authenticator: t?.authenticator ?? null,
117
+ outcome: t?.outcome ?? (isProtected ? null : "public"),
118
+ reason: t?.reason ?? null,
119
+ roles: t?.roles ?? null
120
+ };
121
+ }
122
+ /**
123
+ * Identité de la requête.
124
+ *
125
+ * `context.user` n'est PAS la source de vérité en zone firewall : le token
126
+ * résolu vit dans l'ALS (`RequestContext`), qui n'est plus lisible au teardown
127
+ * où `collect()` s'exécute. Sans le repli sur la trace, une requête pleinement
128
+ * authentifiée (rôles compris) s'afficherait « anonyme ».
129
+ */
130
+ function readUser(ctx) {
131
+ const u = ctx.user;
132
+ if (u && typeof u === "object") {
133
+ const username = u.username;
134
+ if (typeof username === "string") return username;
135
+ }
136
+ return ctx.securityTrace?.user ?? null;
137
+ }
138
+ //#endregion
139
+ export { Profiler, Profiler as default };