-
Notifications
You must be signed in to change notification settings - Fork 2
76 Event Handler Guide
Events are the mechanism by which the Platform executes custom business logic when workflow actions occur. Every time a method is executed on an object, the engine finds all events registered for that action on the object's class (and parent classes) and executes them in sequence.
When ExecuteMethod(pObject, pMethod) is called:
- The engine looks up the action for the method
- It finds all events registered for the object's class + that action
- Events are executed in sequence order
-
parentevent types cause the engine to look up events on parent classes -
eventevent types execute a PL/pgSQL function call
| Code | Purpose |
|---|---|
parent |
Inherit and execute events from the parent class |
event |
Execute a PL/pgSQL function |
plpgsql |
Execute inline PL/pgSQL code (rarely used) |
Events cascade from child to parent (or parent to child, depending on registration order). The order you register events in AddEvent determines execution order:
For most actions (create, open, edit, save, enable, disable, restore):
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events'); -- parent events FIRST
PERFORM AddEvent(pClass, uEvent, r.id, 'Entity created', 'EventSensorCreate();'); -- entity event SECONDParent class events fire first, then the entity's own event. This means the parent (e.g., Document) initializes its data before the child (e.g., Sensor) does its work.
For destructive actions (delete, drop):
PERFORM AddEvent(pClass, uEvent, r.id, 'Entity will be deleted', 'EventSensorDelete();'); -- entity event FIRST
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events'); -- parent events SECONDThe entity's own event fires first (to clean up specialized data), then the parent events fire (to clean up parent data). This prevents FK violations.
Event handler functions follow the naming pattern:
Event{EntityName}{ActionName}
Examples:
-
EventClientCreate-- fires when a Client is created -
EventStationEnable-- fires when a Station is enabled -
EventRegionDrop-- fires when a Region is permanently destroyed
Every event handler has the same basic signature:
CREATE OR REPLACE FUNCTION EventSensorCreate (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
-- handler logic here
END;
$$ LANGUAGE plpgsql;Key points:
-
pObjectdefaults tocontext_object()-- the object the method is being executed on - Returns
void-- events are side-effects - No
SECURITY DEFINERorSET search_path(executes in the caller's context)
For events that need the method parameters (e.g., old/new values on edit):
CREATE OR REPLACE FUNCTION EventClientEdit (
pObject uuid default context_object(),
pParams jsonb default context_params()
) RETURNS void
AS $$
DECLARE
old_email text;
new_email text;
BEGIN
old_email = pParams#>'{old, email}';
new_email = pParams#>'{new, email}';
IF old_email <> new_email THEN
PERFORM EventMessageConfirmEmail(pObject);
END IF;
PERFORM WriteToEventLog('M', 1000, 'edit', 'Client updated.', pObject);
END;
$$ LANGUAGE plpgsql;The pParams parameter receives the JSON passed to ExecuteMethod. For edit actions, this typically contains {old: {...}, new: {...}} with the before/after state.
Every entity should implement these 9 event handlers:
Fires after the object is inserted into the database. Use for:
- Logging
- Sending notifications
- Creating dependent objects
CREATE OR REPLACE FUNCTION EventSensorCreate (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'create', 'Sensor created.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when the object is accessed/viewed.
CREATE OR REPLACE FUNCTION EventSensorOpen (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'open', 'Sensor opened.', pObject);
END;
$$ LANGUAGE plpgsql;Fires after the object is updated. Can receive old/new values via pParams.
CREATE OR REPLACE FUNCTION EventSensorEdit (
pObject uuid default context_object(),
pParams jsonb default context_params()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'edit', 'Sensor updated.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when the object is explicitly saved (distinct from edit in some workflows).
CREATE OR REPLACE FUNCTION EventSensorSave (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'save', 'Sensor saved.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when the object transitions to an enabled state. Common uses:
- Unlocking user accounts
- Activating dependent resources
- Sending welcome notifications
CREATE OR REPLACE FUNCTION EventSensorEnable (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'enable', 'Sensor enabled.', pObject);
END;
$$ LANGUAGE plpgsql;Real-world example from Client:
CREATE OR REPLACE FUNCTION EventClientEnable (
pObject uuid default context_object()
) RETURNS void
AS $$
DECLARE
uUserId uuid;
uArea uuid;
uInterface uuid;
BEGIN
SELECT userid INTO uUserId FROM db.client WHERE id = pObject;
IF uUserId IS NOT NULL THEN
PERFORM UserUnLock(uUserId);
PERFORM DeleteGroupForMember(uUserId, GetGroup('guest'));
PERFORM AddMemberToGroup(uUserId, GetGroup('user'));
SELECT area INTO uArea FROM db.document WHERE id = pObject;
PERFORM AddMemberToArea(uUserId, uArea);
PERFORM SetDefaultArea(uArea, uUserId);
uInterface := GetInterface('user');
PERFORM AddMemberToInterface(uUserId, uInterface);
PERFORM SetDefaultInterface(uInterface, uUserId);
END IF;
PERFORM WriteToEventLog('M', 1000, 'enable', 'Client enabled.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when the object transitions to a disabled state. Common uses:
- Locking user accounts
- Suspending dependent resources
CREATE OR REPLACE FUNCTION EventSensorDisable (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'disable', 'Sensor disabled.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when the object is soft-deleted. The object still exists but is marked as deleted. Use for:
- Cleaning up active resources
- Anonymizing personal data
- Cascading soft-delete to children
CREATE OR REPLACE FUNCTION EventSensorDelete (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'delete', 'Sensor deleted.', pObject);
END;
$$ LANGUAGE plpgsql;Fires when a deleted object is restored back to the created state.
CREATE OR REPLACE FUNCTION EventSensorRestore (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'restore', 'Sensor restored.', pObject);
END;
$$ LANGUAGE plpgsql;Fires immediately before the object is permanently removed from the database. This is the most critical event handler because it must:
- Delete all child/dependent objects first (to avoid FK violations)
- Delete the specialized row from the entity table
- Log the destruction
CREATE OR REPLACE FUNCTION EventSensorDrop (
pObject uuid default context_object()
) RETURNS void
AS $$
DECLARE
r record;
BEGIN
-- 1. Get the label for logging before deletion
SELECT label INTO r FROM db.object_text WHERE object = pObject AND locale = current_locale();
-- 2. Delete the specialized row
DELETE FROM db.sensor WHERE id = pObject;
-- 3. Log the destruction (note: higher severity 2000 and 'W' category)
PERFORM WriteToEventLog('W', 2000, 'drop', '[' || pObject || '] [' || coalesce(r.label, '') || '] Sensor destroyed.');
END;
$$ LANGUAGE plpgsql;For entities with child objects, cascade the drop:
CREATE OR REPLACE FUNCTION EventClientDrop (
pObject uuid default context_object()
) RETURNS void
AS $$
DECLARE
r record;
BEGIN
-- Drop child accounts
FOR r IN SELECT id FROM db.account WHERE client = pObject
LOOP
IF IsActive(r.id) THEN
PERFORM DoDisable(r.id);
END IF;
IF IsDisabled(r.id) THEN
PERFORM DoDelete(r.id);
END IF;
PERFORM DoDrop(r.id);
END LOOP;
-- Drop child identities
FOR r IN SELECT id FROM db.identity WHERE client = pObject
LOOP
IF IsActive(r.id) THEN
PERFORM DoDisable(r.id);
END IF;
IF IsDisabled(r.id) THEN
PERFORM DoDelete(r.id);
END IF;
PERFORM DoDrop(r.id);
END LOOP;
-- Clean up links and files
DELETE FROM db.object_link WHERE linked = pObject;
DELETE FROM db.object_file WHERE object = pObject;
-- Delete specialized row
DELETE FROM db.client_name WHERE client = pObject;
DELETE FROM db.client WHERE id = pObject;
SELECT label INTO r FROM db.object_text WHERE object = pObject AND locale = current_locale();
PERFORM WriteToEventLog('W', 1000, 'drop', '[' || pObject || '] [' || coalesce(r.label, '') || '] Client destroyed.');
END;
$$ LANGUAGE plpgsql;Key pattern: to properly drop a child object, you must transition it through its lifecycle states first (DoDisable -> DoDelete -> DoDrop), because each state transition may have its own cleanup logic.
For entities with custom workflow actions, add corresponding event handlers:
-- Custom: Station heartbeat
CREATE OR REPLACE FUNCTION EventStationHeartbeat (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'heartbeat', 'Station heartbeat.', pObject);
END;
$$ LANGUAGE plpgsql;
-- Custom: Station becomes available
CREATE OR REPLACE FUNCTION EventStationAvailable (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'available', 'Station is available.', pObject);
END;
$$ LANGUAGE plpgsql;
-- Custom: Station becomes unavailable (with side-effects)
CREATE OR REPLACE FUNCTION EventStationUnavailable (
pObject uuid default context_object()
) RETURNS void
AS $$
BEGIN
PERFORM WriteToEventLog('M', 1000, 'unavailable', 'Station is unavailable.', pObject);
PERFORM StopStationTransactions(pObject); -- stop active data transactions
END;
$$ LANGUAGE plpgsql;Events are registered in the Add<Entity>Events function. The complete pattern:
CREATE OR REPLACE FUNCTION AddSensorEvents (
pClass uuid
)
RETURNS void
AS $$
DECLARE
r record;
uParent uuid;
uEvent uuid;
BEGIN
uParent := GetEventType('parent');
uEvent := GetEventType('event');
FOR r IN SELECT * FROM Action
LOOP
IF r.code = 'create' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor created', 'EventSensorCreate();');
END IF;
IF r.code = 'open' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor opened', 'EventSensorOpen();');
END IF;
IF r.code = 'edit' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor updated', 'EventSensorEdit();');
END IF;
IF r.code = 'save' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor saved', 'EventSensorSave();');
END IF;
IF r.code = 'enable' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor enabled', 'EventSensorEnable();');
END IF;
IF r.code = 'disable' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor disabled', 'EventSensorDisable();');
END IF;
IF r.code = 'delete' THEN
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor will be deleted', 'EventSensorDelete();');
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
END IF;
IF r.code = 'restore' THEN
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor restored', 'EventSensorRestore();');
END IF;
IF r.code = 'drop' THEN
PERFORM AddEvent(pClass, uEvent, r.id, 'Sensor will be destroyed', 'EventSensorDrop();');
PERFORM AddEvent(pClass, uParent, r.id, 'Parent class events');
END IF;
END LOOP;
END
$$ LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = kernel, pg_temp;PERFORM WriteToEventLog(pCategory, pPriority, pAction, pMessage, pObject);| Parameter | Type | Description |
|---|---|---|
pCategory |
char |
'M' = message, 'W' = warning, 'E' = error |
pPriority |
integer |
Severity: 1000 = normal, 2000 = elevated |
pAction |
text |
Action code (matches the action that triggered the event) |
pMessage |
text |
Human-readable log message |
pObject |
uuid |
The object UUID (optional, defaults to NULL) |
Events can trigger notifications (push, email, message):
-- Send push notification
PERFORM SendPush(pObject, 'Title', 'Body', uUserId);
-- Send email confirmation
PERFORM EventMessageConfirmEmail(pObject);| Action | Entity Event | Parent Event | Reason |
|---|---|---|---|
| create | second | first | Parent initializes base data first |
| open | second | first | Parent logging first |
| edit | second | first | Parent updates first |
| save | second | first | Parent saves first |
| enable | second | first | Parent activates first |
| disable | second | first | Parent deactivates first |
| restore | second | first | Parent restores first |
| delete | first | second | Entity cleans up before parent |
| drop | first | second | Entity deletes specialized row before parent cascade |
Concepts
API Guide
Authentication & Session
- Connection
- Registration
- Authorization (OAuth 2.0)
- Sign In
- Sign Out
- Password Recovery
- Verification Codes
- Authentication
- Authorization
- Who Am I?
Core Services
Object & Workflow Endpoints
Schema & Internals
Configuration Developer Guide
- Configuration Guide
- Creating an Entity
- Creating a Document
- Creating a Reference
- Workflow Customization
- REST Endpoint Guide
- Event Handler Guide
Operations