Fix base path and restore wasm engine

This commit is contained in:
codecolorist
2026-04-14 13:56:08 +00:00
parent 32c2b01062
commit 7690ba766e
4 changed files with 107 additions and 71 deletions
+4
View File
@@ -1,7 +1,11 @@
import type { NextConfig } from "next";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const nextConfig: NextConfig = {
output: "export",
basePath,
assetPrefix: basePath || undefined,
};
export default nextConfig;
+8 -16
View File
@@ -1,31 +1,23 @@
import type { Engine } from "./types";
import { WASMEngine } from "./wasm";
import { checkWASMSupport as checkSQLiteWASMSupport } from "./wasm";
import { KVEngine } from "./kv";
let wasmSupported: boolean | null = null;
const wasmDisabled = process.env.NEXT_PUBLIC_USE_WASM === "0";
async function checkWASMSupport(): Promise<boolean> {
if (wasmSupported !== null) return wasmSupported;
try {
if (typeof WebAssembly === "undefined") {
wasmSupported = false;
return false;
}
await WebAssembly.instantiate(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
await import("@sqlite.org/sqlite-wasm");
wasmSupported = true;
return true;
} catch {
wasmSupported = false;
return false;
}
wasmSupported = await checkSQLiteWASMSupport();
return wasmSupported;
}
export async function createEngine(group: string): Promise<Engine> {
if (wasmDisabled) {
return new KVEngine(group);
}
const supported = await checkWASMSupport();
if (supported) {
return new WASMEngine();
+3 -3
View File
@@ -1,6 +1,6 @@
import type { Engine } from "./types";
import type { OS } from "@/lib/types";
import { addBasePath, dataBaseURL } from "@/lib/env";
import { dataBaseURL } from "@/lib/env";
import { fetchText, fetchLines } from "@/lib/client";
interface KVRecord {
@@ -55,14 +55,14 @@ export class KVEngine implements Engine {
}
async listOS(): Promise<OS[]> {
const list = await fetchText(addBasePath(`${this.#baseURL}/list.json`));
const list = await fetchText(`${this.#baseURL}/list.json`);
return JSON.parse(list);
}
async getPaths(build: string): Promise<string[]> {
const os = await this.findOS(build);
const tag = `${os.version}_${build}`;
return fetchLines(addBasePath(`${this.#baseURL}/${tag}/paths.txt`));
return fetchLines(`${this.#baseURL}/${tag}/paths.txt`);
}
async getBinaryXML(build: string, path: string): Promise<string> {
+92 -52
View File
@@ -2,40 +2,80 @@ import type { Engine } from "./types";
import type { OS } from "@/lib/types";
import { dataBaseURL } from "@/lib/env";
type SQLite3Database = {
exec: (options: {
sql: string;
bind?: unknown[];
rowMode: "array";
returnValue: "resultRows";
}) => unknown[][];
close: () => void;
};
type SQLite3API = {
Database: new (data: ArrayLike<number | bigint>) => {
exec: (
sql: string,
bind?: unknown[]
) => {
columns: string[];
rows: unknown[][];
}[];
close: () => void;
oo1: {
DB: new (filename?: string, flags?: string, vfs?: string) => SQLite3Database;
};
capi: {
sqlite3_js_posix_create_file: (
filename: string,
data: Uint8Array | ArrayBuffer,
dataLen?: number
) => void;
};
};
let sqlite3Module: SQLite3API | null = null;
let dbInstance: InstanceType<SQLite3API["Database"]> | null = null;
let dbInstance: SQLite3Database | null = null;
let dbReady = false;
async function loadSQLite(): Promise<SQLite3API> {
if (sqlite3Module) return sqlite3Module;
const sqliteModule = await import("@sqlite.org/sqlite-wasm");
sqlite3Module = (sqliteModule.default || sqliteModule) as unknown as SQLite3API;
const initSQLite = sqliteModule.default;
sqlite3Module = (await initSQLite()) as unknown as SQLite3API;
return sqlite3Module;
}
async function getDB(): Promise<InstanceType<SQLite3API["Database"]>> {
export async function checkWASMSupport(): Promise<boolean> {
try {
if (typeof WebAssembly === "undefined") {
return false;
}
await WebAssembly.instantiate(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
await loadSQLite();
return true;
} catch {
return false;
}
}
async function getDB(): Promise<SQLite3Database> {
if (dbReady && dbInstance) return dbInstance;
const sqlite3 = await loadSQLite();
const response = await fetch(`${dataBaseURL()}/ent.db`);
if (!response.ok) {
throw new Error(
`Failed to fetch SQLite database: ${response.status} ${response.statusText}`
);
}
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
const filename = "/ent.db";
dbInstance = new sqlite3.Database(new Uint8Array(buffer));
try {
sqlite3.capi.sqlite3_js_posix_create_file(filename, bytes, bytes.byteLength);
} catch {
// Ignore duplicate file creation in hot-reload or repeated initialization cases.
}
dbInstance = new sqlite3.oo1.DB(filename, "r");
dbReady = true;
return dbInstance;
}
@@ -43,64 +83,64 @@ async function getDB(): Promise<InstanceType<SQLite3API["Database"]>> {
export class WASMEngine implements Engine {
async listOS(): Promise<OS[]> {
const db = await getDB();
const results = db.exec(
"SELECT name, version, build, devices FROM os ORDER BY version DESC"
);
if (!results.length) return [];
const rows = db.exec({
sql: "SELECT name, version, build, devices FROM os ORDER BY version DESC",
rowMode: "array",
returnValue: "resultRows",
});
const cols = results[0].columns;
const nameIdx = cols.indexOf("name");
const versionIdx = cols.indexOf("version");
const buildIdx = cols.indexOf("build");
const devicesIdx = cols.indexOf("devices");
return results[0].rows.map((row) => ({
name: row[nameIdx] as string,
version: row[versionIdx] as string,
build: row[buildIdx] as string,
devices: JSON.parse(row[devicesIdx] as string),
return rows.map((row) => ({
name: row[0] as string,
version: row[1] as string,
build: row[2] as string,
devices: JSON.parse(row[3] as string),
}));
}
async getPaths(build: string): Promise<string[]> {
const db = await getDB();
const results = db.exec(
`SELECT path FROM bin JOIN os ON bin.osid=os.id WHERE os.build=?`,
[build]
);
if (!results.length) return [];
return results[0].rows.map((row) => row[0] as string);
const rows = db.exec({
sql: `SELECT path FROM bin JOIN os ON bin.osid=os.id WHERE os.build=?`,
bind: [build],
rowMode: "array",
returnValue: "resultRows",
});
return rows.map((row) => row[0] as string);
}
async getBinaryXML(build: string, path: string): Promise<string> {
const db = await getDB();
const results = db.exec(
`SELECT xml FROM bin JOIN os ON bin.osid=os.id WHERE os.build=? AND bin.path=?`,
[build, path]
);
if (!results.length || !results[0].rows.length) {
const rows = db.exec({
sql: `SELECT xml FROM bin JOIN os ON bin.osid=os.id WHERE os.build=? AND bin.path=?`,
bind: [build, path],
rowMode: "array",
returnValue: "resultRows",
});
if (!rows.length) {
throw new Error(`Binary not found: ${path}`);
}
return results[0].rows[0][0] as string;
return rows[0][0] as string;
}
async getKeys(build: string): Promise<string[]> {
const db = await getDB();
const results = db.exec(
`SELECT DISTINCT key FROM pair JOIN bin ON pair.binid=bin.id JOIN os ON bin.osid=os.id WHERE os.build=?`,
[build]
);
if (!results.length) return [];
return results[0].rows.map((row) => row[0] as string);
const rows = db.exec({
sql: `SELECT DISTINCT key FROM pair JOIN bin ON pair.binid=bin.id JOIN os ON bin.osid=os.id WHERE os.build=?`,
bind: [build],
rowMode: "array",
returnValue: "resultRows",
});
return rows.map((row) => row[0] as string);
}
async getPathsForKey(build: string, key: string): Promise<string[]> {
const db = await getDB();
const results = db.exec(
`SELECT path FROM bin JOIN pair ON bin.id=pair.binid JOIN os ON bin.osid=os.id WHERE os.build=? AND pair.key=?`,
[build, key]
);
if (!results.length) return [];
return results[0].rows.map((row) => row[0] as string);
const rows = db.exec({
sql: `SELECT path FROM bin JOIN pair ON bin.id=pair.binid JOIN os ON bin.osid=os.id WHERE os.build=? AND pair.key=?`,
bind: [build, key],
rowMode: "array",
returnValue: "resultRows",
});
return rows.map((row) => row[0] as string);
}
}