@confect/react 9.0.0-next.6 → 9.0.0-next.8

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 CHANGED
@@ -1,5 +1,50 @@
1
1
  # @confect/react
2
2
 
3
+ ## 9.0.0-next.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 3fec285: Import Effect from its submodule paths internally to shrink per-function cold-start bundles.
8
+
9
+ Confect's packages now import Effect modules from their submodule paths (`import * as Schema from "effect/Schema"`) instead of the `"effect"` barrel (`import { Schema } from "effect"`).
10
+
11
+ ### Why
12
+
13
+ A barrel import of a namespace re-export defeats esbuild's tree-shaking: accessing `Schema.X` from `import { Schema } from "effect"` retains the _entire_ `Schema` namespace, because the bundler can't prune property access on the barrel's `export * as Schema`. So every Convex function's cold-start bundle was pulling all of `effect/Schema` and `effect/Stream` — and, transitively through Schema's `Arbitrary`, `fast-check` — whether the function used them or not.
14
+
15
+ Importing from the submodule path tree-shakes normally. On a minimal function this cut the bundle esbuild produces by ~54% (the `effect/Schema` module alone by ~75%) and its cold-start module-evaluation time by ~35%, with `fast-check` dropped entirely. This is also the import style Effect v4 recommends, so it's forward-compatible. A `no-restricted-imports` ESLint rule now enforces it across the codebase (type-only imports and `@effect/vitest` are exempt).
16
+
17
+ No API changes — your existing code keeps working.
18
+
19
+ ### Getting the full win in your own code
20
+
21
+ This change shrinks the Confect code in every function bundle, but a function's bundle also includes your own `confect/tables/*` and `*.spec.ts` files. esbuild retains the union across all importers, so a single barrel import anywhere in a function's module graph re-pins the whole `effect/Schema` namespace and undoes the reduction. To get the full bundle/cold-start savings, import Effect from its submodule paths in your own Confect files too:
22
+
23
+ ```diff
24
+ - import { Schema } from "effect";
25
+ + import * as Schema from "effect/Schema";
26
+ ```
27
+
28
+ Bare helpers (`pipe`, `flow`, `identity`) come from `"effect/Function"`.
29
+
30
+ - Updated dependencies [3fec285]
31
+ - @confect/core@9.0.0-next.8
32
+
33
+ ## 9.0.0-next.7
34
+
35
+ ### Patch Changes
36
+
37
+ - 5d19484: Stabilize the identity of `@confect/react` hook results across renders.
38
+
39
+ `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.
40
+
41
+ `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.
42
+
43
+ `Ref.getFunctionReference` now caches the Convex function reference by function name, so repeated calls for the same ref return the same reference.
44
+
45
+ - Updated dependencies [5d19484]
46
+ - @confect/core@9.0.0-next.7
47
+
3
48
  ## 9.0.0-next.6
4
49
 
5
50
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { Pipeable } from "effect";
1
+ import * as Pipeable from "effect/Pipeable";
2
2
 
3
3
  //#region src/QueryResult.d.ts
