UNPKG

@tanstack/optimistic

Version:

Core optimistic updates library

138 lines (137 loc) 4.2 kB
import { createDeferred } from "./deferred.js"; function generateUUID() { if (typeof crypto !== `undefined` && typeof crypto.randomUUID === `function`) { return crypto.randomUUID(); } return `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === `x` ? r : r & 3 | 8; return v.toString(16); }); } const transactions = []; function createTransaction(config) { if (typeof config.mutationFn === `undefined`) { throw `mutationFn is required when creating a transaction`; } let transactionId = config.id; if (!transactionId) { transactionId = generateUUID(); } const newTransaction = new Transaction({ ...config, id: transactionId }); transactions.push(newTransaction); return newTransaction; } let transactionStack = []; function getActiveTransaction() { if (transactionStack.length > 0) { return transactionStack.slice(-1)[0]; } else { return void 0; } } function registerTransaction(tx) { transactionStack.push(tx); } function unregisterTransaction(tx) { transactionStack = transactionStack.filter((t) => t.id !== tx.id); } class Transaction { constructor(config) { this.id = config.id; this.mutationFn = config.mutationFn; this.state = `pending`; this.mutations = []; this.isPersisted = createDeferred(); this.autoCommit = config.autoCommit ?? true; this.createdAt = /* @__PURE__ */ new Date(); this.metadata = config.metadata ?? {}; } setState(newState) { this.state = newState; } mutate(callback) { if (this.state !== `pending`) { throw `You can no longer call .mutate() as the transaction is no longer pending`; } registerTransaction(this); try { callback(); } finally { unregisterTransaction(this); } if (this.autoCommit) { this.commit(); } return this; } applyMutations(mutations) { for (const newMutation of mutations) { const existingIndex = this.mutations.findIndex( (m) => m.key === newMutation.key ); if (existingIndex >= 0) { this.mutations[existingIndex] = newMutation; } else { this.mutations.push(newMutation); } } } rollback(config) { var _a; const isSecondaryRollback = (config == null ? void 0 : config.isSecondaryRollback) ?? false; if (this.state === `completed`) { throw `You can no longer call .rollback() as the transaction is already completed`; } this.setState(`failed`); if (!isSecondaryRollback) { const mutationKeys = /* @__PURE__ */ new Set(); this.mutations.forEach((m) => mutationKeys.add(m.key)); transactions.forEach( (t) => t.state === `pending` && t.mutations.some((m) => mutationKeys.has(m.key)) && t.rollback({ isSecondaryRollback: true }) ); } this.isPersisted.reject((_a = this.error) == null ? void 0 : _a.error); this.touchCollection(); return this; } // Tell collection that something has changed with the transaction touchCollection() { const hasCalled = /* @__PURE__ */ new Set(); this.mutations.forEach((mutation) => { if (!hasCalled.has(mutation.collection.id)) { mutation.collection.transactions.setState((state) => state); mutation.collection.commitPendingTransactions(); hasCalled.add(mutation.collection.id); } }); } async commit() { if (this.state !== `pending`) { throw `You can no longer call .commit() as the transaction is no longer pending`; } this.setState(`persisting`); if (this.mutations.length === 0) { this.setState(`completed`); } try { await this.mutationFn({ transaction: this }); this.setState(`completed`); this.touchCollection(); this.isPersisted.resolve(this); } catch (error) { this.error = { message: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error : new Error(String(error)) }; return this.rollback(); } return this; } } export { Transaction, createTransaction, getActiveTransaction }; //# sourceMappingURL=transactions.js.map