Skip to content
Source
Sign inSign up

shared.ts

128 lines4.1 KB

1import * as Cause from "effect/Cause";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import { RpcClientError } from "effect/rpc";
5​
6import * as AcpSchema from "../_generated/schema.gen.ts";
7import * as AcpError from "../errors.ts";
8const isError = Schema.is(AcpSchema.Error);
9const isAcpError = Schema.is(AcpError.AcpError);
10​
11export const callRpc = <A>(
12 method: string,
13 effect: Effect.Effect<A, RpcClientError.RpcClientError | AcpSchema.Error>,
14): Effect.Effect<A, AcpError.AcpError> =>
15 effect.pipe(
16 Effect.catchIf(isError, (error) =>
17 Effect.fail(AcpError.AcpRequestError.fromProtocolError(error, { method })),
18 ),
19 Effect.catchTags({
20 RpcClientError: (cause) =>
21 Effect.fail(
22 cause.reason._tag === "RpcClientDefect" && isAcpError(cause.reason.cause)
23 ? cause.reason.cause
24 : new AcpError.AcpTransportError({
25 operation: "call-rpc",
26 method,
27 cause,
28 }),
29 ),
30 }),
31 );
32​
33export const isolateNotificationHandler = <E, R>(effect: Effect.Effect<void, E, R>) =>
34 effect.pipe(
35 Effect.catchCause((cause) =>
36 Cause.hasDies(cause)
37 ? Effect.logError("ACP notification handler failed", cause)
38 : Effect.void,
39 ),
40 );
41​
42export const runHandler = Effect.fnUntraced(function* <A, B, Args extends ReadonlyArray<unknown>>(
43 handler: ((payload: A, ...args: Args) => Effect.Effect<B, AcpError.AcpError>) | undefined,
44 payload: A,
45 method: string,
46 ...args: Args
47) {
48 if (!handler) {
49 return yield* Effect.fail(AcpError.AcpRequestError.methodNotFound(method).toProtocolError());
50 }
51 return yield* handler(payload, ...args).pipe(
52 Effect.tapDefect((defect) =>
53 Effect.logError(`ACP request handler failed for '${method}'`, defect),
54 ),
55 Effect.mapError((error) =>
56 AcpError.AcpRequestError.fromCoreHandlerError(error, method).toProtocolError(),
57 ),
58 );
59});
60​
61export function decodeExtRequestRegistration<A, I>(
62 method: string,
63 payload: Schema.Codec<A, I>,
64 handler: (
65 payload: A,
66 context: { readonly requestId: string; readonly method: string },
67 ) => Effect.Effect<unknown, AcpError.AcpError>,
68) {
69 return (
70 params: unknown,
71 context: { readonly requestId: string; readonly method: string },
72 ): Effect.Effect<unknown, AcpError.AcpError> =>
73 Schema.decodeUnknownEffect(payload)(params).pipe(
74 Effect.mapError((error) => AcpError.AcpRequestError.invalidExtensionPayload(method, error)),
75 Effect.flatMap((decoded) => handler(decoded, context)),
76 );
78​
79export function decodeExtNotificationRegistration<A, I>(
80 method: string,
81 payload: Schema.Codec<A, I>,
82 handler: (payload: A) => Effect.Effect<void, AcpError.AcpError>,
83) {
84 return (params: unknown): Effect.Effect<void, AcpError.AcpError> =>
85 Schema.decodeUnknownEffect(payload)(params).pipe(
86 Effect.mapError((error) =>
87 AcpError.AcpProtocolParseError.fromSchemaError(
88 "decode-notification-payload",
89 method,
90 error,
91 ),
92 ),
93 Effect.flatMap((decoded) => handler(decoded)),
94 );
96​
97const encoder = new TextEncoder();
98​
99const JsonRpcId = Schema.Union([Schema.Number, Schema.String]);
100const JsonRpcHeaders = Schema.Array(Schema.Unknown);
101​
102export const jsonRpcRequest = <A, I>(method: string, params: Schema.Codec<A, I>) =>
103 Schema.Struct({
104 jsonrpc: Schema.Literal("2.0"),
105 id: JsonRpcId,
106 method: Schema.Literal(method),
107 params,
108 headers: JsonRpcHeaders.pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
109 });
110​
111export const jsonRpcNotification = <A, I>(method: string, params: Schema.Codec<A, I>) =>
112 Schema.Struct({
113 jsonrpc: Schema.Literal("2.0"),
114 method: Schema.Literal(method),
115 params,
116 });
117​
118export const jsonRpcResponse = <A, I>(result: Schema.Codec<A, I>) =>
119 Schema.Struct({
120 jsonrpc: Schema.Literal("2.0"),
121 id: JsonRpcId,
122 result,
123 });
124​
125export const encodeJsonl = <A, I>(schema: Schema.Codec<A, I>, value: A) =>
126 Effect.map(Schema.encodeEffect(Schema.fromJsonString(schema))(value), (encoded) =>
127 encoder.encode(`${encoded}\n`),
128 );