Define your schema
Your .surql files are the single source of truth. Everything else (client types, sync, permissions) is derived from them.
You write plain SurrealQL in .surql files. Sp00ky reads them and derives your client types, your sync
rules, and your row-level permissions from the same definitions.
That’s the whole contract. Run spky generate and the matching
User type appears in your app, with username as a string and created_at as a Date.
Fields carry the rules
Put constraints in the schema, not in your app code. ASSERT rejects bad writes at the database, and
PERMISSIONS are enforced for every client, including the ones you didn’t write.
PERMISSIONS also shape what live queries return: a client subscribed to user only ever receives
rows the FOR select clause admits. See Authentication for how $auth is
populated and which permission expressions the sync engine can evaluate.
Annotations
Sp00ky reads special comment descriptors in your .surql files. They look like ordinary
SurrealQL comments (-- @name), so SurrealDB ignores them, but the CLI uses them to change how a
table or field is generated and synced.
| Descriptor | Placement | Effect |
|---|---|---|
-- @nosync | above a DEFINE TABLE | Server-only table. Excluded from sync entirely (see below). |
-- @crdt text | above a DEFINE FIELD | Marks a collaborative text field backed by a Loro CRDT. See CRDT fields. |
-- @cursor | above a DEFINE FIELD (with @crdt) | Stores per-session cursors alongside the CRDT snapshot. |
-- @parent | suffix on a DEFINE FIELD ... TYPE record<...> | Marks the parent side of a relationship; written automatically from the auth context, never by client code. |
Keeping a table off the client (@nosync)
Put -- @nosync on the line above a DEFINE TABLE to make it server-only, useful for audit
logs, bookkeeping, or anything that should live in your database but never reach a browser.
A @nosync table is:
- Omitted from generated types (TypeScript / Dart / JSON Schema).
- Omitted from relations: any
record<...>field on another table pointing at it is dropped from the generated relationships. - Never synced: no sync events are emitted, so nothing flows to the scheduler or SSP.
- Excluded from the scheduler snapshot and from SSP bootstrap.
- Still stored in the main database, and still included in backups.
@nosync is distinct from PERMISSIONS FOR select WHERE false. A permission-locked table is
still synced (it shows up in generated types and the snapshot, it just can’t be read); @nosync
removes the table from sync altogether. Reach for @nosync when a table should never leave the
server.