clockworklabs/SpacetimeDB · error · Error

SpacetimeDBQueryClient can only handle spacetimedb queries…

Error message

SpacetimeDBQueryClient can only handle spacetimedb queries, got: ${prefix}

What it means

SpacetimeDBQueryClient implements TanStack Query's QueryFunction contract and validates that each incoming queryKey is the triple ['spacetimedb', accessorName, querySql] produced by the spacetimeDBQuery() helper. Any key whose first element is not the literal 'spacetimedb' is rejected, because the client's subscription and cache-hydration logic only understands keys it created (it also looks the SQL string up in an internal queryRegistry filled by spacetimeDBQuery).

Solutions

  1. Build query options only with spacetimeDBQuery(tables.x) or spacetimeDBQuery(tables.x.where(...)), never by hand
  2. Do not install this queryFn as the QueryClient's defaultQueryFn if the client also serves other queries — pass it per query
  3. Never override or reorder the queryKey from spacetimeDBQuery(); spread the whole options object into useQuery
  4. If you must construct keys manually, use the exact shape ['spacetimedb', accessorName, sql] and make sure spacetimeDBQuery() ran for that query so it is in the registry

Example fix

// before
const { data } = useQuery({
  queryKey: ['players', userId], // prefix is not 'spacetimedb'
  queryFn: stdbQueryClient.queryFn,
});

// after
import { spacetimeDBQuery } from '@clockworklabs/spacetime-db-tanstack';
const { data } = useQuery({
  ...spacetimeDBQuery(tables.player), // queryKey: ['spacetimedb', 'player', sql]
  queryFn: stdbQueryClient.queryFn,
});
Defensive patterns

Strategy: validation

Validate before calling

function isSpacetimeDBKey(key: readonly unknown[]): boolean {
  return key[0] === 'spacetimedb';
}

// before wiring the queryFn:
if (isSpacetimeDBKey(options.queryKey)) {
  // safe for SpacetimeDBQueryClient.queryFn
}

Type guard

function isSpacetimeDBKey(key: readonly unknown[]): key is readonly ['spacetimedb', string, string] {
  return (
    Array.isArray(key) &&
    key.length === 3 &&
    key[0] === 'spacetimedb' &&
    typeof key[1] === 'string' &&
    typeof key[2] === 'string'
  );
}

Try / catch

// better than catching: never hand-build keys — always spread spacetimeDBQuery(...)

Prevention

When it happens

Trigger: Setting SpacetimeDBQueryClient.queryFn as the default queryFn of a QueryClient that also runs non-SpacetimeDB queries; calling queryClient.fetchQuery/prefetchQuery with a hand-built key; overriding or reordering the queryKey returned by spacetimeDBQuery(); spreading the options object but replacing queryKey.

Common situations: Hybrid apps mixing REST and SpacetimeDB queries on one QueryClient; refactors that rename or namespace query keys; copying examples that set defaultOptions.queries.queryFn globally.

Related errors


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

Appendix: source

Thrown at crates/bindings-typescript/src/tanstack/SpacetimeDBQueryClient.ts:135

          }
        }
      });
  }

  queryFn: QueryFunction<any[], QueryKey> = async ({
    queryKey,
  }: {
    queryKey: QueryKey;
  }) => {
    const keyStr = JSON.stringify(queryKey);
    const [prefix, accessorName, querySql] = queryKey as [
      string,
      string,
      string,
    ];

    if (prefix !== 'spacetimedb') {
      throw new Error(
        `SpacetimeDBQueryClient can only handle spacetimedb queries, got: ${prefix}`
      );
    }

    const registered = queryRegistry.get(querySql);
    const whereExpr = registered?.whereExpr;

    const existingSub = this.subscriptions.get(keyStr);
    if (existingSub?.applied) {
      return this.getTableData(existingSub.tableInstance, whereExpr);
    }

    // queue query if connection not ready yet
    if (!this.connection) {
      return new Promise<any[]>(resolve => {
        const pending = this.pendingQueries.get(keyStr) || [];
        pending.push({ resolve, querySql, whereExpr });
        this.pendingQueries.set(keyStr, pending);

View on GitHub (pinned to 6dee26c6ef)