mini-effect 0.0.0 → 0.0.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/README.md +141 -126
- package/dist/concurrency.d.mts +2 -1
- package/dist/concurrency.mjs +6 -1
- package/dist/fetch.d.mts +13 -0
- package/dist/fetch.mjs +22 -0
- package/dist/mini-effect.d.mts +30 -26
- package/dist/mini-effect.mjs +35 -10
- package/dist/schema.d.mts +9 -0
- package/dist/schema.mjs +12 -0
- package/dist/tag.d.mts +14 -0
- package/dist/tag.mjs +16 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,190 +1,205 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mini-effect
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Minimal Effect system for TypeScript. Lazy, composable, cancellable.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**mini-effect** gives you the good stuff:
|
|
7
|
+
```bash
|
|
8
|
+
npm install mini-effect
|
|
9
|
+
```
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
- 🔗 **Pipes** - Compose like a functional programming wizard
|
|
15
|
-
- 🎭 **Generators** - Write async code that doesn't look like callback spaghetti
|
|
16
|
-
- 🎣 **Error handling** - Catch 'em all (or just some of 'em)
|
|
17
|
-
- ⚡ **Concurrency** - `all`, `race`, `any`, `allSettled` - the gang's all here
|
|
18
|
-
- 🛑 **AbortSignal support** - Cancel everything! EVERYTHING!
|
|
11
|
+
## Modules
|
|
19
12
|
|
|
20
|
-
|
|
13
|
+
- `mini-effect` — core primitives
|
|
14
|
+
- `mini-effect/concurrency` — parallel execution
|
|
15
|
+
- `mini-effect/tag` — tagged error handling
|
|
16
|
+
- `mini-effect/fetch` — HTTP requests
|
|
17
|
+
- `mini-effect/schema` — validation (Valibot)
|
|
21
18
|
|
|
22
|
-
##
|
|
19
|
+
## Core
|
|
23
20
|
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
# or
|
|
27
|
-
pnpm add mini-effect
|
|
28
|
-
# or
|
|
29
|
-
yarn add mini-effect
|
|
21
|
+
```typescript
|
|
22
|
+
import { fn, succeed, fail, gen, pipe, run, catchSome } from "mini-effect";
|
|
30
23
|
```
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
### Create Effects
|
|
33
26
|
|
|
34
27
|
```typescript
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const todo = await run(fetchTodo);
|
|
28
|
+
const delay = fn(
|
|
29
|
+
(signal) =>
|
|
30
|
+
new Promise<void>((resolve, reject) => {
|
|
31
|
+
const id = setTimeout(resolve, 1000);
|
|
32
|
+
signal.addEventListener("abort", () => {
|
|
33
|
+
clearTimeout(id);
|
|
34
|
+
reject(signal.reason);
|
|
35
|
+
});
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
const success = succeed(42);
|
|
39
|
+
const failure = fail(new Error("failed"));
|
|
48
40
|
```
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### Generators (a.k.a. "async/await but cooler")
|
|
42
|
+
### Generators
|
|
53
43
|
|
|
54
44
|
```typescript
|
|
55
45
|
const program = gen(function* (signal) {
|
|
56
46
|
const a = yield* fn(() => Promise.resolve(1));
|
|
57
|
-
const b = yield*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return a + b + c; // 6!
|
|
47
|
+
const b = yield* succeed(2);
|
|
48
|
+
return a + b;
|
|
61
49
|
});
|
|
62
|
-
|
|
63
|
-
await run(program); // 6
|
|
64
50
|
```
|
|
65
51
|
|
|
66
|
-
###
|
|
52
|
+
### Pipe
|
|
67
53
|
|
|
68
54
|
```typescript
|
|
69
55
|
const result = await run(
|
|
70
56
|
pipe(
|
|
71
57
|
succeed(5),
|
|
72
|
-
(n: number) => succeed(n * 2),
|
|
73
|
-
(n: number) => succeed(n + 1),
|
|
74
|
-
(n: number) => succeed(`Result: ${n}`), // "Result: 11"
|
|
58
|
+
(n: number) => succeed(n * 2),
|
|
59
|
+
(n: number) => succeed(n + 1),
|
|
75
60
|
),
|
|
76
61
|
);
|
|
77
62
|
```
|
|
78
63
|
|
|
79
|
-
### Error
|
|
64
|
+
### Error Recovery
|
|
80
65
|
|
|
81
66
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class NotFoundError extends Error {
|
|
85
|
-
constructor(public id: number) {
|
|
86
|
-
super(`Not found: ${id}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const safeProgram = pipe(
|
|
67
|
+
pipe(
|
|
91
68
|
fn(() => {
|
|
92
|
-
throw new
|
|
69
|
+
throw new Error("x");
|
|
93
70
|
}),
|
|
94
|
-
|
|
71
|
+
catchSome((cause) =>
|
|
72
|
+
cause instanceof Error ? succeed("recovered") : undefined,
|
|
73
|
+
),
|
|
95
74
|
);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Execution
|
|
96
78
|
|
|
97
|
-
|
|
79
|
+
```typescript
|
|
80
|
+
const result = await run(effect);
|
|
81
|
+
const result = await run(effect, abortController.signal);
|
|
98
82
|
```
|
|
99
83
|
|
|
100
|
-
|
|
84
|
+
## Concurrency
|
|
101
85
|
|
|
102
86
|
```typescript
|
|
103
|
-
import { all,
|
|
104
|
-
|
|
105
|
-
// Run 'em all at once!
|
|
106
|
-
const results = await run(
|
|
107
|
-
all([
|
|
108
|
-
fn(() => fetch("/api/users")),
|
|
109
|
-
fn(() => fetch("/api/posts")),
|
|
110
|
-
fn(() => fetch("/api/comments")),
|
|
111
|
-
]),
|
|
112
|
-
);
|
|
87
|
+
import { all, allSettled, any, race, fork } from "mini-effect/concurrency";
|
|
88
|
+
```
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
90
|
+
| Function | Behavior |
|
|
91
|
+
| ------------ | -------------------------------------- |
|
|
92
|
+
| `all` | Parallel execution, fail-fast |
|
|
93
|
+
| `allSettled` | Parallel execution, collect all |
|
|
94
|
+
| `any` | First success wins |
|
|
95
|
+
| `race` | First completion wins |
|
|
96
|
+
| `fork` | Background execution, returns abort fn |
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
await run(all([effectA, effectB, effectC]));
|
|
100
|
+
const abort = await run(fork(backgroundEffect));
|
|
101
|
+
```
|
|
118
102
|
|
|
119
|
-
|
|
120
|
-
const firstSuccess = await run(any([fn(() => tryThis()), fn(() => tryThat())]));
|
|
103
|
+
## Tagged Errors
|
|
121
104
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
105
|
+
```typescript
|
|
106
|
+
import { failure, catchTags } from "mini-effect/tag";
|
|
107
|
+
|
|
108
|
+
const NotFound = failure("NotFound");
|
|
109
|
+
const Unauthorized = failure("Unauthorized");
|
|
110
|
+
|
|
111
|
+
pipe(
|
|
112
|
+
fn(() => {
|
|
113
|
+
throw new NotFound();
|
|
114
|
+
}),
|
|
115
|
+
catchTags({
|
|
116
|
+
NotFound: () => succeed("fallback"),
|
|
117
|
+
Unauthorized: () => succeed("login"),
|
|
118
|
+
}),
|
|
125
119
|
);
|
|
126
120
|
```
|
|
127
121
|
|
|
128
|
-
|
|
122
|
+
## Fetch
|
|
129
123
|
|
|
130
124
|
```typescript
|
|
131
|
-
|
|
125
|
+
import { request, json, text, blob, bytes, formData } from "mini-effect/fetch";
|
|
126
|
+
```
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
128
|
+
| Function | Returns | Error Tag |
|
|
129
|
+
| ---------- | ------------ | --------------- |
|
|
130
|
+
| `request` | `Response` | `FailedToFetch` |
|
|
131
|
+
| `json` | `unknown` | `FailedToRead` |
|
|
132
|
+
| `text` | `string` | `FailedToRead` |
|
|
133
|
+
| `blob` | `Blob` | `FailedToRead` |
|
|
134
|
+
| `bytes` | `Uint8Array` | `FailedToRead` |
|
|
135
|
+
| `formData` | `FormData` | `FailedToRead` |
|
|
138
136
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
});
|
|
137
|
+
```typescript
|
|
138
|
+
pipe(request("https://api.example.com/data"), (res) => json(res));
|
|
139
|
+
```
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
const promise = run(longRunning, controller.signal);
|
|
141
|
+
## Schema
|
|
146
142
|
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
```typescript
|
|
144
|
+
import { validate, object, string, number } from "mini-effect/schema";
|
|
145
|
+
|
|
146
|
+
const User = object({ name: string(), age: number() });
|
|
147
|
+
const validated = validate(User)({ name: "Alice", age: 30 });
|
|
149
148
|
```
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
Error tag: `FailedToValidate`
|
|
151
|
+
|
|
152
|
+
Re-exports all of [Valibot](https://valibot.dev/).
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### `mini-effect`
|
|
152
157
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
| `fn(thunk)
|
|
156
|
-
| `succeed(value)
|
|
157
|
-
| `fail(error)
|
|
158
|
-
| `gen
|
|
159
|
-
| `pipe(effect, ...
|
|
160
|
-
| `run(effect, signal?)
|
|
161
|
-
| `catchSome(handler)
|
|
162
|
-
| `catchClass(Class, handler)` | Catch errors by type |
|
|
158
|
+
| Export | Signature |
|
|
159
|
+
| ----------- | ---------------------------------------------------------------- |
|
|
160
|
+
| `fn` | `(thunk: (signal: AbortSignal) => T \| Promise<T>) => Effect<T>` |
|
|
161
|
+
| `succeed` | `(value: T) => Effect<T, never>` |
|
|
162
|
+
| `fail` | `(error: E) => Effect<never, E>` |
|
|
163
|
+
| `gen` | `(generator: (signal) => Generator<Effect>) => Effect` |
|
|
164
|
+
| `pipe` | `(effect, ...fns) => Effect` |
|
|
165
|
+
| `run` | `(effect, signal?) => Promise<T>` |
|
|
166
|
+
| `catchSome` | `(handler: (cause) => Effect \| undefined) => WrapEffect` |
|
|
163
167
|
|
|
164
|
-
###
|
|
168
|
+
### `mini-effect/concurrency`
|
|
165
169
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
| `all
|
|
169
|
-
| `allSettled
|
|
170
|
-
| `any
|
|
171
|
-
| `race
|
|
170
|
+
| Export | Signature |
|
|
171
|
+
| ------------ | ------------------------------------------------ |
|
|
172
|
+
| `all` | `(effects: Effect[]) => Effect<T[]>` |
|
|
173
|
+
| `allSettled` | `(effects: Effect[]) => Effect<SettledResult[]>` |
|
|
174
|
+
| `any` | `(effects: Effect[]) => Effect<T>` |
|
|
175
|
+
| `race` | `(effects: Effect[]) => Effect<T>` |
|
|
176
|
+
| `fork` | `(effect: Effect) => Effect<() => void>` |
|
|
172
177
|
|
|
173
|
-
|
|
178
|
+
### `mini-effect/tag`
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
| Export | Signature |
|
|
181
|
+
| ----------- | ------------------------------------------------ |
|
|
182
|
+
| `failure` | `(tag: string) => FailureConstructor` |
|
|
183
|
+
| `catchTags` | `(handlers: Record<Tag, Handler>) => WrapEffect` |
|
|
176
184
|
|
|
177
|
-
|
|
178
|
-
2. **Composable** - Pipe and combine them without execution
|
|
179
|
-
3. **Typed errors** - Your errors are part of the type signature
|
|
180
|
-
4. **Cancellable** - AbortSignal support baked right in
|
|
185
|
+
### `mini-effect/fetch`
|
|
181
186
|
|
|
182
|
-
|
|
187
|
+
| Export | Type |
|
|
188
|
+
| ---------- | -------- |
|
|
189
|
+
| `request` | Function |
|
|
190
|
+
| `json` | Function |
|
|
191
|
+
| `text` | Function |
|
|
192
|
+
| `blob` | Function |
|
|
193
|
+
| `bytes` | Function |
|
|
194
|
+
| `formData` | Function |
|
|
183
195
|
|
|
184
|
-
|
|
196
|
+
### `mini-effect/schema`
|
|
185
197
|
|
|
186
|
-
|
|
198
|
+
| Export | Description |
|
|
199
|
+
| ---------- | ------------------------------- |
|
|
200
|
+
| `validate` | `(schema) => (value) => Effect` |
|
|
201
|
+
| `*` | All Valibot exports |
|
|
187
202
|
|
|
188
|
-
|
|
203
|
+
## License
|
|
189
204
|
|
|
190
|
-
|
|
205
|
+
MIT
|
package/dist/concurrency.d.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Effect, inferError, inferReturn } from "./mini-effect.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/concurrency.d.ts
|
|
4
|
+
declare const fork: <const E extends Effect<any, any>>(effect: E) => Effect<() => void, never>;
|
|
4
5
|
declare const all: <const E extends Effect<any, any>[]>(values: E) => Effect<{ [K in keyof E]: inferReturn<E[K]> }, inferError<E[number]>>;
|
|
5
6
|
declare const allSettled: <const E extends Effect<any, any>[]>(values: E) => Effect<{ [K in keyof E]: PromiseSettledResult<inferReturn<E[K]>> }, inferError<E[number]>>;
|
|
6
7
|
declare const any: <const E extends Effect<any, any>[]>(values: E) => Effect<inferReturn<E[number]>, inferError<E[number]>>;
|
|
7
8
|
declare const race: <const E extends Effect<any, any>[]>(values: E) => Effect<inferReturn<E[number]>, inferError<E[number]>>;
|
|
8
9
|
//#endregion
|
|
9
|
-
export { all, allSettled, any, race };
|
|
10
|
+
export { all, allSettled, any, fork, race };
|
package/dist/concurrency.mjs
CHANGED
|
@@ -6,6 +6,11 @@ const localController = (signal) => {
|
|
|
6
6
|
signal.addEventListener("abort", () => controller.abort(signal.reason));
|
|
7
7
|
return controller;
|
|
8
8
|
};
|
|
9
|
+
const fork = (effect) => fn((signal) => {
|
|
10
|
+
const controller = localController(signal);
|
|
11
|
+
run(effect, controller.signal).catch();
|
|
12
|
+
return () => controller.abort();
|
|
13
|
+
});
|
|
9
14
|
const promiseMethod = (method, values) => fn(async (signal) => {
|
|
10
15
|
let controller = localController(signal);
|
|
11
16
|
let localSignal = controller.signal;
|
|
@@ -21,4 +26,4 @@ const any = (values) => promiseMethod("any", values);
|
|
|
21
26
|
const race = (values) => promiseMethod("race", values);
|
|
22
27
|
|
|
23
28
|
//#endregion
|
|
24
|
-
export { all, allSettled, any, race };
|
|
29
|
+
export { all, allSettled, any, fork, race };
|
package/dist/fetch.d.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Effect } from "./mini-effect.mjs";
|
|
2
|
+
import { FailureConstructor } from "./tag.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/fetch.d.ts
|
|
5
|
+
declare const FailedToReadError: FailureConstructor<"FailedToRead">;
|
|
6
|
+
declare const request: (input: string | URL, init?: RequestInit) => Effect<Response, never>;
|
|
7
|
+
declare const blob: (response: Response) => Effect<any, never>;
|
|
8
|
+
declare const bytes: (response: Response) => Effect<any, never>;
|
|
9
|
+
declare const formData: (response: Response) => Effect<any, never>;
|
|
10
|
+
declare const json: (response: Response) => Effect<unknown, typeof FailedToReadError>;
|
|
11
|
+
declare const text: (response: Response) => Effect<any, never>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { blob, bytes, formData, json, request, text };
|
package/dist/fetch.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { catchSome, fail, fn } from "./mini-effect.mjs";
|
|
2
|
+
import { failure } from "./tag.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/fetch.ts
|
|
5
|
+
const FailedToFetchError = failure("FailedToFetch");
|
|
6
|
+
const FailedToReadError = failure("FailedToRead");
|
|
7
|
+
const request = (input, init) => {
|
|
8
|
+
return fn((signal) => fetch(input, {
|
|
9
|
+
signal,
|
|
10
|
+
...init
|
|
11
|
+
})).pipe(catchSome((cause) => fail(new FailedToFetchError({ cause }))));
|
|
12
|
+
};
|
|
13
|
+
const read = (method, response) => fn(() => response[method]()).pipe(catchSome((cause) => fail(new FailedToReadError({ cause }))));
|
|
14
|
+
new Response().blob;
|
|
15
|
+
const blob = (response) => read("blob", response);
|
|
16
|
+
const bytes = (response) => read("bytes", response);
|
|
17
|
+
const formData = (response) => read("formData", response);
|
|
18
|
+
const json = (response) => read("json", response);
|
|
19
|
+
const text = (response) => read("text", response);
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { blob, bytes, formData, json, request, text };
|
package/dist/mini-effect.d.mts
CHANGED
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
//#region src/mini-effect.d.ts
|
|
2
|
-
declare const
|
|
2
|
+
declare const SYMBOL: SymbolConstructor;
|
|
3
|
+
declare const REMOVE: unique symbol;
|
|
4
|
+
declare const RUN: unique symbol;
|
|
5
|
+
declare const ERROR: unique symbol;
|
|
3
6
|
declare const run: <T, E$1>(effect: Effect<T, E$1>, signal?: AbortSignal) => Promise<T>;
|
|
4
7
|
declare const fn: <R$1, C = never>(thunk: Thunk<R$1>) => Effect<R$1, C>;
|
|
5
8
|
declare const fail: <C>(cause: C) => Effect<never, C>;
|
|
6
9
|
declare const succeed: <R$1>(result: R$1) => Effect<R$1, never>;
|
|
7
|
-
declare const catchSome: <
|
|
8
|
-
declare const catchClass: <T extends {
|
|
9
|
-
new (...args: any[]): any;
|
|
10
|
-
}, E$1 extends Effect<any, any>>(type: T, thunk: (cause: T) => E$1) => Pipeable<any, inferReturn<E$1>, inferError<E$1>, T>;
|
|
10
|
+
declare const catchSome: <T = any, C = any, CE = any>(thunk: (cause: unknown) => Effect<T, C> | undefined) => WrapEffect<T, C, CE>;
|
|
11
11
|
declare const gen: <T extends Effect<any, any>, TReturn, TNext>(thunk: (signal: AbortSignal) => Generator<T, TReturn, TNext>) => Effect<TReturn, inferError<T>>;
|
|
12
|
-
declare const pipe: <F extends Effect
|
|
12
|
+
declare const pipe: <F extends Effect, P$1 extends [Pipeable, ...Pipeable[]]>(first: F, ...pipeable: PipeReturn<[F, ...P$1]> extends never ? never : P$1) => PipeReturn<[F, ...P$1]>;
|
|
13
|
+
declare class Effect<T = any, C = any> {
|
|
14
|
+
[RUN]?: (signal: AbortSignal) => T | PromiseLike<T>;
|
|
15
|
+
[ERROR]?: (cause: unknown) => Effect | undefined;
|
|
16
|
+
constructor(run?: (signal: AbortSignal) => PromiseLike<T> | T, catchSome?: (cause: unknown) => Effect | undefined);
|
|
17
|
+
[SYMBOL.iterator](): Generator<Effect<T, C>, T, any>;
|
|
18
|
+
readonly pipe: <P$1 extends [Pipeable, ...Pipeable[]]>(...pipeable: P$1) => PipeReturn<[Effect<T, C>, ...P$1]>;
|
|
19
|
+
}
|
|
20
|
+
declare class WrapEffect<T = any, C = any, CE = any> {
|
|
21
|
+
readonly [RUN]?: (signal: AbortSignal) => PromiseLike<T> | T;
|
|
22
|
+
readonly [ERROR]?: (cause: unknown) => Effect<any, any> | undefined;
|
|
23
|
+
readonly [REMOVE]: CE;
|
|
24
|
+
constructor(run?: (signal: AbortSignal) => PromiseLike<T> | T, catchSome?: (cause: unknown) => Effect | undefined);
|
|
25
|
+
[SYMBOL.iterator](): Generator<Effect<T, C>, T, any>;
|
|
26
|
+
}
|
|
13
27
|
type Thunk<out R$1> = (signal: AbortSignal) => R$1 | PromiseLike<R$1>;
|
|
14
|
-
type PipeableThunk<
|
|
15
|
-
type Pipeable
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type WrapEffect<
|
|
23
|
-
[WRAP]?(cause: unknown): Effect<T, C> | undefined;
|
|
24
|
-
[EXCLUDE]?: EE;
|
|
25
|
-
};
|
|
26
|
-
type inferInput<T> = T extends PipeableThunk<infer I, any, any> ? I : never;
|
|
27
|
-
type inferReturn<T> = T extends Pipeable<any, infer R, any> ? R : T extends Effect<infer R, any> ? R : never;
|
|
28
|
-
type inferError<T> = T extends Pipeable<any, any, infer E> ? E : T extends Effect<any, infer E> ? E : never;
|
|
29
|
-
type mergePipeable<A extends Pipeable, B extends Pipeable> = A extends Pipeable<any, infer A2, infer A3> ? B extends Pipeable<any, infer B2, infer B3, infer B4> ? Pipeable<any, A2 | B2, Exclude<A3 | B3, B4>, never> : A : B;
|
|
30
|
-
type asEffect<P$1> = P$1 extends Pipeable<any, infer R, infer E> ? Effect<R, E> : never;
|
|
31
|
-
type PipeReturn<F extends Pipeable[]> = asEffect<mergePipeable<CheckPipe<F, F[0]>, Effect<never, inferError<F[number]>>>>;
|
|
32
|
-
type CheckPipe<F extends Pipeable[], L extends Pipeable> = F extends [Pipeable, WrapEffect<any, any, any>, ...infer R] ? R extends Pipeable[] ? CheckPipe<[mergePipeable<L, mergePipeable<F[0], F[1]>>, ...R], mergePipeable<L, mergePipeable<F[0], F[1]>>> : never : F extends [Pipeable, Pipeable, ...infer P] ? ExtendsStrict<inferReturn<F[0]>, inferInput<F[1]>> extends true ? P extends Pipeable[] ? CheckPipe<[F[1], ...P], F[1]> : never : never : F extends [WrapEffect<any, any, any>] ? mergePipeable<F[0], L> : F extends [Pipeable<any, infer R, infer E>] ? Effect<R, E> : never;
|
|
28
|
+
type PipeableThunk<T = any, C = any, I = any> = (next: I) => Effect<T, C>;
|
|
29
|
+
type Pipeable = PipeableThunk | Effect | WrapEffect;
|
|
30
|
+
type inferInput<T> = T extends PipeableThunk<any, any, infer I> ? I : never;
|
|
31
|
+
type inferReturn<T> = T extends WrapEffect<infer R> ? R : T extends Effect<infer R> ? R : T extends PipeableThunk<infer R> ? R : never;
|
|
32
|
+
type inferError<T> = T extends PipeableThunk<any, infer E> ? E : T extends WrapEffect<any, infer E> ? E : T extends Effect<any, infer E> ? E : never;
|
|
33
|
+
type inferExclude<T> = T extends WrapEffect<any, any, infer CE> ? CE : never;
|
|
34
|
+
type Simplify<T> = T extends Effect<infer R, infer E> ? Effect<R, E> : T;
|
|
35
|
+
type PipeReturn<F extends Pipeable[]> = IsNever<CheckPipe<F>> extends true ? never : Simplify<Effect<inferReturn<CheckPipe<F>> | inferReturn<Extract<F[number], WrapEffect<any, any, any>>>, Exclude<inferError<CheckPipe<F>>, inferExclude<F[number]>>>>;
|
|
36
|
+
type CheckPipe<F extends Pipeable[]> = F extends [Effect, WrapEffect, ...infer R] ? R extends [Pipeable, ...Pipeable[]] ? CheckPipe<R> : F[0] : F extends [Pipeable, Pipeable, ...infer P] ? ExtendsStrict<inferReturn<F[0]>, inferInput<F[1]>> extends true ? P extends Pipeable[] ? CheckPipe<[F[1], ...P]> : never : never : F extends [WrapEffect] ? Effect<inferReturn<F[0]>> : F extends [Pipeable] ? F[0] : never;
|
|
33
37
|
type IsNever<T> = [T] extends [never] ? true : false;
|
|
34
38
|
type IsAny<T> = 0 extends 1 & NoInfer<T> ? true : false;
|
|
35
39
|
type ExtendsStrict<Left, Right> = IsAny<Left | Right> extends true ? true : IsNever<Left> extends true ? IsNever<Right> : [Left] extends [Right] ? true : false;
|
|
36
40
|
//#endregion
|
|
37
|
-
export { Effect,
|
|
41
|
+
export { Effect, Pipeable, PipeableThunk, Thunk, WrapEffect, run as _run, run, catchSome, fail, fn, gen, inferError, inferExclude, inferInput, inferReturn, pipe, succeed };
|
package/dist/mini-effect.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//#region src/mini-effect.ts
|
|
2
2
|
const SYMBOL = Symbol;
|
|
3
|
+
const REMOVE = /* @__PURE__ */ SYMBOL();
|
|
3
4
|
const RUN = SYMBOL();
|
|
4
|
-
const
|
|
5
|
+
const ERROR = SYMBOL();
|
|
5
6
|
const DEFAULT_SIGNAL = new AbortController().signal;
|
|
6
7
|
const isFunction = (value) => typeof value === "function";
|
|
7
8
|
const quickYieldResult = (iterator, signal, ret) => {
|
|
@@ -9,15 +10,14 @@ const quickYieldResult = (iterator, signal, ret) => {
|
|
|
9
10
|
return ret.done ? ret.value : run(ret.value, signal).then((r) => quickYieldResult(iterator, signal, r));
|
|
10
11
|
};
|
|
11
12
|
const run = (effect, signal) => new Promise((resolve) => resolve(effect[RUN](signal ?? DEFAULT_SIGNAL)));
|
|
12
|
-
const fn = (thunk) => new
|
|
13
|
+
const fn = (thunk) => new Effect(thunk);
|
|
13
14
|
const fail = (cause) => fn(() => {
|
|
14
15
|
throw cause;
|
|
15
16
|
});
|
|
16
17
|
const succeed = (result) => fn(() => result);
|
|
17
|
-
const catchSome = (thunk) => new
|
|
18
|
-
const catchClass = (type, thunk) => catchSome((cause) => cause instanceof type ? thunk(cause) : void 0);
|
|
18
|
+
const catchSome = (thunk) => new WrapEffect(void 0, thunk);
|
|
19
19
|
const gen = (thunk) => fn((signal) => quickYieldResult(thunk(signal), signal));
|
|
20
|
-
const wrapEffect = (pipe$1, onErr) => new
|
|
20
|
+
const wrapEffect = (pipe$1, onErr) => new WrapEffect(pipe$1[RUN], (cause) => pipe$1[ERROR]?.(cause) ?? onErr(cause));
|
|
21
21
|
const pipe = (first, ...pipeable) => {
|
|
22
22
|
let run$1 = [];
|
|
23
23
|
let wrap = [];
|
|
@@ -32,7 +32,7 @@ const pipe = (first, ...pipeable) => {
|
|
|
32
32
|
};
|
|
33
33
|
let handler, wrappedFirst = wrapEffect(first, handlePipeError);
|
|
34
34
|
for (pipe$1 of pipeable) {
|
|
35
|
-
handler = pipe$1[
|
|
35
|
+
handler = pipe$1[ERROR];
|
|
36
36
|
if (isFunction(handler)) wrap.push(handler);
|
|
37
37
|
if (isFunction(pipe$1) || isFunction(pipe$1[RUN])) run$1.push(pipe$1);
|
|
38
38
|
}
|
|
@@ -45,9 +45,9 @@ const pipe = (first, ...pipeable) => {
|
|
|
45
45
|
return next;
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
|
-
var
|
|
48
|
+
var Effect = class {
|
|
49
49
|
[RUN];
|
|
50
|
-
[
|
|
50
|
+
[ERROR];
|
|
51
51
|
constructor(run$1, catchSome$1) {
|
|
52
52
|
this[RUN] = catchSome$1 && run$1 ? (signal) => {
|
|
53
53
|
let res;
|
|
@@ -63,13 +63,38 @@ var EffectImpl = class {
|
|
|
63
63
|
return handleError(cause);
|
|
64
64
|
}
|
|
65
65
|
} : run$1;
|
|
66
|
-
this[
|
|
66
|
+
this[ERROR] = catchSome$1;
|
|
67
67
|
}
|
|
68
68
|
*[SYMBOL.iterator]() {
|
|
69
69
|
return yield this;
|
|
70
70
|
}
|
|
71
71
|
pipe = (...pipeable) => pipe(this, ...pipeable);
|
|
72
72
|
};
|
|
73
|
+
var WrapEffect = class {
|
|
74
|
+
[RUN];
|
|
75
|
+
[ERROR];
|
|
76
|
+
[REMOVE];
|
|
77
|
+
constructor(run$1, catchSome$1) {
|
|
78
|
+
this[RUN] = catchSome$1 && run$1 ? (signal) => {
|
|
79
|
+
let res;
|
|
80
|
+
let handleError = (cause) => {
|
|
81
|
+
const recovery = catchSome$1(cause);
|
|
82
|
+
if (recovery) return recovery[RUN]?.(signal);
|
|
83
|
+
throw cause;
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
res = run$1(signal);
|
|
87
|
+
return isFunction(res?.then) ? res.then(void 0, handleError) : res;
|
|
88
|
+
} catch (cause) {
|
|
89
|
+
return handleError(cause);
|
|
90
|
+
}
|
|
91
|
+
} : run$1;
|
|
92
|
+
this[ERROR] = catchSome$1;
|
|
93
|
+
}
|
|
94
|
+
*[SYMBOL.iterator]() {
|
|
95
|
+
return yield this;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
73
98
|
|
|
74
99
|
//#endregion
|
|
75
|
-
export { run as _run, run,
|
|
100
|
+
export { Effect, WrapEffect, run as _run, run, catchSome, fail, fn, gen, pipe, succeed };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Effect } from "./mini-effect.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
export * from "valibot";
|
|
4
|
+
|
|
5
|
+
//#region src/schema.d.ts
|
|
6
|
+
type Schema = v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>> | v.BaseSchemaAsync<unknown, unknown, v.BaseIssue<unknown>>;
|
|
7
|
+
declare const validate: <const S extends Schema>(schema: S) => (value: unknown) => Effect<v.InferOutput<S>, never>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { Schema, validate };
|
package/dist/schema.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { catchSome, fail, fn } from "./mini-effect.mjs";
|
|
2
|
+
import { failure } from "./tag.mjs";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
|
|
5
|
+
export * from "valibot"
|
|
6
|
+
|
|
7
|
+
//#region src/schema.ts
|
|
8
|
+
const FaildToValidateError = failure("FailedToValidate");
|
|
9
|
+
const validate = (schema) => (value) => fn(() => v.parseAsync(schema, value)).pipe(catchSome((cause) => fail(new FaildToValidateError({ cause }))));
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { validate };
|
package/dist/tag.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Effect, WrapEffect, inferError, inferReturn } from "./mini-effect.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tag.d.ts
|
|
4
|
+
type Failure<T extends string> = {
|
|
5
|
+
__tag: T;
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
};
|
|
8
|
+
type FailureConstructor<T extends string> = {
|
|
9
|
+
new (options?: ErrorOptions): Failure<T>;
|
|
10
|
+
};
|
|
11
|
+
declare const failure: <T extends string>(tag: T) => FailureConstructor<T>;
|
|
12
|
+
declare const catchTags: <CE extends string, E extends Effect>(handlers: Record<CE, (cause: unknown) => E | undefined>) => WrapEffect<inferReturn<E>, inferError<E>, Failure<CE>>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { Failure, FailureConstructor, catchTags, failure };
|
package/dist/tag.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { catchSome } from "./mini-effect.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tag.ts
|
|
4
|
+
const failure = (tag) => class Failure extends Error {
|
|
5
|
+
__tag = tag;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(tag, options);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const catchTags = (handlers) => catchSome((cause) => {
|
|
11
|
+
let tag = cause?.__tag;
|
|
12
|
+
return typeof tag === "string" ? Object.entries(handlers).find(([t, h]) => t === tag)?.[1]?.(cause) : void 0;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { catchTags, failure };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mini-effect",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.1",
|
|
5
5
|
"description": "A mini-effect library for TypeScript.",
|
|
6
6
|
"author": "Jacob Ebey <jacob.ebey@live.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
},
|
|
16
16
|
"exports": {
|
|
17
17
|
"./concurrency": "./dist/concurrency.mjs",
|
|
18
|
+
"./fetch": "./dist/fetch.mjs",
|
|
18
19
|
"./mini-effect": "./dist/mini-effect.mjs",
|
|
20
|
+
"./schema": "./dist/schema.mjs",
|
|
21
|
+
"./tag": "./dist/tag.mjs",
|
|
19
22
|
"./package.json": "./package.json"
|
|
20
23
|
},
|
|
21
24
|
"main": "./dist/index.mjs",
|
|
@@ -33,7 +36,10 @@
|
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
38
|
"@types/node": "^25.2.3",
|
|
36
|
-
"tsdown": "
|
|
39
|
+
"tsdown": "~0.18.4",
|
|
37
40
|
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"valibot": "^1.2.0"
|
|
38
44
|
}
|
|
39
45
|
}
|