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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +4 -2
  3. package/dist/nodefony/command/assetsPublishCommand.js +1 -1
  4. package/dist/nodefony/command/certificatesCommand.js +1 -1
  5. package/dist/nodefony/command/networkCommand.js +1 -1
  6. package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
  7. package/dist/nodefony/config/config.js +22 -1
  8. package/dist/nodefony/interfaces/IRouting.js +1 -0
  9. package/dist/nodefony/interfaces/ISecurity.js +1 -0
  10. package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
  11. package/dist/nodefony/service/audit-logger.js +16 -13
  12. package/dist/nodefony/service/certificates.js +25 -13
  13. package/dist/nodefony/service/error-renderer.js +11 -8
  14. package/dist/nodefony/service/http-kernel.js +340 -163
  15. package/dist/nodefony/service/pretty-request-logger.js +3 -10
  16. package/dist/nodefony/service/servers/clientError.js +2 -2
  17. package/dist/nodefony/service/servers/server-http.js +20 -20
  18. package/dist/nodefony/service/servers/server-https.js +38 -39
  19. package/dist/nodefony/service/servers/server-static.js +13 -17
  20. package/dist/nodefony/service/servers/server-websocket-secure.js +8 -7
  21. package/dist/nodefony/service/servers/server-websocket.js +8 -7
  22. package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
  23. package/dist/nodefony/service/sessions/sessions-service.js +19 -12
  24. package/dist/nodefony/service/trace.js +1 -1
  25. package/dist/nodefony/service/upload/upload-service.js +8 -7
  26. package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
  27. package/dist/nodefony/src/context/Context.js +34 -19
  28. package/dist/nodefony/src/context/forwarded.js +4 -4
  29. package/dist/nodefony/src/context/http/HttpContext.js +133 -88
  30. package/dist/nodefony/src/context/http/Request.js +93 -44
  31. package/dist/nodefony/src/context/http/Response.js +107 -51
  32. package/dist/nodefony/src/context/http/parser.js +9 -10
  33. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  34. package/dist/nodefony/src/context/http2/Response.js +53 -25
  35. package/dist/nodefony/src/context/metaData.js +1 -1
  36. package/dist/nodefony/src/context/trustProxy.js +4 -3
  37. package/dist/nodefony/src/context/websocket/Response.js +13 -6
  38. package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
  39. package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
  40. package/dist/nodefony/src/cookies/cookie.js +3 -3
  41. package/dist/nodefony/src/errors/httpError.js +6 -6
  42. package/dist/nodefony/src/errors/thrownMessage.js +27 -0
  43. package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
  44. package/dist/nodefony/src/profiler/Profiler.js +4 -4
  45. package/dist/nodefony/src/servers/kernelServers.js +29 -0
  46. package/dist/nodefony/src/servers/portBinder.js +8 -21
  47. package/dist/nodefony/src/session/session.js +18 -10
  48. package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
  49. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
  50. package/dist/types/index.d.ts +16 -0
  51. package/dist/types/nodefony/config/config.d.ts +22 -0
  52. package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
  53. package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
  54. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
  55. package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
  56. package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
  57. package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
  58. package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
  59. package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
  60. package/dist/types/nodefony/interfaces/index.d.ts +2 -0
  61. package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
  62. package/dist/types/nodefony/service/certificates.d.ts +10 -10
  63. package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
  64. package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
  65. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
  66. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
  67. package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
  68. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
  69. package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
  70. package/dist/types/nodefony/src/context/Context.d.ts +21 -10
  71. package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
  72. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
  73. package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
  74. package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
  75. package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
  76. package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
  77. package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
  78. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
  79. package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
  80. package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
  81. package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
  82. package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
  83. package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
  84. package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
  85. package/dist/types/nodefony/src/servers/portBinder.d.ts +6 -19
  86. package/dist/types/nodefony/src/session/session.d.ts +12 -12
  87. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
  88. package/docs/cookies.md +20 -20
  89. package/docs/observabilite.md +25 -25
  90. package/docs/rate-limit.md +18 -18
  91. package/docs/servers.md +52 -52
  92. package/docs/session.md +65 -60
  93. package/docs/upload.md +36 -30
  94. package/package.json +7 -7
@@ -1,25 +1,7 @@
1
- import { isPortListening } from "nodefony";
1
+ import { isPortListening, resolvePortPolicy } from "nodefony";
2
2
  //#region nodefony/src/servers/portBinder.ts
3
3
  /** Nombre de ports essayés après le désiré, en `auto`. */
