puter.events.onPersistent() no longer routes deliveries. delivery: 'broadcast' | 'single' becomes a boolean single, and targets goes away.
API
onPersistent({ ..., single: true })replacesdelivery: 'single'. It still needshandlerName, and keeps today's behavior: each event is leased to one consumer (connected clients first, then the events worker) and stays owed until acked, one at a time.- Without
single, every event runs the app's published handler in its events worker (when the subscription is an app's and has ahandlerName) and is also sent to every connected socket, includingonLocal()subscribers. Before, the server picked one: the socket copy carriedskipHandlerwhenever the worker took it, so the page only ran the handler when the worker couldn't. Now both run.skipHandleris only set for events past the handler-chain limit. delivery: 'single' | 'broadcast'is still accepted (conflictingsingle/deliveryis a 400).targetsis accepted and ignored, on both persistent and session subscribes.- Subscription objects (
onPersistent()result,list()) returnsingle: booleaninstead ofdeliveryandtargets. events:backgroundconsent is required when an app subscribes with ahandlerName, instead of whenever the row targetedworker(which was the app default). An app subscription without a handler no longer needs consent. Revoking consent suspends the app's rows that have a handler.
Existing subscriptions
No schema change. Stored rows are read by delivery alone: 'single' is single, anything else is the default. The targets column is still written (with what older servers expect) but never read. So:
- Default rows that had
targets: ['socket']and a handler now also run in the worker. The run is still gated byevents:backgroundon each delivery, so a row created without consent gets no worker run. singlerows created withtargets: ['worker'](as the background-work recipe suggested) are now offered to connected clients of the app first, like every othersinglerow. The recipe is updated.- Rows with only
push(reserved, never delivered) now deliver normally.
The default mode is not retried: a worker run that fails, or an event with nothing connected and no handler, isn't redelivered. Billing is unchanged, one delivery line per event, however many places run it.
Tests
vitest --config src/backend/vitest.config.ts src/backend/services/events src/backend/controllers/events src/backend/stores/events src/backend/services/notification src/backend/controllers/apps: 1240 passed.- puter.js unit tests for
modules/events: 204 passed. - puter.js API events suite on the node runner: 44 passed.
npm run check:puterjs:types,npm run typecheck: clean.
Routing-only tests (invalid targets, push with single, socket-only/worker-only routing) are removed. New tests cover single, the older delivery spelling, ignored targets, worker plus socket on every default delivery, consent only with a handler, and the store's legacy column handling.
Activity
3Coverage Report for puter.js SDK
| Status | Category | Percentage | Covered / Total |
|---|---|---|---|
| 🔵 | Lines | 59.48% ⬇️ -0.04 pp | 5283 / 8881 |
| 🔵 | Statements | 57.59% ⬇️ -0.03 pp | 5628 / 9772 |
| 🔵 | Functions | 59.33% ⬇️ -0.06 pp | 976 / 1645 |
| 🔵 | Branches | 52.68% ⬇️ -0.05 pp | 3584 / 6803 |
File Coverage
| File | Stmts | Branches | Functions | Lines | Uncovered Lines |
|---|---|---|---|---|---|
| Changed Files | |||||
| src/puter-js/src/modules/events/onPersistent.js | 94.44% 🟰 ±0 pp | 82.85% ⬇️ -2.44 pp | 100% 🟰 ±0 pp | 94.11% 🟰 ±0 pp | 51-54 |
| src/puter-js/src/modules/events/lib/channel.js | 76.4% 🟰 ±0 pp | 59.87% 🟰 ±0 pp | 84.9% 🟰 ±0 pp | 81.3% 🟰 ±0 pp | 103, 133-134, 139, 239-241, 310-311, 331-332, 339, 344-345, 368-372, 375, 382, 423, 428, 430-431, 441, 451, 460-461, 468-499, 508, 536, 559, 577, 612, 634-636, 669, 682-683, 692, 701, 703, 749, 753-755 |
Coverage Report
⬆️ +0.01 pp
⬆️ +0.01 pp
⬇️ -0.02 pp
⬆️ +0.01 pp
File Coverage
🟰 ±0 pp
⬆️ +0.65 pp
🟰 ±0 pp
🟰 ±0 pp
⬆️ +0.05 pp
⬇️ -0.12 pp
⬇️ -0.12 pp
⬆️ +0.15 pp
⬇️ -0.15 pp
🟰 ±0 pp
🟰 ±0 pp
⬇️ -0.09 pp
⬆️ +0.54 pp
⬆️ +0.60 pp
⬇️ -0.58 pp
⬆️ +0.27 pp
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp