From 8671ff3d82e0bf80dfe707ea4368021a5cf07b6b Mon Sep 17 00:00:00 2001 From: Marcus Kohlberg <78424526+marcuskohlberg@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:36:39 +0200 Subject: [PATCH 1/2] tsparser: report database usage outside services Using a database from code outside a service (e.g. SQLDatabase.named in a shared lib/ directory) built a ParseError but never reported it, so the usage was silently dropped. encore run and encore test give the process every database, so it worked locally, but deployed services only get the databases attributed to them, and every query failed with "this database is not configured for use by this process". Report the error, with a hint matching the Go parser's, as is already done for buckets, metrics and cache clusters. --- tsparser/src/legacymeta/mod.rs | 71 ++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/tsparser/src/legacymeta/mod.rs b/tsparser/src/legacymeta/mod.rs index 6d36e8acf1..da6491530e 100644 --- a/tsparser/src/legacymeta/mod.rs +++ b/tsparser/src/legacymeta/mod.rs @@ -637,9 +637,16 @@ impl MetaBuilder<'_> { } Usage::AccessDatabase(access) => { let Some(svc) = self.service_for_range(&access.range) else { - access - .range - .parse_err("cannot determine which service is accessing this database"); + // Without a service the database isn't configured for the process + // using it when deployed, so this must fail at parse time. + HANDLER.with(|h| { + h.struct_span_err( + access.range.to_span(), + "cannot determine which service is accessing this database", + ) + .help("infrastructure resources can only be used within services. To use the database from shared code, pass a reference to it into the library.") + .emit(); + }); continue; }; @@ -1116,6 +1123,11 @@ mod tests { use super::*; fn parse(tmp_dir: &Path, src: &str) -> anyhow::Result { + parse_with_errors(tmp_dir, src).map(|(md, _)| md) + } + + /// Like parse, but also reports whether any errors were emitted. + fn parse_with_errors(tmp_dir: &Path, src: &str) -> anyhow::Result<(v1::Data, bool)> { let globals = Globals::new(); let cm: Rc = Default::default(); let errs = Rc::new(Handler::with_tty_emitter( @@ -1149,11 +1161,62 @@ mod tests { let parser = Parser::new(&pc, pass1); let parse = parser.parse(); let md = compute_meta(&pc, &parse)?; - Ok(md) + Ok((md, errs.has_errors())) }) }) } + const DB_SERVICE: &str = r#" +-- auth/encore.service.ts -- +import { Service } from "encore.dev/service"; +export default new Service("auth"); + +-- auth/db.ts -- +import { SQLDatabase } from "encore.dev/storage/sqldb"; +export const DB = new SQLDatabase("pingvin", { migrations: "./migrations" }); + +-- auth/migrations/1_init.up.sql -- +CREATE TABLE users (id BIGSERIAL PRIMARY KEY); + +-- package.json -- +{ "name": "test", "type": "module", "dependencies": { "encore.dev": "^1.35.0" } } +"#; + + #[test] + fn test_database_usage_in_service() -> anyhow::Result<()> { + let src = format!( + "{DB_SERVICE} +-- auth/auth.ts -- +import {{ DB }} from \"./db\"; +export const count = () => DB.queryRow`SELECT 1`; +" + ); + let tmp_dir = TempDir::with_prefix("tsparser-db-test")?; + let (meta, has_errors) = parse_with_errors(tmp_dir.path(), &src)?; + assert!(!has_errors); + assert_eq!(meta.svcs[0].databases, vec!["pingvin".to_string()]); + Ok(()) + } + + #[test] + fn test_database_usage_outside_service() -> anyhow::Result<()> { + let src = format!( + "{DB_SERVICE} +-- lib/db.ts -- +import {{ SQLDatabase }} from \"encore.dev/storage/sqldb\"; +const DB = SQLDatabase.named(\"pingvin\"); +export const connectionString = () => DB.connectionString; +" + ); + let tmp_dir = TempDir::with_prefix("tsparser-db-test")?; + let (_, has_errors) = parse_with_errors(tmp_dir.path(), &src)?; + assert!( + has_errors, + "using a database outside a service should be an error" + ); + Ok(()) + } + #[test] fn test_legacymeta() -> anyhow::Result<()> { let src = r#" From 1b8ddb3a0e2139ace12cb408ed10a1fd8b8a799f Mon Sep 17 00:00:00 2001 From: Marcus Kohlberg <78424526+marcuskohlberg@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:41:16 +0200 Subject: [PATCH 2/2] tsparser: explain how to use a database from a service Match the docs on sharing databases instead of the Go phrasing about passing a reference into a library. --- tsparser/src/legacymeta/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsparser/src/legacymeta/mod.rs b/tsparser/src/legacymeta/mod.rs index da6491530e..5425b78e1f 100644 --- a/tsparser/src/legacymeta/mod.rs +++ b/tsparser/src/legacymeta/mod.rs @@ -644,7 +644,7 @@ impl MetaBuilder<'_> { access.range.to_span(), "cannot determine which service is accessing this database", ) - .help("infrastructure resources can only be used within services. To use the database from shared code, pass a reference to it into the library.") + .help("databases can only be used within services. Use the database from the service that needs it.") .emit(); }); continue;