4
4
  const DEFAULT_PORT_RETRY_ATTEMPTS = 20;
5
- /**
6
- * Politique de port effective.
7
- *
8
- * Le défaut dépend de l'environnement, et ce n'est pas de la coquetterie :
9
- * - **production** → `strict`. Le port y est un contrat (service k8s, ingress,
10
- * sonde de santé). Un bind silencieux ailleurs donnerait un pod déclaré sain
11
- * que personne n'atteint : une panne invisible, le pire des deux mondes.
12
- * - **test** → `strict`. Un port occupé veut dire « un serveur est resté debout » ;
13
- * le banc doit s'arrêter, pas viser à côté (il taperait le serveur du voisin).
14
- * - **développement** → `auto`. Ici un port pris n'est qu'une nuisance.
15
- *
16
- * @param environment - `kernel.environment` (normalisé `development`/`production`/`test`).
17
- * @param explicit - `servers.portPolicy` s'il est configuré (il gagne toujours).
18
- */
19
- function resolvePortPolicy(environment, explicit) {
20
- if (explicit === "auto" || explicit === "strict") return explicit;
21
- return environment === "development" ? "auto" : "strict";
22
- }
23
5
  /** Port configuré d'un serveur, ou `0` (désactivé / non précisé → choix noyau). */
24
6
  function configuredPort(entry) {
25
7
  if (!entry) return 0;
@@ -106,6 +88,8 @@ function portConflictError(port, host) {
106
88
  error.code = "EADDRINUSE";
107
89
  return error;
108
90
  }
91
+ /** Plus grand numéro de port TCP (RFC 793) : au-delà, `listen` lève un `RangeError`. */
92
+ const MAX_PORT = 65535;
109
93
  /** Prochain candidat : incrémente, en sautant ce que les autres serveurs veulent. */
110
94
  function nextCandidate(from, reserved) {
111
95
  let port = from + 1;
@@ -176,10 +160,13 @@ async function bindWithFallback(server, host, plan, probe = isPortListening) {
176
160
  address: server.address(),
177
161
  shiftedFrom: candidate === plan.desired ? null : plan.desired
178
162
  };
179
- if (!conflict && !isPortUnavailable(failure.code, candidate)) throw failure;
163
+ const code = failure.code;
164
+ if (!conflict && !isPortUnavailable(typeof code === "string" ? code : void 0, candidate)) throw failure;
180
165
  if (plan.desired === 0 || used >= plan.attempts) throw failure;
181
166
  used += 1;
182
- candidate = nextCandidate(candidate, plan.reserved);
167
+ const next = nextCandidate(candidate, plan.reserved);
168
+ if (next > MAX_PORT) throw failure;
169
+ candidate = next;
183
170
  }
184
171
  }
185
172
  //#endregion
