@@ -38,6 +38,122 @@ export interface SqliteError {
3838 message : string
3939}
4040
41+ /**
42+ * **InterruptibleTransaction**
43+ *
44+ * Represents an active interruptible transaction that can be continued, committed, or rolled back.
45+ * Provides methods to read uncommitted data and execute additional statements.
46+ */
47+ export class InterruptibleTransaction {
48+ constructor (
49+ private readonly dbPath : string ,
50+ private readonly transactionId : string
51+ ) { }
52+
53+ /**
54+ * **read**
55+ *
56+ * Read data from the database within this transaction context.
57+ * This allows you to see uncommitted writes from the current transaction.
58+ *
59+ * The query executes on the same connection as the transaction, so you can
60+ * read data that hasn't been committed yet.
61+ *
62+ * @param query - SELECT query to execute
63+ * @param bindValues - Optional parameter values
64+ * @returns Promise that resolves with query results
65+ *
66+ * @example
67+ * ```ts
68+ * const tx = await db.executeInterruptibleTransaction([
69+ * ['INSERT INTO users (name) VALUES ($1)', ['Alice']]
70+ * ]);
71+ *
72+ * const users = await tx.read<User[]>(
73+ * 'SELECT * FROM users WHERE name = $1',
74+ * ['Alice']
75+ * );
76+ * ```
77+ */
78+ async read < T > ( query : string , bindValues ?: SqlValue [ ] ) : Promise < T > {
79+ return await invoke < T > ( 'plugin:sqlite|transaction_read' , {
80+ token : { dbPath : this . dbPath , transactionId : this . transactionId } ,
81+ query,
82+ values : bindValues ?? [ ]
83+ } )
84+ }
85+
86+ /**
87+ * **continue**
88+ *
89+ * Execute additional statements within this transaction and return a new transaction handle.
90+ *
91+ * @param statements - Array of [query, values?] tuples to execute
92+ * @returns Promise that resolves with a new transaction handle
93+ *
94+ * @example
95+ * ```ts
96+ * const tx = await db.executeInterruptibleTransaction([...]);
97+ * const tx2 = await tx.continue([
98+ * ['INSERT INTO users (name) VALUES ($1)', ['Bob']]
99+ * ]);
100+ * await tx2.commit();
101+ * ```
102+ */
103+ async continue ( statements : Array < [ string , SqlValue [ ] ?] > ) : Promise < InterruptibleTransaction > {
104+ const token = await invoke < { dbPath : string ; transactionId : string } > (
105+ 'plugin:sqlite|transaction_continue' ,
106+ {
107+ token : { dbPath : this . dbPath , transactionId : this . transactionId } ,
108+ action : {
109+ type : 'Continue' ,
110+ statements : statements . map ( ( [ query , values ] ) => ( {
111+ query,
112+ values : values ?? [ ]
113+ } ) )
114+ }
115+ }
116+ )
117+ return new InterruptibleTransaction ( token . dbPath , token . transactionId )
118+ }
119+
120+ /**
121+ * **commit**
122+ *
123+ * Commit this transaction and release the write lock.
124+ *
125+ * @example
126+ * ```ts
127+ * const tx = await db.executeInterruptibleTransaction([...]);
128+ * await tx.commit();
129+ * ```
130+ */
131+ async commit ( ) : Promise < void > {
132+ await invoke < void > ( 'plugin:sqlite|transaction_continue' , {
133+ token : { dbPath : this . dbPath , transactionId : this . transactionId } ,
134+ action : { type : 'Commit' }
135+ } )
136+ }
137+
138+ /**
139+ * **rollback**
140+ *
141+ * Rollback this transaction and release the write lock.
142+ *
143+ * @example
144+ * ```ts
145+ * const tx = await db.executeInterruptibleTransaction([...]);
146+ * await tx.rollback();
147+ * ```
148+ */
149+ async rollback ( ) : Promise < void > {
150+ await invoke < void > ( 'plugin:sqlite|transaction_continue' , {
151+ token : { dbPath : this . dbPath , transactionId : this . transactionId } ,
152+ action : { type : 'Rollback' }
153+ } )
154+ }
155+ }
156+
41157/**
42158 * Custom configuration for SQLite database connection
43159 */
@@ -196,6 +312,10 @@ export default class Database {
196312 * Executes multiple write statements atomically within a transaction.
197313 * All statements either succeed together or fail together.
198314 *
315+ * **Use this method** when you have a batch of writes to execute and don't need to
316+ * read data mid-transaction. For transactions that require reading uncommitted data
317+ * to decide how to proceed, use `executeInterruptibleTransaction()` instead.
318+ *
199319 * The function automatically:
200320 * - Begins a transaction (BEGIN)
201321 * - Executes all statements in order
@@ -365,6 +485,69 @@ export default class Database {
365485 return success
366486 }
367487
488+ /**
489+ * **executeInterruptibleTransaction**
490+ *
491+ * Begins an interruptible transaction for cases where you need to **read data mid-transaction
492+ * to decide how to proceed**. For example, inserting a record and then reading its
493+ * generated ID or computed values before continuing with related writes.
494+ *
495+ * The transaction remains open, holding a write lock on the database, until you
496+ * call `commit()` or `rollback()` on the returned transaction handle.
497+ *
498+ * **Use this method when:**
499+ * - You need to read back generated IDs (e.g., AUTOINCREMENT columns)
500+ * - You need to see computed values (e.g., triggers, default values)
501+ * - Your next writes depend on data from earlier writes in the same transaction
502+ *
503+ * **Use `executeTransaction()` instead when:**
504+ * - You just need to execute a batch of writes atomically
505+ * - You know all the data upfront and don't need to read mid-transaction
506+ *
507+ * **Important:** Only one transaction can be active per database at a time. The
508+ * writer connection is held for the entire duration - keep transactions short.
509+ *
510+ * @param initialStatements - Array of [query, values?] tuples to execute initially
511+ * @returns Promise that resolves with an InterruptibleTransaction handle
512+ *
513+ * @example
514+ * ```ts
515+ * // Insert an order and read back its ID
516+ * const tx = await db.executeInterruptibleTransaction([
517+ * ['INSERT INTO orders (user_id, total) VALUES ($1, $2)', [userId, 0]]
518+ * ]);
519+ *
520+ * // Read the generated order ID
521+ * const orders = await tx.read<Array<{ id: number }>>(
522+ * 'SELECT id FROM orders WHERE user_id = $1 ORDER BY id DESC LIMIT 1',
523+ * [userId]
524+ * );
525+ * const orderId = orders[0].id;
526+ *
527+ * // Use the ID in subsequent writes
528+ * const tx2 = await tx.continue([
529+ * ['INSERT INTO order_items (order_id, product_id) VALUES ($1, $2)', [orderId, productId]]
530+ * ]);
531+ *
532+ * await tx2.commit();
533+ * ```
534+ */
535+ async executeInterruptibleTransaction (
536+ initialStatements : Array < [ string , SqlValue [ ] ?] >
537+ ) : Promise < InterruptibleTransaction > {
538+ const token = await invoke < { dbPath : string ; transactionId : string } > (
539+ 'plugin:sqlite|execute_interruptible_transaction' ,
540+ {
541+ db : this . path ,
542+ initialStatements : initialStatements . map ( ( [ query , values ] ) => ( {
543+ query,
544+ values : values ?? [ ]
545+ } ) )
546+ }
547+ )
548+ return new InterruptibleTransaction ( token . dbPath , token . transactionId )
549+ }
550+
368551 /**
369552 * **getMigrationEvents**
370553 *
0 commit comments