@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,320 @@
1
+ import HttpError from "../src/errors/httpError.js";
2
+ import { toWsCloseCode } from "../src/context/websocket/WebsocketContext.js";
3
+ import { Nodefony, nodefonyError } from "nodefony";
4
+ //#region nodefony/service/error-renderer.ts
5
+ /** Une donnée rejetée par la validation n'est pas une requête malformée : 422, pas 400. */
6
+ const VALIDATION_STATUS = 422;
7
+ /**
8
+ * Statut d'un conflit d'ÉTAT : la requête est valide, c'est l'état courant du
9
+ * serveur qui la refuse (RFC 9110 §15.5.10). Réécrire une valeur déclarée unique
10
+ * est exactement ce cas — ni 400 (le corps est lisible), ni 422 (le contenu
11
+ * respecte le schéma), ni 500 (rien n'est cassé).
12
+ */
13
+ const CONFLICT_STATUS = 409;
14
+ /**
15
+ * Codes rendus par les pilotes de base de données quand une écriture viole une
16
+ * contrainte d'UNICITÉ — le seul signal fiable, commun à tous les moteurs.
17
+ *
18
+ * Volontairement restreint aux codes qui ne désignent QUE l'unicité : le
19
+ * `SQLITE_CONSTRAINT` générique couvre aussi NOT NULL, CHECK et les clés
20
+ * étrangères, et le retenir rendrait un 409 là où la vérité est un 422 (ou un
21
+ * 500). Un code inconnu retombe donc sur le comportement d'avant — l'oubli coûte
22
+ * un statut trop pessimiste, jamais un mensonge.
23
+ */
24
+ const UNIQUE_VIOLATION_CODES = /* @__PURE__ */ new Set([
25
+ "SQLITE_CONSTRAINT_UNIQUE",
26
+ "SQLITE_CONSTRAINT_PRIMARYKEY",
27
+ "23505",
28
+ "ER_DUP_ENTRY",
29
+ "1062",
30
+ "11000",
31
+ "11001"
32
+ ]);
33
+ /**
34
+ * Ce qu'un client reçoit sur un doublon.
35
+ *
36
+ * Le message du pilote (« UNIQUE constraint failed: posts.slug ») nomme la table
37
+ * ET la colonne : c'est de la cartographie de schéma offerte à qui frappe la
38
+ * porte. Le détail reste dans la `stack` — journal côté serveur, et corps de
39
+ * réponse hors production.
40
+ */
41
+ const UNIQUE_VIOLATION_MESSAGE = "Conflict — a resource with these unique values already exists";
42
+ /**
43
+ * Ce qu'un client reçoit à la place du détail d'une panne serveur, en production.
44
+ *
45
+ * Le message d'une exception non maîtrisée cite volontiers un chemin de fichier,
46
+ * un nom de table, une requête SQL ou un identifiant interne. Le rendre à un
47
+ * client — a fortiori **anonyme**, un close WS partant avant le firewall —
48
+ * revient à publier de la reconnaissance gratuite.
49
+ */
50
+ const OPAQUE_SERVER_ERROR = "Internal Server Error";
51
+ /**
52
+ * Clés d'un `HttpError` sérialisé qui décrivent les ENTRAILLES du serveur et ne
53
+ * doivent jamais franchir la frontière en production.
54
+ */
55
+ const INTERNAL_ERROR_KEYS = [
56
+ "stack",
57
+ "controller",
58
+ "action",
59
+ "bundle",
60
+ "url",
61
+ "pdu"
62
+ ];
63
+ /**
64
+ * Vrai si le runtime tourne en production — le détail des erreurs reste alors au
65
+ * journal, seul le serveur le voit.
66
+ *
67
+ * ⚠️ La comparaison porte sur `"production"` **en toutes lettres** : `Kernel.setEnv`
68
+ * réduit toujours l'environnement à `"development" | "production"`
69
+ * (`Kernel.resolveRuntimeEnv`). Une garde écrite `=== "prod"` ne se déclenche donc
70
+ * JAMAIS, même si `"prod"` est une valeur acceptée en entrée.
71
+ *
72
+ * Résolu à chaque rendu (pas de cache) : une erreur est déjà un chemin froid, et
73
+ * mémoïser rendrait le masquage insensible à un changement d'environnement entre
74
+ * deux tests — un gate qu'on ne peut plus voir mordre.
75
+ */
76
+ const isProduction = () => Nodefony.getKernel()?.environment === "production";
77
+ /**
78
+ * Reconnaît une erreur de validation Zod et en extrait les champs fautifs.
79
+ *
80
+ * Reconnaissance **structurelle** (`name` + `issues`), pas `instanceof ZodError` :
81
+ * une application peut embarquer sa propre copie de zod (résolutions npm multiples),
82
+ * et `instanceof` échouerait alors en silence — la validation retomberait en 500.
83
+ * Même parti-pris que le duck-typing de `isPromise` dans le framework.
84
+ *
85
+ * Ne coûte rien au chemin nominal : ce code ne tourne que sur une erreur déjà levée.
86
+ *
87
+ * @param error - erreur remontée par le pipeline.
88
+ * @returns les champs fautifs, ou `null` si ce n'est pas une erreur de validation.
89
+ */
90
+ function toValidationFields(error) {
91
+ const candidate = error;
92
+ if (error.name !== "ZodError" || !Array.isArray(candidate.issues)) return null;
93
+ return candidate.issues.map((issue) => ({
94
+ field: Array.isArray(issue.path) ? issue.path.join(".") : "",
95
+ message: issue.message ?? "invalide",
96
+ rule: issue.code
97
+ }));
98
+ }
99
+ /**
100
+ * Construit l'erreur **422** exposée au client à partir des champs fautifs.
101
+ *
102
+ * Pourquoi 422 et pas 400 : la requête est syntaxiquement correcte (le corps a été
103
+ * parsé) — c'est son **contenu** qui viole le contrat (RFC 9110 §15.5.21). Un 400
104
+ * dirait « corps illisible » et enverrait le client chercher au mauvais endroit.
105
+ *
106
+ * Le message d'origine de zod (un JSON d'anomalies) est remplacé par un résumé
107
+ * lisible ; la **stack d'origine est conservée** (débogage), et `fields` est porté par
108
+ * l'erreur elle-même : `toJSON()` sérialise les propriétés propres, donc le client
109
+ * reçoit `error.fields` et sait QUEL champ corriger — pas seulement que « ça a échoué ».
110
+ */
111
+ function toValidationError(error, fields) {
112
+ const summary = fields.map((f) => f.field ? `${f.field}: ${f.message}` : f.message).join(" · ");
113
+ const validation = new nodefonyError(`Validation failed — ${summary}`, VALIDATION_STATUS);
114
+ validation.stack = error.stack;
115
+ validation.fields = fields;
116
+ return validation;
117
+ }
118
+ /**
119
+ * Vrai si l'erreur — ou l'une de ses causes — est une violation de contrainte
120
+ * d'unicité remontée par un pilote de base de données.
121
+ *
122
+ * **La descente dans `cause` n'est pas un raffinement, c'est la condition pour
123
+ * que ça marche** : Drizzle enveloppe toute erreur de pilote dans un
124
+ * `DrizzleQueryError` dont le `code` vaut `undefined`. Sans elle, une écriture
125
+ * en doublon reste un 500.
126
+ *
127
+ * Reconnaissance par CODE seul, jamais par message : « duplicate key » dans un
128
+ * texte d'erreur est un indice, pas une preuve, et un faux positif déguiserait
129
+ * une panne réelle en conflit — le client réessaierait autrement au lieu
130
+ * d'alerter. Même parti-pris de duck-typing que `toValidationFields`, pour la
131
+ * même raison (l'application peut embarquer sa propre copie du pilote).
132
+ *
133
+ * Ne coûte rien au chemin nominal : ne tourne que sur une erreur déjà levée.
134
+ *
135
+ * @param error - erreur remontée par le pipeline.
136
+ * @returns vrai si un code de violation d'unicité est trouvé dans la chaîne.
137
+ */
138
+ function isUniqueViolation(error) {
139
+ let current = error;
140
+ for (let depth = 0; depth < 5 && current instanceof Error; depth += 1) {
141
+ const candidate = current;
142
+ if (candidate.code !== void 0 && UNIQUE_VIOLATION_CODES.has(String(candidate.code)) || candidate.errno !== void 0 && UNIQUE_VIOLATION_CODES.has(String(candidate.errno))) return true;
143
+ current = candidate.cause;
144
+ }
145
+ return false;
146
+ }
147
+ /**
148
+ * Codes rendus par les pilotes quand la requête porte sur une TABLE ou une
149
+ * COLONNE que la base n'a pas — la signature d'un schéma en retard sur le code.
150
+ *
151
+ * Jumelle de {@link UNIQUE_VIOLATION_CODES}, et pour la même raison : le code
152
+ * est le seul signal fiable. Reconnaître « does not exist » dans un message
153
+ * serait un piège — la formulation change d'un pilote et d'une version à
154
+ * l'autre, et la même phrase apparaît dans des erreurs qui n'ont rien à voir.
155
+ *
156
+ * **SQLite n'a pas sa place ici** : il ne rend qu'un `SQLITE_ERROR` générique,
157
+ * partagé avec la syntaxe invalide et bien d'autres fautes — le mettre dans
158
+ * cette table le rendrait aussi ferme que `42P01`, ce qu'il n'est pas. Il est
159
+ * reconnu à part, et plus faiblement, par {@link SQLITE_SCHEMA_PREFIXES}.
160
+ */
161
+ const SCHEMA_MISMATCH_CODES = /* @__PURE__ */ new Set([
162
+ "42703",
163
+ "42P01",
164
+ "ER_BAD_FIELD_ERROR",
165
+ "1054",
166
+ "ER_NO_SUCH_TABLE",
167
+ "1146"
168
+ ]);
169
+ /**
170
+ * Ce que SQLite dit, faute de code dédié.
171
+ *
172
+ * SQLite ne rend qu'un `SQLITE_ERROR` générique — partagé avec la syntaxe
173
+ * invalide et bien d'autres fautes —, donc le code seul ne tranche pas. Ces
174
+ * deux préfixes, eux, sont sans ambiguïté : ils viennent du source de SQLite,
175
+ * ils y sont stables depuis toujours, et aucune autre faute ne les produit.
176
+ *
177
+ * Ce n'est PAS un retour à la reconnaissance par message : on n'accepte que
178
+ * des débuts de message figés, et seulement après avoir constaté le code du
179
+ * pilote. Une formulation libre du genre « does not exist » resterait un
180
+ * piège — elle change d'un pilote et d'une version à l'autre.
181
+ */
182
+ const SQLITE_SCHEMA_PREFIXES = ["no such table:", "no such column:"];
183
+ /**
184
+ * Reconnaît, dans une erreur ou l'une de ses causes, un schéma en retard sur
185
+ * le code — et dit avec quelle FORCE.
186
+ *
187
+ * Détecteur UNIQUE de ce fait dans tout le framework : le corps d'erreur rendu
188
+ * au développeur le lit, et le rechargement d'un instantané au démarrage
189
+ * aussi. Deux reconnaissances de la même chose divergeraient en silence, et
190
+ * chacune passerait ses propres tests.
191
+ *
192
+ * Chaque lecteur choisit son SEUIL, et c'est là que la graduation sert. Le
193
+ * corps d'erreur n'accepte que `certain` : il nomme des tables à un client, et
194
+ * une aide qui se trompe de cause coûte plus cher que pas d'aide du tout. Un
195
+ * appelant qui sait déjà ce qu'il a demandé — une table précise, au démarrage —
196
+ * peut accepter `probable` : il ne publie rien, il décide seulement de ne pas
197
+ * hurler.
198
+ *
199
+ * Même descente dans `cause` que {@link isUniqueViolation}, et pour la même
200
+ * raison : Drizzle enveloppe toute erreur de pilote dans un `DrizzleQueryError`
201
+ * dont le `code` vaut `undefined`. Sans elle, la reconnaissance ne mord jamais.
202
+ *
203
+ * Ne coûte rien au chemin nominal : ne tourne que sur une erreur déjà levée.
204
+ *
205
+ * @param error - erreur remontée par un pilote, éventuellement enveloppée.
206
+ * @returns la force du signal, ou `null` si rien ne dit ça.
207
+ */
208
+ function schemaMismatchOf(error) {
209
+ let current = error;
210
+ for (let depth = 0; depth < 5 && current instanceof Error; depth += 1) {
211
+ const candidate = current;
212
+ if (candidate.code !== void 0 && SCHEMA_MISMATCH_CODES.has(String(candidate.code)) || candidate.errno !== void 0 && SCHEMA_MISMATCH_CODES.has(String(candidate.errno))) return "certain";
213
+ if (String(candidate.code) === "SQLITE_ERROR" && SQLITE_SCHEMA_PREFIXES.some((p) => candidate.message?.startsWith(p))) return "probable";
214
+ current = candidate.cause;
215
+ }
216
+ return null;
217
+ }
218
+ /**
219
+ * L'aide rendue au développeur quand la base est en retard sur le code.
220
+ *
221
+ * Elle ne cite PAS le message du pilote : c'est lui que le lecteur vient de
222
+ * recevoir sans le comprendre, et le répéter n'apprend rien. Elle nomme la
223
+ * cause — le schéma — et donne les deux gestes, dans l'ordre où ils se tentent.
224
+ */
225
+ const SCHEMA_MISMATCH_HINT = {
226
+ kind: "schema-mismatch",
227
+ message: "La base ne correspond pas au code : une table ou une colonne que le code déclare n'existe pas. C'est le schéma qui est en retard, pas la requête.",
228
+ actions: [
229
+ "Redémarrer le serveur — en développement, les colonnes qui acceptent le vide sont rattrapées au démarrage.",
230
+ "nodefony orm:migrate:status — dire ce qui manque, sans rien modifier.",
231
+ "nodefony orm:reset --connector default — SUPPRIME et recrée la base de développement."
232
+ ]
233
+ };
234
+ /**
235
+ * Default Nodefony error renderer — preserves the legacy JSON error shape.
236
+ *
237
+ * HTTP body (unchanged across the migration):
238
+ * {
239
+ * code: <http status>,
240
+ * message: <error message>,
241
+ * error: <HttpError.toJSON() — stack, controller, action, jsonResponse...>,
242
+ * nodefony: { requestId, scheme, name, version, environment, debug, ... },
243
+ * result: null,
244
+ * }
245
+ *
246
+ * WS close frame:
247
+ * code: clamped to WS range (1000-4999), 1011 if HTTP-style code, 500→1011
248
+ * reason: error.message
249
+ *
250
+ * Stateless singleton — zero per-request allocation. Override via
251
+ * `httpKernel.setErrorRenderer(custom)` for prod hardening or RFC 7807.
252
+ */
253
+ var DefaultErrorRenderer = class {
254
+ renderHttp(error, context) {
255
+ const httpError = this.toHttpError(error, context);
256
+ const status = this.normalizeHttpStatus(httpError.code);
257
+ httpError.code = status;
258
+ const obj = context.metaData;
259
+ const serialized = httpError.toJSON();
260
+ const message = isProduction() && status >= 500 ? OPAQUE_SERVER_ERROR : httpError.message;
261
+ if (isProduction()) {
262
+ for (const key of INTERNAL_ERROR_KEYS) delete serialized[key];
263
+ serialized.message = message;
264
+ }
265
+ obj.error = serialized;
266
+ obj.code = status;
267
+ obj.message = message;
268
+ if (!isProduction() && schemaMismatchOf(error) === "certain") obj.hint = SCHEMA_MISMATCH_HINT;
269
+ return {
270
+ status,
271
+ message,
272
+ body: obj
273
+ };
274
+ }
275
+ renderWebsocket(error, context) {
276
+ const httpError = this.toHttpError(error, context);
277
+ const rawCode = httpError.code;
278
+ let code = typeof rawCode === "number" && Number.isInteger(rawCode) ? rawCode : 500;
279
+ if (context && context.rejected === false) code = toWsCloseCode(code);
280
+ else if (code > 599) code = 500;
281
+ const internal = code === 1011 || code >= 500 && code <= 599;
282
+ const reason = isProduction() && internal ? OPAQUE_SERVER_ERROR : httpError.message;
283
+ return {
284
+ code,
285
+ reason
286
+ };
287
+ }
288
+ toHttpError(error, context) {
289
+ if (error instanceof HttpError) return error;
290
+ const fields = toValidationFields(error);
291
+ if (fields) {
292
+ const httpError = new HttpError(toValidationError(error, fields), VALIDATION_STATUS, context);
293
+ httpError.fields = fields;
294
+ return httpError;
295
+ }
296
+ if (isUniqueViolation(error)) {
297
+ const conflict = new nodefonyError(UNIQUE_VIOLATION_MESSAGE, CONFLICT_STATUS);
298
+ conflict.stack = error.stack;
299
+ return new HttpError(conflict, CONFLICT_STATUS, context);
300
+ }
301
+ const code = error.code;
302
+ return new HttpError(error, typeof code === "number" ? code : void 0, context);
303
+ }
304
+ /**
305
+ * Ramène un `code` d'erreur à un statut HTTP réellement émettable.
306
+ *
307
+ * Le filtre de type n'est pas défensif « au cas où » : `nodefonyError.parseMessage`
308
+ * RECOPIE le code de l'erreur source, et pilotes comme Node en produisent des
309
+ * textuels (`"ENOENT"`, `"ECONNRESET"`, `"23505"`). Sans lui, la chaîne partait
310
+ * telle quelle en statut de réponse et le serveur répondait hors RFC 9110 §15.
311
+ */
312
+ normalizeHttpStatus(code) {
313
+ if (typeof code !== "number" || !Number.isInteger(code)) return 500;
314
+ if (code === 200) return 500;
315
+ if (code < 100 || code > 599) return 500;
316
+ return code;
317
+ }
318
+ };
319
+ //#endregion
320
+ export { DefaultErrorRenderer as default, schemaMismatchOf };