UNPKG

sqljs-documentstore

Version:

Minimal, encrypted, sql friendly typed document store, with support for indexed columns. Protects against transactional conflicts

216 lines 10.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.flushHelpers = exports.cryptoHelpers = exports.sqljsHelpers = exports.sqljsPersistence = void 0; var idb_keyval_1 = require("idb-keyval"); var sqljsPersistence; (function (sqljsPersistence) { var store = (0, idb_keyval_1.createStore)('sqljs-documentstore', 'databases'); function save(dbName, key, db) { return __awaiter(this, void 0, void 0, function () { var saltItem, salt; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, (0, idb_keyval_1.get)("".concat(dbName, "-salt"), store)]; case 1: saltItem = _a.sent(); salt = saltItem ? new Uint8Array(saltItem) : crypto.getRandomValues(new Uint8Array(16)); return [4 /*yield*/, _save(dbName, key, salt, db)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); } sqljsPersistence.save = save; function _save(dbName, key, salt, db) { return __awaiter(this, void 0, void 0, function () { var encryptedData; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, cryptoHelpers.encrypt(key, salt, db.export())]; case 1: encryptedData = _a.sent(); return [4 /*yield*/, (0, idb_keyval_1.setMany)([ ["".concat(dbName), encryptedData.data], ["".concat(dbName, "-iv"), encryptedData.iv], ["".concat(dbName, "-salt"), encryptedData.salt] ], store)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); } function load(dbName, passPhrase, sqlJsStatic) { return __awaiter(this, void 0, void 0, function () { var _a, encryptedData, iv, salt, newDb, k_1, k, existingData; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, (0, idb_keyval_1.getMany)([dbName, "".concat(dbName, "-iv"), "".concat(dbName, "-salt")], store)]; case 1: _a = _b.sent(), encryptedData = _a[0], iv = _a[1], salt = _a[2]; if (!!encryptedData) return [3 /*break*/, 4]; newDb = new sqlJsStatic.Database(); return [4 /*yield*/, cryptoHelpers.getKey(passPhrase)]; case 2: k_1 = _b.sent(); return [4 /*yield*/, _save(dbName, k_1.key, k_1.salt, newDb)]; case 3: _b.sent(); return [2 /*return*/, { key: k_1.key, database: newDb }]; case 4: return [4 /*yield*/, cryptoHelpers.getKey(passPhrase, salt)]; case 5: k = _b.sent(); return [4 /*yield*/, cryptoHelpers.decrypt(k.key, iv, encryptedData)]; case 6: existingData = _b.sent(); return [2 /*return*/, { key: k.key, database: new sqlJsStatic.Database(new Uint8Array(existingData)) }]; } }); }); } sqljsPersistence.load = load; })(sqljsPersistence || (exports.sqljsPersistence = sqljsPersistence = {})); var sqljsHelpers; (function (sqljsHelpers) { function query(db, sql, params) { if (params === void 0) { params = []; } var allResults = exec(db, sql, params); //expect only one result set, if there are multiple, just take the last one var result = allResults[allResults.length - 1]; if (result === undefined || result.values.length == 0) return []; var columns = result.columns, values = result.values; return values.map(function (row) { return Object.fromEntries(columns.map(function (col, i) { return [col, row[i]]; })); }); } sqljsHelpers.query = query; function exec(db, sql, params) { if (params === void 0) { params = []; } return db.exec(sql, sanitizeParams(params)); } sqljsHelpers.exec = exec; function run(db, sql, params) { if (params === void 0) { params = []; } db.run(sql, sanitizeParams(params)); } sqljsHelpers.run = run; function isTable(db, table) { return db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", [table]).values.length > 0; } sqljsHelpers.isTable = isTable; function sanitizeParams(params) { return params.map(function (v) { return v == undefined ? null : v == true ? 1 : v == false ? 0 : v; }); } sqljsHelpers.sanitizeParams = sanitizeParams; })(sqljsHelpers || (exports.sqljsHelpers = sqljsHelpers = {})); var cryptoHelpers; (function (cryptoHelpers) { function getKey(passphrase, salt) { return __awaiter(this, void 0, void 0, function () { var keyMaterial, r; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: salt = salt !== null && salt !== void 0 ? salt : crypto.getRandomValues(new Uint8Array(16)); return [4 /*yield*/, crypto.subtle.importKey('raw', new TextEncoder().encode(passphrase), 'PBKDF2', false, ['deriveKey'])]; case 1: keyMaterial = _b.sent(); _a = {}; return [4 /*yield*/, crypto.subtle.deriveKey({ name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])]; case 2: r = (_a.key = _b.sent(), _a.salt = salt, _a); return [2 /*return*/, r]; } }); }); } cryptoHelpers.getKey = getKey; function encrypt(key, salt, data) { return __awaiter(this, void 0, void 0, function () { var iv, encrypted; return __generator(this, function (_a) { switch (_a.label) { case 0: iv = crypto.getRandomValues(new Uint8Array(12)); return [4 /*yield*/, crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, data)]; case 1: encrypted = _a.sent(); return [2 /*return*/, { salt: salt, iv: iv, data: new Uint8Array(encrypted) }]; } }); }); } cryptoHelpers.encrypt = encrypt; function decrypt(key, iv, encryptedData) { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, key, encryptedData)]; case 1: data = _a.sent(); return [2 /*return*/, data]; } }); }); } cryptoHelpers.decrypt = decrypt; })(cryptoHelpers || (exports.cryptoHelpers = cryptoHelpers = {})); var flushHelpers; (function (flushHelpers) { function createAsyncFlushQueue(saveFn) { var isRunning = false; var queued = false; var run = function () { if (isRunning) { queued = true; return; } isRunning = true; saveFn().finally(function () { isRunning = false; if (queued) { queued = false; run(); } }); }; return run; } flushHelpers.createAsyncFlushQueue = createAsyncFlushQueue; })(flushHelpers || (exports.flushHelpers = flushHelpers = {})); //# sourceMappingURL=sqljsHelpers.js.map