Skip to content

Commit e8843a4

Browse files
committed
feat(std): init io module and improve results in helpers
1 parent f7161b9 commit e8843a4

File tree

15 files changed

+197
-15
lines changed

15 files changed

+197
-15
lines changed

packages/std/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
"source": "./src/shared/index.ts",
1515
"default": "./dist/shared.js",
1616
"types": "./dist/shared.d.ts"
17+
},
18+
"./io": {
19+
"source": "./src/io/index.ts",
20+
"default": "./dist/io.js",
21+
"types": "./dist/io.d.ts"
1722
}
1823
},
1924

packages/std/src/io/definition.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path="../shared/index.ts" />
2+
3+
import type { ioTags } from './tag'
4+
5+
declare global {
6+
namespace Std {
7+
// ------------ Errors ------------
8+
interface CustomErrors {
9+
'std/io': typeof ioTags
10+
}
11+
}
12+
}

packages/std/src/io/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import './definition'
2+
3+
export * from './utils/read'
4+
5+
export * from './tag'

packages/std/src/io/tag.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Tags } from '../results'
2+
3+
export const ioTags = Tags.create('std/io')
4+
.add('not-found')
5+
6+
// causes
7+
.add('read')

packages/std/src/io/utils/read.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { $fn, err } from '../../results'
2+
3+
import { ioTags } from '../tag'
4+
5+
export const $read = $fn(async (path: string) => {
6+
const file = Bun.file(path)
7+
const exists = await file.exists()
8+
9+
if (!exists) {
10+
return err(ioTags.get('not-found'), `file: ${path} not found`)
11+
}
12+
13+
return 'sa'
14+
}, ioTags.get('read'))
15+
16+
// biome-ignore lint/suspicious/noConsole: <explanation>
17+
console.log(await $read('./text'))

packages/std/src/results/definition.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
/// <reference path="../shared/index.ts" />
2+
13
import type { BlobType, LiteralUnion } from '../shared'
24

35
import type { resultTags } from './tag'
6+
import type { ResultAsync } from './utils/async'
47
import type { Err } from './utils/err'
58
import type { Ok } from './utils/ok'
69
import type { Tags } from './utils/tag'
@@ -37,7 +40,11 @@ declare global {
3740
*/
3841
type ErrorValues = LiteralUnion<Std.ExtractErrors<Std.Error[keyof Std.Error]>, `?${string}`>
3942

40-
type InferOkType<R> = R extends Std.Result<infer T, BlobType, BlobType[]> ? T : never
43+
type InferOkType<R> = R extends Std.Result<infer T, BlobType, BlobType[]>
44+
? T
45+
: R extends ResultAsync<infer T, BlobType>
46+
? T
47+
: never
4148
type InferNameType<R> = R extends Err<BlobType, infer N, BlobType[]> ? N : never
4249
type InferCauseType<R> = R extends Err<BlobType, BlobType, infer C> ? C : []
4350

@@ -75,8 +82,17 @@ declare global {
7582
| Ok<T, N, C>
7683
| Err<T, N, C>
7784

85+
type ExtractFromNested<U> = U extends PromiseLike<BlobType>
86+
? Std.InferOkType<Awaited<U>> extends never
87+
? Std.ExtractFromNested<Awaited<U>>
88+
: Std.ExtractFromNested<Std.InferOkType<Awaited<U>>>
89+
: U extends Ok<infer T>
90+
? Std.ExtractFromNested<T>
91+
: U
92+
7893
type UnionsToResult<
79-
U,
94+
R,
95+
U = R extends PromiseLike<BlobType> ? Std.ExtractFromNested<R> : R,
8096
N = Exclude<U, Ok<BlobType, BlobType, BlobType[]> | Err<BlobType, BlobType, BlobType>>,
8197
O = Extract<
8298
U | (N extends never ? never : Ok<N, never, never>),
@@ -85,7 +101,9 @@ declare global {
85101
E = Extract<U, Err<BlobType, BlobType, BlobType>>,
86102
> = O | E extends never
87103
? never
88-
: Std.Result<Std.InferOkType<O>, Std.InferNameType<E>, Std.InferCauseType<E>>
104+
: R extends PromiseLike<BlobType>
105+
? ResultAsync<Std.InferOkType<O>, Std.InferNameType<E>, Std.InferCauseType<E>>
106+
: Std.Result<Std.InferOkType<O>, Std.InferNameType<E>, Std.InferCauseType<E>>
89107

90108
type InjectError<
91109
U,
@@ -97,6 +115,12 @@ declare global {
97115
Un | N,
98116
Uc[number] | C[number] extends never ? [] : (Uc[number] | C[number])[]
99117
>
100-
: never
118+
: U extends ResultAsync<infer O, infer Un, infer Uc>
119+
? ResultAsync<
120+
O,
121+
Un | N,
122+
Uc[number] | C[number] extends never ? [] : (Uc[number] | C[number])[]
123+
>
124+
: never
101125
}
102126
}

packages/std/src/results/utils/async.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class ResultAsync<T, N extends Std.ErrorValues, C extends Std.ErrorValues
3434
}
3535
}
3636

