@photostructure/sqlite 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +65 -16
  2. package/README.md +5 -10
  3. package/binding.gyp +2 -2
  4. package/dist/index.cjs +314 -11
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +346 -89
  7. package/dist/index.d.mts +346 -89
  8. package/dist/index.d.ts +346 -89
  9. package/dist/index.mjs +311 -10
  10. package/dist/index.mjs.map +1 -1
  11. package/package.json +72 -63
  12. package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
  13. package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
  14. package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
  15. package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
  16. package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
  17. package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
  18. package/prebuilds/test_extension.so +0 -0
  19. package/prebuilds/win32-arm64/@photostructure+sqlite.glibc.node +0 -0
  20. package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
  21. package/src/aggregate_function.cpp +222 -114
  22. package/src/aggregate_function.h +5 -6
  23. package/src/binding.cpp +30 -21
  24. package/src/enhance.ts +552 -0
  25. package/src/index.ts +84 -9
  26. package/src/shims/node_errors.h +34 -15
  27. package/src/shims/sqlite_errors.h +34 -8
  28. package/src/sql-tag-store.ts +6 -9
  29. package/src/sqlite_impl.cpp +1044 -394
  30. package/src/sqlite_impl.h +46 -7
  31. package/src/transaction.ts +178 -0
  32. package/src/types/database-sync-instance.ts +6 -40
  33. package/src/types/pragma-options.ts +23 -0
  34. package/src/types/statement-sync-instance.ts +38 -12
  35. package/src/types/transaction.ts +72 -0
  36. package/src/upstream/node_sqlite.cc +143 -43
  37. package/src/upstream/node_sqlite.h +15 -11
  38. package/src/upstream/sqlite3.c +102 -58
  39. package/src/upstream/sqlite3.h +5 -5
  40. package/src/user_function.cpp +138 -141
  41. package/src/user_function.h +3 -0
package/src/sqlite_impl.h CHANGED
@@ -14,6 +14,7 @@
14
14
  #include <stdexcept>
15
15
  #include <string>
16
16
  #include <thread>
17
+ #include <unordered_map>
17
18
 
18
19
  // Include our shims
19
20
  #include "shims/base_object.h"
@@ -31,6 +32,7 @@ class DatabaseSync;
31
32
  class StatementSync;
32
33
  class StatementSyncIterator;
33
34
  class Session;
35
+ class BackupJob;
34
36
 
35
37
  // Per-worker instance data
36
38
  struct AddonData {
@@ -43,8 +45,19 @@ struct AddonData {
43
45
  Napi::FunctionReference statementSyncConstructor;
44
46
  Napi::FunctionReference statementSyncIteratorConstructor;
45
47
  Napi::FunctionReference sessionConstructor;
48
+
49
+ // Cached Object.create function for creating objects with null prototype
50
+ Napi::FunctionReference objectCreateFn;
51
+
52
+ // ValueStorage data
53
+ std::mutex value_storage_mutex;
54
+ std::unordered_map<int32_t, Napi::Reference<Napi::Value>> value_storage;
55
+ std::atomic<int32_t> next_value_id{0};
46
56
  };
47
57
 
58
+ // Helper to create an object with null prototype (matches Node.js behavior)
59
+ Napi::Object CreateObjectWithNullPrototype(Napi::Env env);
60
+
48
61
  // Worker thread support functions
49
62
  void RegisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
50
63
  void UnregisterDatabaseInstance(Napi::Env env, DatabaseSync *database);
@@ -103,6 +116,9 @@ public:
103
116
  bool get_enable_defensive() const { return defensive_; }
104
117
  void set_enable_defensive(bool flag) { defensive_ = flag; }
105
118
 
119
+ bool get_open_uri() const { return open_uri_; }
120
+ void set_open_uri(bool flag) { open_uri_ = flag; }
121
+
106
122
  private:
107
123
  std::string location_;
108
124
  bool read_only_ = false;
@@ -113,7 +129,8 @@ private:
113
129
  bool return_arrays_ = false;
114
130
  bool allow_bare_named_params_ = true;
115
131
  bool allow_unknown_named_params_ = false;
116
- bool defensive_ = false;
132
+ bool defensive_ = true; // Node.js v25+ defaults to true
133
+ bool open_uri_ = false;
117
134
  };
