{"version":3,"file":"tauri-sql-driver.cjs","sources":["../../src/tauri-sql-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\nimport type Database from '@tauri-apps/plugin-sql'\n\nexport type TauriSQLiteDatabaseLike = Pick<\n  Database,\n  `execute` | `select` | `close` | `path`\n>\n\nexport type TauriSQLiteDriverOptions = {\n  database: TauriSQLiteDatabaseLike\n}\n\nfunction assertTransactionCallbackHasDriverArg(\n  fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n  if (fn.length > 0) {\n    return\n  }\n\n  throw new InvalidPersistedCollectionConfigError(\n    `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n  )\n}\n\nfunction isTauriSQLiteDatabaseLike(\n  value: unknown,\n): value is TauriSQLiteDatabaseLike {\n  const candidate = value as Partial<TauriSQLiteDatabaseLike>\n  return (\n    typeof value === `object` &&\n    value !== null &&\n    typeof candidate.path === `string` &&\n    typeof candidate.execute === `function` &&\n    typeof candidate.select === `function`\n  )\n}\n\nfunction normalizeQueryRows<TRow>(\n  rows: unknown,\n  sql: string,\n): ReadonlyArray<TRow> {\n  if (Array.isArray(rows)) {\n    return rows as ReadonlyArray<TRow>\n  }\n\n  throw new InvalidPersistedCollectionConfigError(\n    `Unsupported Tauri SQL query result shape for SQL \"${sql}\"`,\n  )\n}\n\nfunction convertSqlitePlaceholdersToTauri(sql: string): string {\n  let result = ``\n  let parameterIndex = 1\n  let inSingleQuote = false\n  let inDoubleQuote = false\n  let inLineComment = false\n  let inBlockComment = false\n\n  for (let index = 0; index < sql.length; index++) {\n    const currentChar = sql[index]\n    const nextChar = sql[index + 1]\n\n    if (inLineComment) {\n      result += currentChar\n      if (currentChar === `\\n`) {\n        inLineComment = false\n      }\n      continue\n    }\n\n    if (inBlockComment) {\n      result += currentChar\n      if (currentChar === `*` && nextChar === `/`) {\n        result += `/`\n        index++\n        inBlockComment = false\n      }\n      continue\n    }\n\n    if (!inSingleQuote && !inDoubleQuote) {\n      if (currentChar === `-` && nextChar === `-`) {\n        result += `--`\n        index++\n        inLineComment = true\n        continue\n      }\n\n      if (currentChar === `/` && nextChar === `*`) {\n        result += `/*`\n        index++\n        inBlockComment = true\n        continue\n      }\n    }\n\n    if (currentChar === `'` && !inDoubleQuote) {\n      result += currentChar\n      if (inSingleQuote && nextChar === `'`) {\n        result += `'`\n        index++\n        continue\n      }\n      inSingleQuote = !inSingleQuote\n      continue\n    }\n\n    if (currentChar === `\"` && !inSingleQuote) {\n      result += currentChar\n      if (inDoubleQuote && nextChar === `\"`) {\n        result += `\"`\n        index++\n        continue\n      }\n      inDoubleQuote = !inDoubleQuote\n      continue\n    }\n\n    if (currentChar === `?` && !inSingleQuote && !inDoubleQuote) {\n      result += `$${String(parameterIndex)}`\n      parameterIndex++\n      continue\n    }\n\n    result += currentChar\n  }\n\n  return result\n}\n\nexport class TauriSQLiteDriver implements SQLiteDriver {\n  private readonly database: TauriSQLiteDatabaseLike\n  private queue: Promise<void> = Promise.resolve()\n  private nextSavepointId = 1\n\n  constructor(options: TauriSQLiteDriverOptions) {\n    if (!isTauriSQLiteDatabaseLike(options.database)) {\n      throw new InvalidPersistedCollectionConfigError(\n        `Tauri SQLite database object must provide execute/select methods`,\n      )\n    }\n\n    this.database = options.database\n  }\n\n  async exec(sql: string): Promise<void> {\n    await this.enqueue(async () => {\n      await this.executeStatement(sql)\n    })\n  }\n\n  async query<TRow>(\n    sql: string,\n    params: ReadonlyArray<unknown> = [],\n  ): Promise<ReadonlyArray<TRow>> {\n    return this.enqueue(async () => {\n      const rows = await this.database.select<ReadonlyArray<TRow>>(\n        convertSqlitePlaceholdersToTauri(sql),\n        params.length > 0 ? [...params] : undefined,\n      )\n      return normalizeQueryRows<TRow>(rows, sql)\n    })\n  }\n\n  async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n    await this.enqueue(async () => {\n      await this.executeStatement(sql, params)\n    })\n  }\n\n  async transaction<TReturn>(\n    fn: (transactionDriver: SQLiteDriver) => Promise<TReturn>,\n  ): Promise<TReturn> {\n    assertTransactionCallbackHasDriverArg(fn)\n    return this.transactionWithDriver(fn)\n  }\n\n  async transactionWithDriver<TReturn>(\n    fn: (transactionDriver: SQLiteDriver) => Promise<TReturn>,\n  ): Promise<TReturn> {\n    return this.enqueue(async () => {\n      await this.executeStatement(`BEGIN IMMEDIATE`)\n      const transactionDriver = this.createTransactionDriver()\n\n      try {\n        const result = await fn(transactionDriver)\n        await this.executeStatement(`COMMIT`)\n        return result\n      } catch (error) {\n        try {\n          await this.executeStatement(`ROLLBACK`)\n        } catch {\n          // Keep the original transaction error as the primary failure.\n        }\n        throw error\n      }\n    })\n  }\n\n  async close(): Promise<void> {\n    if (typeof this.database.close !== `function`) {\n      return\n    }\n\n    await Promise.resolve(this.database.close(this.database.path))\n  }\n\n  getDatabase(): TauriSQLiteDatabaseLike {\n    return this.database\n  }\n\n  private async executeStatement(\n    sql: string,\n    params: ReadonlyArray<unknown> = [],\n  ): Promise<void> {\n    await this.database.execute(\n      convertSqlitePlaceholdersToTauri(sql),\n      params.length > 0 ? [...params] : undefined,\n    )\n  }\n\n  private enqueue<TReturn>(\n    operation: () => Promise<TReturn>,\n  ): Promise<TReturn> {\n    const queuedOperation = this.queue.then(operation, operation)\n    this.queue = queuedOperation.then(\n      () => undefined,\n      () => undefined,\n    )\n    return queuedOperation\n  }\n\n  private createTransactionDriver(): SQLiteDriver {\n    const transactionDriver: SQLiteDriver = {\n      exec: async (sql) => {\n        await this.executeStatement(sql)\n      },\n      query: async <TRow>(\n        sql: string,\n        params: ReadonlyArray<unknown> = [],\n      ): Promise<ReadonlyArray<TRow>> => {\n        const rows = await this.database.select<ReadonlyArray<TRow>>(\n          convertSqlitePlaceholdersToTauri(sql),\n          params.length > 0 ? [...params] : undefined,\n        )\n        return normalizeQueryRows<TRow>(rows, sql)\n      },\n      run: async (sql, params = []) => {\n        await this.executeStatement(sql, params)\n      },\n      transaction: async <TReturn>(\n        fn: (transactionDriver: SQLiteDriver) => Promise<TReturn>,\n      ): Promise<TReturn> => {\n        assertTransactionCallbackHasDriverArg(fn)\n        return this.runNestedTransaction(transactionDriver, fn)\n      },\n      transactionWithDriver: async <TReturn>(\n        fn: (transactionDriver: SQLiteDriver) => Promise<TReturn>,\n      ): Promise<TReturn> => this.runNestedTransaction(transactionDriver, fn),\n    }\n\n    return transactionDriver\n  }\n\n  private async runNestedTransaction<TReturn>(\n    transactionDriver: SQLiteDriver,\n    fn: (transactionDriver: SQLiteDriver) => Promise<TReturn>,\n  ): Promise<TReturn> {\n    const savepointName = `tsdb_sp_${this.nextSavepointId}`\n    this.nextSavepointId++\n    await this.executeStatement(`SAVEPOINT ${savepointName}`)\n\n    try {\n      const result = await fn(transactionDriver)\n      await this.executeStatement(`RELEASE SAVEPOINT ${savepointName}`)\n      return result\n    } catch (error) {\n      await this.executeStatement(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n      await this.executeStatement(`RELEASE SAVEPOINT ${savepointName}`)\n      throw error\n    }\n  }\n}\n\nexport function createTauriSQLiteDriver(\n  options: TauriSQLiteDriverOptions,\n): TauriSQLiteDriver {\n  return new TauriSQLiteDriver(options)\n}\n"],"names":["InvalidPersistedCollectionConfigError"],"mappings":"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"}