linshenkx/prompt-optimizer · error · StorageError

Database transaction error for key ${key}: ${error.message}.

Error message

Database transaction error for key ${key}: ${error.message}. Please try again.

What it means

Specialized StorageError from _performAtomicUpdate when Dexie raises a PrematureCommitError — Dexie's error for code that 'commits' a transaction by doing non-transactional or async-incompatible operations inside it. The message embeds the key and the original error and asks the user to retry.

Source

Thrown at packages/core/src/services/storage/dexieStorageProvider.ts:308

          // 写入新值
          await tx.table('storage').put({
            key,
            value: JSON.stringify(newValue),
            timestamp: Date.now()
          });
        } catch (innerError) {
          // 事务内部错误,让事务回滚
          console.error(`Transaction operation failed (${key}):`, innerError);
          throw innerError;
        }
      });
    } catch (error) {
      console.error(`Atomic update failed (${key}):`, error);

      // 如果是Dexie事务错误,提供更详细的错误信息
      if (this.isError(error) && error.name === 'PrematureCommitError') {
        throw new StorageError(
          `Database transaction error for key ${key}: ${error.message}. Please try again.`,
          'write',
        );
      }

      throw new StorageError(`Failed to perform atomic update: ${key}`, 'write');
    }
  }

  /**
   * 批量更新操作
   */
  async batchUpdate(operations: Array<{
    key: string;
    operation: 'set' | 'remove';
    value?: string;
  }>): Promise<void> {
    await this.initialize();

View on GitHub (pinned to 3e677b1d9f)

Solutions

  1. Make the update callback synchronous with respect to non-Dexie async work: compute values outside, only do Dexie ops inside
  2. Pre-fetch anything async before calling update() and pass results in via closure
  3. Retry the operation once on this specific message, since PrematureCommitError is often transient under contention

Example fix

// before
await provider.update('k', async (old) => {
  const extra = await fetch('/api/x').then(r => r.json()); // kills the transaction
  return merge(old, extra);
});

// after
const extra = await fetch('/api/x').then(r => r.json());
await provider.update('k', (old) => merge(old, extra));
Defensive patterns

Strategy: retry

Validate before calling

// Do all non-Dexie async work BEFORE the atomic update
const external = await loadExternalData();
await provider.update(key, (old) => merge(old, external)); // callback stays Dexie-only/sync

Type guard

const isPrematureCommit = (e: unknown): e is StorageError =>
  e instanceof StorageError && /PrematureCommit|transaction error/i.test(String((e as Error).message));

Try / catch

try { await provider.update(key, fn); } catch (e) { if (isPrematureCommit(e)) { await sleep(100); return provider.update(key, fn); } throw e; }

Prevention

When it happens

Trigger: The update function passed to update() performs operations Dexie cannot include in the transaction: awaiting non-Dexie promises, calling other async APIs, or opening new transactions inside the callback, triggering PrematureCommitError inside db.transaction().

Common situations: Calling external APIs, timers, or other storage backends inside the atomic update callback; upgrading Dexie versions where transaction semantics tightened; doing IDB events (onsuccess handlers) manually inside the transaction.

Related errors


AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27). Data as JSON: /api/errors/b2932b87cb634ba5. Report an issue: GitHub.