@photostructure/sqlite 0.2.1 → 0.4.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 (43) hide show
  1. package/CHANGELOG.md +61 -15
  2. package/README.md +5 -4
  3. package/binding.gyp +2 -2
  4. package/dist/index.cjs +159 -12
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +286 -91
  7. package/dist/index.d.mts +286 -91
  8. package/dist/index.d.ts +286 -91
  9. package/dist/index.mjs +156 -11
  10. package/dist/index.mjs.map +1 -1
  11. package/package.json +74 -65
  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/scripts/prebuild-linux-glibc.sh +6 -4
  22. package/src/aggregate_function.cpp +222 -114
  23. package/src/aggregate_function.h +5 -6
  24. package/src/binding.cpp +30 -21
  25. package/src/enhance.ts +228 -0
  26. package/src/index.ts +83 -9
  27. package/src/shims/node_errors.h +34 -15
  28. package/src/shims/sqlite_errors.h +34 -8
  29. package/src/sql-tag-store.ts +7 -10
  30. package/src/sqlite_impl.cpp +1044 -394
  31. package/src/sqlite_impl.h +46 -7
  32. package/src/transaction.ts +178 -0
  33. package/src/types/database-sync-instance.ts +6 -40
  34. package/src/types/pragma-options.ts +23 -0
  35. package/src/types/sql-tag-store-instance.ts +1 -1
  36. package/src/types/statement-sync-instance.ts +38 -12
  37. package/src/types/transaction.ts +72 -0
  38. package/src/upstream/node_sqlite.cc +143 -43
  39. package/src/upstream/node_sqlite.h +15 -11
  40. package/src/upstream/sqlite3.c +102 -58
  41. package/src/upstream/sqlite3.h +5 -5
  42. package/src/user_function.cpp +138 -141
  43. package/src/user_function.h +3 -0
package/dist/index.d.ts CHANGED
@@ -63,7 +63,7 @@ interface SQLTagStoreInstance {
63
63
  /**
64
64
  * Returns the current number of cached statements.
65
65
  */
66
- size(): number;
66
+ readonly size: number;
67
67
  /**
68
68
  * Clears all cached statements.
69
69
  */
@@ -98,6 +98,41 @@ interface SQLTagStoreInstance {
98
98
  iterate(strings: TemplateStringsArray, ...values: unknown[]): IterableIterator<unknown>;
99
99
  }
100
100
 
101
+ /**
102
+ * Metadata about a column in a prepared statement's result set.
103
+ * Matches Node.js sqlite module's StatementColumnMetadata.
104
+ */
105
+ interface StatementColumnMetadata {
106
+ /**
107
+ * The unaliased name of the column in the origin table, or `null` if the
108
+ * column is the result of an expression or subquery.
109
+ * This property is the result of `sqlite3_column_origin_name()`.
110
+ */
111
+ column: string | null;
112
+ /**
113
+ * The unaliased name of the origin database, or `null` if the column is
114
+ * the result of an expression or subquery.
115
+ * This property is the result of `sqlite3_column_database_name()`.
116
+ */
117
+ database: string | null;
118
+ /**
119
+ * The name assigned to the column in the result set of a SELECT statement.
120
+ * This property is the result of `sqlite3_column_name()`.
121
+ */
122
+ name: string;
123
+ /**
124
+ * The unaliased name of the origin table, or `null` if the column is
125
+ * the result of an expression or subquery.
126
+ * This property is the result of `sqlite3_column_table_name()`.
127
+ */
128
+ table: string | null;
129
+ /**
130
+ * The declared data type of the column, or `null` if the column is
131
+ * the result of an expression or subquery.
132
+ * This property is the result of `sqlite3_column_decltype()`.
133
+ */
134
+ type: string | null;
135
+ }
101
136
  /**
102
137
  * A prepared SQL statement that can be executed multiple times with different parameters.
103
138
  * This interface represents an instance of the StatementSync class.
@@ -107,8 +142,6 @@ interface StatementSyncInstance {
107
142
  readonly sourceSQL: string;
108
143
  /** The expanded SQL string with bound parameters, if expandedSQL option was set. */
109
144
  readonly expandedSQL: string | undefined;
110
- /** Whether this statement has been finalized. */
111
- readonly finalized: boolean;
112
145
  /**
113
146
  * This method executes a prepared statement and returns an object.
114
147
  * @param parameters Optional named and anonymous parameters to bind to the statement.
@@ -159,20 +192,9 @@ interface StatementSyncInstance {
159
192
  setReturnArrays(returnArrays: boolean): void;
160
193
  /**
161
194
  * Returns an array of objects, each representing a column in the statement's result set.
162
- * Each object has a 'name' property for the column name and a 'type' property for the SQLite type.
163
- * @returns Array of column metadata objects.
164
- */
165
- columns(): Array<{
166
- name: string;
167
- type?: string;
168
- }>;
169
- /**
170
- * Finalizes the prepared statement and releases its resources.
171
- * Called automatically by Symbol.dispose.
195
+ * @returns Array of column metadata objects with name, column, database, table, and type.
172
196
  */
173
- finalize(): void;
174
- /** Dispose of the statement resources using the explicit resource management protocol. */
175
- [Symbol.dispose](): void;
197
+ columns(): StatementColumnMetadata[];
176
198
  }
