@zemnmez/future 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/future.d.ts +69 -0
- package/future.d.ts.map +1 -0
- package/future.js +81 -0
- package/future.js.map +1 -0
- package/index.d.ts +88 -0
- package/package.json +32 -3
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# `@zemnmez/future`
|
|
2
|
+
|
|
3
|
+
`@zemnmez/future` is a tiny TypeScript type for a value that may be loading,
|
|
4
|
+
resolved, or errored. A `Future<Then, Loading, Error>` represents all three
|
|
5
|
+
states exactly and requires each state to be handled before its resolved value
|
|
6
|
+
can be used.
|
|
7
|
+
|
|
8
|
+
Like [`@zemnmez/result`][result], `Future` is implemented purely functionally.
|
|
9
|
+
Advanced JavaScript and TypeScript compilers can inline its implementation, so
|
|
10
|
+
no class names, property names, discriminants, or symbols identifying a Future
|
|
11
|
+
need to remain in a minified bundle.
|
|
12
|
+
|
|
13
|
+
## React Query
|
|
14
|
+
|
|
15
|
+
`useQueryFuture` converts a React Query result into a `Future`. This is useful
|
|
16
|
+
when a component should call all of its hooks unconditionally and select its
|
|
17
|
+
loading, error, or resolved UI only after those hooks have run:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useQuery } from '@tanstack/react-query';
|
|
21
|
+
import { future, useQueryFuture } from '@zemnmez/future';
|
|
22
|
+
|
|
23
|
+
interface Todo {
|
|
24
|
+
id: number;
|
|
25
|
+
title: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TodoList() {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
const todos = useQueryFuture(
|
|
31
|
+
useQuery<Todo[], Error>({
|
|
32
|
+
queryKey: ['todos'],
|
|
33
|
+
queryFn: async () => {
|
|
34
|
+
const response = await fetch('/api/todos');
|
|
35
|
+
if (!response.ok) throw new Error('Could not load todos.');
|
|
36
|
+
return response.json();
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return future(
|
|
42
|
+
todos,
|
|
43
|
+
todos => (
|
|
44
|
+
<ul className={theme.todoList}>
|
|
45
|
+
{todos.map(todo => (
|
|
46
|
+
<li key={todo.id}>{todo.title}</li>
|
|
47
|
+
))}
|
|
48
|
+
</ul>
|
|
49
|
+
),
|
|
50
|
+
() => <p>Loading todos…</p>,
|
|
51
|
+
error => <p className={theme.error}>{error.message}</p>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
Construct a Future with `resolve`, `loading`, or `error`, then handle its three
|
|
59
|
+
possible states with `future`:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Future, future, resolve } from '@zemnmez/future';
|
|
63
|
+
|
|
64
|
+
const answer: Future<number, number, Error> = resolve(42);
|
|
65
|
+
const message = future(
|
|
66
|
+
answer,
|
|
67
|
+
value => `The answer is ${value}.`,
|
|
68
|
+
progress => `Loading: ${progress}%`,
|
|
69
|
+
error => `Failed: ${error.message}`
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`future_and_then` maps a resolved value while preserving the loading and error
|
|
74
|
+
types. `future_flatten_then` flattens nested Futures, and `future_collect`
|
|
75
|
+
combines several Futures into one.
|
|
76
|
+
|
|
77
|
+
[result]: https://www.npmjs.com/package/@zemnmez/result
|
package/future.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
/**
|
|
3
|
+
* A value which hasn't arrived yet.
|
|
4
|
+
*/
|
|
5
|
+
export type Future<Then, Loading, Error> = <T1, T2, T3>(
|
|
6
|
+
/**
|
|
7
|
+
* Executed when the {@link Future} succeeds.
|
|
8
|
+
*/
|
|
9
|
+
then: (value: Then) => T1,
|
|
10
|
+
/**
|
|
11
|
+
* Executed when the {@link Future} is loading.
|
|
12
|
+
*/
|
|
13
|
+
loading: (value: Loading) => T2,
|
|
14
|
+
/**
|
|
15
|
+
* Executed when the {@link Future} errors.
|
|
16
|
+
*/
|
|
17
|
+
error: (value: Error) => T3) => T1 | T2 | T3;
|
|
18
|
+
/**
|
|
19
|
+
* A {@link Future} that is never loading
|
|
20
|
+
* and never errors.
|
|
21
|
+
*/
|
|
22
|
+
export declare const resolve: <Then, Loading = never, Error = never>(then_value: Then) => Future<Then, Loading, Error>;
|
|
23
|
+
/**
|
|
24
|
+
* A {@link Future} that always errors.
|
|
25
|
+
*/
|
|
26
|
+
export declare const error: <Error, Loading = never, Then = never>(error_value: Error) => Future<Then, Loading, Error>;
|
|
27
|
+
/**
|
|
28
|
+
* A {@link Future} that is always loading.
|
|
29
|
+
*/
|
|
30
|
+
export declare const loading: <Loading, Then = never, Error = never>(loading_value: Loading) => Future<Then, Loading, Error>;
|
|
31
|
+
export { error as future_error, loading as future_loading, resolve as future_resolve, };
|
|
32
|
+
/**
|
|
33
|
+
* Convert a React Query result into a {@link Future}.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useQueryFuture<Then, Error>(result: UseQueryResult<Then, Error>): Future<Then, void, Error>;
|
|
36
|
+
/**
|
|
37
|
+
* Execute a {@link Future} with a set of handlers.
|
|
38
|
+
*
|
|
39
|
+
* This is identical to calling the future directly, but
|
|
40
|
+
* may read better.
|
|
41
|
+
*/
|
|
42
|
+
export declare const future: <Then, Loading, Error, T1, T2, T3>(f: Future<Then, Loading, Error>, then: (value: Then) => T1, onLoading: (value: Loading) => T2, onError: (value: Error) => T3) => T1 | T2 | T3;
|
|
43
|
+
/**
|
|
44
|
+
* Modify the contained value of an {@link Future} on success.
|
|
45
|
+
*/
|
|
46
|
+
export declare const future_and_then: <Then, Loading, Error, NewThen>(future: Future<Then, Loading, Error>, f_then: (value: Then) => NewThen) => Future<NewThen, Loading, Error>;
|
|
47
|
+
export declare const future_flatten_then: <Then, Loading1, Error1, Loading2, Error2>(a: Future<Future<Then, Loading1, Error1>, Loading2, Error2>) => Future<Then, Loading1 | Loading2, Error1 | Error2>;
|
|
48
|
+
export declare function future_collect<Then, Loading, Error>(futures: readonly Future<Then, Loading, Error>[]): Future<readonly Then[], Loading, Error>;
|
|
49
|
+
export declare function future_collect_incremental<Then, Loading, Error>(futures: readonly Future<Then, Loading, Error>[]): Future<readonly Then[], Loading, Error>;
|
|
50
|
+
export declare const coincide_then: <Then1, Loading1, Error1, Then2, Loading2, Error2, NewThen>(future1: Future<Then1, Loading1, Error1>, future2: Future<Then2, Loading2, Error2>, then: (a: Then1, b: Then2) => NewThen) => Future<NewThen, Loading1 | Loading2, Error1 | Error2>;
|
|
51
|
+
/**
|
|
52
|
+
* Declare that a {@link Future} depends on another {@link Future}.
|
|
53
|
+
*
|
|
54
|
+
* This can be used when it's not possible to {@link future_and_then} pipeline
|
|
55
|
+
* the future but they're co-dependent, such as when using useQuery (which
|
|
56
|
+
* cannot have functions returned if using `localStorage`).
|
|
57
|
+
*
|
|
58
|
+
* If you *don't* use this function to declare the dependency,
|
|
59
|
+
* then the child future will often show a loading state if
|
|
60
|
+
* the parent future is in error.
|
|
61
|
+
*
|
|
62
|
+
* (1) if the child has loaded then child is always returned
|
|
63
|
+
* (2) if child is in error and parent is in error,
|
|
64
|
+
* then parent error is returned
|
|
65
|
+
* usw
|
|
66
|
+
*/
|
|
67
|
+
export declare function future_declare_dependency<T1, T2, L1, L2, E1, E2>(parent: Future<T1, L1, E1>, child: Future<T2, L2, E2>): Future<T2, L1 | L2, E1 | E2>;
|
|
68
|
+
export declare function future_or_else<T, L, E, E2>(fut: Future<T, L, E>, f: (e: E) => E2): Future<T, L, E2>;
|
|
69
|
+
//# sourceMappingURL=future.d.ts.map
|
package/future.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"future.d.ts","sourceRoot":"","sources":["future.ts"],"names":[],"mappings":"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"}
|
package/future.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link Future} that is never loading
|
|
3
|
+
* and never errors.
|
|
4
|
+
*/ export const resolve = (then_value)=>(then, _loading, _error)=>then(then_value);
|
|
5
|
+
/**
|
|
6
|
+
* A {@link Future} that always errors.
|
|
7
|
+
*/ export const error = (error_value)=>(_then, _loading, error)=>error(error_value);
|
|
8
|
+
/**
|
|
9
|
+
* A {@link Future} that is always loading.
|
|
10
|
+
*/ export const loading = (loading_value)=>(_then, loading, _error)=>loading(loading_value);
|
|
11
|
+
export { error as future_error, loading as future_loading, resolve as future_resolve };
|
|
12
|
+
/**
|
|
13
|
+
* Convert a React Query result into a {@link Future}.
|
|
14
|
+
*/ export function useQueryFuture(result) {
|
|
15
|
+
switch(result.status){
|
|
16
|
+
case 'success':
|
|
17
|
+
return resolve(result.data);
|
|
18
|
+
case 'pending':
|
|
19
|
+
return loading(undefined);
|
|
20
|
+
case 'error':
|
|
21
|
+
return error(result.error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Execute a {@link Future} with a set of handlers.
|
|
26
|
+
*
|
|
27
|
+
* This is identical to calling the future directly, but
|
|
28
|
+
* may read better.
|
|
29
|
+
*/ export const future = (f, then, onLoading, onError)=>f(then, onLoading, onError);
|
|
30
|
+
/**
|
|
31
|
+
* Modify the contained value of an {@link Future} on success.
|
|
32
|
+
*/ export const future_and_then = (future, f_then)=>future((actual)=>resolve(f_then(actual)), (l)=>loading(l), (e)=>error(e));
|
|
33
|
+
export const future_flatten_then = (a)=>a((then1)=>then1((then)=>resolve(then), (loading2)=>loading(loading2), (error2)=>error(error2)), (loading1)=>loading(loading1), (error1)=>error(error1));
|
|
34
|
+
export function future_collect(futures) {
|
|
35
|
+
return futures.reduce((collected, next)=>coincide_then(collected, next, (values, value)=>[
|
|
36
|
+
...values,
|
|
37
|
+
value
|
|
38
|
+
]), resolve([]));
|
|
39
|
+
}
|
|
40
|
+
export function future_collect_incremental(futures) {
|
|
41
|
+
const values = [];
|
|
42
|
+
const loadings = [];
|
|
43
|
+
const errors = [];
|
|
44
|
+
for (const future of futures){
|
|
45
|
+
future((value)=>values.push(value), (value)=>loadings.push(value), (value)=>errors.push(value));
|
|
46
|
+
}
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
return error(errors[0]);
|
|
49
|
+
}
|
|
50
|
+
if (values.length > 0) {
|
|
51
|
+
return resolve(values);
|
|
52
|
+
}
|
|
53
|
+
if (loadings.length > 0) {
|
|
54
|
+
return loading(loadings[0]);
|
|
55
|
+
}
|
|
56
|
+
return resolve(values);
|
|
57
|
+
}
|
|
58
|
+
export const coincide_then = (future1, future2, then)=>future1((then1)=>future2((then2)=>resolve(then(then1, then2)), (loading2)=>loading(loading2), (error2)=>error(error2)), (loading1)=>future2(()=>loading(loading1), ()=>loading(loading1), (error2)=>error(error2)), (error1)=>future2(()=>error(error1), ()=>error(error1), ()=>error(error1)));
|
|
59
|
+
/**
|
|
60
|
+
* Declare that a {@link Future} depends on another {@link Future}.
|
|
61
|
+
*
|
|
62
|
+
* This can be used when it's not possible to {@link future_and_then} pipeline
|
|
63
|
+
* the future but they're co-dependent, such as when using useQuery (which
|
|
64
|
+
* cannot have functions returned if using `localStorage`).
|
|
65
|
+
*
|
|
66
|
+
* If you *don't* use this function to declare the dependency,
|
|
67
|
+
* then the child future will often show a loading state if
|
|
68
|
+
* the parent future is in error.
|
|
69
|
+
*
|
|
70
|
+
* (1) if the child has loaded then child is always returned
|
|
71
|
+
* (2) if child is in error and parent is in error,
|
|
72
|
+
* then parent error is returned
|
|
73
|
+
* usw
|
|
74
|
+
*/ export function future_declare_dependency(parent, child) {
|
|
75
|
+
return parent(()=>child((child_then)=>resolve(child_then), (child_loading)=>loading(child_loading), (child_error)=>error(child_error)), (parent_loading)=>child((child_then)=>resolve(child_then), ()=>loading(parent_loading), ()=>loading(parent_loading)), (parent_error)=>child((child_then)=>resolve(child_then), (child_loading)=>loading(child_loading), ()=>error(parent_error)));
|
|
76
|
+
}
|
|
77
|
+
export function future_or_else(fut, f) {
|
|
78
|
+
return fut((t)=>resolve(t), (l)=>loading(l), (e)=>error(f(e)));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=future.js.map
|
package/future.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["future.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\n\n/**\n * A value which hasn't arrived yet.\n */\nexport type Future<Then, Loading, Error> = <T1, T2, T3>(\n\t/**\n\t * Executed when the {@link Future} succeeds.\n\t */\n\tthen: (value: Then) => T1,\n\t/**\n\t * Executed when the {@link Future} is loading.\n\t */\n\tloading: (value: Loading) => T2,\n\t/**\n\t * Executed when the {@link Future} errors.\n\t */\n\terror: (value: Error) => T3\n) => T1 | T2 | T3;\n\n/**\n * A {@link Future} that is never loading\n * and never errors.\n */\nexport const resolve =\n\t<Then, Loading = never, Error = never>(\n\t\tthen_value: Then\n\t): Future<Then, Loading, Error> =>\n\t(then, _loading, _error) =>\n\t\tthen(then_value);\n\n/**\n * A {@link Future} that always errors.\n */\nexport const error =\n\t<Error, Loading = never, Then = never>(\n\t\terror_value: Error\n\t): Future<Then, Loading, Error> =>\n\t(_then, _loading, error) =>\n\t\terror(error_value);\n\n/**\n * A {@link Future} that is always loading.\n */\nexport const loading =\n\t<Loading, Then = never, Error = never>(\n\t\tloading_value: Loading\n\t): Future<Then, Loading, Error> =>\n\t(_then, loading, _error) =>\n\t\tloading(loading_value);\n\nexport {\n\terror as future_error,\n\tloading as future_loading,\n\tresolve as future_resolve,\n};\n\n/**\n * Convert a React Query result into a {@link Future}.\n */\nexport function useQueryFuture<Then, Error>(\n\tresult: UseQueryResult<Then, Error>\n): Future<Then, void, Error> {\n\tswitch (result.status) {\n\t\tcase 'success':\n\t\t\treturn resolve(result.data);\n\t\tcase 'pending':\n\t\t\treturn loading(undefined);\n\t\tcase 'error':\n\t\t\treturn error(result.error);\n\t}\n}\n\n/**\n * Execute a {@link Future} with a set of handlers.\n *\n * This is identical to calling the future directly, but\n * may read better.\n */\nexport const future = <Then, Loading, Error, T1, T2, T3>(\n\tf: Future<Then, Loading, Error>,\n\tthen: (value: Then) => T1,\n\tonLoading: (value: Loading) => T2,\n\tonError: (value: Error) => T3\n): T1 | T2 | T3 => f(then, onLoading, onError);\n\n/**\n * Modify the contained value of an {@link Future} on success.\n */\nexport const future_and_then = <Then, Loading, Error, NewThen>(\n\tfuture: Future<Then, Loading, Error>,\n\tf_then: (value: Then) => NewThen\n): Future<NewThen, Loading, Error> =>\n\tfuture(\n\t\tactual => resolve(f_then(actual)),\n\t\tl => loading(l),\n\t\te => error(e)\n\t);\n\nexport const future_flatten_then = <Then, Loading1, Error1, Loading2, Error2>(\n\ta: Future<Future<Then, Loading1, Error1>, Loading2, Error2>\n): Future<Then, Loading1 | Loading2, Error1 | Error2> =>\n\ta(\n\t\tthen1 =>\n\t\t\tthen1(\n\t\t\t\tthen => resolve(then),\n\t\t\t\tloading2 => loading(loading2),\n\t\t\t\terror2 => error(error2)\n\t\t\t),\n\t\tloading1 => loading(loading1),\n\t\terror1 => error(error1)\n\t);\n\nexport function future_collect<Then, Loading, Error>(\n\tfutures: readonly Future<Then, Loading, Error>[]\n): Future<readonly Then[], Loading, Error> {\n\treturn futures.reduce<Future<readonly Then[], Loading, Error>>(\n\t\t(collected, next) =>\n\t\t\tcoincide_then(collected, next, (values, value) => [\n\t\t\t\t...values,\n\t\t\t\tvalue,\n\t\t\t]),\n\t\tresolve([])\n\t);\n}\n\nexport function future_collect_incremental<Then, Loading, Error>(\n\tfutures: readonly Future<Then, Loading, Error>[]\n): Future<readonly Then[], Loading, Error> {\n\tconst values: Then[] = [];\n\tconst loadings: Loading[] = [];\n\tconst errors: Error[] = [];\n\n\tfor (const future of futures) {\n\t\tfuture(\n\t\t\tvalue => values.push(value),\n\t\t\tvalue => loadings.push(value),\n\t\t\tvalue => errors.push(value)\n\t\t);\n\t}\n\n\tif (errors.length > 0) {\n\t\treturn error(errors[0]!);\n\t}\n\n\tif (values.length > 0) {\n\t\treturn resolve(values);\n\t}\n\n\tif (loadings.length > 0) {\n\t\treturn loading(loadings[0]!);\n\t}\n\n\treturn resolve(values);\n}\n\nexport const coincide_then = <\n\tThen1,\n\tLoading1,\n\tError1,\n\tThen2,\n\tLoading2,\n\tError2,\n\tNewThen,\n>(\n\tfuture1: Future<Then1, Loading1, Error1>,\n\tfuture2: Future<Then2, Loading2, Error2>,\n\tthen: (a: Then1, b: Then2) => NewThen\n): Future<NewThen, Loading1 | Loading2, Error1 | Error2> =>\n\tfuture1(\n\t\tthen1 =>\n\t\t\tfuture2(\n\t\t\t\tthen2 => resolve(then(then1, then2)),\n\t\t\t\tloading2 => loading(loading2),\n\t\t\t\terror2 => error(error2)\n\t\t\t),\n\t\tloading1 =>\n\t\t\tfuture2(\n\t\t\t\t() => loading(loading1),\n\t\t\t\t() => loading(loading1),\n\t\t\t\terror2 => error(error2)\n\t\t\t),\n\t\terror1 =>\n\t\t\tfuture2(\n\t\t\t\t() => error(error1),\n\t\t\t\t() => error(error1),\n\t\t\t\t() => error(error1)\n\t\t\t)\n\t);\n\n/**\n * Declare that a {@link Future} depends on another {@link Future}.\n *\n * This can be used when it's not possible to {@link future_and_then} pipeline\n * the future but they're co-dependent, such as when using useQuery (which\n * cannot have functions returned if using `localStorage`).\n *\n * If you *don't* use this function to declare the dependency,\n * then the child future will often show a loading state if\n * the parent future is in error.\n *\n * (1)\tif the child has loaded then child is always returned\n * (2)\tif child is in error and parent is in error,\n * \t\tthen parent error is returned\n * usw\n */\nexport function future_declare_dependency<T1, T2, L1, L2, E1, E2>(\n\tparent: Future<T1, L1, E1>,\n\tchild: Future<T2, L2, E2>\n): Future<T2, L1 | L2, E1 | E2> {\n\treturn parent(\n\t\t(/*parent_then*/) =>\n\t\t\tchild(\n\t\t\t\tchild_then => resolve(child_then),\n\t\t\t\tchild_loading => loading(child_loading),\n\t\t\t\tchild_error => error(child_error)\n\t\t\t),\n\t\tparent_loading =>\n\t\t\tchild(\n\t\t\t\tchild_then => resolve(child_then),\n\t\t\t\t(/*child_loading*/) => loading(parent_loading),\n\t\t\t\t(/*child_error*/) => loading(parent_loading)\n\t\t\t),\n\t\tparent_error =>\n\t\t\tchild(\n\t\t\t\tchild_then => resolve(child_then),\n\t\t\t\tchild_loading => loading(child_loading),\n\t\t\t\t(/*child_error*/) => error(parent_error)\n\t\t\t)\n\t);\n}\n\nexport function future_or_else<T, L, E, E2>(\n\tfut: Future<T, L, E>,\n\tf: (e: E) => E2\n): Future<T, L, E2> {\n\treturn fut(\n\t\tt => resolve(t),\n\t\tl => loading(l),\n\t\te => error(f(e))\n\t);\n}\n"],"names":["resolve","then_value","then","_loading","_error","error","error_value","_then","loading","loading_value","future_error","future_loading","future_resolve","useQueryFuture","result","status","data","undefined","future","f","onLoading","onError","future_and_then","f_then","actual","l","e","future_flatten_then","a","then1","loading2","error2","loading1","error1","future_collect","futures","reduce","collected","next","coincide_then","values","value","future_collect_incremental","loadings","errors","push","length","future1","future2","then2","future_declare_dependency","parent","child","child_then","child_loading","child_error","parent_loading","parent_error","future_or_else","fut","t"],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
|
|
3
|
+
export declare const coincide_then: <Then1, Loading1, Error1, Then2, Loading2, Error2, NewThen>(future1: Future<Then1, Loading1, Error1>, future2: Future<Then2, Loading2, Error2>, then: (a: Then1, b: Then2) => NewThen) => Future<NewThen, Loading1 | Loading2, Error1 | Error2>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A {@link Future} that always errors.
|
|
7
|
+
*/
|
|
8
|
+
declare const error: <Error, Loading = never, Then = never>(error_value: Error) => Future<Then, Loading, Error>;
|
|
9
|
+
export { error }
|
|
10
|
+
export { error as future_error }
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A value which hasn't arrived yet.
|
|
14
|
+
*/
|
|
15
|
+
export declare type Future<Then, Loading, Error> = <T1, T2, T3>(
|
|
16
|
+
/**
|
|
17
|
+
* Executed when the {@link Future} succeeds.
|
|
18
|
+
*/
|
|
19
|
+
then: (value: Then) => T1,
|
|
20
|
+
/**
|
|
21
|
+
* Executed when the {@link Future} is loading.
|
|
22
|
+
*/
|
|
23
|
+
loading: (value: Loading) => T2,
|
|
24
|
+
/**
|
|
25
|
+
* Executed when the {@link Future} errors.
|
|
26
|
+
*/
|
|
27
|
+
error: (value: Error) => T3) => T1 | T2 | T3;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute a {@link Future} with a set of handlers.
|
|
31
|
+
*
|
|
32
|
+
* This is identical to calling the future directly, but
|
|
33
|
+
* may read better.
|
|
34
|
+
*/
|
|
35
|
+
export declare const future: <Then, Loading, Error, T1, T2, T3>(f: Future<Then, Loading, Error>, then: (value: Then) => T1, onLoading: (value: Loading) => T2, onError: (value: Error) => T3) => T1 | T2 | T3;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Modify the contained value of an {@link Future} on success.
|
|
39
|
+
*/
|
|
40
|
+
export declare const future_and_then: <Then, Loading, Error, NewThen>(future: Future<Then, Loading, Error>, f_then: (value: Then) => NewThen) => Future<NewThen, Loading, Error>;
|
|
41
|
+
|
|
42
|
+
export declare function future_collect<Then, Loading, Error>(futures: readonly Future<Then, Loading, Error>[]): Future<readonly Then[], Loading, Error>;
|
|
43
|
+
|
|
44
|
+
export declare function future_collect_incremental<Then, Loading, Error>(futures: readonly Future<Then, Loading, Error>[]): Future<readonly Then[], Loading, Error>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Declare that a {@link Future} depends on another {@link Future}.
|
|
48
|
+
*
|
|
49
|
+
* This can be used when it's not possible to {@link future_and_then} pipeline
|
|
50
|
+
* the future but they're co-dependent, such as when using useQuery (which
|
|
51
|
+
* cannot have functions returned if using `localStorage`).
|
|
52
|
+
*
|
|
53
|
+
* If you *don't* use this function to declare the dependency,
|
|
54
|
+
* then the child future will often show a loading state if
|
|
55
|
+
* the parent future is in error.
|
|
56
|
+
*
|
|
57
|
+
* (1) if the child has loaded then child is always returned
|
|
58
|
+
* (2) if child is in error and parent is in error,
|
|
59
|
+
* then parent error is returned
|
|
60
|
+
* usw
|
|
61
|
+
*/
|
|
62
|
+
export declare function future_declare_dependency<T1, T2, L1, L2, E1, E2>(parent: Future<T1, L1, E1>, child: Future<T2, L2, E2>): Future<T2, L1 | L2, E1 | E2>;
|
|
63
|
+
|
|
64
|
+
export declare const future_flatten_then: <Then, Loading1, Error1, Loading2, Error2>(a: Future<Future<Then, Loading1, Error1>, Loading2, Error2>) => Future<Then, Loading1 | Loading2, Error1 | Error2>;
|
|
65
|
+
|
|
66
|
+
export declare function future_or_else<T, L, E, E2>(fut: Future<T, L, E>, f: (e: E) => E2): Future<T, L, E2>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A {@link Future} that is always loading.
|
|
70
|
+
*/
|
|
71
|
+
declare const loading: <Loading, Then = never, Error = never>(loading_value: Loading) => Future<Then, Loading, Error>;
|
|
72
|
+
export { loading as future_loading }
|
|
73
|
+
export { loading }
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A {@link Future} that is never loading
|
|
77
|
+
* and never errors.
|
|
78
|
+
*/
|
|
79
|
+
declare const resolve: <Then, Loading = never, Error = never>(then_value: Then) => Future<Then, Loading, Error>;
|
|
80
|
+
export { resolve as future_resolve }
|
|
81
|
+
export { resolve }
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convert a React Query result into a {@link Future}.
|
|
85
|
+
*/
|
|
86
|
+
export declare function useQueryFuture<Then, Error>(result: UseQueryResult<Then, Error>): Future<Then, void, Error>;
|
|
87
|
+
|
|
88
|
+
export { }
|
package/package.json
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
2
3
|
"name": "@zemnmez/future",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
4
|
+
"description": "A tiny, purely functional TypeScript type for values that may be loading, resolved, or errored",
|
|
5
|
+
"main": "future.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"author": "zemnmez",
|
|
5
9
|
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"typescript",
|
|
12
|
+
"future",
|
|
13
|
+
"react",
|
|
14
|
+
"react-query",
|
|
15
|
+
"functional"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": false,
|
|
6
18
|
"publishConfig": {
|
|
7
19
|
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"version": "1.0.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@tanstack/react-query": "5.101.4",
|
|
24
|
+
"react": "19.2.8"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "24.13.3",
|
|
28
|
+
"@types/react": "18.3.31"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/zemn-me/monorepo.git",
|
|
33
|
+
"directory": "ts/future"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/zemn-me/monorepo/issues/new?title=%2F%2Fts%2Ffuture%401.0.0%3A+something+went+wrong%21"
|
|
8
37
|
}
|
|
9
|
-
}
|
|
38
|
+
}
|