Skip to content

Commit 579e030

Browse files
committed
chore: bump @socketsecurity/lib to 5.24.0 and adopt error helpers
- package.json: 5.21.0 → 5.24.0. - scripts/utils/error-message.mts: drop the local `instanceof Error ? .message : String` shim and re-export errorMessage from @socketsecurity/lib/errors. The library version walks the `cause` chain and supplies the UNKNOWN_ERROR sentinel — strictly better than the local one. - CLAUDE.md: add the caught-value helpers list (isError / isErrnoException / errorMessage / errorStack) — scoped to **scripts** only; `src/` stays on its primordial-guarded `src/error.ts` helpers for DoS-hardening. - docs/references/error-messages.md: pick up the new "Working with caught values" section. Out of scope: - src/error.ts and src/result.ts continue to use primordials (`StringPrototypeCharCodeAt`, etc.) and local `instanceof Error` narrowing. Adding external imports there contradicts the prototype-pollution hardening model for the published library. - scripts/publish.mts and scripts/utils/run-command.mts `'code' in e` checks read numeric spawn exit codes, not errno strings.
1 parent f0ad313 commit 579e030

5 files changed

Lines changed: 78 additions & 28 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ Rules for every message:
114114
- Bloat check: if removing a word keeps the information, drop it.
115115
- For allowed-set / conflict lists, use `joinAnd` / `joinOr` from `@socketsecurity/lib/arrays``must be one of: ${joinOr(allowed)}` reads better than a hand-formatted list.
116116

117+
Caught-value helpers from `@socketsecurity/lib/errors` (prefer these in **scripts** over hand-rolled checks; `src/` uses primordial-guarded helpers from `src/error.ts` instead):
118+
119+
- `isError(e)` — replaces `e instanceof Error`. Cross-realm-safe.
120+
- `isErrnoException(e)` — replaces `'code' in err` guards. Narrows to `NodeJS.ErrnoException`.
121+
- `errorMessage(e)` — replaces `e instanceof Error ? e.message : String(e)` and any `'Unknown error'` fallback. Walks the `cause` chain.
122+
- `errorStack(e)` — cause-aware stack or `undefined`.
123+
117124
Examples:
118125

119126
-`Error: invalid config` → ✓ `config.json: part 3 is missing "filename". Add a lowercase filename (e.g. "parsing").`

docs/references/error-messages.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,62 @@ Both wrap `Intl.ListFormat`, so the Oxford comma and one-/two-item cases come ou
9898

9999
Use `joinOr` whenever the error is "must be one of X", `joinAnd` whenever it's "all of X are required / missing / in conflict".
100100

101+
## Working with caught values
102+
103+
`catch (e)` binds `unknown`. The helpers in `@socketsecurity/lib/errors` cover the four patterns that recur everywhere:
104+
105+
```ts
106+
import {
107+
errorMessage,
108+
errorStack,
109+
isError,
110+
isErrnoException,
111+
} from '@socketsecurity/lib/errors'
112+
```
113+
114+
### `isError(value)` — replaces `value instanceof Error`
115+
116+
Cross-realm-safe. Uses the native ES2025 `Error.isError` when the engine ships it, falls back to a spec-compliant shim otherwise. Catches Errors from worker threads, `vm` contexts, and iframes that same-realm `instanceof Error` silently misses.
117+
118+
-`if (e instanceof Error) { … }`
119+
-`if (isError(e)) { … }`
120+
121+
### `isErrnoException(value)` — replaces `'code' in err` guards
122+
123+
Narrows to `NodeJS.ErrnoException` (an Error with a string `code` set by libuv/syscalls like `ENOENT`, `EACCES`, `EBUSY`, `EPERM`). Builds on `isError`, so it's also cross-realm-safe, and it checks that `code` is a string — a merely branded Error without a real errno code returns `false`.
124+
125+
-`if (e && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') { … }`
126+
-`if (isErrnoException(e) && e.code === 'ENOENT') { … }`
127+
128+
### `errorMessage(value)` — replaces the `instanceof Error ? e.message : String(e)` pattern
129+
130+
Walks the `cause` chain via `messageWithCauses`, coerces primitives and objects to string, and returns the shared `UNKNOWN_ERROR` sentinel (the string `'Unknown error'`) for `null`, `undefined`, empty strings, `[object Object]`, or Errors with no message.
131+
132+
That last bullet is the important one: **every `|| 'Unknown error'` fallback in the fleet should collapse into a single `errorMessage(e)` call.**
133+
134+
-`` `Failed: ${e instanceof Error ? e.message : String(e)}` ``
135+
-`` `Failed: ${(e as Error)?.message ?? 'Unknown error'}` ``
136+
-`` `Failed: ${e instanceof Error ? e.message : 'Unknown error'}` ``
137+
-`` `Failed: ${errorMessage(e)}` ``
138+
139+
When you want to preserve the cause chain upstream (recommended), pair it with `{ cause }`:
140+
141+
```ts
142+
try {
143+
await readConfig(path)
144+
} catch (e) {
145+
throw new Error(`Failed to read ${path}: ${errorMessage(e)}`, { cause: e })
146+
}
147+
```
148+
149+
### `errorStack(value)` — cause-aware stack, or `undefined`
150+
151+
Returns the cause-walking stack for Errors; returns `undefined` for non-Errors so logger calls stay safe:
152+
153+
```ts
154+
logger.error(`rebuild failed: ${errorMessage(e)}`, { stack: errorStack(e) })
155+
```
156+
101157
## Voice & tone
102158

103159
- Imperative for the fix: `rename`, `add`, `remove`, `set`.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"@anthropic-ai/claude-code": "2.1.92",
7272
"@babel/parser": "7.29.0",
7373
"@oxlint/migrate": "1.51.0",
74-
"@socketsecurity/lib": "5.21.0",
74+
"@socketsecurity/lib": "5.24.0",
7575
"@socketsecurity/registry": "2.0.2",
7676
"@socketsecurity/sdk": "4.0.1",
7777
"@types/node": "24.9.2",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/utils/error-message.mts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
/**
2-
* @fileoverview Unwrap a readable message string from a thrown value.
2+
* @fileoverview Re-export of the canonical `errorMessage` helper.
33
*
4-
* Mirrors src/error.ts#errorMessage so scripts (which do not depend on
5-
* the compiled library) have the same helper. Keep the two in sync if
6-
* either changes.
7-
*/
8-
9-
/**
10-
* Extract a readable message string from any thrown value.
4+
* `@socketsecurity/lib/errors` walks the `cause` chain, coerces primitives,
5+
* and returns the shared `UNKNOWN_ERROR` sentinel for null/undefined/empty
6+
* — covers every case the old local shim handled and more.
117
*
12-
* Returns `e.message` for Error instances, a coerced string for other
13-
* non-nullish values, and `'Unknown error'` for nullish or
14-
* empty-message cases. Use at boundaries where `catch (e: unknown)`
15-
* needs to surface a message (log lines, error payloads, summaries)
16-
* without a per-call-site type ladder.
8+
* The library helper is not used inside `src/` (that code path uses
9+
* primordial-guarded helpers from `src/error.ts` for DoS-hardening); this
10+
* file is only for build-time scripts outside the published surface.
1711
*/
18-
export function errorMessage(e: unknown): string {
19-
if (e instanceof Error) {
20-
return e.message || 'Unknown error'
21-
}
22-
if (e === null || e === undefined) {
23-
return 'Unknown error'
24-
}
25-
return String(e) || 'Unknown error'
26-
}
12+
13+
export { errorMessage } from '@socketsecurity/lib/errors'

0 commit comments

Comments
 (0)