resultar 3.0.0-alpha.3 → 3.1.0-beta.1

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
@@ -2,9 +2,27 @@
2
2
 
3
3
  Production-facing error handling for TypeScript, without a runtime.
4
4
 
5
- Resultar gives you typed `Result` primitives, real `Error`-based tagged failures, sync/async
6
- composition, and optional no-discard diagnostics. It is small enough to adopt in one module and
7
- strict enough to use at service, HTTP, job, queue, CLI, and integration boundaries.
5
+ Full documentation, long-form recipes, and the complete API map live in
6
+ [DOCUMENTATION.md](../../DOCUMENTATION.md).
7
+
8
+ ## New In 3.1
9
+
10
+ Resultar 3.1 turns common production async policies into typed values instead of rejected promises,
11
+ hidden timers, or helper code that throws.
12
+
13
+ - `ResultAsync.timeout`, `retry`, `retryOrElse`, `race`, `raceAll`, and `withResource` cover
14
+ timeouts, retry policy, fallback, replica reads, and cleanup while preserving the expected error
15
+ type.
16
+ - `ResultAsync.forEach` and `ResultAsync.validateAll` support sequential work, bounded
17
+ `{ concurrency }`, and explicit unbounded execution for batch processing and validation.
18
+ - `AbortError` / `isAbortError` and redacted tagged-error props with `redact`, `isRedacted`, and
19
+ `revealRedacted` make cancellation and sensitive error metadata visible at the type boundary.
20
+
21
+ ## Main Functionalities
22
+
23
+ Resultar gives you typed `Result` primitives, real `Error`-based tagged failures, async control
24
+ helpers, sync/async composition, and optional no-discard diagnostics. It is small enough to adopt in
25
+ one module and strict enough to use at service, HTTP, job, queue, CLI, and integration boundaries.
8
26
 
9
27
  ```ts
10
28
  Result<T, E>
@@ -18,12 +36,14 @@ Use it when expected failures should be impossible to miss:
18
36
  - Function signatures show both the success type and the expected error type.
19
37
  - Domain errors are real `Error` instances with stable tags, messages, `cause`, stack traces, and
20
38
  JSON output.
39
+ - `ResultAsync` can bound concurrent work, race replicas, apply typed timeouts, retry transient
40
+ failures, and pair acquisition with cleanup without adding a runtime scheduler.
21
41
  - Sync and async fallible work compose without `try/catch` blocks scattered through application
22
42
  code.
23
43
  - Ignored `Result` values can be reported by a type-aware no-discard check.
24
44
 
25
- Resultar began as an initial fork of `neverthrow`. The v3 alpha line keeps the explicit wrapper
26
- model, then leans into Resultar-specific tagged errors, strict service-boundary types, TypeScript 6
45
+ Resultar began as an initial fork of `neverthrow`. The v3 line keeps the explicit wrapper model,
46
+ then leans into Resultar-specific tagged errors, strict service-boundary types, TypeScript 6
27
47
  support, and ESM-only packaging.
28
48
 
29
49
  ## Install
@@ -96,6 +116,9 @@ in normal TypeScript.
96
116
  | Expected failures are hidden behind `throw` | `Result<T, E>` and `ResultAsync<T, E>` in function signatures |
97
117
  | Production errors need stable names and metadata | `createTaggedError` classes with `_tag`, template props, `cause`, and `.toJSON()` |
98
118
  | Async flows turn into nested `try/catch` | `tryResultAsync`, `andThen`, `asyncAndThen`, `map`, `orElse`, `safeTry` |
119
+ | Network calls need timeouts, retries, and fallback policy | `ResultAsync.timeout`, `retry`, `retryOrElse`, `race`, and `raceAll` |
120
+ | Batch work needs backpressure without a framework | `ResultAsync.forEach` and mapped `validateAll` with `{ concurrency }` |
121
+ | Resourceful work needs cleanup on every path | `ResultAsync.withResource` and native `AsyncIterable<Result<T, E>>` recipes |
99
122
  | Boundary handlers miss cases | `matchTags` for exhaustive tagged-error handling |
100
123
  | Results are easy to ignore | `resultar-lint check` and the `resultar-lint` TypeScript plugin |
101
124
  | You do not want another application runtime | A small ESM library around explicit values |
@@ -133,6 +156,62 @@ const getPosts = (
133
156
  )
134
157
  ```
135
158
 
