@typed/async-data 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Schema.ts CHANGED
@@ -9,10 +9,11 @@ import * as ParseResult from "@effect/schema/ParseResult"
9
9
  import * as Pretty from "@effect/schema/Pretty"
10
10
  import * as Schema from "@effect/schema/Schema"
11
11
  import * as AsyncData from "@typed/async-data/AsyncData"
12
- import { Equal } from "effect"
13
- import type * as Cause from "effect/Cause"
12
+ import { Data, Equal, FiberId } from "effect"
13
+ import * as Cause from "effect/Cause"
14
14
  import * as Effect from "effect/Effect"
15
15
  import * as Option from "effect/Option"
16
+ import { hasProperty } from "effect/Predicate"
16
17
  import * as P from "./Progress.js"
17
18
 
18
19
  const NO_DATA_PRETTY = "AsyncData.NoData"
@@ -24,55 +25,30 @@ const LOADING_PRETTY = (loading: AsyncData.Loading) =>
24
25
  const FAILURE_PRETTY = <E>(print: Pretty.Pretty<Cause.Cause<E>>) => (failure: AsyncData.Failure<E>) =>
25
26
  Option.match(failure.refreshing, {
26
27
  onNone: () => `AsyncData.Failure(timestamp=${failure.timestamp}, cause=${print(failure.cause)})`,
27
- onSome: (loading) =>
28
- `AsyncData.Failure(timestamp=${failure.timestamp}, refreshing=${LOADING_PRETTY(loading)}, cause=${
29
- print(failure.cause)
30
- })`
28
+ onSome: () => `AsyncData.Failure(timestamp=${failure.timestamp}, refreshing=true, cause=${print(failure.cause)})`
31
29
  })
32
30
  const SUCCESS_PRETTY = <A>(print: Pretty.Pretty<A>) => (success: AsyncData.Success<A>) =>
33
31
  Option.match(success.refreshing, {
34
32
  onNone: () => `AsyncData.Success(timestamp=${success.timestamp}, value=${print(success.value)})`,
35
- onSome: (loading) =>
36
- `AsyncData.Success(timestamp=${success.timestamp}, refreshing=${LOADING_PRETTY(loading)}, value=${
37
- print(success.value)
38
- })`
33
+ onSome: () => `AsyncData.Success(timestamp=${success.timestamp}, refreshing=true, value=${print(success.value)})`
39
34
  })
40
35
 
41
- const NoDataSchemaJson = Schema.struct({
42
- _tag: Schema.literal("NoData")
43
- })
44
- const NoDataSchema = Schema.instanceOf(AsyncData.NoData)
36
+ const OPTIMISTIC_PRETTY =
37
+ <E, A>(printError: Pretty.Pretty<Cause.Cause<E>>, printValue: Pretty.Pretty<A>) =>
38
+ (optimistic: AsyncData.Optimistic<E, A>) =>
39
+ `AsyncData.Optimistic(timestamp=${optimistic.timestamp}, value=${printValue(optimistic.value)}, previous=${
40
+ asyncDataPretty(printError, printValue)(optimistic.previous)
41
+ })`
45
42
 
46
43
  /**
47
44
  * @since 1.0.0
48
45
  */
49
- export const NoData: Schema.Schema<{ readonly _tag: "NoData" }, AsyncData.NoData> = NoDataSchemaJson
50
- .pipe(
51
- Schema.transform(
52
- NoDataSchema,
53
- (): AsyncData.NoData => AsyncData.noData(),
54
- (): Schema.Schema.To<typeof NoDataSchemaJson> => ({
55
- _tag: "NoData"
56
- })
57
- ),
58
- Schema.annotations({
59
- [AST.IdentifierAnnotationId]: NO_DATA_PRETTY,
60
- [Pretty.PrettyHookId]: () => NO_DATA_PRETTY,
61
- [Arbitrary.ArbitraryHookId]: (): Arbitrary.Arbitrary<AsyncData.NoData> => (fc) => fc.constant(AsyncData.noData()),
62
- [Eq.EquivalenceHookId]: () => Equal.equals
63
- })
64
- )
65
-
66
- /**
67
- * @since 1.0.0
68
- */
69
- export type NoDataFrom = Schema.Schema.From<typeof NoData>
46
+ export type NoDataFrom = { readonly _tag: "NoData" }
70
47
 
71
48
  const ProgressSchemaJson = Schema.struct({
72
49
  loaded: Schema.bigint,
73
50
  total: Schema.optional(Schema.bigint)
74
51
  })
75
- type ProgressFromTo = Schema.Schema.To<typeof ProgressSchemaJson>
76
52
 
77
53
  const ProgressSchema: Schema.Schema<
