├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── release.yaml │ ├── rust.yml │ └── security-audit.yml ├── .gitignore ├── .kodiak.toml ├── .pre-commit-config.yaml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile.toml ├── README.md ├── Web.toml ├── build.rs ├── rust-toolchain ├── setup.rs ├── src ├── bin.rs ├── bindings.rs ├── db.rs ├── jse.rs ├── reexporter.cpp ├── rendering.rs ├── state.rs ├── tests.rs └── types.rs ├── static └── index.html └── vercel.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - main 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | 13 | jobs: 14 | update_release_draft: 15 | runs-on: ubuntu-latest 16 | steps: 17 | # (Optional) GitHub Enterprise requires GHE_HOST variable set 18 | #- name: Set GHE_HOST 19 | # run: | 20 | # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV 21 | 22 | # Drafts your next Release notes as Pull Requests are merged into "master" 23 | - uses: release-drafter/release-drafter@v5 24 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 25 | # with: 26 | # config-name: my-config.yml 27 | # disable-autolabeler: true 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: push 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | PROD_DEPLOY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | needs: ['build'] 13 | steps: 14 | - uses: actions/download-artifact@v2 15 | with: 16 | name: deploy 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: '14' 20 | - run: npm i -g vercel 21 | - run: vercel link --confirm --token ${{ secrets.VERCEL_TOKEN }} 22 | - if: ${{ env.PROD_DEPLOY == 'false' }} 23 | run: vercel deploy . --token ${{ secrets.VERCEL_TOKEN }} 24 | - if: ${{ env.PROD_DEPLOY == 'true' }} 25 | run: vercel deploy . --token ${{ secrets.VERCEL_TOKEN }} --prod 26 | 27 | build: 28 | runs-on: ubuntu-latest 29 | container: 30 | image: emscripten/emsdk:2.0.15 31 | 32 | steps: 33 | - uses: actions/checkout@v2 34 | - name: Install latest nightly 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: nightly-2021-03-25 38 | override: true 39 | components: rustfmt, clippy 40 | target: wasm32-unknown-emscripten 41 | - name: Cache .cargo 42 | id: cache-primes 43 | uses: actions/cache@v2 44 | with: 45 | path: ~/.cargo 46 | key: ${{ runner.os }}-${{ hashFiles('Cargo**') }} 47 | - name: Install rust script 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: install 51 | args: cargo-web 52 | use-tool-cache: true 53 | - name: Install deps 54 | run: | 55 | sudo apt update 56 | sudo apt-get install pkg-config build-essential libssl-dev libclang1 libclang-dev gcc-multilib --yes 57 | - uses: davidB/rust-cargo-make@v1 58 | with: 59 | version: 0.32.16 60 | - name: Run CI 61 | uses: actions-rs/cargo@v1 62 | with: 63 | command: make 64 | args: deploy 65 | - uses: actions-rs/clippy-check@v1 66 | with: 67 | token: ${{ secrets.GITHUB_TOKEN }} 68 | args: --all-features --target wasm32-unknown-emscripten 69 | - uses: actions/upload-artifact@v2 70 | with: 71 | name: deploy 72 | path: target/deploy 73 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: actions-rs/audit-check@v1 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.zip 3 | *.wasm 4 | *.log 5 | node_modules 6 | *.a 7 | *.o 8 | 9 | .vercel 10 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | # Minimal config. version is the only required field. 3 | 4 | version = 1 5 | 6 | [merge] 7 | delete_branch_on_merge = true 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/doublify/pre-commit-rust 3 | rev: v1.0 4 | hooks: 5 | - id: fmt 6 | # - id: cargo-check 7 | # - id: clippy 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v3.4.0 10 | hooks: 11 | - id: check-yaml 12 | - id: check-json 13 | - id: check-merge-conflict 14 | - id: debug-statements 15 | - id: check-case-conflict 16 | - id: check-toml 17 | - id: end-of-file-fixer 18 | - id: trailing-whitespace 19 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks 20 | rev: f7d063b 21 | hooks: 22 | - id: pretty-format-toml 23 | args: [--autofix] 24 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.15" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.11.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "bindgen" 36 | version = "0.58.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" 39 | dependencies = [ 40 | "bitflags", 41 | "cexpr", 42 | "clang-sys", 43 | "clap", 44 | "env_logger", 45 | "lazy_static", 46 | "lazycell", 47 | "log", 48 | "peeking_take_while", 49 | "proc-macro2 1.0.24", 50 | "quote 1.0.9", 51 | "regex", 52 | "rustc-hash", 53 | "shlex", 54 | "which 3.1.1", 55 | ] 56 | 57 | [[package]] 58 | name = "bitflags" 59 | version = "1.2.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 62 | 63 | [[package]] 64 | name = "byteorder" 65 | version = "1.4.3" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 68 | 69 | [[package]] 70 | name = "cc" 71 | version = "1.0.68" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" 74 | dependencies = [ 75 | "jobserver", 76 | ] 77 | 78 | [[package]] 79 | name = "cexpr" 80 | version = "0.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 83 | dependencies = [ 84 | "nom", 85 | ] 86 | 87 | [[package]] 88 | name = "cfg-if" 89 | version = "1.0.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 92 | 93 | [[package]] 94 | name = "clang-sys" 95 | version = "1.2.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" 98 | dependencies = [ 99 | "glob", 100 | "libc", 101 | "libloading", 102 | ] 103 | 104 | [[package]] 105 | name = "clap" 106 | version = "2.33.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 109 | dependencies = [ 110 | "ansi_term", 111 | "atty", 112 | "bitflags", 113 | "strsim", 114 | "textwrap", 115 | "unicode-width", 116 | "vec_map", 117 | ] 118 | 119 | [[package]] 120 | name = "convert_case" 121 | version = "0.4.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 124 | 125 | [[package]] 126 | name = "count_tts" 127 | version = "0.2.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "809b88cb47b0327f505fc39f5415e4ab02629d2368d9eadac18d7012c5a40bb2" 130 | 131 | [[package]] 132 | name = "cssparser" 133 | version = "0.27.2" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 136 | dependencies = [ 137 | "cssparser-macros", 138 | "dtoa-short", 139 | "itoa", 140 | "matches", 141 | "phf", 142 | "proc-macro2 1.0.24", 143 | "quote 1.0.9", 144 | "smallvec", 145 | "syn 1.0.67", 146 | ] 147 | 148 | [[package]] 149 | name = "cssparser-macros" 150 | version = "0.6.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" 153 | dependencies = [ 154 | "quote 1.0.9", 155 | "syn 1.0.67", 156 | ] 157 | 158 | [[package]] 159 | name = "derive_more" 160 | version = "0.99.13" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "f82b1b72f1263f214c0f823371768776c4f5841b942c9883aa8e5ec584fd0ba6" 163 | dependencies = [ 164 | "convert_case", 165 | "proc-macro2 1.0.24", 166 | "quote 1.0.9", 167 | "syn 1.0.67", 168 | ] 169 | 170 | [[package]] 171 | name = "dirs-next" 172 | version = "2.0.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 175 | dependencies = [ 176 | "cfg-if", 177 | "dirs-sys-next", 178 | ] 179 | 180 | [[package]] 181 | name = "dirs-sys-next" 182 | version = "0.1.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 185 | dependencies = [ 186 | "libc", 187 | "redox_users", 188 | "winapi", 189 | ] 190 | 191 | [[package]] 192 | name = "dtoa" 193 | version = "0.4.8" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 196 | 197 | [[package]] 198 | name = "dtoa-short" 199 | version = "0.3.3" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" 202 | dependencies = [ 203 | "dtoa", 204 | ] 205 | 206 | [[package]] 207 | name = "either" 208 | version = "1.6.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 211 | 212 | [[package]] 213 | name = "env_logger" 214 | version = "0.8.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" 217 | dependencies = [ 218 | "atty", 219 | "humantime", 220 | "log", 221 | "regex", 222 | "termcolor", 223 | ] 224 | 225 | [[package]] 226 | name = "futf" 227 | version = "0.1.4" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" 230 | dependencies = [ 231 | "mac", 232 | "new_debug_unreachable", 233 | ] 234 | 235 | [[package]] 236 | name = "fxhash" 237 | version = "0.2.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 240 | dependencies = [ 241 | "byteorder", 242 | ] 243 | 244 | [[package]] 245 | name = "getrandom" 246 | version = "0.1.16" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 249 | dependencies = [ 250 | "cfg-if", 251 | "libc", 252 | "wasi 0.9.0+wasi-snapshot-preview1", 253 | ] 254 | 255 | [[package]] 256 | name = "getrandom" 257 | version = "0.2.2" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 260 | dependencies = [ 261 | "cfg-if", 262 | "libc", 263 | "wasi 0.10.2+wasi-snapshot-preview1", 264 | ] 265 | 266 | [[package]] 267 | name = "glob" 268 | version = "0.3.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 271 | 272 | [[package]] 273 | name = "heck" 274 | version = "0.3.2" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" 277 | dependencies = [ 278 | "unicode-segmentation", 279 | ] 280 | 281 | [[package]] 282 | name = "hermit-abi" 283 | version = "0.1.18" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 286 | dependencies = [ 287 | "libc", 288 | ] 289 | 290 | [[package]] 291 | name = "html5ever" 292 | version = "0.25.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" 295 | dependencies = [ 296 | "log", 297 | "mac", 298 | "markup5ever", 299 | "proc-macro2 1.0.24", 300 | "quote 1.0.9", 301 | "syn 1.0.67", 302 | ] 303 | 304 | [[package]] 305 | name = "humantime" 306 | version = "2.1.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 309 | 310 | [[package]] 311 | name = "itoa" 312 | version = "0.4.7" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 315 | 316 | [[package]] 317 | name = "jobserver" 318 | version = "0.1.21" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" 321 | dependencies = [ 322 | "libc", 323 | ] 324 | 325 | [[package]] 326 | name = "kuchiki" 327 | version = "0.8.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" 330 | dependencies = [ 331 | "cssparser", 332 | "html5ever", 333 | "matches", 334 | "selectors", 335 | ] 336 | 337 | [[package]] 338 | name = "lazy_static" 339 | version = "1.4.0" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 342 | 343 | [[package]] 344 | name = "lazycell" 345 | version = "1.3.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 348 | 349 | [[package]] 350 | name = "libc" 351 | version = "0.2.97" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" 354 | 355 | [[package]] 356 | name = "libloading" 357 | version = "0.7.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" 360 | dependencies = [ 361 | "cfg-if", 362 | "winapi", 363 | ] 364 | 365 | [[package]] 366 | name = "log" 367 | version = "0.4.14" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 370 | dependencies = [ 371 | "cfg-if", 372 | ] 373 | 374 | [[package]] 375 | name = "mac" 376 | version = "0.1.1" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 379 | 380 | [[package]] 381 | name = "markup5ever" 382 | version = "0.10.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" 385 | dependencies = [ 386 | "log", 387 | "phf", 388 | "phf_codegen", 389 | "serde", 390 | "serde_derive", 391 | "serde_json", 392 | "string_cache", 393 | "string_cache_codegen", 394 | "tendril", 395 | ] 396 | 397 | [[package]] 398 | name = "matches" 399 | version = "0.1.8" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 402 | 403 | [[package]] 404 | name = "memchr" 405 | version = "2.3.4" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 408 | 409 | [[package]] 410 | name = "new_debug_unreachable" 411 | version = "1.0.4" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 414 | 415 | [[package]] 416 | name = "nodrop" 417 | version = "0.1.14" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 420 | 421 | [[package]] 422 | name = "nom" 423 | version = "5.1.2" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 426 | dependencies = [ 427 | "memchr", 428 | "version_check", 429 | ] 430 | 431 | [[package]] 432 | name = "peeking_take_while" 433 | version = "0.1.2" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 436 | 437 | [[package]] 438 | name = "phf" 439 | version = "0.8.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 442 | dependencies = [ 443 | "phf_macros", 444 | "phf_shared", 445 | "proc-macro-hack", 446 | ] 447 | 448 | [[package]] 449 | name = "phf_codegen" 450 | version = "0.8.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 453 | dependencies = [ 454 | "phf_generator", 455 | "phf_shared", 456 | ] 457 | 458 | [[package]] 459 | name = "phf_generator" 460 | version = "0.8.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 463 | dependencies = [ 464 | "phf_shared", 465 | "rand", 466 | ] 467 | 468 | [[package]] 469 | name = "phf_macros" 470 | version = "0.8.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 473 | dependencies = [ 474 | "phf_generator", 475 | "phf_shared", 476 | "proc-macro-hack", 477 | "proc-macro2 1.0.24", 478 | "quote 1.0.9", 479 | "syn 1.0.67", 480 | ] 481 | 482 | [[package]] 483 | name = "phf_shared" 484 | version = "0.8.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 487 | dependencies = [ 488 | "siphasher", 489 | ] 490 | 491 | [[package]] 492 | name = "ppv-lite86" 493 | version = "0.2.10" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 496 | 497 | [[package]] 498 | name = "precomputed-hash" 499 | version = "0.1.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 502 | 503 | [[package]] 504 | name = "proc-macro-hack" 505 | version = "0.5.19" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 508 | 509 | [[package]] 510 | name = "proc-macro2" 511 | version = "0.4.30" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 514 | dependencies = [ 515 | "unicode-xid 0.1.0", 516 | ] 517 | 518 | [[package]] 519 | name = "proc-macro2" 520 | version = "1.0.24" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 523 | dependencies = [ 524 | "unicode-xid 0.2.1", 525 | ] 526 | 527 | [[package]] 528 | name = "quote" 529 | version = "0.6.13" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 532 | dependencies = [ 533 | "proc-macro2 0.4.30", 534 | ] 535 | 536 | [[package]] 537 | name = "quote" 538 | version = "1.0.9" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 541 | dependencies = [ 542 | "proc-macro2 1.0.24", 543 | ] 544 | 545 | [[package]] 546 | name = "rand" 547 | version = "0.7.3" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 550 | dependencies = [ 551 | "getrandom 0.1.16", 552 | "libc", 553 | "rand_chacha", 554 | "rand_core", 555 | "rand_hc", 556 | "rand_pcg", 557 | ] 558 | 559 | [[package]] 560 | name = "rand_chacha" 561 | version = "0.2.2" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 564 | dependencies = [ 565 | "ppv-lite86", 566 | "rand_core", 567 | ] 568 | 569 | [[package]] 570 | name = "rand_core" 571 | version = "0.5.1" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 574 | dependencies = [ 575 | "getrandom 0.1.16", 576 | ] 577 | 578 | [[package]] 579 | name = "rand_hc" 580 | version = "0.2.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 583 | dependencies = [ 584 | "rand_core", 585 | ] 586 | 587 | [[package]] 588 | name = "rand_pcg" 589 | version = "0.2.1" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 592 | dependencies = [ 593 | "rand_core", 594 | ] 595 | 596 | [[package]] 597 | name = "redox_syscall" 598 | version = "0.2.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 601 | dependencies = [ 602 | "bitflags", 603 | ] 604 | 605 | [[package]] 606 | name = "redox_users" 607 | version = "0.4.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 610 | dependencies = [ 611 | "getrandom 0.2.2", 612 | "redox_syscall", 613 | ] 614 | 615 | [[package]] 616 | name = "regex" 617 | version = "1.4.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" 620 | dependencies = [ 621 | "aho-corasick", 622 | "memchr", 623 | "regex-syntax", 624 | ] 625 | 626 | [[package]] 627 | name = "regex-syntax" 628 | version = "0.6.23" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" 631 | 632 | [[package]] 633 | name = "render" 634 | version = "0.3.1" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "07528d0483b1b4727b1419c82ed4be46926971b08af764d9d2ef0ac04a47e202" 637 | dependencies = [ 638 | "render_macros", 639 | ] 640 | 641 | [[package]] 642 | name = "render_macros" 643 | version = "0.3.1" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "6eec48d491a1f66f67c50545566b74a8c8435fe4b7dbf163d15a1e391aa4a2a7" 646 | dependencies = [ 647 | "proc-macro2 1.0.24", 648 | "quote 1.0.9", 649 | "syn 1.0.67", 650 | ] 651 | 652 | [[package]] 653 | name = "rust_wasm_duckdb" 654 | version = "0.1.0" 655 | dependencies = [ 656 | "bindgen", 657 | "cc", 658 | "count_tts", 659 | "kuchiki", 660 | "lazy_static", 661 | "libc", 662 | "render", 663 | "shellexpand", 664 | "speculate", 665 | "string-error", 666 | "strum_macros", 667 | "which 4.2.1", 668 | ] 669 | 670 | [[package]] 671 | name = "rustc-hash" 672 | version = "1.1.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 675 | 676 | [[package]] 677 | name = "ryu" 678 | version = "1.0.5" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 681 | 682 | [[package]] 683 | name = "selectors" 684 | version = "0.22.0" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 687 | dependencies = [ 688 | "bitflags", 689 | "cssparser", 690 | "derive_more", 691 | "fxhash", 692 | "log", 693 | "matches", 694 | "phf", 695 | "phf_codegen", 696 | "precomputed-hash", 697 | "servo_arc", 698 | "smallvec", 699 | "thin-slice", 700 | ] 701 | 702 | [[package]] 703 | name = "serde" 704 | version = "1.0.125" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 707 | 708 | [[package]] 709 | name = "serde_derive" 710 | version = "1.0.125" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 713 | dependencies = [ 714 | "proc-macro2 1.0.24", 715 | "quote 1.0.9", 716 | "syn 1.0.67", 717 | ] 718 | 719 | [[package]] 720 | name = "serde_json" 721 | version = "1.0.64" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 724 | dependencies = [ 725 | "itoa", 726 | "ryu", 727 | "serde", 728 | ] 729 | 730 | [[package]] 731 | name = "servo_arc" 732 | version = "0.1.1" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 735 | dependencies = [ 736 | "nodrop", 737 | "stable_deref_trait", 738 | ] 739 | 740 | [[package]] 741 | name = "shellexpand" 742 | version = "2.1.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" 745 | dependencies = [ 746 | "dirs-next", 747 | ] 748 | 749 | [[package]] 750 | name = "shlex" 751 | version = "1.0.0" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" 754 | 755 | [[package]] 756 | name = "siphasher" 757 | version = "0.3.5" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" 760 | 761 | [[package]] 762 | name = "smallvec" 763 | version = "1.6.1" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 766 | 767 | [[package]] 768 | name = "speculate" 769 | version = "0.1.2" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "3cb45d32c801cddf308603926b7c3760db4fa2c66d84c64376b64eec860d005e" 772 | dependencies = [ 773 | "proc-macro2 0.4.30", 774 | "quote 0.6.13", 775 | "syn 0.14.9", 776 | "unicode-xid 0.1.0", 777 | ] 778 | 779 | [[package]] 780 | name = "stable_deref_trait" 781 | version = "1.2.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 784 | 785 | [[package]] 786 | name = "string-error" 787 | version = "0.1.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "439558b73299d7afdf52c36399c3c812cca10fe5ca08429f38815df5bacf3ef7" 790 | 791 | [[package]] 792 | name = "string_cache" 793 | version = "0.8.1" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" 796 | dependencies = [ 797 | "lazy_static", 798 | "new_debug_unreachable", 799 | "phf_shared", 800 | "precomputed-hash", 801 | "serde", 802 | ] 803 | 804 | [[package]] 805 | name = "string_cache_codegen" 806 | version = "0.5.1" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" 809 | dependencies = [ 810 | "phf_generator", 811 | "phf_shared", 812 | "proc-macro2 1.0.24", 813 | "quote 1.0.9", 814 | ] 815 | 816 | [[package]] 817 | name = "strsim" 818 | version = "0.8.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 821 | 822 | [[package]] 823 | name = "strum_macros" 824 | version = "0.21.1" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" 827 | dependencies = [ 828 | "heck", 829 | "proc-macro2 1.0.24", 830 | "quote 1.0.9", 831 | "syn 1.0.67", 832 | ] 833 | 834 | [[package]] 835 | name = "syn" 836 | version = "0.14.9" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" 839 | dependencies = [ 840 | "proc-macro2 0.4.30", 841 | "quote 0.6.13", 842 | "unicode-xid 0.1.0", 843 | ] 844 | 845 | [[package]] 846 | name = "syn" 847 | version = "1.0.67" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" 850 | dependencies = [ 851 | "proc-macro2 1.0.24", 852 | "quote 1.0.9", 853 | "unicode-xid 0.2.1", 854 | ] 855 | 856 | [[package]] 857 | name = "tendril" 858 | version = "0.4.2" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" 861 | dependencies = [ 862 | "futf", 863 | "mac", 864 | "utf-8", 865 | ] 866 | 867 | [[package]] 868 | name = "termcolor" 869 | version = "1.1.2" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 872 | dependencies = [ 873 | "winapi-util", 874 | ] 875 | 876 | [[package]] 877 | name = "textwrap" 878 | version = "0.11.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 881 | dependencies = [ 882 | "unicode-width", 883 | ] 884 | 885 | [[package]] 886 | name = "thin-slice" 887 | version = "0.1.1" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 890 | 891 | [[package]] 892 | name = "unicode-segmentation" 893 | version = "1.7.1" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" 896 | 897 | [[package]] 898 | name = "unicode-width" 899 | version = "0.1.8" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 902 | 903 | [[package]] 904 | name = "unicode-xid" 905 | version = "0.1.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 908 | 909 | [[package]] 910 | name = "unicode-xid" 911 | version = "0.2.1" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 914 | 915 | [[package]] 916 | name = "utf-8" 917 | version = "0.7.5" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" 920 | 921 | [[package]] 922 | name = "vec_map" 923 | version = "0.8.2" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 926 | 927 | [[package]] 928 | name = "version_check" 929 | version = "0.9.3" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 932 | 933 | [[package]] 934 | name = "wasi" 935 | version = "0.9.0+wasi-snapshot-preview1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 938 | 939 | [[package]] 940 | name = "wasi" 941 | version = "0.10.2+wasi-snapshot-preview1" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 944 | 945 | [[package]] 946 | name = "which" 947 | version = "3.1.1" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" 950 | dependencies = [ 951 | "libc", 952 | ] 953 | 954 | [[package]] 955 | name = "which" 956 | version = "4.2.1" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "7cc009ab82a2afc94b9e467ab4214aee9cad1356cd9191264203d7d72006e00d" 959 | dependencies = [ 960 | "either", 961 | "lazy_static", 962 | "libc", 963 | ] 964 | 965 | [[package]] 966 | name = "winapi" 967 | version = "0.3.9" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 970 | dependencies = [ 971 | "winapi-i686-pc-windows-gnu", 972 | "winapi-x86_64-pc-windows-gnu", 973 | ] 974 | 975 | [[package]] 976 | name = "winapi-i686-pc-windows-gnu" 977 | version = "0.4.0" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 980 | 981 | [[package]] 982 | name = "winapi-util" 983 | version = "0.1.5" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 986 | dependencies = [ 987 | "winapi", 988 | ] 989 | 990 | [[package]] 991 | name = "winapi-x86_64-pc-windows-gnu" 992 | version = "0.4.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 995 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [[bin]] 2 | name = "bin" 3 | path = "src/bin.rs" 4 | 5 | [build-dependencies] 6 | cc = { version = "*", features = ['parallel'] } 7 | shellexpand = "*" 8 | which = "*" 9 | bindgen = "0.58.1" 10 | 11 | [dependencies] 12 | count_tts = "*" 13 | lazy_static = "*" 14 | libc = "*" 15 | render = "*" 16 | string-error = "0.1.0" 17 | strum_macros = "0.21.1" 18 | 19 | [dev-dependencies] 20 | kuchiki = "0.8.1" 21 | speculate = "*" 22 | 23 | [package] 24 | name = "rust_wasm_duckdb" 25 | version = "0.1.0" 26 | authors = ["Elliana "] 27 | edition = "2018" 28 | description = "A test to integrate the wasm version of duckdb with rust in the browser" 29 | repository = "https://github.com/Mause/rust_wasm_duckdb" 30 | license = "MIT" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Elliana May 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | EMMAKEN_CFLAGS = "-s ERROR_ON_UNDEFINED_SYMBOLS=0 -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=33554432 -sEXTRA_EXPORTED_RUNTIME_METHODS=[ccall,cwrap,callback] -s ASSERTIONS=1 -s SAFE_HEAP=1" 3 | 4 | [tasks.build] 5 | clear = true 6 | dependencies = ['prebuild'] 7 | command = "cargo" 8 | args = ["build", "--tests", "--target", "wasm32-unknown-emscripten"] 9 | 10 | [tasks.deploy] 11 | dependencies = ["ci-flow"] 12 | command = "cargo" 13 | args = ["web", "deploy", "--use-system-emscripten"] 14 | 15 | [tasks.docs] 16 | dependencies = ["prebuild"] 17 | command = "cargo" 18 | args = ["doc", "--target", "wasm32-unknown-emscripten", "--bins", "--document-private-items"] 19 | 20 | [tasks.expand] 21 | command = "cargo" 22 | args = ["expand", "--target", "wasm32-unknown-emscripten", ">", "output.rs"] 23 | 24 | [tasks.prebuild] 25 | condition = { files_not_exist = ["${CARGO_MAKE_WORKING_DIRECTORY}/target/duckdb.wasm", "${CARGO_MAKE_WORKING_DIRECTORY}/target/duckdb.hpp"] } 26 | script_runner = "@rust" 27 | script = { file = "setup.rs" } 28 | 29 | [tasks.start] 30 | dependencies = ["prebuild"] 31 | command = "cargo" 32 | args = ["web", "start"] 33 | 34 | [tasks.test] 35 | clear = true 36 | dependencies = ['build'] 37 | command = "cargo" 38 | args = ["web", "test", "--nodejs", "--use-system-emscripten", "--", "--nocapture"] 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust + WASM + DuckDB 2 | ==================== 3 | 4 | A test to integrate the wasm version of duckdb with rust in the browser 5 | 6 | You'll need cargo-make: `cargo install cargo-make` 7 | 8 | After you can run `cargo make` to build and test 9 | -------------------------------------------------------------------------------- /Web.toml: -------------------------------------------------------------------------------- 1 | default-target = "wasm32-unknown-emscripten" 2 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #![feature(try_trait)] 2 | 3 | use which::which; 4 | 5 | fn eat(command: &mut std::process::Command) { 6 | let res = command.output().expect("Compile"); 7 | 8 | if !res.status.success() { 9 | panic!("{}", String::from_utf8(res.stderr).expect("String")); 10 | } 11 | } 12 | 13 | fn main() -> Result<(), Box> { 14 | // TODO: reenable 15 | // println!("cargo:rustc-link-lib=static-nobundle=stdc++"); 16 | 17 | assert_eq!( 18 | String::from_utf8( 19 | cc::Build::new() 20 | .get_compiler() 21 | .to_command() 22 | .arg("--version") 23 | .output() 24 | .expect("Couldnt check emcc version") 25 | .stdout 26 | ) 27 | .expect("Couldnt check emcc version") 28 | .contains("2.0.15"), 29 | true 30 | ); 31 | 32 | let emar_path = 33 | which("emar").expect("Couldn't find emar, is the emscripten environment activated?"); 34 | 35 | eat(cc::Build::new() 36 | .get_compiler() 37 | .to_command() 38 | .arg("-fvisibility=default") 39 | .arg("-fPIC") 40 | .arg("-DDUCKDB_NO_THREADS=1") 41 | .arg("-sWASM=1") 42 | .arg("-DDUCKDB_BUILD_LIBRARY=1") 43 | .arg("-sWARN_ON_UNDEFINED_SYMBOLS=1") 44 | .arg("-sALLOW_MEMORY_GROWTH=1") 45 | .arg("-sUSE_PTHREADS=0") 46 | .arg("-sDISABLE_EXCEPTION_CATCHING=0") 47 | .arg("-fexceptions") 48 | .arg("-Wno-unused-parameter") 49 | .arg("--no-entry") 50 | .arg("-shared") 51 | .arg("src/reexporter.cpp") 52 | .arg("-Itarget") 53 | // .arg("target/duckdb.wasm") 54 | .arg("target/duckdb.cpp") 55 | .arg("-o") 56 | .arg("duckdb.o")); 57 | 58 | println!("{:?}", &emar_path); 59 | eat(std::process::Command::new(&emar_path) 60 | .arg("rcs") 61 | .arg("target/libduckdb.a") 62 | .arg("duckdb.o")); 63 | 64 | println!("cargo:rustc-link-lib=static-nobundle=duckdb"); 65 | println!( 66 | "cargo:rustc-link-search={}", 67 | std::env::current_dir()? 68 | .join("target") 69 | .to_str() 70 | .expect("aaaaa") 71 | ); 72 | 73 | #[cfg(windows)] 74 | { 75 | let p = emar_path.parent().unwrap().parent().unwrap().join("bin"); 76 | 77 | println!("{:?}", p); 78 | std::env::set_var("LIBCLANG_PATH", &p); 79 | } 80 | 81 | bindgen::builder() 82 | .header("target/duckdb.h") 83 | // .detect_include_paths(true) 84 | .clang_arg(format!( 85 | "-I{}", 86 | emar_path 87 | .join("../cache/sysroot/include") 88 | .to_str() 89 | .expect("include path") 90 | )) 91 | .generate_block(true) 92 | .rustified_enum(".*") 93 | // .clang_arg("-DDUCKDB_BUILD_LIBRARY") 94 | .parse_callbacks(Box::new(bindgen::CargoCallbacks)) 95 | .generate() 96 | .expect("failed?") 97 | .write_to_file(std::env::var("OUT_DIR")? + "/bindings.rs")?; 98 | 99 | Ok(()) 100 | } 101 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2021-03-25 2 | -------------------------------------------------------------------------------- /setup.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [package] 3 | //! edition = "2018" 4 | //! [dependencies] 5 | //! zip = "*" 6 | //! tokio = { version = "1", features = ["full"] } 7 | //! reqwest = {version="*"} 8 | //! octocrab = { version = "*" } 9 | //! ``` 10 | 11 | use octocrab::models::repos::Release; 12 | use std::io::Read; 13 | use tokio::fs::File; 14 | 15 | // fn add_check_annotation() { 16 | 17 | // octocrab::instance().crab.get() 18 | 19 | // let route = format!("orgs/{org}/actions/secrets/public-key", org = org.as_ref()); 20 | 21 | // self.crab.get(route, None::<&()>).await 22 | // } 23 | 24 | #[tokio::main()] 25 | async fn main() -> Result<(), Box> { 26 | let i = octocrab::instance(); 27 | let repo = i.repos("cwida", "duckdb"); 28 | let releases = repo.releases(); 29 | let release = &releases.get_by_tag("v0.2.5").await.expect("current"); 30 | 31 | let latest = &releases.get_latest().await.expect("latest").tag_name; 32 | 33 | if latest != &release.tag_name { 34 | eprintln!("Using {}, latest is {}", &latest, &release.tag_name); 35 | } 36 | 37 | std::fs::create_dir_all("target")?; 38 | 39 | println!("Latest release: {}", &latest); 40 | println!("Current release: {}", &release.tag_name); 41 | tokio::try_join!( 42 | from_file( 43 | &release, 44 | "libduckdb-src.zip", 45 | vec!["duckdb.h", "duckdb.hpp", "duckdb.cpp"] 46 | ), 47 | from_file(&release, "duckdb-wasm32-nothreads.zip", vec!["duckdb.wasm"]) 48 | )?; 49 | 50 | Ok(()) 51 | } 52 | 53 | async fn from_file( 54 | release: &Release, 55 | zip_filename: &str, 56 | inner_filenames: Vec<&str>, 57 | ) -> Result<(), Box> { 58 | let url = release 59 | .assets 60 | .iter() 61 | .find(|f| f.name == zip_filename) 62 | .expect("no sauce?") 63 | .browser_download_url 64 | .clone(); 65 | 66 | println!("Downloading {}", zip_filename); 67 | let res = reqwest::get(url).await.expect("no zip?"); 68 | 69 | let zip = res.bytes().await.expect("no bytes?").to_vec(); 70 | println!("Downloaded {}", zip_filename); 71 | 72 | let mut archive = zip::ZipArchive::new(std::io::Cursor::new(zip)).unwrap(); 73 | 74 | for inner_filename in inner_filenames { 75 | let mut file = archive 76 | .by_name(inner_filename) 77 | .expect(format!("File {} not found", inner_filename).as_str()); 78 | 79 | let mut contents = Vec::new(); 80 | file.read_to_end(&mut contents).expect("read_to_end"); 81 | 82 | tokio::io::copy( 83 | &mut std::io::Cursor::new(contents), 84 | &mut File::create(format!("target/{}", inner_filename)).await?, 85 | ) 86 | .await?; 87 | } 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | #![feature(debug_non_exhaustive)] 2 | #![feature(extern_types)] 3 | #![feature(try_trait)] 4 | #![feature(static_nobundle)] 5 | #![feature(proc_macro_hygiene)] 6 | #![allow(unused_parens)] 7 | #![allow(unused_braces)] 8 | 9 | use crate::state::DuckDBState; 10 | use libc::c_void; 11 | #[allow(non_camel_case_types)] 12 | pub type c_char = i8; 13 | use crate::db::DB; 14 | use crate::rendering::{Form, Table}; 15 | use crate::types::{ 16 | duckdb_blob, duckdb_connection, duckdb_database, duckdb_date, duckdb_hugeint, duckdb_interval, 17 | duckdb_time, duckdb_timestamp, duckdb_type as DuckDBType, DuckDBColumn, DuckDBResult, 18 | }; 19 | use render::html; 20 | use std::cell::RefCell; 21 | use std::convert::{TryFrom, TryInto}; 22 | use std::ffi::{CStr, CString}; 23 | use std::thread_local; 24 | use strum_macros::IntoStaticStr; 25 | 26 | mod bindings; 27 | mod db; 28 | mod jse; 29 | mod rendering; 30 | mod state; 31 | #[cfg(test)] 32 | mod tests; 33 | mod types; 34 | 35 | #[derive(Debug, IntoStaticStr)] 36 | enum DbType { 37 | Boolean(bool), 38 | Tinyint(i8), 39 | Smallint(i16), 40 | Integer(i32), 41 | Bigint(i64), 42 | Float(f32), 43 | Date(duckdb_date), 44 | Time(duckdb_time), 45 | Timestamp(duckdb_timestamp), 46 | Double(f64), 47 | String(String), 48 | Interval(duckdb_interval), 49 | Hugeint(duckdb_hugeint), 50 | Blob(duckdb_blob), 51 | Unknown(DuckDBType), 52 | } 53 | impl ToString for DbType { 54 | fn to_string(&self) -> String { 55 | use crate::DbType::*; 56 | 57 | let value: &dyn ToString = match self { 58 | Boolean(s) => s, 59 | Tinyint(s) => s, 60 | Smallint(s) => s, 61 | Integer(i) => i, 62 | Bigint(s) => s, 63 | Float(f) => f, 64 | Double(f) => f, 65 | String(s) => s, 66 | Time(s) => s, 67 | Timestamp(s) => s, 68 | Date(s) => s, 69 | Blob(s) => s, 70 | Hugeint(s) => s, 71 | Interval(s) => s, 72 | Unknown(_) => &"unknown", 73 | }; 74 | 75 | value.to_string() 76 | } 77 | } 78 | 79 | extern "C" { 80 | fn duckdb_open(path: *const c_char, database: *const duckdb_database) -> DuckDBState; 81 | 82 | fn duckdb_connect(db: *const duckdb_database, con: *const duckdb_connection) -> DuckDBState; 83 | 84 | fn duckdb_disconnect(con: *const duckdb_connection); 85 | 86 | fn ext_duckdb_close(db: *const duckdb_database); 87 | 88 | fn duckdb_query( 89 | con: *const duckdb_connection, 90 | query: *const c_char, 91 | result: *const DuckDBResult, 92 | ) -> DuckDBState; 93 | 94 | fn duckdb_destroy_result(result: *const DuckDBResult); 95 | 96 | /// Converts the specified value to a bool. Returns false on failure or NULL. 97 | fn duckdb_value_boolean(result: *const DuckDBResult, col: u64, row: u64) -> bool; 98 | /// Converts the specified value to an int8_t. Returns 0 on failure or NULL. 99 | fn duckdb_value_int8(result: *const DuckDBResult, col: u64, row: u64) -> i8; 100 | /// Converts the specified value to an int16_t. Returns 0 on failure or NULL. 101 | fn duckdb_value_int16(result: *const DuckDBResult, col: u64, row: u64) -> i16; 102 | /// Converts the specified value to an int64_t. Returns 0 on failure or NULL. 103 | fn duckdb_value_int32(result: *const DuckDBResult, col: u64, row: u64) -> i32; 104 | /// Converts the specified value to an int64_t. Returns 0 on failure or NULL. 105 | fn duckdb_value_int64(result: *const DuckDBResult, col: u64, row: u64) -> i64; 106 | /// Converts the specified value to an uint8_t. Returns 0 on failure or NULL. 107 | fn duckdb_value_uint8(result: *const DuckDBResult, col: u64, row: u64) -> u8; 108 | /// Converts the specified value to an uint16_t. Returns 0 on failure or NULL. 109 | fn duckdb_value_uint16(result: *const DuckDBResult, col: u64, row: u64) -> u16; 110 | /// Converts the specified value to an uint64_t. Returns 0 on failure or NULL. 111 | fn duckdb_value_uint32(result: *const DuckDBResult, col: u64, row: u64) -> u32; 112 | /// Converts the specified value to an uint64_t. Returns 0 on failure or NULL. 113 | fn duckdb_value_uint64(result: *const DuckDBResult, col: u64, row: u64) -> u64; 114 | /// Converts the specified value to a float. Returns 0.0 on failure or NULL. 115 | fn duckdb_value_float(result: *const DuckDBResult, col: u64, row: u64) -> f32; 116 | /// Converts the specified value to a double. Returns 0.0 on failure or NULL. 117 | fn duckdb_value_double(result: *const DuckDBResult, col: u64, row: u64) -> f64; 118 | /// Converts the specified value to a string. Returns nullptr on failure or NULL. The result must be freed with free. 119 | fn duckdb_value_varchar(result: *const DuckDBResult, col: u64, row: u64) -> *const c_char; 120 | /// Fetches a blob from a result set column. Returns a blob with blob.data set to nullptr on failure or NULL. The 121 | /// resulting "blob.data" must be freed with free. 122 | fn duckdb_value_blob(result: *const DuckDBResult, blob: *const duckdb_blob, col: u64, row: u64); 123 | 124 | fn duckdb_value_date(result: *const DuckDBResult, col: u64, row: u64) -> *const duckdb_date; 125 | fn duckdb_value_time(result: *const DuckDBResult, col: u64, row: u64) -> *const duckdb_time; 126 | fn duckdb_value_timestamp( 127 | result: *const DuckDBResult, 128 | col: u64, 129 | row: u64, 130 | ) -> *const duckdb_timestamp; 131 | 132 | fn duckdb_value_hugeint( 133 | result: *const DuckDBResult, 134 | col: u64, 135 | row: u64, 136 | ) -> *const duckdb_hugeint; 137 | fn duckdb_value_interval( 138 | result: *const DuckDBResult, 139 | col: u64, 140 | row: u64, 141 | ) -> *const duckdb_interval; 142 | 143 | pub fn emscripten_asm_const_int( 144 | code: *const u8, 145 | sigPtr: *const u8, 146 | argBuf: *const u8, 147 | ) -> *mut u8; 148 | 149 | pub fn mallocy() -> *const c_void; 150 | } 151 | 152 | fn malloc(_size: usize) -> *const T { 153 | unsafe { mallocy() as *const T } 154 | } 155 | 156 | static PTR: usize = core::mem::size_of::(); 157 | 158 | fn set_body_html(string: String) -> i32 { 159 | let cstring = CString::new(string).expect("string"); 160 | let input = cstring.as_ptr() as *const _ as i32; 161 | 162 | jse!( 163 | b"document.body.innerHTML = UTF8ToString($0, 1000);\x00", 164 | input 165 | ) 166 | } 167 | 168 | fn set_page_title(string: String) -> i32 { 169 | let cstring = CString::new(string).expect("string"); 170 | let input = cstring.as_ptr() as *const _ as i32; 171 | 172 | jse!(b"document.title = UTF8ToString($0, 1000);\x00", input) 173 | } 174 | 175 | #[derive(Debug)] 176 | pub struct ResolvedResult<'a> { 177 | result: *const DuckDBResult, 178 | resolved: &'a DuckDBResult, 179 | columns: Vec, 180 | length: usize, 181 | } 182 | impl<'a> Clone for ResolvedResult<'a> { 183 | fn clone(&self) -> ResolvedResult<'a> { 184 | unsafe { ResolvedResult::new(self.result) } 185 | } 186 | } 187 | impl<'a> Drop for ResolvedResult<'a> { 188 | fn drop(&mut self) { 189 | println!("Dropping {:?}", self); 190 | unsafe { duckdb_destroy_result(self.result) }; 191 | } 192 | } 193 | impl<'a> ResolvedResult<'a> { 194 | unsafe fn new(result: *const DuckDBResult) -> Self { 195 | let resolved = &*result; 196 | 197 | let length = resolved.column_count.try_into().expect("Too many columns"); 198 | let columns: Vec = Vec::from_raw_parts(resolved.columns, length, length); 199 | 200 | Self { 201 | result, 202 | resolved, 203 | columns, 204 | length, 205 | } 206 | } 207 | 208 | fn column(&self, col: u64) -> &DuckDBColumn { 209 | &self.columns[>::try_from(col).expect("Too big")] 210 | } 211 | 212 | fn consume(&self, col: u64, row: u64) -> Result> { 213 | let column: &DuckDBColumn = self.column(col); 214 | let result = self.result; 215 | 216 | Ok(unsafe { 217 | match &column.type_ { 218 | DuckDBType::DUCKDB_TYPE_BOOLEAN => { 219 | DbType::Boolean(duckdb_value_boolean(result, col, row)) 220 | } 221 | DuckDBType::DUCKDB_TYPE_TINYINT => { 222 | DbType::Tinyint(duckdb_value_int8(result, col, row)) 223 | } 224 | DuckDBType::DUCKDB_TYPE_SMALLINT => { 225 | DbType::Smallint(duckdb_value_int16(result, col, row)) 226 | } 227 | DuckDBType::DUCKDB_TYPE_INTEGER => { 228 | DbType::Integer(duckdb_value_int32(result, col, row)) 229 | } 230 | DuckDBType::DUCKDB_TYPE_BIGINT => { 231 | DbType::Bigint(duckdb_value_int64(result, col, row)) 232 | } 233 | DuckDBType::DUCKDB_TYPE_TIME => { 234 | DbType::Time(*duckdb_value_time(result, col, row).as_ref().expect("Time")) 235 | } 236 | DuckDBType::DUCKDB_TYPE_TIMESTAMP => DbType::Timestamp( 237 | *duckdb_value_timestamp(result, col, row) 238 | .as_ref() 239 | .expect("Timestamp"), 240 | ), 241 | DuckDBType::DUCKDB_TYPE_DATE => { 242 | DbType::Date(*duckdb_value_date(result, col, row).as_ref().expect("Date")) 243 | } 244 | DuckDBType::DUCKDB_TYPE_FLOAT => { 245 | DbType::Float(duckdb_value_float(result, col, row)) 246 | } 247 | DuckDBType::DUCKDB_TYPE_DOUBLE => { 248 | DbType::Double(duckdb_value_double(result, col, row)) 249 | } 250 | DuckDBType::DUCKDB_TYPE_VARCHAR => DbType::String( 251 | CStr::from_ptr(duckdb_value_varchar(result, col, row)) 252 | .to_string_lossy() 253 | .to_string(), 254 | ), 255 | DuckDBType::DUCKDB_TYPE_HUGEINT => DbType::Hugeint( 256 | *duckdb_value_hugeint(result, col, row) 257 | .as_ref() 258 | .expect("Hugeint"), 259 | ), 260 | DuckDBType::DUCKDB_TYPE_BLOB => { 261 | let ptr: *const duckdb_blob = malloc(PTR); 262 | duckdb_value_blob(result, ptr, col, row); 263 | DbType::Blob(ptr.as_ref().expect("Blob").clone()) 264 | } 265 | DuckDBType::DUCKDB_TYPE_INTERVAL => DbType::Interval( 266 | *duckdb_value_interval(result, col, row) 267 | .as_ref() 268 | .expect("Interval"), 269 | ), 270 | _ => DbType::Unknown(column.type_), 271 | } 272 | }) 273 | } 274 | } 275 | 276 | thread_local! { 277 | static DATABASE: RefCell> = RefCell::new(None); 278 | } 279 | 280 | unsafe fn run_async() -> Result<(), Box> { 281 | set_page_title("DuckDB Test".to_string()); 282 | 283 | let db = Some(DB::new(Some("db.db"))?); 284 | println!("DB: {:?}", db); 285 | DATABASE.with(|f| f.replace(db)); 286 | 287 | println!("DB open"); 288 | 289 | let string = html! { <>{Form {}} }; 290 | set_body_html(string); 291 | 292 | Ok(()) 293 | } 294 | 295 | fn hook(info: &std::panic::PanicInfo) { 296 | let mut msg = info.to_string(); 297 | 298 | println!("{:?}", msg); 299 | 300 | // Add the error stack to our message. 301 | // 302 | // This ensures that even if the `console` implementation doesn't 303 | // include stacks for `console.error`, the stack is still available 304 | // for the user. Additionally, Firefox's console tries to clean up 305 | // stack traces, and ruins Rust symbols in the process 306 | // (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since 307 | // it only touches the logged message's associated stack, and not 308 | // the message's contents, by including the stack in the message 309 | // contents we make sure it is available to the user. 310 | msg.push_str("\n\nStack:\n\n"); 311 | // #[cfg(not(test))] 312 | // { 313 | // let error = js_sys::Error::new("test1"); 314 | // println!("{:?}", error); 315 | // } 316 | // let stack = error.stack(); 317 | // println!("{:?}", stack); 318 | // msg.push_str(stack.as_str().unwrap_or_default()); 319 | 320 | // Safari's devtools, on the other hand, _do_ mess with logged 321 | // messages' contents, so we attempt to break their heuristics for 322 | // doing that by appending some whitespace. 323 | // https://github.com/rustwasm/console_error_panic_hook/issues/7 324 | msg.push_str("\n\n"); 325 | 326 | // Finally, log the panic with `console.error`! 327 | println!("{}", msg); 328 | } 329 | 330 | #[no_mangle] 331 | extern "C" fn callback(query_: *const c_char) { 332 | let org = unsafe { CStr::from_ptr(query_) }; 333 | let query = org.to_string_lossy(); 334 | 335 | println!("you called?: {} {:?} {:?}", query, org, query_); 336 | 337 | DATABASE.with(|borrowed| { 338 | println!("borrowed: {:?}", borrowed); 339 | let yo = borrowed.borrow(); 340 | println!("yo: {:?}", yo); 341 | 342 | let conn = yo.as_ref().expect("no db?").connection().unwrap(); 343 | 344 | let string = match conn.query(&query) { 345 | Ok(resolved) => { 346 | println!("columns: {:?}", resolved.columns); 347 | 348 | let table = Table { 349 | resolved: &resolved, 350 | }; 351 | html! { 352 |
353 | {Form {}} 354 | {table} 355 |
356 | } 357 | } 358 | Err(error) => { 359 | let e = error.to_string(); 360 | html! { 361 |
362 | {Form {}} 363 |
{e}
364 |
365 | } 366 | } 367 | }; 368 | 369 | println!("{}", string); 370 | 371 | set_body_html(string); 372 | }); 373 | } 374 | 375 | pub fn main() -> Result<(), Box> { 376 | std::panic::set_hook(Box::new(hook)); 377 | 378 | unsafe { 379 | run_async().expect("Ooops"); 380 | } 381 | 382 | Ok(()) 383 | } 384 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(unused)] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 7 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{duckdb_connection, duckdb_database}; 2 | use crate::{ 3 | duckdb_disconnect, duckdb_open, duckdb_query, ext_duckdb_close, malloc, DuckDBState, 4 | ResolvedResult, PTR, 5 | }; 6 | use std::ffi::{CStr, CString}; 7 | 8 | extern "C" { 9 | fn create_connection(db: *const duckdb_database) -> *const duckdb_connection; 10 | } 11 | 12 | #[derive(Debug)] 13 | pub struct DB { 14 | db: *const duckdb_database, 15 | } 16 | impl DB { 17 | pub fn new(path: Option<&str>) -> Result> { 18 | let db = malloc(PTR); 19 | 20 | unsafe { 21 | match path { 22 | Some(path) => { 23 | let path = CString::new(path).unwrap(); 24 | duckdb_open(path.as_ptr(), db)? 25 | } 26 | None => duckdb_open(std::ptr::null(), db)?, 27 | } 28 | }; 29 | 30 | Ok(Self { db }) 31 | } 32 | 33 | pub fn connection(&self) -> Result> { 34 | let connection: *const duckdb_connection = unsafe { create_connection(self.db) }; 35 | println!("conn: {:?}", &connection); 36 | Ok(Connection { connection }) 37 | } 38 | } 39 | impl Drop for DB { 40 | fn drop(&mut self) { 41 | println!("Dropping {:?}", self); 42 | unsafe { ext_duckdb_close(self.db) }; 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub struct Connection { 48 | connection: *const duckdb_connection, 49 | } 50 | impl Connection { 51 | pub fn query(&self, que: &str) -> Result> { 52 | unsafe { 53 | let s = CString::new(que).expect("string"); 54 | 55 | let result = malloc(PTR); 56 | let status = duckdb_query(self.connection, s.as_ptr(), result); 57 | 58 | if matches!(status, DuckDBState::DuckDBError) { 59 | let error_message = CStr::from_ptr((*result).error_message).to_string_lossy(); 60 | 61 | Err(string_error::new_err(&*error_message)) 62 | } else { 63 | Ok(ResolvedResult::new(result)) 64 | } 65 | } 66 | } 67 | } 68 | impl Drop for Connection { 69 | fn drop(&mut self) { 70 | println!("Dropping {:?}", self); 71 | unsafe { duckdb_disconnect(self.connection) }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/jse.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::*; 2 | 3 | lazy_static! { 4 | pub static ref EMPTY_SIG: std::ffi::CString = 5 | unsafe { std::ffi::CString::from_vec_unchecked(vec![]) }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! jse { 10 | ($js_expr:expr, $( $i:ident ),*) => { 11 | { 12 | const LEN: usize = count_tts::count_tts!($($i)*); 13 | 14 | #[repr(C, align(16))] 15 | struct AlignToSixteen([i32; LEN]); 16 | 17 | let array = &AlignToSixteen([$($i,)*]); 18 | let sig = CString::new("i".repeat(LEN)).expect("sig"); 19 | const SNIPPET: &'static [u8] = $js_expr; 20 | 21 | assert_eq!(SNIPPET[..].last().expect("empty snippet?"), &0); 22 | 23 | unsafe { 24 | emscripten_asm_const_int( 25 | SNIPPET as *const _ as *const u8, 26 | sig.as_ptr() as *const _ as *const u8, 27 | array as *const _ as *const u8, 28 | ) as i32 29 | } 30 | } 31 | }; 32 | ($js_expr:expr) => { 33 | { 34 | const SNIPPET: &'static [u8] = $js_expr; 35 | 36 | unsafe { 37 | emscripten_asm_const_int( 38 | SNIPPET as *const _ as *const u8, 39 | crate::jse::EMPTY_SIG.as_ptr() as *const _ as *const u8, 40 | std::ptr::null() as *const _ as *const u8, 41 | ) as i32 42 | } 43 | } 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/reexporter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "duckdb.hpp" 3 | #include 4 | 5 | using namespace duckdb; 6 | 7 | struct DatabaseData { 8 | DatabaseData() : database(nullptr) {} 9 | ~DatabaseData() { 10 | if (database) { 11 | delete database; 12 | } 13 | } 14 | 15 | duckdb::DuckDB *database; 16 | }; 17 | 18 | extern "C" 19 | { 20 | void* mallocy() { 21 | return calloc(1, sizeof(void*)); 22 | } 23 | 24 | duckdb_date *duckdb_value_date(duckdb_result *result, idx_t col, idx_t row) 25 | { 26 | return &((duckdb_date *)result->columns[col].data)[row]; 27 | } 28 | 29 | duckdb_time *duckdb_value_time(duckdb_result *result, idx_t col, idx_t row) 30 | { 31 | return &((duckdb_time *)result->columns[col].data)[row]; 32 | } 33 | 34 | duckdb_interval *duckdb_value_interval(duckdb_result *result, idx_t col, idx_t row) 35 | { 36 | return &((duckdb_interval *)result->columns[col].data)[row]; 37 | } 38 | 39 | duckdb_hugeint *duckdb_value_hugeint(duckdb_result *result, idx_t col, idx_t row) 40 | { 41 | return &((duckdb_hugeint *)result->columns[col].data)[row]; 42 | } 43 | 44 | duckdb_timestamp *duckdb_value_timestamp(duckdb_result *result, idx_t col, idx_t row) 45 | { 46 | return &((duckdb_timestamp *)result->columns[col].data)[row]; 47 | } 48 | 49 | void ext_duckdb_close(duckdb_database *database) { 50 | if (*database) { 51 | auto wrapper = (DatabaseData *)*database; 52 | delete wrapper; 53 | *database = nullptr; 54 | } 55 | } 56 | 57 | duckdb_connection create_connection(DatabaseData* db) { 58 | return (duckdb_connection) new duckdb::Connection(*db->database); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/rendering.rs: -------------------------------------------------------------------------------- 1 | use crate::{DbType, DuckDBColumn, ResolvedResult}; 2 | use render::{component, rsx, Render}; 3 | use std::ffi::CStr; 4 | use std::iter::{FromIterator, Map}; 5 | 6 | pub trait Contain { 7 | fn contain(self) -> Container; 8 | } 9 | 10 | impl Contain for Map 11 | where 12 | F: FnMut(I::Item) -> B, 13 | Vec: FromIterator, 14 | { 15 | fn contain(self) -> Container { 16 | Container(self.collect::>()) 17 | } 18 | } 19 | 20 | pub struct Container(pub Vec); 21 | impl Render for Container { 22 | fn render_into(self, writer: &mut W) -> Result<(), std::fmt::Error> 23 | where 24 | W: std::fmt::Write, 25 | { 26 | for item in self.0 { 27 | item.render_into(writer)?; 28 | } 29 | 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl Render for DbType { 35 | fn render_into(self, writer: &mut W) -> Result<(), std::fmt::Error> { 36 | writer.write_str(&self.to_string()) 37 | } 38 | } 39 | 40 | #[component] 41 | pub fn Table<'a>(resolved: &'a ResolvedResult<'a>) { 42 | let head = (0..resolved.resolved.column_count) 43 | .map(|col_idx| { 44 | let column: &DuckDBColumn = resolved.column(col_idx); 45 | let name = unsafe { CStr::from_ptr(column.name) } 46 | .to_string_lossy() 47 | .to_string(); 48 | 49 | let type_: &str = resolved.consume(col_idx, 0).expect("consume").into(); 50 | 51 | rsx! { {name}{": "}{type_} } 52 | }) 53 | .contain(); 54 | 55 | let body = (0..resolved.resolved.row_count) 56 | .map(|row| { 57 | rsx! { 58 | 59 | { 60 | ( 61 | (0..resolved.resolved.column_count) 62 | .map(|col| { 63 | let value = resolved.consume(col, row).expect("consume"); 64 | 65 | rsx!{{value}} 66 | }) 67 | .contain() 68 | ) 69 | } 70 | 71 | } 72 | }) 73 | .contain(); 74 | 75 | rsx! { 76 | 77 | {head} 78 | {body} 79 |
80 | } 81 | } 82 | 83 | #[component] 84 | pub fn Form() { 85 | let files = Container( 86 | std::fs::read_dir(std::path::Path::new("/")) 87 | .expect("files") 88 | .map(|f| rsx! {
  • {f.unwrap().path().to_str().unwrap().to_string()}
  • }) 89 | .collect(), 90 | ); 91 | 92 | rsx! { 93 |
    94 |
    95 | 96 |
    97 |
      98 | {files} 99 |
    100 |
    101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug)] 4 | #[repr(C)] 5 | pub enum DuckDBState { 6 | DuckDBSuccess = 0, 7 | DuckDBError = 1, 8 | } 9 | impl DuckDBState { 10 | #[allow(dead_code)] 11 | fn is_success(&self) -> bool { 12 | matches!(self, DuckDBState::DuckDBSuccess) 13 | } 14 | } 15 | impl fmt::Display for DuckDBState { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | write!(f, "({:?})", self) 18 | } 19 | } 20 | impl std::error::Error for DuckDBState {} 21 | impl std::ops::Try for DuckDBState { 22 | type Ok = DuckDBState; 23 | type Error = DuckDBState; 24 | 25 | fn into_result(self) -> Result { 26 | match &self { 27 | DuckDBState::DuckDBSuccess => Ok(self), 28 | DuckDBState::DuckDBError => Err(self), 29 | } 30 | } 31 | fn from_ok(_: ::Ok) -> Self { 32 | todo!() 33 | } 34 | fn from_error(_: ::Error) -> Self { 35 | todo!() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::db::DB; 2 | use crate::jse; 3 | use crate::{ 4 | c_char, callback, duckdb_date, duckdb_time, duckdb_timestamp, emscripten_asm_const_int, hook, 5 | main, 6 | }; 7 | use speculate::speculate; 8 | use std::ffi::{CStr, CString}; 9 | 10 | fn parse(html: String) -> kuchiki::NodeRef { 11 | use kuchiki::traits::TendrilSink; 12 | 13 | let mut v = Vec::from(unsafe { html.clone().as_bytes_mut() }); 14 | 15 | let resultant = kuchiki::parse_html() 16 | .from_utf8() 17 | .read_from(&mut std::io::Cursor::new(&mut v)) 18 | .expect("parsing failed"); 19 | 20 | resultant.first_child().expect("first_child") 21 | } 22 | 23 | fn get_document_html() -> String { 24 | let ptr = jse!(b"return allocateUTF8OnStack(document.body.innerHTML);\x00"); 25 | 26 | return unsafe { CStr::from_ptr(ptr as *const c_char) } 27 | .to_string_lossy() 28 | .to_string(); 29 | } 30 | 31 | speculate! { 32 | before { 33 | std::panic::set_hook(Box::new(hook)); 34 | 35 | jse!(b"global.document = {body: {}};\x00"); 36 | } 37 | 38 | after { 39 | jse!(b"delete global.document;\x00"); 40 | } 41 | 42 | test "timestamp" { 43 | duckdb_timestamp::new( 44 | duckdb_date::new(2021, 1, 1), 45 | duckdb_time::new(11, 13, 0, 0) 46 | ); 47 | } 48 | 49 | fn basic_test(query: &str) { 50 | main().unwrap(); 51 | let string = CString::new(query).unwrap(); 52 | callback(string.as_ptr()); 53 | } 54 | 55 | test "version check" { 56 | basic_test("pragma version"); 57 | } 58 | 59 | /* 60 | test "blob" { 61 | basic_test("select 'a'::blob"); 62 | } 63 | */ 64 | 65 | test "works" { 66 | basic_test("select 1"); 67 | 68 | let html = get_document_html(); 69 | 70 | let resultant = parse(html); 71 | let name = &resultant.as_element().expect("as_element").name.local; 72 | 73 | assert_eq!(name, "html"); 74 | } 75 | 76 | test "roundtrip" { 77 | fn internal() -> Result<(), Box> { 78 | use crate::DbType::Date; 79 | 80 | let db = DB::new(None)?; 81 | 82 | let conn = db.connection()?; 83 | 84 | conn.query("create table test (stamp date);")?; 85 | 86 | conn.query("insert into test values (current_date);")?; 87 | 88 | let result = conn.query("select stamp from test")?; 89 | 90 | let data = result.consume(0, 0)?; 91 | 92 | assert_eq!(matches!(data, Date(date)), true); 93 | 94 | Ok(()) 95 | } 96 | 97 | internal().unwrap(); 98 | } 99 | 100 | test "to_string_works" { 101 | let value = duckdb_timestamp::new(duckdb_date::new(1996, 8, 7), duckdb_time::new(12, 10, 0, 0)); 102 | 103 | assert_eq!(value.to_string(), "1996-08-07T12:10:00.0"); 104 | } 105 | 106 | test "multi args works" { 107 | fn addition(a: i32, b: i32) -> i32 { 108 | jse!(b"return $0 + $1;\x00", a, b) 109 | } 110 | 111 | assert_eq!(addition(10, 12), 22); 112 | } 113 | 114 | test "html" { 115 | use render::{component, rsx, html}; 116 | 117 | #[component] 118 | fn Heading<'title>(title: &'title str) { 119 | rsx! {

    {title}

    } 120 | } 121 | 122 | let rendered_html = html! { 123 | 124 | }; 125 | 126 | assert_eq!(rendered_html, r#"

    Hello world!

    "#); 127 | } 128 | 129 | test "use connection" { 130 | let db = DB::new( 131 | None 132 | ).expect("db"); 133 | println!("db: {:?}", &db); 134 | 135 | let conn = db.connection().expect("connection"); 136 | println!("conn: {:?}", &conn); 137 | 138 | conn.query("select 1").expect("query"); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | pub use crate::bindings::{ 2 | duckdb_column as DuckDBColumn, duckdb_connection, duckdb_database, duckdb_date, duckdb_hugeint, 3 | duckdb_interval, duckdb_result as DuckDBResult, duckdb_time, duckdb_timestamp, duckdb_type, 4 | }; 5 | use libc::c_void; 6 | use std::convert::TryInto; 7 | use std::fmt::{Display, Error, Formatter}; 8 | 9 | impl Display for duckdb_interval { 10 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 11 | f.debug_struct("duckdb_interval") 12 | .field("months", &self.months) 13 | .field("days", &self.days) 14 | .field("micros", &self.micros) 15 | .finish() 16 | } 17 | } 18 | impl From<&duckdb_hugeint> for i128 { 19 | fn from(inst: &duckdb_hugeint) -> i128 { 20 | let sign = if inst.upper >= 0 { 1 } else { -1 }; 21 | let upper = if sign == -1 { -inst.upper } else { inst.upper }; 22 | 23 | let mut twisted: i128 = upper.into(); 24 | let mut twisted: u128 = twisted.try_into().unwrap(); 25 | twisted <<= 64; 26 | let step: u128 = inst.lower.into(); 27 | twisted &= step; 28 | 29 | let twisted: i128 = twisted.try_into().unwrap(); 30 | if sign == 1 { 31 | twisted 32 | } else { 33 | -twisted 34 | } 35 | } 36 | } 37 | 38 | impl Display for duckdb_hugeint { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 40 | let value: i128 = self.into(); 41 | f.debug_struct("duckdb_hugeint") 42 | .field("value", &value) 43 | .finish() 44 | } 45 | } 46 | 47 | extern "C" { 48 | fn free(ptr: *const c_void); 49 | } 50 | 51 | #[repr(C)] 52 | #[derive(Debug, Clone)] 53 | pub struct duckdb_blob { 54 | data: *const c_void, 55 | pub size: u64, 56 | } 57 | impl Drop for duckdb_blob { 58 | fn drop(&mut self) { 59 | unsafe { 60 | free(self.data); 61 | }; 62 | } 63 | } 64 | impl Display for duckdb_blob { 65 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 66 | f.debug_struct("duckdb_blob") 67 | .field("size", &self.size) 68 | .finish_non_exhaustive() 69 | } 70 | } 71 | 72 | impl duckdb_date { 73 | pub fn new(year: i32, month: i8, day: i8) -> Self { 74 | Self { year, month, day } 75 | } 76 | } 77 | impl Display for duckdb_date { 78 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 79 | f.write_fmt(format_args!( 80 | "{:0>4}-{:0>2}-{:0>2}", 81 | self.year, self.month, self.day 82 | )) 83 | } 84 | } 85 | 86 | impl duckdb_time { 87 | pub fn new(hour: i8, min: i8, sec: i8, micros: i16) -> Self { 88 | Self { 89 | hour, 90 | min, 91 | sec, 92 | micros, 93 | } 94 | } 95 | } 96 | impl Display for duckdb_time { 97 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 98 | f.write_fmt(format_args!( 99 | "{:0>2}:{:0>2}:{:0>2}.{}", 100 | self.hour, self.min, self.sec, self.micros 101 | )) 102 | } 103 | } 104 | 105 | impl duckdb_timestamp { 106 | pub fn new(date: duckdb_date, time: duckdb_time) -> Self { 107 | Self { date, time } 108 | } 109 | } 110 | impl Display for duckdb_timestamp { 111 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 112 | f.write_fmt(format_args!("{}T{}", self.date, self.time)) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Loading... 11 | 12 | 13 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rust-wasm-duckdb", 3 | "project": "mause" 4 | } 5 | --------------------------------------------------------------------------------