resultar 3.5.2 → 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 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 and language-server plugin |
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 safeTry](#linear-workflows-with-safetry)
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 `safeTry`
637
+ ## Linear Workflows With `Result.gen`
511
638
 
512
- Use `safeTry` when a longer chain reads better as linear code. `yield*` extracts the `Ok` value and
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 { safeTry } from 'resultar'
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
- safeTry(async function* () {
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 = safeTry({
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` with a project-local
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 "typescript@>=7"
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 runs TypeScript first and then Resultar diagnostics over the same `tsconfig.json`. Configure
660
- the TypeScript language-server plugin for equivalent feedback while editing:
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
- After the CLI and language server are working, add the Oxlint, ESLint, or Deno Lint adapter only if
677
- you want optional low-latency syntax feedback. The adapters intentionally expose fewer rules because
678
- they do not have TypeScript type information.
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 rules, editor setup, ignore patterns, and lint adapter configuration.
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)
package/dist/index.d.ts CHANGED
@@ -412,6 +412,10 @@ interface ResultStatic {
412
412
  * Runtime `instanceof Result` support for Ok and Err values.
413
413
  */
414
414
  [Symbol.hasInstance]: (value: unknown) => boolean;
415
+ /**
416
+ * Alias for `safeTry`, providing the same generator vocabulary as `ResultTask.gen`.
417
+ */
418
+ gen: typeof safeTry;
415
419
  /**
416
420
  * Runs synchronous work and captures thrown values into a Result.
417
421
  *
@@ -1527,6 +1531,128 @@ declare const isRedacted: (value: unknown) => value is Redacted<unknown>;
1527
1531
  */
1528
1532
  declare const revealRedacted: <T>(value: Redacted<T>) => T;
1529
1533
  //#endregion
1534
+ //#region src/result-task.d.ts
1535
+ /** A failure cause produced by a `ResultTask` execution. */
1536
+ type Cause<E> = {
1537
+ readonly _tag: "Fail";
1538
+ readonly error: E;
1539
+ } | {
1540
+ readonly _tag: "Die";
1541
+ readonly defect: unknown;
1542
+ };
1543
+ /** The complete outcome of a `ResultTask` execution. */
1544
+ type Exit<A, E> = {
1545
+ readonly _tag: "Success";
1546
+ readonly value: A;
1547
+ } | {
1548
+ readonly _tag: "Failure";
1549
+ readonly cause: Cause<E>;
1550
+ };
1551
+ /** A service that can be requested from a `ResultTask.gen` workflow. */
1552
+ interface ServiceTag<Identifier extends string, Service> {
1553
+ readonly _tag: "ServiceTag";
1554
+ readonly identifier: Identifier;
1555
+ readonly key: symbol;
1556
+ readonly [Symbol.iterator]: () => Generator<ResultTaskServiceYield<ServiceTag<Identifier, Service>>, Service, Service>;
1557
+ }
1558
+ /** Options for running a `ResultTask` with an optional signal and its required services. */
1559
+ type ResultTaskRunOptions<R = never> = {
1560
+ readonly signal?: AbortSignal;
1561
+ } & ([R] extends [never] ? {
1562
+ readonly services?: never;
1563
+ } : {
1564
+ readonly services: ResultTaskServices<R>;
1565
+ });
1566
+ /** The object-shaped service environment required by a task. */
1567
+ type ResultTaskServices<R> = { readonly [Tag in Extract<R, AnyServiceTag> as Tag["identifier"]]: ServiceForTag<Tag>; };
1568
+ /** Options for creating a synchronous task with an explicit error boundary. */
1569
+ interface ResultTaskTryOptions<A, E> {
1570
+ readonly try: () => A;
1571
+ readonly catch: (cause: unknown) => E;
1572
+ }
1573
+ /** Options for creating a lazy promise-producing task with an explicit error boundary. */
1574
+ interface ResultTaskTryPromiseOptions<A, E> {
1575
+ readonly try: (signal: AbortSignal) => PromiseLike<A>;
1576
+ readonly catch: (cause: unknown) => E;
1577
+ }
1578
+ type AnyServiceTag = Pick<ServiceTag<string, unknown>, "_tag" | "identifier" | "key">;
1579
+ type ServiceForTag<Tag> = Tag extends ServiceTag<string, infer Service> ? Service : never;
1580
+ interface ResultTaskYield<A, E, R> {
1581
+ readonly _tag: "ResultTask";
1582
+ readonly task: ResultTask<A, E, R>;
1583
+ }
1584
+ interface ResultTaskServiceYield<Tag extends AnyServiceTag> {
1585
+ readonly _tag: "Service";
1586
+ readonly tag: Tag;
1587
+ }
1588
+ type GeneratorError<Yield> = Yield extends {
1589
+ readonly _tag: "ResultTask";
1590
+ readonly task: infer Task;
1591
+ } ? Task extends ResultTask<infer _A, infer E, infer _R> ? E : never : never;
1592
+ type GeneratorRequirements<Yield> = Yield extends {
1593
+ readonly _tag: "ResultTask";
1594
+ readonly task: infer Task;
1595
+ } ? Task extends ResultTask<infer _A, infer _E, infer R> ? R : never : Yield extends {
1596
+ readonly _tag: "Service";
1597
+ readonly tag: infer Tag;
1598
+ } ? Tag : never;
1599
+ type ResultTaskRunArguments<R> = [R] extends [never] ? [options?: ResultTaskRunOptions<R>] : [options: ResultTaskRunOptions<R>];
1600
+ /**
1601
+ * A lazy, composable workflow that can succeed with `A` or fail with `E`.
1602
+ *
1603
+ * Constructing a `ResultTask` never runs its work. Use `runResult`, `runExit`, or `runPromise` at an
1604
+ * explicit application boundary to execute it.
1605
+ */
1606
+ declare class ResultTask<A, E = never, R = never> extends Pipeable {
1607
+ private readonly execute;
1608
+ private constructor();
1609
+ private readonly typeWitness;
1610
+ /** Creates a task that succeeds with `value` when it is executed. */
1611
+ static succeed<A, E = never>(value: A): ResultTask<A, E>;
1612
+ /** Creates a task that fails with `error` when it is executed. */
1613
+ static fail<const E>(error: E): ResultTask<never, E>;
1614
+ /** Lifts an already computed Result into a lazy task. */
1615
+ static fromResult<A, E>(result: Result<A, E>): ResultTask<A, E>;
1616
+ /** Creates a task for synchronous work. Thrown values are defects, not typed failures. */
1617
+ static sync<A>(evaluate: () => A): ResultTask<A>;
1618
+ /** Creates a task that maps synchronous throws into the typed error channel. */
1619
+ static try<A, E>(options: ResultTaskTryOptions<A, E>): ResultTask<A, E>;
1620
+ /** Creates a lazy task that maps synchronous throws and promise rejections into `E`. */
1621
+ static tryPromise<A, E>(options: ResultTaskTryPromiseOptions<A, E>): ResultTask<A, E>;
1622
+ /** Creates a service tag that can be requested with `yield*` inside `ResultTask.gen`. */
1623
+ static service<Service, const Identifier extends string>(identifier: Identifier): ServiceTag<Identifier, Service>;
1624
+ /** Builds a task from a generator and short-circuits on the first failed task. */
1625
+ static gen<Yield, Return, Next>(body: () => Generator<Yield, Return, Next>): ResultTask<Return, GeneratorError<Yield>, GeneratorRequirements<Yield>>;
1626
+ /** Maps the success value without executing the task. */
1627
+ map<B>(f: (value: A) => B): ResultTask<B, E, R>;
1628
+ /** Chains another task from the success value without executing either task immediately. */
1629
+ flatMap<B, E2, R2>(f: (value: A) => ResultTask<B, E2, R2>): ResultTask<B, E | E2, R | R2>;
1630
+ /** Alias for `flatMap`, matching the existing Resultar vocabulary. */
1631
+ andThen<B, E2, R2>(f: (value: A) => ResultTask<B, E2, R2>): ResultTask<B, E | E2, R | R2>;
1632
+ /** Recovers from a typed failure without recovering from a runtime defect. */
1633
+ catchAll<B, E2, R2>(f: (error: E) => ResultTask<B, E2, R2>): ResultTask<A | B, E2, R | R2>;
1634
+ /** Enables `yield* task` inside `ResultTask.gen` workflows. */
1635
+ [Symbol.iterator](): Generator<ResultTaskYield<A, E, R>, A, Exit<A, E>>;
1636
+ /** Provides one required service and removes it from the task's requirements. */
1637
+ static provideService<A, E, R, Tag extends Extract<R, AnyServiceTag>>(task: ResultTask<A, E, R>, tag: Tag, service: ServiceForTag<Tag>): ResultTask<A, E, Exclude<R, Tag>>;
1638
+ /** Provides all requirements using an object keyed by service identifier. */
1639
+ static provideServices<A, E, R>(task: ResultTask<A, E, R>, services: ResultTaskServices<R>): ResultTask<A, E>;
1640
+ /** Maps the success value using the canonical functional form. */
1641
+ static map<A, E, R, B>(task: ResultTask<A, E, R>, f: (value: A) => B): ResultTask<B, E, R>;
1642
+ /** Chains a task using the canonical functional form. */
1643
+ static flatMap<A, E, R, B, E2, R2>(task: ResultTask<A, E, R>, f: (value: A) => ResultTask<B, E2, R2>): ResultTask<B, E | E2, R | R2>;
1644
+ /** Recovers from a typed failure using the canonical functional form. */
1645
+ static catchAll<A, E, R, B, E2, R2>(task: ResultTask<A, E, R>, f: (error: E) => ResultTask<B, E2, R2>): ResultTask<A | B, E2, R | R2>;
1646
+ private static runExitInternal;
1647
+ private static runResultInternal;
1648
+ /** Runs a task and preserves success, typed failure, and runtime defects in an `Exit`. */
1649
+ static runExit<A, E, R>(task: ResultTask<A, E, R>, ...args: ResultTaskRunArguments<R>): Promise<Exit<A, E>>;
1650
+ /** Runs a task and returns a Result, rejecting only when a runtime defect occurs. */
1651
+ static runResult<A, E, R>(task: ResultTask<A, E, R>, ...args: ResultTaskRunArguments<R>): Promise<Result<A, E>>;
1652
+ /** Runs a task and returns the success value, rejecting on typed failures or defects. */
1653
+ static runPromise<A, E, R>(task: ResultTask<A, E, R>, ...args: ResultTaskRunArguments<R>): Promise<A>;
1654
+ }
1655
+ //#endregion
1530
1656
  //#region src/tagged-error.d.ts
1531
1657
  type Alpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "_";
1532
1658
  type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
@@ -1691,4 +1817,4 @@ declare const matchError: <ErrorType extends Error, ReturnType>(error: ErrorType
1691
1817
  */
1692
1818
  declare const matchErrorPartial: <ErrorType extends Error, ReturnType>(error: ErrorType, handlers: PartialMatchHandlers<ErrorType, ReturnType>, fallback: (error: ErrorType) => ReturnType) => ReturnType;
1693
1819
  //#endregion
1694
- export { AbortError, DisposableResult, DisposableResultAsync, type ErrResult, type OkResult, type Redacted, Result, ResultAsync, type ResultAsyncAbortSignal, type ResultAsyncCallbackCleanup, type ResultAsyncCallbackContext, type ResultAsyncConcurrency, type ResultAsyncFromCallbackOptions, type ResultAsyncRaceHandle, type ResultAsyncRaceTask, type ResultAsyncResourceAcquire, type ResultAsyncResourceEffect, type ResultAsyncResourceRelease, type ResultAsyncResourceReleaseContext, type ResultAsyncResourceUse, type ResultAsyncRetryContext, type ResultAsyncRetryOptions, type ResultAsyncRetryOrElseOptions, type ResultAsyncRetryTask, type ResultAsyncTimeoutOptions, type ResultAsyncWithResourceOptions, type ResultOperations, type SafeTryAsyncOptions, type SafeTryOptions, type StrictResult, type StrictResultAsync, type TaggedEnum, type TaggedEnumFactory, type TaggedErrorClass, type TaggedErrorInstance, type TaggedErrorOptions, type TryResultAsyncOptions, type TryResultOptions, createTaggedError, tryResult as default, tryResult as try, tryResult, err, errAsync, findCause, fromCallback, fromPromise, fromSafePromise, fromThrowable, fromThrowableAsync, isAbortError, isError, isRedacted, matchError, matchErrorPartial, ok, okAsync, redact, revealRedacted, runPromise, runSync, safeTry, taggedEnum, tryResultAsync as tryAsync, tryResultAsync, tryCatch, tryCatchAsync, unit, unitAsync };
1820
+ export { AbortError, type Cause, DisposableResult, DisposableResultAsync, type ErrResult, type Exit, type OkResult, type Redacted, Result, ResultAsync, type ResultAsyncAbortSignal, type ResultAsyncCallbackCleanup, type ResultAsyncCallbackContext, type ResultAsyncConcurrency, type ResultAsyncFromCallbackOptions, type ResultAsyncRaceHandle, type ResultAsyncRaceTask, type ResultAsyncResourceAcquire, type ResultAsyncResourceEffect, type ResultAsyncResourceRelease, type ResultAsyncResourceReleaseContext, type ResultAsyncResourceUse, type ResultAsyncRetryContext, type ResultAsyncRetryOptions, type ResultAsyncRetryOrElseOptions, type ResultAsyncRetryTask, type ResultAsyncTimeoutOptions, type ResultAsyncWithResourceOptions, type ResultOperations, ResultTask, type ResultTaskRunOptions, type ResultTaskServices, type ResultTaskTryOptions, type ResultTaskTryPromiseOptions, type SafeTryAsyncOptions, type SafeTryOptions, type ServiceTag, type StrictResult, type StrictResultAsync, type TaggedEnum, type TaggedEnumFactory, type TaggedErrorClass, type TaggedErrorInstance, type TaggedErrorOptions, type TryResultAsyncOptions, type TryResultOptions, createTaggedError, tryResult as default, tryResult as try, tryResult, err, errAsync, findCause, fromCallback, fromPromise, fromSafePromise, fromThrowable, fromThrowableAsync, isAbortError, isError, isRedacted, matchError, matchErrorPartial, ok, okAsync, redact, revealRedacted, runPromise, runSync, safeTry, taggedEnum, tryResultAsync as tryAsync, tryResultAsync, tryCatch, tryCatchAsync, unit, unitAsync };
package/dist/index.js CHANGED
@@ -192,6 +192,10 @@ var ResultNamespace = class {
192
192
  return value instanceof Ok || value instanceof Err;
193
193
  }
194
194
  /**
195
+ * Alias for the top-level `safeTry` generator workflow.
196
+ */
197
+ static gen = safeTry;
198
+ /**
195
199
  * Creates a `Result` by running a function that might throw.
196
200
  * If the function throws, the error will be caught and returned as an `Err`.
197
201
  * If the function succeeds, the value will be wrapped in an `Ok`.
@@ -2023,4 +2027,282 @@ const runPromise = (result) => result.unwrapOrThrow();
2023
2027
  /** Compatibility alias. Prefer `tryResultAsync` in new code. */
2024
2028
  const tryCatchAsync = tryResultAsync;
2025
2029
  //#endregion
2026
- export { AbortError, DisposableResult, DisposableResultAsync, Result, ResultAsync, createTaggedError, tryResult as default, tryResult as try, tryResult, err, errAsync, findCause, fromCallback, fromPromise, fromSafePromise, fromThrowable, fromThrowableAsync, isAbortError, isError, isRedacted, matchError, matchErrorPartial, ok, okAsync, redact, revealRedacted, runPromise, runSync, safeTry, taggedEnum, tryResultAsync as tryAsync, tryResultAsync, tryCatch, tryCatchAsync, unit, unitAsync };
2030
+ //#region src/result-task.ts
2031
+ const success = (value) => ({
2032
+ _tag: "Success",
2033
+ value
2034
+ });
2035
+ const failure = (cause) => ({
2036
+ _tag: "Failure",
2037
+ cause
2038
+ });
2039
+ const failed = (error) => failure({
2040
+ _tag: "Fail",
2041
+ error
2042
+ });
2043
+ const died = (defect) => failure({
2044
+ _tag: "Die",
2045
+ defect
2046
+ });
2047
+ const throwDefect = (defect) => {
2048
+ throw defect;
2049
+ };
2050
+ const isRecord = (value) => typeof value === "object" && value !== null;
2051
+ const createMissingServiceError = (identifier) => {
2052
+ const error = /* @__PURE__ */ new Error(`Missing ResultTask service: ${identifier}`);
2053
+ error.name = "MissingServiceError";
2054
+ return error;
2055
+ };
2056
+ const isResultTaskYield = (value) => isRecord(value) && value["_tag"] === "ResultTask" && value["task"] instanceof ResultTask;
2057
+ const isServiceYield = (value) => isRecord(value) && value["_tag"] === "Service" && value["tag"] instanceof ServiceTagValue;
2058
+ const lookupService = (tag, context) => {
2059
+ if (context.services.has(tag.key)) return context.services.get(tag.key);
2060
+ return context.namedServices.get(tag.identifier);
2061
+ };
2062
+ const executeTaskSafely = async (task, executeTask) => {
2063
+ try {
2064
+ return await executeTask(task);
2065
+ } catch (error) {
2066
+ return { error };
2067
+ }
2068
+ };
2069
+ const closeGenerator = async (iterator, exit, runtime) => {
2070
+ try {
2071
+ let step = iterator.return(void 0);
2072
+ let finalExit = exit;
2073
+ while (step.done === false) if (isResultTaskYield(step.value)) {
2074
+ const outcome = await executeTaskSafely(step.value.task, runtime.executeTask);
2075
+ if ("error" in outcome) {
2076
+ finalExit = died(outcome.error);
2077
+ step = iterator.return(void 0);
2078
+ } else if (outcome._tag === "Failure") {
2079
+ finalExit = outcome;
2080
+ step = iterator.return(void 0);
2081
+ } else step = iterator.next(outcome);
2082
+ } else if (isServiceYield(step.value)) {
2083
+ const service = lookupService(step.value.tag, runtime.context);
2084
+ if (service === void 0) {
2085
+ finalExit = died(createMissingServiceError(step.value.tag.identifier));
2086
+ step = iterator.return(void 0);
2087
+ } else step = iterator.next(service);
2088
+ } else {
2089
+ finalExit = died(/* @__PURE__ */ new TypeError("ResultTask.gen yielded an unsupported value"));
2090
+ step = iterator.return(void 0);
2091
+ }
2092
+ return finalExit;
2093
+ } catch (error) {
2094
+ return died(error);
2095
+ }
2096
+ };
2097
+ const withServices = (context, services) => ({
2098
+ namedServices: context.namedServices,
2099
+ services,
2100
+ signal: context.signal
2101
+ });
2102
+ const withNamedServices = (context, namedServices) => ({
2103
+ namedServices,
2104
+ services: context.services,
2105
+ signal: context.signal
2106
+ });
2107
+ var ServiceTagValue = class {
2108
+ _tag = "ServiceTag";
2109
+ identifier;
2110
+ key;
2111
+ constructor(identifier) {
2112
+ this.identifier = identifier;
2113
+ this.key = Symbol(identifier);
2114
+ }
2115
+ *[Symbol.iterator]() {
2116
+ return yield {
2117
+ _tag: "Service",
2118
+ tag: this
2119
+ };
2120
+ }
2121
+ };
2122
+ /**
2123
+ * A lazy, composable workflow that can succeed with `A` or fail with `E`.
2124
+ *
2125
+ * Constructing a `ResultTask` never runs its work. Use `runResult`, `runExit`, or `runPromise` at an
2126
+ * explicit application boundary to execute it.
2127
+ */
2128
+ var ResultTask = class ResultTask extends Pipeable {
2129
+ execute;
2130
+ constructor(execute) {
2131
+ super();
2132
+ this.execute = execute;
2133
+ }
2134
+ /** Creates a task that succeeds with `value` when it is executed. */
2135
+ static succeed(value) {
2136
+ return new ResultTask(() => Promise.resolve(success(value)));
2137
+ }
2138
+ /** Creates a task that fails with `error` when it is executed. */
2139
+ static fail(error) {
2140
+ return new ResultTask(() => Promise.resolve(failed(error)));
2141
+ }
2142
+ /** Lifts an already computed Result into a lazy task. */
2143
+ static fromResult(result) {
2144
+ return new ResultTask(() => Promise.resolve(result.isOk() ? success(result.value) : failed(result.error)));
2145
+ }
2146
+ /** Creates a task for synchronous work. Thrown values are defects, not typed failures. */
2147
+ static sync(evaluate) {
2148
+ return new ResultTask(() => Promise.resolve(success(evaluate())));
2149
+ }
2150
+ /** Creates a task that maps synchronous throws into the typed error channel. */
2151
+ static try(options) {
2152
+ return new ResultTask(() => {
2153
+ try {
2154
+ return Promise.resolve(success(options.try()));
2155
+ } catch (error) {
2156
+ return Promise.resolve(failed(options.catch(error)));
2157
+ }
2158
+ });
2159
+ }
2160
+ /** Creates a lazy task that maps synchronous throws and promise rejections into `E`. */
2161
+ static tryPromise(options) {
2162
+ return new ResultTask(async (context) => {
2163
+ try {
2164
+ return success(await options.try(context.signal));
2165
+ } catch (error) {
2166
+ return failed(options.catch(error));
2167
+ }
2168
+ });
2169
+ }
2170
+ /** Creates a service tag that can be requested with `yield*` inside `ResultTask.gen`. */
2171
+ static service(identifier) {
2172
+ return new ServiceTagValue(identifier);
2173
+ }
2174
+ /** Builds a task from a generator and short-circuits on the first failed task. */
2175
+ static gen(body) {
2176
+ return new ResultTask(async (context) => {
2177
+ try {
2178
+ const iterator = body();
2179
+ const executeTask = (task) => task.execute(context);
2180
+ let step = iterator.next();
2181
+ while (step.done === false) if (isResultTaskYield(step.value)) {
2182
+ const outcome = await executeTaskSafely(step.value.task, executeTask);
2183
+ if ("error" in outcome) return await closeGenerator(iterator, died(outcome.error), {
2184
+ context,
2185
+ executeTask
2186
+ });
2187
+ if (outcome._tag === "Failure") return await closeGenerator(iterator, outcome, {
2188
+ context,
2189
+ executeTask
2190
+ });
2191
+ step = iterator.next(outcome);
2192
+ } else if (isServiceYield(step.value)) {
2193
+ const service = lookupService(step.value.tag, context);
2194
+ if (service === void 0) return await closeGenerator(iterator, died(createMissingServiceError(step.value.tag.identifier)), {
2195
+ context,
2196
+ executeTask
2197
+ });
2198
+ step = iterator.next(service);
2199
+ } else return await closeGenerator(iterator, died(/* @__PURE__ */ new TypeError("ResultTask.gen yielded an unsupported value")), {
2200
+ context,
2201
+ executeTask
2202
+ });
2203
+ return success(step.value);
2204
+ } catch (error) {
2205
+ return died(error);
2206
+ }
2207
+ });
2208
+ }
2209
+ /** Maps the success value without executing the task. */
2210
+ map(f) {
2211
+ return new ResultTask(async (context) => {
2212
+ const exit = await this.execute(context);
2213
+ if (exit._tag === "Failure") return exit;
2214
+ return success(f(exit.value));
2215
+ });
2216
+ }
2217
+ /** Chains another task from the success value without executing either task immediately. */
2218
+ flatMap(f) {
2219
+ return new ResultTask(async (context) => {
2220
+ const exit = await this.execute(context);
2221
+ if (exit._tag === "Failure") return exit;
2222
+ return f(exit.value).execute(context);
2223
+ });
2224
+ }
2225
+ /** Alias for `flatMap`, matching the existing Resultar vocabulary. */
2226
+ andThen(f) {
2227
+ return this.flatMap(f);
2228
+ }
2229
+ /** Recovers from a typed failure without recovering from a runtime defect. */
2230
+ catchAll(f) {
2231
+ return new ResultTask(async (context) => {
2232
+ const exit = await this.execute(context);
2233
+ if (exit._tag === "Success") return exit;
2234
+ if (exit.cause._tag === "Die") return exit;
2235
+ return f(exit.cause.error).execute(context);
2236
+ });
2237
+ }
2238
+ /** Enables `yield* task` inside `ResultTask.gen` workflows. */
2239
+ *[Symbol.iterator]() {
2240
+ const exit = yield {
2241
+ _tag: "ResultTask",
2242
+ task: this
2243
+ };
2244
+ if (exit._tag === "Success") return exit.value;
2245
+ return throwDefect(/* @__PURE__ */ new Error("ResultTask generator received a failed task result"));
2246
+ }
2247
+ /** Provides one required service and removes it from the task's requirements. */
2248
+ static provideService(task, tag, service) {
2249
+ return new ResultTask(async (context) => {
2250
+ const services = new Map([...context.services, [tag.key, service]]);
2251
+ return task.execute(withServices(context, services));
2252
+ });
2253
+ }
2254
+ /** Provides all requirements using an object keyed by service identifier. */
2255
+ static provideServices(task, services) {
2256
+ return new ResultTask(async (context) => {
2257
+ const namedServices = new Map(context.namedServices);
2258
+ for (const [identifier, service] of Object.entries(services)) namedServices.set(identifier, service);
2259
+ return task.execute(withNamedServices(context, namedServices));
2260
+ });
2261
+ }
2262
+ /** Maps the success value using the canonical functional form. */
2263
+ static map(task, f) {
2264
+ return task.map(f);
2265
+ }
2266
+ /** Chains a task using the canonical functional form. */
2267
+ static flatMap(task, f) {
2268
+ return task.flatMap(f);
2269
+ }
2270
+ /** Recovers from a typed failure using the canonical functional form. */
2271
+ static catchAll(task, f) {
2272
+ return task.catchAll(f);
2273
+ }
2274
+ static async runExitInternal(task, options) {
2275
+ const signal = options?.signal ?? new AbortController().signal;
2276
+ const suppliedServices = options !== void 0 && "services" in options ? options.services : void 0;
2277
+ const context = {
2278
+ namedServices: new Map(Object.entries(suppliedServices ?? {})),
2279
+ services: /* @__PURE__ */ new Map(),
2280
+ signal
2281
+ };
2282
+ try {
2283
+ return await task.execute(context);
2284
+ } catch (error) {
2285
+ return died(error);
2286
+ }
2287
+ }
2288
+ static async runResultInternal(task, options) {
2289
+ const exit = await ResultTask.runExitInternal(task, options);
2290
+ if (exit._tag === "Success") return ok(exit.value);
2291
+ if (exit.cause._tag === "Fail") return err(exit.cause.error);
2292
+ return throwDefect(exit.cause.defect);
2293
+ }
2294
+ /** Runs a task and preserves success, typed failure, and runtime defects in an `Exit`. */
2295
+ static runExit(task, ...args) {
2296
+ return ResultTask.runExitInternal(task, args[0]);
2297
+ }
2298
+ /** Runs a task and returns a Result, rejecting only when a runtime defect occurs. */
2299
+ static runResult(task, ...args) {
2300
+ return ResultTask.runResultInternal(task, args[0]);
2301
+ }
2302
+ /** Runs a task and returns the success value, rejecting on typed failures or defects. */
2303
+ static async runPromise(task, ...args) {
2304
+ return (await ResultTask.runResultInternal(task, args[0])).unwrapOrThrow();
2305
+ }
2306
+ };
2307
+ //#endregion
2308
+ export { AbortError, DisposableResult, DisposableResultAsync, Result, ResultAsync, ResultTask, createTaggedError, tryResult as default, tryResult as try, tryResult, err, errAsync, findCause, fromCallback, fromPromise, fromSafePromise, fromThrowable, fromThrowableAsync, isAbortError, isError, isRedacted, matchError, matchErrorPartial, ok, okAsync, redact, revealRedacted, runPromise, runSync, safeTry, taggedEnum, tryResultAsync as tryAsync, tryResultAsync, tryCatch, tryCatchAsync, unit, unitAsync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resultar",
3
- "version": "3.5.2",
3
+ "version": "3.6.0",
4
4
  "description": "Result pattern for TypeScript",
5
5
  "keywords": [
6
6
  "error-handling",
@@ -43,14 +43,14 @@
43
43
  "devDependencies": {
44
44
  "@stryker-mutator/core": "10.0.0",
45
45
  "@stryker-mutator/vitest-runner": "10.0.0",
46
- "@types/node": "26.2.0",
47
- "@vitest/coverage-v8": "4.1.10",
48
- "@vitest/ui": "4.1.10",
49
- "fallow": "3.17.0",
50
- "tsx": "4.23.12",
46
+ "@types/node": "26.4.0",
47
+ "@vitest/coverage-v8": "4.1.11",
48
+ "@vitest/ui": "4.1.11",
49
+ "fallow": "3.20.0",
50
+ "tsx": "4.23.13",
51
51
  "typescript": "7.0.2",
52
- "vite-plus": "0.2.9",
53
- "vitest": "4.1.10"
52
+ "vite-plus": "0.3.0",
53
+ "vitest": "4.1.11"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "typescript": ">=7.0.0"