resolve-local-event-broker
Version:
The reSolve framework's event broker for applications on a local machine.
170 lines (140 loc) • 4.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _constants = require("./constants");
const randRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const fullJitter = retries => randRange(0, Math.min(100, 2 * 2 ** retries));
const coerceEmptyString = obj => obj != null && obj.constructor !== String || obj == null ? 'default' : obj;
const interlockPromise = async pool => {
let unlock = null;
while (Promise.resolve(pool[_constants.INTERLOCK_SYMBOL]) === pool[_constants.INTERLOCK_SYMBOL]) {
await pool[_constants.INTERLOCK_SYMBOL];
}
pool[_constants.INTERLOCK_SYMBOL] = new Promise(resolve => {
unlock = () => {
delete pool[_constants.INTERLOCK_SYMBOL];
resolve();
};
});
return unlock;
};
const escapeId = str => `"${String(str).replace(/(["])/gi, '$1$1')}"`;
const escapeStr = str => `'${String(str).replace(/(['])/gi, '$1$1')}'`;
const encodeJsonPath = jsonPath => jsonPath.replace(/\u001a/g, '\u001aSUB').replace(/"/g, '\u001aQUOTE').replace(/\./g, '\u001aDOT').replace(/\\/g, '\u001aSLASH');
const decodeJsonPath = jsonPath => jsonPath.replace(/\u001aSLASH/g, '\\').replace(/\u001aDOT/g, '.').replace(/\u001aQUOTE/g, '"').replace(/\u001aSUB/g, '\u001a');
const emptyTransformer = Function(''); // eslint-disable-line no-new-func
const runCommonQuery = async (pool, isRegular, querySQL) => {
const unlock = await interlockPromise(pool);
try {
const executor = isRegular ? pool.connection.all.bind(pool.connection) : pool.connection.exec.bind(pool.connection);
const transformer = isRegular ? Array.from.bind(Array) : emptyTransformer;
let result = null;
for (let retry = 0;; retry++) {
try {
result = await executor(querySQL);
break;
} catch (error) {
if (error != null && error.code === _constants.SQLITE_BUSY) {
await new Promise(resolve => setTimeout(resolve, fullJitter(retry)));
} else {
throw error;
}
}
}
return transformer(result);
} finally {
unlock();
}
};
const disconnect = async pool => {
if (pool.isDisposed) {
throw new Error('Event broker SQLite connection already closed');
}
pool.isDisposed = true;
await pool.connection.close();
};
const connectDatabase = async (imports, options) => {
const {
SQLite,
fs,
os,
tmp
} = imports;
let {
databaseFile,
...connectionOptions
} = options;
databaseFile = coerceEmptyString(databaseFile);
const pool = { ...imports,
connectionOptions,
databaseFile
};
const database = {
runRawQuery: runCommonQuery.bind(null, pool, false),
runQuery: runCommonQuery.bind(null, pool, true),
dispose: disconnect.bind(null, pool),
encodeJsonPath,
decodeJsonPath,
escapeId,
escapeStr
};
let connector = null;
if (databaseFile === ':memory:') {
if (process.env.RESOLVE_LAUNCH_ID != null) {
const tmpName = `${os.tmpdir()}/event-broker-${+process.env.RESOLVE_LAUNCH_ID}.db`;
const removeCallback = () => {
if (fs.existsSync(tmpName)) {
fs.unlinkSync(tmpName);
}
};
if (!fs.existsSync(tmpName)) {
fs.writeFileSync(tmpName, '');
process.on('SIGINT', removeCallback);
process.on('SIGTERM', removeCallback);
process.on('beforeExit', removeCallback);
process.on('exit', removeCallback);
}
pool.memoryStore = {
name: tmpName,
drop: removeCallback
};
} else if (pool.memoryStore == null || Object.keys(pool.memoryStore).length === 0) {
const temporaryFile = tmp.fileSync();
pool.memoryStore = {
name: temporaryFile.name,
drop: temporaryFile.removeCallback.bind(temporaryFile)
};
}
connector = SQLite.open.bind(SQLite, pool.memoryStore.name);
} else {
connector = SQLite.open.bind(SQLite, databaseFile);
}
for (let retry = 0;; retry++) {
try {
pool.connection = await connector();
break;
} catch (error) {
if (error != null && error.code === _constants.SQLITE_BUSY) {
await new Promise(resolve => setTimeout(resolve, fullJitter(retry)));
} else {
throw error;
}
}
}
await database.runRawQuery(`PRAGMA busy_timeout=0`);
await database.runRawQuery(`PRAGMA locking_mode=EXCLUSIVE`);
await database.runRawQuery(`PRAGMA encoding=${escapeStr('UTF-8')}`);
await database.runRawQuery(`PRAGMA synchronous=EXTRA`);
if (databaseFile === ':memory:') {
await database.runRawQuery(`PRAGMA journal_mode=MEMORY`);
} else {
await database.runRawQuery(`PRAGMA journal_mode=DELETE`);
}
await database.runRawQuery(`BEGIN IMMEDIATE`);
return database;
};
var _default = connectDatabase;
exports.default = _default;
//# sourceMappingURL=connect-database.js.map