diff --git a/.cursor/rules/actor-core-rules.mdc b/.cursor/rules/actor-core-rules.mdc index 4de859b00..c407cdbf0 100644 --- a/.cursor/rules/actor-core-rules.mdc +++ b/.cursor/rules/actor-core-rules.mdc @@ -1,6 +1,6 @@ --- description: ActorCore rules -globs: +globs: alwaysApply: false --- # ActorCore Development Guide @@ -59,11 +59,11 @@ When importing from workspace packages, always check the package's `package.json - **Formatting:** Uses Biome for consistent formatting - **Imports:** Organized imports enforced, unused imports warned - **TypeScript:** Strict mode enabled, target ESNext -- **Naming:** +- **Naming:** - camelCase for variables, functions - PascalCase for classes, interfaces, types - UPPER_CASE for constants -- **Error Handling:** +- **Error Handling:** - Use `UserError` for client-safe errors - Use `InternalError` for internal errors @@ -105,7 +105,7 @@ When importing from workspace packages, always check the package's `package.json - `createState()`: Function that returns initial actor state - `onStart(c)`: Called any time actor is started (after restart/upgrade) -- `onStateChange(c, newState)`: Called when actor state changes +- `onStateChange(c,prevState, newState)`: Called when actor state changes - `onBeforeConnect(c)`: Called when new client connects - `onConnect(c)`: Executed after client connection succeeds - `onDisconnect(c)`: Called when client disconnects @@ -144,4 +144,4 @@ When importing from workspace packages, always check the package's `package.json - Use `assertUnreachable(x: never)` for exhaustive type checking - Add proper JSDoc comments for public APIs - Run `yarn check-types` regularly during development -- Use `tsx` CLI to execute TypeScript scripts directly \ No newline at end of file +- Use `tsx` CLI to execute TypeScript scripts directly diff --git a/packages/core/src/actor/config.ts b/packages/core/src/actor/config.ts index 42419919c..d5938f745 100644 --- a/packages/core/src/actor/config.ts +++ b/packages/core/src/actor/config.ts @@ -345,6 +345,7 @@ interface BaseActorConfig< TAuthData, TDatabase >, + prevState: TState, newState: TState, ) => void; diff --git a/packages/core/src/actor/instance.ts b/packages/core/src/actor/instance.ts index 15f598a2e..213dfc44d 100644 --- a/packages/core/src/actor/instance.ts +++ b/packages/core/src/actor/instance.ts @@ -572,14 +572,18 @@ export class ActorInstance< }); } this.#persistChanged = true; - + const previousState = structuredClone(this.#persist.s); // Inform the inspector about state changes this.inspector.emitter.emit("stateUpdated", this.#persist.s); // Call onStateChange if it exists if (this.#config.onStateChange && this.#ready) { try { - this.#config.onStateChange(this.actorContext, this.#persistRaw.s); + this.#config.onStateChange( + this.actorContext, + previousState, + this.#persistRaw.s, + ); } catch (error) { logger().error("error in `_onStateChange`", { error: stringifyError(error),