@velajs/errors 1.1.0 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Publish from the unified packages workspace with corrected repository paths, shared native tooling, TypeScript 7 checks, and npm OIDC releases. Runnable examples now live in apps/.
8
+
9
+ ## 2.0.0
10
+
11
+ Coordinated Vela 2.0 toolchain and package release. The standalone error behavior is unchanged.
12
+
13
+ Requires the coordinated Vela 2.0 package set. See the workspace migration guide.
14
+
3
15
  ## 1.1.0
4
16
 
5
17
  ### Minor Changes
@@ -15,7 +15,7 @@
15
15
  * content-addressing and grouping keys. Never use it as a MAC, a password hash,
16
16
  * or any other authentication or security primitive.
17
17
  */
18
- declare const sha256Hex: (input: string) => string;
18
+ export declare const sha256Hex: (input: string) => string;
19
19
  //#endregion
20
20
  //#region src/fingerprint.d.ts
21
21
  /**
@@ -25,16 +25,16 @@ declare const sha256Hex: (input: string) => string;
25
25
  * persists fingerprints stores this alongside each hash to know which
26
26
  * generation produced it and when a recompute/backfill is due.
27
27
  */
28
- declare const FINGERPRINT_VERSION: number;
28
+ export declare const FINGERPRINT_VERSION: number;
29
29
  /**
30
30
  * Normalize an error message into its grouping bucket: strip per-occurrence
31
31
  * noise (urls, uuids, ips, request/filesystem paths, long ids, numbers) and
32
32
  * fold whitespace/case, so occurrences of the same logical error collapse to
33
33
  * one bucket. Exported for direct testing of the normalization heuristics.
34
34
  */
35
- declare const bucketMessage: (message: string) => string;
35
+ export declare const bucketMessage: (message: string) => string;
36
36
  /** Everything a fingerprint source can supply. */
