@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.
- package/LICENSE +544 -0
- package/README.md +77 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +108 -0
- package/dist/nodefony/command/assetsPublishCommand.js +102 -0
- package/dist/nodefony/command/certificatesCommand.js +47 -0
- package/dist/nodefony/command/networkCommand.js +27 -0
- package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
- package/dist/nodefony/config/config.js +335 -0
- package/dist/nodefony/config/defineModuleConfig.js +93 -0
- package/dist/nodefony/interfaces/IContext.js +1 -0
- package/dist/nodefony/interfaces/ICookie.js +1 -0
- package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
- package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
- package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
- package/dist/nodefony/interfaces/IRequest.js +1 -0
- package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
- package/dist/nodefony/interfaces/IResponse.js +1 -0
- package/dist/nodefony/interfaces/ISession.js +1 -0
- package/dist/nodefony/interfaces/IUpload.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +376 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
- package/dist/nodefony/service/audit-logger.js +159 -0
- package/dist/nodefony/service/certificates.js +545 -0
- package/dist/nodefony/service/error-renderer.js +320 -0
- package/dist/nodefony/service/http-kernel.js +948 -0
- package/dist/nodefony/service/pretty-request-logger.js +72 -0
- package/dist/nodefony/service/request-logger.js +54 -0
- package/dist/nodefony/service/servers/clientError.js +20 -0
- package/dist/nodefony/service/servers/server-http.js +135 -0
- package/dist/nodefony/service/servers/server-https.js +204 -0
- package/dist/nodefony/service/servers/server-static.js +192 -0
- package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
- package/dist/nodefony/service/servers/server-websocket.js +104 -0
- package/dist/nodefony/service/servers/serverShutdown.js +31 -0
- package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
- package/dist/nodefony/service/sessions/sessions-service.js +580 -0
- package/dist/nodefony/service/trace.js +72 -0
- package/dist/nodefony/service/upload/upload-service.js +171 -0
- package/dist/nodefony/src/assets/collectAssets.js +34 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
- package/dist/nodefony/src/context/Context.js +415 -0
- package/dist/nodefony/src/context/domainMatcher.js +88 -0
- package/dist/nodefony/src/context/forwarded.js +185 -0
- package/dist/nodefony/src/context/http/HttpContext.js +309 -0
- package/dist/nodefony/src/context/http/Request.js +543 -0
- package/dist/nodefony/src/context/http/Response.js +368 -0
- package/dist/nodefony/src/context/http/parser.js +188 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
- package/dist/nodefony/src/context/http2/Request.js +29 -0
- package/dist/nodefony/src/context/http2/Response.js +97 -0
- package/dist/nodefony/src/context/metaData.js +47 -0
- package/dist/nodefony/src/context/requestId.js +41 -0
- package/dist/nodefony/src/context/trustProxy.js +167 -0
- package/dist/nodefony/src/context/websocket/Response.js +181 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
- package/dist/nodefony/src/cookies/cookie.js +258 -0
- package/dist/nodefony/src/errors/httpError.js +69 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
- package/dist/nodefony/src/profiler/Profiler.js +139 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
- package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
- package/dist/nodefony/src/servers/portBinder.js +114 -0
- package/dist/nodefony/src/session/session.js +390 -0
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
- package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
- package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
- package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
- package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
- package/dist/types/nodefony/config/config.d.ts +197 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
- package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
- package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
- package/dist/types/nodefony/service/certificates.d.ts +246 -0
- package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
- package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
- package/dist/types/nodefony/service/request-logger.d.ts +18 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
- package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
- package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
- package/dist/types/nodefony/service/trace.d.ts +39 -0
- package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
- package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
- package/dist/types/nodefony/src/context/Context.d.ts +195 -0
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
- package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
- package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
- package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
- package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
- package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
- package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
- package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
- package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
- package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
- package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
- package/dist/types/nodefony/src/session/session.d.ts +171 -0
- package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
- package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
- package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
- package/docs/cookies.md +365 -0
- package/docs/index.md +163 -0
- package/docs/observabilite.md +460 -0
- package/docs/rate-limit.md +372 -0
- package/docs/servers.md +935 -0
- package/docs/session.md +768 -0
- package/docs/upload.md +460 -0
- 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 };
|