clockworklabs/SpacetimeDB · error · Error
Subscriptions must be SQL strings or typed queries
Error message
Subscriptions must be SQL strings or typed queries
What it means
After collecting the query list, subscribe maps every entry to SQL: strings pass through and RowTypedQuery objects (produced by the query builder obtained via db.getFromBuilder) are converted with toSql(). Anything else - a raw table definition, undefined, null, a number - is rejected.
Source
Thrown at crates/bindings-typescript/src/sdk/subscription_builder_impl.ts:125
| 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 convenience
* for applications where client-side memory use and network bandwidth are not concerns.
* Applications where these resources are a constraint
* should register more precise queries via `subscribe`
* in order to replicate only the subset of data which the client needs to function.
*View on GitHub (pinned to 524b4487d9)
Solutions
- Finish the typed query chain so each entry is a RowTypedQuery, e.g. tables => tables.user.select().where(...)
- Otherwise pass plain SQL strings: builder.subscribe(['SELECT * FROM user'])
- Sanitize the array first: drop null/undefined entries and verify each is a string or builder result
Example fix
// before builder.subscribe([db.getFromBuilder().user, undefined]); // throws // after builder.subscribe([db.getFromBuilder().user.where(ctx => ctx.userId).eq(1)]);
Defensive patterns
Strategy: validation
Validate before calling
const ok = queries.every(q => typeof q === 'string' || (typeof q === 'object' && q !== null));
if (!ok) throw new TypeError('every subscription query must be an SQL string or a typed query');
builder.subscribe(queries.filter(Boolean)); Type guard
function isSqlOrTypedQuery(q: unknown): q is string | object {
return (typeof q === 'string' && q.trim().length > 0) || (typeof q === 'object' && q !== null);
} Prevention
- Finish the builder chain before passing (tables.user.where(...), not tables.user)
- Filter null/undefined out of dynamically built query arrays
- Do not mix query objects created by different SDK versions
When it happens
Trigger: Passing the table accessor itself instead of a completed builder chain; an array containing undefined/null (sparse array or a map callback that returns nothing); an object from a different SDK version that fails the isRowTypedQuery check.
Common situations: Forgetting to finish the fluent chain (passing tables.user instead of tables.user.where(...)); mixing string and builder APIs incorrectly; upgrading one of two packages so the query objects no longer match the expected shape.
Related errors
- Subscriptions must have at least one query
- 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/e03f6732a257fcea.
Report an issue: GitHub.