@@ -57,8 +57,9 @@ var Session = class {
57
57
  constructor(name, options, manager) {
58
58
  this.options = extend({}, defaultSessionOptions, options);
59
59
  this.manager = manager;
60
- this.storage = this.manager.storage;
61
- if (!this.storage) this.status = "disabled";
60
+ const storage = this.manager.storage;
61
+ this.storage = storage;
62
+ if (!storage) this.status = "disabled";
62
63
  this.setName(name);
63
64
  this.strategy = this.manager.sessionStrategy;
64
65
  }
@@ -195,7 +196,7 @@ var Session = class {
195
196
  return true;
196
197
  }
197
198
  setCookieSession(leftTime, options = {}) {
198
- if (this.context && this.context.response) {
199
+ if (this.context?.response) {
199
200
  const settings = extend({}, this.options.cookie, options);
200
201
  if (leftTime) settings.maxAge = leftTime;
201
202
  const cookie = new Cookie(this.context.getSessionCookieName(), this.id, settings);
@@ -207,7 +208,7 @@ var Session = class {
207
208
  return null;
208
209
  }
209
210
  deleteCookieSession() {
210
- if (this.context && this.context.response) {
211
+ if (this.context?.response) {
211
212
  let cookie = this.cookieSession;
212
213
  if (cookie) cookie.expires = /* @__PURE__ */ new Date(0);
213
214
  else cookie = new Cookie(this.context.getSessionCookieName(), "", { expires: /* @__PURE__ */ new Date(0) });
@@ -219,11 +220,17 @@ var Session = class {
219
220
  return this.cookieSession;
220
221
  }
221
222
  isValidSession(_data, context) {
222
- if (this.options.refererCheck) try {
223
- return this.checkSecureReferer(context);
224
- } catch {
225
- this.log(`SESSION REFERER MISMATCH ==> ${this.name} : ${this.id}`, "WARNING");
226
- return false;
223
+ if (this.options.refererCheck) {
224
+ let sameHost = false;
225
+ try {
226
+ sameHost = this.checkSecureReferer(context);
227
+ } catch {
228
+ sameHost = false;
229
+ }
230
+ if (!sameHost) {
231
+ this.log(`SESSION REFERER MISMATCH ==> ${this.name} : ${this.id}`, "WARNING");
232
+ return false;
233
+ }
227
234
  }
228
235
  const now = Date.now();
229
236
  if (this.options.absoluteTimeoutS && this.created) {
@@ -347,7 +354,8 @@ var Session = class {
347
354
  user: user ?? this.user ?? ""
348
355
  };
349
356
  }
350
- deSerialize(data) {
357
+ deSerialize(serialized) {
358
+ const data = serialized;
351
359
  if (data.Attributes) for (const k in data.Attributes) this.attributesBag[k] = data.Attributes[k];
352
360
  if (data.metaBag) for (const k in data.metaBag) this.metaBagStore[k] = data.metaBag[k];
353
361
  if (data.flashBag) for (const k in data.flashBag) this.flashBag[k] = data.flashBag[k];
@@ -1,3 +1,4 @@
1
+ import { thrownMessage } from "../errors/thrownMessage.js";
1
2
  //#region nodefony/src/session/sessionStoreFailure.ts
2
3
  /**
3
4
  * Reconnaît une table de session absente, quel que soit le dialecte.
@@ -18,7 +19,7 @@ function looksLikeMissingTable(text) {
18
19
  * @returns le statut, le corps à servir, et la ligne de journal.
19
20
  */
20
21
  function describeSessionStoreFailure(error) {
21
- const cause = error instanceof Error ? error.message : String(error ?? "cause inconnue");
22
+ const cause = error === null || error === void 0 ? "cause inconnue" : thrownMessage(error);
22
23
  return {
23
24
  statusCode: 500,
24
25
  body: "Internal Server Error — session store unavailable",
@@ -82,14 +82,29 @@ var RevocationGuardStorage = class {
82
82
  constructor(inner) {
83
83
  this.inner = inner;
84
84
  this.sortableFields = inner.sortableFields;
85
- if (typeof inner.listAll === "function") this.listAll = (filter) => inner.listAll(filter);
86
- if (typeof inner.listPage === "function") this.listPage = (query) => inner.listPage(query);
87
- if (typeof inner.countSessions === "function") this.countSessions = (query) => inner.countSessions(query);
88
- if (typeof inner.countDistinctUsers === "function") this.countDistinctUsers = (query) => inner.countDistinctUsers(query);
89
- if (typeof inner.touch === "function") this.touch = (id, idleSeconds) => {
90
- if (this.#isRevoked(id)) return Promise.resolve();
91
- return inner.touch(id, idleSeconds);
92
- };
85
+ if (typeof inner.listAll === "function") {
86
+ const src = inner;
87
+ this.listAll = (filter) => src.listAll(filter);
88
+ }
89
+ if (typeof inner.listPage === "function") {
90
+ const src = inner;
91
+ this.listPage = (query) => src.listPage(query);
92
+ }
93
+ if (typeof inner.countSessions === "function") {
94
+ const src = inner;
95
+ this.countSessions = (query) => src.countSessions(query);
96
+ }
97
+ if (typeof inner.countDistinctUsers === "function") {
98
+ const src = inner;
99
+ this.countDistinctUsers = (query) => src.countDistinctUsers(query);
100
+ }
101
+ if (typeof inner.touch === "function") {
102
+ const src = inner;
103
+ this.touch = (id, idleSeconds) => {
104
+ if (this.#isRevoked(id)) return Promise.resolve();
105
+ return src.touch(id, idleSeconds);
106
+ };
107
+ }
93
108
  }
94
109
  read(id) {
95
110
  return this.inner.read(id);
@@ -117,7 +132,7 @@ var RevocationGuardStorage = class {
117
132
  #revoke(id) {
118
133
  const now = Date.now();
119
134
  this.#purgeExpired(now);
120
- if (this.#tombstones === null) this.#tombstones = /* @__PURE__ */ new Map();
135
+ this.#tombstones ??= /* @__PURE__ */ new Map();
121
136
  this.#tombstones.set(id, now + TOMBSTONE_TTL_MS);
122
137
  }
123
138
  #isRevoked(id) {
@@ -1,4 +1,5 @@
1
1
  import { Kernel, Module } from "nodefony";
2
+ import { type IHttpOverlay } from "./nodefony/config/config.js";
2
3
  import type { IHttpConfig, IHttpConfigInput } from "./nodefony/interfaces/IHttpConfig.js";
3
4
  import { Profiler } from "./nodefony/src/profiler/Profiler.js";
4
5
  import HttpKernel from "./nodefony/service/http-kernel.js";
@@ -22,8 +23,21 @@ declare module "nodefony" {
22
23
  interface NodefonyModuleConfig {
23
24
  "@nodefony/http": IHttpConfigInput;
24
25
  }
26
+ interface NodefonyModuleOverlay {
27
+ "@nodefony/http": IHttpOverlay;
28
+ }
25
29
  }
26
30
  declare class Http extends Module<IHttpConfig> {
31
+ overlaySchema: import("zod").ZodObject<{
32
+ maxBodySize: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
33
+ upload: import("zod").ZodOptional<import("zod").ZodObject<{
34
+ maxFileSize: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
35
+ maxTotalFileSize: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
36
+ maxFiles: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
37
+ maxFields: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
38
+ maxFieldsSize: import("zod").ZodOptional<import("zod").ZodType<number, number, import("zod/v4/core").$ZodTypeInternals<number, number>>>;
39
+ }, import("zod/v4/core").$strict>>;
40
+ }, import("zod/v4/core").$strict>;
27
41
  constructor(kernel: Kernel);
28
42
  /**
29
43
  * Expose le JSON Schema de la config http (dérivé du schéma Zod, flags `meta()`
@@ -75,6 +89,8 @@ export { SESSION_SORTABLE_FIELDS, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_S
75
89
  export type { IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
76
90
  export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/IResponse.js";
77
91
  export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
92
+ export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
93
+ export type { ISecurityZone, IFirewallGate, } from "./nodefony/interfaces/ISecurity.js";
78
94
  export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./nodefony/interfaces/IUpload.js";
79
95
  export type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, } from "./nodefony/interfaces/IErrorRenderer.js";
80
96
  export type { IRequestLogger, IRequestLogEntry, } from "./nodefony/interfaces/IRequestLogger.js";
@@ -189,6 +189,28 @@ export declare const httpConfigSchema: z.ZodObject<{
189
189
  export type IHttpConfig = z.infer<typeof httpConfigSchema>;
190
190
  /** Type d'entrée (toutes sections omissibles — défauts du schéma). */
191
191
  export type IHttpConfigInput = z.input<typeof httpConfigSchema>;
192
+ /**
193
+ * LISTE BLANCHE des clés qu'une requête peut surcharger pour elle seule
194
+ * (`overlayConfig("@nodefony/http", …)`, #494) : les quotas de corps et
195
+ * d'envoi, lus au MOMENT de lire le corps (`Request.readBodyLimits`) — un
196
+ * calque posé avant la lecture du corps (écouteur `onRequestScope`, organisation
197
+ * reconnue à son nom d'hôte) les voit. Tout le reste est refusé : en-têtes de
198
+ * sécurité, pare-feu, `trustProxy`, sessions, limites de débit, certificats —
199
+ * et `queryString`, lu au constructeur de la requête, avant tout calque.
200
+ * Chaque champ réutilise celui du schéma (mêmes bornes) sans son défaut.
201
+ */
202
+ export declare const httpOverlaySchema: z.ZodObject<{
203
+ maxBodySize: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
204
+ upload: z.ZodOptional<z.ZodObject<{
205
+ maxFileSize: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
206
+ maxTotalFileSize: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
207
+ maxFiles: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
208
+ maxFields: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
209
+ maxFieldsSize: z.ZodOptional<z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>>;
210
+ }, z.core.$strict>>;
211
+ }, z.core.$strict>;
212
+ /** Calque accepté par `overlayConfig("@nodefony/http", …)`. */
213
+ export type IHttpOverlay = z.input<typeof httpOverlaySchema>;
192
214
  /**
193
215
  * Défauts du module, matérialisés depuis le schéma (source unique). Toujours
194
216
  * valides par construction ; passés au `super(..., config)` du Module class.
@@ -1,4 +1,5 @@
1
1
  import type { URL } from "node:url";
2
+ import type { MaybePromise } from "nodefony";
2
3
  import type { ICookie } from "./ICookie.js";
3
4
  import type { ISession } from "./ISession.js";
4
5
  import type { HTTPMethodType } from "./IRequest.js";
@@ -10,8 +11,8 @@ export type PhaseName = "parse" | "resolve" | "firewall" | "initialize" | "actio
10
11
  export interface PhaseTiming {
11
12
  name: PhaseName;
12
13
  startMs: number;
13
- endMs?: number;
14
- durationMs?: number;
14
+ endMs?: number | undefined;
15
+ durationMs?: number | undefined;
15
16
  }
16
17
  /** Issue de la traversée d'une zone firewall. */
17
18
  export type SecurityOutcome =
@@ -110,9 +111,25 @@ export interface IContext {
110
111
  export interface IHttpContext extends IContext {
111
112
  proxy: object | null;
112
113
  isRedirect: boolean;
113
- handle(): Promise<object>;
114
- render(chunk: unknown, encoding?: BufferEncoding, status?: string | number, headers?: Record<string, string | number>): Promise<object>;
115
- send(chunk?: unknown, encoding?: BufferEncoding): Promise<object>;
114
+ /**
115
+ * Sert la requête — synchrone quand l'action et son rendu le sont.
116
+ *
117
+ * @returns le résultat du rendu, ou sa promesse dès qu'une étape attend.
118
+ */
119
+ handle(): MaybePromise<object>;
120
+ /**
121
+ * Sérialise puis envoie — synchrone quand l'envoi l'est.
122
+ *
123
+ * @returns la réponse, ou sa promesse dès qu'une étape attend.
124
+ */
125
+ render(chunk: unknown, encoding?: BufferEncoding, status?: string | number, headers?: Record<string, string | number>): MaybePromise<object>;
126
+ /**
127
+ * Envoie la réponse — synchrone sans session à sauver, sans hook `onSend`,
128
+ * pour une réponse unique.
129
+ *
130
+ * @returns la réponse, ou sa promesse dès qu'une étape attend.
131
+ */
132
+ send(chunk?: unknown, encoding?: BufferEncoding): MaybePromise<object>;
116
133
  end(): Promise<object>;
117
134
  redirect(url: string, status?: number | string, headers?: Record<string, string | number>): unknown;
118
135
  getHostName(): string | undefined;
@@ -121,8 +138,8 @@ export interface IHttpContext extends IContext {
121
138
  getUserAgent(): string | undefined;
122
139
  }
123
140
  export interface IWebsocketContext extends IContext {
124
- acceptedProtocol?: string;
125
- connection: unknown | null;
141
+ acceptedProtocol?: string | undefined;
142
+ connection: unknown;
126
143
  rejected: boolean;
127
144
  port: number | string;
128
145
  wsUrl: URL | null;
@@ -1,15 +1,15 @@
1
1
  export type SameSiteType = "Strict" | "Lax" | "None";
2
2
  export type PriorityType = "High" | "Medium" | "Low" | undefined;
3
3
  export interface ICookieOptions {
4
- maxAge?: number;
5
- path?: string;
6
- domain?: string;
7
- secure?: boolean;
8
- expires?: Date | string | number;
9
- sameSite?: SameSiteType;
10
- httpOnly?: boolean;
11
- signed?: boolean;
12
- secret?: string;
4
+ maxAge?: number | undefined;
5
+ path?: string | undefined;
6
+ domain?: string | undefined;
7
+ secure?: boolean | undefined;
8
+ expires?: Date | string | number | undefined;
9
+ sameSite?: SameSiteType | undefined;
10
+ httpOnly?: boolean | undefined;
11
+ signed?: boolean | undefined;
12
+ secret?: string | undefined;
13
13
  priority?: PriorityType;
14
14
  }
15
15
  export interface IWsCookie {
@@ -26,16 +26,16 @@ export interface ICookie {
26
26
  name: string;
27
27
  value: unknown;
28
28
  options: ICookieOptions;
29
- signed?: boolean;
30
- originalMaxAge?: number;
31
- expires?: Date;
32
- maxAge?: number;
33
- path?: string;
29
+ signed?: boolean | undefined;
30
+ originalMaxAge?: number | undefined;
31
+ expires?: Date | undefined;
32
+ maxAge?: number | undefined;
33
+ path?: string | undefined;
34
34
  domain?: string | undefined;
35
- httpOnly?: boolean;
36
- secure?: boolean;
37
- sameSite?: SameSiteType;
38
- priority?: string;
35
+ httpOnly?: boolean | undefined;
36
+ secure?: boolean | undefined;
37
+ sameSite?: SameSiteType | undefined;
38
+ priority?: string | undefined;
39
39
  setValue(value: unknown): unknown;
40
40
  toString(): string;
41
41
  serialize(): string;
@@ -1,9 +1,15 @@
1
+ import type { IServerKernel, MaybePromise } from "nodefony";
1
2
  import type { ServerType } from "./IContext.js";
2
- export interface IHttpKernel {
3
+ export interface IHttpKernel extends IServerKernel {
3
4
  domain: string;
4
- httpPort?: number;
5
- httpsPort?: number;
6
- handle(request: unknown, response: unknown, type: ServerType): Promise<unknown>;
5
+ httpPort?: number | undefined;
6
+ httpsPort?: number | undefined;
7
+ /**
8
+ * Pipeline public d'une requête (appelé par les serveurs HTTP/HTTPS).
9
+ *
10
+ * @returns le contexte servi, ou sa promesse dès qu'une étape attend.
11
+ */
12
+ handle(request: unknown, response: unknown, type: ServerType): MaybePromise<unknown>;
7
13
  handleFrontController(context: object, checkFirewall?: boolean): Promise<unknown>;
8
14
  onError(error: unknown, context?: object, extraHeaders?: object): Promise<object>;
9
15
  isValidDomain(context: object): boolean;
@@ -31,7 +31,7 @@ export interface IHttpResponse extends IResponse {
31
31
  setCookie(cookie: ICookie): void;
32
32
  }
33
33
  export interface IWebsocketResponse extends IResponse {
34
- connection: unknown | null;
34
+ connection: unknown;
35
35
  webSocketVersion?: number;
36
36
  setConnection(connection: unknown): unknown;
37
37
  broadcast(data: Buffer | string): void;
@@ -0,0 +1,87 @@
1
+ import type { MaybePromise } from "nodefony";
2
+ import type { ContextType } from "../service/http-kernel.js";
3
+ /**
4
+ * Ce que le pipeline HTTP/WS lit d'une route résolue — et rien de plus.
5
+ *
6
+ * Le routage vit dans `@nodefony/framework`, qui dépend de `@nodefony/http` :
7
+ * `http` ne peut donc pas nommer ses classes sans recréer un cycle. C'est le
8
+ * LECTEUR qui définit le contrat ; `Route` (framework) le satisfait par
9
+ * structure, et le typecheck de `framework` garantit l'alignement.
10
+ */
11
+ export interface IResolvedRoute {
12
+ /** Nom de la route (journalisation, diagnostic). */
13
+ readonly name: string;
14
+ /** Corps laissé en flux (`@Body({ stream: true })`) : le parse est sauté. */
15
+ readonly bodyStream?: boolean | undefined;
16
+ }
17
+ /**
18
+ * Ce que le pipeline HTTP/WS attend du résolveur d'une requête, posé sur
19
+ * `context.resolver` par le routeur.
20
+ *
21
+ * Implémenté par `Resolver` (`@nodefony/framework`), qui l'étend de ses propres
22
+ * membres — un code de `framework` qui a besoin de la classe entière la tient
23
+ * de son routeur, pas de ce contrat.
24
+ */
25
+ export interface IRouteResolver {
26
+ /** Route matchée, `null` tant qu'aucune ne l'est. */
27
+ readonly route: IResolvedRoute | null;
28
+ /** `true` si une route a matché la requête. */
29
+ resolve: boolean;
30
+ /** Erreur levée pendant la résolution, relue par le pipeline. */
31
+ exception?: Error | null | undefined;
32
+ /** Classe du controller résolu — seul son nom est lu (erreurs, journal). */
33
+ readonly controller?: {
34
+ readonly name: string;
35
+ } | null | undefined;
36
+ /** Nom de l'action résolue. */
37
+ actionName?: string | undefined;
38
+ /**
39
+ * Rejoue le match d'une route sur un contexte (WS : handshake puis frames).
40
+ *
41
+ * @param route - la route à matcher, lue sur {@link IRouteResolver.route}
42
+ * @param context - le contexte courant
43
+ * @param cleanPath - chemin imposé (pont WS-RPC), sinon l'URL du contexte
44
+ */
45
+ match(route: IResolvedRoute, context: ContextType, cleanPath?: string): unknown;
46
+ /**
47
+ * Instancie le controller résolu — le pipeline ne fait que le transmettre.
48
+ *
49
+ * @param context - le contexte de la requête
50
+ * @returns l'instance du controller — directement quand elle existe déjà ou
51
+ * se construit sans rien attendre, sinon la promesse de sa création
52
+ */
53
+ newController(context?: ContextType): MaybePromise<object>;
54
+ /**
55
+ * Exécute l'action résolue, puis rend sa valeur sur le transport.
56
+ *
57
+ * @param data - arguments supplémentaires de l'action
58
+ * @param reload - rejouer depuis l'instance en cache (forward)
59
+ * @returns le résultat du rendu — synchrone quand ni l'action ni le rendu
60
+ * n'attendent, sinon sa promesse
61
+ */
62
+ callController(data?: unknown[], reload?: boolean): MaybePromise<unknown>;
63
+ }
64
+ /**
65
+ * Ce que le pipeline HTTP/WS attend du service `router` — implémenté par
66
+ * `Router` (`@nodefony/framework`).
67
+ */
68
+ export interface IRequestRouter {
69
+ /**
70
+ * Résout la route d'un contexte.
71
+ *
72
+ * @param context - le contexte HTTP/WS courant
73
+ * @param cleanPathOverride - chemin porté par un message (pont WS-RPC)
74
+ * @param methodOverride - méthode logique exigée en plus du transport WS
75
+ * @returns le résolveur (`resolve === true` si une route a matché)
76
+ */
77
+ resolve(context: ContextType, cleanPathOverride?: string, methodOverride?: string): IRouteResolver;
78
+ /**
79
+ * Instance partagée d'un controller `scope: "singleton"`, créée une seule fois.
80
+ *
81
+ * @param ctor - la classe du controller (clé du cache)
82
+ * @param create - fabrique exécutée au premier appel
83
+ * @returns l'instance partagée une fois créée ; la promesse de sa création
84
+ * tant qu'elle est en cours
85
+ */
86
+ getSingletonController<T extends object>(ctor: new (...args: never[]) => T, create: () => MaybePromise<T>): MaybePromise<T>;
87
+ }
@@ -0,0 +1,45 @@
1
+ import type { ContextType } from "../service/http-kernel.js";
2
+ /**
3
+ * Ce que le pipeline HTTP/WS — et le résolveur de framework — lit de la zone de sécurité posée sur
4
+ * `context.security` — et rien de plus.
5
+ *
6
+ * Le pare-feu vit dans `@nodefony/security`, qui dépend de `@nodefony/http` :
7
+ * `http` ne peut donc pas nommer ses classes sans recréer un cycle. C'est le
8
+ * LECTEUR qui définit le contrat ; `ISecuredArea` (security) l'étend, et le
9
+ * typecheck de `security` garantit l'alignement.
10
+ */
11
+ export interface ISecurityZone {
12
+ /** Nom de la zone (journalisation, profiler). */
13
+ readonly name: string;
14
+ /** `true` si la zone exige une authentification. */
15
+ readonly security: boolean;
16
+ /** Zone sans session : aucune session n'est ouverte ni lue. */
17
+ readonly stateless: boolean;
18
+ /** Stratégie des authenticators : le premier qui répond, ou tous. */
19
+ readonly mode: "first" | "all";
20
+ /** Noms des authenticators candidats, dans l'ordre. */
21
+ readonly authenticators: readonly string[];
22
+ /**
23
+ * Rôles exigés par la zone, en OU (un seul suffit) ; `null` = aucune exigence
24
+ * de rôle. Lu par le résolveur de `@nodefony/framework` pour l'autorisation.
25
+ */
26
+ readonly roles: readonly string[] | null;
27
+ }
28
+ /**
29
+ * Ce que le noyau HTTP appelle du pare-feu, service `firewall` du conteneur.
30
+ *
31
+ * Implémenté par `Firewall` (`@nodefony/security`), dont `IFirewall` étend ce
32
+ * contrat de ses propres membres (CSP, authenticators, description).
33
+ */
34
+ export interface IFirewallGate {
35
+ /** Match rapide de zone : pose `context.security`, `true` si une zone capture. */
36
+ isSecure(context: ContextType): boolean;
37
+ /** Authentifie la requête d'une zone protégée ; rejette si elle est refusée. */
38
+ handleSecurity(context: ContextType): Promise<ContextType>;
39
+ /** Refuse une requête qui ne présente pas le jeton CSRF attendu. */
40
+ enforceCsrf(context: ContextType): void;
41
+ /** Répond au preflight CORS ; `204` quand la réponse est déjà envoyée. */
42
+ handleCors(context: ContextType): number | undefined;
43
+ /** Pose les en-têtes de sécurité (CSP, HSTS…) de la réponse. */
44
+ applySecurityHeaders(context: ContextType): void;
45
+ }
@@ -13,7 +13,7 @@ export type MetaBagType = Record<string, unknown>;
13
13
  * - `readOnly` : la session est lue/reprise mais **jamais persistée** (0 write storage).
14
14
  */
15
15
  export interface SessionIntent {
16
- readOnly?: boolean;
16
+ readOnly?: boolean | undefined;
17
17
  }
18
18
  /**
19
19
  * Données de session **sérialisées** échangées avec un {@link ISessionStorage}
@@ -25,8 +25,8 @@ export interface ISerializedSession {
25
25
  metaBag: Record<string, unknown>;
26
26
  flashBag: Record<string, unknown>;
27
27
  user: string;
28
- createdAt?: Date;
29
- updatedAt?: Date;
28
+ createdAt?: Date | undefined;
29
+ updatedAt?: Date | undefined;
30
30
  }
31
31
  /**
32
32
  * Projection **redactée** d'une session pour l'ADMINISTRATION (data plane Studio,
@@ -100,7 +100,7 @@ export interface ISessionListFilter {
100
100
  /** Restreint aux sessions d'un utilisateur (pour « déconnecter partout »). */
101
101
  user?: string;
102
102
  /** Slot multi-tenant — non scopé aujourd'hui (réserve coût-0). */
103
- tenantId?: string | null;
103
+ tenantId?: string | null | undefined;
104
104
  }
105
105
  /**
106
106
  * Requête d'énumération **paginée** des sessions — le {@link IPageQuery} standard
@@ -253,10 +253,10 @@ export interface ISession {
253
253
  cookieSession: ICookie | null | undefined;
254
254
  flashBag: FlashBagType;
255
255
  strategy: SessionStrategyType;
256
- created?: Date;
257
- updated?: Date;
258
- user?: string;
259
- lifetime?: number;
256
+ created?: Date | undefined;
257
+ updated?: Date | undefined;
258
+ user?: string | undefined;
259
+ lifetime?: number | undefined;
260
260
  storage: ISessionStorage;
261
261
  start(context: unknown): Promise<ISession>;
262
262
  save(user?: string): Promise<ISession>;
@@ -28,21 +28,21 @@ export interface IParsedUploadFile {
28
28
  */
29
29
  export interface IUploadOptions {
30
30
  /** Répertoire de dépôt des fichiers temporaires. */
31
- uploadDir?: string;
31
+ uploadDir?: string | undefined;
32
32
  /** Taille max d'UN fichier (octets) — `limits.fileSize` busboy. */
33
- maxFileSize?: number;
33
+ maxFileSize?: number | undefined;
34
34
  /** Taille max CUMULÉE des fichiers d'une requête (octets) — appliquée par Nodefony. */
35
- maxTotalFileSize?: number;
35
+ maxTotalFileSize?: number | undefined;
36
36
  /** Nombre max de fichiers — `limits.files` busboy. */
37
- maxFiles?: number;
37
+ maxFiles?: number | undefined;
38
38
  /** Nombre max de champs texte — `limits.fields` busboy. */
39
- maxFields?: number;
39
+ maxFields?: number | undefined;
40
40
  /** Taille max d'un champ texte (octets) — `limits.fieldSize` busboy. */
41
- maxFieldsSize?: number;
41
+ maxFieldsSize?: number | undefined;
42
42
  /** Hash calculé pendant le stream (défaut `false` = aucun). */
43
- hashAlgorithm?: false | "sha1" | "md5" | "sha256";
43
+ hashAlgorithm?: false | "sha1" | "md5" | "sha256" | undefined;
44
44
  /** Encodage par défaut des parts texte. */
45
- encoding?: string;
45
+ encoding?: string | undefined;
46
46
  }
47
47
  export interface IUploadedFile {
48
48
  filename: string;
@@ -60,7 +60,7 @@ export interface IUploadedFile {
60
60
  getPrettySize(): string;
61
61
  }
62
62
  export interface IUploadService {
63
- path?: string | unknown;
63
+ path?: unknown;
64
64
  /** Construit un `UploadedFile` en async (stat non bloquant). */
65
65
  createUploadFile(file: unknown, name: string): Promise<IUploadedFile>;
66
66
  }
@@ -5,3 +5,5 @@ export type { IRequest, IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType,
5
5
  export type { IResponse, IHttpResponse, IWebsocketResponse } from "./IResponse.js";
6
6
  export type { IContext, IHttpContext, IWebsocketContext, ServerType, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
7
7
  export type { IHttpKernel } from "./IHttpKernel.js";
8
+ export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./IRouting.js";
9
+ export type { ISecurityZone, IFirewallGate } from "./ISecurity.js";