78
54
  {
@@ -112,23 +88,10 @@ export const Progress: Schema.Schema<{ readonly loaded: string; readonly total?:
112
88
  /**
113
89
  * @since 1.0.0
114
90
  */
115
- export type ProgressFrom = Schema.Schema.From<typeof Progress>
116
-
117
- const LoadingSchemaJson = Schema.struct({
118
- _tag: Schema.literal("Loading"),
119
- timestamp: Schema.number,
120
- progress: Schema.optional(ProgressSchemaJson)
121
- })
122
- const LoadingSchema = Schema.instanceOf(AsyncData.Loading)
123
-
124
- const loadingFromJson = (json: Schema.Schema.To<typeof LoadingSchemaJson>): AsyncData.Loading =>
125
- AsyncData.loading({ timestamp: json.timestamp, progress: progressFromJson(json.progress) })
126
-
127
- const loadingToJson = (loading: AsyncData.Loading): Schema.Schema.To<typeof LoadingSchemaJson> => ({
128
- _tag: "Loading",
129
- timestamp: loading.timestamp,
130
- progress: progressToJson(loading.progress)
131
- })
91
+ export type ProgressFrom = {
92
+ readonly loaded: string
93
+ readonly total?: string | undefined
94
+ }
132
95
 
133
96
  const loadingArbitrary: Arbitrary.Arbitrary<AsyncData.Loading> = (fc) =>
134
97
  fc.option(progressArbitrary(fc)).map((progress) =>
@@ -141,70 +104,11 @@ const loadingArbitrary: Arbitrary.Arbitrary<AsyncData.Loading> = (fc) =>
141
104
  /**
142
105
  * @since 1.0.0
143
106
  */
144
- export const Loading: Schema.Schema<
145
- {
146
- readonly _tag: "Loading"
147
- readonly timestamp: number
148
- readonly progress?: ProgressFrom | undefined
149
- },
150
- AsyncData.Loading
151
- > = LoadingSchemaJson
152
- .pipe(
153
- Schema.transform(
154
- LoadingSchema,
155
- loadingFromJson,
156
- loadingToJson
157
- ),
158
- Schema.annotations({
159
- [AST.IdentifierAnnotationId]: "AsyncData.Loading",
160
- [Pretty.PrettyHookId]: () => LOADING_PRETTY,
161
- [Arbitrary.ArbitraryHookId]: () => loadingArbitrary,
162
- [Eq.EquivalenceHookId]: () => Equal.equals
163
- })
164
- )
165
-
166
- /**
167
- * @since 1.0.0
168
- */
169
- export type LoadingFrom = Schema.Schema.From<typeof Loading>
170
-
171
- const FailureSchemaJson = <EI, E>(cause: Schema.Schema<Schema.CauseFrom<EI>, Cause.Cause<E>>) =>
172
- Schema.struct({
173
- _tag: Schema.literal("Failure"),
174
- cause,
175
- timestamp: Schema.number,
176
- refreshing: Schema.optional(LoadingSchemaJson)
177
- })
178
-
179
- const FailureSchema = <EI, E>(
180
- error: Schema.Schema<EI, E>
181
- ): Schema.Schema<AsyncData.Failure<EI>, AsyncData.Failure<E>> =>
182
- Schema.declare(
183
- [Schema.cause(error)],
184
- Schema.struct({}),
185
- (isDecoding, causeSchema) => {
186
- const parseCause = isDecoding ? Schema.parse(causeSchema) : Schema.encode(causeSchema)
187
-
188
- return (input, options) => {
189
- return Effect.gen(function*(_) {
190
- if (!AsyncData.isAsyncData(input)) return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
191
-
192
- switch (input._tag) {
193
- case "Failure": {
194
- const cause = yield* _(parseCause(input.cause, options))
195
-
196
- return AsyncData.failCause(cause, {
197
- timestamp: input.timestamp,
198
- refreshing: Option.getOrUndefined(input.refreshing)
199
- })
200
- }
201
- default:
202
- return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
203
- }
204
- })
205
- }
206
- }
207
- )
107
+ export type LoadingFrom = {
108
+ readonly _tag: "Loading"
109
+ readonly timestamp: number
110
+ readonly progress?: ProgressFrom | undefined
111
+ }
208
112
 
209
113
  const failureArbitrary = <E>(
210
114
  cause: Arbitrary.Arbitrary<Cause.Cause<E>>
@@ -224,113 +128,26 @@ const failureArbitrary = <E>(
224
128
  /**
225
129
  * @since 1.0.0
226
130
  */
227
- export const Failure = <EI, E>(
228
- error: Schema.Schema<EI, E>
229
- ): Schema.Schema<
230
- {
231
- readonly _tag: "Failure"
232
- readonly cause: Schema.CauseFrom<EI>
233
- readonly timestamp: number
234
- readonly refreshing?: {
235
- readonly _tag: "Loading"
236
- readonly timestamp: number
237
- readonly progress?: { readonly loaded: string; readonly total?: string | undefined } | undefined
238
- } | undefined
239
- },
240
- AsyncData.Failure<E>
241
- > =>
242
- FailureSchemaJson(Schema.cause(Schema.from(error)))
243
- .pipe(
244
- Schema.transform(
245
- FailureSchema(error),
246
- (json): AsyncData.Failure<EI> =>
247
- AsyncData.failCause(json.cause, {
248
- timestamp: json.timestamp,
249
- refreshing: json.refreshing ? loadingFromJson(json.refreshing) : undefined
250
- }),
251
- (failure) => ({
252
- _tag: "Failure",
253
- cause: failure.cause,
254
- timestamp: failure.timestamp,
255
- refreshing: Option.getOrUndefined(Option.map(failure.refreshing, loadingToJson))
256
- } as const)
257
- ),
258
- Schema.annotations({
259
- [AST.IdentifierAnnotationId]: "AsyncData.Failure",
260
- [Pretty.PrettyHookId]: FAILURE_PRETTY,
261
- [Arbitrary.ArbitraryHookId]: failureArbitrary,
262
- [Eq.EquivalenceHookId]: () => Equal.equals
263
- })
264
- )
131
+ export type FailureFrom<E> = {
132
+ readonly _tag: "Failure"
133
+ readonly cause: Schema.CauseFrom<E>
134
+ readonly timestamp: number
135
+ readonly refreshing?: LoadingFrom | undefined
136
+ }
265
137
 
266
- /**
267
- * @since 1.0.0
268
- */
269
- export type FailureFrom<E> = Schema.Schema.From<ReturnType<typeof FailureSchemaJson<E, E>>>
138
+ const FailureFrom = <E>(cause: Schema.CauseFrom<E>, timestamp: number, refreshing?: LoadingFrom): FailureFrom<E> => {
139
+ const base = {
140
+ _tag: "Failure",
141
+ cause,
142
+ timestamp
143
+ } as const
270
144
 
271
- const SuccessSchemaJson = <AI, A>(
272
- value: Schema.Schema<AI, A>
273
- ): Schema.Schema<
274
- {
275
- readonly timestamp: number
276
- readonly _tag: "Success"
277
- readonly value: AI
278
- readonly refreshing?: {
279
- readonly timestamp: number
280
- readonly _tag: "Loading"
281
- readonly progress?: { readonly loaded: string; readonly total?: string | undefined } | undefined
282
- } | undefined
283
- },
284
- {
285
- readonly timestamp: number
286
- readonly _tag: "Success"
287
- readonly value: A
288
- readonly refreshing?: {
289
- readonly timestamp: number
290
- readonly _tag: "Loading"
291
- readonly progress?: { readonly loaded: bigint; readonly total?: bigint | undefined } | undefined
292
- } | undefined
145
+ if (refreshing !== undefined) {
146
+ return { ...base, refreshing }
293
147
  }
294
- > =>
295
- Schema.struct({
296
- _tag: Schema.literal("Success"),
297
- value,
298
- timestamp: Schema.number,
299
- refreshing: Schema.optional(LoadingSchemaJson)
300
- })
301
148
 
302
- const SuccessSchema = <AI, A>(
303
- value: Schema.Schema<AI, A>
304
- ): Schema.Schema<AsyncData.Success<AI>, AsyncData.Success<A>> =>
305
- Schema.declare(
306
- [value],
307
- Schema.struct({
308
- _tag: Schema.literal("Success"),
309
- timestamp: Schema.number
310
- }),
311
- (isDecoding, valueSchema) => {
312
- const parseValue = isDecoding ? Schema.parse(valueSchema) : Schema.encode(valueSchema)
313
-
314
- return (input, options) => {
315
- return Effect.gen(function*(_) {
316
- if (!AsyncData.isAsyncData(input)) return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
317
-
318
- switch (input._tag) {
319
- case "Success": {
320
- const value = yield* _(parseValue(input.value, options))
321
-
322
- return AsyncData.success(value, {
323
- timestamp: input.timestamp,
324
- refreshing: Option.getOrUndefined(input.refreshing)
325
- })
326
- }
327
- default:
328
- return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
329
- }
330
- })
331
- }
332
- }
333
- )
149
+ return base
150
+ }
334
151
 
335
152
  const successArbitrary = <A>(
336
153
  value: Arbitrary.Arbitrary<A>
@@ -350,64 +167,299 @@ const successArbitrary = <A>(
350
167
  /**
351
168
  * @since 1.0.0
352
169
  */
353
- export const Success = <AI, A>(
354
- value: Schema.Schema<AI, A>
355
- ): Schema.Schema<
356
- {
357
- readonly timestamp: number
358
- readonly _tag: "Success"
359
- readonly value: AI
360
- readonly refreshing?: LoadingFrom | undefined
361
- },
362
- AsyncData.Success<A>
363
- > =>
364
- SuccessSchemaJson(Schema.from(value))
365
- .pipe(
366
- Schema.transform(
367
- SuccessSchema(value),
368
- (json): AsyncData.Success<AI> =>
369
- AsyncData.success(json.value, {
370
- timestamp: json.timestamp,
371
- refreshing: json.refreshing ? loadingFromJson(json.refreshing) : undefined
372
- }),
373
- (success) => ({
374
- _tag: "Success",
375
- value: success.value,
376
- timestamp: success.timestamp,
377
- refreshing: Option.getOrUndefined(Option.map(success.refreshing, loadingToJson))
378
- } as const)
379
- ),
380
- Schema.annotations({
381
- [AST.IdentifierAnnotationId]: "AsyncData.Success",
382
- [Pretty.PrettyHookId]: SUCCESS_PRETTY,
383
- [Arbitrary.ArbitraryHookId]: successArbitrary,
384
- [Eq.EquivalenceHookId]: () => Equal.equals
385
- })
386
- )
170
+ export type SuccessFrom<A> = {
171
+ readonly timestamp: number
172
+ readonly _tag: "Success"
173
+ readonly value: A
174
+ readonly refreshing?: LoadingFrom | undefined
175
+ }
176
+
177
+ const SuccessFrom = <A>(value: A, timestamp: number, refreshing?: LoadingFrom): SuccessFrom<A> => {
178
+ const base = {
179
+ _tag: "Success",
180
+ value,
181
+ timestamp
182
+ } as const
183
+
184
+ if (refreshing !== undefined) {
185
+ return { ...base, refreshing }
186
+ }
187
+
188
+ return base
189
+ }
387
190
 
388
191
  /**
389
192
  * @since 1.0.0
390
193
  */
391
- export type SuccessFrom<A> = Schema.Schema.From<ReturnType<typeof SuccessSchemaJson<A, A>>>
194
+ export type OptimisticFrom<E, A> = {
195
+ readonly timestamp: number
196
+ readonly _tag: "Optimistic"
197
+ readonly value: A
198
+ readonly previous: AsyncDataFrom<E, A>
199
+ }
200
+
201
+ const OptimisticFrom = <E, A>(value: A, timestamp: number, previous: AsyncDataFrom<E, A>): OptimisticFrom<E, A> => ({
202
+ _tag: "Optimistic",
203
+ value,
204
+ timestamp,
205
+ previous
206
+ })
207
+
208
+ const optimisticArbitrary = <E, A>(
209
+ causeArb: Arbitrary.Arbitrary<Cause.Cause<E>>,
210
+ valueArb: Arbitrary.Arbitrary<A>
211
+ ): Arbitrary.Arbitrary<AsyncData.Optimistic<E, A>> =>
212
+ (fc) =>
213
+ asyncDataArbitrary(causeArb, valueArb)(fc).chain((previous) =>
214
+ valueArb(fc).chain((value) =>
215
+ fc.date().map((date) => AsyncData.optimistic(previous, value, { timestamp: date.getTime() }))
216
+ )
217
+ )
392
218
 
393
219
  /**
394
220
  * @since 1.0.0
395
221
  */
396
- export type AsyncDataFrom<E, A> = NoDataFrom | LoadingFrom | FailureFrom<E> | SuccessFrom<A>
222
+ export type AsyncDataFrom<E, A> = NoDataFrom | LoadingFrom | FailureFrom<E> | SuccessFrom<A> | OptimisticFrom<E, A>
223
+
224
+ const fromEq = (a: AsyncDataFrom<any, any>, b: AsyncDataFrom<any, any>): boolean => {
225
+ if (a._tag !== b._tag) return false
226
+
227
+ switch (a._tag) {
228
+ case "NoData":
229
+ return true
230
+ case "Loading": {
231
+ const loadingB = b as LoadingFrom
232
+
233
+ if (a.timestamp !== loadingB.timestamp) return false
234
+
235
+ if (a.progress === undefined && loadingB.progress === undefined) return true
236
+ if (a.progress === undefined || loadingB.progress === undefined) return false
237
+
238
+ return Equal.equals(Data.struct(a.progress), Data.struct(loadingB.progress))
239
+ }
240
+ case "Failure": {
241
+ const failureB = b as FailureFrom<any>
242
+
243
+ if (
244
+ !(
245
+ Equal.equals(Data.struct(a.cause), Data.struct(failureB.cause)) && a.timestamp === failureB.timestamp
246
+ )
247
+ ) return false
248
+
249
+ if (a.refreshing === undefined && failureB.refreshing === undefined) return true
250
+ if (a.refreshing === undefined || failureB.refreshing === undefined) return false
251
+
252
+ return Equal.equals(Data.struct(a.refreshing), Data.struct(failureB.refreshing))
253
+ }
254
+ case "Success": {
255
+ const successB = b as SuccessFrom<any>
256
+ return Equal.equals(a.value, successB.value) &&
257
+ a.timestamp === successB.timestamp &&
258
+ Equal.equals(a.refreshing, successB.refreshing)
259
+ }
260
+ case "Optimistic": {
261
+ const optimisticB = b as OptimisticFrom<any, any>
262
+ return Equal.equals(a.value, optimisticB.value) &&
263
+ a.timestamp === optimisticB.timestamp &&
264
+ fromEq(a.previous, optimisticB.previous)
265
+ }
266
+ }
267
+ }
268
+
269
+ function isNoDataFrom(value: unknown): value is NoDataFrom {
270
+ return hasProperty(value, "_tag") && value._tag === "NoData"
271
+ }
272
+
273
+ function isProgressFrom(value: unknown): value is ProgressFrom {
274
+ if (!(hasProperty(value, "loaded") && typeof value.loaded === "string")) return false
275
+
276
+ if (hasProperty(value, "total")) {
277
+ if (typeof value.total !== "string") return false
278
+ }
279
+
280
+ return true
281
+ }
282
+
283
+ function isLoadingFrom(value: unknown): value is LoadingFrom {
284
+ return hasProperty(value, "_tag")
285
+ && value._tag === "Loading"
286
+ && hasProperty(value, "timestamp")
287
+ && typeof value.timestamp === "number"
288
+ && (hasProperty(value, "progress") ? isProgressFrom(value.progress) : true)
289
+ }
290
+
291
+ const isCauseFrom = Schema.is(Schema.from(Schema.cause(Schema.unknown)))
292
+
293
+ function isFailureFrom(value: unknown): value is FailureFrom<any> {
294
+ return hasProperty(value, "_tag")
295
+ && value._tag === "Failure"
296
+ && hasProperty(value, "cause")
297
+ && isCauseFrom(value.cause)
298
+ && hasProperty(value, "timestamp")
299
+ && typeof value.timestamp === "number"
300
+ && (hasProperty(value, "refreshing") ? isLoadingFrom(value.refreshing) : true)
301
+ }
302
+
303
+ function isSuccessFrom(value: unknown): value is SuccessFrom<any> {
304
+ return hasProperty(value, "_tag")
305
+ && value._tag === "Success"
306
+ && hasProperty(value, "value")
307
+ && hasProperty(value, "timestamp")
308
+ && typeof value.timestamp === "number"
309
+ && (hasProperty(value, "refreshing") ? isLoadingFrom(value.refreshing) : true)
310
+ }
311
+
312
+ function isOptimisticFrom(value: unknown): value is OptimisticFrom<any, any> {
313
+ return hasProperty(value, "_tag")
314
+ && value._tag === "Optimistic"
315
+ && hasProperty(value, "value")
316
+ && hasProperty(value, "previous")
317
+ && isAsyncDataFrom(value.previous)
318
+ && hasProperty(value, "timestamp")
319
+ && typeof value.timestamp === "number"
320
+ }
321
+
322
+ function isAsyncDataFrom(value: unknown): value is AsyncDataFrom<any, any> {
323
+ return isNoDataFrom(value)
324
+ || isLoadingFrom(value)
325
+ || isFailureFrom(value)
326
+ || isSuccessFrom(value)
327
+ || isOptimisticFrom(value)
328
+ }
397
329
 
398
330
  /**
399
331
  * @since 1.0.0
400
332
  */
401
- export const asyncData = <EI, E, AI, A>(
333
+ export const asyncDataFromJson = <EI, E, AI, A>(
402
334
  error: Schema.Schema<EI, E>,
403
335
  value: Schema.Schema<AI, A>
404
- ): Schema.Schema<AsyncDataFrom<EI, AI>, AsyncData.AsyncData<E, A>> => {
405
- return Schema.union(
406
- NoData,
407
- Loading,
408
- Failure(error),
409
- Success(value)
336
+ ): Schema.Schema<AsyncDataFrom<EI, AI>, AsyncDataFrom<E, A>> =>
337
+ Schema.declare(
338
+ [
339
+ Schema.cause(error),
340
+ value
341
+ ],
342
+ Schema.annotations({
343
+ [Eq.EquivalenceHookId]: () => fromEq
344
+ })(Schema.struct({})),
345
+ (isDecoding, causeSchema, valueSchema) => {
346
+ const parseCause = isDecoding ? Schema.decode(causeSchema) : Schema.encode(causeSchema)
347
+ const parseValue = isDecoding ? Schema.decode(valueSchema) : Schema.encode(valueSchema)
348
+
349
+ const parseAsyncData = (
350
+ input: any,
351
+ options?: AST.ParseOptions
352
+ ): Effect.Effect<
353
+ never,
354
+ ParseResult.ParseError,
355
+ AsyncDataFrom<E, A>
356
+ > =>
357
+ Effect.gen(function*(_) {
358
+ if (!isAsyncDataFrom(input)) {
359
+ return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
360
+ }
361
+
362
+ switch (input._tag) {
363
+ case "NoData":
364
+ case "Loading":
365
+ return input
366
+ case "Failure": {
367
+ const cause = yield* _(parseCause(isDecoding ? input.cause : causeFromToCause(input.cause), options))
368
+ return FailureFrom(causeToCauseFrom(cause), input.timestamp, input.refreshing)
369
+ }
370
+ case "Success": {
371
+ const a = yield* _(parseValue(input.value, options))
372
+ return SuccessFrom(a, input.timestamp, input.refreshing)
373
+ }
374
+ case "Optimistic": {
375
+ const previous: AsyncDataFrom<any, any> = yield* _(parseAsyncData(input.previous, options))
376
+ const value = yield* _(parseValue(input.value, options))
377
+
378
+ return OptimisticFrom(value, input.timestamp, previous)
379
+ }
380
+ }
381
+ })
382
+
383
+ return parseAsyncData
384
+ },
385
+ {
386
+ [AST.IdentifierAnnotationId]: "AsyncDataFrom",
387
+ [Eq.EquivalenceHookId]: () => fromEq
388
+ }
410
389
  )
390
+
391
+ /**
392
+ * @since 1.0.0
393
+ */
394
+ export const asyncData = <EI, E, AI, A>(
395
+ errorSchema: Schema.Schema<EI, E>,
396
+ valueSchema: Schema.Schema<AI, A>
397
+ ): Schema.Schema<AsyncDataFrom<EI, AI>, AsyncData.AsyncData<E, A>> => {
398
+ const encodeCause = Schema.encode(Schema.cause(Schema.to(errorSchema)))
399
+
400
+ return asyncDataFromJson(errorSchema, valueSchema)
401
+ .pipe(Schema.transformOrFail(
402
+ asyncDataFromSelf(Schema.to(errorSchema), Schema.to(valueSchema)),
403
+ function decodeAsyncDataFrom(
404
+ c: AsyncDataFrom<E, A>,
405
+ options?: AST.ParseOptions
406
+ ): Effect.Effect<never, ParseResult.ParseError, AsyncData.AsyncData<E, A>> {
407
+ switch (c._tag) {
408
+ case "NoData":
409
+ return Effect.succeed(AsyncData.noData())
410
+ case "Loading":
411
+ return Effect.succeed(loadingFromJson(c)!)
412
+ case "Failure": {
413
+ console.log(causeFromToCause(c.cause))
414
+
415
+ return Effect.succeed(
416
+ AsyncData.failCause(causeFromToCause(c.cause), {
417
+ timestamp: c.timestamp,
418
+ refreshing: loadingFromJson(c.refreshing)
419
+ })
420
+ )
421
+ }
422
+ case "Success": {
423
+ return Effect.succeed(AsyncData.success(c.value, {
424
+ timestamp: c.timestamp,
425
+ refreshing: loadingFromJson(c.refreshing)
426
+ }))
427
+ }
428
+ case "Optimistic": {
429
+ return Effect.map(
430
+ decodeAsyncDataFrom(c.previous, options),
431
+ (previous) => AsyncData.optimistic(previous, c.value, { timestamp: c.timestamp })
432
+ )
433
+ }
434
+ }
435
+ },
436
+ function encodeAsyncDataFrom(
437
+ a: AsyncData.AsyncData<E, A>,
438
+ options?: AST.ParseOptions
439
+ ): Effect.Effect<never, ParseResult.ParseError, AsyncDataFrom<E, A>> {
440
+ switch (a._tag) {
441
+ case "NoData":
442
+ return Effect.succeed({ _tag: "NoData" })
443
+ case "Loading":
444
+ return Effect.succeed(loadingToJson(a))
445
+ case "Failure":
446
+ return Effect.map(
447
+ encodeCause(a.cause, options),
448
+ (cause) => FailureFrom(cause, a.timestamp, Option.getOrUndefined(Option.map(a.refreshing, loadingToJson)))
449
+ )
450
+ case "Success":
451
+ return Effect.succeed(
452
+ SuccessFrom(a.value, a.timestamp, Option.getOrUndefined(Option.map(a.refreshing, loadingToJson)))
453
+ )
454
+ case "Optimistic": {
455
+ return Effect.map(
456
+ encodeAsyncDataFrom(a.previous, options),
457
+ (previous) => OptimisticFrom(a.value, a.timestamp, previous)
458
+ )
459
+ }
460
+ }
461
+ }
462
+ ))
411
463
  }
412
464
 
413
465
  /**
@@ -425,10 +477,17 @@ export const asyncDataFromSelf = <EI, E, AI, A>(
425
477
  Schema.Schema<Cause.Cause<any>, Cause.Cause<any>>,
426
478
  Schema.Schema<any, any>
427
479
  ]
428
- const parseCause = isDecoding ? Schema.parse(causeSchema) : Schema.encode(causeSchema)
429
- const parseValue = isDecoding ? Schema.parse(valueSchema) : Schema.encode(valueSchema)
430
-
431
- return (input, options) => {
480
+ const parseCause = isDecoding ? Schema.decode(causeSchema) : Schema.encode(causeSchema)
481
+ const parseValue = isDecoding ? Schema.decode(valueSchema) : Schema.encode(valueSchema)
482
+
483
+ const parseAsyncData = (
484
+ input: unknown,
485
+ options?: AST.ParseOptions
486
+ ): Effect.Effect<
487
+ never,
488
+ ParseResult.ParseError,
489
+ AsyncData.AsyncData<any, any>
490
+ > => {
432
491
  return Effect.gen(function*(_) {
433
492
  if (!AsyncData.isAsyncData(input)) return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
434
493
 
@@ -439,7 +498,7 @@ export const asyncDataFromSelf = <EI, E, AI, A>(
439
498
  case "Failure": {
440
499
  const cause = yield* _(parseCause(input.cause, options))
441
500
 
442
- return AsyncData.failCause(cause, {
501
+ return AsyncData.failCause(isDecoding ? cause : causeFromToCause(cause), {
443
502
  timestamp: input.timestamp,
444
503
  refreshing: Option.getOrUndefined(input.refreshing)
445
504
  })
@@ -452,9 +511,19 @@ export const asyncDataFromSelf = <EI, E, AI, A>(
452
511
  refreshing: Option.getOrUndefined(input.refreshing)
453
512
  })
454
513
  }
514
+ case "Optimistic": {
515
+ const previous = yield* _(parseAsyncData(input.previous, options))
516
+ const value = yield* _(parseValue(input.value, options))
517
+
518
+ return AsyncData.optimistic(previous, value, {
519
+ timestamp: input.timestamp
520
+ })
521
+ }
455
522
  }
456
523
  })
457
524
  }
525
+
526
+ return parseAsyncData
458
527
  },
459
528
  {
460
529
  [AST.IdentifierAnnotationId]: "AsyncData",
@@ -473,7 +542,8 @@ function asyncDataPretty<E, A>(
473
542
  NoData: () => NO_DATA_PRETTY,
474
543
  Loading: LOADING_PRETTY,
475
544
  Failure: (_, data) => FAILURE_PRETTY(E)(data),
476
- Success: (_, data) => SUCCESS_PRETTY(A)(data)
545
+ Success: (_, data) => SUCCESS_PRETTY(A)(data),
546
+ Optimistic: (_, data) => OPTIMISTIC_PRETTY(E, A)(data)
477
547
  })
478
548
  }
479
549
 
@@ -481,29 +551,106 @@ function asyncDataArbitrary<E, A>(
481
551
  E: Arbitrary.Arbitrary<Cause.Cause<E>>,
482
552
  A: Arbitrary.Arbitrary<A>
483
553
  ): Arbitrary.Arbitrary<AsyncData.AsyncData<E, A>> {
484
- const noDataArb = Arbitrary.make(NoData)
485
- const loadingArb = Arbitrary.make(Loading)
486
554
  const failureArb = failureArbitrary(E)
487
555
  const successArb = successArbitrary(A)
556
+ const optimisticArb = optimisticArbitrary(E, A)
488
557
 
489
558
  return (fc) =>
490
559
  fc.oneof(
491
- noDataArb(fc),
492
- loadingArb(fc),
560
+ fc.constant(AsyncData.noData()),
561
+ fc.constant(AsyncData.loading()),
493
562
  failureArb(fc),
494
- successArb(fc)
563
+ successArb(fc),
564
+ optimisticArb(fc)
495
565
  )
496
566
  }
497
567
 
498
- function progressFromJson(json: ProgressFromTo | undefined): P.Progress | undefined {
499
- if (json === undefined) return
500
- return P.make(json.loaded, json.total)
568
+ function progressFromJson(json: ProgressFrom | undefined): Option.Option<P.Progress> {
569
+ if (json === undefined) return Option.none()
570
+ return Option.some(P.make(BigInt(json.loaded), json.total === undefined ? undefined : BigInt(json.total)))
501
571
  }
502
572
 
503
- function progressToJson(progres: Option.Option<P.Progress>): ProgressFromTo | undefined {
573
+ function progressToJson(progres: Option.Option<P.Progress>): ProgressFrom | undefined {
504
574
  if (Option.isNone(progres)) return
505
575
  return {
506
- loaded: progres.value.loaded,
507
- total: Option.getOrUndefined(progres.value.total)
576
+ loaded: progres.value.loaded.toString(),
577
+ total: Option.getOrUndefined(progres.value.total)?.toString()
578
+ }
579
+ }
580
+
581
+ function loadingFromJson(json: LoadingFrom | undefined): AsyncData.Loading | undefined {
582
+ if (json === undefined) return
583
+ return AsyncData.loading({
584
+ timestamp: json.timestamp,
585
+ progress: Option.getOrUndefined(progressFromJson(json.progress))
586
+ })
587
+ }
588
+
589
+ function loadingToJson(loading: AsyncData.Loading): LoadingFrom {
590
+ const from: LoadingFrom = {
591
+ _tag: "Loading",
592
+ timestamp: loading.timestamp
593
+ }
594
+
595
+ if (Option.isSome(loading.progress)) {
596
+ return { ...from, progress: progressToJson(loading.progress) }
597
+ }
598
+
599
+ return from
600
+ }
601
+
602
+ function causeFromToCause<E>(from: Schema.CauseFrom<E>): Cause.Cause<E> {
603
+ switch (from._tag) {
604
+ case "Die":
605
+ return Cause.die(from.defect)
606
+ case "Empty":
607
+ return Cause.empty
608
+ case "Fail":
609
+ return Cause.fail(from.error)
610
+ case "Interrupt":
611
+ return Cause.interrupt(fiberIdFromToFiberId(from.fiberId))
612
+ case "Parallel":
613
+ return Cause.parallel(causeFromToCause(from.left), causeFromToCause(from.right))
614
+ case "Sequential":
615
+ return Cause.sequential(causeFromToCause(from.left), causeFromToCause(from.right))
616
+ }
617
+ }
618
+
619
+ function fiberIdFromToFiberId(id: Schema.FiberIdFrom): FiberId.FiberId {
620
+ switch (id._tag) {
621
+ case "None":
622
+ return FiberId.none
623
+ case "Runtime":
624
+ return FiberId.runtime(id.id, id.startTimeMillis)
625
+ case "Composite":
626
+ return FiberId.composite(fiberIdFromToFiberId(id.left), fiberIdFromToFiberId(id.right))
627
+ }
628
+ }
629
+
630
+ function causeToCauseFrom<E>(cause: Cause.Cause<E>): Schema.CauseFrom<E> {
631
+ switch (cause._tag) {
632
+ case "Die":
633
+ return { _tag: "Die", defect: cause.defect }
634
+ case "Empty":
635
+ return { _tag: "Empty" }
636
+ case "Fail":
637
+ return { _tag: "Fail", error: cause.error }
638
+ case "Interrupt":
639
+ return { _tag: "Interrupt", fiberId: fiberIdToFiberIdFrom(cause.fiberId) }
640
+ case "Parallel":
641
+ return { _tag: "Parallel", left: causeToCauseFrom(cause.left), right: causeToCauseFrom(cause.right) }
642
+ case "Sequential":
643
+ return { _tag: "Sequential", left: causeToCauseFrom(cause.left), right: causeToCauseFrom(cause.right) }
644
+ }
645
+ }
646
+
647
+ function fiberIdToFiberIdFrom(id: FiberId.FiberId): Schema.FiberIdFrom {
648
+ switch (id._tag) {
649
+ case "None":
650
+ return { _tag: "None" }
651
+ case "Runtime":
652
+ return { _tag: "Runtime", id: id.id, startTimeMillis: id.startTimeMillis }
653
+ case "Composite":
654
+ return { _tag: "Composite", left: fiberIdToFiberIdFrom(id.left), right: fiberIdToFiberIdFrom(id.right) }
508
655
  }
509
656
  }