118
135
 
119
136
  // Main database class
@@ -175,6 +192,12 @@ public:
175
192
  void RemoveSession(Session *session);
176
193
  void DeleteAllSessions();
177
194
 
195
+ // Backup management - prevents use-after-free when database is closed
196
+ // during an active backup operation
197
+ void AddBackup(BackupJob *backup);
198
+ void RemoveBackup(BackupJob *backup);
199
+ void FinalizeBackups();
200
+
178
201
  // Error handling for user functions
179
202
  void SetIgnoreNextSQLiteError(bool ignore) {
180
203
  ignore_next_sqlite_error_ = ignore;
@@ -192,7 +215,7 @@ public:
192
215
  return deferred_authorizer_exception_.has_value();
193
216
  }
194
217
  const std::string &GetDeferredAuthorizerException() const {
195
- return deferred_authorizer_exception_.value();
218
+ return *deferred_authorizer_exception_;
196
219
  }
197
220
 
198
221
  private:
@@ -207,6 +230,8 @@ private:
207
230
  std::map<std::string, std::unique_ptr<StatementSync>> prepared_statements_;
208
231
  std::set<Session *> sessions_; // Track all active sessions
209
232
  mutable std::mutex sessions_mutex_; // Protect sessions_ for thread safety
233
+ std::set<BackupJob *> backups_; // Track all active backup jobs
234
+ mutable std::mutex backups_mutex_; // Protect backups_ for thread safety
210
235
  std::thread::id creation_thread_;
211
236
  napi_env env_; // Store for cleanup purposes
212
237
  bool ignore_next_sqlite_error_ = false; // For user function error handling
@@ -218,12 +243,16 @@ private:
218
243
  // Authorization callback storage
219
244
  std::unique_ptr<Napi::FunctionReference> authorizer_callback_;
220
245
 
246
+ // Environment cleanup hook - called before environment teardown
247
+ static void CleanupHook(void *arg);
248
+
221
249
  // Store database-level defaults for statement options
222
250
  DatabaseOpenConfiguration config_;
223
251
 
224
252
  bool ValidateThread(Napi::Env env) const;
225
253
  friend class Session;
226
254
  friend class StatementSync;
255
+ friend class BackupJob;
227
256
  };
228
257
 
229
258
  // Statement class
@@ -268,10 +297,6 @@ private:
268
297
  void Reset();
269
298
 
270
299
  DatabaseSync *database_ = nullptr;
271
- // Strong reference to database object to prevent GC while statement exists.
272
- // This fixes use-after-free when database is GC'd before its statements.
273
- // See: https://github.com/nodejs/node/pull/56840
274
- Napi::ObjectReference database_ref_;
275
300
  sqlite3_stmt *statement_ = nullptr;
276
301
  std::string source_sql_;
277
302
  bool finalized_ = false;
@@ -287,6 +312,7 @@ private:
287
312
  std::optional<std::map<std::string, std::string>> bare_named_params_;
288
313
 
289
314
  bool ValidateThread(Napi::Env env) const;
315
+ friend class DatabaseSync;
290
316
  friend class StatementSyncIterator;
291
317
  };
292
318
 
@@ -302,6 +328,7 @@ public:
302
328
  // Iterator methods
303
329
  Napi::Value Next(const Napi::CallbackInfo &info);
304
330
  Napi::Value Return(const Napi::CallbackInfo &info);
331
+ Napi::Value ToArray(const Napi::CallbackInfo &info);
305
332
 
306
333
  private:
307
334
  void SetStatement(StatementSync *stmt);
@@ -363,10 +390,15 @@ public:
363
390
 
364
391
  Napi::Promise GetPromise() { return deferred_.Promise(); }
365
392
 
366
- private:
393
+ public:
394
+ // Cleanup is called by FinalizeBackups when database is closing
367
395
  void Cleanup();
396
+ // Called by FinalizeBackups to prevent double-unregistration in destructor
397
+ void ClearSource() { source_ = nullptr; }
368
398
 
399
+ private:
369
400
  DatabaseSync *source_;
401
+ sqlite3 *source_connection_; // Captured at construction to avoid race
370
402
  std::string destination_path_;
371
403
  std::string source_db_;
372
404
  std::string dest_db_;