159
+ ## Async Control Without A Runtime
160
+
161
+ The 3.1 async helpers cover common production workflows while keeping plain `Promise` and
162
+ `AbortSignal`-compatible APIs underneath. You choose the policy in the type signature instead of
163
+ hiding it in a helper that throws.
164
+
165
+ Retry transient work and fall back to cache only after the retry policy is exhausted:
166
+
167
+ ```ts
168
+ const user = ResultAsync.retryOrElse(
169
+ (attempt, signal) => fetchUser(id, { attempt, signal }),
170
+ {
171
+ times: 2,
172
+ delayMs: ({ nextAttempt }) => nextAttempt * 100,
173
+ while: (error) => error._tag === 'RateLimitError' || error._tag === 'ServiceUnavailableError',
174
+ orElse: () => readCachedUser(id),
175
+ },
176
+ )
177
+ ```
178
+
179
+ Race a primary and replica with cooperative loser abort:
180
+
181
+ ```ts
182
+ const user = ResultAsync.race(
183
+ (signal) => readUserFromPrimary(id, { signal }),
184
+ (signal) => readUserFromReplica(id, { signal }),
185
+ )
186
+ ```
187
+
188
+ Keep timeout as a typed `Err`, not a rejected promise:
189
+
190
+ ```ts
191
+ const user = ResultAsync.timeout(
192
+ (signal) => readUserFromPrimary(id, { signal }),
193
+ {
194
+ timeoutMs: 1_500,
195
+ onTimeout: () => new FetchUserTimeoutError({ id, timeoutMs: 1_500 }),
196
+ },
197
+ )
198
+ ```
199
+
200
+ Process batches with bounded concurrency and explicit cleanup:
201
+
202
+ ```ts
203
+ const imported = ResultAsync.withResource({
204
+ acquire: (signal) => openImportSession({ signal }),
205
+ use: (session, signal) =>
206
+ ResultAsync.forEach(
207
+ rows,
208
+ (row) => importRow(session, row, { signal }),
209
+ { concurrency: 8, discard: true },
210
+ ),
211
+ release: (session) => session.close(),
212
+ })
213
+ ```
214
+
136
215
  ## Quick Start
137
216
 
138
217
  ```ts
@@ -325,14 +404,31 @@ new UserNotFoundError({ id: 'usr_123' })
325
404
  Use `TaggedEnum` only for lightweight tagged unions that do not need to be real errors.
326
405
 
327
406
  ```ts
407
+ import { taggedEnum } from 'resultar'
328
408
  import type { TaggedEnum } from 'resultar'
329
409
 
330
410
  type PaymentError = TaggedEnum<{
331
411
  CardDeclined: { readonly code: string }
332
412
  InsufficientFunds: { readonly balance: number }
333
413
  }>
414
+
415
+ const PaymentError = taggedEnum<{
416
+ CardDeclined: { readonly code: string }
417
+ InsufficientFunds: { readonly balance: number }
418
+ }>()
419
+
420
+ const declined = PaymentError.CardDeclined({ code: 'card_declined' })
421
+
422
+ PaymentError.$is('CardDeclined', declined) // true
423
+ PaymentError.$match(declined, {
424
+ CardDeclined: (error) => error.code,
425
+ InsufficientFunds: (error) => String(error.balance),
426
+ })
334
427
  ```
335
428
 
429
+ Use `redact(value, label?)` for tagged error props that must not leak through messages or JSON.
430
+ Use `revealRedacted(value)` only at the exact boundary that is allowed to see the secret.
431
+
336
432
  ## Composing Results
337
433
 
338
434
  Use `map` for infallible transforms:
@@ -387,6 +483,53 @@ const draftUser = createUser('bad-email').catchTag('InvalidEmailError', (error)
387
483
  )
388
484
  ```
389
485
 
