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 convenience

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Guard before subscribing: if the query list is empty, fall back to subscribeToAllTables() or skip subscribing
  2. Fix the callback so it always returns at least one query
  3. 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

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


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/f6db69c19e757ef3. Report an issue: GitHub.