@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.
- package/.nvmrc +1 -0
- package/biome.json +36 -0
- package/dist/AsyncData.d.ts +203 -0
- package/dist/AsyncData.js +294 -0
- package/dist/AsyncData.js.map +1 -0
- package/dist/LazyRef.d.ts +22 -0
- package/dist/LazyRef.js +28 -0
- package/dist/LazyRef.js.map +1 -0
- package/dist/Progress.d.ts +25 -0
- package/dist/Progress.js +22 -0
- package/dist/Progress.js.map +1 -0
- package/dist/{dts/TypeId.d.ts → TypeId.d.ts} +0 -1
- package/dist/TypeId.js +8 -0
- package/dist/TypeId.js.map +1 -0
- package/dist/_internal.d.ts +20 -0
- package/dist/_internal.js +58 -0
- package/dist/_internal.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -49
- package/readme.md +218 -0
- package/src/AsyncData.test.ts +89 -0
- package/src/AsyncData.ts +552 -595
- package/src/LazyRef.ts +89 -0
- package/src/Progress.ts +20 -66
- package/src/TypeId.ts +1 -1
- package/src/_internal.ts +111 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +27 -0
- package/AsyncData/package.json +0 -6
- package/LICENSE +0 -21
- package/Progress/package.json +0 -6
- package/README.md +0 -5
- package/Schema/package.json +0 -6
- package/TypeId/package.json +0 -6
- package/dist/cjs/AsyncData.js +0 -399
- package/dist/cjs/AsyncData.js.map +0 -1
- package/dist/cjs/Progress.js +0 -62
- package/dist/cjs/Progress.js.map +0 -1
- package/dist/cjs/Schema.js +0 -539
- package/dist/cjs/Schema.js.map +0 -1
- package/dist/cjs/TypeId.js +0 -14
- package/dist/cjs/TypeId.js.map +0 -1
- package/dist/cjs/internal/async-data.js +0 -94
- package/dist/cjs/internal/async-data.js.map +0 -1
- package/dist/cjs/internal/tag.js +0 -12
- package/dist/cjs/internal/tag.js.map +0 -1
- package/dist/dts/AsyncData.d.ts +0 -386
- package/dist/dts/AsyncData.d.ts.map +0 -1
- package/dist/dts/Progress.d.ts +0 -43
- package/dist/dts/Progress.d.ts.map +0 -1
- package/dist/dts/Schema.d.ts +0 -78
- package/dist/dts/Schema.d.ts.map +0 -1
- package/dist/dts/TypeId.d.ts.map +0 -1
- package/dist/dts/internal/async-data.d.ts +0 -43
- package/dist/dts/internal/async-data.d.ts.map +0 -1
- package/dist/dts/internal/tag.d.ts +0 -6
- package/dist/dts/internal/tag.d.ts.map +0 -1
- package/dist/esm/AsyncData.js +0 -372
- package/dist/esm/AsyncData.js.map +0 -1
- package/dist/esm/Progress.js +0 -49
- package/dist/esm/Progress.js.map +0 -1
- package/dist/esm/Schema.js +0 -500
- package/dist/esm/Schema.js.map +0 -1
- package/dist/esm/TypeId.js +0 -8
- package/dist/esm/TypeId.js.map +0 -1
- package/dist/esm/internal/async-data.js +0 -91
- package/dist/esm/internal/async-data.js.map +0 -1
- package/dist/esm/internal/tag.js +0 -6
- package/dist/esm/internal/tag.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/src/Schema.ts +0 -754
- package/src/internal/async-data.ts +0 -114
- package/src/internal/tag.ts +0 -9
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Progress.js","sourceRoot":"","sources":["../src/Progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE3C,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;CAChF,CAAC,CAAA;AAKF,MAAM,CAAC,MAAM,KAAK,GAAa,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;AAEjF;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAGlB,IAAI,CAAC,CAAC,EAAE,SAAS,SAAS,CAAC,QAAkB,EAAE,MAAc;IAC/D,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;AACzD,CAAC,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAGjB,IAAI,CAAC,CAAC,EAAE,SAAS,QAAQ,CAAC,QAAkB,EAAE,KAAa;IAC7D,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AAC9E,CAAC,CAAC,CAAA"}
|
package/dist/TypeId.js
ADDED
|
@@ -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,20 @@
|
|
|
1
|
+
import type * as Effect from 'effect/Effect';
|
|
2
|
+
import * as Equal from 'effect/Equal';
|
|
3
|
+
import * as Hash from 'effect/Hash';
|
|
4
|
+
import * as Schema from 'effect/Schema';
|
|
5
|
+
import type * as SchemaAST from 'effect/SchemaAST';
|
|
6
|
+
/**
|
|
7
|
+
* Constructs the same interface as `Schema.Struct`, but with the ability to
|
|
8
|
+
* wrap it in Data.struct to ensure we can hash the struct.
|
|
9
|
+
*/
|
|
10
|
+
export declare function DataStruct<Fields extends Schema.Struct.Fields>(fields: Fields): Schema.Struct<Fields>;
|
|
11
|
+
export type LiteralWithDefault<K extends string, A extends SchemaAST.LiteralValue> = Schema.PropertySignature<':', A, K, ':', A, true, never>;
|
|
12
|
+
export declare function LiteralWithDefault<K extends string>(): <const A extends SchemaAST.LiteralValue>(value: A) => LiteralWithDefault<K, A>;
|
|
13
|
+
export type DataEffect<F extends Record<string, any>, A, E = never, R = never> = Effect.Effect<A, E, R> & F & Hash.Hash & Equal.Equal;
|
|
14
|
+
export interface DataEffectClass<Tag extends string> {
|
|
15
|
+
readonly _tag: Tag;
|
|
16
|
+
new <Fields extends Record<string, any>, A = never, E = never, R = never>(fields: Fields, commit: Effect.Effect<A, E, R>): DataEffect<Fields & {
|
|
17
|
+
readonly _tag: Tag;
|
|
18
|
+
}, A, E, R>;
|
|
19
|
+
}
|
|
20
|
+
export declare function DataEffect<const Tag extends string>(tag: Tag): DataEffectClass<Tag>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Data from 'effect/Data';
|
|
2
|
+
import * as Effectable from 'effect/Effectable';
|
|
3
|
+
import * as Equal from 'effect/Equal';
|
|
4
|
+
import * as Hash from 'effect/Hash';
|
|
5
|
+
import * as Predicate from 'effect/Predicate';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
import { structuralRegion } from 'effect/Utils';
|
|
8
|
+
/**
|
|
9
|
+
* Constructs the same interface as `Schema.Struct`, but with the ability to
|
|
10
|
+
* wrap it in Data.struct to ensure we can hash the struct.
|
|
11
|
+
*/
|
|
12
|
+
export function DataStruct(fields) {
|
|
13
|
+
return liftStructIntoDataStruct(Schema.Struct(fields));
|
|
14
|
+
}
|
|
15
|
+
function liftStructIntoDataStruct(struct) {
|
|
16
|
+
const data = Schema.Data(struct);
|
|
17
|
+
return Object.assign(data, {
|
|
18
|
+
make: (...params) => Data.struct(struct.make(...params)),
|
|
19
|
+
pick: (...params) => liftStructIntoDataStruct(struct.pick(...params)),
|
|
20
|
+
omit: (...params) => liftStructIntoDataStruct(struct.omit(...params)),
|
|
21
|
+
fields: struct.fields,
|
|
22
|
+
records: struct.records,
|
|
23
|
+
annotations: (...params) => liftStructIntoDataStruct(struct.annotations(...params)),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function LiteralWithDefault() {
|
|
27
|
+
return (value) => {
|
|
28
|
+
return Schema.optionalWith(Schema.Literal(value), { default: () => value });
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
class AbstractDataEffect extends Effectable.Class {
|
|
32
|
+
constructor(fields) {
|
|
33
|
+
super();
|
|
34
|
+
Object.assign(this, fields);
|
|
35
|
+
}
|
|
36
|
+
[Hash.symbol]() {
|
|
37
|
+
return Hash.structure(this);
|
|
38
|
+
}
|
|
39
|
+
[Equal.symbol](that) {
|
|
40
|
+
return Predicate.isObject(that) && structuralRegion(() => Equal.equals(this, that));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function DataEffect(tag) {
|
|
44
|
+
class DataEffect extends AbstractDataEffect {
|
|
45
|
+
effect;
|
|
46
|
+
static _tag = tag;
|
|
47
|
+
_tag = tag;
|
|
48
|
+
constructor(fields, effect) {
|
|
49
|
+
super(fields);
|
|
50
|
+
this.effect = effect;
|
|
51
|
+
}
|
|
52
|
+
commit() {
|
|
53
|
+
return this.effect;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return DataEffect;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=_internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_internal.js","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AAEnC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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,57 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typed/async-data",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.13.1",
|
|
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
|
-
"homepage": "https://github.com/tylors/typed",
|
|
13
16
|
"dependencies": {
|
|
14
|
-
"@
|
|
15
|
-
"effect": "^3.
|
|
16
|
-
|
|
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"
|
|
17
23
|
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
"./Progress": {
|
|
26
|
-
"types": "./dist/dts/Progress.d.ts",
|
|
27
|
-
"import": "./dist/esm/Progress.js",
|
|
28
|
-
"default": "./dist/cjs/Progress.js"
|
|
29
|
-
},
|
|
30
|
-
"./Schema": {
|
|
31
|
-
"types": "./dist/dts/Schema.d.ts",
|
|
32
|
-
"import": "./dist/esm/Schema.js",
|
|
33
|
-
"default": "./dist/cjs/Schema.js"
|
|
34
|
-
},
|
|
35
|
-
"./TypeId": {
|
|
36
|
-
"types": "./dist/dts/TypeId.d.ts",
|
|
37
|
-
"import": "./dist/esm/TypeId.js",
|
|
38
|
-
"default": "./dist/cjs/TypeId.js"
|
|
39
|
-
}
|
|
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"
|
|
40
30
|
},
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
],
|
|
49
|
-
"Schema": [
|
|
50
|
-
"./dist/dts/Schema.d.ts"
|
|
51
|
-
],
|
|
52
|
-
"TypeId": [
|
|
53
|
-
"./dist/dts/TypeId.d.ts"
|
|
54
|
-
]
|
|
55
|
-
}
|
|
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"
|
|
56
38
|
}
|
|
57
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
|
+
})
|