@nodefony/http 10.0.0-alpha.9 → 10.0.0-beta.2

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 (100) hide show
  1. package/README.md +12 -3
  2. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.152.0}/helpers/esm/decorateParam.js +1 -1
  5. package/dist/index.js +6 -4
  6. package/dist/nodefony/command/assetsPublishCommand.js +1 -1
  7. package/dist/nodefony/command/certificatesCommand.js +1 -2
  8. package/dist/nodefony/command/networkCommand.js +1 -1
  9. package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
  10. package/dist/nodefony/config/config.js +26 -4
  11. package/dist/nodefony/interfaces/IRouting.js +1 -0
  12. package/dist/nodefony/interfaces/ISecurity.js +1 -0
  13. package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
  14. package/dist/nodefony/service/audit-logger.js +16 -13
  15. package/dist/nodefony/service/certificates.js +87 -144
  16. package/dist/nodefony/service/error-renderer.js +11 -8
  17. package/dist/nodefony/service/http-kernel.js +342 -165
  18. package/dist/nodefony/service/pretty-request-logger.js +3 -10
  19. package/dist/nodefony/service/servers/clientError.js +2 -2
  20. package/dist/nodefony/service/servers/server-http.js +23 -23
  21. package/dist/nodefony/service/servers/server-https.js +41 -42
  22. package/dist/nodefony/service/servers/server-static.js +13 -17
  23. package/dist/nodefony/service/servers/server-websocket-secure.js +11 -10
  24. package/dist/nodefony/service/servers/server-websocket.js +11 -10
  25. package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
  26. package/dist/nodefony/service/sessions/sessions-service.js +22 -15
  27. package/dist/nodefony/service/trace.js +3 -2
  28. package/dist/nodefony/service/upload/upload-service.js +11 -10
  29. package/dist/nodefony/service/x509.js +324 -0
  30. package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
  31. package/dist/nodefony/src/context/Context.js +34 -19
  32. package/dist/nodefony/src/context/forwarded.js +4 -4
  33. package/dist/nodefony/src/context/http/HttpContext.js +133 -88
  34. package/dist/nodefony/src/context/http/Request.js +108 -46
  35. package/dist/nodefony/src/context/http/Response.js +134 -54
  36. package/dist/nodefony/src/context/http/parser.js +9 -10
  37. package/dist/nodefony/src/context/http/urlFastPath.js +35 -11
  38. package/dist/nodefony/src/context/http2/Response.js +53 -25
  39. package/dist/nodefony/src/context/metaData.js +1 -1
  40. package/dist/nodefony/src/context/trustProxy.js +4 -3
  41. package/dist/nodefony/src/context/websocket/Response.js +13 -6
  42. package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
  43. package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
  44. package/dist/nodefony/src/cookies/cookie.js +3 -3
  45. package/dist/nodefony/src/errors/httpError.js +6 -6
  46. package/dist/nodefony/src/errors/thrownMessage.js +27 -0
  47. package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
  48. package/dist/nodefony/src/profiler/Profiler.js +4 -4
  49. package/dist/nodefony/src/servers/kernelServers.js +29 -0
  50. package/dist/nodefony/src/servers/portBinder.js +65 -23
  51. package/dist/nodefony/src/session/session.js +18 -10
  52. package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
  53. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
  54. package/dist/types/index.d.ts +16 -0
  55. package/dist/types/nodefony/config/config.d.ts +23 -0
  56. package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
  57. package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
  58. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
  59. package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
  60. package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
  61. package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
  62. package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
  63. package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
  64. package/dist/types/nodefony/interfaces/index.d.ts +2 -0
  65. package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
  66. package/dist/types/nodefony/service/certificates.d.ts +42 -57
  67. package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
  68. package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
  69. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
  70. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
  71. package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
  72. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
  73. package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
  74. package/dist/types/nodefony/service/x509.d.ts +86 -0
  75. package/dist/types/nodefony/src/context/Context.d.ts +21 -10
  76. package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
  77. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
  78. package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
  79. package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
  80. package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
  81. package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
  82. package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
  83. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
  84. package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
  85. package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
  86. package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
  87. package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
  88. package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
  89. package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
  90. package/dist/types/nodefony/src/servers/portBinder.d.ts +16 -65
  91. package/dist/types/nodefony/src/session/session.d.ts +12 -12
  92. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
  93. package/docs/cookies.md +25 -25
  94. package/docs/index.md +4 -2
  95. package/docs/observabilite.md +27 -27
  96. package/docs/rate-limit.md +20 -20
  97. package/docs/servers.md +82 -79
  98. package/docs/session.md +74 -69
  99. package/docs/upload.md +49 -43
  100. package/package.json +9 -12
