EdgeAIG's picture
download
raw
29 kB
import * as Cause from "../../Cause.js";
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Exit from "../../Exit.js";
import * as Fiber from "../../Fiber.js";
import { constVoid, dual, flow, identity } from "../../Function.js";
import * as Latch from "../../Latch.js";
import * as Layer from "../../Layer.js";
import * as Option from "../../Option.js";
import * as Pool from "../../Pool.js";
import * as Queue from "../../Queue.js";
import * as Result from "../../Result.js";
import * as Schedule from "../../Schedule.js";
import * as Schema from "../../Schema.js";
import * as Scope from "../../Scope.js";
import * as Stream from "../../Stream.js";
import * as Headers from "../http/Headers.js";
import * as HttpBody from "../http/HttpBody.js";
import * as HttpClient from "../http/HttpClient.js";
import { HttpClientErrorSchema } from "../http/HttpClientError.js";
import * as HttpClientRequest from "../http/HttpClientRequest.js";
import * as Socket from "../socket/Socket.js";
import * as Transferable from "../workers/Transferable.js";
import * as Worker from "../workers/Worker.js";
import * as Rpc from "./Rpc.js";
import { RpcClientDefect, RpcClientError } from "./RpcClientError.js";
import { constPing, RequestId } from "./RpcMessage.js";
import * as RpcSchema from "./RpcSchema.js";
import * as RpcSerialization from "./RpcSerialization.js";
import * as RpcWorker from "./RpcWorker.js";
import { withRunClient } from "./Utils.js";
let requestIdCounter = /*#__PURE__*/BigInt(0);
/**
* Creates an RPC client for an already-decoded message channel, returning the
* client API together with a `write` function for delivering server messages
* back to the client.
*
* @category client
* @since 4.0.0
*/
export const makeNoSerialization = /*#__PURE__*/Effect.fnUntraced(function* (group, options) {
const spanPrefix = options?.spanPrefix ?? "RpcClient";
const supportsAck = options?.supportsAck ?? true;
const disableTracing = options?.disableTracing ?? false;
const generateRequestId = options?.generateRequestId ?? (() => requestIdCounter++);
const services = yield* Effect.context();
const scope = Context.get(services, Scope.Scope);
const entries = new Map();
let isShutdown = false;
yield* Scope.addFinalizer(scope, Effect.withFiber(parent => {
isShutdown = true;
return clearEntries(Exit.interrupt(parent.id));
}));
const clearEntries = Effect.fnUntraced(function* (exit) {
for (const [id, entry] of entries) {
entries.delete(id);
if (entry._tag === "Queue") {
yield* exit._tag === "Success" ? Queue.end(entry.queue) : Queue.failCause(entry.queue, exit.cause);
} else {
entry.resume(exit);
}
}
});
const onRequest = rpc => {
const isStream = RpcSchema.isStreamSchema(rpc.successSchema);
const middleware = getRpcClientMiddleware(rpc);
return (payload, opts) => {
const headers = opts?.headers ? Headers.fromInput(opts.headers) : Headers.empty;
const context = opts?.context ?? Context.empty();
if (!isStream) {
const onRequest = span => onEffectRequest(rpc, middleware, span, rpc.payloadSchema.make(payload), headers, context, opts?.discard ?? false);
return disableTracing ? onRequest(undefined) : Effect.useSpan(`${spanPrefix}.${rpc._tag}`, {
attributes: options.spanAttributes
}, onRequest);
}
const queue = onStreamRequest(rpc, middleware, rpc.payloadSchema.make(payload), headers, opts?.streamBufferSize ?? 16, context);
if (opts?.asQueue) return queue;
return Stream.unwrap(Effect.map(queue, Stream.fromQueue));
};
};
const onEffectRequest = (rpc, middleware, span, payload, headers, context, discard) => Effect.withFiber(parentFiber => {
if (isShutdown) {
return Effect.interrupt;
}
const id = generateRequestId();
const send = middleware(message => options.onFromClient({
message,
context,
discard
}), {
_tag: "Request",
id,
tag: rpc._tag,
payload,
...(span ? {
traceId: span.traceId,
spanId: span.spanId,
sampled: span.sampled
} : {}),
headers: Headers.merge(parentFiber.getRef(CurrentHeaders), headers)
});
if (discard) {
return send;
}
let fiber;
return Effect.onInterrupt(Effect.callback(resume => {
const entry = {
_tag: "Effect",
rpc,
context,
resume(exit) {
resume(exit);
if (fiber && !fiber.pollUnsafe()) {
parentFiber.currentDispatcher.scheduleTask(() => {
fiber.interruptUnsafe(parentFiber.id);
}, 0);
}
}
};
entries.set(id, entry);
fiber = send.pipe(span ? Effect.withParentSpan(span, {
captureStackTrace: false
}) : identity, Effect.runForkWith(parentFiber.context));
fiber.addObserver(exit => {
if (exit._tag === "Failure") {
return resume(exit);
}
});
}), interruptors => {
entries.delete(id);
return Effect.andThen(Fiber.interrupt(fiber), sendInterrupt(id, Array.from(interruptors), context));
});
});
const onStreamRequest = Effect.fnUntraced(function* (rpc, middleware, payload, headers, streamBufferSize, context) {
if (isShutdown) {
return yield* Effect.interrupt;
}
const span = disableTracing ? undefined : yield* Effect.makeSpanScoped(`${spanPrefix}.${rpc._tag}`, {
attributes: options.spanAttributes
});
const fiber = Fiber.getCurrent();
const id = generateRequestId();
const scope = Context.getUnsafe(fiber.context, Scope.Scope);
yield* Scope.addFinalizerExit(scope, exit => {
if (!entries.has(id)) return Effect.void;
entries.delete(id);
return sendInterrupt(id, Exit.isFailure(exit) ? Array.from(Cause.interruptors(exit.cause)) : [], context);
});
const queue = yield* Queue.bounded(streamBufferSize);
entries.set(id, {
_tag: "Queue",
rpc,
queue,
scope,
context
});
yield* middleware(message => options.onFromClient({
message,
context,
discard: false
}), {
_tag: "Request",
id,
tag: rpc._tag,
payload,
...(span ? {
traceId: span.traceId,
spanId: span.spanId,
sampled: span.sampled
} : {}),
headers: Headers.merge(fiber.getRef(CurrentHeaders), headers)
}).pipe(span ? Effect.withParentSpan(span, {
captureStackTrace: false
}) : identity, Effect.catchCause(error => Queue.failCause(queue, error)), Effect.interruptible, Effect.forkIn(scope, {
startImmediately: true
}));
return queue;
});
const getRpcClientMiddleware = rpc => {
const middlewares = [];
for (const tag of rpc.middlewares.values()) {
const middleware = services.mapUnsafe.get(`${tag.key}/Client`);
if (!middleware) continue;
middlewares.push(middleware);
}
if (middlewares.length === 0) {
return (send, request) => send(request);
}
return function loop(send, request, index = middlewares.length - 1) {
if (index === -1) {
return send(request);
}
return middlewares[index]({
rpc,
request,
next(request) {
return loop(send, request, index - 1);
}
});
};
};
const sendInterrupt = (requestId, interruptors, context) => Effect.callback(resume => {
const parentFiber = Fiber.getCurrent();
const fiber = options.onFromClient({
message: {
_tag: "Interrupt",
requestId,
interruptors
},
context,
discard: false
}).pipe(Effect.timeout(1000), Effect.runForkWith(parentFiber.context));
fiber.addObserver(() => {
resume(Effect.void);
});
});
const write = message => {
switch (message._tag) {
case "Chunk":
{
const requestId = message.requestId;
const entry = entries.get(requestId);
if (!entry || entry._tag !== "Queue") return Effect.void;
return Queue.offerAll(entry.queue, message.values).pipe(supportsAck ? Effect.flatMap(() => options.onFromClient({
message: {
_tag: "Ack",
requestId: message.requestId
},
context: entry.context,
discard: false
})) : identity, Effect.catchCause(cause => Queue.failCause(entry.queue, cause)));
}
case "Exit":
{
const requestId = message.requestId;
const entry = entries.get(requestId);
if (!entry) return Effect.void;
entries.delete(requestId);
if (entry._tag === "Effect") {
entry.resume(message.exit);
return Effect.void;
}
return message.exit._tag === "Success" ? Queue.end(entry.queue) : Queue.failCause(entry.queue, message.exit.cause);
}
case "Defect":
{
return clearEntries(Exit.die(message.defect));
}
case "ClientEnd":
{
return Effect.void;
}
}
};
let client;
if (options.flatten) {
const fns = new Map();
client = function client(tag, payload, options) {
let fn = fns.get(tag);
if (!fn) {
fn = onRequest(group.requests.get(tag));
fns.set(tag, fn);
}
return fn(payload, options);
};
} else {
client = {};
group.requests.forEach(rpc => {
client[rpc._tag] = onRequest(rpc);
});
}
return {
client,
write
};
});
let clientIdCounter = 0;
/**
* Creates a schema-aware RPC client for a group using the current client
* `Protocol`, encoding requests and decoding server responses.
*
* @category client
* @since 4.0.0
*/
export const make = /*#__PURE__*/Effect.fnUntraced(function* (group, options) {
const clientId = clientIdCounter++;
const {
run,
send,
supportsAck,
supportsTransferables
} = yield* Protocol;
const entries = new Map();
const {
client,
write
} = yield* makeNoSerialization(group, {
...options,
supportsAck,
onFromClient({
message
}) {
switch (message._tag) {
case "Request":
{
const rpc = group.requests.get(message.tag);
const collector = supportsTransferables ? Transferable.makeCollectorUnsafe() : undefined;
const fiber = Fiber.getCurrent();
const entry = {
rpc,
context: collector ? Context.add(fiber.context, Transferable.Collector, collector) : fiber.context,
schemas: rpcSchemas(rpc)
};
entries.set(message.id, entry);
return entry.schemas.encodePayload(message.payload).pipe(Effect.provideContext(entry.context), Effect.orDie, Effect.flatMap(payload => send(clientId, {
...message,
id: String(message.id),
payload,
headers: Object.entries(message.headers)
}, collector && collector.readUnsafe())));
}
case "Ack":
{
const entry = entries.get(message.requestId);
if (!entry) return Effect.void;
return send(clientId, {
_tag: "Ack",
requestId: String(message.requestId)
});
}
case "Interrupt":
{
const entry = entries.get(message.requestId);
if (!entry) return Effect.void;
entries.delete(message.requestId);
return send(clientId, {
_tag: "Interrupt",
requestId: String(message.requestId)
});
}
case "Eof":
{
return Effect.void;
}
}
}
});
yield* run(clientId, message => {
switch (message._tag) {
case "Chunk":
{
const requestId = RequestId(message.requestId);
const entry = entries.get(requestId);
if (!entry || Option.isNone(entry.schemas.decodeChunk)) return Effect.void;
return entry.schemas.decodeChunk.value(message.values).pipe(Effect.provideContext(entry.context), Effect.orDie, Effect.flatMap(chunk => write({
_tag: "Chunk",
clientId: 0,
requestId: RequestId(message.requestId),
values: chunk
})), Effect.onError(cause => write({
_tag: "Exit",
clientId: 0,
requestId: RequestId(message.requestId),
exit: Exit.failCause(cause)
})));
}
case "Exit":
{
const requestId = RequestId(message.requestId);
const entry = entries.get(requestId);
if (!entry) return Effect.void;
entries.delete(requestId);
return entry.schemas.decodeExit(message.exit).pipe(Effect.provideContext(entry.context), Effect.orDie, Effect.matchCauseEffect({
onSuccess: exit => write({
_tag: "Exit",
clientId: 0,
requestId,
exit
}),
onFailure: cause => write({
_tag: "Exit",
clientId: 0,
requestId,
exit: Exit.failCause(cause)
})
}));
}
case "Defect":
{
return write({
_tag: "Defect",
clientId: 0,
defect: decodeDefect(message.defect)
});
}
case "ClientProtocolError":
{
const exit = Exit.fail(message.error);
return Effect.forEach(entries.keys(), requestId => write({
_tag: "Exit",
clientId: 0,
requestId,
exit: exit
}));
}
default:
{
return Effect.void;
}
}
}).pipe(Effect.catchCause(Effect.logError), Effect.interruptible, Effect.forkScoped);
return client;
});
const rpcSchemasCache = /*#__PURE__*/new WeakMap();
const rpcSchemas = rpc => {
let entry = rpcSchemasCache.get(rpc);
if (entry !== undefined) {
return entry;
}
const streamSchemas = RpcSchema.getStreamSchemas(rpc.successSchema);
entry = {
decodeChunk: Option.map(streamSchemas, streamSchemas => Schema.decodeUnknownEffect(Schema.toCodecJson(Schema.NonEmptyArray(streamSchemas.success)))),
encodePayload: Schema.encodeEffect(Schema.toCodecJson(rpc.payloadSchema)),
decodeExit: Schema.decodeUnknownEffect(Schema.toCodecJson(Rpc.exitSchema(rpc)))
};
rpcSchemasCache.set(rpc, entry);
return entry;
};
/**
* Fiber reference containing headers that are merged into outgoing RPC
* client requests.
*
* **When to use**
*
* Use to set request headers that should be automatically merged into outgoing
* RPC client messages.
*
* @category headers
* @since 4.0.0
*/
export const CurrentHeaders = /*#__PURE__*/Context.Reference("effect/rpc/RpcClient/CurrentHeaders", {
defaultValue: () => Headers.empty
});
/**
* Runs an effect with additional RPC client headers, merging them with the
* current `CurrentHeaders` value for outgoing requests.
*
* @category headers
* @since 4.0.0
*/
export const withHeaders = /*#__PURE__*/dual(2, (effect, headers) => Effect.updateService(effect, CurrentHeaders, Headers.merge(Headers.fromInput(headers))));
/**
* Defines the service interface for an RPC client transport, responsible for running the
* receive loop and sending encoded client messages.
*
* **When to use**
*
* Use to provide the transport boundary for RPC clients over HTTP, WebSocket,
* workers, sockets, or custom protocols.
*
* @category protocols
* @since 4.0.0
*/
export class Protocol extends /*#__PURE__*/Context.Service()("effect/rpc/RpcClient/Protocol") {
/**
* Creates a client protocol service from the supplied RPC request runner.
*
* @since 4.0.0
*/
static make = withRunClient;
}
/**
* Creates a client `Protocol` that sends each RPC request through the supplied
* `HttpClient` and decodes responses with the current `RpcSerialization`.
*
* @category protocols
* @since 4.0.0
*/
export const makeProtocolHttp = client => Protocol.make(Effect.fnUntraced(function* (writeResponse) {
const serialization = yield* RpcSerialization.RpcSerialization;
const isFramed = serialization.includesFraming;
const httpClientError = cause => new RpcClientError({
reason: HttpClientErrorSchema.fromHttpClientError(cause)
});
const protocolDefect = (message, cause) => new RpcClientError({
reason: new RpcClientDefect({
message,
cause
})
});
const emptyResponseError = request => protocolDefect("Received empty HTTP response from RPC server", request);
const send = Effect.fnUntraced(function* (clientId, request) {
if (request._tag !== "Request") {
return;
}
const parser = serialization.makeUnsafe();
const encoded = parser.encode(request);
const body = typeof encoded === "string" ? HttpBody.text(encoded, serialization.contentType) : HttpBody.uint8Array(encoded, serialization.contentType);
const response = yield* client.post("", {
body
}).pipe(Effect.mapError(httpClientError));
if (!isFramed) {
const text = yield* response.text.pipe(Effect.mapError(httpClientError));
const responses = yield* Effect.try({
try: () => parser.decode(text),
catch: cause => protocolDefect("Error decoding HTTP response", cause)
});
if (!Array.isArray(responses)) {
return yield* protocolDefect("Expected an array of responses", responses);
}
if (responses.length === 0) {
return yield* emptyResponseError(request);
}
let i = 0;
return yield* Effect.whileLoop({
while: () => i < responses.length,
body: () => writeResponse(clientId, responses[i++]),
step: constVoid
});
}
let hasResponse = false;
yield* Stream.runForEachArray(response.stream, chunk => Effect.try({
try: () => chunk.flatMap(parser.decode),
catch: cause => protocolDefect("Error decoding HTTP response", cause)
}).pipe(Effect.flatMap(responses => {
if (responses.length === 0) return Effect.void;
hasResponse = true;
let i = 0;
return Effect.whileLoop({
while: () => i < responses.length,
body: () => writeResponse(clientId, responses[i++]),
step: constVoid
});
}))).pipe(Effect.mapError(cause => cause instanceof RpcClientError ? cause : httpClientError(cause)));
if (!hasResponse) {
return yield* emptyResponseError(request);
}
});
return {
send,
supportsAck: false,
supportsTransferables: false
};
}));
/**
* Provides a client `Protocol` backed by `HttpClient`, targeting the configured
* URL and optionally transforming the client before use.
*
* @category protocols
* @since 4.0.0
*/
export const layerProtocolHttp = options => Layer.effect(Protocol)(Effect.flatMap(HttpClient.HttpClient, client => {
client = HttpClient.mapRequest(client, HttpClientRequest.prependUrl(options.url));
return makeProtocolHttp(options.transformClient ? options.transformClient(client) : client);
}));
/**
* Creates a client `Protocol` over the current `Socket`, using the current
* `RpcSerialization`, connection hooks, ping timeouts, and the configured retry
* policy.
*
* @category protocols
* @since 4.0.0
*/
export const makeProtocolSocket = options => Protocol.make(Effect.fnUntraced(function* (writeResponse, clientIds) {
const socket = yield* Socket.Socket;
const serialization = yield* RpcSerialization.RpcSerialization;
const hooks = yield* Effect.serviceOption(ConnectionHooks);
const requestClientMap = new Map();
const write = yield* socket.writer;
let parser = serialization.makeUnsafe();
const pinger = yield* makePinger(write(parser.encode(constPing)));
let currentError;
const onOpen = Effect.suspend(() => {
currentError = undefined;
return Option.isSome(hooks) ? hooks.value.onConnect : Effect.void;
});
const broadcast = response => Effect.forEach(clientIds, clientId => writeResponse(clientId, response));
yield* Effect.suspend(() => {
parser = serialization.makeUnsafe();
pinger.reset();
return socket.runRaw(message => {
try {
const responses = parser.decode(message);
if (responses.length === 0) return;
let i = 0;
return Effect.whileLoop({
while: () => i < responses.length,
body: () => {
const response = responses[i++];
if (response._tag === "Pong") {
pinger.onPong();
return Effect.void;
}
if ("requestId" in response) {
const clientId = requestClientMap.get(response.requestId);
if (clientId !== undefined) {
if (response._tag === "Exit") {
requestClientMap.delete(response.requestId);
}
return writeResponse(clientId, response);
}
}
return broadcast(response);
},
step: constVoid
});
} catch (defect) {
return broadcast({
_tag: "ClientProtocolError",
error: new RpcClientError({
reason: new RpcClientDefect({
message: "Error decoding message",
cause: defect
})
})
});
}
}, {
onOpen
}).pipe(Effect.raceFirst(Effect.flatMap(pinger.timeout, () => Effect.fail(new Socket.SocketError({
reason: new Socket.SocketOpenError({
kind: "Timeout",
cause: new Error("ping timeout")
})
})))));
}).pipe(Effect.flatMap(() => Effect.fail(new Socket.SocketError({
reason: new Socket.SocketCloseError({
code: 1000
})
}))), Option.isSome(hooks) ? Effect.ensuring(hooks.value.onDisconnect) : identity, Effect.tapCause(cause => {
const error = Cause.findError(cause);
const hasError = Result.isSuccess(error);
if (options?.retryTransientErrors && hasError && error.success.reason._tag === "SocketOpenError") {
return Effect.void;
}
currentError = new RpcClientError({
reason: hasError ? error.success.reason : new RpcClientDefect({
message: "Unknown socket error",
cause: Cause.squash(cause)
})
});
return broadcast({
_tag: "ClientProtocolError",
error: currentError
});
}), Effect.retry(options?.retryPolicy ?? defaultRetryPolicy), Effect.annotateLogs({
module: "RpcClient",
method: "makeProtocolSocket"
}), Effect.forkScoped);
return {
send(clientId, request) {
if (currentError) {
return Effect.fail(currentError);
}
if (request._tag === "Request") {
requestClientMap.set(request.id, clientId);
}
const encoded = parser.encode(request);
if (encoded === undefined) return Effect.void;
return Effect.orDie(write(encoded));
},
supportsAck: true,
supportsTransferables: false
};
}));
const defaultRetryPolicy = /*#__PURE__*/Schedule.exponential(500, 1.5).pipe(/*#__PURE__*/Schedule.either(/*#__PURE__*/Schedule.spaced(5000)));
const makePinger = /*#__PURE__*/Effect.fnUntraced(function* (writePing) {
let recievedPong = true;
const latch = Latch.makeUnsafe();
const reset = () => {
recievedPong = true;
latch.closeUnsafe();
};
const onPong = () => {
recievedPong = true;
};
yield* Effect.suspend(() => {
if (!recievedPong) return latch.open;
recievedPong = false;
return writePing;
}).pipe(Effect.delay("5 seconds"), Effect.ignore, Effect.forever, Effect.interruptible, Effect.forkScoped);
return {
timeout: latch.await,
reset,
onPong
};
});
/**
* Provides a client `Protocol` backed by the current `Socket` and
* `RpcSerialization` services.
*
* @category protocols
* @since 4.0.0
*/
export const layerProtocolSocket = options => Layer.effect(Protocol)(makeProtocolSocket(options));
/**
* Creates a client `Protocol` backed by a pool of workers, routing RPC requests
* to workers and supporting transferable values when the platform does.
*
* @category protocols
* @since 4.0.0
*/
export const makeProtocolWorker = options => Protocol.make(Effect.fnUntraced(function* (writeResponse, clientIds) {
const worker = yield* Worker.WorkerPlatform;
const scope = yield* Effect.scope;
let workerId = 0;
const initialMessage = yield* Effect.serviceOption(RpcWorker.InitialMessage);
const hooks = yield* Effect.serviceOption(ConnectionHooks);
const entries = new Map();
const broadcast = response => Effect.forEach(clientIds, clientId => writeResponse(clientId, response));
const acquire = Effect.gen(function* () {
const id = workerId++;
const backing = yield* worker.spawn(id);
yield* backing.run(response => {
if (response._tag === "Exit") {
const entry = entries.get(response.requestId);
if (entry) {
entries.delete(response.requestId);
entry.latch.openUnsafe();
return writeResponse(entry.clientId, response);
}
} else if (response._tag === "Defect") {
for (const [requestId, entry] of entries) {
entries.delete(requestId);
entry.latch.openUnsafe();
}
return broadcast(response);
} else if ("requestId" in response) {
const entry = entries.get(response.requestId);
if (entry) {
return writeResponse(entry.clientId, response);
}
}
return broadcast(response);
}, {
onSpawn: Option.isSome(initialMessage) ? Effect.flatMap(initialMessage.value, ([value, transfers]) => Effect.orDie(backing.send({
_tag: "InitialMessage",
value
}, transfers))) : undefined
}).pipe(Effect.tapCause(cause => {
const error = Cause.findError(cause);
return broadcast({
_tag: "ClientProtocolError",
error: new RpcClientError({
reason: Result.isSuccess(error) ? error.success.reason : new RpcClientDefect({
message: "Error in worker",
cause: Cause.squash(cause)
})
})
});
}), Effect.retry(Schedule.spaced(1000)), Effect.annotateLogs({
module: "RpcClient",
method: "makeProtocolWorker"
}), Effect.interruptible, Effect.forkScoped);
return backing;
});
const pool = "minSize" in options ? yield* Pool.makeWithTTL({
acquire,
min: options.minSize,
max: options.maxSize,
concurrency: options.concurrency,
targetUtilization: options.targetUtilization,
timeToLive: options.timeToLive
}) : yield* Pool.make({
acquire,
size: options.size,
concurrency: options.concurrency,
targetUtilization: options.targetUtilization
});
yield* Scope.addFinalizer(scope, Effect.sync(() => {
for (const entry of entries.values()) {
entry.latch.openUnsafe();
}
entries.clear();
}));
const send = (clientId, request, transferables) => {
switch (request._tag) {
case "Request":
{
return Pool.get(pool).pipe(Effect.flatMap(worker => {
const latch = Latch.makeUnsafe(false);
entries.set(request.id, {
clientId,
worker,
latch
});
return Effect.flatMap(worker.send(request, transferables), () => latch.await);
}), Effect.scoped, Effect.orDie);
}
case "Interrupt":
{
const entry = entries.get(request.requestId);
if (!entry) return Effect.void;
entries.delete(request.requestId);
entry.latch.openUnsafe();
return Effect.orDie(entry.worker.send(request));
}
case "Ack":
{
const entry = entries.get(request.requestId);
if (!entry) return Effect.void;
return Effect.orDie(entry.worker.send(request));
}
}
return Effect.void;
};
yield* Effect.scoped(Pool.get(pool));
if (Option.isSome(hooks)) yield* hooks.value.onConnect;
return {
send,
supportsAck: true,
supportsTransferables: true
};
}));
/**
* Provides a client `Protocol` backed by a worker pool using the current worker
* platform and spawner services.
*
* @category protocols
* @since 4.0.0
*/
export const layerProtocolWorker = /*#__PURE__*/flow(makeProtocolWorker, /*#__PURE__*/Layer.effect(Protocol));
/**
* Represents optional client protocol hooks that run when a transport connects
* and disconnects.
*
* **When to use**
*
* Use to run setup or cleanup effects when an RPC client transport opens or
* closes.
*
* @category connection hooks
* @since 4.0.0
*/
export class ConnectionHooks extends /*#__PURE__*/Context.Service()("effect/rpc/RpcClient/ConnectionHooks") {}
// internal
const decodeDefect = /*#__PURE__*/Schema.decodeSync(/*#__PURE__*/Schema.Defect());
//# sourceMappingURL=RpcClient.js.map

Xet Storage Details

Size:
29 kB
·
Xet hash:
9363a7fc03f1de9b8a304a611aa54fec07c8a451d3aa55ca95edcbacae141688

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.