@typed/async-data 0.2.0 → 0.3.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.
package/src/Schema.ts CHANGED
@@ -4,136 +4,433 @@
4
4
 
5
5
  import * as Arbitrary from "@effect/schema/Arbitrary"
6
6
  import * as AST from "@effect/schema/AST"
7
+ import * as Eq from "@effect/schema/Equivalence"
7
8
  import * as ParseResult from "@effect/schema/ParseResult"
8
9
  import * as Pretty from "@effect/schema/Pretty"
9
10
  import * as Schema from "@effect/schema/Schema"
10
11
  import * as AsyncData from "@typed/async-data/AsyncData"
11
- import { Cause, Chunk, Effect, FiberId, HashSet } from "effect"
12
+ import { Equal } from "effect"
13
+ import type * as Cause from "effect/Cause"
14
+ import * as Effect from "effect/Effect"
12
15
  import * as Option from "effect/Option"
16
+ import * as P from "./Progress.js"
13
17
 
14
- const fiberIdArbitrary: Arbitrary.Arbitrary<FiberId.FiberId> = (fc) =>
15
- fc.oneof(
16
- fc.constant(FiberId.none),
17
- fc.integer().chain((i) => fc.date().map((date) => FiberId.make(i, date.getTime() / 1000)))
18
+ const NO_DATA_PRETTY = "AsyncData.NoData"
19
+ const LOADING_PRETTY = (loading: AsyncData.Loading) =>
20
+ Option.match(loading.progress, {
21
+ onNone: () => `AsyncData.Loading(timestamp=${loading.timestamp})`,
22
+ onSome: (progress) => `AsyncData.Loading(timestamp=${loading.timestamp}, progress=${P.pretty(progress)})`
23
+ })
24
+ const FAILURE_PRETTY = <E>(print: Pretty.Pretty<Cause.Cause<E>>) => (failure: AsyncData.Failure<E>) =>
25
+ Option.match(failure.refreshing, {
26
+ 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
+ })`
31
+ })
32
+ const SUCCESS_PRETTY = <A>(print: Pretty.Pretty<A>) => (success: AsyncData.Success<A>) =>
33
+ Option.match(success.refreshing, {
34
+ 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
+ })`
39
+ })
40
+
41
+ const NoDataSchemaJson = Schema.struct({
42
+ _tag: Schema.literal("NoData")
43
+ })
44
+ const NoDataSchema = Schema.instanceOf(AsyncData.NoData)
45
+
46
+ /**
47
+ * @since 1.0.0
48
+ */
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
+ })
18
64
  )
19
65
 
20
- const causeFromItems = <A>(
21
- items: Array<A>,
22
- join: (first: Cause.Cause<A>, second: Cause.Cause<A>) => Cause.Cause<A>
23
- ) => {
24
- if (items.length === 0) return Cause.empty
25
- if (items.length === 1) return Cause.fail(items[0])
26
- return items.map(Cause.fail).reduce(join)
27
- }
66
+ /**
67
+ * @since 1.0.0
68
+ */
69
+ export type NoDataFrom = Schema.Schema.From<typeof NoData>
28
70
 
