Skip to main content
Version: 3.1

Polling Reaktors

A Polling Reaktor runs on a schedule in the background — processing queues, syncing data, or performing periodic maintenance. Unlike regular Reaktors that execute in response to a user action, Polling Reaktors execute on a timer and are controlled through REST endpoints on each App Instance.

Registering a Polling Reaktor

import { registerPollingReaktor, createAktorComposition, aktorIfElse, aktorConst } from '@operaide/aktor';

const aktorQueueProcessor = createAktorComposition('aktorQueueProcessor', () => {
return aktorIfElse({
if_: aktorHasPendingItems({}),
then_: aktorProcessNextItem({}), // Returns true if more items remain
else_: aktorConst(false), // No work — wait for next interval
});
});

registerPollingReaktor({
reaktorDefinitionId: 'queue-processor',
interval: '2m',
autoStart: false,
label: 'Queue Processor',
description: 'Processes pending items from the work queue',
aktor: () => aktorQueueProcessor({}),
});

The aktor prop is a factory that returns a fresh Aktor graph for each tick. Unlike registerReaktorDefinition — where aktor is the composition itself and the framework wires its inputs — a Polling Reaktor has no caller to supply inputs, so you invoke the composition yourself and return the resulting Aktor.

Configuration

OptionTypeDefaultDescription
reaktorDefinitionIdstringrequiredUnique identifier for this Polling Reaktor
intervalstringrequiredTime between executions (see syntax below)
autoStartbooleanfalseInitial run state when an App Instance first creates the job
aktor() => Aktor<boolean>requiredFactory that returns the Aktor implementing the polling logic
labelstringDisplay name in the UI
descriptionstringDescription of what the Reaktor does

Interval Syntax

The interval value is a string with a number and a unit:

ExampleMeaning
'30ms'30 milliseconds
'5s'5 seconds
'2m'2 minutes
'1h'1 hour
'1d'1 day

The Boolean Return Contract

The Aktor returned by the aktor factory must resolve to a boolean. The return value controls what happens after each execution:

ReturnEffect
trueMore work exists — execute again immediately
falseNo work — wait for the configured interval before running again

This lets a Polling Reaktor drain a queue as fast as possible when there is work, then back off to the configured interval when idle.

A short minimum cycle time of about 250ms floors immediate refire. A Reaktor that returns true runs again as soon as it returns, but consecutive runs stay at least that far apart. Real per-run work is unaffected; the floor only bounds a Reaktor that returns true without doing work.

const aktorProcessQueue = createAktorComposition('aktorProcessQueue', () => {
const result = aktorProcessBatch({});
// result: Aktor<boolean>
// true → more items in queue, run again now
// false → queue empty, wait 2 minutes
return result;
});

One Job per App Instance

Each App Instance that installs the app gets its own polling job, identified by the App Instance and the reaktor name. Two App Instances of the same app poll independently, each with its own start/stop state and interval.

  • autoStart sets the initial state when the job is first created. Later changes to the default do not affect jobs that already exist.
  • interval is the starting value. An operator can override it per App Instance through the control API.
  • The platform runs the reaktor under the identity of whoever started it, or the App Instance owner for an autoStart job, so settings and permissions resolve as they would for a normal call.
  • A reaktor only polls when it is installed as part of an App Instance. A standalone deployed reaktor does not poll.

REST API

Control a Polling Reaktor through the App Instance REST API. The endpoints are scoped to the organization and App Instance, so each App Instance controls its own polling.

MethodEndpointDescription
POST/api/v2/orgs/{orgId}/apps/{instanceName}/polling/{reaktorName}/startStart polling
POST/api/v2/orgs/{orgId}/apps/{instanceName}/polling/{reaktorName}/stopStop polling
GET/api/v2/orgs/{orgId}/apps/{instanceName}/polling/{reaktorName}/statusGet current status

reaktorName is the short name from reaktorDefinitionId. Authenticate with your API key in the X-API-KEY header, the same as other App Instance calls. These endpoints let you control polling from external systems, CI/CD pipelines, or monitoring dashboards.

Full Example: Batch Email Processor

import {
registerPollingReaktor,
aktorConst,
createAktorComposition,
createAktorFunction,
aktorIfElse,
} from '@operaide/aktor';
import { aktorAppDatabase } from '@operaide/database';

const aktorHasPendingEmails = createAktorFunction('aktorHasPendingEmails',
async ({ client }: { client: any }) => {
const result = await client.execute('SELECT COUNT(*) as count FROM emails WHERE status = ?', ['pending']);
return result.rows[0].count > 0;
}
);

const aktorProcessNextEmail = createAktorFunction('aktorProcessNextEmail',
async ({ client }: { client: any }) => {
// Process one email and return whether more exist
await client.execute("UPDATE emails SET status = 'processing' WHERE id = (SELECT id FROM emails WHERE status = 'pending' LIMIT 1)");
// ... send the email ...
const remaining = await client.execute("SELECT COUNT(*) as count FROM emails WHERE status = 'pending'");
return remaining.rows[0].count > 0; // true = more work
}
);

const aktorEmailSender = createAktorComposition('aktorEmailSender', () => {
const db = aktorAppDatabase({});

return aktorIfElse({
if_: aktorHasPendingEmails({ client: db }),
then_: aktorProcessNextEmail({ client: db }),
else_: aktorConst(false),
});
});

registerPollingReaktor({
reaktorDefinitionId: 'email-sender',
interval: '30s',
autoStart: true,
label: 'Email Sender',
description: 'Sends pending emails from the queue',
aktor: () => aktorEmailSender({}),
});