@typed/async-data 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.nvmrc +1 -0
  2. package/biome.json +36 -0
  3. package/dist/AsyncData.d.ts +196 -0
  4. package/dist/AsyncData.js +285 -0
  5. package/dist/AsyncData.js.map +1 -0
  6. package/dist/LazyRef.d.ts +21 -0
  7. package/dist/LazyRef.js +27 -0
  8. package/dist/LazyRef.js.map +1 -0
  9. package/dist/Progress.d.ts +25 -0
  10. package/dist/Progress.js +21 -0
  11. package/dist/Progress.js.map +1 -0
  12. package/dist/{dts/TypeId.d.ts → TypeId.d.ts} +0 -1
  13. package/dist/TypeId.js +8 -0
  14. package/dist/TypeId.js.map +1 -0
  15. package/dist/_internal.d.ts +16 -0
  16. package/dist/_internal.js +53 -0
  17. package/dist/_internal.js.map +1 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/package.json +31 -48
  22. package/readme.md +218 -0
  23. package/src/AsyncData.test.ts +89 -0
  24. package/src/AsyncData.ts +555 -595
  25. package/src/LazyRef.ts +87 -0
  26. package/src/Progress.ts +18 -65
  27. package/src/TypeId.ts +1 -1
  28. package/src/_internal.ts +114 -0
  29. package/src/index.ts +4 -0
  30. package/tsconfig.json +27 -0
  31. package/AsyncData/package.json +0 -6
  32. package/LICENSE +0 -21
  33. package/Progress/package.json +0 -6
  34. package/README.md +0 -5
  35. package/Schema/package.json +0 -6
  36. package/TypeId/package.json +0 -6
  37. package/dist/cjs/AsyncData.js +0 -399
  38. package/dist/cjs/AsyncData.js.map +0 -1
  39. package/dist/cjs/Progress.js +0 -62
  40. package/dist/cjs/Progress.js.map +0 -1
  41. package/dist/cjs/Schema.js +0 -538
  42. package/dist/cjs/Schema.js.map +0 -1
  43. package/dist/cjs/TypeId.js +0 -14
  44. package/dist/cjs/TypeId.js.map +0 -1
  45. package/dist/cjs/internal/async-data.js +0 -94
  46. package/dist/cjs/internal/async-data.js.map +0 -1
  47. package/dist/cjs/internal/tag.js +0 -12
  48. package/dist/cjs/internal/tag.js.map +0 -1
  49. package/dist/dts/AsyncData.d.ts +0 -386
  50. package/dist/dts/AsyncData.d.ts.map +0 -1
  51. package/dist/dts/Progress.d.ts +0 -43
  52. package/dist/dts/Progress.d.ts.map +0 -1
  53. package/dist/dts/Schema.d.ts +0 -78
  54. package/dist/dts/Schema.d.ts.map +0 -1
  55. package/dist/dts/TypeId.d.ts.map +0 -1
  56. package/dist/dts/internal/async-data.d.ts +0 -43
  57. package/dist/dts/internal/async-data.d.ts.map +0 -1
  58. package/dist/dts/internal/tag.d.ts +0 -6
  59. package/dist/dts/internal/tag.d.ts.map +0 -1
  60. package/dist/esm/AsyncData.js +0 -372
  61. package/dist/esm/AsyncData.js.map +0 -1
  62. package/dist/esm/Progress.js +0 -49
  63. package/dist/esm/Progress.js.map +0 -1
  64. package/dist/esm/Schema.js +0 -500
  65. package/dist/esm/Schema.js.map +0 -1
  66. package/dist/esm/TypeId.js +0 -8
  67. package/dist/esm/TypeId.js.map +0 -1
  68. package/dist/esm/internal/async-data.js +0 -91
  69. package/dist/esm/internal/async-data.js.map +0 -1
  70. package/dist/esm/internal/tag.js +0 -6
  71. package/dist/esm/internal/tag.js.map +0 -1
  72. package/dist/esm/package.json +0 -4
  73. package/src/Schema.ts +0 -750
  74. package/src/internal/async-data.ts +0 -114
  75. package/src/internal/tag.ts +0 -9
