{"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":"exception","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/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/sdk/subscription_builder_impl.ts#L107-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nbuilder.subscribe([db.getFromBuilder().user, undefined]); // throws\n\n// after\nbuilder.subscribe([db.getFromBuilder().user.where(ctx => ctx.userId).eq(1)]);","handlingStrategy":"validation","validationCode":"const ok = queries.every(q => typeof q === 'string' || (typeof q === 'object' && q !== null));\nif (!ok) throw new TypeError('every subscription query must be an SQL string or a typed query');\nbuilder.subscribe(queries.filter(Boolean));","typeGuard":"function isSqlOrTypedQuery(q: unknown): q is string | object {\n  return (typeof q === 'string' && q.trim().length > 0) || (typeof q === 'object' && q !== null);\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["subscription","query","validation","typescript"],"backgroundTag":"invalid-query-argument","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}