29
- const causeArbitrary = <A>(item: Arbitrary.Arbitrary<A>): Arbitrary.Arbitrary<Cause.Cause<A>> => (fc) =>
30
- fc.oneof(
31
- fc.constant(Cause.empty),
32
- fc.anything().map(Cause.die),
33
- fiberIdArbitrary(fc).map((id) => Cause.interrupt(id)),
34
- fc.array(item(fc)).chain((items) =>
35
- fc.integer({ min: 0, max: 1 }).map((i) => causeFromItems(items, i > 0.5 ? Cause.sequential : Cause.parallel))
36
- )
71
+ const ProgressSchemaJson = Schema.struct({
72
+ loaded: Schema.bigint,
73
+ total: Schema.optional(Schema.bigint)
74
+ })
75
+ type ProgressFromTo = Schema.Schema.To<typeof ProgressSchemaJson>
76
+
77
+ const ProgressSchema: Schema.Schema<
78
+ {
79
+ readonly loaded: bigint
80
+ readonly total: Option.Option<bigint>
81
+ },
82
+ P.Progress
83
+ > = Schema.data(Schema.struct({
84
+ loaded: Schema.bigintFromSelf,
85
+ total: Schema.optionFromSelf(Schema.bigintFromSelf)
86
+ }))
87
+
88
+ const progressArbitrary: Arbitrary.Arbitrary<P.Progress> = (fc) =>
89
+ fc.bigInt().chain((loaded) => fc.option(fc.bigInt({ min: loaded })).map((total) => P.make(loaded, total)))
90
+
91
+ /**
92
+ * @since 1.0.0
93
+ */
94
+ export const Progress: Schema.Schema<{ readonly loaded: string; readonly total?: string | undefined }, P.Progress> =
95
+ ProgressSchemaJson.pipe(
96
+ Schema.transform(
97
+ ProgressSchema,
98
+ (json): P.Progress => P.make(json.loaded, json.total),
99
+ (progress) => ({
100
+ loaded: progress.loaded,
101
+ total: Option.getOrUndefined(progress.total)
102
+ })
103
+ ),
104
+ Schema.annotations({
105
+ [AST.IdentifierAnnotationId]: "Progress",
106
+ [Pretty.PrettyHookId]: () => "Progress",
107
+ [Arbitrary.ArbitraryHookId]: (): Arbitrary.Arbitrary<P.Progress> => progressArbitrary,
108
+ [Eq.EquivalenceHookId]: () => Equal.equals
109
+ })
110
+ )
111
+
112
+ /**
113
+ * @since 1.0.0
114
+ */
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
+ })
132
+
133
+ const loadingArbitrary: Arbitrary.Arbitrary<AsyncData.Loading> = (fc) =>
134
+ fc.option(progressArbitrary(fc)).map((progress) =>
135
+ AsyncData.loading({
136
+ timestamp: Date.now(),
137
+ progress: progress || undefined
138
+ })
37
139
  )
38
140
 
39
- const causePretty = <A>(): Pretty.Pretty<Cause.Cause<A>> => Cause.pretty
141
+ /**
142
+ * @since 1.0.0
143
+ */
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
+ )
40
165
 
41
166
  /**
42
167
  * @since 1.0.0
43
168
  */
44
- export const cause = <EI, E>(error: Schema.Schema<EI, E>): Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>> => {
45
- const parseE = Schema.parse(Schema.chunkFromSelf(error))
46
-
47
- const self: Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>> = Schema.suspend(() =>
48
- Schema.declare(
49
- [error],
50
- Schema.struct({}),
51
- () => (input, options) =>
52
- Effect.gen(function*(_) {
53
- if (!Cause.isCause(input)) return yield* _(ParseResult.fail(ParseResult.unexpected(input)))
54
-
55
- let output: Cause.Cause<E> = Cause.empty
56
- for (const cause of Cause.linearize<E>(input)) {
57
- const parrallelCauses = Cause.linearize(cause)
58
-
59
- if (HashSet.size(parrallelCauses) === 1) {
60
- const failures = Cause.failures(cause)
61
-
62
- output = Cause.parallel(
63
- output,
64
- Chunk.isEmpty(failures) ? cause : Chunk.reduce(
65
- yield* _(parseE(failures, options)),
66
- Cause.empty as Cause.Cause<E>,
67
- (cause, e) => Cause.sequential(cause, Cause.fail(e))
68
- )
69
- )
70
- } else {
71
- output = Cause.parallel(
72
- output,
73
- yield* _(Schema.parse(self)(cause, options))
74
- )
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
+ })
75
200
  }
201
+ default:
202
+ return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
76
203
  }
77
-
78
- return output
79
- }),
80
- {
81
- [AST.IdentifierAnnotationId]: "Cause",
82
- [Arbitrary.ArbitraryHookId]: causePretty,
83
- [Pretty.PrettyHookId]: causeArbitrary
204
+ })
84
205
  }
