clockworklabs/SpacetimeDB · error · Error
Subscriptions must have at least one query
Error message
Subscriptions must have at least one query
What it means
SubscriptionBuilder's subscribe normalizes its argument into an array of queries (single value, array, or the result of a tables-callback) and requires at least one entry before converting them to SQL strings. An empty list would send a meaningless subscribe message, so it is rejected.
Source
Thrown at crates/bindings-typescript/src/sdk/subscription_builder_impl.ts:120
) => RowTypedQuery<any, any> | RowTypedQuery<any, any>[]
): SubscriptionHandleImpl<RemoteModule>;
subscribe(
query_sql:
| string
| RowTypedQuery<any, any>
| Array<string | RowTypedQuery<any, any>>
| ((tables: any) => RowTypedQuery<any, any> | RowTypedQuery<any, any>[])
): SubscriptionHandleImpl<RemoteModule> {
let queries: Array<string | RowTypedQuery<any, any>>;
if (typeof query_sql === 'function') {
const tables = this.db.getFromBuilder<RemoteModule & UntypedSchemaDef>();
const result = query_sql(tables);
queries = Array.isArray(result) ? result : [result];
} else {
queries = Array.isArray(query_sql) ? query_sql : [query_sql];
}
if (queries.length === 0) {
throw new Error('Subscriptions must have at least one query');
}
const queryStrings = queries.map(q => {
if (typeof q === 'string') return q;
if (isRowTypedQuery(q)) return toSql(q);
throw new Error('Subscriptions must be SQL strings or typed queries');
});
return new SubscriptionHandleImpl(
this.db,
queryStrings,
this.#onApplied,
this.#onError
);
}
/**
* Subscribes to all rows from all tables.
*
* This method is intended as a convenienceView on GitHub (pinned to 524b4487d9)
Solutions
- Guard before subscribing: if the query list is empty, fall back to subscribeToAllTables() or skip subscribing
- Fix the callback so it always returns at least one query
- Log the computed query list when it is empty to catch filter bugs early
Example fix
// before
const qs = selected.filter(t => t.enabled).map(t => `SELECT * FROM ${t}`);
builder.subscribe(qs); // throws when nothing is enabled
// after
const qs = selected.filter(t => t.enabled).map(t => `SELECT * FROM ${t}`);
if (qs.length === 0) builder.subscribeToAllTables();
else builder.subscribe(qs); Defensive patterns
Strategy: validation
Validate before calling
const queries = buildQueries();
if (queries.length === 0) {
builder.subscribeToAllTables();
} else {
builder.subscribe(queries);
} Prevention
- Never build the query list and subscribe in one expression - keep them separate so the empty case is visible
- Log a warning when the computed query list is empty; it usually means a filter removed everything
When it happens
Trigger: Passing [] to .subscribe(...); a callback like tables => myFilteredQueries where the filter produced an empty array; a function callback that returns [] by mistake or falls through without returning a query.
Common situations: Dynamically building a subscription list from user selections where everything is filtered out; an early-return in the query-builder callback; refactoring removed the hardcoded query and left an empty array.
Related errors
- Subscriptions must be SQL strings or typed queries
- Cannot convert ${typeof value} to ${what}: expected bigint,
- `fromCounterV7` requires `randomBytes.length == 4`
- `fromCounterV7` uuid `counter` must be non-negative
- Invalid hex UUID
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/f6db69c19e757ef3.
Report an issue: GitHub.