package/dist/TypeId.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ /**
5
+ * @since 1.0.0
6
+ */
7
+ export const AsyncDataTypeId = Symbol.for('@typed/async-data/AsyncData');
8
+ //# sourceMappingURL=TypeId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypeId.js","sourceRoot":"","sources":["../src/TypeId.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { type Effect, Equal, Hash, Schema, type SchemaAST } from 'effect';
2
+ /**
3
+ * Constructs the same interface as `Schema.Struct`, but with the ability to
4
+ * wrap it in Data.struct to ensure we can hash the struct.
5
+ */
6
+ export declare function DataStruct<Fields extends Schema.Struct.Fields>(fields: Fields): Schema.Struct<Fields>;
7
+ export type LiteralWithDefault<K extends string, A extends SchemaAST.LiteralValue> = Schema.PropertySignature<':', A, K, ':', A, true, never>;
8
+ export declare function LiteralWithDefault<K extends string>(): <const A extends SchemaAST.LiteralValue>(value: A) => LiteralWithDefault<K, A>;
9
+ export type DataEffect<F extends Record<string, any>, A, E = never, R = never> = Effect.Effect<A, E, R> & F & Hash.Hash & Equal.Equal;
10
+ export interface DataEffectClass<Tag extends string> {
11
+ readonly _tag: Tag;
12
+ new <Fields extends Record<string, any>, A = never, E = never, R = never>(fields: Fields, commit: Effect.Effect<A, E, R>): DataEffect<Fields & {
13
+ readonly _tag: Tag;
14
+ }, A, E, R>;
15
+ }
16
+ export declare function DataEffect<const Tag extends string>(tag: Tag): DataEffectClass<Tag>;
@@ -0,0 +1,53 @@
1
+ import { Data, Effectable, Equal, Hash, Predicate, Schema, } from 'effect';
2
+ import { structuralRegion } from 'effect/Utils';
3
+ /**
4
+ * Constructs the same interface as `Schema.Struct`, but with the ability to
5
+ * wrap it in Data.struct to ensure we can hash the struct.
6
+ */
7
+ export function DataStruct(fields) {
8
+ return liftStructIntoDataStruct(Schema.Struct(fields));
9
+ }
10
+ function liftStructIntoDataStruct(struct) {
11
+ const data = Schema.Data(struct);
12
+ return Object.assign(data, {
13
+ make: (...params) => Data.struct(struct.make(...params)),
14
+ pick: (...params) => liftStructIntoDataStruct(struct.pick(...params)),
15
+ omit: (...params) => liftStructIntoDataStruct(struct.omit(...params)),
16
+ fields: struct.fields,
17
+ records: struct.records,
18
+ annotations: (...params) => liftStructIntoDataStruct(struct.annotations(...params)),
19
+ });
20
+ }
21
+ export function LiteralWithDefault() {
22
+ return (value) => {
23
+ return Schema.optionalWith(Schema.Literal(value), { default: () => value });
24
+ };
25
+ }
26
+ class AbstractDataEffect extends Effectable.Class {
27
+ constructor(fields) {
28
+ super();
29
+ Object.assign(this, fields);
30
+ }
31
+ [Hash.symbol]() {
32
+ return Hash.structure(this);
33
+ }
34
+ [Equal.symbol](that) {
35
+ return Predicate.isObject(that) && structuralRegion(() => Equal.equals(this, that));
36
+ }
37
+ }
38
+ export function DataEffect(tag) {
39
+ class DataEffect extends AbstractDataEffect {
40
+ effect;
41
+ static _tag = tag;
42
+ _tag = tag;
43
+ constructor(fields, effect) {
44
+ super(fields);
45
+ this.effect = effect;
46
+ }
47
+ commit() {
48
+ return this.effect;
49
+ }
50
+ }
51
+ return DataEffect;
52
+ }
53
+ //# sourceMappingURL=_internal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_internal.js","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EAEJ,UAAU,EACV,KAAK,EACL,IAAI,EACJ,SAAS,EACT,MAAM,GAEP,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,MAAc;IAEd,OAAO,wBAAwB,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;AACxD,CAAC;AAED,SAAS,wBAAwB,CAC/B,MAA6B;IAE7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAEhC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QACzB,IAAI,EAAE,CAAC,GAAG,MAAsC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QACxF,IAAI,EAAE,CAAwC,GAAG,MAAS,EAAE,EAAE,CAC5D,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QAClD,IAAI,EAAE,CAAwC,GAAG,MAAS,EAAE,EAAE,CAC5D,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QAClD,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,CAAC,GAAG,MAA6C,EAAE,EAAE,CAChE,wBAAwB,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC;KAC1D,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAyC,KAAQ,EAA4B,EAAE;QACpF,OAAO,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAQ,CAAA;IACpF,CAAC,CAAA;AACH,CAAC;AAoBD,MAAe,kBACb,SAAQ,UAAU,CAAC,KAAc;IAGjC,YAAY,MAAS;QACnB,KAAK,EAAE,CAAA;QACP,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC7B,CAAC;IAID,CAAC,IAAI,CAAC,MAAM,CAAC;QACX,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAED,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAa;QAC1B,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IACrF,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAA2B,GAAQ;IAC3D,MAAM,UAKJ,SAAQ,kBAAmC;QAMhC;QALX,MAAM,CAAU,IAAI,GAAG,GAAG,CAAA;QACjB,IAAI,GAAG,GAAG,CAAA;QAEnB,YACE,MAAc,EACL,MAA8B;YAEvC,KAAK,CAAC,MAAM,CAAC,CAAA;YAFJ,WAAM,GAAN,MAAM,CAAwB;QAGzC,CAAC;QAED,MAAM;YACJ,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC;;IAGH,OAAO,UAAiB,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './AsyncData.js';
2
+ export * from './LazyRef.js';
3
+ export * from './Progress.js';
4
+ export * from './TypeId.js';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from './AsyncData.js';
2
+ export * from './LazyRef.js';
3
+ export * from './Progress.js';
4
+ export * from './TypeId.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
package/package.json CHANGED
@@ -1,56 +1,39 @@
1
1
  {
2
2
  "name": "@typed/async-data",
3
- "version": "0.11.0",
4
- "description": "",
3
+ "version": "0.13.0",
4
+ "description": "Async Data for Effect",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "keywords": [
9
+ "async",
10
+ "data",
11
+ "effect",
12
+ "typescript"
13
+ ],
14
+ "author": "Tylor Steinberger <tlsteinberger167@gmail.com>",
5
15
  "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/tylors/typed.git"
9
- },
10
- "sideEffects": [],
11
- "author": "Typed contributors",
12
16
  "dependencies": {
13
- "@effect/schema": "^0.68.1",
14
- "effect": "^3.3.5",
15
- "fast-check": "^3.19.0"
17
+ "@typed/lazy-ref": "^0.3.2",
18
+ "effect": "^3.11.9"
19
+ },
20
+ "peerDependencies": {
21
+ "@typed/lazy-ref": "^0.3.2",
22
+ "effect": "^3.11.9"
16
23
  },