37-
export const okAsync = <T, N extends Std.ErrorValues>(value: T): ResultAsync<T, N, never> =>
37+
export const okAsync = <T>(value: T): ResultAsync<T, never, never> =>
3838
new ResultAsync(Promise.resolve(new Ok<T, never, never>(value)))
3939

4040
export const errAsync = <N extends Std.ErrorValues>(

packages/std/src/results/utils/err.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class Err<
1414
// biome-ignore lint/nursery/useConsistentMemberAccessibility: Redundant
1515
public message: string,
1616
// biome-ignore lint/nursery/useConsistentMemberAccessibility: Redundant
17-
public cause = [] as unknown as C,
17+
public causes = [] as unknown as C,
1818
// biome-ignore lint/nursery/useConsistentMemberAccessibility: Redundant
1919
public data: BlobType[] = [],
2020
// biome-ignore lint/nursery/useConsistentMemberAccessibility: Redundant
@@ -46,8 +46,8 @@ export class Err<
4646
*/
4747
appendCause<const A extends Std.ErrorValues[] = []>(...additionalCauses: A) {
4848
for (const additionalCause of additionalCauses) {
49-
if (additionalCause && this.cause[this.cause.length - 1] !== additionalCause) {
50-
this.cause.push(additionalCause)
49+
if (additionalCause && this.causes[this.causes.length - 1] !== additionalCause) {
50+
this.causes.push(additionalCause)
5151
}
5252
}
5353

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { type BlobType, isPromise } from '../../shared'
2+
3+
import { ResultAsync } from './async'
4+
import { Err } from './err'
5+
import { Ok, ok } from './ok'
6+
7+
export const extractNestedResult = (value: BlobType): Std.Result<BlobType, BlobType, BlobType> => {
8+
let result = value
9+
10+
while (result instanceof Ok) {
11+
result = result.value
12+
}
13+
14+
if (result instanceof Err) {
15+
return result
16+
}
17+
18+
return ok(result)
19+
}
20+
21+
export const extractNestedAsyncResult = (
22+
value: BlobType
23+
): ResultAsync<BlobType, BlobType, BlobType> => {
24+
return new ResultAsync(
25+
value.then(async (rawData: BlobType) => {
26+
const data = rawData.value ?? rawData
27+
28+
if (data instanceof Err) {
29+
return data
30+
}
31+
32+
if (data instanceof Ok) {
33+
return extractNestedResult(data)
34+
}
35+
36+
let result = data
37+
38+
while (isPromise(result)) {
39+
const awaited = await result
40+
41+
if (awaited instanceof Err) {
42+
return awaited
43+
}
44+
45+
if (awaited instanceof Ok) {
46+
return extractNestedResult(awaited)
47+
}
48+
49+
result = awaited.value ?? awaited
50+
}
51+
52+
return ok(result)
53+
})
54+
)
55+
}

packages/std/src/results/utils/fn.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type { BlobType } from '../../shared'
1+
import { type BlobType, isPromise } from '../../shared'
22

33
import { Err } from './err'
4+
import { extractNestedAsyncResult, extractNestedResult } from './extract'
45
import { Ok, ok } from './ok'
56

67
export const $fn = <A extends BlobType[], R, C extends Std.ErrorValues = never>(
@@ -10,6 +11,10 @@ export const $fn = <A extends BlobType[], R, C extends Std.ErrorValues = never>(
1011
return ((...args: A) => {
1112
const result = fn(...args)
1213

14+
if (isPromise(result)) {
15+
return extractNestedAsyncResult(result)
16+
}
17+
1318
if (!(result instanceof Err || result instanceof Ok)) {
1419
return ok(result)
1520
}
@@ -18,6 +23,6 @@ export const $fn = <A extends BlobType[], R, C extends Std.ErrorValues = never>(
1823
result.appendCause(additionalCause)
1924
}
2025

21-
return result
26+
return extractNestedResult(result)
2227
}) as BlobType
2328
}

packages/std/src/results/utils/try.ts

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,47 @@
1-
import type { BlobType } from '../../shared'
1+
import { type BlobType, isPromise } from '../../shared'
22

33
import { resultTags } from '../tag'
44
import { Err, err } from './err'
5+
import { extractNestedAsyncResult, extractNestedResult } from './extract'
56
import { Ok, ok } from './ok'
67

78
const invalidUsage = resultTags.get('invalid-usage')
89
const cause = resultTags.get('try')
910

10-
export const $try = <R, R2, C extends Std.ErrorValues = never>(
11+
export function $try<R, R2, C extends Std.ErrorValues = never>(
1112
body: () => Generator<R, R2>,
1213
additionalCause?: C
1314
): Std.InjectError<
1415
Std.UnionsToResult<R | R2>,
1516
typeof invalidUsage,
1617
C extends never ? (typeof cause)[] : (typeof cause | C)[]
17-
> => {
18+
>
19+
export function $try<R, R2, C extends Std.ErrorValues = never>(
20+
body: () => AsyncGenerator<R, R2>,
21+
additionalCause?: C
22+
): Std.InjectError<
23+
Std.UnionsToResult<
24+
(R extends never ? never : Promise<R>) | (R2 extends never ? never : Promise<R2>)
25+
>,
26+
typeof invalidUsage,
27+
C extends never ? (typeof cause)[] : (typeof cause | C)[]
28+
>
29+
export function $try<R, R2, R3, R4, C extends Std.ErrorValues = never>(
30+
body: (() => AsyncGenerator<R, R2>) | (() => Generator<R3, R4>),
31+
additionalCause?: C
32+
): Std.InjectError<
33+
Std.UnionsToResult<
34+
(R extends never ? never : Promise<R>) | (R2 extends never ? never : Promise<R2>) | R3 | R4
35+
>,
36+
typeof invalidUsage,
37+
C extends never ? (typeof cause)[] : (typeof cause | C)[]
38+
> {
1839
const n = body().next()
1940

41+
if (isPromise(n)) {
42+
return extractNestedAsyncResult(n) as BlobType
43+
}
44+
2045
if (n.value instanceof Err) {
2146
if (additionalCause) {
2247
n.value.appendCause(additionalCause)
@@ -26,7 +51,7 @@ export const $try = <R, R2, C extends Std.ErrorValues = never>(
2651
}
2752

2853
if (n.value instanceof Ok) {
29-
return n.value as BlobType
54+
return extractNestedResult(n) as BlobType
3055
}
3156

3257
if (!n.done) {

packages/std/src/shared/definition.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path="../results/index.ts" />
2+
3+
import type { sharedTags } from './tag'
4+
5+
declare global {
6+
namespace Std {
7+
// ------------ Errors ------------
8+
interface CustomErrors {
9+
'std/shared': typeof sharedTags
10+
}
11+
}
12+
}

packages/std/src/shared/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import './definition'
2+
13
export * from './types/common'
24

3-
export * from 'type-fest'
5+
export * from './utils/types'
6+
7+
export type * from 'type-fest'

packages/std/src/shared/tag.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Tags } from '../results'
2+
3+
export const sharedTags = Tags.create('std/shared')
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { BlobType } from '../types/common'
2+
3+
/**
4+
* Checks if value is an promise
5+
*/
6+
export const isPromise = (value: unknown): value is PromiseLike<BlobType> => {
7+
return typeof value === 'object' && typeof (value as BlobType)?.then === 'function'
8+
}

0 commit comments

Comments
 (0)