486
+ ## Reasoned Tagged Errors
487
+
488
+ Use `catchReason`, `catchReasons`, and `unwrapReason` when a stable parent error has a nested tagged
489
+ reason. This keeps the outer error useful for logs and boundaries while allowing local recovery by
490
+ the precise reason.
491
+
492
+ ```ts
493
+ import { createTaggedError, err, ok, taggedEnum } from 'resultar'
494
+ import type { StrictResult, TaggedEnum } from 'resultar'
495
+
496
+ const AiReason = taggedEnum<{
497
+ QuotaExceededError: { readonly limit: number }
498
+ RateLimitError: { readonly retryAfterMs: number }
499
+ }>()
500
+
501
+ type AiReason = TaggedEnum<{
502
+ QuotaExceededError: { readonly limit: number }
503
+ RateLimitError: { readonly retryAfterMs: number }
504
+ }>
505
+
506
+ class AiError extends createTaggedError({
507
+ name: 'AiError',
508
+ message: 'AI request failed',
509
+ }) {
510
+ public readonly reason: AiReason
511
+
512
+ public constructor(props: { readonly cause?: unknown; readonly reason: AiReason }) {
513
+ super({ cause: props.cause })
514
+ this.reason = props.reason
515
+ }
516
+ }
517
+
518
+ const askAi = (): StrictResult<string, AiError> =>
519
+ err(new AiError({ reason: AiReason.RateLimitError({ retryAfterMs: 1_000 }) }))
520
+ ```
521
+
522
+ ```ts
523
+ const recovered = askAi().catchReason('AiError', 'RateLimitError', (reason) =>
524
+ ok(`retry after ${reason.retryAfterMs}ms`),
525
+ )
526
+
527
+ const unwrapped = askAi().unwrapReason('AiError')
528
+ ```
529
+
530
+ `catchReason` removes only the handled reason from the remaining error type. `unwrapReason` moves the
531
+ reason union into the error channel when the parent tag matches.
532
+
390
533
  Use `match`, `matchTags`, or `matchTagsPartial` at boundaries where a result becomes a response,
391
534
  log entry, queue acknowledgement, CLI exit code, or UI state.
392
535
 
