surformSurrealDB schemas, declared

Events

An event is a trigger: when a record in its table changes, its SurrealQL runs. when: filters which changes fire; then: is the action (or a comma list of actions).

The shape

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

/// Log every user creation.
event user_created {
  on: user;
  when: $event = 'CREATE';
  then: { create log set at = time::now(), user = $after.id; };
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE EVENT OVERWRITE user_created ON `user`
    WHEN $event = 'CREATE'
    THEN { CREATE log SET at = time::now(), user = $after.id }
    COMMENT 'Log every user creation.';

on: names the table (standalone form — nested inside the table block, the line disappears). The body sees SurrealDB's event variables:

variablevalue
$event'CREATE', 'UPDATE', or 'DELETE'
$beforethe record before the change (nothing on create)
$afterthe record after the change (nothing on delete)
$valuethe record itself — pre-change on delete, post otherwise

Then-only: react to everything

Omitting when: means every change — the database records that as the literal WHEN true, and normalize folds it back to omission:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table post {
  schemafull;
  field title: string;
};

event touch_counter {
  on: post;
  then: (update counter:posts set total = total + 1);
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE `post` TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE title ON `post` TYPE string;
DEFINE EVENT OVERWRITE touch_counter ON `post` THEN (UPDATE counter:posts SET total = total + 1);

Async: run outside the transaction

async; detaches the action from the triggering write. The flag carries defaults (retry once, depth 3); the block form tunes them, and the block collapses back to the bare flag when the values equal the defaults:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table invoice {
  schemafull;
  field total: number;
};

event webhook_fanout {
  on: invoice;
  async {
    retry: 5;
    maxdepth: 8;
  };
  then: {
    if rand::bool() { throw 'flaky downstream'; }
  };
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE invoice TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE total ON invoice TYPE number;
DEFINE EVENT OVERWRITE webhook_fanout ON invoice
    ASYNC RETRY 5 MAXDEPTH 8
    THEN { IF `rand`::bool() { THROW 'flaky downstream' } };

Multi-step reactions

then: takes a comma list — several actions, one trigger:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table payment {
  schemafull;
  field status: string;
};

event double_bookkeeping {
  on: payment;
  when: $event = 'UPDATE' and $before.status != $after.status;
  then: (create ledger_entry set payment = $after.id),
        (update stats:payments set changes = changes + 1);
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE payment TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE status ON payment TYPE string;
DEFINE EVENT OVERWRITE double_bookkeeping ON payment
    WHEN $event = 'UPDATE' AND $before.status != $after.status
    THEN (CREATE ledger_entry SET payment = $after.id),
    (UPDATE stats:payments SET changes = changes + 1);

Nested form

Inside its table, an event drops the on: line — the usual nesting rule:

applied to SurrealDB 3.3.0
schema.surdl
sdl
namespace acme;
database shop;

table audit_example {
  schemafull;
  field title: string;

  event audit_title {
    when: $event = 'UPDATE' and $before.title != $after.title;
    then: { create audit set at = time::now(), id = $after.id; };
  };
};
surform build
sql
DEFINE NAMESPACE OVERWRITE acme;
USE NS acme;
DEFINE DATABASE OVERWRITE shop;
USE NS acme DB shop;
DEFINE TABLE OVERWRITE audit_example TYPE NORMAL SCHEMAFULL;
DEFINE FIELD OVERWRITE title ON audit_example TYPE string;
DEFINE EVENT OVERWRITE audit_title ON audit_example
    WHEN $event = 'UPDATE' AND $before.title != $after.title
    THEN { CREATE audit SET at = time::now(), id = $after.id };

Where next

  • Tables — where events usually live.
  • Functions — call your own functions from then:.