/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Lifetime } from "#util/Lifetime.js"; import { MaybePromise } from "#util/Promises.js"; import { Participant } from "./Participant.js"; import { Resource } from "./Resource.js"; import { ResourceSet } from "./ResourceSet.js"; import { Status } from "./Status.js"; /** * Two-phase commit implementation. * * Transactions are either shared (for reads) or exclusive (for writes). Exclusive transactions do not block shared * transactions but state updates will not be visible until the transaction completes. * * Writes do block other writes. Transactions start automatically when a write occurs. Since this usually happens * synchronously, the best Matter.js can do is throw an error if two write transactions would conflict. However, you * can avoid this by using {@link begin} which will wait for other transactions to complete before acquiring resource * locks. * * Persistence is implemented by a list of participants. Commits are two phase. If an error throws in phase one all * participants roll back. An error in phase 2 could result in data inconsistency. * * TODO - does prevent deadlock but we should probably add a timeout for resource locking */ export interface Transaction extends Lifetime.Owner { /** * Diagnostic description of the transaction's source. */ readonly via: string; /** * The {@link Transaction.IsolationLevel} of this transaction. */ readonly isolation: Transaction.IsolationLevel; /** * The status of the transaction. */ readonly status: Status; /** * Transaction participants. */ readonly participants: Set; /** * Resources addressed by the participants. */ readonly resources: Set; /** * The transactions currently blocking this transaction, if any. */ readonly waitingOn: Iterable | undefined; /** * Listen for transaction commit or roll back. This may occur more than once for a given transaction. */ onShared(actor: () => void, once?: boolean): void; /** * Listen for {@link Transaction.status} close. */ onClose(actor: () => void): void; /** * Add {@link Resources} to the transaction. * * If the transaction is exclusive (writing) the transaction will acquire the lock on each {@link ResourceType}, * waiting for other writers to finish if necessary. */ addResources(...resources: Resource[]): Promise; /** * Add {@link ResourceType}s to the transaction synchronously. * * Unlike {@link addResources}, this method will throw an error if the transaction is exclusive and the resources * cannot be locked. */ addResourcesSync(...resources: Resource[]): void; /** * Begin an exclusive transaction. * * Transactions begin automatically on write but there are a few reasons you may want to use this method to start an * exclusive transaction explicitly: * * 1. Automatic transactions are started in a synchronous context so conflicting transactions will throw an error. * If you start a transaction, your code will await any transaction that would otherwise throw an error. * * 2. Transaction isolation means your view of data may become stale if a write occurs in another transaction. * Once you start a transaction you block other writers so can be assured you're dealing with newest state. * * 3. Say transaction A has an exclusive lock on resource 1 and awaits resource 2. Transaction B has an exclusive * lock on resource 2. Transaction B cannot then await resource 1 without causing a deadlock. Matter.js will * detect the deadlock and throw an error. One way to prevent this is to begin a transaction and acquire locks * in a specific order. * * None of the issues above are likely and are probably not a concern for your application. If you do encounter * these issues the error message will suggest solutions. */ begin(): Promise; /** * Begin an exclusive transaction in a synchronous context. * * Unlike {@link begin}, this method will throw an error if any participant has already joined an exclusive * transaction. */ beginSync(): void; /** * Add {@link ParticipantType}s to the transaction. */ addParticipants(...participants: Participant[]): void; /** * Retrieve a participant with a specific role. */ getParticipant(role: {}): Participant | undefined; /** * Commit the transaction. * * Matter.js commits automatically when an interaction completes. You may commit manually to publish your changes * mid-interaction. * * After commit an exclusive transaction becomes shared and data references refresh to the most recent value. */ commit(): MaybePromise; /** * Roll back the transaction. * * Matter.js rolls back automatically when an interaction fails. You may roll back manually to undo your changes * mid-interaction. * * After rollback an exclusive transaction becomes shared and data references refresh to the most recent value. */ rollback(): MaybePromise; /** * Destroy the transaction without proper commit or rollback. * * This guarantees a synchronous resolution but will result in an error if {@link Transaction#status} is not shared, * read-only or destroyed. */ [Symbol.dispose](): void; /** * Wait for a set of transactions to complete. * * @param others the set of transactions to await; cleared on return */ waitFor(others: Set): Promise; } type StatusType = Status; type ResourceType = Resource; type ResourceSetType = ResourceSet; type ParticipantType = Participant; export declare const Transaction: { /** * Perform a transactional operation. * * This creates a read/write transaction scoped to the life of an optionally async function call. * * The transaction will commit automatically if it is exclusive (write mode) after the actor returns. * * The transaction is destroyed when {@link act} returns. You will receive an error if you access it after it is * destroyed. */ act(via: string, lifetime: Lifetime.Owner, actor: (transaction: Transaction) => MaybePromise): MaybePromise; /** * Create a transaction. * * Transactions must be closed using {@link Finalization#resolve} or {@link Finalization#reject}. * * When closed the transaction commits automatically if exclusive. */ open(via: string, lifetime: Lifetime.Owner, isolation?: Transaction.IsolationLevel): Transaction & Transaction.Finalization; Status: typeof Status; Resource: typeof Resource; [Symbol.toStringTag]: string; }; export declare namespace Transaction { type Status = StatusType; type Resource = ResourceType; type ResourceSet = ResourceSetType; type Participant = ParticipantType; interface Disposable extends Transaction, AsyncDisposable { close(): MaybePromise; } interface Finalization { /** * Finish the transaction. If {@link result} is a promise this may result in commit or rollback. */ resolve(result: T): MaybePromise>; /** * Roll back, close the transaction and throw an error. */ reject(cause: unknown): MaybePromise; } type IsolationLevel = /** * Transaction reads only committed and may enter exclusive (write) mode. */ "rw" /** * Transaction is read-only but updates are visible after commit. */ | "ro" /** * Transaction is read-only and data remains at version of first access. */ | "snapshot"; } export {}; //# sourceMappingURL=Transaction.d.ts.map