Skip to content
NEW

Introducing SurrealDB Studio, the new official app of SurrealDB

Learn more

1/5

Course content preview

11: Milestones

Milestones group activities and expose roll-up progress. You already created database/schema/milestone.surql in part 10 and left it sitting there. Now we will look at what the file does, preview the change with surrealkit sync --dry-run, then apply it once with a rollout, the same path you would use on a shared database.

On a normal day you would just sync this locally. We are skipping that apply on purpose so you can practise plan → start → complete without putting the same definitions in twice.

Here is the file again for reference:

DEFINE TABLE milestone SCHEMAFULL;
DEFINE FIELD project ON milestone TYPE record<project>;
DEFINE FIELD activities ON milestone TYPE array<record<activity>>;
DEFINE FIELD name ON milestone TYPE string;
DEFINE FIELD last_updated ON milestone VALUE time::now();
DEFINE FIELD progress ON milestone COMPUTED math::mean(activities.progress);
DEFINE FIELD is_complete ON milestone COMPUTED activities.all(|$a| $a.progress > 0.95);


FieldMechanism
last_updatedVALUE time::now() on every write
progressCOMPUTED mean over linked activities
is_completeCOMPUTED threshold across the group


If the file is missing, create it with the statements above before continuing. Still do not sync it: dry-run first, then rollout.

Do not run a normal surrealkit sync yet. Ask what sync would do:

surrealkit sync --dry-run --user root --pass secret --ns main --db main


You should see milestone.surql (and maybe other files whose hashes have changed) listed as pending applies. The live catalog stays the same. If something looks wrong, fix the file and dry-run again.

One item to keep in mind is that dry-run output is fairly coarse. Applies are reported per file, not per field. Prunes only show up when SurrealKit has stale managed entities to remove. For a detailed expand/contract list, open the rollout manifest after you plan.

Important

After a clean dry-run, leave the catalog alone until rollout start. A real sync (or sync --watch) would apply milestone immediately, and the later rollout would have little or nothing left to expand.

From the SurrealKit project root (the folder that contains database/):

surrealkit rollout plan --name add_milestones


Generated rollout manifest ./database/rollouts/20260302153045__add_milestones.toml
Updated ./database/snapshots/catalog_snapshot.json


The manifest id is that filename without .toml:

database/rollouts/20260302153045__add_milestones.toml
→ manifest id: 20260302153045__add_milestones


Your timestamp will differ. Planning compares database/schema/ to snapshots under database/snapshots/ (part 14 covers rollout baseline when you adopt a brownfield database).

Open the manifest and the diff should be exactly one file, because part 10's probe left the snapshots matching everything else:

[[steps]]
id = "apply_expand_schema"
phase = "start"
kind = "apply_files"
files = ["database/schema/milestone.surql"]


If yours lists all five schema files, you skipped the probe in part 10; that is harmless here, since re-applying an unchanged definition is a no-op, but the rest of this chapter is easier to follow with a one-file plan.

If you edit any schema file after planning, the hash will no longer match and start will refuse. Re-plan in that case. Also make sure each field appears only once in a file: two DEFINE FIELD progress lines, for example, share one metadata key and can break start.

Optional check before start:

surrealkit rollout lint 20260302153045__add_milestones


Rollout 20260302153045__add_milestones is valid (checksum e6d2adfc13c4c86db8633974fbc7c378282d5f9a3c767240470ac1c135c84545).


