@confect/js 10.0.0-next.17 → 10.0.0-next.19
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 +64 -0
- package/dist/WebSocketClient.d.ts +9 -6
- package/dist/WebSocketClient.d.ts.map +1 -1
- package/dist/WebSocketClient.js +22 -11
- package/dist/WebSocketClient.js.map +1 -1
- package/package.json +9 -5
- package/src/WebSocketClient.ts +59 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# @confect/js
|
|
2
2
|
|
|
3
|
+
## 10.0.0-next.19
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c1087eb: Add `@confect/foldkit` — client-side bindings for [Foldkit](https://foldkit.dev) apps. The package maps Confect's client surface onto Foldkit's integration seams: `Client.layer` provides the scoped WebSocket client and pagination-session allocator through an application's `resources`, `Subscription.reactiveQuery` builds a subscription entry that opens, re-opens, and closes a reactive query as `Option`-wrapped args derived from the Model change, and `Command.query` / `Command.mutation` / `Command.action` build Command definitions whose args are the ref's args and whose Messages are declared with the same `messages` field Foldkit's own `Command.define` takes. Every failure is folded into a Message via the required `onError` handler, so Command and Subscription error channels stay `never` as Foldkit requires.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import * as Confect from "@confect/foldkit";
|
|
11
|
+
import * as Schema from "effect/Schema";
|
|
12
|
+
import * as Subscription from "foldkit/subscription";
|
|
13
|
+
|
|
14
|
+
const SaveNote = Confect.Command.mutation(
|
|
15
|
+
"SaveNote",
|
|
16
|
+
refs.public.notes.insert,
|
|
17
|
+
{
|
|
18
|
+
messages: [SucceededSaveNote, FailedSaveNote],
|
|
19
|
+
onSuccess: (noteId) => SucceededSaveNote({ noteId }),
|
|
20
|
+
onError: (error) => FailedSaveNote({ message: String(error) }),
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const subscriptions = Subscription.make<
|
|
25
|
+
Model,
|
|
26
|
+
Message,
|
|
27
|
+
Confect.Client.Client
|
|
28
|
+
>()(() => ({
|
|
29
|
+
note: Confect.Subscription.reactiveQuery<Model>()(refs.public.notes.get, {
|
|
30
|
+
args: (model) => Option.map(model.noteId, (noteId) => ({ noteId })),
|
|
31
|
+
onSuccess: (note) => SucceededGetNote({ note }),
|
|
32
|
+
onError: (error) => FailedGetNote({ message: String(error) }),
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The Command factories accept Foldkit's `interrupt` option — `true` keys invocations by the Command name, `{ keyFields, toKey }` by a part derived from the ref's args — and the returned definition gains the `Interrupt` constructor for stopping in-flight invocations. Factory-built definitions instantiate Foldkit's own definition interfaces, so they are accepted wherever Foldkit accepts a Command definition — including Story/Scene `Command.resolve` and `expectExact` matchers. `Command.queryEffect`, `Command.mutationEffect`, and `Command.actionEffect` return execute bodies for hand-written `Command.define` calls (custom args schemas, multi-call Commands), and `Subscription.reactiveQueryStream` is the `dependenciesToStream` escape hatch for hand-written subscription entries.
|
|
38
|
+
|
|
39
|
+
`PaginatedQuery` navigates a paginated query one page at a time over Convex's cursor pagination. `PaginatedQuery.make(ref)` returns the Model and settlement schemas. Its states use Foldkit `AsyncData`'s vocabulary and semantics: `Idle`, `Loading`, `Refreshing`, `Success`, `Failure`, and `Stale`. Failed settlements preserve the complete error contract: declared function and middleware errors are wrapped as `FunctionError`, Convex's invalid-cursor pseudo-error is normalized to the new `@confect/core/PaginationError.InvalidCursor`, and `WebSocketClientError` and `SchemaError` are carried directly. `settle` accepts a request-bound `Result`, turns a failed refresh into `Stale`, automatically resets an invalid cursor, and ignores successes or failures from superseded requests. All machine operations (`init`, `reinitialize`, `reset`, `close`, `next`, `prev`, and `first`) remain pure.
|
|
40
|
+
|
|
41
|
+
`Subscription.paginatedQuery` keeps exactly one live, reactive page subscription in sync with the machine and emits one `onSettled` Message shape for either outcome. It allocates Convex's pagination `id` internally and returns it with the first settlement, so session allocation is not a separate Command or application Message. Installing that id is keep-alive-equivalent and does not restart the subscription. Query errors are emitted as values without closing the underlying Convex subscription, allowing a later successful result to recover naturally. Each request supports `maximumRowsRead` and `maximumBytesRead` and uses `initialNumItems` for the initial page and split heuristic. Page splits remain transparent, the last complete page stays visible during navigation and after refresh failure, and an empty terminal page automatically retreats to the preceding page.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const Notes = Confect.PaginatedQuery.make(refs.public.notes.paginate);
|
|
45
|
+
|
|
46
|
+
// Model: notes: Notes.schema
|
|
47
|
+
// Initial Model value: notes: Notes.idle
|
|
48
|
+
// Message: settlement: Notes.settlement
|
|
49
|
+
// update after matching Idle: Notes.init(idle, { channel }, { initialNumItems: 20 })
|
|
50
|
+
|
|
51
|
+
const subscriptions = Subscription.make<
|
|
52
|
+
Model,
|
|
53
|
+
Message,
|
|
54
|
+
Confect.Client.Client
|
|
55
|
+
>()(() => ({
|
|
56
|
+
notesPage: Confect.Subscription.paginatedQuery<Model>()(Notes, {
|
|
57
|
+
state: (model) => model.notes,
|
|
58
|
+
onSettled: (settlement) => SettledGetNotesPage({ settlement }),
|
|
59
|
+
}),
|
|
60
|
+
}));
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- aa7d903: Add per-module subpath exports to `@confect/js`, so `import * as WebSocketClient from "@confect/js/WebSocketClient"` and `import * as HttpClient from "@confect/js/HttpClient"` now resolve. The root import (`import { HttpClient, WebSocketClient } from "@confect/js"`) is unchanged.
|
|
64
|
+
|
|
65
|
+
## 10.0.0-next.18
|
|
66
|
+
|
|
3
67
|
## 10.0.0-next.17
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as Ref from "@confect/core/Ref";
|
|
2
2
|
import { ConvexClient } from "convex/browser";
|
|
3
|
-
import * as Cause from "effect/Cause";
|
|
4
3
|
import * as Context from "effect/Context";
|
|
5
4
|
import * as Effect from "effect/Effect";
|
|
6
5
|
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Result from "effect/Result";
|
|
7
7
|
import * as Schema from "effect/Schema";
|
|
8
8
|
import * as Stream from "effect/Stream";
|
|
9
9
|
declare const WebSocketClientError_base: Schema.Class<WebSocketClientError, Schema.TaggedStruct<"WebSocketClientError", {
|
|
10
|
-
readonly cause: Schema.
|
|
11
|
-
}>, Cause.YieldableError>;
|
|
10
|
+
readonly cause: Schema.Defect;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
12
|
export declare class WebSocketClientError extends WebSocketClientError_base {
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
@@ -22,7 +22,8 @@ export declare const WebSocketClient: Context.Service<{
|
|
|
22
22
|
query: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Effect.Effect<Ref.Returns<Query>, Ref.Error<Query> | WebSocketClientError | Schema.SchemaError>;
|
|
23
23
|
mutation: <Mutation extends Ref.AnyPublicMutation>(ref: Mutation, ...rest: Ref.OptionalArgs<Mutation>) => Effect.Effect<Ref.Returns<Mutation>, Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError>;
|
|
24
24
|
action: <Action extends Ref.AnyPublicAction>(ref: Action, ...rest: Ref.OptionalArgs<Action>) => Effect.Effect<Ref.Returns<Action>, Ref.Error<Action> | WebSocketClientError | Schema.SchemaError>;
|
|
25
|
-
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>,
|
|
25
|
+
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>;
|
|
26
|
+
reactiveQueryResult: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Result.Result<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>>;
|
|
26
27
|
}, {
|
|
27
28
|
url: string;
|
|
28
29
|
setAuth: (fetchToken: (args: {
|
|
@@ -31,7 +32,8 @@ export declare const WebSocketClient: Context.Service<{
|
|
|
31
32
|
query: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Effect.Effect<Ref.Returns<Query>, Ref.Error<Query> | WebSocketClientError | Schema.SchemaError>;
|
|
32
33
|
mutation: <Mutation extends Ref.AnyPublicMutation>(ref: Mutation, ...rest: Ref.OptionalArgs<Mutation>) => Effect.Effect<Ref.Returns<Mutation>, Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError>;
|
|
33
34
|
action: <Action extends Ref.AnyPublicAction>(ref: Action, ...rest: Ref.OptionalArgs<Action>) => Effect.Effect<Ref.Returns<Action>, Ref.Error<Action> | WebSocketClientError | Schema.SchemaError>;
|
|
34
|
-
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>,
|
|
35
|
+
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>;
|
|
36
|
+
reactiveQueryResult: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Result.Result<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>>;
|
|
35
37
|
}>;
|
|
36
38
|
export type WebSocketClient = typeof WebSocketClient.Identifier;
|
|
37
39
|
export declare const layer: (address: string, options?: ConstructorParameters<typeof ConvexClient>[1]) => Layer.Layer<{
|
|
@@ -42,7 +44,8 @@ export declare const layer: (address: string, options?: ConstructorParameters<ty
|
|
|
42
44
|
query: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Effect.Effect<Ref.Returns<Query>, Ref.Error<Query> | WebSocketClientError | Schema.SchemaError>;
|
|
43
45
|
mutation: <Mutation extends Ref.AnyPublicMutation>(ref: Mutation, ...rest: Ref.OptionalArgs<Mutation>) => Effect.Effect<Ref.Returns<Mutation>, Ref.Error<Mutation> | WebSocketClientError | Schema.SchemaError>;
|
|
44
46
|
action: <Action extends Ref.AnyPublicAction>(ref: Action, ...rest: Ref.OptionalArgs<Action>) => Effect.Effect<Ref.Returns<Action>, Ref.Error<Action> | WebSocketClientError | Schema.SchemaError>;
|
|
45
|
-
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>,
|
|
47
|
+
reactiveQuery: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>;
|
|
48
|
+
reactiveQueryResult: <Query extends Ref.AnyPublicQuery>(ref: Query, ...rest: Ref.OptionalArgs<Query>) => Stream.Stream<Result.Result<Ref.Returns<Query>, Schema.SchemaError | WebSocketClientError | Ref.Error<Query>>>;
|
|
46
49
|
}, never, never>;
|
|
47
50
|
export {};
|
|
48
51
|
//# sourceMappingURL=WebSocketClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketClient.d.ts","sourceRoot":"","sources":["../src/WebSocketClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"WebSocketClient.d.ts","sourceRoot":"","sources":["../src/WebSocketClient.ts"],"names":[],"mappings":"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"}
|
package/dist/WebSocketClient.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
2
|
import * as Ref from "@confect/core/Ref";
|
|
3
3
|
import { ConvexClient } from "convex/browser";
|
|
4
|
-
import * as Cause from "effect/Cause";
|
|
5
4
|
import * as Context from "effect/Context";
|
|
6
5
|
import * as Effect from "effect/Effect";
|
|
7
6
|
import * as Layer from "effect/Layer";
|
|
8
7
|
import * as Queue from "effect/Queue";
|
|
8
|
+
import * as Result from "effect/Result";
|
|
9
9
|
import * as Schema from "effect/Schema";
|
|
10
10
|
import * as Stream from "effect/Stream";
|
|
11
11
|
//#region src/WebSocketClient.ts
|
|
@@ -14,7 +14,7 @@ var WebSocketClient_exports = /* @__PURE__ */ __exportAll({
|
|
|
14
14
|
WebSocketClientError: () => WebSocketClientError,
|
|
15
15
|
layer: () => layer
|
|
16
16
|
});
|
|
17
|
-
var WebSocketClientError = class extends Schema.TaggedError()("WebSocketClientError", { cause: Schema.
|
|
17
|
+
var WebSocketClientError = class extends Schema.TaggedError()("WebSocketClientError", { cause: Schema.Defect() }) {};
|
|
18
18
|
const make = (address, options) => Effect.acquireRelease(Effect.sync(() => new ConvexClient(address, options)), (convexClient) => Effect.promise(() => convexClient.close())).pipe(Effect.map((convexClient) => {
|
|
19
19
|
const url = address;
|
|
20
20
|
const setAuth = (fetchToken, onChange) => Effect.sync(() => {
|
|
@@ -33,29 +33,40 @@ const make = (address, options) => Effect.acquireRelease(Effect.sync(() => new C
|
|
|
33
33
|
const args = rest[0] ?? {};
|
|
34
34
|
return Ref.runWithCodec(ref, args, (functionReference, encodedArgs) => convexClient.action(functionReference, encodedArgs), mapUnknownError);
|
|
35
35
|
};
|
|
36
|
-
const
|
|
36
|
+
const reactiveQueryResult = (ref, ...rest) => {
|
|
37
37
|
const args = rest[0] ?? {};
|
|
38
38
|
const functionReference = Ref.getFunctionReference(ref);
|
|
39
39
|
const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);
|
|
40
|
-
return Stream.unwrap(Effect.
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
return Stream.unwrap(Effect.match(Ref.encodeArgs(ref, args), {
|
|
41
|
+
onFailure: (error) => Stream.succeed(Result.fail(error)),
|
|
42
|
+
onSuccess: (encodedArgs) => Stream.callback((queue) => Effect.gen(function* () {
|
|
43
43
|
const unsubscribe = convexClient.onUpdate(functionReference, encodedArgs, (result) => {
|
|
44
|
-
Queue.offerUnsafe(queue, result);
|
|
44
|
+
Queue.offerUnsafe(queue, Result.succeed(result));
|
|
45
45
|
}, (error) => {
|
|
46
|
-
Queue.
|
|
46
|
+
Queue.offerUnsafe(queue, Result.fail(onError(error)));
|
|
47
47
|
});
|
|
48
48
|
yield* Effect.addFinalizer(() => Effect.sync(() => unsubscribe()));
|
|
49
|
-
}))
|
|
50
|
-
})).pipe(Stream.mapEffect((
|
|
49
|
+
}))
|
|
50
|
+
})).pipe(Stream.mapEffect((result) => Result.match(result, {
|
|
51
|
+
onFailure: (error) => Effect.succeed(Result.fail(error)),
|
|
52
|
+
onSuccess: (encodedReturns) => Effect.match(Ref.decodeReturns(ref, encodedReturns), {
|
|
53
|
+
onFailure: Result.fail,
|
|
54
|
+
onSuccess: Result.succeed
|
|
55
|
+
})
|
|
56
|
+
})));
|
|
51
57
|
};
|
|
58
|
+
const reactiveQuery = (ref, ...rest) => reactiveQueryResult(ref, ...rest).pipe(Stream.mapEffect(Result.match({
|
|
59
|
+
onFailure: Effect.fail,
|
|
60
|
+
onSuccess: Effect.succeed
|
|
61
|
+
})));
|
|
52
62
|
return {
|
|
53
63
|
url,
|
|
54
64
|
setAuth,
|
|
55
65
|
query,
|
|
56
66
|
mutation,
|
|
57
67
|
action,
|
|
58
|
-
reactiveQuery
|
|
68
|
+
reactiveQuery,
|
|
69
|
+
reactiveQueryResult
|
|
59
70
|
};
|
|
60
71
|
}));
|
|
61
72
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketClient.js","names":[],"sources":["../src/WebSocketClient.ts"],"sourcesContent":["import * as Ref from \"@confect/core/Ref\";\nimport { ConvexClient } from \"convex/browser\";\nimport * as
|
|
1
|
+
{"version":3,"file":"WebSocketClient.js","names":[],"sources":["../src/WebSocketClient.ts"],"sourcesContent":["import * as Ref from \"@confect/core/Ref\";\nimport { ConvexClient } from \"convex/browser\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\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\nconst make = (\n address: string,\n options?: ConstructorParameters<typeof ConvexClient>[1],\n) =>\n Effect.acquireRelease(\n Effect.sync(() => new ConvexClient(address, options)),\n (convexClient) => Effect.promise(() => convexClient.close()),\n ).pipe(\n Effect.map((convexClient) => {\n const url = address;\n\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) =>\n Stream.succeed<QueryResult>(Result.fail(error)),\n onSuccess: (encodedArgs) =>\n Stream.callback<\n 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,\n setAuth,\n query,\n mutation,\n action,\n reactiveQuery,\n reactiveQueryResult,\n };\n }),\n );\n\n/**\n * A Confect client which uses a WebSocket to communicate with your Convex backend and supports reactive query subscriptions. The WebSocket connection is managed by the layer's scope and closed automatically when the scope ends. Wraps [ConvexClient](https://docs.convex.dev/api/classes/browser.ConvexClient).\n */\nexport const WebSocketClient = Context.Service<\n Effect.Success<ReturnType<typeof make>>\n>(\"@confect/js/WebSocketClient\");\n\nexport type WebSocketClient = typeof WebSocketClient.Identifier;\n\nexport const layer = (\n address: string,\n options?: ConstructorParameters<typeof ConvexClient>[1],\n) => Layer.effect(WebSocketClient, make(address, options));\n"],"mappings":";;;;;;;;;;;;;;;;AAUA,IAAa,uBAAb,cAA0C,OAAO,YAAkC,CAAC,CAClF,wBACA,EACE,OAAO,OAAO,OAAO,EACvB,CACF,CAAC,CAAC,CAAC;AAEH,MAAM,QACJ,SACA,YAEA,OAAO,eACL,OAAO,WAAW,IAAI,aAAa,SAAS,OAAO,CAAC,IACnD,iBAAiB,OAAO,cAAc,aAAa,MAAM,CAAC,CAC7D,CAAC,CAAC,KACA,OAAO,KAAK,iBAAiB;CAC3B,MAAM,MAAM;CAEZ,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,UACV,OAAO,QAAqB,OAAO,KAAK,KAAK,CAAC;GAChD,YAAY,gBACV,OAAO,UAEJ,UACD,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,CACH;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;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF,CAAC,CACH;;;;AAKF,MAAa,kBAAkB,QAAQ,QAErC,6BAA6B;AAI/B,MAAa,SACX,SACA,YACG,MAAM,OAAO,iBAAiB,KAAK,SAAS,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@confect/js",
|
|
3
3
|
"description": "JavaScript client bindings for any JS runtime",
|
|
4
|
-
"version": "10.0.0-next.
|
|
4
|
+
"version": "10.0.0-next.19",
|
|
5
5
|
"author": "RJ Dellecese",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/rjdellecese/confect/issues"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@effect/vitest": "4.0.0-rc.
|
|
10
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
11
11
|
"@types/node": "22.20.1",
|
|
12
|
-
"effect": "4.0.0-rc.
|
|
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",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
25
|
},
|
|
26
|
+
"./*": {
|
|
27
|
+
"types": "./dist/*.d.ts",
|
|
28
|
+
"default": "./dist/*.js"
|
|
29
|
+
},
|
|
26
30
|
"./package.json": "./package.json"
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
@@ -44,8 +48,8 @@
|
|
|
44
48
|
"module": "./dist/index.js",
|
|
45
49
|
"peerDependencies": {
|
|
46
50
|
"convex": "^1.32.0",
|
|
47
|
-
"effect": "^4.0.0-rc.
|
|
48
|
-
"@confect/core": "^10.0.0-next.
|
|
51
|
+
"effect": "^4.0.0-rc.112",
|
|
52
|
+
"@confect/core": "^10.0.0-next.19"
|
|
49
53
|
},
|
|
50
54
|
"repository": {
|
|
51
55
|
"type": "git",
|
package/src/WebSocketClient.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as Ref from "@confect/core/Ref";
|
|
2
2
|
import { ConvexClient } from "convex/browser";
|
|
3
|
-
import * as Cause from "effect/Cause";
|
|
4
3
|
import * as Context from "effect/Context";
|
|
5
4
|
import * as Effect from "effect/Effect";
|
|
6
5
|
import * as Layer from "effect/Layer";
|
|
7
6
|
import * as Queue from "effect/Queue";
|
|
7
|
+
import * as Result from "effect/Result";
|
|
8
8
|
import * as Schema from "effect/Schema";
|
|
9
9
|
import * as Stream from "effect/Stream";
|
|
10
10
|
|
|
11
11
|
export class WebSocketClientError extends Schema.TaggedError<WebSocketClientError>()(
|
|
12
12
|
"WebSocketClientError",
|
|
13
13
|
{
|
|
14
|
-
cause: Schema.
|
|
14
|
+
cause: Schema.Defect(),
|
|
15
15
|
},
|
|
16
16
|
) {}
|
|
17
17
|
|
|
@@ -98,49 +98,77 @@ const make = (
|
|
|
98
98
|
);
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
type ReactiveQueryError<Query extends Ref.AnyPublicQuery> =
|
|
102
|
+
| Ref.Error<Query>
|
|
103
|
+
| WebSocketClientError
|
|
104
|
+
| Schema.SchemaError;
|
|
105
|
+
|
|
106
|
+
const reactiveQueryResult = <Query extends Ref.AnyPublicQuery>(
|
|
102
107
|
ref: Query,
|
|
103
108
|
...rest: Ref.OptionalArgs<Query>
|
|
104
109
|
): Stream.Stream<
|
|
105
|
-
Ref.Returns<Query>,
|
|
106
|
-
Ref.Error<Query> | WebSocketClientError | Schema.SchemaError
|
|
110
|
+
Result.Result<Ref.Returns<Query>, ReactiveQueryError<Query>>
|
|
107
111
|
> => {
|
|
112
|
+
type QueryResult = Result.Result<
|
|
113
|
+
Ref.Returns<Query>,
|
|
114
|
+
ReactiveQueryError<Query>
|
|
115
|
+
>;
|
|
108
116
|
const args = (rest[0] ?? {}) as Ref.Args<Query>;
|
|
109
117
|
const functionReference = Ref.getFunctionReference(ref);
|
|
110
118
|
const onError = Ref.decodeErrorOrElse(ref, mapUnknownError);
|
|
111
119
|
|
|
112
120
|
return Stream.unwrap(
|
|
113
|
-
Effect.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
),
|
|
136
144
|
}),
|
|
137
145
|
).pipe(
|
|
138
|
-
Stream.mapEffect((
|
|
139
|
-
|
|
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
|
+
}),
|
|
140
155
|
),
|
|
141
156
|
);
|
|
142
157
|
};
|
|
143
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
|
+
|
|
144
172
|
return {
|
|
145
173
|
url,
|
|
146
174
|
setAuth,
|
|
@@ -148,6 +176,7 @@ const make = (
|
|
|
148
176
|
mutation,
|
|
149
177
|
action,
|
|
150
178
|
reactiveQuery,
|
|
179
|
+
reactiveQueryResult,
|
|
151
180
|
};
|
|
152
181
|
}),
|
|
153
182
|
);
|