@typed/async-data 0.11.0 → 0.13.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.
Files changed (75) hide show
  1. package/.nvmrc +1 -0
  2. package/biome.json +36 -0
  3. package/dist/AsyncData.d.ts +196 -0
  4. package/dist/AsyncData.js +285 -0
  5. package/dist/AsyncData.js.map +1 -0
  6. package/dist/LazyRef.d.ts +21 -0
  7. package/dist/LazyRef.js +27 -0
  8. package/dist/LazyRef.js.map +1 -0
  9. package/dist/Progress.d.ts +25 -0
  10. package/dist/Progress.js +21 -0
  11. package/dist/Progress.js.map +1 -0
  12. package/dist/{dts/TypeId.d.ts → TypeId.d.ts} +0 -1
  13. package/dist/TypeId.js +8 -0
  14. package/dist/TypeId.js.map +1 -0
  15. package/dist/_internal.d.ts +16 -0
  16. package/dist/_internal.js +53 -0
  17. package/dist/_internal.js.map +1 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/package.json +31 -48
  22. package/readme.md +218 -0
  23. package/src/AsyncData.test.ts +89 -0
  24. package/src/AsyncData.ts +555 -595
  25. package/src/LazyRef.ts +87 -0
  26. package/src/Progress.ts +18 -65
  27. package/src/TypeId.ts +1 -1
  28. package/src/_internal.ts +114 -0
  29. package/src/index.ts +4 -0
  30. package/tsconfig.json +27 -0
  31. package/AsyncData/package.json +0 -6
  32. package/LICENSE +0 -21
  33. package/Progress/package.json +0 -6
  34. package/README.md +0 -5
  35. package/Schema/package.json +0 -6
  36. package/TypeId/package.json +0 -6
  37. package/dist/cjs/AsyncData.js +0 -399
  38. package/dist/cjs/AsyncData.js.map +0 -1
  39. package/dist/cjs/Progress.js +0 -62
  40. package/dist/cjs/Progress.js.map +0 -1
  41. package/dist/cjs/Schema.js +0 -538
  42. package/dist/cjs/Schema.js.map +0 -1
  43. package/dist/cjs/TypeId.js +0 -14
  44. package/dist/cjs/TypeId.js.map +0 -1
  45. package/dist/cjs/internal/async-data.js +0 -94
  46. package/dist/cjs/internal/async-data.js.map +0 -1
  47. package/dist/cjs/internal/tag.js +0 -12
  48. package/dist/cjs/internal/tag.js.map +0 -1
  49. package/dist/dts/AsyncData.d.ts +0 -386
  50. package/dist/dts/AsyncData.d.ts.map +0 -1
  51. package/dist/dts/Progress.d.ts +0 -43
  52. package/dist/dts/Progress.d.ts.map +0 -1
  53. package/dist/dts/Schema.d.ts +0 -78
  54. package/dist/dts/Schema.d.ts.map +0 -1
  55. package/dist/dts/TypeId.d.ts.map +0 -1
  56. package/dist/dts/internal/async-data.d.ts +0 -43
  57. package/dist/dts/internal/async-data.d.ts.map +0 -1
  58. package/dist/dts/internal/tag.d.ts +0 -6
  59. package/dist/dts/internal/tag.d.ts.map +0 -1
  60. package/dist/esm/AsyncData.js +0 -372
  61. package/dist/esm/AsyncData.js.map +0 -1
  62. package/dist/esm/Progress.js +0 -49
  63. package/dist/esm/Progress.js.map +0 -1
  64. package/dist/esm/Schema.js +0 -500
  65. package/dist/esm/Schema.js.map +0 -1
  66. package/dist/esm/TypeId.js +0 -8
  67. package/dist/esm/TypeId.js.map +0 -1
  68. package/dist/esm/internal/async-data.js +0 -91
  69. package/dist/esm/internal/async-data.js.map +0 -1
  70. package/dist/esm/internal/tag.js +0 -6
  71. package/dist/esm/internal/tag.js.map +0 -1
  72. package/dist/esm/package.json +0 -4
  73. package/src/Schema.ts +0 -750
  74. package/src/internal/async-data.ts +0 -114
  75. package/src/internal/tag.ts +0 -9
package/src/AsyncData.ts CHANGED
@@ -1,49 +1,57 @@
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 * as Cause from "effect/Cause"
9
- import * as Data from "effect/Data"
10
- import * as Duration from "effect/Duration"
11
- import type * as Effect from "effect/Effect"
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"
16
- import { dual } from "effect/Function"
17
- import * as Option from "effect/Option"
18
- import * as Unify from "effect/Unify"
19
- import * as internal from "./internal/async-data.js"
20
- import { FAILURE_TAG, LOADING_TAG, NO_DATA_TAG, OPTIMISTIC_TAG, SUCCESS_TAG } from "./internal/tag.js"
21
- import * as Progress from "./Progress.js"
22
- import { AsyncDataTypeId } from "./TypeId.js"
23
-
24
- const getCurrentTimestamp = () => Date.now()
25
-
26
- /**
27
- * AsyncData represents a piece of data which is acquired asynchronously with loading, failure, and progress states
28
- * in addition to Option-like states of NoData and Success.
29
- *
30
- * @since 1.0.0
31
- */
32
- export type AsyncData<A, E = never> = NoData | Loading | Failure<E> | Success<A> | Optimistic<A, E>
33
-
34
- /**
35
- * @since 1.0.0
36
- */
1
+ import {
2
+ Cause,
3
+ Effect,
4
+ Either,
5
+ Equal,
6
+ Equivalence,
7
+ Exit,
8
+ identity,
9
+ Option,
10
+ Schema,
11
+ SchemaAST,
12
+ Unify,
13
+ } from 'effect'
14
+ import { dual } from 'effect/Function'
15
+ import { Progress, type ProgressEncoded } from './Progress.js'
16
+ import { DataEffect, LiteralWithDefault } from './_internal.js'
17
+
18
+ export type AsyncData<A, E = never> =
19
+ | NoData
20
+ | Loading
21
+ | Success<A>
22
+ | Failure<E>
23
+ | Refreshing<A, E>
24
+ | Optimistic<A, E>
25
+
37
26
  export namespace AsyncData {
38
- /**
39
- * @since 1.0.0
40
- */
41
- export type Error<T> = [T] extends [AsyncData<infer _, infer E>] ? E : never
27
+ export type Encoded<A, E = never> =
28
+ | typeof NoData.Encoded
29
+ | typeof Loading.Encoded
30
+ | SuccessEncoded<A>
31
+ | FailureEncoded<E>
32
+ | RefreshingEncoded<A, E>
33
+ | OptimisticEncoded<A, E>
34
+ export interface SuccessEncoded<A> {
35
+ readonly _tag: 'Success'
36
+ readonly value: A
37
+ }
42
38
 
43
- /**
44
- * @since 1.0.0
45
- */
46
- export type Success<T> = [T] extends [AsyncData<infer A, infer _>] ? A : never
39
+ export interface FailureEncoded<E> {
40
+ readonly _tag: 'Failure'
41
+ readonly cause: Schema.CauseEncoded<E, unknown>
42
+ }
43
+
44
+ export type RefreshingEncoded<A, E = never> = {
45
+ readonly _tag: 'Refreshing'
46
+ readonly previous: SuccessEncoded<A> | FailureEncoded<E>
47
+ readonly progress?: ProgressEncoded
48
+ }
49
+
50
+ export interface OptimisticEncoded<A, E = never> {
51
+ readonly _tag: 'Optimistic'
52
+ readonly previous: Encoded<A, E>
53
+ readonly value: A
54
+ }
47
55
 
48
56
  /**
49
57
  * @category models
@@ -51,15 +59,24 @@ export namespace AsyncData {
51
59
  */
52
60
  export interface Unify<A extends { [Unify.typeSymbol]?: any }> extends Effect.EffectUnify<A> {
53
61
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
- AsyncData: () => Unify_<A[Unify.typeSymbol]> extends AsyncData<infer E0, infer A0> | infer _ ? AsyncData<E0, A0>
62
+ AsyncData: () => Unify_<A[Unify.typeSymbol]> extends AsyncData<infer E0, infer A0> | infer _
63
+ ? AsyncData<E0, A0>
55
64
  : never
56
65
  }
57
66
 
58
- type Unify_<T extends AsyncData<any, any>> = T extends NoData ? AsyncData<never, never> :
59
- T extends Loading ? AsyncData<never, never> :
60
- T extends Failure<infer E> ? AsyncData<E, never>
61
- : T extends Success<infer A> ? AsyncData<never, A>
62
- : never
67
+ type Unify_<T extends AsyncData<any, any>> = T extends NoData
68
+ ? AsyncData<never, never>
69
+ : T extends Loading
70
+ ? AsyncData<never, never>
71
+ : T extends Failure<infer E>
72
+ ? AsyncData<E, never>
73
+ : T extends Success<infer A>
74
+ ? AsyncData<never, A>
75
+ : T extends Optimistic<infer A, infer E>
76
+ ? AsyncData<A, E>
77
+ : T extends Refreshing<infer A, infer E>
78
+ ? AsyncData<A, E>
79
+ : never
63
80
 
64
81
  /**
65
82
  * @category models
@@ -70,625 +87,568 @@ export namespace AsyncData {
70
87
  }
71
88
  }
72
89
 
73
- /**
74
- * @since 1.0.0
75
- */
76
- export class NoData extends Data.TaggedError(NO_DATA_TAG)<{}> {
77
- /**
78
- * @since 1.0.0
79
- */
80
- readonly [AsyncDataTypeId]: AsyncDataTypeId = AsyncDataTypeId
81
-
82
- /**
83
- * @since 1.0.0
84
- */
85
- readonly [Unify.typeSymbol]!: unknown
86
- /**
87
- * @since 1.0.0
88
- */
89
- readonly [Unify.unifySymbol]!: AsyncData.Unify<this>
90
- /**
91
- * @since 1.0.0
92
- */
93
- readonly [Unify.ignoreSymbol]!: AsyncData.IgnoreList
94
- }
90
+ export class NoData
91
+ extends Schema.TaggedError<NoData>()('NoData', {})
92
+ implements Effect.Effect<never, NoData, never> {}
95
93
 
96
- /**
97
- * @since 1.0.0
98
- */
99
94
  export const noData: {
100
95
  (): NoData
101
- <A, E>(): AsyncData<A, E>
102
- } = (): NoData => new NoData()
103
-
104
- /**
105
- * @since 1.0.0
106
- */
107
- export class Loading extends Data.TaggedError(LOADING_TAG)<LoadingOptions> {
108
- /**
109
- * @since 1.0.0
110
- */
111
- readonly [AsyncDataTypeId]: AsyncDataTypeId = AsyncDataTypeId
96
+ <A, E = never>(): AsyncData<A, E>
97
+ } = () => NoData.make()
112
98
 
113
- /**
114
- * @since 1.0.0
115
- */
116
- readonly [Unify.typeSymbol]!: unknown
117
- /**
118
- * @since 1.0.0
119
- */
120
- readonly [Unify.unifySymbol]!: AsyncData.Unify<this>
121
- /**
122
- * @since 1.0.0
123
- */
124
- readonly [Unify.ignoreSymbol]!: AsyncData.IgnoreList
125
- }
99
+ export class Loading
100
+ extends Schema.TaggedError<Loading>()('Loading', {
101
+ progress: Schema.optionalWith(Progress, { as: 'Option', default: undefined }),
102
+ })
103
+ implements Effect.Effect<never, Loading, never> {}
126
104
 
127
- /**
128
- * @since 1.0.0
129
- */
130
- export type LoadingOptions = {
131
- readonly timestamp: number // Date.now()
132
- readonly progress: Option.Option<Progress.Progress>
105
+ export function loading(progress?: Progress): Loading
106
+ export function loading<A, E = never>(progress?: Progress): AsyncData<A, E>
107
+ export function loading<A, E = never>(progress?: Progress): AsyncData<A, E> {
108
+ return Loading.make({ progress: Option.fromNullable(progress) })
133
109
  }
134
110
 
135
- /**
136
- * @since 1.0.0
137
- */
138
- export type OptionalPartial<A> = [
139
- {
140
- [K in keyof A]+?: [A[K]] extends [Option.Option<infer R>] ? R | undefined : A[K]
111
+ export class Success<A>
112
+ extends DataEffect('Success')<{ readonly value: A }, A>
113
+ implements Effect.Effect<A>
114
+ {
115
+ constructor(value: A) {
116
+ super({ value }, Effect.succeed(value))
141
117
  }
142
- ] extends [infer R] ? { readonly [K in keyof R]: R[K] } : never
143
-
144
- /**
145
- * @since 1.0.0
146
- */
147
- export const loading: {
148
- (options?: OptionalPartial<LoadingOptions>): Loading
149
- <A, E>(options?: OptionalPartial<LoadingOptions>): AsyncData<A, E>
150
- } = (options?: OptionalPartial<LoadingOptions>): Loading =>
151
- new Loading({
152
- [AsyncDataTypeId]: AsyncDataTypeId,
153
- timestamp: options?.timestamp ?? getCurrentTimestamp(),
154
- progress: Option.fromNullable(options?.progress)
155
- } as any)
156
-
157
- /**
158
- * @since 1.0.0
159
- */
160
- export interface Failure<out E> extends Effect.Effect<never, E, never> {
161
- readonly [AsyncDataTypeId]: AsyncDataTypeId
162
-
163
- /**
164
- * @since 1.18.0
165
- */
166
- readonly _tag: typeof FAILURE_TAG
167
-
168
- /**
169
- * @since 1.18.0
170
- */
171
- readonly cause: Cause.Cause<E>
172
-
173
- /**
174
- * @since 1.20.0
175
- */
176
- readonly timestamp: number // Date.now()
177
118
 
178
- /**
179
- * @since 1.18.0
180
- */
181
- readonly refreshing: Option.Option<Loading>
119
+ static schema<A, I, R>(
120
+ value: Schema.Schema<A, I, R>,
121
+ ): Schema.SchemaClass<Success<A>, AsyncData.SuccessEncoded<I>, R> {
122
+ return Schema.Struct({
123
+ _tag: LiteralWithDefault<'_tag'>()('Success'),
124
+ value,
125
+ }).pipe(
126
+ Schema.transform(Schema.instanceOf(Success<A>), {
127
+ strict: true,
128
+ decode: (from) => new Success(from.value),
129
+ encode: identity,
130
+ }),
131
+ )
132
+ }
133
+ }
182
134
 
183
- /**
184
- * @since 1.18.0
185
- */
186
- readonly [Unify.typeSymbol]: unknown
135
+ export function success<A>(value: A): Success<A>
136
+ export function success<A, E = never>(value: A): AsyncData<A, E>
137
+ export function success<A>(value: A): Success<A> {
138
+ return new Success(value)
139
+ }
187
140
 
188
- /**
189
- * @since 1.18.0
190
- */
191
- readonly [Unify.unifySymbol]: AsyncData.Unify<this>
141
+ export class Failure<E>
142
+ extends DataEffect('Failure')<{ readonly cause: Cause.Cause<E> }, never, E, never>
143
+ implements Effect.Effect<never, E, never>
144
+ {
145
+ constructor(cause: Cause.Cause<E>) {
146
+ super({ cause }, Effect.failCause(cause))
147
+ }
192
148
 
193
- /**
194
- * @since 1.18.0
195
- */
196
- readonly [Unify.ignoreSymbol]: AsyncData.IgnoreList
149
+ static schema<E, I, R>(
150
+ error: Schema.Schema<E, I, R>,
151
+ ): Schema.SchemaClass<Failure<E>, AsyncData.FailureEncoded<I>, R> {
152
+ return Schema.Struct({
153
+ _tag: LiteralWithDefault<'_tag'>()('Failure'),
154
+ cause: Schema.Cause({
155
+ error,
156
+ defect: Schema.Unknown,
157
+ }),
158
+ }).pipe(
159
+ Schema.transform(Schema.instanceOf(Failure<E>), {
160
+ strict: true,
161
+ decode: (from) => new Failure(from.cause),
162
+ encode: identity,
163
+ }),
164
+ )
165
+ }
197
166
  }
198
167
 
199
- /**
200
- * @since 1.0.0
201
- */
202
- export type FailureOptions = {
203
- readonly timestamp: number // Date.now()
204
- readonly refreshing: Option.Option<Loading>
168
+ export function failure<E>(cause: Cause.Cause<E>): Failure<E>
169
+ export function failure<E, A = never>(cause: Cause.Cause<E>): AsyncData<A, E>
170
+ export function failure<E>(cause: Cause.Cause<E>): Failure<E> {
171
+ return new Failure(cause)
205
172
  }
206
173
 
207
- /**
208
- * @since 1.0.0
209
- */
210
- export const failCause: {
211
- <E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): Failure<E>
212
- <A, E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): AsyncData<A, E>
213
- } = <E>(cause: Cause.Cause<E>, options?: OptionalPartial<FailureOptions>): Failure<E> =>
214
- new internal.FailureImpl(
215
- cause,
216
- options?.timestamp ?? getCurrentTimestamp(),
217
- Option.fromNullable(options?.refreshing)
218
- ) as any
219
-
220
- /**
221
- * @since 1.0.0
222
- */
223
- export const fail: {
224
- <E>(error: E, options?: OptionalPartial<FailureOptions>): Failure<E>
225
- <A, E>(error: E, options?: OptionalPartial<FailureOptions>): AsyncData<A, E>
226
- } = <E>(error: E, options?: OptionalPartial<FailureOptions>): Failure<E> => failCause<E>(Cause.fail(error), options)
227
-
228
- /**
229
- * @since 1.0.0
230
- */
231
- export interface Success<out A> extends Effect.Effect<never, never, A> {
232
- readonly [AsyncDataTypeId]: AsyncDataTypeId
233
-
234
- readonly _tag: typeof SUCCESS_TAG
235
- readonly value: A
236
- /**
237
- * @since 1.20.0
238
- */
239
- readonly timestamp: number // Date.now()
240
- readonly refreshing: Option.Option<Loading>
241
-
242
- readonly [Unify.typeSymbol]: unknown
243
- readonly [Unify.unifySymbol]: AsyncData.Unify<this>
244
- readonly [Unify.ignoreSymbol]: AsyncData.IgnoreList
174
+ export function die(cause: unknown): Failure<never>
175
+ export function die<A, E = never>(cause: unknown): AsyncData<A, E>
176
+ export function die<E>(cause: unknown): Failure<E> {
177
+ return new Failure(Cause.die(cause))
245
178
  }
246
179
 
247
- /**
248
- * @since 1.0.0
249
- */
250
- export type SuccessOptions = {
251
- readonly timestamp: number // Date.now()
252
- readonly refreshing: Option.Option<Loading>
180
+ export function fail<E>(error: E): Failure<E> {
181
+ return new Failure(Cause.fail(error))
253
182
  }
254
183
 
255
- /**
256
- * @since 1.0.0
257
- */
258
- export const success: {
259
- <A>(value: A, options?: OptionalPartial<SuccessOptions>): Success<A>
260
- <A, E>(value: A, options?: OptionalPartial<SuccessOptions>): AsyncData<A, E>
261
- } = <A>(value: A, options?: OptionalPartial<SuccessOptions>): Success<A> =>
262
- new internal.SuccessImpl(
263
- value,
264
- options?.timestamp ?? getCurrentTimestamp(),
265
- Option.fromNullable(options?.refreshing)
266
- ) as any
267
-
268
- /**
269
- * @since 1.0.0
270
- */
271
- export interface Optimistic<A, E = never> extends Effect.Effect<never, never, A> {
272
- readonly [AsyncDataTypeId]: AsyncDataTypeId
273
- readonly _tag: "Optimistic"
274
- readonly value: A
275
- readonly timestamp: number // Date.now()
276
- readonly previous: AsyncData<A, E>
277
-
278
- readonly [Unify.typeSymbol]: unknown
279
- readonly [Unify.unifySymbol]: AsyncData.Unify<this>
280
- readonly [Unify.ignoreSymbol]: AsyncData.IgnoreList
184
+ export class Refreshing<A, E>
185
+ extends DataEffect('Refreshing')<
186
+ { readonly previous: Success<A> | Failure<E>; readonly progress: Option.Option<Progress> },
187
+ A,
188
+ E,
189
+ never
190
+ >
191
+ implements Effect.Effect<A, E>
192
+ {
193
+ constructor(previous: Success<A> | Failure<E>, progress: Option.Option<Progress>) {
194
+ super({ previous, progress }, previous)
195
+ }
196
+
197
+ static schema<A, EI, E, AI, R>(
198
+ previous: Schema.Schema<
199
+ Success<A> | Failure<E>,
200
+ AsyncData.SuccessEncoded<AI> | AsyncData.FailureEncoded<EI>,
201
+ R
202
+ >,
203
+ ): Schema.SchemaClass<Refreshing<A, E>, AsyncData.RefreshingEncoded<AI, EI>, R> {
204
+ return Schema.Struct({
205
+ _tag: LiteralWithDefault<'_tag'>()('Refreshing'),
206
+ previous,
207
+ progress: Schema.optionalWith(Progress, { as: 'Option', default: undefined }),
208
+ }).pipe(
209
+ Schema.transform(Schema.instanceOf(Refreshing<A, E>), {
210
+ strict: true,
211
+ decode: (from) => new Refreshing(from.previous, from.progress),
212
+ encode: identity,
213
+ }),
214
+ )
215
+ }
281
216
  }
282
217
 
283
- /**
284
- * @since 1.0.0
285
- */
286
- export interface OptimisticOptions {
287
- readonly timestamp: number // Date.now()
218
+ export function refreshing<A = never, E = never>(
219
+ previous: Success<A> | Failure<E>,
220
+ progress?: Progress,
221
+ ): Refreshing<A, E> {
222
+ return new Refreshing(previous, Option.fromNullable(progress))
288
223
  }
289
224
 
290
- const isAsyncDataFirst = (args: IArguments) => isAsyncData(args[0])
225
+ export class Optimistic<A, E>
226
+ extends DataEffect('Optimistic')<{ readonly previous: AsyncData<A, E>; readonly value: A }, A>
227
+ implements Effect.Effect<A>
228
+ {
229
+ constructor(previous: AsyncData<A, E>, value: A) {
230
+ super({ previous, value }, Effect.succeed(value))
231
+ }
291
232
 
292
- /**
293
- * @since 1.0.0
294
- */
295
- export const optimistic: {
296
- <A>(value: A, options?: OptionalPartial<OptimisticOptions>): <E>(previous: AsyncData<A, E>) => Optimistic<A, E>
297
- <A, E = never>(previous: AsyncData<A, E>, value: A, options?: OptionalPartial<OptimisticOptions>): Optimistic<A, E>
298
- } = dual(
299
- (args) => args.length === 3 || isAsyncDataFirst(args),
300
- <A, E>(previous: AsyncData<A, E>, value: A, options?: OptionalPartial<OptimisticOptions>): Optimistic<A, E> =>
301
- new internal.OptimisticImpl<A, E>(
233
+ static schema<A, EI, E, AI, R, R2>(
234
+ previous: Schema.Schema<AsyncData<A, E>, AsyncData.Encoded<AI, EI>, R>,
235
+ value: Schema.Schema<A, AI, R2>,
236
+ ): Schema.SchemaClass<Optimistic<A, E>, AsyncData.OptimisticEncoded<AI, EI>, R | R2> {
237
+ return Schema.Struct({
238
+ _tag: LiteralWithDefault<'_tag'>()('Optimistic'),
239
+ previous,
302
240
  value,
303
- options?.timestamp ?? getCurrentTimestamp(),
304
- // We don't want to nest Optimistic values, so we unwrap the previous value if it's already optimistic
305
- previous._tag === "Optimistic" ? previous.previous : previous
306
- ) as any
307
- )
308
-
309
- /**
310
- * @since 1.0.0
311
- */
312
- export const isSuccess = <A, E>(data: AsyncData<A, E>): data is Success<A> => data._tag === SUCCESS_TAG
313
-
314
- /**
315
- * @since 1.0.0
316
- */
317
- export const isOptimistic = <A, E>(data: AsyncData<A, E>): data is Optimistic<A, E> => data._tag === OPTIMISTIC_TAG
318
-
319
- /**
320
- * @since 1.0.0
321
- */
322
- export const isFailure = <A, E>(data: AsyncData<A, E>): data is Failure<E> => data._tag === FAILURE_TAG
323
-
324
- /**
325
- * @since 1.0.0
326
- */
327
- export const isLoading = <A, E>(data: AsyncData<A, E>): data is Loading => data._tag === LOADING_TAG
328
-
329
- /**
330
- * @since 1.0.0
331
- */
332
- export const isNoData = <A, E>(data: AsyncData<A, E>): data is NoData => data._tag === NO_DATA_TAG
333
-
334
- /**
335
- * @since 1.0.0
336
- */
337
- export type Refreshing<A, E> = RefreshingFailure<E> | RefreshingSuccess<A>
338
-
339
- /**
340
- * @since 1.0.0
341
- */
342
- export interface RefreshingFailure<E> extends Failure<E> {
343
- readonly refreshing: Option.Some<Loading>
241
+ }).pipe(
242
+ Schema.transform(Schema.instanceOf(Optimistic<A, E>), {
243
+ strict: true,
244
+ decode: (from) => new Optimistic(from.previous, from.value),
245
+ encode: identity,
246
+ }),
247
+ )
248
+ }
344
249
  }
345
250
 
346
- /**
347
- * @since 1.0.0
348
- */
349
- export interface RefreshingSuccess<A> extends Success<A> {
350
- readonly refreshing: Option.Some<Loading>
351
- }
251
+ export const optimistic: {
252
+ <A>(value: A): <E>(previous: AsyncData<A, E>) => Optimistic<A, E>
253
+ <A, E>(previous: AsyncData<A, E>, value: A): Optimistic<A, E>
254
+ } = dual(2, function optimistic<A, E>(previous: AsyncData<A, E>, value: A): Optimistic<A, E> {
255
+ return new Optimistic(previous, value)
256
+ })
352
257
 
353
- /**
354
- * @since 1.0.0
355
- */
356
- export const isRefreshing = <A, E>(data: AsyncData<A, E>): data is Refreshing<A, E> =>
357
- isSuccess(data) || isFailure(data)
358
- ? Option.isSome(data.refreshing)
359
- : isOptimistic(data)
360
- ? isRefreshing(data.previous)
361
- : false
362
-
363
- /**
364
- * @since 1.0.0
365
- */
366
- export const isLoadingOrRefreshing = <A, E>(data: AsyncData<A, E>): data is Loading | Refreshing<A, E> =>
367
- isLoading(data) || isRefreshing(data) || (isOptimistic(data) && isLoadingOrRefreshing(data.previous))
368
-
369
- /**
370
- * @since 1.0.0
371
- */
372
- export const match: {
373
- <A, E, R1, R2, R3, R4, R5>(
258
+ export const matchAll: {
259
+ <A, E, R1, R2, R3, R4, R5, R6>(
260
+ data: AsyncData<A, E>,
374
261
  matchers: {
375
- NoData: (data: NoData) => R1
376
- Loading: (data: Loading) => R2
377
- Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
378
- Success: (value: A, data: Success<A>) => R4
379
- Optimistic: (value: A, data: Optimistic<A, E>) => R5
380
- }
381
- ): (data: AsyncData<A, E>) => Unify.Unify<R1 | R2 | R3 | R4 | R5>
382
-
383
- <A, E, R1, R2, R3, R4, R5>(
262
+ readonly NoData: () => R1
263
+ readonly Loading: (progress: Option.Option<Progress>) => R2
264
+ readonly Success: (value: A) => R3
265
+ readonly Failure: (cause: Cause.Cause<E>) => R4
266
+ readonly Refreshing: (
267
+ previous: Success<A> | Failure<E>,
268
+ progress: Option.Option<Progress>,
269
+ ) => R5
270
+ readonly Optimistic: (value: A, previous: AsyncData<A, E>) => R6
271
+ },
272
+ ): R1 | R2 | R3 | R4 | R5 | R6
273
+
274
+ <A, E, R1, R2, R3, R4, R5, R6>(
384
275
  data: AsyncData<A, E>,
385
276
  matchers: {
386
- NoData: (data: NoData) => R1
387
- Loading: (data: Loading) => R2
388
- Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
389
- Success: (value: A, data: Success<A>) => R4
390
- Optimistic: (value: A, data: Optimistic<A, E>) => R5
277
+ readonly NoData: () => R1
278
+ readonly Loading: (progress: Option.Option<Progress>) => R2
279
+ readonly Success: (value: A) => R3
280
+ readonly Failure: (cause: Cause.Cause<E>) => R4
281
+ readonly Refreshing: (
282
+ previous: Success<A> | Failure<E>,
283
+ progress: Option.Option<Progress>,
284
+ ) => R5
285
+ readonly Optimistic: (value: A, previous: AsyncData<A, E>) => R6
286
+ },
287
+ ): R1 | R2 | R3 | R4 | R5 | R6
288
+ } = dual(
289
+ 2,
290
+ function matchAll<A, E, R1, R2, R3, R4, R5, R6>(
291
+ data: AsyncData<A, E>,
292
+ matchers: {
293
+ readonly NoData: () => R1
294
+ readonly Loading: (progress: Option.Option<Progress>) => R2
295
+ readonly Success: (value: A) => R3
296
+ readonly Failure: (cause: Cause.Cause<E>) => R4
297
+ readonly Refreshing: (
298
+ previous: Success<A> | Failure<E>,
299
+ progress: Option.Option<Progress>,
300
+ ) => R5
301
+ readonly Optimistic: (value: A, previous: AsyncData<A, E>) => R6
302
+ },
303
+ ): R1 | R2 | R3 | R4 | R5 | R6 {
304
+ switch (data._tag) {
305
+ case 'NoData':
306
+ return matchers.NoData()
307
+ case 'Loading':
308
+ return matchers.Loading(data.progress)
309
+ case 'Success':
310
+ return matchers.Success(data.value)
311
+ case 'Failure':
312
+ return matchers.Failure(data.cause)
313
+ case 'Refreshing':
314
+ return matchers.Refreshing(data.previous, data.progress)
315
+ case 'Optimistic':
316
+ return matchers.Optimistic(data.value, data.previous)
391
317
  }
392
- ): Unify.Unify<R1 | R2 | R3 | R4 | R5>
393
- } = dual(2, <A, E, R1, R2, R3, R4, R5>(data: AsyncData<A, E>, matchers: {
394
- NoData: (data: NoData) => R1
395
- Loading: (data: Loading) => R2
396
- Failure: (cause: Cause.Cause<E>, data: Failure<E>) => R3
397
- Success: (value: A, data: Success<A>) => R4
398
- Optimistic: (value: A, data: Optimistic<A, E>) => R5
399
- }): Unify.Unify<R1 | R2 | R3 | R4> => {
400
- if (isSuccess(data)) {
401
- return matchers.Success(data.value, data) as Unify.Unify<R1 | R2 | R3 | R4>
402
- } else if (isFailure(data)) {
403
- return matchers.Failure(data.cause, data) as Unify.Unify<R1 | R2 | R3 | R4>
404
- } else if (isLoading(data)) {
405
- return matchers.Loading(data) as Unify.Unify<R1 | R2 | R3 | R4>
406
- } else if (isNoData(data)) {
407
- return matchers.NoData(data) as Unify.Unify<R1 | R2 | R3 | R4>
408
- } else {
409
- return matchers.Optimistic(data.value, data) as Unify.Unify<R1 | R2 | R3 | R4>
410
- }
411
- })
412
-
413
- /**
414
- * @since 1.0.0
415
- */
416
- export const map: {
417
- <A, B>(f: (a: A) => B): <E>(data: AsyncData<A, E>) => AsyncData<B, E>
418
- <A, E, B>(data: AsyncData<A, E>, f: (a: A) => B): AsyncData<B, E>
419
- } = dual(2, function map<A, E, B>(data: AsyncData<A, E>, f: (a: A) => B): AsyncData<B, E> {
420
- if (isSuccess(data)) {
421
- return success(f(data.value), {
422
- timestamp: data.timestamp,
423
- refreshing: Option.getOrUndefined(data.refreshing)
424
- })
425
- } else if (isOptimistic(data)) {
426
- return optimistic(map(data.previous, f), f(data.value), { timestamp: data.timestamp })
427
- } else {
428
- return data
429
- }
430
- })
318
+ },
319
+ )
431
320
 
432
- /**
433
- * @since 1.0.0
434
- */
435
- export const flatMap: {
436
- <A, E, B, E2>(
437
- f: (a: A, data: Success<A> | Optimistic<A, E>) => AsyncData<B, E2>
438
- ): (data: AsyncData<A, E>) => AsyncData<B, E | E2>
439
- <A, E, B, E2>(
321
+ export const match: {
322
+ <A, E, R1, R2, R3, R4>(matchers: {
323
+ readonly NoData: () => R1
324
+ readonly Loading: (progress: Option.Option<Progress>) => R2
325
+ readonly Success: (
326
+ value: A,
327
+ params: {
328
+ readonly isRefreshing: boolean
329
+ readonly isOptimistic: boolean
330
+ readonly progress: Option.Option<Progress>
331
+ },
332
+ ) => R3
333
+ readonly Failure: (
334
+ cause: Cause.Cause<E>,
335
+ params: { readonly isRefreshing: boolean; readonly progress: Option.Option<Progress> },
336
+ ) => R4
337
+ }): (data: AsyncData<A, E>) => Unify.Unify<R1 | R2 | R3 | R4>
338
+
339
+ <A, E, R1, R2, R3, R4>(
440
340
  data: AsyncData<A, E>,
441
- f: (a: A, data: Success<A> | Optimistic<A, E>) => AsyncData<B, E>
442
- ): AsyncData<B, E | E2>
341
+ matchers: {
342
+ readonly NoData: () => R1
343
+ readonly Loading: (progress: Option.Option<Progress>) => R2
344
+ readonly Success: (
345
+ value: A,
346
+ params: {
347
+ readonly isRefreshing: boolean
348
+ readonly isOptimistic: boolean
349
+ readonly progress: Option.Option<Progress>
350
+ },
351
+ ) => R3
352
+ readonly Failure: (
353
+ cause: Cause.Cause<E>,
354
+ params: { readonly isRefreshing: boolean; readonly progress: Option.Option<Progress> },
355
+ ) => R4
356
+ },
357
+ ): Unify.Unify<R1 | R2 | R3 | R4>
443
358
  } = dual(
444
359
  2,
445
- function<A, E, B, E2>(
360
+ function match<A, E, R1, R2, R3, R4>(
446
361
  data: AsyncData<A, E>,
447
- f: (a: A, data: Success<A> | Optimistic<A, E>) => AsyncData<B, E2>
448
- ): AsyncData<B, E | E2> {
449
- if (isSuccess(data) || isOptimistic(data)) {
450
- return f(data.value, data)
451
- } else {
362
+ matchers: {
363
+ readonly NoData: () => R1
364
+ readonly Loading: (progress: Option.Option<Progress>) => R2
365
+ readonly Success: (
366
+ value: A,
367
+ params: {
368
+ readonly isRefreshing: boolean
369
+ readonly isOptimistic: boolean
370
+ readonly progress: Option.Option<Progress>
371
+ },
372
+ ) => R3
373
+ readonly Failure: (
374
+ cause: Cause.Cause<E>,
375
+ params: { readonly isRefreshing: boolean; readonly progress: Option.Option<Progress> },
376
+ ) => R4
377
+ },
378
+ ): Unify.Unify<R1 | R2 | R3 | R4> {
379
+ const match_ = (
380
+ data: AsyncData<A, E>,
381
+ params: {
382
+ readonly isRefreshing: boolean
383
+ readonly isOptimistic: boolean
384
+ readonly progress: Option.Option<Progress>
385
+ },
386
+ ): R1 | R2 | R3 | R4 =>
387
+ matchAll(data, {
388
+ NoData: matchers.NoData,
389
+ Loading: matchers.Loading,
390
+ Success: (value) => matchers.Success(value, params),
391
+ Failure: (cause) => matchers.Failure(cause, params),
392
+ Refreshing: (previous, progress) =>
393
+ match_(previous, { ...params, isRefreshing: true, progress }),
394
+ Optimistic: (value) => matchers.Success(value, { ...params, isOptimistic: true }),
395
+ })
396
+
397
+ return Unify.unify(
398
+ match_(data, {
399
+ isRefreshing: false,
400
+ isOptimistic: false,
401
+ progress: Option.none(),
402
+ }),
403
+ )
404
+ },
405
+ )
406
+
407
+ export function startLoading<A, E>(data: AsyncData<A, E>, progress?: Progress): AsyncData<A, E> {
408
+ switch (data._tag) {
409
+ case 'Success':
410
+ case 'Failure':
411
+ return refreshing(data, progress)
412
+ case 'NoData':
413
+ return loading(progress)
414
+ case 'Optimistic':
415
+ return optimistic(startLoading(data.previous, progress), data.value)
416
+ default:
452
417
  return data
453
- }
454
418
  }
455
- )
419
+ }
456
420
 
457
- /**
458
- * @since 1.0.0
459
- */
460
- export const startLoading = <A, E>(
461
- data: AsyncData<A, E>,
462
- options?: OptionalPartial<LoadingOptions>
463
- ): AsyncData<A, E> => {
464
- if (isSuccess(data)) {
465
- return Option.isSome(data.refreshing) ? data : success(data.value, { ...data, refreshing: loading(options) })
466
- } else if (isFailure(data)) {
467
- return Option.isSome(data.refreshing)
468
- ? data
469
- : failCause(data.cause, { ...data, refreshing: loading(options) })
470
- } else if (isOptimistic(data)) {
471
- return optimistic(startLoading(data.previous, options), data.value, data)
472
- } else {
473
- return loading(options)
421
+ export function stopLoading<A, E>(data: AsyncData<A, E>): AsyncData<A, E> {
422
+ switch (data._tag) {
423
+ case 'Refreshing':
424
+ return data.previous
425
+ case 'Loading':
426
+ return noData()
427
+ case 'Optimistic':
428
+ return optimistic(stopLoading(data.previous), data.value)
429
+ default:
430
+ return data
474
431
  }
475
432
  }
476
433
 
477
- /**
478
- * @since 1.0.0
479
- */
480
- export const stopLoading = <A, E>(data: AsyncData<A, E>): AsyncData<A, E> => {
481
- if (isSuccess(data)) {
482
- return Option.isSome(data.refreshing) ? success(data.value, { timestamp: data.timestamp }) : data
483
- } else if (isFailure(data)) {
484
- return Option.isSome(data.refreshing) ? failCause(data.cause, { timestamp: data.timestamp }) : data
485
- } else if (isOptimistic(data)) {
486
- return optimistic(stopLoading(data.previous), data.value, data)
487
- } else {
488
- return noData()
434
+ export function updateProgress<A, E>(data: AsyncData<A, E>, progress: Progress): AsyncData<A, E> {
435
+ switch (data._tag) {
436
+ case 'Refreshing':
437
+ return refreshing(data.previous, progress)
438
+ case 'Loading':
439
+ return loading(progress)
440
+ case 'Optimistic':
441
+ return optimistic(updateProgress(data.previous, progress), data.value)
442
+ default:
443
+ return data
489
444
  }
490
445
  }
491
446
 
492
- /**
493
- * @since 1.0.0
494
- */
495
- export const isAsyncData: <A, E>(u: unknown) => u is AsyncData<A, E> = internal.isAsyncData
496
-
497
- /**
498
- * @since 1.0.0
499
- */
500
- export const done = <A, E = never>(exit: Exit.Exit<A, E>): AsyncData<A, E> =>
501
- Exit.match(exit, {
502
- onFailure: (cause) => failCause(cause),
503
- onSuccess: (value) => success(value)
504
- })
447
+ export interface AsyncDataSchemaClass<E, EI, ER, A, AI, AR>
448
+ extends Schema.SchemaClass<AsyncData<A, E>, AsyncData.Encoded<AI, EI>, AR | ER> {
449
+ readonly eq: Equivalence.Equivalence<AsyncData<A, E>>
450
+
451
+ readonly success: (value: A) => AsyncData<A, E>
452
+ readonly failCause: (cause: Cause.Cause<E>) => AsyncData<A, E>
453
+ readonly fail: (error: E) => AsyncData<A, E>
454
+ readonly die: (cause: unknown) => AsyncData<A, E>
455
+ readonly optimistic: (previous: AsyncData<A, E>, value: A) => AsyncData<A, E>
456
+ readonly refreshing: (previous: Success<A> | Failure<E>, progress?: Progress) => AsyncData<A, E>
457
+ readonly loading: (progress?: Progress) => AsyncData<A, E>
458
+ readonly noData: () => AsyncData<A, E>
459
+ }
505
460
 
506
- /**
507
- * @since 1.0.0
508
- */
509
- export const getFailure = <A, E>(data: AsyncData<A, E>): Option.Option<E> =>
510
- isFailure(data) ? Cause.failureOption(data.cause) : Option.none()
461
+ export function AsyncData<A, AI, AR, E, EI, ER>(
462
+ schemas: {
463
+ readonly success: Schema.Schema<A, AI, AR>
464
+ readonly failure: Schema.Schema<E, EI, ER>
465
+ },
466
+ annotations?: Schema.Annotations.Schema<AsyncData<A, E>>,
467
+ ): AsyncDataSchemaClass<E, EI, ER, A, AI, AR> {
468
+ const identifier = Option.all({
469
+ E: getIdentifier(schemas.failure.ast),
470
+ A: getIdentifier(schemas.success.ast),
471
+ }).pipe(
472
+ Option.match({
473
+ onNone: () => 'AsyncData',
474
+ onSome: ({ E, A }) => `AsyncData<${E}, ${A}>`,
475
+ }),
476
+ )
477
+
478
+ const equivalence = makeEquivalence(
479
+ Schema.equivalence(schemas.success),
480
+ Schema.equivalence(schemas.failure),
481
+ )
482
+
483
+ const recursive: Schema.Schema<
484
+ AsyncData<A, E>,
485
+ AsyncData.Encoded<AI, EI>,
486
+ AR | ER
487
+ > = Schema.suspend(() => AsyncData).annotations({
488
+ identifier,
489
+ equivalence: () => equivalence,
490
+ ...annotations,
491
+ })
511
492
 
512
- /**
513
- * @since 1.0.0
514
- */
515
- export const getSuccess = <A, E>(data: AsyncData<A, E>): Option.Option<A> =>
516
- isSuccess(data) || isOptimistic(data) ? Option.some(data.value) : Option.none()
493
+ const successAndFailure = Schema.Union(
494
+ Success.schema(schemas.success),
495
+ Failure.schema(schemas.failure),
496
+ )
497
+
498
+ const AsyncData = Schema.Union(
499
+ NoData,
500
+ Loading,
501
+ successAndFailure,
502
+ Refreshing.schema(successAndFailure),
503
+ Optimistic.schema(recursive, schemas.success),
504
+ ).annotations({
505
+ equivalence: () => equivalence,
506
+ })
517
507
 
518
- const optionProgressEq = Option.getEquivalence(Progress.equals)
508
+ return class extends AsyncData {
509
+ static readonly eq = equivalence
510
+ static readonly success = success
511
+ static readonly failCause = failure
512
+ static readonly fail = fail
513
+ static readonly die = die
514
+ static readonly optimistic = optimistic
515
+ static readonly refreshing = refreshing
516
+ static readonly loading = loading
517
+ static readonly noData = noData
518
+ }
519
+ }
519
520
 
520
- const loadingEquivalence: Equivalence.Equivalence<Loading> = Equivalence.struct({
521
- _tag: Equivalence.string,
522
- timestamp: Equivalence.number,
523
- progress: optionProgressEq
524
- })
521
+ function getIdentifier(ast: SchemaAST.AST) {
522
+ return SchemaAST.getJSONIdentifier(ast).pipe(
523
+ Option.orElse(() => SchemaAST.getTitleAnnotation(ast)),
524
+ )
525
+ }
525
526
 
526
- const optionLoadingEq = Option.getEquivalence(loadingEquivalence)
527
+ export function fromExit<A, E>(exit: Exit.Exit<A, E>): AsyncData<A, E> {
528
+ return Exit.match(exit, { onSuccess: success<A>, onFailure: failure<E> })
529
+ }
527
530
 
528
- const failureEquivalence: Equivalence.Equivalence<Failure<any>> = Equivalence.struct({
529
- _tag: Equivalence.string,
530
- cause: Equal.equals,
531
- timestamp: Equivalence.number,
532
- refreshing: optionLoadingEq
533
- })
531
+ export function fromEither<A, E>(either: Either.Either<A, E>): AsyncData<A, E> {
532
+ return Either.match(either, { onLeft: fail<E>, onRight: success<A> })
533
+ }
534
534
 
535
- const successEquivalence = <A>(valueEq: Equivalence.Equivalence<A>): Equivalence.Equivalence<Success<A>> =>
536
- Equivalence.struct({
537
- _tag: Equivalence.string,
538
- value: valueEq,
539
- timestamp: Equivalence.number,
540
- refreshing: optionLoadingEq
541
- })
535
+ export function fromOption<A>(option: Option.Option<A>): AsyncData<A> {
536
+ return Option.match(option, { onSome: success<A>, onNone: noData<A> })
537
+ }
542
538
 
543
- const optimisticEquivalence = <A, E>(
544
- valueEq: Equivalence.Equivalence<A>
545
- ): Equivalence.Equivalence<Optimistic<A, E>> => {
546
- let previousEq: Equivalence.Equivalence<AsyncData<A, E>> | undefined
547
- const get = () => {
548
- if (previousEq === undefined) {
549
- previousEq = getEquivalence(valueEq)
539
+ export const map: {
540
+ <A, B>(f: (a: A) => B): <E>(data: AsyncData<A, E>) => AsyncData<B, E>
541
+ <A, E, B>(data: AsyncData<A, E>, f: (a: A) => B): AsyncData<B, E>
542
+ } = dual(2, function map<A, E, B>(data: AsyncData<A, E>, f: (a: A) => B): AsyncData<B, E> {
543
+ if (data._tag === 'Success') {
544
+ return new Success(f(data.value))
545
+ } else if (data._tag === 'Refreshing' && data.previous._tag === 'Success') {
546
+ return new Refreshing(new Success(f(data.previous.value)), data.progress)
547
+ } else if (data._tag === 'Optimistic') {
548
+ if (data.previous._tag === 'Success') {
549
+ return new Optimistic(new Success(f(data.previous.value)), f(data.value))
550
+ } else if (data.previous._tag === 'Failure') {
551
+ return new Optimistic(data.previous, f(data.value))
550
552
  }
551
- return previousEq
552
553
  }
553
554
 
554
- return Equivalence.struct({
555
- _tag: Equivalence.string,
556
- value: valueEq,
557
- timestamp: Equivalence.number,
558
- previous: (a: AsyncData<A, E>, b: AsyncData<A, E>) => get()(a, b)
559
- })
560
- }
561
-
562
- /**
563
- * @since 1.0.0
564
- */
565
- export const getEquivalence = <A, E>(
566
- valueEq: Equivalence.Equivalence<A> = Equal.equals
567
- ): Equivalence.Equivalence<AsyncData<A, E>> => {
568
- const successEq_ = successEquivalence(valueEq)
569
- const optimisticEq_ = optimisticEquivalence(valueEq)
570
- return (a, b) => {
571
- if (a === b) return true
555
+ return data as AsyncData<B, E>
556
+ })
572
557
 
573
- return match(a, {
574
- NoData: () => isNoData(b),
575
- Loading: (l1) => isLoading(b) ? loadingEquivalence(l1, b) : false,
576
- Failure: (_, f1) => isFailure(b) ? failureEquivalence(f1, b) : false,
577
- Success: (_, s1) => isSuccess(b) ? successEq_(s1, b) : false,
578
- Optimistic: (_, o1) => isOptimistic(b) ? optimisticEq_(o1, b) : false
579
- })
558
+ export const flatMap: {
559
+ <A, B, E2>(f: (a: A) => AsyncData<B, E2>): <E>(data: AsyncData<A, E>) => AsyncData<B, E | E2>
560
+ <A, E, B, E2>(data: AsyncData<A, E>, f: (a: A) => AsyncData<B, E2>): AsyncData<B, E | E2>
561
+ } = dual(2, function flatMap<
562
+ A,
563
+ E,
564
+ B,
565
+ E2,
566
+ >(data: AsyncData<A, E>, f: (a: A) => AsyncData<B, E2>): AsyncData<B, E | E2> {
567
+ if (data._tag === 'Success' || data._tag === 'Optimistic') {
568
+ return f(data.value)
569
+ } else if (data._tag === 'Refreshing' && data.previous._tag === 'Success') {
570
+ return f(data.previous.value)
580
571
  }
581
- }
582
572
 
583
- /**
584
- * @since 1.0.0
585
- */
586
- export function fromExit<A, E>(exit: Exit.Exit<A, E>): Success<A> | Failure<E> {
587
- return Exit.match(exit, {
588
- onFailure: (cause) => failCause(cause),
589
- onSuccess: (value) => success(value)
590
- })
573
+ return data as AsyncData<B, E>
574
+ })
575
+
576
+ export function isNoData<A, E>(data: AsyncData<A, E>): data is NoData {
577
+ return data._tag === 'NoData'
591
578
  }
592
579
 
593
- /**
594
- * @since 1.0.0
595
- */
596
- export function fromEither<A, E = never>(either: Either.Either<A, E>): Success<A> | Failure<E> {
597
- return Either.match(either, {
598
- onLeft: (e) => fail(e),
599
- onRight: (a) => success(a)
600
- })
580
+ export function isLoading<A, E>(data: AsyncData<A, E>): data is Loading {
581
+ return data._tag === 'Loading'
601
582
  }
602
583
 
603
- /**
604
- * @since 1.0.0
605
- */
606
- export const isExpired: {
607
- (ttl: Duration.DurationInput, now?: number): <A, E>(data: AsyncData<A, E>) => boolean
608
- <A, E>(data: AsyncData<A, E>, ttl: Duration.DurationInput, now?: number): boolean
609
- } = dual(isAsyncDataFirst, function isExpired<A, E>(
610
- data: AsyncData<A, E>,
611
- ttl: Duration.DurationInput,
612
- now: number = getCurrentTimestamp()
613
- ): boolean {
614
- return match(data, {
615
- NoData: () => true,
616
- Loading: ({ timestamp }) => isPastTTL(timestamp, ttl, now),
617
- Failure: (_, f) =>
618
- Option.isNone(f.refreshing)
619
- ? isPastTTL(f.timestamp, ttl, now)
620
- : isPastTTL(f.refreshing.value.timestamp, ttl, now),
621
- Success: (_, s) =>
622
- Option.isNone(s.refreshing)
623
- ? isPastTTL(s.timestamp, ttl, now) :
624
- isPastTTL(s.refreshing.value.timestamp, ttl, now),
625
- Optimistic: (_, o) =>
626
- isPastTTL(o.timestamp, ttl, now) || (o.previous._tag === NO_DATA_TAG ? false : isExpired(o.previous, ttl, now))
627
- })
628
- })
584
+ export function isSuccess<A, E>(data: AsyncData<A, E>): data is Success<A> {
585
+ return data._tag === 'Success'
586
+ }
629
587
 
630
- function isPastTTL(timestamp: number, ttl: Duration.DurationInput, now: number): boolean {
631
- const millis = Duration.toMillis(ttl)
588
+ export function isFailure<A, E>(data: AsyncData<A, E>): data is Failure<E> {
589
+ return data._tag === 'Failure'
590
+ }
632
591
 
633
- return now - timestamp >= millis
592
+ export function isRefreshing<A, E>(data: AsyncData<A, E>): data is Refreshing<A, E> {
593
+ return data._tag === 'Refreshing'
634
594
  }
635
595
 
636
- const optionDataEqual = Option.getEquivalence(dataEqual)
637
-
638
- /**
639
- * Checks if two AsyncData are equal, disregarding the timestamps associated with them. Useful for testing
640
- * without needing to manage timestamps.
641
- *
642
- * @since 1.0.0
643
- */
644
- export function dataEqual<A, E>(first: AsyncData<A, E>, second: AsyncData<A, E>): boolean {
645
- return match(first, {
646
- NoData: () => isNoData(second),
647
- Loading: (l) => isLoading(second) && Equal.equals(l.progress, second.progress),
648
- Failure: (_, f1) =>
649
- isFailure(second) && Equal.equals(f1.cause, second.cause) && optionDataEqual(f1.refreshing, second.refreshing),
650
- Success: (_, s1) =>
651
- isSuccess(second) && Equal.equals(s1.value, second.value) && optionDataEqual(s1.refreshing, second.refreshing),
652
- Optimistic: (_, o1) =>
653
- isOptimistic(second) && Equal.equals(o1.value, second.value) && dataEqual(o1.previous, second.previous)
654
- })
596
+ export function isLoadingOrRefreshing<A, E>(
597
+ data: AsyncData<A, E>,
598
+ ): data is Loading | Refreshing<A, E> {
599
+ return isLoading(data) || isRefreshing(data)
655
600
  }
656
601
 
657
- /**
658
- * @since 1.0.0
659
- */
660
- export function toOption<A, E>(data: AsyncData<A, E>): Option.Option<A> {
661
- return match(data, {
662
- NoData: Option.none,
663
- Loading: Option.none,
664
- Failure: Option.none,
665
- Success: Option.some,
666
- Optimistic: Option.some
667
- })
602
+ export function isOptimistic<A, E>(data: AsyncData<A, E>): data is Optimistic<A, E> {
603
+ return data._tag === 'Optimistic'
668
604
  }
669
605
 
670
- /**
671
- * @since 1.0.0
672
- */
673
- export function toOptionCause<A, E>(data: AsyncData<A, E>): Option.Option<Cause.Cause<E>> {
674
- return match(data, {
675
- NoData: Option.none,
676
- Loading: Option.none,
677
- Failure: Option.some,
678
- Success: Option.none,
679
- Optimistic: Option.none
606
+ export function makeEquivalence<A, E>(
607
+ eqA: Equivalence.Equivalence<A>,
608
+ eqB: Equivalence.Equivalence<E>,
609
+ ): Equivalence.Equivalence<AsyncData<A, E>> {
610
+ const eqCause = Equivalence.make((a: Cause.Cause<E>, b: Cause.Cause<E>) => {
611
+ const failuresA = Array.from(Cause.failures(a))
612
+ const failuresB = Array.from(Cause.failures(b))
613
+ if (failuresA.length > 0) {
614
+ return (
615
+ failuresA.length === failuresB.length && failuresA.every((e, i) => eqB(e, failuresB[i]))
616
+ )
617
+ }
618
+
619
+ return Equal.equals(a, b)
680
620
  })
621
+
622
+ const eq: Equivalence.Equivalence<AsyncData<A, E>> = Equivalence.make((a, b) =>
623
+ matchAll(a, {
624
+ NoData: () => isNoData(b),
625
+ Loading: (progress) => isLoading(b) && Equal.equals(progress, b.progress),
626
+ Success: (value) => isSuccess(b) && eqA(value, b.value),
627
+ Failure: (cause) => isFailure(b) && eqCause(cause, b.cause),
628
+ Refreshing: (previous, progress) =>
629
+ isRefreshing(b) && eq(previous, b.previous) && Equal.equals(progress, b.progress),
630
+ Optimistic: (value, previous) =>
631
+ isOptimistic(b) && eqA(value, b.value) && eq(previous, b.previous),
632
+ }),
633
+ )
634
+
635
+ return eq
681
636
  }
682
637
 
683
- /**
684
- * @since 1.0.0
685
- */
686
- export function toOptionError<A, E>(data: AsyncData<A, E>): Option.Option<E> {
687
- return match(data, {
688
- NoData: Option.none,
689
- Loading: Option.none,
690
- Failure: Cause.failureOption,
691
- Success: Option.none,
692
- Optimistic: Option.none
693
- })
638
+ type EqValue<T> = T extends Equivalence.Equivalence<infer A> ? A : never
639
+
640
+ function unionEquivalence<EQS extends ReadonlyArray<Equivalence.Equivalence<any>>>(
641
+ eqs: EQS,
642
+ ): Equivalence.Equivalence<EqValue<EQS[number]>> {
643
+ return (a, b) => {
644
+ for (const eq of eqs) {
645
+ if (eq(a, b)) {
646
+ return true
647
+ }
648
+ }
649
+
650
+ return false
651
+ }
694
652
  }
653
+
654
+ export const equals = makeEquivalence(Equal.equals, Equal.equals)