clockworklabs/SpacetimeDB · error · Error

Subscriptions must have at least one query

Error message

Subscriptions must have at least one query

What it means

SubscriptionBuilderImpl.subscribe normalizes its argument (a SQL string, an array, or a callback returning query/queries) into a query list and requires at least one entry. An empty array — or a callback that returns an empty array — throws before any subscription message is sent.

Solutions

  1. Guard before subscribing: if (queries.length === 0) skip or throw your own config error
  2. If broad subscription is the intent, use subscribeToAllTables() instead
  3. Default the dynamic list to at least one explicit query such as 'SELECT * FROM <core_table>'

Example fix

// before
sub.subscribe(enabledTables.map(t => `SELECT * FROM ${t}`)); // [] -> throws

// after
if (enabledTables.length > 0) {
  sub.subscribe(enabledTables.map(t => `SELECT * FROM ${t}`));
}
Defensive patterns

Strategy: validation

Validate before calling

if (queries.length === 0) {
  // skip subscribing, or throw a precise config error
  throw new Error('no queries configured for subscription');
}
sub.onApplied(onApplied).subscribe(queries);

Prevention

When it happens

Trigger: subscriptionBuilder().onApplied(cb).subscribe([]); or a callback like tables => enabledTables.map(t => tables[t].all()) where enabledTables is empty.

Common situations: Building subscriptions dynamically from configuration that can legitimately be empty; a filter step that removes every table; a previously hardcoded single query becoming data-driven.

Related errors


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

Appendix: 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 6dee26c6ef)