@akterjs/client 0.0.0-stage → 0.1.1

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,331 @@
1
+ import {
2
+ Array as Arr,
3
+ Clock,
4
+ Effect,
5
+ FiberMap,
6
+ Latch,
7
+ Option,
8
+ Predicate,
9
+ Record as Rec,
10
+ Ref,
11
+ Schema,
12
+ String as Str,
13
+ } from "effect";
14
+ import type { Scope } from "effect";
15
+ import type { HttpClient } from "effect/http";
16
+ import { RpcClient, RpcClientError, RpcMessage } from "effect/rpc";
17
+ import { Socket } from "effect/socket";
18
+
19
+ import { trackedFloors } from "./read-floors.pure.js";
20
+ import type { ReadFloors } from "./read-floors.pure.js";
21
+ import { remoteInterrupts } from "./remote-interrupt.pure.js";
22
+ import type { RemoteInterrupts } from "./remote-interrupt.pure.js";
23
+
24
+ type Headers = () => Readonly<Record<string, string>>;
25
+
26
+ type Deliver = (response: RpcMessage.FromServerEncoded) => Effect.Effect<void>;
27
+
28
+ type RequestId = RpcMessage.RequestEncoded["id"];
29
+
30
+ type Frame = Uint8Array | string;
31
+
32
+ interface ActorProtocolOptions {
33
+ readonly headers: Headers;
34
+ readonly http: HttpClient.HttpClient;
35
+ readonly url: string;
36
+ readonly webSocket: typeof globalThis.WebSocket;
37
+ }
38
+
39
+ interface Live {
40
+ readonly deliver: Deliver;
41
+ readonly request: RpcMessage.RequestEncoded;
42
+ }
43
+
44
+ interface Opened {
45
+ readonly reader: Socket.Reader;
46
+ readonly writer: Socket.Writer;
47
+ }
48
+
49
+ interface Receiving extends Opened {
50
+ readonly deliver: Deliver;
51
+ readonly ended: Latch.Latch;
52
+ readonly seen: Ref.Ref<number>;
53
+ }
54
+
55
+ const streamSuffix = "/subscribe";
56
+
57
+ const liveProtocol = "akter-rpc";
58
+
59
+ const authProtocol = "akter-auth.";
60
+
61
+ const authorizationHeader = "authorization";
62
+
63
+ const bearerPrefix = "Bearer ";
64
+
65
+ const pingEvery = "10 seconds";
66
+
67
+ const silentMillis = 25_000;
68
+
69
+ const openTimeout = "10 seconds";
70
+
71
+ const normalClose = 1000;
72
+
73
+ const ClientProtocolError = Schema.TaggedStruct("ClientProtocolError", {
74
+ error: RpcClientError.RpcClientError,
75
+ requestIds: Schema.Array(Schema.Union([Schema.String, Schema.Finite])),
76
+ });
77
+
78
+ const JsonText = Schema.fromJsonString(Schema.Unknown);
79
+
80
+ const encodeJson = Schema.encodeEffect(JsonText);
81
+
82
+ const decodeJson = Schema.decodeEffect(JsonText);
83
+
84
+ const utf8 = new globalThis.TextDecoder();
85
+
86
+ function serverMessages(values: readonly unknown[]): readonly RpcMessage.FromServerEncoded[];
87
+ function serverMessages(values: readonly unknown[]): readonly unknown[] {
88
+ return values;
89
+ }
90
+
91
+ const textOf = (message: RpcMessage.FromClientEncoded): Effect.Effect<string> => Effect.orDie(encodeJson(message));
92
+
93
+ const tokenOf = (headers: Readonly<Record<string, string>>): Option.Option<string> =>
94
+ Option.map(
95
+ Option.filter(Rec.get(headers, authorizationHeader), Str.startsWith(bearerPrefix)),
96
+ Str.slice(bearerPrefix.length),
97
+ );
98
+
99
+ const withoutAuthorization = (headers: Readonly<Record<string, string>>): readonly [string, string][] =>
100
+ Arr.filter(Rec.toEntries(headers), ([name]) => Str.toLowerCase(name) !== authorizationHeader);
101
+
102
+ const socketUrl = (url: string, request: RpcMessage.RequestEncoded): Effect.Effect<string> =>
103
+ Effect.map(textOf({ ...request, headers: [] }), (text) => {
104
+ const target = new globalThis.URL(url);
105
+
106
+ target.protocol = Str.replace(/^http/u, "ws")(target.protocol);
107
+ target.searchParams.set("request", text);
108
+
109
+ return target.href;
110
+ });
111
+
112
+ const lost = (message: string, cause: unknown): RpcClientError.RpcClientError =>
113
+ RpcClientError.RpcClientError.make({ reason: RpcClientError.RpcClientDefect.make({ cause, message }) });
114
+
115
+ const textFrom = (frame: Frame): string => {
116
+ if (Predicate.isString(frame)) {
117
+ return frame;
118
+ }
119
+
120
+ return utf8.decode(frame);
121
+ };
122
+
123
+ const decodeFrame = (
124
+ frame: Frame,
125
+ ): Effect.Effect<readonly RpcMessage.FromServerEncoded[], RpcClientError.RpcClientError> =>
126
+ Effect.mapBoth(decodeJson(textFrom(frame)), {
127
+ onFailure: (cause) => lost("the live socket sent a frame that isn't RPC JSON", cause),
128
+ onSuccess: (decoded) => serverMessages(Arr.ensure(decoded)),
129
+ });
130
+
131
+ const opened = (
132
+ { headers, url, webSocket }: ActorProtocolOptions,
133
+ request: RpcMessage.RequestEncoded,
134
+ ): Effect.Effect<Opened, Socket.SocketError, Scope.Scope> =>
135
+ Effect.gen(function* openLiveSocket() {
136
+ const sent = headers();
137
+ const credentials = Option.map(tokenOf(sent), (token) => `${authProtocol}${token}`);
138
+ const protocols = [liveProtocol, ...Option.toArray(credentials)];
139
+
140
+ const target = yield* socketUrl(url, request);
141
+
142
+ const socket = yield* Socket.fromWebSocket(
143
+ Effect.acquireRelease(
144
+ Effect.sync(() => new webSocket(target, protocols)),
145
+ (native) =>
146
+ Effect.sync(() => {
147
+ native.close(normalClose);
148
+ }),
149
+ ),
150
+ { openTimeout },
151
+ );
152
+
153
+ const reader = yield* socket.reader;
154
+ const writer = yield* socket.writer;
155
+ const opening = yield* textOf({ ...request, headers: [...request.headers, ...withoutAuthorization(sent)] });
156
+
157
+ yield* writer.write(opening);
158
+
159
+ return { reader, writer };
160
+ });
161
+
162
+ const received =
163
+ ({ deliver, ended }: Receiving) =>
164
+ (response: RpcMessage.FromServerEncoded): Effect.Effect<void> => {
165
+ if (Predicate.isTagged(response, "Pong")) {
166
+ return Effect.void;
167
+ }
168
+
169
+ if (RpcMessage.isTerminalResponse(response)) {
170
+ return Effect.andThen(deliver(response), Latch.open(ended));
171
+ }
172
+
173
+ return deliver(response);
174
+ };
175
+
176
+ const receiving = (live: Receiving): Effect.Effect<never, RpcClientError.RpcClientError | Socket.SocketError> => {
177
+ const onFrame = (frame: Frame): Effect.Effect<void, RpcClientError.RpcClientError> =>
178
+ Effect.flatMap(decodeFrame(frame), (responses) =>
179
+ Effect.andThen(
180
+ Effect.flatMap(Clock.currentTimeMillis, (now) => Ref.set(live.seen, now)),
181
+ Effect.forEach(responses, received(live), { discard: true }),
182
+ ),
183
+ );
184
+
185
+ return Effect.forever(Effect.flatMap(live.reader.pull, (frames) => Effect.forEach(frames, onFrame)));
186
+ };
187
+
188
+ const pinging = ({
189
+ seen,
190
+ writer,
191
+ }: Receiving): Effect.Effect<never, RpcClientError.RpcClientError | Socket.SocketError> =>
192
+ Effect.forever(
193
+ Effect.gen(function* pingLiveSocket() {
194
+ yield* Effect.sleep(pingEvery);
195
+
196
+ const now = yield* Clock.currentTimeMillis;
197
+ const last = yield* Ref.get(seen);
198
+
199
+ if (now - last > silentMillis) {
200
+ return yield* lost("the live socket went silent", now - last);
201
+ }
202
+
203
+ const frame = yield* textOf(RpcMessage.constPing);
204
+
205
+ return yield* writer.write(frame);
206
+ }),
207
+ );
208
+
209
+ const transportFailure = (
210
+ failure: RpcClientError.RpcClientError | Socket.SocketError,
211
+ ): RpcClientError.RpcClientError => {
212
+ if (Predicate.isTagged(failure, "RpcClientError")) {
213
+ return failure;
214
+ }
215
+
216
+ return RpcClientError.RpcClientError.make({ reason: failure.reason });
217
+ };
218
+
219
+ const liveStream =
220
+ (options: ActorProtocolOptions, writers: Map<RequestId, Socket.Writer>) =>
221
+ ({ deliver, request }: Live): Effect.Effect<void> => {
222
+ const served = Effect.gen(function* serveLiveRequest() {
223
+ const { reader, writer } = yield* opened(options, request);
224
+ const ended = yield* Latch.make();
225
+ const now = yield* Clock.currentTimeMillis;
226
+ const live = { deliver, ended, reader, seen: yield* Ref.make(now), writer };
227
+
228
+ yield* Effect.acquireRelease(
229
+ Effect.sync(() => writers.set(request.id, writer)),
230
+ () => Effect.sync(() => writers.delete(request.id)),
231
+ );
232
+
233
+ yield* Effect.raceFirst(Effect.raceFirst(receiving(live), pinging(live)), ended.await);
234
+ });
235
+
236
+ return Effect.matchEffect(Effect.scoped(served), {
237
+ onFailure: (failure) =>
238
+ deliver(ClientProtocolError.make({ error: transportFailure(failure), requestIds: [request.id] })),
239
+ onSuccess: () => Effect.void,
240
+ });
241
+ };
242
+
243
+ const liveRequestOf = (message: RpcMessage.FromClientEncoded): Option.Option<RpcMessage.RequestEncoded> => {
244
+ if (Predicate.isTagged(message, "Request") && Str.endsWith(streamSuffix)(message.tag)) {
245
+ return Option.some(message);
246
+ }
247
+
248
+ return Option.none();
249
+ };
250
+
251
+ interface Deliveries {
252
+ readonly deliverTo: (clientId: number) => Deliver;
253
+ readonly floors: ReadFloors;
254
+ readonly interrupts: RemoteInterrupts;
255
+ readonly run: RpcClient.Protocol["Service"]["run"];
256
+ }
257
+
258
+ const deliveries = (http: RpcClient.Protocol["Service"]): Deliveries => {
259
+ const delivers = new Map<number, Deliver>();
260
+ const floors = trackedFloors();
261
+ const interrupts = remoteInterrupts();
262
+
263
+ const deliverTo =
264
+ (clientId: number): Deliver =>
265
+ (response) =>
266
+ Option.match(Option.fromNullishOr(delivers.get(clientId)), {
267
+ onNone: () => Effect.void,
268
+ onSome: (deliver) => deliver(interrupts.received(clientId, response)),
269
+ });
270
+
271
+ const run: Deliveries["run"] = (clientId, deliver) =>
272
+ Effect.ensuring(
273
+ Effect.suspend(() => {
274
+ delivers.set(clientId, deliver);
275
+
276
+ return http.run(clientId, (response) =>
277
+ Effect.andThen(
278
+ Effect.sync(() => {
279
+ floors.received(clientId, response);
280
+ }),
281
+ Effect.suspend(() => deliver(interrupts.received(clientId, response))),
282
+ ),
283
+ );
284
+ }),
285
+ Effect.sync(() => delivers.delete(clientId)),
286
+ );
287
+
288
+ return { deliverTo, floors, interrupts, run };
289
+ };
290
+
291
+ const actorProtocol = Effect.fnUntraced(function* actorProtocol(options: ActorProtocolOptions) {
292
+ const http = yield* RpcClient.makeProtocolHttp(options.http);
293
+ const lives = yield* FiberMap.make<RequestId>();
294
+ const writers = new Map<RequestId, Socket.Writer>();
295
+ const { deliverTo, floors, interrupts, run } = deliveries(http);
296
+ const serve = liveStream(options, writers);
297
+
298
+ const writeTo = (requestId: RequestId, message: RpcMessage.FromClientEncoded): Effect.Effect<void> =>
299
+ Option.match(Option.fromNullishOr(writers.get(requestId)), {
300
+ onNone: () => Effect.void,
301
+ onSome: (writer) => Effect.ignore(Effect.flatMap(textOf(message), (frame) => writer.write(frame))),
302
+ });
303
+
304
+ const send: RpcClient.Protocol["Service"]["send"] = (clientId, message, transferables) => {
305
+ const live = liveRequestOf(message);
306
+
307
+ interrupts.sent(clientId, message);
308
+
309
+ if (Option.isSome(live)) {
310
+ const streamed = serve({ deliver: deliverTo(clientId), request: live.value });
311
+
312
+ return Effect.asVoid(FiberMap.run(lives, live.value.id, streamed));
313
+ }
314
+
315
+ if (Predicate.isTagged(message, "Ack")) {
316
+ return writeTo(message.requestId, message);
317
+ }
318
+
319
+ if (Predicate.isTagged(message, "Interrupt")) {
320
+ return Effect.andThen(writeTo(message.requestId, message), FiberMap.remove(lives, message.requestId));
321
+ }
322
+
323
+ return http.send(clientId, floors.sent(clientId, message), transferables);
324
+ };
325
+
326
+ return RpcClient.Protocol.of({ codecFor: http.codecFor, run, send, supportsAck: true, supportsTransferables: false });
327
+ });
328
+
329
+ export { actorProtocol };
330
+
331
+ export type { ActorProtocolOptions };
@@ -0,0 +1,179 @@
1
+ import { Array as Arr, Effect, Schema, Stream } from "effect";
2
+ import type { Cause, Duration, Scope } from "effect";
3
+ import { Rpc, RpcClient, RpcGroup } from "effect/rpc";
4
+
5
+ import type { ActorKind, ActorRequest } from "./actor-operations.pure.js";
6
+
7
+ // What a request or reply looks like on the wire for a caller without the app's contract, such as the akter CLI or
8
+ // The MCP server: key and payload go as the JSON the actor's own schemas decode, and the answer or error comes back
9
+ // As the JSON the actor encoded.
10
+ interface WireFailure {
11
+ readonly _tag: string;
12
+ }
13
+
14
+ type WireRequest = Readonly<Record<string, Schema.Json>>;
15
+
16
+ type WireSend = (tag: string, request: WireRequest) => Effect.Effect<Schema.Json | undefined, WireFailure>;
17
+
18
+ type WireWatch = (tag: string, request: WireRequest) => Stream.Stream<Schema.Json, WireFailure>;
19
+
20
+ type Wired = Effect.Effect<unknown, never, RpcClient.Protocol | Scope.Scope>;
21
+
22
+ const commandFields = { commandId: Schema.String, key: Schema.Json, payload: Schema.Json };
23
+
24
+ const queryFields = { key: Schema.Json, payload: Schema.Json };
25
+
26
+ const keyFields = { key: Schema.Json };
27
+
28
+ const computerFields = { computer: Schema.Json, key: Schema.Json };
29
+
30
+ const listFields = {
31
+ cursor: Schema.optionalKey(Schema.Json),
32
+ key: Schema.Json,
33
+ limit: Schema.optionalKey(Schema.Json),
34
+ prefix: Schema.optionalKey(Schema.Json),
35
+ };
36
+
37
+ // The RPCs a kind sends: one each, except an upload, a session that opens, appends and closes.
38
+ type WireKind = ActorKind | "files/upload/append" | "files/upload/close" | "files/upload/open";
39
+
40
+ // Each RPC's fields are those of its RPC in rpcsOf, fileRpcsOf and computerRpcsOf (akter ADR 0016).
41
+ const fieldsOf: Readonly<Record<WireKind, Schema.Struct.Fields>> = {
42
+ call: commandFields,
43
+ "computers/cp": { ...computerFields, computerPath: Schema.Json, direction: Schema.Json, path: Schema.Json },
44
+ "computers/delete": computerFields,
45
+ "computers/get": computerFields,
46
+ "computers/list": keyFields,
47
+ "computers/logs": {
48
+ ...computerFields,
49
+ after: Schema.optionalKey(Schema.Json),
50
+ limit: Schema.optionalKey(Schema.Json),
51
+ },
52
+ "computers/pause": computerFields,
53
+ "computers/resume": computerFields,
54
+ "computers/stop": computerFields,
55
+ create: commandFields,
56
+ ensure: commandFields,
57
+ "files/download": {
58
+ key: Schema.Json,
59
+ length: Schema.optionalKey(Schema.Json),
60
+ offset: Schema.optionalKey(Schema.Json),
61
+ path: Schema.Json,
62
+ },
63
+ "files/list": listFields,
64
+ "files/upload": keyFields,
65
+ "files/upload/append": { bytes: Schema.Json, key: Schema.Json, offset: Schema.Json, upload: Schema.Json },
66
+ "files/upload/close": { bytes: Schema.Json, key: Schema.Json, upload: Schema.Json },
67
+ "files/upload/open": keyFields,
68
+ "files/usage": listFields,
69
+ fork: { commandId: Schema.String, into: Schema.Json, key: Schema.Json },
70
+ query: queryFields,
71
+ state: keyFields,
72
+ "steps/get": { key: Schema.Json, step: Schema.Json },
73
+ "steps/list": { key: Schema.Json, limit: Schema.optionalKey(Schema.Json) },
74
+ warm: keyFields,
75
+ watch: queryFields,
76
+ };
77
+
78
+ interface Addressed {
79
+ readonly kind: WireKind;
80
+ readonly tag: string;
81
+ }
82
+
83
+ // A one-RPC client for the request, so RpcClient encodes and decodes it as the typed actor client does.
84
+ function unaryClient(addressed: Addressed): Effect.Effect<WireSend, never, RpcClient.Protocol | Scope.Scope>;
85
+ function unaryClient({ kind, tag }: Addressed): Wired {
86
+ const rpc = Rpc.make(tag, {
87
+ error: Schema.Json,
88
+ payload: Schema.Struct(fieldsOf[kind]),
89
+ success: Schema.UndefinedOr(Schema.Json),
90
+ });
91
+
92
+ return RpcClient.make(RpcGroup.make(rpc), { flatten: true });
93
+ }
94
+
95
+ function streamClient(addressed: Addressed): Effect.Effect<WireWatch, never, RpcClient.Protocol | Scope.Scope>;
96
+ function streamClient({ kind, tag }: Addressed): Wired {
97
+ const rpc = Rpc.make(tag, {
98
+ error: Schema.Json,
99
+ payload: Schema.Struct(fieldsOf[kind]),
100
+ stream: true,
101
+ success: Schema.Json,
102
+ });
103
+
104
+ return RpcClient.make(RpcGroup.make(rpc), { flatten: true });
105
+ }
106
+
107
+ // The largest chunk one upload append sends (akter ADR 0016's files RPC group).
108
+ const uploadChunkBytes = 1_048_576;
109
+
110
+ const Base64 = Schema.Uint8ArrayFromBase64;
111
+
112
+ const Opened = Schema.Struct({ upload: Schema.String });
113
+
114
+ type Transferring = RpcClient.Protocol | Scope.Scope;
115
+
116
+ type TransferFailure = Cause.TimeoutError | WireFailure;
117
+
118
+ // A file transfer: the operation's request, as actorRequestOf builds it, and how long each RPC may take.
119
+ interface Transfer {
120
+ readonly request: ActorRequest;
121
+ readonly wait: Duration.Input;
122
+ }
123
+
124
+ interface Session extends Transfer {
125
+ readonly content: Uint8Array;
126
+ readonly upload: string;
127
+ }
128
+
129
+ // Sends the bytes in order, at most 1 MiB an append, each at its offset so a retried one is taken once.
130
+ const appended = Effect.fnUntraced(function* appended({ content, request, upload, wait }: Session) {
131
+ const tag = `${request.tag}/append`;
132
+ const append = yield* unaryClient({ kind: "files/upload/append", tag });
133
+ const count = Math.ceil(content.length / uploadChunkBytes);
134
+
135
+ const chunks = Arr.makeBy(count, (index) => ({
136
+ bytes: content.subarray(index * uploadChunkBytes, index * uploadChunkBytes + uploadChunkBytes),
137
+ offset: index * uploadChunkBytes,
138
+ }));
139
+
140
+ yield* Effect.forEach(chunks, ({ bytes, offset }) =>
141
+ Effect.flatMap(Effect.orDie(Schema.encodeEffect(Base64)(bytes)), (encoded) =>
142
+ Effect.timeout(append(tag, { ...request.payload, bytes: encoded, offset, upload }), wait),
143
+ ),
144
+ );
145
+ });
146
+
147
+ // Uploads a file through the session its RPCs make: open it, append the bytes, then close it with their length. The
148
+ // Answer is the close RPC's own, { receipt }, which a command attaches at a path.
149
+ const uploadSession = (
150
+ upload: Transfer & { readonly content: Uint8Array },
151
+ ): Effect.Effect<Schema.Json | undefined, TransferFailure, Transferring> =>
152
+ Effect.gen(function* runUploadSession() {
153
+ const { content, request, wait } = upload;
154
+ const open = yield* unaryClient({ kind: "files/upload/open", tag: `${request.tag}/open` });
155
+ const close = yield* unaryClient({ kind: "files/upload/close", tag: `${request.tag}/close` });
156
+ const opened = yield* Effect.timeout(open(`${request.tag}/open`, request.payload), wait);
157
+ const session = yield* Effect.orDie(Schema.decodeUnknownEffect(Opened)(opened));
158
+
159
+ yield* appended({ content, request, upload: session.upload, wait });
160
+
161
+ const closing = { ...request.payload, bytes: content.length, upload: session.upload };
162
+
163
+ return yield* Effect.timeout(close(`${request.tag}/close`, closing), wait);
164
+ });
165
+
166
+ // Downloads a file whole: every base64 chunk the download RPC streams, or a failure, never part of a file.
167
+ const downloadChunks = ({
168
+ request,
169
+ wait,
170
+ }: Transfer): Effect.Effect<readonly Schema.Json[], TransferFailure, Transferring> =>
171
+ Effect.gen(function* collectDownload() {
172
+ const download = yield* streamClient({ kind: "files/download", tag: request.tag });
173
+
174
+ return yield* Effect.timeout(Stream.runCollect(download(request.tag, request.payload)), wait);
175
+ });
176
+
177
+ export { downloadChunks, streamClient, unaryClient, uploadChunkBytes, uploadSession };
178
+
179
+ export type { Transfer, TransferFailure, WireFailure, WireKind, WireRequest };
@@ -0,0 +1,67 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { SecureRandom } from "effect/crypto";
3
+ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
4
+ import { RpcClient, RpcSerialization } from "effect/rpc";
5
+
6
+ import { ActorClients } from "@akterjs/effect/contract/actor-client";
7
+ import { rpcFileClients } from "@akterjs/effect/contract/file-clients";
8
+ import type { ActorFileClients } from "@akterjs/effect/contract/file-clients";
9
+
10
+ import { actorProtocol } from "./actor-protocol.layer.js";
11
+
12
+ interface AkterClientOptions {
13
+ readonly fetch?: typeof globalThis.fetch;
14
+ readonly headers?: () => Readonly<Record<string, string>>;
15
+ readonly url: string;
16
+ readonly webSocket?: typeof globalThis.WebSocket;
17
+ }
18
+
19
+ const noHeaders = (): Readonly<Record<string, string>> => ({});
20
+
21
+ const rpcPath = "/rpc";
22
+
23
+ export const akterClient = ({
24
+ fetch = globalThis.fetch,
25
+ headers = noHeaders,
26
+ url,
27
+ webSocket = globalThis.WebSocket,
28
+ }: AkterClientOptions): Layer.Layer<ActorClients | ActorFileClients | RpcClient.Protocol> => {
29
+ const rpcUrl = new globalThis.URL(rpcPath, url).href;
30
+
31
+ const httpClient = Layer.effect(
32
+ HttpClient.HttpClient,
33
+ Effect.gen(function* withHeaders() {
34
+ const client = yield* HttpClient.HttpClient;
35
+
36
+ return HttpClient.mapRequest(client, (request) =>
37
+ HttpClientRequest.setHeaders(HttpClientRequest.prependUrl(request, rpcUrl), headers()),
38
+ );
39
+ }),
40
+ ).pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetch)));
41
+
42
+ const protocol = Layer.effect(
43
+ RpcClient.Protocol,
44
+ Effect.flatMap(HttpClient.HttpClient, (http) => actorProtocol({ headers, http, url: rpcUrl, webSocket })),
45
+ ).pipe(Layer.provide([httpClient, RpcSerialization.layerNdjson]));
46
+
47
+ const clients = Layer.merge(ActorClients.layer.pipe(Layer.provide(SecureRandom.layerWebCrypto)), rpcFileClients);
48
+
49
+ return Layer.provideMerge(clients, protocol);
50
+ };
51
+
52
+ export {
53
+ ActorAlreadyExistsError,
54
+ ActorNotCreatedError,
55
+ ActorErasedError,
56
+ ActorUnavailableError,
57
+ BillingUnavailableError,
58
+ CallerNotAllowedError,
59
+ CommandIdExpiredError,
60
+ HandlerFailedError,
61
+ InvalidRequestError,
62
+ SpendCapReachedError,
63
+ StorageLimitError,
64
+ UnauthenticatedError,
65
+ } from "@akterjs/effect/contract/errors";
66
+
67
+ export type { AkterClientOptions };