@@ -381,6 +413,13 @@ private:
381
413
  Napi::FunctionReference progress_func_;
382
414
  Napi::Promise::Deferred deferred_;
383
415
 
416
+ // Error from progress callback (set on main thread, checked in OnOK)
417
+ std::optional<std::string> progress_error_;
418
+
419
+ // Environment cleanup hook - called before environment teardown
420
+ static void CleanupHook(void *arg);
421
+ napi_env env_;
422
+
384
423
  static std::atomic<int> active_jobs_;
385
424
  static std::mutex active_jobs_mutex_;
386
425
  static std::set<BackupJob *> active_job_instances_;
@@ -0,0 +1,178 @@
1
+ import type { DatabaseSyncInstance } from "./types/database-sync-instance";
2
+ import type { TransactionFunction, TransactionMode } from "./types/transaction";
3
+
4
+ /**
5
+ * Internal counter for generating unique savepoint names.
6
+ * Using a global counter ensures uniqueness even with deeply nested transactions.
7
+ */
8
+ let savepointCounter = 0;
9
+
10
+ /**
11
+ * Creates a transaction-wrapped version of a function.
12
+ *
13
+ * When the returned function is called, it will:
14
+ * 1. Begin a transaction (or create a savepoint if already in a transaction)
15
+ * 2. Execute the wrapped function
16
+ * 3. Commit the transaction (or release the savepoint) on success
17
+ * 4. Rollback the transaction (or rollback to savepoint) on error
18
+ *
19
+ * The wrapped function **must not** return a Promise. SQLite transactions are
20
+ * synchronous, and allowing async operations would leave the transaction open
21
+ * across event loop ticks, which is dangerous and can cause deadlocks.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const db = new DatabaseSync(':memory:');
26
+ * db.exec('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
27
+ *
28
+ * const insert = db.prepare('INSERT INTO items (name) VALUES (?)');
29
+ *
30
+ * // Create a transaction function
31
+ * const insertMany = db.transaction((names: string[]) => {
32
+ * for (const name of names) {
33
+ * insert.run(name);
34
+ * }
35
+ * return names.length;
36
+ * });
37
+ *
38
+ * // Execute - automatically wrapped in BEGIN/COMMIT
39
+ * const count = insertMany(['Alice', 'Bob', 'Charlie']);
40
+ * console.log(count); // 3
41
+ *
42
+ * // If an error occurs, automatically rolls back
43
+ * try {
44
+ * insertMany(['Dave', 'FAIL']); // Assume this throws
45
+ * } catch (e) {
46
+ * // Transaction was rolled back, Dave was not inserted
47
+ * }
48
+ *
49
+ * // Use different transaction modes
50
+ * insertMany.immediate(['Eve']); // BEGIN IMMEDIATE
51
+ * insertMany.exclusive(['Frank']); // BEGIN EXCLUSIVE
52
+ * ```
53
+ *
54
+ * @param db - The database connection
55
+ * @param fn - The function to wrap in a transaction
56
+ * @returns A transaction function with `.deferred`, `.immediate`, and `.exclusive` variants
57
+ */
58
+ export function createTransaction<F extends (...args: any[]) => any>(
59
+ db: DatabaseSyncInstance,
60
+ fn: F,
61
+ ): TransactionFunction<F> {
62
+ if (typeof fn !== "function") {
63
+ throw new TypeError("Expected first argument to be a function");
64
+ }
65
+
66
+ // Create all four variants
67
+ const variants = {
68
+ deferred: createVariant(db, fn, "deferred"),
69
+ immediate: createVariant(db, fn, "immediate"),
70
+ exclusive: createVariant(db, fn, "exclusive"),
71
+ };
72
+
73
+ // The default function uses DEFERRED mode (SQLite's default)
74
+ const defaultFn = variants.deferred as TransactionFunction<F>;
75
+
76
+ // Set up the variant properties on each function
77
+ // Each variant has access to all other variants
78
+ for (const variant of Object.values(variants)) {
79
+ Object.defineProperties(variant, {
80
+ deferred: { value: variants.deferred, enumerable: true },
81
+ immediate: { value: variants.immediate, enumerable: true },
82
+ exclusive: { value: variants.exclusive, enumerable: true },
83
+ database: { value: db, enumerable: true },
84
+ });
85
+ }
86
+
87
+ return defaultFn;
88
+ }
89
+
90
+ /**
91
+ * Creates a single transaction variant for a specific mode.
92
+ */
93
+ function createVariant<F extends (...args: any[]) => any>(
94
+ db: DatabaseSyncInstance,
95
+ fn: F,
96
+ mode: TransactionMode,
97
+ ): (...args: Parameters<F>) => ReturnType<F> {
98
+ const beginStatement = getBeginStatement(mode);
99
+
100
+ return function transactionWrapper(
101
+ this: unknown,
102
+ ...args: Parameters<F>
103
+ ): ReturnType<F> {
104
+ // Check if we're already in a transaction (nested transaction)
105
+ const isNested = db.isTransaction;
106
+
107
+ let begin: string;
108
+ let commit: string;
109
+ let rollback: string;
110
+
111
+ if (isNested) {
112
+ // Use savepoints for nested transactions
113
+ // The savepoint name uses backticks to allow special characters
114
+ // and a counter to ensure uniqueness
115
+ const savepointName = `\`_txn_${++savepointCounter}\``;
116
+ begin = `SAVEPOINT ${savepointName}`;
117
+ commit = `RELEASE ${savepointName}`;
118
+ rollback = `ROLLBACK TO ${savepointName}`;
119
+ } else {
120
+ // Top-level transaction
121
+ begin = beginStatement;
122
+ commit = "COMMIT";
123
+ rollback = "ROLLBACK";
124
+ }
125
+
126
+ // Begin the transaction or savepoint
127
+ db.exec(begin);
128
+
129
+ try {
130
+ // Execute the wrapped function
131
+ const result = fn.apply(this, args);
132
+
133
+ // Check for promises - async functions break transaction semantics
134
+ if (result !== null && typeof result === "object" && "then" in result) {
135
+ throw new TypeError(
136
+ "Transaction function must not return a Promise. " +
137
+ "SQLite transactions are synchronous and cannot span across async operations. " +
138
+ "Use synchronous code within transactions.",
139
+ );
140
+ }
141
+
142
+ // Commit the transaction or release the savepoint
143
+ db.exec(commit);
144
+
145
+ return result;
146
+ } catch (error) {
147
+ // Only attempt rollback if we're still in a transaction
148
+ // (SQLite may have already rolled back due to constraint violations, etc.)
149
+ if (db.isTransaction) {
150
+ db.exec(rollback);
151
+ // For nested transactions, we also need to release the savepoint
152
+ // after rolling back to it (the savepoint still exists after ROLLBACK TO)
153
+ if (isNested) {
154
+ db.exec(commit);
155
+ }
156
+ }
157
+
158
+ throw error;
159
+ }
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Returns the appropriate BEGIN statement for a transaction mode.
165
+ */
166
+ function getBeginStatement(mode: TransactionMode): string {
167
+ switch (mode) {
168
+ case "deferred":
169
+ return "BEGIN DEFERRED";
170
+ case "immediate":
171
+ return "BEGIN IMMEDIATE";
172
+ case "exclusive":
173
+ return "BEGIN EXCLUSIVE";
174
+ default:
175
+ // TypeScript should catch this, but just in case
176
+ throw new Error(`Unknown transaction mode: ${mode}`);
177
+ }
178
+ }
@@ -88,13 +88,18 @@ export interface DatabaseSyncInstance {
88
88
  * ```
89
89
  */
90
90
  createTagStore(capacity?: number): SQLTagStoreInstance;
91
+
91
92
  /**
92
93
  * Apply a changeset to the database.
93
94
  * @param changeset The changeset data to apply.
94
95
  * @param options Optional configuration for applying the changeset.
95
96
  * @returns true if successful, false if aborted.
96
97
  */
97
- applyChangeset(changeset: Buffer, options?: ChangesetApplyOptions): boolean;
98
+ applyChangeset(
99
+ changeset: Uint8Array,
100
+ options?: ChangesetApplyOptions,
101
+ ): boolean;
102
+
98
103
  /**
99
104
  * Enables or disables the loading of SQLite extensions.
100
105
  * @param enable If true, enables extension loading. If false, disables it.
@@ -159,45 +164,6 @@ export interface DatabaseSyncInstance {
159
164
  | null,
160
165
  ): void;
161
166
 
162
- /**
163
- * Makes a backup of the database. This method abstracts the sqlite3_backup_init(),
164
- * sqlite3_backup_step() and sqlite3_backup_finish() functions.
165
- *
166
- * The backed-up database can be used normally during the backup process. Mutations
167
- * coming from the same connection will be reflected in the backup right away.
168
- * However, mutations from other connections will cause the backup process to restart.
169
- *
170
- * @param path The path where the backup will be created. If the file already exists, the contents will be overwritten.
171
- * @param options Optional configuration for the backup operation.
172
- * @param options.rate Number of pages to be transmitted in each batch of the backup. @default 100
173
- * @param options.source Name of the source database. This can be 'main' (the default primary database) or any other database that have been added with ATTACH DATABASE. @default 'main'
174
- * @param options.target Name of the target database. This can be 'main' (the default primary database) or any other database that have been added with ATTACH DATABASE. @default 'main'
175
- * @param options.progress Callback function that will be called with the number of pages copied and the total number of pages.
176
- * @returns A promise that resolves when the backup is completed and rejects if an error occurs.
177
- *
178
- * @example
179
- * // Basic backup
180
- * await db.backup('./backup.db');
181
- *
182
- * @example
183
- * // Backup with progress
184
- * await db.backup('./backup.db', {
185
- * rate: 10,
186
- * progress: ({ totalPages, remainingPages }) => {
187
- * console.log(`Progress: ${totalPages - remainingPages}/${totalPages}`);
188
- * }
189
- * });
190
- */
191
- backup(
192
- path: string | Buffer | URL,
193
- options?: {
194
- rate?: number;
195
- source?: string;
196
- target?: string;
197
- progress?: (info: { totalPages: number; remainingPages: number }) => void;
198
- },
199
- ): Promise<number>;
200
-
201
167
  /** Dispose of the database resources using the explicit resource management protocol. */
202
168
  [Symbol.dispose](): void;
203
169
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Options for the pragma() method.
3
+ *
4
+ * @see https://sqlite.org/pragma.html
5
+ */
6
+ export interface PragmaOptions {
7
+ /**
8
+ * When true, returns only the first column of the first row.
9
+ * This is useful for pragmas that return a single value.
10
+ *
11
+ * @default false
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Without simple: returns [{ cache_size: -16000 }]
16
+ * db.pragma('cache_size');
17
+ *
18
+ * // With simple: returns -16000
19
+ * db.pragma('cache_size', { simple: true });
20
+ * ```
21
+ */
22
+ readonly simple?: boolean;
23
+ }
@@ -1,3 +1,39 @@
1
+ /**
2
+ * Metadata about a column in a prepared statement's result set.
3
+ * Matches Node.js sqlite module's StatementColumnMetadata.
4
+ */
5
+ export interface StatementColumnMetadata {
6
+ /**
7
+ * The unaliased name of the column in the origin table, or `null` if the
8
+ * column is the result of an expression or subquery.
9
+ * This property is the result of `sqlite3_column_origin_name()`.
10
+ */
11
+ column: string | null;
12
+ /**
13
+ * The unaliased name of the origin database, or `null` if the column is
14
+ * the result of an expression or subquery.
15
+ * This property is the result of `sqlite3_column_database_name()`.
16
+ */
17
+ database: string | null;
18
+ /**
19
+ * The name assigned to the column in the result set of a SELECT statement.
20
+ * This property is the result of `sqlite3_column_name()`.
21
+ */
22
+ name: string;
23
+ /**
24
+ * The unaliased name of the origin table, or `null` if the column is
25
+ * the result of an expression or subquery.
26
+ * This property is the result of `sqlite3_column_table_name()`.
27
+ */
28
+ table: string | null;
29
+ /**
30
+ * The declared data type of the column, or `null` if the column is
31
+ * the result of an expression or subquery.
32
+ * This property is the result of `sqlite3_column_decltype()`.
33
+ */
34
+ type: string | null;
35
+ }
36
+
1
37
  /**
2
38
  * A prepared SQL statement that can be executed multiple times with different parameters.
3
39
  * This interface represents an instance of the StatementSync class.
@@ -7,8 +43,6 @@ export interface StatementSyncInstance {
7
43
  readonly sourceSQL: string;
8
44
  /** The expanded SQL string with bound parameters, if expandedSQL option was set. */
9
45
  readonly expandedSQL: string | undefined;
10
- /** Whether this statement has been finalized. */
11
- readonly finalized: boolean;
12
46
  /**
13
47
  * This method executes a prepared statement and returns an object.
14
48
  * @param parameters Optional named and anonymous parameters to bind to the statement.
@@ -59,15 +93,7 @@ export interface StatementSyncInstance {
59
93
  setReturnArrays(returnArrays: boolean): void;
60
94
  /**
61
95
  * Returns an array of objects, each representing a column in the statement's result set.
62
- * Each object has a 'name' property for the column name and a 'type' property for the SQLite type.
63
- * @returns Array of column metadata objects.
64
- */
65
- columns(): Array<{ name: string; type?: string }>;
66
- /**
67
- * Finalizes the prepared statement and releases its resources.
68
- * Called automatically by Symbol.dispose.
96
+ * @returns Array of column metadata objects with name, column, database, table, and type.
69
97
  */
70
- finalize(): void;
71
- /** Dispose of the statement resources using the explicit resource management protocol. */
72
- [Symbol.dispose](): void;
98
+ columns(): StatementColumnMetadata[];
73
99
  }
@@ -0,0 +1,72 @@
1
+ import type { DatabaseSyncInstance } from "./database-sync-instance";
2
+
3
+ /**
4
+ * Transaction isolation modes supported by SQLite.
5
+ *
6
+ * @see https://sqlite.org/lang_transaction.html
7
+ */
8
+ export type TransactionMode = "deferred" | "immediate" | "exclusive";
9
+
10
+ /**
11
+ * A function wrapped in a transaction. When called, it automatically:
12
+ * - Begins a transaction (or savepoint if nested)
13
+ * - Executes the wrapped function
14
+ * - Commits on success, rolls back on error
15
+ *
16
+ * The function also has variants for different transaction modes accessible as
17
+ * properties (`.deferred`, `.immediate`, `.exclusive`).
18
+ *
19
+ * **Note:** Each variant is itself a `TransactionFunction` with circular
20
+ * references to all other variants. This matches better-sqlite3's behavior
21
+ * where you can chain variants like `txn.deferred.immediate()` - the last
22
+ * variant in the chain determines the actual transaction mode used.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const insertMany = db.transaction((items: Item[]) => {
27
+ * for (const item of items) insert.run(item);
28
+ * return items.length;
29
+ * });
30
+ *
31
+ * // Use default mode (DEFERRED)
32
+ * insertMany([{ id: 1 }, { id: 2 }]);
33
+ *
34
+ * // Use IMMEDIATE mode for write transactions
35
+ * insertMany.immediate([{ id: 3 }, { id: 4 }]);
36
+ *
37
+ * // Use EXCLUSIVE mode for exclusive access
38
+ * insertMany.exclusive([{ id: 5 }, { id: 6 }]);
39
+ * ```
40
+ */
41
+ export interface TransactionFunction<F extends (...args: any[]) => any> {
42
+ /**
43
+ * Execute the wrapped function within a transaction using the default mode (DEFERRED).
44
+ */
45
+ (...args: Parameters<F>): ReturnType<F>;
46
+
47
+ /**
48
+ * Execute with DEFERRED transaction mode.
49
+ * The database lock is not acquired until the first read or write operation.
50
+ * This is the default SQLite behavior.
51
+ */
52
+ readonly deferred: TransactionFunction<F>;
53
+
54
+ /**
55
+ * Execute with IMMEDIATE transaction mode.
56
+ * Acquires a write lock immediately, blocking other writers.
57
+ * Recommended for transactions that will write to prevent SQLITE_BUSY errors.
58
+ */
59
+ readonly immediate: TransactionFunction<F>;
60
+
61
+ /**
62
+ * Execute with EXCLUSIVE transaction mode.
63
+ * Acquires an exclusive lock immediately, blocking all other connections.
64
+ * Use sparingly as it prevents all concurrent access.
65
+ */
66
+ readonly exclusive: TransactionFunction<F>;
67
+
68
+ /**
69
+ * The database connection this transaction function is bound to.
70
+ */
71
+ readonly database: DatabaseSyncInstance;
72
+ }