@@ -410,9 +553,9 @@ const response = createUser(input.email).matchTagsPartial(
410
553
 
411
554
  Use fallback APIs when an expected failure has a useful recovery path.
412
555
 
413
- | Effect fallback idea | Resultar equivalent |
556
+ | Fallback idea | Resultar API |
414
557
  | --- | --- |
415
- | try another effect | `result.orElse(() => fallbackResult)` |
558
+ | try another branch | `result.orElse(() => fallbackResult)` |
416
559
  | replace the failure | `mapErr(...)` or `orElse(() => err(newError))` |
417
560
  | replace the failure with success | `orElse(() => ok(defaultValue))` inside a pipeline |
418
561
  | default at the edge | `unwrapOr(defaultValue)` |
@@ -453,10 +596,147 @@ const user = ResultAsync.firstSuccessOf([
453
596
  ])
454
597
  ```
455
598
 
599
+ Use `combine` with records when independent results should keep names:
600
+
601
+ ```ts
602
+ const context = Result.combine({
603
+ org: readOrg(orgId),
604
+ user: readUser(userId),
605
+ })
606
+
607
+ const validated = Result.combineWithAllErrors({
608
+ email: validateEmail(input.email),
609
+ name: validateName(input.name),
610
+ })
611
+ ```
612
+
613
+ For lazy async traversal, `ResultAsync.forEach` and mapped `ResultAsync.validateAll` run
614
+ sequentially by default. Pass `{ concurrency: n }` for bounded concurrency or
615
+ `{ concurrency: "unbounded" }` to start every mapped task immediately. See
616
+ [DOCUMENTATION.md](../../DOCUMENTATION.md#async-concurrency-mapping) for async concurrency details
617
+ and the runtime features Resultar intentionally keeps out of core.
618
+
619
+ For concurrent async races, use lazy signal-aware tasks. `race` and `raceAll` return the first
620
+ success. `raceFirst` returns the first completed result, success or failure. `timeout` is a
621
+ `raceFirst` convenience.
622
+
623
+ ```ts
624
+ const user = ResultAsync.timeout(
625
+ (signal) => fetchUser(id, { signal }),
626
+ {
627
+ timeoutMs: 2_000,
628
+ onTimeout: () => new FetchUserTimeoutError({ id }),
629
+ },
630
+ )
631
+ ```
632
+
633
+ For transient async failures, use lazy retry tasks. The task receives a zero-based attempt number and
634
+ a `ResultAsyncAbortSignal`-compatible signal; `times` is the number of retries after the first
635
+ attempt.
636
+
637
+ ```ts
638
+ const user = ResultAsync.retry(
639
+ (attempt, signal) => fetchUser(id, { attempt, signal }),
640
+ {
641
+ times: 2,
642
+ delayMs: ({ nextAttempt }) => nextAttempt * 100,
643
+ while: (error) => error._tag === 'RateLimitError',
644
+ },
645
+ )
646
+
647
+ const withFallback = ResultAsync.retryOrElse(
648
+ (attempt, signal) => fetchUser(id, { attempt, signal }),
649
+ {
650
+ times: 2,
651
+ orElse: () => readCachedUser(id),
652
+ },
653
+ )
654
+ ```
655
+
656
+ For resourceful async work, use `ResultAsync.withResource` when acquisition and cleanup must stay
657
+ paired. Cleanup is best-effort and always runs after successful acquisition, including when the use
658
+ step returns `Err` or rejects.
659
+
660
+ ```ts
661
+ const lines = ResultAsync.withResource({
662
+ acquire: (signal) => openFile(path, { signal }),
663
+ use: (file, signal) => readLines(file, { signal }),
664
+ release: (file) => file.close(),
665
+ })
666
+ ```
667
+
668
+ For pull-based streams, keep the runtime native: expose an `AsyncIterable<Result<T, E>>` and close
669
+ resources in `finally`. See
670
+ [DOCUMENTATION.md](../../DOCUMENTATION.md#resourceful-async-iterables) for the full recipe.
671
+
456
672
  Use `catchTag` / `catchTags` when recovery depends on a specific tagged error. Use
457
673
  `matchTagsPartial` when only a boundary needs fallback mapping. Use `unwrapOr` only at final edges
458
674
  where defaulting is intentional.
459
675
 
676
+ ## Catching Errors
677
+
678
+ Resultar has different catch APIs for different error sources. Use the smallest API that matches
679
+ where the failure appears.
680
+
681
+ | Need | Use |
682
+ | --- | --- |
683
+ | Catch thrown sync code now | `tryResult(fn, toError?)` |
684
+ | Wrap a throwing sync function for later | `fromThrowable(fn, toError?)` |
685
+ | Catch an existing rejecting promise | `fromPromise(promise, toError)` |
686
+ | Catch a promise or async factory | `tryResultAsync(promiseOrFactory, toError?)` |
687
+ | Wrap an async function for later | `fromThrowableAsync(fn, toError?)` |
688
+ | Turn one error into another | `mapErr(fn)` |
689
+ | Recover with another result | `orElse(fn)` |
690
+ | Recover specific tagged errors | `catchTag(tag, fn)` or `catchTags(handlers)` |
691
+ | Recover nested tagged reasons | `catchReason(parentTag, reasonTag, fn)` or `catchReasons(parentTag, handlers)` |
692
+ | Convert a result at a boundary | `match`, `matchTags`, or `matchTagsPartial` |
693
+ | Handle an existing `Error` value | `matchError` or `matchErrorPartial` |
694
+ | Map thrown errors in linear code | `safeTry({ try, catch })` |
695
+ | Recover after retry exhaustion | `ResultAsync.retryOrElse(task, options)` |
696
+
697
+ Catch uncontrolled code at the edge:
698
+
699
+ ```ts
700
+ const parsed: StrictResult<Config, ParseConfigError> = tryResult(
701
+ () => JSON.parse(input) as Config,
702
+ (cause) => new ParseConfigError({ cause }),
703
+ )
704
+
705
+ const response: StrictResultAsync<Response, FetchPayloadError> = tryResultAsync(
706
+ () => fetch(url),
707
+ (cause) => new FetchPayloadError({ cause, url }),
708
+ )
709
+ ```
710
+
711
+ Thrown or rejected values are `unknown` at the boundary. The mapper decides the documented error
712
+ type: `ParseConfigError` for parsing and `FetchPayloadError` for fetching.
713
+
714
+ Recover inside the pipeline:
715
+
716
+ ```ts
717
+ const user: StrictResultAsync<User, ReadUserError> = readUserFromCache(id)
718
+ .orElse(() => readUserFromDatabase(id))
719
+ .mapErr((cause) => new ReadUserError({ cause, id }))
720
+ ```
721
+
722
+ The cache/database error union is normalized into one public `ReadUserError` while preserving the
723
+ original error as `cause`.
724
+
725
+ Handle tagged errors at the boundary:
726
+
727
+ ```ts
728
+ const response: HttpResponse = createUser(input).matchTagsPartial(
729
+ (user) => ({ body: user, statusCode: 201 }),
730
+ {
731
+ InvalidEmailError: (error) => ({ body: { code: error._tag }, statusCode: 400 }),
732
+ },
733
+ (error) => ({ body: { code: error._tag }, statusCode: 500 }),
734
+ )
735
+ ```
736
+
737
+ For the full decision map, see
738
+ [Catching And Recovering Errors](../../DOCUMENTATION.md#catching-and-recovering-errors).
739
+
460
740
  ## Wrapping Throwing Code
461
741
 
462
742
  Use `tryResult` and `tryResultAsync` at the edge of uncontrolled code:
@@ -486,6 +766,41 @@ const user = fromPromise(loadUserFromRemote(id), (cause) => new FetchUserError({
486
766
  After that edge conversion, keep your own domain functions returning `Result` values instead of
487
767
  throwing expected failures.
488
768
 
769
+ ## Validation Errors
770
+
771
+ Keep validation failures as normal tagged errors. Map external validators into a small issue shape
772
+ at the adapter boundary.
773
+
774
+ ```ts
775
+ type ValidationIssue = {
776
+ readonly message: string
777
+ readonly path: string
778
+ }
779
+
780
+ class ValidationError extends createTaggedError({
781
+ name: 'ValidationError',
782
+ message: 'Invalid input',
783
+ }) {
784
+ public readonly issues: readonly ValidationIssue[]
785
+
786
+ public constructor(props: {
787
+ readonly cause?: unknown
788
+ readonly issues: readonly ValidationIssue[]
789
+ }) {
790
+ super({ cause: props.cause })
791
+ this.issues = props.issues
792
+ }
793
+ }
794
+
795
+ const issueFromPath = (path: readonly (number | string)[], message: string): ValidationIssue => ({
796
+ message,
797
+ path: path.join('.'),
798
+ })
799
+ ```
800
+
801
+ For Zod, Standard Schema, or any other validator, decode at the edge and map the library-specific
802
+ issue format into `ValidationIssue[]`. Resultar does not depend on any validator package.
803
+
489
804
  ## Unexpected Errors
490
805
 
491
806
  Expected application failures belong in `Err`. Bugs, impossible states, and unrecoverable conditions
@@ -579,20 +894,25 @@ Add a lint-like script:
579
894
  }
580
895
  ```
581
896
 
582
- This fails:
897
+ These fail in the default `must-use` mode:
583
898
 
584
899
  ```ts
585
900
  saveUser(input)
901
+ const result = saveUser(input)
586
902
  ```
587
903
 
588
904
  These are intentional:
589
905
 
590
906
  ```ts
591
- const result = saveUser(input)
592
907
  return saveUser(input)
593
908
  void saveUser(input)
909
+ saveUser(input).match(handleSaved, handleError)
594
910
  ```
595
911
 
912
+ The default mode is neverthrow-style `must-use`: it also reports assigned `Result` values that are
913
+ only passed around and never consumed with `match`, `unwrapOr`, `_unsafeUnwrap`, `isOk`, `isErr`,
914
+ returned, or explicitly discarded. Use `--mode direct` for the lower-noise expression-only check.
915
+
596
916
  For editor diagnostics, enable the TypeScript language-service plugin:
597
917
 
598
918
  ```json
@@ -613,7 +933,7 @@ pnpm exec resultar-lint unpatch
613
933
  ```
614
934
 
615
935
  For TypeScript 7 native-preview projects, `resultar-tsgo` exposes a `tsgo` wrapper that runs native
616
- TypeScript and then Resultar no-discard validation.
936
+ TypeScript and then Resultar lint validation.
617
937
 
618
938
  ## API Decision Guide
619
939
 
@@ -632,6 +952,8 @@ TypeScript and then Resultar no-discard validation.
632
952
  | Transform error | `mapErr` |
633
953
  | Continue with fallible work | `andThen` or `asyncAndThen` |
634
954
  | Recover from failure | `orElse`, `catchTag`, or `catchTags` |
955
+ | Recover from a nested tagged reason | `catchReason` or `catchReasons` |
956
+ | Move a parent error reason into the error channel | `unwrapReason` |
635
957
  | Replace failure with another failure | `mapErr` or `orElse(() => err(newError))` |
636
958
  | Replace failure with success | `orElse(() => ok(defaultValue))` |
637
959
  | Handle a boundary | `match`, `matchTags`, or `matchTagsPartial` |
@@ -640,14 +962,29 @@ TypeScript and then Resultar no-discard validation.
640
962
  | Combine many independent results | `Result.combine` or `ResultAsync.combine` |
641
963
  | Collect all validation failures | `validateAll` or `combineWithAllErrors` |
642
964
  | Try fallback candidates | `firstSuccessOf` |
965
+ | Bound lazy async traversal | `ResultAsync.forEach` or mapped `ResultAsync.validateAll` with `{ concurrency }` |
966
+ | Race concurrent tasks | `ResultAsync.race`, `raceAll`, `raceFirst`, or `raceWith` |
967
+ | Apply a cooperative timeout | `ResultAsync.timeout` |
968
+ | Retry transient async work | `ResultAsync.retry` or `ResultAsync.retryOrElse` |
969
+ | Pair async acquisition with cleanup | `ResultAsync.withResource` |
643
970
  | Process collections sequentially | `loop`, `iterate`, or `forEach` |
644
971
  | Observe without changing outcome | `tap`, `tapError`, or `log` |
645
972
  | Throw intentionally at a final edge | `unwrapOrThrow()` |
646
973
  | Default intentionally at a final edge | `unwrapOr(defaultValue)` |
647
974
 
648
- ## Version 3 Alpha Notes
975
+ ## What Stays Out Of Core
976
+
977
+ Resultar keeps the scope narrow: explicit error values, typed expected failures, and composable
978
+ recovery.
979
+
980
+ - No generator-based runtime DSL.
981
+ - No schedule engine in core.
982
+ - No config, cache, request resolver, resource, fiber, or service runtime.
983
+ - No raw `Error | T` conversion API.
984
+
985
+ ## Version 3 Notes
649
986
 
650
- `3.0.0-alpha.1` is a semver-major prerelease:
987
+ `3.0.0` is a semver-major release:
651
988
 
652
989
  - `no-discard` tooling moved out of the runtime package and into dedicated packages.
653
990
  - TypeScript peer support is now `>=6.0.0`.
@@ -667,27 +1004,29 @@ and `tryResultAsync` in new code and docs.
667
1004
 
668
1005
  ## Documentation
669
1006
 
670
- This README is the project entry point. For a full guide with larger examples and the complete API
671
- map, see [DOCUMENTATION.md](DOCUMENTATION.md).
1007
+ This README is the runtime package entry point. For a full guide with larger examples and the
1008
+ complete API map, see the repository documentation.
672
1009
 
673
1010
  More focused material:
674
1011
 
675
- - [Type-safe error handling article, English](articles/en/type-safe.md)
676
- - [Artigo sobre tratamento de erros type-safe, Portuguese](articles/pt/type-safe.md)
677
- - [resultar-lint package guide](packages/resultar-lint/README.md)
678
- - [resultar-tsgo package guide](packages/resultar-tsgo/README.md)
1012
+ - [Full guide](https://github.com/inaiat/resultar/blob/main/DOCUMENTATION.md)
1013
+ - [Type-safe error handling article, English](https://github.com/inaiat/resultar/blob/main/articles/en/type-safe.md)
1014
+ - [Artigo sobre tratamento de erros type-safe, Portuguese](https://github.com/inaiat/resultar/blob/main/articles/pt/type-safe.md)
1015
+ - [resultar-lint package guide](https://github.com/inaiat/resultar/blob/main/packages/resultar-lint/README.md)
1016
+ - [resultar-tsgo package guide](https://github.com/inaiat/resultar/blob/main/packages/resultar-tsgo/README.md)
679
1017
 
680
- ## Workspace
1018
+ ## Repository
681
1019
 
682
1020
  This repository is a pnpm workspace:
683
1021
 
684
1022
  - `packages/resultar`: Resultar runtime package.
685
1023
  - `packages/resultar-lint`: TypeScript language-service diagnostics and no-discard CLI.
686
- - `packages/resultar-tsgo`: TypeScript 7 native-preview `tsgo` wrapper plus Resultar no-discard
1024
+ - `packages/resultar-tsgo`: TypeScript 7 native-preview `tsgo` wrapper plus Resultar lint
687
1025
  validation.
688
1026
  - `benchmarks`: benchmark package.
689
- - `examples/resultar-lint`: TypeScript 6 no-discard smoke example.
690
- - `examples/tsgo`: TypeScript 7 native-preview smoke example.
1027
+ - `examples/resultar`: runnable core Resultar cookbook.
1028
+ - `examples/lint`: TypeScript 6 Resultar lint, patched `tsc`, and Vite+ / Oxlint smoke example.
1029
+ - `examples/tsgo-lint`: TypeScript 7 native-preview Resultar lint smoke example.
691
1030
 
692
1031
  Common commands:
693
1032
 
@@ -699,7 +1038,7 @@ pnpm test
699
1038
  pnpm run test:cov
700
1039
  pnpm run build
701
1040
  pnpm run check:full
702
- pnpm run test:language-service
1041
+ pnpm run test:examples
703
1042
  pnpm run bench
704
1043
  ```
705
1044