@typed/async-data 0.1.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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/AsyncData.js +225 -0
- package/dist/cjs/AsyncData.js.map +1 -0
- package/dist/cjs/Progress.js +47 -0
- package/dist/cjs/Progress.js.map +1 -0
- package/dist/cjs/Schema.js +104 -0
- package/dist/cjs/Schema.js.map +1 -0
- package/dist/cjs/internal/async-data.js +91 -0
- package/dist/cjs/internal/async-data.js.map +1 -0
- package/dist/cjs/internal/tag.js +11 -0
- package/dist/cjs/internal/tag.js.map +1 -0
- package/dist/dts/AsyncData.d.ts +288 -0
- package/dist/dts/AsyncData.d.ts.map +1 -0
- package/dist/dts/Progress.d.ts +38 -0
- package/dist/dts/Progress.d.ts.map +1 -0
- package/dist/dts/Schema.d.ts +15 -0
- package/dist/dts/Schema.d.ts.map +1 -0
- package/dist/dts/internal/async-data.d.ts +34 -0
- package/dist/dts/internal/async-data.d.ts.map +1 -0
- package/dist/dts/internal/tag.d.ts +5 -0
- package/dist/dts/internal/tag.d.ts.map +1 -0
- package/dist/esm/AsyncData.js +203 -0
- package/dist/esm/AsyncData.js.map +1 -0
- package/dist/esm/Progress.js +38 -0
- package/dist/esm/Progress.js.map +1 -0
- package/dist/esm/Schema.js +93 -0
- package/dist/esm/Schema.js.map +1 -0
- package/dist/esm/internal/async-data.js +85 -0
- package/dist/esm/internal/async-data.js.map +1 -0
- package/dist/esm/internal/tag.js +5 -0
- package/dist/esm/internal/tag.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +48 -0
- package/src/AsyncData.ts +434 -0
- package/src/Progress.ts +65 -0
- package/src/Schema.ts +166 -0
- package/src/internal/async-data.ts +101 -0
- package/src/internal/tag.ts +7 -0
package/src/AsyncData.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncData represents a piece of data which is acquired asynchronously with loading, failure, and progress states
|
|
3
|
+
* in addition to Option-like states of NoData and Success.
|
|
4
|
+
*
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Effect } from "effect"
|
|
9
|
+
import { Cause, Data, Equal, Equivalence, Exit, Option, Unify } from "effect"
|
|
10
|
+
import { dual } from "effect/Function"
|
|
11
|
+
import * as internal from "./internal/async-data"
|
|
12
|
+
import { FAILURE_TAG, LOADING_TAG, NO_DATA_TAG, SUCCESS_TAG } from "./internal/tag"
|
|
13
|
+
import * as Progress from "./Progress"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AsyncData represents a piece of data which is acquired asynchronously with loading, failure, and progress states
|
|
17
|
+
* in addition to Option-like states of NoData and Success.
|
|
18
|
+
*
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export type AsyncData<E, A> = NoData | Loading | Failure<E> | Success<A>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
export namespace AsyncData {
|
|
27
|
+
/**
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
export type Error<T> = [T] extends [AsyncData<infer E, infer _>] ? E : never
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @since 1.0.0
|
|
34
|
+
*/
|
|
35
|
+
export type Success<T> = [T] extends [AsyncData<infer _, infer A>] ? A : never
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @category models
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
*/
|
|
41
|
+
export interface Unify<A extends { [Unify.typeSymbol]?: any }> extends Effect.EffectUnify<A> {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
43
|
+
AsyncData: () => Unify_<A[Unify.typeSymbol]> extends AsyncData<infer E0, infer A0> | infer _ ? AsyncData<E0, A0>
|
|
44
|
+
: never
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type Unify_<T extends AsyncData<any, any>> = T extends NoData ? AsyncData<never, never> :
|
|
48
|
+
T extends Loading ? AsyncData<never, never> :
|
|
49
|
+
T extends Failure<infer E> ? AsyncData<E, never>
|
|
50
|
+
: T extends Success<infer A> ? AsyncData<never, A>
|
|
51
|
+
: never
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 1.0.0
|
|
56
|
+
*/
|
|
57
|
+
export interface IgnoreList extends Effect.EffectUnifyIgnore {
|
|
58
|
+
Effect: true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
*/
|
|
65
|
+
export class NoData extends Data.TaggedError(NO_DATA_TAG)<{}> {
|
|
66
|
+
/**
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
*/
|
|
69
|
+
readonly [Unify.typeSymbol]!: unknown
|
|
70
|
+
/**
|
|
71
|
+
* @since 1.0.0
|
|
72
|
+
*/
|
|
73
|
+
readonly [Unify.unifySymbol]!: AsyncData.Unify<this>
|
|
74
|
+
/**
|
|
75
|
+
* @since 1.0.0
|
|
76
|
+
*/
|
|
77
|
+
readonly [Unify.ignoreSymbol]!: AsyncData.IgnoreList
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
*/
|
|
83
|
+
export const noData: {
|
|
84
|
+
(): NoData
|
|
85
|
+
<E, A>(): AsyncData<E, A>
|
|
86
|
+
} = (): NoData => new NoData()
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @since 1.0.0
|
|
90
|
+
*/
|
|
91
|
+
export class Loading extends Data.TaggedError(LOADING_TAG)<LoadingOptions> {
|
|
92
|
+
/**
|
|
93
|
+
* @since 1.0.0
|
|
94
|
+
*/
|
|
95
|
+
readonly [Unify.typeSymbol]!: unknown
|
|
96
|
+
/**
|
|
97
|
+
* @since 1.0.0
|
|
98
|
+
*/
|
|
99
|
+
readonly [Unify.unifySymbol]!: AsyncData.Unify<this>
|
|
100
|
+
/**
|
|
101
|
+
* @since 1.0.0
|
|
102
|
+
*/
|
|
103
|
+
readonly [Unify.ignoreSymbol]!: AsyncData.IgnoreList
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @since 1.0.0
|
|
108
|
+
*/
|
|
109
|
+
export type LoadingOptions = {
|
|
110
|
+
readonly progress: Option.Option<Progress.Progress>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @since 1.0.0
|
|
115
|
+
*/
|
|
116
|
+
export type OptionalPartial<A> = {
|
|
117
|
+
[K in keyof A]+?: [A[K]] extends [Option.Option<infer R>] ? R | undefined : A[K]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @since 1.0.0
|
|
122
|
+
*/
|
|
123
|
+
export const loading: {
|
|
124
|
+
(options?: OptionalPartial<LoadingOptions>): Loading
|
|
125
|
+
<E, A>(options?: OptionalPartial<LoadingOptions>): AsyncData<E, A>
|
|
126
|
+
} = (options?: OptionalPartial<LoadingOptions>): Loading =>
|
|
127
|
+
new Loading({
|
|
128
|
+
progress: Option.fromNullable(options?.progress)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @since 1.0.0
|
|
133
|
+
*/
|
|
134
|
+
export interface Failure<E> extends Effect.Effect<never, E, never> {
|
|
135
|
+
/**
|
|
136
|
+
* @since 1.18.0
|
|
137
|
+
*/
|
|
138
|
+
readonly _tag: typeof FAILURE_TAG
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @since 1.18.0
|
|
142
|
+
*/
|
|
143
|
+
readonly cause: Cause.Cause<E>
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @since 1.18.0
|
|
147
|
+
*/
|
|
148
|
+
readonly refreshing: Option.Option<Loading>
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @since 1.18.0
|
|
152
|
+
*/
|
|
153
|
+
readonly [Unify.typeSymbol]: unknown
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @since 1.18.0
|
|
157
|
+
*/
|
|
158
|
+
readonly [Unify.unifySymbol]: AsyncData.Unify<this>
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @since 1.18.0
|
|
162
|
+
*/
|
|
163
|
+
readonly [Unify.ignoreSymbol]: AsyncData.IgnoreList
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @since 1.0.0
|
|
168
|
+
*/
|
|
169
|
+
export type FailureOptions = {
|
|
170
|
+
readonly refreshing: Option.Option<Loading>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @since 1.0.0
|
|
175
|
+
*/
|
|
176
|
+
export const failCause: {
|
|
177
|
+
<E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): Failure<E>
|
|
178
|
+
<E, A>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): AsyncData<E, A>
|
|
179
|
+
} = <E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): Failure<E> =>
|
|
180
|
+
new internal.FailureImpl(
|
|
181
|
+
cause,
|
|
182
|
+
Option.fromNullable(options?.refreshing)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @since 1.0.0
|
|
187
|
+
*/
|
|
188
|
+
export const fail: {
|
|
189
|
+
<E>(error: E, options?: OptionalPartial<FailureOptions>): Failure<E>
|
|
190
|
+
<E, A>(error: E, options?: OptionalPartial<FailureOptions>): AsyncData<E, A>
|
|
191
|
+
} = <E>(error: E, options?: OptionalPartial<FailureOptions>): Failure<E> => failCause<E>(Cause.fail(error), options)
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @since 1.0.0
|
|
195
|
+
*/
|
|
196
|
+
export interface Success<A> extends Effect.Effect<never, never, A> {
|
|
197
|
+
readonly _tag: typeof SUCCESS_TAG
|
|
198
|
+
readonly value: A
|
|
199
|
+
readonly refreshing: Option.Option<Loading>
|
|
200
|
+
|
|
201
|
+
readonly [Unify.typeSymbol]: unknown
|
|
202
|
+
readonly [Unify.unifySymbol]: AsyncData.Unify<this>
|
|
203
|
+
readonly [Unify.ignoreSymbol]: AsyncData.IgnoreList
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @since 1.0.0
|
|
208
|
+
*/
|
|
209
|
+
export type SuccessOptions = {
|
|
210
|
+
readonly refreshing: Option.Option<Loading>
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @since 1.0.0
|
|
215
|
+
*/
|
|
216
|
+
export const success: {
|
|
217
|
+
<A>(value: A, options?: OptionalPartial<SuccessOptions>): Success<A>
|
|
218
|
+
<E, A>(value: A, options?: OptionalPartial<SuccessOptions>): AsyncData<E, A>
|
|
219
|
+
} = <A>(value: A, options?: OptionalPartial<SuccessOptions>): Success<A> =>
|
|
220
|
+
new internal.SuccessImpl(
|
|
221
|
+
value,
|
|
222
|
+
Option.fromNullable(options?.refreshing)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @since 1.0.0
|
|
227
|
+
*/
|
|
228
|
+
export const isSuccess = <E, A>(data: AsyncData<E, A>): data is Success<A> => data._tag === SUCCESS_TAG
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @since 1.0.0
|
|
232
|
+
*/
|
|
233
|
+
export const isFailure = <E, A>(data: AsyncData<E, A>): data is Failure<E> => data._tag === FAILURE_TAG
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @since 1.0.0
|
|
237
|
+
*/
|
|
238
|
+
export const isLoading = <E, A>(data: AsyncData<E, A>): data is Loading => data._tag === LOADING_TAG
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @since 1.0.0
|
|
242
|
+
*/
|
|
243
|
+
export const isNoData = <E, A>(data: AsyncData<E, A>): data is NoData => data._tag === NO_DATA_TAG
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @since 1.0.0
|
|
247
|
+
*/
|
|
248
|
+
export type Refreshing<E, A> = RefreshingFailure<E> | RefreshingSuccess<A>
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @since 1.0.0
|
|
252
|
+
*/
|
|
253
|
+
export interface RefreshingFailure<E> extends Failure<E> {
|
|
254
|
+
readonly refreshing: Option.Some<Loading>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @since 1.0.0
|
|
259
|
+
*/
|
|
260
|
+
export interface RefreshingSuccess<A> extends Success<A> {
|
|
261
|
+
readonly refreshing: Option.Some<Loading>
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @since 1.0.0
|
|
266
|
+
*/
|
|
267
|
+
export const isRefreshing = <E, A>(data: AsyncData<E, A>): data is Refreshing<E, A> =>
|
|
268
|
+
isSuccess(data) || isFailure(data) ? Option.isSome(data.refreshing) : false
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @since 1.0.0
|
|
272
|
+
*/
|
|
273
|
+
export const isLoadingOrRefreshing = <E, A>(data: AsyncData<E, A>): data is Loading | Refreshing<E, A> =>
|
|
274
|
+
isLoading(data) || isRefreshing(data)
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @since 1.0.0
|
|
278
|
+
*/
|
|
279
|
+
export const match: {
|
|
280
|
+
<E, A, R1, R2, R3, R4>(
|
|
281
|
+
matchers: {
|
|
282
|
+
NoData: (data: NoData) => R1
|
|
283
|
+
Loading: (data: Loading) => R2
|
|
284
|
+
Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
|
|
285
|
+
Success: (value: A, data: Success<A>) => R4
|
|
286
|
+
}
|
|
287
|
+
): (data: AsyncData<E, A>) => Unify.Unify<R1 | R2 | R3 | R4>
|
|
288
|
+
|
|
289
|
+
<E, A, R1, R2, R3, R4>(
|
|
290
|
+
data: AsyncData<E, A>,
|
|
291
|
+
matchers: {
|
|
292
|
+
NoData: (data: NoData) => R1
|
|
293
|
+
Loading: (data: Loading) => R2
|
|
294
|
+
Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
|
|
295
|
+
Success: (value: A, data: Success<A>) => R4
|
|
296
|
+
}
|
|
297
|
+
): Unify.Unify<R1 | R2 | R3 | R4>
|
|
298
|
+
} = dual(2, <E, A, R1, R2, R3, R4>(data: AsyncData<E, A>, matchers: {
|
|
299
|
+
NoData: (data: NoData) => R1
|
|
300
|
+
Loading: (data: Loading) => R2
|
|
301
|
+
Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
|
|
302
|
+
Success: (value: A, data: Success<A>) => R4
|
|
303
|
+
}): Unify.Unify<R1 | R2 | R3 | R4> => {
|
|
304
|
+
if (isSuccess(data)) {
|
|
305
|
+
return matchers.Success(data.value, data) as Unify.Unify<R1 | R2 | R3 | R4>
|
|
306
|
+
} else if (isFailure(data)) {
|
|
307
|
+
return matchers.Failure(data.cause, data) as Unify.Unify<R1 | R2 | R3 | R4>
|
|
308
|
+
} else if (isLoading(data)) {
|
|
309
|
+
return matchers.Loading(data) as Unify.Unify<R1 | R2 | R3 | R4>
|
|
310
|
+
} else {
|
|
311
|
+
return matchers.NoData(data) as Unify.Unify<R1 | R2 | R3 | R4>
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @since 1.0.0
|
|
317
|
+
*/
|
|
318
|
+
export const map: {
|
|
319
|
+
<A, B>(f: (a: A) => B): <E>(data: AsyncData<E, A>) => AsyncData<E, B>
|
|
320
|
+
<E, A, B>(data: AsyncData<E, A>, f: (a: A) => B): AsyncData<E, B>
|
|
321
|
+
} = dual(2, function<E, A, B>(data: AsyncData<E, A>, f: (a: A) => B): AsyncData<E, B> {
|
|
322
|
+
return isSuccess(data) ?
|
|
323
|
+
success(f(data.value), {
|
|
324
|
+
refreshing: Option.getOrUndefined(data.refreshing)
|
|
325
|
+
}) :
|
|
326
|
+
data
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @since 1.0.0
|
|
331
|
+
*/
|
|
332
|
+
export const flatMap: {
|
|
333
|
+
<A, E2, B>(f: (a: A, options: SuccessOptions) => AsyncData<E2, B>): <E>(data: AsyncData<E, A>) => AsyncData<E | E2, B>
|
|
334
|
+
<E, A, E2, B>(data: AsyncData<E, A>, f: (a: A, options: SuccessOptions) => AsyncData<E, B>): AsyncData<E | E2, B>
|
|
335
|
+
} = dual(
|
|
336
|
+
2,
|
|
337
|
+
function<E, A, E2, B>(
|
|
338
|
+
data: AsyncData<E, A>,
|
|
339
|
+
f: (a: A, options: SuccessOptions) => AsyncData<E2, B>
|
|
340
|
+
): AsyncData<E | E2, B> {
|
|
341
|
+
return isSuccess(data) ? f(data.value, data) : data
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @since 1.0.0
|
|
347
|
+
*/
|
|
348
|
+
export const startLoading = <E, A>(data: AsyncData<E, A>): AsyncData<E, A> => {
|
|
349
|
+
if (isSuccess(data)) {
|
|
350
|
+
return Option.isSome(data.refreshing) ? data : success(data.value, { ...data, refreshing: loading() })
|
|
351
|
+
} else if (isFailure(data)) {
|
|
352
|
+
return Option.isSome(data.refreshing)
|
|
353
|
+
? data
|
|
354
|
+
: failCause(data.cause, { ...data, refreshing: loading() })
|
|
355
|
+
} else {
|
|
356
|
+
return loading()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @since 1.0.0
|
|
362
|
+
*/
|
|
363
|
+
export const stopLoading = <E, A>(data: AsyncData<E, A>): AsyncData<E, A> => {
|
|
364
|
+
if (isSuccess(data)) {
|
|
365
|
+
return Option.isSome(data.refreshing) ? success(data.value) : data
|
|
366
|
+
} else if (isFailure(data)) {
|
|
367
|
+
return Option.isSome(data.refreshing) ? failCause(data.cause) : data
|
|
368
|
+
} else {
|
|
369
|
+
return noData()
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @since 1.0.0
|
|
375
|
+
*/
|
|
376
|
+
export const isAsyncData: <E, A>(u: unknown) => u is AsyncData<E, A> = internal.isAsyncData
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @since 1.0.0
|
|
380
|
+
*/
|
|
381
|
+
export const done = <E, A>(exit: Exit.Exit<E, A>): AsyncData<E, A> =>
|
|
382
|
+
Exit.match(exit, {
|
|
383
|
+
onFailure: (cause) => failCause(cause),
|
|
384
|
+
onSuccess: (value) => success(value)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @since 1.0.0
|
|
389
|
+
*/
|
|
390
|
+
export const getFailure = <E, A>(data: AsyncData<E, A>): Option.Option<E> =>
|
|
391
|
+
isFailure(data) ? Cause.failureOption(data.cause) : Option.none()
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @since 1.0.0
|
|
395
|
+
*/
|
|
396
|
+
export const getSuccess = <E, A>(data: AsyncData<E, A>): Option.Option<A> =>
|
|
397
|
+
isSuccess(data) ? Option.some(data.value) : Option.none()
|
|
398
|
+
|
|
399
|
+
const optionProgressEq = Option.getEquivalence(Progress.equals)
|
|
400
|
+
|
|
401
|
+
const loadingEquivalence: Equivalence.Equivalence<Loading> = Equivalence.struct({
|
|
402
|
+
_tag: Equivalence.string,
|
|
403
|
+
progress: optionProgressEq
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
const optionLoadingEq = Option.getEquivalence(loadingEquivalence)
|
|
407
|
+
|
|
408
|
+
const failureEquivalence: Equivalence.Equivalence<Failure<any>> = Equivalence.struct({
|
|
409
|
+
_tag: Equivalence.string,
|
|
410
|
+
cause: Equal.equals,
|
|
411
|
+
refreshing: optionLoadingEq
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
const successEquivalence = <A>(valueEq: Equivalence.Equivalence<A>): Equivalence.Equivalence<Success<A>> =>
|
|
415
|
+
Equivalence.struct({
|
|
416
|
+
_tag: Equivalence.string,
|
|
417
|
+
value: valueEq,
|
|
418
|
+
refreshing: optionLoadingEq
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* @since 1.0.0
|
|
423
|
+
*/
|
|
424
|
+
export const getEquivalence =
|
|
425
|
+
<E, A>(valueEq: Equivalence.Equivalence<A> = Equal.equals): Equivalence.Equivalence<AsyncData<E, A>> => (a, b) => {
|
|
426
|
+
if (a === b) return true
|
|
427
|
+
|
|
428
|
+
return match(a, {
|
|
429
|
+
NoData: () => isNoData(b),
|
|
430
|
+
Loading: (l1) => isLoading(b) ? loadingEquivalence(l1, b) : false,
|
|
431
|
+
Failure: (_, f1) => isFailure(b) ? failureEquivalence(f1, b) : false,
|
|
432
|
+
Success: (_, s1) => isSuccess(b) ? successEquivalence(valueEq)(s1, b) : false
|
|
433
|
+
})
|
|
434
|
+
}
|
package/src/Progress.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Equivalence, Option } from "effect"
|
|
6
|
+
import { dual } from "effect/Function"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
export interface Progress {
|
|
12
|
+
readonly loaded: bigint
|
|
13
|
+
readonly total: Option.Option<bigint>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @since 1.0.0
|
|
18
|
+
*/
|
|
19
|
+
export function Progress(loaded: bigint, total: Option.Option<bigint> = Option.none()): Progress {
|
|
20
|
+
return {
|
|
21
|
+
loaded,
|
|
22
|
+
total
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
*/
|
|
29
|
+
export const make = (loaded: bigint, total?: bigint | null): Progress => Progress(loaded, Option.fromNullable(total))
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @since 1.0.0
|
|
33
|
+
*/
|
|
34
|
+
export const setLoaded: {
|
|
35
|
+
(loaded: bigint): (progress: Progress) => Progress
|
|
36
|
+
(progress: Progress, loaded: bigint): Progress
|
|
37
|
+
} = dual(2, function setLoaded(progress: Progress, loaded: bigint): Progress {
|
|
38
|
+
return Progress(
|
|
39
|
+
loaded,
|
|
40
|
+
progress.total
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
*/
|
|
47
|
+
export const setTotal: {
|
|
48
|
+
(total: bigint): (progress: Progress) => Progress
|
|
49
|
+
(progress: Progress, total: bigint): Progress
|
|
50
|
+
} = dual(2, function setTotal(progress: Progress, total: bigint): Progress {
|
|
51
|
+
return Progress(
|
|
52
|
+
progress.loaded,
|
|
53
|
+
Option.some(total)
|
|
54
|
+
)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @since 1.0.0
|
|
59
|
+
*/
|
|
60
|
+
export const equals: Equivalence.Equivalence<Progress> = Equivalence.struct<
|
|
61
|
+
{ readonly [K in keyof Progress]: Equivalence.Equivalence<Progress[K]> }
|
|
62
|
+
>({
|
|
63
|
+
loaded: Equivalence.bigint,
|
|
64
|
+
total: Option.getEquivalence(Equivalence.bigint)
|
|
65
|
+
})
|
package/src/Schema.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as Arbitrary from "@effect/schema/Arbitrary"
|
|
6
|
+
import * as AST from "@effect/schema/AST"
|
|
7
|
+
import * as ParseResult from "@effect/schema/ParseResult"
|
|
8
|
+
import * as Pretty from "@effect/schema/Pretty"
|
|
9
|
+
import * as Schema from "@effect/schema/Schema"
|
|
10
|
+
import * as AsyncData from "@typed/async-data/AsyncData"
|
|
11
|
+
import { Cause, Chunk, Effect, FiberId, HashSet } from "effect"
|
|
12
|
+
import * as Option from "effect/Option"
|
|
13
|
+
|
|
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.lazy(() =>
|
|
48
|
+
Schema.declare(
|
|
49
|
+
[error],
|
|
50
|
+
Schema.struct({}),
|
|
51
|
+
() => (input, options) =>
|
|
52
|
+
Effect.gen(function*(_) {
|
|
53
|
+
if (!Cause.isCause(input)) return yield* _(ParseResult.failure(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
|
+
const asyncDataPretty = <E, A>(
|
|
92
|
+
prettyCause: Pretty.Pretty<Cause.Cause<E>>,
|
|
93
|
+
prettyValue: Pretty.Pretty<A>
|
|
94
|
+
): Pretty.Pretty<AsyncData.AsyncData<E, A>> =>
|
|
95
|
+
AsyncData.match({
|
|
96
|
+
NoData: () => `AsyncData.NoData`,
|
|
97
|
+
Loading: () => `AsyncData.Loading`,
|
|
98
|
+
Failure: (cause, data) =>
|
|
99
|
+
`AsyncData.Failure(refreshing=${Option.isSome(data.refreshing)}, cause=${prettyCause(cause)})`,
|
|
100
|
+
Success: (value, data) =>
|
|
101
|
+
`AsyncData.Success(refreshing=${Option.isSome(data.refreshing)}, value=${prettyValue(value)})`
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const asyncDataArbitrary = <E, A>(
|
|
105
|
+
causeArbitrary: Arbitrary.Arbitrary<Cause.Cause<E>>,
|
|
106
|
+
valueArbitrary: Arbitrary.Arbitrary<A>
|
|
107
|
+
): Arbitrary.Arbitrary<AsyncData.AsyncData<E, A>> =>
|
|
108
|
+
(fc) =>
|
|
109
|
+
fc.oneof(
|
|
110
|
+
fc.constant(AsyncData.noData()),
|
|
111
|
+
fc.constant(AsyncData.loading()),
|
|
112
|
+
causeArbitrary(fc).map((cause) => AsyncData.failCause(cause)),
|
|
113
|
+
valueArbitrary(fc).map((a) => AsyncData.success(a))
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @since 1.0.0
|
|
118
|
+
*/
|
|
119
|
+
export const asyncData = <EI, E, AI, A>(
|
|
120
|
+
error: Schema.Schema<EI, E>,
|
|
121
|
+
value: Schema.Schema<AI, A>
|
|
122
|
+
): Schema.Schema<AsyncData.AsyncData<EI, AI>, AsyncData.AsyncData<E, A>> => {
|
|
123
|
+
return Schema.declare(
|
|
124
|
+
[cause(error), value],
|
|
125
|
+
Schema.struct({}),
|
|
126
|
+
(_, ...params) => {
|
|
127
|
+
const [causeSchema, valueSchema] = params as readonly [
|
|
128
|
+
Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>>,
|
|
129
|
+
Schema.Schema<AI, A>
|
|
130
|
+
]
|
|
131
|
+
const parseCause = Schema.parse(causeSchema)
|
|
132
|
+
const parseValue = Schema.parse(valueSchema)
|
|
133
|
+
|
|
134
|
+
return (input, options) => {
|
|
135
|
+
return Effect.gen(function*(_) {
|
|
136
|
+
if (!AsyncData.isAsyncData<EI, AI>(input)) return yield* _(ParseResult.failure(ParseResult.unexpected(input)))
|
|
137
|
+
|
|
138
|
+
switch (input._tag) {
|
|
139
|
+
case "NoData":
|
|
140
|
+
case "Loading":
|
|
141
|
+
return input
|
|
142
|
+
case "Failure": {
|
|
143
|
+
const cause = yield* _(parseCause(input.cause, options))
|
|
144
|
+
|
|
145
|
+
return AsyncData.failCause(cause, {
|
|
146
|
+
refreshing: Option.getOrUndefined(input.refreshing)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
case "Success": {
|
|
150
|
+
const a = yield* _(parseValue(input.value, options))
|
|
151
|
+
|
|
152
|
+
return AsyncData.success(a, {
|
|
153
|
+
refreshing: Option.getOrUndefined(input.refreshing)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
[AST.IdentifierAnnotationId]: "AsyncData",
|
|
162
|
+
[Arbitrary.ArbitraryHookId]: asyncDataPretty,
|
|
163
|
+
[Pretty.PrettyHookId]: asyncDataArbitrary
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
}
|