From 6a0ab8bec6f499047f67928a6ef53aa71d2e05e9 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 20 May 2025 10:17:52 -0700 Subject: [PATCH] chore: throw error if attempting to create actor multiple times --- packages/actor-core/src/actor/errors.ts | 10 ++++++++++ packages/actor-core/src/test/driver/manager.ts | 8 ++++++++ packages/drivers/file-system/src/manager.ts | 7 +++++++ packages/drivers/memory/src/manager.ts | 8 ++++++++ packages/drivers/redis/src/manager.ts | 8 ++++++++ .../platforms/cloudflare-workers/src/manager-driver.ts | 7 +++++++ packages/platforms/rivet/src/manager-driver.ts | 7 +++++++ 7 files changed, 55 insertions(+) diff --git a/packages/actor-core/src/actor/errors.ts b/packages/actor-core/src/actor/errors.ts index fa71a5969..943e96bd9 100644 --- a/packages/actor-core/src/actor/errors.ts +++ b/packages/actor-core/src/actor/errors.ts @@ -254,6 +254,16 @@ export class ActorNotFound extends ActorError { } } +export class ActorAlreadyExists extends ActorError { + constructor(name: string, key: string[]) { + super( + "actor_already_exists", + `Actor already exists with name "${name}" and key ${JSON.stringify(key)}`, + { public: true } + ); + } +} + export class ProxyError extends ActorError { constructor(operation: string, error?: unknown) { super( diff --git a/packages/actor-core/src/test/driver/manager.ts b/packages/actor-core/src/test/driver/manager.ts index 9c2f8f1c3..fe9f599c9 100644 --- a/packages/actor-core/src/test/driver/manager.ts +++ b/packages/actor-core/src/test/driver/manager.ts @@ -6,7 +6,9 @@ import type { GetWithKeyInput, ManagerDriver, } from "@/driver-helpers/mod"; +import { ActorAlreadyExists } from "@/actor/errors"; import type { TestGlobalState } from "./global-state"; +import * as crypto from "node:crypto"; import { ManagerInspector } from "@/inspector/manager"; import type { ActorCoreApp } from "@/app/mod"; @@ -117,6 +119,12 @@ export class TestManagerDriver implements ManagerDriver { name, key, }: CreateActorInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + const actorId = crypto.randomUUID(); this.#state.createActor(actorId, name, key); diff --git a/packages/drivers/file-system/src/manager.ts b/packages/drivers/file-system/src/manager.ts index 0124d1bda..f98cd7b47 100644 --- a/packages/drivers/file-system/src/manager.ts +++ b/packages/drivers/file-system/src/manager.ts @@ -7,6 +7,7 @@ import type { GetWithKeyInput, ManagerDriver, } from "actor-core/driver-helpers"; +import { ActorAlreadyExists } from "actor-core/actor/errors"; import { logger } from "./log"; import type { FileSystemGlobalState } from "./global-state"; import type { ActorCoreApp } from "actor-core"; @@ -95,6 +96,12 @@ export class FileSystemManagerDriver implements ManagerDriver { name, key, }: CreateActorInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + const actorId = crypto.randomUUID(); await this.#state.createActor(actorId, name, key); diff --git a/packages/drivers/memory/src/manager.ts b/packages/drivers/memory/src/manager.ts index 4db4fa344..d3dc495d1 100644 --- a/packages/drivers/memory/src/manager.ts +++ b/packages/drivers/memory/src/manager.ts @@ -6,7 +6,9 @@ import type { GetWithKeyInput, ManagerDriver, } from "actor-core/driver-helpers"; +import { ActorAlreadyExists } from "actor-core/actor/errors"; import type { MemoryGlobalState } from "./global-state"; +import * as crypto from "node:crypto"; import { ManagerInspector } from "actor-core/inspector"; import type { ActorCoreApp } from "actor-core"; @@ -86,6 +88,12 @@ export class MemoryManagerDriver implements ManagerDriver { name, key, }: CreateActorInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + const actorId = crypto.randomUUID(); this.#state.createActor(actorId, name, key); diff --git a/packages/drivers/redis/src/manager.ts b/packages/drivers/redis/src/manager.ts index 7ba8ed060..d4994847b 100644 --- a/packages/drivers/redis/src/manager.ts +++ b/packages/drivers/redis/src/manager.ts @@ -6,7 +6,9 @@ import type { GetWithKeyInput, ManagerDriver, } from "actor-core/driver-helpers"; +import { ActorAlreadyExists } from "actor-core/actor/errors"; import type Redis from "ioredis"; +import * as crypto from "node:crypto"; import { KEYS } from "./keys"; import { ManagerInspector } from "actor-core/inspector"; import type { ActorCoreApp } from "actor-core"; @@ -93,6 +95,12 @@ export class RedisManagerDriver implements ManagerDriver { name, key, }: CreateActorInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + const actorId = crypto.randomUUID(); const actorKeyRedisKey = this.#generateActorKeyRedisKey(name, key); diff --git a/packages/platforms/cloudflare-workers/src/manager-driver.ts b/packages/platforms/cloudflare-workers/src/manager-driver.ts index 8f172c93f..07552097a 100644 --- a/packages/platforms/cloudflare-workers/src/manager-driver.ts +++ b/packages/platforms/cloudflare-workers/src/manager-driver.ts @@ -5,6 +5,7 @@ import type { CreateActorInput, GetActorOutput, } from "actor-core/driver-helpers"; +import { ActorAlreadyExists } from "actor-core/actor/errors"; import { Bindings } from "./mod"; import { logger } from "./log"; import { serializeNameAndKey, serializeKey } from "./util"; @@ -112,6 +113,12 @@ export class CloudflareWorkersManagerDriver implements ManagerDriver { if (!c) throw new Error("Missing Hono context"); const log = logger(); + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ c, name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + // Create a deterministic ID from the actor name and key // This ensures that actors with the same name and key will have the same ID const nameKeyString = serializeNameAndKey(name, key); diff --git a/packages/platforms/rivet/src/manager-driver.ts b/packages/platforms/rivet/src/manager-driver.ts index e90ba5956..d3e5560c7 100644 --- a/packages/platforms/rivet/src/manager-driver.ts +++ b/packages/platforms/rivet/src/manager-driver.ts @@ -1,4 +1,5 @@ import { assertUnreachable } from "actor-core/utils"; +import { ActorAlreadyExists } from "actor-core/actor/errors"; import type { ManagerDriver, GetForIdInput, @@ -121,6 +122,12 @@ export class RivetManagerDriver implements ManagerDriver { key, region, }: CreateActorInput): Promise { + // Check if actor with the same name and key already exists + const existingActor = await this.getWithKey({ name, key }); + if (existingActor) { + throw new ActorAlreadyExists(name, key); + } + // Create the actor request let actorLogLevel: string | undefined = undefined; if (typeof Deno !== "undefined") {