@rivetkit/cloudflare-workers
Version:
Cloudflare Workers adapter for RivetKit actors
1,027 lines (986 loc) • 31.1 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;// src/actor-handler-do.ts
var _cloudflareworkers = require('cloudflare:workers');
var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant);
var _rivetkit = require('rivetkit');
var _driverhelpers = require('rivetkit/driver-helpers');
// src/actor-driver.ts
var _utils = require('rivetkit/utils');
// src/actor-id.ts
function buildActorId(doId, generation) {
return `${doId}:${generation}`;
}
function parseActorId(actorId) {
const parts = actorId.split(":");
if (parts.length !== 2) {
throw new Error(`Invalid actor ID format: ${actorId}`);
}
const [doId, generationStr] = parts;
const generation = parseInt(generationStr, 10);
if (Number.isNaN(generation)) {
throw new Error(`Invalid generation number in actor ID: ${actorId}`);
}
return [doId, generation];
}
// src/actor-kv.ts
function kvGet(sql, key) {
const cursor = sql.exec(
"SELECT value FROM _rivetkit_kv_storage WHERE key = ?",
key
);
const result = cursor.raw().next();
if (!result.done && result.value) {
return toUint8Array(result.value[0]);
}
return null;
}
function kvPut(sql, key, value) {
sql.exec(
"INSERT OR REPLACE INTO _rivetkit_kv_storage (key, value) VALUES (?, ?)",
key,
value
);
}
function kvDelete(sql, key) {
sql.exec("DELETE FROM _rivetkit_kv_storage WHERE key = ?", key);
}
function kvListPrefix(sql, prefix) {
const cursor = sql.exec("SELECT key, value FROM _rivetkit_kv_storage");
const entries = [];
for (const row of cursor.raw()) {
const key = toUint8Array(row[0]);
const value = toUint8Array(row[1]);
if (hasPrefix(key, prefix)) {
entries.push([key, value]);
}
}
return entries;
}
function toUint8Array(value) {
var _a;
if (value instanceof Uint8Array) {
return value;
}
if (value instanceof ArrayBuffer) {
return new Uint8Array(value);
}
throw new Error(
`Unexpected SQL value type: ${typeof value} (${(_a = value == null ? void 0 : value.constructor) == null ? void 0 : _a.name})`
);
}
function hasPrefix(arr, prefix) {
if (prefix.length > arr.length) return false;
for (let i = 0; i < prefix.length; i++) {
if (arr[i] !== prefix[i]) return false;
}
return true;
}
// src/global-kv.ts
var GLOBAL_KV_KEYS = {
actorMetadata: (actorId) => {
return `actor:${actorId}:metadata`;
}
};
// src/handler.ts
// src/config.ts
var _zod = require('zod');
var ConfigSchemaBase = _driverhelpers.RunConfigSchema.removeDefault().omit({ driver: true, getUpgradeWebSocket: true }).extend({
/** Path that the Rivet manager API will be mounted. */
managerPath: _zod.z.string().optional().default("/rivet"),
fetch: _zod.z.custom().optional()
});
var ConfigSchema = ConfigSchemaBase.default(
() => ConfigSchemaBase.parse({})
);
// src/manager-driver.ts
var _errors = require('rivetkit/errors');
// src/log.ts
var _log = require('rivetkit/log');
function logger() {
return _log.getLogger.call(void 0, "driver-cloudflare-workers");
}
// src/util.ts
var EMPTY_KEY = "(none)";
var KEY_SEPARATOR = ",";
function serializeNameAndKey(name, key) {
const escapedName = name.replace(/:/g, "\\:");
if (key.length === 0) {
return `${escapedName}:${EMPTY_KEY}`;
}
const serializedKey = serializeKey(key);
return `${escapedName}:${serializedKey}`;
}
function serializeKey(key) {
if (key.length === 0) {
return EMPTY_KEY;
}
const escapedParts = key.map((part) => {
if (part === EMPTY_KEY) {
return `\\${EMPTY_KEY}`;
}
let escaped = part.replace(/\\/g, "\\\\");
escaped = escaped.replace(/,/g, "\\,");
return escaped;
});
return escapedParts.join(KEY_SEPARATOR);
}
// src/manager-driver.ts
var STANDARD_WEBSOCKET_HEADERS = [
"connection",
"upgrade",
"sec-websocket-key",
"sec-websocket-version",
"sec-websocket-protocol",
"sec-websocket-extensions"
];
var CloudflareActorsManagerDriver = class {
async sendRequest(actorId, actorRequest) {
const env3 = getCloudflareAmbientEnv();
const [doId] = parseActorId(actorId);
logger().debug({
msg: "sending request to durable object",
actorId,
doId,
method: actorRequest.method,
url: actorRequest.url
});
const id = env3.ACTOR_DO.idFromString(doId);
const stub = env3.ACTOR_DO.get(id);
return await stub.fetch(actorRequest);
}
async openWebSocket(path, actorId, encoding, params) {
const env3 = getCloudflareAmbientEnv();
const [doId] = parseActorId(actorId);
logger().debug({
msg: "opening websocket to durable object",
actorId,
doId,
path
});
const id = env3.ACTOR_DO.idFromString(doId);
const stub = env3.ACTOR_DO.get(id);
const protocols = [];
protocols.push(_driverhelpers.WS_PROTOCOL_STANDARD);
protocols.push(`${_driverhelpers.WS_PROTOCOL_TARGET}actor`);
protocols.push(`${_driverhelpers.WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
protocols.push(`${_driverhelpers.WS_PROTOCOL_ENCODING}${encoding}`);
if (params) {
protocols.push(
`${_driverhelpers.WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`
);
}
const headers = {
Upgrade: "websocket",
Connection: "Upgrade",
"sec-websocket-protocol": protocols.join(", ")
};
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
const url = `http://actor${normalizedPath}`;
logger().debug({ msg: "rewriting websocket url", from: path, to: url });
const response = await stub.fetch(url, {
headers
});
const webSocket = response.webSocket;
if (!webSocket) {
throw new (0, _errors.InternalError)(
`missing websocket connection in response from DO
Status: ${response.status}
Response: ${await response.text()}`
);
}
logger().debug({
msg: "durable object websocket connection open",
actorId
});
webSocket.accept();
setTimeout(() => {
var _a;
const event = new Event("open");
(_a = webSocket.onopen) == null ? void 0 : _a.call(webSocket, event);
webSocket.dispatchEvent(event);
}, 0);
return webSocket;
}
async proxyRequest(c, actorRequest, actorId) {
const [doId] = parseActorId(actorId);
logger().debug({
msg: "forwarding request to durable object",
actorId,
doId,
method: actorRequest.method,
url: actorRequest.url
});
const id = c.env.ACTOR_DO.idFromString(doId);
const stub = c.env.ACTOR_DO.get(id);
return await stub.fetch(actorRequest);
}
async proxyWebSocket(c, path, actorId, encoding, params) {
logger().debug({
msg: "forwarding websocket to durable object",
actorId,
path
});
const upgradeHeader = c.req.header("Upgrade");
if (!upgradeHeader || upgradeHeader !== "websocket") {
return new Response("Expected Upgrade: websocket", {
status: 426
});
}
const newUrl = new URL(`http://actor${path}`);
const actorRequest = new Request(newUrl, c.req.raw);
logger().debug({
msg: "rewriting websocket url",
from: c.req.url,
to: actorRequest.url
});
const headerKeys = [];
actorRequest.headers.forEach((v, k) => {
headerKeys.push(k);
});
for (const k of headerKeys) {
if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) {
actorRequest.headers.delete(k);
}
}
const protocols = [];
protocols.push(_driverhelpers.WS_PROTOCOL_STANDARD);
protocols.push(`${_driverhelpers.WS_PROTOCOL_TARGET}actor`);
protocols.push(`${_driverhelpers.WS_PROTOCOL_ACTOR}${encodeURIComponent(actorId)}`);
protocols.push(`${_driverhelpers.WS_PROTOCOL_ENCODING}${encoding}`);
if (params) {
protocols.push(
`${_driverhelpers.WS_PROTOCOL_CONN_PARAMS}${encodeURIComponent(JSON.stringify(params))}`
);
}
actorRequest.headers.set(
"sec-websocket-protocol",
protocols.join(", ")
);
const [doId] = parseActorId(actorId);
const id = c.env.ACTOR_DO.idFromString(doId);
const stub = c.env.ACTOR_DO.get(id);
return await stub.fetch(actorRequest);
}
async getForId({
c,
actorId
}) {
const env3 = getCloudflareAmbientEnv();
const [doId, expectedGeneration] = parseActorId(actorId);
const id = env3.ACTOR_DO.idFromString(doId);
const stub = env3.ACTOR_DO.get(id);
const result = await stub.getMetadata();
if (!result) {
logger().debug({
msg: "getForId: actor not found",
actorId
});
return void 0;
}
if (result.actorId !== actorId) {
logger().debug({
msg: "getForId: generation mismatch",
requestedActorId: actorId,
actualActorId: result.actorId
});
return void 0;
}
if (result.destroying) {
throw new (0, _errors.ActorNotFound)(actorId);
}
return {
actorId: result.actorId,
name: result.name,
key: result.key
};
}
async getWithKey({
c,
name,
key
}) {
const env3 = getCloudflareAmbientEnv();
logger().debug({ msg: "getWithKey: searching for actor", name, key });
const nameKeyString = serializeNameAndKey(name, key);
const doId = env3.ACTOR_DO.idFromName(nameKeyString).toString();
const id = env3.ACTOR_DO.idFromString(doId);
const stub = env3.ACTOR_DO.get(id);
const result = await stub.getMetadata();
if (result) {
logger().debug({
msg: "getWithKey: found actor with matching name and key",
actorId: result.actorId,
name: result.name,
key: result.key
});
return {
actorId: result.actorId,
name: result.name,
key: result.key
};
} else {
logger().debug({
msg: "getWithKey: no actor found with matching name and key",
name,
key,
doId
});
return void 0;
}
}
async getOrCreateWithKey({
c,
name,
key,
input
}) {
const env3 = getCloudflareAmbientEnv();
const nameKeyString = serializeNameAndKey(name, key);
const doId = env3.ACTOR_DO.idFromName(nameKeyString);
const actor = env3.ACTOR_DO.get(doId);
const result = await actor.create({
name,
key,
input,
allowExisting: true
});
if ("success" in result) {
const { actorId, created } = result.success;
logger().debug({
msg: "getOrCreateWithKey result",
actorId,
name,
key,
created
});
return {
actorId,
name,
key
};
} else if ("error" in result) {
throw new Error(`Error: ${JSON.stringify(result.error)}`);
} else {
_utils.assertUnreachable.call(void 0, result);
}
}
async createActor({
c,
name,
key,
input
}) {
const env3 = getCloudflareAmbientEnv();
const nameKeyString = serializeNameAndKey(name, key);
const doId = env3.ACTOR_DO.idFromName(nameKeyString);
const actor = env3.ACTOR_DO.get(doId);
const result = await actor.create({
name,
key,
input,
allowExisting: false
});
if ("success" in result) {
const { actorId } = result.success;
return {
actorId,
name,
key
};
} else if ("error" in result) {
if (result.error.actorAlreadyExists) {
throw new (0, _errors.ActorDuplicateKey)(name, key);
}
throw new (0, _errors.InternalError)(
`Unknown error creating actor: ${JSON.stringify(result.error)}`
);
} else {
_utils.assertUnreachable.call(void 0, result);
}
}
async listActors({ c, name }) {
logger().warn({
msg: "listActors not fully implemented for Cloudflare Workers",
name
});
return [];
}
displayInformation() {
return {
name: "Cloudflare Workers",
properties: {}
};
}
getOrCreateInspectorAccessToken() {
return _driverhelpers.generateRandomString.call(void 0, );
}
};
// src/websocket.ts
var _ws = require('hono/ws');
var upgradeWebSocket = _ws.defineWebSocketHelper.call(void 0, async (c, events) => {
var _a, _b;
const upgradeHeader = c.req.header("Upgrade");
if (upgradeHeader !== "websocket") {
return;
}
const webSocketPair = new WebSocketPair();
const client = webSocketPair[0];
const server = webSocketPair[1];
const wsContext = new (0, _ws.WSContext)({
close: (code, reason) => server.close(code, reason),
get protocol() {
return server.protocol;
},
raw: server,
get readyState() {
return server.readyState;
},
url: server.url ? new URL(server.url) : null,
send: (source) => server.send(source)
});
if (events.onClose) {
server.addEventListener(
"close",
(evt) => {
var _a2;
return (_a2 = events.onClose) == null ? void 0 : _a2.call(events, evt, wsContext);
}
);
}
if (events.onMessage) {
server.addEventListener(
"message",
(evt) => {
var _a2;
return (_a2 = events.onMessage) == null ? void 0 : _a2.call(events, evt, wsContext);
}
);
}
if (events.onError) {
server.addEventListener(
"error",
(evt) => {
var _a2;
return (_a2 = events.onError) == null ? void 0 : _a2.call(events, evt, wsContext);
}
);
}
(_a = server.accept) == null ? void 0 : _a.call(server);
(_b = events.onOpen) == null ? void 0 : _b.call(events, new Event("open"), wsContext);
const headers = {};
const protocols = c.req.header("Sec-WebSocket-Protocol");
if (typeof protocols === "string" && protocols.split(",").map((x) => x.trim()).includes(_driverhelpers.WS_PROTOCOL_STANDARD)) {
headers["Sec-WebSocket-Protocol"] = _driverhelpers.WS_PROTOCOL_STANDARD;
}
return new Response(null, {
status: 101,
headers,
webSocket: client
});
});
// src/handler.ts
function getCloudflareAmbientEnv() {
return _cloudflareworkers.env;
}
function createInlineClient(registry, inputConfig) {
inputConfig = { ...inputConfig, runnerKey: "" };
const config = ConfigSchema.parse(inputConfig);
const runConfig = {
...config,
driver: {
name: "cloudflare-workers",
manager: () => new CloudflareActorsManagerDriver(),
// HACK: We can't build the actor driver until we're inside the Durable Object
actor: void 0
},
getUpgradeWebSocket: () => upgradeWebSocket
};
const ActorHandler = createActorDurableObject(registry, runConfig);
const { client, fetch } = registry.start(runConfig);
return { client, fetch, config, ActorHandler };
}
function createHandler(registry, inputConfig) {
const { client, fetch, config, ActorHandler } = createInlineClient(
registry,
inputConfig
);
const handler = {
fetch: async (request, cfEnv, ctx) => {
const url = new URL(request.url);
const env3 = Object.assign({ RIVET: client }, cfEnv);
if (url.pathname.startsWith(config.managerPath)) {
const strippedPath = url.pathname.substring(
config.managerPath.length
);
url.pathname = strippedPath;
const modifiedRequest = new Request(url.toString(), request);
return fetch(modifiedRequest, env3, ctx);
}
if (config.fetch) {
return config.fetch(request, env3, ctx);
} else {
return new Response(
"This is a RivetKit server.\n\nLearn more at https://rivetkit.org\n",
{ status: 200 }
);
}
}
};
return { handler, ActorHandler };
}
// src/actor-driver.ts
var CloudflareDurableObjectGlobalState = class {
// Map of actor ID -> DO state
#dos = /* @__PURE__ */ new Map();
// WeakMap of DO state -> ActorGlobalState for proper GC
#actors = /* @__PURE__ */ new WeakMap();
getDOState(doId) {
const state = this.#dos.get(doId);
_invariant2.default.call(void 0,
state !== void 0,
"durable object state not in global state"
);
return state;
}
setDOState(doId, state) {
this.#dos.set(doId, state);
}
getActorState(ctx) {
return this.#actors.get(ctx);
}
setActorState(ctx, actorState) {
this.#actors.set(ctx, actorState);
}
};
var ActorGlobalState = (_class = class {constructor() { _class.prototype.__init.call(this); }
// Initialization state
// Loaded actor state
/**
* Indicates if `startDestroy` has been called.
*
* This is stored in memory instead of SQLite since the destroy may be cancelled.
*
* See the corresponding `destroyed` property in SQLite metadata.
*/
__init() {this.destroying = false}
reset() {
this.initialized = void 0;
this.actor = void 0;
this.actorInstance = void 0;
this.actorPromise = void 0;
this.destroying = false;
}
}, _class);
var CloudflareActorsActorDriver = class {
#registryConfig;
#runConfig;
#managerDriver;
#inlineClient;
#globalState;
constructor(registryConfig, runConfig, managerDriver, inlineClient, globalState) {
this.#registryConfig = registryConfig;
this.#runConfig = runConfig;
this.#managerDriver = managerDriver;
this.#inlineClient = inlineClient;
this.#globalState = globalState;
}
#getDOCtx(actorId) {
const [doId] = parseActorId(actorId);
return this.#globalState.getDOState(doId).ctx;
}
async loadActor(actorId) {
var _a;
const [doId, expectedGeneration] = parseActorId(actorId);
const doState = this.#globalState.getDOState(doId);
let actorState = this.#globalState.getActorState(doState.ctx);
if (actorState == null ? void 0 : actorState.actorInstance) {
return actorState.actorInstance;
}
if (!actorState) {
actorState = new ActorGlobalState();
actorState.actorPromise = _utils.promiseWithResolvers.call(void 0, );
this.#globalState.setActorState(doState.ctx, actorState);
} else if (actorState.actorPromise) {
await actorState.actorPromise.promise;
if (!actorState.actorInstance) {
throw new Error(
`Actor ${actorId} failed to load in concurrent request`
);
}
return actorState.actorInstance;
}
const sql = doState.ctx.storage.sql;
const cursor = sql.exec(
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata LIMIT 1"
);
const result = cursor.raw().next();
if (result.done || !result.value) {
throw new Error(
`Actor ${actorId} is not initialized - missing metadata`
);
}
const name = result.value[0];
const key = JSON.parse(result.value[1]);
const destroyed = result.value[2];
const generation = result.value[3];
if (destroyed) {
throw new Error(`Actor ${actorId} is destroyed`);
}
if (generation !== expectedGeneration) {
throw new Error(
`Actor ${actorId} generation mismatch - expected ${expectedGeneration}, got ${generation}`
);
}
const definition = _rivetkit.lookupInRegistry.call(void 0, this.#registryConfig, name);
actorState.actorInstance = definition.instantiate();
await actorState.actorInstance.start(
this,
this.#inlineClient,
actorId,
name,
key,
"unknown"
// TODO: Support regions in Cloudflare
);
(_a = actorState.actorPromise) == null ? void 0 : _a.resolve();
actorState.actorPromise = void 0;
return actorState.actorInstance;
}
getContext(actorId) {
const [doId] = parseActorId(actorId);
const state = this.#globalState.getDOState(doId);
return { state: state.ctx };
}
async setAlarm(actor, timestamp) {
await this.#getDOCtx(actor.id).storage.setAlarm(timestamp);
}
async getDatabase(actorId) {
return this.#getDOCtx(actorId).storage.sql;
}
// Batch KV operations
async kvBatchPut(actorId, entries) {
const sql = this.#getDOCtx(actorId).storage.sql;
for (const [key, value] of entries) {
kvPut(sql, key, value);
}
}
async kvBatchGet(actorId, keys) {
const sql = this.#getDOCtx(actorId).storage.sql;
const results = [];
for (const key of keys) {
results.push(kvGet(sql, key));
}
return results;
}
async kvBatchDelete(actorId, keys) {
const sql = this.#getDOCtx(actorId).storage.sql;
for (const key of keys) {
kvDelete(sql, key);
}
}
async kvListPrefix(actorId, prefix) {
const sql = this.#getDOCtx(actorId).storage.sql;
return kvListPrefix(sql, prefix);
}
startDestroy(actorId) {
const [doId, generation] = parseActorId(actorId);
const doState = this.#globalState.getDOState(doId);
const actorState = this.#globalState.getActorState(doState.ctx);
if (!(actorState == null ? void 0 : actorState.actorInstance)) {
return;
}
if (actorState.destroying) {
return;
}
actorState.destroying = true;
this.#callOnStopAsync(actorId, doId, actorState.actorInstance);
}
async #callOnStopAsync(actorId, doId, actor) {
await actor.onStop("destroy");
const doState = this.#globalState.getDOState(doId);
const sql = doState.ctx.storage.sql;
sql.exec("UPDATE _rivetkit_metadata SET destroyed = 1 WHERE 1=1");
sql.exec("DELETE FROM _rivetkit_kv_storage");
await doState.ctx.storage.deleteAlarm();
const env3 = getCloudflareAmbientEnv();
doState.ctx.waitUntil(
env3.ACTOR_KV.delete(GLOBAL_KV_KEYS.actorMetadata(actorId))
);
const actorHandle = this.#globalState.getActorState(doState.ctx);
actorHandle == null ? void 0 : actorHandle.reset();
}
};
function createCloudflareActorsActorDriverBuilder(globalState) {
return (registryConfig, runConfig, managerDriver, inlineClient) => {
return new CloudflareActorsActorDriver(
registryConfig,
runConfig,
managerDriver,
inlineClient,
globalState
);
};
}
// src/actor-handler-do.ts
function createActorDurableObject(registry, rootRunConfig) {
const globalState = new CloudflareDurableObjectGlobalState();
const runConfig = Object.assign({}, rootRunConfig, { role: "runner" });
return class ActorHandler extends _cloudflareworkers.DurableObject {
/**
* This holds a strong reference to ActorGlobalState.
* CloudflareDurableObjectGlobalState holds a weak reference so we can
* access it elsewhere.
**/
#state;
constructor(...args) {
super(...args);
this.ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS _rivetkit_kv_storage(
key BLOB PRIMARY KEY,
value BLOB
);
`);
this.ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS _rivetkit_metadata(
id INTEGER PRIMARY KEY CHECK (id = 1),
name TEXT NOT NULL,
key TEXT NOT NULL,
destroyed INTEGER DEFAULT 0,
generation INTEGER DEFAULT 0
);
`);
const state = globalState.getActorState(this.ctx);
if (state) {
this.#state = state;
} else {
this.#state = new ActorGlobalState();
globalState.setActorState(this.ctx, this.#state);
}
}
async #loadActor() {
var _a;
_invariant2.default.call(void 0, this.#state, "State should be initialized");
if (!this.#state.initialized) {
const cursor = this.ctx.storage.sql.exec(
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
);
const result = cursor.raw().next();
if (!result.done && result.value) {
const name = result.value[0];
const key = JSON.parse(
result.value[1]
);
const destroyed = result.value[2];
const generation = result.value[3];
if (!destroyed) {
logger().debug({
msg: "already initialized",
name,
key,
generation
});
this.#state.initialized = { name, key, generation };
} else {
logger().debug("actor is destroyed, cannot load");
throw new Error("Actor is destroyed");
}
} else {
logger().debug("not initialized");
throw new Error("Actor is not initialized");
}
}
if (this.#state.actor) {
_invariant2.default.call(void 0,
!this.#state.initialized || this.#state.actor.generation === this.#state.initialized.generation,
`Stale actor cached: actor generation ${this.#state.actor.generation} != initialized generation ${(_a = this.#state.initialized) == null ? void 0 : _a.generation}. This should not happen.`
);
return this.#state.actor;
}
if (!this.#state.initialized) throw new Error("Not initialized");
const actorId = this.ctx.id.toString();
globalState.setDOState(actorId, { ctx: this.ctx, env: _cloudflareworkers.env });
_invariant2.default.call(void 0, runConfig.driver, "runConfig.driver");
runConfig.driver.actor = createCloudflareActorsActorDriverBuilder(globalState);
const managerDriver = runConfig.driver.manager(
registry.config,
runConfig
);
const inlineClient = _rivetkit.createClientWithDriver.call(void 0,
managerDriver,
runConfig
);
const actorDriver = runConfig.driver.actor(
registry.config,
runConfig,
managerDriver,
inlineClient
);
const actorRouter = _rivetkit.createActorRouter.call(void 0,
runConfig,
actorDriver,
false
);
this.#state.actor = {
actorRouter,
actorDriver,
generation: this.#state.initialized.generation
};
const actorIdWithGen = buildActorId(
actorId,
this.#state.initialized.generation
);
await actorDriver.loadActor(actorIdWithGen);
return this.#state.actor;
}
/** RPC called to get actor metadata without creating it */
async getMetadata() {
var _a;
const cursor = this.ctx.storage.sql.exec(
"SELECT name, key, destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
);
const result = cursor.raw().next();
if (!result.done && result.value) {
const name = result.value[0];
const key = JSON.parse(result.value[1]);
const destroyed = result.value[2];
const generation = result.value[3];
if (destroyed) {
logger().debug({
msg: "getMetadata: actor is destroyed",
name,
key,
generation
});
return void 0;
}
const doId = this.ctx.id.toString();
const actorId = buildActorId(doId, generation);
const destroying = _nullishCoalesce(((_a = globalState.getActorState(this.ctx)) == null ? void 0 : _a.destroying), () => ( false));
logger().debug({
msg: "getMetadata: found actor metadata",
actorId,
name,
key,
generation,
destroying
});
return { actorId, name, key, destroying };
}
logger().debug({
msg: "getMetadata: no metadata found"
});
return void 0;
}
/** RPC called by the manager to create a DO. Can optionally allow existing actors. */
async create(req) {
const checkCursor = this.ctx.storage.sql.exec(
"SELECT destroyed, generation FROM _rivetkit_metadata WHERE id = 1"
);
const checkResult = checkCursor.raw().next();
let created = false;
let generation = 0;
if (!checkResult.done && checkResult.value) {
const destroyed = checkResult.value[0];
generation = checkResult.value[1];
if (!destroyed) {
if (!req.allowExisting) {
logger().debug({
msg: "create failed: actor already exists",
name: req.name,
key: req.key,
generation
});
return { error: { actorAlreadyExists: true } };
}
logger().debug({
msg: "actor already exists",
key: req.key,
generation
});
const doId2 = this.ctx.id.toString();
const actorId2 = buildActorId(doId2, generation);
return { success: { actorId: actorId2, created: false } };
}
generation = generation + 1;
created = true;
if (this.#state) {
this.#state.actor = void 0;
}
logger().debug({
msg: "resurrecting destroyed actor",
key: req.key,
oldGeneration: generation - 1,
newGeneration: generation
});
} else {
generation = 0;
created = true;
logger().debug({
msg: "creating new actor",
key: req.key,
generation
});
}
this.ctx.storage.sql.exec(
`INSERT INTO _rivetkit_metadata (id, name, key, destroyed, generation)
VALUES (1, ?, ?, 0, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
key = excluded.key,
destroyed = 0,
generation = excluded.generation`,
req.name,
JSON.stringify(req.key),
generation
);
this.#state.initialized = {
name: req.name,
key: req.key,
generation
};
const doId = this.ctx.id.toString();
const actorId = buildActorId(doId, generation);
if (created) {
initializeActorKvStorage(this.ctx.storage.sql, req.input);
const env3 = getCloudflareAmbientEnv();
const actorData = { name: req.name, key: req.key, generation };
this.ctx.waitUntil(
env3.ACTOR_KV.put(
GLOBAL_KV_KEYS.actorMetadata(actorId),
JSON.stringify(actorData)
)
);
}
await this.#loadActor();
logger().debug({
msg: created ? "actor created/resurrected" : "returning existing actor",
actorId,
created,
generation
});
return { success: { actorId, created } };
}
async fetch(request) {
const { actorRouter, generation } = await this.#loadActor();
const doId = this.ctx.id.toString();
const actorId = buildActorId(doId, generation);
return await actorRouter.fetch(request, {
actorId
});
}
async alarm() {
const { actorDriver, generation } = await this.#loadActor();
const doId = this.ctx.id.toString();
const actorId = buildActorId(doId, generation);
const actor = await actorDriver.loadActor(actorId);
await actor.onAlarm();
}
};
}
function initializeActorKvStorage(sql, input) {
const initialKvState = _driverhelpers.getInitialActorKvState.call(void 0, input);
for (const [key, value] of initialKvState) {
kvPut(sql, key, value);
}
}
exports.createActorDurableObject = createActorDurableObject; exports.createHandler = createHandler; exports.createInlineClient = createInlineClient;
//# sourceMappingURL=mod.cjs.map