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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +201 -543
  2. package/README.md +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorate.js +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateMetadata.js +1 -1
  5. package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateParam.js +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/nodefony/command/assetsPublishCommand.js +2 -4
  8. package/dist/nodefony/command/proxyGenerateCommand.js +28 -9
  9. package/dist/nodefony/config/config.js +6 -6
  10. package/dist/nodefony/config/defineModuleConfig.js +2 -2
  11. package/dist/nodefony/service/certificates.js +29 -4
  12. package/dist/nodefony/service/http-kernel.js +2 -2
  13. package/dist/nodefony/service/servers/server-http.js +3 -3
  14. package/dist/nodefony/service/servers/server-https.js +3 -3
  15. package/dist/nodefony/service/servers/server-websocket-secure.js +3 -3
  16. package/dist/nodefony/service/servers/server-websocket.js +3 -3
  17. package/dist/nodefony/service/sessions/sessions-service.js +5 -5
  18. package/dist/nodefony/service/upload/upload-service.js +3 -3
  19. package/dist/nodefony/src/context/domainMatcher.js +34 -1
  20. package/dist/nodefony/src/proxy/generateProxyConfig.js +34 -7
  21. package/dist/types/index.d.ts +2 -2
  22. package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +4 -0
  23. package/dist/types/nodefony/config/config.d.ts +4 -4
  24. package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +14 -10
  25. package/dist/types/nodefony/service/certificates.d.ts +33 -2
  26. package/dist/types/nodefony/service/http-kernel.d.ts +2 -2
  27. package/dist/types/nodefony/src/context/domainMatcher.d.ts +25 -2
  28. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +24 -0
  29. package/docs/cookies.md +2 -2
  30. package/docs/observabilite.md +2 -2
  31. package/docs/rate-limit.md +5 -5
  32. package/docs/servers.md +39 -39
  33. package/docs/session.md +8 -8
  34. package/docs/upload.md +3 -3
  35. package/package.json +6 -6
package/README.md CHANGED
@@ -74,4 +74,4 @@ npm run test:memory # GATE mémoire (memory.test.ts) — AVANT tout commit
74
74
 
75
75
  ## Licence
76
76
 
77
- CeCILL-B — Christophe CAMENSULI.
77
+ Apache 2.0 — Christophe CAMENSULI.
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.149.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.148.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.149.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.148.0/helpers/esm/decorateParam.js
1
+ //#region \0@oxc-project+runtime@0.149.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
@@ -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.148.0/helpers/esm/decorateMetadata.js";
25
- import __decorate from "./_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
24
+ import __decorateMetadata from "./_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
25
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.149.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";
@@ -43,14 +43,12 @@ var AssetsPublish = class extends Command {
43
43
  async generate(opts) {
44
44
  const outDir = opts.out ? isAbsolute(opts.out) ? opts.out : resolve(process.cwd(), opts.out) : join(process.cwd(), "dist-assets");
45
45
  const sources = this.collectSources();
46
- if (sources.length === 0) {
47
- this.log("Aucune source d'assets (0 mount natif, 0 bundle frontend) — rien à publier.", "WARNING");
48
- return this;
49
- }
46
+ if (sources.length === 0) process.stdout.write(`Aucune source d'assets (0 mount natif, 0 bundle frontend) — arbre vide → ${outDir}\n`);
50
47
  if (opts.clean && existsSync(outDir)) await fsp.rm(outDir, {
51
48
  recursive: true,
52
49
  force: true
53
50
  });
51
+ await fsp.mkdir(outDir, { recursive: true });
54
52
  const plan = planAssetPublish(sources, outDir);
55
53
  const manifest = {
56
54
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1,6 +1,9 @@
1
+ import { resolveTrustedHostNames } from "../src/context/domainMatcher.js";
1
2
  import { defaultIntrospection, generateHaproxyConfig, generateNginxConfig } from "../src/proxy/generateProxyConfig.js";
3
+ import { planAssetPublish } from "../src/assets/collectAssets.js";
2
4
  import { Command } from "nodefony";
3
5
  import fsp from "node:fs/promises";
6
+ import { isAbsolute, resolve } from "node:path";
4
7
  //#region nodefony/command/proxyGenerateCommand.ts
5
8
  const options = {
6
9
  helpGroup: "FRONT ET RÉSEAU",
@@ -21,17 +24,20 @@ var ProxyGenerate = class extends Command {
21
24
  this.addOption("-b, --backend <host>", "backend host the proxy connects to (default 127.0.0.1)");
22
25
  this.addOption("-l, --listen <port>", "proxy listen port (default 80)");
23
26
  this.addOption("--reencrypt", "re-encrypt to the HTTPS backend (TLS proxy↔backend) instead of clear");
27
+ this.addOption("--assets-root <dir>", "serve every static asset from this single tree (see `assets:publish`)");
28
+ this.addOption("--tls-cert <file>", "TLS certificate chain (nginx only)");
29
+ this.addOption("--tls-key <file>", "TLS private key (nginx only)");
30
+ this.addOption("--tls-listen <port>", "TLS listen port (default 443)");
24
31
  }
25
32
  async generate(target, opts) {
26
- if (target !== "nginx" && target !== "haproxy") {
27
- this.log(`Cible inconnue '${target}' attendu: nginx | haproxy.`, "ERROR");
28
- return this;
29
- }
33
+ if (target !== "nginx" && target !== "haproxy") throw new Error(`Cible inconnue '${target}' — attendu: nginx | haproxy.`);
34
+ if (Boolean(opts.tlsCert) !== Boolean(opts.tlsKey)) throw new Error("--tls-cert et --tls-key vont ensemble — l'un sans l'autre ne produit aucune écoute TLS.");
35
+ if (opts.tlsCert && target === "haproxy") throw new Error("--tls-cert/--tls-key ne valent que pour nginx : haproxy exige un PEM combiné (cf docker/certs/build-haproxy-pem.sh).");
30
36
  const intro = this.buildIntrospection(opts);
31
37
  const conf = target === "nginx" ? generateNginxConfig(intro) : generateHaproxyConfig(intro);
32
38
  if (opts.out) {
33
39
  await fsp.writeFile(opts.out, conf, "utf8");
34
- this.log(`Configuration ${target} écrite → ${opts.out}`, "INFO");
40
+ process.stdout.write(`Configuration ${target} écrite → ${opts.out}\n`);
35
41
  } else process.stdout.write(conf);
36
42
  return this;
37
43
  }
@@ -42,14 +48,22 @@ var ProxyGenerate = class extends Command {
42
48
  const servers = (this.kernel?.options)?.servers;
43
49
  const staticSvc = module?.get("server-static");
44
50
  staticSvc?.mountModulePublics?.();
45
- const staticRoots = staticSvc?.servers ? Object.keys(staticSvc.servers) : [];
46
- const mounts = (staticSvc?.mounts ?? []).map((m) => ({
51
+ let staticRoots = staticSvc?.servers ? Object.keys(staticSvc.servers) : [];
52
+ let mounts = (staticSvc?.mounts ?? []).map((m) => ({
47
53
  prefix: m.prefix,
48
54
  dir: m.dir
49
55
  }));
56
+ if (opts.assetsRoot) {
57
+ const root = isAbsolute(opts.assetsRoot) ? opts.assetsRoot : resolve(process.cwd(), opts.assetsRoot);
58
+ mounts = planAssetPublish(mounts, root).map((p) => ({
59
+ prefix: p.prefix,
60
+ dir: p.target
61
+ }));
62
+ staticRoots = [root];
63
+ }
50
64
  return {
51
65
  ...defaultIntrospection,
52
- domains: httpOpts.trustedHosts ?? [],
66
+ domains: resolveTrustedHostNames(this.kernel?.domain ?? "", httpOpts.trustedHosts),
53
67
  backendHost: opts.backend ?? "127.0.0.1",
54
68
  httpPort: Number(servers?.http?.port) || defaultIntrospection.httpPort,
55
69
  httpsPort: Number(servers?.https?.port) || defaultIntrospection.httpsPort,
@@ -58,7 +72,12 @@ var ProxyGenerate = class extends Command {
58
72
  listen: opts.listen ? Number(opts.listen) : defaultIntrospection.listen,
59
73
  reencrypt: Boolean(opts.reencrypt),
60
74
  maxBodyBytes: Number(httpOpts.maxBodySize) || 0,
61
- keepaliveIntervalMs: Number(httpOpts.websocket?.keepaliveInterval) || 0
75
+ keepaliveIntervalMs: Number(httpOpts.websocket?.keepaliveInterval) || 0,
76
+ tls: opts.tlsCert && opts.tlsKey ? {
77
+ certPath: opts.tlsCert,
78
+ keyPath: opts.tlsKey,
79
+ listen: Number(opts.tlsListen) || 443
80
+ } : null
62
81
  };
63
82
  }
64
83
  };
@@ -58,7 +58,7 @@ import { z } from "zod";
58
58
  * Ce schéma reste PUR (pas de `Nodefony.getKernel()` ni `process.env`) → sa
59
59
  * sortie est déterministe et sérialisable en JSON Schema (`httpConfigJsonSchema`
60
60
  * dans `defineModuleConfig.ts`). Les défauts dérivés du kernel
61
- * (`upload.uploadDir` ← `kernel.tmpDir`, `certificates.openssl.attrs` ←
61
+ * (`upload.uploadDir` ← `kernel.tmpDir`, `certificates.selfSigned.attrs` ←
62
62
  * `kernel.domain`/`projectName`) sont injectés APRÈS le parse, dans le builder
63
63
  * (kernel disponible à `onKernelRegister`).
64
64
  *
@@ -155,12 +155,12 @@ const http2Schema = z.looseObject({
155
155
  maxSessionMemory: z.number().int().positive().default(10).describe("Mémoire max (Mo) par session HTTP/2 — borne l'amplification mémoire.")
156
156
  }).describe("Serveur HTTP/2 (sur le port HTTPS, allowHTTP1). Loose : options `http2.SecureServerOptions` transmises.");
157
157
  const http3Schema = z.looseObject({});
158
- const opensslAttrSchema = z.looseObject({
158
+ const selfSignedAttrSchema = z.looseObject({
159
159
  name: z.string().optional(),
160
160
  value: z.string().optional(),
161
161
  shortName: z.string().optional()
162
162
  }).describe("Champ de sujet/issuer node-forge (CertificateField).");
163
- const opensslSchema = z.strictObject({
163
+ const selfSignedSchema = z.strictObject({
164
164
  size: z.number().int().positive().default(2048).describe("Taille de la clé RSA (bits). 2048 minimum, 4096 recommandé en prod."),
165
165
  hash: z.enum([
166
166
  "sha256",
@@ -169,11 +169,11 @@ const opensslSchema = z.strictObject({
169
169
  ]).default("sha256").describe("Algorithme de hachage de la signature. SHA-1 INTERDIT (collision SHAttered 2017 ; CA/Browser Forum depuis 2016)."),
170
170
  validityDays: z.number().int().positive().default(365).describe("Durée de validité du certificat (jours). ≤ 398 recommandé (CA/B Forum)."),
171
171
  backdateMinutes: z.number().int().min(0).default(5).describe("Recul de notBefore (minutes) — tolère le décalage d'horloge du client (évite « certificate not yet valid »)."),
172
- attrs: z.array(opensslAttrSchema).default([]).meta({
172
+ attrs: z.array(selfSignedAttrSchema).default([]).meta({
173
173
  kernelDerived: true,
174
174
  description: "Attributs du certificat (commonName, organizationName…). Vide (défaut) = dérivés du kernel par le builder (commonName ← domain)."
175
175
  })
176
- }).describe("Options de génération du certificat auto-signé (node-forge).");
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é.");
177
177
  const sanSchema = z.strictObject({
178
178
  dns: z.array(z.string()).default([]).meta({
179
179
  kernelDerived: true,
@@ -195,7 +195,7 @@ const certificatesSchema = z.strictObject({
195
195
  privateKeyMode: z.number().int().default(384).meta({ description: "Permissions POSIX de la clé privée écrite sur disque (0600 = lecture/écriture owner uniquement). Une clé TLS ne doit JAMAIS être world-readable." }),
196
196
  san: sanSchema.default(() => sanSchema.parse({})),
197
197
  dev: certDevSchema.default(() => certDevSchema.parse({})),
198
- openssl: opensslSchema.default(() => opensslSchema.parse({}))
198
+ selfSigned: selfSignedSchema.default(() => selfSignedSchema.parse({}))
199
199
  }).describe("Certificats TLS du serveur HTTPS et WSS.");
200
200
  const websocketSchema = z.looseObject({
201
201
  keepaliveInterval: z.number().int().positive().default(2e4).describe("Intervalle (ms) des pings keep-alive — détecte les connexions zombies."),
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
  * Injecte les défauts dérivés du kernel APRÈS le parse (le schéma reste pur).
7
7
  *
8
8
  * - `upload.uploadDir` vide → répertoire temporaire du kernel (`kernel.tmpDir`).
9
- * - `certificates.openssl.attrs` vide → sujet du certificat dérivé du kernel
9
+ * - `certificates.selfSigned.attrs` vide → sujet du certificat dérivé du kernel
10
10
  * (`commonName` ← `kernel.domain`, `organizationName` ← `kernel.projectName`).
11
11
  * Sans ce remplissage, le certificat auto-signé n'aurait pas de commonName.
12
12
  * - `certificates.san` vide → Subject Alternative Name dérivé du kernel
@@ -28,7 +28,7 @@ function applyKernelDefaults(config, kernel) {
28
28
  const p = kernel?.tmpDir?.path;
29
29
  config.upload.uploadDir = typeof p === "string" ? p : "/tmp";
30
30
  }
31
- if (config.certificates.openssl.attrs.length === 0) config.certificates.openssl.attrs = [
31
+ if (config.certificates.selfSigned.attrs.length === 0) config.certificates.selfSigned.attrs = [
32
32
  {
33
33
  name: "commonName",
34
34
  value: kernel?.domain ?? "nodefony.com"
@@ -22,7 +22,7 @@ const defaultOptions = {
22
22
  dns: [],
23
23
  ip: []
24
24
  },
25
- openssl: {
25
+ selfSigned: {
26
26
  size: 2048,
27
27
  hash: "sha256",
28
28
  validityDays: 365,
@@ -98,7 +98,32 @@ var Certificate = class Certificate extends Service {
98
98
  if (!this.forge) throw new Error("node-forge non chargé — appeler loadForge() avant toute génération.");
99
99
  return this.forge;
100
100
  }
101
+ /**
102
+ * Fabrique-t-on un certificat au démarrage ?
103
+ *
104
+ * 🔴 Seulement si un serveur TLS est ACTIF. Sans cette question, le hook
105
+ * ci-dessous écrit dans `nodefony/config/certificates` à CHAQUE boot — y
106
+ * compris celui d'une application qui a coupé son écoute TLS, et y compris un
107
+ * run de console qui n'ouvre aucun port.
108
+ *
109
+ * Ce qu'il en coûtait, mesuré sur une image générée : le code d'une image
110
+ * appartient à `root` et le processus tourne en `1000` (c'est voulu — une
111
+ * application qui peut réécrire son propre `dist/` offre à une faille un moyen
112
+ * de PERSISTER). Le `mkdir` mourait donc en `EACCES`, le hook de boot était
113
+ * « critique », et l'application ne démarrait PAS — quel que soit son préset.
114
+ * L'erreur nommait un dossier de certificats sur une application qui n'en veut
115
+ * aucun : elle envoyait chercher du côté du TLS un défaut de permission.
116
+ *
117
+ * C'est aussi ce que le gabarit d'application promet en toutes lettres : en
118
+ * production, l'écoute TLS est coupée tant qu'aucun port HTTPS n'est demandé,
119
+ * précisément pour ne PAS fabriquer une clé RSA à chaque démarrage de chaque
120
+ * exemplaire. La promesse était écrite ; rien ne la tenait.
121
+ */
122
+ get tlsWanted() {
123
+ return !!this.module.kernel?.options?.servers?.https;
124
+ }
101
125
  async init() {
126
+ if (!this.tlsWanted) return this;
102
127
  this.kernel?.once("onBoot", async () => {
103
128
  this.options = extend(true, this.options, this.module.options.certificates || {});
104
129
  await this.generateServerCertificates();
@@ -418,7 +443,7 @@ var Certificate = class Certificate extends Service {
418
443
  }
419
444
  }
420
445
  generateKeys() {
421
- return this.forgeLib.pki.rsa.generateKeyPair(this.certOptions.openssl.size);
446
+ return this.forgeLib.pki.rsa.generateKeyPair(this.certOptions.selfSigned.size);
422
447
  }
423
448
  generatePrivateKeyPem() {
424
449
  if (this.keysPair) return Buffer.from(this.forgeLib.pki.privateKeyToPem(this.keysPair.privateKey));
@@ -434,7 +459,7 @@ var Certificate = class Certificate extends Service {
434
459
  }
435
460
  createCertificate() {
436
461
  if (!this.keysPair) throw new Error(`KeyPair not found`);
437
- const o = this.certOptions.openssl;
462
+ const o = this.certOptions.selfSigned;
438
463
  const cert = this.forgeLib.pki.createCertificate();
439
464
  cert.publicKey = this.keysPair.publicKey;
440
465
  cert.serialNumber = Certificate.generateSerialHex();
@@ -491,7 +516,7 @@ var Certificate = class Certificate extends Service {
491
516
  */
492
517
  sign() {
493
518
  if (!this.certForge || !this.keysPair) throw new Error(`pki.rsa.KeyPair or pki.Certificate not found`);
494
- this.certForge.sign(this.keysPair.privateKey, this.digestFor(this.certOptions.openssl.hash));
519
+ this.certForge.sign(this.keysPair.privateKey, this.digestFor(this.certOptions.selfSigned.hash));
495
520
  }
496
521
  /**
497
522
  * Résumé introspectable du certificat serveur courant — réutilisé par la
@@ -12,8 +12,8 @@ import DefaultRequestLogger from "./request-logger.js";
12
12
  import JsonAuditLogger from "./audit-logger.js";
13
13
  import PrettyRequestLogger from "./pretty-request-logger.js";
14
14
  import { resolveTraceparent } from "./trace.js";
15
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
16
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
15
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
16
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
17
17
  import { GcScheduler, Module, RequestContext, Service, injectable, logColor, nodefonyError, writeRuntimeState } from "nodefony";
18
18
  import cluster from "node:cluster";
19
19
  import Ws from "ws";
@@ -1,10 +1,10 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import http_kernel_default from "../http-kernel.js";
4
4
  import { handleClientError } from "./clientError.js";
5
5
  import { createDrainTerminator } from "./serverShutdown.js";
6
6
  import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
7
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
7
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
8
8
  import { Module, Service, extend, inject, nodefonyError } from "nodefony";
9
9
  import http from "node:http";
10
10
  //#region nodefony/service/servers/server-http.ts
@@ -1,10 +1,10 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import http_kernel_default from "../http-kernel.js";
4
4
  import { handleClientError } from "./clientError.js";
5
5
  import { createDrainTerminator } from "./serverShutdown.js";
6
6
  import { bindWithFallback, buildBindPlan } from "../../src/servers/portBinder.js";
7
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
7
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
8
8
  import { Module, Service, extend, inject, nodefonyError } from "nodefony";
9
9
  import http2 from "node:http2";
10
10
  import https from "node:https";
@@ -1,7 +1,7 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import http_kernel_default from "../http-kernel.js";
4
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
4
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
5
5
  import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
6
6
  import { Module, Service, inject } from "nodefony";
7
7
  import Ws, { WebSocketServer } from "ws";
@@ -1,7 +1,7 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import http_kernel_default from "../http-kernel.js";
4
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
4
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
5
5
  import { startHeartbeat, trackPong } from "./wsHeartbeat.js";
6
6
  import { Module, Service, inject } from "nodefony";
7
7
  import Ws, { WebSocketServer } from "ws";
@@ -1,12 +1,12 @@
1
1
  import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
2
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import http_kernel_default from "../http-kernel.js";
5
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
5
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
6
6
  import Session from "../../src/session/session.js";
7
7
  import MemorySessionStorage from "../../src/session/storage/MemorySessionStorage.js";
8
8
  import RevocationGuardStorage from "../../src/session/storage/RevocationGuardStorage.js";
9
- import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Module, Nodefony, RequestContext, Service, countFacets, extend, inject, injectable, readStoreLocation, resolveAutoStore } from "nodefony";
9
+ import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Module, Nodefony, RequestContext, Service, countFacets, extend, inject, injectable, readStoreLocation, resolveAutoStore, runNeedsExternalServices } from "nodefony";
10
10
  import { createHash, createHmac } from "node:crypto";
11
11
  //#region nodefony/service/sessions/sessions-service.ts
12
12
  var _SessionsService;
@@ -142,7 +142,7 @@ let SessionsService = class SessionsService extends Service {
142
142
  const configured = storeName;
143
143
  let reason = `store explicitement configuré ("${configured}")`;
144
144
  if (storeName === AUTO_STORE) {
145
- const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory");
145
+ const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory", runNeedsExternalServices(this.kernel));
146
146
  storeName = auto.store;
147
147
  reason = auto.reason;
148
148
  this.log(`session.store "auto" → "${storeName}" (${auto.reason})`, "INFO");
@@ -1,7 +1,7 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
1
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
3
3
  import http_kernel_default from "../http-kernel.js";
4
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js";
4
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateParam.js";
5
5
  import { Cli, FileClass, Module, Service, inject } from "nodefony";
6
6
  import fs from "node:fs";
7
7
  import fsp from "node:fs/promises";
@@ -74,6 +74,39 @@ function compileTrustedHosts(domain, trusted, isDev) {
74
74
  return compileDomainPatterns(patterns);
75
75
  }
76
76
  /**
77
+ * Rend les NOMS d'hôtes que la barrière `trustedHosts` accepte — la même
78
+ * politique que {@link compileTrustedHosts}, mais lisible par un humain ou par
79
+ * un générateur de configuration (`server_name` nginx, `hdr(host)` haproxy).
80
+ *
81
+ * Pourquoi une seconde lecture de la même règle : une `RegExp` ne se réécrit pas
82
+ * en nom d'hôte. La commande `proxy:generate` lisait donc `trustedHosts` à sa
83
+ * façon, en le supposant TOUJOURS `string[]` — alors que sa valeur par DÉFAUT
84
+ * est `false`, celle de toute application générée. Une politique lue à deux
85
+ * endroits finit par diverger : ici les deux fonctions partent de la même
86
+ * valeur, et la règle « le domaine canonique est toujours accepté » n'est
87
+ * écrite qu'une fois.
88
+ *
89
+ * Le loopback de développement n'en fait volontairement pas partie : une
90
+ * configuration de proxy décrit un déploiement, pas la machine de l'auteur.
91
+ *
92
+ * @param domain - domaine canonique du serveur (`kernel.domain`).
93
+ * @param trusted - config `http.trustedHosts` (optionnelle).
94
+ * @returns les noms acceptés, sans doublon. **Vide** si `trusted === true`
95
+ * (bypass : le proxy filtre déjà le `Host`, aucun nom n'est à imposer) ; les
96
+ * motifs `RegExp` sont écartés, faute d'être exprimables en nom d'hôte.
97
+ */
98
+ function resolveTrustedHostNames(domain, trusted) {
99
+ if (trusted === true) return [];
100
+ const patterns = [domain];
101
+ if (trusted) {
102
+ if (Array.isArray(trusted)) patterns.push(...trusted);
103
+ else patterns.push(trusted);
104
+ }
105
+ const names = [];
106
+ for (const p of patterns) if (typeof p === "string" && p && !names.includes(p)) names.push(p);
107
+ return names;
108
+ }
109
+ /**
77
110
  * Teste un `Host` entrant contre une liste de `RegExp` pré-compilée.
78
111
  *
79
112
  * @param regAlias - sortie de {@link compileTrustedHosts} ou {@link compileDomainPatterns}.
@@ -85,4 +118,4 @@ function isDomainAllowed(regAlias, domain) {
85
118
  return false;
86
119
  }
87
120
  //#endregion
88
- export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed };
121
+ export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, resolveTrustedHostNames };
@@ -10,7 +10,8 @@ const defaultIntrospection = {
10
10
  listen: 80,
11
11
  reencrypt: false,
12
12
  maxBodyBytes: 0,
13
- keepaliveIntervalMs: 0
13
+ keepaliveIntervalMs: 0,
14
+ tls: null
14
15
  };
15
16
  /**
16
17
  * Délai d'inactivité, en secondes, qu'un proxy doit accorder à une connexion
@@ -68,23 +69,49 @@ function generateNginxConfig(intro) {
68
69
  const backendPort = intro.reencrypt ? intro.httpsPort : intro.httpPort;
69
70
  const idleSeconds = idleTimeoutSeconds(intro);
70
71
  const lines = [];
71
- lines.push("# Généré par `nodefony proxy:generate nginx` — NE PAS éditer à la main.", "# Reverse-proxy dérivé de l'introspection Nodefony (domaines, statiques, ports).", "worker_processes auto;", "events { worker_connections 1024; }", "", "http {", " # Upgrade WebSocket — HTTP et WS co-habitent sur le même port Nodefony.", " map $http_upgrade $connection_upgrade { default upgrade; '' close; }", "", ` upstream nodefony { server ${intro.backendHost}:${backendPort}; keepalive 32; }`, "");
72
+ lines.push("# Généré par `nodefony proxy:generate nginx` — NE PAS éditer à la main.", "# Reverse-proxy dérivé de l'introspection Nodefony (domaines, statiques, ports).", "worker_processes auto;", "events { worker_connections 1024; }", "", "http {", " include /etc/nginx/mime.types;", " default_type application/octet-stream;", "", " server_tokens off;", "", " sendfile on;", " tcp_nopush on;", " tcp_nodelay on;", "", " gzip on;", " gzip_vary on;", " gzip_min_length 1024;", " gzip_proxied any;", " gzip_types text/plain text/css text/xml application/javascript application/json application/xml image/svg+xml application/manifest+json;", "", " # Upgrade WebSocket — HTTP et WS co-habitent sur le même port Nodefony.", " map $http_upgrade $connection_upgrade { default upgrade; '' close; }", "", ` upstream nodefony { server ${intro.backendHost}:${backendPort}; keepalive 32; }`, "");
72
73
  if (intro.maxBodyBytes > 0) lines.push(` # Aligné sur \`http.maxBodySize\` (${intro.maxBodyBytes} octets) — sans quoi`, " # nginx rendrait 413 à 1 Mo, son défaut, sans que le serveur le sache.", ` client_max_body_size ${intro.maxBodyBytes};`, "");
73
- lines.push(" server {", ` listen ${intro.listen};`, ` server_name ${serverNames(intro.domains)};`);
74
+ lines.push(...nginxServerBlock(intro, scheme, idleSeconds, null));
75
+ if (intro.tls) lines.push("", ...nginxServerBlock(intro, scheme, idleSeconds, intro.tls));
76
+ lines.push("}", "");
77
+ return lines.join("\n");
78
+ }
79
+ /**
80
+ * Un bloc `server {}` nginx — corps IDENTIQUE en clair et en TLS.
81
+ *
82
+ * Le scheme annoncé au backend reste `$scheme`, que nginx CONSTATE sur la
83
+ * connexion entrante : le même corps sert donc les deux écoutes sans qu'aucune
84
+ * n'ait à savoir laquelle elle est. C'est ce qui fait qu'un cookie `Secure`
85
+ * tient derrière le frontal alors que le lien interne est en clair.
86
+ *
87
+ * @param intro - modèle d'introspection Nodefony.
88
+ * @param scheme - `http` ou `https` vers le BACKEND (re-chiffrement).
89
+ * @param idleSeconds - inactivité tolérée, dérivée du heartbeat WebSocket.
90
+ * @param tls - terminaison TLS de CE bloc, ou `null` pour une écoute en clair.
91
+ * @returns les lignes du bloc `server`.
92
+ */
93
+ function nginxServerBlock(intro, scheme, idleSeconds, tls) {
94
+ const lines = [" server {"];
95
+ if (tls) lines.push(` listen ${tls.listen} ssl;`, " http2 on;", ` server_name ${serverNames(intro.domains)};`, "", " # Terminaison TLS au frontal. Ces chemins sont ceux d'un MONTAGE, à", " # pourvoir au déploiement (volume compose, secret k8s) : une clé privée", " # n'entre pas dans une image, où la couche reste lisible même effacée.", ` ssl_certificate ${tls.certPath};`, ` ssl_certificate_key ${tls.keyPath};`, " ssl_protocols TLSv1.2 TLSv1.3;", " ssl_session_cache shared:SSL:10m;", " ssl_session_timeout 1h;", "", " error_page 497 =301 https://$http_host$request_uri;");
96
+ else lines.push(` listen ${intro.listen};`, ` server_name ${serverNames(intro.domains)};`);
74
97
  if (intro.reencrypt) lines.push(" # Re-encrypt : valider le cert backend (cf docker/certs).", " # proxy_ssl_trusted_certificate /etc/nginx/certs/ca.pem;", " # proxy_ssl_verify on; proxy_ssl_name nodefony.com;");
75
- for (const m of intro.mounts) lines.push("", ` location ${m.prefix} {`, ` alias ${ensureTrailingSlash(m.dir)};`, " access_log off;", " expires 1h;", " }");
98
+ for (const m of intro.mounts) {
99
+ const prefix = ensureTrailingSlash(m.prefix);
100
+ const dir = ensureTrailingSlash(m.dir);
101
+ lines.push("", ` location ${prefix}assets/ {`, ` alias ${dir}assets/;`, " access_log off;", " expires 1y;", " add_header Cache-Control \"public, immutable\";", " }", "", ` location ${prefix} {`, ` alias ${dir};`, " access_log off;", " expires 1h;", " }");
102
+ }
76
103
  lines.push("", ` location @nodefony {`, ` proxy_pass ${scheme}://nodefony;`, " proxy_http_version 1.1;", nginxForwardHeaders(idleSeconds), " }");
77
104
  if (intro.staticRoots.length === 0) lines.push("", " location / {", ` proxy_pass ${scheme}://nodefony;`, " proxy_http_version 1.1;", nginxForwardHeaders(idleSeconds), " }");
78
105
  else {
79
106
  const roots = intro.staticRoots;
80
- lines.push("", " # Statiques multi-dossiers (racine app + modules) : chaîne try_files,", " # fallback vers le backend Nodefony si aucun fichier ne matche.", " location / {", ` root ${roots[0]};`, ` try_files $uri ${roots.length > 1 ? "@r1" : "@nodefony"};`, " }");
107
+ lines.push("", roots.length > 1 ? " # Statiques multi-dossiers (racine app + modules) : chaîne try_files,\n # fallback vers le backend Nodefony si aucun fichier ne matche." : " # Statiques servis par le frontal ; fallback vers le backend\n # Nodefony si aucun fichier ne correspond.", " location / {", ` root ${roots[0]};`, ` try_files $uri ${roots.length > 1 ? "@r1" : "@nodefony"};`, " }");
81
108
  for (let i = 1; i < roots.length; i++) {
82
109
  const next = i + 1 < roots.length ? `@r${i + 1}` : "@nodefony";
83
110
  lines.push(` location @r${i} {`, ` root ${roots[i]};`, ` try_files $uri ${next};`, " }");
84
111
  }
85
112
  }
86
- lines.push(" }", "}", "");
87
- return lines.join("\n");
113
+ lines.push(" }");
114
+ return lines;
88
115
  }
89
116
  /**
90
117
  * Génère une configuration haproxy (reverse-proxy + Forwarded RFC 7239).
@@ -54,11 +54,11 @@ declare class Http extends Module<IHttpConfig> {
54
54
  }
55
55
  export default Http;
56
56
  export { defineHttpConfig, httpConfigJsonSchema, } from "./nodefony/config/defineModuleConfig.js";
57
- export { httpConfigSchema, type HttpConfig, type HttpConfigInput, } from "./nodefony/config/config.js";
57
+ export { httpConfigSchema } from "./nodefony/config/config.js";
58
58
  export type { IHttpConfig, IHttpConfigInput, } from "./nodefony/interfaces/IHttpConfig.js";
59
59
  export { decideSend, readBackpressureOptions, type WsBackpressurePolicy, type WsSendDecision, type IWsBackpressureOptions, type IBackpressureSocket, type IBackpressureTarget, } from "./nodefony/src/context/websocket/wsBackpressure.js";
60
60
  export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, } from "./nodefony/src/context/domainMatcher.js";
61
- export type { DomainPattern, TrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
61
+ export type { DomainPattern, ITrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
62
62
  export { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
63
63
  export type { IRateLimitStore, IRateLimitOptions, RateLimitVerdict, } from "./nodefony/src/rateLimit/IRateLimitStore.js";
64
64
  export { Context, Session, SessionsService, HttpError, HttpKernel, Response, Http2Response, Response as HttpResponse, wsResponse, wsResponse as WebsocketResponse, Cookie, HttpContext, WebsocketContext, HttpRequest, Http2Request, DefaultErrorRenderer, DefaultRequestLogger, JsonAuditLogger, PrettyRequestLogger, Profiler, };
@@ -12,6 +12,10 @@ declare class ProxyGenerate extends Command {
12
12
  backend?: string;
13
13
  listen?: string;
14
14
  reencrypt?: boolean;
15
+ assetsRoot?: string;
16
+ tlsCert?: string;
17
+ tlsKey?: string;
18
+ tlsListen?: string;
15
19
  }): Promise<this>;
16
20
  /** Construit le modèle d'introspection depuis le kernel + le service statique. */
17
21
  private buildIntrospection;
@@ -74,7 +74,7 @@ export declare const httpConfigSchema: z.ZodObject<{
74
74
  dev: z.ZodDefault<z.ZodObject<{
75
75
  useMkcert: z.ZodDefault<z.ZodBoolean>;
76
76
  }, z.core.$strict>>;
77
- openssl: z.ZodDefault<z.ZodObject<{
77
+ selfSigned: z.ZodDefault<z.ZodObject<{
78
78
  size: z.ZodDefault<z.ZodNumber>;
79
79
  hash: z.ZodDefault<z.ZodEnum<{
80
80
  sha256: "sha256";
@@ -186,12 +186,12 @@ export declare const httpConfigSchema: z.ZodObject<{
186
186
  wsMaxConnectionsPerIp: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
187
187
  }, z.core.$strict>;
188
188
  /** Type de sortie (config normalisée + défauts appliqués). */
189
- export type HttpConfig = z.infer<typeof httpConfigSchema>;
189
+ export type IHttpConfig = z.infer<typeof httpConfigSchema>;
190
190
  /** Type d'entrée (toutes sections omissibles — défauts du schéma). */
191
- export type HttpConfigInput = z.input<typeof httpConfigSchema>;
191
+ export type IHttpConfigInput = z.input<typeof httpConfigSchema>;
192
192
  /**
193
193
  * Défauts du module, matérialisés depuis le schéma (source unique). Toujours
194
194
  * valides par construction ; passés au `super(..., config)` du Module class.
195
195
  */
196
- declare const config: HttpConfig;
196
+ declare const config: IHttpConfig;
197
197
  export default config;
@@ -1,12 +1,16 @@
1
- import type { HttpConfig, HttpConfigInput } from "../config/config.js";
2
1
  /**
3
- * Configuration validée de `@nodefony/http` (sortie du parse Zod + défauts
4
- * kernel appliqués par le builder). Source de vérité = `../config/config.ts`.
2
+ * Barrel d'interfaces de la configuration de `@nodefony/http`.
3
+ *
4
+ * Ces types sont DÉRIVÉS du schéma Zod — leur source de vérité unique est
5
+ * `../config/config.ts`, et ce fichier ne fait que les republier sous le chemin
6
+ * d'interfaces attendu par la convention du dépôt (`nodefony/interfaces/I*.ts`).
7
+ *
8
+ * ⚠️ Un ré-export, jamais un alias (`export type IHttpConfig = HttpConfig`) :
9
+ * un alias publierait DEUX noms pour le même type, tous deux dans les `.d.ts`
10
+ * et l'autocomplétion du consommateur, sans que rien ne dise lequel est le bon.
5
11
  */
6
- export type IHttpConfig = HttpConfig;
7
- /**
8
- * Configuration brute fournie par l'application (toutes sections optionnelles —
9
- * les sections omises reçoivent leurs défauts). Entrée du builder
10
- * {@link import("../config/defineModuleConfig").defineHttpConfig}.
11
- */
12
- export type IHttpConfigInput = HttpConfigInput;
12
+ export type {
13
+ /** Type de sortie (config normalisée + défauts appliqués). */
14
+ IHttpConfig,
15
+ /** Type d'entrée (toutes sections omissibles défauts du schéma). */
16
+ IHttpConfigInput, } from "../config/config.js";