errore 0.12.0 → 0.14.0
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/README.md +46 -22
- package/SKILL.md +152 -355
- package/dist/cli.js +2 -1
- package/dist/error.d.ts +30 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +51 -10
- package/dist/factory.d.ts +11 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +85 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.test.js +191 -8
- package/dist/serialize-cause.d.ts +5 -0
- package/dist/serialize-cause.d.ts.map +1 -0
- package/dist/serialize-cause.js +9 -0
- package/package.json +4 -5
- package/src/cli.ts +2 -1
- package/src/error.ts +50 -10
- package/src/factory.ts +104 -35
- package/src/index.test.ts +233 -7
- package/src/index.ts +1 -1
- package/src/serialize-cause.ts +9 -0
package/README.md
CHANGED
|
@@ -26,6 +26,20 @@ console.log(user.username) // user is User, fully narrowed
|
|
|
26
26
|
npm install errore
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Agent Skill
|
|
30
|
+
|
|
31
|
+
errore ships with a skill file that teaches AI coding agents the errore convention. Install it with:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npx skills add remorses/errore
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then add this to your `AGENTS.md`:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
This codebase uses the errore.org convention. ALWAYS read the errore skill before editing any code.
|
|
41
|
+
```
|
|
42
|
+
|
|
29
43
|
## Quick Start
|
|
30
44
|
|
|
31
45
|
Define typed errors with **variable interpolation** and return **Error or Value** directly:
|
|
@@ -46,14 +60,12 @@ class DbError extends errore.createTaggedError({
|
|
|
46
60
|
|
|
47
61
|
// Function returns Error | Value (no wrapper!)
|
|
48
62
|
async function getUser(id: string): Promise<NotFoundError | DbError | User> {
|
|
49
|
-
const result = await
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
})
|
|
53
|
-
|
|
63
|
+
const result = await db.query(id)
|
|
64
|
+
.catch((e) => new DbError({ reason: e.message, cause: e }))
|
|
65
|
+
|
|
54
66
|
if (result instanceof Error) return result
|
|
55
67
|
if (!result) return new NotFoundError({ id })
|
|
56
|
-
|
|
68
|
+
|
|
57
69
|
return result
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -84,7 +96,7 @@ import * as errore from 'errore'
|
|
|
84
96
|
// Base class with shared functionality
|
|
85
97
|
class AppError extends Error {
|
|
86
98
|
statusCode: number = 500
|
|
87
|
-
|
|
99
|
+
|
|
88
100
|
toResponse() {
|
|
89
101
|
return { error: this.message, code: this.statusCode }
|
|
90
102
|
}
|
|
@@ -105,7 +117,7 @@ class ValidationError extends errore.createTaggedError({
|
|
|
105
117
|
|
|
106
118
|
class UnauthorizedError extends errore.createTaggedError({
|
|
107
119
|
name: 'UnauthorizedError',
|
|
108
|
-
|
|
120
|
+
|
|
109
121
|
extends: AppError
|
|
110
122
|
}) {}
|
|
111
123
|
|
|
@@ -118,28 +130,28 @@ async function updateUser(
|
|
|
118
130
|
if (!session) {
|
|
119
131
|
return new UnauthorizedError({ message: 'Not logged in' })
|
|
120
132
|
}
|
|
121
|
-
|
|
133
|
+
|
|
122
134
|
const user = await db.users.find(userId)
|
|
123
135
|
if (!user) {
|
|
124
136
|
return new NotFoundError({ resource: `User ${userId}` })
|
|
125
137
|
}
|
|
126
|
-
|
|
138
|
+
|
|
127
139
|
if (data.email && !isValidEmail(data.email)) {
|
|
128
140
|
return new ValidationError({ field: 'email', reason: 'Invalid email format' })
|
|
129
141
|
}
|
|
130
|
-
|
|
142
|
+
|
|
131
143
|
return db.users.update(userId, data)
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
// API handler
|
|
135
147
|
app.post('/users/:id', async (req, res) => {
|
|
136
148
|
const result = await updateUser(req.params.id, req.body)
|
|
137
|
-
|
|
149
|
+
|
|
138
150
|
if (result instanceof Error) {
|
|
139
151
|
// All errors have toResponse() from AppError base
|
|
140
152
|
return res.status(result.statusCode).json(result.toResponse())
|
|
141
153
|
}
|
|
142
|
-
|
|
154
|
+
|
|
143
155
|
return res.json(result)
|
|
144
156
|
})
|
|
145
157
|
```
|
|
@@ -172,6 +184,13 @@ class EmptyError extends errore.createTaggedError({
|
|
|
172
184
|
}) {}
|
|
173
185
|
new EmptyError() // no args required
|
|
174
186
|
|
|
187
|
+
// Message omitted — caller provides it at construction time
|
|
188
|
+
class GenericError extends errore.createTaggedError({
|
|
189
|
+
name: 'GenericError',
|
|
190
|
+
}) {}
|
|
191
|
+
new GenericError({ message: 'caller decides the message' })
|
|
192
|
+
// fingerprint is stable regardless of what message is passed
|
|
193
|
+
|
|
175
194
|
// With cause for error chaining
|
|
176
195
|
class WrapperError extends errore.createTaggedError({
|
|
177
196
|
name: 'WrapperError',
|
|
@@ -195,6 +214,8 @@ err.statusCode // 500 (inherited from AppError)
|
|
|
195
214
|
err instanceof AppError // true
|
|
196
215
|
```
|
|
197
216
|
|
|
217
|
+
**Reserved variable names:** `$_tag`, `$name`, `$stack`, `$cause` cannot be used in message templates — they conflict with Error internals.
|
|
218
|
+
|
|
198
219
|
### Error Wrapping and Context
|
|
199
220
|
|
|
200
221
|
Wrap errors with additional context while **preserving the original error** via `cause`:
|
|
@@ -203,11 +224,11 @@ Wrap errors with additional context while **preserving the original error** via
|
|
|
203
224
|
// Wrap with context, preserve original in cause
|
|
204
225
|
async function processUser(id: string): Promise<ServiceError | ProcessedUser> {
|
|
205
226
|
const user = await getUser(id) // returns NotFoundError | User
|
|
206
|
-
|
|
227
|
+
|
|
207
228
|
if (user instanceof Error) {
|
|
208
229
|
return new ServiceError({ id, cause: user })
|
|
209
230
|
}
|
|
210
|
-
|
|
231
|
+
|
|
211
232
|
return process(user)
|
|
212
233
|
}
|
|
213
234
|
|
|
@@ -215,7 +236,7 @@ async function processUser(id: string): Promise<ServiceError | ProcessedUser> {
|
|
|
215
236
|
const result = await processUser('123')
|
|
216
237
|
if (result instanceof Error) {
|
|
217
238
|
console.log(result.message) // "Failed to process user 123"
|
|
218
|
-
|
|
239
|
+
|
|
219
240
|
if (result.cause instanceof NotFoundError) {
|
|
220
241
|
console.log(result.cause.id) // access original error's properties
|
|
221
242
|
}
|
|
@@ -283,9 +304,9 @@ This solves the problem where `result.cause instanceof MyError` only checks one
|
|
|
283
304
|
|
|
284
305
|
```ts
|
|
285
306
|
// A -> B -> C chain
|
|
286
|
-
const c = new DbError({
|
|
307
|
+
const c = new DbError({ reason: 'connection reset' })
|
|
287
308
|
const b = new ServiceError({ id: '123', cause: c })
|
|
288
|
-
const a = new
|
|
309
|
+
const a = new NotFoundError({ id: '456', cause: b })
|
|
289
310
|
|
|
290
311
|
// Manual check only finds B
|
|
291
312
|
a.cause instanceof DbError // false — only checks one level
|
|
@@ -355,10 +376,11 @@ const parsed = errore.try({
|
|
|
355
376
|
catch: e => new ParseError({ reason: e.message, cause: e })
|
|
356
377
|
})
|
|
357
378
|
|
|
358
|
-
// Async
|
|
359
|
-
const response = await
|
|
379
|
+
// Async — prefer .catch() for promises (no wrapper needed)
|
|
380
|
+
const response = await fetch(url)
|
|
381
|
+
.catch((e) => new NetworkError({ url, cause: e }))
|
|
360
382
|
|
|
361
|
-
// Async
|
|
383
|
+
// Async — errore.tryAsync also works, but .catch() is preferred
|
|
362
384
|
const response = await errore.tryAsync({
|
|
363
385
|
try: () => fetch(url),
|
|
364
386
|
catch: e => new NetworkError({ url, cause: e })
|
|
@@ -366,6 +388,8 @@ const response = await errore.tryAsync({
|
|
|
366
388
|
```
|
|
367
389
|
|
|
368
390
|
> **Best practices for `try` / `tryAsync`:**
|
|
391
|
+
> - **For async code, prefer `.catch()`** — `promise.catch((e) => new MyError({ cause: e }))` is simpler and avoids the wrapper. `errore.tryAsync` still works but `.catch()` is the idiomatic choice.
|
|
392
|
+
> - **Use `errore.try` for sync code** — there's no equivalent of `.catch()` for synchronous throwing calls, so `errore.try(() => JSON.parse(input))` is the right tool.
|
|
369
393
|
> - **Use as low as possible in the call stack** — only at boundaries with uncontrolled dependencies (third-party libs, `JSON.parse`, `fetch`, file I/O). Your own functions should return errors as values, never throw.
|
|
370
394
|
> - **Keep the callback minimal** — wrap only the single throwing call, not your business logic. The `try` callback should be a one-liner.
|
|
371
395
|
> - **Always prefer `errore.try` over `errore.tryFn`** — they are the same function, but `try` is the canonical name.
|
|
@@ -764,7 +788,7 @@ if (user instanceof Error) return user
|
|
|
764
788
|
console.log(user.name)
|
|
765
789
|
```
|
|
766
790
|
|
|
767
|
-
The `errore` package just provides conveniences: `createTaggedError` for less boilerplate, `matchError` for exhaustive pattern matching, `
|
|
791
|
+
The `errore` package just provides conveniences: `createTaggedError` for less boilerplate, `matchError` for exhaustive pattern matching, `try` for catching sync exceptions (and `.catch()` for async promises). But the core pattern—**errors as union types**—works with zero dependencies.
|
|
768
792
|
|
|
769
793
|
### Perfect for Libraries
|
|
770
794
|
|