diff --git a/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx b/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx
index d04089a7e1a..8c84417578d 100644
--- a/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx
+++ b/web/core/components/issues/issue-layouts/roots/project-view-layout-root.tsx
@@ -53,11 +53,12 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
{ revalidateIfStale: false, revalidateOnFocus: false }
);
- const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
+ const issueFilters = issuesFilter?.getIssueFilters(viewId?.toString());
+ const activeLayout = issueFilters?.displayFilters?.layout;
if (!workspaceSlug || !projectId || !viewId) return <>>;
- if (isLoading) {
+ if (isLoading && !issueFilters) {
return (
diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts
index 099063aca11..224b5013d01 100644
--- a/web/core/local-db/storage.sqlite.ts
+++ b/web/core/local-db/storage.sqlite.ts
@@ -1,4 +1,5 @@
-import * as Sentry from "@sentry/nextjs";
+import { getActiveSpan, startSpan } from "@sentry/nextjs";
+import * as Comlink from "comlink";
import set from "lodash/set";
// plane
import { EIssueGroupBYServerToProperty } from "@plane/constants";
@@ -16,15 +17,11 @@ import { loadWorkSpaceData } from "./utils/load-workspace";
import { issueFilterCountQueryConstructor, issueFilterQueryConstructor } from "./utils/query-constructor";
import { runQuery } from "./utils/query-executor";
import { createTables } from "./utils/tables";
-import { getGroupedIssueResults, getSubGroupedIssueResults, log, logError, logInfo } from "./utils/utils";
-
-declare module "@sqlite.org/sqlite-wasm" {
- export function sqlite3Worker1Promiser(...args: any): any;
-}
+import { clearOPFS, getGroupedIssueResults, getSubGroupedIssueResults, log, logError } from "./utils/utils";
const DB_VERSION = 1;
-const PAGE_SIZE = 1000;
-const BATCH_SIZE = 200;
+const PAGE_SIZE = 500;
+const BATCH_SIZE = 500;
type TProjectStatus = {
issues: { status: undefined | "loading" | "ready" | "error" | "syncing"; sync: Promise
| undefined };
@@ -43,6 +40,9 @@ export class Storage {
}
reset = () => {
+ if (this.db) {
+ this.db.close();
+ }
this.db = null;
this.status = undefined;
this.projectStatus = {};
@@ -51,10 +51,8 @@ export class Storage {
clearStorage = async () => {
try {
- const storageManager = window.navigator.storage;
- const fileSystemDirectoryHandle = await storageManager.getDirectory();
- //@ts-expect-error , clear local issue cache
- await fileSystemDirectoryHandle.remove({ recursive: true });
+ await this.db.close();
+ await clearOPFS();
this.reset();
} catch (e) {
console.error("Error clearing sqlite sync storage", e);
@@ -62,13 +60,13 @@ export class Storage {
};
initialize = async (workspaceSlug: string): Promise => {
- if (document.hidden || !rootStore.user.localDBEnabled) return false; // return if the window gets hidden
+ if (!rootStore.user.localDBEnabled) return false; // return if the window gets hidden
if (workspaceSlug !== this.workspaceSlug) {
this.reset();
}
try {
- await Sentry.startSpan({ name: "INIT_DB" }, async () => await this._initialize(workspaceSlug));
+ await startSpan({ name: "INIT_DB" }, async () => await this._initialize(workspaceSlug));
return true;
} catch (err) {
logError(err);
@@ -91,71 +89,61 @@ export class Storage {
return false;
}
- logInfo("Loading and initializing SQLite3 module...");
-
- this.workspaceSlug = workspaceSlug;
- this.dbName = workspaceSlug;
- const { sqlite3Worker1Promiser } = await import("@sqlite.org/sqlite-wasm");
-
try {
- const promiser: any = await new Promise((resolve) => {
- const _promiser = sqlite3Worker1Promiser({
- onready: () => resolve(_promiser),
- });
- });
+ const { DBClass } = await import("./worker/db");
+ const worker = new Worker(new URL("./worker/db.ts", import.meta.url));
+ const MyWorker = Comlink.wrap(worker);
- const configResponse = await promiser("config-get", {});
- log("Running SQLite3 version", configResponse.result.version.libVersion);
+ // Add cleanup on window unload
+ window.addEventListener("unload", () => worker.terminate());
+
+ this.workspaceSlug = workspaceSlug;
+ this.dbName = workspaceSlug;
+ const instance = await new MyWorker();
+ await instance.init(workspaceSlug);
- const openResponse = await promiser("open", {
- filename: `file:${this.dbName}.sqlite3?vfs=opfs`,
- });
- const { dbId } = openResponse;
this.db = {
- dbId,
- exec: async (val: any) => {
- if (typeof val === "string") {
- val = { sql: val };
- }
- return promiser("exec", { dbId, ...val });
- },
+ exec: instance.exec,
+ close: instance.close,
};
- // dump DB of db version is matching
- const dbVersion = await this.getOption("DB_VERSION");
- if (dbVersion !== "" && parseInt(dbVersion) !== DB_VERSION) {
- await this.clearStorage();
- this.reset();
- await this._initialize(workspaceSlug);
- return false;
- }
-
- log(
- "OPFS is available, created persisted database at",
- openResponse.result.filename.replace(/^file:(.*?)\?vfs=opfs$/, "$1")
- );
this.status = "ready";
// Your SQLite code here.
await createTables();
await this.setOption("DB_VERSION", DB_VERSION.toString());
- } catch (err) {
- logError(err);
- throw err;
+ return true;
+ } catch (error) {
+ this.status = "error";
+ throw new Error(`Failed to initialize database worker: ${error}`);
}
-
- return true;
};
syncWorkspace = async () => {
- if (document.hidden || !rootStore.user.localDBEnabled) return; // return if the window gets hidden
- await Sentry.startSpan({ name: "LOAD_WS", attributes: { slug: this.workspaceSlug } }, async () => {
- await loadWorkSpaceData(this.workspaceSlug);
- });
+ if (!rootStore.user.localDBEnabled) return;
+ const syncInProgress = await this.getIsWriteInProgress("sync_workspace");
+ if (syncInProgress) {
+ log("Sync in progress, skipping");
+ return;
+ }
+ try {
+ await startSpan({ name: "LOAD_WS", attributes: { slug: this.workspaceSlug } }, async () => {
+ this.setOption("sync_workspace", new Date().toUTCString());
+ await loadWorkSpaceData(this.workspaceSlug);
+ this.deleteOption("sync_workspace");
+ });
+ } catch (e) {
+ logError(e);
+ this.deleteOption("sync_workspace");
+ }
};
syncProject = async (projectId: string) => {
- if (document.hidden || !rootStore.user.localDBEnabled) return false; // return if the window gets hidden
+ if (
+ // document.hidden ||
+ !rootStore.user.localDBEnabled
+ )
+ return false; // return if the window gets hidden
// Load labels, members, states, modules, cycles
await this.syncIssues(projectId);
@@ -173,10 +161,11 @@ export class Storage {
};
syncIssues = async (projectId: string) => {
- if (document.hidden || !rootStore.user.localDBEnabled) return false; // return if the window gets hidden
-
+ if (!rootStore.user.localDBEnabled || !this.db) {
+ return false;
+ }
try {
- const sync = Sentry.startSpan({ name: `SYNC_ISSUES` }, () => this._syncIssues(projectId));
+ const sync = startSpan({ name: `SYNC_ISSUES` }, () => this._syncIssues(projectId));
this.setSync(projectId, sync);
await sync;
} catch (e) {
@@ -186,12 +175,12 @@ export class Storage {
};
_syncIssues = async (projectId: string) => {
- const activeSpan = Sentry.getActiveSpan();
+ const activeSpan = getActiveSpan();
log("### Sync started");
let status = this.getStatus(projectId);
if (status === "loading" || status === "syncing") {
- logInfo(`Project ${projectId} is already loading or syncing`);
+ log(`Project ${projectId} is already loading or syncing`);
return;
}
const syncPromise = this.getSync(projectId);
@@ -222,8 +211,8 @@ export class Storage {
const issueService = new IssueService();
const response = await issueService.getIssuesForSync(this.workspaceSlug, projectId, queryParams);
- addIssuesBulk(response.results, BATCH_SIZE);
+ await addIssuesBulk(response.results, BATCH_SIZE);
if (response.total_pages > 1) {
const promiseArray = [];
for (let i = 1; i < response.total_pages; i++) {
@@ -290,7 +279,7 @@ export class Storage {
!rootStore.user.localDBEnabled
) {
if (rootStore.user.localDBEnabled) {
- logInfo(`Project ${projectId} is loading, falling back to server`);
+ log(`Project ${projectId} is loading, falling back to server`);
}
const issueService = new IssueService();
return await issueService.getIssuesFromServer(workspaceSlug, projectId, queries);
@@ -310,11 +299,9 @@ export class Storage {
const issueService = new IssueService();
return await issueService.getIssuesFromServer(workspaceSlug, projectId, queries);
}
- // const issuesRaw = await runQuery(query);
const end = performance.now();
const { total_count } = count[0];
- // const total_count = 2300;
const [pageSize, page, offset] = cursor.split(":");
@@ -347,7 +334,6 @@ export class Storage {
Parsing: parsingEnd - parsingStart,
Grouping: groupingEnd - grouping,
};
- log(issueResults);
if ((window as any).DEBUG) {
console.table(times);
}
@@ -364,7 +350,7 @@ export class Storage {
total_pages,
};
- const activeSpan = Sentry.getActiveSpan();
+ const activeSpan = getActiveSpan();
activeSpan?.setAttributes({
projectId,
count: total_count,
@@ -413,7 +399,7 @@ export class Storage {
set(this.projectStatus, `${projectId}.issues.sync`, sync);
};
- getOption = async (key: string, fallback = "") => {
+ getOption = async (key: string, fallback?: string | boolean | number) => {
try {
const options = await runQuery(`select * from options where key='${key}'`);
if (options.length) {
@@ -429,6 +415,9 @@ export class Storage {
await runQuery(`insert or replace into options (key, value) values ('${key}', '${value}')`);
};
+ deleteOption = async (key: string) => {
+ await runQuery(` DELETE FROM options where key='${key}'`);
+ };
getOptions = async (keys: string[]) => {
const options = await runQuery(`select * from options where key in ('${keys.join("','")}')`);
return options.reduce((acc: any, option: any) => {
@@ -436,6 +425,21 @@ export class Storage {
return acc;
}, {});
};
+
+ getIsWriteInProgress = async (op: string) => {
+ const writeStartTime = await this.getOption(op, false);
+ if (writeStartTime) {
+ // Check if it has been more than 5seconds
+ const current = new Date();
+ const start = new Date(writeStartTime);
+
+ if (current.getTime() - start.getTime() < 5000) {
+ return true;
+ }
+ return false;
+ }
+ return false;
+ };
}
export const persistence = new Storage();
diff --git a/web/core/local-db/utils/indexes.ts b/web/core/local-db/utils/indexes.ts
index 214bacb44f5..0b2e987ed37 100644
--- a/web/core/local-db/utils/indexes.ts
+++ b/web/core/local-db/utils/indexes.ts
@@ -1,6 +1,6 @@
import { persistence } from "../storage.sqlite";
+import { log } from "./utils";
-const log = console.log;
export const createIssueIndexes = async () => {
const columns = [
"state_id",
@@ -34,10 +34,8 @@ export const createWorkSpaceIndexes = async () => {
const promises: Promise[] = [];
// Labels
promises.push(persistence.db.exec({ sql: `CREATE INDEX labels_name_idx ON labels (id,name,project_id)` }));
-
// Modules
promises.push(persistence.db.exec({ sql: `CREATE INDEX modules_name_idx ON modules (id,name,project_id)` }));
-
// States
promises.push(persistence.db.exec({ sql: `CREATE INDEX states_name_idx ON states (id,name,project_id)` }));
// Cycles
@@ -49,7 +47,7 @@ export const createWorkSpaceIndexes = async () => {
// Estimate Points @todo
promises.push(persistence.db.exec({ sql: `CREATE INDEX estimate_points_name_idx ON estimate_points (id,value)` }));
// Options
- promises.push(persistence.db.exec({ sql: `CREATE INDEX options_name_idx ON options (name)` }));
+ promises.push(persistence.db.exec({ sql: `CREATE INDEX options_key_idx ON options (key)` }));
await Promise.all(promises);
};
diff --git a/web/core/local-db/utils/load-issues.ts b/web/core/local-db/utils/load-issues.ts
index 1524edea7d3..4a1986f1e19 100644
--- a/web/core/local-db/utils/load-issues.ts
+++ b/web/core/local-db/utils/load-issues.ts
@@ -4,47 +4,53 @@ import { IssueService } from "@/services/issue";
import { persistence } from "../storage.sqlite";
import { ARRAY_FIELDS, PRIORITY_MAP } from "./constants";
import { issueSchema } from "./schemas";
+import { log } from "./utils";
export const PROJECT_OFFLINE_STATUS: Record = {};
export const addIssue = async (issue: any) => {
- if (document.hidden || !rootStore.user.localDBEnabled) return;
-
- persistence.db.exec("BEGIN TRANSACTION;");
- stageIssueInserts(issue);
- persistence.db.exec("COMMIT;");
+ if (document.hidden || !rootStore.user.localDBEnabled || !persistence.db) return;
+ await persistence.db.exec("BEGIN;");
+ await stageIssueInserts(issue);
+ await persistence.db.exec("COMMIT;");
};
export const addIssuesBulk = async (issues: any, batchSize = 100) => {
- if (!rootStore.user.localDBEnabled) return;
+ if (!rootStore.user.localDBEnabled || !persistence.db) return;
+
+ const insertStart = performance.now();
+ await persistence.db.exec("BEGIN;");
for (let i = 0; i < issues.length; i += batchSize) {
const batch = issues.slice(i, i + batchSize);
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((issue: any) => {
+ for (let j = 0; j < batch.length; j++) {
+ const issue = batch[j];
if (!issue.type_id) {
issue.type_id = "";
}
- stageIssueInserts(issue);
- });
- await persistence.db.exec("COMMIT;");
+ await stageIssueInserts(issue);
+ }
}
+ await persistence.db.exec("COMMIT;");
+
+ const insertEnd = performance.now();
+ log("Inserted issues in ", `${insertEnd - insertStart}ms`);
};
export const deleteIssueFromLocal = async (issue_id: any) => {
- if (!rootStore.user.localDBEnabled) return;
+ if (!rootStore.user.localDBEnabled || !persistence.db) return;
const deleteQuery = `delete from issues where id='${issue_id}'`;
const deleteMetaQuery = `delete from issue_meta where issue_id='${issue_id}'`;
- persistence.db.exec("BEGIN TRANSACTION;");
+ persistence.db.exec("BEGIN;");
persistence.db.exec(deleteQuery);
persistence.db.exec(deleteMetaQuery);
persistence.db.exec("COMMIT;");
};
// @todo: Update deletes the issue description from local. Implement a separate update.
export const updateIssue = async (issue: TIssue & { is_local_update: number }) => {
- if (document.hidden || !rootStore.user.localDBEnabled) return;
+ if (document.hidden || !rootStore.user.localDBEnabled || !persistence.db) return;
const issue_id = issue.id;
// delete the issue and its meta data
@@ -53,7 +59,7 @@ export const updateIssue = async (issue: TIssue & { is_local_update: number }) =
};
export const syncDeletesToLocal = async (workspaceId: string, projectId: string, queries: any) => {
- if (!rootStore.user.localDBEnabled) return;
+ if (!rootStore.user.localDBEnabled || !persistence.db) return;
const issueService = new IssueService();
const response = await issueService.getDeletedIssues(workspaceId, projectId, queries);
@@ -62,7 +68,7 @@ export const syncDeletesToLocal = async (workspaceId: string, projectId: string,
}
};
-const stageIssueInserts = (issue: any) => {
+const stageIssueInserts = async (issue: any) => {
const issue_id = issue.id;
issue.priority_proxy = PRIORITY_MAP[issue.priority as keyof typeof PRIORITY_MAP];
@@ -94,7 +100,7 @@ const stageIssueInserts = (issue: any) => {
const query = `INSERT OR REPLACE INTO issues (${columns}) VALUES (${values});`;
persistence.db.exec(query);
- persistence.db.exec({
+ await persistence.db.exec({
sql: `DELETE from issue_meta where issue_id='${issue_id}'`,
});
diff --git a/web/core/local-db/utils/load-workspace.ts b/web/core/local-db/utils/load-workspace.ts
index 87231161c6f..bb8ee1454da 100644
--- a/web/core/local-db/utils/load-workspace.ts
+++ b/web/core/local-db/utils/load-workspace.ts
@@ -7,9 +7,18 @@ import { ModuleService } from "@/services/module.service";
import { ProjectStateService } from "@/services/project";
import { WorkspaceService } from "@/services/workspace.service";
import { persistence } from "../storage.sqlite";
-import { cycleSchema, estimatePointSchema, labelSchema, memberSchema, Schema, stateSchema } from "./schemas";
+import {
+ cycleSchema,
+ estimatePointSchema,
+ labelSchema,
+ memberSchema,
+ moduleSchema,
+ Schema,
+ stateSchema,
+} from "./schemas";
+import { log } from "./utils";
-const stageInserts = (table: string, schema: Schema, data: any) => {
+const stageInserts = async (table: string, schema: Schema, data: any) => {
const keys = Object.keys(schema);
// Pick only the keys that are in the schema
const filteredData = keys.reduce((acc: any, key) => {
@@ -36,67 +45,46 @@ const stageInserts = (table: string, schema: Schema, data: any) => {
})
.join(", ");
const query = `INSERT OR REPLACE INTO ${table} (${columns}) VALUES (${values});`;
- persistence.db.exec(query);
+ await persistence.db.exec(query);
};
-export const loadLabels = async (workspaceSlug: string, batchSize = 500) => {
+const batchInserts = async (data: any[], table: string, schema: Schema, batchSize = 500) => {
+ for (let i = 0; i < data.length; i += batchSize) {
+ const batch = data.slice(i, i + batchSize);
+ for (let j = 0; j < batch.length; j++) {
+ const item = batch[j];
+ await stageInserts(table, schema, item);
+ }
+ }
+};
+
+export const getLabels = async (workspaceSlug: string) => {
const issueLabelService = new IssueLabelService();
const objects = await issueLabelService.getWorkspaceIssueLabels(workspaceSlug);
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((label: any) => {
- stageInserts("labels", labelSchema, label);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
-export const loadModules = async (workspaceSlug: string, batchSize = 500) => {
+export const getModules = async (workspaceSlug: string) => {
const moduleService = new ModuleService();
const objects = await moduleService.getWorkspaceModules(workspaceSlug);
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
-
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((label: any) => {
- stageInserts("modules", labelSchema, label);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
-export const loadCycles = async (workspaceSlug: string, batchSize = 500) => {
+export const getCycles = async (workspaceSlug: string) => {
const cycleService = new CycleService();
const objects = await cycleService.getWorkspaceCycles(workspaceSlug);
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
-
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((cycle: any) => {
- stageInserts("cycles", cycleSchema, cycle);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
-export const loadStates = async (workspaceSlug: string, batchSize = 500) => {
+export const getStates = async (workspaceSlug: string) => {
const stateService = new ProjectStateService();
const objects = await stateService.getWorkspaceStates(workspaceSlug);
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
-
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((state: any) => {
- stageInserts("states", stateSchema, state);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
-export const loadEstimatePoints = async (workspaceSlug: string, batchSize = 500) => {
+export const getEstimatePoints = async (workspaceSlug: string) => {
const estimateService = new EstimateService();
const estimates = await estimateService.fetchWorkspaceEstimates(workspaceSlug);
const objects: IEstimatePoint[] = [];
@@ -105,38 +93,36 @@ export const loadEstimatePoints = async (workspaceSlug: string, batchSize = 500)
objects.concat(estimate.points);
}
});
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
-
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((point: any) => {
- stageInserts("estimate_points", estimatePointSchema, point);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
-export const loadMembers = async (workspaceSlug: string, batchSize = 500) => {
+export const getMembers = async (workspaceSlug: string) => {
const workspaceService = new WorkspaceService(API_BASE_URL);
const members = await workspaceService.fetchWorkspaceMembers(workspaceSlug);
const objects = members.map((member: IWorkspaceMember) => member.member);
- for (let i = 0; i < objects.length; i += batchSize) {
- const batch = objects.slice(i, i + batchSize);
- persistence.db.exec("BEGIN TRANSACTION;");
- batch.forEach((member: any) => {
- stageInserts("members", memberSchema, member);
- });
- await persistence.db.exec("COMMIT;");
- }
+ return objects;
};
export const loadWorkSpaceData = async (workspaceSlug: string) => {
+ log("Loading workspace data");
const promises = [];
- promises.push(loadLabels(workspaceSlug));
- promises.push(loadModules(workspaceSlug));
- promises.push(loadCycles(workspaceSlug));
- promises.push(loadStates(workspaceSlug));
- promises.push(loadEstimatePoints(workspaceSlug));
- promises.push(loadMembers(workspaceSlug));
- await Promise.all(promises);
+ promises.push(getLabels(workspaceSlug));
+ promises.push(getModules(workspaceSlug));
+ promises.push(getCycles(workspaceSlug));
+ promises.push(getStates(workspaceSlug));
+ promises.push(getEstimatePoints(workspaceSlug));
+ promises.push(getMembers(workspaceSlug));
+ const [labels, modules, cycles, states, estimates, memebers] = await Promise.all(promises);
+
+ const start = performance.now();
+ await persistence.db.exec("BEGIN TRANSACTION;");
+ await batchInserts(labels, "labels", labelSchema);
+ await batchInserts(modules, "modules", moduleSchema);
+ await batchInserts(cycles, "cycles", cycleSchema);
+ await batchInserts(states, "states", stateSchema);
+ await batchInserts(estimates, "estimate_points", estimatePointSchema);
+ await batchInserts(memebers, "members", memberSchema);
+ await persistence.db.exec("COMMIT");
+ const end = performance.now();
+ log("Time taken to load workspace data", end - start);
};
diff --git a/web/core/local-db/utils/query-constructor.ts b/web/core/local-db/utils/query-constructor.ts
index 864f160706e..6fd538f1e77 100644
--- a/web/core/local-db/utils/query-constructor.ts
+++ b/web/core/local-db/utils/query-constructor.ts
@@ -25,7 +25,7 @@ export const issueFilterQueryConstructor = (workspaceSlug: string, projectId: st
per_page,
group_by,
sub_group_by,
- order_by = "created_at",
+ order_by = "-created_at",
...otherProps
} = translateQueryParams(queries);
diff --git a/web/core/local-db/utils/query-executor.ts b/web/core/local-db/utils/query-executor.ts
index 9002d5a7bfc..a6844dff434 100644
--- a/web/core/local-db/utils/query-executor.ts
+++ b/web/core/local-db/utils/query-executor.ts
@@ -1,13 +1,11 @@
-// import { SQL } from "./sqlite";
-
import { persistence } from "../storage.sqlite";
export const runQuery = async (sql: string) => {
- const data = await persistence.db.exec({
+ const data = await persistence.db?.exec({
sql,
rowMode: "object",
returnValue: "resultRows",
});
- return data.result.resultRows;
+ return data;
};
diff --git a/web/core/local-db/utils/query.utils.ts b/web/core/local-db/utils/query.utils.ts
index fbc42fec991..520338e6af6 100644
--- a/web/core/local-db/utils/query.utils.ts
+++ b/web/core/local-db/utils/query.utils.ts
@@ -4,8 +4,20 @@ import { issueSchema } from "./schemas";
import { wrapDateTime } from "./utils";
export const translateQueryParams = (queries: any) => {
- const { group_by, sub_group_by, labels, assignees, state, cycle, module, priority, type, issue_type, ...otherProps } =
- queries;
+ const {
+ group_by,
+ layout,
+ sub_group_by,
+ labels,
+ assignees,
+ state,
+ cycle,
+ module,
+ priority,
+ type,
+ issue_type,
+ ...otherProps
+ } = queries;
const order_by = queries.order_by;
if (state) otherProps.state_id = state;
@@ -33,7 +45,7 @@ export const translateQueryParams = (queries: any) => {
}
// Fix invalid orderby when switching from spreadsheet layout
- if ((group_by || sub_group_by) && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
+ if (layout === "spreadsheet" && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
otherProps.order_by = "sort_order";
}
// For each property value, replace None with empty string
diff --git a/web/core/local-db/utils/tables.ts b/web/core/local-db/utils/tables.ts
index 32ad5d50cf8..67ada8465e2 100644
--- a/web/core/local-db/utils/tables.ts
+++ b/web/core/local-db/utils/tables.ts
@@ -24,17 +24,18 @@ const createTableSQLfromSchema = (tableName: string, schema: Schema) => {
};
export const createTables = async () => {
- persistence.db.exec("BEGIN TRANSACTION;");
+ //@todo use promise.all or send all statements in one go
+ await persistence.db.exec("BEGIN;");
- persistence.db.exec(createTableSQLfromSchema("issues", issueSchema));
- persistence.db.exec(createTableSQLfromSchema("issue_meta", issueMetaSchema));
- persistence.db.exec(createTableSQLfromSchema("modules", moduleSchema));
- persistence.db.exec(createTableSQLfromSchema("labels", labelSchema));
- persistence.db.exec(createTableSQLfromSchema("states", stateSchema));
- persistence.db.exec(createTableSQLfromSchema("cycles", cycleSchema));
- persistence.db.exec(createTableSQLfromSchema("estimate_points", estimatePointSchema));
- persistence.db.exec(createTableSQLfromSchema("members", memberSchema));
- persistence.db.exec(createTableSQLfromSchema("options", optionsSchema));
+ await persistence.db.exec(createTableSQLfromSchema("issues", issueSchema));
+ await persistence.db.exec(createTableSQLfromSchema("issue_meta", issueMetaSchema));
+ await persistence.db.exec(createTableSQLfromSchema("modules", moduleSchema));
+ await persistence.db.exec(createTableSQLfromSchema("labels", labelSchema));
+ await persistence.db.exec(createTableSQLfromSchema("states", stateSchema));
+ await persistence.db.exec(createTableSQLfromSchema("cycles", cycleSchema));
+ await persistence.db.exec(createTableSQLfromSchema("estimate_points", estimatePointSchema));
+ await persistence.db.exec(createTableSQLfromSchema("members", memberSchema));
+ await persistence.db.exec(createTableSQLfromSchema("options", optionsSchema));
- persistence.db.exec("COMMIT;");
+ await persistence.db.exec("COMMIT;");
};
diff --git a/web/core/local-db/utils/utils.ts b/web/core/local-db/utils/utils.ts
index 39c49d745cc..9fb81d4c250 100644
--- a/web/core/local-db/utils/utils.ts
+++ b/web/core/local-db/utils/utils.ts
@@ -1,4 +1,4 @@
-import * as Sentry from "@sentry/nextjs";
+import { captureException } from "@sentry/nextjs";
import pick from "lodash/pick";
import { TIssue } from "@plane/types";
import { rootStore } from "@/lib/store-context";
@@ -14,8 +14,8 @@ export const logError = (e: any) => {
if (e?.result?.errorClass === "SQLite3Error") {
e = parseSQLite3Error(e);
}
- Sentry.captureException(e);
- console.log(e);
+ console.error(e);
+ captureException(e);
};
export const logInfo = console.info;
@@ -152,3 +152,25 @@ const parseSQLite3Error = (error: any) => {
error.result = JSON.stringify(error.result);
return error;
};
+
+export const clearOPFS = async () => {
+ const storageManager = window.navigator.storage;
+ const fileSystemDirectoryHandle = await storageManager.getDirectory();
+ const userAgent = navigator.userAgent;
+ const isChrome = userAgent.includes("Chrome") && !userAgent.includes("Edg") && !userAgent.includes("OPR");
+
+ if (isChrome) {
+ await (fileSystemDirectoryHandle as any).remove({ recursive: true });
+ return;
+ }
+
+ const entries = await (fileSystemDirectoryHandle as any).entries();
+ for await (const entry of entries) {
+ const [name] = entry;
+ try {
+ await fileSystemDirectoryHandle.removeEntry(name);
+ } catch (e) {
+ console.log("Error", e);
+ }
+ }
+};
diff --git a/web/core/local-db/worker/db.ts b/web/core/local-db/worker/db.ts
new file mode 100644
index 00000000000..17e326b7877
--- /dev/null
+++ b/web/core/local-db/worker/db.ts
@@ -0,0 +1,114 @@
+import * as Comlink from "comlink";
+import { OPFSCoopSyncVFS as MyVFS } from "./wa-sqlite/src/OPFSCoopSyncVFS";
+import * as SQLite from "./wa-sqlite/src/sqlite-api";
+import SQLiteESMFactory from "./wa-sqlite/src/wa-sqlite.mjs";
+
+type TQueryProps = {
+ sql: string;
+ rowMode: string;
+ returnValue: string;
+ bind: any[];
+};
+const mergeToObject = (columns: string[], row: any[]) => {
+ const obj: any = {};
+ columns.forEach((column, index) => {
+ obj[column] = row[index];
+ });
+ return obj;
+};
+interface SQLiteInstance {
+ db: unknown;
+ exec: (sql: string) => Promise;
+}
+
+export class DBClass {
+ private instance: SQLiteInstance = {} as SQLiteInstance;
+ private sqlite3: any;
+ private tp: Promise | null = null;
+ private tpResolver: any;
+ async init(dbName: string) {
+ if (!dbName || typeof dbName !== "string") {
+ throw new Error("Invalid database name");
+ }
+
+ try {
+ const m = await SQLiteESMFactory();
+ this.sqlite3 = SQLite.Factory(m);
+ const vfs = await MyVFS.create("plane", m);
+ this.sqlite3.vfs_register(vfs, true);
+ const db = await this.sqlite3.open_v2(`${dbName}.sqlite3`);
+ this.instance.db = db;
+ this.instance.exec = async (sql: string) => {
+ const rows: any[] = [];
+ await this.sqlite3.exec(db, sql, (row: any[], columns: string[]) => {
+ rows.push(mergeToObject(columns, row));
+ });
+
+ return rows;
+ };
+ return true;
+ } catch (error) {
+ throw new Error(`Failed to initialize database: ${(error as any)?.message}`);
+ }
+ }
+
+ runQuery(sql: string) {
+ return this.instance.exec(sql);
+ }
+
+ async exec(props: string | TQueryProps) {
+ if (this.tp && props === "BEGIN;") {
+ await this.tp;
+ }
+ let sql: string, bind: any[];
+ if (typeof props === "string") {
+ sql = props;
+ } else {
+ ({ sql, bind } = props);
+ if (bind) {
+ for await (const stmt of this.sqlite3.statements(this.instance.db, sql)) {
+ bind.forEach((b, i) => {
+ this.sqlite3.bind(stmt, i + 1, b);
+ });
+
+ const rows = [];
+
+ do {
+ const columns = await this.sqlite3.column_names(stmt);
+ const row = await this.sqlite3.row(stmt);
+ rows.push(mergeToObject(columns, row));
+ } while ((await this.sqlite3.step(stmt)) === SQLite.SQLITE_ROW);
+
+ return rows;
+ }
+ }
+ }
+
+ if (sql === "BEGIN;") {
+ this.tp = new Promise((resolve, reject) => {
+ this.tpResolver = { resolve, reject };
+ });
+ }
+
+ if (sql === "COMMIT;" && this.tp) {
+ await this.instance.exec(sql);
+ this.tpResolver.resolve();
+ this.tp = null;
+ return;
+ }
+ return await this.instance.exec(sql);
+ }
+ async close() {
+ try {
+ if (!this.instance.db) {
+ return;
+ }
+ await this.sqlite3.close(this.instance.db);
+ // Clear instance to prevent usage after closing
+ this.instance = {} as SQLiteInstance;
+ } catch (error) {
+ throw new Error(`Failed to close database: ${(error as any)?.message}`);
+ }
+ }
+}
+Comlink.expose(DBClass);
diff --git a/web/core/local-db/worker/wa-sqlite/src/FacadeVFS.js b/web/core/local-db/worker/wa-sqlite/src/FacadeVFS.js
new file mode 100644
index 00000000000..c975fdc913c
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/FacadeVFS.js
@@ -0,0 +1,508 @@
+// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
+import * as VFS from './VFS.js';
+
+const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
+
+// Convenience base class for a JavaScript VFS.
+// The raw xOpen, xRead, etc. function signatures receive only C primitives
+// which aren't easy to work with. This class provides corresponding calls
+// like jOpen, jRead, etc., which receive JavaScript-friendlier arguments
+// such as string, Uint8Array, and DataView.
+export class FacadeVFS extends VFS.Base {
+ /**
+ * @param {string} name
+ * @param {object} module
+ */
+ constructor(name, module) {
+ super(name, module);
+ }
+
+ /**
+ * Override to indicate which methods are asynchronous.
+ * @param {string} methodName
+ * @returns {boolean}
+ */
+ hasAsyncMethod(methodName) {
+ // The input argument is a string like "xOpen", so convert to "jOpen".
+ // Then check if the method exists and is async.
+ const jMethodName = `j${methodName.slice(1)}`;
+ return this[jMethodName] instanceof AsyncFunction;
+ }
+
+ /**
+ * Return the filename for a file id for use by mixins.
+ * @param {number} pFile
+ * @returns {string}
+ */
+ getFilename(pFile) {
+ throw new Error('unimplemented');
+ }
+
+ /**
+ * @param {string?} filename
+ * @param {number} pFile
+ * @param {number} flags
+ * @param {DataView} pOutFlags
+ * @returns {number|Promise}
+ */
+ jOpen(filename, pFile, flags, pOutFlags) {
+ return VFS.SQLITE_CANTOPEN;
+ }
+
+ /**
+ * @param {string} filename
+ * @param {number} syncDir
+ * @returns {number|Promise}
+ */
+ jDelete(filename, syncDir) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {string} filename
+ * @param {number} flags
+ * @param {DataView} pResOut
+ * @returns {number|Promise}
+ */
+ jAccess(filename, flags, pResOut) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {string} filename
+ * @param {Uint8Array} zOut
+ * @returns {number|Promise}
+ */
+ jFullPathname(filename, zOut) {
+ // Copy the filename to the output buffer.
+ const { read, written } = new TextEncoder().encodeInto(filename, zOut);
+ if (read < filename.length) return VFS.SQLITE_IOERR;
+ if (written >= zOut.length) return VFS.SQLITE_IOERR;
+ zOut[written] = 0;
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {Uint8Array} zBuf
+ * @returns {number|Promise}
+ */
+ jGetLastError(zBuf) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ jClose(pFile) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number|Promise}
+ */
+ jRead(pFile, pData, iOffset) {
+ pData.fill(0);
+ return VFS.SQLITE_IOERR_SHORT_READ;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number|Promise}
+ */
+ jWrite(pFile, pData, iOffset) {
+ return VFS.SQLITE_IOERR_WRITE;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} size
+ * @returns {number|Promise}
+ */
+ jTruncate(pFile, size) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} flags
+ * @returns {number|Promise}
+ */
+ jSync(pFile, flags) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {DataView} pSize
+ * @returns {number|Promise}
+ */
+ jFileSize(pFile, pSize) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ jLock(pFile, lockType) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ jUnlock(pFile, lockType) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {DataView} pResOut
+ * @returns {number|Promise}
+ */
+ jCheckReservedLock(pFile, pResOut) {
+ pResOut.setInt32(0, 0, true);
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} op
+ * @param {DataView} pArg
+ * @returns {number|Promise}
+ */
+ jFileControl(pFile, op, pArg) {
+ return VFS.SQLITE_NOTFOUND;
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ jSectorSize(pFile) {
+ return super.xSectorSize(pFile);
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ jDeviceCharacteristics(pFile) {
+ return 0;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} pFile
+ * @param {number} flags
+ * @param {number} pOutFlags
+ * @returns {number|Promise}
+ */
+ xOpen(pVfs, zName, pFile, flags, pOutFlags) {
+ const filename = this.#decodeFilename(zName, flags);
+ const pOutFlagsView = this.#makeTypedDataView('Int32', pOutFlags);
+ this['log']?.('jOpen', filename, pFile, '0x' + flags.toString(16));
+ return this.jOpen(filename, pFile, flags, pOutFlagsView);
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} syncDir
+ * @returns {number|Promise}
+ */
+ xDelete(pVfs, zName, syncDir) {
+ const filename = this._module.UTF8ToString(zName);
+ this['log']?.('jDelete', filename, syncDir);
+ return this.jDelete(filename, syncDir);
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} flags
+ * @param {number} pResOut
+ * @returns {number|Promise}
+ */
+ xAccess(pVfs, zName, flags, pResOut) {
+ const filename = this._module.UTF8ToString(zName);
+ const pResOutView = this.#makeTypedDataView('Int32', pResOut);
+ this['log']?.('jAccess', filename, flags);
+ return this.jAccess(filename, flags, pResOutView);
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} nOut
+ * @param {number} zOut
+ * @returns {number|Promise}
+ */
+ xFullPathname(pVfs, zName, nOut, zOut) {
+ const filename = this._module.UTF8ToString(zName);
+ const zOutArray = this._module.HEAPU8.subarray(zOut, zOut + nOut);
+ this['log']?.('jFullPathname', filename, nOut);
+ return this.jFullPathname(filename, zOutArray);
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} nBuf
+ * @param {number} zBuf
+ * @returns {number|Promise}
+ */
+ xGetLastError(pVfs, nBuf, zBuf) {
+ const zBufArray = this._module.HEAPU8.subarray(zBuf, zBuf + nBuf);
+ this['log']?.('jGetLastError', nBuf);
+ return this.jGetLastError(zBufArray);
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xClose(pFile) {
+ this['log']?.('jClose', pFile);
+ return this.jClose(pFile);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pData
+ * @param {number} iAmt
+ * @param {number} iOffsetLo
+ * @param {number} iOffsetHi
+ * @returns {number|Promise}
+ */
+ xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
+ const pDataArray = this.#makeDataArray(pData, iAmt);
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
+ this['log']?.('jRead', pFile, iAmt, iOffset);
+ return this.jRead(pFile, pDataArray, iOffset);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pData
+ * @param {number} iAmt
+ * @param {number} iOffsetLo
+ * @param {number} iOffsetHi
+ * @returns {number|Promise}
+ */
+ xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
+ const pDataArray = this.#makeDataArray(pData, iAmt);
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
+ this['log']?.('jWrite', pFile, pDataArray, iOffset);
+ return this.jWrite(pFile, pDataArray, iOffset);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} sizeLo
+ * @param {number} sizeHi
+ * @returns {number|Promise}
+ */
+ xTruncate(pFile, sizeLo, sizeHi) {
+ const size = delegalize(sizeLo, sizeHi);
+ this['log']?.('jTruncate', pFile, size);
+ return this.jTruncate(pFile, size);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} flags
+ * @returns {number|Promise}
+ */
+ xSync(pFile, flags) {
+ this['log']?.('jSync', pFile, flags);
+ return this.jSync(pFile, flags);
+ }
+
+ /**
+ *
+ * @param {number} pFile
+ * @param {number} pSize
+ * @returns {number|Promise}
+ */
+ xFileSize(pFile, pSize) {
+ const pSizeView = this.#makeTypedDataView('BigInt64', pSize);
+ this['log']?.('jFileSize', pFile);
+ return this.jFileSize(pFile, pSizeView);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ xLock(pFile, lockType) {
+ this['log']?.('jLock', pFile, lockType);
+ return this.jLock(pFile, lockType);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ xUnlock(pFile, lockType) {
+ this['log']?.('jUnlock', pFile, lockType);
+ return this.jUnlock(pFile, lockType);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pResOut
+ * @returns {number|Promise}
+ */
+ xCheckReservedLock(pFile, pResOut) {
+ const pResOutView = this.#makeTypedDataView('Int32', pResOut);
+ this['log']?.('jCheckReservedLock', pFile);
+ return this.jCheckReservedLock(pFile, pResOutView);
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} op
+ * @param {number} pArg
+ * @returns {number|Promise}
+ */
+ xFileControl(pFile, op, pArg) {
+ const pArgView = new DataView(
+ this._module.HEAPU8.buffer,
+ this._module.HEAPU8.byteOffset + pArg);
+ this['log']?.('jFileControl', pFile, op, pArgView);
+ return this.jFileControl(pFile, op, pArgView);
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xSectorSize(pFile) {
+ this['log']?.('jSectorSize', pFile);
+ return this.jSectorSize(pFile);
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xDeviceCharacteristics(pFile) {
+ this['log']?.('jDeviceCharacteristics', pFile);
+ return this.jDeviceCharacteristics(pFile);
+ }
+
+ /**
+ * Wrapped DataView for pointer arguments.
+ * Pointers to a single value are passed using DataView. A Proxy
+ * wrapper prevents use of incorrect type or endianness.
+ * @param {'Int32'|'BigInt64'} type
+ * @param {number} byteOffset
+ * @returns {DataView}
+ */
+ #makeTypedDataView(type, byteOffset) {
+ const byteLength = type === 'Int32' ? 4 : 8;
+ const getter = `get${type}`;
+ const setter = `set${type}`;
+ const makeDataView = () => new DataView(
+ this._module.HEAPU8.buffer,
+ this._module.HEAPU8.byteOffset + byteOffset,
+ byteLength);
+ let dataView = makeDataView();
+ return new Proxy(dataView, {
+ get(_, prop) {
+ if (dataView.buffer.byteLength === 0) {
+ // WebAssembly memory resize detached the buffer.
+ dataView = makeDataView();
+ }
+ if (prop === getter) {
+ return function(byteOffset, littleEndian) {
+ if (!littleEndian) throw new Error('must be little endian');
+ return dataView[prop](byteOffset, littleEndian);
+ }
+ }
+ if (prop === setter) {
+ return function(byteOffset, value, littleEndian) {
+ if (!littleEndian) throw new Error('must be little endian');
+ return dataView[prop](byteOffset, value, littleEndian);
+ }
+ }
+ if (typeof prop === 'string' && (prop.match(/^(get)|(set)/))) {
+ throw new Error('invalid type');
+ }
+ const result = dataView[prop];
+ return typeof result === 'function' ? result.bind(dataView) : result;
+ }
+ });
+ }
+
+ /**
+ * @param {number} byteOffset
+ * @param {number} byteLength
+ */
+ #makeDataArray(byteOffset, byteLength) {
+ let target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
+ return new Proxy(target, {
+ get: (_, prop, receiver) => {
+ if (target.buffer.byteLength === 0) {
+ // WebAssembly memory resize detached the buffer.
+ target = this._module.HEAPU8.subarray(byteOffset, byteOffset + byteLength);
+ }
+ const result = target[prop];
+ return typeof result === 'function' ? result.bind(target) : result;
+ }
+ });
+ }
+
+ #decodeFilename(zName, flags) {
+ if (flags & VFS.SQLITE_OPEN_URI) {
+ // The first null-terminated string is the URI path. Subsequent
+ // strings are query parameter keys and values.
+ // https://www.sqlite.org/c3ref/open.html#urifilenamesinsqlite3open
+ let pName = zName;
+ let state = 1;
+ const charCodes = [];
+ while (state) {
+ const charCode = this._module.HEAPU8[pName++];
+ if (charCode) {
+ charCodes.push(charCode);
+ } else {
+ if (!this._module.HEAPU8[pName]) state = null;
+ switch (state) {
+ case 1: // path
+ charCodes.push('?'.charCodeAt(0));
+ state = 2;
+ break;
+ case 2: // key
+ charCodes.push('='.charCodeAt(0));
+ state = 3;
+ break;
+ case 3: // value
+ charCodes.push('&'.charCodeAt(0));
+ state = 2;
+ break;
+ }
+ }
+ }
+ return new TextDecoder().decode(new Uint8Array(charCodes));
+ }
+ return zName ? this._module.UTF8ToString(zName) : null;
+ }
+}
+
+// Emscripten "legalizes" 64-bit integer arguments by passing them as
+// two 32-bit signed integers.
+function delegalize(lo32, hi32) {
+ return (hi32 * 0x100000000) + lo32 + (lo32 < 0 ? 2**32 : 0);
+}
diff --git a/web/core/local-db/worker/wa-sqlite/src/OPFSCoopSyncVFS.js b/web/core/local-db/worker/wa-sqlite/src/OPFSCoopSyncVFS.js
new file mode 100644
index 00000000000..2757ca9db9d
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/OPFSCoopSyncVFS.js
@@ -0,0 +1,592 @@
+// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
+import { FacadeVFS } from "./FacadeVFS.js";
+import * as VFS from "./VFS.js";
+
+const DEFAULT_TEMPORARY_FILES = 10;
+const LOCK_NOTIFY_INTERVAL = 1000;
+
+const DB_RELATED_FILE_SUFFIXES = ["", "-journal", "-wal"];
+
+const finalizationRegistry = new FinalizationRegistry((releaser) => releaser());
+
+class File {
+ /** @type {string} */ path;
+ /** @type {number} */ flags;
+ /** @type {FileSystemSyncAccessHandle} */ accessHandle;
+
+ /** @type {PersistentFile?} */ persistentFile;
+
+ constructor(path, flags) {
+ this.path = path;
+ this.flags = flags;
+ }
+}
+
+class PersistentFile {
+ /** @type {FileSystemFileHandle} */ fileHandle;
+ /** @type {FileSystemSyncAccessHandle} */ accessHandle = null;
+
+ // The following properties are for main database files.
+
+ /** @type {boolean} */ isLockBusy = false;
+ /** @type {boolean} */ isFileLocked = false;
+ /** @type {boolean} */ isRequestInProgress = false;
+ /** @type {function} */ handleLockReleaser = null;
+
+ /** @type {BroadcastChannel} */ handleRequestChannel;
+ /** @type {boolean} */ isHandleRequested = false;
+
+ constructor(fileHandle) {
+ this.fileHandle = fileHandle;
+ }
+}
+
+export class OPFSCoopSyncVFS extends FacadeVFS {
+ /** @type {Map} */ mapIdToFile = new Map();
+
+ lastError = null;
+ log = null; //function(...args) { console.log(`[${contextName}]`, ...args) };
+
+ /** @type {Map} */ persistentFiles = new Map();
+ /** @type {Map} */ boundAccessHandles = new Map();
+ /** @type {Set} */ unboundAccessHandles = new Set();
+ /** @type {Set} */ accessiblePaths = new Set();
+ releaser = null;
+
+ static async create(name, module) {
+ const vfs = new OPFSCoopSyncVFS(name, module);
+ await Promise.all([vfs.isReady(), vfs.#initialize(DEFAULT_TEMPORARY_FILES)]);
+ return vfs;
+ }
+
+ constructor(name, module) {
+ super(name, module);
+ }
+
+ async #initialize(nTemporaryFiles) {
+ // Delete temporary directories no longer in use.
+ const root = await navigator.storage.getDirectory();
+ // @ts-ignore
+ for await (const entry of root.values()) {
+ if (entry.kind === "directory" && entry.name.startsWith(".ahp-")) {
+ // A lock with the same name as the directory protects it from
+ // being deleted.
+ await navigator.locks.request(entry.name, { ifAvailable: true }, async (lock) => {
+ if (lock) {
+ this.log?.(`Deleting temporary directory ${entry.name}`);
+ await root.removeEntry(entry.name, { recursive: true });
+ } else {
+ this.log?.(`Temporary directory ${entry.name} is in use`);
+ }
+ });
+ }
+ }
+
+ // Create our temporary directory.
+ const tmpDirName = `.ahp-${Math.random().toString(36).slice(2)}`;
+ this.releaser = await new Promise((resolve) => {
+ navigator.locks.request(tmpDirName, () => {
+ return new Promise((release) => {
+ resolve(release);
+ });
+ });
+ });
+ finalizationRegistry.register(this, this.releaser);
+ const tmpDir = await root.getDirectoryHandle(tmpDirName, { create: true });
+
+ // Populate temporary directory.
+ for (let i = 0; i < nTemporaryFiles; i++) {
+ const tmpFile = await tmpDir.getFileHandle(`${i}.tmp`, { create: true });
+ const tmpAccessHandle = await tmpFile.createSyncAccessHandle();
+ this.unboundAccessHandles.add(tmpAccessHandle);
+ }
+ }
+
+ /**
+ * @param {string?} zName
+ * @param {number} fileId
+ * @param {number} flags
+ * @param {DataView} pOutFlags
+ * @returns {number}
+ */
+ jOpen(zName, fileId, flags, pOutFlags) {
+ try {
+ const url = new URL(zName || Math.random().toString(36).slice(2), "file://");
+ const path = url.pathname;
+
+ if (flags & VFS.SQLITE_OPEN_MAIN_DB) {
+ const persistentFile = this.persistentFiles.get(path);
+ if (persistentFile?.isRequestInProgress) {
+ // Should not reach here unless SQLite itself retries an open.
+ // Otherwise, asynchronous operations started on a previous
+ // open try should have completed.
+ return VFS.SQLITE_BUSY;
+ } else if (!persistentFile) {
+ // This is the usual starting point for opening a database.
+ // Register a Promise that resolves when the database and related
+ // files are ready to be used.
+ this.log?.(`creating persistent file for ${path}`);
+ const create = !!(flags & VFS.SQLITE_OPEN_CREATE);
+ this._module.retryOps.push(
+ (async () => {
+ try {
+ // Get the path directory handle.
+ let dirHandle = await navigator.storage.getDirectory();
+ const directories = path.split("/").filter((d) => d);
+ const filename = directories.pop();
+ for (const directory of directories) {
+ dirHandle = await dirHandle.getDirectoryHandle(directory, { create });
+ }
+
+ // Get file handles for the database and related files,
+ // and create persistent file instances.
+ for (const suffix of DB_RELATED_FILE_SUFFIXES) {
+ const fileHandle = await dirHandle.getFileHandle(filename + suffix, { create });
+ await this.#createPersistentFile(fileHandle);
+ }
+
+ // Get access handles for the files.
+ const file = new File(path, flags);
+ file.persistentFile = this.persistentFiles.get(path);
+ await this.#requestAccessHandle(file);
+ } catch (e) {
+ // Use an invalid persistent file to signal this error
+ // for the retried open.
+ const persistentFile = new PersistentFile(null);
+ this.persistentFiles.set(path, persistentFile);
+ console.error(e);
+ }
+ })()
+ );
+ return VFS.SQLITE_BUSY;
+ } else if (!persistentFile.fileHandle) {
+ // The asynchronous open operation failed.
+ this.persistentFiles.delete(path);
+ return VFS.SQLITE_CANTOPEN;
+ } else if (!persistentFile.accessHandle) {
+ // This branch is reached if the database was previously opened
+ // and closed.
+ this._module.retryOps.push(
+ (async () => {
+ const file = new File(path, flags);
+ file.persistentFile = this.persistentFiles.get(path);
+ await this.#requestAccessHandle(file);
+ })()
+ );
+ return VFS.SQLITE_BUSY;
+ }
+ }
+
+ if (!this.accessiblePaths.has(path) && !(flags & VFS.SQLITE_OPEN_CREATE)) {
+ throw new Error(`File ${path} not found`);
+ }
+
+ const file = new File(path, flags);
+ this.mapIdToFile.set(fileId, file);
+
+ if (this.persistentFiles.has(path)) {
+ file.persistentFile = this.persistentFiles.get(path);
+ } else if (this.boundAccessHandles.has(path)) {
+ // This temporary file was previously created and closed. Reopen
+ // the same access handle.
+ file.accessHandle = this.boundAccessHandles.get(path);
+ } else if (this.unboundAccessHandles.size) {
+ // Associate an unbound access handle to this file.
+ file.accessHandle = this.unboundAccessHandles.values().next().value;
+ file.accessHandle.truncate(0);
+ this.unboundAccessHandles.delete(file.accessHandle);
+ this.boundAccessHandles.set(path, file.accessHandle);
+ }
+ this.accessiblePaths.add(path);
+
+ pOutFlags.setInt32(0, flags, true);
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_CANTOPEN;
+ }
+ }
+
+ /**
+ * @param {string} zName
+ * @param {number} syncDir
+ * @returns {number}
+ */
+ jDelete(zName, syncDir) {
+ try {
+ const url = new URL(zName, "file://");
+ const path = url.pathname;
+ if (this.persistentFiles.has(path)) {
+ const persistentFile = this.persistentFiles.get(path);
+ persistentFile.accessHandle.truncate(0);
+ } else {
+ this.boundAccessHandles.get(path)?.truncate(0);
+ }
+ this.accessiblePaths.delete(path);
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_DELETE;
+ }
+ }
+
+ /**
+ * @param {string} zName
+ * @param {number} flags
+ * @param {DataView} pResOut
+ * @returns {number}
+ */
+ jAccess(zName, flags, pResOut) {
+ try {
+ const url = new URL(zName, "file://");
+ const path = url.pathname;
+ pResOut.setInt32(0, this.accessiblePaths.has(path) ? 1 : 0, true);
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_ACCESS;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @returns {number}
+ */
+ jClose(fileId) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+ this.mapIdToFile.delete(fileId);
+
+ if (file?.flags & VFS.SQLITE_OPEN_MAIN_DB) {
+ if (file.persistentFile?.handleLockReleaser) {
+ this.#releaseAccessHandle(file);
+ }
+ } else if (file?.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
+ file.accessHandle.truncate(0);
+ this.accessiblePaths.delete(file.path);
+ if (!this.persistentFiles.has(file.path)) {
+ this.boundAccessHandles.delete(file.path);
+ this.unboundAccessHandles.add(file.accessHandle);
+ }
+ }
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_CLOSE;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number}
+ */
+ jRead(fileId, pData, iOffset) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+
+ // On Chrome (at least), passing pData to accessHandle.read() is
+ // an error because pData is a Proxy of a Uint8Array. Calling
+ // subarray() produces a real Uint8Array and that works.
+ const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
+ const bytesRead = accessHandle.read(pData.subarray(), { at: iOffset });
+
+ // Opening a database file performs one read without a xLock call.
+ if (file.flags & VFS.SQLITE_OPEN_MAIN_DB && !file.persistentFile.isFileLocked) {
+ this.#releaseAccessHandle(file);
+ }
+
+ if (bytesRead < pData.byteLength) {
+ pData.fill(0, bytesRead);
+ return VFS.SQLITE_IOERR_SHORT_READ;
+ }
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_READ;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number}
+ */
+ jWrite(fileId, pData, iOffset) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+
+ // On Chrome (at least), passing pData to accessHandle.write() is
+ // an error because pData is a Proxy of a Uint8Array. Calling
+ // subarray() produces a real Uint8Array and that works.
+ const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
+ const nBytes = accessHandle.write(pData.subarray(), { at: iOffset });
+ if (nBytes !== pData.byteLength) throw new Error("short write");
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_WRITE;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {number} iSize
+ * @returns {number}
+ */
+ jTruncate(fileId, iSize) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+ const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
+ accessHandle.truncate(iSize);
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_TRUNCATE;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {number} flags
+ * @returns {number}
+ */
+ jSync(fileId, flags) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+ const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
+ accessHandle.flush();
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_FSYNC;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {DataView} pSize64
+ * @returns {number}
+ */
+ jFileSize(fileId, pSize64) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+ const accessHandle = file.accessHandle || file.persistentFile.accessHandle;
+ const size = accessHandle.getSize();
+ pSize64.setBigInt64(0, BigInt(size), true);
+ return VFS.SQLITE_OK;
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR_FSTAT;
+ }
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {number} lockType
+ * @returns {number}
+ */
+ jLock(fileId, lockType) {
+ const file = this.mapIdToFile.get(fileId);
+ if (file.persistentFile.isRequestInProgress) {
+ file.persistentFile.isLockBusy = true;
+ return VFS.SQLITE_BUSY;
+ }
+
+ file.persistentFile.isFileLocked = true;
+ if (!file.persistentFile.handleLockReleaser) {
+ // Start listening for notifications from other connections.
+ // This is before we actually get access handles, but waiting to
+ // listen until then allows a race condition where notifications
+ // are missed.
+ file.persistentFile.handleRequestChannel.onmessage = () => {
+ this.log?.(`received notification for ${file.path}`);
+ if (file.persistentFile.isFileLocked) {
+ // We're still using the access handle, so mark it to be
+ // released when we're done.
+ file.persistentFile.isHandleRequested = true;
+ } else {
+ // Release the access handles immediately.
+ this.#releaseAccessHandle(file);
+ }
+ file.persistentFile.handleRequestChannel.onmessage = null;
+ };
+
+ this.#requestAccessHandle(file);
+ this.log?.("returning SQLITE_BUSY");
+ file.persistentFile.isLockBusy = true;
+ return VFS.SQLITE_BUSY;
+ }
+ file.persistentFile.isLockBusy = false;
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {number} lockType
+ * @returns {number}
+ */
+ jUnlock(fileId, lockType) {
+ const file = this.mapIdToFile.get(fileId);
+ if (lockType === VFS.SQLITE_LOCK_NONE) {
+ // Don't change any state if this unlock is because xLock returned
+ // SQLITE_BUSY.
+ if (!file.persistentFile.isLockBusy) {
+ if (file.persistentFile.isHandleRequested) {
+ // Another connection wants the access handle.
+ this.#releaseAccessHandle(file);
+ this.isHandleRequested = false;
+ }
+ file.persistentFile.isFileLocked = false;
+ }
+ }
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} fileId
+ * @param {number} op
+ * @param {DataView} pArg
+ * @returns {number|Promise}
+ */
+ jFileControl(fileId, op, pArg) {
+ try {
+ const file = this.mapIdToFile.get(fileId);
+ switch (op) {
+ case VFS.SQLITE_FCNTL_PRAGMA:
+ const key = extractString(pArg, 4);
+ const value = extractString(pArg, 8);
+ this.log?.("xFileControl", file.path, "PRAGMA", key, value);
+ switch (key.toLowerCase()) {
+ case "journal_mode":
+ if (value && !["off", "memory", "delete", "wal"].includes(value.toLowerCase())) {
+ throw new Error('journal_mode must be "off", "memory", "delete", or "wal"');
+ }
+ break;
+ }
+ break;
+ }
+ } catch (e) {
+ this.lastError = e;
+ return VFS.SQLITE_IOERR;
+ }
+ return VFS.SQLITE_NOTFOUND;
+ }
+
+ /**
+ * @param {Uint8Array} zBuf
+ * @returns
+ */
+ jGetLastError(zBuf) {
+ if (this.lastError) {
+ console.error(this.lastError);
+ const outputArray = zBuf.subarray(0, zBuf.byteLength - 1);
+ const { written } = new TextEncoder().encodeInto(this.lastError.message, outputArray);
+ zBuf[written] = 0;
+ }
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {FileSystemFileHandle} fileHandle
+ * @returns {Promise}
+ */
+ async #createPersistentFile(fileHandle) {
+ const persistentFile = new PersistentFile(fileHandle);
+ const root = await navigator.storage.getDirectory();
+ const relativePath = await root.resolve(fileHandle);
+ const path = `/${relativePath.join("/")}`;
+ persistentFile.handleRequestChannel = new BroadcastChannel(`ahp:${path}`);
+ this.persistentFiles.set(path, persistentFile);
+
+ const f = await fileHandle.getFile();
+ if (f.size) {
+ this.accessiblePaths.add(path);
+ }
+ return persistentFile;
+ }
+
+ /**
+ * @param {File} file
+ */
+ #requestAccessHandle(file) {
+ console.assert(!file.persistentFile.handleLockReleaser);
+ if (!file.persistentFile.isRequestInProgress) {
+ file.persistentFile.isRequestInProgress = true;
+ this._module.retryOps.push(
+ (async () => {
+ // Acquire the Web Lock.
+ file.persistentFile.handleLockReleaser = await this.#acquireLock(file.persistentFile);
+
+ // Get access handles for the database and releated files in parallel.
+ this.log?.(`creating access handles for ${file.path}`);
+ await Promise.all(
+ DB_RELATED_FILE_SUFFIXES.map(async (suffix) => {
+ const persistentFile = this.persistentFiles.get(file.path + suffix);
+ if (persistentFile) {
+ persistentFile.accessHandle = await persistentFile.fileHandle.createSyncAccessHandle();
+ }
+ })
+ );
+ file.persistentFile.isRequestInProgress = false;
+ })()
+ );
+ return this._module.retryOps.at(-1);
+ }
+ return Promise.resolve();
+ }
+
+ /**
+ * @param {File} file
+ */
+ async #releaseAccessHandle(file) {
+ DB_RELATED_FILE_SUFFIXES.forEach(async (suffix) => {
+ const persistentFile = this.persistentFiles.get(file.path + suffix);
+ if (persistentFile) {
+ persistentFile.accessHandle?.close();
+ persistentFile.accessHandle = null;
+ }
+ });
+ this.log?.(`access handles closed for ${file.path}`);
+
+ file.persistentFile.handleLockReleaser?.();
+ file.persistentFile.handleLockReleaser = null;
+ this.log?.(`lock released for ${file.path}`);
+ }
+
+ /**
+ * @param {PersistentFile} persistentFile
+ * @returns {Promise} lock releaser
+ */
+ #acquireLock(persistentFile) {
+ return new Promise((resolve) => {
+ // Tell other connections we want the access handle.
+ const lockName = persistentFile.handleRequestChannel.name;
+ const notify = () => {
+ this.log?.(`notifying for ${lockName}`);
+ persistentFile.handleRequestChannel.postMessage(null);
+ };
+ const notifyId = setInterval(notify, LOCK_NOTIFY_INTERVAL);
+ setTimeout(notify);
+
+ this.log?.(`lock requested: ${lockName}`);
+ navigator.locks.request(lockName, (lock) => {
+ // We have the lock. Stop asking other connections for it.
+ this.log?.(`lock acquired: ${lockName}`, lock);
+ clearInterval(notifyId);
+ return new Promise(resolve);
+ });
+ });
+ }
+}
+
+function extractString(dataView, offset) {
+ const p = dataView.getUint32(offset, true);
+ if (p) {
+ const chars = new Uint8Array(dataView.buffer, p);
+ return new TextDecoder().decode(chars.subarray(0, chars.indexOf(0)));
+ }
+ return null;
+}
diff --git a/web/core/local-db/worker/wa-sqlite/src/VFS.js b/web/core/local-db/worker/wa-sqlite/src/VFS.js
new file mode 100644
index 00000000000..12966b9cfbe
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/VFS.js
@@ -0,0 +1,222 @@
+// Copyright 2024 Roy T. Hashimoto. All Rights Reserved.
+import * as VFS from './sqlite-constants.js';
+export * from './sqlite-constants.js';
+
+const DEFAULT_SECTOR_SIZE = 512;
+
+// Base class for a VFS.
+export class Base {
+ name;
+ mxPathname = 64;
+ _module;
+
+ /**
+ * @param {string} name
+ * @param {object} module
+ */
+ constructor(name, module) {
+ this.name = name;
+ this._module = module;
+ }
+
+ /**
+ * @returns {void|Promise}
+ */
+ close() {
+ }
+
+ /**
+ * @returns {boolean|Promise}
+ */
+ isReady() {
+ return true;
+ }
+
+ /**
+ * Overload in subclasses to indicate which methods are asynchronous.
+ * @param {string} methodName
+ * @returns {boolean}
+ */
+ hasAsyncMethod(methodName) {
+ return false;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} pFile
+ * @param {number} flags
+ * @param {number} pOutFlags
+ * @returns {number|Promise}
+ */
+ xOpen(pVfs, zName, pFile, flags, pOutFlags) {
+ return VFS.SQLITE_CANTOPEN;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} syncDir
+ * @returns {number|Promise}
+ */
+ xDelete(pVfs, zName, syncDir) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} flags
+ * @param {number} pResOut
+ * @returns {number|Promise}
+ */
+ xAccess(pVfs, zName, flags, pResOut) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} zName
+ * @param {number} nOut
+ * @param {number} zOut
+ * @returns {number|Promise}
+ */
+ xFullPathname(pVfs, zName, nOut, zOut) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pVfs
+ * @param {number} nBuf
+ * @param {number} zBuf
+ * @returns {number|Promise}
+ */
+ xGetLastError(pVfs, nBuf, zBuf) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xClose(pFile) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pData
+ * @param {number} iAmt
+ * @param {number} iOffsetLo
+ * @param {number} iOffsetHi
+ * @returns {number|Promise}
+ */
+ xRead(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pData
+ * @param {number} iAmt
+ * @param {number} iOffsetLo
+ * @param {number} iOffsetHi
+ * @returns {number|Promise}
+ */
+ xWrite(pFile, pData, iAmt, iOffsetLo, iOffsetHi) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} sizeLo
+ * @param {number} sizeHi
+ * @returns {number|Promise}
+ */
+ xTruncate(pFile, sizeLo, sizeHi) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} flags
+ * @returns {number|Promise}
+ */
+ xSync(pFile, flags) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ *
+ * @param {number} pFile
+ * @param {number} pSize
+ * @returns {number|Promise}
+ */
+ xFileSize(pFile, pSize) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ xLock(pFile, lockType) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} lockType
+ * @returns {number|Promise}
+ */
+ xUnlock(pFile, lockType) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} pResOut
+ * @returns {number|Promise}
+ */
+ xCheckReservedLock(pFile, pResOut) {
+ return VFS.SQLITE_OK;
+ }
+
+ /**
+ * @param {number} pFile
+ * @param {number} op
+ * @param {number} pArg
+ * @returns {number|Promise}
+ */
+ xFileControl(pFile, op, pArg) {
+ return VFS.SQLITE_NOTFOUND;
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xSectorSize(pFile) {
+ return DEFAULT_SECTOR_SIZE;
+ }
+
+ /**
+ * @param {number} pFile
+ * @returns {number|Promise}
+ */
+ xDeviceCharacteristics(pFile) {
+ return 0;
+ }
+}
+
+export const FILE_TYPE_MASK = [
+ VFS.SQLITE_OPEN_MAIN_DB,
+ VFS.SQLITE_OPEN_MAIN_JOURNAL,
+ VFS.SQLITE_OPEN_TEMP_DB,
+ VFS.SQLITE_OPEN_TEMP_JOURNAL,
+ VFS.SQLITE_OPEN_TRANSIENT_DB,
+ VFS.SQLITE_OPEN_SUBJOURNAL,
+ VFS.SQLITE_OPEN_SUPER_JOURNAL,
+ VFS.SQLITE_OPEN_WAL
+].reduce((mask, element) => mask | element);
\ No newline at end of file
diff --git a/web/core/local-db/worker/wa-sqlite/src/sqlite-api.js b/web/core/local-db/worker/wa-sqlite/src/sqlite-api.js
new file mode 100644
index 00000000000..500980b4ecd
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/sqlite-api.js
@@ -0,0 +1,899 @@
+// Copyright 2021 Roy T. Hashimoto. All Rights Reserved.
+
+import * as SQLite from "./sqlite-constants.js";
+export * from "./sqlite-constants.js";
+
+const MAX_INT64 = 0x7fffffffffffffffn;
+const MIN_INT64 = -0x8000000000000000n;
+
+const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
+
+export class SQLiteError extends Error {
+ constructor(message, code) {
+ super(message);
+ this.code = code;
+ }
+}
+
+const async = true;
+
+/**
+ * Builds a Javascript API from the Emscripten module. This API is still
+ * low-level and closely corresponds to the C API exported by the module,
+ * but differs in some specifics like throwing exceptions on errors.
+ * @param {*} Module SQLite Emscripten module
+ * @returns {SQLiteAPI}
+ */
+export function Factory(Module) {
+ /** @type {SQLiteAPI} */ const sqlite3 = {};
+
+ Module.retryOps = [];
+ const sqliteFreeAddress = Module._getSqliteFree();
+
+ // Allocate some space for 32-bit returned values.
+ const tmp = Module._malloc(8);
+ const tmpPtr = [tmp, tmp + 4];
+
+ // Convert a JS string to a C string. sqlite3_malloc is used to allocate
+ // memory (use sqlite3_free to deallocate).
+ function createUTF8(s) {
+ if (typeof s !== "string") return 0;
+ const utf8 = new TextEncoder().encode(s);
+ const zts = Module._sqlite3_malloc(utf8.byteLength + 1);
+ Module.HEAPU8.set(utf8, zts);
+ Module.HEAPU8[zts + utf8.byteLength] = 0;
+ return zts;
+ }
+
+ /**
+ * Concatenate 32-bit numbers into a 64-bit (signed) BigInt.
+ * @param {number} lo32
+ * @param {number} hi32
+ * @returns {bigint}
+ */
+ function cvt32x2ToBigInt(lo32, hi32) {
+ return (BigInt(hi32) << 32n) | (BigInt(lo32) & 0xffffffffn);
+ }
+
+ /**
+ * Concatenate 32-bit numbers and return as number or BigInt, depending
+ * on the value.
+ * @param {number} lo32
+ * @param {number} hi32
+ * @returns {number|bigint}
+ */
+ const cvt32x2AsSafe = (function () {
+ const hiMax = BigInt(Number.MAX_SAFE_INTEGER) >> 32n;
+ const hiMin = BigInt(Number.MIN_SAFE_INTEGER) >> 32n;
+
+ return function (lo32, hi32) {
+ if (hi32 > hiMax || hi32 < hiMin) {
+ // Can't be expressed as a Number so use BigInt.
+ return cvt32x2ToBigInt(lo32, hi32);
+ } else {
+ // Combine the upper and lower 32-bit numbers. The complication is
+ // that lo32 is a signed integer which makes manipulating its bits
+ // a little tricky - the sign bit gets handled separately.
+ return hi32 * 0x100000000 + (lo32 & 0x7fffffff) - (lo32 & 0x80000000);
+ }
+ };
+ })();
+
+ const databases = new Set();
+ function verifyDatabase(db) {
+ if (!databases.has(db)) {
+ throw new SQLiteError("not a database", SQLite.SQLITE_MISUSE);
+ }
+ }
+
+ const mapStmtToDB = new Map();
+ function verifyStatement(stmt) {
+ if (!mapStmtToDB.has(stmt)) {
+ throw new SQLiteError("not a statement", SQLite.SQLITE_MISUSE);
+ }
+ }
+
+ sqlite3.bind_collection = function (stmt, bindings) {
+ verifyStatement(stmt);
+ const isArray = Array.isArray(bindings);
+ const nBindings = sqlite3.bind_parameter_count(stmt);
+ for (let i = 1; i <= nBindings; ++i) {
+ const key = isArray ? i - 1 : sqlite3.bind_parameter_name(stmt, i);
+ const value = bindings[key];
+ if (value !== undefined) {
+ sqlite3.bind(stmt, i, value);
+ }
+ }
+ return SQLite.SQLITE_OK;
+ };
+
+ sqlite3.bind = function (stmt, i, value) {
+ verifyStatement(stmt);
+ switch (typeof value) {
+ case "number":
+ if (value === (value | 0)) {
+ return sqlite3.bind_int(stmt, i, value);
+ } else {
+ return sqlite3.bind_double(stmt, i, value);
+ }
+ case "string":
+ return sqlite3.bind_text(stmt, i, value);
+ default:
+ if (value instanceof Uint8Array || Array.isArray(value)) {
+ return sqlite3.bind_blob(stmt, i, value);
+ } else if (value === null) {
+ return sqlite3.bind_null(stmt, i);
+ } else if (typeof value === "bigint") {
+ return sqlite3.bind_int64(stmt, i, value);
+ } else if (value === undefined) {
+ // Existing binding (or NULL) will be used.
+ return SQLite.SQLITE_NOTICE;
+ } else {
+ console.warn("unknown binding converted to null", value);
+ return sqlite3.bind_null(stmt, i);
+ }
+ }
+ };
+
+ sqlite3.bind_blob = (function () {
+ const fname = "sqlite3_bind_blob";
+ const f = Module.cwrap(fname, ...decl("nnnnn:n"));
+ return function (stmt, i, value) {
+ verifyStatement(stmt);
+ // @ts-ignore
+ const byteLength = value.byteLength ?? value.length;
+ const ptr = Module._sqlite3_malloc(byteLength);
+ Module.HEAPU8.subarray(ptr).set(value);
+ const result = f(stmt, i, ptr, byteLength, sqliteFreeAddress);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.bind_parameter_count = (function () {
+ const fname = "sqlite3_bind_parameter_count";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (stmt) {
+ verifyStatement(stmt);
+ const result = f(stmt);
+ return result;
+ };
+ })();
+
+ sqlite3.bind_double = (function () {
+ const fname = "sqlite3_bind_double";
+ const f = Module.cwrap(fname, ...decl("nnn:n"));
+ return function (stmt, i, value) {
+ verifyStatement(stmt);
+ const result = f(stmt, i, value);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.bind_int = (function () {
+ const fname = "sqlite3_bind_int";
+ const f = Module.cwrap(fname, ...decl("nnn:n"));
+ return function (stmt, i, value) {
+ verifyStatement(stmt);
+ if (value > 0x7fffffff || value < -0x80000000) return SQLite.SQLITE_RANGE;
+
+ const result = f(stmt, i, value);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.bind_int64 = (function () {
+ const fname = "sqlite3_bind_int64";
+ const f = Module.cwrap(fname, ...decl("nnnn:n"));
+ return function (stmt, i, value) {
+ verifyStatement(stmt);
+ if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
+
+ const lo32 = value & 0xffffffffn;
+ const hi32 = value >> 32n;
+ const result = f(stmt, i, Number(lo32), Number(hi32));
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.bind_null = (function () {
+ const fname = "sqlite3_bind_null";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, i) {
+ verifyStatement(stmt);
+ const result = f(stmt, i);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.bind_parameter_name = (function () {
+ const fname = "sqlite3_bind_parameter_name";
+ const f = Module.cwrap(fname, ...decl("n:s"));
+ return function (stmt, i) {
+ verifyStatement(stmt);
+ const result = f(stmt, i);
+ return result;
+ };
+ })();
+
+ sqlite3.bind_text = (function () {
+ const fname = "sqlite3_bind_text";
+ const f = Module.cwrap(fname, ...decl("nnnnn:n"));
+ return function (stmt, i, value) {
+ verifyStatement(stmt);
+ const ptr = createUTF8(value);
+ const result = f(stmt, i, ptr, -1, sqliteFreeAddress);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.changes = (function () {
+ const fname = "sqlite3_changes";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (db) {
+ verifyDatabase(db);
+ const result = f(db);
+ return result;
+ };
+ })();
+
+ sqlite3.clear_bindings = (function () {
+ const fname = "sqlite3_clear_bindings";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (stmt) {
+ verifyStatement(stmt);
+ const result = f(stmt);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.close = (function () {
+ const fname = "sqlite3_close";
+ const f = Module.cwrap(fname, ...decl("n:n"), { async });
+ return async function (db) {
+ verifyDatabase(db);
+ const result = await f(db);
+ databases.delete(db);
+ return check(fname, result, db);
+ };
+ })();
+
+ sqlite3.column = function (stmt, iCol) {
+ verifyStatement(stmt);
+ const type = sqlite3.column_type(stmt, iCol);
+ switch (type) {
+ case SQLite.SQLITE_BLOB:
+ return sqlite3.column_blob(stmt, iCol);
+ case SQLite.SQLITE_FLOAT:
+ return sqlite3.column_double(stmt, iCol);
+ case SQLite.SQLITE_INTEGER:
+ const lo32 = sqlite3.column_int(stmt, iCol);
+ const hi32 = Module.getTempRet0();
+ return cvt32x2AsSafe(lo32, hi32);
+ case SQLite.SQLITE_NULL:
+ return null;
+ case SQLite.SQLITE_TEXT:
+ return sqlite3.column_text(stmt, iCol);
+ default:
+ throw new SQLiteError("unknown type", type);
+ }
+ };
+
+ sqlite3.column_blob = (function () {
+ const fname = "sqlite3_column_blob";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const nBytes = sqlite3.column_bytes(stmt, iCol);
+ const address = f(stmt, iCol);
+ const result = Module.HEAPU8.subarray(address, address + nBytes);
+ return result;
+ };
+ })();
+
+ sqlite3.column_bytes = (function () {
+ const fname = "sqlite3_column_bytes";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.column_count = (function () {
+ const fname = "sqlite3_column_count";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (stmt) {
+ verifyStatement(stmt);
+ const result = f(stmt);
+ return result;
+ };
+ })();
+
+ sqlite3.column_double = (function () {
+ const fname = "sqlite3_column_double";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.column_int = (function () {
+ // Retrieve int64 but use only the lower 32 bits. The upper 32-bits are
+ // accessible with Module.getTempRet0().
+ const fname = "sqlite3_column_int64";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.column_int64 = (function () {
+ const fname = "sqlite3_column_int64";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const lo32 = f(stmt, iCol);
+ const hi32 = Module.getTempRet0();
+ const result = cvt32x2ToBigInt(lo32, hi32);
+ return result;
+ };
+ })();
+
+ sqlite3.column_name = (function () {
+ const fname = "sqlite3_column_name";
+ const f = Module.cwrap(fname, ...decl("nn:s"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.column_names = function (stmt) {
+ const columns = [];
+ const nColumns = sqlite3.column_count(stmt);
+ for (let i = 0; i < nColumns; ++i) {
+ columns.push(sqlite3.column_name(stmt, i));
+ }
+ return columns;
+ };
+
+ sqlite3.column_text = (function () {
+ const fname = "sqlite3_column_text";
+ const f = Module.cwrap(fname, ...decl("nn:s"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.column_type = (function () {
+ const fname = "sqlite3_column_type";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (stmt, iCol) {
+ verifyStatement(stmt);
+ const result = f(stmt, iCol);
+ return result;
+ };
+ })();
+
+ sqlite3.create_function = function (db, zFunctionName, nArg, eTextRep, pApp, xFunc, xStep, xFinal) {
+ verifyDatabase(db);
+
+ // Convert SQLite callback arguments to JavaScript-friendly arguments.
+ function adapt(f) {
+ return f instanceof AsyncFunction
+ ? async (ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n))
+ : (ctx, n, values) => f(ctx, Module.HEAP32.subarray(values / 4, values / 4 + n));
+ }
+
+ const result = Module.create_function(
+ db,
+ zFunctionName,
+ nArg,
+ eTextRep,
+ pApp,
+ xFunc && adapt(xFunc),
+ xStep && adapt(xStep),
+ xFinal
+ );
+ return check("sqlite3_create_function", result, db);
+ };
+
+ sqlite3.data_count = (function () {
+ const fname = "sqlite3_data_count";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (stmt) {
+ verifyStatement(stmt);
+ const result = f(stmt);
+ return result;
+ };
+ })();
+
+ sqlite3.exec = async function (db, sql, callback) {
+ for await (const stmt of sqlite3.statements(db, sql)) {
+ let columns;
+ while ((await sqlite3.step(stmt)) === SQLite.SQLITE_ROW) {
+ if (callback) {
+ columns = columns ?? sqlite3.column_names(stmt);
+ const row = sqlite3.row(stmt);
+ await callback(row, columns);
+ }
+ }
+ }
+ return SQLite.SQLITE_OK;
+ };
+
+ sqlite3.finalize = (function () {
+ const fname = "sqlite3_finalize";
+ const f = Module.cwrap(fname, ...decl("n:n"), { async });
+ return async function (stmt) {
+ const result = await f(stmt);
+ mapStmtToDB.delete(stmt);
+
+ // Don't throw on error here. Typically the error has already been
+ // thrown and finalize() is part of the cleanup.
+ return result;
+ };
+ })();
+
+ sqlite3.get_autocommit = (function () {
+ const fname = "sqlite3_get_autocommit";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (db) {
+ const result = f(db);
+ return result;
+ };
+ })();
+
+ sqlite3.libversion = (function () {
+ const fname = "sqlite3_libversion";
+ const f = Module.cwrap(fname, ...decl(":s"));
+ return function () {
+ const result = f();
+ return result;
+ };
+ })();
+
+ sqlite3.libversion_number = (function () {
+ const fname = "sqlite3_libversion_number";
+ const f = Module.cwrap(fname, ...decl(":n"));
+ return function () {
+ const result = f();
+ return result;
+ };
+ })();
+
+ sqlite3.limit = (function () {
+ const fname = "sqlite3_limit";
+ const f = Module.cwrap(fname, ...decl("nnn:n"));
+ return function (db, id, newVal) {
+ const result = f(db, id, newVal);
+ return result;
+ };
+ })();
+
+ sqlite3.open_v2 = (function () {
+ const fname = "sqlite3_open_v2";
+ const f = Module.cwrap(fname, ...decl("snnn:n"), { async });
+ return async function (zFilename, flags, zVfs) {
+ flags = flags || SQLite.SQLITE_OPEN_CREATE | SQLite.SQLITE_OPEN_READWRITE;
+ zVfs = createUTF8(zVfs);
+ try {
+ // Allow retry operations.
+ const rc = await retry(() => f(zFilename, tmpPtr[0], flags, zVfs));
+
+ const db = Module.getValue(tmpPtr[0], "*");
+ databases.add(db);
+
+ Module.ccall("RegisterExtensionFunctions", "void", ["number"], [db]);
+ check(fname, rc);
+ return db;
+ } finally {
+ Module._sqlite3_free(zVfs);
+ }
+ };
+ })();
+
+ sqlite3.progress_handler = function (db, nProgressOps, handler, userData) {
+ verifyDatabase(db);
+ Module.progress_handler(db, nProgressOps, handler, userData);
+ };
+
+ sqlite3.reset = (function () {
+ const fname = "sqlite3_reset";
+ const f = Module.cwrap(fname, ...decl("n:n"), { async });
+ return async function (stmt) {
+ verifyStatement(stmt);
+ const result = await f(stmt);
+ return check(fname, result, mapStmtToDB.get(stmt));
+ };
+ })();
+
+ sqlite3.result = function (context, value) {
+ switch (typeof value) {
+ case "number":
+ if (value === (value | 0)) {
+ sqlite3.result_int(context, value);
+ } else {
+ sqlite3.result_double(context, value);
+ }
+ break;
+ case "string":
+ sqlite3.result_text(context, value);
+ break;
+ default:
+ if (value instanceof Uint8Array || Array.isArray(value)) {
+ sqlite3.result_blob(context, value);
+ } else if (value === null) {
+ sqlite3.result_null(context);
+ } else if (typeof value === "bigint") {
+ return sqlite3.result_int64(context, value);
+ } else {
+ console.warn("unknown result converted to null", value);
+ sqlite3.result_null(context);
+ }
+ break;
+ }
+ };
+
+ sqlite3.result_blob = (function () {
+ const fname = "sqlite3_result_blob";
+ const f = Module.cwrap(fname, ...decl("nnnn:n"));
+ return function (context, value) {
+ // @ts-ignore
+ const byteLength = value.byteLength ?? value.length;
+ const ptr = Module._sqlite3_malloc(byteLength);
+ Module.HEAPU8.subarray(ptr).set(value);
+ f(context, ptr, byteLength, sqliteFreeAddress); // void return
+ };
+ })();
+
+ sqlite3.result_double = (function () {
+ const fname = "sqlite3_result_double";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (context, value) {
+ f(context, value); // void return
+ };
+ })();
+
+ sqlite3.result_int = (function () {
+ const fname = "sqlite3_result_int";
+ const f = Module.cwrap(fname, ...decl("nn:n"));
+ return function (context, value) {
+ f(context, value); // void return
+ };
+ })();
+
+ sqlite3.result_int64 = (function () {
+ const fname = "sqlite3_result_int64";
+ const f = Module.cwrap(fname, ...decl("nnn:n"));
+ return function (context, value) {
+ if (value > MAX_INT64 || value < MIN_INT64) return SQLite.SQLITE_RANGE;
+
+ const lo32 = value & 0xffffffffn;
+ const hi32 = value >> 32n;
+ f(context, Number(lo32), Number(hi32)); // void return
+ };
+ })();
+
+ sqlite3.result_null = (function () {
+ const fname = "sqlite3_result_null";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (context) {
+ f(context); // void return
+ };
+ })();
+
+ sqlite3.result_text = (function () {
+ const fname = "sqlite3_result_text";
+ const f = Module.cwrap(fname, ...decl("nnnn:n"));
+ return function (context, value) {
+ const ptr = createUTF8(value);
+ f(context, ptr, -1, sqliteFreeAddress); // void return
+ };
+ })();
+
+ sqlite3.row = function (stmt) {
+ const row = [];
+ const nColumns = sqlite3.data_count(stmt);
+ for (let i = 0; i < nColumns; ++i) {
+ const value = sqlite3.column(stmt, i);
+
+ // Copy blob if aliasing volatile WebAssembly memory. This avoids an
+ // unnecessary copy if users monkey patch column_blob to copy.
+ // @ts-ignore
+ row.push(value?.buffer === Module.HEAPU8.buffer ? value.slice() : value);
+ }
+ return row;
+ };
+
+ sqlite3.set_authorizer = function (db, xAuth, pApp) {
+ verifyDatabase(db);
+
+ // Convert SQLite callback arguments to JavaScript-friendly arguments.
+ function cvtArgs(_, iAction, p3, p4, p5, p6) {
+ return [
+ _,
+ iAction,
+ Module.UTF8ToString(p3),
+ Module.UTF8ToString(p4),
+ Module.UTF8ToString(p5),
+ Module.UTF8ToString(p6),
+ ];
+ }
+ function adapt(f) {
+ return f instanceof AsyncFunction
+ ? async (_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6))
+ : (_, iAction, p3, p4, p5, p6) => f(...cvtArgs(_, iAction, p3, p4, p5, p6));
+ }
+
+ const result = Module.set_authorizer(db, adapt(xAuth), pApp);
+ return check("sqlite3_set_authorizer", result, db);
+ };
+
+ sqlite3.sql = (function () {
+ const fname = "sqlite3_sql";
+ const f = Module.cwrap(fname, ...decl("n:s"));
+ return function (stmt) {
+ verifyStatement(stmt);
+ const result = f(stmt);
+ return result;
+ };
+ })();
+
+ sqlite3.statements = function (db, sql, options = {}) {
+ const prepare = Module.cwrap(
+ "sqlite3_prepare_v3",
+ "number",
+ ["number", "number", "number", "number", "number", "number"],
+ { async: true }
+ );
+
+ return (async function* () {
+ const onFinally = [];
+ try {
+ // Encode SQL string to UTF-8.
+ const utf8 = new TextEncoder().encode(sql);
+
+ // Copy encoded string to WebAssembly memory. The SQLite docs say
+ // zero-termination is a minor optimization so add room for that.
+ // Also add space for the statement handle and SQL tail pointer.
+ const allocSize = utf8.byteLength - (utf8.byteLength % 4) + 12;
+ const pzHead = Module._sqlite3_malloc(allocSize);
+ const pzEnd = pzHead + utf8.byteLength + 1;
+ onFinally.push(() => Module._sqlite3_free(pzHead));
+ Module.HEAPU8.set(utf8, pzHead);
+ Module.HEAPU8[pzEnd - 1] = 0;
+
+ // Use extra space for the statement handle and SQL tail pointer.
+ const pStmt = pzHead + allocSize - 8;
+ const pzTail = pzHead + allocSize - 4;
+
+ // Ensure that statement handles are not leaked.
+ let stmt;
+ function maybeFinalize() {
+ if (stmt && !options.unscoped) {
+ sqlite3.finalize(stmt);
+ }
+ stmt = 0;
+ }
+ onFinally.push(maybeFinalize);
+
+ // Loop over statements.
+ Module.setValue(pzTail, pzHead, "*");
+ do {
+ // Reclaim resources for the previous iteration.
+ maybeFinalize();
+
+ // Call sqlite3_prepare_v3() for the next statement.
+ // Allow retry operations.
+ const zTail = Module.getValue(pzTail, "*");
+ const rc = await retry(() => {
+ return prepare(db, zTail, pzEnd - pzTail, options.flags || 0, pStmt, pzTail);
+ });
+
+ if (rc !== SQLite.SQLITE_OK) {
+ check("sqlite3_prepare_v3", rc, db);
+ }
+
+ stmt = Module.getValue(pStmt, "*");
+ if (stmt) {
+ mapStmtToDB.set(stmt, db);
+ yield stmt;
+ }
+ } while (stmt);
+ } finally {
+ while (onFinally.length) {
+ onFinally.pop()();
+ }
+ }
+ })();
+ };
+
+ sqlite3.step = (function () {
+ const fname = "sqlite3_step";
+ const f = Module.cwrap(fname, ...decl("n:n"), { async });
+ return async function (stmt) {
+ verifyStatement(stmt);
+
+ // Allow retry operations.
+ const rc = await retry(() => f(stmt));
+
+ return check(fname, rc, mapStmtToDB.get(stmt), [SQLite.SQLITE_ROW, SQLite.SQLITE_DONE]);
+ };
+ })();
+
+ sqlite3.update_hook = function (db, xUpdateHook) {
+ verifyDatabase(db);
+
+ // Convert SQLite callback arguments to JavaScript-friendly arguments.
+ function cvtArgs(iUpdateType, dbName, tblName, lo32, hi32) {
+ return [iUpdateType, Module.UTF8ToString(dbName), Module.UTF8ToString(tblName), cvt32x2ToBigInt(lo32, hi32)];
+ }
+ function adapt(f) {
+ return f instanceof AsyncFunction
+ ? async (iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32))
+ : (iUpdateType, dbName, tblName, lo32, hi32) => f(...cvtArgs(iUpdateType, dbName, tblName, lo32, hi32));
+ }
+
+ Module.update_hook(db, adapt(xUpdateHook));
+ };
+
+ sqlite3.value = function (pValue) {
+ const type = sqlite3.value_type(pValue);
+ switch (type) {
+ case SQLite.SQLITE_BLOB:
+ return sqlite3.value_blob(pValue);
+ case SQLite.SQLITE_FLOAT:
+ return sqlite3.value_double(pValue);
+ case SQLite.SQLITE_INTEGER:
+ const lo32 = sqlite3.value_int(pValue);
+ const hi32 = Module.getTempRet0();
+ return cvt32x2AsSafe(lo32, hi32);
+ case SQLite.SQLITE_NULL:
+ return null;
+ case SQLite.SQLITE_TEXT:
+ return sqlite3.value_text(pValue);
+ default:
+ throw new SQLiteError("unknown type", type);
+ }
+ };
+
+ sqlite3.value_blob = (function () {
+ const fname = "sqlite3_value_blob";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const nBytes = sqlite3.value_bytes(pValue);
+ const address = f(pValue);
+ const result = Module.HEAPU8.subarray(address, address + nBytes);
+ return result;
+ };
+ })();
+
+ sqlite3.value_bytes = (function () {
+ const fname = "sqlite3_value_bytes";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const result = f(pValue);
+ return result;
+ };
+ })();
+
+ sqlite3.value_double = (function () {
+ const fname = "sqlite3_value_double";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const result = f(pValue);
+ return result;
+ };
+ })();
+
+ sqlite3.value_int = (function () {
+ const fname = "sqlite3_value_int64";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const result = f(pValue);
+ return result;
+ };
+ })();
+
+ sqlite3.value_int64 = (function () {
+ const fname = "sqlite3_value_int64";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const lo32 = f(pValue);
+ const hi32 = Module.getTempRet0();
+ const result = cvt32x2ToBigInt(lo32, hi32);
+ return result;
+ };
+ })();
+
+ sqlite3.value_text = (function () {
+ const fname = "sqlite3_value_text";
+ const f = Module.cwrap(fname, ...decl("n:s"));
+ return function (pValue) {
+ const result = f(pValue);
+ return result;
+ };
+ })();
+
+ sqlite3.value_type = (function () {
+ const fname = "sqlite3_value_type";
+ const f = Module.cwrap(fname, ...decl("n:n"));
+ return function (pValue) {
+ const result = f(pValue);
+ return result;
+ };
+ })();
+
+ sqlite3.vfs_register = function (vfs, makeDefault) {
+ const result = Module.vfs_register(vfs, makeDefault);
+ return check("sqlite3_vfs_register", result);
+ };
+
+ function check(fname, result, db = null, allowed = [SQLite.SQLITE_OK]) {
+ if (allowed.includes(result)) return result;
+ const message = db ? Module.ccall("sqlite3_errmsg", "string", ["number"], [db]) : fname;
+ throw new SQLiteError(message, result);
+ }
+
+ // This function is used to automatically retry failed calls that
+ // have pending retry operations that should allow the retry to
+ // succeed.
+ async function retry(f) {
+ let rc;
+ do {
+ // Wait for all pending retry operations to complete. This is
+ // normally empty on the first loop iteration.
+ if (Module.retryOps.length) {
+ await Promise.all(Module.retryOps);
+ Module.retryOps = [];
+ }
+
+ rc = await f();
+
+ // Retry on failure with new pending retry operations.
+ } while (rc && Module.retryOps.length);
+ return rc;
+ }
+
+ return sqlite3;
+}
+
+// Helper function to use a more compact signature specification.
+function decl(s) {
+ const result = [];
+ const m = s.match(/([ns@]*):([nsv@])/);
+ switch (m[2]) {
+ case "n":
+ result.push("number");
+ break;
+ case "s":
+ result.push("string");
+ break;
+ case "v":
+ result.push(null);
+ break;
+ }
+
+ const args = [];
+ for (let c of m[1]) {
+ switch (c) {
+ case "n":
+ args.push("number");
+ break;
+ case "s":
+ args.push("string");
+ break;
+ }
+ }
+ result.push(args);
+ return result;
+}
diff --git a/web/core/local-db/worker/wa-sqlite/src/sqlite-constants.js b/web/core/local-db/worker/wa-sqlite/src/sqlite-constants.js
new file mode 100644
index 00000000000..3878b169631
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/sqlite-constants.js
@@ -0,0 +1,275 @@
+// Primary result codes.
+// https://www.sqlite.org/rescode.html
+export const SQLITE_OK = 0;
+export const SQLITE_ERROR = 1;
+export const SQLITE_INTERNAL = 2;
+export const SQLITE_PERM = 3;
+export const SQLITE_ABORT = 4;
+export const SQLITE_BUSY = 5;
+export const SQLITE_LOCKED = 6;
+export const SQLITE_NOMEM = 7;
+export const SQLITE_READONLY = 8;
+export const SQLITE_INTERRUPT = 9;
+export const SQLITE_IOERR = 10;
+export const SQLITE_CORRUPT = 11;
+export const SQLITE_NOTFOUND = 12;
+export const SQLITE_FULL = 13;
+export const SQLITE_CANTOPEN = 14;
+export const SQLITE_PROTOCOL = 15;
+export const SQLITE_EMPTY = 16;
+export const SQLITE_SCHEMA = 17;
+export const SQLITE_TOOBIG = 18;
+export const SQLITE_CONSTRAINT = 19;
+export const SQLITE_MISMATCH = 20;
+export const SQLITE_MISUSE = 21;
+export const SQLITE_NOLFS = 22;
+export const SQLITE_AUTH = 23;
+export const SQLITE_FORMAT = 24;
+export const SQLITE_RANGE = 25;
+export const SQLITE_NOTADB = 26;
+export const SQLITE_NOTICE = 27;
+export const SQLITE_WARNING = 28;
+export const SQLITE_ROW = 100;
+export const SQLITE_DONE = 101;
+
+// Extended error codes.
+export const SQLITE_IOERR_ACCESS = 3338;
+export const SQLITE_IOERR_CHECKRESERVEDLOCK = 3594;
+export const SQLITE_IOERR_CLOSE = 4106;
+export const SQLITE_IOERR_DATA = 8202;
+export const SQLITE_IOERR_DELETE = 2570;
+export const SQLITE_IOERR_DELETE_NOENT = 5898;
+export const SQLITE_IOERR_DIR_FSYNC = 1290;
+export const SQLITE_IOERR_FSTAT = 1802;
+export const SQLITE_IOERR_FSYNC = 1034;
+export const SQLITE_IOERR_GETTEMPPATH = 6410;
+export const SQLITE_IOERR_LOCK = 3850;
+export const SQLITE_IOERR_NOMEM = 3082;
+export const SQLITE_IOERR_READ = 266;
+export const SQLITE_IOERR_RDLOCK = 2314;
+export const SQLITE_IOERR_SEEK = 5642;
+export const SQLITE_IOERR_SHORT_READ = 522;
+export const SQLITE_IOERR_TRUNCATE = 1546;
+export const SQLITE_IOERR_UNLOCK = 2058;
+export const SQLITE_IOERR_VNODE = 6922;
+export const SQLITE_IOERR_WRITE = 778;
+export const SQLITE_IOERR_BEGIN_ATOMIC = 7434;
+export const SQLITE_IOERR_COMMIT_ATOMIC = 7690;
+export const SQLITE_IOERR_ROLLBACK_ATOMIC = 7946;
+
+// Other extended result codes.
+export const SQLITE_CONSTRAINT_CHECK = 275;
+export const SQLITE_CONSTRAINT_COMMITHOOK = 531;
+export const SQLITE_CONSTRAINT_FOREIGNKEY = 787;
+export const SQLITE_CONSTRAINT_FUNCTION = 1043;
+export const SQLITE_CONSTRAINT_NOTNULL = 1299;
+export const SQLITE_CONSTRAINT_PINNED = 2835;
+export const SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
+export const SQLITE_CONSTRAINT_ROWID = 2579;
+export const SQLITE_CONSTRAINT_TRIGGER = 1811;
+export const SQLITE_CONSTRAINT_UNIQUE = 2067;
+export const SQLITE_CONSTRAINT_VTAB = 2323;
+
+// Open flags.
+// https://www.sqlite.org/c3ref/c_open_autoproxy.html
+export const SQLITE_OPEN_READONLY = 0x00000001;
+export const SQLITE_OPEN_READWRITE = 0x00000002;
+export const SQLITE_OPEN_CREATE = 0x00000004;
+export const SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
+export const SQLITE_OPEN_EXCLUSIVE = 0x00000010;
+export const SQLITE_OPEN_AUTOPROXY = 0x00000020;
+export const SQLITE_OPEN_URI = 0x00000040;
+export const SQLITE_OPEN_MEMORY = 0x00000080;
+export const SQLITE_OPEN_MAIN_DB = 0x00000100;
+export const SQLITE_OPEN_TEMP_DB = 0x00000200;
+export const SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
+export const SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
+export const SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
+export const SQLITE_OPEN_SUBJOURNAL = 0x00002000;
+export const SQLITE_OPEN_SUPER_JOURNAL = 0x00004000;
+export const SQLITE_OPEN_NOMUTEX = 0x00008000;
+export const SQLITE_OPEN_FULLMUTEX = 0x00010000;
+export const SQLITE_OPEN_SHAREDCACHE = 0x00020000;
+export const SQLITE_OPEN_PRIVATECACHE = 0x00040000;
+export const SQLITE_OPEN_WAL = 0x00080000;
+export const SQLITE_OPEN_NOFOLLOW = 0x01000000;
+
+// Locking levels.
+// https://www.sqlite.org/c3ref/c_lock_exclusive.html
+export const SQLITE_LOCK_NONE = 0;
+export const SQLITE_LOCK_SHARED = 1;
+export const SQLITE_LOCK_RESERVED = 2;
+export const SQLITE_LOCK_PENDING = 3;
+export const SQLITE_LOCK_EXCLUSIVE = 4;
+
+// Device characteristics.
+// https://www.sqlite.org/c3ref/c_iocap_atomic.html
+export const SQLITE_IOCAP_ATOMIC = 0x00000001;
+export const SQLITE_IOCAP_ATOMIC512 = 0x00000002;
+export const SQLITE_IOCAP_ATOMIC1K = 0x00000004;
+export const SQLITE_IOCAP_ATOMIC2K = 0x00000008;
+export const SQLITE_IOCAP_ATOMIC4K = 0x00000010;
+export const SQLITE_IOCAP_ATOMIC8K = 0x00000020;
+export const SQLITE_IOCAP_ATOMIC16K = 0x00000040;
+export const SQLITE_IOCAP_ATOMIC32K = 0x00000080;
+export const SQLITE_IOCAP_ATOMIC64K = 0x00000100;
+export const SQLITE_IOCAP_SAFE_APPEND = 0x00000200;
+export const SQLITE_IOCAP_SEQUENTIAL = 0x00000400;
+export const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800;
+export const SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000;
+export const SQLITE_IOCAP_IMMUTABLE = 0x00002000;
+export const SQLITE_IOCAP_BATCH_ATOMIC = 0x00004000;
+
+// xAccess flags.
+// https://www.sqlite.org/c3ref/c_access_exists.html
+export const SQLITE_ACCESS_EXISTS = 0;
+export const SQLITE_ACCESS_READWRITE = 1;
+export const SQLITE_ACCESS_READ = 2;
+
+// File control opcodes
+// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
+export const SQLITE_FCNTL_LOCKSTATE = 1;
+export const SQLITE_FCNTL_GET_LOCKPROXYFILE = 2;
+export const SQLITE_FCNTL_SET_LOCKPROXYFILE = 3;
+export const SQLITE_FCNTL_LAST_ERRNO = 4;
+export const SQLITE_FCNTL_SIZE_HINT = 5;
+export const SQLITE_FCNTL_CHUNK_SIZE = 6;
+export const SQLITE_FCNTL_FILE_POINTER = 7;
+export const SQLITE_FCNTL_SYNC_OMITTED = 8;
+export const SQLITE_FCNTL_WIN32_AV_RETRY = 9;
+export const SQLITE_FCNTL_PERSIST_WAL = 10;
+export const SQLITE_FCNTL_OVERWRITE = 11;
+export const SQLITE_FCNTL_VFSNAME = 12;
+export const SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13;
+export const SQLITE_FCNTL_PRAGMA = 14;
+export const SQLITE_FCNTL_BUSYHANDLER = 15;
+export const SQLITE_FCNTL_TEMPFILENAME = 16;
+export const SQLITE_FCNTL_MMAP_SIZE = 18;
+export const SQLITE_FCNTL_TRACE = 19;
+export const SQLITE_FCNTL_HAS_MOVED = 20;
+export const SQLITE_FCNTL_SYNC = 21;
+export const SQLITE_FCNTL_COMMIT_PHASETWO = 22;
+export const SQLITE_FCNTL_WIN32_SET_HANDLE = 23;
+export const SQLITE_FCNTL_WAL_BLOCK = 24;
+export const SQLITE_FCNTL_ZIPVFS = 25;
+export const SQLITE_FCNTL_RBU = 26;
+export const SQLITE_FCNTL_VFS_POINTER = 27;
+export const SQLITE_FCNTL_JOURNAL_POINTER = 28;
+export const SQLITE_FCNTL_WIN32_GET_HANDLE = 29;
+export const SQLITE_FCNTL_PDB = 30;
+export const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
+export const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
+export const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
+export const SQLITE_FCNTL_LOCK_TIMEOUT = 34;
+export const SQLITE_FCNTL_DATA_VERSION = 35;
+export const SQLITE_FCNTL_SIZE_LIMIT = 36;
+export const SQLITE_FCNTL_CKPT_DONE = 37;
+export const SQLITE_FCNTL_RESERVE_BYTES = 38;
+export const SQLITE_FCNTL_CKPT_START = 39;
+
+// Fundamental datatypes.
+// https://www.sqlite.org/c3ref/c_blob.html
+export const SQLITE_INTEGER = 1;
+export const SQLITE_FLOAT = 2;
+export const SQLITE_TEXT = 3;
+export const SQLITE_BLOB = 4;
+export const SQLITE_NULL = 5;
+
+// Special destructor behavior.
+// https://www.sqlite.org/c3ref/c_static.html
+export const SQLITE_STATIC = 0;
+export const SQLITE_TRANSIENT = -1;
+
+// Text encodings.
+// https://sqlite.org/c3ref/c_any.html
+export const SQLITE_UTF8 = 1; /* IMP: R-37514-35566 */
+export const SQLITE_UTF16LE = 2; /* IMP: R-03371-37637 */
+export const SQLITE_UTF16BE = 3; /* IMP: R-51971-34154 */
+export const SQLITE_UTF16 = 4; /* Use native byte order */
+
+// Module constraint ops.
+export const SQLITE_INDEX_CONSTRAINT_EQ = 2;
+export const SQLITE_INDEX_CONSTRAINT_GT = 4;
+export const SQLITE_INDEX_CONSTRAINT_LE = 8;
+export const SQLITE_INDEX_CONSTRAINT_LT = 16;
+export const SQLITE_INDEX_CONSTRAINT_GE = 32;
+export const SQLITE_INDEX_CONSTRAINT_MATCH = 64;
+export const SQLITE_INDEX_CONSTRAINT_LIKE = 65;
+export const SQLITE_INDEX_CONSTRAINT_GLOB = 66;
+export const SQLITE_INDEX_CONSTRAINT_REGEXP = 67;
+export const SQLITE_INDEX_CONSTRAINT_NE = 68;
+export const SQLITE_INDEX_CONSTRAINT_ISNOT = 69;
+export const SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70;
+export const SQLITE_INDEX_CONSTRAINT_ISNULL = 71;
+export const SQLITE_INDEX_CONSTRAINT_IS = 72;
+export const SQLITE_INDEX_CONSTRAINT_FUNCTION = 150;
+export const SQLITE_INDEX_SCAN_UNIQUE = 1; /* Scan visits at most = 1 row */
+
+// Function flags
+export const SQLITE_DETERMINISTIC = 0x000000800;
+export const SQLITE_DIRECTONLY = 0x000080000;
+export const SQLITE_SUBTYPE = 0x000100000;
+export const SQLITE_INNOCUOUS = 0x000200000;
+
+// Sync flags
+export const SQLITE_SYNC_NORMAL = 0x00002;
+export const SQLITE_SYNC_FULL = 0x00003;
+export const SQLITE_SYNC_DATAONLY = 0x00010;
+
+// Authorizer action codes
+export const SQLITE_CREATE_INDEX = 1;
+export const SQLITE_CREATE_TABLE = 2;
+export const SQLITE_CREATE_TEMP_INDEX = 3;
+export const SQLITE_CREATE_TEMP_TABLE = 4;
+export const SQLITE_CREATE_TEMP_TRIGGER = 5;
+export const SQLITE_CREATE_TEMP_VIEW = 6;
+export const SQLITE_CREATE_TRIGGER = 7;
+export const SQLITE_CREATE_VIEW = 8;
+export const SQLITE_DELETE = 9;
+export const SQLITE_DROP_INDEX = 10;
+export const SQLITE_DROP_TABLE = 11;
+export const SQLITE_DROP_TEMP_INDEX = 12;
+export const SQLITE_DROP_TEMP_TABLE = 13;
+export const SQLITE_DROP_TEMP_TRIGGER = 14;
+export const SQLITE_DROP_TEMP_VIEW = 15;
+export const SQLITE_DROP_TRIGGER = 16;
+export const SQLITE_DROP_VIEW = 17;
+export const SQLITE_INSERT = 18;
+export const SQLITE_PRAGMA = 19;
+export const SQLITE_READ = 20;
+export const SQLITE_SELECT = 21;
+export const SQLITE_TRANSACTION = 22;
+export const SQLITE_UPDATE = 23;
+export const SQLITE_ATTACH = 24;
+export const SQLITE_DETACH = 25;
+export const SQLITE_ALTER_TABLE = 26;
+export const SQLITE_REINDEX = 27;
+export const SQLITE_ANALYZE = 28;
+export const SQLITE_CREATE_VTABLE = 29;
+export const SQLITE_DROP_VTABLE = 30;
+export const SQLITE_FUNCTION = 31;
+export const SQLITE_SAVEPOINT = 32;
+export const SQLITE_COPY = 0;
+export const SQLITE_RECURSIVE = 33;
+
+// Authorizer return codes
+export const SQLITE_DENY = 1;
+export const SQLITE_IGNORE = 2;
+
+// Limit categories
+export const SQLITE_LIMIT_LENGTH = 0;
+export const SQLITE_LIMIT_SQL_LENGTH = 1;
+export const SQLITE_LIMIT_COLUMN = 2;
+export const SQLITE_LIMIT_EXPR_DEPTH = 3;
+export const SQLITE_LIMIT_COMPOUND_SELECT = 4;
+export const SQLITE_LIMIT_VDBE_OP = 5;
+export const SQLITE_LIMIT_FUNCTION_ARG = 6;
+export const SQLITE_LIMIT_ATTACHED = 7;
+export const SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8;
+export const SQLITE_LIMIT_VARIABLE_NUMBER = 9;
+export const SQLITE_LIMIT_TRIGGER_DEPTH = 10;
+export const SQLITE_LIMIT_WORKER_THREADS = 11;
+
+export const SQLITE_PREPARE_PERSISTENT = 0x01;
+export const SQLITE_PREPARE_NORMALIZED = 0x02;
+export const SQLITE_PREPARE_NO_VTAB = 0x04;
\ No newline at end of file
diff --git a/web/core/local-db/worker/wa-sqlite/src/types/globals.d.ts b/web/core/local-db/worker/wa-sqlite/src/types/globals.d.ts
new file mode 100644
index 00000000000..7c4507cec10
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/types/globals.d.ts
@@ -0,0 +1,60 @@
+declare namespace Asyncify {
+ function handleAsync(f: () => Promise);
+}
+
+declare function UTF8ToString(ptr: number): string;
+declare function lengthBytesUTF8(s: string): number;
+declare function stringToUTF8(s: string, p: number, n: number);
+declare function ccall(name: string, returns: string, args: Array, options?: object): any;
+declare function getValue(ptr: number, type: string): number;
+declare function setValue(ptr: number, value: number, type: string): number;
+declare function mergeInto(library: object, methods: object): void;
+
+declare var HEAPU8: Uint8Array;
+declare var HEAPU32: Uint32Array;
+declare var LibraryManager;
+declare var Module;
+declare var _vfsAccess;
+declare var _vfsCheckReservedLock;
+declare var _vfsClose;
+declare var _vfsDelete;
+declare var _vfsDeviceCharacteristics;
+declare var _vfsFileControl;
+declare var _vfsFileSize;
+declare var _vfsLock;
+declare var _vfsOpen;
+declare var _vfsRead;
+declare var _vfsSectorSize;
+declare var _vfsSync;
+declare var _vfsTruncate;
+declare var _vfsUnlock;
+declare var _vfsWrite;
+
+declare var _jsFunc;
+declare var _jsStep;
+declare var _jsFinal;
+
+declare var _modStruct;
+declare var _modCreate;
+declare var _modConnect;
+declare var _modBestIndex;
+declare var _modDisconnect;
+declare var _modDestroy;
+declare var _modOpen;
+declare var _modClose;
+declare var _modFilter;
+declare var _modNext;
+declare var _modEof;
+declare var _modColumn;
+declare var _modRowid;
+declare var _modUpdate;
+declare var _modBegin;
+declare var _modSync;
+declare var _modCommit;
+declare var _modRollback;
+declare var _modFindFunction;
+declare var _modRename;
+
+declare var _jsAuth;
+
+declare var _jsProgress;
\ No newline at end of file
diff --git a/web/core/local-db/worker/wa-sqlite/src/types/index.d.ts b/web/core/local-db/worker/wa-sqlite/src/types/index.d.ts
new file mode 100644
index 00000000000..4056786243d
--- /dev/null
+++ b/web/core/local-db/worker/wa-sqlite/src/types/index.d.ts
@@ -0,0 +1,1317 @@
+/**
+ * This is a WebAssembly build of SQLite with experimental support for
+ * writing SQLite virtual file systems and modules (for virtual tables)
+ * in Javascript. Also see the
+ * [GitHub repository](https://github.com/rhashimoto/wa-sqlite) and the
+ * [online demo](https://rhashimoto.github.io/wa-sqlite/demo/).
+ * @module
+ */
+
+/**
+ * Javascript types that SQLite can use
+ *
+ * C integer and floating-point types both map to/from Javascript `number`.
+ * Blob data can be provided to SQLite as `Uint8Array` or `number[]` (with
+ * each element converted to a byte); SQLite always returns blob data as
+ * `Uint8Array`
+ */
+type SQLiteCompatibleType = number|string|Uint8Array|Array|bigint|null;
+
+/**
+ * SQLite Virtual File System object
+ *
+ * Objects with this interface can be passed to {@link SQLiteAPI.vfs_register}
+ * to define a new filesystem.
+ *
+ * There are examples of a synchronous
+ * [MemoryVFS.js](https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/MemoryVFS.js),
+ * and asynchronous
+ * [MemoryAsyncVFS.js](https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/MemoryAsyncVFS.js)
+ * and
+ * [IndexedDbVFS.js](https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/IndexedDbVFS.js).
+ *
+ * @see https://sqlite.org/vfs.html
+ * @see https://sqlite.org/c3ref/io_methods.html
+ */
+declare interface SQLiteVFS {
+ /** Maximum length of a file path in UTF-8 bytes (default 64) */
+ mxPathName?: number;
+
+ close(): void|Promise;
+ isReady(): boolean|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xClose(fileId: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xRead(
+ fileId: number,
+ pData: number,
+ iAmt: number,
+ iOffsetLo: number,
+ iOffsetHi: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xWrite(
+ fileId: number,
+ pData: number,
+ iAmt: number,
+ iOffsetLo: number,
+ iOffsetHi: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xTruncate(fileId: number, iSizeLo: number, iSizeHi): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xSync(fileId: number, flags: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xFileSize(
+ fileId: number,
+ pSize64: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xLock(fileId: number, flags: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xUnlock(fileId: number, flags: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xCheckReservedLock(
+ fileId: number,
+ pResOut: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xFileControl(
+ fileId: number,
+ flags: number,
+ pOut: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/io_methods.html */
+ xDeviceCharacteristics(fileId: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/vfs.html */
+ xOpen(
+ pVfs: number,
+ zName: number,
+ pFile: number,
+ flags: number,
+ pOutFlags: number
+ ): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/vfs.html */
+ xDelete(pVfs: number, zName: number, syncDir: number): number|Promise;
+
+ /** @see https://sqlite.org/c3ref/vfs.html */
+ xAccess(
+ pVfs: number,
+ zName: number,
+ flags: number,
+ pResOut: number
+ ): number|Promise;
+}
+
+/**
+ * Options object argument for {@link SQLiteAPI.statements}
+ */
+declare interface SQLitePrepareOptions {
+ /**
+ * Statement handles prepared and yielded by {@link SQLiteAPI.statements}
+ * are normally valid only within the scope of an iteration.
+ * Set `unscoped` to `true` to give iterated statements an arbitrary
+ * lifetime.
+ */
+ unscoped?: boolean;
+
+ /**
+ * SQLITE_PREPARE_* flags
+ * @see https://www.sqlite.org/c3ref/c_prepare_normalize.html#sqlitepreparepersistent
+ */
+ flags?: number;
+}
+
+/**
+ * Javascript wrappers for the SQLite C API (plus a few convenience functions)
+ *
+ * Function signatures have been slightly modified to be more
+ * Javascript-friendly. For the C functions that return an error code,
+ * the corresponding Javascript wrapper will throw an exception with a
+ * `code` property on an error.
+ *
+ * Note that a few functions return a Promise in order to accomodate
+ * either a synchronous or asynchronous SQLite build, generally those
+ * involved with opening/closing a database or executing a statement.
+ *
+ * To create an instance of the API, follow these steps:
+ *
+ * ```javascript
+ * // Import an ES6 module factory function from one of the
+ * // package builds, either 'wa-sqlite.mjs' (synchronous) or
+ * // 'wa-sqlite-async.mjs' (asynchronous). You should only
+ * // use the asynchronous build if you plan to use an
+ * // asynchronous VFS or module.
+ * import SQLiteESMFactory from 'wa-sqlite/dist/wa-sqlite.mjs';
+ *
+ * // Import the Javascript API wrappers.
+ * import * as SQLite from 'wa-sqlite';
+ *
+ * // Use an async function to simplify Promise handling.
+ * (async function() {
+ * // Invoke the ES6 module factory to create the SQLite
+ * // Emscripten module. This will fetch and compile the
+ * // .wasm file.
+ * const module = await SQLiteESMFactory();
+ *
+ * // Use the module to build the API instance.
+ * const sqlite3 = SQLite.Factory(module);
+ *
+ * // Use the API to open and access a database.
+ * const db = await sqlite3.open_v2('myDB');
+ * ...
+ * })();
+ * ```
+ *
+ * @see https://sqlite.org/c3ref/funclist.html
+ */
+declare interface SQLiteAPI {
+ /**
+ * Bind a collection of values to a statement
+ *
+ * This convenience function binds values from either an array or object
+ * to a prepared statement with placeholder parameters.
+ *
+ * Array example using numbered parameters (numbering is implicit in
+ * this example):
+ * ```
+ * const sql = 'INSERT INTO tbl VALUES (?, ?, ?)';
+ * for await (const stmt of sqlite3.statements(db, sql) {
+ * sqlite3.bind_collection(stmt, [42, 'hello', null]);
+ * ...
+ * }
+ * ```
+ *
+ * Object example using named parameters (':', '@', or '$' prefixes
+ * are allowed):
+ * ```
+ * const sql = 'INSERT INTO tbl VALUES (?, ?, ?)';
+ * for await (const stmt of sqlite3.statements(db, sql) {
+ * sqlite3.bind_collection(stmt, {
+ * '@foo': 42,
+ * '@bar': 'hello',
+ * '@baz': null,
+ * });
+ * ...
+ * }
+ * ```
+ *
+ * Note that SQLite bindings are indexed beginning with 1, but when
+ * binding values from an array `a` the values begin with `a[0]`.
+ * @param stmt prepared statement pointer
+ * @param bindings
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_collection(
+ stmt: number,
+ bindings: {[index: string]: SQLiteCompatibleType|null}|Array
+ ): number;
+
+ /**
+ * Bind value to prepared statement
+ *
+ * This convenience function calls the appropriate `bind_*` function
+ * based on the type of `value`. Note that binding indices begin with 1.
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind(stmt: number, i: number, value: SQLiteCompatibleType|null): number;
+
+ /**
+ * Bind blob to prepared statement parameter
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_blob(stmt: number, i: number, value: Uint8Array|Array): number;
+
+ /**
+ * Bind number to prepared statement parameter
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_double(stmt: number, i: number, value: number): number;
+
+ /**
+ * Bind number to prepared statement parameter
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_int(stmt: number, i: number, value: number): number;
+
+ /**
+ * Bind number to prepared statement parameter
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_int64(stmt: number, i: number, value: bigint): number;
+
+ /**
+ * Bind null to prepared statement
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_null(stmt: number, i: number): number;
+
+ /**
+ * Get number of bound parameters
+ * @see https://www.sqlite.org/c3ref/bind_parameter_count.html
+ * @param stmt prepared statement pointer
+ * @returns number of statement binding locations
+ */
+ bind_parameter_count(stmt: number): number;
+
+ /**
+ * Get name of bound parameter
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_parameter_name.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @returns binding name
+ */
+ bind_parameter_name(stmt: number, i: number): string;
+
+ /**
+ * Bind string to prepared statement
+ *
+ * Note that binding indices begin with 1.
+ * @see https://www.sqlite.org/c3ref/bind_blob.html
+ * @param stmt prepared statement pointer
+ * @param i binding index
+ * @param value
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ bind_text(stmt: number, i: number, value: string): number;
+
+ /**
+ * Get count of rows modified by last insert/update
+ * @see https://www.sqlite.org/c3ref/changes.html
+ * @param db database pointer
+ * @returns number of rows modified
+ */
+ changes(db): number;
+
+ /**
+ * Reset all bindings on a prepared statement.
+ * @see https://www.sqlite.org/c3ref/clear_bindings.html
+ * @param stmt prepared statement pointer
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ clear_bindings(stmt: number): number;
+
+ /**
+ * Close database connection
+ * @see https://www.sqlite.org/c3ref/close.html
+ * @param db database pointer
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ close(db): Promise;
+
+ /**
+ * Call the appropriate `column_*` function based on the column type
+ *
+ * The type is determined by calling {@link column_type}, which may
+ * not match the type declared in `CREATE TABLE`. Note that if the column
+ * value is a blob then as with `column_blob` the result may be invalid
+ * after the next SQLite call; copy if it needs to be retained.
+ *
+ * Integer values are returned as Number if within the min/max safe
+ * integer bounds, otherwise they are returned as BigInt.
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column(stmt: number, i: number): SQLiteCompatibleType;
+
+ /**
+ * Extract a column value from a row after a prepared statment {@link step}
+ *
+ * The contents of the returned buffer may be invalid after the
+ * next SQLite call. Make a copy of the data (e.g. with `.slice()`)
+ * if longer retention is required.
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column_blob(stmt: number, i: number): Uint8Array;
+
+ /**
+ * Get storage size for column text or blob
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns number of bytes in column text or blob
+ */
+ column_bytes(stmt: number, i: number): number;
+
+ /**
+ * Get number of columns for a prepared statement
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @returns number of columns
+ */
+ column_count(stmt: number): number;
+
+ /**
+ * Extract a column value from a row after a prepared statment {@link step}
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column_double(stmt: number, i: number): number;
+
+ /**
+ * Extract a column value from a row after a prepared statment {@link step}
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column_int(stmt: number, i: number): number;
+
+ /**
+ * Extract a column value from a row after a prepared statment {@link step}
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column_int64(stmt: number, i: number): bigint;
+
+ /**
+ * Get a column name for a prepared statement
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column name
+ */
+ column_name(stmt: number, i: number): string;
+
+ /**
+ * Get names for all columns of a prepared statement
+ *
+ * This is a convenience function that calls {@link column_count} and
+ * {@link column_name}.
+ * @param stmt
+ * @returns array of column names
+ */
+ column_names(stmt: number): Array;
+
+ /**
+ * Extract a column value from a row after a prepared statment {@link step}
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns column value
+ */
+ column_text(stmt: number, i: number): string;
+
+ /**
+ * Get column type for a prepared statement
+ *
+ * Note that this type may not match the type declared in `CREATE TABLE`.
+ * @see https://www.sqlite.org/c3ref/column_blob.html
+ * @param stmt prepared statement pointer
+ * @param i column index
+ * @returns enumeration value for type
+ */
+ column_type(stmt: number, i: number): number;
+
+ /**
+ * Create or redefine SQL functions
+ *
+ * The application data passed is ignored. Use closures instead.
+ *
+ * If any callback function returns a Promise, that function must
+ * be declared `async`, i.e. it must allow use of `await`.
+ * @see https://sqlite.org/c3ref/create_function.html
+ * @param db database pointer
+ * @param zFunctionName
+ * @param nArg number of function arguments
+ * @param eTextRep text encoding (and other flags)
+ * @param pApp application data (ignored)
+ * @param xFunc
+ * @param xStep
+ * @param xFinal
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ create_function(
+ db: number,
+ zFunctionName: string,
+ nArg: number,
+ eTextRep: number,
+ pApp: number,
+ xFunc?: (context: number, values: Uint32Array) => void|Promise,
+ xStep?: (context: number, values: Uint32Array) => void|Promise,
+ xFinal?: (context: number) => void|Promise): number;
+
+ /**
+ * Get number of columns in current row of a prepared statement
+ * @see https://www.sqlite.org/c3ref/data_count.html
+ * @param stmt prepared statement pointer
+ * @returns number of columns
+ */
+ data_count(stmt: number): number;
+
+ /**
+ * One-step query execution interface
+ *
+ * The implementation of this function uses {@link row}, which makes a
+ * copy of blobs and returns BigInt for integers outside the safe integer
+ * bounds for Number.
+ * @see https://www.sqlite.org/c3ref/exec.html
+ * @param db database pointer
+ * @param zSQL queries
+ * @param callback called for each output row
+ * @returns Promise resolving to `SQLITE_OK` (rejects on error)
+ */
+ exec(
+ db: number,
+ zSQL: string,
+ callback?: (row: Array, columns: string[]) => void
+ ): Promise;
+
+ /**
+ * Destroy a prepared statement object compiled by {@link statements}
+ * with the `unscoped` option set to `true`
+ *
+ * This function does *not* throw on error.
+ * @see https://www.sqlite.org/c3ref/finalize.html
+ * @param stmt prepared statement pointer
+ * @returns Promise resolving to `SQLITE_OK` or error status
+ */
+ finalize(stmt: number): Promise;
+
+ /**
+ * Test for autocommit mode
+ * @see https://sqlite.org/c3ref/get_autocommit.html
+ * @param db database pointer
+ * @returns Non-zero if autocommit mode is on, zero otherwise
+ */
+ get_autocommit(db: number): number;
+
+ /**
+ * Get SQLite library version
+ * @see https://www.sqlite.org/c3ref/libversion.html
+ * @returns version string, e.g. '3.35.5'
+ */
+ libversion(): string;
+
+ /**
+ * Get SQLite library version
+ * @see https://www.sqlite.org/c3ref/libversion.html
+ * @returns version number, e.g. 3035005
+ */
+ libversion_number(): number
+
+ /**
+ * Set a usage limit on a connection.
+ * @see https://www.sqlite.org/c3ref/limit.html
+ * @param db database pointer
+ * @param id limit category
+ * @param newVal
+ * @returns previous setting
+ */
+ limit(
+ db: number,
+ id: number,
+ newVal: number): number;
+
+ /**
+ * Opening a new database connection.
+ *
+ * Note that this function differs from the C API in that it
+ * returns the Promise-wrapped database pointer (instead of a
+ * result code).
+ * @see https://sqlite.org/c3ref/open.html
+ * @param zFilename
+ * @param iFlags `SQLite.SQLITE_OPEN_CREATE | SQLite.SQLITE_OPEN_READWRITE` (0x6) if omitted
+ * @param zVfs VFS name
+ * @returns Promise-wrapped database pointer.
+ */
+ open_v2(
+ zFilename: string,
+ iFlags?: number,
+ zVfs?: string
+ ): Promise;
+
+ /**
+ * Specify callback to be invoked between long-running queries
+ *
+ * The application data passed is ignored. Use closures instead.
+ *
+ * If any callback function returns a Promise, that function must
+ * be declared `async`, i.e. it must allow use of `await`.
+ * @param db database pointer
+ * @param nProgressOps target number of database operations between handler invocations
+ * @param handler
+ * @param userData
+ */
+ progress_handler(db: number, nProgressOps: number, handler: (userData: any) => number|Promise, userData);
+
+ /**
+ * Reset a prepared statement object
+ * @see https://www.sqlite.org/c3ref/reset.html
+ * @param stmt prepared statement pointer
+ * @returns Promise-wrapped `SQLITE_OK` (rejects on error)
+ */
+ reset(stmt: number): Promise;
+
+ /**
+ * Convenience function to call `result_*` based of the type of `value`
+ * @param context context pointer
+ * @param value
+ */
+ result(context: number, value: (SQLiteCompatibleType|number[])|null): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ * @param value
+ */
+ result_blob(context: number, value: Uint8Array|number[]): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ * @param value
+ */
+ result_double(context: number, value: number): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ * @param value
+ */
+ result_int(context: number, value: number): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ * @param value
+ */
+ result_int64(context: number, value: bigint): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ */
+ result_null(context: number): void;
+
+ /**
+ * Set the result of a function or vtable column
+ * @see https://sqlite.org/c3ref/result_blob.html
+ * @param context context pointer
+ * @param value
+ */
+ result_text(context: number, value: string): void;
+
+ /**
+ * Get all column data for a row from a prepared statement step
+ *
+ * This convenience function will return a copy of any blob, unlike
+ * {@link column_blob} which returns a value referencing volatile WASM
+ * memory with short validity. Like {@link column}, it will return a
+ * BigInt for integers outside the safe integer bounds for Number.
+ * @param stmt prepared statement pointer
+ * @returns row data
+ */
+ row(stmt: number): Array;
+
+ /**
+ * Register a callback function that is invoked to authorize certain SQL statement actions.
+ * @see https://www.sqlite.org/c3ref/set_authorizer.html
+ * @param db database pointer
+ * @param authFunction
+ * @param userData
+ */
+ set_authorizer(
+ db: number,
+ authFunction: (userData: any, iActionCode: number, param3: string|null, param4: string|null, param5: string|null, param6: string|null) => number|Promise,
+ userData: any): number;
+
+ /**
+ * Get statement SQL
+ * @see https://www.sqlite.org/c3ref/expanded_sql.html
+ * @param stmt prepared statement pointer
+ * @returns SQL
+ */
+ sql(stmt: number): string;
+
+ /**
+ * SQL statement iterator
+ *
+ * This function manages statement compilation by creating an async
+ * iterator that yields a prepared statement handle on each iteration.
+ * It is typically used with a `for await` loop (in an async function),
+ * like this:
+ * ```javascript
+ * // Compile one statement on each iteration of this loop.
+ * for await (const stmt of sqlite3.statements(db, sql)) {
+ * // Bind parameters here if using SQLite placeholders.
+ *
+ * // Execute the statement with this loop.
+ * while (await sqlite3.step(stmt) === SQLite.SQLITE_ROW) {
+ * // Collect row data here.
+ * }
+ *
+ * // Change bindings, reset, and execute again if desired.
+ * }
+ * ```
+ *
+ * By default, the lifetime of a yielded prepared statement is managed
+ * automatically by the iterator, ending at the end of each iteration.
+ * {@link finalize} should *not* be called on a statement provided by
+ * the iterator unless the `unscoped` option is set to `true` (that
+ * option is provided for applications that wish to manage statement
+ * lifetimes manually).
+ *
+ * If using the iterator manually, i.e. by calling its `next`
+ * method, be sure to call the `return` method if iteration
+ * is abandoned before completion (`for await` and other implicit
+ * traversals provided by Javascript do this automatically)
+ * to ensure that all allocated resources are released.
+ * @see https://www.sqlite.org/c3ref/prepare.html
+ * @param db database pointer
+ * @param sql
+ * @param options
+ */
+ statements(db: number, sql: string, options?: SQLitePrepareOptions): AsyncIterable;
+
+ /**
+ * Evaluate an SQL statement
+ * @see https://www.sqlite.org/c3ref/step.html
+ * @param stmt prepared statement pointer
+ * @returns Promise resolving to `SQLITE_ROW` or `SQLITE_DONE`
+ * (rejects on error)
+ */
+ step(stmt: number): Promise;
+
+ /**
+ * Register an update hook
+ *
+ * The callback is invoked whenever a row is updated, inserted, or deleted
+ * in a rowid table on this connection.
+ * @see https://www.sqlite.org/c3ref/update_hook.html
+ *
+ * updateType is one of:
+ * - SQLITE_DELETE: 9
+ * - SQLITE_INSERT: 18
+ * - SQLITE_UPDATE: 23
+ * @see https://www.sqlite.org/c3ref/c_alter_table.html
+ *
+ * @param db database pointer
+ * @param callback
+ */
+ update_hook(
+ db: number,
+ callback: (updateType: number, dbName: string|null, tblName: string|null, rowid: bigint) => void): void;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ *
+ * This is a convenience function that calls the appropriate `value_*`
+ * function based on its type. Note that if the value is a blob then as
+ * with `value_blob` the result may be invalid after the next SQLite call.
+ *
+ * Integer values are returned as Number if within the min/max safe
+ * integer bounds, otherwise they are returned as BigInt.
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value(pValue: number): SQLiteCompatibleType;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ *
+ * The contents of the returned buffer may be invalid after the
+ * next SQLite call. Make a copy of the data (e.g. with `.slice()`)
+ * if longer retention is required.
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value_blob(pValue: number): Uint8Array;
+
+ /**
+ * Get blob or text size for value
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns size
+ */
+ value_bytes(pValue: number): number;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value_double(pValue: number): number;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value_int(pValue: number): number;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value_int64(pValue: number): bigint;
+
+ /**
+ * Extract a value from `sqlite3_value`
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns value
+ */
+ value_text(pValue: number): string;
+
+ /**
+ * Get type of `sqlite3_value`
+ * @see https://sqlite.org/c3ref/value_blob.html
+ * @param pValue `sqlite3_value` pointer
+ * @returns enumeration value for type
+ */
+ value_type(pValue: number): number;
+
+ /**
+ * Register a new Virtual File System.
+ *
+ * @see https://www.sqlite.org/c3ref/vfs_find.html
+ * @param vfs VFS object
+ * @param makeDefault
+ * @returns `SQLITE_OK` (throws exception on error)
+ */
+ vfs_register(vfs: SQLiteVFS, makeDefault?: boolean): number;
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/sqlite-constants.js' {
+ export const SQLITE_OK: 0;
+ export const SQLITE_ERROR: 1;
+ export const SQLITE_INTERNAL: 2;
+ export const SQLITE_PERM: 3;
+ export const SQLITE_ABORT: 4;
+ export const SQLITE_BUSY: 5;
+ export const SQLITE_LOCKED: 6;
+ export const SQLITE_NOMEM: 7;
+ export const SQLITE_READONLY: 8;
+ export const SQLITE_INTERRUPT: 9;
+ export const SQLITE_IOERR: 10;
+ export const SQLITE_CORRUPT: 11;
+ export const SQLITE_NOTFOUND: 12;
+ export const SQLITE_FULL: 13;
+ export const SQLITE_CANTOPEN: 14;
+ export const SQLITE_PROTOCOL: 15;
+ export const SQLITE_EMPTY: 16;
+ export const SQLITE_SCHEMA: 17;
+ export const SQLITE_TOOBIG: 18;
+ export const SQLITE_CONSTRAINT: 19;
+ export const SQLITE_MISMATCH: 20;
+ export const SQLITE_MISUSE: 21;
+ export const SQLITE_NOLFS: 22;
+ export const SQLITE_AUTH: 23;
+ export const SQLITE_FORMAT: 24;
+ export const SQLITE_RANGE: 25;
+ export const SQLITE_NOTADB: 26;
+ export const SQLITE_NOTICE: 27;
+ export const SQLITE_WARNING: 28;
+ export const SQLITE_ROW: 100;
+ export const SQLITE_DONE: 101;
+ export const SQLITE_IOERR_ACCESS: 3338;
+ export const SQLITE_IOERR_CHECKRESERVEDLOCK: 3594;
+ export const SQLITE_IOERR_CLOSE: 4106;
+ export const SQLITE_IOERR_DATA: 8202;
+ export const SQLITE_IOERR_DELETE: 2570;
+ export const SQLITE_IOERR_DELETE_NOENT: 5898;
+ export const SQLITE_IOERR_DIR_FSYNC: 1290;
+ export const SQLITE_IOERR_FSTAT: 1802;
+ export const SQLITE_IOERR_FSYNC: 1034;
+ export const SQLITE_IOERR_GETTEMPPATH: 6410;
+ export const SQLITE_IOERR_LOCK: 3850;
+ export const SQLITE_IOERR_NOMEM: 3082;
+ export const SQLITE_IOERR_READ: 266;
+ export const SQLITE_IOERR_RDLOCK: 2314;
+ export const SQLITE_IOERR_SEEK: 5642;
+ export const SQLITE_IOERR_SHORT_READ: 522;
+ export const SQLITE_IOERR_TRUNCATE: 1546;
+ export const SQLITE_IOERR_UNLOCK: 2058;
+ export const SQLITE_IOERR_VNODE: 6922;
+ export const SQLITE_IOERR_WRITE: 778;
+ export const SQLITE_IOERR_BEGIN_ATOMIC: 7434;
+ export const SQLITE_IOERR_COMMIT_ATOMIC: 7690;
+ export const SQLITE_IOERR_ROLLBACK_ATOMIC: 7946;
+ export const SQLITE_CONSTRAINT_CHECK: 275;
+ export const SQLITE_CONSTRAINT_COMMITHOOK: 531;
+ export const SQLITE_CONSTRAINT_FOREIGNKEY: 787;
+ export const SQLITE_CONSTRAINT_FUNCTION: 1043;
+ export const SQLITE_CONSTRAINT_NOTNULL: 1299;
+ export const SQLITE_CONSTRAINT_PINNED: 2835;
+ export const SQLITE_CONSTRAINT_PRIMARYKEY: 1555;
+ export const SQLITE_CONSTRAINT_ROWID: 2579;
+ export const SQLITE_CONSTRAINT_TRIGGER: 1811;
+ export const SQLITE_CONSTRAINT_UNIQUE: 2067;
+ export const SQLITE_CONSTRAINT_VTAB: 2323;
+ export const SQLITE_OPEN_READONLY: 1;
+ export const SQLITE_OPEN_READWRITE: 2;
+ export const SQLITE_OPEN_CREATE: 4;
+ export const SQLITE_OPEN_DELETEONCLOSE: 8;
+ export const SQLITE_OPEN_EXCLUSIVE: 16;
+ export const SQLITE_OPEN_AUTOPROXY: 32;
+ export const SQLITE_OPEN_URI: 64;
+ export const SQLITE_OPEN_MEMORY: 128;
+ export const SQLITE_OPEN_MAIN_DB: 256;
+ export const SQLITE_OPEN_TEMP_DB: 512;
+ export const SQLITE_OPEN_TRANSIENT_DB: 1024;
+ export const SQLITE_OPEN_MAIN_JOURNAL: 2048;
+ export const SQLITE_OPEN_TEMP_JOURNAL: 4096;
+ export const SQLITE_OPEN_SUBJOURNAL: 8192;
+ export const SQLITE_OPEN_SUPER_JOURNAL: 16384;
+ export const SQLITE_OPEN_NOMUTEX: 32768;
+ export const SQLITE_OPEN_FULLMUTEX: 65536;
+ export const SQLITE_OPEN_SHAREDCACHE: 131072;
+ export const SQLITE_OPEN_PRIVATECACHE: 262144;
+ export const SQLITE_OPEN_WAL: 524288;
+ export const SQLITE_OPEN_NOFOLLOW: 16777216;
+ export const SQLITE_LOCK_NONE: 0;
+ export const SQLITE_LOCK_SHARED: 1;
+ export const SQLITE_LOCK_RESERVED: 2;
+ export const SQLITE_LOCK_PENDING: 3;
+ export const SQLITE_LOCK_EXCLUSIVE: 4;
+ export const SQLITE_IOCAP_ATOMIC: 1;
+ export const SQLITE_IOCAP_ATOMIC512: 2;
+ export const SQLITE_IOCAP_ATOMIC1K: 4;
+ export const SQLITE_IOCAP_ATOMIC2K: 8;
+ export const SQLITE_IOCAP_ATOMIC4K: 16;
+ export const SQLITE_IOCAP_ATOMIC8K: 32;
+ export const SQLITE_IOCAP_ATOMIC16K: 64;
+ export const SQLITE_IOCAP_ATOMIC32K: 128;
+ export const SQLITE_IOCAP_ATOMIC64K: 256;
+ export const SQLITE_IOCAP_SAFE_APPEND: 512;
+ export const SQLITE_IOCAP_SEQUENTIAL: 1024;
+ export const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN: 2048;
+ export const SQLITE_IOCAP_POWERSAFE_OVERWRITE: 4096;
+ export const SQLITE_IOCAP_IMMUTABLE: 8192;
+ export const SQLITE_IOCAP_BATCH_ATOMIC: 16384;
+ export const SQLITE_ACCESS_EXISTS: 0;
+ export const SQLITE_ACCESS_READWRITE: 1;
+ export const SQLITE_ACCESS_READ: 2;
+ export const SQLITE_FCNTL_LOCKSTATE: 1;
+ export const SQLITE_FCNTL_GET_LOCKPROXYFILE: 2;
+ export const SQLITE_FCNTL_SET_LOCKPROXYFILE: 3;
+ export const SQLITE_FCNTL_LAST_ERRNO: 4;
+ export const SQLITE_FCNTL_SIZE_HINT: 5;
+ export const SQLITE_FCNTL_CHUNK_SIZE: 6;
+ export const SQLITE_FCNTL_FILE_POINTER: 7;
+ export const SQLITE_FCNTL_SYNC_OMITTED: 8;
+ export const SQLITE_FCNTL_WIN32_AV_RETRY: 9;
+ export const SQLITE_FCNTL_PERSIST_WAL: 10;
+ export const SQLITE_FCNTL_OVERWRITE: 11;
+ export const SQLITE_FCNTL_VFSNAME: 12;
+ export const SQLITE_FCNTL_POWERSAFE_OVERWRITE: 13;
+ export const SQLITE_FCNTL_PRAGMA: 14;
+ export const SQLITE_FCNTL_BUSYHANDLER: 15;
+ export const SQLITE_FCNTL_TEMPFILENAME: 16;
+ export const SQLITE_FCNTL_MMAP_SIZE: 18;
+ export const SQLITE_FCNTL_TRACE: 19;
+ export const SQLITE_FCNTL_HAS_MOVED: 20;
+ export const SQLITE_FCNTL_SYNC: 21;
+ export const SQLITE_FCNTL_COMMIT_PHASETWO: 22;
+ export const SQLITE_FCNTL_WIN32_SET_HANDLE: 23;
+ export const SQLITE_FCNTL_WAL_BLOCK: 24;
+ export const SQLITE_FCNTL_ZIPVFS: 25;
+ export const SQLITE_FCNTL_RBU: 26;
+ export const SQLITE_FCNTL_VFS_POINTER: 27;
+ export const SQLITE_FCNTL_JOURNAL_POINTER: 28;
+ export const SQLITE_FCNTL_WIN32_GET_HANDLE: 29;
+ export const SQLITE_FCNTL_PDB: 30;
+ export const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: 31;
+ export const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: 32;
+ export const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: 33;
+ export const SQLITE_FCNTL_LOCK_TIMEOUT: 34;
+ export const SQLITE_FCNTL_DATA_VERSION: 35;
+ export const SQLITE_FCNTL_SIZE_LIMIT: 36;
+ export const SQLITE_FCNTL_CKPT_DONE: 37;
+ export const SQLITE_FCNTL_RESERVE_BYTES: 38;
+ export const SQLITE_FCNTL_CKPT_START: 39;
+ export const SQLITE_INTEGER: 1;
+ export const SQLITE_FLOAT: 2;
+ export const SQLITE_TEXT: 3;
+ export const SQLITE_BLOB: 4;
+ export const SQLITE_NULL: 5;
+ export const SQLITE_STATIC: 0;
+ export const SQLITE_TRANSIENT: -1;
+ export const SQLITE_UTF8: 1;
+ export const SQLITE_UTF16LE: 2;
+ export const SQLITE_UTF16BE: 3;
+ export const SQLITE_UTF16: 4;
+ export const SQLITE_INDEX_CONSTRAINT_EQ: 2;
+ export const SQLITE_INDEX_CONSTRAINT_GT: 4;
+ export const SQLITE_INDEX_CONSTRAINT_LE: 8;
+ export const SQLITE_INDEX_CONSTRAINT_LT: 16;
+ export const SQLITE_INDEX_CONSTRAINT_GE: 32;
+ export const SQLITE_INDEX_CONSTRAINT_MATCH: 64;
+ export const SQLITE_INDEX_CONSTRAINT_LIKE: 65;
+ export const SQLITE_INDEX_CONSTRAINT_GLOB: 66;
+ export const SQLITE_INDEX_CONSTRAINT_REGEXP: 67;
+ export const SQLITE_INDEX_CONSTRAINT_NE: 68;
+ export const SQLITE_INDEX_CONSTRAINT_ISNOT: 69;
+ export const SQLITE_INDEX_CONSTRAINT_ISNOTNULL: 70;
+ export const SQLITE_INDEX_CONSTRAINT_ISNULL: 71;
+ export const SQLITE_INDEX_CONSTRAINT_IS: 72;
+ export const SQLITE_INDEX_CONSTRAINT_FUNCTION: 150;
+ export const SQLITE_INDEX_SCAN_UNIQUE: 1;
+ export const SQLITE_DETERMINISTIC: 0x000000800;
+ export const SQLITE_DIRECTONLY: 0x000080000;
+ export const SQLITE_SUBTYPE: 0x000100000;
+ export const SQLITE_INNOCUOUS: 0x000200000;
+ export const SQLITE_SYNC_NORMAL: 0x00002;
+ export const SQLITE_SYNC_FULL: 0x00003;
+ export const SQLITE_SYNC_DATAONLY: 0x00010;
+ export const SQLITE_CREATE_INDEX: 1;
+ export const SQLITE_CREATE_TABLE: 2;
+ export const SQLITE_CREATE_TEMP_INDEX: 3;
+ export const SQLITE_CREATE_TEMP_TABLE: 4;
+ export const SQLITE_CREATE_TEMP_TRIGGER: 5;
+ export const SQLITE_CREATE_TEMP_VIEW: 6;
+ export const SQLITE_CREATE_TRIGGER: 7;
+ export const SQLITE_CREATE_VIEW: 8;
+ export const SQLITE_DELETE: 9;
+ export const SQLITE_DROP_INDEX: 10;
+ export const SQLITE_DROP_TABLE: 11;
+ export const SQLITE_DROP_TEMP_INDEX: 12;
+ export const SQLITE_DROP_TEMP_TABLE: 13;
+ export const SQLITE_DROP_TEMP_TRIGGER: 14;
+ export const SQLITE_DROP_TEMP_VIEW: 15;
+ export const SQLITE_DROP_TRIGGER: 16;
+ export const SQLITE_DROP_VIEW: 17;
+ export const SQLITE_INSERT: 18;
+ export const SQLITE_PRAGMA: 19;
+ export const SQLITE_READ: 20;
+ export const SQLITE_SELECT: 21;
+ export const SQLITE_TRANSACTION: 22;
+ export const SQLITE_UPDATE: 23;
+ export const SQLITE_ATTACH: 24;
+ export const SQLITE_DETACH: 25;
+ export const SQLITE_ALTER_TABLE: 26;
+ export const SQLITE_REINDEX: 27;
+ export const SQLITE_ANALYZE: 28;
+ export const SQLITE_CREATE_VTABLE: 29;
+ export const SQLITE_DROP_VTABLE: 30;
+ export const SQLITE_FUNCTION: 31;
+ export const SQLITE_SAVEPOINT: 32;
+ export const SQLITE_COPY: 0;
+ export const SQLITE_RECURSIVE: 33;
+ export const SQLITE_DENY: 1;
+ export const SQLITE_IGNORE: 2;
+ export const SQLITE_LIMIT_LENGTH: 0;
+ export const SQLITE_LIMIT_SQL_LENGTH: 1;
+ export const SQLITE_LIMIT_COLUMN: 2;
+ export const SQLITE_LIMIT_EXPR_DEPTH: 3;
+ export const SQLITE_LIMIT_COMPOUND_SELECT: 4;
+ export const SQLITE_LIMIT_VDBE_OP: 5;
+ export const SQLITE_LIMIT_FUNCTION_ARG: 6;
+ export const SQLITE_LIMIT_ATTACHED: 7;
+ export const SQLITE_LIMIT_LIKE_PATTERN_LENGTH: 8;
+ export const SQLITE_LIMIT_VARIABLE_NUMBER: 9;
+ export const SQLITE_LIMIT_TRIGGER_DEPTH: 10;
+ export const SQLITE_LIMIT_WORKER_THREADS: 11;
+ export const SQLITE_PREPARE_PERSISTENT: 0x01;
+ export const SQLITE_PREPARE_NORMALIZED: 0x02;
+ export const SQLITE_PREPARE_NO_VTAB: 0x04;
+}
+
+declare module 'wa-sqlite' {
+ export * from 'wa-sqlite/src/sqlite-constants.js';
+
+ /**
+ * @ignore
+ * Builds a Javascript API from the Emscripten module. This API is still
+ * low-level and closely corresponds to the C API exported by the module,
+ * but differs in some specifics like throwing exceptions on errors.
+ * @param {*} Module SQLite module
+ * @returns {SQLiteAPI}
+ */
+ export function Factory(Module: any): SQLiteAPI;
+
+ export class SQLiteError extends Error {
+ constructor(message: any, code: any);
+ code: any;
+ }
+}
+
+/** @ignore */
+declare module 'wa-sqlite/dist/wa-sqlite.mjs' {
+ function ModuleFactory(config?: object): Promise;
+ export = ModuleFactory;
+}
+
+/** @ignore */
+declare module 'wa-sqlite/dist/wa-sqlite-async.mjs' {
+ function ModuleFactory(config?: object): Promise;
+ export = ModuleFactory;
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/VFS.js' {
+ export * from 'wa-sqlite/src/sqlite-constants.js';
+
+ export class Base {
+ mxPathName: number;
+ /**
+ * @param {number} fileId
+ * @returns {number|Promise}
+ */
+ xClose(fileId: number): number;
+ /**
+ * @param {number} fileId
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number}
+ */
+ xRead(fileId: number, pData: {
+ size: number;
+ value: Uint8Array;
+ }, iOffset: number): number;
+ /**
+ * @param {number} fileId
+ * @param {Uint8Array} pData
+ * @param {number} iOffset
+ * @returns {number}
+ */
+ xWrite(fileId: number, pData: {
+ size: number;
+ value: Uint8Array;
+ }, iOffset: number): number;
+ /**
+ * @param {number} fileId
+ * @param {number} iSize
+ * @returns {number}
+ */
+ xTruncate(fileId: number, iSize: number): number;
+ /**
+ * @param {number} fileId
+ * @param {*} flags
+ * @returns {number}
+ */
+ xSync(fileId: number, flags: any): number;
+ /**
+ * @param {number} fileId
+ * @param {DataView} pSize64
+ * @returns {number|Promise}
+ */
+ xFileSize(fileId: number, pSize64: DataView): number;
+ /**
+ * @param {number} fileId
+ * @param {number} flags
+ * @returns {number}
+ */
+ xLock(fileId: number, flags: number): number;
+ /**
+ * @param {number} fileId
+ * @param {number} flags
+ * @returns {number}
+ */
+ xUnlock(fileId: number, flags: number): number;
+ /**
+ * @param {number} fileId
+ * @param {DataView} pResOut
+ * @returns {number}
+ */
+ xCheckReservedLock(fileId: number, pResOut: DataView): number;
+ /**
+ * @param {number} fileId
+ * @param {number} flags
+ * @param {DataView} pArg
+ * @returns {number}
+ */
+ xFileControl(fileId: number, flags: number, pArg: DataView): number;
+ /**
+ * @param {number} fileId
+ * @returns {number}
+ */
+ xSectorSize(fileId: number): number;
+ /**
+ * @param {number} fileId
+ * @returns {number}
+ */
+ xDeviceCharacteristics(fileId: number): number;
+ /**
+ * @param {string?} name
+ * @param {number} fileId
+ * @param {number} flags
+ * @param {DataView} pOutFlags
+ * @returns {number}
+ */
+ xOpen(name: string | null, fileId: number, flags: number, pOutFlags: DataView): number;
+ /**
+ *
+ * @param {string} name
+ * @param {number} syncDir
+ * @returns {number}
+ */
+ xDelete(name: string, syncDir: number): number;
+ /**
+ * @param {string} name
+ * @param {number} flags
+ * @param {DataView} pResOut
+ * @returns {number}
+ */
+ xAccess(name: string, flags: number, pResOut: DataView): number;
+ /**
+ * Handle asynchronous operation. This implementation will be overriden on
+ * registration by an Asyncify build.
+ * @param {function(): Promise} f
+ * @returns {number}
+ */
+ handleAsync(f: () => Promise): number;
+ }
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/examples/IndexedDbVFS.js' {
+ import * as VFS from "wa-sqlite/src/VFS.js";
+ export class IndexedDbVFS extends VFS.Base {
+ /**
+ * @param {string} idbName Name of IndexedDB database.
+ */
+ constructor(idbName?: string);
+ name: string;
+ mapIdToFile: Map;
+ cacheSize: number;
+ db: any;
+ close(): Promise;
+ /**
+ * Delete a file from IndexedDB.
+ * @param {string} name
+ */
+ deleteFile(name: string): Promise;
+ /**
+ * Forcibly clear an orphaned file lock.
+ * @param {string} name
+ */
+ forceClearLock(name: string): Promise;
+ _getStore(mode?: string): any;
+ /**
+ * Returns the key for file metadata.
+ * @param {string} name
+ * @returns
+ */
+ _metaKey(name: string): string;
+ /**
+ * Returns the key for file block data.
+ * @param {string} name
+ * @param {number} index
+ * @returns
+ */
+ _blockKey(name: string, index: number): string;
+ _getBlock(store: any, file: any, index: any): Promise;
+ _putBlock(store: any, file: any, index: any, blockData: any): void;
+ _purgeCache(store: any, file: any, size?: number): void;
+ _flushCache(store: any, file: any): Promise;
+ _sync(file: any): Promise;
+ /**
+ * Helper function that deletes all keys greater or equal to `key`
+ * provided they start with `prefix`.
+ * @param {string} key
+ * @param {string} [prefix]
+ * @returns
+ */
+ _delete(key: string, prefix?: string): Promise;
+ }
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/examples/MemoryVFS.js' {
+ import * as VFS from "wa-sqlite/src/VFS.js";
+ /** @ignore */
+ export class MemoryVFS extends VFS.Base {
+ name: string;
+ mapNameToFile: Map;
+ mapIdToFile: Map;
+ }
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/examples/MemoryAsyncVFS.js' {
+ import { MemoryVFS } from "wa-sqlite/src/examples/MemoryVFS.js";
+ export class MemoryAsyncVFS extends MemoryVFS {
+ }
+}
+
+/** @ignore */
+declare module 'wa-sqlite/src/examples/tag.js' {
+ /**
+ * @ignore
+ * Template tag builder. This function creates a tag with an API and
+ * database from the same module, then the tag can be used like this:
+ * ```
+ * const sql = tag(sqlite3, db);
+ * const results = await sql`
+ * SELECT 1 + 1;
+ * SELECT 6 * 7;
+ * `;
+ * ```
+ * The returned Promise value contains an array of results for each
+ * SQL statement that produces output. Each result is an object with
+ * properties `columns` (array of names) and `rows` (array of array
+ * of values).
+ * @param {SQLiteAPI} sqlite3
+ * @param {number} db
+ * @returns {function(TemplateStringsArray, ...any): Promise