@jarenjs/db 0.87.0 → 0.89.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.
@@ -0,0 +1,14 @@
1
+ //@ts-check
2
+ /** Native filesystem identity stays below the Node/Bun entry points. */
3
+ import { chain } from '../driver.js';
4
+ import { sqliteDatabasePath } from '../migration-target.js';
5
+
6
+ /** Device/inode identity also catches hardlinks that SQLite filenames cannot.
7
+ * The filesystem is imported only when identifying an opened file; two private
8
+ * in-memory databases remain independent.
9
+ * @param {any} connection @returns {any} value-or-promise of string or null */
10
+ export function nativeDatabaseIdentity(connection) {
11
+ return chain(sqliteDatabasePath(connection), (path) => path === null ? null
12
+ : import('node:fs/promises').then((fs) => fs.stat(path, { bigint: true }))
13
+ .then((stat) => `${stat.dev}:${stat.ino}`));
14
+ }
@@ -9,6 +9,7 @@
9
9
  import { lazyOpen, openConnection } from '../driver.js';
10
10
  import { sqliteDialect } from '../dialects/sqlite.js';
11
11
  import { PRAGMA_NAMES } from '../pragmas.js';
12
+ import { nativeDatabaseIdentity } from './file-identity.js';
12
13
 
13
14
  /**
14
15
  * Adapt an already-constructed `node:sqlite` `DatabaseSync` (or any
@@ -109,6 +110,7 @@ export function nodeDriver() {
109
110
  return Object.freeze({
110
111
  name: 'node-sqlite',
111
112
  dialect: sqliteDialect,
113
+ databaseIdentity: nativeDatabaseIdentity,
112
114
  /**
113
115
  * @param {string} path
114
116
  * @param {{ timeout?: number, readOnly?: boolean,
@@ -0,0 +1,50 @@
1
+ //@ts-check
2
+ /** SQLite's connection settings belong outside the transaction they govern. */
3
+ import { isThenable } from '@jarenjs/core/function';
4
+ import { chain } from './driver.js';
5
+ import { DbCompileError } from './errors.js';
6
+
7
+ /** Suspend enforcement and legacy rewrites for a caller-owned transaction,
8
+ * restoring the exact original settings after success or failure. The caller
9
+ * still must run foreign_key_check before committing. Value-or-promise, so both
10
+ * synchronous table helpers and asynchronous migration drivers share one bracket.
11
+ * @param {any} connection @param {()=>any} fn @returns {any} */
12
+ export function withForeignKeySettings(connection, fn) {
13
+ const dialect = connection.dialect;
14
+ const read = (name) => chain(connection.prepare(dialect.introspect.pragma(name)), (s) => chain(s.get([]), (row) => row[name]));
15
+ return chain(read('foreign_keys'), (foreignKeys) => chain(read('legacy_alter_table'), (legacy) => {
16
+ const restore = () => {
17
+ const errors = [];
18
+ const attempt = (sql) => {
19
+ try {
20
+ const result = connection.exec(sql);
21
+ return isThenable(result) ? result.then(() => undefined, (error) => { errors.push(error); }) : undefined;
22
+ }
23
+ catch (error) { errors.push(error); return undefined; }
24
+ };
25
+ // These settings are independent: a failed legacy restoration must
26
+ // not prevent the attempt to re-enable foreign-key enforcement.
27
+ return chain(attempt(dialect.pragma.set('legacy_alter_table', legacy ? 'ON' : 'OFF')),
28
+ () => chain(attempt(dialect.pragma.foreignKeys(!!foreignKeys)), () => errors));
29
+ };
30
+ const finish = (value) => chain(restore(), (errors) => {
31
+ if (errors.length === 1) throw errors[0];
32
+ if (errors.length > 1) throw new AggregateError(errors, 'migration connection settings could not be restored');
33
+ return value;
34
+ });
35
+ const fail = (error) => chain(restore(), (errors) => {
36
+ if (errors.length > 0) throw new AggregateError([error, ...errors],
37
+ 'migration failed and its connection settings could not be restored', { cause: error });
38
+ throw error;
39
+ });
40
+ let result;
41
+ try {
42
+ result = chain(connection.exec(dialect.pragma.foreignKeys(false)), () => chain(read('foreign_keys'), (actual) => {
43
+ if (actual !== 0) throw new DbCompileError('JD0021', 'foreign_keys cannot change inside a transaction; establish the outer migration scope first');
44
+ return chain(connection.exec(dialect.pragma.set('legacy_alter_table', 'ON')), fn);
45
+ }));
46
+ }
47
+ catch (error) { return fail(error); }
48
+ return isThenable(result) ? result.then(finish, fail) : finish(result);
49
+ }));
50
+ }