@gaganref/confect-js 0.0.0-stage → 10.3.1-fork.2
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 +1324 -0
- package/LICENSE +7 -0
- package/README.md +16 -2
- package/dist/HttpClient.d.ts +35 -0
- package/dist/HttpClient.d.ts.map +1 -0
- package/dist/HttpClient.js +33 -0
- package/dist/HttpClient.js.map +1 -0
- package/dist/WebSocketClient.d.ts +47 -0
- package/dist/WebSocketClient.d.ts.map +1 -0
- package/dist/WebSocketClient.js +36 -0
- package/dist/WebSocketClient.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/internal/HttpClient.d.ts +26 -0
- package/dist/internal/HttpClient.d.ts.map +1 -0
- package/dist/internal/HttpClient.js +41 -0
- package/dist/internal/HttpClient.js.map +1 -0
- package/dist/internal/WebSocketClient.d.ts +44 -0
- package/dist/internal/WebSocketClient.d.ts.map +1 -0
- package/dist/internal/WebSocketClient.js +74 -0
- package/dist/internal/WebSocketClient.js.map +1 -0
- package/package.json +53 -3
- package/src/HttpClient.ts +40 -0
- package/src/WebSocketClient.ts +56 -0
- package/src/index.ts +2 -0
- package/src/internal/HttpClient.ts +119 -0
- package/src/internal/WebSocketClient.ts +224 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ConvexClient } from "convex/browser";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as InternalWebSocketClient from "./internal/WebSocketClient";
|
|
6
|
+
|
|
7
|
+
export { WebSocketClientError } from "./internal/WebSocketClient";
|
|
8
|
+
|
|
9
|
+
const make = (
|
|
10
|
+
address: string,
|
|
11
|
+
options?: ConstructorParameters<typeof ConvexClient>[1],
|
|
12
|
+
) =>
|
|
13
|
+
InternalWebSocketClient.makeScoped(
|
|
14
|
+
address,
|
|
15
|
+
Effect.sync(() => {
|
|
16
|
+
const convexClient = new ConvexClient(address, options);
|
|
17
|
+
return {
|
|
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
|
+
),
|
|
36
|
+
};
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A Confect client which uses a WebSocket to communicate with your Convex
|
|
42
|
+
* backend and supports reactive query subscriptions. The WebSocket connection
|
|
43
|
+
* is managed by the layer's scope and closed automatically when the scope ends.
|
|
44
|
+
* Wraps
|
|
45
|
+
* [ConvexClient](https://docs.convex.dev/api/classes/browser.ConvexClient).
|
|
46
|
+
*/
|
|
47
|
+
export const WebSocketClient = Context.Service<
|
|
48
|
+
Effect.Success<ReturnType<typeof make>>
|
|
49
|
+
>("@confect/js/WebSocketClient");
|
|
50
|
+
|
|
51
|
+
export type WebSocketClient = typeof WebSocketClient.Identifier;
|
|
52
|
+
|
|
53
|
+
export const layer = (
|
|
54
|
+
address: string,
|
|
55
|
+
options?: ConstructorParameters<typeof ConvexClient>[1],
|
|
56
|
+
) => Layer.effect(WebSocketClient, make(address, options));
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
const runQuery = Effect.fn("HttpClient.query")(
|
|
31
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const runMutation = Effect.fn("HttpClient.mutation")(
|
|
35
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const runAction = Effect.fn("HttpClient.action")(
|
|
39
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const make = (client: Transport) => {
|
|
43
|
+
const setAuth = (token: string) =>
|
|
44
|
+
Effect.sync(() => {
|
|
45
|
+
client.setAuth(token);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const clearAuth = Effect.sync(() => {
|
|
49
|
+
client.clearAuth();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const mapUnknownError = (cause: unknown) => new HttpClientError({ cause });
|
|
53
|
+
|
|
54
|
+
const query = <Query extends Ref.AnyPublicQuery>(
|
|
55
|
+
ref: Query,
|
|
56
|
+
...rest: Ref.OptionalArgs<Query>
|
|
57
|
+
): Effect.Effect<
|
|
58
|
+
Ref.Returns<Query>,
|
|
59
|
+
Ref.Error<Query> | HttpClientError | Schema.SchemaError
|
|
60
|
+
> => {
|
|
61
|
+
const args = (rest[0] ?? {}) as Ref.Args<Query>;
|
|
62
|
+
return runQuery(
|
|
63
|
+
Ref.runWithCodec(
|
|
64
|
+
ref,
|
|
65
|
+
args,
|
|
66
|
+
(functionReference, encodedArgs) =>
|
|
67
|
+
client.query(functionReference, encodedArgs),
|
|
68
|
+
mapUnknownError,
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const mutation = <Mutation extends Ref.AnyPublicMutation>(
|
|
74
|
+
ref: Mutation,
|
|
75
|
+
...rest: Ref.OptionalArgs<Mutation>
|
|
76
|
+
): Effect.Effect<
|
|
77
|
+
Ref.Returns<Mutation>,
|
|
78
|
+
Ref.Error<Mutation> | HttpClientError | Schema.SchemaError
|
|
79
|
+
> => {
|
|
80
|
+
const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
|
|
81
|
+
return runMutation(
|
|
82
|
+
Ref.runWithCodec(
|
|
83
|
+
ref,
|
|
84
|
+
args,
|
|
85
|
+
(functionReference, encodedArgs) =>
|
|
86
|
+
client.mutation(functionReference, encodedArgs),
|
|
87
|
+
mapUnknownError,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const action = <Action extends Ref.AnyPublicAction>(
|
|
93
|
+
ref: Action,
|
|
94
|
+
...rest: Ref.OptionalArgs<Action>
|
|
95
|
+
): Effect.Effect<
|
|
96
|
+
Ref.Returns<Action>,
|
|
97
|
+
Ref.Error<Action> | HttpClientError | Schema.SchemaError
|
|
98
|
+
> => {
|
|
99
|
+
const args = (rest[0] ?? {}) as Ref.Args<Action>;
|
|
100
|
+
return runAction(
|
|
101
|
+
Ref.runWithCodec(
|
|
102
|
+
ref,
|
|
103
|
+
args,
|
|
104
|
+
(functionReference, encodedArgs) =>
|
|
105
|
+
client.action(functionReference, encodedArgs),
|
|
106
|
+
mapUnknownError,
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
url: client.url,
|
|
113
|
+
setAuth,
|
|
114
|
+
clearAuth,
|
|
115
|
+
query,
|
|
116
|
+
mutation,
|
|
117
|
+
action,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
const runQuery = Effect.fn("WebSocketClient.query")(
|
|
44
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const runMutation = Effect.fn("WebSocketClient.mutation")(
|
|
48
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const runAction = Effect.fn("WebSocketClient.action")(
|
|
52
|
+
<A, E>(effect: Effect.Effect<A, E>): Effect.Effect<A, E> => effect,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export const make = (address: string, convexClient: Transport) => {
|
|
56
|
+
const setAuth = <R1 = never, R2 = never>(
|
|
57
|
+
fetchToken: (args: {
|
|
58
|
+
forceRefreshToken: boolean;
|
|
59
|
+
}) => Effect.Effect<string | null | undefined, never, R1>,
|
|
60
|
+
onChange?: (isAuthenticated: boolean) => Effect.Effect<void, never, R2>,
|
|
61
|
+
): Effect.Effect<void, never, R1 | R2> =>
|
|
62
|
+
Effect.gen(function* () {
|
|
63
|
+
const context = yield* Effect.context<R1 | R2>();
|
|
64
|
+
const runPromise = Effect.runPromiseWith(context);
|
|
65
|
+
const runFork = Effect.runForkWith(context);
|
|
66
|
+
convexClient.setAuth(
|
|
67
|
+
(args) => runPromise(Effect.suspend(() => fetchToken(args))),
|
|
68
|
+
...(onChange
|
|
69
|
+
? [
|
|
70
|
+
(isAuthenticated: boolean) => {
|
|
71
|
+
runFork(Effect.suspend(() => onChange(isAuthenticated)));
|
|
72
|
+
},
|
|
73
|
+
]
|
|
74
|
+
: []),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const mapUnknownError = (cause: unknown) =>
|
|
79
|
+
new WebSocketClientError({ cause });
|
|
80
|
+
|
|
81
|
+
const query = <Query extends Ref.AnyPublicQuery>(
|
|
82
|
+
ref: Query,
|
|
83
|
+
...rest: Ref.OptionalArgs<Query>
|
|
84
|
+
): Effect.Effect<
|
|
85
|
+
Ref.Returns<Query>,
|
|
86
|
+
Ref.Error<Query> | WebSocketClientError | Schema.SchemaError
|
|
87
|
+
> => {
|
|
88
|
+
const args = (rest[0] ?? {}) as Ref.Args<Query>;
|
|
89
|
+
return runQuery(
|
|
90
|
+
Ref.runWithCodec(
|
|
91
|
+
ref,
|
|
92
|
+
args,
|
|
93
|
+
(functionReference, encodedArgs) =>
|
|
94
|
+
convexClient.query(functionReference, encodedArgs),
|
|
95
|
+
mapUnknownError,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mutation = <Mutation extends Ref.AnyPublicMutation>(
|
|
101
|
+
ref: Mutation,
|
|
102
|
+
...rest: Ref.OptionalArgs<Mutation>
|
|
103
|
+
): Effect.Effect<
|
|
104
|
+
Ref.Returns<Mutation>,
|
|
105
|
+
Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError
|
|
106
|
+
> => {
|
|
107
|
+
const args = (rest[0] ?? {}) as Ref.Args<Mutation>;
|
|
108
|
+
return runMutation(
|
|
109
|
+
Ref.runWithCodec(
|
|
110
|
+
ref,
|
|
111
|
+
args,
|
|
112
|
+
(functionReference, encodedArgs) =>
|
|
113
|
+
convexClient.mutation(functionReference, encodedArgs),
|
|
114
|
+
mapUnknownError,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const action = <Action extends Ref.AnyPublicAction>(
|
|
120
|
+
ref: Action,
|
|
121
|
+
...rest: Ref.OptionalArgs<Action>
|
|
122
|
+
): Effect.Effect<
|
|
123
|
+
Ref.Returns<Action>,
|
|
124
|
+
Ref.Error<Action> | WebSocketClientError | Schema.SchemaError
|
|
125
|
+
> => {
|
|
126
|
+
const args = (rest[0] ?? {}) as Ref.Args<Action>;
|
|
127
|
+
return runAction(
|
|
128
|
+
Ref.runWithCodec(
|
|
129
|
+
ref,
|
|
130
|
+
args,
|
|
131
|
+
(functionReference, encodedArgs) =>
|
|
132
|
+
convexClient.action(functionReference, encodedArgs),
|
|
133
|
+
mapUnknownError,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
type ReactiveQueryError<Query extends Ref.AnyPublicQuery> =
|
|
139
|
+
| Ref.Error<Query>
|
|
140
|
+
| WebSocketClientError
|
|
141
|
+
| Schema.SchemaError;
|
|
142
|
+
|
|
143
|
+
const reactiveQueryResult = <Query extends Ref.AnyPublicQuery>(
|
|
144
|
+
ref: Query,
|
|
145
|
+
...rest: Ref.OptionalArgs<Query>
|
|
146
|
+
): Stream.Stream<
|
|
147
|
+
Result.Result<Ref.Returns<Query>, ReactiveQueryError<Query>>
|
|
148
|
+
> => {
|
|
149
|
+
type QueryResult = Result.Result<
|
|
150
|
+
Ref.Returns<Query>,
|
|
151
|
+
ReactiveQueryError<Query>
|
|
152
|
+
>;
|
|
153
|
+
const args = (rest[0] ?? {}) as Ref.Args<Query>;
|
|
154
|
+
const functionReference = Ref.getFunctionReference(ref);
|
|
155
|
+
const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);
|
|
156
|
+
|
|
157
|
+
return Stream.unwrap(
|
|
158
|
+
Effect.match(Ref.encodeArgs(ref, args), {
|
|
159
|
+
onFailure: (error) => Stream.succeed<QueryResult>(Result.fail(error)),
|
|
160
|
+
onSuccess: (encodedArgs) =>
|
|
161
|
+
Stream.callback<Result.Result<unknown, ReactiveQueryError<Query>>>(
|
|
162
|
+
(queue) =>
|
|
163
|
+
Effect.gen(function* () {
|
|
164
|
+
const unsubscribe = convexClient.onUpdate(
|
|
165
|
+
functionReference,
|
|
166
|
+
encodedArgs,
|
|
167
|
+
(result) => {
|
|
168
|
+
Queue.offerUnsafe(queue, Result.succeed(result));
|
|
169
|
+
},
|
|
170
|
+
(error) => {
|
|
171
|
+
Queue.offerUnsafe(queue, Result.fail(onError(error)));
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
yield* Effect.addFinalizer(() =>
|
|
175
|
+
Effect.sync(() => unsubscribe()),
|
|
176
|
+
);
|
|
177
|
+
}),
|
|
178
|
+
),
|
|
179
|
+
}),
|
|
180
|
+
).pipe(
|
|
181
|
+
Stream.mapEffect((result): Effect.Effect<QueryResult> =>
|
|
182
|
+
Result.match(result, {
|
|
183
|
+
onFailure: (error) => Effect.succeed(Result.fail(error)),
|
|
184
|
+
onSuccess: (encodedReturns) =>
|
|
185
|
+
Effect.match(Ref.decodeReturns(ref, encodedReturns), {
|
|
186
|
+
onFailure: Result.fail,
|
|
187
|
+
onSuccess: Result.succeed,
|
|
188
|
+
}),
|
|
189
|
+
}),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const reactiveQuery = <Query extends Ref.AnyPublicQuery>(
|
|
195
|
+
ref: Query,
|
|
196
|
+
...rest: Ref.OptionalArgs<Query>
|
|
197
|
+
): Stream.Stream<Ref.Returns<Query>, ReactiveQueryError<Query>> =>
|
|
198
|
+
reactiveQueryResult(ref, ...rest).pipe(
|
|
199
|
+
Stream.mapEffect(
|
|
200
|
+
Result.match({
|
|
201
|
+
onFailure: Effect.fail,
|
|
202
|
+
onSuccess: Effect.succeed,
|
|
203
|
+
}),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
url: address,
|
|
209
|
+
setAuth,
|
|
210
|
+
query,
|
|
211
|
+
mutation,
|
|
212
|
+
action,
|
|
213
|
+
reactiveQuery,
|
|
214
|
+
reactiveQueryResult,
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const makeScoped = <E, R>(
|
|
219
|
+
address: string,
|
|
220
|
+
acquire: Effect.Effect<Transport, E, R>,
|
|
221
|
+
) =>
|
|
222
|
+
Effect.acquireRelease(acquire, (convexClient) =>
|
|
223
|
+
Effect.promise(() => convexClient.close()),
|
|
224
|
+
).pipe(Effect.map((convexClient) => make(address, convexClient)));
|