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
| Option | Type | Default | Description |
|---|---|---|---|
reaktorDefinitionId | string | required | Unique identifier for this Polling Reaktor |
interval | string | required | Time between executions (see syntax below) |
autoStart | boolean | false | Start automatically when the server starts |
aktor | () => Aktor<boolean> | required | Factory that returns the Aktor implementing the polling logic |
label | string | — | Display name in the UI |
description | string | — | Description of what the Reaktor does |
Interval Syntax
The interval value is a string with a number and a unit:
| Example | Meaning |
|---|---|
'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:
| Return | Effect |
|---|---|
true | More work exists — execute again immediately |
false | No 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:
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/polling/{reaktorId}/start | Start polling |
POST | /api/v1/polling/{reaktorId}/stop | Stop polling |
GET | /api/v1/polling/{reaktorId}/status | Get 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({}),
});