17
- "exports": {
18
- "./package.json": "./package.json",
19
- "./AsyncData": {
20
- "types": "./dist/dts/AsyncData.d.ts",
21
- "import": "./dist/esm/AsyncData.js",
22
- "default": "./dist/cjs/AsyncData.js"
23
- },
24
- "./Progress": {
25
- "types": "./dist/dts/Progress.d.ts",
26
- "import": "./dist/esm/Progress.js",
27
- "default": "./dist/cjs/Progress.js"
28
- },
29
- "./Schema": {
30
- "types": "./dist/dts/Schema.d.ts",
31
- "import": "./dist/esm/Schema.js",
32
- "default": "./dist/cjs/Schema.js"
33
- },
34
- "./TypeId": {
35
- "types": "./dist/dts/TypeId.d.ts",
36
- "import": "./dist/esm/TypeId.js",
37
- "default": "./dist/cjs/TypeId.js"
38
- }
24
+ "devDependencies": {
25
+ "@biomejs/biome": "^1.9.4",
26
+ "@effect/vitest": "^0.16.0",
27
+ "@types/node": "^22.10.2",
28
+ "typescript": "^5.4.2",
29
+ "vitest": "^2.1.8"
39
30
  },
40
- "typesVersions": {
41
- "*": {
42
- "AsyncData": [
43
- "./dist/dts/AsyncData.d.ts"
44
- ],
45
- "Progress": [
46
- "./dist/dts/Progress.d.ts"
47
- ],
48
- "Schema": [
49
- "./dist/dts/Schema.d.ts"
50
- ],
51
- "TypeId": [
52
- "./dist/dts/TypeId.d.ts"
53
- ]
54
- }
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "dev": "tsc --watch",
34
+ "lint": "biome lint --write",
35
+ "test:watch": "vitest --typecheck",
36
+ "test": "vitest run --typecheck",
37
+ "typecheck": "tsc --noEmit"
55
38
  }
56
39
  }
package/readme.md ADDED
@@ -0,0 +1,218 @@
1
+ # @typed/async-data
2
+
3
+ A type-safe, Effect-based library for managing asynchronous data states in TypeScript applications. Built on top of the Effect ecosystem, it provides a powerful and composable way to handle loading, success, failure, and optimistic states of your data.
4
+
5
+ Key benefits:
6
+ - 🎯 **Type-safe**: Full TypeScript support with strict typing
7
+ - 🔄 **Effect Integration**: Seamless integration with the Effect ecosystem
8
+ - 📊 **Progress Tracking**: Built-in support for loading progress
9
+ - 🎭 **State Management**: Comprehensive state handling for async operations
10
+ - 🔄 **Optimistic Updates**: First-class support for optimistic UI updates
11
+ - 🛡️ **Error Handling**: Type-safe error handling with Effect
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @typed/async-data
17
+ # or
18
+ pnpm add @typed/async-data
19
+ # or
20
+ yarn add @typed/async-data
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ```typescript
26
+ import * as AsyncData from '@typed/async-data'
27
+ import { Effect } from 'effect'
28
+
29
+ // Create an AsyncData instance
30
+ const noData = AsyncData.noData()
31
+ const loading = AsyncData.loading()
32
+ const success = AsyncData.success(1)
33
+ const failure = AsyncData.failure(Cause.fail('error'))
34
+ const refreshing = AsyncData.refreshing(AsyncData.success(1))
35
+ const optimistic = AsyncData.optimistic(AsyncData.loading(), 1)
36
+ ```
37
+
38
+ ## Core Features
39
+
40
+ ### State Types
41
+
42
+ AsyncData represents data that can be in one of six states:
43
+
44
+ 1. `NoData` - Initial state when no data has been loaded
45
+ 2. `Loading` - Data is being loaded for the first time
46
+ 3. `Success<A>` - Successfully loaded data of type A
47
+ 4. `Failure<E>` - Failed to load data with error of type E
48
+ 5. `Refreshing<A, E>` - Reloading data while preserving previous state
49
+ 6. `Optimistic<A, E>` - Optimistically updated while waiting for confirmation
50
+
51
+ ### Effect Integration
52
+
53
+ AsyncData instances are Effect values, allowing seamless integration with Effect's ecosystem:
54
+
55
+ ```typescript
56
+ import { Effect, Cause } from 'effect'
57
+ import * as AsyncData from '@typed/async-data'
58
+
59
+ const program = Effect.gen(function* (_) {
60
+ // NoData and Loading are Failures
61
+ const noData = AsyncData.noData()
62
+ const noDataExit = yield* Effect.exit(noData)
63
+ // noDataExit === Exit.fail(noData)
64
+
65
+ // Success values are Successes
66
+ const success = AsyncData.success(1)
67
+ const value = yield* success
68
+ // value === 1
69
+
70
+ // Refreshing preserves the previous state
71
+ const refreshing = AsyncData.refreshing(AsyncData.success(1))
72
+ const refreshValue = yield* refreshing
73
+ // refreshValue === 1
74
+
75
+ // Optimistic updates provide immediate feedback
76
+ const optimistic = AsyncData.optimistic(AsyncData.fail('not used'), 1)
77
+ const optValue = yield* optimistic
78
+ // optValue === 1
79
+ })
80
+ ```
81
+
82
+ ### LazyRef Integration
83
+
84
+ AsyncData can be used with LazyRef for managing state over time:
85
+
86
+ ```typescript
87
+ import { Effect, Schema } from 'effect'
88
+ import * as AsyncData from '@typed/async-data'
89
+
90
+ // Define a schema-based AsyncData type
91
+ class Example extends AsyncData.AsyncData({
92
+ success: Schema.Number,
93
+ failure: Schema.String,
94
+ }) {}
95
+
96
+ const program = Effect.gen(function* (_) {
97
+ // Create a lazy reference
98
+ const example = yield* AsyncData.lazyRef(Example)
99
+
100
+ // Run an async operation
101
+ yield* Effect.forkScoped(
102
+ AsyncData.runEffect(
103
+ example,
104
+ Effect.delay(Effect.succeed(1), 500)
105
+ )
106
+ )
107
+
108
+ // State transitions automatically
109
+ const initial = yield* example
110
+ // initial === Example.noData()
111
+
112
+ yield* TestClock.adjust(1)
113
+ const loading = yield* example
114
+ // loading === Example.loading()
115
+
116
+ yield* TestClock.adjust(500)
117
+ const final = yield* example
118
+ // final === Example.success(1)
119
+ })
120
+ ```
121
+
122
+ ### Progress Tracking
123
+
124
+ AsyncData includes built-in support for tracking loading progress:
125
+
126
+ ```typescript
127
+ import * as AsyncData from '@typed/async-data'
128
+ import { Progress } from '@typed/async-data'
129
+
130
+ // Create loading state with progress
131
+ const progress = Progress.make({
132
+ loaded: 50,
133
+ total: Option.some(100)
134
+ })
135
+ const loading = AsyncData.loading(progress)
136
+
137
+ // Update progress
138
+ const updated = AsyncData.updateProgress(loading,
139
+ Progress.make({ loaded: 75, total: Option.some(100) })
140
+ )
141
+ ```
142
+
143
+ ### Pattern Matching
144
+
145
+ AsyncData provides comprehensive pattern matching capabilities:
146
+
147
+ ```typescript
148
+ import * as AsyncData from '@typed/async-data'
149
+
150
+ const result = AsyncData.match(data, {
151
+ NoData: () => 'No data yet',
152
+ Loading: (progress) => `Loading... ${progress}`,
153
+ Success: (value, { isRefreshing, isOptimistic }) =>
154
+ `Value: ${value} ${isRefreshing ? '(refreshing)' : ''} ${isOptimistic ? '(optimistic)' : ''}`,
155
+ Failure: (cause, { isRefreshing }) =>
156
+ `Error: ${cause} ${isRefreshing ? '(refreshing)' : ''}`
157
+ })
158
+ ```
159
+
160
+ ## Advanced Usage
161
+
162
+ ### Tagged Services
163
+
164
+ Create tagged services with AsyncData:
165
+
166
+ ```typescript
167
+ import * as AsyncData from '@typed/async-data'
168
+ import { Effect, Schema } from 'effect'
169
+
170
+ const UserService = AsyncData.Tag('UserService')<
171
+ typeof UserService,
172
+ User,
173
+ string
174
+ >()
175
+
176
+ const program = Effect.gen(function* (_) {
177
+ const users = yield* UserService
178
+
179
+ // Run effects and automatically manage state
180
+ yield* UserService.run(
181
+ Effect.succeed({ id: 1, name: 'John' })
182
+ )
183
+ })
184
+
185
+ // Provide implementation
186
+ program.pipe(
187
+ Effect.provide(UserService.Default)
188
+ )
189
+ ```
190
+
191
+ ### Type-safe Error Handling
192
+
193
+ AsyncData ensures type-safe error handling:
194
+
195
+ ```typescript
196
+ import * as AsyncData from '@typed/async-data'
197
+ import { Effect, Cause } from 'effect'
198
+
199
+ type ApiError = { code: number; message: string }
200
+
201
+ const result = AsyncData.failure<ApiError>(
202
+ Cause.fail({ code: 404, message: 'Not found' })
203
+ )
204
+
205
+ AsyncData.match(result, {
206
+ Failure: (cause) => {
207
+ // cause is typed as Cause<ApiError>
208
+ const errors = Array.from(Cause.failures(cause))
209
+ // Handle errors
210
+ },
211
+ // ... other cases
212
+ })
213
+ ```
214
+
215
+ ## License
216
+
217
+ MIT
218
+
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from '@effect/vitest'
2
+ import { Cause, Effect, Exit, Schema, TestClock } from 'effect'
3
+ import * as AsyncData from './index.js'
4
+
5
+ describe('AsyncData', () => {
6
+ describe('Effect', () => {
7
+ it.effect('NoData is a Failure', () =>
8
+ Effect.gen(function* () {
9
+ const noData = AsyncData.noData()
10
+ const exit = yield* Effect.exit(noData)
11
+
12
+ expect(exit).toEqual(Exit.fail(noData))
13
+ }),
14
+ )
15
+
16
+ it.effect('Loading is a Failure', () =>
17
+ Effect.gen(function* () {
18
+ const loading = AsyncData.loading()
19
+ const exit = yield* Effect.exit(loading)
20
+
21
+ expect(exit).toEqual(Exit.fail(loading))
22
+ }),
23
+ )
24
+
25
+ it.effect('Failure is a Failure', () =>
26
+ Effect.gen(function* () {
27
+ const cause = Cause.fail('test')
28
+ const failure = AsyncData.failure(cause)
29
+ const exit = yield* Effect.exit(failure)
30
+
31
+ expect(exit).toEqual(Exit.failCause(cause))
32
+ }),
33
+ )
34
+
35
+ it.effect('Success is a Success', () =>
36
+ Effect.gen(function* () {
37
+ expect(yield* AsyncData.success(1)).toEqual(1)
38
+ }),
39
+ )
40
+
41
+ it.effect('Refreshing<Success> is a Success', () =>
42
+ Effect.gen(function* () {
43
+ expect(yield* AsyncData.refreshing(AsyncData.success(1))).toEqual(1)
44
+ }),
45
+ )
46
+
47
+ it.effect('Refreshing<Failure> is a Failure', () =>
48
+ Effect.gen(function* () {
49
+ const cause = Cause.fail('test')
50
+ const failure = AsyncData.failure(cause)
51
+ const exit = yield* Effect.exit(AsyncData.refreshing(failure))
52
+
53
+ expect(exit).toEqual(Exit.failCause(cause))
54
+ }),
55
+ )
56
+
57
+ it.effect('Optimistic is a Success', () =>
58
+ Effect.gen(function* () {
59
+ expect(yield* AsyncData.optimistic(AsyncData.fail('not used'), 1)).toEqual(1)
60
+ }),
61
+ )
62
+ })
63
+
64
+ describe('Schema + LazyRef', () => {
65
+ class Example extends AsyncData.AsyncData({
66
+ success: Schema.Number,
67
+ failure: Schema.String,
68
+ }) {}
69
+
70
+ it.scoped('keeps state', () =>
71
+ Effect.gen(function* () {
72
+ const example = yield* AsyncData.lazyRef(Example)
73
+
74
+ // Simulate an async operation
75
+ yield* Effect.forkScoped(AsyncData.runEffect(example, Effect.delay(Effect.succeed(1), 500)))
76
+
77
+ expect(yield* example).toEqual(Example.noData())
78
+
79
+ yield* TestClock.adjust(1)
80
+
81
+ expect(yield* example).toEqual(Example.loading())
82
+
83
+ yield* TestClock.adjust(500)
84
+
85
+ expect(yield* example).toEqual(Example.success(1))
86
+ }),
87
+ )
88
+ })
89
+ })