@resonatehq/cloudflare
Version:
Resonate FaaS handler for Cloudflare Workers (TypeScript)
122 lines (121 loc) • 5.47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Resonate = void 0;
const sdk_1 = require("@resonatehq/sdk");
const encryptor_1 = require("@resonatehq/sdk/dist/src/encryptor");
class Resonate {
constructor({ verbose = false, encryptor = undefined, } = {}) {
this.registry = new sdk_1.Registry();
this.dependencies = new Map();
this.verbose = verbose;
this.encryptor = encryptor ?? new encryptor_1.NoopEncryptor();
}
register(nameOrFunc, funcOrOptions, maybeOptions = {}) {
const { version = 1 } = (typeof funcOrOptions === "object" ? funcOrOptions : maybeOptions) ?? {};
const func = typeof nameOrFunc === "function" ? nameOrFunc : funcOrOptions;
const name = typeof nameOrFunc === "string" ? nameOrFunc : func.name;
this.registry.add(func, name, version);
}
onInitialize(fn) {
this.initializer = fn;
}
setDependency(name, obj) {
this.dependencies.set(name, obj);
}
handlerHttp() {
return {
fetch: async (request, env, _ctx) => {
if (this.initializer !== undefined) {
await this.initializer(env);
}
try {
if (request.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed. Use POST." }), {
status: 405,
});
}
const formattedUrl = new URL(request.url);
const url = `${formattedUrl.protocol}//${formattedUrl.host}`;
const body = await request.json();
if (!request.body) {
return new Response(JSON.stringify({
error: "Request body missing.",
}), {
status: 400,
});
}
if (!body ||
!(body.type === "invoke" || body.type === "resume") ||
!body.task) {
return new Response(JSON.stringify({
error: 'Request body must contain "type" and "task" for Resonate invocation.',
}), {
status: 400,
});
}
const encoder = new sdk_1.JsonEncoder();
const network = new sdk_1.HttpNetwork({
headers: {},
timeout: 60 * 1000, // 60s
url: body.href.base,
verbose: this.verbose,
});
const resonateInner = new sdk_1.ResonateInner({
anycastNoPreference: url,
anycastPreference: url,
clock: new sdk_1.WallClock(),
dependencies: this.dependencies,
handler: new sdk_1.Handler(network, encoder, this.encryptor),
heartbeat: new sdk_1.NoopHeartbeat(),
network,
pid: `pid-${Math.random().toString(36).substring(7)}`,
registry: this.registry,
ttl: 30 * 1000, // 30s
unicast: url,
verbose: this.verbose,
});
const task = { kind: "unclaimed", task: body.task };
const completion = new Promise((resolve) => {
resonateInner.process(task, (error, status) => {
if (error || !status) {
resolve(new Response(JSON.stringify({
error: "Task processing failed",
details: { error, status },
}), {
status: 500,
}));
return;
}
if (status.kind === "completed") {
resolve(new Response(JSON.stringify({
status: "completed",
result: status.promise.value,
requestUrl: url,
}), {
status: 200,
}));
return;
}
else if (status.kind === "suspended") {
resolve(new Response(JSON.stringify({
status: "suspended",
requestUrl: url,
}), {
status: 200,
}));
return;
}
});
});
return completion;
}
catch (error) {
return new Response(JSON.stringify({
error: `Handler failed: ${error}`,
}), { status: 500 });
}
},
};
}
}
exports.Resonate = Resonate;