surrealkit 0.5.8

Manage migrations, seeding and tests for your SurrealDB via CLI
use std::fs;
use std::path::Path;

use anyhow::{Context, Result};

pub fn scaffold() -> Result<()> {
	let database_dir = Path::new("database");
	let schema_dir = database_dir.join("schema");
	let rollouts_dir = database_dir.join("rollouts");
	let state_dir = database_dir.join("snapshots");
	let tests_dir = database_dir.join("tests");
	let test_suites_dir = tests_dir.join("suites");
	let test_fixtures_dir = tests_dir.join("fixtures");

	fs::create_dir_all(&schema_dir).context("creating database/schema")?;
	fs::create_dir_all(&rollouts_dir).context("creating database/rollouts")?;
	fs::create_dir_all(&state_dir).context("creating database/snapshots")?;
	fs::create_dir_all(&tests_dir).context("creating database/tests")?;
	fs::create_dir_all(&test_suites_dir).context("creating database/tests/suites")?;
	fs::create_dir_all(&test_fixtures_dir).context("creating database/tests/fixtures")?;

	let seed_dir = database_dir.join("seed");
	fs::create_dir_all(&seed_dir).context("creating database/seed")?;
	let seed_path = seed_dir.join("seed.surql");
	if !seed_path.exists() {
		fs::write(&seed_path, "--- SEED\n").context("Writing database/seed/seed.surql")?;
	}

	// setup.surql defines SurrealKit metadata tables.
	let setup_path = database_dir.join("setup.surql");
	if !setup_path.exists() {
		fs::write(&setup_path, DEFAULT_SETUP).context("Writing setup.surql")?;
	}

	let test_config_path = tests_dir.join("config.toml");
	if !test_config_path.exists() {
		fs::write(&test_config_path, DEFAULT_TEST_CONFIG)
			.context("Writing database/tests/config.toml")?;
	}

	let test_suite_path = test_suites_dir.join("smoke.toml");
	if !test_suite_path.exists() {
		fs::write(&test_suite_path, DEFAULT_TEST_SUITE)
			.context("Writing database/tests/suites/smoke.toml")?;
	}

	println!("Scaffolded project in ./database\n");
	println!("  database/");
	println!("  ├── schema/");
	println!("  ├── rollouts/");
	println!("  ├── snapshots/");
	println!("  ├── tests/");
	println!("  │   ├── suites/");
	println!("  │   └── fixtures/");
	println!("  ├── seed/");
	println!("  │   └── seed.surql");
	println!("  └── setup.surql");
	Ok(())
}

pub const DEFAULT_SETUP: &str = r#"DEFINE TABLE OVERWRITE __entity SCHEMAFULL
	PERMISSIONS NONE;

DEFINE FIELD OVERWRITE ns ON __entity
	TYPE string;

DEFINE FIELD OVERWRITE key ON __entity
	TYPE string;

DEFINE FIELD OVERWRITE val ON __entity
	TYPE any;

DEFINE FIELD OVERWRITE updated_at ON __entity
	TYPE datetime
	DEFAULT time::now();

DEFINE INDEX OVERWRITE by_ns_key ON __entity
	FIELDS ns, key
	UNIQUE;

DEFINE TABLE OVERWRITE __rollout SCHEMAFULL
	PERMISSIONS NONE;

DEFINE FIELD OVERWRITE id ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE name ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE manifest_path ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE manifest_checksum ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE source_schema_hash ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE target_schema_hash ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE status ON __rollout
	TYPE string;

DEFINE FIELD OVERWRITE source_entities ON __rollout
	TYPE any;

DEFINE FIELD OVERWRITE target_entities ON __rollout
	TYPE any;

DEFINE FIELD OVERWRITE steps ON __rollout
	TYPE any
	DEFAULT [];

DEFINE FIELD OVERWRITE started_at ON __rollout
	TYPE datetime
	DEFAULT time::now();

DEFINE FIELD OVERWRITE completed_at ON __rollout
	TYPE option<datetime>;

DEFINE FIELD OVERWRITE updated_at ON __rollout
	TYPE datetime
	DEFAULT time::now();

DEFINE FIELD OVERWRITE last_error ON __rollout
	TYPE option<string>;

DEFINE INDEX OVERWRITE by_rollout_id ON __rollout
	FIELDS id
	UNIQUE;
"#;

pub const DEFAULT_TEST_CONFIG: &str = r#"[defaults]
timeout_ms = 10000

[actors.root]
kind = "root"
"#;

pub const DEFAULT_TEST_SUITE: &str = r#"name = "smoke"
tags = ["smoke"]

[[cases]]
name = "rollout_table_visible"
kind = "schema_metadata"
sql = "INFO FOR TABLE __rollout;"
contains = ["__rollout"]
"#;