errore 0.9.0 → 0.11.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 +367 -13
- package/SKILL.md +236 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +19 -0
- package/dist/error.d.ts +20 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +43 -1
- package/dist/factory.d.ts +6 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +14 -20
- 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.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +1124 -0
- package/package.json +11 -4
- package/src/cli.ts +24 -0
- package/src/error.ts +56 -7
- package/src/factory.ts +24 -2
- package/src/index.test.ts +395 -0
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -4,13 +4,20 @@ Type-safe errors as values for TypeScript. Like Go, but with full type inference
|
|
|
4
4
|
|
|
5
5
|
## Why?
|
|
6
6
|
|
|
7
|
-
Instead of wrapping values in a `Result<T, E>` type, functions simply return `E | T`. TypeScript's type narrowing handles the rest:
|
|
7
|
+
Instead of wrapping values in a `Result<T, E>` type, functions simply return `E | T`. TypeScript's **type narrowing** handles the rest:
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
// Go-style: errors as values
|
|
11
|
-
const user = await
|
|
12
|
-
if (user instanceof
|
|
13
|
-
console.
|
|
11
|
+
const user = await getUser(id)
|
|
12
|
+
if (user instanceof NotFoundError) {
|
|
13
|
+
console.error('Missing:', user.id)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
if (user instanceof DbError) {
|
|
17
|
+
console.error('DB failed:', user.reason)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
console.log(user.username) // user is User, fully narrowed
|
|
14
21
|
```
|
|
15
22
|
|
|
16
23
|
## Install
|
|
@@ -21,6 +28,8 @@ npm install errore
|
|
|
21
28
|
|
|
22
29
|
## Quick Start
|
|
23
30
|
|
|
31
|
+
Define typed errors with **variable interpolation** and return **Error or Value** directly:
|
|
32
|
+
|
|
24
33
|
```ts
|
|
25
34
|
import * as errore from 'errore'
|
|
26
35
|
|
|
@@ -67,7 +76,7 @@ console.log(user.name)
|
|
|
67
76
|
|
|
68
77
|
## Example: API Error Handling
|
|
69
78
|
|
|
70
|
-
A complete example with custom base class
|
|
79
|
+
A complete example with **custom base class** and HTTP status codes:
|
|
71
80
|
|
|
72
81
|
```ts
|
|
73
82
|
import * as errore from 'errore'
|
|
@@ -139,7 +148,7 @@ app.post('/users/:id', async (req, res) => {
|
|
|
139
148
|
|
|
140
149
|
### createTaggedError
|
|
141
150
|
|
|
142
|
-
Create typed errors with
|
|
151
|
+
Create typed errors with **variable interpolation** in the message:
|
|
143
152
|
|
|
144
153
|
```ts
|
|
145
154
|
import * as errore from 'errore'
|
|
@@ -186,8 +195,140 @@ err.statusCode // 500 (inherited from AppError)
|
|
|
186
195
|
err instanceof AppError // true
|
|
187
196
|
```
|
|
188
197
|
|
|
198
|
+
### Error Wrapping and Context
|
|
199
|
+
|
|
200
|
+
Wrap errors with additional context while **preserving the original error** via `cause`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// Wrap with context, preserve original in cause
|
|
204
|
+
async function processUser(id: string): Promise<ServiceError | ProcessedUser> {
|
|
205
|
+
const user = await getUser(id) // returns NotFoundError | User
|
|
206
|
+
|
|
207
|
+
if (user instanceof Error) {
|
|
208
|
+
return new ServiceError({ id, cause: user })
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return process(user)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Access original error via cause
|
|
215
|
+
const result = await processUser('123')
|
|
216
|
+
if (result instanceof Error) {
|
|
217
|
+
console.log(result.message) // "Failed to process user 123"
|
|
218
|
+
|
|
219
|
+
if (result.cause instanceof NotFoundError) {
|
|
220
|
+
console.log(result.cause.id) // access original error's properties
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The error definitions:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
import * as errore from 'errore'
|
|
229
|
+
|
|
230
|
+
class NotFoundError extends errore.createTaggedError({
|
|
231
|
+
name: 'NotFoundError',
|
|
232
|
+
message: 'User $id not found'
|
|
233
|
+
}) {}
|
|
234
|
+
|
|
235
|
+
class ServiceError extends errore.createTaggedError({
|
|
236
|
+
name: 'ServiceError',
|
|
237
|
+
message: 'Failed to process user $id'
|
|
238
|
+
}) {}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Browser console** prints the full cause chain:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
ServiceError: Failed to process user 123
|
|
245
|
+
at processUser (app.js:12)
|
|
246
|
+
at main (app.js:20)
|
|
247
|
+
Caused by: NotFoundError: User 123 not found
|
|
248
|
+
at getUser (app.js:5)
|
|
249
|
+
at processUser (app.js:8)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### findCause
|
|
253
|
+
|
|
254
|
+
Walk the `.cause` chain to find an ancestor matching a specific error class. Similar to Go's `errors.As` — checks the error itself first, then traverses `.cause` recursively:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import * as errore from 'errore'
|
|
258
|
+
|
|
259
|
+
class NotFoundError extends errore.createTaggedError({
|
|
260
|
+
name: 'NotFoundError',
|
|
261
|
+
message: 'User $id not found'
|
|
262
|
+
}) {}
|
|
263
|
+
|
|
264
|
+
class ServiceError extends errore.createTaggedError({
|
|
265
|
+
name: 'ServiceError',
|
|
266
|
+
message: 'Failed to process user $id'
|
|
267
|
+
}) {}
|
|
268
|
+
|
|
269
|
+
// Deep chain: ServiceError -> NotFoundError
|
|
270
|
+
const notFound = new NotFoundError({ id: '123' })
|
|
271
|
+
const service = new ServiceError({ id: '123', cause: notFound })
|
|
272
|
+
|
|
273
|
+
// Instance method on tagged errors
|
|
274
|
+
const found = service.findCause(NotFoundError)
|
|
275
|
+
found?.id // '123' — type-safe access
|
|
276
|
+
|
|
277
|
+
// Standalone function for any Error
|
|
278
|
+
const found2 = errore.findCause(service, NotFoundError)
|
|
279
|
+
found2?.id // '123'
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This solves the problem where `result.cause instanceof MyError` only checks one level deep. `findCause` walks the entire chain:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
// A -> B -> C chain
|
|
286
|
+
const c = new DbError({ message: 'connection reset' })
|
|
287
|
+
const b = new ServiceError({ id: '123', cause: c })
|
|
288
|
+
const a = new ApiError({ message: 'request failed', cause: b })
|
|
289
|
+
|
|
290
|
+
// Manual check only finds B
|
|
291
|
+
a.cause instanceof DbError // false — only checks one level
|
|
292
|
+
|
|
293
|
+
// findCause walks the full chain
|
|
294
|
+
a.findCause(DbError) // finds C ✓
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Returns `undefined` if no matching ancestor is found. Safe against circular `.cause` references.
|
|
298
|
+
|
|
299
|
+
### Custom Base Class with `extends`
|
|
300
|
+
|
|
301
|
+
Use `extends` to inherit from a custom base class. The error will pass `instanceof` for both the base class and the specific error class:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import * as errore from 'errore'
|
|
305
|
+
|
|
306
|
+
class AppError extends Error {
|
|
307
|
+
statusCode = 500
|
|
308
|
+
toResponse() { return { error: this.message, code: this.statusCode } }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
class NotFoundError extends errore.createTaggedError({
|
|
312
|
+
name: 'NotFoundError',
|
|
313
|
+
message: 'Resource $id not found',
|
|
314
|
+
extends: AppError
|
|
315
|
+
}) {
|
|
316
|
+
statusCode = 404
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const err = new NotFoundError({ id: '123' })
|
|
320
|
+
err instanceof NotFoundError // true
|
|
321
|
+
err instanceof AppError // true
|
|
322
|
+
err instanceof Error // true
|
|
323
|
+
|
|
324
|
+
err.statusCode // 404
|
|
325
|
+
err.toResponse() // { error: 'Resource 123 not found', code: 404 }
|
|
326
|
+
```
|
|
327
|
+
|
|
189
328
|
### Type Guards
|
|
190
329
|
|
|
330
|
+
Use **instanceof checks** to narrow union types:
|
|
331
|
+
|
|
191
332
|
```ts
|
|
192
333
|
const result: NetworkError | User = await fetchUser(id)
|
|
193
334
|
|
|
@@ -200,14 +341,16 @@ if (result instanceof Error) {
|
|
|
200
341
|
|
|
201
342
|
### Try Functions
|
|
202
343
|
|
|
344
|
+
**Wrap exceptions** as error values:
|
|
345
|
+
|
|
203
346
|
```ts
|
|
204
347
|
import * as errore from 'errore'
|
|
205
348
|
|
|
206
349
|
// Sync - wraps exceptions in UnhandledError
|
|
207
|
-
const parsed = errore.
|
|
350
|
+
const parsed = errore.try(() => JSON.parse(input))
|
|
208
351
|
|
|
209
352
|
// Sync - with custom error type
|
|
210
|
-
const parsed = errore.
|
|
353
|
+
const parsed = errore.try({
|
|
211
354
|
try: () => JSON.parse(input),
|
|
212
355
|
catch: e => new ParseError({ reason: e.message, cause: e })
|
|
213
356
|
})
|
|
@@ -224,6 +367,8 @@ const response = await errore.tryAsync({
|
|
|
224
367
|
|
|
225
368
|
### Transformations
|
|
226
369
|
|
|
370
|
+
**Transform and chain** operations:
|
|
371
|
+
|
|
227
372
|
```ts
|
|
228
373
|
import * as errore from 'errore'
|
|
229
374
|
|
|
@@ -242,6 +387,8 @@ const logged = errore.tap(user, u => console.log('Got user:', u.name))
|
|
|
242
387
|
|
|
243
388
|
### Extraction
|
|
244
389
|
|
|
390
|
+
**Extract values** or throw, **split arrays** by success/error:
|
|
391
|
+
|
|
245
392
|
```ts
|
|
246
393
|
import * as errore from 'errore'
|
|
247
394
|
|
|
@@ -264,7 +411,7 @@ const [users, errors] = errore.partition(results)
|
|
|
264
411
|
|
|
265
412
|
### Error Matching
|
|
266
413
|
|
|
267
|
-
|
|
414
|
+
**Exhaustive pattern matching** with `matchError`. Always assign results to a variable and keep callbacks pure:
|
|
268
415
|
|
|
269
416
|
```ts
|
|
270
417
|
import * as errore from 'errore'
|
|
@@ -298,7 +445,7 @@ ValidationError.is(value) // specific class
|
|
|
298
445
|
|
|
299
446
|
## How Type Safety Works
|
|
300
447
|
|
|
301
|
-
TypeScript narrows types after `instanceof Error` checks:
|
|
448
|
+
TypeScript **narrows types** after `instanceof Error` checks:
|
|
302
449
|
|
|
303
450
|
```ts
|
|
304
451
|
function example(result: NetworkError | User): string {
|
|
@@ -318,7 +465,7 @@ This works because:
|
|
|
318
465
|
|
|
319
466
|
## Result + Option Combined: `Error | T | null`
|
|
320
467
|
|
|
321
|
-
|
|
468
|
+
Naturally combine **error handling with optional values**. No wrapper nesting needed!
|
|
322
469
|
|
|
323
470
|
```ts
|
|
324
471
|
import * as errore from 'errore'
|
|
@@ -362,14 +509,80 @@ console.log(user.name)
|
|
|
362
509
|
| Zig | `!?T` (error union + optional) | Yes, specific syntax |
|
|
363
510
|
| **errore** | `Error \| T \| null` | **No!** Check in any order |
|
|
364
511
|
|
|
365
|
-
With errore
|
|
512
|
+
With errore you **check in any order**:
|
|
366
513
|
- Use `?.` and `??` naturally
|
|
367
514
|
- Check `instanceof Error` or `=== null` in any order
|
|
368
515
|
- No unwrapping ceremony
|
|
369
516
|
- TypeScript infers everything
|
|
370
517
|
|
|
518
|
+
## Why This Is Better Than Go
|
|
519
|
+
|
|
520
|
+
Go's error handling uses **two separate return values**:
|
|
521
|
+
|
|
522
|
+
```go
|
|
523
|
+
user, err := fetchUser(id)
|
|
524
|
+
// Oops! Forgot to check err
|
|
525
|
+
fmt.Println(user.Name) // Compiles fine, crashes at runtime
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
The compiler can't save you here. You can ignore `err` entirely and use `user` directly.
|
|
529
|
+
|
|
530
|
+
With errore, **forgetting to check is impossible**:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
const user = await fetchUser(id) // type: NotFoundError | User
|
|
534
|
+
|
|
535
|
+
console.log(user.id) // TS Error: Property 'id' does not exist on type 'NotFoundError'
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
Since errore uses a **single union variable** instead of two separate values, TypeScript forces you to narrow the type before accessing value-specific properties. You literally cannot use the value without first doing an `instanceof Error` check.
|
|
539
|
+
|
|
540
|
+
> **Note:** Properties that exist on both `Error` and your value type (like `name`, `message`) can still be accessed without narrowing. This is a small set of 4 fields: `name`, `message`, `stack`, `cause`.
|
|
541
|
+
|
|
542
|
+
### The Remaining Gap
|
|
543
|
+
|
|
544
|
+
There's still one case errore can't catch: **ignored return values**:
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
// Oops! Completely ignoring the return value
|
|
548
|
+
updateUser(id, data) // No error, but we should check!
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
For this, use **TypeScript's built-in checks** or a linter:
|
|
552
|
+
|
|
553
|
+
**TypeScript `tsconfig.json`:**
|
|
554
|
+
```json
|
|
555
|
+
{
|
|
556
|
+
"compilerOptions": {
|
|
557
|
+
"noUnusedLocals": true
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
This catches unused variables, though not ignored return values directly.
|
|
563
|
+
|
|
564
|
+
**oxlint `no-unused-expressions`:**
|
|
565
|
+
|
|
566
|
+
`oxlint.json`:
|
|
567
|
+
```json
|
|
568
|
+
{
|
|
569
|
+
"rules": {
|
|
570
|
+
"no-unused-expressions": "error"
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Or via CLI:
|
|
576
|
+
```bash
|
|
577
|
+
oxlint --deny no-unused-expressions
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Combined with errore's type safety, these tools give you near-complete protection against ignored errors.
|
|
581
|
+
|
|
371
582
|
## Comparison with Result Types
|
|
372
583
|
|
|
584
|
+
**Direct returns** vs wrapper methods:
|
|
585
|
+
|
|
373
586
|
| Result Pattern | errore |
|
|
374
587
|
|---------------|--------|
|
|
375
588
|
| `Result.ok(value)` | just `return value` |
|
|
@@ -379,9 +592,150 @@ With errore:
|
|
|
379
592
|
| `Result<User, Error>` | `Error \| User` |
|
|
380
593
|
| `Result<Option<T>, E>` | `Error \| T \| null` |
|
|
381
594
|
|
|
595
|
+
## Vs neverthrow / better-result
|
|
596
|
+
|
|
597
|
+
These libraries wrap values in a **Result container**. You construct results with `ok()` and `err()`, then unwrap them with `.value` and `.error`:
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
// neverthrow
|
|
601
|
+
import { ok, err, Result } from 'neverthrow'
|
|
602
|
+
|
|
603
|
+
function getUser(id: string): Result<User, NotFoundError> {
|
|
604
|
+
const user = db.find(id)
|
|
605
|
+
if (!user) return err(new NotFoundError({ id }))
|
|
606
|
+
return ok(user) // must wrap
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const result = getUser('123')
|
|
610
|
+
if (result.isErr()) {
|
|
611
|
+
console.log(result.error) // must unwrap
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
console.log(result.value.name) // must unwrap
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
// errore
|
|
619
|
+
function getUser(id: string): User | NotFoundError {
|
|
620
|
+
const user = db.find(id)
|
|
621
|
+
if (!user) return new NotFoundError({ id })
|
|
622
|
+
return user // just return
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const user = getUser('123')
|
|
626
|
+
if (user instanceof Error) {
|
|
627
|
+
console.log(user) // it's already the error
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
console.log(user.name) // it's already the user
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
**The key insight**: `T | Error` already encodes success/failure. TypeScript's type narrowing does the rest. No wrapper needed.
|
|
634
|
+
|
|
635
|
+
| Feature | neverthrow | errore |
|
|
636
|
+
|---------|------------|--------|
|
|
637
|
+
| Type-safe errors | ✓ | ✓ |
|
|
638
|
+
| Exhaustive handling | ✓ | ✓ |
|
|
639
|
+
| Works with null | `Result<T \| null, E>` | `T \| E \| null` |
|
|
640
|
+
| Learning curve | New API (`ok`, `err`, `map`, `andThen`, ...) | Just `instanceof` |
|
|
641
|
+
| Bundle size | ~3KB min | **~0 bytes** |
|
|
642
|
+
| Interop | Requires wrapping/unwrapping at boundaries | Native TypeScript |
|
|
643
|
+
|
|
644
|
+
neverthrow also requires an [eslint plugin](https://github.com/mdbetancourt/eslint-plugin-neverthrow) to catch unhandled results. With errore, TypeScript itself prevents you from using a value without checking the error first.
|
|
645
|
+
|
|
646
|
+
## Vs Effect.ts
|
|
647
|
+
|
|
648
|
+
Effect is not just error handling—it's a **complete functional programming framework** with dependency injection, concurrency primitives, resource management, streaming, and more.
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
// Effect.ts - a paradigm shift
|
|
652
|
+
import { Effect, pipe } from 'effect'
|
|
653
|
+
|
|
654
|
+
const program = pipe(
|
|
655
|
+
fetchUser(id),
|
|
656
|
+
Effect.flatMap(user => fetchPosts(user.id)),
|
|
657
|
+
Effect.map(posts => posts.filter(p => p.published)),
|
|
658
|
+
Effect.catchTag('NotFoundError', () => Effect.succeed([]))
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
const result = await Effect.runPromise(program)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
// errore - regular TypeScript
|
|
666
|
+
const user = await fetchUser(id)
|
|
667
|
+
if (user instanceof Error) return []
|
|
668
|
+
|
|
669
|
+
const posts = await fetchPosts(user.id)
|
|
670
|
+
if (posts instanceof Error) return []
|
|
671
|
+
|
|
672
|
+
return posts.filter(p => p.published)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
Effect is powerful if you need its full feature set. But if you just want type-safe errors:
|
|
676
|
+
|
|
677
|
+
| | Effect | errore |
|
|
678
|
+
|-|--------|--------|
|
|
679
|
+
| Learning curve | Steep (new paradigm) | Minimal (just `instanceof`) |
|
|
680
|
+
| Codebase impact | Pervasive (everything becomes an Effect) | Surgical (adopt incrementally) |
|
|
681
|
+
| Bundle size | ~50KB+ | **~0 bytes** |
|
|
682
|
+
| Use case | Full FP framework | Just error handling |
|
|
683
|
+
|
|
684
|
+
**Use Effect** when you want dependency injection, structured concurrency, and the full functional programming experience.
|
|
685
|
+
|
|
686
|
+
**Use errore** when you just want type-safe errors without rewriting your codebase.
|
|
687
|
+
|
|
688
|
+
## Zero-Dependency Philosophy
|
|
689
|
+
|
|
690
|
+
errore is more a **way of writing code** than a library. The core pattern requires nothing:
|
|
691
|
+
|
|
692
|
+
```ts
|
|
693
|
+
// You can write this without installing errore at all
|
|
694
|
+
class NotFoundError extends Error {
|
|
695
|
+
readonly _tag = 'NotFoundError'
|
|
696
|
+
constructor(public id: string) {
|
|
697
|
+
super(`User ${id} not found`)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function getUser(id: string): Promise<User | NotFoundError> {
|
|
702
|
+
const user = await db.find(id)
|
|
703
|
+
if (!user) return new NotFoundError(id)
|
|
704
|
+
return user
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const user = await getUser('123')
|
|
708
|
+
if (user instanceof Error) return user
|
|
709
|
+
console.log(user.name)
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
The `errore` package just provides conveniences: `createTaggedError` for less boilerplate, `matchError` for exhaustive pattern matching, `tryAsync` for catching exceptions. But the core pattern—**errors as union types**—works with zero dependencies.
|
|
713
|
+
|
|
714
|
+
### Perfect for Libraries
|
|
715
|
+
|
|
716
|
+
Ideal for library authors. Return **plain TypeScript unions** instead of forcing users to adopt your error handling framework:
|
|
717
|
+
|
|
718
|
+
```ts
|
|
719
|
+
// ❌ Library that forces a dependency on users
|
|
720
|
+
import { Result } from 'some-result-lib'
|
|
721
|
+
export function parse(input: string): Result<AST, ParseError>
|
|
722
|
+
|
|
723
|
+
// Users must now install and learn 'some-result-lib'
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
// ✓ Library using plain TypeScript unions
|
|
728
|
+
export function parse(input: string): AST | ParseError
|
|
729
|
+
|
|
730
|
+
// Users handle errors with standard instanceof checks
|
|
731
|
+
// No new dependencies, no new concepts to learn
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
Your library stays lightweight. Users get type-safe errors without adopting an opinionated wrapper. Everyone wins.
|
|
735
|
+
|
|
382
736
|
## Import Style
|
|
383
737
|
|
|
384
|
-
> **Note:** Always use `import * as errore from 'errore'` instead of named imports. This makes code easier to move between files, and more readable
|
|
738
|
+
> **Note:** Always use `import * as errore from 'errore'` instead of named imports. This makes code easier to move between files, and more readable since every function call is **clearly namespaced** (e.g. `errore.isOk()` instead of just `isOk()`).
|
|
385
739
|
|
|
386
740
|
## License
|
|
387
741
|
|