@nodefony/http 10.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +77 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
  6. package/dist/index.js +108 -0
  7. package/dist/nodefony/command/assetsPublishCommand.js +102 -0
  8. package/dist/nodefony/command/certificatesCommand.js +47 -0
  9. package/dist/nodefony/command/networkCommand.js +27 -0
  10. package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
  11. package/dist/nodefony/config/config.js +335 -0
  12. package/dist/nodefony/config/defineModuleConfig.js +93 -0
  13. package/dist/nodefony/interfaces/IContext.js +1 -0
  14. package/dist/nodefony/interfaces/ICookie.js +1 -0
  15. package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
  16. package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
  17. package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
  18. package/dist/nodefony/interfaces/IRequest.js +1 -0
  19. package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
  20. package/dist/nodefony/interfaces/IResponse.js +1 -0
  21. package/dist/nodefony/interfaces/ISession.js +1 -0
  22. package/dist/nodefony/interfaces/IUpload.js +1 -0
  23. package/dist/nodefony/interfaces/index.js +1 -0
  24. package/dist/nodefony/service/HttpAdminApi.js +376 -0
  25. package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
  26. package/dist/nodefony/service/audit-logger.js +159 -0
  27. package/dist/nodefony/service/certificates.js +545 -0
  28. package/dist/nodefony/service/error-renderer.js +320 -0
  29. package/dist/nodefony/service/http-kernel.js +948 -0
  30. package/dist/nodefony/service/pretty-request-logger.js +72 -0
  31. package/dist/nodefony/service/request-logger.js +54 -0
  32. package/dist/nodefony/service/servers/clientError.js +20 -0
  33. package/dist/nodefony/service/servers/server-http.js +135 -0
  34. package/dist/nodefony/service/servers/server-https.js +204 -0
  35. package/dist/nodefony/service/servers/server-static.js +192 -0
  36. package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
  37. package/dist/nodefony/service/servers/server-websocket.js +104 -0
  38. package/dist/nodefony/service/servers/serverShutdown.js +31 -0
  39. package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
  40. package/dist/nodefony/service/sessions/sessions-service.js +580 -0
  41. package/dist/nodefony/service/trace.js +72 -0
  42. package/dist/nodefony/service/upload/upload-service.js +171 -0
  43. package/dist/nodefony/src/assets/collectAssets.js +34 -0
  44. package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
  45. package/dist/nodefony/src/context/Context.js +415 -0
  46. package/dist/nodefony/src/context/domainMatcher.js +88 -0
  47. package/dist/nodefony/src/context/forwarded.js +185 -0
  48. package/dist/nodefony/src/context/http/HttpContext.js +309 -0
  49. package/dist/nodefony/src/context/http/Request.js +543 -0
  50. package/dist/nodefony/src/context/http/Response.js +368 -0
  51. package/dist/nodefony/src/context/http/parser.js +188 -0
  52. package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
  53. package/dist/nodefony/src/context/http2/Request.js +29 -0
  54. package/dist/nodefony/src/context/http2/Response.js +97 -0
  55. package/dist/nodefony/src/context/metaData.js +47 -0
  56. package/dist/nodefony/src/context/requestId.js +41 -0
  57. package/dist/nodefony/src/context/trustProxy.js +167 -0
  58. package/dist/nodefony/src/context/websocket/Response.js +181 -0
  59. package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
  60. package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
  61. package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
  62. package/dist/nodefony/src/cookies/cookie.js +258 -0
  63. package/dist/nodefony/src/errors/httpError.js +69 -0
  64. package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
  65. package/dist/nodefony/src/profiler/Profiler.js +139 -0
  66. package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
  67. package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
  68. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
  69. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
  70. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
  71. package/dist/nodefony/src/servers/portBinder.js +114 -0
  72. package/dist/nodefony/src/session/session.js +390 -0
  73. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
  74. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
  75. package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
  76. package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
  77. package/dist/types/index.d.ts +83 -0
  78. package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
  79. package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
  80. package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
  81. package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
  82. package/dist/types/nodefony/config/config.d.ts +197 -0
  83. package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
  84. package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
  85. package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
  86. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
  87. package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
  88. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
  89. package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
  90. package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
  91. package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
  92. package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
  93. package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
  94. package/dist/types/nodefony/interfaces/index.d.ts +7 -0
  95. package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
  96. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
  97. package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
  98. package/dist/types/nodefony/service/certificates.d.ts +246 -0
  99. package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
  100. package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
  101. package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
  102. package/dist/types/nodefony/service/request-logger.d.ts +18 -0
  103. package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
  104. package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
  105. package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
  106. package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
  107. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
  108. package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
  109. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
  110. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
  111. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
  112. package/dist/types/nodefony/service/trace.d.ts +39 -0
  113. package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
  114. package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
  115. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
  116. package/dist/types/nodefony/src/context/Context.d.ts +195 -0
  117. package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
  118. package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
  119. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
  120. package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
  121. package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
  122. package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
  123. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
  124. package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
  125. package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
  126. package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
  127. package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
  128. package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
  129. package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
  130. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
  131. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
  132. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
  133. package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
  134. package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
  135. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
  136. package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
  137. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
  138. package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
  139. package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
  140. package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
  141. package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
  142. package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
  143. package/dist/types/nodefony/src/session/session.d.ts +171 -0
  144. package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
  145. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
  146. package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
  147. package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
  148. package/docs/cookies.md +365 -0
  149. package/docs/index.md +163 -0
  150. package/docs/observabilite.md +460 -0
  151. package/docs/rate-limit.md +372 -0
  152. package/docs/servers.md +935 -0
  153. package/docs/session.md +768 -0
  154. package/docs/upload.md +460 -0
  155. package/package.json +101 -0