37
- interface ErrorFingerprintInput {
37
+ export interface ErrorFingerprintInput {
38
38
  /**
39
39
  * What raised the error — the invoked function/route path, e.g.
40
40
  * `messages:list`. Part of the grouping key.
@@ -55,7 +55,6 @@ interface ErrorFingerprintInput {
55
55
  * `functionPath` and logically-equal `message` always yield the same hash
56
56
  * regardless of the `code` supplied or per-occurrence noise in the message.
57
57
  */
58
- declare const fingerprintError: (input: ErrorFingerprintInput) => string;
58
+ export declare const fingerprintError: (input: ErrorFingerprintInput) => string;
59
59
  //#endregion
60
- export { ErrorFingerprintInput, FINGERPRINT_VERSION, bucketMessage, fingerprintError, sha256Hex };
61
60
  //# sourceMappingURL=fingerprint.d.ts.map
@@ -161,7 +161,8 @@ const bucketMessage = (message) => {
161
161
  */
162
162
  const fingerprintError = (input) => {
163
163
  const source = input.functionPath.length > 0 ? input.functionPath : "unknown";
164
- return sha256Hex(`${SCHEME}\n${source}\n${bucketMessage(input.message)}`).slice(0, HASH_LENGTH);
164
+ const canonical = `${SCHEME}\n${source}\n${bucketMessage(input.message)}`;
165
+ return sha256Hex(canonical).slice(0, HASH_LENGTH);
165
166
  };
166
167
  //#endregion
167
168
  export { FINGERPRINT_VERSION, bucketMessage, fingerprintError, sha256Hex };
@@ -1 +1 @@
1
- {"version":3,"file":"fingerprint.js","names":[],"sources":["../src/sha256.ts","../src/fingerprint.ts"],"sourcesContent":["/**\n * Portable, synchronous SHA-256 (FIPS 180-4) returning a lowercase hex digest.\n *\n * Implemented straight from the published standard so this file carries no\n * runtime dependency and produces byte-for-byte identical output on every\n * target Vela supports — browsers, the Cloudflare Workers (workerd) runtime,\n * and Node. Neither built-in alternative fits the fingerprinter:\n * - `node:crypto` (`createHash`) is absent in browsers and needs\n * `nodejs_compat` to load in workerd.\n * - `crypto.subtle.digest` is async, which is clumsy for folding error rows\n * into a group key one synchronous call at a time.\n *\n * SECURITY: this is a plain, non-constant-time digest meant ONLY for\n * content-addressing and grouping keys. Never use it as a MAC, a password hash,\n * or any other authentication or security primitive.\n */\n\nconst BLOCK_BYTES = 64;\n\n// Round constants K[0..63]: the first 32 bits of the fractional parts of the\n// cube roots of the first 64 primes (FIPS 180-4 §4.2.2).\nconst ROUND = Uint32Array.of(\n 0x428a2f98,\n 0x71374491,\n 0xb5c0fbcf,\n 0xe9b5dba5,\n 0x3956c25b,\n 0x59f111f1,\n 0x923f82a4,\n 0xab1c5ed5,\n 0xd807aa98,\n 0x12835b01,\n 0x243185be,\n 0x550c7dc3,\n 0x72be5d74,\n 0x80deb1fe,\n 0x9bdc06a7,\n 0xc19bf174,\n 0xe49b69c1,\n 0xefbe4786,\n 0x0fc19dc6,\n 0x240ca1cc,\n 0x2de92c6f,\n 0x4a7484aa,\n 0x5cb0a9dc,\n 0x76f988da,\n 0x983e5152,\n 0xa831c66d,\n 0xb00327c8,\n 0xbf597fc7,\n 0xc6e00bf3,\n 0xd5a79147,\n 0x06ca6351,\n 0x14292967,\n 0x27b70a85,\n 0x2e1b2138,\n 0x4d2c6dfc,\n 0x53380d13,\n 0x650a7354,\n 0x766a0abb,\n 0x81c2c92e,\n 0x92722c85,\n 0xa2bfe8a1,\n 0xa81a664b,\n 0xc24b8b70,\n 0xc76c51a3,\n 0xd192e819,\n 0xd6990624,\n 0xf40e3585,\n 0x106aa070,\n 0x19a4c116,\n 0x1e376c08,\n 0x2748774c,\n 0x34b0bcb5,\n 0x391c0cb3,\n 0x4ed8aa4a,\n 0x5b9cca4f,\n 0x682e6ff3,\n 0x748f82ee,\n 0x78a5636f,\n 0x84c87814,\n 0x8cc70208,\n 0x90befffa,\n 0xa4506ceb,\n 0xbef9a3f7,\n 0xc67178f2,\n);\n\nconst rotr = (word: number, bits: number): number => (word >>> bits) | (word << (32 - bits));\n\nconst toHex8 = (word: number): string => (word >>> 0).toString(16).padStart(8, '0');\n\nexport const sha256Hex = (input: string): string => {\n const message = new TextEncoder().encode(input);\n const bitLength = message.length * 8;\n\n // One 0x80 marker byte, an 8-byte length trailer, zero fill in between, all\n // rounded up to whole 64-byte blocks.\n const totalBytes = (Math.floor((message.length + 8) / BLOCK_BYTES) + 1) * BLOCK_BYTES;\n const padded = new Uint8Array(totalBytes);\n padded.set(message);\n padded[message.length] = 0x80;\n\n const frame = new DataView(padded.buffer);\n // Big-endian 64-bit bit length in the final 8 bytes. Fingerprint inputs are\n // short strings, so the high word stays zero in practice, but compute it\n // anyway for correctness.\n frame.setUint32(totalBytes - 8, Math.floor(bitLength / 0x1_0000_0000), false);\n frame.setUint32(totalBytes - 4, bitLength >>> 0, false);\n\n // Initial state H[0..7]: first 32 bits of the fractional parts of the square\n // roots of the first 8 primes (FIPS 180-4 §5.3.3).\n let h0 = 0x6a09e667;\n let h1 = 0xbb67ae85;\n let h2 = 0x3c6ef372;\n let h3 = 0xa54ff53a;\n let h4 = 0x510e527f;\n let h5 = 0x9b05688c;\n let h6 = 0x1f83d9ab;\n let h7 = 0x5be0cd19;\n\n const schedule = new Uint32Array(64);\n\n for (let base = 0; base < totalBytes; base += BLOCK_BYTES) {\n for (let t = 0; t < 16; t += 1) {\n schedule[t] = frame.getUint32(base + t * 4, false);\n }\n for (let t = 16; t < 64; t += 1) {\n const x = schedule[t - 15] as number;\n const y = schedule[t - 2] as number;\n const sigma0 = rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3);\n const sigma1 = rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10);\n schedule[t] =\n ((schedule[t - 16] as number) + sigma0 + (schedule[t - 7] as number) + sigma1) >>> 0;\n }\n\n let a = h0;\n let b = h1;\n let c = h2;\n let d = h3;\n let e = h4;\n let f = h5;\n let g = h6;\n let h = h7;\n\n for (let t = 0; t < 64; t += 1) {\n const bigSigma1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);\n const choose = (e & f) ^ (~e & g);\n const t1 = (h + bigSigma1 + choose + (ROUND[t] as number) + (schedule[t] as number)) >>> 0;\n const bigSigma0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const t2 = (bigSigma0 + majority) >>> 0;\n\n h = g;\n g = f;\n f = e;\n e = (d + t1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (t1 + t2) >>> 0;\n }\n\n h0 = (h0 + a) >>> 0;\n h1 = (h1 + b) >>> 0;\n h2 = (h2 + c) >>> 0;\n h3 = (h3 + d) >>> 0;\n h4 = (h4 + e) >>> 0;\n h5 = (h5 + f) >>> 0;\n h6 = (h6 + g) >>> 0;\n h7 = (h7 + h) >>> 0;\n }\n\n return (\n toHex8(h0) +\n toHex8(h1) +\n toHex8(h2) +\n toHex8(h3) +\n toHex8(h4) +\n toHex8(h5) +\n toHex8(h6) +\n toHex8(h7)\n );\n};\n","/**\n * `@velajs/errors/fingerprint` — zero-dependency, cross-runtime error grouping.\n *\n * Collapses noisy repeats of the same error into one stable \"issue\" identity: a\n * 16-hex-character hash over the function path plus a *normalized* message, so a\n * live in-flight error and one recomputed later from a persisted log row fold\n * onto the same fingerprint. The machine `code` rides along as metadata and is\n * deliberately excluded from the hash, so the redacted wire view produced by\n * `toErrorBody` and the raw server-side error group together.\n *\n * The digest is a portable synchronous SHA-256 (see `./sha256`) truncated to 16\n * hex chars — content-addressing only, never a security or MAC primitive.\n *\n * The message-normalization / grouping approach is inspired by\n * `@superlog/fingerprint` (Apache-2.0); this is an independent implementation.\n */\nimport { sha256Hex } from './sha256';\n\n/**\n * Heuristic generation of {@link bucketMessage}. Bump it whenever the\n * normalizer's rules change: changed heuristics re-partition history (they may\n * split one group into several or merge several into one), so a consumer that\n * persists fingerprints stores this alongside each hash to know which\n * generation produced it and when a recompute/backfill is due.\n */\nexport const FINGERPRINT_VERSION: number = 1;\n\n/**\n * Upper bound on the raw message length fed to the normalizer's regexes. A few\n * of them (the email and long-run patterns especially) can backtrack\n * super-linearly on a long delimiter-free run, and an error message can carry\n * attacker-influenced input of unbounded size — so clamp first to keep the\n * regex work bounded (a ReDoS guard). The final bucket is capped far below this\n * anyway, so the clamp is transparent for any real message.\n */\nconst MAX_INPUT_LENGTH = 1024;\n\n/** Cap on the normalized bucket so one runaway message can't bloat the key. */\nconst MAX_BUCKET_LENGTH = 160;\n\n/** Hex length of the truncated digest used as the grouping id. */\nconst HASH_LENGTH = 16;\n\n/** Namespaces the hash so a fingerprint can't collide with another content hash. */\nconst SCHEME = 'velajs.errors/fingerprint';\n\n/**\n * Ordered noise-stripping rules applied to a message before hashing. Each\n * replaces a class of per-occurrence identifier with a stable placeholder, so\n * two errors that differ only in their variable parts land in the same bucket.\n * Order matters: broader shapes (urls, timestamps) run before the greedy\n * numeric/id sweeps that would otherwise chew their digits.\n */\nconst NOISE_RULES: readonly (readonly [RegExp, string])[] = [\n // Whole URLs (before path/number rules can nibble their insides).\n [/https?:\\/\\/\\S+/gi, '[url]'],\n // Email addresses.\n [/\\b[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}\\b/gi, '[email]'],\n // RFC-4122 UUIDs.\n [/\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/gi, '[uuid]'],\n // ISO-8601-ish timestamps (before the ip/number rules touch their digits).\n [/\\b\\d{4}-\\d{2}-\\d{2}[t ][\\d:.]+z?(?:[+-]\\d{2}:?\\d{2})?\\b/gi, '[time]'],\n // IPv4 addresses with an optional port.\n [/\\b(?:\\d{1,3}\\.){3}\\d{1,3}(?::\\d+)?\\b/g, '[ip]'],\n // Unix-style request/filesystem paths at a token boundary — keeps in-word\n // slashes (client/server, and/or) intact while folding a scanner's probed\n // paths (/wp-admin, /.env, /.git/config) onto one placeholder.\n [/(^|\\s)\\/\\S*/g, '$1[path]'],\n // Windows drive-letter paths.\n [/\\b[a-z]:\\\\[^\\s]*/gi, '[path]'],\n // Hex literals (0x…).\n [/\\b0x[0-9a-f]+\\b/gi, '[hex]'],\n // Long opaque ids: tokens, hashes, base-ish ids. Threshold high enough to\n // spare ordinary words while catching machine identifiers.\n [/\\b[a-z0-9_]{20,}\\b/gi, '[id]'],\n // Any remaining bare integer.\n [/\\b\\d+\\b/g, '[num]'],\n];\n\n/**\n * Normalize an error message into its grouping bucket: strip per-occurrence\n * noise (urls, uuids, ips, request/filesystem paths, long ids, numbers) and\n * fold whitespace/case, so occurrences of the same logical error collapse to\n * one bucket. Exported for direct testing of the normalization heuristics.\n */\nexport const bucketMessage = (message: string): string => {\n if (message.length === 0) {\n return '';\n }\n\n let text = message.length > MAX_INPUT_LENGTH ? message.slice(0, MAX_INPUT_LENGTH) : message;\n\n for (const [pattern, placeholder] of NOISE_RULES) {\n text = text.replace(pattern, placeholder);\n }\n\n text = text.replace(/\\s+/g, ' ').trim().toLowerCase();\n\n return text.length > MAX_BUCKET_LENGTH ? text.slice(0, MAX_BUCKET_LENGTH) : text;\n};\n\n/** Everything a fingerprint source can supply. */\nexport interface ErrorFingerprintInput {\n /**\n * What raised the error — the invoked function/route path, e.g.\n * `messages:list`. Part of the grouping key.\n */\n functionPath: string;\n /** Human-readable message (may embed user input); normalized into the key. */\n message: string;\n /**\n * Machine error code, when known. Pure metadata: **never folded into the\n * hash**, so the redacted wire error (which may rewrite or drop the code) and\n * the raw server-side error still produce the same fingerprint.\n */\n code?: string;\n}\n\n/**\n * Fold an error into its stable 16-hex-character grouping fingerprint. Pure and\n * synchronous, safe to call per row when grouping a log page. The same\n * `functionPath` and logically-equal `message` always yield the same hash\n * regardless of the `code` supplied or per-occurrence noise in the message.\n */\nexport const fingerprintError = (input: ErrorFingerprintInput): string => {\n const source = input.functionPath.length > 0 ? input.functionPath : 'unknown';\n const canonical = `${SCHEME}\\n${source}\\n${bucketMessage(input.message)}`;\n return sha256Hex(canonical).slice(0, HASH_LENGTH);\n};\n\nexport { sha256Hex } from './sha256';\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,cAAc;AAIpB,MAAM,QAAQ,YAAY,GACxB,YACA,YACA,YACA,YACA,WACA,YACA,YACA,YACA,YACA,WACA,WACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,WACA,WACA,WACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,WACA,WACA,WACA,WACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,WACA,WACA,WACA,WACA,WACA,WACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,YACA,UACF;AAEA,MAAM,QAAQ,MAAc,SAA0B,SAAS,OAAS,QAAS,KAAK;AAEtF,MAAM,UAAU,UAA0B,SAAS,EAAA,CAAG,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;AAElF,MAAa,aAAa,UAA0B;CAClD,MAAM,UAAU,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK;CAC9C,MAAM,YAAY,QAAQ,SAAS;CAInC,MAAM,cAAc,KAAK,OAAO,QAAQ,SAAS,KAAK,WAAW,IAAI,KAAK;CAC1E,MAAM,SAAS,IAAI,WAAW,UAAU;CACxC,OAAO,IAAI,OAAO;CAClB,OAAO,QAAQ,UAAU;CAEzB,MAAM,QAAQ,IAAI,SAAS,OAAO,MAAM;CAIxC,MAAM,UAAU,aAAa,GAAG,KAAK,MAAM,YAAY,UAAa,GAAG,KAAK;CAC5E,MAAM,UAAU,aAAa,GAAG,cAAc,GAAG,KAAK;CAItD,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,KAAK;CAET,MAAM,2BAAW,IAAI,YAAY,EAAE;CAEnC,KAAK,IAAI,OAAO,GAAG,OAAO,YAAY,QAAQ,aAAa;EACzD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,GAC3B,SAAS,KAAK,MAAM,UAAU,OAAO,IAAI,GAAG,KAAK;EAEnD,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG;GAC/B,MAAM,IAAI,SAAS,IAAI;GACvB,MAAM,IAAI,SAAS,IAAI;GACvB,MAAM,SAAS,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAK,MAAM;GACjD,MAAM,SAAS,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,IAAK,MAAM;GAClD,SAAS,KACL,SAAS,IAAI,MAAiB,SAAU,SAAS,IAAI,KAAgB,WAAY;EACvF;EAEA,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EACR,IAAI,IAAI;EAER,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,GAAG;GAC9B,MAAM,YAAY,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE;GACvD,MAAM,SAAU,IAAI,IAAM,CAAC,IAAI;GAC/B,MAAM,KAAM,IAAI,YAAY,SAAU,MAAM,KAAiB,SAAS,OAAmB;GAGzF,MAAM,MAFY,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,MACrC,IAAI,IAAM,IAAI,IAAM,IAAI,OACJ;GAEtC,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAK,IAAI,OAAQ;GACjB,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAK,KAAK,OAAQ;EACpB;EAEA,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;EAClB,KAAM,KAAK,MAAO;CACpB;CAEA,OACE,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE,IACT,OAAO,EAAE;AAEb;;;;;;;;;;;;;;;;;;;;;;;;;;AC9JA,MAAa,sBAA8B;;;;;;;;;AAU3C,MAAM,mBAAmB;;AAGzB,MAAM,oBAAoB;;AAG1B,MAAM,cAAc;;AAGpB,MAAM,SAAS;;;;;;;;AASf,MAAM,cAAsD;CAE1D,CAAC,oBAAoB,OAAO;CAE5B,CAAC,qCAAqC,SAAS;CAE/C,CAAC,sEAAsE,QAAQ;CAE/E,CAAC,6DAA6D,QAAQ;CAEtE,CAAC,yCAAyC,MAAM;CAIhD,CAAC,gBAAgB,UAAU;CAE3B,CAAC,sBAAsB,QAAQ;CAE/B,CAAC,qBAAqB,OAAO;CAG7B,CAAC,wBAAwB,MAAM;CAE/B,CAAC,YAAY,OAAO;AACtB;;;;;;;AAQA,MAAa,iBAAiB,YAA4B;CACxD,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,IAAI,OAAO,QAAQ,SAAS,mBAAmB,QAAQ,MAAM,GAAG,gBAAgB,IAAI;CAEpF,KAAK,MAAM,CAAC,SAAS,gBAAgB,aACnC,OAAO,KAAK,QAAQ,SAAS,WAAW;CAG1C,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;CAEpD,OAAO,KAAK,SAAS,oBAAoB,KAAK,MAAM,GAAG,iBAAiB,IAAI;AAC9E;;;;;;;AAyBA,MAAa,oBAAoB,UAAyC;CACxE,MAAM,SAAS,MAAM,aAAa,SAAS,IAAI,MAAM,eAAe;CAEpE,OAAO,UAAU,GADI,OAAO,IAAI,OAAO,IAAI,cAAc,MAAM,OAAO,GAC5C,CAAC,CAAC,MAAM,GAAG,WAAW;AAClD"}
1
+ {"version":3,"file":"fingerprint.js","names":[],"sources":["../src/sha256.ts","../src/fingerprint.ts"],"sourcesContent":["/**\n * Portable, synchronous SHA-256 (FIPS 180-4) returning a lowercase hex digest.\n *\n * Implemented straight from the published standard so this file carries no\n * runtime dependency and produces byte-for-byte identical output on every\n * target Vela supports — browsers, the Cloudflare Workers (workerd) runtime,\n * and Node. Neither built-in alternative fits the fingerprinter:\n * - `node:crypto` (`createHash`) is absent in browsers and needs\n * `nodejs_compat` to load in workerd.\n * - `crypto.subtle.digest` is async, which is clumsy for folding error rows\n * into a group key one synchronous call at a time.\n *\n * SECURITY: this is a plain, non-constant-time digest meant ONLY for\n * content-addressing and grouping keys. Never use it as a MAC, a password hash,\n * or any other authentication or security primitive.\n */\n\nconst BLOCK_BYTES = 64;\n\n// Round constants K[0..63]: the first 32 bits of the fractional parts of the\n// cube roots of the first 64 primes (FIPS 180-4 §4.2.2).\nconst ROUND = Uint32Array.of(\n 0x428a2f98,\n 0x71374491,\n 0xb5c0fbcf,\n 0xe9b5dba5,\n 0x3956c25b,\n 0x59f111f1,\n 0x923f82a4,\n 0xab1c5ed5,\n 0xd807aa98,\n 0x12835b01,\n 0x243185be,\n 0x550c7dc3,\n 0x72be5d74,\n 0x80deb1fe,\n 0x9bdc06a7,\n 0xc19bf174,\n 0xe49b69c1,\n 0xefbe4786,\n 0x0fc19dc6,\n 0x240ca1cc,\n 0x2de92c6f,\n 0x4a7484aa,\n 0x5cb0a9dc,\n 0x76f988da,\n 0x983e5152,\n 0xa831c66d,\n 0xb00327c8,\n 0xbf597fc7,\n 0xc6e00bf3,\n 0xd5a79147,\n 0x06ca6351,\n 0x14292967,\n 0x27b70a85,\n 0x2e1b2138,\n 0x4d2c6dfc,\n 0x53380d13,\n 0x650a7354,\n 0x766a0abb,\n 0x81c2c92e,\n 0x92722c85,\n 0xa2bfe8a1,\n 0xa81a664b,\n 0xc24b8b70,\n 0xc76c51a3,\n 0xd192e819,\n 0xd6990624,\n 0xf40e3585,\n 0x106aa070,\n 0x19a4c116,\n 0x1e376c08,\n 0x2748774c,\n 0x34b0bcb5,\n 0x391c0cb3,\n 0x4ed8aa4a,\n 0x5b9cca4f,\n 0x682e6ff3,\n 0x748f82ee,\n 0x78a5636f,\n 0x84c87814,\n 0x8cc70208,\n 0x90befffa,\n 0xa4506ceb,\n 0xbef9a3f7,\n 0xc67178f2,\n);\n\nconst rotr = (word: number, bits: number): number => (word >>> bits) | (word << (32 - bits));\n\nconst toHex8 = (word: number): string => (word >>> 0).toString(16).padStart(8, '0');\n\nexport const sha256Hex = (input: string): string => {\n const message = new TextEncoder().encode(input);\n const bitLength = message.length * 8;\n\n // One 0x80 marker byte, an 8-byte length trailer, zero fill in between, all\n // rounded up to whole 64-byte blocks.\n const totalBytes = (Math.floor((message.length + 8) / BLOCK_BYTES) + 1) * BLOCK_BYTES;\n const padded = new Uint8Array(totalBytes);\n padded.set(message);\n padded[message.length] = 0x80;\n\n const frame = new DataView(padded.buffer);\n // Big-endian 64-bit bit length in the final 8 bytes. Fingerprint inputs are\n // short strings, so the high word stays zero in practice, but compute it\n // anyway for correctness.\n frame.setUint32(totalBytes - 8, Math.floor(bitLength / 0x1_0000_0000), false);\n frame.setUint32(totalBytes - 4, bitLength >>> 0, false);\n\n // Initial state H[0..7]: first 32 bits of the fractional parts of the square\n // roots of the first 8 primes (FIPS 180-4 §5.3.3).\n let h0 = 0x6a09e667;\n let h1 = 0xbb67ae85;\n let h2 = 0x3c6ef372;\n let h3 = 0xa54ff53a;\n let h4 = 0x510e527f;\n let h5 = 0x9b05688c;\n let h6 = 0x1f83d9ab;\n let h7 = 0x5be0cd19;\n\n const schedule = new Uint32Array(64);\n\n for (let base = 0; base < totalBytes; base += BLOCK_BYTES) {\n for (let t = 0; t < 16; t += 1) {\n schedule[t] = frame.getUint32(base + t * 4, false);\n }\n for (let t = 16; t < 64; t += 1) {\n const x = schedule[t - 15] as number;\n const y = schedule[t - 2] as number;\n const sigma0 = rotr(x, 7) ^ rotr(x, 18) ^ (x >>> 3);\n const sigma1 = rotr(y, 17) ^ rotr(y, 19) ^ (y >>> 10);\n schedule[t] =\n ((schedule[t - 16] as number) + sigma0 + (schedule[t - 7] as number) + sigma1) >>> 0;\n }\n\n let a = h0;\n let b = h1;\n let c = h2;\n let d = h3;\n let e = h4;\n let f = h5;\n let g = h6;\n let h = h7;\n\n for (let t = 0; t < 64; t += 1) {\n const bigSigma1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);\n const choose = (e & f) ^ (~e & g);\n const t1 = (h + bigSigma1 + choose + (ROUND[t] as number) + (schedule[t] as number)) >>> 0;\n const bigSigma0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const t2 = (bigSigma0 + majority) >>> 0;\n\n h = g;\n g = f;\n f = e;\n e = (d + t1) >>> 0;\n d = c;\n c = b;\n b = a;\n a = (t1 + t2) >>> 0;\n }\n\n h0 = (h0 + a) >>> 0;\n h1 = (h1 + b) >>> 0;\n h2 = (h2 + c) >>> 0;\n h3 = (h3 + d) >>> 0;\n h4 = (h4 + e) >>> 0;\n h5 = (h5 + f) >>> 0;\n h6 = (h6 + g) >>> 0;\n h7 = (h7 + h) >>> 0;\n }\n\n return (\n toHex8(h0) +\n toHex8(h1) +\n toHex8(h2) +\n toHex8(h3) +\n toHex8(h4) +\n toHex8(h5) +\n toHex8(h6) +\n toHex8(h7)\n );\n};\n","/**\n * `@velajs/errors/fingerprint` — zero-dependency, cross-runtime error grouping.\n *\n * Collapses noisy repeats of the same error into one stable \"issue\" identity: a\n * 16-hex-character hash over the function path plus a *normalized* message, so a\n * live in-flight error and one recomputed later from a persisted log row fold\n * onto the same fingerprint. The machine `code` rides along as metadata and is\n * deliberately excluded from the hash, so the redacted wire view produced by\n * `toErrorBody` and the raw server-side error group together.\n *\n * The digest is a portable synchronous SHA-256 (see `./sha256`) truncated to 16\n * hex chars — content-addressing only, never a security or MAC primitive.\n *\n * The message-normalization / grouping approach is inspired by\n * `@superlog/fingerprint` (Apache-2.0); this is an independent implementation.\n */\nimport { sha256Hex } from './sha256';\n\n/**\n * Heuristic generation of {@link bucketMessage}. Bump it whenever the\n * normalizer's rules change: changed heuristics re-partition history (they may\n * split one group into several or merge several into one), so a consumer that\n * persists fingerprints stores this alongside each hash to know which\n * generation produced it and when a recompute/backfill is due.\n */\nexport const FINGERPRINT_VERSION: number = 1;\n\n/**\n * Upper bound on the raw message length fed to the normalizer's regexes. A few\n * of them (the email and long-run patterns especially) can backtrack\n * super-linearly on a long delimiter-free run, and an error message can carry\n * attacker-influenced input of unbounded size — so clamp first to keep the\n * regex work bounded (a ReDoS guard). The final bucket is capped far below this\n * anyway, so the clamp is transparent for any real message.\n */\nconst MAX_INPUT_LENGTH = 1024;\n\n/** Cap on the normalized bucket so one runaway message can't bloat the key. */\nconst MAX_BUCKET_LENGTH = 160;\n\n/** Hex length of the truncated digest used as the grouping id. */\nconst HASH_LENGTH = 16;\n\n/** Namespaces the hash so a fingerprint can't collide with another content hash. */\nconst SCHEME = 'velajs.errors/fingerprint';\n\n/**\n * Ordered noise-stripping rules applied to a message before hashing. Each\n * replaces a class of per-occurrence identifier with a stable placeholder, so\n * two errors that differ only in their variable parts land in the same bucket.\n * Order matters: broader shapes (urls, timestamps) run before the greedy\n * numeric/id sweeps that would otherwise chew their digits.\n */\nconst NOISE_RULES: readonly (readonly [RegExp, string])[] = [\n // Whole URLs (before path/number rules can nibble their insides).\n [/https?:\\/\\/\\S+/gi, '[url]'],\n // Email addresses.\n [/\\b[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}\\b/gi, '[email]'],\n // RFC-4122 UUIDs.\n [/\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/gi, '[uuid]'],\n // ISO-8601-ish timestamps (before the ip/number rules touch their digits).\n [/\\b\\d{4}-\\d{2}-\\d{2}[t ][\\d:.]+z?(?:[+-]\\d{2}:?\\d{2})?\\b/gi, '[time]'],\n // IPv4 addresses with an optional port.\n [/\\b(?:\\d{1,3}\\.){3}\\d{1,3}(?::\\d+)?\\b/g, '[ip]'],\n // Unix-style request/filesystem paths at a token boundary — keeps in-word\n // slashes (client/server, and/or) intact while folding a scanner's probed\n // paths (/wp-admin, /.env, /.git/config) onto one placeholder.\n [/(^|\\s)\\/\\S*/g, '$1[path]'],\n // Windows drive-letter paths.\n [/\\b[a-z]:\\\\[^\\s]*/gi, '[path]'],\n // Hex literals (0x…).\n [/\\b0x[0-9a-f]+\\b/gi, '[hex]'],\n // Long opaque ids: tokens, hashes, base-ish ids. Threshold high enough to\n // spare ordinary words while catching machine identifiers.\n [/\\b[a-z0-9_]{20,}\\b/gi, '[id]'],\n // Any remaining bare integer.\n [/\\b\\d+\\b/g, '[num]'],\n];\n\n/**\n * Normalize an error message into its grouping bucket: strip per-occurrence\n * noise (urls, uuids, ips, request/filesystem paths, long ids, numbers) and\n * fold whitespace/case, so occurrences of the same logical error collapse to\n * one bucket. Exported for direct testing of the normalization heuristics.\n */\nexport const bucketMessage = (message: string): string => {\n if (message.length === 0) {\n return '';\n }\n\n let text = message.length > MAX_INPUT_LENGTH ? message.slice(0, MAX_INPUT_LENGTH) : message;\n\n for (const [pattern, placeholder] of NOISE_RULES) {\n text = text.replace(pattern, placeholder);\n }\n\n text = text.replace(/\\s+/g, ' ').trim().toLowerCase();\n\n return text.length > MAX_BUCKET_LENGTH ? text.slice(0, MAX_BUCKET_LENGTH) : text;\n};\n\n/** Everything a fingerprint source can supply. */\nexport interface ErrorFingerprintInput {\n /**\n * What raised the error — the invoked function/route path, e.g.\n * `messages:list`. Part of the grouping key.\n */\n functionPath: string;\n /** Human-readable message (may embed user input); normalized into the key. */\n message: string;\n /**\n * Machine error code, when known. Pure metadata: **never folded into the\n * hash**, so the redacted wire error (which may rewrite or drop the code) and\n * the raw server-side error still produce the same fingerprint.\n */\n code?: string;\n}\n\n/**\n * Fold an error into its stable 16-hex-character grouping fingerprint. Pure and\n * synchronous, safe to call per row when grouping a log page. The same\n * `functionPath` and logically-equal `message` always yield the same hash\n * regardless of the `code` supplied or per-occurrence noise in the message.\n */\nexport const fingerprintError = (input: ErrorFingerprintInput): string => {\n const source = input.functionPath.length > 0 ? input.functionPath : 'unknown';\n const canonical = `${SCHEME}\\n${source}\\n${bucketMessage(input.message)}`;\n return sha256Hex(canonical).slice(0, HASH_LENGTH);\n};\n\nexport { sha256Hex } from './sha256';\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ interface ErrorCatalogEntry {
7
7
  /** Redaction posture: true → message/hint/data are never echoed to clients. */
8
8
  internal?: boolean;
9
9
  }
10
- declare const CORE_ENTRIES: {
10
+ export declare const CORE_ENTRIES: {
11
11
  readonly bad_request: {
12
12
  readonly status: 400;
13
13
  readonly title: 'Bad Request';
@@ -91,7 +91,7 @@ interface VelaErrorOptions {
91
91
  * serialization path. `type` is the brand `isVelaError` checks — it must
92
92
  * survive serialization, which own+enumerable guarantees.
93
93
  */
94
- declare class VelaError extends Error {
94
+ export declare class VelaError extends Error {
95
95
  readonly type = "VelaError";
96
96
  readonly code: string;
97
97
  readonly status: number;
@@ -112,10 +112,10 @@ interface Catalog<C extends string = string> {
112
112
  has(code: string): boolean;
113
113
  get(code: string): ErrorCatalogEntry | undefined;
114
114
  }
115
- declare const defineErrorCatalog: <const T extends Record<string, ErrorCatalogEntry>>(entries: T) => Catalog<Extract<keyof T, string>>;
116
- declare const composeCatalogs: (...catalogs: Array<Catalog<string>>) => Catalog<string>;
117
- declare const CORE_CATALOG: Catalog<CoreErrorCode>;
118
- declare const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>>;
115
+ export declare const defineErrorCatalog: <const T extends Record<string, ErrorCatalogEntry>>(entries: T) => Catalog<Extract<keyof T, string>>;
116
+ export declare const composeCatalogs: (...catalogs: Array<Catalog<string>>) => Catalog<string>;
117
+ export declare const CORE_CATALOG: Catalog<CoreErrorCode>;
118
+ export declare const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>>;
119
119
  //#endregion
120
120
  //#region src/guard.d.ts
121
121
  /**
@@ -133,7 +133,7 @@ interface VelaErrorLike extends Error {
133
133
  docsUrl?: string;
134
134
  data?: unknown;
135
135
  }
136
- declare const isVelaError: (error: unknown) => error is VelaErrorLike;
136
+ export declare const isVelaError: (error: unknown) => error is VelaErrorLike;
137
137
  //#endregion
138
138
  //#region src/to-error-body.d.ts
139
139
  interface WireErrorObject {
@@ -168,12 +168,12 @@ interface ToErrorBodyOptions {
168
168
  * identically everywhere. `redacted: true` is the caller's signal to log the
169
169
  * raw error server-side — this function never logs (zero-dep purity).
170
170
  */
171
- declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ErrorBodyResult;
171
+ export declare const toErrorBody: (error: unknown, options?: ToErrorBodyOptions) => ErrorBodyResult;
172
172
  //#endregion
173
173
  //#region src/invariant.d.ts
174
174
  /** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */
175
- declare function invariant(condition: unknown, message: string, data?: unknown): asserts condition;
176
- declare function unreachable(value: never, message?: string): never;
175
+ export declare function invariant(condition: unknown, message: string, data?: unknown): asserts condition;
176
+ export declare function unreachable(value: never, message?: string): never;
177
177
  //#endregion
178
- export { CORE_CATALOG, CORE_ENTRIES, type Catalog, type CoreErrorCode, type ErrorBodyResult, type ErrorCatalogEntry, STATUS_TO_CODE, type ToErrorBodyOptions, VelaError, type VelaErrorLike, type VelaErrorOptions, type WireErrorObject, composeCatalogs, defineErrorCatalog, invariant, isVelaError, toErrorBody, unreachable };
178
+ export type { Catalog, CoreErrorCode, ErrorBodyResult, ErrorCatalogEntry, ToErrorBodyOptions, VelaErrorLike, VelaErrorOptions, WireErrorObject };
179
179
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/catalog-data.ts","../src/error.ts","../src/catalog.ts","../src/guard.ts","../src/to-error-body.ts","../src/invariant.ts"],"sourcesContent":["export interface ErrorCatalogEntry {\n status: number;\n title: string;\n hint?: string;\n docsUrl?: string;\n /** Redaction posture: true → message/hint/data are never echoed to clients. */\n internal?: boolean;\n}\n\nexport const CORE_ENTRIES = {\n bad_request: { status: 400, title: 'Bad Request' },\n unauthorized: { status: 401, title: 'Unauthorized' },\n forbidden: { status: 403, title: 'Forbidden' },\n not_found: { status: 404, title: 'Not Found' },\n method_not_allowed: { status: 405, title: 'Method Not Allowed' },\n conflict: { status: 409, title: 'Conflict' },\n gone: { status: 410, title: 'Gone' },\n payload_too_large: { status: 413, title: 'Payload Too Large' },\n unsupported_media_type: { status: 415, title: 'Unsupported Media Type' },\n unprocessable: { status: 422, title: 'Unprocessable Entity' },\n too_many_requests: { status: 429, title: 'Too Many Requests' },\n internal: { status: 500, title: 'Internal Server Error', internal: true },\n not_implemented: { status: 501, title: 'Not Implemented' },\n bad_gateway: { status: 502, title: 'Bad Gateway' },\n service_unavailable: { status: 503, title: 'Service Unavailable' },\n gateway_timeout: { status: 504, title: 'Gateway Timeout' },\n} as const satisfies Record<string, ErrorCatalogEntry>;\n\nexport type CoreErrorCode = keyof typeof CORE_ENTRIES;\n","import { CORE_ENTRIES, type CoreErrorCode } from './catalog-data';\n\nexport interface VelaErrorOptions {\n message?: string;\n status?: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n cause?: unknown;\n}\n\n/**\n * The one Vela error. Every field is an OWN ENUMERABLE property so the error\n * rides any wire codec / structuredClone / DO-RPC prop-copy with no special\n * serialization path. `type` is the brand `isVelaError` checks — it must\n * survive serialization, which own+enumerable guarantees.\n */\nexport class VelaError extends Error {\n readonly type = 'VelaError';\n readonly code: string;\n readonly status: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n readonly data?: unknown;\n\n constructor(code: CoreErrorCode, options?: VelaErrorOptions);\n constructor(code: string, options: VelaErrorOptions & { status: number });\n constructor(code: string, options: VelaErrorOptions = {}) {\n const entry = (\n CORE_ENTRIES as Record<\n string,\n { status: number; title: string; hint?: string; docsUrl?: string }\n >\n )[code];\n super(\n options.message ?? entry?.title ?? code,\n options.cause !== undefined ? { cause: options.cause } : undefined,\n );\n this.name = 'VelaError';\n this.code = code;\n this.status = options.status ?? entry?.status ?? 500;\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n if (hint !== undefined) this.hint = hint;\n if (docsUrl !== undefined) this.docsUrl = docsUrl;\n if (options.data !== undefined) this.data = options.data;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { CORE_ENTRIES, type CoreErrorCode, type ErrorCatalogEntry } from './catalog-data';\nimport { VelaError, type VelaErrorOptions } from './error';\n\nexport type { CoreErrorCode, ErrorCatalogEntry } from './catalog-data';\nexport { CORE_ENTRIES } from './catalog-data';\n\nexport interface Catalog<C extends string = string> {\n readonly entries: Readonly<Record<C, ErrorCatalogEntry>>;\n /** Typed thrower bound to this catalog's defaults. */\n error(code: C | (string & {}), options?: VelaErrorOptions): VelaError;\n has(code: string): boolean;\n get(code: string): ErrorCatalogEntry | undefined;\n}\n\nconst makeCatalog = <C extends string>(\n entries: Readonly<Record<C, ErrorCatalogEntry>>,\n): Catalog<C> => ({\n entries,\n error(code, options = {}) {\n const entry = (entries as Record<string, ErrorCatalogEntry>)[code];\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n return new VelaError(code, {\n ...options,\n status: options.status ?? entry?.status ?? 500,\n ...(hint === undefined ? {} : { hint }),\n ...(docsUrl === undefined ? {} : { docsUrl }),\n });\n },\n has: (code) => Object.hasOwn(entries, code),\n get: (code) =>\n Object.hasOwn(entries, code) ? (entries as Record<string, ErrorCatalogEntry>)[code] : undefined,\n});\n\nexport const defineErrorCatalog = <const T extends Record<string, ErrorCatalogEntry>>(\n entries: T,\n): Catalog<Extract<keyof T, string>> => makeCatalog(entries);\n\nexport const composeCatalogs = (...catalogs: Array<Catalog<string>>): Catalog<string> => {\n const merged: Record<string, ErrorCatalogEntry> = {};\n for (const catalog of catalogs) {\n for (const [code, entry] of Object.entries<ErrorCatalogEntry>(catalog.entries)) {\n if (Object.hasOwn(merged, code)) {\n throw new VelaError('internal', {\n message: `duplicate error code '${code}' while composing catalogs`,\n });\n }\n merged[code] = entry;\n }\n }\n return makeCatalog(merged);\n};\n\nexport const CORE_CATALOG: Catalog<CoreErrorCode> = makeCatalog(CORE_ENTRIES);\n\nexport const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>> = Object.fromEntries(\n (Object.entries(CORE_ENTRIES) as Array<[CoreErrorCode, ErrorCatalogEntry]>).map(([code, e]) => [\n e.status,\n code,\n ]),\n) as Record<number, CoreErrorCode>;\n","/**\n * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable\n * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape\n * check lets foreign driver errors ride the client-echo path. The brand\n * (`type === 'VelaError'`, an own enumerable prop that survives serialization)\n * closes both failure modes. Nothing load-bearing may use `instanceof`.\n */\nexport interface VelaErrorLike extends Error {\n type: 'VelaError';\n code: string;\n status: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n}\n\nexport const isVelaError = (error: unknown): error is VelaErrorLike => {\n if (!(error instanceof Error)) return false;\n const candidate = error as Partial<VelaErrorLike>;\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.status === 'number' &&\n candidate.type === 'VelaError'\n );\n};\n","import { CORE_CATALOG, STATUS_TO_CODE, type Catalog } from './catalog';\nimport { isVelaError } from './guard';\n\nexport interface WireErrorObject {\n code: string;\n message: string;\n hint?: string;\n docsUrl?: string;\n details?: unknown;\n}\n\nexport interface ErrorBodyResult {\n body: { error: WireErrorObject };\n status: number;\n redacted: boolean;\n}\n\nexport interface ToErrorBodyOptions {\n /** Composed catalog; defaults to the core catalog. */\n catalog?: Catalog<string>;\n /** Status used for unbranded errors. Default 500. */\n fallbackStatus?: number;\n redactedMessage?: (status: number) => string;\n /** Injectable wire codec for `data` → `details` (bigint/bytes etc.). */\n encodeData?: (data: unknown) => unknown;\n /** Default true. */\n includeHint?: boolean;\n}\n\nconst defaultRedactedMessage = (status: number, catalog: Catalog<string>): string => {\n const code = STATUS_TO_CODE[status];\n return (code && catalog.get(code)?.title) || 'Internal Server Error';\n};\n\n/**\n * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue\n * reporting) builds its client-bound error content here, so the invariant\n * \"unbranded or internal-coded errors never echo their message\" holds\n * identically everywhere. `redacted: true` is the caller's signal to log the\n * raw error server-side — this function never logs (zero-dep purity).\n */\nexport const toErrorBody = (error: unknown, options: ToErrorBodyOptions = {}): ErrorBodyResult => {\n const catalog = options.catalog ?? CORE_CATALOG;\n const message = options.redactedMessage ?? ((s: number) => defaultRedactedMessage(s, catalog));\n\n const redact = (status: number, code: string): ErrorBodyResult => ({\n body: { error: { code, message: message(status) } },\n status,\n redacted: true,\n });\n\n if (!isVelaError(error)) {\n const status = options.fallbackStatus ?? 500;\n return redact(status, STATUS_TO_CODE[status] ?? 'internal');\n }\n\n const entry = catalog.get(error.code);\n if (error.code === 'internal' || entry?.internal === true) {\n return redact(error.status, error.code);\n }\n\n const wire: WireErrorObject = { code: error.code, message: error.message };\n const hint = error.hint ?? entry?.hint;\n if (options.includeHint !== false && hint !== undefined) wire.hint = hint;\n const docsUrl = error.docsUrl ?? entry?.docsUrl;\n if (docsUrl !== undefined) wire.docsUrl = docsUrl;\n if (error.data !== undefined)\n wire.details = options.encodeData ? options.encodeData(error.data) : error.data;\n return { body: { error: wire }, status: error.status, redacted: false };\n};\n","import { VelaError } from './error';\n\n/** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */\nexport function invariant(condition: unknown, message: string, data?: unknown): asserts condition {\n if (!condition) {\n throw new VelaError('internal', { message: `Invariant violation: ${message}`, data });\n }\n}\n\nexport function unreachable(value: never, message = 'unreachable code reached'): never {\n throw new VelaError('internal', { message, data: { value } });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/catalog-data.ts","../src/error.ts","../src/catalog.ts","../src/guard.ts","../src/to-error-body.ts","../src/invariant.ts"],"sourcesContent":["export interface ErrorCatalogEntry {\n status: number;\n title: string;\n hint?: string;\n docsUrl?: string;\n /** Redaction posture: true → message/hint/data are never echoed to clients. */\n internal?: boolean;\n}\n\nexport const CORE_ENTRIES = {\n bad_request: { status: 400, title: 'Bad Request' },\n unauthorized: { status: 401, title: 'Unauthorized' },\n forbidden: { status: 403, title: 'Forbidden' },\n not_found: { status: 404, title: 'Not Found' },\n method_not_allowed: { status: 405, title: 'Method Not Allowed' },\n conflict: { status: 409, title: 'Conflict' },\n gone: { status: 410, title: 'Gone' },\n payload_too_large: { status: 413, title: 'Payload Too Large' },\n unsupported_media_type: { status: 415, title: 'Unsupported Media Type' },\n unprocessable: { status: 422, title: 'Unprocessable Entity' },\n too_many_requests: { status: 429, title: 'Too Many Requests' },\n internal: { status: 500, title: 'Internal Server Error', internal: true },\n not_implemented: { status: 501, title: 'Not Implemented' },\n bad_gateway: { status: 502, title: 'Bad Gateway' },\n service_unavailable: { status: 503, title: 'Service Unavailable' },\n gateway_timeout: { status: 504, title: 'Gateway Timeout' },\n} as const satisfies Record<string, ErrorCatalogEntry>;\n\nexport type CoreErrorCode = keyof typeof CORE_ENTRIES;\n","import { CORE_ENTRIES, type CoreErrorCode } from './catalog-data';\n\nexport interface VelaErrorOptions {\n message?: string;\n status?: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n cause?: unknown;\n}\n\n/**\n * The one Vela error. Every field is an OWN ENUMERABLE property so the error\n * rides any wire codec / structuredClone / DO-RPC prop-copy with no special\n * serialization path. `type` is the brand `isVelaError` checks — it must\n * survive serialization, which own+enumerable guarantees.\n */\nexport class VelaError extends Error {\n readonly type = 'VelaError';\n readonly code: string;\n readonly status: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n readonly data?: unknown;\n\n constructor(code: CoreErrorCode, options?: VelaErrorOptions);\n constructor(code: string, options: VelaErrorOptions & { status: number });\n constructor(code: string, options: VelaErrorOptions = {}) {\n const entry = (\n CORE_ENTRIES as Record<\n string,\n { status: number; title: string; hint?: string; docsUrl?: string }\n >\n )[code];\n super(\n options.message ?? entry?.title ?? code,\n options.cause !== undefined ? { cause: options.cause } : undefined,\n );\n this.name = 'VelaError';\n this.code = code;\n this.status = options.status ?? entry?.status ?? 500;\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n if (hint !== undefined) this.hint = hint;\n if (docsUrl !== undefined) this.docsUrl = docsUrl;\n if (options.data !== undefined) this.data = options.data;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { CORE_ENTRIES, type CoreErrorCode, type ErrorCatalogEntry } from './catalog-data';\nimport { VelaError, type VelaErrorOptions } from './error';\n\nexport type { CoreErrorCode, ErrorCatalogEntry } from './catalog-data';\nexport { CORE_ENTRIES } from './catalog-data';\n\nexport interface Catalog<C extends string = string> {\n readonly entries: Readonly<Record<C, ErrorCatalogEntry>>;\n /** Typed thrower bound to this catalog's defaults. */\n error(code: C | (string & {}), options?: VelaErrorOptions): VelaError;\n has(code: string): boolean;\n get(code: string): ErrorCatalogEntry | undefined;\n}\n\nconst makeCatalog = <C extends string>(\n entries: Readonly<Record<C, ErrorCatalogEntry>>,\n): Catalog<C> => ({\n entries,\n error(code, options = {}) {\n const entry = (entries as Record<string, ErrorCatalogEntry>)[code];\n const hint = options.hint ?? entry?.hint;\n const docsUrl = options.docsUrl ?? entry?.docsUrl;\n return new VelaError(code, {\n ...options,\n status: options.status ?? entry?.status ?? 500,\n ...(hint === undefined ? {} : { hint }),\n ...(docsUrl === undefined ? {} : { docsUrl }),\n });\n },\n has: (code) => Object.hasOwn(entries, code),\n get: (code) =>\n Object.hasOwn(entries, code) ? (entries as Record<string, ErrorCatalogEntry>)[code] : undefined,\n});\n\nexport const defineErrorCatalog = <const T extends Record<string, ErrorCatalogEntry>>(\n entries: T,\n): Catalog<Extract<keyof T, string>> => makeCatalog(entries);\n\nexport const composeCatalogs = (...catalogs: Array<Catalog<string>>): Catalog<string> => {\n const merged: Record<string, ErrorCatalogEntry> = {};\n for (const catalog of catalogs) {\n for (const [code, entry] of Object.entries<ErrorCatalogEntry>(catalog.entries)) {\n if (Object.hasOwn(merged, code)) {\n throw new VelaError('internal', {\n message: `duplicate error code '${code}' while composing catalogs`,\n });\n }\n merged[code] = entry;\n }\n }\n return makeCatalog(merged);\n};\n\nexport const CORE_CATALOG: Catalog<CoreErrorCode> = makeCatalog(CORE_ENTRIES);\n\nexport const STATUS_TO_CODE: Readonly<Record<number, CoreErrorCode>> = Object.fromEntries(\n (Object.entries(CORE_ENTRIES) as Array<[CoreErrorCode, ErrorCatalogEntry]>).map(([code, e]) => [\n e.status,\n code,\n ]),\n) as Record<number, CoreErrorCode>;\n","/**\n * Structural, realm-safe, BRANDED guard. `instanceof VelaError` is unreliable\n * across DO↔worker RPC and for wire-decoded twins; a bare code+status shape\n * check lets foreign driver errors ride the client-echo path. The brand\n * (`type === 'VelaError'`, an own enumerable prop that survives serialization)\n * closes both failure modes. Nothing load-bearing may use `instanceof`.\n */\nexport interface VelaErrorLike extends Error {\n type: 'VelaError';\n code: string;\n status: number;\n hint?: string;\n docsUrl?: string;\n data?: unknown;\n}\n\nexport const isVelaError = (error: unknown): error is VelaErrorLike => {\n if (!(error instanceof Error)) return false;\n const candidate = error as Partial<VelaErrorLike>;\n return (\n typeof candidate.code === 'string' &&\n typeof candidate.status === 'number' &&\n candidate.type === 'VelaError'\n );\n};\n","import { CORE_CATALOG, STATUS_TO_CODE, type Catalog } from './catalog';\nimport { isVelaError } from './guard';\n\nexport interface WireErrorObject {\n code: string;\n message: string;\n hint?: string;\n docsUrl?: string;\n details?: unknown;\n}\n\nexport interface ErrorBodyResult {\n body: { error: WireErrorObject };\n status: number;\n redacted: boolean;\n}\n\nexport interface ToErrorBodyOptions {\n /** Composed catalog; defaults to the core catalog. */\n catalog?: Catalog<string>;\n /** Status used for unbranded errors. Default 500. */\n fallbackStatus?: number;\n redactedMessage?: (status: number) => string;\n /** Injectable wire codec for `data` → `details` (bigint/bytes etc.). */\n encodeData?: (data: unknown) => unknown;\n /** Default true. */\n includeHint?: boolean;\n}\n\nconst defaultRedactedMessage = (status: number, catalog: Catalog<string>): string => {\n const code = STATUS_TO_CODE[status];\n return (code && catalog.get(code)?.title) || 'Internal Server Error';\n};\n\n/**\n * THE single wire-redaction seam. Every transport edge (HTTP, WS, live, queue\n * reporting) builds its client-bound error content here, so the invariant\n * \"unbranded or internal-coded errors never echo their message\" holds\n * identically everywhere. `redacted: true` is the caller's signal to log the\n * raw error server-side — this function never logs (zero-dep purity).\n */\nexport const toErrorBody = (error: unknown, options: ToErrorBodyOptions = {}): ErrorBodyResult => {\n const catalog = options.catalog ?? CORE_CATALOG;\n const message = options.redactedMessage ?? ((s: number) => defaultRedactedMessage(s, catalog));\n\n const redact = (status: number, code: string): ErrorBodyResult => ({\n body: { error: { code, message: message(status) } },\n status,\n redacted: true,\n });\n\n if (!isVelaError(error)) {\n const status = options.fallbackStatus ?? 500;\n return redact(status, STATUS_TO_CODE[status] ?? 'internal');\n }\n\n const entry = catalog.get(error.code);\n if (error.code === 'internal' || entry?.internal === true) {\n return redact(error.status, error.code);\n }\n\n const wire: WireErrorObject = { code: error.code, message: error.message };\n const hint = error.hint ?? entry?.hint;\n if (options.includeHint !== false && hint !== undefined) wire.hint = hint;\n const docsUrl = error.docsUrl ?? entry?.docsUrl;\n if (docsUrl !== undefined) wire.docsUrl = docsUrl;\n if (error.data !== undefined)\n wire.details = options.encodeData ? options.encodeData(error.data) : error.data;\n return { body: { error: wire }, status: error.status, redacted: false };\n};\n","import { VelaError } from './error';\n\n/** Throws an internal-coded VelaError — rich in server logs, redacted on the wire. */\nexport function invariant(condition: unknown, message: string, data?: unknown): asserts condition {\n if (!condition) {\n throw new VelaError('internal', { message: `Invariant violation: ${message}`, data });\n }\n}\n\nexport function unreachable(value: never, message = 'unreachable code reached'): never {\n throw new VelaError('internal', { message, data: { value } });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/errors",
3
- "version": "1.1.0",
3
+ "version": "2.0.1",
4
4
  "description": "Unified error layer for Vela: branded VelaError, composable error catalogs, and the single toErrorBody wire-redaction seam",
5
5
  "keywords": [
6
6
  "error-catalog",
@@ -9,15 +9,16 @@
9
9
  "redaction",
10
10
  "vela"
11
11
  ],
12
- "homepage": "https://github.com/velajs/errors#readme",
12
+ "homepage": "https://github.com/velajs/vela/tree/main/packages/errors#readme",
13
13
  "bugs": {
14
- "url": "https://github.com/velajs/errors/issues"
14
+ "url": "https://github.com/velajs/vela/issues"
15
15
  },
16
16
  "license": "MIT",
17
17
  "author": "ksh",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/velajs/errors.git"
20
+ "url": "git+https://github.com/velajs/vela.git",
21
+ "directory": "packages/errors"
21
22
  },
22
23
  "files": [
23
24
  "dist",
@@ -40,18 +41,21 @@
40
41
  }
41
42
  },
42
43
  "devDependencies": {
43
- "@arethetypeswrong/cli": "^0.18.5",
44
- "@changesets/cli": "^2.31.0",
45
- "oxfmt": "^0.58.0",
46
- "oxlint": "^1.73.0",
47
- "publint": "^0.3.21",
48
- "tsdown": "^0.22.4",
49
- "typescript": "^7.0.2",
50
- "vitest": "^4.1.10"
44
+ "@arethetypeswrong/cli": "0.18.5",
45
+ "@changesets/cli": "3.0.1",
46
+ "oxfmt": "0.58.0",
47
+ "oxlint": "1.73.0",
48
+ "publint": "0.3.21",
49
+ "tsdown": "0.23.0",
50
+ "typescript": "7.0.2",
51
+ "vitest": "4.1.10"
51
52
  },
52
53
  "engines": {
53
54
  "node": ">=24"
54
55
  },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
55
59
  "scripts": {
56
60
  "build": "tsdown",
57
61
  "test": "vitest run",
@@ -61,9 +65,6 @@
61
65
  "format:check": "oxfmt --check .",
62
66
  "publint": "publint",
63
67
  "attw": "attw --pack . --profile esm-only",
64
- "changeset": "changeset",
65
- "version-packages": "changeset version",
66
- "release": "pnpm build && changeset publish",
67
68
  "verify": "pnpm lint && pnpm format:check && pnpm build && pnpm typecheck && pnpm test && pnpm publint && pnpm attw"
68
69
  }
69
70
  }