@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.mts 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 };