177
199
 
178
200
  /**
@@ -274,7 +296,7 @@ interface DatabaseSyncInstance {
274
296
  * @param options Optional configuration for applying the changeset.
275
297
  * @returns true if successful, false if aborted.
276
298
  */
277
- applyChangeset(changeset: Buffer, options?: ChangesetApplyOptions): boolean;
299
+ applyChangeset(changeset: Uint8Array, options?: ChangesetApplyOptions): boolean;
278
300
  /**
279
301
  * Enables or disables the loading of SQLite extensions.
280
302
  * @param enable If true, enables extension loading. If false, disables it.
@@ -327,44 +349,6 @@ interface DatabaseSyncInstance {
327
349
  * @see https://sqlite.org/c3ref/set_authorizer.html
328
350
  */
329
351
  setAuthorizer(callback: ((actionCode: number, param1: string | null, param2: string | null, param3: string | null, param4: string | null) => number) | null): void;
330
- /**
331
- * Makes a backup of the database. This method abstracts the sqlite3_backup_init(),
332
- * sqlite3_backup_step() and sqlite3_backup_finish() functions.
333
- *
334
- * The backed-up database can be used normally during the backup process. Mutations
335
- * coming from the same connection will be reflected in the backup right away.
336
- * However, mutations from other connections will cause the backup process to restart.
337
- *
338
- * @param path The path where the backup will be created. If the file already exists, the contents will be overwritten.
339
- * @param options Optional configuration for the backup operation.
340
- * @param options.rate Number of pages to be transmitted in each batch of the backup. @default 100
341
- * @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'
342
- * @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'
343
- * @param options.progress Callback function that will be called with the number of pages copied and the total number of pages.
344
- * @returns A promise that resolves when the backup is completed and rejects if an error occurs.
345
- *
346
- * @example
347
- * // Basic backup
348
- * await db.backup('./backup.db');
349
- *
350
- * @example
351
- * // Backup with progress
352
- * await db.backup('./backup.db', {
353
- * rate: 10,
354
- * progress: ({ totalPages, remainingPages }) => {
355
- * console.log(`Progress: ${totalPages - remainingPages}/${totalPages}`);
356
- * }
357
- * });
358
- */
359
- backup(path: string | Buffer | URL, options?: {
360
- rate?: number;
361
- source?: string;
362
- target?: string;
363
- progress?: (info: {
364
- totalPages: number;
365
- remainingPages: number;
366
- }) => void;
367
- }): Promise<number>;
368
352
  /** Dispose of the database resources using the explicit resource management protocol. */
