@confect/react 9.0.1 → 9.1.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/CHANGELOG.md +24 -13
- package/dist/OptimisticLocalStore.d.ts +25 -0
- package/dist/OptimisticLocalStore.d.ts.map +1 -0
- package/dist/OptimisticLocalStore.js +39 -0
- package/dist/OptimisticLocalStore.js.map +1 -0
- package/dist/index.d.ts +23 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.src.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/OptimisticLocalStore.ts +70 -0
- package/src/index.ts +79 -13
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/react",
|
|
3
3
|
"description": "Client-side bindings for React apps",
|
|
4
|
-
"version": "9.0
|
|
4
|
+
"version": "9.1.0",
|
|
5
5
|
"author": "RJ Dellecese",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/rjdellecese/confect/issues"
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"convex": "^1.32.0",
|
|
58
58
|
"effect": "^3.21.2",
|
|
59
59
|
"react": "^18.0.0 || ^19.0.0",
|
|
60
|
-
"@confect/core": "^9.0
|
|
60
|
+
"@confect/core": "^9.1.0"
|
|
61
61
|
},
|
|
62
62
|
"repository": {
|
|
63
63
|
"type": "git",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Ref } from "@confect/core";
|
|
2
|
+
import type { OptimisticLocalStore as ConvexOptimisticLocalStore } from "convex/browser";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Confect counterpart to Convex's `OptimisticLocalStore`.
|
|
7
|
+
*
|
|
8
|
+
* Its methods accept Confect query `Ref`s and operate on decoded (Effect
|
|
9
|
+
* Schema) values rather than the encoded values stored by Convex. Query values
|
|
10
|
+
* are wrapped in `Option`: `Option.none()` represents a query that is not
|
|
11
|
+
* present in the store, and `Option.some(value)` carries the decoded value.
|
|
12
|
+
*/
|
|
13
|
+
export interface OptimisticLocalStore {
|
|
14
|
+
getQuery<Query extends Ref.AnyPublicQuery>(
|
|
15
|
+
queryRef: Query,
|
|
16
|
+
...args: Ref.OptionalArgs<Query>
|
|
17
|
+
): Option.Option<Ref.Returns<Query>>;
|
|
18
|
+
|
|
19
|
+
getAllQueries<Query extends Ref.AnyPublicQuery>(
|
|
20
|
+
queryRef: Query,
|
|
21
|
+
): Array<{
|
|
22
|
+
args: Ref.Args<Query>;
|
|
23
|
+
value: Option.Option<Ref.Returns<Query>>;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
setQuery<Query extends Ref.AnyPublicQuery>(
|
|
27
|
+
queryRef: Query,
|
|
28
|
+
args: Ref.Args<Query>,
|
|
29
|
+
value: Option.Option<Ref.Returns<Query>>,
|
|
30
|
+
): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wraps Convex's `OptimisticLocalStore` so that it accepts Confect query `Ref`s
|
|
35
|
+
* and decoded values, handling the encoding/decoding against Convex's store.
|
|
36
|
+
*/
|
|
37
|
+
export const make = (
|
|
38
|
+
convexLocalStore: ConvexOptimisticLocalStore,
|
|
39
|
+
): OptimisticLocalStore => ({
|
|
40
|
+
getQuery: (queryRef, ...rest) => {
|
|
41
|
+
const functionReference = Ref.getFunctionReference(queryRef);
|
|
42
|
+
const args = (rest[0] ?? {}) as Ref.Args<typeof queryRef>;
|
|
43
|
+
const encodedArgs = Ref.encodeArgsSync(queryRef, args);
|
|
44
|
+
const encoded = convexLocalStore.getQuery(functionReference, encodedArgs);
|
|
45
|
+
return encoded === undefined
|
|
46
|
+
? Option.none()
|
|
47
|
+
: Option.some(Ref.decodeReturnsSync(queryRef, encoded));
|
|
48
|
+
},
|
|
49
|
+
getAllQueries: (queryRef) => {
|
|
50
|
+
const functionReference = Ref.getFunctionReference(queryRef);
|
|
51
|
+
return convexLocalStore
|
|
52
|
+
.getAllQueries(functionReference)
|
|
53
|
+
.map(({ args, value }) => ({
|
|
54
|
+
args: Ref.decodeArgsSync(queryRef, args),
|
|
55
|
+
value:
|
|
56
|
+
value === undefined
|
|
57
|
+
? Option.none()
|
|
58
|
+
: Option.some(Ref.decodeReturnsSync(queryRef, value)),
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
setQuery: (queryRef, args, value) => {
|
|
62
|
+
const functionReference = Ref.getFunctionReference(queryRef);
|
|
63
|
+
const encodedArgs = Ref.encodeArgsSync(queryRef, args);
|
|
64
|
+
const encodedValue = Option.match(value, {
|
|
65
|
+
onNone: () => undefined,
|
|
66
|
+
onSome: (decoded) => Ref.encodeReturnsSync(queryRef, decoded),
|
|
67
|
+
});
|
|
68
|
+
convexLocalStore.setQuery(functionReference, encodedArgs, encodedValue);
|
|
69
|
+
},
|
|
70
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Ref } from "@confect/core";
|
|
2
|
+
import type { OptimisticUpdate as ConvexOptimisticUpdate } from "convex/browser";
|
|
2
3
|
import {
|
|
3
4
|
useAction as useConvexAction,
|
|
4
5
|
useMutation as useConvexMutation,
|
|
5
6
|
useQuery as useConvexQuery,
|
|
7
|
+
type ReactMutation as ConvexReactMutation,
|
|
6
8
|
} from "convex/react";
|
|
9
|
+
import type { FunctionReference } from "convex/server";
|
|
10
|
+
import type { Value } from "convex/values";
|
|
7
11
|
import * as Cause from "effect/Cause";
|
|
8
12
|
import * as Effect from "effect/Effect";
|
|
9
13
|
import * as Either from "effect/Either";
|
|
@@ -11,9 +15,10 @@ import * as Exit from "effect/Exit";
|
|
|
11
15
|
import * as Option from "effect/Option";
|
|
12
16
|
import { useCallback, useMemo } from "react";
|
|
13
17
|
|
|
18
|
+
import * as OptimisticLocalStore from "./OptimisticLocalStore";
|
|
14
19
|
import * as QueryResult from "./QueryResult";
|
|
15
20
|
|
|
16
|
-
export { QueryResult };
|
|
21
|
+
export { OptimisticLocalStore, QueryResult };
|
|
17
22
|
|
|
18
23
|
export type InvokeReturn<Ref_ extends Ref.Any> = [Ref.Error<Ref_>] extends [
|
|
19
24
|
never,
|
|
@@ -76,7 +81,69 @@ export const useQuery = <Query extends Ref.AnyPublicQuery>(
|
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
/**
|
|
79
|
-
*
|
|
84
|
+
* An optimistic update for a Confect mutation. Mirrors Convex's
|
|
85
|
+
* `OptimisticUpdate`, but receives a Confect {@link OptimisticLocalStore} and
|
|
86
|
+
* the decoded mutation `args`.
|
|
87
|
+
*/
|
|
88
|
+
export type OptimisticUpdate<Mutation extends Ref.AnyPublicMutation> = (
|
|
89
|
+
localStore: OptimisticLocalStore.OptimisticLocalStore,
|
|
90
|
+
args: Ref.Args<Mutation>,
|
|
91
|
+
) => void;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The handle returned by {@link useMutation}. It is callable like the function
|
|
95
|
+
* returned by Convex's `useMutation`, and additionally exposes
|
|
96
|
+
* `withOptimisticUpdate` for attaching an optimistic update. Mirrors the
|
|
97
|
+
* `ReactMutation` type from `convex/react`.
|
|
98
|
+
*/
|
|
99
|
+
export interface ReactMutation<Mutation extends Ref.AnyPublicMutation> {
|
|
100
|
+
(...args: Ref.OptionalArgs<Mutation>): InvokeReturn<Mutation>;
|
|
101
|
+
withOptimisticUpdate(
|
|
102
|
+
optimisticUpdate: OptimisticUpdate<Mutation>,
|
|
103
|
+
): ReactMutation<Mutation>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const makeReactMutation = <Mutation extends Ref.AnyPublicMutation>(
|
|
107
|
+
ref: Mutation,
|
|
108
|
+
convexReactMutation: ConvexReactMutation<
|
|
109
|
+
Ref.FunctionReference<Mutation> & FunctionReference<"mutation">
|
|
110
|
+
>,
|
|
111
|
+
): ReactMutation<Mutation> => {
|
|
112
|
+
const callable = ((...args: Ref.OptionalArgs<Mutation>) =>
|
|
113
|
+
invokeAsEither(
|
|
114
|
+
ref,
|
|
115
|
+
(_, encodedArgs) => convexReactMutation(encodedArgs as never),
|
|
116
|
+
args,
|
|
117
|
+
).then((either) =>
|
|
118
|
+
Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
|
|
119
|
+
)) as (...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>;
|
|
120
|
+
|
|
121
|
+
const withOptimisticUpdate = (
|
|
122
|
+
optimisticUpdate: OptimisticUpdate<Mutation>,
|
|
123
|
+
): ReactMutation<Mutation> => {
|
|
124
|
+
const wrappedUpdate: ConvexOptimisticUpdate<Record<string, Value>> = (
|
|
125
|
+
convexLocalStore,
|
|
126
|
+
encodedArgs,
|
|
127
|
+
) => {
|
|
128
|
+
const decodedArgs = Ref.decodeArgsSync(ref, encodedArgs);
|
|
129
|
+
optimisticUpdate(
|
|
130
|
+
OptimisticLocalStore.make(convexLocalStore),
|
|
131
|
+
decodedArgs,
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
const nextConvexReactMutation =
|
|
135
|
+
convexReactMutation.withOptimisticUpdate(wrappedUpdate);
|
|
136
|
+
return makeReactMutation(ref, nextConvexReactMutation);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return Object.assign(callable, { withOptimisticUpdate });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns a {@link ReactMutation} handle for the provided `Ref`'s mutation. The
|
|
144
|
+
* handle is callable to invoke the mutation, and exposes `withOptimisticUpdate`
|
|
145
|
+
* for attaching an optimistic update, mirroring `useMutation` from
|
|
146
|
+
* `convex/react`.
|
|
80
147
|
*
|
|
81
148
|
* If the `Ref` declares an `error` schema, the returned promise resolves to an
|
|
82
149
|
* `Either` with the decoded `returns` value on the right and the decoded error
|
|
@@ -90,20 +157,19 @@ export const useQuery = <Query extends Ref.AnyPublicQuery>(
|
|
|
90
157
|
*/
|
|
91
158
|
export const useMutation = <Mutation extends Ref.AnyPublicMutation>(
|
|
92
159
|
ref: Mutation,
|
|
93
|
-
):
|
|
160
|
+
): ReactMutation<Mutation> => {
|
|
94
161
|
const functionReference = Ref.getFunctionReference(ref);
|
|
95
|
-
const
|
|
162
|
+
const convexReactMutation = useConvexMutation(functionReference);
|
|
96
163
|
|
|
97
|
-
return
|
|
98
|
-
(
|
|
99
|
-
|
|
164
|
+
return useMemo(
|
|
165
|
+
() =>
|
|
166
|
+
makeReactMutation(
|
|
100
167
|
ref,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
[ref, actualMutation],
|
|
168
|
+
convexReactMutation as ConvexReactMutation<
|
|
169
|
+
Ref.FunctionReference<Mutation> & FunctionReference<"mutation">
|
|
170
|
+
>,
|
|
171
|
+
),
|
|
172
|
+
[ref, convexReactMutation],
|
|
107
173
|
);
|
|
108
174
|
};
|
|
109
175
|
|