@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
package/README.md CHANGED
@@ -34,6 +34,13 @@ serveur → http-kernel.handle() → rate-limit IP → createContext()
34
34
  Chaque contexte (HTTP + WS) porte un `requestId` (UUID v4, ou `X-Request-Id` client),
35
35
  réinjecté dans la réponse et corrélé dans les logs via AsyncLocalStorage.
36
36
 
37
+ Chaque requête HTTP — et chaque connexion WebSocket — reçoit aussi **son propre conteneur de
38
+ services** : un scope ouvert à l'entrée (`enterScope`), dont le prototype est le conteneur du kernel,
39
+ refermé après les hooks `onAfterResponse`/`onFinish` (`leaveScope`), y compris quand un hook lève.
40
+ Le serveur le pose dans le contexte asynchrone de la requête : `RequestContext.getScope()` le rend
41
+ depuis n'importe quel code, et les services `@injectable({ scope: "request" })` y vivent. Détail :
42
+ [Injection & portées](https://github.com/nodefony/nodefony-core/blob/main/docs/architecture/injection-portees.md).
43
+
37
44
  ## Rate-limit général par IP
38
45
 
39
46
  Plafond de trafic **par IP cliente** sur toutes les routes HTTP — protège la capacité du serveur
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import config, { httpConfigSchema } from "./nodefony/config/config.js";
1
+ import config, { httpConfigSchema, httpOverlaySchema } from "./nodefony/config/config.js";
2
2
  import { defineHttpConfig, httpConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
3
3
  import { createHttpAdminApi } from "./nodefony/service/HttpAdminApi.js";
4
4
  import { createProfilerAdminApi } from "./nodefony/service/ProfilerAdminApi.js";
@@ -42,6 +42,7 @@ import { PrebuiltUi, resolveUiDelivery } from "./nodefony/src/assets/prebuiltUi.
42
42
  import { Kernel, Module, services } from "nodefony";
43
43
  //#region index.ts
44
44
  let Http = class Http extends Module {
45
+ overlaySchema = httpOverlaySchema;
45
46
  constructor(kernel) {
46
47
  super("http", kernel, import.meta.url, config);
47
48
  this.addCommand(Network);
@@ -68,7 +69,8 @@ let Http = class Http extends Module {
68
69
  * certificats `serialNumber`) — cf `defineHttpConfig`.
69
70
  */
70
71
  async onKernelRegister() {
71
- this.options = defineHttpConfig(this.options ?? {}, this.kernel);
72
+ const input = this.options;
73
+ this.options = defineHttpConfig(input ?? {}, this.kernel);
72
74
  return this;
73
75
  }
74
76
  /**
@@ -80,7 +80,7 @@ var AssetsPublish = class extends Command {
80
80
  }
81
81
  /** Sources = mounts natifs (`server-static`) + bundles `@nodefony/frontend`. */
82
82
  collectSources() {
83
- const modules = this.kernel?.getModules?.() ?? {};
83
+ const modules = this.kernel?.getModules() ?? {};
84
84
  const sources = [];
85
85
  const staticSvc = modules["http"]?.get("server-static");
86
86
  staticSvc?.mountModulePublics?.();
@@ -21,7 +21,7 @@ var Certificates = class extends Command {
21
21
  this.addOption("-j, --json", "output the certificate summary as JSON");
22
22
  }
23
23
  async generate(opts) {
24
- const service = (this.kernel?.getModules()?.["http"])?.get("certificates") ?? null;
24
+ const service = (this.kernel?.getModules()["http"])?.get("certificates") ?? null;
25
25
  if (!service) {
26
26
  this.log("Service 'certificates' introuvable (module @nodefony/http non chargé).", "ERROR");
27
27
  return this;
@@ -16,7 +16,7 @@ var Network = class extends Command {
16
16
  let network = this.kernel?.getNetwork();
17
17
  let result = {};
18
18
  if (arg) {
19
- if (network?.interfaces[arg]) result = network?.interfaces[arg];
19
+ if (network?.interfaces[arg]) result = network.interfaces[arg];
20
20
  } else result = network?.interfaces;
21
21
  if (opts.json) process.stdout.write(`${JSON.stringify(result, void 0, " ")}\n`);
22
22
  else process.stdout.write(`${inspect(result, { colors: process.stdout.isTTY })}\n`);
@@ -43,7 +43,7 @@ var ProxyGenerate = class extends Command {
43
43
  }
44
44
  /** Construit le modèle d'introspection depuis le kernel + le service statique. */
45
45
  buildIntrospection(opts) {
46
- const module = this.kernel?.getModules()?.["http"];
46
+ const module = this.kernel?.getModules()["http"];
47
47
  const httpOpts = module?.options ?? {};
48
48
  const servers = (this.kernel?.options)?.servers;
49
49
  const staticSvc = module?.get("server-static");
@@ -1,3 +1,4 @@
1
+ import { overlayField } from "nodefony";
1
2
  import { z } from "zod";
2
3
  //#region nodefony/config/config.ts
3
4
  /**
@@ -327,9 +328,29 @@ const httpConfigSchema = z.strictObject({
327
328
  })
328
329
  }).describe("Configuration de @nodefony/http.");
329
330
  /**
331
+ * LISTE BLANCHE des clés qu'une requête peut surcharger pour elle seule
332
+ * (`overlayConfig("@nodefony/http", …)`, #494) : les quotas de corps et
333
+ * d'envoi, lus au MOMENT de lire le corps (`Request.readBodyLimits`) — un
334
+ * calque posé avant la lecture du corps (écouteur `onRequestScope`, organisation
335
+ * reconnue à son nom d'hôte) les voit. Tout le reste est refusé : en-têtes de
336
+ * sécurité, pare-feu, `trustProxy`, sessions, limites de débit, certificats —
337
+ * et `queryString`, lu au constructeur de la requête, avant tout calque.
338
+ * Chaque champ réutilise celui du schéma (mêmes bornes) sans son défaut.
339
+ */
340
+ const httpOverlaySchema = z.strictObject({
341
+ maxBodySize: overlayField(httpConfigSchema.shape.maxBodySize),
342
+ upload: z.strictObject({
343
+ maxFileSize: overlayField(uploadSchema.shape.maxFileSize),
344
+ maxTotalFileSize: overlayField(uploadSchema.shape.maxTotalFileSize),
345
+ maxFiles: overlayField(uploadSchema.shape.maxFiles),
346
+ maxFields: overlayField(uploadSchema.shape.maxFields),
347
+ maxFieldsSize: overlayField(uploadSchema.shape.maxFieldsSize)
348
+ }).partial()
349
+ }).partial().describe("Clés de @nodefony/http surchargeables par requête.");
350
+ /**
330
351
  * Défauts du module, matérialisés depuis le schéma (source unique). Toujours
331
352
  * valides par construction ; passés au `super(..., config)` du Module class.
332
353
  */
333
354
  const config = httpConfigSchema.parse({});
334
355
  //#endregion
335
- export { config as default, httpConfigSchema };
356
+ export { config as default, httpConfigSchema, httpOverlaySchema };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -51,7 +51,8 @@ function createProfilerAdminApi(profiler) {
51
51
  path: "{id}",
52
52
  summary: "Full profile (phase timeline) for a requestId",
53
53
  handler: ({ params }) => {
54
- const entry = profiler.get(params.id);
54
+ const { id = "" } = params;
55
+ const entry = profiler.get(id);
55
56
  if (!entry) return {
56
57
  status: 404,
57
58
  body: {
@@ -2,6 +2,19 @@ import { RequestContext } from "nodefony";
2
2
  import { performance } from "node:perf_hooks";
3
3
  //#region nodefony/service/audit-logger.ts
4
4
  /**
5
+ * Durée totale de la requête, de la première phase (`startMs`) à maintenant.
6
+ * `null` si le chronométrage est coupé (aucune phase enregistrée). Partagée
7
+ * par le journal d'audit et le journal lisible — une seule définition.
8
+ *
9
+ * @param phases - phases chronométrées du contexte
10
+ * @returns durée en millisecondes, ou `null`
11
+ */
12
+ function computeDurationMs(phases) {
13
+ const first = phases.at(0);
14
+ if (first === void 0 || typeof first.startMs !== "number") return null;
15
+ return performance.now() - first.startMs;
16
+ }
17
+ /**
5
18
  * Severity derived from HTTP status code — RFC 9110 categories.
6
19
  * 1xx/2xx/3xx → INFO ; 4xx → WARNING ; 5xx → ERROR.
7
20
  * Unknown/missing status → INFO.
@@ -77,7 +90,7 @@ var JsonAuditLogger = class {
77
90
  method: ctx.method,
78
91
  url: ctx.url,
79
92
  status,
80
- durationMs: this.computeDurationMs(ctx.phases),
93
+ durationMs: computeDurationMs(ctx.phases),
81
94
  remoteAddress: ctx.remoteAddress ?? null,
82
95
  host: ctx.getHost?.() ?? null,
83
96
  userAgent: ctx.getUserAgent?.() ?? null,
@@ -108,7 +121,7 @@ var JsonAuditLogger = class {
108
121
  method: ctx.method,
109
122
  url: ctx.url,
110
123
  status,
111
- durationMs: this.computeDurationMs(ctx.phases),
124
+ durationMs: computeDurationMs(ctx.phases),
112
125
  remoteAddress: ctx.remoteAddress ?? null,
113
126
  host: ctx.getHost?.() ?? null,
114
127
  userAgent: ctx.getUserAgent?.() ?? null,
@@ -128,16 +141,6 @@ var JsonAuditLogger = class {
128
141
  };
129
142
  }
130
143
  /**
131
- * Total request duration computed from the first phase startMs to now.
132
- * Returns null if timing is disabled (no phases recorded).
133
- */
134
- computeDurationMs(phases) {
135
- if (!phases.length) return null;
136
- const first = phases[0];
137
- if (typeof first.startMs !== "number") return null;
138
- return performance.now() - first.startMs;
139
- }
140
- /**
141
144
  * Serialise an Error (recursively for `cause` chain) into an AuditErrorEntry.
142
145
  * Stack is included only when `includeStack === true` (dev default).
143
146
  * Cause chain is capped at `maxCauseDepth`.
@@ -156,4 +159,4 @@ var JsonAuditLogger = class {
156
159
  }
157
160
  };
158
161
  //#endregion
159
- export { JsonAuditLogger as default, severityFromStatus };
162
+ export { computeDurationMs, JsonAuditLogger as default, severityFromStatus };
@@ -1,3 +1,4 @@
1
+ import { asServersConfig } from "../src/servers/kernelServers.js";
1
2
  import { Service, extend } from "nodefony";
2
3
  import { randomBytes } from "node:crypto";
3
4
  import fsp from "node:fs/promises";
@@ -31,6 +32,15 @@ const defaultOptions = {
31
32
  }
32
33
  };
33
34
  /**
35
+ * Valeur texte d'un champ de certificat node-forge (`getField` rend `any`),
36
+ * ou `null` si le champ est absent.
37
+ */
38
+ function certFieldValue(field) {
39
+ if (field === null || typeof field !== "object") return null;
40
+ const value = field.value;
41
+ return typeof value === "string" ? value : String(value);
42
+ }
43
+ /**
34
44
  * Service de fourniture du certificat TLS du serveur HTTPS/WSS.
35
45
  *
36
46
  * Trois stratégies : `explicit` (certificat fourni en config — le cas de
@@ -90,7 +100,7 @@ var Certificate = class Certificate extends Service {
90
100
  * ne l'appelle jamais.
91
101
  */
92
102
  async loadForge() {
93
- if (!this.forge) this.forge = (await import("node-forge")).default;
103
+ this.forge ??= (await import("node-forge")).default;
94
104
  return this.forge;
95
105
  }
96
106
  /** Accès au backend forge déjà chargé (lève si `loadForge` n'a pas été appelé). */
@@ -120,7 +130,7 @@ var Certificate = class Certificate extends Service {
120
130
  * exemplaire. La promesse était écrite ; rien ne la tenait.
121
131
  */
122
132
  get tlsWanted() {
123
- return !!this.module.kernel?.options?.servers?.https;
133
+ return !!asServersConfig(this.module.kernel?.options.servers)?.https;
124
134
  }
125
135
  async init() {
126
136
  if (!this.tlsWanted) return this;
@@ -138,8 +148,8 @@ var Certificate = class Certificate extends Service {
138
148
  */
139
149
  static generateSerialHex() {
140
150
  const bytes = randomBytes(16);
141
- bytes[0] &= 127;
142
- if (bytes[0] === 0) bytes[0] = 1;
151
+ const head = bytes.readUInt8(0) & 127;
152
+ bytes[0] = head === 0 ? 1 : head;
143
153
  return bytes.toString("hex");
144
154
  }
145
155
  setFiles() {
@@ -179,7 +189,7 @@ var Certificate = class Certificate extends Service {
179
189
  path.dirname(this.certPath),
180
190
  path.dirname(this.fullchainPath)
181
191
  ];
182
- for await (const directory of directories) try {
192
+ for (const directory of directories) try {
183
193
  await fsp.access(directory);
184
194
  } catch {
185
195
  await fsp.mkdir(directory, { recursive: true });
@@ -237,7 +247,7 @@ var Certificate = class Certificate extends Service {
237
247
  }
238
248
  if (requested === "auto" && this.hasExplicitCert()) return "explicit";
239
249
  if (requested === "mkcert" || requested === "auto") {
240
- if (this.isDev() && this.certOptions.dev?.useMkcert !== false) {
250
+ if (this.isDev() && this.certOptions.dev.useMkcert !== false) {
241
251
  const caRoot = await this.detectMkcert();
242
252
  if (caRoot) {
243
253
  this.mkcertCaRoot = caRoot;
@@ -345,8 +355,8 @@ var Certificate = class Certificate extends Service {
345
355
  const ext = cert.getExtension("subjectAltName");
346
356
  if (!ext || !this.sanCovers(ext.altNames ?? [])) return false;
347
357
  if (strategy === "mkcert") {
348
- const org = cert.issuer.getField("O");
349
- return Boolean(org && /mkcert/i.test(String(org.value)));
358
+ const org = certFieldValue(cert.issuer.getField("O"));
359
+ return org !== null && /mkcert/i.test(org);
350
360
  }
351
361
  return cert.signatureOid !== pki.oids.sha1WithRSAEncryption;
352
362
  } catch {
@@ -401,7 +411,7 @@ var Certificate = class Certificate extends Service {
401
411
  }
402
412
  async writeCertificates(force = false) {
403
413
  await this.ensureDirectoriesExist();
404
- for await (const file of this.files) try {
414
+ for (const file of this.files) try {
405
415
  if (file.variable) {
406
416
  const mode = file.path === this.privateKeyPath ? this.certOptions.privateKeyMode ?? 384 : 420;
407
417
  if (force) await fsp.rm(file.path, { force: true });
@@ -542,8 +552,8 @@ var Certificate = class Certificate extends Service {
542
552
  info.validFrom = cert.validity.notBefore.toISOString();
543
553
  info.validTo = cert.validity.notAfter.toISOString();
544
554
  info.signatureAlgorithm = this.oidName(cert.signatureOid, pki);
545
- const cn = cert.subject.getField("CN");
546
- if (cn) info.commonName = String(cn.value);
555
+ const cn = certFieldValue(cert.subject.getField("CN"));
556
+ if (cn !== null) info.commonName = cn;
547
557
  const ext = cert.getExtension("subjectAltName");
548
558
  if (ext?.altNames) info.san = ext.altNames.map((a) => a.type === 7 ? a.ip ?? "" : a.value ?? "");
549
559
  } catch {}
@@ -560,9 +570,11 @@ var Certificate = class Certificate extends Service {
560
570
  case "sha512": return md.sha512.create();
561
571
  case "sha384": return md.sha384.create();
562
572
  case "sha256": return md.sha256.create();
563
- default:
564
- this.log(`Hachage '${hash}' refusé (SHA-1 interdit) → SHA-256.`, "WARNING");
573
+ default: {
574
+ const refused = hash;
575
+ this.log(`Hachage '${refused}' refusé (SHA-1 interdit) → SHA-256.`, "WARNING");
565
576
  return md.sha256.create();
577
+ }
566
578
  }
567
579
  }
568
580
  };
@@ -111,7 +111,7 @@ function toValidationFields(error) {
111
111
  function toValidationError(error, fields) {
112
112
  const summary = fields.map((f) => f.field ? `${f.field}: ${f.message}` : f.message).join(" · ");
113
113
  const validation = new nodefonyError(`Validation failed — ${summary}`, VALIDATION_STATUS);
114
- validation.stack = error.stack;
114
+ if (error.stack !== void 0) validation.stack = error.stack;
115
115
  validation.fields = fields;
116
116
  return validation;
117
117
  }
@@ -206,8 +206,11 @@ function malformedIdentifierOf(error) {
206
206
  const candidate = current;
207
207
  if (candidate.name === "CastError" && (candidate.kind === "ObjectId" || candidate.kind === "UUID") && (typeof candidate.value === "string" || typeof candidate.value === "number")) return String(candidate.value);
208
208
  if (String(candidate.code) === "22P02") {
209
- const match = PG_INVALID_INPUT.exec(candidate.message ?? "");
210
- if (match && IDENTIFIER_COLUMN_TYPES.has(match[1])) return match[2];
209
+ const match = PG_INVALID_INPUT.exec(candidate.message);
210
+ if (match) {
211
+ const [, type = "", value = null] = match;
212
+ if (IDENTIFIER_COLUMN_TYPES.has(type)) return value;
213
+ }
211
214
  }
212
215
  current = candidate.cause;
213
216
  }
@@ -307,7 +310,7 @@ function schemaMismatchOf(error) {
307
310
  for (let depth = 0; depth < 5 && current instanceof Error; depth += 1) {
308
311
  const candidate = current;
309
312
  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";
310
- if (String(candidate.code) === "SQLITE_ERROR" && SQLITE_SCHEMA_PREFIXES.some((p) => candidate.message?.startsWith(p))) return "probable";
313
+ if (String(candidate.code) === "SQLITE_ERROR" && SQLITE_SCHEMA_PREFIXES.some((p) => candidate.message.startsWith(p))) return "probable";
311
314
  current = candidate.cause;
312
315
  }
313
316
  return null;
@@ -373,7 +376,7 @@ var DefaultErrorRenderer = class {
373
376
  const httpError = this.toHttpError(error, context);
374
377
  const rawCode = httpError.code;
375
378
  let code = typeof rawCode === "number" && Number.isInteger(rawCode) ? rawCode : 500;
376
- if (context && context.rejected === false) code = toWsCloseCode(code);
379
+ if (context.rejected === false) code = toWsCloseCode(code);
377
380
  else if (code > 599) code = 500;
378
381
  const internal = code === 1011 || code >= 500 && code <= 599;
379
382
  const reason = isProduction() && internal ? OPAQUE_SERVER_ERROR : httpError.message;
@@ -392,20 +395,20 @@ var DefaultErrorRenderer = class {
392
395
  }
393
396
  if (isUniqueViolation(error)) {
394
397
  const conflict = new nodefonyError(UNIQUE_VIOLATION_MESSAGE, CONFLICT_STATUS);
395
- conflict.stack = error.stack;
398
+ if (error.stack !== void 0) conflict.stack = error.stack;
396
399
  return new HttpError(conflict, CONFLICT_STATUS, context);
397
400
  }
398
401
  const referencedBy = referencedByOf(error);
399
402
  if (referencedBy !== null) {
400
403
  const conflict = new nodefonyError(`Conflict — this resource is still referenced by ${referencedBy}`, CONFLICT_STATUS);
401
- conflict.stack = error.stack;
404
+ if (error.stack !== void 0) conflict.stack = error.stack;
402
405
  return new HttpError(conflict, CONFLICT_STATUS, context);
403
406
  }
404
407
  const malformed = malformedIdentifierOf(error);
405
408
  if (malformed !== null) {
406
409
  const status = malformedIdentifierStatus(malformed, context?.url);
407
410
  const refused = new nodefonyError(status === 404 ? "Not Found — this identifier cannot designate any resource" : "Bad Request — malformed identifier", status);
408
- refused.stack = error.stack;
411
+ if (error.stack !== void 0) refused.stack = error.stack;
409
412
  return new HttpError(refused, status, context);
410
413
  }
411
414
  const code = error.code;