sqlocal
Version:
SQLocal makes it easy to run SQLite3 in the browser, backed by the origin private file system.
454 lines • 17.4 kB
JavaScript
import coincident from 'coincident';
import { createMutex } from './lib/create-mutex.js';
import { SQLiteMemoryDriver } from './drivers/sqlite-memory-driver.js';
export class SQLocalProcessor {
constructor(driver) {
Object.defineProperty(this, "driver", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "config", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "userFunctions", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
Object.defineProperty(this, "initMutex", {
enumerable: true,
configurable: true,
writable: true,
value: createMutex()
});
Object.defineProperty(this, "transactionMutex", {
enumerable: true,
configurable: true,
writable: true,
value: createMutex()
});
Object.defineProperty(this, "transactionKey", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "proxy", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "reinitChannel", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onmessage", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "init", {
enumerable: true,
configurable: true,
writable: true,
value: async (reason) => {
if (!this.config.databasePath)
return;
await this.initMutex.lock();
try {
try {
await this.driver.init(this.config);
}
catch {
console.warn(`Persistence failed, so ${this.config.databasePath} will not be saved. For origin private file system persistence, make sure your web server is configured to use the correct HTTP response headers (See https://sqlocal.dev/guide/setup#cross-origin-isolation).`);
this.config.databasePath = ':memory:';
this.driver = new SQLiteMemoryDriver();
await this.driver.init(this.config);
}
if (this.driver.storageType !== 'memory') {
this.reinitChannel = new BroadcastChannel(`_sqlocal_reinit_(${this.config.databasePath})`);
this.reinitChannel.onmessage = (event) => {
const message = event.data;
if (this.config.clientKey === message.clientKey)
return;
switch (message.type) {
case 'reinit':
this.init(message.reason);
break;
case 'close':
this.driver.destroy();
break;
}
};
}
await Promise.all(Array.from(this.userFunctions.values()).map((fn) => {
return this.initUserFunction(fn);
}));
await this.execInitStatements();
this.emitMessage({ type: 'event', event: 'connect', reason });
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey: null,
});
await this.destroy();
}
finally {
await this.initMutex.unlock();
}
}
});
Object.defineProperty(this, "postMessage", {
enumerable: true,
configurable: true,
writable: true,
value: async (event, _transfer) => {
const message = event instanceof MessageEvent ? event.data : event;
await this.initMutex.lock();
switch (message.type) {
case 'config':
this.editConfig(message);
break;
case 'query':
case 'batch':
case 'transaction':
this.exec(message);
break;
case 'function':
this.createUserFunction(message);
break;
case 'getinfo':
this.getDatabaseInfo(message);
break;
case 'import':
this.importDb(message);
break;
case 'export':
this.exportDb(message);
break;
case 'delete':
this.deleteDb(message);
break;
case 'destroy':
this.destroy(message);
break;
}
await this.initMutex.unlock();
}
});
Object.defineProperty(this, "emitMessage", {
enumerable: true,
configurable: true,
writable: true,
value: (message, transfer = []) => {
if (this.onmessage) {
this.onmessage(message, transfer);
}
}
});
Object.defineProperty(this, "editConfig", {
enumerable: true,
configurable: true,
writable: true,
value: (message) => {
this.config = message.config;
this.init('initial');
}
});
Object.defineProperty(this, "exec", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
try {
const response = {
type: 'data',
queryKey: message.queryKey,
data: [],
};
switch (message.type) {
case 'query':
const partOfTransaction = this.transactionKey !== null &&
this.transactionKey === message.transactionKey;
try {
if (!partOfTransaction) {
await this.transactionMutex.lock();
}
const statementData = await this.driver.exec(message);
response.data.push(statementData);
}
finally {
if (!partOfTransaction) {
await this.transactionMutex.unlock();
}
}
break;
case 'batch':
try {
await this.transactionMutex.lock();
const results = await this.driver.execBatch(message.statements);
response.data.push(...results);
}
finally {
await this.transactionMutex.unlock();
}
break;
case 'transaction':
if (message.action === 'begin') {
await this.transactionMutex.lock();
this.transactionKey = message.transactionKey;
await this.driver.exec({ sql: 'BEGIN' });
}
if ((message.action === 'commit' || message.action === 'rollback') &&
this.transactionKey !== null &&
this.transactionKey === message.transactionKey) {
const sql = message.action === 'commit' ? 'COMMIT' : 'ROLLBACK';
await this.driver.exec({ sql });
this.transactionKey = null;
await this.transactionMutex.unlock();
}
break;
}
this.emitMessage(response);
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey: message.queryKey,
});
}
}
});
Object.defineProperty(this, "execInitStatements", {
enumerable: true,
configurable: true,
writable: true,
value: async () => {
if (this.config.onInitStatements) {
for (let statement of this.config.onInitStatements) {
await this.driver.exec(statement);
}
}
}
});
Object.defineProperty(this, "getDatabaseInfo", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
try {
this.emitMessage({
type: 'info',
queryKey: message.queryKey,
info: {
databasePath: this.config.databasePath,
storageType: this.driver.storageType,
databaseSizeBytes: await this.driver.getDatabaseSizeBytes(),
persisted: await this.driver.isDatabasePersisted(),
},
});
}
catch (error) {
this.emitMessage({
type: 'error',
queryKey: message.queryKey,
error,
});
}
}
});
Object.defineProperty(this, "createUserFunction", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
const { functionName: name, functionType: type, queryKey } = message;
let fn;
if (this.userFunctions.has(name)) {
this.emitMessage({
type: 'error',
error: new Error(`A user-defined function with the name "${name}" has already been created for this SQLocal instance.`),
queryKey,
});
return;
}
switch (type) {
case 'callback':
fn = {
type,
name,
func: (...args) => {
this.emitMessage({ type: 'callback', name, args });
},
};
break;
case 'scalar':
fn = {
type,
name,
func: this.proxy[`_sqlocal_func_${name}`],
};
break;
case 'aggregate':
fn = {
type,
name,
func: {
step: this.proxy[`_sqlocal_func_${name}_step`],
final: this.proxy[`_sqlocal_func_${name}_final`],
},
};
break;
}
try {
await this.initUserFunction(fn);
this.emitMessage({
type: 'success',
queryKey,
});
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey,
});
}
}
});
Object.defineProperty(this, "initUserFunction", {
enumerable: true,
configurable: true,
writable: true,
value: async (fn) => {
await this.driver.createFunction(fn);
this.userFunctions.set(fn.name, fn);
}
});
Object.defineProperty(this, "importDb", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
const { queryKey, database } = message;
let errored = false;
try {
await this.driver.import(database);
if (this.driver.storageType === 'memory') {
await this.execInitStatements();
}
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey,
});
errored = true;
}
finally {
if (this.driver.storageType !== 'memory') {
await this.init('overwrite');
}
}
if (!errored) {
this.emitMessage({
type: 'success',
queryKey,
});
}
}
});
Object.defineProperty(this, "exportDb", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
const { queryKey } = message;
try {
const { name, data } = await this.driver.export();
this.emitMessage({
type: 'buffer',
queryKey,
bufferName: name,
buffer: data,
}, [data]);
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey,
});
}
}
});
Object.defineProperty(this, "deleteDb", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
const { queryKey } = message;
let errored = false;
try {
await this.driver.clear();
}
catch (error) {
this.emitMessage({
type: 'error',
error,
queryKey,
});
errored = true;
}
finally {
await this.init('delete');
}
if (!errored) {
this.emitMessage({
type: 'success',
queryKey,
});
}
}
});
Object.defineProperty(this, "destroy", {
enumerable: true,
configurable: true,
writable: true,
value: async (message) => {
await this.driver.exec({ sql: 'PRAGMA optimize' });
await this.driver.destroy();
if (this.reinitChannel) {
this.reinitChannel.close();
this.reinitChannel = undefined;
}
if (message) {
this.emitMessage({
type: 'success',
queryKey: message.queryKey,
});
}
}
});
const isInWorker = typeof WorkerGlobalScope !== 'undefined' &&
globalThis instanceof WorkerGlobalScope;
const proxy = isInWorker ? coincident(globalThis) : globalThis;
this.proxy = proxy;
this.driver = driver;
}
}
//# sourceMappingURL=processor.js.map