{"record":{"id":"e03f6732a257fcea","repo":"clockworklabs/SpacetimeDB","slug":"subscriptions-must-be-sql-strings-or-typed-queries","errorCode":null,"errorMessage":"Subscriptions must be SQL strings or typed queries","messagePattern":"Subscriptions must be SQL strings or typed queries","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/sdk/subscription_builder_impl.ts","lineNumber":125,"sourceCode":"      | RowTypedQuery<any, any>\n      | Array<string | RowTypedQuery<any, any>>\n      | ((tables: any) => RowTypedQuery<any, any> | RowTypedQuery<any, any>[])\n  ): SubscriptionHandleImpl<RemoteModule> {\n    let queries: Array<string | RowTypedQuery<any, any>>;\n    if (typeof query_sql === 'function') {\n      const tables = this.db.getFromBuilder<RemoteModule & UntypedSchemaDef>();\n      const result = query_sql(tables);\n      queries = Array.isArray(result) ? result : [result];\n    } else {\n      queries = Array.isArray(query_sql) ? query_sql : [query_sql];\n    }\n    if (queries.length === 0) {\n      throw new Error('Subscriptions must have at least one query');\n    }\n    const queryStrings = queries.map(q => {\n      if (typeof q === 'string') return q;\n      if (isRowTypedQuery(q)) return toSql(q);\n      throw new Error('Subscriptions must be SQL strings or typed queries');\n    });\n    return new SubscriptionHandleImpl(\n      this.db,\n      queryStrings,\n      this.#onApplied,\n      this.#onError\n    );\n  }\n\n  /**\n   * Subscribes to all rows from all tables.\n   *\n   * This method is intended as a convenience\n   * for applications where client-side memory use and network bandwidth are not concerns.\n   * Applications where these resources are a constraint\n   * should register more precise queries via `subscribe`\n   * in order to replicate only the subset of data which the client needs to function.\n   *","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-typescript/src/sdk/subscription_builder_impl.ts#L107-L143","documentation":"Each element passed to subscribe must be either a SQL string or a RowTypedQuery produced by the typed query builder, checked with isRowTypedQuery from lib/query. Elements of any other shape — raw table definition objects, numbers, plain objects with a toString — throw immediately after the empty-list check.","triggerScenarios":"Passing a table accessor object (e.g. db.tables.players) instead of a query built from db.from(...); spreading a mixed array; passing results of a custom helper that does not return RowTypedQuery objects.","commonSituations":"Migrating from raw-SQL code and mixing string and object shapes; assuming any stringifyable object is accepted; older API versions that took table objects directly.","solutions":["Pass SQL strings ('SELECT * FROM players') or query-builder results from db.from(...)","Validate elements with the exported isRowTypedQuery(q) before subscribing","Type your query arrays as Array<string | RowTypedQuery<any, any>> so the compiler rejects other shapes"],"exampleFix":"// before\nsub.subscribe([db.tables.players]); // table def, not a query -> throws\n\n// after\nsub.subscribe(['SELECT * FROM players']); // or db.from('players')... builder result","handlingStrategy":"type-guard","validationCode":"import { isRowTypedQuery } from '../lib/query';\nconst ok = queries.every(q => typeof q === 'string' || isRowTypedQuery(q));\nif (!ok) {\n  throw new Error('subscribe expects SQL strings or RowTypedQuery objects only');\n}\nsub.onApplied(onApplied).subscribe(queries);","typeGuard":"import { isRowTypedQuery } from '../lib/query';\ntype SubscribableQuery = string | RowTypedQuery<any, any>;\nfunction isSubscribableQuery(q: unknown): q is SubscribableQuery {\n  return typeof q === 'string' || isRowTypedQuery(q);\n}","tryCatchPattern":null,"preventionTips":["Build queries exclusively via db.from(...) or plain SQL strings","Type query arrays as Array<string | RowTypedQuery<any, any>>","Never pass table definition objects (db.tables.x) to subscribe"],"tags":["subscription","query","typescript","argument-validation"],"backgroundTag":"invalid-query-argument","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}