@tanstack/optimistic
Version:
Core optimistic updates library
138 lines (137 loc) • 4.36 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const deferred = require("./deferred.cjs");
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 = deferred.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;
}
}
exports.Transaction = Transaction;
exports.createTransaction = createTransaction;
exports.getActiveTransaction = getActiveTransaction;
//# sourceMappingURL=transactions.cjs.map