Indexes, events & functions

Beyond fields, a SurrealDB schema can carry indexes, events, and functions. Schemic defines all three from TypeScript so they live in the same source of truth and migrate alongside your tables.

Indexes

An index speeds up lookups and can enforce uniqueness. Add one to a single field with .index() or .unique():

schema.ts TypeScript
export const User = defineTable("user", {
  id: s.string(),
  email: s.email().unique(),
  handle: s.string().index(),
});

produces:

Generated DDL SurrealQL
DEFINE TABLE user TYPE NORMAL SCHEMAFULL;
DEFINE FIELD email ON TABLE user TYPE string ASSERT string::is_email($value);
DEFINE INDEX user_email_idx ON TABLE user FIELDS email UNIQUE;
DEFINE FIELD handle ON TABLE user TYPE string;
DEFINE INDEX user_handle_idx ON TABLE user FIELDS handle;

For a composite index across several fields, declare it at the table level with .index(name, fields, options):

TypeScript
defineTable("event", {
  id: s.string(),
  calendarId: Calendar.record(),
  startsAt: s.datetime(),
})
  .index("event_calendar_start", ["calendarId", "startsAt"], { unique: true });

.index(name, fields, opts) takes the index name, an array of field names, and options (unique, count). It emits DEFINE INDEX <name> ON TABLE <table> FIELDS <fields>.

Events

An event runs SurrealQL when a row changes. Define one with .event(name, { when?, then }):

TypeScript
defineTable("account", {
  id: s.string(),
  balance: s.decimal(),
})
  .event("log_overdraft", {
    when: surql`$after.balance < 0`,
    then: surql`CREATE overdraft SET account = $after.id, at = time::now()`,
  });

This emits DEFINE EVENT log_overdraft ON TABLE account WHEN $after.balance < 0 THEN .... The body can read $before, $after, $event, and $value. Omit when to fire on every change, and pass an array to then to run several statements.

Functions

defineFunction(name, args) declares a reusable SurrealQL function. Args and the return type are s schemas, so they infer to SurrealQL types the same way fields do. Chain .returns(...) and .body(...):

schema.ts TypeScript
import { s, defineFunction } from "@schemic/core";
import { surql } from "surrealdb";

export const greet = defineFunction("greet", { name: s.string() })
  .returns(s.string())
  .body(surql`RETURN "Hello, " + $name;`);

produces:

Generated DDL SurrealQL
DEFINE FUNCTION fn::greet($name: string) -> string {
    RETURN "Hello, " + $name;
};

Call it from SurrealQL as fn::greet("Ada"). Add .permissions(...) to set who may run it and .comment(...) to document it.

Verify it

Indexes, events, and functions show up in the generated migration. Preview them before writing:

Shell
npx schemic diff --live

Where to go next