@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.
- package/CHANGELOG.md +61 -15
- package/README.md +5 -4
- package/binding.gyp +2 -2
- package/dist/index.cjs +159 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +286 -91
- package/dist/index.d.mts +286 -91
- package/dist/index.d.ts +286 -91
- package/dist/index.mjs +156 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +74 -65
- package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/test_extension.so +0 -0
- package/prebuilds/win32-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
- package/scripts/prebuild-linux-glibc.sh +6 -4
- package/src/aggregate_function.cpp +222 -114
- package/src/aggregate_function.h +5 -6
- package/src/binding.cpp +30 -21
- package/src/enhance.ts +228 -0
- package/src/index.ts +83 -9
- package/src/shims/node_errors.h +34 -15
- package/src/shims/sqlite_errors.h +34 -8
- package/src/sql-tag-store.ts +7 -10
- package/src/sqlite_impl.cpp +1044 -394
- package/src/sqlite_impl.h +46 -7
- package/src/transaction.ts +178 -0
- package/src/types/database-sync-instance.ts +6 -40
- package/src/types/pragma-options.ts +23 -0
- package/src/types/sql-tag-store-instance.ts +1 -1
- package/src/types/statement-sync-instance.ts +38 -12
- package/src/types/transaction.ts +72 -0
- package/src/upstream/node_sqlite.cc +143 -43
- package/src/upstream/node_sqlite.h +15 -11
- package/src/upstream/sqlite3.c +102 -58
- package/src/upstream/sqlite3.h +5 -5
- package/src/user_function.cpp +138 -141
- package/src/user_function.h +3 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/index.ts","../src/stack-path.ts","../src/dirname.ts","../src/lru-cache.ts","../src/sql-tag-store.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","// Load the native binding with support for both CJS and ESM\nimport nodeGypBuild from \"node-gyp-build\";\nimport { join } from \"node:path\";\nimport { _dirname } from \"./dirname\";\nimport { SQLTagStore } from \"./sql-tag-store\";\nimport { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nimport { DatabaseSyncOptions } from \"./types/database-sync-options\";\nimport { SQLTagStoreInstance } from \"./types/sql-tag-store-instance\";\nimport { SqliteAuthorizationActions } from \"./types/sqlite-authorization-actions\";\nimport { SqliteAuthorizationResults } from \"./types/sqlite-authorization-results\";\nimport { SqliteChangesetConflictTypes } from \"./types/sqlite-changeset-conflict-types\";\nimport { SqliteChangesetResolution } from \"./types/sqlite-changeset-resolution\";\nimport { SqliteOpenFlags } from \"./types/sqlite-open-flags\";\nimport { StatementSyncInstance } from \"./types/statement-sync-instance\";\n\nexport type { AggregateOptions } from \"./types/aggregate-options\";\nexport type { ChangesetApplyOptions } from \"./types/changeset-apply-options\";\nexport type { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nexport type { DatabaseSyncOptions } from \"./types/database-sync-options\";\nexport type { SessionOptions } from \"./types/session-options\";\nexport type { SQLTagStoreInstance } from \"./types/sql-tag-store-instance\";\nexport type { SqliteAuthorizationActions } from \"./types/sqlite-authorization-actions\";\nexport type { SqliteAuthorizationResults } from \"./types/sqlite-authorization-results\";\nexport type { SqliteChangesetConflictTypes } from \"./types/sqlite-changeset-conflict-types\";\nexport type { SqliteChangesetResolution } from \"./types/sqlite-changeset-resolution\";\nexport type { SqliteOpenFlags } from \"./types/sqlite-open-flags\";\nexport type { StatementSyncInstance } from \"./types/statement-sync-instance\";\nexport type { UserFunctionOptions } from \"./types/user-functions-options\";\n\n// Use _dirname() helper that works in both CJS/ESM and Jest\nconst binding = nodeGypBuild(join(_dirname(), \"..\"));\n\n/**\n * All SQLite constants exported by this module.\n *\n * This is a union of all constant category interfaces:\n * - {@link SqliteOpenFlags} - Database open flags (extension beyond `node:sqlite`)\n * - {@link SqliteChangesetResolution} - Changeset conflict resolution values\n * - {@link SqliteChangesetConflictTypes} - Changeset conflict type codes\n * - {@link SqliteAuthorizationResults} - Authorization return values\n * - {@link SqliteAuthorizationActions} - Authorization action codes\n *\n * **Note:** The categorized interfaces (`SqliteOpenFlags`, etc.) are extensions\n * provided by `@photostructure/sqlite`. The `node:sqlite` module exports only\n * a flat `constants` object without these type categories.\n */\nexport type SqliteConstants = SqliteOpenFlags &\n SqliteChangesetResolution &\n SqliteChangesetConflictTypes &\n SqliteAuthorizationResults &\n SqliteAuthorizationActions;\n\n/**\n * Options for creating a prepared statement.\n */\nexport interface StatementOptions {\n /** If true, the prepared statement's expandedSQL property will contain the expanded SQL. @default false */\n readonly expandedSQL?: boolean;\n /** If true, anonymous parameters are enabled for the statement. @default false */\n readonly anonymousParameters?: boolean;\n}\n\nexport interface Session {\n /**\n * Generate a changeset containing all changes recorded by the session.\n * @returns A Buffer containing the changeset data.\n */\n changeset(): Buffer;\n /**\n * Generate a patchset containing all changes recorded by the session.\n * @returns A Buffer containing the patchset data.\n */\n patchset(): Buffer;\n /**\n * Close the session and release its resources.\n */\n close(): void;\n}\n\n/**\n * The main SQLite module interface.\n */\nexport interface SqliteModule {\n /**\n * The DatabaseSync class represents a synchronous connection to a SQLite database.\n * All operations are performed synchronously, blocking until completion.\n */\n DatabaseSync: new (\n location?: string | Buffer | URL,\n options?: DatabaseSyncOptions,\n ) => DatabaseSyncInstance;\n /**\n * The StatementSync class represents a synchronous prepared statement.\n * This class should not be instantiated directly; use Database.prepare() instead.\n */\n StatementSync: new (\n database: DatabaseSyncInstance,\n sql: string,\n options?: StatementOptions,\n ) => StatementSyncInstance;\n /**\n * The Session class for recording database changes.\n * This class should not be instantiated directly; use Database.createSession() instead.\n */\n Session: new () => Session;\n /**\n * SQLite constants for various operations and flags.\n * @see {@link SqliteConstants} for the type definition\n * @see {@link SqliteOpenFlags} for database open flags (extension beyond `node:sqlite`)\n * @see {@link SqliteChangesetResolution} for changeset conflict resolution values\n * @see {@link SqliteChangesetConflictTypes} for changeset conflict type codes\n * @see {@link SqliteAuthorizationResults} for authorization return values\n * @see {@link SqliteAuthorizationActions} for authorization action codes\n */\n constants: SqliteConstants;\n}\n\n/**\n * The DatabaseSync class represents a synchronous connection to a SQLite database.\n * All database operations are performed synchronously, blocking the thread until completion.\n *\n * @example\n * ```typescript\n * import { DatabaseSync } from '@photostructure/sqlite';\n *\n * // Create an in-memory database\n * const db = new DatabaseSync(':memory:');\n *\n * // Create a file-based database\n * const fileDb = new DatabaseSync('./mydata.db');\n *\n * // Create with options\n * const readOnlyDb = new DatabaseSync('./data.db', { readOnly: true });\n * ```\n */\nexport const DatabaseSync =\n binding.DatabaseSync as SqliteModule[\"DatabaseSync\"];\n\n// node:sqlite implements createTagStore and SQLTagStore entirely in native C++.\n// We use a TypeScript implementation instead, attached via prototype extension.\n// This maintains API compatibility with node:sqlite while avoiding the complexity\n// of a native LRU cache. Performance is equivalent since the real cost is SQLite\n// execution, not cache lookups - V8's Map is highly optimized for string keys.\n(DatabaseSync.prototype as DatabaseSyncInstance).createTagStore = function (\n this: DatabaseSyncInstance,\n capacity?: number,\n): SQLTagStoreInstance {\n return new SQLTagStore(this, capacity);\n};\n\n/**\n * The StatementSync class represents a prepared SQL statement.\n * This class should not be instantiated directly; use DatabaseSync.prepare() instead.\n *\n * @example\n * ```typescript\n * const stmt = db.prepare('SELECT * FROM users WHERE id = ?');\n * const user = stmt.get(123);\n * stmt.finalize();\n * ```\n */\nexport const StatementSync =\n binding.StatementSync as SqliteModule[\"StatementSync\"];\n\n/**\n * The Session class for recording database changes.\n * This class should not be instantiated directly; use DatabaseSync.createSession() instead.\n *\n * @example\n * ```typescript\n * const session = db.createSession({ table: 'users' });\n * // Make some changes to the users table\n * const changeset = session.changeset();\n * session.close();\n * ```\n */\nexport const Session = binding.Session as SqliteModule[\"Session\"];\n\n/**\n * The SQLTagStore class for cached prepared statements via tagged template syntax.\n * This class should not be instantiated directly; use DatabaseSync.createTagStore() instead.\n *\n * @example\n * ```typescript\n * const sql = db.createTagStore();\n * sql.run`INSERT INTO users VALUES (${id}, ${name})`;\n * const user = sql.get`SELECT * FROM users WHERE id = ${id}`;\n * ```\n */\nexport { SQLTagStore };\n\n/**\n * SQLite constants for various operations and flags.\n *\n * @example\n * ```typescript\n * import { constants } from '@photostructure/sqlite';\n *\n * const db = new DatabaseSync('./data.db', {\n * readOnly: true,\n * // Uses SQLITE_OPEN_READONLY internally\n * });\n * ```\n */\nexport const constants: SqliteConstants = binding.constants;\n\n/**\n * Options for the backup() function.\n */\nexport interface BackupOptions {\n /** Number of pages to be transmitted in each batch of the backup. @default 100 */\n rate?: number;\n /** Name of the source database. Can be 'main' or any attached database. @default 'main' */\n source?: string;\n /** Name of the target database. Can be 'main' or any attached database. @default 'main' */\n target?: string;\n /** Callback function that will be called with progress information. */\n progress?: (info: { totalPages: number; remainingPages: number }) => void;\n}\n\n/**\n * Standalone function to make a backup of a database.\n *\n * This function matches the Node.js `node:sqlite` module API which exports\n * `backup()` as a standalone function in addition to the `db.backup()` method.\n *\n * @param sourceDb The database to backup from.\n * @param destination The path where the backup will be created.\n * @param options Optional configuration for the backup operation.\n * @returns A promise that resolves when the backup is completed.\n *\n * @example\n * ```typescript\n * import { DatabaseSync, backup } from '@photostructure/sqlite';\n *\n * const db = new DatabaseSync('./source.db');\n * await backup(db, './backup.db');\n *\n * // With options\n * await backup(db, './backup.db', {\n * rate: 10,\n * progress: ({ totalPages, remainingPages }) => {\n * console.log(`Progress: ${totalPages - remainingPages}/${totalPages}`);\n * }\n * });\n * ```\n */\nexport const backup: (\n sourceDb: DatabaseSyncInstance,\n destination: string | Buffer | URL,\n options?: BackupOptions,\n) => Promise<number> = binding.backup;\n\n// Default export for CommonJS compatibility\nexport default binding as SqliteModule;\n","import { dirname } from \"node:path\";\n\nexport function getCallerDirname(): string {\n const e = new Error();\n if (e.stack == null) {\n Error.captureStackTrace(e);\n }\n return dirname(extractCallerPath(e.stack as string));\n}\n\n// Comprehensive regex patterns for different stack frame formats\nconst patterns =\n process.platform === \"win32\"\n ? [\n // File URLs: \"at functionName (file:///C:/path/file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>file:\\/\\/\\/.+?):\\d+:\\d+\\)$/,\n // File URLs direct: \"at file:///C:/path/file.js:1:1\"\n /\\bat\\s(?<path>file:\\/\\/\\/.+?):\\d+:\\d+$/,\n // Standard: \"at functionName (C:\\path\\file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>[A-Z]:\\\\.+):\\d+:\\d+\\)$/,\n // direct: \"at C:\\path\\file.js:1:1\"\n /\\bat\\s(?<path>[A-Z]:\\\\.+):\\d+:\\d+$/,\n // UNC: \"at functionName (\\\\server\\share\\path\\file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>\\\\\\\\.+):\\d+:\\d+\\)$/,\n // direct: \"at \\\\server\\share\\path\\file.js:1:1\"\n /\\bat\\s(?<path>\\\\\\\\.+):\\d+:\\d+$/,\n ]\n : [\n // Standard: \"at functionName (/path/file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>\\/.+?):\\d+:\\d+\\)$/,\n // Anonymous or direct: \"at /path/file.js:1:1\"\n // eslint-disable-next-line security/detect-unsafe-regex -- Pattern is safe: no nested quantifiers\n /\\bat\\s(?:[^\\s()]+\\s)?(?<path>\\/[^:]+):\\d+:\\d+$/,\n ];\n\nconst MaybeUrlRE = /^[a-z]{2,5}:\\/\\//i;\n\n// only exposed for tests:\nexport function extractCallerPath(stack: string): string {\n const frames = stack.split(\"\\n\").filter(Boolean);\n\n // First find getCallerDirname() in the stack:\n const callerFrame = frames.findIndex((frame) =>\n frame.includes(\"getCallerDirname\"),\n );\n if (callerFrame === -1) {\n throw new Error(\"Invalid stack trace format: missing caller frame\");\n }\n for (let i = callerFrame + 1; i < frames.length; i++) {\n // eslint-disable-next-line security/detect-object-injection -- Index is from controlled for-loop\n const frame = frames[i];\n for (const pattern of patterns) {\n const g = frame?.trim().match(pattern)?.groups;\n if (g != null && g[\"path\"]) {\n const path = g[\"path\"];\n // Windows requires us to check if it's a reasonable URL, as URL accepts\n // \"C:\\\\path\\\\file.txt\" as valid (!!)\n if (MaybeUrlRE.test(path)) {\n try {\n return new URL(path).pathname;\n } catch {\n // ignore\n }\n }\n return path;\n }\n }\n }\n throw new Error(\"Invalid stack trace format: no parsable frames\");\n}\n","import { getCallerDirname } from \"./stack-path\";\n\n// Thanks to tsup shims, __dirname should always be defined except when run by\n// jest (which will use the stack_path shim)\nexport function _dirname() {\n try {\n if (typeof __dirname !== \"undefined\") return __dirname;\n } catch {\n // ignore\n }\n // we must be in jest. Use the stack_path ~~hack~~ shim:\n return getCallerDirname();\n}\n","/**\n * Simple LRU (Least Recently Used) cache implementation.\n * Uses Map's insertion order to track recency - first key is oldest.\n */\nexport class LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxCapacity: number;\n\n constructor(capacity: number) {\n if (capacity < 1) {\n throw new RangeError(\"LRU cache capacity must be at least 1\");\n }\n this.maxCapacity = capacity;\n }\n\n /**\n * Get a value from the cache.\n * If found, moves the entry to the end (most recently used).\n */\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used) by reinserting\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n /**\n * Set a value in the cache.\n * If key exists, updates and moves to end.\n * If at capacity, evicts the oldest entry first.\n */\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n // Update existing - delete and reinsert at end\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxCapacity) {\n // Evict oldest (first key in Map iteration order)\n const oldestKey = this.cache.keys().next().value;\n if (oldestKey !== undefined) {\n this.cache.delete(oldestKey);\n }\n }\n this.cache.set(key, value);\n }\n\n /**\n * Delete an entry from the cache.\n */\n delete(key: K): boolean {\n return this.cache.delete(key);\n }\n\n /**\n * Check if a key exists in the cache.\n * Does NOT update recency.\n */\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n /**\n * Clear all entries from the cache.\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get the current number of entries in the cache.\n */\n size(): number {\n return this.cache.size;\n }\n\n /**\n * Get the maximum capacity of the cache.\n */\n capacity(): number {\n return this.maxCapacity;\n }\n}\n","import { LRUCache } from \"./lru-cache\";\nimport { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nimport type { StatementSyncInstance } from \"./types/statement-sync-instance\";\n\n/**\n * Default capacity for the statement cache.\n * Matches Node.js SQLTagStore default.\n */\nconst DEFAULT_CAPACITY = 1000;\n\n/**\n * SQLTagStore provides cached prepared statements via tagged template syntax.\n *\n * @example\n * ```js\n * const sql = db.createTagStore();\n * sql.run`INSERT INTO users VALUES (${id}, ${name})`;\n * const user = sql.get`SELECT * FROM users WHERE id = ${id}`;\n * ```\n */\nexport class SQLTagStore {\n private readonly database: DatabaseSyncInstance;\n private readonly cache: LRUCache<string, StatementSyncInstance>;\n private readonly maxCapacity: number;\n\n constructor(db: DatabaseSyncInstance, capacity: number = DEFAULT_CAPACITY) {\n if (!db.isOpen) {\n throw new Error(\"Database is not open\");\n }\n this.database = db;\n this.maxCapacity = capacity;\n this.cache = new LRUCache(capacity);\n }\n\n /**\n * Returns the associated database instance.\n */\n get db(): DatabaseSyncInstance {\n return this.database;\n }\n\n /**\n * Returns the maximum capacity of the statement cache.\n */\n get capacity(): number {\n return this.maxCapacity;\n }\n\n /**\n * Returns the current number of cached statements.\n */\n size(): number {\n return this.cache.size();\n }\n\n /**\n * Clears all cached statements.\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Execute an INSERT, UPDATE, DELETE or other statement that doesn't return rows.\n * Returns an object with `changes` and `lastInsertRowid`.\n */\n run(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): { changes: number; lastInsertRowid: number | bigint } {\n const stmt = this.getOrPrepare(strings);\n return stmt.run(...values);\n }\n\n /**\n * Execute a query and return the first row, or undefined if no rows.\n */\n get(strings: TemplateStringsArray, ...values: unknown[]): unknown {\n const stmt = this.getOrPrepare(strings);\n return stmt.get(...values);\n }\n\n /**\n * Execute a query and return all rows as an array.\n */\n all(strings: TemplateStringsArray, ...values: unknown[]): unknown[] {\n const stmt = this.getOrPrepare(strings);\n return stmt.all(...values);\n }\n\n /**\n * Execute a query and return an iterator over the rows.\n */\n iterate(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): IterableIterator<unknown> {\n const stmt = this.getOrPrepare(strings);\n return stmt.iterate(...values);\n }\n\n /**\n * Get a cached statement or prepare a new one.\n * If a cached statement has been finalized, it's evicted and a new one is prepared.\n */\n private getOrPrepare(strings: TemplateStringsArray): StatementSyncInstance {\n if (!this.database.isOpen) {\n throw new Error(\"Database is not open\");\n }\n\n const sql = this.buildSQL(strings);\n\n // Check cache - evict if finalized\n const cached = this.cache.get(sql);\n if (cached) {\n if (!cached.finalized) {\n return cached;\n }\n // Statement was finalized externally - remove from cache\n this.cache.delete(sql);\n }\n\n // Prepare new statement and cache it\n const stmt = this.database.prepare(sql);\n this.cache.set(sql, stmt);\n return stmt;\n }\n\n /**\n * Build the SQL string by joining template parts with `?` placeholders.\n */\n private buildSQL(strings: TemplateStringsArray): string {\n let sql = strings[0] ?? \"\";\n for (let i = 1; i < strings.length; i++) {\n // eslint-disable-next-line security/detect-object-injection -- Index is from controlled for-loop\n sql += \"?\" + (strings[i] ?? \"\");\n }\n return sql;\n }\n}\n"],"mappings":"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","names":["path"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/tsup/assets/esm_shims.js","../src/index.ts","../src/stack-path.ts","../src/dirname.ts","../src/lru-cache.ts","../src/sql-tag-store.ts","../src/transaction.ts","../src/enhance.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","// Load the native binding with support for both CJS and ESM\nimport nodeGypBuild from \"node-gyp-build\";\nimport { join } from \"node:path\";\nimport { _dirname } from \"./dirname\";\nimport { SQLTagStore } from \"./sql-tag-store\";\nimport { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nimport { DatabaseSyncOptions } from \"./types/database-sync-options\";\nimport { SQLTagStoreInstance } from \"./types/sql-tag-store-instance\";\nimport { SqliteAuthorizationActions } from \"./types/sqlite-authorization-actions\";\nimport { SqliteAuthorizationResults } from \"./types/sqlite-authorization-results\";\nimport { SqliteChangesetConflictTypes } from \"./types/sqlite-changeset-conflict-types\";\nimport { SqliteChangesetResolution } from \"./types/sqlite-changeset-resolution\";\nimport { SqliteOpenFlags } from \"./types/sqlite-open-flags\";\nimport { StatementSyncInstance } from \"./types/statement-sync-instance\";\n\nexport type { AggregateOptions } from \"./types/aggregate-options\";\nexport type { ChangesetApplyOptions } from \"./types/changeset-apply-options\";\nexport type { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nexport type { DatabaseSyncOptions } from \"./types/database-sync-options\";\nexport type { PragmaOptions } from \"./types/pragma-options\";\nexport type { SessionOptions } from \"./types/session-options\";\nexport type { SQLTagStoreInstance } from \"./types/sql-tag-store-instance\";\nexport type { SqliteAuthorizationActions } from \"./types/sqlite-authorization-actions\";\nexport type { SqliteAuthorizationResults } from \"./types/sqlite-authorization-results\";\nexport type { SqliteChangesetConflictTypes } from \"./types/sqlite-changeset-conflict-types\";\nexport type { SqliteChangesetResolution } from \"./types/sqlite-changeset-resolution\";\nexport type { SqliteOpenFlags } from \"./types/sqlite-open-flags\";\nexport type {\n StatementColumnMetadata,\n StatementSyncInstance,\n} from \"./types/statement-sync-instance\";\nexport type { TransactionFunction, TransactionMode } from \"./types/transaction\";\nexport type { UserFunctionOptions } from \"./types/user-functions-options\";\n\n// Enhancement utilities for adding better-sqlite3-style methods to any compatible database\nexport {\n enhance,\n isEnhanced,\n type EnhanceableDatabaseSync,\n type EnhancedDatabaseSync,\n type EnhancedMethods,\n} from \"./enhance\";\n\n// Use _dirname() helper that works in both CJS/ESM and Jest\nconst binding = nodeGypBuild(join(_dirname(), \"..\"));\n\n/**\n * All SQLite constants exported by this module.\n *\n * This is a union of all constant category interfaces:\n * - {@link SqliteOpenFlags} - Database open flags (extension beyond `node:sqlite`)\n * - {@link SqliteChangesetResolution} - Changeset conflict resolution values\n * - {@link SqliteChangesetConflictTypes} - Changeset conflict type codes\n * - {@link SqliteAuthorizationResults} - Authorization return values\n * - {@link SqliteAuthorizationActions} - Authorization action codes\n *\n * **Note:** The categorized interfaces (`SqliteOpenFlags`, etc.) are extensions\n * provided by `@photostructure/sqlite`. The `node:sqlite` module exports only\n * a flat `constants` object without these type categories.\n */\nexport type SqliteConstants = SqliteOpenFlags &\n SqliteChangesetResolution &\n SqliteChangesetConflictTypes &\n SqliteAuthorizationResults &\n SqliteAuthorizationActions;\n\n/**\n * Options for creating a prepared statement.\n *\n * **Note:** The per-statement override options (`readBigInts`, `returnArrays`,\n * `allowBareNamedParameters`, `allowUnknownNamedParameters`) are a **Node.js v25+**\n * feature. On Node.js v24 and earlier, `node:sqlite` silently ignores these options.\n * This library implements them for forward compatibility with Node.js v25+.\n */\nexport interface StatementOptions {\n /** If true, the prepared statement's expandedSQL property will contain the expanded SQL. @default false */\n readonly expandedSQL?: boolean;\n /** If true, anonymous parameters are enabled for the statement. @default false */\n readonly anonymousParameters?: boolean;\n /**\n * If true, read integer values as JavaScript BigInt. Overrides database-level setting.\n * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.\n * @default database default\n */\n readonly readBigInts?: boolean;\n /**\n * If true, return results as arrays rather than objects. Overrides database-level setting.\n * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.\n * @default database default\n */\n readonly returnArrays?: boolean;\n /**\n * If true, allows bare named parameters (without prefix). Overrides database-level setting.\n * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.\n * @default database default\n */\n readonly allowBareNamedParameters?: boolean;\n /**\n * If true, unknown named parameters are ignored. Overrides database-level setting.\n * **Node.js v25+ feature** - silently ignored by `node:sqlite` on v24 and earlier.\n * @default database default\n */\n readonly allowUnknownNamedParameters?: boolean;\n}\n\nexport interface Session {\n /**\n * Generate a changeset containing all changes recorded by the session.\n * @returns A Uint8Array containing the changeset data.\n */\n changeset(): Uint8Array;\n /**\n * Generate a patchset containing all changes recorded by the session.\n * @returns A Uint8Array containing the patchset data.\n */\n patchset(): Uint8Array;\n /**\n * Close the session and release its resources.\n */\n close(): void;\n}\n\n/**\n * The main SQLite module interface.\n */\nexport interface SqliteModule {\n /**\n * The DatabaseSync class represents a synchronous connection to a SQLite database.\n * All operations are performed synchronously, blocking until completion.\n */\n DatabaseSync: new (\n location?: string | Buffer | URL,\n options?: DatabaseSyncOptions,\n ) => DatabaseSyncInstance;\n /**\n * The StatementSync class represents a synchronous prepared statement.\n * This class should not be instantiated directly; use Database.prepare() instead.\n */\n StatementSync: new (\n database: DatabaseSyncInstance,\n sql: string,\n options?: StatementOptions,\n ) => StatementSyncInstance;\n /**\n * The Session class for recording database changes.\n * This class should not be instantiated directly; use Database.createSession() instead.\n */\n Session: new () => Session;\n /**\n * SQLite constants for various operations and flags.\n * @see {@link SqliteConstants} for the type definition\n * @see {@link SqliteOpenFlags} for database open flags (extension beyond `node:sqlite`)\n * @see {@link SqliteChangesetResolution} for changeset conflict resolution values\n * @see {@link SqliteChangesetConflictTypes} for changeset conflict type codes\n * @see {@link SqliteAuthorizationResults} for authorization return values\n * @see {@link SqliteAuthorizationActions} for authorization action codes\n */\n constants: SqliteConstants;\n}\n\n/**\n * The DatabaseSync class represents a synchronous connection to a SQLite database.\n * All database operations are performed synchronously, blocking the thread until completion.\n *\n * @example\n * ```typescript\n * import { DatabaseSync } from '@photostructure/sqlite';\n *\n * // Create an in-memory database\n * const db = new DatabaseSync(':memory:');\n *\n * // Create a file-based database\n * const fileDb = new DatabaseSync('./mydata.db');\n *\n * // Create with options\n * const readOnlyDb = new DatabaseSync('./data.db', { readOnly: true });\n * ```\n */\n// Store the native binding's DatabaseSync\nconst _DatabaseSync = binding.DatabaseSync;\n// Wrapper around the native constructor to enforce usage of `new` with the correct error code.\n// We use a function wrapper instead of a Proxy for better performance and explicit prototype handling.\nexport const DatabaseSync = function DatabaseSync(this: any, ...args: any[]) {\n if (!new.target) {\n const err = new TypeError(\"Cannot call constructor without `new`\");\n (err as NodeJS.ErrnoException).code = \"ERR_CONSTRUCT_CALL_REQUIRED\";\n throw err;\n }\n return Reflect.construct(_DatabaseSync, args, new.target);\n} as unknown as SqliteModule[\"DatabaseSync\"];\nObject.setPrototypeOf(DatabaseSync, _DatabaseSync);\nDatabaseSync.prototype = _DatabaseSync.prototype;\n\n// node:sqlite implements createTagStore and SQLTagStore entirely in native C++.\n// We use a TypeScript implementation instead, attached via prototype extension.\n// This maintains API compatibility with node:sqlite while avoiding the complexity\n// of a native LRU cache. Performance is equivalent since the real cost is SQLite\n// execution, not cache lookups - V8's Map is highly optimized for string keys.\n(DatabaseSync.prototype as DatabaseSyncInstance).createTagStore = function (\n this: DatabaseSyncInstance,\n capacity?: number,\n): SQLTagStoreInstance {\n return new SQLTagStore(this, capacity);\n};\n\n// NOTE: .pragma() and .transaction() are NOT added to the prototype by default.\n// This keeps DatabaseSync 100% API-compatible with node:sqlite.\n// Users who want better-sqlite3-style methods should use enhance():\n//\n// import { DatabaseSync, enhance } from '@photostructure/sqlite';\n// const db = enhance(new DatabaseSync(':memory:'));\n// db.pragma('journal_mode', { simple: true });\n// db.transaction(() => { ... });\n\n/**\n * The StatementSync class represents a prepared SQL statement.\n * This class should not be instantiated directly; use DatabaseSync.prepare() instead.\n *\n * @example\n * ```typescript\n * const stmt = db.prepare('SELECT * FROM users WHERE id = ?');\n * const user = stmt.get(123);\n * stmt.finalize();\n * ```\n */\n// Store the native binding's StatementSync for internal use\nconst _StatementSync = binding.StatementSync;\n// Export a wrapper that throws ERR_ILLEGAL_CONSTRUCTOR when called directly\n// but preserves instanceof checks and prototype chain\nexport const StatementSync = function StatementSync() {\n const err = new TypeError(\"Illegal constructor\");\n (err as NodeJS.ErrnoException).code = \"ERR_ILLEGAL_CONSTRUCTOR\";\n throw err;\n} as unknown as SqliteModule[\"StatementSync\"];\n// Use the native prototype directly so instanceof checks work correctly\n// (stmt instanceof StatementSync will check if StatementSync.prototype is in stmt's chain)\nStatementSync.prototype = _StatementSync.prototype;\n\n/**\n * The Session class for recording database changes.\n * This class should not be instantiated directly; use DatabaseSync.createSession() instead.\n *\n * @example\n * ```typescript\n * const session = db.createSession({ table: 'users' });\n * // Make some changes to the users table\n * const changeset = session.changeset();\n * session.close();\n * ```\n */\nexport const Session = binding.Session as SqliteModule[\"Session\"];\n\n/**\n * The SQLTagStore class for cached prepared statements via tagged template syntax.\n * This class should not be instantiated directly; use DatabaseSync.createTagStore() instead.\n *\n * @example\n * ```typescript\n * const sql = db.createTagStore();\n * sql.run`INSERT INTO users VALUES (${id}, ${name})`;\n * const user = sql.get`SELECT * FROM users WHERE id = ${id}`;\n * ```\n */\nexport { SQLTagStore };\n\n/**\n * SQLite constants for various operations and flags.\n *\n * @example\n * ```typescript\n * import { constants } from '@photostructure/sqlite';\n *\n * const db = new DatabaseSync('./data.db', {\n * readOnly: true,\n * // Uses SQLITE_OPEN_READONLY internally\n * });\n * ```\n */\nexport const constants: SqliteConstants = binding.constants;\n\n/**\n * Options for the backup() function.\n */\nexport interface BackupOptions {\n /** Number of pages to be transmitted in each batch of the backup. @default 100 */\n rate?: number;\n /** Name of the source database. Can be 'main' or any attached database. @default 'main' */\n source?: string;\n /** Name of the target database. Can be 'main' or any attached database. @default 'main' */\n target?: string;\n /** Callback function that will be called with progress information. */\n progress?: (info: { totalPages: number; remainingPages: number }) => void;\n}\n\n/**\n * Standalone function to make a backup of a database.\n *\n * This function matches the Node.js `node:sqlite` module API which exports\n * `backup()` as a standalone function in addition to the `db.backup()` method.\n *\n * @param sourceDb The database to backup from.\n * @param destination The path where the backup will be created.\n * @param options Optional configuration for the backup operation.\n * @returns A promise that resolves when the backup is completed.\n *\n * @example\n * ```typescript\n * import { DatabaseSync, backup } from '@photostructure/sqlite';\n *\n * const db = new DatabaseSync('./source.db');\n * await backup(db, './backup.db');\n *\n * // With options\n * await backup(db, './backup.db', {\n * rate: 10,\n * progress: ({ totalPages, remainingPages }) => {\n * console.log(`Progress: ${totalPages - remainingPages}/${totalPages}`);\n * }\n * });\n * ```\n */\nexport const backup: (\n sourceDb: DatabaseSyncInstance,\n destination: string | Buffer | URL,\n options?: BackupOptions,\n) => Promise<number> = binding.backup;\n\n// Default export for CommonJS compatibility\nexport default binding as SqliteModule;\n","import { dirname } from \"node:path\";\n\nexport function getCallerDirname(): string {\n const e = new Error();\n if (e.stack == null) {\n Error.captureStackTrace(e);\n }\n return dirname(extractCallerPath(e.stack as string));\n}\n\n// Comprehensive regex patterns for different stack frame formats\nconst patterns =\n process.platform === \"win32\"\n ? [\n // File URLs: \"at functionName (file:///C:/path/file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>file:\\/\\/\\/.+?):\\d+:\\d+\\)$/,\n // File URLs direct: \"at file:///C:/path/file.js:1:1\"\n /\\bat\\s(?<path>file:\\/\\/\\/.+?):\\d+:\\d+$/,\n // Standard: \"at functionName (C:\\path\\file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>[A-Z]:\\\\.+):\\d+:\\d+\\)$/,\n // direct: \"at C:\\path\\file.js:1:1\"\n /\\bat\\s(?<path>[A-Z]:\\\\.+):\\d+:\\d+$/,\n // UNC: \"at functionName (\\\\server\\share\\path\\file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>\\\\\\\\.+):\\d+:\\d+\\)$/,\n // direct: \"at \\\\server\\share\\path\\file.js:1:1\"\n /\\bat\\s(?<path>\\\\\\\\.+):\\d+:\\d+$/,\n ]\n : [\n // Standard: \"at functionName (/path/file.js:1:1)\"\n /\\bat\\s.+?\\((?<path>\\/.+?):\\d+:\\d+\\)$/,\n // Anonymous or direct: \"at /path/file.js:1:1\"\n // eslint-disable-next-line security/detect-unsafe-regex -- Pattern is safe: no nested quantifiers\n /\\bat\\s(?:[^\\s()]+\\s)?(?<path>\\/[^:]+):\\d+:\\d+$/,\n ];\n\nconst MaybeUrlRE = /^[a-z]{2,5}:\\/\\//i;\n\n// only exposed for tests:\nexport function extractCallerPath(stack: string): string {\n const frames = stack.split(\"\\n\").filter(Boolean);\n\n // First find getCallerDirname() in the stack:\n const callerFrame = frames.findIndex((frame) =>\n frame.includes(\"getCallerDirname\"),\n );\n if (callerFrame === -1) {\n throw new Error(\"Invalid stack trace format: missing caller frame\");\n }\n for (let i = callerFrame + 1; i < frames.length; i++) {\n // eslint-disable-next-line security/detect-object-injection -- Index is from controlled for-loop\n const frame = frames[i];\n for (const pattern of patterns) {\n const g = frame?.trim().match(pattern)?.groups;\n if (g != null && g[\"path\"]) {\n const path = g[\"path\"];\n // Windows requires us to check if it's a reasonable URL, as URL accepts\n // \"C:\\\\path\\\\file.txt\" as valid (!!)\n if (MaybeUrlRE.test(path)) {\n try {\n return new URL(path).pathname;\n } catch {\n // ignore\n }\n }\n return path;\n }\n }\n }\n throw new Error(\"Invalid stack trace format: no parsable frames\");\n}\n","import { getCallerDirname } from \"./stack-path\";\n\n// Thanks to tsup shims, __dirname should always be defined except when run by\n// jest (which will use the stack_path shim)\nexport function _dirname() {\n try {\n if (typeof __dirname !== \"undefined\") return __dirname;\n } catch {\n // ignore\n }\n // we must be in jest. Use the stack_path ~~hack~~ shim:\n return getCallerDirname();\n}\n","/**\n * Simple LRU (Least Recently Used) cache implementation.\n * Uses Map's insertion order to track recency - first key is oldest.\n */\nexport class LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxCapacity: number;\n\n constructor(capacity: number) {\n if (capacity < 1) {\n throw new RangeError(\"LRU cache capacity must be at least 1\");\n }\n this.maxCapacity = capacity;\n }\n\n /**\n * Get a value from the cache.\n * If found, moves the entry to the end (most recently used).\n */\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used) by reinserting\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n /**\n * Set a value in the cache.\n * If key exists, updates and moves to end.\n * If at capacity, evicts the oldest entry first.\n */\n set(key: K, value: V): void {\n if (this.cache.has(key)) {\n // Update existing - delete and reinsert at end\n this.cache.delete(key);\n } else if (this.cache.size >= this.maxCapacity) {\n // Evict oldest (first key in Map iteration order)\n const oldestKey = this.cache.keys().next().value;\n if (oldestKey !== undefined) {\n this.cache.delete(oldestKey);\n }\n }\n this.cache.set(key, value);\n }\n\n /**\n * Delete an entry from the cache.\n */\n delete(key: K): boolean {\n return this.cache.delete(key);\n }\n\n /**\n * Check if a key exists in the cache.\n * Does NOT update recency.\n */\n has(key: K): boolean {\n return this.cache.has(key);\n }\n\n /**\n * Clear all entries from the cache.\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Get the current number of entries in the cache.\n */\n size(): number {\n return this.cache.size;\n }\n\n /**\n * Get the maximum capacity of the cache.\n */\n capacity(): number {\n return this.maxCapacity;\n }\n}\n","import { LRUCache } from \"./lru-cache\";\nimport { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nimport type { StatementSyncInstance } from \"./types/statement-sync-instance\";\n\n/**\n * Default capacity for the statement cache.\n * Matches Node.js SQLTagStore default.\n */\nconst DEFAULT_CAPACITY = 1000;\n\n/**\n * SQLTagStore provides cached prepared statements via tagged template syntax.\n *\n * @example\n * ```js\n * const sql = db.createTagStore();\n * sql.run`INSERT INTO users VALUES (${id}, ${name})`;\n * const user = sql.get`SELECT * FROM users WHERE id = ${id}`;\n * ```\n */\nexport class SQLTagStore {\n private readonly database: DatabaseSyncInstance;\n private readonly cache: LRUCache<string, StatementSyncInstance>;\n private readonly maxCapacity: number;\n\n constructor(db: DatabaseSyncInstance, capacity: number = DEFAULT_CAPACITY) {\n if (!db.isOpen) {\n const err = new Error(\"database is not open\");\n (err as NodeJS.ErrnoException).code = \"ERR_INVALID_STATE\";\n throw err;\n }\n this.database = db;\n this.maxCapacity = capacity;\n this.cache = new LRUCache(capacity);\n }\n\n /**\n * Returns the associated database instance.\n */\n get db(): DatabaseSyncInstance {\n return this.database;\n }\n\n /**\n * Returns the maximum capacity of the statement cache.\n */\n get capacity(): number {\n return this.maxCapacity;\n }\n\n /**\n * Returns the current number of cached statements.\n */\n get size(): number {\n return this.cache.size();\n }\n\n /**\n * Clears all cached statements.\n */\n clear(): void {\n this.cache.clear();\n }\n\n /**\n * Execute an INSERT, UPDATE, DELETE or other statement that doesn't return rows.\n * Returns an object with `changes` and `lastInsertRowid`.\n */\n run(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): { changes: number; lastInsertRowid: number | bigint } {\n const stmt = this.getOrPrepare(strings);\n return stmt.run(...values);\n }\n\n /**\n * Execute a query and return the first row, or undefined if no rows.\n */\n get(strings: TemplateStringsArray, ...values: unknown[]): unknown {\n const stmt = this.getOrPrepare(strings);\n return stmt.get(...values);\n }\n\n /**\n * Execute a query and return all rows as an array.\n */\n all(strings: TemplateStringsArray, ...values: unknown[]): unknown[] {\n const stmt = this.getOrPrepare(strings);\n return stmt.all(...values);\n }\n\n /**\n * Execute a query and return an iterator over the rows.\n */\n iterate(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ): IterableIterator<unknown> {\n const stmt = this.getOrPrepare(strings);\n return stmt.iterate(...values);\n }\n\n /**\n * Get a cached statement or prepare a new one.\n */\n private getOrPrepare(strings: TemplateStringsArray): StatementSyncInstance {\n if (!this.database.isOpen) {\n throw new Error(\"database is not open\");\n }\n\n const sql = this.buildSQL(strings);\n\n // Check cache\n const cached = this.cache.get(sql);\n if (cached) {\n return cached;\n }\n\n // Prepare new statement and cache it\n const stmt = this.database.prepare(sql);\n this.cache.set(sql, stmt);\n return stmt;\n }\n\n /**\n * Build the SQL string by joining template parts with `?` placeholders.\n */\n private buildSQL(strings: TemplateStringsArray): string {\n let sql = strings[0] ?? \"\";\n for (let i = 1; i < strings.length; i++) {\n // eslint-disable-next-line security/detect-object-injection -- Index is from controlled for-loop\n sql += \"?\" + (strings[i] ?? \"\");\n }\n return sql;\n }\n}\n","import type { DatabaseSyncInstance } from \"./types/database-sync-instance\";\nimport type { TransactionFunction, TransactionMode } from \"./types/transaction\";\n\n/**\n * Internal counter for generating unique savepoint names.\n * Using a global counter ensures uniqueness even with deeply nested transactions.\n */\nlet savepointCounter = 0;\n\n/**\n * Creates a transaction-wrapped version of a function.\n *\n * When the returned function is called, it will:\n * 1. Begin a transaction (or create a savepoint if already in a transaction)\n * 2. Execute the wrapped function\n * 3. Commit the transaction (or release the savepoint) on success\n * 4. Rollback the transaction (or rollback to savepoint) on error\n *\n * The wrapped function **must not** return a Promise. SQLite transactions are\n * synchronous, and allowing async operations would leave the transaction open\n * across event loop ticks, which is dangerous and can cause deadlocks.\n *\n * @example\n * ```typescript\n * const db = new DatabaseSync(':memory:');\n * db.exec('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');\n *\n * const insert = db.prepare('INSERT INTO items (name) VALUES (?)');\n *\n * // Create a transaction function\n * const insertMany = db.transaction((names: string[]) => {\n * for (const name of names) {\n * insert.run(name);\n * }\n * return names.length;\n * });\n *\n * // Execute - automatically wrapped in BEGIN/COMMIT\n * const count = insertMany(['Alice', 'Bob', 'Charlie']);\n * console.log(count); // 3\n *\n * // If an error occurs, automatically rolls back\n * try {\n * insertMany(['Dave', 'FAIL']); // Assume this throws\n * } catch (e) {\n * // Transaction was rolled back, Dave was not inserted\n * }\n *\n * // Use different transaction modes\n * insertMany.immediate(['Eve']); // BEGIN IMMEDIATE\n * insertMany.exclusive(['Frank']); // BEGIN EXCLUSIVE\n * ```\n *\n * @param db - The database connection\n * @param fn - The function to wrap in a transaction\n * @returns A transaction function with `.deferred`, `.immediate`, and `.exclusive` variants\n */\nexport function createTransaction<F extends (...args: any[]) => any>(\n db: DatabaseSyncInstance,\n fn: F,\n): TransactionFunction<F> {\n if (typeof fn !== \"function\") {\n throw new TypeError(\"Expected first argument to be a function\");\n }\n\n // Create all four variants\n const variants = {\n deferred: createVariant(db, fn, \"deferred\"),\n immediate: createVariant(db, fn, \"immediate\"),\n exclusive: createVariant(db, fn, \"exclusive\"),\n };\n\n // The default function uses DEFERRED mode (SQLite's default)\n const defaultFn = variants.deferred as TransactionFunction<F>;\n\n // Set up the variant properties on each function\n // Each variant has access to all other variants\n for (const variant of Object.values(variants)) {\n Object.defineProperties(variant, {\n deferred: { value: variants.deferred, enumerable: true },\n immediate: { value: variants.immediate, enumerable: true },\n exclusive: { value: variants.exclusive, enumerable: true },\n database: { value: db, enumerable: true },\n });\n }\n\n return defaultFn;\n}\n\n/**\n * Creates a single transaction variant for a specific mode.\n */\nfunction createVariant<F extends (...args: any[]) => any>(\n db: DatabaseSyncInstance,\n fn: F,\n mode: TransactionMode,\n): (...args: Parameters<F>) => ReturnType<F> {\n const beginStatement = getBeginStatement(mode);\n\n return function transactionWrapper(\n this: unknown,\n ...args: Parameters<F>\n ): ReturnType<F> {\n // Check if we're already in a transaction (nested transaction)\n const isNested = db.isTransaction;\n\n let begin: string;\n let commit: string;\n let rollback: string;\n\n if (isNested) {\n // Use savepoints for nested transactions\n // The savepoint name uses backticks to allow special characters\n // and a counter to ensure uniqueness\n const savepointName = `\\`_txn_${++savepointCounter}\\``;\n begin = `SAVEPOINT ${savepointName}`;\n commit = `RELEASE ${savepointName}`;\n rollback = `ROLLBACK TO ${savepointName}`;\n } else {\n // Top-level transaction\n begin = beginStatement;\n commit = \"COMMIT\";\n rollback = \"ROLLBACK\";\n }\n\n // Begin the transaction or savepoint\n db.exec(begin);\n\n try {\n // Execute the wrapped function\n const result = fn.apply(this, args);\n\n // Check for promises - async functions break transaction semantics\n if (result !== null && typeof result === \"object\" && \"then\" in result) {\n throw new TypeError(\n \"Transaction function must not return a Promise. \" +\n \"SQLite transactions are synchronous and cannot span across async operations. \" +\n \"Use synchronous code within transactions.\",\n );\n }\n\n // Commit the transaction or release the savepoint\n db.exec(commit);\n\n return result;\n } catch (error) {\n // Only attempt rollback if we're still in a transaction\n // (SQLite may have already rolled back due to constraint violations, etc.)\n if (db.isTransaction) {\n db.exec(rollback);\n // For nested transactions, we also need to release the savepoint\n // after rolling back to it (the savepoint still exists after ROLLBACK TO)\n if (isNested) {\n db.exec(commit);\n }\n }\n\n throw error;\n }\n };\n}\n\n/**\n * Returns the appropriate BEGIN statement for a transaction mode.\n */\nfunction getBeginStatement(mode: TransactionMode): string {\n switch (mode) {\n case \"deferred\":\n return \"BEGIN DEFERRED\";\n case \"immediate\":\n return \"BEGIN IMMEDIATE\";\n case \"exclusive\":\n return \"BEGIN EXCLUSIVE\";\n default:\n // TypeScript should catch this, but just in case\n throw new Error(`Unknown transaction mode: ${mode}`);\n }\n}\n","/**\n * Enhancement utilities for adding better-sqlite3-style methods to any\n * compatible database, including `node:sqlite` DatabaseSync and this package's\n * DatabaseSync.\n *\n * This module provides the `enhance()` function which adds `.pragma()` and\n * `.transaction()` methods to database instances that don't have them (e.g.,\n * node:sqlite DatabaseSync).\n */\n\nimport { createTransaction } from \"./transaction\";\nimport type { PragmaOptions } from \"./types/pragma-options\";\nimport type { TransactionFunction } from \"./types/transaction\";\n\n/**\n * Minimal interface for a database that can be enhanced. This matches the\n * subset of functionality needed by pragma() and transaction().\n */\nexport interface EnhanceableDatabaseSync {\n /** Execute SQL without returning results */\n exec(sql: string): void;\n /** Prepare a statement that can return results */\n prepare(sql: string): { all(): unknown[] };\n /** Whether a transaction is currently active */\n readonly isTransaction: boolean;\n}\n\n/**\n * Interface for an enhanced database with pragma() and transaction() methods.\n */\nexport interface EnhancedMethods {\n /**\n * Executes a PRAGMA statement and returns its result.\n *\n * @param source The PRAGMA command (without \"PRAGMA\" prefix)\n * @param options Optional configuration\n * @returns Array of rows, or single value if `simple: true`\n *\n * @example\n * ```typescript\n * db.pragma('cache_size', { simple: true }); // -16000\n * db.pragma('journal_mode = wal');\n * ```\n */\n pragma(source: string, options?: PragmaOptions): unknown;\n\n /**\n * Creates a function that always runs inside a transaction.\n *\n * @param fn The function to wrap in a transaction\n * @returns A transaction function with `.deferred`, `.immediate`,\n * `.exclusive` variants\n *\n * @example\n * ```typescript\n * const insertMany = db.transaction((items) => {\n * for (const item of items) insert.run(item);\n * });\n * insertMany(['a', 'b', 'c']); // All in one transaction\n * ```\n */\n transaction<F extends (...args: any[]) => any>(fn: F): TransactionFunction<F>;\n}\n\n/**\n * A database instance that has been enhanced with pragma() and transaction() methods.\n */\nexport type EnhancedDatabaseSync<T extends EnhanceableDatabaseSync> = T &\n EnhancedMethods;\n\n/**\n * Implementation of pragma() that works on any EnhanceableDatabaseSync.\n */\nfunction pragmaImpl(\n this: EnhanceableDatabaseSync,\n source: string,\n options?: PragmaOptions,\n): unknown {\n if (typeof source !== \"string\") {\n throw new TypeError(\"Expected first argument to be a string\");\n }\n if (options != null && typeof options !== \"object\") {\n throw new TypeError(\"Expected second argument to be an options object\");\n }\n\n const simple = options?.simple === true;\n\n // Validate that simple is a boolean if provided\n if (\n options != null &&\n \"simple\" in options &&\n typeof options.simple !== \"boolean\"\n ) {\n throw new TypeError('Expected the \"simple\" option to be a boolean');\n }\n\n const stmt = this.prepare(`PRAGMA ${source}`);\n const rows = stmt.all() as Record<string, unknown>[];\n\n if (simple) {\n // Return the first column of the first row, or undefined if no rows\n const firstRow = rows[0];\n if (firstRow == null) {\n return undefined;\n }\n const keys = Object.keys(firstRow);\n const firstKey = keys[0];\n if (firstKey == null) {\n return undefined;\n }\n return firstRow[firstKey];\n }\n\n return rows;\n}\n\n/**\n * Implementation of transaction() that works on any EnhanceableDatabaseSync.\n */\nfunction transactionImpl<F extends (...args: any[]) => any>(\n this: EnhanceableDatabaseSync,\n fn: F,\n): TransactionFunction<F> {\n // createTransaction expects DatabaseSyncInstance but only uses the subset\n // defined in EnhanceableDatabaseSync, so this cast is safe\n return createTransaction(this as any, fn);\n}\n\n/**\n * Checks if a database instance already has the enhanced methods.\n */\nfunction hasEnhancedMethods(\n db: EnhanceableDatabaseSync,\n): db is EnhanceableDatabaseSync & EnhancedMethods {\n return (\n typeof (db as any).pragma === \"function\" &&\n typeof (db as any).transaction === \"function\"\n );\n}\n\n/**\n * Ensures that `.pragma()` and `.transaction()` methods are available on the\n * given database.\n *\n * This function can enhance:\n * - `node:sqlite` DatabaseSync instances (adds the methods)\n * - `@photostructure/sqlite` DatabaseSync instances (no-op, already has these\n * methods)\n * - Any object with compatible `exec()`, `prepare()`, and `isTransaction`\n *\n * The enhancement is done by adding methods directly to the instance, not the\n * prototype, so it won't affect other instances or the original class.\n *\n * @param db The database instance to enhance\n * @returns The same instance with `.pragma()` and `.transaction()` methods\n * guaranteed\n *\n * @example\n * ```typescript\n * // With node:sqlite\n * import { DatabaseSync } from 'node:sqlite';\n * import { enhance } from '@photostructure/sqlite';\n *\n * const db = enhance(new DatabaseSync(':memory:'));\n *\n * // Now you can use better-sqlite3-style methods\n * db.pragma('journal_mode = wal');\n * const insertMany = db.transaction((items) => {\n * for (const item of items) insert.run(item);\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With @photostructure/sqlite (no-op, already enhanced)\n * import { DatabaseSync, enhance } from '@photostructure/sqlite';\n *\n * const db = enhance(new DatabaseSync(':memory:'));\n * // db already had these methods, enhance() just returns it unchanged\n * ```\n */\nexport function enhance<T extends EnhanceableDatabaseSync>(\n db: T,\n): EnhancedDatabaseSync<T> {\n // If already enhanced, return as-is\n if (hasEnhancedMethods(db)) {\n return db;\n }\n\n // Add methods directly to the instance\n // Using Object.defineProperty to make them non-enumerable like native methods\n Object.defineProperty(db, \"pragma\", {\n value: pragmaImpl,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n\n Object.defineProperty(db, \"transaction\", {\n value: transactionImpl,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n\n return db as EnhancedDatabaseSync<T>;\n}\n\n/**\n * Type guard to check if a database has enhanced methods.\n *\n * @param db The database to check\n * @returns True if the database has `.pragma()` and `.transaction()` methods\n *\n * @example\n * ```typescript\n * import { isEnhanced } from '@photostructure/sqlite';\n *\n * if (isEnhanced(db)) {\n * db.pragma('cache_size', { simple: true });\n * }\n * ```\n */\nexport function isEnhanced(\n db: EnhanceableDatabaseSync,\n): db is EnhanceableDatabaseSync & EnhancedMethods {\n return hasEnhancedMethods(db);\n}\n"],"mappings":"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","names":["path","DatabaseSync","StatementSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photostructure/sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Drop-in replacement for node:sqlite",
|
|
5
5
|
"homepage": "https://photostructure.github.io/node-sqlite/",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -27,68 +27,76 @@
|
|
|
27
27
|
"private": false,
|
|
28
28
|
"scripts": {
|
|
29
29
|
"install": "node-gyp-build",
|
|
30
|
-
"
|
|
30
|
+
"//build": "build commands",
|
|
31
|
+
"build": "run-p build:dist build:native",
|
|
32
|
+
"build:dist": "tsup && node scripts/post-build.mjs",
|
|
33
|
+
"build:native": "tsx scripts/build-native.ts",
|
|
34
|
+
"build:native:linux": "bash scripts/prebuild-linux-glibc.sh",
|
|
35
|
+
"build:native:rebuild": "node-gyp rebuild",
|
|
36
|
+
"rebuild": "run-s clean build",
|
|
37
|
+
"//clean": "cleanup commands",
|
|
38
|
+
"clean": "run-p clean:dist clean:native",
|
|
31
39
|
"clean:dist": "del-cli dist \"*.tsbuildinfo\"",
|
|
32
40
|
"clean:native": "node-gyp clean && del-cli prebuilds",
|
|
41
|
+
"clean:docker": "docker rm -f $(docker ps -aq --filter name=node-sqlite-test 2>/dev/null) 2>/dev/null || true",
|
|
42
|
+
"clean:docker:cache": "del-cli .cache",
|
|
43
|
+
"clean:all": "run-p clean:dist clean:native clean:docker clean:docker:cache",
|
|
44
|
+
"//sync": "sync upstream sources",
|
|
45
|
+
"sync": "run-s sync:node sync:sqlite sync:tests",
|
|
33
46
|
"sync:node": "tsx scripts/sync-from-node.ts",
|
|
34
47
|
"sync:sqlite": "tsx scripts/sync-from-sqlite.ts",
|
|
35
|
-
"
|
|
36
|
-
"//
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"//
|
|
49
|
-
"
|
|
50
|
-
"// test": "support `npm t name_of_file` (and don't fail due to missing coverage)",
|
|
51
|
-
"test": "npm run tests:cjs -- --no-coverage",
|
|
52
|
-
"test:serial": "npm run tests:cjs -- --runInBand --no-coverage",
|
|
53
|
-
"test:all": "run-s \"tests:cjs -- {@}\" \"tests:esm -- {@}\" --",
|
|
54
|
-
"// test:api-compat": "run by the api compatibility CI workflow",
|
|
55
|
-
"test:api-compat": "jest --config jest.config.api-compat.cjs",
|
|
56
|
-
"test:node-compat": "cross-env NODE_OPTIONS=\"--experimental-sqlite --no-warnings --expose-gc\" npm run tests:cjs -- --no-coverage test/node-compatibility.test.ts",
|
|
57
|
-
"test:build-extension": "cd test/fixtures/test-extension && node build.js",
|
|
58
|
-
"memory:test": "cross-env TEST_MEMORY=1 node --expose-gc node_modules/jest/bin/jest.js --no-coverage test/memory.test.ts",
|
|
59
|
-
"memory:valgrind": "bash scripts/valgrind-test.sh",
|
|
60
|
-
"memory:asan": "bash scripts/sanitizers-test.sh",
|
|
61
|
-
"check:memory": "npx --yes tsx scripts/check-memory.ts",
|
|
62
|
-
"benchmark": "cd benchmark && npm install && npm run bench",
|
|
63
|
-
"benchmark:memory": "cd benchmark && npm install && npm run bench:memory",
|
|
64
|
-
"benchmark:full": "cd benchmark && npm install && npm run bench && npm run bench:memory",
|
|
65
|
-
"stress-test": "cd benchmark && npm install && cd .. && tsx scripts/stress-test.ts",
|
|
66
|
-
"stress-test:validate": "npm run tests:cjs -- --no-coverage benchmark/stress-test.test.ts",
|
|
67
|
-
"stress-test:ci": "cd benchmark && npm install && cd .. && tsx scripts/stress-test.ts --ci --output-file stress-test-results.json",
|
|
68
|
-
"lint": "run-p lint:*",
|
|
48
|
+
"sync:tests": "tsx scripts/sync-node-tests.ts",
|
|
49
|
+
"//test": "'test' is quick dev feedback, 'test:all' is comprehensive",
|
|
50
|
+
"test": "npm run build:dist && node --expose-gc node_modules/jest/bin/jest.js --no-coverage",
|
|
51
|
+
"test:cjs": "node --expose-gc node_modules/jest/bin/jest.js",
|
|
52
|
+
"test:esm": "cross-env TEST_ESM=1 node --expose-gc --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js",
|
|
53
|
+
"test:all": "npm run build && run-s test:cjs test:esm",
|
|
54
|
+
"test:serial": "node --expose-gc node_modules/jest/bin/jest.js --runInBand --no-coverage",
|
|
55
|
+
"test:api": "jest --config jest.config.api-compat.cjs",
|
|
56
|
+
"test:node": "node --test test/node-compat/*.js test/node-compat/*.mjs",
|
|
57
|
+
"test:memory": "cross-env TEST_MEMORY=1 node --expose-gc node_modules/jest/bin/jest.js --no-coverage test/memory.test.ts",
|
|
58
|
+
"test:extension": "cd test/fixtures/test-extension && node build.js",
|
|
59
|
+
"test:docker:debian": "REUSE_BUILD=1 REUSE_CONTAINER=1 bash scripts/test-docker-debian.sh",
|
|
60
|
+
"test:docker:alpine": "REUSE_BUILD=1 REUSE_CONTAINER=1 bash scripts/test-docker-alpine.sh",
|
|
61
|
+
"//lint": "'lint' runs everywhere, 'lint:full' adds optional tools",
|
|
62
|
+
"lint": "run-p lint:ts lint:eslint",
|
|
69
63
|
"lint:ts": "tsc --noEmit",
|
|
70
|
-
"lint:ts
|
|
71
|
-
"lint:ts
|
|
72
|
-
"lint:ts
|
|
64
|
+
"lint:ts:build": "tsc -p tsconfig.build.json --noEmit",
|
|
65
|
+
"lint:ts:scripts": "tsc --noEmit --module esnext --target es2022 --moduleResolution node --project scripts/tsconfig.json",
|
|
66
|
+
"lint:ts:bench": "cd benchmark && npm install && tsc --noEmit",
|
|
73
67
|
"lint:eslint": "eslint src/ test/ scripts/",
|
|
74
|
-
"// lint:api-compat": "run by the api compatibility CI workflow",
|
|
75
|
-
"lint:api-compat": "tsx scripts/check-api-compat.ts",
|
|
76
68
|
"lint:native": "tsx scripts/clang-tidy.ts",
|
|
77
|
-
"
|
|
69
|
+
"lint:api": "tsx scripts/check-api-compat.ts",
|
|
70
|
+
"lint:full": "run-s lint lint:native lint:api",
|
|
71
|
+
"//fmt": "code formatting",
|
|
72
|
+
"fmt": "run-p fmt:prettier fmt:native",
|
|
78
73
|
"fmt:prettier": "prettier --write .",
|
|
79
|
-
"// fmt:native": "on ubuntu: `sudo apt install clang-format`. NOTE: we don't reformat src/upstream!",
|
|
80
74
|
"fmt:native": "clang-format --style=LLVM -i src/shims/*.h src/*.cpp src/*.h test/fixtures/test-extension/*.c || true",
|
|
75
|
+
"//memory": "memory analysis tools",
|
|
76
|
+
"memory:check": "npx --yes tsx scripts/check-memory.ts",
|
|
77
|
+
"memory:valgrind": "bash scripts/valgrind-test.sh",
|
|
78
|
+
"memory:asan": "bash scripts/sanitizers-test.sh",
|
|
79
|
+
"//bench": "benchmarks",
|
|
80
|
+
"bench": "cd benchmark && npm install && npm run bench",
|
|
81
|
+
"bench:memory": "cd benchmark && npm install && npm run bench:memory",
|
|
82
|
+
"bench:full": "cd benchmark && npm install && npm run bench && npm run bench:memory",
|
|
83
|
+
"//stress": "stress tests",
|
|
84
|
+
"stress": "cd benchmark && npm install && cd .. && tsx scripts/stress-test.ts",
|
|
85
|
+
"stress:validate": "node --expose-gc node_modules/jest/bin/jest.js --no-coverage benchmark/stress-test.test.ts",
|
|
86
|
+
"stress:ci": "cd benchmark && npm install && cd .. && tsx scripts/stress-test.ts --ci --output-file stress-test-results.json",
|
|
87
|
+
"//security": "security scanning",
|
|
88
|
+
"security": "run-s security:audit security:osv",
|
|
89
|
+
"security:audit": "npm audit --production",
|
|
90
|
+
"security:audit:fix": "npm audit fix --force",
|
|
91
|
+
"security:osv": "osv-scanner scan --recursive . || true",
|
|
92
|
+
"//docs": "documentation",
|
|
81
93
|
"docs": "typedoc",
|
|
82
|
-
"//
|
|
94
|
+
"//ci": "CI and release commands",
|
|
83
95
|
"precommit": "npx --yes tsx scripts/precommit.ts",
|
|
84
|
-
"prepare
|
|
85
|
-
"
|
|
86
|
-
"security:audit-fix": "npm audit fix --force",
|
|
87
|
-
"security:audit": "npm audit --production",
|
|
96
|
+
"release:prepare": "npm run build:dist",
|
|
97
|
+
"update:actions": "pinact run -u",
|
|
88
98
|
"install:osv-scanner": "go install github.com/google/osv-scanner/cmd/osv-scanner@latest",
|
|
89
|
-
"
|
|
90
|
-
"install:actions": "go install github.com/suzuki-shunsuke/pinact/cmd/pinact@latest",
|
|
91
|
-
"update:actions": "pinact run -u"
|
|
99
|
+
"install:actions": "go install github.com/suzuki-shunsuke/pinact/cmd/pinact@latest"
|
|
92
100
|
},
|
|
93
101
|
"gypfile": true,
|
|
94
102
|
"publishConfig": {
|
|
@@ -124,31 +132,32 @@
|
|
|
124
132
|
"node-gyp-build": "^4.8.4"
|
|
125
133
|
},
|
|
126
134
|
"devDependencies": {
|
|
135
|
+
"@mceachen/sqlite-vec": "^0.3.2",
|
|
127
136
|
"@types/jest": "^30.0.0",
|
|
128
|
-
"@types/node": "^
|
|
129
|
-
"better-sqlite3": "^12.
|
|
137
|
+
"@types/node": "^25.0.3",
|
|
138
|
+
"better-sqlite3": "^12.6.2",
|
|
130
139
|
"cross-env": "^10.1.0",
|
|
131
140
|
"del-cli": "^7.0.0",
|
|
132
|
-
"eslint": "^9.39.
|
|
133
|
-
"eslint-plugin-regexp": "^
|
|
141
|
+
"eslint": "^9.39.2",
|
|
142
|
+
"eslint-plugin-regexp": "^3.0.0",
|
|
134
143
|
"eslint-plugin-security": "^3.0.1",
|
|
135
|
-
"globals": "^
|
|
144
|
+
"globals": "^17.0.0",
|
|
136
145
|
"jest": "^30.2.0",
|
|
137
|
-
"node-gyp": "^12.
|
|
146
|
+
"node-gyp": "^12.2.0",
|
|
138
147
|
"npm-check-updates": "^19",
|
|
139
|
-
"npm-run-
|
|
148
|
+
"npm-run-all2": "8.0.4",
|
|
140
149
|
"prebuildify": "^6.0.1",
|
|
141
|
-
"prettier": "^3.
|
|
150
|
+
"prettier": "^3.8.1",
|
|
142
151
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
143
|
-
"ts-jest": "^29.4.
|
|
152
|
+
"ts-jest": "^29.4.6",
|
|
144
153
|
"tsup": "^8.5.1",
|
|
145
|
-
"tsx": "^4.
|
|
146
|
-
"typedoc": "^0.28.
|
|
154
|
+
"tsx": "^4.21.0",
|
|
155
|
+
"typedoc": "^0.28.16",
|
|
147
156
|
"typescript": "^5.9.3",
|
|
148
|
-
"typescript-eslint": "^8.
|
|
157
|
+
"typescript-eslint": "^8.54.0"
|
|
149
158
|
},
|
|
150
159
|
"versions": {
|
|
151
|
-
"nodejs": "v25.
|
|
152
|
-
"sqlite": "3.51.
|
|
160
|
+
"nodejs": "v25.6.1@dffcdb3",
|
|
161
|
+
"sqlite": "3.51.2"
|
|
153
162
|
}
|
|
154
163
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -97,10 +97,12 @@ docker exec "$CONTAINER_NAME" sh -c "
|
|
|
97
97
|
$BUILD_CMD
|
|
98
98
|
"
|
|
99
99
|
|
|
100
|
-
# Copy artifacts back with
|
|
101
|
-
docker
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
# Copy artifacts back with correct ownership (docker cp creates root-owned files)
|
|
101
|
+
docker exec "$CONTAINER_NAME" tar -cf - -C /tmp/project \
|
|
102
|
+
prebuilds \
|
|
103
|
+
build \
|
|
104
|
+
config.gypi \
|
|
105
|
+
2>/dev/null | tar -xf - --owner="$(id -u)" --group="$(id -g)" || true
|
|
104
106
|
|
|
105
107
|
# Clean up container
|
|
106
108
|
docker rm -f "$CONTAINER_NAME" >/dev/null
|