{"record":{"id":"30755897389c3cdf","repo":"remix-run/remix","slug":"cannot-call-method-from-a-transaction-sc","errorCode":null,"errorMessage":"Cannot call ' + method + '() from a transaction-scoped database","messagePattern":"Cannot call ' \\+ method \\+ '\\(\\) from a transaction-scoped database","errorType":"exception","errorClass":"DataTableQueryError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database.ts","lineNumber":514,"sourceCode":"\n  /**\n   * Wipes the database, applies migrations, and optionally seeds data.\n   *\n   * @param options Migrations and optional seed function used to rebuild the database.\n   * @returns A promise that resolves when the database has been rebuilt.\n   */\n  async reset(options: DatabaseResetOptions): Promise<void> {\n    this.#assertLifecycleOperationAllowed('reset')\n    await this.wipe()\n    await this.migrate(options.migrations, { journalTable: options.journalTable })\n    await options.seed?.(this)\n  }\n\n  #assertLifecycleOperationAllowed(\n    method: 'close' | 'migrate' | 'migrationStatus' | 'reset' | 'wipe',\n  ): void {\n    if (this.#token) {\n      throw new DataTableQueryError(\n        'Cannot call ' + method + '() from a transaction-scoped database',\n      )\n    }\n  }\n\n  now(): unknown {\n    return this.#now()\n  }\n\n  query<\n    tableName extends string,\n    row extends Record<string, unknown>,\n    primaryKey extends readonly (keyof row & string)[],\n  >(\n    table: QueryTableInput<tableName, row, primaryKey>,\n  ): QueryObject<\n    QueryTableInput<tableName, row, primaryKey>,\n    Pretty<QueryColumnTypeMapFromRow<tableName, row>>,","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database.ts#L496-L532","documentation":"When you call db.transaction(cb), the callback receives a transaction-scoped Database whose lifecycle is owned by the outer transaction. Lifecycle methods (close, wipe, migrate, migrationStatus, reset) are blocked on that inner handle via #assertLifecycleOperationAllowed because committing/rolling back or closing the connection is the outer transaction's job.","triggerScenarios":"Calling tx.close(), tx.migrate(...), tx.migrationStatus(), tx.reset(), or tx.wipe() on the database passed into a transaction callback.","commonSituations":"Passing the tx-scoped db deep into helpers (e.g. a wipe/migrate utility or test setup function) that normally receive the root db; refactors where a helper that manages schema is now called inside transactional test wrappers.","solutions":["Call lifecycle methods only on the root database, outside any transaction callback.","Refactor helpers so they don't manage schema/close, or pass the root db explicitly to schema-management code."],"exampleFix":"// before\nawait db.transaction(async (tx) => {\n  await tx.migrate(migrations)\n})\n\n// after\nawait db.migrate(migrations)\nawait db.transaction(async (tx) => { ... })","handlingStrategy":"try-catch","validationCode":"// Discipline: never pass the tx handle to schema-management code.\n// Approximate guard by tracking which handle you passed around\nfunction assertRootDatabase(db: Database) {\n  if (Object.is(db, currentTransactionHandle)) {\n    throw new Error('Pass the root database, not the tx')\n  }\n}","typeGuard":"function isTransactionScoped(db: Database): boolean {\n  return Object.is(db, currentTransactionHandle)\n}","tryCatchPattern":"try {\n  await tx.migrate(migrations)\n} catch (error) {\n  if (error instanceof DataTableQueryError && /transaction-scoped/.test(error.message)) {\n    // move the call outside the transaction\n  }\n}","preventionTips":["Keep migrate/reset/wipe/close calls at app startup, never inside transaction callbacks.","Give helpers an explicit db parameter that only ever receives the root Database."],"tags":["transactions","lifecycle","data-table"],"backgroundTag":"transaction-scope-violation","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}