resultar 3.5.1 → 3.6.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 +149 -18
- package/dist/index.d.ts +900 -760
- package/dist/index.js +286 -7
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -8,10 +8,12 @@ Resultar makes expected failures visible in function signatures instead of hidin
|
|
|
8
8
|
```ts
|
|
9
9
|
Result<T, E>
|
|
10
10
|
ResultAsync<T, E>
|
|
11
|
+
ResultTask<T, E, R>
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
- `T` is the success value.
|
|
14
15
|
- `E` is the expected failure.
|
|
16
|
+
- `R` is the typed service environment still required by a `ResultTask`.
|
|
15
17
|
- Callers must handle the result before they can use the value.
|
|
16
18
|
|
|
17
19
|
Resultar stays focused on explicit values. It does not require a framework, dependency injection
|
|
@@ -22,12 +24,13 @@ container, scheduler, or application runtime.
|
|
|
22
24
|
| You need | Resultar gives you |
|
|
23
25
|
| --- | --- |
|
|
24
26
|
| Expected failures in the type system | `Result<T, E>` and `ResultAsync<T, E>` |
|
|
27
|
+
| Reusable lazy workflows | `ResultTask<T, E, R>` with explicit execution boundaries and typed services |
|
|
25
28
|
| Production errors with useful metadata | Real `Error` subclasses from `createTaggedError` |
|
|
26
29
|
| Composable sync and async workflows | `map`, `mapErr`, `andThen`, `asyncAndThen`, and `orElse` |
|
|
27
30
|
| Linear code without scattered `try/catch` | `safeTry` with `yield*` short-circuiting |
|
|
28
31
|
| Explicit production async policy | Typed timeout, retry, race, concurrency, and cleanup helpers |
|
|
29
32
|
| Exhaustive application boundaries | `matchTags` for tagged error unions |
|
|
30
|
-
| Guardrails for ignored results | The optional `resultar-check` CLI
|
|
33
|
+
| Guardrails for ignored results | The optional native `resultar-check` CLI |
|
|
31
34
|
|
|
32
35
|
Resultar began as a fork of `neverthrow`. The current API keeps the familiar explicit Result model
|
|
33
36
|
and adds Resultar-specific tagged errors, production async helpers, exhaustive tagged boundaries,
|
|
@@ -41,8 +44,9 @@ and optional TypeScript-backed diagnostics.
|
|
|
41
44
|
- [Compose Results](#compose-results)
|
|
42
45
|
- [Tagged Errors](#tagged-errors)
|
|
43
46
|
- [Async Work](#async-work)
|
|
47
|
+
- [Lazy Workflows With ResultTask](#lazy-workflows-with-resulttask)
|
|
44
48
|
- [Production Async Policies](#production-async-policies)
|
|
45
|
-
- [Linear Workflows With
|
|
49
|
+
- [Linear Workflows With Result.gen](#linear-workflows-with-resultgen)
|
|
46
50
|
- [Recover Inside A Workflow](#recover-inside-a-workflow)
|
|
47
51
|
- [Handle Results At Boundaries](#handle-results-at-boundaries)
|
|
48
52
|
- [Strict Results At Production Boundaries](#strict-results-at-production-boundaries)
|
|
@@ -400,6 +404,129 @@ Choose the wrapper that matches the external boundary:
|
|
|
400
404
|
|
|
401
405
|
Prefer a factory with `tryResultAsync` when creating the promise can also throw synchronously.
|
|
402
406
|
|
|
407
|
+
## Lazy Workflows With ResultTask
|
|
408
|
+
|
|
409
|
+
`ResultTask<T, E, R>` is the lazy workflow primitive in Resultar. Creating one does not start the
|
|
410
|
+
work; `runResult`, `runExit`, or `runPromise` executes it explicitly. `R` records the service tags
|
|
411
|
+
still required by the workflow, so the execution boundary can require an explicit environment.
|
|
412
|
+
|
|
413
|
+
Unlike `ResultAsync`, a `ResultTask` is a reusable description of work rather than an already-started
|
|
414
|
+
operation. Mapping, chaining, recovery, service provision, and generator composition all remain lazy.
|
|
415
|
+
|
|
416
|
+
| Need | API |
|
|
417
|
+
| --- | --- |
|
|
418
|
+
| Create an immediate success or failure | `succeed`, `fail`, `fromResult` |
|
|
419
|
+
| Defer synchronous work | `sync`, `try` |
|
|
420
|
+
| Defer promise-producing work | `tryPromise` |
|
|
421
|
+
| Transform or chain | `map`, `flatMap`, `andThen` |
|
|
422
|
+
| Recover typed failures | `catchAll` |
|
|
423
|
+
| Write a linear lazy workflow | `gen` with `yield*` |
|
|
424
|
+
| Declare and provide dependencies | `service`, `provideService`, `provideServices` |
|
|
425
|
+
| Execute at the application boundary | `runExit`, `runResult`, `runPromise` |
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { ResultTask } from 'resultar'
|
|
429
|
+
|
|
430
|
+
const loadUser = (id: string) =>
|
|
431
|
+
ResultTask.tryPromise({
|
|
432
|
+
try: (signal) => fetch(`/users/${id}`, { signal }).then((response) => response.json()),
|
|
433
|
+
catch: (cause) => new Error(`Could not load user: ${String(cause)}`),
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
const task = loadUser('user_123')
|
|
437
|
+
const result = await ResultTask.runResult(task)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
`sync` treats a thrown value as an unexpected defect. Use `try` or `tryPromise` when the boundary is
|
|
441
|
+
expected to throw or reject and should map that cause into `E`. `tryPromise` receives the execution
|
|
442
|
+
`AbortSignal`, so callers can cancel cooperative work without starting it early.
|
|
443
|
+
|
|
444
|
+
Choose the execution boundary based on how much information the application needs:
|
|
445
|
+
|
|
446
|
+
| Boundary | Result |
|
|
447
|
+
| --- | --- |
|
|
448
|
+
| `runExit(task)` | `Exit<T, E>` preserving `Success`, typed `Fail`, and unexpected `Die` causes |
|
|
449
|
+
| `runResult(task)` | `Result<T, E>`; a `Die` rejects instead of entering the typed error channel |
|
|
450
|
+
| `runPromise(task)` | `T`; typed failures and defects reject for integration with Promise-only APIs |
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
const controller = new AbortController()
|
|
454
|
+
const exit = await ResultTask.runExit(loadUser('user_123'), {
|
|
455
|
+
signal: controller.signal,
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
if (exit._tag === 'Failure' && exit.cause._tag === 'Die') {
|
|
459
|
+
console.error('Unexpected defect', exit.cause.defect)
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Instance methods and their static functional forms preserve laziness and infer combined error and
|
|
464
|
+
environment types:
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const userName = loadUser('user_123')
|
|
468
|
+
.map((user) => String(user.name))
|
|
469
|
+
.andThen((name) => ResultTask.succeed(name.trim()))
|
|
470
|
+
.catchAll((error) => ResultTask.succeed(`unavailable: ${error.message}`))
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
`catchAll` recovers only typed failures. Runtime defects remain defects and are visible through
|
|
474
|
+
`runExit`. The equivalent functional forms are `ResultTask.map`, `ResultTask.flatMap`, and
|
|
475
|
+
`ResultTask.catchAll`.
|
|
476
|
+
|
|
477
|
+
Workflows can request typed services with `yield*` and receive them at the boundary. Pass the service
|
|
478
|
+
type and its literal identifier so the named environment remains checked:
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
const Database = ResultTask.service<
|
|
482
|
+
{ findUser: (id: string) => Promise<string> },
|
|
483
|
+
'Database'
|
|
484
|
+
>('Database')
|
|
485
|
+
|
|
486
|
+
const taskWithDatabase = ResultTask.gen(function* () {
|
|
487
|
+
const database = yield* Database
|
|
488
|
+
return yield* ResultTask.tryPromise({
|
|
489
|
+
try: () => database.findUser('user_123'),
|
|
490
|
+
catch: () => 'database-error' as const,
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
const resultWithDatabase = await ResultTask.runResult(taskWithDatabase, {
|
|
495
|
+
services: { Database: { findUser: async () => 'Ada' } },
|
|
496
|
+
})
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
The environment requirement is part of the task type. A missing or misspelled `Database` property is
|
|
500
|
+
a compile-time error at `runResult`, `runExit`, or `runPromise`. Dependencies can also be bound before
|
|
501
|
+
the final boundary:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
const database = { findUser: async () => 'Ada' }
|
|
505
|
+
|
|
506
|
+
const readyWithOne = ResultTask.provideService(taskWithDatabase, Database, database)
|
|
507
|
+
const readyWithAll = ResultTask.provideServices(taskWithDatabase, { Database: database })
|
|
508
|
+
|
|
509
|
+
await ResultTask.runResult(readyWithOne)
|
|
510
|
+
await ResultTask.runResult(readyWithAll)
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
`ResultTask.gen` composes tasks and services linearly. On short-circuit, generator `finally` blocks
|
|
514
|
+
are closed and any yielded cleanup tasks or services are interpreted before execution completes:
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
const program = ResultTask.gen(function* () {
|
|
518
|
+
try {
|
|
519
|
+
return yield* loadUser('user_123')
|
|
520
|
+
} finally {
|
|
521
|
+
yield* ResultTask.sync(() => logger.info('load-user finished'))
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
If cleanup itself fails or defects, that cleanup exit becomes the final exit. ResultTask 3.6 keeps
|
|
527
|
+
execution deliberately small: it provides laziness, typed services, cooperative cancellation, and
|
|
528
|
+
explicit exits, but does not yet include a scheduler, scopes, or `Fiber` runtime.
|
|
529
|
+
|
|
403
530
|
## Production Async Policies
|
|
404
531
|
|
|
405
532
|
Resultar models common resilience policy in the expected error channel. These helpers use lazy tasks
|
|
@@ -507,19 +634,19 @@ See the full guides for
|
|
|
507
634
|
[concurrency](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md#async-concurrency-mapping),
|
|
508
635
|
and [resource cleanup](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md#resourceful-async-iterables).
|
|
509
636
|
|
|
510
|
-
## Linear Workflows With `
|
|
637
|
+
## Linear Workflows With `Result.gen`
|
|
511
638
|
|
|
512
|
-
Use `
|
|
513
|
-
short-circuits on the first `Err`.
|
|
639
|
+
Use `Result.gen` when a longer chain reads better as linear code. `yield*` extracts the `Ok` value and
|
|
640
|
+
short-circuits on the first `Err`. `safeTry` remains an exact compatibility alias.
|
|
514
641
|
|
|
515
642
|
```ts
|
|
516
|
-
import {
|
|
643
|
+
import { Result } from 'resultar'
|
|
517
644
|
import type { ResultAsync } from 'resultar'
|
|
518
645
|
|
|
519
646
|
const createAccountLinear = (
|
|
520
647
|
input: string,
|
|
521
648
|
): ResultAsync<Account, CreateAccountAsyncError> =>
|
|
522
|
-
|
|
649
|
+
Result.gen(async function* () {
|
|
523
650
|
const email = yield* normalizeEmail(input)
|
|
524
651
|
yield* ensureAccountIsAvailable(email)
|
|
525
652
|
|
|
@@ -531,7 +658,7 @@ The object form can map unexpected throws from inside the generator while leavin
|
|
|
531
658
|
values unchanged:
|
|
532
659
|
|
|
533
660
|
```ts
|
|
534
|
-
const account =
|
|
661
|
+
const account = Result.gen({
|
|
535
662
|
async *try() {
|
|
536
663
|
const email = yield* normalizeEmail(input)
|
|
537
664
|
return persistAccount(email)
|
|
@@ -639,11 +766,10 @@ messages, causes, stack traces, and structured metadata.
|
|
|
639
766
|
|
|
640
767
|
## Prevent Ignored Results
|
|
641
768
|
|
|
642
|
-
Result values are useful only when callers handle them. Install `resultar-check`
|
|
643
|
-
TypeScript 7+:
|
|
769
|
+
Result values are useful only when callers handle them. Install the native `resultar-check` CLI:
|
|
644
770
|
|
|
645
771
|
```sh
|
|
646
|
-
pnpm add -D resultar-check
|
|
772
|
+
pnpm add -D resultar-check
|
|
647
773
|
```
|
|
648
774
|
|
|
649
775
|
Use the CLI as the authoritative local and CI check:
|
|
@@ -656,8 +782,8 @@ Use the CLI as the authoritative local and CI check:
|
|
|
656
782
|
}
|
|
657
783
|
```
|
|
658
784
|
|
|
659
|
-
The CLI
|
|
660
|
-
|
|
785
|
+
The CLI uses TypeScript-Go to run compiler and Resultar diagnostics over the same `tsconfig.json`.
|
|
786
|
+
Configure its rules in the project file:
|
|
661
787
|
|
|
662
788
|
```json
|
|
663
789
|
{
|
|
@@ -673,13 +799,13 @@ the TypeScript language-server plugin for equivalent feedback while editing:
|
|
|
673
799
|
}
|
|
674
800
|
```
|
|
675
801
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
802
|
+
The `plugins` entry is configuration consumed by the native CLI and stdio LSP server; it does not
|
|
803
|
+
install an editor extension. Run `resultar-check lsp` from an editor language-server configuration.
|
|
804
|
+
A separate TypeScript installation is not required.
|
|
679
805
|
|
|
680
806
|
See the
|
|
681
807
|
[`resultar-check` guide](https://github.com/inaiat/resultar/blob/main/packages/check/README.md)
|
|
682
|
-
for all
|
|
808
|
+
for all diagnostics, severities, modes, and ignore patterns.
|
|
683
809
|
|
|
684
810
|
## HTTP Request Packages
|
|
685
811
|
|
|
@@ -708,7 +834,11 @@ service needs.
|
|
|
708
834
|
| Continue fallible work | `andThen`, `asyncAndThen` |
|
|
709
835
|
| Recover from failure | `orElse`, `catchTag`, `catchTags` |
|
|
710
836
|
| Wrap throwing or rejecting code | `tryResult`, `tryResultAsync`, `fromPromise` |
|
|
711
|
-
| Write linear Result code | `safeTry` |
|
|
837
|
+
| Write linear Result code | `Result.gen` (`safeTry` compatibility alias) |
|
|
838
|
+
| Describe reusable lazy work | `ResultTask.succeed`, `sync`, `try`, `tryPromise` |
|
|
839
|
+
| Compose or recover lazy work | `ResultTask.map`, `flatMap`, `andThen`, `catchAll`, `gen` |
|
|
840
|
+
| Require or bind typed services | `ResultTask.service`, `provideService`, `provideServices` |
|
|
841
|
+
| Execute lazy work explicitly | `ResultTask.runExit`, `runResult`, `runPromise` |
|
|
712
842
|
| Handle a final boundary | `match`, `matchTags`, `matchTagsPartial` |
|
|
713
843
|
| Combine independent results | `zip`, `combine`, `combineWithAllErrors` |
|
|
714
844
|
| Try ordered fallback candidates | `firstSuccessOf` |
|
|
@@ -729,6 +859,7 @@ reasons, disposable results, and compatibility APIs.
|
|
|
729
859
|
## More Documentation
|
|
730
860
|
|
|
731
861
|
- [Full Resultar guide](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md)
|
|
862
|
+
- [ResultTask core RFC](https://github.com/inaiat/resultar/blob/main/packages/resultar/RESULT-TASK-CORE-RFC.md)
|
|
732
863
|
- [Runnable core cookbook](https://github.com/inaiat/resultar/tree/main/examples/resultar)
|
|
733
864
|
- [Catching and recovering errors](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md#catching-and-recovering-errors)
|
|
734
865
|
- [Safe Try](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md#safe-try)
|