mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-26 11:24:32 +01:00
hack imports of wasm due to the issues in Vite and Next.js build systems
This commit is contained in:
22
bindings/javascript/packages/browser/index-default.ts
Normal file
22
bindings/javascript/packages/browser/index-default.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { setupMainThread } from "@tursodatabase/database-browser-common";
|
||||
|
||||
const __wasmUrl = new URL('./turso.wasm32-wasi.wasm', import.meta.url).href;
|
||||
const __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())
|
||||
|
||||
export let MainWorker = null;
|
||||
const napiModule = await setupMainThread(__wasmFile, () => {
|
||||
const worker = new Worker(new URL('./worker.js', import.meta.url), {
|
||||
name: 'turso-database',
|
||||
type: 'module',
|
||||
})
|
||||
MainWorker = worker;
|
||||
return worker
|
||||
});
|
||||
|
||||
export default napiModule.exports
|
||||
export const Database = napiModule.exports.Database
|
||||
export const Opfs = napiModule.exports.Opfs
|
||||
export const OpfsFile = napiModule.exports.OpfsFile
|
||||
export const Statement = napiModule.exports.Statement
|
||||
export const connect = napiModule.exports.connect
|
||||
export const initThreadPool = napiModule.exports.initThreadPool
|
||||
25
bindings/javascript/packages/browser/index-turbopack-hack.ts
Normal file
25
bindings/javascript/packages/browser/index-turbopack-hack.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { setupMainThread } from "@tursodatabase/database-browser-common";
|
||||
import { tursoWasm } from "./wasm-inline.js";
|
||||
|
||||
// Next (turbopack) has issues with loading wasm module: https://github.com/vercel/next.js/issues/82520
|
||||
// So, we inline wasm binary in the source code in order to avoid issues with loading it from the file
|
||||
const __wasmFile = await tursoWasm();
|
||||
|
||||
export let MainWorker = null;
|
||||
|
||||
const napiModule = await setupMainThread(__wasmFile, () => {
|
||||
const worker = new Worker(new URL('./worker.js', import.meta.url), {
|
||||
name: 'turso-database',
|
||||
type: 'module',
|
||||
})
|
||||
MainWorker = worker;
|
||||
return worker
|
||||
});
|
||||
|
||||
export default napiModule.exports
|
||||
export const Database = napiModule.exports.Database
|
||||
export const Opfs = napiModule.exports.Opfs
|
||||
export const OpfsFile = napiModule.exports.OpfsFile
|
||||
export const Statement = napiModule.exports.Statement
|
||||
export const connect = napiModule.exports.connect
|
||||
export const initThreadPool = napiModule.exports.initThreadPool
|
||||
41
bindings/javascript/packages/browser/index-vite-dev-hack.ts
Normal file
41
bindings/javascript/packages/browser/index-vite-dev-hack.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { isWebWorker, setupMainThread, setupWebWorker } from "@tursodatabase/database-browser-common";
|
||||
import { tursoWasm } from "./wasm-inline.js";
|
||||
|
||||
let napiModule = {
|
||||
exports: {
|
||||
Database: {} as any,
|
||||
Opfs: {} as any,
|
||||
OpfsFile: {} as any,
|
||||
Statement: {} as any,
|
||||
connect: {} as any,
|
||||
initThreadPool: {} as any,
|
||||
}
|
||||
};
|
||||
|
||||
export let MainWorker = null;
|
||||
if (isWebWorker()) {
|
||||
setupWebWorker();
|
||||
} else {
|
||||
// Vite has issues with loading wasm modules and worker in dev server: https://github.com/vitejs/vite/issues/8427
|
||||
// So, the mitigation for dev server only is:
|
||||
// 1. inline wasm binary in the source code in order to avoid issues with loading it from the file
|
||||
// 2. use same file as worker entry point
|
||||
const __wasmFile = await tursoWasm();
|
||||
|
||||
napiModule = await setupMainThread(__wasmFile, () => {
|
||||
const worker = new Worker(import.meta.url, {
|
||||
name: 'turso-database',
|
||||
type: 'module',
|
||||
})
|
||||
MainWorker = worker;
|
||||
return worker
|
||||
});
|
||||
}
|
||||
|
||||
export default napiModule.exports
|
||||
export const Database = napiModule.exports.Database
|
||||
export const Opfs = napiModule.exports.Opfs
|
||||
export const OpfsFile = napiModule.exports.OpfsFile
|
||||
export const Statement = napiModule.exports.Statement
|
||||
export const connect = napiModule.exports.connect
|
||||
export const initThreadPool = napiModule.exports.initThreadPool
|
||||
@@ -1,68 +0,0 @@
|
||||
import {
|
||||
createOnMessage as __wasmCreateOnMessageForFsProxy,
|
||||
getDefaultContext as __emnapiGetDefaultContext,
|
||||
instantiateNapiModule as __emnapiInstantiateNapiModule,
|
||||
WASI as __WASI,
|
||||
} from '@napi-rs/wasm-runtime'
|
||||
|
||||
import { MainDummyImports } from "@tursodatabase/database-browser-common";
|
||||
|
||||
|
||||
const __wasi = new __WASI({
|
||||
version: 'preview1',
|
||||
})
|
||||
|
||||
const __wasmUrl = new URL('./turso.wasm32-wasi.wasm', import.meta.url).href
|
||||
const __emnapiContext = __emnapiGetDefaultContext()
|
||||
|
||||
|
||||
const __sharedMemory = new WebAssembly.Memory({
|
||||
initial: 4000,
|
||||
maximum: 65536,
|
||||
shared: true,
|
||||
})
|
||||
|
||||
const __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())
|
||||
|
||||
export let MainWorker = null;
|
||||
|
||||
const {
|
||||
instance: __napiInstance,
|
||||
module: __wasiModule,
|
||||
napiModule: __napiModule,
|
||||
} = await __emnapiInstantiateNapiModule(__wasmFile, {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: 1,
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./worker.mjs', import.meta.url), {
|
||||
type: 'module',
|
||||
})
|
||||
MainWorker = worker;
|
||||
return worker
|
||||
},
|
||||
overwriteImports(importObject) {
|
||||
importObject.env = {
|
||||
...importObject.env,
|
||||
...importObject.napi,
|
||||
...importObject.emnapi,
|
||||
...MainDummyImports,
|
||||
memory: __sharedMemory,
|
||||
}
|
||||
return importObject
|
||||
},
|
||||
beforeInit({ instance }) {
|
||||
for (const name of Object.keys(instance.exports)) {
|
||||
if (name.startsWith('__napi_register__')) {
|
||||
instance.exports[name]()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
export default __napiModule.exports
|
||||
export const Database = __napiModule.exports.Database
|
||||
export const Opfs = __napiModule.exports.Opfs
|
||||
export const OpfsFile = __napiModule.exports.OpfsFile
|
||||
export const Statement = __napiModule.exports.Statement
|
||||
export const connect = __napiModule.exports.connect
|
||||
export const initThreadPool = __napiModule.exports.initThreadPool
|
||||
@@ -10,12 +10,21 @@
|
||||
"main": "dist/promise.js",
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"files": [
|
||||
"index.js",
|
||||
"worker.mjs",
|
||||
"turso.wasm32-wasi.wasm",
|
||||
"dist/**",
|
||||
"README.md"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/promise-default.js"
|
||||
},
|
||||
"./vite": {
|
||||
"development": "./dist/promise-vite-dev-hack.js",
|
||||
"default": "./dist/promise-default.js"
|
||||
},
|
||||
"./turbopack": {
|
||||
"default": "./dist/promise-turbopack-hack.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.1.5",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
@@ -25,7 +34,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"napi-build": "napi build --features browser --release --platform --target wasm32-wasip1-threads --no-js --manifest-path ../../Cargo.toml --output-dir . && rm index.d.ts turso.wasi* wasi* browser.js",
|
||||
"tsc-build": "npm exec tsc",
|
||||
"tsc-build": "npm exec tsc && cp turso.wasm32-wasi.wasm ./dist/turso.wasm32-wasi.wasm && WASM_FILE=turso.wasm32-wasi.wasm JS_FILE=./dist/wasm-inline.js node ../../scripts/inline-wasm-base64.js",
|
||||
"build": "npm run napi-build && npm run tsc-build",
|
||||
"test": "CI=1 vitest --browser=chromium --run && CI=1 vitest --browser=firefox --run"
|
||||
},
|
||||
@@ -35,11 +44,7 @@
|
||||
"wasm32-wasip1-threads"
|
||||
]
|
||||
},
|
||||
"imports": {
|
||||
"#index": "./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@napi-rs/wasm-runtime": "^1.0.3",
|
||||
"@tursodatabase/database-browser-common": "^0.2.0-pre.1",
|
||||
"@tursodatabase/database-common": "^0.2.0-pre.1"
|
||||
}
|
||||
|
||||
22
bindings/javascript/packages/browser/promise-default.ts
Normal file
22
bindings/javascript/packages/browser/promise-default.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { DatabaseOpts, SqliteError, } from "@tursodatabase/database-common"
|
||||
import { connect as promiseConnect, Database } from "./promise.js";
|
||||
import { connect as nativeConnect, initThreadPool, MainWorker } from "./index-default.js";
|
||||
|
||||
/**
|
||||
* Creates a new database connection asynchronously.
|
||||
*
|
||||
* @param {string} path - Path to the database file.
|
||||
* @param {Object} opts - Options for database behavior.
|
||||
* @returns {Promise<Database>} - A promise that resolves to a Database instance.
|
||||
*/
|
||||
async function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {
|
||||
return await promiseConnect(path, opts, nativeConnect, async () => {
|
||||
await initThreadPool();
|
||||
if (MainWorker == null) {
|
||||
throw new Error("panic: MainWorker is not initialized");
|
||||
}
|
||||
return MainWorker;
|
||||
});
|
||||
}
|
||||
|
||||
export { connect, Database, SqliteError }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { DatabaseOpts, SqliteError, } from "@tursodatabase/database-common"
|
||||
import { connect as promiseConnect, Database } from "./promise.js";
|
||||
import { connect as nativeConnect, initThreadPool, MainWorker } from "./index-turbopack-hack.js";
|
||||
|
||||
/**
|
||||
* Creates a new database connection asynchronously.
|
||||
*
|
||||
* @param {string} path - Path to the database file.
|
||||
* @param {Object} opts - Options for database behavior.
|
||||
* @returns {Promise<Database>} - A promise that resolves to a Database instance.
|
||||
*/
|
||||
async function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {
|
||||
return await promiseConnect(path, opts, nativeConnect, async () => {
|
||||
await initThreadPool();
|
||||
if (MainWorker == null) {
|
||||
throw new Error("panic: MainWorker is not initialized");
|
||||
}
|
||||
return MainWorker;
|
||||
});
|
||||
}
|
||||
|
||||
export { connect, Database, SqliteError }
|
||||
@@ -0,0 +1,22 @@
|
||||
import { DatabaseOpts, SqliteError, } from "@tursodatabase/database-common"
|
||||
import { connect as promiseConnect, Database } from "./promise.js";
|
||||
import { connect as nativeConnect, initThreadPool, MainWorker } from "./index-vite-dev-hack.js";
|
||||
|
||||
/**
|
||||
* Creates a new database connection asynchronously.
|
||||
*
|
||||
* @param {string} path - Path to the database file.
|
||||
* @param {Object} opts - Options for database behavior.
|
||||
* @returns {Promise<Database>} - A promise that resolves to a Database instance.
|
||||
*/
|
||||
async function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {
|
||||
return await promiseConnect(path, opts, nativeConnect, async () => {
|
||||
await initThreadPool();
|
||||
if (MainWorker == null) {
|
||||
throw new Error("panic: MainWorker is not initialized");
|
||||
}
|
||||
return MainWorker;
|
||||
});
|
||||
}
|
||||
|
||||
export { connect, Database, SqliteError }
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test, afterEach } from 'vitest'
|
||||
import { connect } from './promise.js'
|
||||
import { connect } from './promise-default.js'
|
||||
|
||||
test('in-memory db', async () => {
|
||||
const db = await connect(":memory:");
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-browser-common"
|
||||
import { DatabasePromise, NativeDatabase, DatabaseOpts, SqliteError, } from "@tursodatabase/database-common"
|
||||
import { connect as nativeConnect, initThreadPool, MainWorker } from "#index";
|
||||
|
||||
class Database extends DatabasePromise {
|
||||
path: string | null;
|
||||
constructor(db: NativeDatabase, fsPath: string | null, opts: DatabaseOpts = {}) {
|
||||
worker: Worker | null;
|
||||
constructor(db: NativeDatabase, worker: Worker | null, fsPath: string | null, opts: DatabaseOpts = {}) {
|
||||
super(db, opts)
|
||||
this.path = fsPath;
|
||||
this.worker = worker;
|
||||
}
|
||||
async close() {
|
||||
if (this.path != null) {
|
||||
if (this.path != null && this.worker != null) {
|
||||
await Promise.all([
|
||||
unregisterFileAtWorker(MainWorker, this.path),
|
||||
unregisterFileAtWorker(MainWorker, `${this.path}-wal`)
|
||||
unregisterFileAtWorker(this.worker, this.path),
|
||||
unregisterFileAtWorker(this.worker, `${this.path}-wal`)
|
||||
]);
|
||||
}
|
||||
this.db.close();
|
||||
@@ -26,21 +27,18 @@ class Database extends DatabasePromise {
|
||||
* @param {Object} opts - Options for database behavior.
|
||||
* @returns {Promise<Database>} - A promise that resolves to a Database instance.
|
||||
*/
|
||||
async function connect(path: string, opts: DatabaseOpts = {}): Promise<Database> {
|
||||
async function connect(path: string, opts: DatabaseOpts, connect: any, init: () => Promise<Worker>): Promise<Database> {
|
||||
if (path == ":memory:") {
|
||||
const db = await nativeConnect(path, { tracing: opts.tracing });
|
||||
return new Database(db, null, opts);
|
||||
}
|
||||
await initThreadPool();
|
||||
if (MainWorker == null) {
|
||||
throw new Error("panic: MainWorker is not set");
|
||||
const db = await connect(path, { tracing: opts.tracing });
|
||||
return new Database(db, null, null, opts);
|
||||
}
|
||||
const worker = await init();
|
||||
await Promise.all([
|
||||
registerFileAtWorker(MainWorker, path),
|
||||
registerFileAtWorker(MainWorker, `${path}-wal`)
|
||||
registerFileAtWorker(worker, path),
|
||||
registerFileAtWorker(worker, `${path}-wal`)
|
||||
]);
|
||||
const db = await nativeConnect(path, { tracing: opts.tracing });
|
||||
return new Database(db, path, opts);
|
||||
const db = await connect(path, { tracing: opts.tracing });
|
||||
return new Database(db, worker, path, opts);
|
||||
}
|
||||
|
||||
export { connect, Database, SqliteError }
|
||||
|
||||
@@ -11,12 +11,7 @@
|
||||
"es2020",
|
||||
"DOM",
|
||||
"WebWorker"
|
||||
],
|
||||
"paths": {
|
||||
"#index": [
|
||||
"./index.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"*"
|
||||
|
||||
9
bindings/javascript/packages/browser/wasm-inline.ts
Normal file
9
bindings/javascript/packages/browser/wasm-inline.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const tursoWasmBase64 = '__PLACEHOLDER__';
|
||||
async function convertBase64ToBinary(base64Url: string): Promise<ArrayBuffer> {
|
||||
const blob = await fetch(base64Url).then(res => res.blob());
|
||||
return await blob.arrayBuffer();
|
||||
}
|
||||
|
||||
export async function tursoWasm(): Promise<ArrayBuffer> {
|
||||
return await convertBase64ToBinary(tursoWasmBase64);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime'
|
||||
import { OpfsDirectory, workerImports } from '@tursodatabase/database-browser-common';
|
||||
|
||||
var opfs = new OpfsDirectory();
|
||||
var memory = null;
|
||||
|
||||
const handler = new MessageHandler({
|
||||
onLoad({ wasmModule, wasmMemory }) {
|
||||
memory = wasmMemory;
|
||||
const wasi = new WASI({
|
||||
print: function () {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log.apply(console, arguments)
|
||||
},
|
||||
printErr: function () {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error.apply(console, arguments)
|
||||
},
|
||||
})
|
||||
return instantiateNapiModuleSync(wasmModule, {
|
||||
childThread: true,
|
||||
wasi,
|
||||
overwriteImports(importObject) {
|
||||
importObject.env = {
|
||||
...importObject.env,
|
||||
...importObject.napi,
|
||||
...importObject.emnapi,
|
||||
...workerImports(opfs, memory),
|
||||
memory: wasmMemory,
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
globalThis.onmessage = async function (e) {
|
||||
if (e.data.__turso__ == 'register') {
|
||||
try {
|
||||
await opfs.registerFile(e.data.path);
|
||||
self.postMessage({ id: e.data.id });
|
||||
} catch (error) {
|
||||
self.postMessage({ id: e.data.id, error: error });
|
||||
}
|
||||
return;
|
||||
} else if (e.data.__turso__ == 'unregister') {
|
||||
try {
|
||||
await opfs.unregisterFile(e.data.path);
|
||||
self.postMessage({ id: e.data.id });
|
||||
} catch (error) {
|
||||
self.postMessage({ id: e.data.id, error: error });
|
||||
}
|
||||
return;
|
||||
}
|
||||
handler.handle(e)
|
||||
}
|
||||
2
bindings/javascript/packages/browser/worker.ts
Normal file
2
bindings/javascript/packages/browser/worker.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import { setupWebWorker } from "@tursodatabase/database-browser-common";
|
||||
setupWebWorker();
|
||||
Reference in New Issue
Block a user