On a team project, commit the schema diff and the new database/rollouts/*.toml file (plus updated snapshots if SurrealKit refreshed them). Reviewers should see the intended apply, not only the .surql changes. For this course exercise you can keep going without a git commit.

Use your manifest id from step 1:

surrealkit rollout start 20260302153045__add_milestones --user root --pass secret --ns main --db main


Rollout 20260302153045__add_milestones is ready to complete.


start applies the non-destructive steps: new tables, new fields, new indexes (part 12). Check that milestone shows up in INFO FOR DB, and peek at the fields with INFO FOR TABLE milestone.

surrealkit rollout status now shows the rollout sitting between its two phases:

__rollout:20260302153045__add_milestones [ready_to_complete] add_milestones
  started_at: 2026-03-02T15:31:12.694795Z
  - apply_expand_schema [start:apply_files] completed


In production you would deploy the app that uses milestones while the old and new code can still coexist, then run complete.

surrealkit rollout complete 20260302153045__add_milestones --user root --pass secret --ns main --db main


Completed rollout 20260302153045__add_milestones.


complete is where destructive steps live: REMOVE FIELD, REMOVE TABLE, dropped indexes, and so on. Adding milestone only adds things, so this manifest has no complete step at all and the command simply moves the status to completed. Running it anyway is a habit worth keeping, because the moment a change does remove something, that becomes the phase in which it lands.

If something goes wrong after start:

surrealkit rollout rollback 20260302153045__add_milestones --user root --pass secret --ns main --db main


Once a rollout is completed, that door is shut:

Error: rollout '20260302153045__add_milestones' is already completed


Note

Dry-run answered “what would sync do?” Rollout start answered “apply the expand.” On a shared host, CI holds the credentials for start / complete. Your laptop .env should keep pointing at disposable databases for everyday sync.

Run these statements now in SurrealDB Studio or surreal sql so your current database has the milestones. Also append them to database/seed/demo_project.surql so a later fresh database gets the same records on first seed. Do not re-run surrealkit seed here: appending to the file changes its hash, so seed would replay the whole thing, starting with CREATE project:one and the earlier activities, and fail with “already exists” (part 7).

CREATE milestone:start SET
    project = project:one,
    activities = [activity:kickoff],
    name = "Project start";

CREATE milestone:construction SET
    project = project:one,
    activities = [activity:concrete],
    name = "Initial construction";


Then inspect the roll-ups:

SELECT name, progress, is_complete FROM milestone;


Part 3’s gradual migration pattern (union types, backfill, tighten) is about data. Rollouts are about catalog changes:

Data (SurrealQL scripts)Catalog (SurrealKit)
UPDATE to backfillrollout start adds milestone
DEFINE EVENT normaliserDeploy app
ALTER FIELD tightenrollout complete removes deprecated defs


SurrealDB has no field-rename statement, and SurrealKit will not ask “did you mean rename?” when one DEFINE FIELD disappears and another appears. If you REMOVE FIELD description and DEFINE FIELD class in one go, existing values under description stay on the records as orphaned data (or vanish from a SCHEMAFULL view) unless you copy them first.

Treat a rename the same way as promoting a string field to record<table>: add the new field (expand), UPDATE to copy or map values (data script / seed), then remove the old field on rollout complete (contract). Do not fold “new name + drop old name” into a single sync on a shared database.

  • milestone with COMPUTED roll-ups

  • Previewed with sync --dry-run, applied once with rollout start (then complete)

  • Seed records by hand, and the same statements appended to demo_project.surql

  • A real manifest id from database/rollouts/ for start / complete / rollback

Previous

10: Sync vs rollouts

Next lesson

12: People and indexes

SurrealDB

The context layer for AI agents.

Documents, graphs, vectors, time-series, and memory.
One transaction, one query, one deployment.

Explore with AI

Stay in the loop

Tutorials, AI agent recipes, and product updates, every two weeks.

Independently verified

SOC 2 Type 2

GDPR

Cyber Essentials Plus

ISO 27001

Trust Centre

Copyright © 2026 SurrealDB Ltd. Registered in England and Wales. Company no. 13615201

Registered address: 3rd Floor 1 Ashley Road, Altrincham, Cheshire, WA14 2DT, United Kingdom

Trading address: Huckletree Oxford Circus, 213 Oxford Street, London, W1D 2LG, United Kingdom