206
+ }
207
+ )
208
+
209
+ const failureArbitrary = <E>(
210
+ cause: Arbitrary.Arbitrary<Cause.Cause<E>>
211
+ ): Arbitrary.Arbitrary<AsyncData.Failure<E>> =>
212
+ (fc) =>
213
+ fc.option(loadingArbitrary(fc)).chain((refreshing) =>
214
+ cause(fc).chain((cause) =>
215
+ fc.date().map((date) =>
216
+ AsyncData.failCause(cause, {
217
+ timestamp: date.getTime(),
218
+ refreshing: refreshing || undefined
219
+ })
220
+ )
85
221
  )
86
222
  )
87
223
 
88
- return self
89
- }
224
+ /**
225
+ * @since 1.0.0
226
+ */
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
+ )
265
+
266
+ /**
267
+ * @since 1.0.0
268
+ */
269
+ export type FailureFrom<E> = Schema.Schema.From<ReturnType<typeof FailureSchemaJson<E, E>>>
90
270
 
91
- const asyncDataPretty = <E, A>(
92
- prettyCause: Pretty.Pretty<Cause.Cause<E>>,
93
- prettyValue: Pretty.Pretty<A>
94
- ): Pretty.Pretty<AsyncData.AsyncData<E, A>> =>
95
- AsyncData.match({
96
- NoData: () => `AsyncData.NoData`,
97
- Loading: () => `AsyncData.Loading`,
98
- Failure: (cause, data) =>
99
- `AsyncData.Failure(refreshing=${Option.isSome(data.refreshing)}, cause=${prettyCause(cause)})`,
100
- Success: (value, data) =>
101
- `AsyncData.Success(refreshing=${Option.isSome(data.refreshing)}, value=${prettyValue(value)})`
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
293
+ }
294
+ > =>
295
+ Schema.struct({
296
+ _tag: Schema.literal("Success"),
297
+ value,
298
+ timestamp: Schema.number,
299
+ refreshing: Schema.optional(LoadingSchemaJson)
102
300
  })
103
301
 
104
- const asyncDataArbitrary = <E, A>(
105
- causeArbitrary: Arbitrary.Arbitrary<Cause.Cause<E>>,
106
- valueArbitrary: Arbitrary.Arbitrary<A>
107
- ): Arbitrary.Arbitrary<AsyncData.AsyncData<E, A>> =>
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
+ )
334
+
335
+ const successArbitrary = <A>(
336
+ value: Arbitrary.Arbitrary<A>
337
+ ): Arbitrary.Arbitrary<AsyncData.Success<A>> =>
108
338
  (fc) =>
109
- fc.oneof(
110
- fc.constant(AsyncData.noData()),
111
- fc.constant(AsyncData.loading()),
112
- causeArbitrary(fc).map((cause) => AsyncData.failCause(cause)),
113
- valueArbitrary(fc).map((a) => AsyncData.success(a))
339
+ fc.option(loadingArbitrary(fc)).chain((refreshing) =>
340
+ value(fc).chain((a) =>
341
+ fc.date().map((date) =>
342
+ AsyncData.success(a, {
343
+ timestamp: date.getTime(),
344
+ refreshing: refreshing || undefined
345
+ })
346
+ )
347
+ )
114
348
  )
115
349
 
350
+ /**
351
+ * @since 1.0.0
352
+ */
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
+ )
387
+
388
+ /**
389
+ * @since 1.0.0
390
+ */
391
+ export type SuccessFrom<A> = Schema.Schema.From<ReturnType<typeof SuccessSchemaJson<A, A>>>
392
+
393
+ /**
394
+ * @since 1.0.0
395
+ */
396
+ export type AsyncDataFrom<E, A> = NoDataFrom | LoadingFrom | FailureFrom<E> | SuccessFrom<A>
397
+
116
398
  /**
117
399
  * @since 1.0.0
118
400
  */
119
401
  export const asyncData = <EI, E, AI, A>(
120
402
  error: Schema.Schema<EI, E>,
121
403
  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)
410
+ )
411
+ }
412
+
413
+ /**
414
+ * @since 1.0.0
415
+ */
416
+ export const asyncDataFromSelf = <EI, E, AI, A>(
417
+ error: Schema.Schema<EI, E>,
418
+ value: Schema.Schema<AI, A>
122
419
  ): Schema.Schema<AsyncData.AsyncData<EI, AI>, AsyncData.AsyncData<E, A>> => {
123
420
  return Schema.declare(
124
- [cause(error), value],
421
+ [Schema.cause(error), value],
125
422
  Schema.struct({}),
126
- (_, ...params) => {
423
+ (isDecoding, ...params) => {
127
424
  const [causeSchema, valueSchema] = params as readonly [
128
- Schema.Schema<Cause.Cause<EI>, Cause.Cause<E>>,
129
- Schema.Schema<AI, A>
425
+ Schema.Schema<Cause.Cause<any>, Cause.Cause<any>>,
426
+ Schema.Schema<any, any>
130
427
  ]
131
- const parseCause = Schema.parse(causeSchema)
132
- const parseValue = Schema.parse(valueSchema)
428
+ const parseCause = isDecoding ? Schema.parse(causeSchema) : Schema.encode(causeSchema)
429
+ const parseValue = isDecoding ? Schema.parse(valueSchema) : Schema.encode(valueSchema)
133
430
 
134
431
  return (input, options) => {
135
432
  return Effect.gen(function*(_) {
136
- if (!AsyncData.isAsyncData<EI, AI>(input)) return yield* _(ParseResult.fail(ParseResult.unexpected(input)))
433
+ if (!AsyncData.isAsyncData(input)) return yield* _(ParseResult.fail(ParseResult.forbidden(input)))
137
434
 
138
435
  switch (input._tag) {
139
436
  case "NoData":
@@ -143,6 +440,7 @@ export const asyncData = <EI, E, AI, A>(
143
440
  const cause = yield* _(parseCause(input.cause, options))
144
441
 
145
442
  return AsyncData.failCause(cause, {
443
+ timestamp: input.timestamp,
146
444
  refreshing: Option.getOrUndefined(input.refreshing)
147
445
  })
148
446
  }
@@ -150,6 +448,7 @@ export const asyncData = <EI, E, AI, A>(
150
448
  const a = yield* _(parseValue(input.value, options))
151
449
 
152
450
  return AsyncData.success(a, {
451
+ timestamp: input.timestamp,
153
452
  refreshing: Option.getOrUndefined(input.refreshing)
154
453
  })
155
454
  }
@@ -159,8 +458,52 @@ export const asyncData = <EI, E, AI, A>(
159
458
  },
160
459
  {
161
460
  [AST.IdentifierAnnotationId]: "AsyncData",
162
- [Arbitrary.ArbitraryHookId]: asyncDataPretty,
163
- [Pretty.PrettyHookId]: asyncDataArbitrary
461
+ [Pretty.PrettyHookId]: asyncDataPretty,
462
+ [Arbitrary.ArbitraryHookId]: asyncDataArbitrary,
463
+ [Eq.EquivalenceHookId]: () => Equal.equals
164
464
  }
165
465
  )
166
466
  }
467
+
468
+ function asyncDataPretty<E, A>(
469
+ E: Pretty.Pretty<Cause.Cause<E>>,
470
+ A: Pretty.Pretty<A>
471
+ ): Pretty.Pretty<AsyncData.AsyncData<E, A>> {
472
+ return AsyncData.match({
473
+ NoData: () => NO_DATA_PRETTY,
474
+ Loading: LOADING_PRETTY,
475
+ Failure: (_, data) => FAILURE_PRETTY(E)(data),
476
+ Success: (_, data) => SUCCESS_PRETTY(A)(data)
477
+ })
478
+ }
479
+
480
+ function asyncDataArbitrary<E, A>(
481
+ E: Arbitrary.Arbitrary<Cause.Cause<E>>,
482
+ A: Arbitrary.Arbitrary<A>
483
+ ): Arbitrary.Arbitrary<AsyncData.AsyncData<E, A>> {
484
+ const noDataArb = Arbitrary.make(NoData)
485
+ const loadingArb = Arbitrary.make(Loading)
486
+ const failureArb = failureArbitrary(E)
487
+ const successArb = successArbitrary(A)
488
+
489
+ return (fc) =>
490
+ fc.oneof(
491
+ noDataArb(fc),
492
+ loadingArb(fc),
493
+ failureArb(fc),
494
+ successArb(fc)
495
+ )
496
+ }
497
+
498
+ function progressFromJson(json: ProgressFromTo | undefined): P.Progress | undefined {
499
+ if (json === undefined) return
500
+ return P.make(json.loaded, json.total)
501
+ }
502
+
503
+ function progressToJson(progres: Option.Option<P.Progress>): ProgressFromTo | undefined {
504
+ if (Option.isNone(progres)) return
505
+ return {
506
+ loaded: progres.value.loaded,
507
+ total: Option.getOrUndefined(progres.value.total)
508
+ }
509
+ }
@@ -1,7 +1,13 @@
1
1
  // Internal
2
2
 
3
- import { Cause, Effect, Effectable, Equal, Hash, Option, pipe, Unify } from "effect"
4
- import { constant } from "effect/Function"
3
+ import * as Cause from "effect/Cause"
4
+ import * as Effect from "effect/Effect"
5
+ import * as Effectable from "effect/Effectable"
6
+ import * as Equal from "effect/Equal"
7
+ import { constant, pipe } from "effect/Function"
8
+ import * as Hash from "effect/Hash"
9
+ import * as Option from "effect/Option"
10
+ import * as Unify from "effect/Unify"
5
11
  import { type AsyncData, type Failure, type Loading, type Success } from "../AsyncData.js"
6
12
  import { FAILURE_TAG, LOADING_TAG, NO_DATA_TAG, SUCCESS_TAG } from "./tag.js"
7
13
 
@@ -14,7 +20,7 @@ export class FailureImpl<E> extends Effectable.Class<never, E, never> implements
14
20
  [Unify.unifySymbol]!: AsyncData.Unify<this>;
15
21
  [Unify.ignoreSymbol]!: AsyncData.IgnoreList
16
22
 
17
- constructor(readonly cause: Cause.Cause<E>, readonly refreshing: Option.Option<Loading>) {
23
+ constructor(readonly cause: Cause.Cause<E>, readonly timestamp: number, readonly refreshing: Option.Option<Loading>) {
18
24
  super()
19
25
 
20
26
  this.commit = constant(Effect.failCause(cause))
@@ -23,6 +29,7 @@ export class FailureImpl<E> extends Effectable.Class<never, E, never> implements
23
29
  [Equal.symbol] = (that: unknown) => {
24
30
  return isAsyncData(that) && that._tag === "Failure"
25
31
  && Equal.equals(this.cause, that.cause)
32
+ && Equal.equals(this.timestamp, that.timestamp)
26
33
  && Equal.equals(this.refreshing, that.refreshing)
27
34
  };
28
35
 
@@ -44,7 +51,7 @@ export class SuccessImpl<A> extends Effectable.Class<never, never, A> implements
44
51
  [Unify.unifySymbol]!: AsyncData.Unify<this>;
45
52
  [Unify.ignoreSymbol]!: AsyncData.IgnoreList
46
53
 
47
- constructor(readonly value: A, readonly refreshing: Option.Option<Loading>) {
54
+ constructor(readonly value: A, readonly timestamp: number, readonly refreshing: Option.Option<Loading>) {
48
55
  super()
49
56
 
50
57
  this.commit = constant(Effect.succeed(value))
@@ -53,6 +60,7 @@ export class SuccessImpl<A> extends Effectable.Class<never, never, A> implements
53
60
  [Equal.symbol] = (that: unknown) => {
54
61
  return isAsyncData(that) && that._tag === "Success"
55
62
  && Equal.equals(this.value, that.value)
63
+ && Equal.equals(this.timestamp, that.timestamp)
56
64
  && Equal.equals(this.refreshing, that.refreshing)
57
65
  };
58
66