Skip to content

feat(functions-compiler): deliver platform events into Actor rooms - #696

Open
carmelc wants to merge 12 commits into
mainfrom
actor-platform-events
Open

carmelc wants to merge 12 commits into
mainfrom
actor-platform-events

Conversation

@carmelc

@carmelc carmelc commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Note

Description

Adds a platform-event delivery path into Actor rooms: the backend can now tell one room that something it waits for happened (today, a generated video settling). The generated Actor Worker answers POST /rooms/{room}/events, verifies a dedicated EdDSA event token, validates the body, and forwards it into the Durable Object so the room's new handlePlatformEvent(event) hook runs. Delivery is at-least-once, so the wrapper drops a repeated event_id while the instance stays resident and coalesces a retry that lands mid-run. Ships as @base44/functions-compiler v0.3.0-beta.3.

Related Issue

None

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Other (please describe):

Changes Made

  • New event route (src/actor-compat.ts): the generated wrapper accepts POST /rooms/{room}/events alongside the existing direct and legacy connect routes.
  • Event token verification (verifyActorEvent): same per-actor Ed25519 key as connection tokens, but audience base44-actor-event, purpose actor-platform-event, 60 s TTL, and an event_id claim — a connection token is never accepted as an event token, nor the reverse. 401 for a token this key did not mint or that lapsed; 403 for a valid token belonging to another app, actor, script or room.
  • Body validation (readPlatformEvent): one JSON object of at most 64 KB whose type is in the platform's __ namespace and whose event_id matches the token; otherwise 400 (413 when oversized).
  • Internal forward (platformEventRequest): rewritten as POST /parties/<actor>/<room>/events, a shape no client route can produce, so the Durable Object can trust it on shape alone.
  • Wrapper routing: platform events are handled ahead of the user's own fetch override, then answered 204.
  • Actor base class (src/shim/actor.ts): new PlatformEvent type and overridable handlePlatformEvent(event) (a no-op by default), plus the internal _b44PlatformEvent entry point that names the room (so handleStart runs on a cold wake), remembers the last 64 taken event_ids, shares one in-flight run across concurrent retries, and re-runs maintainTicker() afterwards.
  • Refactor: extracted verifiedClaims and validTimes from verifyActorConnection so connection and event tokens share one JWT verification and time-window check.
  • SDK bump: the Actor shim's this.client moves from npm:@base44/sdk@0.8.41 to 0.8.54, with the vitest alias to the test stub updated to match.
  • Version: package.json and src/version.ts bumped 0.2.0 → 0.3.0-beta.3.
  • Docs: new "Platform events into an Actor room" section in the package README covering the token, body, delivery and retry semantics, with pointers to the apper minting side and dispatcher route.

Testing

  • I have tested these changes locally
  • I have added/updated tests as needed
  • All tests pass (`npm test`)

New coverage:

  • test/actor-compat.test.ts — routes a platform event past the user's `fetch` override into the room hook; accepts only an event token minted for this app, actor, script and room.
  • test/actor-bundle.e2e.test.ts — three Miniflare/workerd end-to-end cases: an event that wakes a cold room names it and runs `handleStart` first; a verified event reaches `handlePlatformEvent` exactly once per `event_id`; a retry landing while the hook is still running runs the hook once.

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (if applicable)
  • My changes generate no new warnings
  • I have updated `docs/` (AGENTS.md) if I made architectural changes

Additional Notes

The package is published as a prerelease (`0.3.0-beta.3`) — apper's compiler pin should be bumped only after this is released, and existing compiled Workers need rebuilding to gain the route. The counterpart minting and dispatch code lives outside this repo (`backend/app/user_apps/actors/actor_events.py` and `workers/base44-dispatcher/src/actor-routing.ts`).


🤖 Generated by Claude | 2026-11-10 15:26 UTC | 5fe6f82

The backend can now tell one room that something it waits for happened (a
generated video settling). The generated Actor Worker answers
POST /rooms/{room}/events, which the public Actor dispatcher forwards from
POST /v1/actors/{script}/rooms/{room}/events.

- The auth module verifies an event token: the same per-actor Ed25519 key as
  connection tokens, its own audience and purpose, a 60 s TTL, bound to app,
  actor, script, room and event_id. 401 for a token the key did not mint or
  that lapsed, 403 for a good token aimed at another target. Neither token
  kind is accepted as the other.
- The body is one JSON object of at most 64 KB in the "__" type namespace whose
  event_id matches the token.
- The edge forwards it into the Durable Object as
  POST /parties/<actor>/<room>/events, a shape no client route can produce; the
  wrapper intercepts it ahead of any user fetch override and the shim's Actor
  runs handlePlatformEvent(event), a no-op by default, dropping an event_id the
  resident instance already took.

Counterpart: base44-dev/apper#39033 (token minting, sender, dispatcher route).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bQbJenvYqAg7Zznf4BgrZ
@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/cli@0.3.2-pr.696.5fe6f82

Prefer not to change any import paths? Install using npm alias so your code still imports base44:

npm i "base44@npm:@base44-preview/cli@0.3.2-pr.696.5fe6f82"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "base44": "npm:@base44-preview/cli@0.3.2-pr.696.5fe6f82"
  }
}

Preview published to npm registry — try new features instantly!

carmelc and others added 10 commits November 9, 2026 08:10
…and runs handleStart

The wrapper hands a platform event to _b44PlatformEvent ahead of
partyserver's fetch, which is what sets the room's name from
x-partykit-room and runs onStart. A room that an event woke threw on
this.name and skipped handleStart, so every delivery answered 500.
_b44PlatformEvent now calls setName(room) first; setName initializes the
room once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shim pinned 0.8.41, which sends filter(query, { sort, limit })
options as filters, so a room written against the app's SDK (0.8.54:
options object, a page with .items) got a 400 from every entity read.
0.8.54 keeps the positional filter(query, sort, limit) array form, so
existing rooms are unaffected; createClient's config is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent retries

A retry that arrived while handlePlatformEvent was still awaiting ran the hook
a second time, because the event_id was only remembered after the hook
returned. Concurrent deliveries of one event_id now share a single run and its
outcome. Also pins that no client route reaches the internal event path.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bQbJf79vT1iB1jEUyGdA8
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bQbJf79vT1iB1jEUyGdA8
yurynix
yurynix previously approved these changes Nov 10, 2026
The publish workflow sets the version itself (npm version <input>), which
fails with "Version not changed" when package.json already holds it. The
0.3.0 release runs the workflow on main instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants