feat: publish Dever core with project scaffolding and versioned AI skills

This commit is contained in:
Antigravity committed 2026-10-04 13:37:30 +00:00
1 parent 70fa73cdc2
commit ac458ffa7f
960 files changed
+221319 -1163

No files matched your search

+570
View File
@@ -0,0 +1,570 @@
#[path = "support/orm_sqlite.rs"]
mod orm_sqlite;
mod support;
use dever_runtime::orm::{ErrorKind, Value};
use dever_runtime::sqlite::Database;
use orm_sqlite::{
TestField, bool_default, bytes_field, created_at, database_state, id, index, migration, model,
text, text_field,
};
fn text_model(
revision: &str,
text_maximum: usize,
bytes_maximum: usize,
) -> dever_runtime::database::Model {
model(
revision,
"seed-empty",
vec![
id(),
text_field("name", text_maximum, false),
bytes_field("payload", bytes_maximum, false),
created_at(),
],
vec![],
vec![],
vec![],
)
}
#[tokio::test]
async fn new_database_uses_only_the_final_schema_and_records_real_catalog() {
let root = support::temp::TemporaryDirectory::new();
let database = Database::open(root.path().join("new.db"), 1, 50).unwrap();
let final_model = model(
"1000000000000001",
"seed-empty",
vec![
id(),
text_field("display_name", 128, false).renamed_from("nickname"),
bytes_field("payload", 16, true),
bool_default("enabled"),
created_at(),
],
vec![index(
"_dever_idx_user_display_name",
&["display_name"],
true,
)],
vec![],
vec![],
);
database.migrate(final_model).await.unwrap();
let state = database_state(&database).await;
let columns = state
.columns
.iter()
.map(|row| text(row, 0))
.collect::<Vec<_>>();
assert_eq!(
columns,
["id", "display_name", "payload", "enabled", "created_at"]
);
assert!(!columns.contains(&"nickname"));
assert_eq!(state.history.len(), 3);
assert!(
state
.history
.iter()
.any(|row| { text(row, 0) == "schema" && text(row, 2) == "1000000000000001" })
);
assert!(state.catalog.iter().any(|row| {
text(row, 0) == "table"
&& text(row, 2).contains("length(\"display_name\") <= 128")
&& text(row, 2).contains("DEFAULT 1")
}));
assert!(
database
.query(
"SELECT name FROM sqlite_schema WHERE type = 'table' AND name LIKE '_dever_user_%'",
vec![],
)
.await
.unwrap()
.is_empty()
);
}
#[tokio::test]
async fn rename_widen_and_add_fields_preserve_existing_data_and_are_idempotent() {
let root = support::temp::TemporaryDirectory::new();
let database = Database::open(root.path().join("automatic.db"), 1, 50).unwrap();
let initial = model(
"2000000000000001",
"seed-empty",
vec![
id(),
text_field("nickname", 64, false),
bytes_field("payload", 8, false),
created_at(),
],
vec![],
vec![],
vec![],
);
database.migrate(initial).await.unwrap();
database
.execute_batch(
"INSERT INTO user (nickname, payload) VALUES ('Administrator', X'0102')".into(),
)
.await
.unwrap();
let upgraded = || {
model(
"2000000000000002",
"seed-empty",
vec![
id(),
text_field("display_name", 128, false).renamed_from("nickname"),
bytes_field("payload", 16, false),
text_field("bio", 32, true),
bool_default("enabled"),
created_at(),
],
vec![],
vec![],
vec![],
)
};
database.migrate(upgraded()).await.unwrap();
let once = database_state(&database).await;
database.migrate(upgraded()).await.unwrap();
assert_eq!(database_state(&database).await, once);
let rows = database
.query(
"SELECT display_name, payload, bio, enabled FROM user",
vec![],
)
.await
.unwrap();
assert_eq!(
rows[0].get(0).unwrap(),
&Value::Text("Administrator".into())
);
assert_eq!(rows[0].get(1).unwrap(), &Value::Bytes(vec![1, 2]));
assert_eq!(rows[0].get(2).unwrap(), &Value::Null);
assert_eq!(rows[0].get(3).unwrap(), &Value::Int(1));
}
#[tokio::test]
async fn text_and_bytes_tightening_succeeds_or_rolls_back_as_one_unit() {
let root = support::temp::TemporaryDirectory::new();
let valid = Database::open(root.path().join("narrow-valid.db"), 1, 50).unwrap();
valid
.migrate(text_model("3000000000000001", 16, 8))
.await
.unwrap();
valid
.execute_batch("INSERT INTO user (name, payload) VALUES ('short', X'0102')".into())
.await
.unwrap();
valid
.migrate(text_model("3000000000000002", 5, 2))
.await
.unwrap();
let valid_state = database_state(&valid).await;
assert!(valid_state.catalog.iter().any(|row| {
text(row, 0) == "table"
&& text(row, 2).contains("length(\"name\") <= 5")
&& text(row, 2).contains("length(\"payload\") <= 2")
}));
let invalid = Database::open(root.path().join("narrow-invalid.db"), 1, 50).unwrap();
invalid
.migrate(text_model("3000000000000001", 16, 8))
.await
.unwrap();
invalid
.execute_batch("INSERT INTO user (name, payload) VALUES ('too-long', X'010203')".into())
.await
.unwrap();
let before = database_state(&invalid).await;
assert_eq!(
invalid
.migrate(text_model("3000000000000002", 5, 2))
.await
.unwrap_err()
.kind(),
ErrorKind::Constraint
);
assert_eq!(database_state(&invalid).await, before);
}
#[tokio::test]
async fn not_null_and_unique_changes_validate_existing_rows_before_commit() {
let root = support::temp::TemporaryDirectory::new();
let nullable = |revision: &str, required: bool| {
model(
revision,
"seed-empty",
vec![
id(),
text_field("code", 16, false),
text_field("note", 32, !required),
created_at(),
],
vec![],
vec![],
vec![],
)
};
let nullable_valid = Database::open(root.path().join("nullable-valid.db"), 1, 50).unwrap();
nullable_valid
.migrate(nullable("4000000000000001", false))
.await
.unwrap();
nullable_valid
.execute_batch("INSERT INTO user (code, note) VALUES ('one', 'value')".into())
.await
.unwrap();
nullable_valid
.migrate(nullable("4000000000000002", true))
.await
.unwrap();
assert_eq!(
orm_sqlite::integer(&database_state(&nullable_valid).await.columns[2], 2),
1
);
let nullable_invalid = Database::open(root.path().join("nullable-invalid.db"), 1, 50).unwrap();
nullable_invalid
.migrate(nullable("4000000000000001", false))
.await
.unwrap();
nullable_invalid
.execute_batch("INSERT INTO user (code, note) VALUES ('one', NULL)".into())
.await
.unwrap();
let before = database_state(&nullable_invalid).await;
assert_eq!(
nullable_invalid
.migrate(nullable("4000000000000002", true))
.await
.unwrap_err()
.kind(),
ErrorKind::Constraint
);
assert_eq!(database_state(&nullable_invalid).await, before);
let unique = |revision: &str, enabled: bool| {
model(
revision,
"seed-empty",
vec![id(), text_field("code", 16, false), created_at()],
enabled
.then(|| index("_dever_idx_user_code", &["code"], true))
.into_iter()
.collect(),
vec![],
vec![],
)
};
let unique_valid = Database::open(root.path().join("unique-valid.db"), 1, 50).unwrap();
unique_valid
.migrate(unique("4000000000000011", false))
.await
.unwrap();
unique_valid
.execute_batch("INSERT INTO user (code) VALUES ('one'), ('two')".into())
.await
.unwrap();
unique_valid
.migrate(unique("4000000000000012", true))
.await
.unwrap();
assert!(
database_state(&unique_valid)
.await
.catalog
.iter()
.any(|row| {
text(row, 1) == "_dever_idx_user_code"
&& text(row, 2).starts_with("CREATE UNIQUE INDEX")
})
);
let unique_invalid = Database::open(root.path().join("unique-invalid.db"), 1, 50).unwrap();
unique_invalid
.migrate(unique("4000000000000011", false))
.await
.unwrap();
unique_invalid
.execute_batch("INSERT INTO user (code) VALUES ('same'), ('same')".into())
.await
.unwrap();
let before = database_state(&unique_invalid).await;
assert_eq!(
unique_invalid
.migrate(unique("4000000000000012", true))
.await
.unwrap_err()
.kind(),
ErrorKind::Constraint
);
assert_eq!(database_state(&unique_invalid).await, before);
}
#[tokio::test]
async fn adding_a_foreign_key_accepts_valid_rows_and_rolls_back_orphans() {
let root = support::temp::TemporaryDirectory::new();
let schema = |revision: &str, foreign_key: bool| {
let owner = if foreign_key {
TestField::new(
"owner_id",
"id:app.model.team",
false,
"\"owner_id\" INTEGER NOT NULL REFERENCES \"team\" (\"id\")",
)
} else {
TestField::new("owner_id", "Int", false, "\"owner_id\" INTEGER NOT NULL")
};
model(
revision,
"seed-empty",
vec![id(), owner, created_at()],
vec![],
vec![],
vec![],
)
};
let valid = Database::open(root.path().join("foreign-key-valid.db"), 1, 50).unwrap();
valid
.execute_batch(
"CREATE TABLE team (id INTEGER PRIMARY KEY); INSERT INTO team (id) VALUES (1)".into(),
)
.await
.unwrap();
valid
.migrate(schema("5000000000000001", false))
.await
.unwrap();
valid
.execute_batch("INSERT INTO user (owner_id) VALUES (1)".into())
.await
.unwrap();
valid
.migrate(schema("5000000000000002", true))
.await
.unwrap();
let foreign_keys = valid
.query(
"SELECT \"table\", \"from\", \"to\" FROM pragma_foreign_key_list('user')",
vec![],
)
.await
.unwrap();
assert_eq!(foreign_keys.len(), 1);
assert_eq!(text(&foreign_keys[0], 0), "team");
assert_eq!(text(&foreign_keys[0], 1), "owner_id");
assert_eq!(text(&foreign_keys[0], 2), "id");
drop(valid);
let invalid = Database::open(root.path().join("foreign-key-invalid.db"), 1, 50).unwrap();
invalid
.execute_batch("CREATE TABLE team (id INTEGER PRIMARY KEY)".into())
.await
.unwrap();
invalid
.migrate(schema("5000000000000001", false))
.await
.unwrap();
invalid
.execute_batch("INSERT INTO user (owner_id) VALUES (99)".into())
.await
.unwrap();
let before = database_state(&invalid).await;
assert_eq!(
invalid
.migrate(schema("5000000000000002", true))
.await
.unwrap_err()
.kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&invalid).await, before);
drop(invalid);
let cross_family = Database::open(root.path().join("foreign-key-type.db"), 1, 50).unwrap();
cross_family
.execute_batch("CREATE TABLE team (id INTEGER PRIMARY KEY)".into())
.await
.unwrap();
cross_family
.migrate(model(
"5000000000000011",
"seed-empty",
vec![
id(),
TestField::new("owner_id", "Float", false, "\"owner_id\" REAL NOT NULL"),
created_at(),
],
vec![],
vec![],
vec![],
))
.await
.unwrap();
cross_family
.execute_batch("INSERT INTO user (owner_id) VALUES (1.0)".into())
.await
.unwrap();
let before = database_state(&cross_family).await;
assert_eq!(
cross_family
.migrate(schema("5000000000000012", true))
.await
.unwrap_err()
.kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&cross_family).await, before);
}
#[tokio::test]
async fn named_drop_runs_once_and_applied_content_cannot_change_or_disappear() {
let root = support::temp::TemporaryDirectory::new();
let database = Database::open(root.path().join("named-drop.db"), 1, 50).unwrap();
let initial = || {
model(
"6000000000000001",
"seed-empty",
vec![
id(),
text_field("code", 16, false),
text_field("legacy", 32, true),
created_at(),
],
vec![],
vec![],
vec![],
)
};
database.migrate(initial()).await.unwrap();
database
.execute_batch("INSERT INTO user (code, legacy) VALUES ('one', 'old')".into())
.await
.unwrap();
let dropped = |migration_revision: &str, include_migration: bool| {
model(
"6000000000000002",
"seed-empty",
vec![id(), text_field("code", 16, false), created_at()],
vec![],
include_migration
.then(|| migration("remove_legacy", migration_revision, &["legacy"]))
.into_iter()
.collect(),
vec![],
)
};
let before_drop = database_state(&database).await;
assert_eq!(
database
.migrate(dropped("remove-legacy-v1", false))
.await
.unwrap_err()
.kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&database).await, before_drop);
database
.migrate(dropped("remove-legacy-v1", true))
.await
.unwrap();
let applied = database_state(&database).await;
assert_eq!(
applied
.columns
.iter()
.map(|row| text(row, 0))
.collect::<Vec<_>>(),
["id", "code", "created_at"]
);
assert!(applied.history.iter().any(|row| {
text(row, 0) == "migration"
&& text(row, 1) == "remove_legacy"
&& text(row, 2) == "remove-legacy-v1"
}));
database
.migrate(dropped("remove-legacy-v1", true))
.await
.unwrap();
assert_eq!(database_state(&database).await, applied);
assert_eq!(
database
.migrate(dropped("remove-legacy-v2", true))
.await
.unwrap_err()
.kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&database).await, applied);
assert_eq!(
database
.migrate(dropped("remove-legacy-v1", false))
.await
.unwrap_err()
.kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&database).await, applied);
}
#[tokio::test]
async fn catalog_drift_is_rejected_without_rewriting_history_or_data() {
let root = support::temp::TemporaryDirectory::new();
let indexed = || {
model(
"8000000000000001",
"seed-empty",
vec![id(), text_field("code", 16, false), created_at()],
vec![index("_dever_idx_user_code", &["code"], true)],
vec![],
vec![],
)
};
let index_drift = Database::open(root.path().join("index-drift.db"), 1, 50).unwrap();
index_drift.migrate(indexed()).await.unwrap();
index_drift
.execute_batch(
"INSERT INTO user (code) VALUES ('one'); \
DROP INDEX _dever_idx_user_code; \
CREATE INDEX _dever_idx_user_code ON user (code)"
.into(),
)
.await
.unwrap();
let before = database_state(&index_drift).await;
assert_eq!(
index_drift.migrate(indexed()).await.unwrap_err().kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&index_drift).await, before);
let table_drift = Database::open(root.path().join("table-drift.db"), 1, 50).unwrap();
table_drift.migrate(indexed()).await.unwrap();
table_drift
.execute_batch(
"INSERT INTO user (code) VALUES ('one'); ALTER TABLE user ADD COLUMN rogue TEXT".into(),
)
.await
.unwrap();
let before = database_state(&table_drift).await;
assert_eq!(
table_drift.migrate(indexed()).await.unwrap_err().kind(),
ErrorKind::Migration
);
assert_eq!(database_state(&table_drift).await, before);
}