feat: release Dever 0.1.5 with direct workers and long transactions

This commit is contained in:
Codex committed 2026-10-11 11:17:33 +00:00
1 parent 89629c2197
commit a900d7e8db
153 files changed
+3676 -3708

No files matched your search

+151 -2
View File
@@ -40,6 +40,155 @@ fn reject(files: &[(&str, &str)], expected: &str) {
);
}
#[test]
fn ordinary_and_long_entries_have_explicit_transaction_policies() {
for entry in ["public post create = app.create", "cmd create = app.create"] {
let files = [
(
"content/item/model.dever",
"global type Item { name: Text }",
),
(
"content/item/app.dever",
"create(name: Text) (value: Text) { saved = model.create({ name = name })\nvalue = saved.name }",
),
("content/item/api.dever", entry),
];
check(&files);
let long_entry = if entry.starts_with("public ") {
entry.replace("public ", "public long ")
} else {
format!("long {entry}")
};
let mut long = files;
long[2].1 = &long_entry;
reject(
&long,
"long entry database writes must be enclosed in an explicit transaction",
);
let mut transactional = long;
let app = format!("transaction {}", files[1].1);
transactional[1].1 = &app;
check(&transactional);
}
}
#[test]
fn entry_orchestration_can_wait_between_explicit_transactions() {
let files = [
(
"content/item/model.dever",
"global type Item { name: Text }",
),
(
"content/item/app.dever",
"create() (count: Int) { first = store(\"first\")\ndever.task.sleep(1)\nsecond = store(\"second\")\ncount = first + second }\ntransaction store(name: Text) (count: Int) { saved = model.create({ name = name })\ncount = model.count() }",
),
(
"content/item/api.dever",
"public long post create = app.create\nlong cmd create = app.create",
),
];
let source_map = sources(&files);
let program = dever_core::check(&source_map).unwrap_or_else(|errors| panic!("{errors:?}"));
let native = dever_core::native::emit_project(
&program,
&source_map,
dever_runtime::config::RuntimeProfile {
sqlite: true,
postgres: false,
},
)
.unwrap();
assert!(!native.contains("let transaction = transaction_database.begin()"));
assert!(native.contains("let transaction_owner = if _database.is_none()"));
let (_root, settings) = auth_settings();
let mut configured = identity_sources().to_vec();
configured.extend([
files[0],
files[1],
("content/item/api/admin/create.dever", files[2].1),
]);
let configured_sources = sources(&configured);
let configured_program = dever_core::check_with_settings(&configured_sources, &settings)
.unwrap_or_else(|errors| panic!("{errors:?}"));
dever_core::llvm::emit_application(&configured_program, &configured_sources).unwrap();
let ordinary_entry = files[2].1.replace("long ", "");
let mut ordinary = files;
ordinary[2].1 = &ordinary_entry;
reject(&ordinary, "automatic entry transactions cannot contain");
let mut invalid = files;
let app = files[1].1.replace("create()", "transaction create()");
invalid[1].1 = &app;
reject(
&invalid,
"transaction functions cannot start concurrent work",
);
}
#[test]
fn job_entries_require_long_before_using_staged_transactions() {
let model = "global type Item { name: Text }";
let ordinary = "apply() () { saved = model.create({ name = \"job\" }) }";
let transactional = "transaction apply() () { saved = model.create({ name = \"job\" }) }";
for long in [false, true] {
let prefix = if long { "long " } else { "" };
let job = format!(
"database default\n{prefix}job apply() () retry(3) timeout(30000) {{ app.apply() }}"
);
let mut files = [
("content/item/model.dever", model),
("content/item/app.dever", ordinary),
("content/item/job.dever", job.as_str()),
];
if long {
reject(&files, "long entry database writes must be enclosed");
files[1].1 = transactional;
}
check(&files);
}
let files = [
("content/item/model.dever", model),
(
"content/item/app.dever",
"apply() () { save()\ndever.task.sleep(1) }\ntransaction save() () { saved = model.create({ name = \"job\" }) }",
),
(
"content/item/job.dever",
"database default\njob apply() () retry(3) timeout(30000) { app.apply() }",
),
];
reject(&files, "declare the entry 'long'");
let mut long = files;
long[2].1 = "database default\nlong job apply() () retry(3) timeout(30000) { app.apply() }";
check(&long);
}
#[test]
fn long_is_an_entry_modifier_not_an_app_function_modifier() {
for (path, text) in [
("content/item/api.dever", "long rest"),
(
"content/item/api.dever",
"long public post create = app.create",
),
("content/item/app.dever", "long create() () {}"),
("content/item/app.dever", "long transaction create() () {}"),
(
"content/item/api.dever",
"public long cmd create = app.create",
),
] {
let mut source = SourceMap::default();
let id = source.add(path, text);
assert!(dever_core::parse(source.get(id)).is_err());
}
check(&[(
"content/item/app.dever",
"long() (value: Int) { value = 1 }",
)]);
}
fn auth_settings() -> (temp::TemporaryDirectory, Settings) {
let root = temp::TemporaryDirectory::new();
fs::create_dir(root.path().join("config")).unwrap();
@@ -698,7 +847,7 @@ fn sql_projection_files(sqlite: &str, postgres: &str, method: &str) -> [(&'stati
(
"news/article/app.dever",
"type View { name: Text }\n\
read(name: Text, size: Int) (rows: List<View>) {\n\
transaction read(name: Text, size: Int) (rows: List<View>) {\n\
rows = model.lookup(name, size)\n}\n"
.into(),
),
@@ -767,7 +916,7 @@ fn unproven_sql_keeps_its_write_effect_in_each_dialect() {
#[test]
fn get_rejects_writes_reached_through_cross_domain_app_calls() {
let writer = "create(name: Text) (value: Text) { account = model.create({ name = text.trim(name) })\nvalue = account.name }";
let writer = "transaction create(name: Text) (value: Text) { account = model.create({ name = text.trim(name) })\nvalue = account.name }";
reject(
&[
(