@@ -0,0 +1,27 @@
1
+ import { HttpTerminator } from "http-terminator";
2
+ import type { Server as HttpServer } from "node:http";
3
+ import type { Server as HttpsServer } from "node:https";
4
+ import type { Http2SecureServer } from "node:http2";
5
+ /**
6
+ * Fabrique le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
7
+ *
8
+ * Au `terminate()` (shutdown SIGTERM/docker stop) : les requêtes in-flight se
9
+ * terminent (header `connection: close` injecté sur les réponses en cours), les
10
+ * sockets idle sont fermées immédiatement, et tout ce qui reste après
11
+ * `shutdownTimeout` ms est détruit de force. Le terminator appelle lui-même
12
+ * `server.close()` — ne pas le rappeler derrière.
13
+ *
14
+ * ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
15
+ * SANS frame Close par le terminator → les serveurs WS doivent fermer leurs
16
+ * clients (close 1001) AVANT ce drain. Garanti par l'ordre des listeners
17
+ * `onTerminate` : WS/WSS s'attachent en `prependOnceListener`, les serveurs
18
+ * HTTP en `once`.
19
+ *
20
+ * @param server - serveur Node à drainer (créé, pas forcément listening)
21
+ * @param shutdownTimeout - délai de drain en ms avant destruction forcée
22
+ * @returns le terminator à invoquer au shutdown
23
+ */
24
+ export declare function createDrainTerminator(server: HttpServer | HttpsServer | Http2SecureServer, shutdownTimeout?: number): HttpTerminator;
25
+ /** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
26
+ export declare const DEFAULT_SHUTDOWN_TIMEOUT = 5000;
27
+ export type { HttpTerminator };
@@ -0,0 +1,46 @@
1
+ import Ws, { WebSocketServer } from "ws";
2
+ /**
3
+ * Options keep-alive d'un serveur WebSocket.
4
+ *
5
+ * Héritées de l'ancienne lib `websocket` (theturtle32) qui les gérait nativement.
6
+ * `ws@8` n'a AUCUN keep-alive natif (il n'expose que `ping()`/`pong()` manuels) :
7
+ * on réimplémente donc la sémantique au-dessus de `ws` pour que ces knobs — déjà
8
+ * déclarés dans le schéma Zod — ne soient plus une config qui ment.
9
+ */
10
+ export interface IWsHeartbeatOptions {
11
+ /** Intervalle (ms) entre deux pings. `0` ou absent → keep-alive désactivé. */
12
+ keepaliveInterval?: number;
13
+ /** Délai (ms) accordé pour recevoir le `pong` avant de couper la socket. */
14
+ keepaliveGracePeriod?: number;
15
+ }
16
+ /**
17
+ * Arme le suivi keep-alive d'UNE connexion : initialise l'horodatage de vie et
18
+ * attache l'unique listener `pong` qui le rafraîchit.
19
+ *
20
+ * Le listener `pong` vit et meurt avec la socket (l'émetteur EST la socket `ws`,
21
+ * détruite au `close`/`terminate`) → aucun `removeListener` explicite à prévoir.
22
+ * Un seul listener par connexion, c'est le pair-event obligatoire du ping.
23
+ *
24
+ * @param ws - la socket fraîchement connectée
25
+ */
26
+ export declare const trackPong: (ws: Ws) => void;
27
+ /**
28
+ * Démarre le heartbeat keep-alive du serveur : détecte les connexions zombies
29
+ * (half-open — TCP encore ouvert mais le pair a disparu sans frame Close) et les
30
+ * `terminate()` pour libérer slot mémoire + descripteur de fichier.
31
+ *
32
+ * Sémantique (fidèle à l'ancienne lib `websocket`) : un ping est émis tous les
33
+ * `keepaliveInterval` ms ; si aucun `pong` n'arrive dans les `keepaliveGracePeriod`
34
+ * ms qui suivent ce ping, la socket est détruite. Temps de réclamation borné par
35
+ * `keepaliveInterval + keepaliveGracePeriod` (+ une granularité de tick).
36
+ *
37
+ * PERF (hot path WS) : **UN seul `setInterval` par serveur** — jamais un timer par
38
+ * connexion. Chaque tick ne lit/écrit que des `number` sur la socket → 0 allocation
39
+ * sur le chemin nominal. `unref()` pour ne pas retenir le process à l'arrêt. Le timer
40
+ * DOIT être `clearInterval` au shutdown (cf appelant).
41
+ *
42
+ * @param server - le `WebSocketServer` dont on surveille `clients`
43
+ * @param options - knobs keep-alive (depuis la config Zod du module)
44
+ * @returns le timer à nettoyer au shutdown, ou `null` si le keep-alive est désactivé
45
+ */
46
+ export declare const startHeartbeat: (server: WebSocketServer, options: IWsHeartbeatOptions) => ReturnType<typeof setInterval> | null;
@@ -0,0 +1,218 @@
1
+ import { Service, Module } from "nodefony";
2
+ import type { IPage } from "nodefony";
3
+ import type { ISessionStorage, ISessionSummary, ISessionRecord, ISessionListQuery } from "../../interfaces/ISession.js";
4
+ import HttpKernel, { ContextType } from "../http-kernel.js";
5
+ import Session, { OptionsSessionType } from "../../src/session/session.js";
6
+ import Certificate from "../../service/certificates.js";
7
+ import type { ISessionCounts } from "../../src/session/storage/sessionFilters.js";
8
+ export type sessionStrategyType = "none" | "migrate" | "invalidate";
9
+ export type FlashBagSessionType = Record<string, unknown>;
10
+ export type MetaBagSessionType = Record<string, unknown>;
11
+ /** Constructeur d'un storage de session (enregistré dans le registre). */
12
+ export type SessionStorageCtor = new (manager: SessionsService) => ISessionStorage;
13
+ /**
14
+ * Dérive le pseudonyme public d'une session — `HMAC-SHA256(secret, id)` tronqué,
15
+ * préfixé `sess_`. **Non réversible** : exposer ce `ref` ne révèle pas l'id de
16
+ * session (= le jeton du cookie). Fonction **pure** (testable sans instancier le
17
+ * service ni démarrer de serveur).
18
+ */
19
+ export declare function computeSessionRef(secret: Buffer, id: string): string;
20
+ /**
21
+ * Projette une entrée brute {@link ISessionRecord} en {@link ISessionSummary}
22
+ * **redacté par construction** (allowlist) : jamais `Attributes`, jamais
23
+ * `flashBag`, jamais l'id brut — seulement le `ref` + des champs sûrs
24
+ * (`user`/`ip`/`ua`/dates). Fonction **pure** (cœur de la garantie anti-fuite,
25
+ * testée isolément).
26
+ */
27
+ export declare function toSessionSummary(rec: ISessionRecord, ref: string, currentRef?: string | null): ISessionSummary;
28
+ declare class SessionsService extends Service {
29
+ httpKernel: HttpKernel;
30
+ /**
31
+ * Registre des storages de session — inversion de contrôle.
32
+ *
33
+ * Chaque module qui fournit un storage l'enregistre à son chargement
34
+ * (`SessionsService.registerStorage("drizzle", DrizzleStorage)`). Ainsi
35
+ * `@nodefony/http` **ne dépend d'aucun ORM** : pas d'import croisé, pas de
36
+ * cycle, et ajouter un driver ne touche plus ce fichier. Le handler de la
37
+ * config (`session.store`) sélectionne le storage par son nom.
38
+ */
39
+ private static readonly storages;
40
+ /**
41
+ * Enregistre un storage de session sous un nom de store (insensible à la
42
+ * casse) et émet l'événement kernel `onRegisterSessionStorage` (observabilité
43
+ * Studio / extension). Le kernel peut être absent au tout premier chargement
44
+ * (registration statique) → fire gardé.
45
+ */
46
+ static registerStorage(name: string, ctor: SessionStorageCtor): void;
47
+ /** Storage enregistré pour un store, ou `undefined`. */
48
+ static getStorage(name: string): SessionStorageCtor | undefined;
49
+ /** Noms des handlers de session enregistrés. */
50
+ static storageHandlers(): string[];
51
+ sessionStrategy: sessionStrategyType;
52
+ storage: ISessionStorage | null;
53
+ module: Module;
54
+ defaultSessionName: string;
55
+ secret?: Buffer;
56
+ iv?: Buffer;
57
+ certificates: Certificate | null;
58
+ private gcScheduler;
59
+ constructor(module: Module, httpKernel: HttpKernel);
60
+ init(): Promise<this>;
61
+ initializeStorage(): ISessionStorage | null;
62
+ createSecret(): Buffer;
63
+ createIv(): Buffer;
64
+ start(context: ContextType, readOnly?: boolean): Promise<Session | null>;
65
+ saveSession(context: ContextType): Promise<Session | null>;
66
+ createSession(name: string, options?: OptionsSessionType): Session;
67
+ setSessionStrategy(strategy: sessionStrategyType): void;
68
+ /**
69
+ * Une passe de purge du store (`storage.gc(idle, absolute)`) — point d'entrée
70
+ * public d'un ordonnanceur : le {@link GcScheduler} l'appelle, mais un futur
71
+ * worker cron (`session:gc` / k8s CronJob) peut l'appeler à sa place (poser
72
+ * alors `gcIntervalS:0`). L'anti-empilement et la capture d'erreur vivent dans
73
+ * le GcScheduler (via `onError`) — ici, la passe métier nue.
74
+ */
75
+ runGc(): Promise<void>;
76
+ /**
77
+ * `true` si le backend de session courant sait s'énumérer **par pages**
78
+ * (`listPage`). Un store KV/edge sans scan retourne `false` → l'endpoint admin
79
+ * répond **501** (refus honnête, jamais une liste vide trompeuse).
80
+ *
81
+ * C'est bien `listPage` — et non `listAll` — qui fait foi : toute la surface
82
+ * d'administration (listing, révocation par référence, « déconnecter partout »)
83
+ * est bâtie sur la pagination, pour que son coût mémoire soit **indépendant du
84
+ * nombre de sessions**. Un store qui ne saurait que tout charger serait une
85
+ * régression déguisée en capacité.
86
+ */
87
+ supportsEnumeration(): boolean;
88
+ /**
89
+ * Champs de tri que le backend de session **actuellement configuré** sait
90
+ * honorer, en vocabulaire public.
91
+ *
92
+ * La capacité se CONSTATE au runtime : la même application rend `["updatedAt",
93
+ * "id"]` sur SQLite et `[]` sur Redis (`SCAN` ne donne aucun ordre global).
94
+ * Le data plane transmet cette liste à `parsePageQuery`, qui **refuse** (400)
95
+ * un `order` qu'aucun store ne pourrait honorer — au lieu de rendre une page
96
+ * non triée en laissant croire le contraire.
97
+ *
98
+ * @returns les champs triables, liste vide si le backend ne trie pas.
99
+ */
100
+ sortableFields(): readonly string[];
101
+ /**
102
+ * Dérive le pseudonyme public d'une session — `HMAC-SHA256(secret, id)` tronqué,
103
+ * préfixé `sess_`. **Non réversible** : exposer ce `ref` ne révèle pas l'id de
104
+ * session (= le jeton du cookie). Le secret HMAC = celui de la couche session
105
+ * (`this.secret`, dérivé de la clé du certificat au boot), jamais sérialisé.
106
+ *
107
+ * @throws Error si le secret n'est pas initialisé (service pas démarré) — capté
108
+ * en 503 côté endpoint plutôt que d'émettre un `ref` faible.
109
+ */
110
+ sessionRef(id: string): string;
111
+ /**
112
+ * Énumère les sessions persistées en {@link ISessionSummary} **redactés** (jamais
113
+ * d'id brut ni d'`Attributes`), des plus récentes aux plus anciennes. Le filtre
114
+ * `user` est poussé au store (WHERE SQL) PUIS ré-appliqué ici (défense si un
115
+ * store l'ignore). Pré-condition : {@link supportsEnumeration} (sinon throw).
116
+ */
117
+ /**
118
+ * Référence publique de la session qui porte la **requête en cours**, lue dans
119
+ * le contexte de l'ALS — ou `null` quand la requête n'en porte aucune (CLI,
120
+ * appel interne, session jamais démarrée).
121
+ *
122
+ * Point UNIQUE de cette dérivation : marquer « cet appareil » se fait ici, et
123
+ * les deux énumérations (admin et self-service) s'en servent — sans quoi
124
+ * chaque appelant recalculerait la règle et l'une des copies dériverait.
125
+ * Un HMAC par page d'administration, jamais sur le chemin nominal.
126
+ */
127
+ currentSessionRef(): string | null;
128
+ listSessionsPage(query: ISessionListQuery): Promise<IPage<ISessionSummary>>;
129
+ /**
130
+ * Compte les sessions sans les énumérer (KPI de la console). Renvoie **`-1`**
131
+ * si le backend ne sait pas compter à coût raisonnable (Redis) — l'appelant
132
+ * affiche alors l'inconnu plutôt qu'un chiffre inventé.
133
+ */
134
+ countSessions(query?: Partial<ISessionListQuery>): Promise<number>;
135
+ /**
136
+ * Compte les utilisateurs **distincts** ayant une session — le second nombre
137
+ * de la console, celui que `countSessions` ne dit pas (« 400 sessions » n'est
138
+ * pas « 400 personnes »).
139
+ *
140
+ * @returns le nombre d'utilisateurs distincts, ou **`-1`** si le backend ne
141
+ * sait pas agréger (Redis).
142
+ */
143
+ countDistinctUsers(query?: Partial<ISessionListQuery>): Promise<number>;
144
+ /**
145
+ * Les compteurs de tête de la console — posés sur la collection ENTIÈRE, pas
146
+ * sur la page affichée.
147
+ *
148
+ * C'est la correction d'un mensonge d'affichage : les cartes étaient calculées
149
+ * dans le navigateur à partir des sessions chargées, donc bornées par la
150
+ * fenêtre du tableau. Elles décrivaient l'échantillon visible en ayant l'air
151
+ * de décrire le parc.
152
+ *
153
+ * Chaque compteur vaut `null` quand le backend ne sait pas répondre — la
154
+ * console affiche alors l'inconnu (« — ») plutôt qu'un zéro qui se lirait
155
+ * comme une absence.
156
+ *
157
+ * @param query - filtres à appliquer avant comptage (sans fenêtre).
158
+ */
159
+ countSessionFacets(query?: Partial<ISessionListQuery>): Promise<ISessionCounts>;
160
+ /**
161
+ * Storage courant, garanti énumérable — factorise la pré-condition de toute la
162
+ * surface admin (une seule formulation de l'erreur, un seul point à faire
163
+ * évoluer).
164
+ *
165
+ * @throws Error si le storage est absent ou n'implémente pas `listPage`.
166
+ */
167
+ private enumerable;
168
+ /**
169
+ * Parcourt les sessions **page par page**, en ne gardant JAMAIS plus d'une page
170
+ * en mémoire — le cœur de la gouvernance bornée : retrouver une session par sa
171
+ * référence publique impose de recalculer un HMAC sur chaque id, mais pas de
172
+ * charger le parc entier pour le faire.
173
+ *
174
+ * Gère les deux modes du contrat de façon transparente pour l'appelant :
175
+ * curseur (`nextCursor` du store) ou offset (avance de `SCAN_PAGE`). Le visiteur
176
+ * renvoie `true` pour **arrêter** le parcours (court-circuit dès le match).
177
+ *
178
+ * @param filter - restriction poussée au store (ex. `user`).
179
+ * @param visit - appelé pour chaque record ; `true` = stop.
180
+ * @returns `true` si le parcours a été arrêté par le visiteur.
181
+ */
182
+ private eachSessionRecord;
183
+ /**
184
+ * Révoque une session par son `ref` public : re-scanne, recalcule le HMAC de
185
+ * chaque id pour retrouver l'id réel (l'id brut ne quitte jamais le process),
186
+ * puis `destroy()`. `ref` étant public (pas un secret), la comparaison directe
187
+ * est sûre. Idempotent — `false` si aucun `ref` ne correspond.
188
+ */
189
+ destroyByRef(ref: string, actor?: string | null): Promise<boolean>;
190
+ /**
191
+ * « Déconnexion partout » : détruit TOUTES les sessions d'un utilisateur (scan
192
+ * O(N) — pas d'index inverse, acceptable en admin). Renvoie le nombre détruit.
193
+ */
194
+ destroyByUser(identifier: string, actor?: string | null): Promise<number>;
195
+ /**
196
+ * Énumère **une page** des sessions APPARTENANT à `identifier`
197
+ * ({@link ISessionSummary} redactés). Délègue à {@link listSessionsPage} avec le
198
+ * filtre `user` (poussé au store PUIS ré-appliqué — défense en profondeur). Un
199
+ * `identifier` vide renvoie une page vide (jamais les sessions anonymes
200
+ * `user===""`, qui appartiennent à tout le monde et donc à personne).
201
+ */
202
+ listOwnSessionsPage(identifier: string, query: ISessionListQuery): Promise<IPage<ISessionSummary>>;
203
+ /**
204
+ * Révoque UNE session **possédée par `identifier`**, désignée par son `ref`
205
+ * public. Contrairement à {@link destroyByRef} (admin, scan GLOBAL), le scan est
206
+ * RESTREINT aux sessions de `identifier` (+ re-check d'appartenance) : un `ref`
207
+ * qui ne lui appartient pas est introuvable → `false`, ce qui ferme l'IDOR.
208
+ * Idempotent. Audité (`self: true`, acteur = le propriétaire).
209
+ */
210
+ destroyOwnByRef(identifier: string, ref: string, actor?: string | null): Promise<boolean>;
211
+ /**
212
+ * Émet un événement dans le journal d'audit de `@nodefony/security` s'il est
213
+ * monté (résolu par nom au runtime) — **no-op** si security est absent/désactivé.
214
+ * L'action de révocation reste tracée par `this.log()` dans tous les cas.
215
+ */
216
+ private emitAudit;
217
+ }
218
+ export default SessionsService;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * W3C Trace Context — parse + generate `traceparent` headers (P2.7).
3
+ *
4
+ * Spec: https://www.w3.org/TR/trace-context/
5
+ * Format: `<version>-<traceId>-<parentId>-<flags>`
6
+ * - version: 2 hex chars (currently always `00`; `ff` is reserved/invalid)
7
+ * - traceId: 32 hex chars (16 bytes), non-zero
8
+ * - parentId / spanId: 16 hex chars (8 bytes), non-zero
9
+ * - flags: 2 hex chars (`01` = sampled)
10
+ *
11
+ * Behaviour at the request boundary:
12
+ * - Valid incoming traceparent → keep `version`/`traceId`/`flags`, generate
13
+ * a fresh `parentId` (we are a child span in the existing trace).
14
+ * - Missing or invalid → mint a brand-new traceparent (version `00`,
15
+ * `flags=01` sampled by default).
16
+ *
17
+ * The result is propagated through {@link RequestContext} and echoed on the
18
+ * HTTP response so downstream services and clients can stitch the trace.
19
+ */
20
+ export interface ParsedTraceparent {
21
+ version: string;
22
+ traceId: string;
23
+ parentId: string;
24
+ flags: string;
25
+ }
26
+ /**
27
+ * Parse a `traceparent` header value. Returns `null` when the header is
28
+ * missing, malformed, or carries an all-zero traceId/spanId (per W3C the
29
+ * recipient MUST NOT propagate such values).
30
+ */
31
+ export declare function parseTraceparent(header: string | undefined | null): ParsedTraceparent | null;
32
+ /**
33
+ * Resolve the traceparent to attach to a new request. Honors an incoming
34
+ * valid header, generates a fresh one otherwise.
35
+ *
36
+ * @param header - raw value read from `request.headers.traceparent`
37
+ * @returns the traceparent string to propagate (always well-formed)
38
+ */
39
+ export declare function resolveTraceparent(header: string | undefined | null): string;
@@ -0,0 +1,61 @@
1
+ import { Service, FileClass, Severity, Msgid, Pdu, Message, Pci, Module } from "nodefony";
2
+ import HttpKernel from "../http-kernel.js";
3
+ import fs from "node:fs";
4
+ import type { IParsedUploadFile } from "../../interfaces/IUpload.js";
5
+ export declare class upload extends Service {
6
+ httpKernel: HttpKernel;
7
+ path?: string | fs.PathLike;
8
+ module: Module;
9
+ constructor(module: Module, httpKernel: HttpKernel);
10
+ /**
11
+ * Construit un `UploadedFile` à partir d'un fichier multipart parsé —
12
+ * **async, non bloquant** (stat via `fsp.lstat`, plus de `lstatSync` par
13
+ * fichier uploadé).
14
+ *
15
+ * @param file - fichier parsé (forme neutre `IParsedUploadFile`, busboy).
16
+ * @param name - nom de champ (fallback si pas de `originalFilename`).
17
+ * @returns le `UploadedFile` hydraté.
18
+ */
19
+ createUploadFile(file: IParsedUploadFile, name: string): Promise<UploadedFile>;
20
+ log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
21
+ }
22
+ declare class UploadedFile extends FileClass {
23
+ #private;
24
+ parsedFile: IParsedUploadFile;
25
+ size: number;
26
+ prettySize: string;
27
+ filename: string;
28
+ lastModifiedDate: Date | null | undefined;
29
+ hashAlgorithm: false | "sha1" | "md5" | "sha256";
30
+ hash: string | null | undefined;
31
+ constructor(parsedFile: IParsedUploadFile, name: string, options?: {
32
+ defer?: boolean;
33
+ });
34
+ /**
35
+ * Construit un `UploadedFile` SANS `lstatSync` bloquant — stat résolu en async
36
+ * (`FileClass.stat`). À utiliser dans le pipeline d'upload (per-request).
37
+ *
38
+ * @param parsedFile - fichier multipart parsé (forme neutre busboy).
39
+ * @param name - nom de champ (fallback de nom).
40
+ * @returns le `UploadedFile` hydraté (stats async).
41
+ * @remarks Nommée `create` (pas `from`) pour ne pas entrer en conflit avec la
42
+ * signature statique de `FileClass.from(path)` (TS2417).
43
+ */
44
+ static create(parsedFile: IParsedUploadFile, name: string): Promise<UploadedFile>;
45
+ getSize(): number;
46
+ getPrettySize(): string;
47
+ realName(name?: string): string;
48
+ getMimeType(): string | false;
49
+ move(target: string): FileClass;
50
+ /**
51
+ * Variante **async** de `move()` — déplace le fichier uploadé sans bloquer
52
+ * l'event-loop (`fsp.access`/`fsp.rename` via `FileClass.moveAsync`).
53
+ * À préférer dans le pipeline (controller).
54
+ *
55
+ * @param target - destination (fichier ou dossier existant).
56
+ * @returns nouvelle instance `FileClass` (hydratée async) sur la destination.
57
+ */
58
+ moveAsync(target: fs.PathLike): Promise<FileClass>;
59
+ }
60
+ export default upload;
61
+ export { UploadedFile };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Une source d'assets statiques : un dossier servi sous un préfixe d'URL.
3
+ * Provient soit des montages natifs `server-static.mounts` (publics de module),
4
+ * soit des bundles `@nodefony/frontend` (`publicPath` → `outDir` buildé).
5
+ */
6
+ export interface AssetSource {
7
+ /** Préfixe d'URL public (ex. `/_assets/studio/`, `/test/`). */
8
+ prefix: string;
9
+ /** Dossier ABSOLU servi sous ce préfixe. */
10
+ dir: string;
11
+ }
12
+ /**
13
+ * Une entrée du plan de publication : copier `dir` → `target` (sous-arbre de
14
+ * `outDir` miroir du préfixe d'URL), servi à terme par le CDN sous `prefix`.
15
+ */
16
+ export interface AssetPlanEntry {
17
+ prefix: string;
18
+ dir: string;
19
+ /** Dossier de destination ABSOLU dans l'arbre `outDir`. */
20
+ target: string;
21
+ }
22
+ /**
23
+ * Construit le plan de publication des assets : pour chaque source unique
24
+ * (dédupliquée par préfixe — le DERNIER gagne, comme `addMount`), calcule le
25
+ * dossier cible `outDir/<préfixe-en-chemin>` miroir de l'URL.
26
+ *
27
+ * PUR (0 I/O) → testable. La copie réelle + le manifeste sont faits par la
28
+ * commande `assets:publish`. L'upload (S3/CDN/rsync) reste à l'orchestrateur :
29
+ * Nodefony assemble l'arbre, le déploiement le pousse (cloud-native).
30
+ *
31
+ * @param sources dossiers + préfixes (mounts natifs + bundles frontend)
32
+ * @param outDir racine ABSOLUE de l'arbre de sortie (ex. `<root>/dist-assets`)
33
+ * @returns plan ordonné, 1 entrée par préfixe unique
34
+ */
35
+ export declare function planAssetPublish(sources: ReadonlyArray<AssetSource>, outDir: string): AssetPlanEntry[];
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Molette de livraison de l'UI embarquée d'un module (`ui` dans sa config).
3
+ * - `auto` : Vite si possible (dev + sources + @nodefony/frontend), sinon statique.
4
+ * - `static` : force les assets pré-buildés shippés dans le paquet npm.
5
+ * - `vite` : force le dev-server Vite (repo self-hosted / contrib).
6
+ */
7
+ export type UiDeliveryMode = "auto" | "static" | "vite";
8
+ /** Mode effectivement résolu — `none` = UI indisponible (fail-loud à l'appelant). */
9
+ export type UiDeliveryResolved = "vite" | "static" | "none";
10
+ /** Résultat de {@link resolveUiDelivery} : mode effectif + raison LOGGABLE. */
11
+ export interface IUiDeliveryResolution {
12
+ mode: UiDeliveryResolved;
13
+ /** Pourquoi ce mode (toujours renseigné) — à logger tel quel par le module. */
14
+ reason: string;
15
+ }
16
+ /** Entrées de {@link resolveUiDelivery}. */
17
+ export interface IUiDeliveryOptions {
18
+ /** Molette demandée par la config du module (défaut `auto`). */
19
+ requested?: UiDeliveryMode;
20
+ /** `kernel.environment` (`development`, `production`, …). */
21
+ environment: string | undefined;
22
+ /** Le service `frontend` (@nodefony/frontend) est-il présent dans le container ? */
23
+ hasFrontendService: boolean;
24
+ /** Dossier des SOURCES front du module (ex. `<module>/frontend/src`). */
25
+ sourcesDir: string;
26
+ /** Chemin de l'index pré-buildé shippé npm (ex. `<module>/dist/frontend/index.html`). */
27
+ distIndex: string;
28
+ }
29
+ /**
30
+ * Résout le mode de livraison de l'UI embarquée d'un module.
31
+ *
32
+ * Pattern universel des admin-UI embarquées (bull-board, GraphiQL, profiler
33
+ * Symfony) : le consommateur ne compile JAMAIS l'UI d'un module tiers — les
34
+ * assets sont pré-buildés au publish et servis statiques. Le mode `vite`
35
+ * (HMR) n'a de sens que là où les sources existent (repo self-hosted, `--link`).
36
+ *
37
+ * PUR hormis deux `existsSync` (boot uniquement, jamais dans le hot path).
38
+ *
39
+ * @returns le mode effectif + la raison à logger (fail-loud si `none`)
40
+ */
41
+ export declare function resolveUiDelivery(opts: IUiDeliveryOptions): IUiDeliveryResolution;
42
+ /** Vue minimale du Container (résolution par nom uniquement). */
43
+ interface IContainerView {
44
+ get?(name: string): unknown;
45
+ }
46
+ /** Vue minimale du Kernel (retry du mount si `server-static` pas encore créé). */
47
+ interface IKernelView {
48
+ once(event: "onReady", cb: () => void): unknown;
49
+ }
50
+ /** Options de {@link PrebuiltUi}. */
51
+ export interface IPrebuiltUiOptions {
52
+ /** Préfixe public des assets (ex. `/_assets/studio/`) — normalisé `/x/`. */
53
+ publicPath: string;
54
+ /** Dossier ABSOLU des assets pré-buildés (ex. `<module>/dist/frontend`). */
55
+ distDir: string;
56
+ /** Nom de l'index dans `distDir` (défaut `index.html`). */
57
+ indexFile?: string;
58
+ }
59
+ /**
60
+ * Livraison STATIQUE de l'UI pré-buildée d'un module (mode `static`).
61
+ *
62
+ * Deux responsabilités, zéro dépendance à @nodefony/frontend :
63
+ * 1. {@link mount} — sert `distDir` sous `publicPath` via `server-static`
64
+ * (assets hashés immuables produits par `vite build` au publish).
65
+ * 2. {@link renderIndex} — document HTML d'entrée (l'`index.html` transformé
66
+ * par Vite, tags déjà réécrits vers `publicPath`), nonce CSP injecté par
67
+ * requête. Le SPA fallback reste au controller du module (routes littérales,
68
+ * cf. StudioController).
69
+ *
70
+ * Perf : l'index est lu UNE fois (lazy, caché) ; seule l'injection du nonce
71
+ * coûte un `replaceAll` par rendu — route d'entrée UI, jamais un hot path.
72
+ */
73
+ export declare class PrebuiltUi {
74
+ readonly publicPath: string;
75
+ readonly distDir: string;
76
+ private readonly indexPath;
77
+ /** Template HTML caché — `null` tant que non lu (lazy). */
78
+ private template;
79
+ constructor(opts: IPrebuiltUiOptions);
80
+ /**
81
+ * Monte `distDir` sous `publicPath` auprès de `server-static` (résolu par
82
+ * nom). Si le service n'existe pas encore (module chargé avant http), un
83
+ * retry unique est armé sur `onReady`.
84
+ *
85
+ * @returns `true` si monté immédiatement, `false` si différé/indisponible
86
+ * (l'appelant loggue — ce helper n'a pas de logger)
87
+ */
88
+ mount(container: IContainerView | null | undefined, kernel?: IKernelView | null): boolean;
89
+ /**
90
+ * Document HTML d'entrée de l'UI (index pré-buildé par Vite).
91
+ *
92
+ * @param nonce nonce CSP de la requête (`Context.cspNonce`) — injecté sur
93
+ * chaque `<script>` ; les assets `src` same-origin passent déjà
94
+ * par `'self'`, le nonce couvre un éventuel inline Vite.
95
+ * @returns le HTML, ou un commentaire HTML fail-loud si l'index est absent
96
+ */
97
+ renderIndex(nonce?: string): string;
98
+ }
99
+ export {};