Skip to content

Events and Changes

SysopKit uses a type-safe event system with branded symbols to prevent accidental collisions.

type Event<T> = symbol & {
readonly __value?: T;
readonly __type?: 'sysopkit.event';
};

Use emit() to dispatch events from any context:

import { emit, Event, task } from 'sysopkit';
const MY_EVENT: Event<string> = Symbol('my.event');
await task('demo', async () => {
emit(MY_EVENT, 'hello');
});

Events propagate up the parent context chain, calling registered handlers at each level.

Register handlers with ctx.on():

import { task } from 'sysopkit';
await task('demo', async (ctx) => {
ctx.on(MY_EVENT, (data) => {
console.log('received:', data);
});
await task('subtask', async () => {
emit(MY_EVENT, 'hello from subtask');
});
});

The CHANGE_EVENT carries ChangeEntry objects describing infrastructure modifications:

interface ChangeEntry {
type: string; // e.g., "file", "user", "package"
resource: string; // e.g., "/etc/nginx/nginx.conf"
property?: string; // e.g., "mode", "content"
from?: string; // previous value (omitted on create)
to?: string; // new value (omitted on delete)
}

A convenience function for operations to report changes:

import { emitChanged } from 'sysopkit';
emitChanged({
type: 'file',
resource: '/etc/foo.conf',
property: 'content',
from: 'old',
to: 'new',
});

Use onChange() to register a scoped change listener for the duration of a function:

import { onChange, latch } from 'sysopkit';
const restart = latch();
await onChange(restart, async () => {
await createFile({ path: '/etc/foo.conf', content: 'bar' });
});
if (restart()) {
// restart service since configuration changed
}

The latch() utility returns a function that flips to true when a change is detected.