@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.
- package/LICENSE +201 -543
- package/README.md +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.148.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorateParam.js +1 -1
- package/dist/index.js +2 -2
- package/dist/nodefony/command/assetsPublishCommand.js +2 -4
- package/dist/nodefony/command/proxyGenerateCommand.js +28 -9
- package/dist/nodefony/config/config.js +6 -6
- package/dist/nodefony/config/defineModuleConfig.js +2 -2
- package/dist/nodefony/service/certificates.js +29 -4
- package/dist/nodefony/service/http-kernel.js +2 -2
- package/dist/nodefony/service/servers/server-http.js +3 -3
- package/dist/nodefony/service/servers/server-https.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +3 -3
- package/dist/nodefony/service/servers/server-websocket.js +3 -3
- package/dist/nodefony/service/sessions/sessions-service.js +5 -5
- package/dist/nodefony/service/upload/upload-service.js +3 -3
- package/dist/nodefony/src/context/domainMatcher.js +34 -1
- package/dist/nodefony/src/proxy/generateProxyConfig.js +34 -7
- package/dist/types/index.d.ts +2 -2
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +4 -0
- package/dist/types/nodefony/config/config.d.ts +4 -4
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +14 -10
- package/dist/types/nodefony/service/certificates.d.ts +33 -2
- package/dist/types/nodefony/service/http-kernel.d.ts +2 -2
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +25 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +24 -0
- package/docs/cookies.md +2 -2
- package/docs/observabilite.md +2 -2
- package/docs/rate-limit.md +5 -5
- package/docs/servers.md +39 -39
- package/docs/session.md +8 -8
- package/docs/upload.md +3 -3
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
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.
|
|
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
|
}
|
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.
|
|
25
|
-
import __decorate from "./_virtual/_@oxc-project_runtime@0.
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
16
|
-
import __decorate from "../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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.
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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.
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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.
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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.
|
|
3
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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.
|
|
2
|
-
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.
|
|
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.
|
|
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(
|
|
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)
|
|
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
|
|
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
|
|
113
|
+
lines.push(" }");
|
|
114
|
+
return lines;
|
|
88
115
|
}
|
|
89
116
|
/**
|
|
90
117
|
* Génère une configuration haproxy (reverse-proxy + Forwarded RFC 7239).
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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";
|