actor-kit
Version:
Actor Kit is a library for running state machines in Cloudflare Workers, leveraging XState for robust state management. It provides a framework for managing the logic, lifecycle, persistence, synchronization, and access control of actors in a distributed
112 lines (96 loc) • 2.96 kB
text/typescript
import { StateValueFrom } from "xstate";
import { z } from "zod";
import { AnyActorKitStateMachine, CallerSnapshotFrom, ClientEventFrom } from "./types";
const ResponseSchema = z.object({
snapshot: z.record(z.any()),
checksum: z.string(),
});
export function createActorFetch<TMachine extends AnyActorKitStateMachine>({
actorType,
host,
}: {
actorType: string;
host: string;
}) {
return async function fetchActor(
props: {
actorId: string;
accessToken: string;
input?: Record<string, unknown>;
waitForEvent?: ClientEventFrom<TMachine>;
waitForState?: StateValueFrom<TMachine>;
timeout?: number;
errorOnWaitTimeout?: boolean;
},
options?: RequestInit
): Promise<{
snapshot: CallerSnapshotFrom<TMachine>;
checksum: string;
}> {
const input = props.input ?? {};
if (!host) throw new Error("Actor Kit host is not defined");
const route = getActorRoute(actorType, props.actorId);
const protocol = getHttpProtocol(host);
const url = new URL(`${protocol}://${host}${route}`);
// Add input to URL parameters
url.searchParams.append("input", JSON.stringify(input));
// Add waitForEvent or waitForState to URL parameters
if (props.waitForEvent) {
url.searchParams.append(
"waitForEvent",
JSON.stringify(props.waitForEvent)
);
}
if (props.waitForState) {
url.searchParams.append(
"waitForState",
JSON.stringify(props.waitForState)
);
}
// Add timeout to URL parameters if specified
if (props.timeout) {
url.searchParams.append("timeout", props.timeout.toString());
}
// Add errorOnWaitTimeout to URL parameters if specified
if (props.errorOnWaitTimeout !== undefined) {
url.searchParams.append(
"errorOnWaitTimeout",
props.errorOnWaitTimeout.toString()
);
}
const response = await fetch(url.toString(), {
...options,
headers: {
...options?.headers,
Authorization: `Bearer ${props.accessToken}`,
},
});
if (!response.ok) {
if (response.status === 408 && props.errorOnWaitTimeout !== false) {
throw new Error(
`Timeout waiting for actor response: ${response.statusText}`
);
}
throw new Error(`Failed to fetch actor: ${response.statusText}`);
}
const data = await response.json();
const { checksum, snapshot } = ResponseSchema.parse(data);
return {
snapshot: snapshot as CallerSnapshotFrom<TMachine>,
checksum,
};
};
}
function getActorRoute(actorType: string, actorId: string) {
return `/api/${actorType}/${actorId}`;
}
function getHttpProtocol(host: string): "http" | "https" {
return isLocal(host) ? "http" : "https";
}
function isLocal(host: string): boolean {
return (
host.startsWith("localhost") ||
host.startsWith("127.0.0.1") ||
host.startsWith("0.0.0.0")
);
}