369
353
  [Symbol.dispose](): void;
370
354
  }
@@ -395,7 +379,7 @@ declare class SQLTagStore {
395
379
  /**
396
380
  * Returns the current number of cached statements.
397
381
  */
398
- size(): number;
382
+ get size(): number;
399
383
  /**
400
384
  * Clears all cached statements.
401
385
  */
@@ -422,7 +406,6 @@ declare class SQLTagStore {
422
406
  iterate(strings: TemplateStringsArray, ...values: unknown[]): IterableIterator<unknown>;
423
407
  /**
424
408
  * Get a cached statement or prepare a new one.
425
- * If a cached statement has been finalized, it's evicted and a new one is prepared.
426
409
  */
427
410
  private getOrPrepare;
428
411
  /**
@@ -682,6 +665,218 @@ interface SqliteOpenFlags {
682
665
  SQLITE_OPEN_WAL: number;
683
666
  }
684
667
 
668
+ /**
669
+ * Options for the pragma() method.
670
+ *
671
+ * @see https://sqlite.org/pragma.html
672
+ */
673
+ interface PragmaOptions {
674
+ /**
675
+ * When true, returns only the first column of the first row.
676
+ * This is useful for pragmas that return a single value.
677
+ *
678
+ * @default false
679
+ *
680
+ * @example
681
+ * ```typescript
682
+ * // Without simple: returns [{ cache_size: -16000 }]
683
+ * db.pragma('cache_size');
684
+ *
685
+ * // With simple: returns -16000
686
+ * db.pragma('cache_size', { simple: true });
687
+ * ```
688
+ */
689
+ readonly simple?: boolean;
690
+ }
691
+
692
+ /**
693
+ * Transaction isolation modes supported by SQLite.
694
+ *
695
+ * @see https://sqlite.org/lang_transaction.html
696
+ */
697
+ type TransactionMode = "deferred" | "immediate" | "exclusive";
698
+ /**
699
+ * A function wrapped in a transaction. When called, it automatically:
700
+ * - Begins a transaction (or savepoint if nested)
701
+ * - Executes the wrapped function
702
+ * - Commits on success, rolls back on error
703
+ *
704
+ * The function also has variants for different transaction modes accessible as
705
+ * properties (`.deferred`, `.immediate`, `.exclusive`).
706
+ *
707
+ * **Note:** Each variant is itself a `TransactionFunction` with circular
708
+ * references to all other variants. This matches better-sqlite3's behavior
709
+ * where you can chain variants like `txn.deferred.immediate()` - the last
710
+ * variant in the chain determines the actual transaction mode used.
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * const insertMany = db.transaction((items: Item[]) => {
715
+ * for (const item of items) insert.run(item);
716
+ * return items.length;
717
+ * });
718
+ *
719
+ * // Use default mode (DEFERRED)
720
+ * insertMany([{ id: 1 }, { id: 2 }]);
721
+ *
722
+ * // Use IMMEDIATE mode for write transactions
723
+ * insertMany.immediate([{ id: 3 }, { id: 4 }]);
724
+ *
725
+ * // Use EXCLUSIVE mode for exclusive access
726
+ * insertMany.exclusive([{ id: 5 }, { id: 6 }]);
727
+ * ```
728
+ */
729
+ interface TransactionFunction<F extends (...args: any[]) => any> {
730
+ /**
731
+ * Execute the wrapped function within a transaction using the default mode (DEFERRED).
732
+ */
733
+ (...args: Parameters<F>): ReturnType<F>;
734
+ /**
735
+ * Execute with DEFERRED transaction mode.
736
+ * The database lock is not acquired until the first read or write operation.
737
+ * This is the default SQLite behavior.
738
+ */
739
+ readonly deferred: TransactionFunction<F>;
740
+ /**
741
+ * Execute with IMMEDIATE transaction mode.
742
+ * Acquires a write lock immediately, blocking other writers.
743
+ * Recommended for transactions that will write to prevent SQLITE_BUSY errors.
744
+ */
745
+ readonly immediate: TransactionFunction<F>;
746
+ /**
747
+ * Execute with EXCLUSIVE transaction mode.
748
+ * Acquires an exclusive lock immediately, blocking all other connections.
749
+ * Use sparingly as it prevents all concurrent access.
750
+ */
751
+ readonly exclusive: TransactionFunction<F>;
752
+ /**
753
+ * The database connection this transaction function is bound to.
754
+ */
755
+ readonly database: DatabaseSyncInstance;
756
+ }
757
+
758
+ /**
759
+ * Enhancement utilities for adding better-sqlite3-style methods to any
760
+ * compatible database, including `node:sqlite` DatabaseSync and this package's
761
+ * DatabaseSync.
762
+ *
763
+ * This module provides the `enhance()` function which adds `.pragma()` and
764
+ * `.transaction()` methods to database instances that don't have them (e.g.,
765
+ * node:sqlite DatabaseSync).
766
+ */
767
+
768
+ /**
769
+ * Minimal interface for a database that can be enhanced. This matches the
770
+ * subset of functionality needed by pragma() and transaction().
771
+ */
772
+ interface EnhanceableDatabaseSync {
773
+ /** Execute SQL without returning results */
774
+ exec(sql: string): void;
775
+ /** Prepare a statement that can return results */
776
+ prepare(sql: string): {
777
+ all(): unknown[];
778
+ };
779
+ /** Whether a transaction is currently active */
780
+ readonly isTransaction: boolean;
781
+ }
782
+ /**
783
+ * Interface for an enhanced database with pragma() and transaction() methods.
784
+ */
785
+ interface EnhancedMethods {
786
+ /**
787
+ * Executes a PRAGMA statement and returns its result.
788
+ *
789
+ * @param source The PRAGMA command (without "PRAGMA" prefix)
790
+ * @param options Optional configuration
791
+ * @returns Array of rows, or single value if `simple: true`
792
+ *
793
+ * @example
794
+ * ```typescript
795
+ * db.pragma('cache_size', { simple: true }); // -16000
796
+ * db.pragma('journal_mode = wal');
797
+ * ```
798
+ */
799
+ pragma(source: string, options?: PragmaOptions): unknown;
800
+ /**
801
+ * Creates a function that always runs inside a transaction.
802
+ *
803
+ * @param fn The function to wrap in a transaction
804
+ * @returns A transaction function with `.deferred`, `.immediate`,
805
+ * `.exclusive` variants
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * const insertMany = db.transaction((items) => {
810
+ * for (const item of items) insert.run(item);
811
+ * });
812
+ * insertMany(['a', 'b', 'c']); // All in one transaction
813
+ * ```
814
+ */
815
+ transaction<F extends (...args: any[]) => any>(fn: F): TransactionFunction<F>;
816
+ }
817
+ /**
818
+ * A database instance that has been enhanced with pragma() and transaction() methods.
819
+ */
820
+ type EnhancedDatabaseSync<T extends EnhanceableDatabaseSync> = T & EnhancedMethods;
821
+ /**
822
+ * Ensures that `.pragma()` and `.transaction()` methods are available on the
823
+ * given database.
824
+ *
825
+ * This function can enhance:
826
+ * - `node:sqlite` DatabaseSync instances (adds the methods)
827
+ * - `@photostructure/sqlite` DatabaseSync instances (no-op, already has these
828
+ * methods)
829
+ * - Any object with compatible `exec()`, `prepare()`, and `isTransaction`
830
+ *
831
+ * The enhancement is done by adding methods directly to the instance, not the
832
+ * prototype, so it won't affect other instances or the original class.
833
+ *
834
+ * @param db The database instance to enhance
835
+ * @returns The same instance with `.pragma()` and `.transaction()` methods
836
+ * guaranteed
837
+ *
838
+ * @example
839
+ * ```typescript
840
+ * // With node:sqlite
841
+ * import { DatabaseSync } from 'node:sqlite';
842
+ * import { enhance } from '@photostructure/sqlite';
843
+ *
844
+ * const db = enhance(new DatabaseSync(':memory:'));
845
+ *
846
+ * // Now you can use better-sqlite3-style methods
847
+ * db.pragma('journal_mode = wal');
848
+ * const insertMany = db.transaction((items) => {
849
+ * for (const item of items) insert.run(item);
850
+ * });
851
+ * ```
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * // With @photostructure/sqlite (no-op, already enhanced)
856
+ * import { DatabaseSync, enhance } from '@photostructure/sqlite';
857
+ *
858
+ * const db = enhance(new DatabaseSync(':memory:'));
859
+ * // db already had these methods, enhance() just returns it unchanged
860
+ * ```
861
+ */
862
+ declare function enhance<T extends EnhanceableDatabaseSync>(db: T): EnhancedDatabaseSync<T>;
863
+ /**
864
+ * Type guard to check if a database has enhanced methods.
865
+ *
866
+ * @param db The database to check
867
+ * @returns True if the database has `.pragma()` and `.transaction()` methods
868
+ *
869
+ * @example
870
+ * ```typescript
871
+ * import { isEnhanced } from '@photostructure/sqlite';
872
+ *
873
+ * if (isEnhanced(db)) {
874
+ * db.pragma('cache_size', { simple: true });
875
+ * }
876
+ * ```
877
+ */
878
+ declare function isEnhanced(db: EnhanceableDatabaseSync): db is EnhanceableDatabaseSync & EnhancedMethods;
879
+
685
880
  /**
686
881
  * All SQLite constants exported by this module.
687
882
  *
@@ -699,12 +894,41 @@ interface SqliteOpenFlags {
699
894
  type SqliteConstants = SqliteOpenFlags & SqliteChangesetResolution & SqliteChangesetConflictTypes & SqliteAuthorizationResults & SqliteAuthorizationActions;
700
895
  /**
701
896
  * Options for creating a prepared statement.
897
+ *
898
+ * **Note:** The per-statement override options (`readBigInts`, `returnArrays`,
899
+ * `allowBareNamedParameters`, `allowUnknownNamedParameters`) are a **Node.js v25+**
900
+ * feature. On Node.js v24 and earlier, `node:sqlite` silently ignores these options.
901
+ * This library implements them for forward compatibility with Node.js v25+.
702
902
  */
703
903
  interface StatementOptions {
704
904
  /** If true, the prepared statement's expandedSQL property will contain the expanded SQL. @default false */
705
905
  readonly expandedSQL?: boolean;
706
906
  /** If true, anonymous parameters are enabled for the statement. @default false */
707
907
  readonly anonymousParameters?: boolean;
908
+ /**
909
+ * If true, read integer values as JavaScript BigInt. Overrides database-level setting.
910
+ * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.
911
+ * @default database default
912
+ */
913
+ readonly readBigInts?: boolean;
914
+ /**
915
+ * If true, return results as arrays rather than objects. Overrides database-level setting.
916
+ * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.
917
+ * @default database default
918
+ */
919
+ readonly returnArrays?: boolean;
920
+ /**
921
+ * If true, allows bare named parameters (without prefix). Overrides database-level setting.
922
+ * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.
923
+ * @default database default
924
+ */
925
+ readonly allowBareNamedParameters?: boolean;
926
+ /**
927
+ * If true, unknown named parameters are ignored. Overrides database-level setting.
928
+ * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.
929
+ * @default database default
930
+ */
931
+ readonly allowUnknownNamedParameters?: boolean;
708
932
  }
709
933
  /**
710
934
  * The main SQLite module interface.
@@ -736,48 +960,19 @@ interface SqliteModule {
736
960
  */
737
961
  constants: SqliteConstants;
738
962
  }
739
- /**
740
- * The DatabaseSync class represents a synchronous connection to a SQLite database.
741
- * All database operations are performed synchronously, blocking the thread until completion.
742
- *
743
- * @example
744
- * ```typescript
745
- * import { DatabaseSync } from '@photostructure/sqlite';
746
- *
747
- * // Create an in-memory database
748
- * const db = new DatabaseSync(':memory:');
749
- *
750
- * // Create a file-based database
751
- * const fileDb = new DatabaseSync('./mydata.db');
752
- *
753
- * // Create with options
754
- * const readOnlyDb = new DatabaseSync('./data.db', { readOnly: true });
755
- * ```
756
- */
757
963
  declare const DatabaseSync: SqliteModule["DatabaseSync"];
758
- /**
759
- * The StatementSync class represents a prepared SQL statement.
760
- * This class should not be instantiated directly; use DatabaseSync.prepare() instead.
761
- *
762
- * @example
763
- * ```typescript
764
- * const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
765
- * const user = stmt.get(123);
766
- * stmt.finalize();
767
- * ```
768
- */
769
964
  declare const StatementSync: SqliteModule["StatementSync"];
770
965
  interface Session {
771
966
  /**
772
967
  * Generate a changeset containing all changes recorded by the session.
773
- * @returns A Buffer containing the changeset data.
968
+ * @returns A Uint8Array containing the changeset data.
774
969
  */
775
- changeset(): Buffer;
970
+ changeset(): Uint8Array;
776
971
  /**
777
972
  * Generate a patchset containing all changes recorded by the session.
778
- * @returns A Buffer containing the patchset data.
973
+ * @returns A Uint8Array containing the patchset data.
779
974
  */
780
- patchset(): Buffer;
975
+ patchset(): Uint8Array;
781
976
  /**
782
977
  * Close the session and release its resources.
783
978
  */
@@ -857,4 +1052,4 @@ interface BackupOptions {
857
1052
  declare const backup: (sourceDb: DatabaseSyncInstance, destination: string | Buffer | URL, options?: BackupOptions) => Promise<number>;
858
1053
  declare const _default: SqliteModule;
859
1054
 
860
- export { type AggregateOptions, type BackupOptions, type ChangesetApplyOptions, DatabaseSync, type DatabaseSyncInstance, type DatabaseSyncOptions, SQLTagStore, type SQLTagStoreInstance, Session, type SessionOptions, type SqliteAuthorizationActions, type SqliteAuthorizationResults, type SqliteChangesetConflictTypes, type SqliteChangesetResolution, type SqliteConstants, type SqliteModule, type SqliteOpenFlags, type StatementOptions, StatementSync, type StatementSyncInstance, type UserFunctionOptions, backup, constants, _default as default };
1055
+ export { type AggregateOptions, type BackupOptions, type ChangesetApplyOptions, DatabaseSync, type DatabaseSyncInstance, type DatabaseSyncOptions, type EnhanceableDatabaseSync, type EnhancedDatabaseSync, type EnhancedMethods, type PragmaOptions, SQLTagStore, type SQLTagStoreInstance, Session, type SessionOptions, type SqliteAuthorizationActions, type SqliteAuthorizationResults, type SqliteChangesetConflictTypes, type SqliteChangesetResolution, type SqliteConstants, type SqliteModule, type SqliteOpenFlags, type StatementColumnMetadata, type StatementOptions, StatementSync, type StatementSyncInstance, type TransactionFunction, type TransactionMode, type UserFunctionOptions, backup, constants, _default as default, enhance, isEnhanced };
package/dist/index.mjs CHANGED
@@ -154,7 +154,9 @@ var SQLTagStore = class {
154
154
  maxCapacity;
155
155
  constructor(db, capacity = DEFAULT_CAPACITY) {
156
156
  if (!db.isOpen) {
157
- throw new Error("Database is not open");
157
+ const err = new Error("database is not open");
158
+ err.code = "ERR_INVALID_STATE";
159
+ throw err;
158
160
  }
159
161
  this.database = db;
160
162
  this.maxCapacity = capacity;
@@ -175,7 +177,7 @@ var SQLTagStore = class {
175
177
  /**
176
178
  * Returns the current number of cached statements.
177
179
  */
178
- size() {
180
+ get size() {
179
181
  return this.cache.size();
180
182
  }
181
183
  /**
@@ -215,19 +217,15 @@ var SQLTagStore = class {
215
217
  }
216
218
  /**
217
219
  * Get a cached statement or prepare a new one.
218
- * If a cached statement has been finalized, it's evicted and a new one is prepared.
219
220
  */
220
221
  getOrPrepare(strings) {
221
222
  if (!this.database.isOpen) {
222
- throw new Error("Database is not open");
223
+ throw new Error("database is not open");
223
224
  }
224
225
  const sql = this.buildSQL(strings);
225
226
  const cached = this.cache.get(sql);
226
227
  if (cached) {
227
- if (!cached.finalized) {
228
- return cached;
229
- }
230
- this.cache.delete(sql);
228
+ return cached;
231
229
  }
232
230
  const stmt = this.database.prepare(sql);
233
231
  this.cache.set(sql, stmt);
@@ -245,13 +243,158 @@ var SQLTagStore = class {
245
243
  }
246
244
  };
247
245
 
246
+ // src/transaction.ts
247
+ var savepointCounter = 0;
248
+ function createTransaction(db, fn) {
249
+ if (typeof fn !== "function") {
250
+ throw new TypeError("Expected first argument to be a function");
251
+ }
252
+ const variants = {
253
+ deferred: createVariant(db, fn, "deferred"),
254
+ immediate: createVariant(db, fn, "immediate"),
255
+ exclusive: createVariant(db, fn, "exclusive")
256
+ };
257
+ const defaultFn = variants.deferred;
258
+ for (const variant of Object.values(variants)) {
259
+ Object.defineProperties(variant, {
260
+ deferred: { value: variants.deferred, enumerable: true },
261
+ immediate: { value: variants.immediate, enumerable: true },
262
+ exclusive: { value: variants.exclusive, enumerable: true },
263
+ database: { value: db, enumerable: true }
264
+ });
265
+ }
266
+ return defaultFn;
267
+ }
268
+ function createVariant(db, fn, mode) {
269
+ const beginStatement = getBeginStatement(mode);
270
+ return function transactionWrapper(...args) {
271
+ const isNested = db.isTransaction;
272
+ let begin;
273
+ let commit;
274
+ let rollback;
275
+ if (isNested) {
276
+ const savepointName = `\`_txn_${++savepointCounter}\``;
277
+ begin = `SAVEPOINT ${savepointName}`;
278
+ commit = `RELEASE ${savepointName}`;
279
+ rollback = `ROLLBACK TO ${savepointName}`;
280
+ } else {
281
+ begin = beginStatement;
282
+ commit = "COMMIT";
283
+ rollback = "ROLLBACK";
284
+ }
285
+ db.exec(begin);
286
+ try {
287
+ const result = fn.apply(this, args);
288
+ if (result !== null && typeof result === "object" && "then" in result) {
289
+ throw new TypeError(
290
+ "Transaction function must not return a Promise. SQLite transactions are synchronous and cannot span across async operations. Use synchronous code within transactions."
291
+ );
292
+ }
293
+ db.exec(commit);
294
+ return result;
295
+ } catch (error) {
296
+ if (db.isTransaction) {
297
+ db.exec(rollback);
298
+ if (isNested) {
299
+ db.exec(commit);
300
+ }
301
+ }
302
+ throw error;
303
+ }
304
+ };
305
+ }
306
+ function getBeginStatement(mode) {
307
+ switch (mode) {
308
+ case "deferred":
309
+ return "BEGIN DEFERRED";
310
+ case "immediate":
311
+ return "BEGIN IMMEDIATE";
312
+ case "exclusive":
313
+ return "BEGIN EXCLUSIVE";
314
+ default:
315
+ throw new Error(`Unknown transaction mode: ${mode}`);
316
+ }
317
+ }
318
+
319
+ // src/enhance.ts
320
+ function pragmaImpl(source, options) {
321
+ if (typeof source !== "string") {
322
+ throw new TypeError("Expected first argument to be a string");
323
+ }
324
+ if (options != null && typeof options !== "object") {
325
+ throw new TypeError("Expected second argument to be an options object");
326
+ }
327
+ const simple = options?.simple === true;
328
+ if (options != null && "simple" in options && typeof options.simple !== "boolean") {
329
+ throw new TypeError('Expected the "simple" option to be a boolean');
330
+ }
331
+ const stmt = this.prepare(`PRAGMA ${source}`);
332
+ const rows = stmt.all();
333
+ if (simple) {
334
+ const firstRow = rows[0];
335
+ if (firstRow == null) {
336
+ return void 0;
337
+ }
338
+ const keys = Object.keys(firstRow);
339
+ const firstKey = keys[0];
340
+ if (firstKey == null) {
341
+ return void 0;
342
+ }
343
+ return firstRow[firstKey];
344
+ }
345
+ return rows;
346
+ }
347
+ function transactionImpl(fn) {
348
+ return createTransaction(this, fn);
349
+ }
350
+ function hasEnhancedMethods(db) {
351
+ return typeof db.pragma === "function" && typeof db.transaction === "function";
352
+ }
353
+ function enhance(db) {
354
+ if (hasEnhancedMethods(db)) {
355
+ return db;
356
+ }
357
+ Object.defineProperty(db, "pragma", {
358
+ value: pragmaImpl,
359
+ writable: true,
360
+ configurable: true,
361
+ enumerable: false
362
+ });
363
+ Object.defineProperty(db, "transaction", {
364
+ value: transactionImpl,
365
+ writable: true,
366
+ configurable: true,
367
+ enumerable: false
368
+ });
369
+ return db;
370
+ }
371
+ function isEnhanced(db) {
372
+ return hasEnhancedMethods(db);
373
+ }
374
+
248
375
  // src/index.ts
249
376
  var binding = nodeGypBuild(join(_dirname(), ".."));
250
- var DatabaseSync = binding.DatabaseSync;
377
+ var _DatabaseSync = binding.DatabaseSync;
378
+ var DatabaseSync = function DatabaseSync2(...args) {
379
+ if (!new.target) {
380
+ const err = new TypeError("Cannot call constructor without `new`");
381
+ err.code = "ERR_CONSTRUCT_CALL_REQUIRED";
382
+ throw err;
383
+ }
384
+ return Reflect.construct(_DatabaseSync, args, new.target);
385
+ };
386
+ Object.setPrototypeOf(DatabaseSync, _DatabaseSync);
387
+ DatabaseSync.prototype = _DatabaseSync.prototype;
251
388
  DatabaseSync.prototype.createTagStore = function(capacity) {
252
389
  return new SQLTagStore(this, capacity);
253
390
  };
254
- var StatementSync = binding.StatementSync;
391
+ var _StatementSync = binding.StatementSync;
392
+ var StatementSync = function StatementSync2() {
393
+ const err = new TypeError("Illegal constructor");
394
+ err.code = "ERR_ILLEGAL_CONSTRUCTOR";
395
+ throw err;
396
+ };
397
+ StatementSync.prototype = _StatementSync.prototype;
255
398
  var Session = binding.Session;
256
399
  var constants = binding.constants;
257
400
  var backup = binding.backup;
@@ -263,6 +406,8 @@ export {
263
406
  StatementSync,
264
407
  backup,
265
408
  constants,
266
- index_default as default
409
+ index_default as default,
410
+ enhance,
411
+ isEnhanced
267
412
  };
268
413
  //# sourceMappingURL=index.mjs.map