@typed/async-data 0.2.0 → 0.3.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/dist/cjs/AsyncData.js +99 -33
- package/dist/cjs/AsyncData.js.map +1 -1
- package/dist/cjs/Progress.js +10 -7
- package/dist/cjs/Progress.js.map +1 -1
- package/dist/cjs/Schema.js +5 -42
- package/dist/cjs/Schema.js.map +1 -1
- package/dist/cjs/internal/async-data.js +37 -25
- package/dist/cjs/internal/async-data.js.map +1 -1
- package/dist/dts/AsyncData.d.ts +41 -1
- package/dist/dts/AsyncData.d.ts.map +1 -1
- package/dist/dts/Progress.d.ts +2 -1
- package/dist/dts/Progress.d.ts.map +1 -1
- package/dist/dts/Schema.d.ts +0 -5
- package/dist/dts/Schema.d.ts.map +1 -1
- package/dist/dts/internal/async-data.d.ts +11 -3
- package/dist/dts/internal/async-data.d.ts.map +1 -1
- package/dist/esm/AsyncData.js +68 -3
- package/dist/esm/AsyncData.js.map +1 -1
- package/dist/esm/Progress.js +2 -1
- package/dist/esm/Progress.js.map +1 -1
- package/dist/esm/Schema.js +3 -40
- package/dist/esm/Schema.js.map +1 -1
- package/dist/esm/internal/async-data.js +16 -4
- package/dist/esm/internal/async-data.js.map +1 -1
- package/package.json +4 -4
- package/src/AsyncData.ts +99 -1
- package/src/Progress.ts +2 -1
- package/src/Schema.ts +4 -80
- package/src/internal/async-data.ts +12 -4
package/src/AsyncData.ts
CHANGED
|
@@ -6,12 +6,22 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Effect } from "effect"
|
|
9
|
-
import
|
|
9
|
+
import * as Cause from "effect/Cause"
|
|
10
|
+
import * as Data from "effect/Data"
|
|
11
|
+
import * as Duration from "effect/Duration"
|
|
12
|
+
import * as Either from "effect/Either"
|
|
13
|
+
import * as Equal from "effect/Equal"
|
|
14
|
+
import * as Equivalence from "effect/Equivalence"
|
|
15
|
+
import * as Exit from "effect/Exit"
|
|
10
16
|
import { dual } from "effect/Function"
|
|
17
|
+
import * as Option from "effect/Option"
|
|
18
|
+
import * as Unify from "effect/Unify"
|
|
11
19
|
import * as internal from "./internal/async-data.js"
|
|
12
20
|
import { FAILURE_TAG, LOADING_TAG, NO_DATA_TAG, SUCCESS_TAG } from "./internal/tag.js"
|
|
13
21
|
import * as Progress from "./Progress.js"
|
|
14
22
|
|
|
23
|
+
const getCurrentTimestamp = () => Date.now()
|
|
24
|
+
|
|
15
25
|
/**
|
|
16
26
|
* AsyncData represents a piece of data which is acquired asynchronously with loading, failure, and progress states
|
|
17
27
|
* in addition to Option-like states of NoData and Success.
|
|
@@ -107,6 +117,7 @@ export class Loading extends Data.TaggedError(LOADING_TAG)<LoadingOptions> {
|
|
|
107
117
|
* @since 1.0.0
|
|
108
118
|
*/
|
|
109
119
|
export type LoadingOptions = {
|
|
120
|
+
readonly timestamp: number // Date.now()
|
|
110
121
|
readonly progress: Option.Option<Progress.Progress>
|
|
111
122
|
}
|
|
112
123
|
|
|
@@ -125,6 +136,7 @@ export const loading: {
|
|
|
125
136
|
<E, A>(options?: OptionalPartial<LoadingOptions>): AsyncData<E, A>
|
|
126
137
|
} = (options?: OptionalPartial<LoadingOptions>): Loading =>
|
|
127
138
|
new Loading({
|
|
139
|
+
timestamp: options?.timestamp ?? getCurrentTimestamp(),
|
|
128
140
|
progress: Option.fromNullable(options?.progress)
|
|
129
141
|
})
|
|
130
142
|
|
|
@@ -142,6 +154,11 @@ export interface Failure<E> extends Effect.Effect<never, E, never> {
|
|
|
142
154
|
*/
|
|
143
155
|
readonly cause: Cause.Cause<E>
|
|
144
156
|
|
|
157
|
+
/**
|
|
158
|
+
* @since 1.20.0
|
|
159
|
+
*/
|
|
160
|
+
readonly timestamp: number // Date.now()
|
|
161
|
+
|
|
145
162
|
/**
|
|
146
163
|
* @since 1.18.0
|
|
147
164
|
*/
|
|
@@ -167,6 +184,7 @@ export interface Failure<E> extends Effect.Effect<never, E, never> {
|
|
|
167
184
|
* @since 1.0.0
|
|
168
185
|
*/
|
|
169
186
|
export type FailureOptions = {
|
|
187
|
+
readonly timestamp: number // Date.now()
|
|
170
188
|
readonly refreshing: Option.Option<Loading>
|
|
171
189
|
}
|
|
172
190
|
|
|
@@ -179,6 +197,7 @@ export const failCause: {
|
|
|
179
197
|
} = <E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): Failure<E> =>
|
|
180
198
|
new internal.FailureImpl(
|
|
181
199
|
cause,
|
|
200
|
+
options?.timestamp ?? getCurrentTimestamp(),
|
|
182
201
|
Option.fromNullable(options?.refreshing)
|
|
183
202
|
)
|
|
184
203
|
|
|
@@ -196,6 +215,10 @@ export const fail: {
|
|
|
196
215
|
export interface Success<A> extends Effect.Effect<never, never, A> {
|
|
197
216
|
readonly _tag: typeof SUCCESS_TAG
|
|
198
217
|
readonly value: A
|
|
218
|
+
/**
|
|
219
|
+
* @since 1.20.0
|
|
220
|
+
*/
|
|
221
|
+
readonly timestamp: number // Date.now()
|
|
199
222
|
readonly refreshing: Option.Option<Loading>
|
|
200
223
|
|
|
201
224
|
readonly [Unify.typeSymbol]: unknown
|
|
@@ -207,6 +230,7 @@ export interface Success<A> extends Effect.Effect<never, never, A> {
|
|
|
207
230
|
* @since 1.0.0
|
|
208
231
|
*/
|
|
209
232
|
export type SuccessOptions = {
|
|
233
|
+
readonly timestamp: number // Date.now()
|
|
210
234
|
readonly refreshing: Option.Option<Loading>
|
|
211
235
|
}
|
|
212
236
|
|
|
@@ -219,6 +243,7 @@ export const success: {
|
|
|
219
243
|
} = <A>(value: A, options?: OptionalPartial<SuccessOptions>): Success<A> =>
|
|
220
244
|
new internal.SuccessImpl(
|
|
221
245
|
value,
|
|
246
|
+
options?.timestamp ?? getCurrentTimestamp(),
|
|
222
247
|
Option.fromNullable(options?.refreshing)
|
|
223
248
|
)
|
|
224
249
|
|
|
@@ -400,6 +425,7 @@ const optionProgressEq = Option.getEquivalence(Progress.equals)
|
|
|
400
425
|
|
|
401
426
|
const loadingEquivalence: Equivalence.Equivalence<Loading> = Equivalence.struct({
|
|
402
427
|
_tag: Equivalence.string,
|
|
428
|
+
timestamp: Equivalence.number,
|
|
403
429
|
progress: optionProgressEq
|
|
404
430
|
})
|
|
405
431
|
|
|
@@ -408,6 +434,7 @@ const optionLoadingEq = Option.getEquivalence(loadingEquivalence)
|
|
|
408
434
|
const failureEquivalence: Equivalence.Equivalence<Failure<any>> = Equivalence.struct({
|
|
409
435
|
_tag: Equivalence.string,
|
|
410
436
|
cause: Equal.equals,
|
|
437
|
+
timestamp: Equivalence.number,
|
|
411
438
|
refreshing: optionLoadingEq
|
|
412
439
|
})
|
|
413
440
|
|
|
@@ -415,6 +442,7 @@ const successEquivalence = <A>(valueEq: Equivalence.Equivalence<A>): Equivalence
|
|
|
415
442
|
Equivalence.struct({
|
|
416
443
|
_tag: Equivalence.string,
|
|
417
444
|
value: valueEq,
|
|
445
|
+
timestamp: Equivalence.number,
|
|
418
446
|
refreshing: optionLoadingEq
|
|
419
447
|
})
|
|
420
448
|
|
|
@@ -432,3 +460,73 @@ export const getEquivalence =
|
|
|
432
460
|
Success: (_, s1) => isSuccess(b) ? successEquivalence(valueEq)(s1, b) : false
|
|
433
461
|
})
|
|
434
462
|
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @since 1.0.0
|
|
466
|
+
*/
|
|
467
|
+
export function fromExit<E, A>(exit: Exit.Exit<E, A>): AsyncData<E, A> {
|
|
468
|
+
return Exit.match(exit, {
|
|
469
|
+
onFailure: (cause) => failCause(cause),
|
|
470
|
+
onSuccess: (value) => success(value)
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* @since 1.0.0
|
|
476
|
+
*/
|
|
477
|
+
export function fromEither<E, A>(either: Either.Either<E, A>): AsyncData<E, A> {
|
|
478
|
+
return Either.match(either, {
|
|
479
|
+
onLeft: (e) => fail(e),
|
|
480
|
+
onRight: (a) => success(a)
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const isAsyncDataFirst = (args: IArguments) => args.length === 3 || isAsyncData(args[0])
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @since 1.0.0
|
|
488
|
+
*/
|
|
489
|
+
export const isExpired: {
|
|
490
|
+
(ttl: Duration.DurationInput, now?: number): <E, A>(data: AsyncData<E, A>) => boolean
|
|
491
|
+
<E, A>(data: AsyncData<E, A>, ttl: Duration.DurationInput, now?: number): boolean
|
|
492
|
+
} = dual(isAsyncDataFirst, function isExpired<E, A>(
|
|
493
|
+
data: AsyncData<E, A>,
|
|
494
|
+
ttl: Duration.DurationInput,
|
|
495
|
+
now: number = getCurrentTimestamp()
|
|
496
|
+
): boolean {
|
|
497
|
+
return match(data, {
|
|
498
|
+
NoData: () => true,
|
|
499
|
+
Loading: () => false,
|
|
500
|
+
Failure: (_, f) =>
|
|
501
|
+
Option.isNone(f.refreshing)
|
|
502
|
+
? isPastTTL(f.timestamp, ttl, now)
|
|
503
|
+
: isPastTTL(f.refreshing.value.timestamp, ttl, now),
|
|
504
|
+
Success: (_, s) =>
|
|
505
|
+
Option.isNone(s.refreshing)
|
|
506
|
+
? isPastTTL(s.timestamp, ttl, now) :
|
|
507
|
+
isPastTTL(s.refreshing.value.timestamp, ttl, now)
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
function isPastTTL(timestamp: number, ttl: Duration.DurationInput, now: number): boolean {
|
|
512
|
+
const millis = Duration.toMillis(ttl)
|
|
513
|
+
|
|
514
|
+
return now - timestamp > millis
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Checks if two AsyncData are equal, disregarding the timestamps associated with them. Useful for testing
|
|
519
|
+
* without needing to manage timestamps.
|
|
520
|
+
*
|
|
521
|
+
* @since 1.0.0
|
|
522
|
+
*/
|
|
523
|
+
export function dataEqual<E, A>(first: AsyncData<E, A>, second: AsyncData<E, A>): boolean {
|
|
524
|
+
return match(first, {
|
|
525
|
+
NoData: () => isNoData(second),
|
|
526
|
+
Loading: (l) => isLoading(second) && Equal.equals(l.progress, second.progress),
|
|
527
|
+
Failure: (_, f1) =>
|
|
528
|
+
isFailure(second) && Equal.equals(f1.cause, second.cause) && Equal.equals(f1.refreshing, second.refreshing),
|
|
529
|
+
Success: (_, s1) =>
|
|
530
|
+
isSuccess(second) && Equal.equals(s1.value, second.value) && Equal.equals(s1.refreshing, second.refreshing)
|
|
531
|
+
})
|
|
532
|
+
}
|
package/src/Progress.ts
CHANGED
package/src/Schema.ts
CHANGED
|
@@ -8,86 +8,10 @@ import * as ParseResult from "@effect/schema/ParseResult"
|
|
|
8
8
|
import * as Pretty from "@effect/schema/Pretty"
|
|
9
9
|
import * as Schema from "@effect/schema/Schema"
|
|
10
10
|
import * as AsyncData from "@typed/async-data/AsyncData"
|
|
11
|
-
import
|
|
11
|
+
import type * as Cause from "effect/Cause"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
12
13
|
import * as Option from "effect/Option"
|
|
13
14
|
|
|
14
|
-
const fiberIdArbitrary: Arbitrary.Arbitrary<FiberId.FiberId> = (fc) =>
|
|
15
|
-
fc.oneof(
|
|
16
|
-
fc.constant(FiberId.none),
|
|
17
|
-
fc.integer().chain((i) => fc.date().map((date) => FiberId.make(i, date.getTime() / 1000)))
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
const causeFromItems = <A>(
|
|
21
|
-
items: Array<A>,
|
|
22
|
-
join: (first: Cause.Cause<A>, second: Cause.Cause<A>) => Cause.Cause<A>
|
|
23
|
-
) => {
|
|
24
|
-
if (items.length === 0) return Cause.empty
|
|
25
|
-
if (items.length === 1) return Cause.fail(items[0])
|
|
26
|
-
return items.map(Cause.fail).reduce(join)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const causeArbitrary = <A>(item: Arbitrary.Arbitrary<A>): Arbitrary.Arbitrary<Cause.Cause<A>> => (fc) =>
|
|
30
|
-
fc.oneof(
|
|
31
|
-
fc.constant(Cause.empty),
|
|
32
|
-
fc.anything().map(Cause.die),
|
|
33
|
-
fiberIdArbitrary(fc).map((id) => Cause.interrupt(id)),
|
|
34
|
-
fc.array(item(fc)).chain((items) =>
|
|
35
|
-
fc.integer({ min: 0, max: 1 }).map((i) => causeFromItems(items, i > 0.5 ? Cause.sequential : Cause.parallel))
|
|
36
|
-
)
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const causePretty = <A>(): Pretty.Pretty<Cause.Cause<A>> => Cause.pretty
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @since 1.0.0
|
|
43
|
-
*/
|
|
44
|
-
export const cause = <EI, E>(error: Schema.Schema<EI, E>): Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>> => {
|
|
45
|
-
const parseE = Schema.parse(Schema.chunkFromSelf(error))
|
|
46
|
-
|
|
47
|
-
const self: Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>> = Schema.suspend(() =>
|
|
48
|
-
Schema.declare(
|
|
49
|
-
[error],
|
|
50
|
-
Schema.struct({}),
|
|
51
|
-
() => (input, options) =>
|
|
52
|
-
Effect.gen(function*(_) {
|
|
53
|
-
if (!Cause.isCause(input)) return yield* _(ParseResult.fail(ParseResult.unexpected(input)))
|
|
54
|
-
|
|
55
|
-
let output: Cause.Cause<E> = Cause.empty
|
|
56
|
-
for (const cause of Cause.linearize<E>(input)) {
|
|
57
|
-
const parrallelCauses = Cause.linearize(cause)
|
|
58
|
-
|
|
59
|
-
if (HashSet.size(parrallelCauses) === 1) {
|
|
60
|
-
const failures = Cause.failures(cause)
|
|
61
|
-
|
|
62
|
-
output = Cause.parallel(
|
|
63
|
-
output,
|
|
64
|
-
Chunk.isEmpty(failures) ? cause : Chunk.reduce(
|
|
65
|
-
yield* _(parseE(failures, options)),
|
|
66
|
-
Cause.empty as Cause.Cause<E>,
|
|
67
|
-
(cause, e) => Cause.sequential(cause, Cause.fail(e))
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
} else {
|
|
71
|
-
output = Cause.parallel(
|
|
72
|
-
output,
|
|
73
|
-
yield* _(Schema.parse(self)(cause, options))
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return output
|
|
79
|
-
}),
|
|
80
|
-
{
|
|
81
|
-
[AST.IdentifierAnnotationId]: "Cause",
|
|
82
|
-
[Arbitrary.ArbitraryHookId]: causePretty,
|
|
83
|
-
[Pretty.PrettyHookId]: causeArbitrary
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
return self
|
|
89
|
-
}
|
|
90
|
-
|
|
91
15
|
const asyncDataPretty = <E, A>(
|
|
92
16
|
prettyCause: Pretty.Pretty<Cause.Cause<E>>,
|
|
93
17
|
prettyValue: Pretty.Pretty<A>
|
|
@@ -121,7 +45,7 @@ export const asyncData = <EI, E, AI, A>(
|
|
|
121
45
|
value: Schema.Schema<AI, A>
|
|
122
46
|
): Schema.Schema<AsyncData.AsyncData<EI, AI>, AsyncData.AsyncData<E, A>> => {
|
|
123
47
|
return Schema.declare(
|
|
124
|
-
[cause(error), value],
|
|
48
|
+
[Schema.cause(error), value],
|
|
125
49
|
Schema.struct({}),
|
|
126
50
|
(_, ...params) => {
|
|
127
51
|
const [causeSchema, valueSchema] = params as readonly [
|
|
@@ -133,7 +57,7 @@ export const asyncData = <EI, E, AI, A>(
|
|
|
133
57
|
|
|
134
58
|
return (input, options) => {
|
|
135
59
|
return Effect.gen(function*(_) {
|
|
136
|
-
if (!AsyncData.isAsyncData<EI, AI>(input)) return yield* _(ParseResult.fail(ParseResult.
|
|
60
|
+
if (!AsyncData.isAsyncData<EI, AI>(input)) return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
|
|
137
61
|
|
|
138
62
|
switch (input._tag) {
|
|
139
63
|
case "NoData":
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// Internal
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import * as Cause from "effect/Cause"
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
import * as Effectable from "effect/Effectable"
|
|
6
|
+
import * as Equal from "effect/Equal"
|
|
7
|
+
import { constant, pipe } from "effect/Function"
|
|
8
|
+
import * as Hash from "effect/Hash"
|
|
9
|
+
import * as Option from "effect/Option"
|
|
10
|
+
import * as Unify from "effect/Unify"
|
|
5
11
|
import { type AsyncData, type Failure, type Loading, type Success } from "../AsyncData.js"
|
|
6
12
|
import { FAILURE_TAG, LOADING_TAG, NO_DATA_TAG, SUCCESS_TAG } from "./tag.js"
|
|
7
13
|
|
|
@@ -14,7 +20,7 @@ export class FailureImpl<E> extends Effectable.Class<never, E, never> implements
|
|
|
14
20
|
[Unify.unifySymbol]!: AsyncData.Unify<this>;
|
|
15
21
|
[Unify.ignoreSymbol]!: AsyncData.IgnoreList
|
|
16
22
|
|
|
17
|
-
constructor(readonly cause: Cause.Cause<E>, readonly refreshing: Option.Option<Loading>) {
|
|
23
|
+
constructor(readonly cause: Cause.Cause<E>, readonly timestamp: number, readonly refreshing: Option.Option<Loading>) {
|
|
18
24
|
super()
|
|
19
25
|
|
|
20
26
|
this.commit = constant(Effect.failCause(cause))
|
|
@@ -23,6 +29,7 @@ export class FailureImpl<E> extends Effectable.Class<never, E, never> implements
|
|
|
23
29
|
[Equal.symbol] = (that: unknown) => {
|
|
24
30
|
return isAsyncData(that) && that._tag === "Failure"
|
|
25
31
|
&& Equal.equals(this.cause, that.cause)
|
|
32
|
+
&& Equal.equals(this.timestamp, that.timestamp)
|
|
26
33
|
&& Equal.equals(this.refreshing, that.refreshing)
|
|
27
34
|
};
|
|
28
35
|
|
|
@@ -44,7 +51,7 @@ export class SuccessImpl<A> extends Effectable.Class<never, never, A> implements
|
|
|
44
51
|
[Unify.unifySymbol]!: AsyncData.Unify<this>;
|
|
45
52
|
[Unify.ignoreSymbol]!: AsyncData.IgnoreList
|
|
46
53
|
|
|
47
|
-
constructor(readonly value: A, readonly refreshing: Option.Option<Loading>) {
|
|
54
|
+
constructor(readonly value: A, readonly timestamp: number, readonly refreshing: Option.Option<Loading>) {
|
|
48
55
|
super()
|
|
49
56
|
|
|
50
57
|
this.commit = constant(Effect.succeed(value))
|
|
@@ -53,6 +60,7 @@ export class SuccessImpl<A> extends Effectable.Class<never, never, A> implements
|
|
|
53
60
|
[Equal.symbol] = (that: unknown) => {
|
|
54
61
|
return isAsyncData(that) && that._tag === "Success"
|
|
55
62
|
&& Equal.equals(this.value, that.value)
|
|
63
|
+
&& Equal.equals(this.timestamp, that.timestamp)
|
|
56
64
|
&& Equal.equals(this.refreshing, that.refreshing)
|
|
57
65
|
};
|
|
58
66
|
|