4
4
  declare namespace QueryResult_d_exports {
@@ -1 +1 @@
1
- {"version":3,"file":"QueryResult.d.ts","names":[],"sources":["../src/QueryResult.ts"],"mappings":";;;;;;cAEM,MAAA;AAAA,KACD,MAAA,UAAgB,MAAA;;;;;;;KAQT,WAAA,iBACR,OAAA,CAAQ,CAAA,EAAG,CAAA,IACX,OAAA,CAAQ,CAAA,EAAG,CAAA,IACX,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,kBAEU,WAAA;EAAA,UACN,KAAA,eAAoB,QAAA,CAAS,QAAA;IAAA,UAClC,MAAA;MAAA,SACC,CAAA,GAAI,CAAA,YAAa,CAAA;MAAA,SACjB,CAAA,GAAI,CAAA,YAAa,CAAA;IAAA;EAAA;EAAA,KAKlB,OAAA,MAAa,CAAA,SAAU,WAAA,sBAAiC,CAAA;EAAA,KAGxD,OAAA,MAAa,CAAA,SAAU,WAAA,sBAAiC,CAAA;AAAA;AAAA,UAGrD,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,KAAA,EAAO,CAAA;AAAA;AAAA,UAGD,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,KAAA,EAAO,CAAA;AAAA;AAAA,cAGL,aAAA,GAAiB,CAAA,cAAa,CAAA,IAAK,WAAA;AAAA,cA8CnC,IAAA,yBAA8B,OAAA,cAAmB,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM5D,OAAA,iBAAyB,KAAA,EAAO,CAAA,KAAI,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM/C,IAAA,iBAAsB,KAAA,EAAO,CAAA,KAAI,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM5C,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAEhB,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAEhB,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,KAExB,YAAA;EAAA,SACM,SAAA,GAAY,OAAA,cAAqB,CAAA;EAAA,SACjC,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,CAAA;AAAA,MAC9B,CAAA;EAAA,SAAqC,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,CAAA;AAAA;AAAA,KAE/D,YAAA,gBAA4B,CAAA,oBAAqB,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA;;;;;;;;;;AA/GtE;;;;cA8Ha,KAAA;EAAA,wBAET,OAAA,EAAS,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,KAChC,IAAA,EAAM,WAAA,CAAY,CAAA,EAAG,CAAA,MAAO,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA;EAAA,wBAEpD,IAAA,EAAM,WAAA,CAAY,CAAA,EAAG,CAAA,GACrB,OAAA,EAAS,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,IACjC,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA;AAAA"}
1
+ {"version":3,"file":"QueryResult.d.ts","names":[],"sources":["../src/QueryResult.ts"],"mappings":";;;;;;cAOM,MAAA;AAAA,KACD,MAAA,UAAgB,MAAA;;;;;;;KAQT,WAAA,iBACR,OAAA,CAAQ,CAAA,EAAG,CAAA,IACX,OAAA,CAAQ,CAAA,EAAG,CAAA,IACX,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,kBAEU,WAAA;EAAA,UACN,KAAA,eAAoB,QAAA,CAAS,QAAA;IAAA,UAClC,MAAA;MAAA,SACC,CAAA,GAAI,CAAA,YAAa,CAAA;MAAA,SACjB,CAAA,GAAI,CAAA,YAAa,CAAA;IAAA;EAAA;EAAA,KAKlB,OAAA,MAAa,CAAA,SAAU,WAAA,sBAAiC,CAAA;EAAA,KAGxD,OAAA,MAAa,CAAA,SAAU,WAAA,sBAAiC,CAAA;AAAA;AAAA,UAGrD,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,OAAA;AAAA;AAAA,UAGM,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,KAAA,EAAO,CAAA;AAAA;AAAA,UAGD,OAAA,uBAA8B,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAA;EAAA,SACzD,IAAA;EAAA,SACA,KAAA,EAAO,CAAA;AAAA;AAAA,cAGL,aAAA,GAAiB,CAAA,cAAa,CAAA,IAAK,WAAA;AAAA,cA8CnC,IAAA,yBAA8B,OAAA,cAAmB,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM5D,OAAA,iBAAyB,KAAA,EAAO,CAAA,KAAI,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM/C,IAAA,iBAAsB,KAAA,EAAO,CAAA,KAAI,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAM5C,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAEhB,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,cAEhB,SAAA,SACX,WAAA,EAAa,WAAA,CAAY,CAAA,EAAG,CAAA,MAC3B,WAAA,IAAe,OAAA,CAAQ,CAAA,EAAG,CAAA;AAAA,KAExB,YAAA;EAAA,SACM,SAAA,GAAY,OAAA,cAAqB,CAAA;EAAA,SACjC,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,CAAA;AAAA,MAC9B,CAAA;EAAA,SAAqC,SAAA,GAAY,KAAA,EAAO,CAAA,KAAM,CAAA;AAAA;AAAA,KAE/D,YAAA,gBAA4B,CAAA,oBAAqB,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,CAAA;;;;;;;;;;AA/GtE;;;;cA8Ha,KAAA;EAAA,wBAET,OAAA,EAAS,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,KAChC,IAAA,EAAM,WAAA,CAAY,CAAA,EAAG,CAAA,MAAO,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA;EAAA,wBAEpD,IAAA,EAAM,WAAA,CAAY,CAAA,EAAG,CAAA,GACrB,OAAA,EAAS,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,IACjC,YAAA,CAAa,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA;AAAA"}
@@ -1,5 +1,10 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { Equal, Function, Hash, Pipeable, Predicate, identity } from "effect";
2
+ import * as Function from "effect/Function";
3
+ import { identity } from "effect/Function";
4
+ import * as Equal from "effect/Equal";
5
+ import * as Hash from "effect/Hash";
6
+ import * as Pipeable from "effect/Pipeable";
7
+ import * as Predicate from "effect/Predicate";
3
8
 
4
9
  //#region src/QueryResult.ts
5
10
  var QueryResult_exports = /* @__PURE__ */ __exportAll({
@@ -1 +1 @@
1
- {"version":3,"file":"QueryResult.js","names":[],"sources":["../src/QueryResult.ts"],"sourcesContent":["import { Equal, Function, Hash, identity, Pipeable, Predicate } from \"effect\";\n\nconst TypeId = \"@confect/react/QueryResult\";\ntype TypeId = typeof TypeId;\n\n/**\n * A `QueryResult` represents the result of a Confect query.\n *\n * @typeParam A - The type of the decoded `returns` value in the `Success` variant.\n * @typeParam E - The type of the decoded typed error in the `Failure` variant.\n */\nexport type QueryResult<A, E = never> =\n | Loading<A, E>\n | Success<A, E>\n | Failure<A, E>;\n\nexport declare namespace QueryResult {\n export interface Proto<A, E> extends Pipeable.Pipeable {\n readonly [TypeId]: {\n readonly E: (_: never) => E;\n readonly A: (_: never) => A;\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n export type Success<R> = R extends QueryResult<infer A, infer _E> ? A : never;\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n export type Failure<R> = R extends QueryResult<infer _A, infer E> ? E : never;\n}\n\nexport interface Loading<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Loading\";\n readonly skipped: boolean;\n}\n\nexport interface Success<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Success\";\n readonly value: A;\n}\n\nexport interface Failure<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Failure\";\n readonly error: E;\n}\n\nexport const isQueryResult = (u: unknown): u is QueryResult<unknown, unknown> =>\n Predicate.hasProperty(u, TypeId);\n\nconst QueryResultProto = {\n [TypeId]: {\n E: identity,\n A: identity,\n },\n pipe(this: QueryResult<any, any>, ...args: ReadonlyArray<unknown>) {\n return Pipeable.pipeArguments(\n this,\n args as unknown as Parameters<typeof Pipeable.pipeArguments>[1],\n );\n },\n [Equal.symbol](\n this: QueryResult<any, any>,\n that: QueryResult<any, any>,\n ): boolean {\n if (this._tag !== that._tag) {\n return false;\n }\n switch (this._tag) {\n case \"Loading\":\n return this.skipped === (that as Loading<any, any>).skipped;\n case \"Success\":\n return Equal.equals(this.value, (that as Success<any, any>).value);\n case \"Failure\":\n return Equal.equals(this.error, (that as Failure<any, any>).error);\n }\n },\n [Hash.symbol](this: QueryResult<any, any>): number {\n const tagHash = Hash.string(this._tag);\n switch (this._tag) {\n case \"Loading\":\n return Hash.cached(\n this,\n Hash.combine(tagHash)(Hash.hash(this.skipped)),\n );\n case \"Success\":\n return Hash.cached(this, Hash.combine(tagHash)(Hash.hash(this.value)));\n case \"Failure\":\n return Hash.cached(this, Hash.combine(tagHash)(Hash.hash(this.error)));\n }\n },\n};\n\nexport const load = <A = never, E = never>(skipped: boolean): Loading<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Loading\" as const,\n skipped,\n });\n\nexport const succeed = <A, E = never>(value: A): Success<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Success\" as const,\n value,\n });\n\nexport const fail = <E, A = never>(error: E): Failure<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Failure\" as const,\n error,\n });\n\nexport const isLoading = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Loading<A, E> => queryResult._tag === \"Loading\";\n\nexport const isSuccess = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Success<A, E> => queryResult._tag === \"Success\";\n\nexport const isFailure = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Failure<A, E> => queryResult._tag === \"Failure\";\n\ntype MatchOptions<A, E, X, Y, Z> = {\n readonly onLoading: (skipped: boolean) => X;\n readonly onSuccess: (value: A) => Y;\n} & ([E] extends [never] ? {} : { readonly onFailure: (error: E) => Z });\n\ntype MatchReturns<E, X, Y, Z> = [E] extends [never] ? X | Y : X | Y | Z;\n\n/**\n * Matches a {@link QueryResult} to the appropriate handler based on its tag. If\n * the provided `QueryResult` cannot fail (i.e. `E` is `never`), `onFailure` is\n * not required.\n *\n * @example\n * ```tsx\n * const result = QueryResult.match(queryResult, {\n * onLoading: (skipped) => skipped ? null : <p>Loading…</p>,\n * onSuccess: (value) => <p>{value.text}</p>,\n * onFailure: (error) => <p>Error: {error.message}</p>,\n * });\n */\nexport const match: {\n <A, E, X, Y, Z = never>(\n options: MatchOptions<A, E, X, Y, Z>,\n ): (self: QueryResult<A, E>) => MatchReturns<E, X, Y, Z>;\n <A, E, X, Y, Z = never>(\n self: QueryResult<A, E>,\n options: MatchOptions<A, E, X, Y, Z>,\n ): MatchReturns<E, X, Y, Z>;\n} = Function.dual(\n 2,\n <A, E, X, Y, Z = never>(\n self: QueryResult<A, E>,\n options: MatchOptions<A, E, X, Y, Z>,\n ): MatchReturns<E, X, Y, Z> => {\n switch (self._tag) {\n case \"Loading\":\n return options.onLoading(self.skipped);\n case \"Success\":\n return options.onSuccess(self.value);\n case \"Failure\": {\n if (Predicate.hasProperty(options, \"onFailure\")) {\n return options.onFailure(self.error) as MatchReturns<E, X, Y, Z>;\n }\n throw new Error(\n \"`onFailure` is required when error schema is provided\",\n );\n }\n }\n },\n);\n"],"mappings":";;;;;;;;;;;;;;AAEA,MAAM,SAAS;AA4Cf,MAAa,iBAAiB,MAC5B,UAAU,YAAY,GAAG,OAAO;AAElC,MAAM,mBAAmB;EACtB,SAAS;EACR,GAAG;EACH,GAAG;EACJ;CACD,KAAkC,GAAG,MAA8B;AACjE,SAAO,SAAS,cACd,MACA,KACD;;CAEH,CAAC,MAAM,QAEL,MACS;AACT,MAAI,KAAK,SAAS,KAAK,KACrB,QAAO;AAET,UAAQ,KAAK,MAAb;GACE,KAAK,UACH,QAAO,KAAK,YAAa,KAA2B;GACtD,KAAK,UACH,QAAO,MAAM,OAAO,KAAK,OAAQ,KAA2B,MAAM;GACpE,KAAK,UACH,QAAO,MAAM,OAAO,KAAK,OAAQ,KAA2B,MAAM;;;CAGxE,CAAC,KAAK,UAA6C;EACjD,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK;AACtC,UAAQ,KAAK,MAAb;GACE,KAAK,UACH,QAAO,KAAK,OACV,MACA,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,QAAQ,CAAC,CAC/C;GACH,KAAK,UACH,QAAO,KAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC;GACxE,KAAK,UACH,QAAO,KAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC;;;CAG7E;AAED,MAAa,QAA8B,YACzC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,WAAyB,UACpC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,QAAsB,UACjC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,aACX,gBACiC,YAAY,SAAS;AAExD,MAAa,aACX,gBACiC,YAAY,SAAS;AAExD,MAAa,aACX,gBACiC,YAAY,SAAS;;;;;;;;;;;;;;AAsBxD,MAAa,QAQT,SAAS,KACX,IAEE,MACA,YAC6B;AAC7B,SAAQ,KAAK,MAAb;EACE,KAAK,UACH,QAAO,QAAQ,UAAU,KAAK,QAAQ;EACxC,KAAK,UACH,QAAO,QAAQ,UAAU,KAAK,MAAM;EACtC,KAAK;AACH,OAAI,UAAU,YAAY,SAAS,YAAY,CAC7C,QAAO,QAAQ,UAAU,KAAK,MAAM;AAEtC,SAAM,IAAI,MACR,wDACD;;EAIR"}
1
+ {"version":3,"file":"QueryResult.js","names":[],"sources":["../src/QueryResult.ts"],"sourcesContent":["import { identity } from \"effect/Function\";\nimport * as Equal from \"effect/Equal\";\nimport * as Function from \"effect/Function\";\nimport * as Hash from \"effect/Hash\";\nimport * as Pipeable from \"effect/Pipeable\";\nimport * as Predicate from \"effect/Predicate\";\n\nconst TypeId = \"@confect/react/QueryResult\";\ntype TypeId = typeof TypeId;\n\n/**\n * A `QueryResult` represents the result of a Confect query.\n *\n * @typeParam A - The type of the decoded `returns` value in the `Success` variant.\n * @typeParam E - The type of the decoded typed error in the `Failure` variant.\n */\nexport type QueryResult<A, E = never> =\n | Loading<A, E>\n | Success<A, E>\n | Failure<A, E>;\n\nexport declare namespace QueryResult {\n export interface Proto<A, E> extends Pipeable.Pipeable {\n readonly [TypeId]: {\n readonly E: (_: never) => E;\n readonly A: (_: never) => A;\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n export type Success<R> = R extends QueryResult<infer A, infer _E> ? A : never;\n\n // eslint-disable-next-line @typescript-eslint/no-shadow\n export type Failure<R> = R extends QueryResult<infer _A, infer E> ? E : never;\n}\n\nexport interface Loading<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Loading\";\n readonly skipped: boolean;\n}\n\nexport interface Success<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Success\";\n readonly value: A;\n}\n\nexport interface Failure<A, E = never> extends QueryResult.Proto<A, E> {\n readonly _tag: \"Failure\";\n readonly error: E;\n}\n\nexport const isQueryResult = (u: unknown): u is QueryResult<unknown, unknown> =>\n Predicate.hasProperty(u, TypeId);\n\nconst QueryResultProto = {\n [TypeId]: {\n E: identity,\n A: identity,\n },\n pipe(this: QueryResult<any, any>, ...args: ReadonlyArray<unknown>) {\n return Pipeable.pipeArguments(\n this,\n args as unknown as Parameters<typeof Pipeable.pipeArguments>[1],\n );\n },\n [Equal.symbol](\n this: QueryResult<any, any>,\n that: QueryResult<any, any>,\n ): boolean {\n if (this._tag !== that._tag) {\n return false;\n }\n switch (this._tag) {\n case \"Loading\":\n return this.skipped === (that as Loading<any, any>).skipped;\n case \"Success\":\n return Equal.equals(this.value, (that as Success<any, any>).value);\n case \"Failure\":\n return Equal.equals(this.error, (that as Failure<any, any>).error);\n }\n },\n [Hash.symbol](this: QueryResult<any, any>): number {\n const tagHash = Hash.string(this._tag);\n switch (this._tag) {\n case \"Loading\":\n return Hash.cached(\n this,\n Hash.combine(tagHash)(Hash.hash(this.skipped)),\n );\n case \"Success\":\n return Hash.cached(this, Hash.combine(tagHash)(Hash.hash(this.value)));\n case \"Failure\":\n return Hash.cached(this, Hash.combine(tagHash)(Hash.hash(this.error)));\n }\n },\n};\n\nexport const load = <A = never, E = never>(skipped: boolean): Loading<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Loading\" as const,\n skipped,\n });\n\nexport const succeed = <A, E = never>(value: A): Success<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Success\" as const,\n value,\n });\n\nexport const fail = <E, A = never>(error: E): Failure<A, E> =>\n Object.assign(Object.create(QueryResultProto), {\n _tag: \"Failure\" as const,\n error,\n });\n\nexport const isLoading = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Loading<A, E> => queryResult._tag === \"Loading\";\n\nexport const isSuccess = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Success<A, E> => queryResult._tag === \"Success\";\n\nexport const isFailure = <A, E>(\n queryResult: QueryResult<A, E>,\n): queryResult is Failure<A, E> => queryResult._tag === \"Failure\";\n\ntype MatchOptions<A, E, X, Y, Z> = {\n readonly onLoading: (skipped: boolean) => X;\n readonly onSuccess: (value: A) => Y;\n} & ([E] extends [never] ? {} : { readonly onFailure: (error: E) => Z });\n\ntype MatchReturns<E, X, Y, Z> = [E] extends [never] ? X | Y : X | Y | Z;\n\n/**\n * Matches a {@link QueryResult} to the appropriate handler based on its tag. If\n * the provided `QueryResult` cannot fail (i.e. `E` is `never`), `onFailure` is\n * not required.\n *\n * @example\n * ```tsx\n * const result = QueryResult.match(queryResult, {\n * onLoading: (skipped) => skipped ? null : <p>Loading…</p>,\n * onSuccess: (value) => <p>{value.text}</p>,\n * onFailure: (error) => <p>Error: {error.message}</p>,\n * });\n */\nexport const match: {\n <A, E, X, Y, Z = never>(\n options: MatchOptions<A, E, X, Y, Z>,\n ): (self: QueryResult<A, E>) => MatchReturns<E, X, Y, Z>;\n <A, E, X, Y, Z = never>(\n self: QueryResult<A, E>,\n options: MatchOptions<A, E, X, Y, Z>,\n ): MatchReturns<E, X, Y, Z>;\n} = Function.dual(\n 2,\n <A, E, X, Y, Z = never>(\n self: QueryResult<A, E>,\n options: MatchOptions<A, E, X, Y, Z>,\n ): MatchReturns<E, X, Y, Z> => {\n switch (self._tag) {\n case \"Loading\":\n return options.onLoading(self.skipped);\n case \"Success\":\n return options.onSuccess(self.value);\n case \"Failure\": {\n if (Predicate.hasProperty(options, \"onFailure\")) {\n return options.onFailure(self.error) as MatchReturns<E, X, Y, Z>;\n }\n throw new Error(\n \"`onFailure` is required when error schema is provided\",\n );\n }\n }\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAOA,MAAM,SAAS;AA4Cf,MAAa,iBAAiB,MAC5B,UAAU,YAAY,GAAG,OAAO;AAElC,MAAM,mBAAmB;EACtB,SAAS;EACR,GAAG;EACH,GAAG;EACJ;CACD,KAAkC,GAAG,MAA8B;AACjE,SAAO,SAAS,cACd,MACA,KACD;;CAEH,CAAC,MAAM,QAEL,MACS;AACT,MAAI,KAAK,SAAS,KAAK,KACrB,QAAO;AAET,UAAQ,KAAK,MAAb;GACE,KAAK,UACH,QAAO,KAAK,YAAa,KAA2B;GACtD,KAAK,UACH,QAAO,MAAM,OAAO,KAAK,OAAQ,KAA2B,MAAM;GACpE,KAAK,UACH,QAAO,MAAM,OAAO,KAAK,OAAQ,KAA2B,MAAM;;;CAGxE,CAAC,KAAK,UAA6C;EACjD,MAAM,UAAU,KAAK,OAAO,KAAK,KAAK;AACtC,UAAQ,KAAK,MAAb;GACE,KAAK,UACH,QAAO,KAAK,OACV,MACA,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,QAAQ,CAAC,CAC/C;GACH,KAAK,UACH,QAAO,KAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC;GACxE,KAAK,UACH,QAAO,KAAK,OAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC;;;CAG7E;AAED,MAAa,QAA8B,YACzC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,WAAyB,UACpC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,QAAsB,UACjC,OAAO,OAAO,OAAO,OAAO,iBAAiB,EAAE;CAC7C,MAAM;CACN;CACD,CAAC;AAEJ,MAAa,aACX,gBACiC,YAAY,SAAS;AAExD,MAAa,aACX,gBACiC,YAAY,SAAS;AAExD,MAAa,aACX,gBACiC,YAAY,SAAS;;;;;;;;;;;;;;AAsBxD,MAAa,QAQT,SAAS,KACX,IAEE,MACA,YAC6B;AAC7B,SAAQ,KAAK,MAAb;EACE,KAAK,UACH,QAAO,QAAQ,UAAU,KAAK,QAAQ;EACxC,KAAK,UACH,QAAO,QAAQ,UAAU,KAAK,MAAM;EACtC,KAAK;AACH,OAAI,UAAU,YAAY,SAAS,YAAY,CAC7C,QAAO,QAAQ,UAAU,KAAK,MAAM;AAEtC,SAAM,IAAI,MACR,wDACD;;EAIR"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { QueryResult, QueryResult_d_exports } from "./QueryResult.js";
2
2
  import { Ref } from "@confect/core";
3
- import { Either } from "effect";
3
+ import * as Either from "effect/Either";
4
4
 
5
5
  //#region src/index.d.ts
6
6
  type InvokeReturn<Ref_ extends Ref.Any> = [Ref.Error<Ref_>] extends [never] ? Promise<Ref.Returns<Ref_>> : Promise<Either.Either<Ref.Returns<Ref_>, Ref.Error<Ref_>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;KAYY,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;;;;;;;;;;;;;;cA6C5C,WAAA,oBAAgC,GAAA,CAAI,iBAAA,EAC/C,GAAA,EAAK,QAAA,UACC,IAAA,EAAM,GAAA,CAAI,YAAA,CAAa,QAAA,MAAc,YAAA,CAAa,QAAA;;;;;;;;;;;;;;cA2B7C,SAAA,kBAA4B,GAAA,CAAI,eAAA,EAC3C,GAAA,EAAK,MAAA,UACC,IAAA,EAAM,GAAA,CAAI,YAAA,CAAa,MAAA,MAAY,YAAA,CAAa,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;KAiBY,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
@@ -1,24 +1,34 @@
1
1
  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
- import { Cause, Effect, Either, Exit, Option } from "effect";
4
+ import * as Cause from "effect/Cause";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Either from "effect/Either";
7
+ import * as Exit from "effect/Exit";
8
+ import * as Option from "effect/Option";
9
+ import { useCallback, useMemo } from "react";
5
10
 
6
11
  //#region src/index.ts
7
12
  const useQuery = (ref, ...rest) => {
8
13
  const functionReference = Ref.getFunctionReference(ref);
9
14
  const args = rest[0];
10
- const encodedArgs = args === "skip" ? "skip" : Ref.encodeArgsSync(ref, args ?? {});
11
- try {
12
- const encodedReturnsOrUndefined = useQuery$1(functionReference, encodedArgs);
13
- if (encodedReturnsOrUndefined === void 0) return load(args === "skip");
14
- return succeed(Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined));
15
- } catch (error) {
16
- if (Ref.isConvexError(error)) {
17
- const decoded = Ref.decodeErrorSync(ref, error.data);
18
- if (Option.isSome(decoded)) return fail(decoded.value);
15
+ const skipped = args === "skip";
16
+ const encodedArgs = skipped ? "skip" : Ref.encodeArgsSync(ref, args ?? {});
17
+ const encodedReturnsOrError = Either.try(() => useQuery$1(functionReference, encodedArgs));
18
+ return useMemo(() => Either.match(encodedReturnsOrError, {
19
+ onRight: (encodedReturnsOrUndefined) => encodedReturnsOrUndefined === void 0 ? load(skipped) : succeed(Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined)),
20
+ onLeft: (error) => {
21
+ if (Ref.isConvexError(error)) {
22
+ const decoded = Ref.decodeErrorSync(ref, error.data);
23
+ if (Option.isSome(decoded)) return fail(decoded.value);
24
+ }
25
+ throw error;
19
26
  }
20
- throw error;
21
- }
27
+ }), [
28
+ ref,
29
+ skipped,
30
+ Either.merge(encodedReturnsOrError)
31
+ ]);
22
32
  };
23
33
  /**
24
34
  * Returns a function that invokes the provided `Ref`'s mutation.
@@ -35,7 +45,7 @@ const useQuery = (ref, ...rest) => {
35
45
  */
36
46
  const useMutation = (ref) => {
37
47
  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)));
48
+ return useCallback(((...args) => invokeAsEither(ref, (_, encodedArgs) => actualMutation(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either))), [ref, actualMutation]);
39
49
  };
40
50
  /**
41
51
  * Returns a function that invokes the provided `Ref`'s action.
@@ -52,7 +62,7 @@ const useMutation = (ref) => {
52
62
  */
53
63
  const useAction = (ref) => {
54
64
  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)));
65
+ return useCallback(((...args) => invokeAsEither(ref, (_, encodedArgs) => actualAction(encodedArgs), args).then((either) => Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either))), [ref, actualAction]);
56
66
  };
57
67
  const invokeAsEither = async (ref, invoke, args) => {
58
68
  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 encodedArgs =\n args === \"skip\"\n ? \"skip\"\n : Ref.encodeArgsSync(ref, (args ?? {}) as Ref.Args<Query>);\n\n try {\n const encodedReturnsOrUndefined = useConvexQuery(\n functionReference,\n encodedArgs,\n );\n\n if (encodedReturnsOrUndefined === undefined) {\n return QueryResult.load(args === \"skip\");\n }\n\n return QueryResult.succeed(\n Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined),\n );\n } catch (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\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 ((...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};\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 ((...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};\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":";;;;;;AAuBA,MAAa,YACX,KACA,GAAG,SAC+D;CAClE,MAAM,oBAAoB,IAAI,qBAAqB,IAAI;CACvD,MAAM,OAAO,KAAK;CAClB,MAAM,cACJ,SAAS,SACL,SACA,IAAI,eAAe,KAAM,QAAQ,EAAE,CAAqB;AAE9D,KAAI;EACF,MAAM,4BAA4BA,WAChC,mBACA,YACD;AAED,MAAI,8BAA8B,OAChC,QAAOC,KAAiB,SAAS,OAAO;AAG1C,SAAOC,QACL,IAAI,kBAAkB,KAAK,0BAA0B,CACtD;UACM,OAAO;AACd,MAAI,IAAI,cAAc,MAAM,EAAE;GAC5B,MAAM,UAAU,IAAI,gBAAgB,KAAK,MAAM,KAAK;AACpD,OAAI,OAAO,OAAO,QAAQ,CACxB,QAAOC,KAAiB,QAAQ,MAAM;;AAG1C,QAAM;;;;;;;;;;;;;;;;AAiBV,MAAa,eACX,QACsE;CAEtE,MAAM,iBAAiBC,cADG,IAAI,qBAAqB,IAAI,CACI;AAE3D,UAAS,GAAG,SACV,eACE,MACC,GAAG,gBAAgB,eAAe,YAAY,EAC/C,KACD,CAAC,MAAM,WACN,IAAI,eAAe,IAAI,GAAG,SAAS,OAAO,WAAW,OAAO,CAC7D;;;;;;;;;;;;;;;AAgBL,MAAa,aACX,QACkE;CAElE,MAAM,eAAeC,YADK,IAAI,qBAAqB,IAAI,CACA;AAEvD,UAAS,GAAG,SACV,eACE,MACC,GAAG,gBAAgB,aAAa,YAAY,EAC7C,KACD,CAAC,MAAM,WACN,IAAI,eAAe,IAAI,GAAG,SAAS,OAAO,WAAW,OAAO,CAC7D;;AAGL,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"}
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 * as Cause from \"effect/Cause\";\nimport * as Effect from \"effect/Effect\";\nimport * as Either from \"effect/Either\";\nimport * as Exit from \"effect/Exit\";\nimport * as Option from \"effect/Option\";\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":";;;;;;;;;;;AA4BA,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,7 +1,7 @@
1
1
  {
2
2
  "name": "@confect/react",
3
3
  "description": "Client-side bindings for React apps",
4
- "version": "9.0.0-next.6",
4
+ "version": "9.0.0-next.8",
5
5
  "author": "RJ Dellecese",
6
6
  "bugs": {
7
7
  "url": "https://github.com/rjdellecese/confect/issues"
@@ -9,11 +9,17 @@
9
9
  "devDependencies": {
10
10
  "@effect/vitest": "0.29.0",
11
11
  "@eslint/js": "10.0.1",
12
+ "@testing-library/react": "16.3.2",
12
13
  "@types/node": "25.3.3",
14
+ "@types/react": "19.2.14",
15
+ "@types/react-dom": "19.2.3",
13
16
  "convex": "1.39.1",
14
17
  "effect": "3.21.2",
15
18
  "eslint": "10.0.2",
19
+ "happy-dom": "15.11.7",
16
20
  "prettier": "3.8.1",
21
+ "react": "19.2.4",
22
+ "react-dom": "19.2.4",
17
23
  "tsdown": "0.20.3",
18
24
  "typescript": "5.9.3",
19
25
  "typescript-eslint": "8.56.1",
@@ -55,7 +61,7 @@
55
61
  "convex": "^1.30.0",
56
62
  "effect": "^3.21.2",
57
63
  "react": "^18.0.0 || ^19.0.0",
58
- "@confect/core": "^9.0.0-next.6"
64
+ "@confect/core": "^9.0.0-next.8"
59
65
  },
60
66
  "repository": {
61
67
  "type": "git",
@@ -1,4 +1,9 @@
1
- import { Equal, Function, Hash, identity, Pipeable, Predicate } from "effect";
1
+ import { identity } from "effect/Function";
2
+ import * as Equal from "effect/Equal";
3
+ import * as Function from "effect/Function";
4
+ import * as Hash from "effect/Hash";
5
+ import * as Pipeable from "effect/Pipeable";
6
+ import * as Predicate from "effect/Predicate";
2
7
 
3
8
  const TypeId = "@confect/react/QueryResult";
4
9
  type TypeId = typeof TypeId;
package/src/index.ts CHANGED
@@ -4,7 +4,12 @@ import {
4
4
  useMutation as useConvexMutation,
5
5
  useQuery as useConvexQuery,
6
6
  } from "convex/react";
7
- import { Cause, Effect, Either, Exit, Option } from "effect";
7
+ import * as Cause from "effect/Cause";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Either from "effect/Either";
10
+ import * as Exit from "effect/Exit";
11
+ import * as Option from "effect/Option";
12
+ import { useCallback, useMemo } from "react";
8
13
 
9
14
  import * as QueryResult from "./QueryResult";
10
15
 
@@ -27,33 +32,47 @@ export const useQuery = <Query extends Ref.AnyPublicQuery>(
27
32
  ): QueryResult.QueryResult<Ref.Returns<Query>, Ref.Error<Query>> => {
28
33
  const functionReference = Ref.getFunctionReference(ref);
29
34
  const args = rest[0];
30
- const encodedArgs =
31
- args === "skip"
32
- ? "skip"
33
- : Ref.encodeArgsSync(ref, (args ?? {}) as Ref.Args<Query>);
35
+ const skipped = args === "skip";
36
+ const encodedArgs = skipped
37
+ ? "skip"
38
+ : Ref.encodeArgsSync(ref, (args ?? {}) as Ref.Args<Query>);
34
39
 
35
- try {
36
- const encodedReturnsOrUndefined = useConvexQuery(
37
- functionReference,
38
- encodedArgs,
39
- );
40
-
41
- if (encodedReturnsOrUndefined === undefined) {
42
- return QueryResult.load(args === "skip");
43
- }
40
+ // `useConvexQuery` returns a referentially stable value while the underlying
41
+ // Convex result is unchanged, and throws a stable error when the query
42
+ // fails. We capture either outcome as an `Either` and decode/wrap it inside
43
+ // `useMemo` so that the returned `QueryResult` keeps a stable identity across
44
+ // renders when nothing has actually changed. Decoding on every render would
45
+ // hand consumers a fresh object each time, breaking effects and memoization
46
+ // that depend on the result's identity.
47
+ const encodedReturnsOrError: Either.Either<unknown, unknown> = Either.try(
48
+ () => useConvexQuery(functionReference, encodedArgs),
49
+ );
44
50
 
45
- return QueryResult.succeed(
46
- Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined),
47
- );
48
- } catch (error) {
49
- if (Ref.isConvexError(error)) {
50
- const decoded = Ref.decodeErrorSync(ref, error.data);
51
- if (Option.isSome(decoded)) {
52
- return QueryResult.fail(decoded.value);
53
- }
54
- }
55
- throw error;
56
- }
51
+ return useMemo(
52
+ () =>
53
+ Either.match(encodedReturnsOrError, {
54
+ onRight: (encodedReturnsOrUndefined) =>
55
+ encodedReturnsOrUndefined === undefined
56
+ ? QueryResult.load(skipped)
57
+ : QueryResult.succeed(
58
+ Ref.decodeReturnsSync(ref, encodedReturnsOrUndefined),
59
+ ),
60
+ onLeft: (error) => {
61
+ if (Ref.isConvexError(error)) {
62
+ const decoded = Ref.decodeErrorSync(ref, error.data);
63
+ if (Option.isSome(decoded)) {
64
+ return QueryResult.fail(decoded.value);
65
+ }
66
+ }
67
+ throw error;
68
+ },
69
+ }),
70
+ // `Either.try` allocates a fresh wrapper each render, so we key the memo on
71
+ // the stable value it carries (the Convex result or thrown error) rather
72
+ // than the wrapper itself; the decoded result is a function of that value,
73
+ // `ref`, and `skipped`.
74
+ [ref, skipped, Either.merge(encodedReturnsOrError)],
75
+ );
57
76
  };
58
77
 
59
78
  /**
@@ -75,14 +94,17 @@ export const useMutation = <Mutation extends Ref.AnyPublicMutation>(
75
94
  const functionReference = Ref.getFunctionReference(ref);
76
95
  const actualMutation = useConvexMutation(functionReference);
77
96
 
78
- return ((...args: Ref.OptionalArgs<Mutation>) =>
79
- invokeAsEither(
80
- ref,
81
- (_, encodedArgs) => actualMutation(encodedArgs),
82
- args,
83
- ).then((either) =>
84
- Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
85
- )) as (...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>;
97
+ return useCallback(
98
+ ((...args: Ref.OptionalArgs<Mutation>) =>
99
+ invokeAsEither(
100
+ ref,
101
+ (_, encodedArgs) => actualMutation(encodedArgs),
102
+ args,
103
+ ).then((either) =>
104
+ Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
105
+ )) as (...args: Ref.OptionalArgs<Mutation>) => InvokeReturn<Mutation>,
106
+ [ref, actualMutation],
107
+ );
86
108
  };
87
109
 
88
110
  /**
@@ -104,14 +126,17 @@ export const useAction = <Action extends Ref.AnyPublicAction>(
104
126
  const functionReference = Ref.getFunctionReference(ref);
105
127
  const actualAction = useConvexAction(functionReference);
106
128
 
107
- return ((...args: Ref.OptionalArgs<Action>) =>
108
- invokeAsEither(
109
- ref,
110
- (_, encodedArgs) => actualAction(encodedArgs),
111
- args,
112
- ).then((either) =>
113
- Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
114
- )) as (...args: Ref.OptionalArgs<Action>) => InvokeReturn<Action>;
129
+ return useCallback(
130
+ ((...args: Ref.OptionalArgs<Action>) =>
131
+ invokeAsEither(
132
+ ref,
133
+ (_, encodedArgs) => actualAction(encodedArgs),
134
+ args,
135
+ ).then((either) =>
136
+ Ref.hasErrorSchema(ref) ? either : Either.getOrThrow(either),
137
+ )) as (...args: Ref.OptionalArgs<Action>) => InvokeReturn<Action>,
138
+ [ref, actualAction],
139
+ );
115
140
  };
116
141
 
117
142
  const invokeAsEither = async <Ref_ extends Ref.Any>(