Skip to main content
Version: 3.0

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 via auto-generated REST endpoints.

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)
autoStartbooleanfalseStart automatically when the server starts
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.

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;
});

REST API

Every Polling Reaktor automatically gets three REST endpoints:

MethodEndpointDescription
POST/api/v1/polling/{reaktorId}/startStart polling
POST/api/v1/polling/{reaktorId}/stopStop polling
GET/api/v1/polling/{reaktorId}/statusGet current status

These endpoints let you control Polling Reaktors from external systems, CI/CD pipelines, or monitoring dashboards.

Full Example: Batch Email Processor

import {
registerPollingReaktor,
aktorConst,
aktorSetting,
createAktorComposition,
createAktorFunction,
aktorIfElse,
} from '@operaide/aktor';
import { aktorDatabase, aktorQuery } from '@operaide/database';
import { z } from 'zod';

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 databaseId = aktorSetting(z.string(), '<database-id>', 'Database ID');
const db = aktorDatabase({ id: databaseId });

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({}),
});