@confect/js 10.0.0-next.20 → 10.0.0-next.21

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketClient.js","names":[],"sources":["../../src/internal/WebSocketClient.ts"],"sourcesContent":["import * as Ref from \"@confect/core/Ref\";\nimport * as Effect from \"effect/Effect\";\nimport * as Queue from \"effect/Queue\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport * as Stream from \"effect/Stream\";\n\nexport class WebSocketClientError extends Schema.TaggedError<WebSocketClientError>()(\n \"WebSocketClientError\",\n {\n cause: Schema.Defect(),\n },\n) {}\n\nexport interface Transport {\n readonly setAuth: (\n fetchToken: (args: {\n readonly forceRefreshToken: boolean;\n }) => Promise<string | null | undefined>,\n onChange?: (isAuthenticated: boolean) => void,\n ) => void;\n readonly close: () => Promise<void>;\n readonly query: (\n functionReference: Ref.FunctionReference<Ref.AnyPublicQuery>,\n encodedArgs: unknown,\n ) => PromiseLike<unknown>;\n readonly mutation: (\n functionReference: Ref.FunctionReference<Ref.AnyPublicMutation>,\n encodedArgs: unknown,\n ) => PromiseLike<unknown>;\n readonly action: (\n functionReference: Ref.FunctionReference<Ref.AnyPublicAction>,\n encodedArgs: unknown,\n ) => PromiseLike<unknown>;\n readonly onUpdate: (\n functionReference: Ref.FunctionReference<Ref.AnyPublicQuery>,\n encodedArgs: unknown,\n onUpdate: (result: unknown) => void,\n onError: (error: Error) => void,\n ) => () => void;\n}\n\nexport const make = (address: string, convexClient: Transport) => {\n const setAuth = (\n fetchToken: (args: {\n forceRefreshToken: boolean;\n }) => Effect.Effect<string | null | undefined>,\n onChange?: (isAuthenticated: boolean) => Effect.Effect<void>,\n ) =>\n Effect.sync(() => {\n convexClient.setAuth(\n (args) => Effect.runPromise(fetchToken(args)),\n ...(onChange\n ? [\n (isAuthenticated: boolean) =>\n Effect.runFork(onChange(isAuthenticated)),\n ]\n : []),\n );\n });\n\n const mapUnknownError = (cause: unknown) =>\n new WebSocketClientError({ cause });\n\n const query = <Query extends Ref.AnyPublicQuery>(\n ref: Query,\n ...rest: Ref.OptionalArgs<Query>\n ): Effect.Effect<\n Ref.Returns<Query>,\n Ref.Error<Query> | WebSocketClientError | Schema.SchemaError\n > => {\n const args = (rest[0] ?? {}) as Ref.Args<Query>;\n return Ref.runWithCodec(\n ref,\n args,\n (functionReference, encodedArgs) =>\n convexClient.query(functionReference, encodedArgs),\n mapUnknownError,\n );\n };\n\n const mutation = <Mutation extends Ref.AnyPublicMutation>(\n ref: Mutation,\n ...rest: Ref.OptionalArgs<Mutation>\n ): Effect.Effect<\n Ref.Returns<Mutation>,\n Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError\n > => {\n const args = (rest[0] ?? {}) as Ref.Args<Mutation>;\n return Ref.runWithCodec(\n ref,\n args,\n (functionReference, encodedArgs) =>\n convexClient.mutation(functionReference, encodedArgs),\n mapUnknownError,\n );\n };\n\n const action = <Action extends Ref.AnyPublicAction>(\n ref: Action,\n ...rest: Ref.OptionalArgs<Action>\n ): Effect.Effect<\n Ref.Returns<Action>,\n Ref.Error<Action> | WebSocketClientError | Schema.SchemaError\n > => {\n const args = (rest[0] ?? {}) as Ref.Args<Action>;\n return Ref.runWithCodec(\n ref,\n args,\n (functionReference, encodedArgs) =>\n convexClient.action(functionReference, encodedArgs),\n mapUnknownError,\n );\n };\n\n type ReactiveQueryError<Query extends Ref.AnyPublicQuery> =\n | Ref.Error<Query>\n | WebSocketClientError\n | Schema.SchemaError;\n\n const reactiveQueryResult = <Query extends Ref.AnyPublicQuery>(\n ref: Query,\n ...rest: Ref.OptionalArgs<Query>\n ): Stream.Stream<\n Result.Result<Ref.Returns<Query>, ReactiveQueryError<Query>>\n > => {\n type QueryResult = Result.Result<\n Ref.Returns<Query>,\n ReactiveQueryError<Query>\n >;\n const args = (rest[0] ?? {}) as Ref.Args<Query>;\n const functionReference = Ref.getFunctionReference(ref);\n const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);\n\n return Stream.unwrap(\n Effect.match(Ref.encodeArgs(ref, args), {\n onFailure: (error) => Stream.succeed<QueryResult>(Result.fail(error)),\n onSuccess: (encodedArgs) =>\n Stream.callback<Result.Result<unknown, ReactiveQueryError<Query>>>(\n (queue) =>\n Effect.gen(function* () {\n const unsubscribe = convexClient.onUpdate(\n functionReference,\n encodedArgs,\n (result) => {\n Queue.offerUnsafe(queue, Result.succeed(result));\n },\n (error) => {\n Queue.offerUnsafe(queue, Result.fail(onError(error)));\n },\n );\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => unsubscribe()),\n );\n }),\n ),\n }),\n ).pipe(\n Stream.mapEffect((result): Effect.Effect<QueryResult> =>\n Result.match(result, {\n onFailure: (error) => Effect.succeed(Result.fail(error)),\n onSuccess: (encodedReturns) =>\n Effect.match(Ref.decodeReturns(ref, encodedReturns), {\n onFailure: Result.fail,\n onSuccess: Result.succeed,\n }),\n }),\n ),\n );\n };\n\n const reactiveQuery = <Query extends Ref.AnyPublicQuery>(\n ref: Query,\n ...rest: Ref.OptionalArgs<Query>\n ): Stream.Stream<Ref.Returns<Query>, ReactiveQueryError<Query>> =>\n reactiveQueryResult(ref, ...rest).pipe(\n Stream.mapEffect(\n Result.match({\n onFailure: Effect.fail,\n onSuccess: Effect.succeed,\n }),\n ),\n );\n\n return {\n url: address,\n setAuth,\n query,\n mutation,\n action,\n reactiveQuery,\n reactiveQueryResult,\n };\n};\n\nexport const makeScoped = <E, R>(\n address: string,\n acquire: Effect.Effect<Transport, E, R>,\n) =>\n Effect.acquireRelease(acquire, (convexClient) =>\n Effect.promise(() => convexClient.close()),\n ).pipe(Effect.map((convexClient) => make(address, convexClient)));\n"],"mappings":";;;;;;;AAOA,IAAa,uBAAb,cAA0C,OAAO,YAAkC,CAAC,CAClF,wBACA,EACE,OAAO,OAAO,OAAO,EACvB,CACF,CAAC,CAAC,CAAC;AA8BH,MAAa,QAAQ,SAAiB,iBAA4B;CAChE,MAAM,WACJ,YAGA,aAEA,OAAO,WAAW;EAChB,aAAa,SACV,SAAS,OAAO,WAAW,WAAW,IAAI,CAAC,GAC5C,GAAI,WACA,EACG,oBACC,OAAO,QAAQ,SAAS,eAAe,CAAC,CAC5C,IACA,CAAC,CACP;CACF,CAAC;CAEH,MAAM,mBAAmB,UACvB,IAAI,qBAAqB,EAAE,MAAM,CAAC;CAEpC,MAAM,SACJ,KACA,GAAG,SAIA;EACH,MAAM,OAAQ,KAAK,MAAM,CAAC;EAC1B,OAAO,IAAI,aACT,KACA,OACC,mBAAmB,gBAClB,aAAa,MAAM,mBAAmB,WAAW,GACnD,eACF;CACF;CAEA,MAAM,YACJ,KACA,GAAG,SAIA;EACH,MAAM,OAAQ,KAAK,MAAM,CAAC;EAC1B,OAAO,IAAI,aACT,KACA,OACC,mBAAmB,gBAClB,aAAa,SAAS,mBAAmB,WAAW,GACtD,eACF;CACF;CAEA,MAAM,UACJ,KACA,GAAG,SAIA;EACH,MAAM,OAAQ,KAAK,MAAM,CAAC;EAC1B,OAAO,IAAI,aACT,KACA,OACC,mBAAmB,gBAClB,aAAa,OAAO,mBAAmB,WAAW,GACpD,eACF;CACF;CAOA,MAAM,uBACJ,KACA,GAAG,SAGA;EAKH,MAAM,OAAQ,KAAK,MAAM,CAAC;EAC1B,MAAM,oBAAoB,IAAI,qBAAqB,GAAG;EACtD,MAAM,UAAU,IAAI,kBAAkB,KAAK,eAAe;EAE1D,OAAO,OAAO,OACZ,OAAO,MAAM,IAAI,WAAW,KAAK,IAAI,GAAG;GACtC,YAAY,UAAU,OAAO,QAAqB,OAAO,KAAK,KAAK,CAAC;GACpE,YAAY,gBACV,OAAO,UACJ,UACC,OAAO,IAAI,aAAa;IACtB,MAAM,cAAc,aAAa,SAC/B,mBACA,cACC,WAAW;KACV,MAAM,YAAY,OAAO,OAAO,QAAQ,MAAM,CAAC;IACjD,IACC,UAAU;KACT,MAAM,YAAY,OAAO,OAAO,KAAK,QAAQ,KAAK,CAAC,CAAC;IACtD,CACF;IACA,OAAO,OAAO,mBACZ,OAAO,WAAW,YAAY,CAAC,CACjC;GACF,CAAC,CACL;EACJ,CAAC,CACH,CAAC,CAAC,KACA,OAAO,WAAW,WAChB,OAAO,MAAM,QAAQ;GACnB,YAAY,UAAU,OAAO,QAAQ,OAAO,KAAK,KAAK,CAAC;GACvD,YAAY,mBACV,OAAO,MAAM,IAAI,cAAc,KAAK,cAAc,GAAG;IACnD,WAAW,OAAO;IAClB,WAAW,OAAO;GACpB,CAAC;EACL,CAAC,CACH,CACF;CACF;CAEA,MAAM,iBACJ,KACA,GAAG,SAEH,oBAAoB,KAAK,GAAG,IAAI,CAAC,CAAC,KAChC,OAAO,UACL,OAAO,MAAM;EACX,WAAW,OAAO;EAClB,WAAW,OAAO;CACpB,CAAC,CACH,CACF;CAEF,OAAO;EACL,KAAK;EACL;EACA;EACA;EACA;EACA;EACA;CACF;AACF;AAEA,MAAa,cACX,SACA,YAEA,OAAO,eAAe,UAAU,iBAC9B,OAAO,cAAc,aAAa,MAAM,CAAC,CAC3C,CAAC,CAAC,KAAK,OAAO,KAAK,iBAAiB,KAAK,SAAS,YAAY,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@confect/js",
3
3
  "description": "JavaScript client bindings for any JS runtime",
