@confect/react 9.0.0-next.5 → 9.0.0-next.7
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 +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -13
- package/dist/index.js.map +1 -1
- package/package.json +45 -40
- package/src/index.ts +62 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @confect/react
|
|
2
2
|
|
|
3
|
+
## 9.0.0-next.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5d19484: Stabilize the identity of `@confect/react` hook results across renders.
|
|
8
|
+
|
|
9
|
+
`useQuery` previously decoded and wrapped the Convex result on every render, handing consumers a brand new `QueryResult` even when the underlying Convex data was unchanged. Effects and memoization that depend on the result's identity (e.g. `useEffect(..., [user])` derived via `QueryResult.match`) would re-run on every render, which could escalate into `Maximum update depth exceeded`. The decoded `QueryResult` is now memoized by the (referentially stable) Convex result, so unchanged data keeps a stable identity.
|
|
10
|
+
|
|
11
|
+
`useMutation` and `useAction` now return a stable callback via `useCallback`, matching the identity contract of Convex's own hooks, instead of allocating a fresh function each render.
|
|
12
|
+
|
|
13
|
+
`Ref.getFunctionReference` now caches the Convex function reference by function name, so repeated calls for the same ref return the same reference.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [5d19484]
|
|
16
|
+
- @confect/core@9.0.0-next.7
|
|
17
|
+
|
|
18
|
+
## 9.0.0-next.6
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [46045a9]
|
|
23
|
+
- Updated dependencies [762f7eb]
|
|
24
|
+
- @confect/core@9.0.0-next.6
|
|
25
|
+
|
|
3
26
|
## 9.0.0-next.5
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;KAaY,YAAA,cAA0B,GAAA,CAAI,GAAA,KAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,qBAGxD,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,IAAA,KACpB,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,OAAA,CAAQ,IAAA,GAAO,GAAA,CAAI,KAAA,CAAM,IAAA;AAAA,KAElD,YAAA,eAA2B,GAAA,CAAI,cAAA,UAC5B,GAAA,CAAI,IAAA,CAAK,KAAA,mBACV,IAAA,GAAO,GAAA,CAAI,IAAA,CAAK,KAAA,eAChB,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,KAAA;AAAA,cAET,QAAA,iBAA0B,GAAA,CAAI,cAAA,EACzC,GAAA,EAAK,KAAA,KACF,IAAA,EAAM,YAAA,CAAa,KAAA,MACrB,WAAA,CAAwB,GAAA,CAAI,OAAA,CAAQ,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,KAAA;;;;;;;;;;;;;;cA2D5C,WAAA,oBAAgC,GAAA,CAAI,iBAAA,EAC/C,GAAA,EAAK,QAAA,UACC,IAAA,EAAM,GAAA,CAAI,YAAA,CAAa,QAAA,MAAc,YAAA,CAAa,QAAA;;;;;;;;;;;;;;cA8B7C,SAAA,kBAA4B,GAAA,CAAI,eAAA,EAC3C,GAAA,EAAK,MAAA,UACC,IAAA,EAAM,GAAA,CAAI,YAAA,CAAa,MAAA,MAAY,YAAA,CAAa,MAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,23 +2,29 @@ import { QueryResult_exports, fail, load, succeed } from "./QueryResult.js";
|
|
|
2
2
|
import { Ref } from "@confect/core";
|
|
3
3
|
import { useAction as useAction$1, useMutation as useMutation$1, useQuery as useQuery$1 } from "convex/react";
|
|
4
4
|
import { Cause, Effect, Either, Exit, Option } from "effect";
|
|
5
|
+
import { useCallback, useMemo } from "react";
|
|
5
6
|
|
|
6
7
|
//#region src/index.ts
|
|
7
8
|
const useQuery = (ref, ...rest) => {
|
|
8
9
|
const functionReference = Ref.getFunctionReference(ref);
|
|
9
10
|
const args = rest[0];
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const skipped = args === "skip";
|
|
12
|
+
const encodedArgs = skipped ? "skip" : Ref.encodeArgsSync(ref, args ?? {});
|
|
13
|
+
const encodedReturnsOrError = Either.try(() => useQuery$1(functionReference, encodedArgs));
|
|
14
|
+
return useMemo(() => Either.match(encodedReturnsOrError, {
|
|
15
|
+
onRight: (encodedReturnsOrUndefined) => encodedReturnsOrUndefined === void 0 ? load(skipped) : succeed(Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined)),
|
|
16
|
+
onLeft: (error) => {
|
|
17
|
+
if (Ref.isConvexError(error)) {
|
|
18
|
+
const decoded = Ref.decodeErrorSync(ref, error.data);
|
|
19
|
+
if (Option.isSome(decoded)) return fail(decoded.value);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
19
22
|
}
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
}), [
|
|
24
|
+
ref,
|
|
25
|
+
skipped,
|
|
26
|
+
Either.merge(encodedReturnsOrError)
|
|
27
|
+
]);
|
|
22
28
|
};
|
|
23
29
|
/**
|
|
24
30
|
* Returns a function that invokes the provided `Ref`'s mutation.
|
|
@@ -35,7 +41,7 @@ const useQuery = (ref, ...rest) => {
|
|
|
35
41
|
*/
|
|
36
42
|
const useMutation = (ref) => {
|
|
37
43
|
const actualMutation = useMutation$1(Ref.getFunctionReference(ref));
|
|
38
|
-
return ((...args) => invokeAsEither(ref, (_, encodedArgs) => actualMutation(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either)));
|
|
44
|
+
return useCallback(((...args) => invokeAsEither(ref, (_, encodedArgs) => actualMutation(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either))), [ref, actualMutation]);
|
|
39
45
|
};
|
|
40
46
|
/**
|
|
41
47
|
* Returns a function that invokes the provided `Ref`'s action.
|
|
@@ -52,7 +58,7 @@ const useMutation = (ref) => {
|
|
|
52
58
|
*/
|
|
53
59
|
const useAction = (ref) => {
|
|
54
60
|
const actualAction = useAction$1(Ref.getFunctionReference(ref));
|
|
55
|
-
return ((...args) => invokeAsEither(ref, (_, encodedArgs) => actualAction(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either)));
|
|
61
|
+
return useCallback(((...args) => invokeAsEither(ref, (_, encodedArgs) => actualAction(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either))), [ref, actualAction]);
|
|
56
62
|
};
|
|
57
63
|
const invokeAsEither = async (ref, invoke, args) => {
|
|
58
64
|
const exit = await Effect.runPromiseExit(Ref.runWithCodec(ref, args[0] ?? {}, invoke).pipe(Effect.catchTag("ParseError", Effect.die), Effect.either));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["useConvexQuery","QueryResult.load","QueryResult.succeed","QueryResult.fail","useConvexMutation","useConvexAction"],"sources":["../src/index.ts"],"sourcesContent":["import { Ref } from \"@confect/core\";\nimport {\n useAction as useConvexAction,\n useMutation as useConvexMutation,\n useQuery as useConvexQuery,\n} from \"convex/react\";\nimport { Cause, Effect, Either, Exit, Option } from \"effect\";\n\nimport * as QueryResult from \"./QueryResult\";\n\nexport { QueryResult };\n\nexport type InvokeReturn<Ref_ extends Ref.Any> = [Ref.Error<Ref_>] extends [\n never,\n]\n ? Promise<Ref.Returns<Ref_>>\n : Promise<Either.Either<Ref.Returns<Ref_>, Ref.Error<Ref_>>>;\n\ntype UseQueryArgs<Query extends Ref.AnyPublicQuery> =\n keyof Ref.Args<Query> extends never\n ? [args?: Ref.Args<Query> | \"skip\"]\n : [args: Ref.Args<Query> | \"skip\"];\n\nexport const useQuery = <Query extends Ref.AnyPublicQuery>(\n ref: Query,\n ...rest: UseQueryArgs<Query>\n): QueryResult.QueryResult<Ref.Returns<Query>, Ref.Error<Query>> => {\n const functionReference = Ref.getFunctionReference(ref);\n const args = rest[0];\n const
|
|
1
|
+
{"version":3,"file":"index.js","names":["useConvexQuery","QueryResult.load","QueryResult.succeed","QueryResult.fail","useConvexMutation","useConvexAction"],"sources":["../src/index.ts"],"sourcesContent":["import { Ref } from \"@confect/core\";\nimport {\n useAction as useConvexAction,\n useMutation as useConvexMutation,\n useQuery as useConvexQuery,\n} from \"convex/react\";\nimport { Cause, Effect, Either, Exit, Option } from \"effect\";\nimport { useCallback, useMemo } from \"react\";\n\nimport * as QueryResult from \"./QueryResult\";\n\nexport { QueryResult };\n\nexport type InvokeReturn<Ref_ extends Ref.Any> = [Ref.Error<Ref_>] extends [\n never,\n]\n ? Promise<Ref.Returns<Ref_>>\n : Promise<Either.Either<Ref.Returns<Ref_>, Ref.Error<Ref_>>>;\n\ntype UseQueryArgs<Query extends Ref.AnyPublicQuery> =\n keyof Ref.Args<Query> extends never\n ? [args?: Ref.Args<Query> | \"skip\"]\n : [args: Ref.Args<Query> | \"skip\"];\n\nexport const useQuery = <Query extends Ref.AnyPublicQuery>(\n ref: Query,\n ...rest: UseQueryArgs<Query>\n): QueryResult.QueryResult<Ref.Returns<Query>, Ref.Error<Query>> => {\n const functionReference = Ref.getFunctionReference(ref);\n const args = rest[0];\n const skipped = args === \"skip\";\n const encodedArgs = skipped\n ? \"skip\"\n : Ref.encodeArgsSync(ref, (args ?? {}) as Ref.Args<Query>);\n\n // `useConvexQuery` returns a referentially stable value while the underlying\n // Convex result is unchanged, and throws a stable error when the query\n // fails. We capture either outcome as an `Either` and decode/wrap it inside\n // `useMemo` so that the returned `QueryResult` keeps a stable identity across\n // renders when nothing has actually changed. Decoding on every render would\n // hand consumers a fresh object each time, breaking effects and memoization\n // that depend on the result's identity.\n const encodedReturnsOrError: Either.Either<unknown, unknown> = Either.try(\n () => useConvexQuery(functionReference, encodedArgs),\n );\n\n return useMemo(\n () =>\n Either.match(encodedReturnsOrError, {\n onRight: (encodedReturnsOrUndefined) =>\n encodedReturnsOrUndefined === undefined\n ? QueryResult.load(skipped)\n : QueryResult.succeed(\n Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined),\n ),\n onLeft: (error) => {\n if (Ref.isConvexError(error)) {\n const decoded = Ref.decodeErrorSync(ref, error.data);\n if (Option.isSome(decoded)) {\n return QueryResult.fail(decoded.value);\n }\n }\n throw error;\n },\n }),\n // `Either.try` allocates a fresh wrapper each render, so we key the memo on\n // the stable value it carries (the Convex result or thrown error) rather\n // than the wrapper itself; the decoded result is a function of that value,\n // `ref`, and `skipped`.\n [ref, skipped, Either.merge(encodedReturnsOrError)],\n );\n};\n\n/**\n * Returns a function that invokes the provided `Ref`'s mutation.\n *\n * If the `Ref` declares an `error` schema, the returned promise resolves to an\n * `Either` with the decoded `returns` value on the right and the decoded error\n * on the left.\n *\n * If the `Ref` does not declare an `error` schema, the promise resolves\n * directly to the decoded `returns` value, matching the behavior of\n * `useMutation` from `convex/react`.\n *\n * Any other failure rejects the promise.\n */\nexport const useMutation = <Mutation extends Ref.AnyPublicMutation>(\n ref: Mutation,\n): ((...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>) => {\n const functionReference = Ref.getFunctionReference(ref);\n const actualMutation = useConvexMutation(functionReference);\n\n return useCallback(\n ((...args: Ref.OptionalArgs<Mutation>) =>\n invokeAsEither(\n ref,\n (_, encodedArgs) => actualMutation(encodedArgs),\n args,\n ).then((either) =>\n Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),\n )) as (...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>,\n [ref, actualMutation],\n );\n};\n\n/**\n * Returns a function that invokes the provided `Ref`'s action.\n *\n * If the `Ref` declares an `error` schema, the returned promise resolves to an\n * `Either` with the decoded `returns` value on the right and the decoded error\n * on the left.\n *\n * If the `Ref` does not declare an `error` schema, the promise resolves\n * directly to the decoded `returns` value, matching the behavior of\n * `useMutation` from `convex/react`.\n *\n * Any other failure rejects the promise.\n */\nexport const useAction = <Action extends Ref.AnyPublicAction>(\n ref: Action,\n): ((...args: Ref.OptionalArgs<Action>) => InvokeReturn<Action>) => {\n const functionReference = Ref.getFunctionReference(ref);\n const actualAction = useConvexAction(functionReference);\n\n return useCallback(\n ((...args: Ref.OptionalArgs<Action>) =>\n invokeAsEither(\n ref,\n (_, encodedArgs) => actualAction(encodedArgs),\n args,\n ).then((either) =>\n Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),\n )) as (...args: Ref.OptionalArgs<Action>) => InvokeReturn<Action>,\n [ref, actualAction],\n );\n};\n\nconst invokeAsEither = async <Ref_ extends Ref.Any>(\n ref: Ref_,\n invoke: (\n fnRef: Ref.FunctionReference<Ref_>,\n encodedArgs: unknown,\n ) => PromiseLike<unknown>,\n args: Ref.OptionalArgs<Ref_>,\n): Promise<Either.Either<Ref.Returns<Ref_>, Ref.Error<Ref_>>> => {\n const exit = await Effect.runPromiseExit(\n Ref.runWithCodec(ref, (args[0] ?? {}) as Ref.Args<Ref_>, invoke).pipe(\n Effect.catchTag(\"ParseError\", Effect.die),\n Effect.either,\n ),\n );\n if (Exit.isSuccess(exit)) return exit.value;\n throw Cause.squash(exit.cause);\n};\n"],"mappings":";;;;;;;AAwBA,MAAa,YACX,KACA,GAAG,SAC+D;CAClE,MAAM,oBAAoB,IAAI,qBAAqB,IAAI;CACvD,MAAM,OAAO,KAAK;CAClB,MAAM,UAAU,SAAS;CACzB,MAAM,cAAc,UAChB,SACA,IAAI,eAAe,KAAM,QAAQ,EAAE,CAAqB;CAS5D,MAAM,wBAAyD,OAAO,UAC9DA,WAAe,mBAAmB,YAAY,CACrD;AAED,QAAO,cAEH,OAAO,MAAM,uBAAuB;EAClC,UAAU,8BACR,8BAA8B,SAC1BC,KAAiB,QAAQ,GACzBC,QACE,IAAI,kBAAkB,KAAK,0BAA0B,CACtD;EACP,SAAS,UAAU;AACjB,OAAI,IAAI,cAAc,MAAM,EAAE;IAC5B,MAAM,UAAU,IAAI,gBAAgB,KAAK,MAAM,KAAK;AACpD,QAAI,OAAO,OAAO,QAAQ,CACxB,QAAOC,KAAiB,QAAQ,MAAM;;AAG1C,SAAM;;EAET,CAAC,EAKJ;EAAC;EAAK;EAAS,OAAO,MAAM,sBAAsB;EAAC,CACpD;;;;;;;;;;;;;;;AAgBH,MAAa,eACX,QACsE;CAEtE,MAAM,iBAAiBC,cADG,IAAI,qBAAqB,IAAI,CACI;AAE3D,QAAO,cACH,GAAG,SACH,eACE,MACC,GAAG,gBAAgB,eAAe,YAAY,EAC/C,KACD,CAAC,MAAM,WACN,IAAI,eAAe,IAAI,GAAG,SAAS,OAAO,WAAW,OAAO,CAC7D,GACH,CAAC,KAAK,eAAe,CACtB;;;;;;;;;;;;;;;AAgBH,MAAa,aACX,QACkE;CAElE,MAAM,eAAeC,YADK,IAAI,qBAAqB,IAAI,CACA;AAEvD,QAAO,cACH,GAAG,SACH,eACE,MACC,GAAG,gBAAgB,aAAa,YAAY,EAC7C,KACD,CAAC,MAAM,WACN,IAAI,eAAe,IAAI,GAAG,SAAS,OAAO,WAAW,OAAO,CAC7D,GACH,CAAC,KAAK,aAAa,CACpB;;AAGH,MAAM,iBAAiB,OACrB,KACA,QAIA,SAC+D;CAC/D,MAAM,OAAO,MAAM,OAAO,eACxB,IAAI,aAAa,KAAM,KAAK,MAAM,EAAE,EAAqB,OAAO,CAAC,KAC/D,OAAO,SAAS,cAAc,OAAO,IAAI,EACzC,OAAO,OACR,CACF;AACD,KAAI,KAAK,UAAU,KAAK,CAAE,QAAO,KAAK;AACtC,OAAM,MAAM,OAAO,KAAK,MAAM"}
|
package/package.json
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/react",
|
|
3
|
-
"version": "9.0.0-next.5",
|
|
4
3
|
"description": "Client-side bindings for React apps",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"url": "https://github.com/rjdellecese/confect.git"
|
|
8
|
-
},
|
|
4
|
+
"version": "9.0.0-next.7",
|
|
5
|
+
"author": "RJ Dellecese",
|
|
9
6
|
"bugs": {
|
|
10
7
|
"url": "https://github.com/rjdellecese/confect/issues"
|
|
11
8
|
},
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@effect/vitest": "0.29.0",
|
|
11
|
+
"@eslint/js": "10.0.1",
|
|
12
|
+
"@testing-library/react": "16.3.2",
|
|
13
|
+
"@types/node": "25.3.3",
|
|
14
|
+
"@types/react": "19.2.14",
|
|
15
|
+
"@types/react-dom": "19.2.3",
|
|
16
|
+
"convex": "1.39.1",
|
|
17
|
+
"effect": "3.21.2",
|
|
18
|
+
"eslint": "10.0.2",
|
|
19
|
+
"happy-dom": "15.11.7",
|
|
20
|
+
"prettier": "3.8.1",
|
|
21
|
+
"react": "19.2.4",
|
|
22
|
+
"react-dom": "19.2.4",
|
|
23
|
+
"tsdown": "0.20.3",
|
|
24
|
+
"typescript": "5.9.3",
|
|
25
|
+
"typescript-eslint": "8.56.1",
|
|
26
|
+
"vite": "7.3.1",
|
|
27
|
+
"vitest": "3.2.4"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22"
|
|
31
|
+
},
|
|
23
32
|
"exports": {
|
|
24
33
|
".": {
|
|
25
34
|
"types": "./dist/index.d.ts",
|
|
@@ -31,47 +40,43 @@
|
|
|
31
40
|
},
|
|
32
41
|
"./package.json": "./package.json"
|
|
33
42
|
},
|
|
43
|
+
"files": [
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md",
|
|
47
|
+
"dist",
|
|
48
|
+
"package.json",
|
|
49
|
+
"src"
|
|
50
|
+
],
|
|
51
|
+
"homepage": "https://confect.dev",
|
|
34
52
|
"keywords": [
|
|
35
|
-
"effect",
|
|
36
53
|
"convex",
|
|
54
|
+
"effect",
|
|
37
55
|
"react"
|
|
38
56
|
],
|
|
39
|
-
"author": "RJ Dellecese",
|
|
40
57
|
"license": "ISC",
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
"@eslint/js": "10.0.1",
|
|
44
|
-
"@types/node": "25.3.3",
|
|
45
|
-
"convex": "1.39.1",
|
|
46
|
-
"effect": "3.21.2",
|
|
47
|
-
"eslint": "10.0.2",
|
|
48
|
-
"prettier": "3.8.1",
|
|
49
|
-
"tsdown": "0.20.3",
|
|
50
|
-
"typescript": "5.9.3",
|
|
51
|
-
"typescript-eslint": "8.56.1",
|
|
52
|
-
"vite": "7.3.1",
|
|
53
|
-
"vitest": "3.2.4"
|
|
54
|
-
},
|
|
58
|
+
"main": "./dist/index.js",
|
|
59
|
+
"module": "./dist/index.js",
|
|
55
60
|
"peerDependencies": {
|
|
56
61
|
"convex": "^1.30.0",
|
|
57
62
|
"effect": "^3.21.2",
|
|
58
63
|
"react": "^18.0.0 || ^19.0.0",
|
|
59
|
-
"@confect/core": "^9.0.0-next.
|
|
64
|
+
"@confect/core": "^9.0.0-next.7"
|
|
60
65
|
},
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "https://github.com/rjdellecese/confect.git"
|
|
64
69
|
},
|
|
65
|
-
"
|
|
66
|
-
"
|
|
70
|
+
"sideEffects": false,
|
|
71
|
+
"type": "module",
|
|
67
72
|
"types": "./dist/index.d.ts",
|
|
68
73
|
"scripts": {
|
|
69
74
|
"build": "tsdown --config-loader unrun",
|
|
75
|
+
"clean": "rm -rf dist coverage node_modules",
|
|
70
76
|
"dev": "tsdown --watch",
|
|
71
|
-
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
72
77
|
"fix": "prettier --write . && eslint --fix . --max-warnings=0",
|
|
73
78
|
"format": "prettier --check .",
|
|
74
79
|
"lint": "eslint . --max-warnings=0",
|
|
75
|
-
"
|
|
80
|
+
"typecheck": "tsc --noEmit --project tsconfig.json"
|
|
76
81
|
}
|
|
77
82
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useQuery as useConvexQuery,
|
|
6
6
|
} from "convex/react";
|
|
7
7
|
import { Cause, Effect, Either, Exit, Option } from "effect";
|
|
8
|
+
import { useCallback, useMemo } from "react";
|
|
8
9
|
|
|
9
10
|
import * as QueryResult from "./QueryResult";
|
|
10
11
|
|
|
@@ -27,33 +28,47 @@ export const useQuery = <Query extends Ref.AnyPublicQuery>(
|
|
|
27
28
|
): QueryResult.QueryResult<Ref.Returns<Query>, Ref.Error<Query>> => {
|
|
28
29
|
const functionReference = Ref.getFunctionReference(ref);
|
|
29
30
|
const args = rest[0];
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const skipped = args === "skip";
|
|
32
|
+
const encodedArgs = skipped
|
|
33
|
+
? "skip"
|
|
34
|
+
: Ref.encodeArgsSync(ref, (args ?? {}) as Ref.Args<Query>);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
// `useConvexQuery` returns a referentially stable value while the underlying
|
|
37
|
+
// Convex result is unchanged, and throws a stable error when the query
|
|
38
|
+
// fails. We capture either outcome as an `Either` and decode/wrap it inside
|
|
39
|
+
// `useMemo` so that the returned `QueryResult` keeps a stable identity across
|
|
40
|
+
// renders when nothing has actually changed. Decoding on every render would
|
|
41
|
+
// hand consumers a fresh object each time, breaking effects and memoization
|
|
42
|
+
// that depend on the result's identity.
|
|
43
|
+
const encodedReturnsOrError: Either.Either<unknown, unknown> = Either.try(
|
|
44
|
+
() => useConvexQuery(functionReference, encodedArgs),
|
|
45
|
+
);
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
return useMemo(
|
|
48
|
+
() =>
|
|
49
|
+
Either.match(encodedReturnsOrError, {
|
|
50
|
+
onRight: (encodedReturnsOrUndefined) =>
|
|
51
|
+
encodedReturnsOrUndefined === undefined
|
|
52
|
+
? QueryResult.load(skipped)
|
|
53
|
+
: QueryResult.succeed(
|
|
54
|
+
Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined),
|
|
55
|
+
),
|
|
56
|
+
onLeft: (error) => {
|
|
57
|
+
if (Ref.isConvexError(error)) {
|
|
58
|
+
const decoded = Ref.decodeErrorSync(ref, error.data);
|
|
59
|
+
if (Option.isSome(decoded)) {
|
|
60
|
+
return QueryResult.fail(decoded.value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
// `Either.try` allocates a fresh wrapper each render, so we key the memo on
|
|
67
|
+
// the stable value it carries (the Convex result or thrown error) rather
|
|
68
|
+
// than the wrapper itself; the decoded result is a function of that value,
|
|
69
|
+
// `ref`, and `skipped`.
|
|
70
|
+
[ref, skipped, Either.merge(encodedReturnsOrError)],
|
|
71
|
+
);
|
|
57
72
|
};
|
|
58
73
|
|
|
59
74
|
/**
|
|
@@ -75,14 +90,17 @@ export const useMutation = <Mutation extends Ref.AnyPublicMutation>(
|
|
|
75
90
|
const functionReference = Ref.getFunctionReference(ref);
|
|
76
91
|
const actualMutation = useConvexMutation(functionReference);
|
|
77
92
|
|
|
78
|
-
return (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
return useCallback(
|
|
94
|
+
((...args: Ref.OptionalArgs<Mutation>) =>
|
|
95
|
+
invokeAsEither(
|
|
96
|
+
ref,
|
|
97
|
+
(_, encodedArgs) => actualMutation(encodedArgs),
|
|
98
|
+
args,
|
|
99
|
+
).then((either) =>
|
|
100
|
+
Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
|
|
101
|
+
)) as (...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>,
|
|
102
|
+
[ref, actualMutation],
|
|
103
|
+
);
|
|
86
104
|
};
|
|
87
105
|
|
|
88
106
|
/**
|
|
@@ -104,14 +122,17 @@ export const useAction = <Action extends Ref.AnyPublicAction>(
|
|
|
104
122
|
const functionReference = Ref.getFunctionReference(ref);
|
|
105
123
|
const actualAction = useConvexAction(functionReference);
|
|
106
124
|
|
|
107
|
-
return (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
return useCallback(
|
|
126
|
+
((...args: Ref.OptionalArgs<Action>) =>
|
|
127
|
+
invokeAsEither(
|
|
128
|
+
ref,
|
|
129
|
+
(_, encodedArgs) => actualAction(encodedArgs),
|
|
130
|
+
args,
|
|
131
|
+
).then((either) =>
|
|
132
|
+
Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
|
|
133
|
+
)) as (...args: Ref.OptionalArgs<Action>) => InvokeReturn<Action>,
|
|
134
|
+
[ref, actualAction],
|
|
135
|
+
);
|
|
115
136
|
};
|
|
116
137
|
|
|
117
138
|
const invokeAsEither = async <Ref_ extends Ref.Any>(
|