package/README.md CHANGED
@@ -6,7 +6,7 @@ pipeline de contexte/controller.
6
6
 
7
7
  > Docs IA (dans le dépôt) : [`CLAUDE.md`](https://github.com/nodefony/nodefony-core/blob/main/src/packages/@nodefony/http/CLAUDE.md) · [`MEMORY.md`](https://github.com/nodefony/nodefony-core/blob/main/src/packages/@nodefony/http/MEMORY.md) · [`docs/`](./docs).
8
8
  > Règle dure : ce module ne peut PAS importer `@nodefony/framework` (cycle) — accès au resolver
9
- > via `(context as any)?.resolver`.
9
+ > via `context.resolver`, typé par le contrat `IRouteResolver` que ce module définit.
10
10
 
11
11
  ## Serveurs
12
12
 
@@ -26,14 +26,23 @@ serveur → http-kernel.handle() → rate-limit IP → createContext()
26
26
  → Router.resolve() (match avant le parse du body)
27
27
  → Firewall.applySecurityHeaders() → fallback statique si aucune route
28
28
  → parse du body
29
- → handleFrontController() (Resolver.resolve → controller + initialize())
29
+ → prepareFrontController() (route + zone armées, AUCUNE instanciation)
30
30
  → Firewall.enforceCsrf() → startSession() → Firewall.handleSecurity()
31
- → action du controller → Response.send()
31
+ → @IsGranted → instanciation DI + initialize() → action du controller → Response.send()
32
32
  ```
33
33
 
34
+ En WebSocket, le contrôleur est instancié au handshake (`handleFrontController()`), avant le pare-feu.
35
+
34
36
  Chaque contexte (HTTP + WS) porte un `requestId` (UUID v4, ou `X-Request-Id` client),
35
37
  réinjecté dans la réponse et corrélé dans les logs via AsyncLocalStorage.
36
38
 
39
+ Chaque requête HTTP — et chaque connexion WebSocket — reçoit aussi **son propre conteneur de
40
+ services** : un scope ouvert à l'entrée (`enterScope`), dont le prototype est le conteneur du kernel,
41
+ refermé après les hooks `onAfterResponse`/`onFinish` (`leaveScope`), y compris quand un hook lève.
42
+ Le serveur le pose dans le contexte asynchrone de la requête : `RequestContext.getScope()` le rend
43
+ depuis n'importe quel code, et les services `@injectable({ scope: "request" })` y vivent. Détail :
44
+ [Injection & portées](https://github.com/nodefony/nodefony-core/blob/main/docs/architecture/injection-portees.md).
45
+
37
46
  ## Rate-limit général par IP
38
47
 
39
48
  Plafond de trafic **par IP cliente** sur toutes les routes HTTP — protège la capacité du serveur
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.151.0/helpers/esm/decorateParam.js
1
+ //#region \0@oxc-project+runtime@0.152.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import config, { httpConfigSchema } from "./nodefony/config/config.js";
1
+ import config, { httpConfigSchema, httpOverlaySchema } from "./nodefony/config/config.js";
2
2
  import { defineHttpConfig, httpConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
3
3
  import { createHttpAdminApi } from "./nodefony/service/HttpAdminApi.js";
4
4
  import { createProfilerAdminApi } from "./nodefony/service/ProfilerAdminApi.js";
@@ -21,8 +21,8 @@ import DefaultErrorRenderer, { schemaMismatchOf } from "./nodefony/service/error
21
21
  import DefaultRequestLogger from "./nodefony/service/request-logger.js";
22
22
  import JsonAuditLogger from "./nodefony/service/audit-logger.js";
23
23
  import PrettyRequestLogger from "./nodefony/service/pretty-request-logger.js";
24
- import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.js";
25
- import __decorate from "./_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.js";
24
+ import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
25
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
26
26
  import http_kernel_default from "./nodefony/service/http-kernel.js";
27
27
  import server_http_default from "./nodefony/service/servers/server-http.js";
28
28
  import server_https_default from "./nodefony/service/servers/server-https.js";
@@ -42,6 +42,7 @@ import { PrebuiltUi, resolveUiDelivery } from "./nodefony/src/assets/prebuiltUi.
42
42
  import { Kernel, Module, services } from "nodefony";
43
43
  //#region index.ts
44
44
  let Http = class Http extends Module {
45
+ overlaySchema = httpOverlaySchema;
45
46
  constructor(kernel) {
46
47
  super("http", kernel, import.meta.url, config);
47
48
  this.addCommand(Network);
@@ -68,7 +69,8 @@ let Http = class Http extends Module {
68
69
  * certificats `serialNumber`) — cf `defineHttpConfig`.
69
70
  */
70
71
  async onKernelRegister() {
71
- this.options = defineHttpConfig(this.options ?? {}, this.kernel);
72
+ const input = this.options;
73
+ this.options = defineHttpConfig(input ?? {}, this.kernel);
72
74
  return this;
73
75
  }
74
76
  /**
@@ -80,7 +80,7 @@ var AssetsPublish = class extends Command {
80
80
  }
81
81
  /** Sources = mounts natifs (`server-static`) + bundles `@nodefony/frontend`. */
82
82
  collectSources() {
83
- const modules = this.kernel?.getModules?.() ?? {};
83
+ const modules = this.kernel?.getModules() ?? {};
84
84
  const sources = [];
85
85
  const staticSvc = modules["http"]?.get("server-static");
86
86
  staticSvc?.mountModulePublics?.();
@@ -21,12 +21,11 @@ var Certificates = class extends Command {
21
21
  this.addOption("-j, --json", "output the certificate summary as JSON");
22
22
  }
23
23
  async generate(opts) {
24
- const service = (this.kernel?.getModules()?.["http"])?.get("certificates") ?? null;
24
+ const service = (this.kernel?.getModules()["http"])?.get("certificates") ?? null;
25
25
  if (!service) {
26
26
  this.log("Service 'certificates' introuvable (module @nodefony/http non chargé).", "ERROR");
27
27
  return this;
28
28
  }
29
- await service.loadForge();
30
29
  await service.generateServerCertificates(Boolean(opts.force));
31
30
  const info = await service.describe();
32
31
  if (opts.json) process.stdout.write(`${JSON.stringify(info, void 0, 2)}\n`);
@@ -16,7 +16,7 @@ var Network = class extends Command {
16
16
  let network = this.kernel?.getNetwork();
17
17
  let result = {};
18
18
  if (arg) {
19
- if (network?.interfaces[arg]) result = network?.interfaces[arg];
19
+ if (network?.interfaces[arg]) result = network.interfaces[arg];
20
20
  } else result = network?.interfaces;
21
21
  if (opts.json) process.stdout.write(`${JSON.stringify(result, void 0, " ")}\n`);
22
22
  else process.stdout.write(`${inspect(result, { colors: process.stdout.isTTY })}\n`);
@@ -43,7 +43,7 @@ var ProxyGenerate = class extends Command {
43
43
  }
44
44
  /** Construit le modèle d'introspection depuis le kernel + le service statique. */
45
45
  buildIntrospection(opts) {
46
- const module = this.kernel?.getModules()?.["http"];
46
+ const module = this.kernel?.getModules()["http"];
47
47
  const httpOpts = module?.options ?? {};
48
48
  const servers = (this.kernel?.options)?.servers;
49
49
  const staticSvc = module?.get("server-static");
@@ -1,3 +1,4 @@
1
+ import { overlayField } from "nodefony";
1
2
  import { z } from "zod";
2
3
  //#region nodefony/config/config.ts
3
4
  /**
@@ -158,8 +159,9 @@ const http3Schema = z.looseObject({});
158
159
  const selfSignedAttrSchema = z.looseObject({
159
160
  name: z.string().optional(),
160
161
  value: z.string().optional(),
161
- shortName: z.string().optional()
162
- }).describe("Champ de sujet/issuer node-forge (CertificateField).");
162
+ shortName: z.string().optional(),
163
+ type: z.string().optional()
164
+ }).describe("Attribut du sujet/émetteur : `name` (commonName…), `shortName` (CN…) ou `type` (OID), et `value`.");
163
165
  const selfSignedSchema = z.strictObject({
164
166
  size: z.number().int().positive().default(2048).describe("Taille de la clé RSA (bits). 2048 minimum, 4096 recommandé en prod."),
165
167
  hash: z.enum([
@@ -173,7 +175,7 @@ const selfSignedSchema = z.strictObject({
173
175
  kernelDerived: true,
174
176
  description: "Attributs du certificat (commonName, organizationName…). Vide (défaut) = dérivés du kernel par le builder (commonName ← domain)."
175
177
  })
176
- }).describe("Génération du certificat AUTO-SIGNÉ (node-forge, JavaScript pur — aucun binaire externe n'est invoqué). Ces réglages ne valent QUE pour la stratégie `selfsigned` : sous `mkcert` (défaut en développement) ils sont ignorés — mkcert ne reçoit que les noms d'hôtes — et sous `strategy: \"explicit\"` le certificat est fourni, donc rien n'est généré.");
178
+ }).describe("Génération du certificat AUTO-SIGNÉ (node:crypto — aucun binaire externe ni dépendance tierce). Ces réglages ne valent QUE pour la stratégie `selfsigned` : sous `mkcert` (défaut en développement) ils sont ignorés — mkcert ne reçoit que les noms d'hôtes — et sous `strategy: \"explicit\"` le certificat est fourni, donc rien n'est généré.");
177
179
  const sanSchema = z.strictObject({
178
180
  dns: z.array(z.string()).default([]).meta({
179
181
  kernelDerived: true,
@@ -327,9 +329,29 @@ const httpConfigSchema = z.strictObject({
327
329
  })
328
330
  }).describe("Configuration de @nodefony/http.");
329
331
  /**
332
+ * LISTE BLANCHE des clés qu'une requête peut surcharger pour elle seule
333
+ * (`overlayConfig("@nodefony/http", …)`, #494) : les quotas de corps et
334
+ * d'envoi, lus au MOMENT de lire le corps (`Request.readBodyLimits`) — un
335
+ * calque posé avant la lecture du corps (écouteur `onRequestScope`, organisation
336
+ * reconnue à son nom d'hôte) les voit. Tout le reste est refusé : en-têtes de
337
+ * sécurité, pare-feu, `trustProxy`, sessions, limites de débit, certificats —
338
+ * et `queryString`, lu au constructeur de la requête, avant tout calque.
339
+ * Chaque champ réutilise celui du schéma (mêmes bornes) sans son défaut.
340
+ */
341
+ const httpOverlaySchema = z.strictObject({
342
+ maxBodySize: overlayField(httpConfigSchema.shape.maxBodySize),
343
+ upload: z.strictObject({
344
+ maxFileSize: overlayField(uploadSchema.shape.maxFileSize),
345
+ maxTotalFileSize: overlayField(uploadSchema.shape.maxTotalFileSize),
346
+ maxFiles: overlayField(uploadSchema.shape.maxFiles),
347
+ maxFields: overlayField(uploadSchema.shape.maxFields),
348
+ maxFieldsSize: overlayField(uploadSchema.shape.maxFieldsSize)
349
+ }).partial()
350
+ }).partial().describe("Clés de @nodefony/http surchargeables par requête.");
351
+ /**
330
352
  * Défauts du module, matérialisés depuis le schéma (source unique). Toujours
331
353
  * valides par construction ; passés au `super(..., config)` du Module class.
332
354
  */
333
355
  const config = httpConfigSchema.parse({});
334
356
  //#endregion
335
- export { config as default, httpConfigSchema };
357
+ export { config as default, httpConfigSchema, httpOverlaySchema };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -51,7 +51,8 @@ function createProfilerAdminApi(profiler) {
51
51
  path: "{id}",
52
52
  summary: "Full profile (phase timeline) for a requestId",
53
53
  handler: ({ params }) => {
54
- const entry = profiler.get(params.id);
54
+ const { id = "" } = params;
55
+ const entry = profiler.get(id);
55
56
  if (!entry) return {
56
57
  status: 404,
57
58
  body: {
@@ -2,6 +2,19 @@ import { RequestContext } from "nodefony";
2
2
  import { performance } from "node:perf_hooks";
3
3
  //#region nodefony/service/audit-logger.ts
4
4
  /**
5
+ * Durée totale de la requête, de la première phase (`startMs`) à maintenant.
6
+ * `null` si le chronométrage est coupé (aucune phase enregistrée). Partagée
7
+ * par le journal d'audit et le journal lisible — une seule définition.
8
+ *
9
+ * @param phases - phases chronométrées du contexte
10
+ * @returns durée en millisecondes, ou `null`
11
+ */
12
+ function computeDurationMs(phases) {
13
+ const first = phases.at(0);
14
+ if (first === void 0 || typeof first.startMs !== "number") return null;
15
+ return performance.now() - first.startMs;
16
+ }
17
+ /**
5
18
  * Severity derived from HTTP status code — RFC 9110 categories.
6
19
  * 1xx/2xx/3xx → INFO ; 4xx → WARNING ; 5xx → ERROR.
7
20
  * Unknown/missing status → INFO.
@@ -77,7 +90,7 @@ var JsonAuditLogger = class {
77
90
  method: ctx.method,
78
91
  url: ctx.url,
79
92
  status,
80
- durationMs: this.computeDurationMs(ctx.phases),
93
+ durationMs: computeDurationMs(ctx.phases),
81
94
  remoteAddress: ctx.remoteAddress ?? null,
82
95
  host: ctx.getHost?.() ?? null,
83
96
  userAgent: ctx.getUserAgent?.() ?? null,
@@ -108,7 +121,7 @@ var JsonAuditLogger = class {
108
121
  method: ctx.method,
109
122
  url: ctx.url,
110
123
  status,
111
- durationMs: this.computeDurationMs(ctx.phases),
124
+ durationMs: computeDurationMs(ctx.phases),
112
125
  remoteAddress: ctx.remoteAddress ?? null,
113
126
  host: ctx.getHost?.() ?? null,
114
127
  userAgent: ctx.getUserAgent?.() ?? null,
@@ -128,16 +141,6 @@ var JsonAuditLogger = class {
128
141
  };
129
142
  }
130
143
  /**
131
- * Total request duration computed from the first phase startMs to now.
132
- * Returns null if timing is disabled (no phases recorded).
133
- */
134
- computeDurationMs(phases) {
135
- if (!phases.length) return null;
136
- const first = phases[0];
137
- if (typeof first.startMs !== "number") return null;
138
- return performance.now() - first.startMs;
139
- }
140
- /**
141
144
  * Serialise an Error (recursively for `cause` chain) into an AuditErrorEntry.
142
145
  * Stack is included only when `includeStack === true` (dev default).
143
146
  * Cause chain is capped at `maxCauseDepth`.
@@ -156,4 +159,4 @@ var JsonAuditLogger = class {
156
159
  }
157
160
  };
158
161
  //#endregion
159
- export { JsonAuditLogger as default, severityFromStatus };
162
+ export { computeDurationMs, JsonAuditLogger as default, severityFromStatus };