* feat(events): simplify onPersistent delivery
Replace delivery/targets with a boolean single. Without single, every
event runs the app's published handler in its events worker and is also
sent to every connected client; the server no longer routes between the
two. single keeps leasing each event to one consumer that acks.
delivery: 'single' | 'broadcast' is still accepted and targets is
ignored. Stored rows are read by their delivery column alone.
* docs(events): compare onLocal, onPersistent and single
* feat(events): rename single to queue
onPersistent({ queue: true }) is the work-queue mode: each event goes to
one consumer at a time and is offered again until acknowledged.
delivery: 'single' is still accepted as an alias, and subscription
objects report queue instead of single.
The docs explain the three modes (onLocal, default onPersistent, queue)
and each events recipe says which one it uses and why.
* docs(events): retries are the only repeat for a queued event
* feat(events): queue deliveries fall through to the worker on decline
A queued delivery gets one socket attempt under a 15 s lease instead of
two 60 s ones. A client with no handler for it, or whose handler fails,
sends events.nack and the worker takes it without waiting for the lease.
Declines are relayed to the region holding the lease like acks are, and
a decline for a lease that is no longer the client's is ignored.
* feat(events): drop thrown errors, retry only temporary ones
A queued handler that throws now drops the event and counts one failure
toward the pause. Network errors, timeouts, rate limits, 5xx responses
and errors thrown with retry: true are retried with the existing
backoff. One classifier is shared by puter.js and the events worker
runtime, which inlines it at build time. A dropped event is billed once,
like a completed one. ack() remains as an optional early release.
* docs(events): queued events reach every tab with a handler
4.8 KiB
title, description, platforms
| title | description | platforms | ||||
|---|---|---|---|---|---|---|
| puter.events.workers | List and destroy the events worker a published handler set stands up. |
|
An events worker runs an app's published handlers: every event for a subscription with a handlerName, whether or not a client is connected, and a queue delivery once no connected client takes it. Each app has at most one, created when it publishes its first handler, however many handlers it publishes.
A hosted Puter deployment may bill each app's events worker as a monthly cost, even if nothing ever delivers to it. Use this API to see which apps have one and remove the ones you don't need.
const { items } = await puter.events.workers.list(); // [{ appUid, appName, handlerCount, ... }]
await puter.events.workers.destroy(items[0].appUid); // removes every handler that app published
An account session or API token sees every app you own. An app sees only its own worker (0 or 1 item), and only if the signed-in user owns the app.
On a website with nobody signed in, each method asks the user to sign in first, as other Puter.js calls do. An app running on Puter is always signed in.
The worker starts on the first delivery that needs it, and again after it's been idle long enough to be shut down, so the first background delivery after a publish has a short cold start. Only the platform can invoke it.
list()
puter.events.workers.list()
puter.events.workers.list(options)
options.limit(Number): Apps per page.options.cursor(String): Thecursorfrom a previous page. Omit to start from the first page.
Resolves to { items, cursor, deployable }:
- Each item is
{ appUid, appName, appTitle, handlerCount, createdAt, updatedAt, script }.createdAtis when the app's first handler was published,updatedAtis its latest publish, andscriptnames the deployed script (useful when reporting an issue). cursoris present while more pages exist.deployableisfalsewhen this server doesn't run events workers (a self-hosted install without the runtime turned on). Handlers can still be published there, but background deliveries never run.
destroy()
puter.events.workers.destroy(appUid)
Removes every handler the app has published, the same as calling puter.events.handlers.remove() on each: their subscriptions are suspended with handler_not_found, not deleted, and publishing the handlers again resumes them.
It also ends the worker session background deliveries ran under, for every user. The next delivery after a republish starts a new one.
Resolves to { appUid, removed, suspended }: how many handlers were deleted and how many subscriptions were suspended. An app with nothing published rejects with events_handler_not_found.
Errors
Both methods reject with { message, code }:
code |
Meaning |
|---|---|
auth_canceled |
Nobody was signed in, and the user closed the sign-in without finishing it. |
events_worker_owner_only |
list() was called with a scoped access token, such as the one in a getReadURL() URL. |
events_handler_not_found |
destroy() named an app with no published handlers. |
events_handler_forbidden |
The caller doesn't own the app (or it doesn't exist), or is a scoped access token. |
too_many_requests |
Over the publish/remove or listing rate limit. |
events_disabled |
Events aren't enabled on this server. |
events_failed |
The server sent a response the SDK couldn't read. |
Example
List an account's events workers, and destroy one that is no longer needed
<html>
<body>
<script src="https://js.puter.com/v2/"></script>
<script>
(async () => {
const name = `retired-${puter.randName()}`;
const app = await puter.apps.create(name, `https://example.com/${name}`);
await puter.events.handlers.publish(
'ingestUpload',
async ({ event, ctx }) => {
await fetch(ctx.endpoint, { method: 'POST', body: event.path });
},
{ appUid: app.uid },
);
const { items, deployable } = await puter.events.workers.list();
puter.print(`this server deploys events workers: ${deployable}<br>`);
for (const worker of items) {
puter.print(`${worker.appName}: ${worker.handlerCount} handler(s)<br>`);
}
const destroyed = await puter.events.workers.destroy(app.uid);
puter.print(`removed ${destroyed.removed}, suspended ${destroyed.suspended}<br>`);
})();
</script>
</body>
</html>