@typed/async-data 0.12.0 → 0.13.1

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