4
- "version": "10.0.0-next.20",
4
+ "version": "10.0.0-next.21",
5
5
  "author": "RJ Dellecese",
6
6
  "bugs": {
7
7
  "url": "https://github.com/rjdellecese/confect/issues"
8
8
  },
9
9
  "devDependencies": {
10
10
  "@effect/vitest": "4.0.0-rc.112",
11
- "@types/node": "22.20.1",
11
+ "@types/node": "24.13.3",
12
12
  "effect": "4.0.0-rc.112",
13
13
  "tsdown": "0.22.14",
14
14
  "typescript": "7.0.2",
15
15
  "unrun": "0.3.1",
16
- "vitest": "4.1.10"
16
+ "vitest": "4.1.11"
17
17
  },
18
18
  "engines": {
19
- "node": ">=22"
19
+ "node": ">=24"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -47,9 +47,9 @@
47
47
  "main": "./dist/index.js",
48
48
  "module": "./dist/index.js",
49
49
  "peerDependencies": {
50
+ "@confect/core": "^10.0.0-next.21",
50
51
  "convex": "^1.32.0",
51
- "effect": "^4.0.0-rc.112",
52
- "@confect/core": "^10.0.0-next.20"
52
+ "effect": "^4.0.0-rc.112"
53
53
  },
54
54
  "repository": {
55
55
  "type": "git",
package/src/HttpClient.ts CHANGED
@@ -1,95 +1,26 @@
1
- import * as Ref from "@confect/core/Ref";
2
1
  import { ConvexHttpClient } from "convex/browser";
3
2
  import * as Context from "effect/Context";
4
- import * as Effect from "effect/Effect";
5
3
  import * as Layer from "effect/Layer";
6
- import * as Schema from "effect/Schema";
4
+ import * as InternalHttpClient from "./internal/HttpClient";
7
5
 
8
- export class HttpClientError extends Schema.TaggedError<HttpClientError>()(
9
- "HttpClientError",
10
- {
11
- cause: Schema.Unknown,
12
- },
13
- ) {}
6
+ export { HttpClientError } from "./internal/HttpClient";
14
7
 
15
8
  const make = (
16
9
  address: string,
17
10
  options?: ConstructorParameters<typeof ConvexHttpClient>[1],
18
11
  ) => {
19
12
  const client = new ConvexHttpClient(address, options);
20
-
21
- const url = client.url;
22
-
23
- const setAuth = (token: string) =>
24
- Effect.sync(() => {
25
- client.setAuth(token);
26
- });
27
-
28
- const clearAuth = Effect.sync(() => {
29
- client.clearAuth();
13
+ return InternalHttpClient.make({
14
+ url: client.url,
15
+ setAuth: (token) => client.setAuth(token),
16
+ clearAuth: () => client.clearAuth(),
17
+ query: (functionReference, encodedArgs) =>
18
+ client.query(functionReference, encodedArgs),
19
+ mutation: (functionReference, encodedArgs) =>
20
+ client.mutation(functionReference, encodedArgs),
21
+ action: (functionReference, encodedArgs) =>
22
+ client.action(functionReference, encodedArgs),
30
23
  });
31
-
32
- const mapUnknownError = (cause: unknown) => new HttpClientError({ cause });
33
-
34
- const query = <Query extends Ref.AnyPublicQuery>(
35
- ref: Query,
36
- ...rest: Ref.OptionalArgs<Query>
37
- ): Effect.Effect<
38
- Ref.Returns<Query>,
39
- Ref.Error<Query> | HttpClientError | Schema.SchemaError
40
- > => {
41
- const args = (rest[0] ?? {}) as Ref.Args<Query>;
42
- return Ref.runWithCodec(
43
- ref,
44
- args,
45
- (functionReference, encodedArgs) =>
46
- client.query(functionReference, encodedArgs),
47
- mapUnknownError,
48
- );
49
- };
50
-
51
- const mutation = <Mutation extends Ref.AnyPublicMutation>(
52
- ref: Mutation,
53
- ...rest: Ref.OptionalArgs<Mutation>
54
- ): Effect.Effect<
55
- Ref.Returns<Mutation>,
56
- Ref.Error<Mutation> | HttpClientError | Schema.SchemaError
57
- > => {
58
- const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
59
- return Ref.runWithCodec(
60
- ref,
61
- args,
62
- (functionReference, encodedArgs) =>
63
- client.mutation(functionReference, encodedArgs),
64
- mapUnknownError,
65
- );
66
- };
67
-
68
- const action = <Action extends Ref.AnyPublicAction>(
69
- ref: Action,
70
- ...rest: Ref.OptionalArgs<Action>
71
- ): Effect.Effect<
72
- Ref.Returns<Action>,
73
- Ref.Error<Action> | HttpClientError | Schema.SchemaError
74
- > => {
75
- const args = (rest[0] ?? {}) as Ref.Args<Action>;
76
- return Ref.runWithCodec(
77
- ref,
78
- args,
79
- (functionReference, encodedArgs) =>
80
- client.action(functionReference, encodedArgs),
81
- mapUnknownError,
82
- );
83
- };
84
-
85
- return {
86
- url,
87
- setAuth,
88
- clearAuth,
89
- query,
90
- mutation,
91
- action,
92
- };
93
24
  };
94
25
 
95
26
  /**
@@ -1,182 +1,38 @@
1
- import * as Ref from "@confect/core/Ref";
2
1
  import { ConvexClient } from "convex/browser";
3
2
  import * as Context from "effect/Context";
4
3
  import * as Effect from "effect/Effect";
5
4
  import * as Layer from "effect/Layer";
6
- import * as Queue from "effect/Queue";
7
- import * as Result from "effect/Result";
8
- import * as Schema from "effect/Schema";
9
- import * as Stream from "effect/Stream";
5
+ import * as InternalWebSocketClient from "./internal/WebSocketClient";
10
6
 
11
- export class WebSocketClientError extends Schema.TaggedError<WebSocketClientError>()(
12
- "WebSocketClientError",
13
- {
14
- cause: Schema.Defect(),
15
- },
16
- ) {}
7
+ export { WebSocketClientError } from "./internal/WebSocketClient";
17
8
 
18
9
  const make = (
19
10
  address: string,
20
11
  options?: ConstructorParameters<typeof ConvexClient>[1],
21
12
  ) =>
22
- Effect.acquireRelease(
23
- Effect.sync(() => new ConvexClient(address, options)),
24
- (convexClient) => Effect.promise(() => convexClient.close()),
25
- ).pipe(
26
- Effect.map((convexClient) => {
27
- const url = address;
28
-
29
- const setAuth = (
30
- fetchToken: (args: {
31
- forceRefreshToken: boolean;
32
- }) => Effect.Effect<string | null | undefined>,
33
- onChange?: (isAuthenticated: boolean) => Effect.Effect<void>,
34
- ) =>
35
- Effect.sync(() => {
36
- convexClient.setAuth(
37
- (args) => Effect.runPromise(fetchToken(args)),
38
- ...(onChange
39
- ? [
40
- (isAuthenticated: boolean) =>
41
- Effect.runFork(onChange(isAuthenticated)),
42
- ]
43
- : []),
44
- );
45
- });
46
-
47
- const mapUnknownError = (cause: unknown) =>
48
- new WebSocketClientError({ cause });
49
-
50
- const query = <Query extends Ref.AnyPublicQuery>(
51
- ref: Query,
52
- ...rest: Ref.OptionalArgs<Query>
53
- ): Effect.Effect<
54
- Ref.Returns<Query>,
55
- Ref.Error<Query> | WebSocketClientError | Schema.SchemaError
56
- > => {
57
- const args = (rest[0] ?? {}) as Ref.Args<Query>;
58
- return Ref.runWithCodec(
59
- ref,
60
- args,
61
- (functionReference, encodedArgs) =>
62
- convexClient.query(functionReference, encodedArgs),
63
- mapUnknownError,
64
- );
65
- };
66
-
67
- const mutation = <Mutation extends Ref.AnyPublicMutation>(
68
- ref: Mutation,
69
- ...rest: Ref.OptionalArgs<Mutation>
70
- ): Effect.Effect<
71
- Ref.Returns<Mutation>,
72
- Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError
73
- > => {
74
- const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
75
- return Ref.runWithCodec(
76
- ref,
77
- args,
78
- (functionReference, encodedArgs) =>
79
- convexClient.mutation(functionReference, encodedArgs),
80
- mapUnknownError,
81
- );
82
- };
83
-
84
- const action = <Action extends Ref.AnyPublicAction>(
85
- ref: Action,
86
- ...rest: Ref.OptionalArgs<Action>
87
- ): Effect.Effect<
88
- Ref.Returns<Action>,
89
- Ref.Error<Action> | WebSocketClientError | Schema.SchemaError
90
- > => {
91
- const args = (rest[0] ?? {}) as Ref.Args<Action>;
92
- return Ref.runWithCodec(
93
- ref,
94
- args,
95
- (functionReference, encodedArgs) =>
96
- convexClient.action(functionReference, encodedArgs),
97
- mapUnknownError,
98
- );
99
- };
100
-
101
- type ReactiveQueryError<Query extends Ref.AnyPublicQuery> =
102
- | Ref.Error<Query>
103
- | WebSocketClientError
104
- | Schema.SchemaError;
105
-
106
- const reactiveQueryResult = <Query extends Ref.AnyPublicQuery>(
107
- ref: Query,
108
- ...rest: Ref.OptionalArgs<Query>
109
- ): Stream.Stream<
110
- Result.Result<Ref.Returns<Query>, ReactiveQueryError<Query>>
111
- > => {
112
- type QueryResult = Result.Result<
113
- Ref.Returns<Query>,
114
- ReactiveQueryError<Query>
115
- >;
116
- const args = (rest[0] ?? {}) as Ref.Args<Query>;
117
- const functionReference = Ref.getFunctionReference(ref);
118
- const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);
119
-
120
- return Stream.unwrap(
121
- Effect.match(Ref.encodeArgs(ref, args), {
122
- onFailure: (error) =>
123
- Stream.succeed<QueryResult>(Result.fail(error)),
124
- onSuccess: (encodedArgs) =>
125
- Stream.callback<
126
- Result.Result<unknown, ReactiveQueryError<Query>>
127
- >((queue) =>
128
- Effect.gen(function* () {
129
- const unsubscribe = convexClient.onUpdate(
130
- functionReference,
131
- encodedArgs,
132
- (result) => {
133
- Queue.offerUnsafe(queue, Result.succeed(result));
134
- },
135
- (error) => {
136
- Queue.offerUnsafe(queue, Result.fail(onError(error)));
137
- },
138
- );
139
- yield* Effect.addFinalizer(() =>
140
- Effect.sync(() => unsubscribe()),
141
- );
142
- }),
143
- ),
144
- }),
145
- ).pipe(
146
- Stream.mapEffect((result): Effect.Effect<QueryResult> =>
147
- Result.match(result, {
148
- onFailure: (error) => Effect.succeed(Result.fail(error)),
149
- onSuccess: (encodedReturns) =>
150
- Effect.match(Ref.decodeReturns(ref, encodedReturns), {
151
- onFailure: Result.fail,
152
- onSuccess: Result.succeed,
153
- }),
154
- }),
155
- ),
156
- );
157
- };
158
-
159
- const reactiveQuery = <Query extends Ref.AnyPublicQuery>(
160
- ref: Query,
161
- ...rest: Ref.OptionalArgs<Query>
162
- ): Stream.Stream<Ref.Returns<Query>, ReactiveQueryError<Query>> =>
163
- reactiveQueryResult(ref, ...rest).pipe(
164
- Stream.mapEffect(
165
- Result.match({
166
- onFailure: Effect.fail,
167
- onSuccess: Effect.succeed,
168
- }),
169
- ),
170
- );
171
-
13
+ InternalWebSocketClient.makeScoped(
14
+ address,
15
+ Effect.sync(() => {
16
+ const convexClient = new ConvexClient(address, options);
172
17
  return {
173
- url,
174
- setAuth,
175
- query,
176
- mutation,
177
- action,
178
- reactiveQuery,
179
- reactiveQueryResult,
18
+ setAuth: (fetchToken, onChange) =>
19
+ onChange === undefined
20
+ ? convexClient.setAuth(fetchToken)
21
+ : convexClient.setAuth(fetchToken, onChange),
22
+ close: () => convexClient.close(),
23
+ query: (functionReference, encodedArgs) =>
24
+ convexClient.query(functionReference, encodedArgs),
25
+ mutation: (functionReference, encodedArgs) =>
26
+ convexClient.mutation(functionReference, encodedArgs),
27
+ action: (functionReference, encodedArgs) =>
28
+ convexClient.action(functionReference, encodedArgs),
29
+ onUpdate: (functionReference, encodedArgs, onUpdate, onError) =>
30
+ convexClient.onUpdate(
31
+ functionReference,
32
+ encodedArgs,
33
+ onUpdate,
34
+ onError,
35
+ ),
180
36
  };
181
37
  }),
182
38
  );
@@ -0,0 +1,101 @@
1
+ import * as Ref from "@confect/core/Ref";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Schema from "effect/Schema";
4
+
5
+ export class HttpClientError extends Schema.TaggedError<HttpClientError>()(
6
+ "HttpClientError",
7
+ {
8
+ cause: Schema.Unknown,
9
+ },
10
+ ) {}
11
+
12
+ export interface Transport {
13
+ readonly url: string;
14
+ readonly setAuth: (token: string) => void;
15
+ readonly clearAuth: () => void;
16
+ readonly query: (
17
+ functionReference: Ref.FunctionReference<Ref.AnyPublicQuery>,
18
+ encodedArgs: unknown,
19
+ ) => PromiseLike<unknown>;
20
+ readonly mutation: (
21
+ functionReference: Ref.FunctionReference<Ref.AnyPublicMutation>,
22
+ encodedArgs: unknown,
23
+ ) => PromiseLike<unknown>;
24
+ readonly action: (
25
+ functionReference: Ref.FunctionReference<Ref.AnyPublicAction>,
26
+ encodedArgs: unknown,
27
+ ) => PromiseLike<unknown>;
28
+ }
29
+
30
+ export const make = (client: Transport) => {
31
+ const setAuth = (token: string) =>
32
+ Effect.sync(() => {
33
+ client.setAuth(token);
34
+ });
35
+
36
+ const clearAuth = Effect.sync(() => {
37
+ client.clearAuth();
38
+ });
39
+
40
+ const mapUnknownError = (cause: unknown) => new HttpClientError({ cause });
41
+
42
+ const query = <Query extends Ref.AnyPublicQuery>(
43
+ ref: Query,
44
+ ...rest: Ref.OptionalArgs<Query>
45
+ ): Effect.Effect<
46
+ Ref.Returns<Query>,
47
+ Ref.Error<Query> | HttpClientError | Schema.SchemaError
48
+ > => {
49
+ const args = (rest[0] ?? {}) as Ref.Args<Query>;
50
+ return Ref.runWithCodec(
51
+ ref,
52
+ args,
53
+ (functionReference, encodedArgs) =>
54
+ client.query(functionReference, encodedArgs),
55
+ mapUnknownError,
56
+ );
57
+ };
58
+
59
+ const mutation = <Mutation extends Ref.AnyPublicMutation>(
60
+ ref: Mutation,
61
+ ...rest: Ref.OptionalArgs<Mutation>
62
+ ): Effect.Effect<
63
+ Ref.Returns<Mutation>,
64
+ Ref.Error<Mutation> | HttpClientError | Schema.SchemaError
65
+ > => {
66
+ const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
67
+ return Ref.runWithCodec(
68
+ ref,
69
+ args,
70
+ (functionReference, encodedArgs) =>
71
+ client.mutation(functionReference, encodedArgs),
72
+ mapUnknownError,
73
+ );
74
+ };
75
+
76
+ const action = <Action extends Ref.AnyPublicAction>(
77
+ ref: Action,
78
+ ...rest: Ref.OptionalArgs<Action>
79
+ ): Effect.Effect<
80
+ Ref.Returns<Action>,
81
+ Ref.Error<Action> | HttpClientError | Schema.SchemaError
82
+ > => {
83
+ const args = (rest[0] ?? {}) as Ref.Args<Action>;
84
+ return Ref.runWithCodec(
85
+ ref,
86
+ args,
87
+ (functionReference, encodedArgs) =>
88
+ client.action(functionReference, encodedArgs),
89
+ mapUnknownError,
90
+ );
91
+ };
92
+
93
+ return {
94
+ url: client.url,
95
+ setAuth,
96
+ clearAuth,
97
+ query,
98
+ mutation,
99
+ action,
100
+ };
101
+ };
@@ -0,0 +1,202 @@
1
+ import * as Ref from "@confect/core/Ref";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Queue from "effect/Queue";
4
+ import * as Result from "effect/Result";
5
+ import * as Schema from "effect/Schema";
6
+ import * as Stream from "effect/Stream";
7
+
8
+ export class WebSocketClientError extends Schema.TaggedError<WebSocketClientError>()(
9
+ "WebSocketClientError",
10
+ {
11
+ cause: Schema.Defect(),
12
+ },
13
+ ) {}
14
+
15
+ export interface Transport {
16
+ readonly setAuth: (
17
+ fetchToken: (args: {
18
+ readonly forceRefreshToken: boolean;
19
+ }) => Promise<string | null | undefined>,
20
+ onChange?: (isAuthenticated: boolean) => void,
21
+ ) => void;
22
+ readonly close: () => Promise<void>;
23
+ readonly query: (
24
+ functionReference: Ref.FunctionReference<Ref.AnyPublicQuery>,
25
+ encodedArgs: unknown,
26
+ ) => PromiseLike<unknown>;
27
+ readonly mutation: (
28
+ functionReference: Ref.FunctionReference<Ref.AnyPublicMutation>,
29
+ encodedArgs: unknown,
30
+ ) => PromiseLike<unknown>;
31
+ readonly action: (
32
+ functionReference: Ref.FunctionReference<Ref.AnyPublicAction>,
33
+ encodedArgs: unknown,
34
+ ) => PromiseLike<unknown>;
35
+ readonly onUpdate: (
36
+ functionReference: Ref.FunctionReference<Ref.AnyPublicQuery>,
37
+ encodedArgs: unknown,
38
+ onUpdate: (result: unknown) => void,
39
+ onError: (error: Error) => void,
40
+ ) => () => void;
41
+ }
42
+
43
+ export const make = (address: string, convexClient: Transport) => {
44
+ const setAuth = (
45
+ fetchToken: (args: {
46
+ forceRefreshToken: boolean;
47
+ }) => Effect.Effect<string | null | undefined>,
48
+ onChange?: (isAuthenticated: boolean) => Effect.Effect<void>,
49
+ ) =>
50
+ Effect.sync(() => {
51
+ convexClient.setAuth(
52
+ (args) => Effect.runPromise(fetchToken(args)),
53
+ ...(onChange
54
+ ? [
55
+ (isAuthenticated: boolean) =>
56
+ Effect.runFork(onChange(isAuthenticated)),
57
+ ]
58
+ : []),
59
+ );
60
+ });
61
+
62
+ const mapUnknownError = (cause: unknown) =>
63
+ new WebSocketClientError({ cause });
64
+
65
+ const query = <Query extends Ref.AnyPublicQuery>(
66
+ ref: Query,
67
+ ...rest: Ref.OptionalArgs<Query>
68
+ ): Effect.Effect<
69
+ Ref.Returns<Query>,
70
+ Ref.Error<Query> | WebSocketClientError | Schema.SchemaError
71
+ > => {
72
+ const args = (rest[0] ?? {}) as Ref.Args<Query>;
73
+ return Ref.runWithCodec(
74
+ ref,
75
+ args,
76
+ (functionReference, encodedArgs) =>
77
+ convexClient.query(functionReference, encodedArgs),
78
+ mapUnknownError,
79
+ );
80
+ };
81
+
82
+ const mutation = <Mutation extends Ref.AnyPublicMutation>(
83
+ ref: Mutation,
84
+ ...rest: Ref.OptionalArgs<Mutation>
85
+ ): Effect.Effect<
86
+ Ref.Returns<Mutation>,
87
+ Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError
88
+ > => {
89
+ const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
90
+ return Ref.runWithCodec(
91
+ ref,
92
+ args,
93
+ (functionReference, encodedArgs) =>
94
+ convexClient.mutation(functionReference, encodedArgs),
95
+ mapUnknownError,
96
+ );
97
+ };
98
+
99
+ const action = <Action extends Ref.AnyPublicAction>(
100
+ ref: Action,
101
+ ...rest: Ref.OptionalArgs<Action>
102
+ ): Effect.Effect<
103
+ Ref.Returns<Action>,
104
+ Ref.Error<Action> | WebSocketClientError | Schema.SchemaError
105
+ > => {
106
+ const args = (rest[0] ?? {}) as Ref.Args<Action>;
107
+ return Ref.runWithCodec(
108
+ ref,
109
+ args,
110
+ (functionReference, encodedArgs) =>
111
+ convexClient.action(functionReference, encodedArgs),
112
+ mapUnknownError,
113
+ );
114
+ };
115
+
116
+ type ReactiveQueryError<Query extends Ref.AnyPublicQuery> =
117
+ | Ref.Error<Query>
118
+ | WebSocketClientError
119
+ | Schema.SchemaError;
120
+
121
+ const reactiveQueryResult = <Query extends Ref.AnyPublicQuery>(
122
+ ref: Query,
123
+ ...rest: Ref.OptionalArgs<Query>
124
+ ): Stream.Stream<
125
+ Result.Result<Ref.Returns<Query>, ReactiveQueryError<Query>>
126
+ > => {
127
+ type QueryResult = Result.Result<
128
+ Ref.Returns<Query>,
129
+ ReactiveQueryError<Query>
130
+ >;
131
+ const args = (rest[0] ?? {}) as Ref.Args<Query>;
132
+ const functionReference = Ref.getFunctionReference(ref);
133
+ const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);
134
+
135
+ return Stream.unwrap(
136
+ Effect.match(Ref.encodeArgs(ref, args), {
137
+ onFailure: (error) => Stream.succeed<QueryResult>(Result.fail(error)),
138
+ onSuccess: (encodedArgs) =>
139
+ Stream.callback<Result.Result<unknown, ReactiveQueryError<Query>>>(
140
+ (queue) =>
141
+ Effect.gen(function* () {
142
+ const unsubscribe = convexClient.onUpdate(
143
+ functionReference,
144
+ encodedArgs,
145
+ (result) => {
146
+ Queue.offerUnsafe(queue, Result.succeed(result));
147
+ },
148
+ (error) => {
149
+ Queue.offerUnsafe(queue, Result.fail(onError(error)));
150
+ },
151
+ );
152
+ yield* Effect.addFinalizer(() =>
153
+ Effect.sync(() => unsubscribe()),
154
+ );
155
+ }),
156
+ ),
157
+ }),
158
+ ).pipe(
159
+ Stream.mapEffect((result): Effect.Effect<QueryResult> =>
160
+ Result.match(result, {
161
+ onFailure: (error) => Effect.succeed(Result.fail(error)),
162
+ onSuccess: (encodedReturns) =>
163
+ Effect.match(Ref.decodeReturns(ref, encodedReturns), {
164
+ onFailure: Result.fail,
165
+ onSuccess: Result.succeed,
166
+ }),
167
+ }),
168
+ ),
169
+ );
170
+ };
171
+
172
+ const reactiveQuery = <Query extends Ref.AnyPublicQuery>(
173
+ ref: Query,
174
+ ...rest: Ref.OptionalArgs<Query>
175
+ ): Stream.Stream<Ref.Returns<Query>, ReactiveQueryError<Query>> =>
176
+ reactiveQueryResult(ref, ...rest).pipe(
177
+ Stream.mapEffect(
178
+ Result.match({
179
+ onFailure: Effect.fail,
180
+ onSuccess: Effect.succeed,
181
+ }),
182
+ ),
183
+ );
184
+
185
+ return {
186
+ url: address,
187
+ setAuth,
188
+ query,
189
+ mutation,
190
+ action,
191
+ reactiveQuery,
192
+ reactiveQueryResult,
193
+ };
194
+ };
195
+
196
+ export const makeScoped = <E, R>(
197
+ address: string,
198
+ acquire: Effect.Effect<Transport, E, R>,
199
+ ) =>
200
+ Effect.acquireRelease(acquire, (convexClient) =>
201
+ Effect.promise(() => convexClient.close()),
202
+ ).pipe(Effect.map((convexClient) => make(address, convexClient)));