Skip to content

Commit f9663fc

Browse files
committed
made Database class extendable, and fixed importing into jest environments
1 parent 6d05e9e commit f9663fc

19 files changed

+491
-438
lines changed

lib/aggregate.js

-40
This file was deleted.

lib/backup.js

-65
This file was deleted.

lib/database.js

+30-9
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,58 @@ const {
99
} = require('bindings')('better_sqlite3.node');
1010

1111
function Database(filenameGiven, options) {
12+
if (new.target !== Database) {
13+
return new Database(filenameGiven, options);
14+
}
15+
16+
// Apply defaults
1217
if (filenameGiven == null) filenameGiven = '';
1318
if (options == null) options = {};
19+
20+
// Validate arguments
1421
if (typeof filenameGiven !== 'string') throw new TypeError('Expected first argument to be a string');
1522
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
1623
if ('readOnly' in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"');
1724
if ('memory' in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)');
1825

26+
// Interpret options
1927
const filename = filenameGiven.trim();
2028
const anonymous = filename === '' || filename === ':memory:';
2129
const readonly = util.getBooleanOption(options, 'readonly');
2230
const fileMustExist = util.getBooleanOption(options, 'fileMustExist');
2331
const timeout = 'timeout' in options ? options.timeout : 5000;
2432
const verbose = 'verbose' in options ? options.verbose : null;
2533

34+
// Validate interpreted options
2635
if (readonly && anonymous) throw new TypeError('In-memory/temporary databases cannot be readonly');
2736
if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer');
2837
if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647');
2938
if (verbose != null && typeof verbose !== 'function') throw new TypeError('Expected the "verbose" option to be a function');
3039

40+
// Make sure the specified directory exists
3141
if (!anonymous && !fs.existsSync(path.dirname(filename))) {
3242
throw new TypeError('Cannot open database because the directory does not exist');
3343
}
34-
return new CPPDatabase(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null);
44+
45+
Object.defineProperties(this, {
46+
[util.cppdb]: { value: new CPPDatabase(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null) },
47+
...wrappers.getters,
48+
});
3549
}
3650

37-
setErrorConstructor(require('./sqlite-error'));
38-
util.wrap(CPPDatabase, 'pragma', require('./pragma'));
39-
util.wrap(CPPDatabase, 'function', require('./function'));
40-
util.wrap(CPPDatabase, 'aggregate', require('./aggregate'));
41-
util.wrap(CPPDatabase, 'backup', require('./backup'));
42-
CPPDatabase.prototype.transaction = require('./transaction');
43-
CPPDatabase.prototype.constructor = Database;
44-
Database.prototype = CPPDatabase.prototype;
51+
const wrappers = require('./methods/wrappers');
52+
Database.prototype.prepare = wrappers.prepare;
53+
Database.prototype.transaction = require('./methods/transaction');
54+
Database.prototype.pragma = require('./methods/pragma');
55+
Database.prototype.backup = require('./methods/backup');
56+
Database.prototype.function = require('./methods/function');
57+
Database.prototype.aggregate = require('./methods/aggregate');
58+
Database.prototype.loadExtension = wrappers.loadExtension;
59+
Database.prototype.exec = wrappers.exec;
60+
Database.prototype.close = wrappers.close;
61+
Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
62+
Database.prototype.unsafeMode = wrappers.unsafeMode;
63+
Database.prototype[util.inspect] = require('./methods/inspect');
64+
4565
module.exports = Database;
66+
setErrorConstructor(require('./sqlite-error'));

lib/function.js

-26
This file was deleted.

lib/methods/aggregate.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
const { getBooleanOption, cppdb } = require('../util');
3+
4+
module.exports = function defineAggregate(name, options) {
5+
// Validate arguments
6+
if (typeof name !== 'string') throw new TypeError('Expected first argument to be a string');
7+
if (typeof options !== 'object' || options === null) throw new TypeError('Expected second argument to be an options object');
8+
if (!name) throw new TypeError('User-defined function name cannot be an empty string');
9+
10+
// Interpret options
11+
const start = 'start' in options ? options.start : null;
12+
const step = getFunctionOption(options, 'step', true);
13+
const inverse = getFunctionOption(options, 'inverse', false);
14+
const result = getFunctionOption(options, 'result', false);
15+
const safeIntegers = 'safeIntegers' in options ? +getBooleanOption(options, 'safeIntegers') : 2;
16+
const deterministic = getBooleanOption(options, 'deterministic');
17+
const varargs = getBooleanOption(options, 'varargs');
18+
let argCount = -1;
19+
20+
// Determine argument count
21+
if (!varargs) {
22+
argCount = Math.max(getLength(step), inverse ? getLength(inverse) : 0);
23+
if (argCount > 0) argCount -= 1;
24+
if (argCount > 100) throw new RangeError('User-defined functions cannot have more than 100 arguments');
25+
}
26+
27+
this[cppdb].aggregate(start, step, inverse, result, name, argCount, safeIntegers, deterministic);
28+
return this;
29+
};
30+
31+
const getFunctionOption = (options, key, required) => {
32+
const value = key in options ? options[key] : null;
33+
if (typeof value === 'function') return value;
34+
if (value != null) throw new TypeError(`Expected the "${key}" option to be a function`);
35+
if (required) throw new TypeError(`Missing required option "${key}"`);
36+
return null;
37+
};
38+
39+
const getLength = ({ length }) => {
40+
if (Number.isInteger(length) && length >= 0) return length;
41+
throw new TypeError('Expected function.length to be a positive integer');
42+
};

lib/methods/backup.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
const fs = require('fs');
3+
const path = require('path');
4+
const { promisify } = require('util');
5+
const { cppdb } = require('../util');
6+
const fsAccess = promisify(fs.access);
7+
8+
module.exports = async function backup(filename, options) {
9+
if (options == null) options = {};
10+
11+
// Validate arguments
12+
if (typeof filename !== 'string') throw new TypeError('Expected first argument to be a string');
13+
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
14+
15+
// Interpret options
16+
filename = filename.trim();
17+
const attachedName = 'attached' in options ? options.attached : 'main';
18+
const handler = 'progress' in options ? options.progress : null;
19+
20+
// Validate interpreted options
21+
if (!filename) throw new TypeError('Backup filename cannot be an empty string');
22+
if (filename === ':memory:') throw new TypeError('Invalid backup filename ":memory:"');
23+
if (typeof attachedName !== 'string') throw new TypeError('Expected the "attached" option to be a string');
24+
if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
25+
if (handler != null && typeof handler !== 'function') throw new TypeError('Expected the "progress" option to be a function');
26+
27+
// Make sure the specified directory exists
28+
await fsAccess(path.dirname(filename)).catch(() => {
29+
throw new TypeError('Cannot save backup because the directory does not exist');
30+
});
31+
32+
const isNewFile = await fsAccess(filename).then(() => false, () => true);
33+
return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
34+
};
35+
36+
const runBackup = (backup, handler) => {
37+
let rate = 0;
38+
let useDefault = true;
39+
40+
return new Promise((resolve, reject) => {
41+
setImmediate(function step() {
42+
try {
43+
const progress = backup.transfer(rate);
44+
if (!progress.remainingPages) {
45+
backup.close();
46+
resolve(progress);
47+
return;
48+
}
49+
if (useDefault) {
50+
useDefault = false;
51+
rate = 100;
52+
}
53+
if (handler) {
54+
const ret = handler(progress);
55+
if (ret !== undefined) {
56+
if (typeof ret === 'number' && ret === ret) rate = Math.max(0, Math.min(0x7fffffff, Math.round(ret)));
57+
else throw new TypeError('Expected progress callback to return a number or undefined');
58+
}
59+
}
60+
setImmediate(step);
61+
} catch (err) {
62+
backup.close();
63+
reject(err);
64+
}
65+
});
66+
});
67+
};

lib/methods/function.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
const { getBooleanOption, cppdb } = require('../util');
3+
4+
module.exports = function defineFunction(name, options, fn) {
5+
// Apply defaults
6+
if (options == null) options = {};
7+
if (typeof options === 'function') { fn = options; options = {}; }
8+
9+
// Validate arguments
10+
if (typeof name !== 'string') throw new TypeError('Expected first argument to be a string');
11+
if (typeof fn !== 'function') throw new TypeError('Expected last argument to be a function');
12+
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
13+
if (!name) throw new TypeError('User-defined function name cannot be an empty string');
14+
15+
// Interpret options
16+
const safeIntegers = 'safeIntegers' in options ? +getBooleanOption(options, 'safeIntegers') : 2;
17+
const deterministic = getBooleanOption(options, 'deterministic');
18+
const varargs = getBooleanOption(options, 'varargs');
19+
let argCount = -1;
20+
21+
// Determine argument count
22+
if (!varargs) {
23+
argCount = fn.length;
24+
if (!Number.isInteger(argCount) || argCount < 0) throw new TypeError('Expected function.length to be a positive integer');
25+
if (argCount > 100) throw new RangeError('User-defined functions cannot have more than 100 arguments');
26+
}
27+
28+
this[cppdb].function(fn, name, argCount, safeIntegers, deterministic);
29+
return this;
30+
};

lib/methods/inspect.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
const DatabaseInspection = function Database() {};
3+
4+
module.exports = function inspect(depth, opts) {
5+
return Object.assign(new DatabaseInspection(), this);
6+
};
7+

lib/methods/pragma.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
const { getBooleanOption, cppdb } = require('../util');
3+
4+
module.exports = function pragma(source, options) {
5+
if (options == null) options = {};
6+
if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string');
7+
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object');
8+
const simple = getBooleanOption(options, 'simple');
9+
10+
const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true);
11+
return simple ? stmt.pluck().get() : stmt.all();
12+
};

0 commit comments

Comments
 (0)