mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 08:34:19 +01:00
Remove wasm binding
With napi v3 we can compile our javascript binding to wasm, which can reduce a lot of maintenance overhead and complexity
This commit is contained in:
64
.github/labeler.yml
vendored
64
.github/labeler.yml
vendored
@@ -1,108 +1,106 @@
|
||||
simulator:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'simulator/**/*'
|
||||
- any-glob-to-any-file: "simulator/**/*"
|
||||
docs:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'docs/**/*'
|
||||
- any-glob-to-any-file: "docs/**/*"
|
||||
|
||||
extensionlib:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['extensions/core/**/*', 'macros/src/ext/*', 'core/ext/*']
|
||||
- any-glob-to-any-file:
|
||||
["extensions/core/**/*", "macros/src/ext/*", "core/ext/*"]
|
||||
|
||||
macros:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['macros/**/*']
|
||||
- any-glob-to-any-file: ["macros/**/*"]
|
||||
|
||||
extensions-other:
|
||||
- all:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'extensions/**/*'
|
||||
- all-globs-to-all-files: '!extensions/core/**/*'
|
||||
- all:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "extensions/**/*"
|
||||
- all-globs-to-all-files: "!extensions/core/**/*"
|
||||
|
||||
fuzzing:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'fuzz/**/*'
|
||||
- any-glob-to-any-file: "fuzz/**/*"
|
||||
|
||||
perf/benchmarks:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['perf/**/*', 'core/benches/*']
|
||||
- any-glob-to-any-file: ["perf/**/*", "core/benches/*"]
|
||||
|
||||
go-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/go/**/*'
|
||||
|
||||
wasm-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/wasm/**/*'
|
||||
- any-glob-to-any-file: "bindings/go/**/*"
|
||||
|
||||
python-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/python/**/*'
|
||||
- any-glob-to-any-file: "bindings/python/**/*"
|
||||
|
||||
js-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/javascript/**/*'
|
||||
- any-glob-to-any-file: "bindings/javascript/**/*"
|
||||
|
||||
rust-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/rust/**/*'
|
||||
- any-glob-to-any-file: "bindings/rust/**/*"
|
||||
|
||||
java-bindings:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'bindings/java/**/*'
|
||||
- any-glob-to-any-file: "bindings/java/**/*"
|
||||
|
||||
parser:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "vendored/sqlite3-parser/*"
|
||||
- any-glob-to-any-file: "vendored/sqlite3-parser/*"
|
||||
|
||||
cli:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "cli/**/*"
|
||||
- any-glob-to-any-file: "cli/**/*"
|
||||
|
||||
sqlite3:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "sqlite3/**/*"
|
||||
- any-glob-to-any-file: "sqlite3/**/*"
|
||||
|
||||
core:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/**/*'
|
||||
- any-glob-to-any-file: "core/**/*"
|
||||
|
||||
optimizer:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/translate/optimizer/*'
|
||||
- any-glob-to-any-file: "core/translate/optimizer/*"
|
||||
|
||||
translation/planning:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/translate/*.rs'
|
||||
- any-glob-to-any-file: "core/translate/*.rs"
|
||||
|
||||
io:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/io/*'
|
||||
- any-glob-to-any-file: "core/io/*"
|
||||
|
||||
mvcc:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/mvcc/**/*'
|
||||
- any-glob-to-any-file: "core/mvcc/**/*"
|
||||
|
||||
vdbe:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/vdbe/*'
|
||||
- any-glob-to-any-file: "core/vdbe/*"
|
||||
|
||||
json:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/json/*'
|
||||
- any-glob-to-any-file: "core/json/*"
|
||||
|
||||
storage:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/storage/*'
|
||||
- any-glob-to-any-file: "core/storage/*"
|
||||
|
||||
vector:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'core/vector/*'
|
||||
- any-glob-to-any-file: "core/vector/*"
|
||||
|
||||
antithesis:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['antithesis-tests/**/*', 'scripts/antithesis/*', 'stress/**/*']
|
||||
- any-glob-to-any-file:
|
||||
["antithesis-tests/**/*", "scripts/antithesis/*", "stress/**/*"]
|
||||
|
||||
ci-actions:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '.github/workflows/*.yml'
|
||||
- any-glob-to-any-file: ".github/workflows/*.yml"
|
||||
|
||||
71
.github/workflows/rust.yml
vendored
71
.github/workflows/rust.yml
vendored
@@ -2,9 +2,9 @@ name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
cargo-fmt-check:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Check formatting (fuzz)
|
||||
run: cd fuzz && cargo fmt --check
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Check formatting (fuzz)
|
||||
run: cd fuzz && cargo fmt --check
|
||||
|
||||
build-native:
|
||||
strategy:
|
||||
@@ -28,21 +28,21 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: useblacksmith/rust-cache@v3
|
||||
with:
|
||||
prefix-key: "v1-rust" # can be updated if we need to reset caches due to non-trivial change in the dependencies (for example, custom env var were set for single workspace project)
|
||||
- name: Set up Python 3.10
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Test
|
||||
env:
|
||||
RUST_LOG: ${{ runner.debug && 'turso_core::storage=trace' || '' }}
|
||||
run: cargo test --verbose
|
||||
timeout-minutes: 20
|
||||
- uses: actions/checkout@v3
|
||||
- uses: useblacksmith/rust-cache@v3
|
||||
with:
|
||||
prefix-key: "v1-rust" # can be updated if we need to reset caches due to non-trivial change in the dependencies (for example, custom env var were set for single workspace project)
|
||||
- name: Set up Python 3.10
|
||||
uses: useblacksmith/setup-python@v6
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Test
|
||||
env:
|
||||
RUST_LOG: ${{ runner.debug && 'turso_core::storage=trace' || '' }}
|
||||
run: cargo test --verbose
|
||||
timeout-minutes: 20
|
||||
|
||||
clippy:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
@@ -50,21 +50,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clippy
|
||||
run: |
|
||||
cargo clippy --workspace --all-features --all-targets --exclude limbo-wasm -- --deny=warnings
|
||||
- name: Clippy `limbo-wasm` crate `nodejs` feature
|
||||
run: |
|
||||
cargo clippy --package limbo-wasm --features nodejs --all-targets --no-deps -- -A clippy::all -W clippy::correctness -W clippy::perf -W clippy::suspicious --deny=warnings
|
||||
- name: Clippy `limbo-wasm` crate `web` feature
|
||||
run: |
|
||||
cargo clippy --package limbo-wasm --no-default-features --features web --all-targets --no-deps -- -A clippy::all -W clippy::correctness -W clippy::perf -W clippy::suspicious --deny=warnings
|
||||
|
||||
build-wasm:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- run: wasm-pack build --target nodejs bindings/wasm
|
||||
cargo clippy --workspace --all-features --all-targets -- --deny=warnings
|
||||
|
||||
simulator:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
@@ -95,10 +81,10 @@ jobs:
|
||||
- name: Test
|
||||
run: make test
|
||||
timeout-minutes: 20
|
||||
# - uses: "./.github/shared/install_sqlite"
|
||||
# - name: Test with index enabled
|
||||
# run: SQLITE_EXEC="scripts/limbo-sqlite3-index-experimental" make test
|
||||
# timeout-minutes: 20
|
||||
# - uses: "./.github/shared/install_sqlite"
|
||||
# - name: Test with index enabled
|
||||
# run: SQLITE_EXEC="scripts/limbo-sqlite3-index-experimental" make test
|
||||
# timeout-minutes: 20
|
||||
test-sqlite:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
@@ -106,4 +92,3 @@ jobs:
|
||||
- uses: "./.github/shared/install_sqlite"
|
||||
- name: Test
|
||||
run: SQLITE_EXEC="sqlite3" make test-compat
|
||||
|
||||
|
||||
1057
Cargo.lock
generated
1057
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
47
Cargo.toml
47
Cargo.toml
@@ -3,30 +3,29 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"bindings/dart/rust",
|
||||
"bindings/go",
|
||||
"bindings/java",
|
||||
"bindings/javascript",
|
||||
"bindings/python",
|
||||
"bindings/rust",
|
||||
"bindings/wasm",
|
||||
"cli",
|
||||
"core",
|
||||
"extensions/completion",
|
||||
"extensions/core",
|
||||
"extensions/crypto",
|
||||
"extensions/csv",
|
||||
"extensions/ipaddr",
|
||||
"extensions/percentile",
|
||||
"extensions/regexp",
|
||||
"extensions/tests",
|
||||
"macros",
|
||||
"simulator",
|
||||
"sqlite3",
|
||||
"stress",
|
||||
"testing/sqlite_test_ext",
|
||||
"tests",
|
||||
"vendored/sqlite3-parser/sqlparser_bench",
|
||||
"bindings/dart/rust",
|
||||
"bindings/go",
|
||||
"bindings/java",
|
||||
"bindings/javascript",
|
||||
"bindings/python",
|
||||
"bindings/rust",
|
||||
"cli",
|
||||
"core",
|
||||
"extensions/completion",
|
||||
"extensions/core",
|
||||
"extensions/crypto",
|
||||
"extensions/csv",
|
||||
"extensions/ipaddr",
|
||||
"extensions/percentile",
|
||||
"extensions/regexp",
|
||||
"extensions/tests",
|
||||
"macros",
|
||||
"simulator",
|
||||
"sqlite3",
|
||||
"stress",
|
||||
"testing/sqlite_test_ext",
|
||||
"tests",
|
||||
"vendored/sqlite3-parser/sqlparser_bench",
|
||||
]
|
||||
exclude = ["perf/latency/limbo"]
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ COPY ./bindings/java ./bindings/java/
|
||||
COPY ./bindings/javascript ./bindings/javascript/
|
||||
COPY ./bindings/python ./bindings/python/
|
||||
COPY ./bindings/rust ./bindings/rust/
|
||||
COPY ./bindings/wasm ./bindings/wasm/
|
||||
COPY ./cli ./cli/
|
||||
COPY ./core ./core/
|
||||
COPY ./extensions ./extensions/
|
||||
@@ -35,7 +34,7 @@ RUN cargo chef prepare --bin turso_stress --recipe-path recipe.json
|
||||
# Build the project.
|
||||
#
|
||||
|
||||
FROM chef AS builder
|
||||
FROM chef AS builder
|
||||
|
||||
ARG antithesis=true
|
||||
|
||||
@@ -62,7 +61,6 @@ COPY --from=planner /app/bindings/go ./bindings/go/
|
||||
COPY --from=planner /app/bindings/javascript ./bindings/javascript/
|
||||
COPY --from=planner /app/bindings/java ./bindings/java/
|
||||
COPY --from=planner /app/bindings/python ./bindings/python/
|
||||
COPY --from=planner /app/bindings/wasm ./bindings/wasm/
|
||||
COPY --from=planner /app/core ./core/
|
||||
COPY --from=planner /app/extensions ./extensions/
|
||||
COPY --from=planner /app/macros ./macros/
|
||||
@@ -97,7 +95,7 @@ COPY --from=builder /app/target/antithesis/turso_stress /symbols
|
||||
COPY stress/docker-entrypoint.sh /bin
|
||||
RUN chmod +x /bin/docker-entrypoint.sh
|
||||
|
||||
COPY --from=builder /app/target/wheels/* /tmp
|
||||
COPY --from=builder /app/target/wheels/* /tmp
|
||||
RUN pip install /tmp/*.whl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
15
Makefile
15
Makefile
@@ -9,7 +9,7 @@ MINIMUM_TCL_VERSION := 8.6
|
||||
SQLITE_EXEC ?= scripts/limbo-sqlite3
|
||||
RUST_LOG := off
|
||||
|
||||
all: check-rust-version check-wasm-target limbo limbo-wasm
|
||||
all: check-rust-version limbo
|
||||
.PHONY: all
|
||||
|
||||
check-rust-version:
|
||||
@@ -39,14 +39,6 @@ check-tcl-version:
|
||||
| tclsh
|
||||
.PHONY: check-tcl-version
|
||||
|
||||
check-wasm-target:
|
||||
@echo "Checking wasm32-wasip1 target..."
|
||||
@if ! rustup target list | grep -q "wasm32-wasip1 (installed)"; then \
|
||||
echo "Installing wasm32-wasip1 target..."; \
|
||||
rustup target add wasm32-wasip1; \
|
||||
fi
|
||||
.PHONY: check-wasm-target
|
||||
|
||||
limbo:
|
||||
cargo build
|
||||
.PHONY: limbo
|
||||
@@ -55,11 +47,6 @@ limbo-c:
|
||||
cargo cbuild
|
||||
.PHONY: limbo-c
|
||||
|
||||
limbo-wasm:
|
||||
rustup target add wasm32-wasip1
|
||||
cargo build --package limbo-wasm --target wasm32-wasip1
|
||||
.PHONY: limbo-wasm
|
||||
|
||||
uv-sync:
|
||||
uv sync --all-packages
|
||||
.PHONE: uv-sync
|
||||
|
||||
@@ -40,7 +40,7 @@ Turso Database is a _work-in-progress_, in-process OLTP database engine library
|
||||
* [Java](bindings/java)
|
||||
* [Python](bindings/python)
|
||||
* [Rust](bindings/rust)
|
||||
* [WebAssembly](bindings/wasm)
|
||||
* [WebAssembly](bindings/javascript)
|
||||
* **Asynchronous I/O** support on Linux with `io_uring`
|
||||
* **OS support** for Linux, macOS, and Windows
|
||||
|
||||
|
||||
@@ -2,3 +2,7 @@ nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
enableHardenedMode: false
|
||||
supportedArchitectures:
|
||||
cpu:
|
||||
- current
|
||||
- wasm32
|
||||
|
||||
1
bindings/javascript/browser.js
Normal file
1
bindings/javascript/browser.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@tursodatabase/turso-wasm32-wasi'
|
||||
1915
bindings/javascript/package-lock.json
generated
1915
bindings/javascript/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,19 +6,24 @@
|
||||
"url": "https://github.com/tursodatabase/turso"
|
||||
},
|
||||
"description": "The Turso database library",
|
||||
"main": "wrapper.js",
|
||||
"files": [
|
||||
"wrapper.js",
|
||||
"browser.js"
|
||||
],
|
||||
"types": "index.d.ts",
|
||||
"napi": {
|
||||
"binaryName": "turso",
|
||||
"targets": [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"universal-apple-darwin"
|
||||
"universal-apple-darwin",
|
||||
"wasm32-wasip1-threads"
|
||||
]
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^3.0.4",
|
||||
"@napi-rs/wasm-runtime": "^1.0.1",
|
||||
"ava": "^6.0.1",
|
||||
"better-sqlite3": "^11.9.1"
|
||||
},
|
||||
|
||||
60
bindings/javascript/turso.wasi-browser.js
Normal file
60
bindings/javascript/turso.wasi-browser.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
createOnMessage as __wasmCreateOnMessageForFsProxy,
|
||||
getDefaultContext as __emnapiGetDefaultContext,
|
||||
instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync,
|
||||
WASI as __WASI,
|
||||
} from '@napi-rs/wasm-runtime'
|
||||
|
||||
|
||||
|
||||
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())
|
||||
|
||||
const {
|
||||
instance: __napiInstance,
|
||||
module: __wasiModule,
|
||||
napiModule: __napiModule,
|
||||
} = __emnapiInstantiateNapiModuleSync(__wasmFile, {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: 4,
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
|
||||
type: 'module',
|
||||
})
|
||||
|
||||
return worker
|
||||
},
|
||||
overwriteImports(importObject) {
|
||||
importObject.env = {
|
||||
...importObject.env,
|
||||
...importObject.napi,
|
||||
...importObject.emnapi,
|
||||
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 Statement = __napiModule.exports.Statement
|
||||
112
bindings/javascript/turso.wasi.cjs
Normal file
112
bindings/javascript/turso.wasi.cjs
Normal file
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
|
||||
/* auto-generated by NAPI-RS */
|
||||
|
||||
const __nodeFs = require('node:fs')
|
||||
const __nodePath = require('node:path')
|
||||
const { WASI: __nodeWASI } = require('node:wasi')
|
||||
const { Worker } = require('node:worker_threads')
|
||||
|
||||
const {
|
||||
createOnMessage: __wasmCreateOnMessageForFsProxy,
|
||||
getDefaultContext: __emnapiGetDefaultContext,
|
||||
instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync,
|
||||
} = require('@napi-rs/wasm-runtime')
|
||||
|
||||
const __rootDir = __nodePath.parse(process.cwd()).root
|
||||
|
||||
const __wasi = new __nodeWASI({
|
||||
version: 'preview1',
|
||||
env: process.env,
|
||||
preopens: {
|
||||
[__rootDir]: __rootDir,
|
||||
}
|
||||
})
|
||||
|
||||
const __emnapiContext = __emnapiGetDefaultContext()
|
||||
|
||||
const __sharedMemory = new WebAssembly.Memory({
|
||||
initial: 4000,
|
||||
maximum: 65536,
|
||||
shared: true,
|
||||
})
|
||||
|
||||
let __wasmFilePath = __nodePath.join(__dirname, 'turso.wasm32-wasi.wasm')
|
||||
const __wasmDebugFilePath = __nodePath.join(__dirname, 'turso.wasm32-wasi.debug.wasm')
|
||||
|
||||
if (__nodeFs.existsSync(__wasmDebugFilePath)) {
|
||||
__wasmFilePath = __wasmDebugFilePath
|
||||
} else if (!__nodeFs.existsSync(__wasmFilePath)) {
|
||||
try {
|
||||
__wasmFilePath = __nodePath.resolve('@tursodatabase/turso-wasm32-wasi')
|
||||
} catch {
|
||||
throw new Error('Cannot find turso.wasm32-wasi.wasm file, and @tursodatabase/turso-wasm32-wasi package is not installed.')
|
||||
}
|
||||
}
|
||||
|
||||
const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__wasmFilePath), {
|
||||
context: __emnapiContext,
|
||||
asyncWorkPoolSize: (function() {
|
||||
const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE)
|
||||
// NaN > 0 is false
|
||||
if (threadsSizeFromEnv > 0) {
|
||||
return threadsSizeFromEnv
|
||||
} else {
|
||||
return 4
|
||||
}
|
||||
})(),
|
||||
reuseWorker: true,
|
||||
wasi: __wasi,
|
||||
onCreateWorker() {
|
||||
const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), {
|
||||
env: process.env,
|
||||
})
|
||||
worker.onmessage = ({ data }) => {
|
||||
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)
|
||||
}
|
||||
|
||||
// The main thread of Node.js waits for all the active handles before exiting.
|
||||
// But Rust threads are never waited without `thread::join`.
|
||||
// So here we hack the code of Node.js to prevent the workers from being referenced (active).
|
||||
// According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415,
|
||||
// a worker is consist of two handles: kPublicPort and kHandle.
|
||||
{
|
||||
const kPublicPort = Object.getOwnPropertySymbols(worker).find(s =>
|
||||
s.toString().includes("kPublicPort")
|
||||
);
|
||||
if (kPublicPort) {
|
||||
worker[kPublicPort].ref = () => {};
|
||||
}
|
||||
|
||||
const kHandle = Object.getOwnPropertySymbols(worker).find(s =>
|
||||
s.toString().includes("kHandle")
|
||||
);
|
||||
if (kHandle) {
|
||||
worker[kHandle].ref = () => {};
|
||||
}
|
||||
|
||||
worker.unref();
|
||||
}
|
||||
return worker
|
||||
},
|
||||
overwriteImports(importObject) {
|
||||
importObject.env = {
|
||||
...importObject.env,
|
||||
...importObject.napi,
|
||||
...importObject.emnapi,
|
||||
memory: __sharedMemory,
|
||||
}
|
||||
return importObject
|
||||
},
|
||||
beforeInit({ instance }) {
|
||||
for (const name of Object.keys(instance.exports)) {
|
||||
if (name.startsWith('__napi_register__')) {
|
||||
instance.exports[name]()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
module.exports = __napiModule.exports
|
||||
module.exports.Database = __napiModule.exports.Database
|
||||
module.exports.Statement = __napiModule.exports.Statement
|
||||
32
bindings/javascript/wasi-worker-browser.mjs
Normal file
32
bindings/javascript/wasi-worker-browser.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime'
|
||||
|
||||
const handler = new MessageHandler({
|
||||
onLoad({ wasmModule, 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,
|
||||
memory: wasmMemory,
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
globalThis.onmessage = function (e) {
|
||||
handler.handle(e)
|
||||
}
|
||||
63
bindings/javascript/wasi-worker.mjs
Normal file
63
bindings/javascript/wasi-worker.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { parse } from "node:path";
|
||||
import { WASI } from "node:wasi";
|
||||
import { parentPort, Worker } from "node:worker_threads";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const { instantiateNapiModuleSync, MessageHandler, getDefaultContext } = require("@napi-rs/wasm-runtime");
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on("message", (data) => {
|
||||
globalThis.onmessage({ data });
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(globalThis, {
|
||||
self: globalThis,
|
||||
require,
|
||||
Worker,
|
||||
importScripts: function (f) {
|
||||
;(0, eval)(fs.readFileSync(f, "utf8") + "//# sourceURL=" + f);
|
||||
},
|
||||
postMessage: function (msg) {
|
||||
if (parentPort) {
|
||||
parentPort.postMessage(msg);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const emnapiContext = getDefaultContext();
|
||||
|
||||
const __rootDir = parse(process.cwd()).root;
|
||||
|
||||
const handler = new MessageHandler({
|
||||
onLoad({ wasmModule, wasmMemory }) {
|
||||
const wasi = new WASI({
|
||||
version: 'preview1',
|
||||
env: process.env,
|
||||
preopens: {
|
||||
[__rootDir]: __rootDir,
|
||||
},
|
||||
});
|
||||
|
||||
return instantiateNapiModuleSync(wasmModule, {
|
||||
childThread: true,
|
||||
wasi,
|
||||
context: emnapiContext,
|
||||
overwriteImports(importObject) {
|
||||
importObject.env = {
|
||||
...importObject.env,
|
||||
...importObject.napi,
|
||||
...importObject.emnapi,
|
||||
memory: wasmMemory
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
globalThis.onmessage = function (e) {
|
||||
handler.handle(e);
|
||||
};
|
||||
@@ -1073,6 +1073,7 @@ __metadata:
|
||||
resolution: "@tursodatabase/turso@workspace:."
|
||||
dependencies:
|
||||
"@napi-rs/cli": "npm:^3.0.4"
|
||||
"@napi-rs/wasm-runtime": "npm:^1.0.1"
|
||||
ava: "npm:^6.0.1"
|
||||
better-sqlite3: "npm:^11.9.1"
|
||||
languageName: unknown
|
||||
|
||||
5
bindings/wasm/.gitignore
vendored
5
bindings/wasm/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
node_modules/
|
||||
*.wasm
|
||||
**/dist/
|
||||
limbo-wasm*tgz
|
||||
claude.md
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "limbo-wasm"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = "0.3.72"
|
||||
turso_core = { path = "../../core", default-features = false }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
|
||||
[features]
|
||||
web = []
|
||||
nodejs = []
|
||||
default = ["nodejs"]
|
||||
@@ -1,42 +0,0 @@
|
||||
# Limbo Wasm bindings
|
||||
|
||||
This source tree contains Limbo Wasm bindings.
|
||||
|
||||
## Building
|
||||
|
||||
For nodejs
|
||||
```
|
||||
./scripts/build
|
||||
```
|
||||
For web
|
||||
|
||||
```
|
||||
./scripts/build web
|
||||
```
|
||||
|
||||
# Browser Support
|
||||
|
||||
Adding experimental support for limbo in the browser. This is done by adding support for OPFS as a VFS.
|
||||
|
||||
To see a basic example of this `npm run dev` and navigate to `http://localhost:5173/limbo-opfs-test.html` and open the console.
|
||||
|
||||
## Design
|
||||
|
||||
This design mirrors sqlite's approach for OPFS support. It has a sync api in `opfs.js` which communicates with `opfs-sync-proxy.js` via `SharedArrayBuffer` and `Atomics.wait`. This allows us to live the VFS api in `lib.rs` unchanged.
|
||||
|
||||
You can see `limbo-opfs-test.html` for basic usage.
|
||||
|
||||
## UTs
|
||||
|
||||
There are OPFS specific unit tests and then some basic limbo unit tests. These are run via `npm test` or `npx vitest`.
|
||||
|
||||
For more info and log output you can run `npx vitest:ui` but you can get some parallel execution of test cases which cause issues.
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
-[] Add a wrapper js that provides a clean interface to the `limbo-worker.js`
|
||||
-[] Add more tests for opfs.js operations
|
||||
-[] Add error return handling
|
||||
-[] Make sure posix flags for open are handled instead of just being ignored (this requires creating a mapping of behaviors from posix to opfs as far as makes sense)
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# class Database
|
||||
|
||||
The `Database` class represents a connection that can prepare and execute SQL statements.
|
||||
|
||||
## Methods
|
||||
|
||||
### new Database(path) ⇒ Database
|
||||
|
||||
Creates a new database connection.
|
||||
|
||||
| Param | Type | Description |
|
||||
| ------- | ------------------- | ------------------------- |
|
||||
| path | <code>string</code> | Path to the database file |
|
||||
|
||||
### prepare(sql) ⇒ Statement
|
||||
|
||||
Prepares a SQL statement for execution.
|
||||
|
||||
| Param | Type | Description |
|
||||
| ------ | ------------------- | ------------------------------------ |
|
||||
| sql | <code>string</code> | The SQL statement string to prepare. |
|
||||
|
||||
The function returns a `Statement` object.
|
||||
|
||||
### transaction(function) ⇒ function
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### pragma(string, [options]) ⇒ results
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### backup(destination, [options]) ⇒ promise
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### serialize([options]) ⇒ Buffer
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### function(name, [options], function) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### aggregate(name, options) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### table(name, definition) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### loadExtension(path, [entryPoint]) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### exec(sql) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### close() ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
# class Statement
|
||||
|
||||
## Methods
|
||||
|
||||
### run([...bindParameters]) ⇒ object
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### get([...bindParameters]) ⇒ row
|
||||
|
||||
Executes the SQL statement and returns the first row.
|
||||
|
||||
| Param | Type | Description |
|
||||
| -------------- | ----------------------------- | ------------------------------------------------ |
|
||||
| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |
|
||||
|
||||
### all([...bindParameters]) ⇒ array of rows
|
||||
|
||||
Executes the SQL statement and returns an array of the resulting rows.
|
||||
|
||||
| Param | Type | Description |
|
||||
| -------------- | ----------------------------- | ------------------------------------------------ |
|
||||
| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |
|
||||
|
||||
### iterate([...bindParameters]) ⇒ iterator
|
||||
|
||||
Executes the SQL statement and returns an iterator to the resulting rows.
|
||||
|
||||
| Param | Type | Description |
|
||||
| -------------- | ----------------------------- | ------------------------------------------------ |
|
||||
| bindParameters | <code>array of objects</code> | The bind parameters for executing the statement. |
|
||||
|
||||
### pluck([toggleState]) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### expand([toggleState]) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### raw([rawMode]) ⇒ this
|
||||
|
||||
Toggle raw mode.
|
||||
|
||||
| Param | Type | Description |
|
||||
| ------- | -------------------- | --------------------------------------------------------------------------------- |
|
||||
| rawMode | <code>boolean</code> | Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled. |
|
||||
|
||||
This function enables or disables raw mode. Prepared statements return objects by default, but if raw mode is enabled, the functions return arrays instead.
|
||||
|
||||
### columns() ⇒ array of objects
|
||||
|
||||
This function is currently not supported.
|
||||
|
||||
### bind([...bindParameters]) ⇒ this
|
||||
|
||||
This function is currently not supported.
|
||||
@@ -1,12 +0,0 @@
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import * as s from 'drizzle-orm/sqlite-core';
|
||||
import { Database } from 'limbo-wasm';
|
||||
|
||||
const sqlite = new Database('sqlite.db');
|
||||
const db = drizzle({ client: sqlite });
|
||||
const users = s.sqliteTable("users", {
|
||||
id: s.integer(),
|
||||
name: s.text(),
|
||||
})
|
||||
const result = db.select().from(users).all();
|
||||
console.log(result);
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Database } from 'limbo-wasm';
|
||||
|
||||
const db = new Database('hello.db');
|
||||
|
||||
db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
||||
|
||||
db.exec('INSERT INTO users (name) VALUES (\'Alice\')');
|
||||
|
||||
const stmt = db.prepare('SELECT * FROM users');
|
||||
|
||||
const users = stmt.all();
|
||||
|
||||
console.log(users);
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "limbo-example",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"drizzle-orm": "^0.36.3",
|
||||
"limbo-wasm": ".."
|
||||
}
|
||||
}
|
||||
3707
bindings/wasm/integration-tests/package-lock.json
generated
3707
bindings/wasm/integration-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "limbo-wasm-integration-tests",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "PROVIDER=better-sqlite3 ava tests/test.js && PROVIDER=limbo-wasm ava tests/test.js && rm *.db *.db-wal"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^6.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"limbo-wasm": ".."
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import test from "ava";
|
||||
import { unlinkSync, existsSync } from "node:fs"
|
||||
|
||||
test.beforeEach(async (t) => {
|
||||
const [db, errorType, provider] = await connect();
|
||||
// DROP TABLE IF EXISTS users; <- is not supported in lib.rs yet
|
||||
db.exec(`
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)
|
||||
`);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')"
|
||||
);
|
||||
db.exec(
|
||||
"INSERT INTO users (id, name, email) VALUES (2, 'Bob', 'bob@example.com')"
|
||||
);
|
||||
t.context = {
|
||||
db,
|
||||
errorType,
|
||||
provider
|
||||
};
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().all()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
[1, "Alice", "alice@example.org"],
|
||||
[2, "Bob", "bob@example.com"],
|
||||
];
|
||||
t.deepEqual(stmt.raw().all(), expected);
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().get()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
1, "Alice", "alice@example.org"
|
||||
];
|
||||
t.deepEqual(stmt.raw().get(), expected);
|
||||
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(emptyStmt.raw().get(), undefined);
|
||||
});
|
||||
|
||||
test.serial("Statement.raw().iterate()", async (t) => {
|
||||
const db = t.context.db;
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
const expected = [
|
||||
{ done: false, value: [1, "Alice", "alice@example.org"] },
|
||||
{ done: false, value: [2, "Bob", "bob@example.com"] },
|
||||
{ done: true, value: undefined },
|
||||
];
|
||||
|
||||
let iter = stmt.raw().iterate();
|
||||
t.is(typeof iter[Symbol.iterator], 'function');
|
||||
t.deepEqual(iter.next(), expected[0])
|
||||
t.deepEqual(iter.next(), expected[1])
|
||||
t.deepEqual(iter.next(), expected[2])
|
||||
|
||||
const emptyStmt = db.prepare("SELECT * FROM users WHERE id = -1");
|
||||
t.is(typeof emptyStmt[Symbol.iterator], 'undefined');
|
||||
t.throws(() => emptyStmt.next(), { instanceOf: TypeError });
|
||||
});
|
||||
|
||||
const connect = async (path_opt) => {
|
||||
// delete hello.db if it exists
|
||||
if (existsSync("hello.db")) {
|
||||
unlinkSync("hello.db");
|
||||
}
|
||||
|
||||
const path = path_opt ?? "hello.db";
|
||||
const provider = process.env.PROVIDER;
|
||||
if (provider === "limbo-wasm") {
|
||||
const database = process.env.LIBSQL_DATABASE ?? path;
|
||||
const x = await import("limbo-wasm");
|
||||
const options = {};
|
||||
const db = new x.Database(database, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
}
|
||||
if (provider == "better-sqlite3") {
|
||||
const x = await import("better-sqlite3");
|
||||
const options = {};
|
||||
const db = x.default(path, options);
|
||||
return [db, x.SqliteError, provider];
|
||||
}
|
||||
throw new Error("Unknown provider: " + provider);
|
||||
};
|
||||
@@ -1,439 +0,0 @@
|
||||
#[cfg(all(feature = "web", feature = "nodejs"))]
|
||||
compile_error!("Features 'web' and 'nodejs' cannot be enabled at the same time");
|
||||
|
||||
use js_sys::{Array, Object};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use turso_core::{Clock, Instant, OpenFlags, Result};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[wasm_bindgen]
|
||||
pub struct Database {
|
||||
db: Arc<turso_core::Database>,
|
||||
conn: Arc<turso_core::Connection>,
|
||||
}
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
#[wasm_bindgen]
|
||||
impl Database {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(path: &str) -> Database {
|
||||
let io: Arc<dyn turso_core::IO> = Arc::new(PlatformIO { vfs: VFS::new() });
|
||||
let file = io.open_file(path, OpenFlags::Create, false).unwrap();
|
||||
let db_file = Arc::new(DatabaseFile::new(file));
|
||||
let db = turso_core::Database::open(io, path, db_file, false, false).unwrap();
|
||||
let conn = db.connect().unwrap();
|
||||
Database { db, conn }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn exec(&self, _sql: &str) {
|
||||
self.conn.execute(_sql).unwrap();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn prepare(&self, _sql: &str) -> Statement {
|
||||
let stmt = self.conn.prepare(_sql).unwrap();
|
||||
Statement::new(RefCell::new(stmt), false)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct RowIterator {
|
||||
inner: RefCell<turso_core::Statement>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RowIterator {
|
||||
fn new(inner: RefCell<turso_core::Statement>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> JsValue {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
match stmt.step() {
|
||||
Ok(turso_core::StepResult::Row) => {
|
||||
let row = stmt.row().unwrap();
|
||||
let row_array = Array::new();
|
||||
for value in row.get_values() {
|
||||
let value = to_js_value(value);
|
||||
row_array.push(&value);
|
||||
}
|
||||
JsValue::from(row_array)
|
||||
}
|
||||
Ok(turso_core::StepResult::IO) => JsValue::UNDEFINED,
|
||||
Ok(turso_core::StepResult::Done) | Ok(turso_core::StepResult::Interrupt) => {
|
||||
JsValue::UNDEFINED
|
||||
}
|
||||
|
||||
Ok(turso_core::StepResult::Busy) => JsValue::UNDEFINED,
|
||||
Err(e) => panic!("Error: {e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Statement {
|
||||
inner: RefCell<turso_core::Statement>,
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Statement {
|
||||
fn new(inner: RefCell<turso_core::Statement>, raw: bool) -> Self {
|
||||
Self { inner, raw }
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn raw(mut self, toggle: Option<bool>) -> Self {
|
||||
self.raw = toggle.unwrap_or(true);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get(&self) -> JsValue {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
match stmt.step() {
|
||||
Ok(turso_core::StepResult::Row) => {
|
||||
let row = stmt.row().unwrap();
|
||||
let row_array = js_sys::Array::new();
|
||||
for value in row.get_values() {
|
||||
let value = to_js_value(value);
|
||||
row_array.push(&value);
|
||||
}
|
||||
JsValue::from(row_array)
|
||||
}
|
||||
|
||||
Ok(turso_core::StepResult::IO)
|
||||
| Ok(turso_core::StepResult::Done)
|
||||
| Ok(turso_core::StepResult::Interrupt)
|
||||
| Ok(turso_core::StepResult::Busy) => JsValue::UNDEFINED,
|
||||
Err(e) => panic!("Error: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all(&self) -> js_sys::Array {
|
||||
let array = js_sys::Array::new();
|
||||
loop {
|
||||
let mut stmt = self.inner.borrow_mut();
|
||||
match stmt.step() {
|
||||
Ok(turso_core::StepResult::Row) => {
|
||||
let row = stmt.row().unwrap();
|
||||
let row_array = js_sys::Array::new();
|
||||
for value in row.get_values() {
|
||||
let value = to_js_value(value);
|
||||
row_array.push(&value);
|
||||
}
|
||||
array.push(&row_array);
|
||||
}
|
||||
Ok(turso_core::StepResult::IO) => {}
|
||||
Ok(turso_core::StepResult::Interrupt) => break,
|
||||
Ok(turso_core::StepResult::Done) => break,
|
||||
Ok(turso_core::StepResult::Busy) => break,
|
||||
Err(e) => panic!("Error: {e:?}"),
|
||||
}
|
||||
}
|
||||
array
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn iterate(self) -> JsValue {
|
||||
let iterator = RowIterator::new(self.inner);
|
||||
let iterator_obj = Object::new();
|
||||
|
||||
// Define the next method that will be called by JavaScript
|
||||
let next_fn = js_sys::Function::new_with_args(
|
||||
"",
|
||||
"const value = this.iterator.next();
|
||||
const done = value === undefined;
|
||||
return {
|
||||
value,
|
||||
done
|
||||
};",
|
||||
);
|
||||
|
||||
js_sys::Reflect::set(&iterator_obj, &JsValue::from_str("next"), &next_fn).unwrap();
|
||||
|
||||
js_sys::Reflect::set(
|
||||
&iterator_obj,
|
||||
&JsValue::from_str("iterator"),
|
||||
&JsValue::from(iterator),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let symbol_iterator = js_sys::Function::new_no_args("return this;");
|
||||
js_sys::Reflect::set(&iterator_obj, &js_sys::Symbol::iterator(), &symbol_iterator).unwrap();
|
||||
|
||||
JsValue::from(iterator_obj)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_js_value(value: &turso_core::Value) -> JsValue {
|
||||
match value {
|
||||
turso_core::Value::Null => JsValue::null(),
|
||||
turso_core::Value::Integer(i) => {
|
||||
let i = *i;
|
||||
if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
|
||||
JsValue::from(i as i32)
|
||||
} else {
|
||||
JsValue::from(i)
|
||||
}
|
||||
}
|
||||
turso_core::Value::Float(f) => JsValue::from(*f),
|
||||
turso_core::Value::Text(t) => JsValue::from_str(t.as_str()),
|
||||
turso_core::Value::Blob(b) => js_sys::Uint8Array::from(b.as_slice()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
vfs: VFS,
|
||||
fd: i32,
|
||||
}
|
||||
|
||||
unsafe impl Send for File {}
|
||||
unsafe impl Sync for File {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl File {
|
||||
fn new(vfs: VFS, fd: i32) -> Self {
|
||||
Self { vfs, fd }
|
||||
}
|
||||
}
|
||||
|
||||
impl turso_core::File for File {
|
||||
fn lock_file(&self, _exclusive: bool) -> Result<()> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlock_file(&self) -> Result<()> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pread(
|
||||
&self,
|
||||
pos: usize,
|
||||
c: Arc<turso_core::Completion>,
|
||||
) -> Result<Arc<turso_core::Completion>> {
|
||||
let r = match c.completion_type {
|
||||
turso_core::CompletionType::Read(ref r) => r,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let nr = {
|
||||
let mut buf = r.buf_mut();
|
||||
let buf: &mut [u8] = buf.as_mut_slice();
|
||||
self.vfs.pread(self.fd, buf, pos)
|
||||
};
|
||||
r.complete(nr);
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn pwrite(
|
||||
&self,
|
||||
pos: usize,
|
||||
buffer: Arc<std::cell::RefCell<turso_core::Buffer>>,
|
||||
c: Arc<turso_core::Completion>,
|
||||
) -> Result<Arc<turso_core::Completion>> {
|
||||
let w = match c.completion_type {
|
||||
turso_core::CompletionType::Write(ref w) => w,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let buf = buffer.borrow();
|
||||
let buf: &[u8] = buf.as_slice();
|
||||
self.vfs.pwrite(self.fd, buf, pos);
|
||||
w.complete(buf.len() as i32);
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn sync(&self, c: Arc<turso_core::Completion>) -> Result<Arc<turso_core::Completion>> {
|
||||
self.vfs.sync(self.fd);
|
||||
c.complete(0);
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<u64> {
|
||||
Ok(self.vfs.size(self.fd))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlatformIO {
|
||||
vfs: VFS,
|
||||
}
|
||||
unsafe impl Send for PlatformIO {}
|
||||
unsafe impl Sync for PlatformIO {}
|
||||
|
||||
impl Clock for PlatformIO {
|
||||
fn now(&self) -> Instant {
|
||||
let date = Date::new();
|
||||
let ms_since_epoch = date.getTime();
|
||||
|
||||
Instant {
|
||||
secs: (ms_since_epoch / 1000.0) as i64,
|
||||
micros: ((ms_since_epoch % 1000.0) * 1000.0) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl turso_core::IO for PlatformIO {
|
||||
fn open_file(
|
||||
&self,
|
||||
path: &str,
|
||||
_flags: OpenFlags,
|
||||
_direct: bool,
|
||||
) -> Result<Arc<dyn turso_core::File>> {
|
||||
let fd = self.vfs.open(path, "a+");
|
||||
Ok(Arc::new(File {
|
||||
vfs: VFS::new(),
|
||||
fd,
|
||||
}))
|
||||
}
|
||||
|
||||
fn wait_for_completion(&self, c: Arc<turso_core::Completion>) -> Result<()> {
|
||||
while !c.is_completed() {
|
||||
self.run_once()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_once(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_random_number(&self) -> i64 {
|
||||
let mut buf = [0u8; 8];
|
||||
getrandom::getrandom(&mut buf).unwrap();
|
||||
i64::from_ne_bytes(buf)
|
||||
}
|
||||
|
||||
fn get_memory_io(&self) -> Arc<turso_core::MemoryIO> {
|
||||
Arc::new(turso_core::MemoryIO::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type Date;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> Date;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn toISOString(this: &Date) -> String;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn getTime(this: &Date) -> f64;
|
||||
}
|
||||
|
||||
pub struct DatabaseFile {
|
||||
file: Arc<dyn turso_core::File>,
|
||||
}
|
||||
|
||||
unsafe impl Send for DatabaseFile {}
|
||||
unsafe impl Sync for DatabaseFile {}
|
||||
|
||||
impl DatabaseFile {
|
||||
pub fn new(file: Arc<dyn turso_core::File>) -> Self {
|
||||
Self { file }
|
||||
}
|
||||
}
|
||||
|
||||
impl turso_core::DatabaseStorage for DatabaseFile {
|
||||
fn read_page(&self, page_idx: usize, c: turso_core::Completion) -> Result<()> {
|
||||
let r = match c.completion_type {
|
||||
turso_core::CompletionType::Read(ref r) => r,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let size = r.buf().len();
|
||||
assert!(page_idx > 0);
|
||||
if !(512..=65536).contains(&size) || size & (size - 1) != 0 {
|
||||
return Err(turso_core::LimboError::NotADB);
|
||||
}
|
||||
let pos = (page_idx - 1) * size;
|
||||
self.file.pread(pos, c.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_page(
|
||||
&self,
|
||||
page_idx: usize,
|
||||
buffer: Arc<std::cell::RefCell<turso_core::Buffer>>,
|
||||
c: turso_core::Completion,
|
||||
) -> Result<()> {
|
||||
let size = buffer.borrow().len();
|
||||
let pos = (page_idx - 1) * size;
|
||||
self.file.pwrite(pos, buffer, c.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(&self, c: turso_core::Completion) -> Result<()> {
|
||||
let _ = self.file.sync(c.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<u64> {
|
||||
self.file.size()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "web", not(feature = "nodejs")))]
|
||||
#[wasm_bindgen(module = "/web/src/web-vfs.js")]
|
||||
extern "C" {
|
||||
type VFS;
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> VFS;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn open(this: &VFS, path: &str, flags: &str) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn close(this: &VFS, fd: i32) -> bool;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn pwrite(this: &VFS, fd: i32, buffer: &[u8], offset: usize) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn pread(this: &VFS, fd: i32, buffer: &mut [u8], offset: usize) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn size(this: &VFS, fd: i32) -> u64;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn sync(this: &VFS, fd: i32);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "nodejs", not(feature = "web")))]
|
||||
#[wasm_bindgen(module = "/node/src/vfs.cjs")]
|
||||
extern "C" {
|
||||
type VFS;
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> VFS;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn open(this: &VFS, path: &str, flags: &str) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn close(this: &VFS, fd: i32) -> bool;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn pwrite(this: &VFS, fd: i32, buffer: &[u8], offset: usize) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn pread(this: &VFS, fd: i32, buffer: &mut [u8], offset: usize) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn size(this: &VFS, fd: i32) -> u64;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn sync(this: &VFS, fd: i32);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
const fs = require('node:fs');
|
||||
|
||||
class VFS {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
open(path, flags) {
|
||||
return fs.openSync(path, flags);
|
||||
}
|
||||
|
||||
close(fd) {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
|
||||
pread(fd, buffer, offset) {
|
||||
return fs.readSync(fd, buffer, 0, buffer.length, offset);
|
||||
}
|
||||
|
||||
pwrite(fd, buffer, offset) {
|
||||
return fs.writeSync(fd, buffer, 0, buffer.length, offset);
|
||||
}
|
||||
|
||||
size(fd) {
|
||||
let stats = fs.fstatSync(fd);
|
||||
return BigInt(stats.size);
|
||||
}
|
||||
|
||||
sync(fd) {
|
||||
fs.fsyncSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { VFS };
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "limbo-perf",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"limbo-wasm": "../pkg",
|
||||
"better-sqlite3": "^9.5.0",
|
||||
"libsql": "..",
|
||||
"mitata": "^0.1.11"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { run, bench, group, baseline } from 'mitata';
|
||||
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
const db = new Database('better-sqlite3.db');
|
||||
|
||||
db.exec('CREATE TABLE t (x)');
|
||||
db.exec('INSERT INTO t VALUES (1)');
|
||||
db.exec('INSERT INTO t VALUES (2)');
|
||||
db.exec('INSERT INTO t VALUES (3)');
|
||||
|
||||
group('SQL queries [all()]', () => {
|
||||
const stmt1 = db.prepare("SELECT 1");
|
||||
bench('SELECT 1', () => {
|
||||
stmt1.all();
|
||||
});
|
||||
const stmt2 = db.prepare("SELECT * FROM t");
|
||||
bench('SELECT * FROM t', () => {
|
||||
stmt2.all();
|
||||
});
|
||||
});
|
||||
|
||||
group('SQL queries [iterate()]', () => {
|
||||
const stmt1 = db.prepare("SELECT 1");
|
||||
const it1 = stmt1.iterate();
|
||||
bench('SELECT 1', () => {
|
||||
it1.next();
|
||||
});
|
||||
const stmt2 = db.prepare("SELECT * FROM t");
|
||||
const it2 = stmt2.iterate();
|
||||
bench('SELECT * FROM t', () => {
|
||||
it2.next();
|
||||
});
|
||||
});
|
||||
|
||||
await run({
|
||||
units: false,
|
||||
silent: false,
|
||||
avg: true,
|
||||
json: false,
|
||||
colors: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import { run, bench, group, baseline } from 'mitata';
|
||||
|
||||
import { Database } from 'limbo-wasm';
|
||||
|
||||
const db = new Database('limbo.db');
|
||||
|
||||
db.exec('CREATE TABLE t (x)');
|
||||
db.exec('INSERT INTO t VALUES (1)');
|
||||
db.exec('INSERT INTO t VALUES (2)');
|
||||
db.exec('INSERT INTO t VALUES (3)');
|
||||
|
||||
group('SQL queries [all()]', () => {
|
||||
const stmt1 = db.prepare("SELECT 1");
|
||||
bench('SELECT 1', () => {
|
||||
stmt1.all();
|
||||
});
|
||||
const stmt2 = db.prepare("SELECT * FROM t");
|
||||
bench('SELECT * FROM t', () => {
|
||||
stmt2.all();
|
||||
});
|
||||
});
|
||||
|
||||
group('SQL queries [iterate()]', () => {
|
||||
const stmt1 = db.prepare("SELECT 1");
|
||||
const it1 = stmt1.iterate();
|
||||
bench('SELECT 1', () => {
|
||||
it1.next();
|
||||
});
|
||||
const stmt2 = db.prepare("SELECT * FROM t");
|
||||
const it2 = stmt2.iterate();
|
||||
bench('SELECT * FROM t', () => {
|
||||
it2.next();
|
||||
});
|
||||
});
|
||||
|
||||
await run({
|
||||
units: false,
|
||||
silent: false,
|
||||
avg: true,
|
||||
json: false,
|
||||
colors: true,
|
||||
min_max: true,
|
||||
percentiles: true,
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Define final output directories
|
||||
NODE_DIR="node"
|
||||
WEB_DIR="web"
|
||||
|
||||
rm -rf $NODE_DIR/dist $WEB_DIR/dist pkg
|
||||
mkdir -p $NODE_DIR/dist $WEB_DIR/dist pkg
|
||||
|
||||
# Build Node.js target
|
||||
npx wasm-pack build \
|
||||
--target nodejs \
|
||||
--out-name index \
|
||||
--no-default-features \
|
||||
--features nodejs
|
||||
|
||||
rm -rf pkg/package.json # don't want generated package.json
|
||||
mv pkg/* $NODE_DIR/dist/
|
||||
cp -r $NODE_DIR/src/* $NODE_DIR/dist/
|
||||
cd node/dist/
|
||||
for f in *.js; do
|
||||
cp "$f" "${f%.js}.cjs"
|
||||
done
|
||||
rm *.js
|
||||
cd ../../
|
||||
rm -r pkg
|
||||
|
||||
# Build web target
|
||||
npx wasm-pack build \
|
||||
--target web \
|
||||
--out-name index \
|
||||
--no-default-features \
|
||||
--features web
|
||||
|
||||
rm -rf pkg/package.json # don't want generated package.json
|
||||
mv pkg/* $WEB_DIR/dist/
|
||||
cp -r $WEB_DIR/src/* $WEB_DIR/dist/
|
||||
|
||||
|
||||
# mv $WEB_DIR/index.js $WEB_DIR/index.mjs
|
||||
rm -rf pkg
|
||||
1
bindings/wasm/test-limbo-pkg/.gitignore
vendored
1
bindings/wasm/test-limbo-pkg/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
test.db
|
||||
@@ -1,11 +0,0 @@
|
||||
// import { Database } from "limbo-wasm/node";
|
||||
|
||||
const { Database } = require("limbo-wasm");
|
||||
// Rest of your code...
|
||||
|
||||
const db = new Database("test.db");
|
||||
db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
|
||||
db.exec("INSERT INTO users (name) VALUES ('test')");
|
||||
|
||||
const stmt = db.prepare("SELECT * FROM users");
|
||||
console.log(stmt.all());
|
||||
@@ -1,86 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Limbo Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
function waitForMessage(worker, type, op) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = (e) => {
|
||||
if (e.data.type === type && (!op || e.data.op === op)) {
|
||||
worker.removeEventListener('message', handler);
|
||||
resolve(e.data);
|
||||
} else if (e.data.type === 'error') {
|
||||
worker.removeEventListener('message', handler);
|
||||
reject(e.data.error);
|
||||
}
|
||||
};
|
||||
worker.addEventListener('message', handler);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
// const worker = new Worker('./src/limbo-worker.js', { type: 'module' });
|
||||
// const worker = new Worker('limbo-wasm/src/limbo-worker.js', { type: 'module' });
|
||||
// const worker = new Worker('./node_modules/limbo-wasm/src/limbo-worker.js', { type: 'module' });
|
||||
const worker = new Worker(new URL('limbo-wasm/limbo-worker.js', import.meta.url), { type: 'module' });
|
||||
|
||||
// Wait for ready then send createDb
|
||||
await waitForMessage(worker, 'ready');
|
||||
worker.postMessage({
|
||||
op: 'createDb',
|
||||
path: 'test.db'
|
||||
});
|
||||
|
||||
// Wait for createDb success then send exec
|
||||
await waitForMessage(worker, 'success', 'createDb');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
|
||||
`
|
||||
});
|
||||
console.log("made it here");
|
||||
|
||||
// Wait for exec success then send prepare
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (1, 'Alice', 'alice@example.org');
|
||||
`
|
||||
});
|
||||
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (2, 'Bob', 'bob@example.org');
|
||||
`
|
||||
});
|
||||
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (3, 'bill', 'bill@example.com');
|
||||
`
|
||||
});
|
||||
|
||||
// Wait for exec success then send prepare
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'prepare',
|
||||
sql: 'SELECT * FROM users;'
|
||||
});
|
||||
|
||||
const results = await waitForMessage(worker, 'result');
|
||||
console.log('Query results:', results);
|
||||
}
|
||||
|
||||
runTests().catch(console.error);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
962
bindings/wasm/test-limbo-pkg/package-lock.json
generated
962
bindings/wasm/test-limbo-pkg/package-lock.json
generated
@@ -1,962 +0,0 @@
|
||||
{
|
||||
"name": "test-limbo",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "test-limbo",
|
||||
"dependencies": {
|
||||
"limbo-wasm": ".."
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-wasm": "^3.4.1"
|
||||
}
|
||||
},
|
||||
"..": {},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
|
||||
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
|
||||
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
|
||||
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
|
||||
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
|
||||
"integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz",
|
||||
"integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz",
|
||||
"integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz",
|
||||
"integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz",
|
||||
"integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz",
|
||||
"integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz",
|
||||
"integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz",
|
||||
"integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz",
|
||||
"integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz",
|
||||
"integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
|
||||
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.2",
|
||||
"@esbuild/android-arm": "0.25.2",
|
||||
"@esbuild/android-arm64": "0.25.2",
|
||||
"@esbuild/android-x64": "0.25.2",
|
||||
"@esbuild/darwin-arm64": "0.25.2",
|
||||
"@esbuild/darwin-x64": "0.25.2",
|
||||
"@esbuild/freebsd-arm64": "0.25.2",
|
||||
"@esbuild/freebsd-x64": "0.25.2",
|
||||
"@esbuild/linux-arm": "0.25.2",
|
||||
"@esbuild/linux-arm64": "0.25.2",
|
||||
"@esbuild/linux-ia32": "0.25.2",
|
||||
"@esbuild/linux-loong64": "0.25.2",
|
||||
"@esbuild/linux-mips64el": "0.25.2",
|
||||
"@esbuild/linux-ppc64": "0.25.2",
|
||||
"@esbuild/linux-riscv64": "0.25.2",
|
||||
"@esbuild/linux-s390x": "0.25.2",
|
||||
"@esbuild/linux-x64": "0.25.2",
|
||||
"@esbuild/netbsd-arm64": "0.25.2",
|
||||
"@esbuild/netbsd-x64": "0.25.2",
|
||||
"@esbuild/openbsd-arm64": "0.25.2",
|
||||
"@esbuild/openbsd-x64": "0.25.2",
|
||||
"@esbuild/sunos-x64": "0.25.2",
|
||||
"@esbuild/win32-arm64": "0.25.2",
|
||||
"@esbuild/win32-ia32": "0.25.2",
|
||||
"@esbuild/win32-x64": "0.25.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/limbo-wasm": {
|
||||
"resolved": "..",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.30.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz",
|
||||
"integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.30.1",
|
||||
"@rollup/rollup-android-arm64": "4.30.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.30.1",
|
||||
"@rollup/rollup-darwin-x64": "4.30.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.30.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.30.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.30.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.30.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.30.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.30.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.30.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.30.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.30.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.30.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"postcss": "^8.5.3",
|
||||
"rollup": "^4.30.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-wasm": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.4.1.tgz",
|
||||
"integrity": "sha512-ja3nSo2UCkVeitltJGkS3pfQHAanHv/DqGatdI39ja6McgABlpsZ5hVgl6wuR8Qx5etY3T5qgDQhOWzc5RReZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vite": "^2 || ^3 || ^4 || ^5 || ^6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "test-limbo",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"limbo-wasm": ".."
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-wasm": "^3.4.1"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import wasm from "vite-plugin-wasm";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [wasm()],
|
||||
server: {
|
||||
headers: {
|
||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||
"Cross-Origin-Opener-Policy": "same-origin",
|
||||
"Cross-Origin-Resource-Policy": "cross-origin",
|
||||
},
|
||||
fs: {
|
||||
allow: ["../web/dist"],
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: "es",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<script type="module">
|
||||
import { VFSInterface } from './src/opfs-interface.js';
|
||||
window.VFSInterface = VFSInterface;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,83 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Limbo Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
function waitForMessage(worker, type, op) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = (e) => {
|
||||
if (e.data.type === type && (!op || e.data.op === op)) {
|
||||
worker.removeEventListener('message', handler);
|
||||
resolve(e.data);
|
||||
} else if (e.data.type === 'error') {
|
||||
worker.removeEventListener('message', handler);
|
||||
reject(e.data.error);
|
||||
}
|
||||
};
|
||||
worker.addEventListener('message', handler);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
const worker = new Worker('./src/limbo-worker.js', { type: 'module' });
|
||||
|
||||
// Wait for ready then send createDb
|
||||
await waitForMessage(worker, 'ready');
|
||||
worker.postMessage({
|
||||
op: 'createDb',
|
||||
path: 'test.db'
|
||||
});
|
||||
|
||||
// Wait for createDb success then send exec
|
||||
await waitForMessage(worker, 'success', 'createDb');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);
|
||||
`
|
||||
});
|
||||
console.log("made it here");
|
||||
|
||||
// Wait for exec success then send prepare
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (1, 'Alice', 'alice@example.org');
|
||||
`
|
||||
});
|
||||
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (2, 'Bob', 'bob@example.org');
|
||||
`
|
||||
});
|
||||
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'exec',
|
||||
sql: `
|
||||
INSERT INTO users VALUES (3, 'bill', 'bill@example.com');
|
||||
`
|
||||
});
|
||||
|
||||
// Wait for exec success then send prepare
|
||||
await waitForMessage(worker, 'success', 'exec');
|
||||
worker.postMessage({
|
||||
op: 'prepare',
|
||||
sql: 'SELECT * FROM users;'
|
||||
});
|
||||
|
||||
const results = await waitForMessage(worker, 'result');
|
||||
console.log('Query results:', results);
|
||||
}
|
||||
|
||||
runTests().catch(console.error);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Limbo Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
window.Worker = Worker;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +0,0 @@
|
||||
// Using Playwright (recommended)
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
// playwright.config.js
|
||||
export default {
|
||||
use: {
|
||||
headless: true,
|
||||
// Required for SharedArrayBuffer
|
||||
launchOptions: {
|
||||
args: ["--cross-origin-isolated"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { VFS } from "./opfs.js";
|
||||
import init, { Database } from "../dist/index.js";
|
||||
|
||||
let db = null;
|
||||
let currentStmt = null;
|
||||
|
||||
async function initVFS() {
|
||||
const vfs = new VFS();
|
||||
await vfs.ready;
|
||||
self.vfs = vfs;
|
||||
return vfs;
|
||||
}
|
||||
|
||||
async function initAll() {
|
||||
await initVFS();
|
||||
await init();
|
||||
}
|
||||
|
||||
initAll().then(() => {
|
||||
self.postMessage({ type: "ready" });
|
||||
|
||||
self.onmessage = (e) => {
|
||||
try {
|
||||
switch (e.data.op) {
|
||||
case "createDb": {
|
||||
db = new Database(e.data.path);
|
||||
self.postMessage({ type: "success", op: "createDb" });
|
||||
break;
|
||||
}
|
||||
case "exec": {
|
||||
log(e.data.sql);
|
||||
db.exec(e.data.sql);
|
||||
self.postMessage({ type: "success", op: "exec" });
|
||||
break;
|
||||
}
|
||||
case "prepare": {
|
||||
currentStmt = db.prepare(e.data.sql);
|
||||
const results = currentStmt.raw().all();
|
||||
self.postMessage({ type: "result", result: results });
|
||||
break;
|
||||
}
|
||||
case "get": {
|
||||
const row = currentStmt?.raw().get();
|
||||
self.postMessage({ type: "result", result: row });
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
self.postMessage({ type: "error", error: err.toString() });
|
||||
}
|
||||
};
|
||||
}).catch((error) => {
|
||||
self.postMessage({ type: "error", error: error.toString() });
|
||||
});
|
||||
|
||||
// logLevel:
|
||||
//
|
||||
// 0 = no logging output
|
||||
// 1 = only errors
|
||||
// 2 = warnings and errors
|
||||
// 3 = debug, warnings, and errors
|
||||
const logLevel = 1;
|
||||
|
||||
const loggers = {
|
||||
0: console.error.bind(console),
|
||||
1: console.warn.bind(console),
|
||||
2: console.log.bind(console),
|
||||
};
|
||||
const logImpl = (level, ...args) => {
|
||||
if (logLevel > level) loggers[level]("OPFS asyncer:", ...args);
|
||||
};
|
||||
const log = (...args) => logImpl(2, ...args);
|
||||
const warn = (...args) => logImpl(1, ...args);
|
||||
const error = (...args) => logImpl(0, ...args);
|
||||
@@ -1,67 +0,0 @@
|
||||
export class VFSInterface {
|
||||
constructor(workerUrl) {
|
||||
this.worker = new Worker(workerUrl, { type: "module" });
|
||||
this.nextMessageId = 1;
|
||||
this.pendingRequests = new Map();
|
||||
|
||||
this.worker.onmessage = (event) => {
|
||||
console.log("interface onmessage: ", event.data);
|
||||
let { id, result, error } = event.data;
|
||||
const resolver = this.pendingRequests.get(id);
|
||||
if (event.data?.buffer && event.data?.size) {
|
||||
result = { size: event.data.size, buffer: event.data.buffer };
|
||||
}
|
||||
|
||||
if (resolver) {
|
||||
this.pendingRequests.delete(id);
|
||||
if (error) {
|
||||
resolver.reject(new Error(error));
|
||||
} else {
|
||||
resolver.resolve(result);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_sendMessage(method, args) {
|
||||
const id = this.nextMessageId++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pendingRequests.set(id, { resolve, reject });
|
||||
this.worker.postMessage({ id, method, args });
|
||||
});
|
||||
}
|
||||
|
||||
async open(path, flags) {
|
||||
return await this._sendMessage("open", { path, flags });
|
||||
}
|
||||
|
||||
async close(fd) {
|
||||
return await this._sendMessage("close", { fd });
|
||||
}
|
||||
|
||||
async pwrite(fd, buffer, offset) {
|
||||
return await this._sendMessage("pwrite", { fd, buffer, offset }, [
|
||||
buffer.buffer,
|
||||
]);
|
||||
}
|
||||
|
||||
async pread(fd, buffer, offset) {
|
||||
console.log("interface in buffer: ", [...buffer]);
|
||||
const result = await this._sendMessage("pread", {
|
||||
fd,
|
||||
buffer: buffer,
|
||||
offset,
|
||||
}, []);
|
||||
console.log("interface out buffer: ", [...buffer]);
|
||||
buffer.set(new Uint8Array(result.buffer));
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
async size(fd) {
|
||||
return await this._sendMessage("size", { fd });
|
||||
}
|
||||
|
||||
async sync(fd) {
|
||||
return await this._sendMessage("sync", { fd });
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// opfs-sync-proxy.js
|
||||
let transferBuffer, statusBuffer, statusArray, statusView;
|
||||
let transferArray;
|
||||
let rootDir = null;
|
||||
const handles = new Map();
|
||||
let nextFd = 1;
|
||||
|
||||
self.postMessage("ready");
|
||||
|
||||
onmessage = async (e) => {
|
||||
log("handle message: ", e.data);
|
||||
if (e.data.cmd === "init") {
|
||||
log("init");
|
||||
transferBuffer = e.data.transferBuffer;
|
||||
statusBuffer = e.data.statusBuffer;
|
||||
|
||||
transferArray = new Uint8Array(transferBuffer);
|
||||
statusArray = new Int32Array(statusBuffer);
|
||||
statusView = new DataView(statusBuffer);
|
||||
|
||||
self.postMessage("done");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await handleCommand(e.data);
|
||||
sendResult(result);
|
||||
};
|
||||
|
||||
self.onerror = (error) => {
|
||||
console.error("opfssync error: ", error);
|
||||
// Don't close, keep running
|
||||
return true; // Prevents default error handling
|
||||
};
|
||||
|
||||
function handleCommand(msg) {
|
||||
log(`handle message: ${msg.cmd}`);
|
||||
switch (msg.cmd) {
|
||||
case "open":
|
||||
return handleOpen(msg.path);
|
||||
case "close":
|
||||
return handleClose(msg.fd);
|
||||
case "read":
|
||||
return handleRead(msg.fd, msg.offset, msg.size);
|
||||
case "write":
|
||||
return handleWrite(msg.fd, msg.buffer, msg.offset);
|
||||
case "size":
|
||||
return handleSize(msg.fd);
|
||||
case "sync":
|
||||
return handleSync(msg.fd);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOpen(path) {
|
||||
if (!rootDir) {
|
||||
rootDir = await navigator.storage.getDirectory();
|
||||
}
|
||||
const fd = nextFd++;
|
||||
|
||||
const handle = await rootDir.getFileHandle(path, { create: true });
|
||||
const syncHandle = await handle.createSyncAccessHandle();
|
||||
|
||||
handles.set(fd, syncHandle);
|
||||
return { fd };
|
||||
}
|
||||
|
||||
function handleClose(fd) {
|
||||
const handle = handles.get(fd);
|
||||
handle.close();
|
||||
handles.delete(fd);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
function handleRead(fd, offset, size) {
|
||||
const handle = handles.get(fd);
|
||||
const readBuffer = new ArrayBuffer(size);
|
||||
const readSize = handle.read(readBuffer, { at: offset });
|
||||
log("opfssync read: size: ", readBuffer.byteLength);
|
||||
|
||||
const tmp = new Uint8Array(readBuffer);
|
||||
log("opfssync read buffer: ", [...tmp]);
|
||||
|
||||
transferArray.set(tmp);
|
||||
|
||||
return { success: true, length: readSize };
|
||||
}
|
||||
|
||||
function handleWrite(fd, buffer, offset) {
|
||||
log("opfssync buffer size:", buffer.byteLength);
|
||||
log("opfssync write buffer: ", [...buffer]);
|
||||
const handle = handles.get(fd);
|
||||
const size = handle.write(buffer, { at: offset });
|
||||
return { success: true, length: size };
|
||||
}
|
||||
|
||||
function handleSize(fd) {
|
||||
const handle = handles.get(fd);
|
||||
return { success: true, length: handle.getSize() };
|
||||
}
|
||||
|
||||
function handleSync(fd) {
|
||||
const handle = handles.get(fd);
|
||||
handle.flush();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
function sendResult(result) {
|
||||
if (result?.fd) {
|
||||
statusView.setInt32(4, result.fd, true);
|
||||
} else {
|
||||
log("opfs-sync-proxy: result.length: ", result.length);
|
||||
statusView.setInt32(4, result?.length || 0, true);
|
||||
}
|
||||
|
||||
Atomics.store(statusArray, 0, 1);
|
||||
Atomics.notify(statusArray, 0);
|
||||
}
|
||||
|
||||
// logLevel:
|
||||
//
|
||||
// 0 = no logging output
|
||||
// 1 = only errors
|
||||
// 2 = warnings and errors
|
||||
// 3 = debug, warnings, and errors
|
||||
const logLevel = 1;
|
||||
|
||||
const loggers = {
|
||||
0: console.error.bind(console),
|
||||
1: console.warn.bind(console),
|
||||
2: console.log.bind(console),
|
||||
};
|
||||
const logImpl = (level, ...args) => {
|
||||
if (logLevel > level) loggers[level]("OPFS asyncer:", ...args);
|
||||
};
|
||||
const log = (...args) => logImpl(2, ...args);
|
||||
const warn = (...args) => logImpl(1, ...args);
|
||||
const error = (...args) => logImpl(0, ...args);
|
||||
@@ -1,57 +0,0 @@
|
||||
import { VFS } from "./opfs.js";
|
||||
|
||||
const vfs = new VFS();
|
||||
|
||||
onmessage = async function (e) {
|
||||
if (!vfs.isReady) {
|
||||
console.log("opfs ready: ", vfs.isReady);
|
||||
await vfs.ready;
|
||||
console.log("opfs ready: ", vfs.isReady);
|
||||
}
|
||||
|
||||
const { id, method, args } = e.data;
|
||||
|
||||
console.log(`interface onmessage method: ${method}`);
|
||||
try {
|
||||
let result;
|
||||
switch (method) {
|
||||
case "open":
|
||||
result = vfs.open(args.path, args.flags);
|
||||
break;
|
||||
case "close":
|
||||
result = vfs.close(args.fd);
|
||||
break;
|
||||
case "pread": {
|
||||
const buffer = new Uint8Array(args.buffer);
|
||||
result = vfs.pread(args.fd, buffer, args.offset);
|
||||
self.postMessage(
|
||||
{ id, size: result, error: null, buffer },
|
||||
);
|
||||
console.log("read size: ", result);
|
||||
console.log("read buffer: ", [...buffer]);
|
||||
return;
|
||||
}
|
||||
case "pwrite": {
|
||||
result = vfs.pwrite(args.fd, args.buffer, args.offset);
|
||||
console.log("write size: ", result);
|
||||
break;
|
||||
}
|
||||
case "size":
|
||||
result = vfs.size(args.fd);
|
||||
break;
|
||||
case "sync":
|
||||
result = vfs.sync(args.fd);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
}
|
||||
|
||||
self.postMessage(
|
||||
{ id, result, error: null },
|
||||
);
|
||||
} catch (error) {
|
||||
self.postMessage({ id, result: null, error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
console.log("opfs-worker.js");
|
||||
@@ -1,154 +0,0 @@
|
||||
// First file: VFS class
|
||||
class VFS {
|
||||
constructor() {
|
||||
this.transferBuffer = new SharedArrayBuffer(1024 * 1024); // 1mb
|
||||
this.statusBuffer = new SharedArrayBuffer(8); // Room for status + size
|
||||
|
||||
this.statusArray = new Int32Array(this.statusBuffer);
|
||||
this.statusView = new DataView(this.statusBuffer);
|
||||
|
||||
this.worker = new Worker(
|
||||
new URL("./opfs-sync-proxy.js", import.meta.url),
|
||||
{ type: "module" },
|
||||
);
|
||||
|
||||
this.isReady = false;
|
||||
this.ready = new Promise((resolve, reject) => {
|
||||
this.worker.addEventListener("message", async (e) => {
|
||||
if (e.data === "ready") {
|
||||
await this.initWorker();
|
||||
this.isReady = true;
|
||||
resolve();
|
||||
}
|
||||
}, { once: true });
|
||||
this.worker.addEventListener("error", reject, { once: true });
|
||||
});
|
||||
|
||||
this.worker.onerror = (e) => {
|
||||
console.error("Sync proxy worker error:", e.message);
|
||||
};
|
||||
}
|
||||
|
||||
initWorker() {
|
||||
return new Promise((resolve) => {
|
||||
this.worker.addEventListener("message", (e) => {
|
||||
log("eventListener: ", e.data);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
|
||||
this.worker.postMessage({
|
||||
cmd: "init",
|
||||
transferBuffer: this.transferBuffer,
|
||||
statusBuffer: this.statusBuffer,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
open(path) {
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({ cmd: "open", path });
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
|
||||
const result = this.statusView.getInt32(4, true);
|
||||
log("opfs.js open result: ", result);
|
||||
log("opfs.js open result type: ", typeof result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
close(fd) {
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({ cmd: "close", fd });
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
pread(fd, buffer, offset) {
|
||||
let bytesRead = 0;
|
||||
|
||||
while (bytesRead < buffer.byteLength) {
|
||||
const chunkSize = Math.min(
|
||||
this.transferBuffer.byteLength,
|
||||
buffer.byteLength - bytesRead,
|
||||
);
|
||||
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({
|
||||
cmd: "read",
|
||||
fd,
|
||||
offset: offset + bytesRead,
|
||||
size: chunkSize,
|
||||
});
|
||||
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
const readSize = this.statusView.getInt32(4, true);
|
||||
buffer.set(
|
||||
new Uint8Array(this.transferBuffer, 0, readSize),
|
||||
bytesRead,
|
||||
);
|
||||
log("opfs pread buffer: ", [...buffer]);
|
||||
|
||||
bytesRead += readSize;
|
||||
if (readSize < chunkSize) break;
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
pwrite(fd, buffer, offset) {
|
||||
log("write buffer size: ", buffer.byteLength);
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({
|
||||
cmd: "write",
|
||||
fd,
|
||||
buffer: buffer,
|
||||
offset: offset,
|
||||
});
|
||||
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
log(
|
||||
"opfs pwrite length statusview: ",
|
||||
this.statusView.getInt32(4, true),
|
||||
);
|
||||
return this.statusView.getInt32(4, true);
|
||||
}
|
||||
|
||||
size(fd) {
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({ cmd: "size", fd });
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
|
||||
const result = this.statusView.getInt32(4, true);
|
||||
log("opfs.js size result: ", result);
|
||||
log("opfs.js size result type: ", typeof result);
|
||||
return BigInt(result);
|
||||
}
|
||||
|
||||
sync(fd) {
|
||||
Atomics.store(this.statusArray, 0, 0);
|
||||
this.worker.postMessage({ cmd: "sync", fd });
|
||||
Atomics.wait(this.statusArray, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// logLevel:
|
||||
//
|
||||
// 0 = no logging output
|
||||
// 1 = only errors
|
||||
// 2 = warnings and errors
|
||||
// 3 = debug, warnings, and errors
|
||||
const logLevel = 1;
|
||||
|
||||
const loggers = {
|
||||
0: console.error.bind(console),
|
||||
1: console.warn.bind(console),
|
||||
2: console.log.bind(console),
|
||||
};
|
||||
const logImpl = (level, ...args) => {
|
||||
if (logLevel > level) loggers[level]("OPFS asyncer:", ...args);
|
||||
};
|
||||
const log = (...args) => logImpl(2, ...args);
|
||||
const warn = (...args) => logImpl(1, ...args);
|
||||
const error = (...args) => logImpl(0, ...args);
|
||||
|
||||
export { VFS };
|
||||
@@ -1,29 +0,0 @@
|
||||
export class VFS {
|
||||
constructor() {
|
||||
return self.vfs;
|
||||
}
|
||||
|
||||
open(path, flags) {
|
||||
return self.vfs.open(path);
|
||||
}
|
||||
|
||||
close(fd) {
|
||||
return self.vfs.close(fd);
|
||||
}
|
||||
|
||||
pread(fd, buffer, offset) {
|
||||
return self.vfs.pread(fd, buffer, offset);
|
||||
}
|
||||
|
||||
pwrite(fd, buffer, offset) {
|
||||
return self.vfs.pwrite(fd, buffer, offset);
|
||||
}
|
||||
|
||||
size(fd) {
|
||||
return self.vfs.size(fd);
|
||||
}
|
||||
|
||||
sync(fd) {
|
||||
return self.vfs.sync(fd);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { createServer } from "vite";
|
||||
import { chromium } from "playwright";
|
||||
|
||||
export async function setupTestEnvironment(port) {
|
||||
const server = await createServer({
|
||||
configFile: "./vite.config.js",
|
||||
root: ".",
|
||||
server: { port },
|
||||
});
|
||||
await server.listen();
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
globalThis.__page__ = page;
|
||||
|
||||
return { server, browser, context, page };
|
||||
}
|
||||
|
||||
export async function teardownTestEnvironment({ server, browser, context }) {
|
||||
await context.close();
|
||||
await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from "vitest";
|
||||
import { setupTestEnvironment, teardownTestEnvironment } from "./helpers.js";
|
||||
|
||||
let testEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await setupTestEnvironment(5174);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const { page } = testEnv;
|
||||
await page.goto("http://localhost:5174/limbo-test.html");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestEnvironment(testEnv);
|
||||
});
|
||||
|
||||
test("basic database operations", async () => {
|
||||
const { page } = testEnv;
|
||||
const result = await page.evaluate(async () => {
|
||||
const worker = new Worker("./src/limbo-worker.js", { type: "module" });
|
||||
|
||||
const waitForMessage = (type, op) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const handler = (e) => {
|
||||
if (e.data.type === type && (!op || e.data.op === op)) {
|
||||
worker.removeEventListener("message", handler);
|
||||
resolve(e.data);
|
||||
} else if (e.data.type === "error") {
|
||||
worker.removeEventListener("message", handler);
|
||||
reject(e.data.error);
|
||||
}
|
||||
};
|
||||
worker.addEventListener("message", handler);
|
||||
});
|
||||
|
||||
try {
|
||||
await waitForMessage("ready");
|
||||
worker.postMessage({ op: "createDb", path: "test.db" });
|
||||
await waitForMessage("success", "createDb");
|
||||
|
||||
worker.postMessage({
|
||||
op: "exec",
|
||||
sql:
|
||||
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);",
|
||||
});
|
||||
await waitForMessage("success", "exec");
|
||||
|
||||
worker.postMessage({
|
||||
op: "exec",
|
||||
sql: "INSERT INTO users VALUES (1, 'Alice', 'alice@example.org');",
|
||||
});
|
||||
await waitForMessage("success", "exec");
|
||||
|
||||
worker.postMessage({
|
||||
op: "prepare",
|
||||
sql: "SELECT * FROM users;",
|
||||
});
|
||||
|
||||
const results = await waitForMessage("result");
|
||||
return results;
|
||||
} catch (error) {
|
||||
return { error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(`Test failed: ${result.error}`);
|
||||
expect(result.result).toHaveLength(1);
|
||||
expect(result.result[0]).toEqual([1, "Alice", "alice@example.org"]);
|
||||
});
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// test/opfs.test.js
|
||||
import { afterAll, beforeAll, beforeEach, expect, test } from "vitest";
|
||||
import { setupTestEnvironment, teardownTestEnvironment } from "./helpers.js";
|
||||
|
||||
let testEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
testEnv = await setupTestEnvironment(5173);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const { page } = testEnv;
|
||||
await page.goto("http://localhost:5173/index.html");
|
||||
await page.waitForFunction(() => window.VFSInterface !== undefined);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teardownTestEnvironment(testEnv);
|
||||
});
|
||||
|
||||
const opfsImport = "./src/opfs-worker.js";
|
||||
|
||||
test("basic read/write functionality", async () => {
|
||||
const { page } = testEnv;
|
||||
const result = await page.evaluate(async () => {
|
||||
const vfs = new window.VFSInterface("./src/opfs-worker.js");
|
||||
let fd;
|
||||
try {
|
||||
fd = await vfs.open("test.txt", {});
|
||||
const writeData = new Uint8Array([1, 2, 3, 4]);
|
||||
const bytesWritten = await vfs.pwrite(fd, writeData, 0);
|
||||
const readData = new Uint8Array(4);
|
||||
const bytesRead = await vfs.pread(fd, readData, 0);
|
||||
await vfs.close(fd);
|
||||
return { fd, bytesWritten, bytesRead, readData: Array.from(readData) };
|
||||
} catch (error) {
|
||||
if (fd !== undefined) await vfs.close(fd);
|
||||
return { error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(`Test failed: ${result.error}`);
|
||||
expect(result.fd).toBe(1);
|
||||
expect(result.bytesWritten).toBe(4);
|
||||
expect(result.bytesRead).toBe(4);
|
||||
expect(result.readData).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
test("larger data read/write", async () => {
|
||||
const { page } = testEnv;
|
||||
const result = await page.evaluate(async () => {
|
||||
const vfs = new window.VFSInterface("./src/opfs-worker.js");
|
||||
let fd;
|
||||
try {
|
||||
fd = await vfs.open("large.txt", {});
|
||||
const writeData = new Uint8Array(1024).map((_, i) => i % 256);
|
||||
const bytesWritten = await vfs.pwrite(fd, writeData, 0);
|
||||
const readData = new Uint8Array(1024);
|
||||
const bytesRead = await vfs.pread(fd, readData, 0);
|
||||
await vfs.close(fd);
|
||||
return { bytesWritten, bytesRead, readData: Array.from(readData) };
|
||||
} catch (error) {
|
||||
if (fd !== undefined) await vfs.close(fd);
|
||||
return { error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(`Test failed: ${result.error}`);
|
||||
expect(result.bytesWritten).toBe(1024);
|
||||
expect(result.bytesRead).toBe(1024);
|
||||
expect(result.readData).toEqual(
|
||||
Array.from({ length: 1024 }, (_, i) => i % 256),
|
||||
);
|
||||
});
|
||||
|
||||
test("partial reads and writes", async () => {
|
||||
const { page } = testEnv;
|
||||
const result = await page.evaluate(async () => {
|
||||
const vfs = new window.VFSInterface("./src/opfs-worker.js");
|
||||
let fd;
|
||||
try {
|
||||
fd = await vfs.open("partial.txt", {});
|
||||
|
||||
const writeData1 = new Uint8Array([1, 2, 3, 4]);
|
||||
const writeData2 = new Uint8Array([5, 6, 7, 8]);
|
||||
await vfs.pwrite(fd, writeData1, 0);
|
||||
await vfs.pwrite(fd, writeData2, 4);
|
||||
|
||||
const readData1 = new Uint8Array(2);
|
||||
const readData2 = new Uint8Array(4);
|
||||
const readData3 = new Uint8Array(2);
|
||||
|
||||
await vfs.pread(fd, readData1, 0);
|
||||
await vfs.pread(fd, readData2, 2);
|
||||
await vfs.pread(fd, readData3, 6);
|
||||
|
||||
await vfs.close(fd);
|
||||
return {
|
||||
readData1: Array.from(readData1),
|
||||
readData2: Array.from(readData2),
|
||||
readData3: Array.from(readData3),
|
||||
};
|
||||
} catch (error) {
|
||||
if (fd !== undefined) await vfs.close(fd);
|
||||
return { error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(`Test failed: ${result.error}`);
|
||||
expect(result.readData1).toEqual([1, 2]);
|
||||
expect(result.readData2).toEqual([3, 4, 5, 6]);
|
||||
expect(result.readData3).toEqual([7, 8]);
|
||||
});
|
||||
|
||||
test("file size operations", async () => {
|
||||
const { page } = testEnv;
|
||||
const result = await page.evaluate(async () => {
|
||||
const vfs = new window.VFSInterface("./src/opfs-worker.js");
|
||||
let fd;
|
||||
try {
|
||||
fd = await vfs.open("size.txt", {});
|
||||
const writeData1 = new Uint8Array([1, 2, 3, 4]);
|
||||
await vfs.pwrite(fd, writeData1, 0);
|
||||
const size1 = await vfs.size(fd);
|
||||
|
||||
const writeData2 = new Uint8Array([5, 6, 7, 8]);
|
||||
await vfs.pwrite(fd, writeData2, 4);
|
||||
const size2 = await vfs.size(fd);
|
||||
|
||||
await vfs.close(fd);
|
||||
return { size1, size2 };
|
||||
} catch (error) {
|
||||
if (fd !== undefined) await vfs.close(fd);
|
||||
return { error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(`Test failed: ${result.error}`);
|
||||
expect(Number(result.size1)).toBe(4);
|
||||
expect(Number(result.size2)).toBe(8);
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import wasm from "vite-plugin-wasm";
|
||||
|
||||
export default defineConfig({
|
||||
publicDir: "./html",
|
||||
root: "./",
|
||||
plugins: [wasm()],
|
||||
test: {
|
||||
globals: true,
|
||||
setupFiles: ["./test/setup.js"],
|
||||
include: ["test/*.test.js"],
|
||||
},
|
||||
server: {
|
||||
headers: {
|
||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||
"Cross-Origin-Opener-Policy": "same-origin",
|
||||
"Cross-Origin-Resource-Policy": "cross-origin",
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
format: "es",
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: "es",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -20,7 +20,7 @@ NPM_PACKAGES = [
|
||||
"bindings/javascript/npm/darwin-universal",
|
||||
"bindings/javascript/npm/linux-x64-gnu",
|
||||
"bindings/javascript/npm/win32-x64-msvc",
|
||||
"bindings/wasm",
|
||||
"bindings/javascript/npm/wasm32-wasip1-threads",
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user