diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..907690b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +# The compiler is not installed by any step here. rust-toolchain.toml +# pins the version and names the components, and the rustup every +# hosted image ships installs both on the first cargo command. A step +# that installed a toolchain would be a second place the version is +# written, and the pin exists so there is only one. +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - run: cargo fmt --check + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: Swatinem/rust-cache@v2 + # The library target and not --all-targets. This crate is a + # cdylib whose whole surface is reached through napi's + # registration, which a test harness build does not link, so + # every exported item reads as dead code there and the lints + # that matter are drowned by six that cannot be true. The tests + # are JavaScript, and the job below is what runs them. + - run: cargo clippy --all-features -- -D warnings + + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + # 24 is the active LTS and the version the package requires. 26 + # is the current release, and the one where Temporal is + # unflagged, so it is where the opt-in of a later milestone will + # be exercised. Bun and Deno join this matrix with the line of + # DX3 that claims them, since claiming a runtime nothing runs on + # is how a client acquires a broken runtime. + node: [24, 26] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.node }} + - uses: Swatinem/rust-cache@v2 + - run: npm ci + # Debug, because what this job is asking is whether the binding + # is correct rather than how fast it is, and a release build of + # the engine is several minutes of LTO per row of the matrix. + - run: npm run build:debug + - run: npm test diff --git a/.gitignore b/.gitignore index e43b0f9..2148910 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,13 @@ .DS_Store +target/ +node_modules/ +dist/ + +# The built addon, which napi drops into the source tree so that the +# tests load the same file a published package would. +*.node + +# A local override pointing at a checkout of the engine instead of the +# pinned revision. Untracked on purpose: the pin is what the release +# builds against. +.cargo/config.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c2b0355 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1389 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "napi" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459197f1592f4c3dbbf9c1b13f5a4599a343e4ef66b96bc340e2a518b36a6662" +dependencies = [ + "bitflags", + "ctor", + "futures", + "libc", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", +] + +[[package]] +name = "napi-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab" + +[[package]] +name = "napi-derive" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af" +dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "napi-derive-backend" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn 2.0.119", +] + +[[package]] +name = "napi-sys" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" +dependencies = [ + "libloading", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object_store" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d354792e39fa5f0009e47623cf8b15b099bf9a652fa55c6f817fe28ac84fea50" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures-channel", + "futures-core", + "futures-util", + "http", + "humantime", + "itertools", + "parking_lot", + "percent-encoding", + "thiserror", + "url", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ruzstd" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "zu" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "zu-common", + "zu-encoding", + "zu-exec", + "zu-query", + "zu-s3", + "zu-sqlite", + "zu-storage", + "zu-vector", + "zu-zu1", +] + +[[package]] +name = "zu-common" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "zu-encoding" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "ruzstd", + "zu-common", +] + +[[package]] +name = "zu-exec" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "zu-common", + "zu-query", + "zu-vector", +] + +[[package]] +name = "zu-query" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "crossbeam-deque", + "zu-common", + "zu-storage", + "zu-vector", +] + +[[package]] +name = "zu-s3" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "crc32c", + "object_store", + "zu-common", + "zu-storage", +] + +[[package]] +name = "zu-sqlite" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "rusqlite", + "zu-common", + "zu-storage", +] + +[[package]] +name = "zu-storage" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "zu-common", + "zu-encoding", +] + +[[package]] +name = "zu-vector" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "zu-common", +] + +[[package]] +name = "zu-zu1" +version = "0.0.1" +source = "git+https://github.com/tamnd/zu?rev=1b6575a9f258d467d7fce56ebd594b42d247789f#1b6575a9f258d467d7fce56ebd594b42d247789f" +dependencies = [ + "crc32c", + "loom", + "zu-common", + "zu-encoding", +] + +[[package]] +name = "zudb-node" +version = "0.0.1" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "zu", + "zu-common", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6d03df7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "zudb-node" +version = "0.0.1" +edition = "2024" +rust-version = "1.97" +license = "Apache-2.0" +repository = "https://github.com/tamnd/zu-node" +authors = ["Tam Nguyen "] +publish = false + +[lib] +name = "zudb" +crate-type = ["cdylib"] + +[dependencies] +# The engine, by revision rather than by version. Nothing is published +# yet, and a binding is built against the engine commit it will ship +# with (ADR 0002), so a revision is the honest way to say which one. +# A local checkout is used instead with a `paths` override in +# `.cargo/config.toml`, which is untracked on purpose. +zudb = { package = "zu", git = "https://github.com/tamnd/zu", rev = "1b6575a9f258d467d7fce56ebd594b42d247789f" } +zu-common = { git = "https://github.com/tamnd/zu", rev = "1b6575a9f258d467d7fce56ebd594b42d247789f" } +# N-API by way of napi-rs (ADR 0002). `napi9` is the version of N-API +# this addon declares it needs, which is what makes one binary work +# across Node 24, Node 26, Electron and Bun without a rebuild: the +# runtime promises everything up to the declared version and the addon +# promises to ask for nothing above it. Node 24 is the oldest runtime +# this supports and it has N-API 10, so 9 is a floor with a version to +# spare rather than the highest number that happens to work here. +napi = { version = "3", default-features = false, features = ["napi9"] } +napi-derive = "3" + +[build-dependencies] +napi-build = "2" + +[profile.release] +# The same shape the engine's own release profile has, because what +# ships here is the engine: one codegen unit and fat LTO across the +# crate graph, which is worth the build minutes on an artifact that is +# built once per release and downloaded by everyone. +lto = "fat" +codegen-units = 1 +strip = "symbols" diff --git a/README.md b/README.md index efab676..68e9b6e 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,43 @@ import { connect } from "zudb"; await using conn = await connect("social.zu1"); -await conn.exec(`CREATE NODE TABLE Person(id INT64 PRIMARY KEY, name STRING)`); -await conn.loadCsv("Person", "people.csv"); +await conn.exec(`INSERT (p:Person {id: 1, name: 'ada'})`); +await conn.exec(`INSERT (p:Person {id: $id, name: $name})`, { id: 2n, name: "zoe" }); -const rows = await conn.query<{ name: string; n: bigint }>( - `MATCH (p:Person)-[:Follows]->(f) - RETURN p.name AS name, count(*) AS n ORDER BY n DESC LIMIT 5`, +const rows = await conn.query<{ id: bigint; name: string }>( + `MATCH (p:Person) WHERE p.name = $name + RETURN p.id AS id, p.name AS name`, + { name: "zoe" }, ); -for (const { name, n } of rows) console.log(name, n); -``` +for (const { id, name } of rows) console.log(id, name); +rows.columns; // ["id", "name"], in the order they were written +rows.gqlstatus; // "00000" ``` -npm i zudb -``` -No `node-gyp`, no postinstall script, no compiler. `optionalDependencies` fetches exactly one prebuilt binary for your platform, and a platform with no prebuild falls back to the WASM build rather than failing the install. +The first insert into a table is what declares it, and the engine works out what each column holds from the values it was given, which is why that one is written with literals and every one after it takes parameters. + +The rows are an array, so iterating them is `for (const row of rows)` and nothing else. What a wrapper object would have carried rides beside the elements as properties that are not enumerable, which is what lets the same value spread, stringify and compare as the plain array it is. ## Decisions worth knowing before you start -- **INT64 is `bigint`.** Always, by default. `{ bigIntMode: "number" }` exists and is documented with its precision hazard, and is never the default, because `count(*)` returning a bigint surprises you once and a corrupted id surprises you in production. -- **Temporal where the runtime has it.** Temporal reached Stage 4 in March 2026 but is unflagged only in Node 26 and recent Chrome, Edge, and Firefox. Node 24 is still the active LTS and Safari is still behind a flag, so the stable public types are `ZuDate`, `ZuTimestamp`, and `ZuDuration`, each with `toTemporal()`. Pass `{ temporal: true }` to get Temporal objects directly, and it throws at connect time on a runtime that lacks it rather than handing you `undefined` three frames later. -- **Nothing blocks the event loop.** Every native call runs on the threadpool. `Sync` variants exist and their doc comments say they belong in scripts, not servers. -- **`await using` is the intended scoping.** `close()` stays public for callers who cannot use it. -- **Web Streams and `AbortSignal`** work the way you expect: `conn.stream(gql)` gives a `ReadableStream` that respects backpressure, and aborting a signal interrupts the query within 50 ms. +- **INT64 is `bigint`.** Always, by default. A JavaScript number stops being exact at 2^53 and zu's integers go to 2^63, so a count that came back as a number would be a count you cannot trust. `{ bigIntMode: "number" }` is planned, will be documented with its precision hazard, and will never be the default. +- **Nothing blocks the event loop.** Every native call runs on libuv's threadpool and hands back a promise before the statement has started. There is no synchronous variant, and the ones that arrive later will say in their own documentation that they belong in scripts, not servers. +- **`await using` is the intended scoping.** A connection is `Symbol.asyncDispose`, and `close()` stays public for callers who cannot use the syntax. +- **A failure is an ordinary `Error`.** Every `catch`, logger and rejection handler already knows what to do with one. What makes it a zu error is the fields, and none of them has to be parsed back out of the message: `code` is the GQLSTATUS and picks the branch, `condition` is the standard's own words for it, `line` and `column` and `excerpt` underline the token, and `retryable` decides whether a retry loop goes round again. A mistake this client caught before the engine saw it carries no `code` and is named `ZuUsageError`, so a caller mapping codes to branches can tell a missing code from one it does not recognize. +- **A refusal is a rejection.** A closed connection and a parameter of a type nothing can bind are refused inside the promise rather than thrown out of the call, so one `await` catches everything one statement can do. + +## What works today + +`connect`, `query`, `exec`, `close`, `dispose` and `await using`. Named parameters both ways, including lists, records and nesting. Every scalar the engine has, plus nodes, edges and paths with their tables named rather than numbered, and `ZuDate`, `ZuTime`, `ZuTimestamp` and `ZuDuration`. Read-only connections, memory and thread limits. The full error surface above. + +Build it with `npm run build`, and run the suite with `npm test`. There are no published binaries yet, so `npm i zudb` is not a thing you can type at anybody's terminal. + +`npm run bench` measures what this package adds to the engine, which is a row object and one JavaScript value per column: the same scan with the rows dropped is the floor, and the difference between the two is what the boundary costs. Run it against a release build, since a debug build of the engine moves the floor by an order of magnitude and not the rest of it. + +## Still to come + +Prebuilt binaries under `optionalDependencies`, with no postinstall script and no `node-gyp`. `AsyncIterable` and Web Streams over a result, and `AbortSignal` wired to the engine's interrupt. `bigIntMode`. `toTemporal()` and `{ temporal: true }`, for the runtimes where Temporal is unflagged: it reached Stage 4 in March 2026 and is unflagged in Node 26, but Node 24 is still the active LTS and Safari is still behind a flag, which is why the stable types are the four classes above. Dual ESM and CJS, with types first in every export condition. Bun and Deno in CI, and the WASM build for the browser. ## Runtimes @@ -37,7 +51,7 @@ No `node-gyp`, no postinstall script, no compiler. `optionalDependencies` fetche |---|---|---| | Node >= 24 (LTS) | prebuilt N-API binary | CI runs 24 and 26 | | Bun >= 1.3 | the same binary | tested as a first-class target, not assumed | -| Deno >= 2.5 | the same binary via `npm:zudb`, or `jsr:@zu/zudb` | needs `--allow-ffi --allow-read` | +| Deno >= 2.9 | the same binary via `npm:zudb`, or `jsr:@zu/zudb` | needs `--allow-ffi --allow-read` | | Browser and edge | `zudb/wasm` | read-mostly, over OPFS or HTTP range requests | | Electron | the same binary | N-API is ABI-stable across Electron versions, so no per-Electron rebuild | diff --git a/bench/query.mjs b/bench/query.mjs new file mode 100644 index 0000000..22a99eb --- /dev/null +++ b/bench/query.mjs @@ -0,0 +1,106 @@ +// What a statement costs on the JavaScript side of the boundary. +// +// The engine's own numbers live in tamnd/zu and are measured there. +// What this measures is what this package adds to them: the promise, the +// lock, the row objects, and one JavaScript value per column per row. +// That last one is the whole cost of a large result and the reason the +// numbers below are per row rather than per statement. +// +// Run it against a release build. A debug build of the engine is an +// order of magnitude slower and the ratio between these rows is not the +// same one, so a debug number is not a slow measurement of the right +// thing. +// +// npm run build && npm run bench + +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import { connect } from '../index.js' + +const ROWS = Number(process.env.ZU_BENCH_ROWS ?? 50_000) +const REPEATS = Number(process.env.ZU_BENCH_REPEATS ?? 9) +// One statement per row is a write per row, and this is not a write +// benchmark. A batch big enough to amortize that and small enough to +// parse quickly is what gets the fixture built. +const BATCH = 500 + +const dir = await mkdtemp(join(tmpdir(), 'zu-bench-')) +const conn = await connect(join(dir, 'bench.zu1')) + +// The declaring insert is written with literals, because that is what +// tells the engine what each column holds. +await conn.exec("INSERT (p:person {id: 0, name: 'n0'})") +for (let start = 1; start < ROWS; start += BATCH) { + const end = Math.min(start + BATCH, ROWS) + const parts = [] + for (let ix = start; ix < end; ix++) parts.push(`(p${ix}:person {id: ${ix}, name: 'n${ix}'})`) + await conn.exec(`INSERT ${parts.join(', ')}`) +} + +/// The fastest of `REPEATS` runs, in milliseconds, after one warmup. +/// +/// The fastest rather than the mean or the median, because everything +/// that makes a run slower than the statement itself is something that +/// happened to it: a collection, another process, a core that clocked +/// down. On this machine the mean of nine runs moves by a factor of +/// three between invocations and the fastest moves by a tenth, so the +/// fastest is the number a change can be compared across. +async function time(run) { + await run() + let best = Infinity + for (let round = 0; round < REPEATS; round++) { + const started = performance.now() + await run() + best = Math.min(best, performance.now() - started) + } + return best +} + +const cases = [ + { + name: 'scan, two columns', + per: 'row', + run: () => conn.query('MATCH (p:person) RETURN p.id AS id, p.name AS name'), + }, + { + name: 'scan, one INT64 column', + per: 'row', + run: () => conn.query('MATCH (p:person) RETURN p.id AS id'), + }, + { + name: 'scan, whole nodes', + per: 'row', + run: () => conn.query('MATCH (p:person) RETURN p AS p'), + }, + { + name: 'scan, rows dropped', + per: 'row', + run: () => conn.exec('MATCH (p:person) RETURN p.id AS id, p.name AS name'), + }, + { + name: 'aggregate, one row out', + per: 'statement', + run: () => conn.query('MATCH (p:person) RETURN count(*) AS n'), + }, + { + name: 'aggregate, one parameter in', + per: 'statement', + run: () => conn.query('MATCH (p:person) WHERE p.name = $name RETURN count(*) AS n', { + name: 'n1', + }), + }, +] + +console.log(`${ROWS} rows, fastest of ${REPEATS}`) +for (const { name, per, run } of cases) { + const ms = await time(run) + const each = per === 'row' ? (ms * 1e6) / ROWS : ms * 1e6 + console.log( + `${name.padEnd(28)} ${ms.toFixed(2).padStart(9)} ms ${Math.round(each).toString().padStart(9)} ns/${per}`, + ) +} + +conn.close() +await rm(dir, { recursive: true, force: true }) diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bffce49 --- /dev/null +++ b/build.rs @@ -0,0 +1,8 @@ +// napi-build writes the linker arguments an addon needs on the +// platform it is being built for: the undefined-symbol allowance macOS +// wants, the import library Windows wants, and nothing at all on +// Linux. It is the whole build script and there is no code generation +// behind it. +fn main() { + napi_build::setup(); +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..b551ad9 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,360 @@ +/* auto-generated by NAPI-RS */ +/* eslint-disable */ + +/** + * A value a statement can hold, going out. + * + * INT64 is `bigint` and FLOAT is `number`, which is the one rule worth + * learning before anything else here: a JavaScript number stops being + * exact at 2^53 and zu's integers go to 2^63, so a count that came back + * as a number would be a count you cannot trust. + */ +export type ZuValue = + | null + | boolean + | number + | bigint + | string + | ZuNode + | ZuRel + | ZuPath + | ZuDate + | ZuTime + | ZuTimestamp + | ZuDuration + | ZuValue[] + | { [field: string]: ZuValue } + +/** + * A value a statement can be given, coming in. + * + * Wider than what comes out, because a `number` that is whole binds as + * INT64 and `undefined` binds as null, which is what makes an optional + * field of a plain object pass straight through. + */ +export type ZuParam = + | null + | undefined + | boolean + | number + | bigint + | string + | ZuDate + | ZuTime + | ZuTimestamp + | ZuDuration + | ZuParam[] + | { [field: string]: ZuParam } + +/** + * A walk through the graph: nodes and edges, alternating, a node at + * each end. + * + * The two lists are kept apart rather than interleaved, because the + * question a caller asks is almost always about one of them. A path of + * one node has no edges and is the shortest there is. + */ +export interface ZuPath { + readonly nodes: ZuNode[] + readonly rels: ZuRel[] +} + +/** + * Something the engine wants to say about a statement that ran anyway. + * + * A notice is a completion condition of its own, so it carries a + * GQLSTATUS the same way a failure does. The one raised today is + * `01G11`, which says an aggregate ignored a null. + */ +export interface ZuNotice { + readonly code: string + readonly condition: string + readonly message: string + readonly docUrl: string +} + +/** + * The rows a statement gave back. + * + * An array, so iterating it is `for (const row of rows)` and nothing + * else. What a wrapper object would have carried is carried as + * properties beside the elements: `columns` for the projection in the + * order it was written, `gqlstatus` for the condition the statement + * completed with, and `notices` for what it wanted to say on the way. + */ +export interface ZuRows> extends Array { + readonly columns: string[] + readonly gqlstatus: string + readonly notices: ZuNotice[] +} + +/** + * What a failed call throws. + * + * An ordinary `Error`, so every `catch`, logger and unhandled rejection + * handler already knows what to do with it. What makes it a zu error is + * the fields, and none of them has to be parsed back out of the + * message: `code` picks the branch, `line` and `column` underline the + * token, `retryable` decides whether a retry loop goes round again. + */ +export interface ZuError extends Error { + /** + * The condition's class, written out: `ZuSyntaxError`, + * `ZuDataError`, `ZuTransactionError`, `ZuConnectionError`, + * `ZuInterrupted`, `ZuUsageError`, `ZuInternalError`, or `ZuError` + * for a condition in a class none of those name. + */ + readonly name: string + /** Whether running the same statement again could succeed. */ + readonly retryable: boolean + /** + * The GQLSTATUS, five characters. Absent on a mistake this client + * caught before the engine saw it, which is why a caller mapping + * codes to branches has to tell a missing one from an unknown one. + */ + readonly code?: string + /** The standard's own words for that code. */ + readonly condition?: string + readonly severity?: 'success' | 'noData' | 'warning' | 'informational' | 'exception' + readonly docUrl?: string + /** Where in the statement, for a condition that happened somewhere. */ + readonly line?: number + readonly column?: number + readonly offset?: number + /** The whole line `column` indexes into, for underlining it. */ + readonly excerpt?: string +} +/** + * One connection to one database. + * + * Statements run on it in order, one at a time. It reads the database + * as of when it was opened, which is why a program that wants to see + * another writer's work takes a new connection rather than waiting on + * this one. + */ +export declare class Connection { + /** Where the database this is connected to lives. */ + get path(): string + /** Whether this connection refuses every statement that writes. */ + get readOnly(): boolean + /** Whether the connection is still open. */ + get open(): boolean + /** + * Runs one statement and gives back its rows. + * + * The parameters are named, never positional, because zuQL names + * them: `$id` in the statement is `id` in the object. A name the + * statement does not use is an error from the engine rather than a + * value quietly ignored. + */ + query>(statement: string, params?: Record | null): Promise> + /** + * Runs one statement for its effect and gives back nothing. + * + * The same call as [`Connection::query`] with the rows dropped, + * which is what a schema statement or a write wants: a result nobody + * reads still costs a row object per row on the way out. + */ + exec(statement: string, params?: Record | null): Promise + /** + * Closes the connection and releases the database. + * + * Closing twice does nothing the second time, which is what makes + * `await using` safe to combine with an explicit close in a branch + * that ran first. + */ + close(): void + /** + * The disposal `await using` calls, which is the intended way to + * scope a connection. `close` stays public for callers who cannot + * use it. + * + * It is also reachable as `Symbol.asyncDispose`, which is what + * `await using` actually looks for and which [`wire_disposal`] puts + * on every connection as it is made. + */ + dispose(): Promise +} + +/** + * A date, as days from 1970-01-01. + * + * A count rather than a set of fields, which is what the engine + * stores and what makes two dates compare as two numbers. `Temporal` + * reached Stage 4 in March 2026 but is unflagged only in Node 26 and + * the newest browsers, and Node 24 is still the active LTS, so this + * is the stable type and the runtimes that have `Temporal` reach it + * through the opt-in rather than through a type that changes shape + * under them. + */ +export declare class ZuDate { + days: number + constructor(days: number) + /** + * The same thing as a plain object, for the reason [`ZuNode::to_json`] + * gives. + */ + toJSON(): object +} + +/** + * A duration, which is a count of months or a count of nanoseconds + * and never both. + * + * The two kinds do not mix, because no number of days is a month: a + * value holding both would have to invent an answer for one month + * after 31 January, and the standard does not ask for the invention. + * `kind` says which one this is, and the other count is zero. + */ +export declare class ZuDuration { + /** A duration of `months` months, which is the year-month kind. */ + static ofMonths(months: bigint): ZuDuration + /** A duration of `nanos` nanoseconds, which is the day-time kind. */ + static ofNanos(nanos: bigint): ZuDuration + /** Which of the two kinds this is: `yearMonth` or `dayTime`. */ + get kind(): string + /** The months, which is zero for a day-time duration. */ + get months(): bigint + /** The nanoseconds, which is zero for a year-month duration. */ + get nanos(): bigint + /** + * The same thing as a plain object, for the reason [`ZuNode::to_json`] + * gives. + */ + toJSON(): object +} + +/** + * One node of the graph. + * + * The table is the name written in the schema and the offset is the + * row it sits at in that table, which together are what identifies a + * node in zu. The offset is a `bigint` for the reason every other + * 64-bit integer here is one. + * + * The 64-bit fields are held as integers and handed out as `bigint` by + * a getter, rather than being held as a `bigint` and handed out + * directly, because a `bigint` is a JavaScript value and a value + * cannot be held by a struct that is built on a threadpool thread + * where there is no environment to build one in. + */ +export declare class ZuNode { + get table(): string + get offset(): bigint + /** + * The same thing as a plain object. + * + * Every field here is a getter on the prototype, which is what + * lets a 64-bit field be handed out as a `bigint`, and a getter is + * invisible to `JSON.stringify` and to a shallow copy. So each of + * these classes says what it holds when it is asked, and a caller + * stringifying a row gets the node rather than `{}`. A `bigint` + * still has no JSON spelling, so a caller doing that passes a + * replacer, which they already had to. + */ + toJSON(): object +} + +/** + * One edge of the graph. + * + * `ord` is where the edge's properties sit, which is its place in the + * order the table was loaded in. That is what names an edge: a pair of + * endpoints does not, since the same pair may run more than once and + * each of those edges carries its own values. It is the field a caller + * usually ignores and the one nothing else can replace. + */ +export declare class ZuRel { + get table(): string + get src(): bigint + get dst(): bigint + get ord(): bigint + /** + * The same thing as a plain object, for the reason [`ZuNode::to_json`] + * gives. + */ + toJSON(): object +} + +/** + * A time of day, as nanoseconds from midnight, with the offset from + * UTC in minutes for a zoned one and `null` for a local one. + * + * The offset is minutes and never an IANA name, which is the engine's + * decision and worth repeating here: a name is a rule that changes + * under a stored value when the zone database is updated, and a value + * that means something different tomorrow is not a value. + */ +export declare class ZuTime { + offset?: number + constructor(nanos: bigint, offset?: number | undefined | null) + get nanos(): bigint + /** + * The same thing as a plain object, for the reason [`ZuNode::to_json`] + * gives. + */ + toJSON(): object +} + +/** + * A date and a time together, as nanoseconds from the epoch, with the + * offset from UTC in minutes for a zoned one and `null` for a local + * one. + * + * A zoned one holds the instant in UTC and the offset separately, so + * two of them compare as instants while each still prints in the zone + * it was written in. + */ +export declare class ZuTimestamp { + offset?: number + constructor(nanos: bigint, offset?: number | undefined | null) + get nanos(): bigint + /** + * The same thing as a plain object, for the reason [`ZuNode::to_json`] + * gives. + */ + toJSON(): object +} + +/** + * The revision of the C ABI this client implements the semantics of. + * + * Not the same number as [`version`] and not meant to be: the ABI is + * the specification every zu client is graded against, so this is what + * says which edition of that specification the behaviour here matches, + * and it moves when the specification does rather than when this crate + * does. + */ +export declare function abiVersion(): string + +/** + * Opens the database at `path` and connects to it. + * + * Creates one when the path holds nothing, which is what a first + * program expects and what every embedded database does. A read-only + * connection never creates anything. + */ +export declare function connect(path: string, options?: ConnectOptions | undefined | null): Promise + +/** + * What a connection can be opened with. + * + * Every field is optional and every default is the engine's, because + * a client that invents its own defaults is a client whose numbers + * have to be found and changed twice. + */ +export interface ConnectOptions { + /** + * Opens without creating and refuses every statement that writes. + * A read-only connection never creates a database, so a mistyped + * path is an error here rather than an empty database. + */ + readOnly?: boolean + /** How much memory the executor may hold, in bytes. */ + memoryLimit?: bigint + /** How many threads the executor may use. */ + threads?: number +} + +/** The version of the client. */ +export declare function version(): string diff --git a/index.js b/index.js new file mode 100644 index 0000000..527648d --- /dev/null +++ b/index.js @@ -0,0 +1,712 @@ +// prettier-ignore +/* eslint-disable */ +// @ts-nocheck +/* auto-generated by NAPI-RS */ + +const { readFileSync } = require('fs') +let nativeBinding = null +const loadErrors = [] + +const isMusl = () => { + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() + if (musl === null) { + musl = isMuslFromReport() + } + if (musl === null) { + musl = isMuslFromChildProcess() + } + } + return musl +} + +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') + +const isMuslFromFilesystem = () => { + try { + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') + } catch { + return null + } +} + +const isMuslFromReport = () => { + let report = null + if (process.report && typeof process.report.getReport === 'function') { + process.report.excludeNetwork = true + report = process.report.getReport() + } + if (!report) { + return null + } + if (report.header && report.header.glibcVersionRuntime) { + return false + } + if (Array.isArray(report.sharedObjects)) { + if (report.sharedObjects.some(isFileMusl)) { + return true + } + } + return false +} + +const isMuslFromChildProcess = () => { + try { + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') + } catch (e) { + // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false + return false + } +} + +function requireNative() { + if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { + try { + return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); + } catch (err) { + loadErrors.push(err) + } + } else if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./zudb.android-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-android-arm64') + const bindingPackageVersion = require('zudb-android-arm64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm') { + try { + return require('./zudb.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-android-arm-eabi') + const bindingPackageVersion = require('zudb-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) + } + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) { + try { + return require('./zudb.win32-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-win32-x64-gnu') + const bindingPackageVersion = require('zudb-win32-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-win32-x64-msvc') + const bindingPackageVersion = require('zudb-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'ia32') { + try { + return require('./zudb.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-win32-ia32-msvc') + const bindingPackageVersion = require('zudb-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./zudb.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-win32-arm64-msvc') + const bindingPackageVersion = require('zudb-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) + } + } else if (process.platform === 'darwin') { + try { + return require('./zudb.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-darwin-universal') + const bindingPackageVersion = require('zudb-darwin-universal/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + if (process.arch === 'x64') { + try { + return require('./zudb.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-darwin-x64') + const bindingPackageVersion = require('zudb-darwin-x64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./zudb.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-darwin-arm64') + const bindingPackageVersion = require('zudb-darwin-arm64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) + } + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./zudb.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-freebsd-x64') + const bindingPackageVersion = require('zudb-freebsd-x64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./zudb.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-freebsd-arm64') + const bindingPackageVersion = require('zudb-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { + try { + return require('./zudb.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-x64-musl') + const bindingPackageVersion = require('zudb-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-x64-gnu') + const bindingPackageVersion = require('zudb-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'arm64') { + if (isMusl()) { + try { + return require('./zudb.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-arm64-musl') + const bindingPackageVersion = require('zudb-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-arm64-gnu') + const bindingPackageVersion = require('zudb-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'arm') { + if (isMusl()) { + try { + return require('./zudb.linux-arm-musleabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-arm-musleabihf') + const bindingPackageVersion = require('zudb-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-arm-gnueabihf') + const bindingPackageVersion = require('zudb-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'loong64') { + if (isMusl()) { + try { + return require('./zudb.linux-loong64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-loong64-musl') + const bindingPackageVersion = require('zudb-linux-loong64-musl/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.linux-loong64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-loong64-gnu') + const bindingPackageVersion = require('zudb-linux-loong64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'riscv64') { + if (isMusl()) { + try { + return require('./zudb.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-riscv64-musl') + const bindingPackageVersion = require('zudb-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./zudb.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-riscv64-gnu') + const bindingPackageVersion = require('zudb-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'ppc64') { + try { + return require('./zudb.linux-ppc64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-ppc64-gnu') + const bindingPackageVersion = require('zudb-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 's390x') { + try { + return require('./zudb.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-linux-s390x-gnu') + const bindingPackageVersion = require('zudb-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) + } + } else if (process.platform === 'openharmony') { + if (process.arch === 'arm64') { + try { + return require('./zudb.openharmony-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-openharmony-arm64') + const bindingPackageVersion = require('zudb-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'x64') { + try { + return require('./zudb.openharmony-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-openharmony-x64') + const bindingPackageVersion = require('zudb-openharmony-x64/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm') { + try { + return require('./zudb.openharmony-arm.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('zudb-openharmony-arm') + const bindingPackageVersion = require('zudb-openharmony-arm/package.json').version + if (bindingPackageVersion !== '0.0.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)) + } + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } +} + +function createLoadErrorChain(errors) { + return errors.reduce((previous, current) => { + let message + try { + message = + current && typeof current.message === 'string' + ? current.message + : String(current) + } catch { + message = 'Unknown error' + } + const error = new Error(message) + error.cause = previous + return error + }, null) +} + +// NAPI_RS_FORCE_WASI is a tri-state flag: +// unset / any other value → native binding preferred, WASI is only a fallback +// 'true' → prefer WASI, but retain native as a lazy fallback +// 'error' → require WASI without initializing a native fallback +// Treating any non-empty string as truthy (the historical behavior) meant +// NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered +// the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. +// +// NAPI_RS_WASI_FLAVOR selects one exact generated flavor and implies strict +// WASI loading. It never crosses into another flavor or falls back to native. +const __napiWasiFlavors = ["wasm32-wasi"] +const __napiWasiFlavor = process.env.NAPI_RS_WASI_FLAVOR +const __napiWasiFlavorRequested = + typeof __napiWasiFlavor === 'string' && __napiWasiFlavor.length > 0 +if ( + __napiWasiFlavorRequested && + __napiWasiFlavors.indexOf(__napiWasiFlavor) === -1 +) { + throw new Error( + 'Unsupported WASI flavor "' + + __napiWasiFlavor + + '". Available flavors: ' + + __napiWasiFlavors.join(', '), + ) +} +const forceWasiError = process.env.NAPI_RS_FORCE_WASI === 'error' +const forceWasi = + process.env.NAPI_RS_FORCE_WASI === 'true' || + forceWasiError || + __napiWasiFlavorRequested + +if (!forceWasi) { + nativeBinding = requireNative() +} + +if (!nativeBinding || forceWasi) { + let wasiBinding = null + let wasiBindingLoaded = false + const wasiBindingErrors = [] + const __napiWasiResolveCandidate = (specifier, isPackage, localArtifacts) => { + try { + require.resolve(specifier) + } catch (resolveError) { + if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') { + throw resolveError + } + if (isPackage) { + try { + require.resolve(specifier + '/package.json') + } catch (packageError) { + if (packageError && packageError.code === 'MODULE_NOT_FOUND') { + return resolveError + } + // An exports restriction proves the package exists even when its + // package.json is not public. Preserve the root resolution failure. + throw resolveError + } + // The package exists but its main/export target is broken. + throw resolveError + } + return resolveError + } + if (localArtifacts) { + let artifactError = null + for (let i = 0; i < localArtifacts.length; i++) { + try { + require.resolve(localArtifacts[i]) + return null + } catch (resolveError) { + if (!resolveError || resolveError.code !== 'MODULE_NOT_FOUND') { + throw resolveError + } + artifactError = resolveError + } + } + return artifactError + } + return null + } + if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === "wasm32-wasi")) { + let candidateError = null + let candidateFailed = false + try { + candidateError = __napiWasiResolveCandidate('./zudb.wasi.cjs', false, ["./zudb.wasm32-wasi.debug.wasm","./zudb.wasm32-wasi.wasm"]) + candidateFailed = candidateError !== null + if (!candidateFailed) { + wasiBinding = require('./zudb.wasi.cjs') + nativeBinding = wasiBinding + wasiBindingLoaded = true + } + } catch (err) { + candidateError = err + candidateFailed = true + } + if (candidateFailed) { + wasiBindingErrors.push(candidateError) + loadErrors.push(candidateError) + } + } + if (!wasiBindingLoaded && (!__napiWasiFlavorRequested || __napiWasiFlavor === "wasm32-wasi")) { + let candidateError = null + let candidateFailed = false + try { + candidateError = __napiWasiResolveCandidate('zudb-wasm32-wasi', true, undefined) + candidateFailed = candidateError !== null + if (!candidateFailed) { + if (process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + const bindingPackageVersion = require('zudb-wasm32-wasi/package.json').version + if (bindingPackageVersion !== '0.0.1') { + throw new Error(`WASI binding package version mismatch, expected 0.0.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + } + wasiBinding = require('zudb-wasm32-wasi') + nativeBinding = wasiBinding + wasiBindingLoaded = true + } + } catch (err) { + candidateError = err + candidateFailed = true + } + if (candidateFailed) { + wasiBindingErrors.push(candidateError) + loadErrors.push(candidateError) + } + } + if ( + !wasiBindingLoaded && + forceWasi && + !forceWasiError && + !__napiWasiFlavorRequested + ) { + nativeBinding = requireNative() + } + if ((forceWasiError || __napiWasiFlavorRequested) && !wasiBindingLoaded) { + const error = new Error( + __napiWasiFlavorRequested + ? 'WASI binding for flavor "' + __napiWasiFlavor + '" not found' + : 'WASI binding not found and NAPI_RS_FORCE_WASI is set to error', + ) + error.cause = createLoadErrorChain(wasiBindingErrors) + throw error + } +} + +if (!nativeBinding) { + if (loadErrors.length > 0) { + const error = new Error( + `Cannot find native binding. ` + + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', + ) + // assign instead of the `new Error(message, { cause })` options form, + // which Node < 16.9 silently ignores + error.cause = createLoadErrorChain(loadErrors) + throw error + } + throw new Error(`Failed to load native binding`) +} + +module.exports = nativeBinding +module.exports.Connection = nativeBinding.Connection +module.exports.ZuDate = nativeBinding.ZuDate +module.exports.ZuDuration = nativeBinding.ZuDuration +module.exports.ZuNode = nativeBinding.ZuNode +module.exports.ZuRel = nativeBinding.ZuRel +module.exports.ZuTime = nativeBinding.ZuTime +module.exports.ZuTimestamp = nativeBinding.ZuTimestamp +module.exports.abiVersion = nativeBinding.abiVersion +module.exports.connect = nativeBinding.connect +module.exports.version = nativeBinding.version diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6a34abb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1975 @@ +{ + "name": "zudb", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zudb", + "version": "0.0.1", + "license": "Apache-2.0", + "devDependencies": { + "@napi-rs/cli": "^3.8.6" + }, + "engines": { + "node": ">= 24" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@napi-rs/cli": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-3.8.6.tgz", + "integrity": "sha512-FnJ9fghsV9Q4zh2aJGPSvQiUlJRC27B6KhzAXcIW2rlSD8keak3mhXw4tJYa3KJkP9whETfsPwqp/DJRnQg5ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^8.5.2", + "@napi-rs/cross-toolchain": "^1.0.3", + "@napi-rs/wasm-tools": "^1.1.0", + "@octokit/rest": "^22.0.1", + "clipanion": "^4.0.0-rc.4", + "colorette": "^2.0.20", + "es-toolkit": "^1.47.0", + "js-yaml": "^4.2.0", + "obug": "^2.1.2", + "semver": "^7.8.2", + "typanion": "^3.14.0", + "typescript": "^6.0.3" + }, + "bin": { + "napi": "dist/cli.js", + "napi-raw": "cli.mjs" + }, + "engines": { + "node": "^20.17.0 || ^22.13.0 || >= 23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4", + "emnapi": "^1.7.1 || ^2.0.0-alpha.4" + }, + "peerDependenciesMeta": { + "@emnapi/core": { + "optional": true + }, + "@emnapi/runtime": { + "optional": true + }, + "emnapi": { + "optional": true + } + } + }, + "node_modules/@napi-rs/cross-toolchain": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@napi-rs/cross-toolchain/-/cross-toolchain-1.0.3.tgz", + "integrity": "sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==", + "dev": true, + "license": "MIT", + "workspaces": [ + ".", + "arm64/*", + "x64/*" + ], + "dependencies": { + "@napi-rs/lzma": "^1.4.5", + "@napi-rs/tar": "^1.1.0", + "debug": "^4.4.1" + }, + "peerDependencies": { + "@napi-rs/cross-toolchain-arm64-target-aarch64": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-armv7": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-ppc64le": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-s390x": "^1.0.3", + "@napi-rs/cross-toolchain-arm64-target-x86_64": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-aarch64": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-armv7": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-ppc64le": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-s390x": "^1.0.3", + "@napi-rs/cross-toolchain-x64-target-x86_64": "^1.0.3" + }, + "peerDependenciesMeta": { + "@napi-rs/cross-toolchain-arm64-target-aarch64": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-armv7": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-ppc64le": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-s390x": { + "optional": true + }, + "@napi-rs/cross-toolchain-arm64-target-x86_64": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-aarch64": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-armv7": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-ppc64le": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-s390x": { + "optional": true + }, + "@napi-rs/cross-toolchain-x64-target-x86_64": { + "optional": true + } + } + }, + "node_modules/@napi-rs/lzma": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma/-/lzma-1.5.1.tgz", + "integrity": "sha512-sgOZ89+y8cDbY+3WbzR8CtIhCuFRWotZ9/2PjPVDJHz6np5KFTAev0DrwiyTJTgFsCRDhfGlbmhMgyhHbWdZ6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.20 || ^24.12 || >=25" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/lzma-android-arm-eabi": "1.5.1", + "@napi-rs/lzma-android-arm64": "1.5.1", + "@napi-rs/lzma-darwin-arm64": "1.5.1", + "@napi-rs/lzma-darwin-x64": "1.5.1", + "@napi-rs/lzma-freebsd-x64": "1.5.1", + "@napi-rs/lzma-linux-arm-gnueabihf": "1.5.1", + "@napi-rs/lzma-linux-arm64-gnu": "1.5.1", + "@napi-rs/lzma-linux-arm64-musl": "1.5.1", + "@napi-rs/lzma-linux-ppc64-gnu": "1.5.1", + "@napi-rs/lzma-linux-riscv64-gnu": "1.5.1", + "@napi-rs/lzma-linux-s390x-gnu": "1.5.1", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@napi-rs/lzma-linux-x64-musl": "1.5.1", + "@napi-rs/lzma-wasm32-wasi": "1.5.1", + "@napi-rs/lzma-win32-arm64-msvc": "1.5.1", + "@napi-rs/lzma-win32-ia32-msvc": "1.5.1", + "@napi-rs/lzma-win32-x64-msvc": "1.5.1" + } + }, + "node_modules/@napi-rs/lzma-android-arm-eabi": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.5.1.tgz", + "integrity": "sha512-sahBe4ko2Z69NPTddaX6ZgbQZu9SDoITxw1S3dWl1gAGynZG34qHHCT8UaUMFxf3h3zMhCJjEzz4basaBxiTuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-android-arm64": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.5.1.tgz", + "integrity": "sha512-7tkQAJJuBHxAxiEBNFgSTpvrtGpbwZYYJUSOmGEK3OfbdbNeoT2rdBxpM/gY1s+itEVbtOSlpaRPPG19MnwOzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-darwin-arm64": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.5.1.tgz", + "integrity": "sha512-XWX8gtF+GHGk3nH3Wm3QUZNcxw9QHsFVZz3MzVLhWWHhceede1J4/vD+3dj3E1iKB9G6mualaZxOoD08R3E+7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-darwin-x64": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.5.1.tgz", + "integrity": "sha512-CfsqUpMTI1z8enrA/b+GcHM6YDI8D0kqCiqPYEnst4rbOABQ9KZ92ybTTNnlnZ7A017WoMZKUEWc36KXDwi0xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-freebsd-x64": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.5.1.tgz", + "integrity": "sha512-bTyNfg90FXIgE61U7l14aMmVOqRQ6AyP5JMT3jmCStaZI18apLNPdzZ8i7yqxZfKvRMVfPjE2brXIw27c+RRgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-arm-gnueabihf": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.5.1.tgz", + "integrity": "sha512-vNE+D8nrw+eOkBsdKCsmDhowDV3pIMKXEhedvXfbgrWbrO7GlZJH+RXL+X+RYLxGwi8Ym61ZMt15sIOnNmh9Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.5.1.tgz", + "integrity": "sha512-csUem4WgoKGTprv/pOPm9UIWbb+hrfUwYXefpTHPAEGVFLl5behEFabisJ7FtihCa3yG2Efcl+yw25rlhhrIYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-arm64-musl": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.5.1.tgz", + "integrity": "sha512-kB/xhlVN1eLvVmDJSKZEjp5Gg2xDYexNrB5jwpSMbOkeGS6N9AasByPBg5VqCpMYC+zZi7DM458DRhtWYhqXTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-ppc64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-ppc64-gnu/-/lzma-linux-ppc64-gnu-1.5.1.tgz", + "integrity": "sha512-s28RW0W1yBWQc1nbPdF7tp14koqslY3ZWLVI8uaanX292Dc6ezd4NPVwxEoCNBVON/oD7BmUbWGtyFvmm7dQ5A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-riscv64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-riscv64-gnu/-/lzma-linux-riscv64-gnu-1.5.1.tgz", + "integrity": "sha512-+lGNwYlIN14YPMTNvYtIJJqHFevDTd6Juw/1NmXbWx/iRd/LLrjhlM/yluMX6pxs6NkOGsuuEXJJrbbEUS59OQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-s390x-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-s390x-gnu/-/lzma-linux-s390x-gnu-1.5.1.tgz", + "integrity": "sha512-PB44FFWWFrLeQowhcep1hPD1YcLqKlnnY60RMU74qrxTlr4YGEyzeMItJqh2uivBfv9kQScOF/B0J9+Vab/oyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-musl": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.5.1.tgz", + "integrity": "sha512-I3nsYrWtrW9JpeCr+mkJIVDt0HY3m6qVUBs5vTtoIvJQxwqf1PBXSy5IS7T53ksQFH2kd2UX8rLxJ7B4WISpZg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-wasm32-wasi": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-wasm32-wasi/-/lzma-wasm32-wasi-1.5.1.tgz", + "integrity": "sha512-gy3wwPBa6+XEyA4fUzq6CClrXA1ajXjuVf5zbnHytJRgoHznj+mvpU3+co2fxXwqTCmIpn6KrzqH5bRDztBPhA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-win32-arm64-msvc": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.5.1.tgz", + "integrity": "sha512-dK+huOsHiyH6oJjij+cnjqFCakk2HgWmpI12Xm4pLUyPphe4ebYoJBgehaNAxprmjFqBQ7nL95YPVz9BHyqmPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-win32-ia32-msvc": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.5.1.tgz", + "integrity": "sha512-dGE8L+0EQ+GyU9ap9InqB/t/PmPG/bLj918q7OsJ29FuTdn8fK4OX3U4IQZhylHIA+/dQ/SXJk5n4yfah2XVvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/lzma-win32-x64-msvc": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.5.1.tgz", + "integrity": "sha512-EKW4t/iqdCT/xnd5t9oXLvVER/PMNAWXKqUAl3fgvUcOILeZIIht77/dVnfFcc9htA/DCBXC/6YQWdW+LusjFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@napi-rs/tar": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar/-/tar-1.1.1.tgz", + "integrity": "sha512-p6q2HhUc5vwH1CNwfOcrhLoxfgn8ust8Sqlfx+sA4VzAcp1cMbvbkl99tZZlDqOjCHgQNSiTfk/yWPjl/D42qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@napi-rs/tar-android-arm-eabi": "1.1.1", + "@napi-rs/tar-android-arm64": "1.1.1", + "@napi-rs/tar-darwin-arm64": "1.1.1", + "@napi-rs/tar-darwin-x64": "1.1.1", + "@napi-rs/tar-freebsd-x64": "1.1.1", + "@napi-rs/tar-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/tar-linux-arm64-gnu": "1.1.1", + "@napi-rs/tar-linux-arm64-musl": "1.1.1", + "@napi-rs/tar-linux-ppc64-gnu": "1.1.1", + "@napi-rs/tar-linux-s390x-gnu": "1.1.1", + "@napi-rs/tar-linux-x64-gnu": "1.1.1", + "@napi-rs/tar-linux-x64-musl": "1.1.1", + "@napi-rs/tar-wasm32-wasi": "1.1.1", + "@napi-rs/tar-win32-arm64-msvc": "1.1.1", + "@napi-rs/tar-win32-ia32-msvc": "1.1.1", + "@napi-rs/tar-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/tar-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm-eabi/-/tar-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-cAhnA10cSusAUbcE9HtjQY/tZ9BH/0w2sKtRcQc94TzIlnm7QSr1htJSd/PPrbWNPtrv1orXb2CkrHlVlbnlHA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-android-arm64/-/tar-android-arm64-1.1.1.tgz", + "integrity": "sha512-EslUWHCDBY/g5abTPBiHLsMaML4GagV0TXLm5WL9hAjx/DDtlxz9fegMb77RJ+f7nFLOIsUxF/3QWFvgOT0sMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-arm64/-/tar-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-+A42/6ES5G9CQ35BOwzwA+WBjLID28r2jNPgc0dteD2hhClIhng0mva7D2ujUlXBNmgNOsr1LHn3stA4uTf4NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-darwin-x64/-/tar-darwin-x64-1.1.1.tgz", + "integrity": "sha512-RYtE8w1dkEvj8hSJCDV5Jw0Rz2i13fsM7u893zv5O9n/4Ad5GNsw/f4RQ7/0YGSFaenkVxqPFrjmEvUHlKzsrg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-freebsd-x64/-/tar-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-rEepBvCJUwcuvUYkY83e8aot8RsR5Jcnal4PsG3tbWGKW1yAvcXhyMXf0fN6ZGpVRZFnB+FJqDyBxvsCPEXKhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm-gnueabihf/-/tar-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-an1bJdfyhI5FpZYyTQ20mrqwR+a676i8GkaYc4Uy12dH/a7TJIfrK6Qa2Gm46arZvxUvx56qxoRKXbpOjUPvwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-gnu/-/tar-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-w++Vtx36T2yHTKws7GVnmHHcUT1ybB59xLWSh9A8bwEpJVG4dG7Qub9mFe5cpcbfrJ+XP2mKKxC3oUJSunK3iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-arm64-musl/-/tar-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-Rh6UFhNtj3i4deJHOBINFIeRL0072mgbeyuK5rl1HokKnNoMKx8qKIZNEzBTTqpogMfDHWGvzyTQdnVxes5dpA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-ppc64-gnu/-/tar-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-Cp+AxFbv9zcyAXtnzQi0OzmgDnQgy2w9D4Ubr+iwzMtVgJcztzcEoCcCrN1k2ATdEB01LX2Vb49IaocGOZhC9Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-s390x-gnu/-/tar-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-ZyscC3SYKTBWyDRYjLOKAd5TyJ7q0KACRdQ8bWrb3rgrra1CCIJD66CsGTH6Dh0AVSdfLwZ8MfIIXU6+14BMjQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-gnu/-/tar-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-LlIv+zg4fiOQge9LQX/ieBdRWE2fhVDjCTHxnunZkbugNmdhdelxWf1RpZb/6ZujWpNF4LPu4N/MW7ygg2oYAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-linux-x64-musl/-/tar-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-gZBeoKLjanOVj55qk4EMu13P2i9M0SuINmlGQkOxm1niIJofexzddHUYtqO5o/5QqtyL8lADmAcZplLILMLhHA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-wasm32-wasi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-wasm32-wasi/-/tar-wasm32-wasi-1.1.1.tgz", + "integrity": "sha512-rwtQ1Mdt/ft6g6I54fJzbUeLspl4yTwj6I3UJ6mitKnrN42soJkcDrdh3Y/FGvlpqZTad2YMQ96fGJl3EtAm2Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/tar-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-arm64-msvc/-/tar-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-30PVp1AehRpfwxmv5wI4cg0yj3WmWBsZ+1QnLGnvEELu7Eu/+dhNU0nrmhI7VfPgLwSRK2eg9DQTB3tP7Wv9bA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-ia32-msvc/-/tar-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-aI3/rmz+izUChiSeaPxcasAOxhf3FpJNuIHMXlxS/vpW+HIxUsSDR5+XV61PEG5DL4L/75iENVUxmSGM5l2yaw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/tar-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/tar-win32-x64-msvc/-/tar-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-yJsB2IsrODQVLKbm2Fg1nHiVRbEj49mSPbj4x7JPZWJI0jGVPjohE2Sif0FBbx8OxsVoUODvS0BwksZZ8jl/OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@napi-rs/wasm-tools": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools/-/wasm-tools-1.1.0.tgz", + "integrity": "sha512-VjHyKEqXAwYZK+HY7iJctYvRm3TFEbaQxeZwvAG1QRkoo1a39phMY8J6x9tUEqJI03W6MysB8F2jacI6wvcx+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.22.0" + }, + "optionalDependencies": { + "@napi-rs/wasm-tools-android-arm-eabi": "1.1.0", + "@napi-rs/wasm-tools-android-arm64": "1.1.0", + "@napi-rs/wasm-tools-darwin-arm64": "1.1.0", + "@napi-rs/wasm-tools-darwin-x64": "1.1.0", + "@napi-rs/wasm-tools-freebsd-x64": "1.1.0", + "@napi-rs/wasm-tools-linux-arm64-gnu": "1.1.0", + "@napi-rs/wasm-tools-linux-arm64-musl": "1.1.0", + "@napi-rs/wasm-tools-linux-x64-gnu": "1.1.0", + "@napi-rs/wasm-tools-linux-x64-musl": "1.1.0", + "@napi-rs/wasm-tools-wasm32-wasi": "1.1.0", + "@napi-rs/wasm-tools-win32-arm64-msvc": "1.1.0", + "@napi-rs/wasm-tools-win32-ia32-msvc": "1.1.0", + "@napi-rs/wasm-tools-win32-x64-msvc": "1.1.0" + } + }, + "node_modules/@napi-rs/wasm-tools-android-arm-eabi": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm-eabi/-/wasm-tools-android-arm-eabi-1.1.0.tgz", + "integrity": "sha512-p6J8PB59I8d/XItXB/go5JH6nKW+xIbpzaL43EBTV0hi7mrS/Z4gs+MsB04ZrlqZN29BdZV8fChRyasuXLhRaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-android-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-android-arm64/-/wasm-tools-android-arm64-1.1.0.tgz", + "integrity": "sha512-lWoKN3suypeBSCIRPIw+++sH9V2K6nQkhtdt1opu7XY3v9JwLs6Gw063HWRqkNjphlYpkd/Qy8XcfSPGbJj7nQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-darwin-arm64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-arm64/-/wasm-tools-darwin-arm64-1.1.0.tgz", + "integrity": "sha512-jfw5vyNDUf6oe0kP8lMveFN9U7cLk1cUosS7uMIfw/xmqmopYfKQ198DAx2g/6aEF7Tm+CqER2gpMpYKui30LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-darwin-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-darwin-x64/-/wasm-tools-darwin-x64-1.1.0.tgz", + "integrity": "sha512-R+pjeudAB7BYdH1vKkOJM61Tfv5jB6uXkxmFscYd+KKpdUpWBlNG+s4hr0w4i1rMBM91VhIAETZn2pz+MDHK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-freebsd-x64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-freebsd-x64/-/wasm-tools-freebsd-x64-1.1.0.tgz", + "integrity": "sha512-hQJTe+aazrT++Vgm6I4lUd9099ItUCFYdd+aKg6Ys6nax6d/cZ1barDLTwA2lwOoVDsXMekJI/FOL6ZvVlIYBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-arm64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-gnu/-/wasm-tools-linux-arm64-gnu-1.1.0.tgz", + "integrity": "sha512-1TAXJxUHsWGar90k3W/MknavvBMwOWzjh7Q6Spxo8twRcWJbBD5Kow/Q2KhhDq5hxh2sKGDXn3uLc1tdtz4WUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-arm64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-arm64-musl/-/wasm-tools-linux-arm64-musl-1.1.0.tgz", + "integrity": "sha512-7rw3nlubTjNAVRH2LwphCxHy1b/N2/TerXocQ6XRn4Q+buaY1Z7P/hbdALy1i1ex2yfOU2Xcij7ib7ZLi/lKfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-x64-gnu": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-gnu/-/wasm-tools-linux-x64-gnu-1.1.0.tgz", + "integrity": "sha512-1sel0t9MRjI/tdT89M8Dd6gPfANeeFP24Xa46R11WeHNwhjsXXZh+xUk50uWCRTSGcaCy3ugm3AMK/lmHYQJkg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-linux-x64-musl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-linux-x64-musl/-/wasm-tools-linux-x64-musl-1.1.0.tgz", + "integrity": "sha512-o2jH5AMfor4EKF2HII1LBnMQxoWu7+usPifTEY8Zk6e9OiSi4EJkAXf9v3ANlX7TI2V/cUEV34OEW7r10GiVIA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-wasm32-wasi/-/wasm-tools-wasm32-wasi-1.1.0.tgz", + "integrity": "sha512-s6YDtDR1UWrsqJPtaxf+JLYLceWVyn3l8OpQYElHkDhf3Qfz9R6Ba3S0OgznTBv38L5/TIHysQ9Q4yO73Z0csg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-tools-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-arm64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-arm64-msvc/-/wasm-tools-win32-arm64-msvc-1.1.0.tgz", + "integrity": "sha512-x+NuxbG84VxU68tU8w7Rf5lSyq0l584M6dVlke5DTweHYFZoMyeqkpbwEq+qsyAX6ivfipK8xRsmFwamb5uDnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-ia32-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-ia32-msvc/-/wasm-tools-win32-ia32-msvc-1.1.0.tgz", + "integrity": "sha512-mdD96QDEp70SX67rXFTY6c725nVYeqEEjyDqzzbNh6u1APj7CI7IMNpMmvE75XbCRl4C2MHZVU4U6AWdAzvyQQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@napi-rs/wasm-tools-win32-x64-msvc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-tools-win32-x64-msvc/-/wasm-tools-win32-x64-msvc-1.1.0.tgz", + "integrity": "sha512-bVVjuvhlyVX++3eJXfDR63cXdw1ay5QYac6iq0MKQw8wZARInTM+bXCtByDT4fzVFI3+7ZthYb/ERWRdBNIqgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.22.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.7.tgz", + "integrity": "sha512-DcB0M3KFgr9ECI328lhBMVsyFT2DnmNucSBTqEN3exyNKUzkkpUSCHmTRcunF41Eou2TIQKW4seewri8ON9bSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.4", + "@octokit/request": "^10.0.13", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.4.tgz", + "integrity": "sha512-f1cOWoHPmxryJFknxbtDdjODWfV8A9tc8Aae6ermXPNgHFZ/x91AtHIz4gicEjL8hkJiip+u21QHJORfBv/qiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.4.tgz", + "integrity": "sha512-5s15CCiY8XXQ+FG+b1YQcl6Z2FA++nwAz/tg2VUrTmnMncP+2nnGUEYANImdnxsA2Fnq+Mbl7hDjUTw7cFAwcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.13", + "@octokit/types": "^17.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz", + "integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.14.tgz", + "integrity": "sha512-bgWgiSfFS689/AxQDarU+b3Qu1FYewfVr5vI/jhV84s7GQ3C2OsdRxukGGYKB37MCgwcS50UrfBLlHzhiG13sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.1.1", + "@octokit/types": "^17.0.0", + "content-type": "^2.0.0", + "json-with-bigint": "^3.5.12", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.1.tgz", + "integrity": "sha512-+eaY7G2VVpSf2pc5Gn1+mph837V/d/TYTJAgWL9Tb0ogGYcpN3IlAVFgjL+Vv93F/sevrxkvsYCedtpLdcFLzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz", + "integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^28.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clipanion": { + "version": "4.0.0-rc.4", + "resolved": "https://registry.npmjs.org/clipanion/-/clipanion-4.0.0-rc.4.tgz", + "integrity": "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ], + "dependencies": { + "typanion": "^3.8.0" + }, + "peerDependencies": { + "typanion": "*" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/es-toolkit": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.51.0.tgz", + "integrity": "sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-with-bigint": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", + "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typanion": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/typanion/-/typanion-3.14.0.tgz", + "integrity": "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==", + "dev": true, + "license": "MIT", + "workspaces": [ + "website" + ] + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7c32f48 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "zudb", + "version": "0.0.1", + "description": "The JavaScript client for zu, an embedded property-graph database", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/tamnd/zu-node.git" + }, + "homepage": "https://zu.dev", + "keywords": [ + "graph", + "database", + "embedded", + "gql", + "cypher" + ], + "engines": { + "node": ">= 24" + }, + "main": "index.js", + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts", + "README.md", + "LICENSE" + ], + "napi": { + "binaryName": "zudb", + "dtsHeaderFile": "types/header.d.ts", + "targets": [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "x86_64-pc-windows-msvc", + "aarch64-pc-windows-msvc" + ] + }, + "scripts": { + "build": "napi build --platform --release", + "build:debug": "napi build --platform", + "test": "node --test \"test/*.test.mjs\"", + "bench": "node bench/query.mjs" + }, + "devDependencies": { + "@napi-rs/cli": "^3.8.6" + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..45d0ca1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,7 @@ +# The same compiler the engine pins, because this crate links the +# engine's crates directly and a binding built with a different one is +# a different build of the same code. Held to the version in +# tamnd/zu's toolchains.toml. +[toolchain] +channel = "1.97.1" +components = ["rustfmt", "clippy"] diff --git a/src/conn.rs b/src/conn.rs new file mode 100644 index 0000000..eced97b --- /dev/null +++ b/src/conn.rs @@ -0,0 +1,513 @@ +//! The connection and what a statement gives back. +//! +//! Nothing here runs on the event loop. Every engine call is a task on +//! libuv's threadpool, which is what the `AsyncTask` type is, and the +//! JavaScript side gets a promise back before the statement has +//! started. A synchronous native call in a server is a production +//! incident (ADR 0002), so there is no synchronous variant of a +//! statement in this file and the ones that arrive later will say in +//! their own doc comments that they belong in a script. +//! +//! A connection is not thread-safe in the engine and every method +//! takes `&mut self` there, so the one held here sits behind a mutex. +//! That is not a way of making a connection concurrent: a statement +//! holds the mutex for as long as it runs, and two statements that +//! should overlap want two connections. It is there so that a program +//! which shares one by accident waits rather than corrupts. + +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use napi::bindgen_prelude::*; +use napi::{Env, ScopedTask, ValueType}; +use napi_derive::napi; +use zudb::query::{QueryResult, Value}; +use zudb::{Config, Database, ZuError}; + +use crate::error::{raise, usage}; +use crate::value::{Names, from_js, to_js}; + +/// What a connection can be opened with. +/// +/// Every field is optional and every default is the engine's, because +/// a client that invents its own defaults is a client whose numbers +/// have to be found and changed twice. +#[napi(object)] +pub struct ConnectOptions { + /// Opens without creating and refuses every statement that writes. + /// A read-only connection never creates a database, so a mistyped + /// path is an error here rather than an empty database. + pub read_only: Option, + /// How much memory the executor may hold, in bytes. + pub memory_limit: Option, + /// How many threads the executor may use. + pub threads: Option, +} + +/// One connection to one database. +/// +/// Statements run on it in order, one at a time. It reads the database +/// as of when it was opened, which is why a program that wants to see +/// another writer's work takes a new connection rather than waiting on +/// this one. +#[napi] +pub struct Connection { + /// `None` once closed, which is what makes a second `close()` do + /// nothing and a statement after one an error rather than a crash. + /// + /// Counted rather than plain, because the threadpool thread a + /// statement runs on takes a share of it: the task outlives the + /// call that made it in the type system even though it never does + /// in fact. + inner: Arc>>, + /// Whether the connection is still open, kept beside the lock + /// rather than inside it, because asking a connection whether it is + /// closed should not queue behind a ten second statement. + alive: Arc, + path: String, + read_only: bool, +} + +/// Opens the database at `path` and connects to it. +/// +/// Creates one when the path holds nothing, which is what a first +/// program expects and what every embedded database does. A read-only +/// connection never creates anything. +#[napi(ts_return_type = "Promise")] +pub fn connect(path: String, options: Option) -> AsyncTask { + AsyncTask::new(ConnectTask { path, options }) +} + +pub struct ConnectTask { + path: String, + options: Option, +} + +impl<'task> ScopedTask<'task> for ConnectTask { + type Output = std::result::Result; + type JsValue = ClassInstance<'task, Connection>; + + fn compute(&mut self) -> Result { + let read_only = self + .options + .as_ref() + .and_then(|options| options.read_only) + .unwrap_or(false); + let mut config = Config::new().read_only(read_only); + if let Some(options) = &self.options { + if let Some(limit) = &options.memory_limit { + let (_, bytes, _) = limit.get_u64(); + config = config.memory_limit(bytes as usize); + } + if let Some(threads) = options.threads { + config = config.threads(threads as usize); + } + } + Ok(open(PathBuf::from(&self.path), read_only, config)) + } + + fn resolve(&mut self, env: &'task Env, output: Self::Output) -> Result { + let opened = output.map_err(|err| raise(env, err))?; + let mut instance = Connection { + inner: Arc::new(Mutex::new(Some(opened.conn))), + alive: Arc::new(AtomicBool::new(true)), + path: opened.path, + read_only: opened.read_only, + } + .into_instance(env)?; + wire_disposal(env, &mut instance)?; + Ok(instance) + } +} + +/// Puts `dispose` on the connection under `Symbol.asyncDispose`, which +/// is the key `await using` looks up and the one thing about this class +/// that cannot be spelled in the attribute that declares the rest of +/// it: a method's name there is a string, and this key is a symbol. +/// +/// On the instance rather than on the prototype, because reaching the +/// prototype from here means calling `Object.getPrototypeOf` through +/// three more layers of FFI for a property that is looked up once per +/// connection either way. +fn wire_disposal(env: &Env, instance: &mut ClassInstance<'_, Connection>) -> Result<()> { + // `Symbol` is a function, and the well-known symbols hang off it as + // properties of that function. + let symbols: Function<'_, (), Unknown<'_>> = env.get_global()?.get_named_property("Symbol")?; + let key: Unknown<'_> = symbols.get_named_property("asyncDispose")?; + // A runtime old enough to lack the symbol still has `dispose` and + // `close`, so it loses the syntax rather than the capability. + if key.get_type()? != ValueType::Symbol { + return Ok(()); + } + let dispose: Unknown<'_> = instance.get_named_property("dispose")?; + instance.set_property(key, dispose) +} + +pub struct Opened { + conn: zudb::Connection, + path: String, + read_only: bool, +} + +/// Opens or creates, then connects. +/// +/// A read-only open of a path that holds nothing fails as an open +/// rather than creating an empty database and then refusing every +/// statement against it, which would be the same typo reported three +/// calls later and less clearly. +fn open(path: PathBuf, read_only: bool, config: Config) -> std::result::Result { + let database = if read_only || path.exists() { + Database::open_with(&path, config)? + } else { + Database::create_with(&path, config)? + }; + let stored = database.path().to_string_lossy().into_owned(); + let conn = database.connect()?; + Ok(Opened { + conn, + path: stored, + read_only, + }) +} + +#[napi] +impl Connection { + /// Where the database this is connected to lives. + #[napi(getter)] + pub fn path(&self) -> String { + self.path.clone() + } + + /// Whether this connection refuses every statement that writes. + #[napi(getter)] + pub fn read_only(&self) -> bool { + self.read_only + } + + /// Whether the connection is still open. + #[napi(getter)] + pub fn open(&self) -> bool { + self.alive.load(Ordering::Acquire) + } + + /// Runs one statement and gives back its rows. + /// + /// The parameters are named, never positional, because zuQL names + /// them: `$id` in the statement is `id` in the object. A name the + /// statement does not use is an error from the engine rather than a + /// value quietly ignored. + #[napi( + ts_generic_types = "Row = Record", + ts_args_type = "statement: string, params?: Record | null", + ts_return_type = "Promise>" + )] + pub fn query( + &self, + env: &Env, + statement: String, + params: Option>, + ) -> AsyncTask { + AsyncTask::new(self.task(env, statement, params)) + } + + /// Runs one statement for its effect and gives back nothing. + /// + /// The same call as [`Connection::query`] with the rows dropped, + /// which is what a schema statement or a write wants: a result nobody + /// reads still costs a row object per row on the way out. + #[napi( + ts_args_type = "statement: string, params?: Record | null", + ts_return_type = "Promise" + )] + pub fn exec( + &self, + env: &Env, + statement: String, + params: Option>, + ) -> AsyncTask { + AsyncTask::new(ExecTask(self.task(env, statement, params))) + } + + /// The task one statement runs as, whether or not it is going to + /// work. + /// + /// A statement this client refuses is refused inside the task and + /// not here, so that every call gives back a promise and a caller + /// who wrote `await` or `.catch` has somewhere to catch it. A native + /// method that throws for a closed connection and rejects for a + /// failed statement is a method every caller has to wrap twice. + fn task(&self, env: &Env, statement: String, params: Option>) -> QueryTask { + // The parameters are read here rather than on the threadpool + // thread, because reading a JavaScript value is something only + // the thread that owns the runtime may do. + let bound = if self.alive.load(Ordering::Acquire) { + bind(env, params) + } else { + Err(CLOSED.to_string()) + }; + let (params, refused) = match bound { + Ok(params) => (params, None), + Err(message) => (Vec::new(), Some(message)), + }; + QueryTask { + inner: Arc::clone(&self.inner), + statement, + params, + refused, + } + } + + /// Closes the connection and releases the database. + /// + /// Closing twice does nothing the second time, which is what makes + /// `await using` safe to combine with an explicit close in a branch + /// that ran first. + #[napi] + pub fn close(&self) { + if self.alive.swap(false, Ordering::AcqRel) { + // A statement that is running holds the lock, so this waits + // for it. That is the right order: a connection dropped out + // from under a running statement is the one thing this + // cannot allow. + if let Ok(mut held) = self.inner.lock() { + held.take(); + } + } + } + + /// The disposal `await using` calls, which is the intended way to + /// scope a connection. `close` stays public for callers who cannot + /// use it. + /// + /// It is also reachable as `Symbol.asyncDispose`, which is what + /// `await using` actually looks for and which [`wire_disposal`] puts + /// on every connection as it is made. + #[napi(ts_return_type = "Promise")] + pub fn dispose(&self) -> AsyncTask { + AsyncTask::new(CloseTask { + inner: Arc::clone(&self.inner), + alive: Arc::clone(&self.alive), + }) + } +} + +/// What a statement was told when it could not run. +/// +/// The two are told apart because a caller does something different +/// with each: a condition the engine raised has a GQLSTATUS and may be +/// worth retrying, and a mistake this client caught is a bug in the +/// calling program that no retry fixes. +pub enum Failure { + /// The engine raised a condition. + Engine(ZuError), + /// This client refused the call before the engine saw it. + Usage(String), +} + +impl From for Failure { + fn from(err: ZuError) -> Self { + Failure::Engine(err) + } +} + +impl Failure { + /// The exception this rejects the caller's promise with. + fn raise(self, env: &Env) -> Error { + match self { + Failure::Engine(err) => raise(env, err), + Failure::Usage(message) => usage(env, message), + } + } +} + +/// What a closed connection says, wherever it is noticed. +const CLOSED: &str = "the connection is closed, so there is nothing left to run a statement on"; + +/// Reads the parameter object into the values the engine binds. +/// +/// Every failure comes back as the message to refuse the call with, +/// including a boundary failure, because a caller who cannot be given +/// the value they passed is being told the same thing either way and +/// would rather hear it as a rejection than as a throw. +fn bind( + env: &Env, + params: Option>, +) -> std::result::Result, String> { + let Some(params) = params else { + return Ok(Vec::new()); + }; + let mut bound = Vec::new(); + for name in Object::keys(¶ms).map_err(|err| err.reason)? { + let value: Unknown<'_> = params + .get_named_property(name.as_str()) + .map_err(|err| err.reason)?; + let value = from_js(env, &name, value).map_err(|err| err.reason)?; + bound.push((name, value)); + } + Ok(bound) +} + +pub struct QueryTask { + inner: Arc>>, + statement: String, + params: Vec<(String, Value)>, + /// Why this statement is not going to run, when it is not. + refused: Option, +} + +impl QueryTask { + /// Runs the statement, with the names of the tables it saw. + /// + /// The names are read while the lock is held, because a catalog + /// borrowed from the connection cannot outlive it and a result that + /// names its tables has to carry them. + fn run(&mut self) -> std::result::Result<(QueryResult, Names), Failure> { + if let Some(message) = self.refused.take() { + return Err(Failure::Usage(message)); + } + let mut held = match self.inner.lock() { + Ok(held) => held, + // A thread that panicked inside the engine left the + // connection in a state nothing here can vouch for, so this + // says so rather than carrying on with it. + Err(_) => { + return Err(Failure::Usage( + "the connection was left in an unknown state by a statement that panicked" + .to_string(), + )); + } + }; + // Closed between the call and the thread picking it up, which is + // a race the caller cannot see and this has to check anyway. + let Some(conn) = held.as_mut() else { + return Err(Failure::Usage(CLOSED.to_string())); + }; + let params: Vec<(&str, Value)> = self + .params + .iter() + .map(|(name, value)| (name.as_str(), value.clone())) + .collect(); + let names = Names::of(conn.session_mut().catalog()); + let result = conn.query_with(&self.statement, ¶ms)?; + Ok((result, names)) + } +} + +impl<'task> ScopedTask<'task> for QueryTask { + type Output = std::result::Result<(QueryResult, Names), Failure>; + type JsValue = Array<'task>; + + fn compute(&mut self) -> Result { + Ok(self.run()) + } + + fn resolve(&mut self, env: &'task Env, output: Self::Output) -> Result { + let (result, names) = output.map_err(|failure| failure.raise(env))?; + rows(env, &result, &names) + } +} + +/// The rows, as an array of objects keyed by column name. +/// +/// An array rather than a wrapper with a `rows` field inside it, +/// because iterating the answer is what a caller does with it and +/// `for (const row of await conn.query(...))` should be the whole +/// sentence. What a wrapper would have carried is carried as +/// properties of the array: `columns`, `gqlstatus` and `notices` are +/// there for the caller that wants them and out of the way of the one +/// that does not. Out of the way means not enumerable, so that the +/// array spreads, stringifies, deep-equals a plain array and answers +/// `Object.keys` as though they were not there at all. +fn rows<'env>(env: &'env Env, result: &QueryResult, names: &Names) -> Result> { + let mut array = env.create_array(result.rows.len() as u32)?; + for (ix, row) in result.rows.iter().enumerate() { + let mut object = Object::new(env)?; + for (column, value) in result.columns.iter().zip(row) { + object.set(column.as_str(), to_js(env, value, names)?)?; + } + array.set(ix as u32, object)?; + } + let mut raised = env.create_array(result.notices.len() as u32)?; + for (ix, notice) in result.notices.iter().enumerate() { + let mut record = Object::new(env)?; + record.set("code", notice.status.code())?; + record.set("condition", notice.status.standard_text())?; + record.set("message", notice.detail.as_str())?; + record.set("docUrl", notice.doc_url())?; + raised.set(ix as u32, record)?; + } + // The same value seen as an object, which is what an array is. The + // three properties go on there rather than at an index, so the + // array still has exactly as many elements as the statement had + // rows. + let mut object = array.coerce_to_object()?; + object.define_properties(&[ + beside( + env, + "columns", + Array::from_ref_vec_string(env, &result.columns)?, + )?, + beside(env, "gqlstatus", result.status().code())?, + beside(env, "notices", raised)?, + ])?; + Ok(array) +} + +/// One property that rides beside the rows rather than among them. +/// +/// Readable and replaceable like any other, but not enumerable, which +/// is the whole difference between a result that is an array and one +/// that merely looks like one. +fn beside(env: &Env, name: &str, value: T) -> Result { + Ok(Property::new() + .with_utf8_name(name)? + .with_napi_value(env, value)? + .with_property_attributes(PropertyAttributes::Writable | PropertyAttributes::Configurable)) +} + +/// The same statement with the rows thrown away. +pub struct ExecTask(QueryTask); + +impl<'task> ScopedTask<'task> for ExecTask { + type Output = std::result::Result<(), Failure>; + // Undefined rather than null, because a call that was never going to + // answer anything has no answer, and `null` is a value a statement + // can return. + type JsValue = (); + + fn compute(&mut self) -> Result { + Ok(self.0.run().map(|_| ())) + } + + fn resolve(&mut self, env: &'task Env, output: Self::Output) -> Result { + output.map_err(|failure| failure.raise(env)) + } +} + +/// Closing off the event loop, which is what `await using` gets. +/// +/// A close waits for the statement that is running, and waiting is the +/// thing that must not happen on the event loop. +pub struct CloseTask { + inner: Arc>>, + alive: Arc, +} + +impl<'task> ScopedTask<'task> for CloseTask { + type Output = (); + type JsValue = (); + + fn compute(&mut self) -> Result { + if self.alive.swap(false, Ordering::AcqRel) + && let Ok(mut held) = self.inner.lock() + { + held.take(); + } + Ok(()) + } + + fn resolve(&mut self, _env: &'task Env, _output: Self::Output) -> Result { + Ok(()) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..49bd126 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,142 @@ +//! Raising an engine condition as a JavaScript exception. +//! +//! What a caller catches is an ordinary `Error`, because that is what +//! `try`/`catch`, `unhandledRejection` and every logger in the +//! ecosystem already understand, and because a subclass of `Error` +//! built in a native addon is a class the addon has to keep working +//! across four runtimes for no gain the caller can see. +//! +//! What makes it a zu error is the fields it carries. Every field the +//! error model gives a binding is a property here, and none of them is +//! parsed back out of a message: `err.code` picks the branch, +//! `err.line` underlines the token, `err.retryable` decides whether a +//! retry loop goes round again. `err.name` is the condition's class +//! written out, so a stack trace says `ZuSyntaxError` rather than +//! `Error`, and `isZuError` in the TypeScript layer is the guard that +//! narrows the `unknown` a `catch` gives you. + +use napi::bindgen_prelude::*; +use napi::{Env, Status}; +use zudb::ZuError; + +/// Builds the exception for `err` and wraps it as the failure to +/// return. +/// +/// Wrapped rather than thrown into the environment, because most of +/// what fails here fails inside a promise and a promise has to be +/// rejected rather than thrown out of. An `Error` built from a JS value +/// keeps a reference to it, and napi hands that same object back when +/// it rejects or throws, so every field survives either way and the +/// caller sees the object built here rather than a copy of its message. +pub fn raise(env: &Env, err: ZuError) -> Error { + match build(env, &err).and_then(|object| object.into_unknown(env)) { + Ok(value) => Error::from(value), + // A failure to build the exception is reported as itself. A + // condition that cannot be dressed up is still worth less than + // a report that the dressing failed, since the second one is a + // bug here and the first one is a bug in the caller's + // statement. + Err(broken) => broken, + } +} + +fn build<'env>(env: &'env Env, err: &ZuError) -> Result> { + let mut object = blank(env, &err.to_string())?; + object.set("name", name_for(err))?; + object.set("retryable", err.retryable())?; + if let Some(status) = err.gqlstatus() { + object.set("code", status.code())?; + object.set("condition", status.standard_text())?; + object.set("severity", severity(status.severity()))?; + object.set("docUrl", status.doc_url())?; + } + if let Some(at) = err.position() { + object.set("line", at.line)?; + object.set("column", at.column)?; + object.set("offset", at.offset)?; + } + if let Some(excerpt) = err.excerpt() { + object.set("excerpt", excerpt)?; + } + Ok(object) +} + +/// The name a condition wears. +/// +/// One name per GQLSTATUS class, which is what the two characters that +/// open a code are for: a caller that wants every one of the forty-two +/// conditions in class 22 tests one name rather than listing them, and +/// a condition zu adds to that class later answers to the same test. +fn name_for(err: &ZuError) -> &'static str { + let Some(status) = err.gqlstatus() else { + return match err { + ZuError::Interrupted => "ZuInterrupted", + ZuError::InvalidArgument(_) => "ZuUsageError", + ZuError::Conflict(_) => "ZuTransactionError", + // A file that is not there, or not readable, is the same + // kind of thing as a database that cannot be reached, and + // class 08 is what the standard calls that. Reporting a + // missing path as an internal error would tell the caller + // to file a bug about their own typo. + ZuError::Io(_) => "ZuConnectionError", + // And so is a file that is there and is not a database, + // which is the same typo landing on a real file. + ZuError::Corrupt { .. } => "ZuConnectionError", + _ => "ZuInternalError", + }; + }; + match status.class() { + "08" => "ZuConnectionError", + "22" => "ZuDataError", + "25" | "2D" | "40" => "ZuTransactionError", + "42" => "ZuSyntaxError", + _ => "ZuError", + } +} + +/// The severity letter's name, because `"exception"` is what a person +/// reading a log understands and `X` is what the standard's table says. +fn severity(severity: zudb::Severity) -> &'static str { + match severity { + zudb::Severity::Success => "success", + zudb::Severity::NoData => "noData", + zudb::Severity::Warning => "warning", + zudb::Severity::Informational => "informational", + zudb::Severity::Exception => "exception", + } +} + +/// A mistake the program made, as the exception for one. +/// +/// Not every failure comes from the engine. A call on a connection +/// that is closed, or a parameter of a type nothing here can bind, is +/// the caller's doing rather than a condition the standard has a code +/// for, and it says so by name: `ZuUsageError`, with no `code`, since +/// a caller mapping codes to branches has to be able to tell a missing +/// code from one it does not recognize. +pub fn usage(env: &Env, message: impl AsRef) -> Error { + match usage_object(env, message.as_ref()).and_then(|object| object.into_unknown(env)) { + Ok(value) => Error::from(value), + Err(broken) => broken, + } +} + +fn usage_object<'env>(env: &'env Env, message: &str) -> Result> { + let mut object = blank(env, message)?; + object.set("name", "ZuUsageError")?; + object.set("retryable", false)?; + Ok(object) +} + +/// An `Error` with `message` and nothing else on it yet. +/// +/// napi writes the status it was handed into `code`, and `code` here is +/// the GQLSTATUS. A caller reading `err.code === '42001'` cannot be +/// given `GenericFailure` for a condition that has no code of its own, +/// so the one napi wrote is removed and the real one, when there is +/// one, is written in its place. +fn blank<'env>(env: &'env Env, message: &str) -> Result> { + let mut object = env.create_error(Error::new(Status::GenericFailure, message.to_string()))?; + object.delete_named_property("code")?; + Ok(object) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6cd690c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,39 @@ +//! The zu client for JavaScript. +//! +//! zu is an embedded graph database, so this is not a driver: there is +//! no socket, no protocol and no server, and a statement runs in the +//! process that called it. What crosses the boundary is a value, and +//! the whole of this crate is about crossing it faithfully, which means +//! an INT64 arrives as a `bigint` (ADR 0003), a node arrives as a node +//! rather than as a shape a caller has to recognize, and a failure +//! arrives with its GQLSTATUS, its position and whether it is worth +//! retrying. +//! +//! The binding is napi-rs rather than the C ABI (ADR 0002). The ABI is +//! the specification of what a binding must mean and this one is graded +//! against the same conformance corpus, but a runtime is where the work +//! is: promises, disposal, streams and cancellation are all things the +//! ABI has nothing to say about and a JavaScript program cannot do +//! without. + +mod conn; +mod error; +mod value; + +/// The version of the client. +#[napi_derive::napi] +pub fn version() -> String { + env!("CARGO_PKG_VERSION").to_string() +} + +/// The revision of the C ABI this client implements the semantics of. +/// +/// Not the same number as [`version`] and not meant to be: the ABI is +/// the specification every zu client is graded against, so this is what +/// says which edition of that specification the behaviour here matches, +/// and it moves when the specification does rather than when this crate +/// does. +#[napi_derive::napi] +pub fn abi_version() -> String { + zudb::C_ABI_VERSION.to_string() +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..8ac4886 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,613 @@ +//! Values, both ways across the boundary. +//! +//! A row that arrives as JavaScript objects is the slow path and the +//! one every first program uses, so it is the one that has to be +//! obvious: null is `null`, a string is a string, and a graph value is +//! a class with named fields rather than a tuple whose third element +//! is the ordinal if you remember the order. +//! +//! INT64 is `bigint`, always, which is ADR 0003 in the engine +//! repository. A JavaScript number is an IEEE double and stops being +//! exact at 2^53, zu's integers go to 2^63, and `count(*)` over a +//! large graph gets there on its own. A client that returns a number +//! here is a client whose users file "the id came back wrong" a year +//! later. +//! +//! Going the other way a `number` that is a whole number binds as +//! INT64 and one that is not binds as FLOAT, because `{ id: 1 }` is +//! what a caller writes and refusing it would be pedantry. A `bigint` +//! always binds as INT64, which is the way to be sure. + +use std::collections::HashMap; + +use napi::bindgen_prelude::*; +use napi::{Env, ValueType}; +use napi_derive::napi; +use zu_common::{DurationKind, Temporal}; +use zudb::query::Value; +use zudb::zu1::catalog::Catalog; + +/// What the tables in a result are called. +/// +/// A node value carries the id of the table it came from and nothing +/// else, because that is what a row holds. A person reading a result +/// wants the name, so the names are taken off the catalog once when a +/// statement runs and carried with the rows. A catalog holds tens of +/// tables, so this is a copy of a few short strings and not a +/// structure worth sharing. +#[derive(Default)] +pub struct Names { + nodes: HashMap, + rels: HashMap, +} + +impl Names { + pub fn of(catalog: &Catalog) -> Names { + Names { + nodes: catalog + .node_tables() + .iter() + .map(|table| (table.id, table.name.clone())) + .collect(), + rels: catalog + .rel_tables() + .iter() + .map(|table| (table.id, table.name.clone())) + .collect(), + } + } + + /// The table's name, or its id written out for a table the catalog + /// no longer has. A result outlives nothing here, but a name is for + /// reading and an unreadable one should still print. + fn node(&self, id: u32) -> String { + self.nodes + .get(&id) + .cloned() + .unwrap_or_else(|| format!("#{id}")) + } + + fn rel(&self, id: u32) -> String { + self.rels + .get(&id) + .cloned() + .unwrap_or_else(|| format!("#{id}")) + } +} + +/// One node of the graph. +/// +/// The table is the name written in the schema and the offset is the +/// row it sits at in that table, which together are what identifies a +/// node in zu. The offset is a `bigint` for the reason every other +/// 64-bit integer here is one. +/// +/// The 64-bit fields are held as integers and handed out as `bigint` by +/// a getter, rather than being held as a `bigint` and handed out +/// directly, because a `bigint` is a JavaScript value and a value +/// cannot be held by a struct that is built on a threadpool thread +/// where there is no environment to build one in. +#[napi] +pub struct ZuNode { + table: String, + offset: u64, +} + +#[napi] +impl ZuNode { + #[napi(getter)] + pub fn table(&self) -> &str { + &self.table + } + + #[napi(getter)] + pub fn offset(&self) -> BigInt { + BigInt::from(self.offset) + } + + /// The same thing as a plain object. + /// + /// Every field here is a getter on the prototype, which is what + /// lets a 64-bit field be handed out as a `bigint`, and a getter is + /// invisible to `JSON.stringify` and to a shallow copy. So each of + /// these classes says what it holds when it is asked, and a caller + /// stringifying a row gets the node rather than `{}`. A `bigint` + /// still has no JSON spelling, so a caller doing that passes a + /// replacer, which they already had to. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("table", self.table.as_str())?; + object.set("offset", BigInt::from(self.offset))?; + Ok(object) + } +} + +/// One edge of the graph. +/// +/// `ord` is where the edge's properties sit, which is its place in the +/// order the table was loaded in. That is what names an edge: a pair of +/// endpoints does not, since the same pair may run more than once and +/// each of those edges carries its own values. It is the field a caller +/// usually ignores and the one nothing else can replace. +#[napi] +pub struct ZuRel { + table: String, + src: u64, + dst: u64, + ord: u64, +} + +#[napi] +impl ZuRel { + #[napi(getter)] + pub fn table(&self) -> &str { + &self.table + } + + #[napi(getter)] + pub fn src(&self) -> BigInt { + BigInt::from(self.src) + } + + #[napi(getter)] + pub fn dst(&self) -> BigInt { + BigInt::from(self.dst) + } + + #[napi(getter)] + pub fn ord(&self) -> BigInt { + BigInt::from(self.ord) + } + + /// The same thing as a plain object, for the reason [`ZuNode::to_json`] + /// gives. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("table", self.table.as_str())?; + object.set("src", BigInt::from(self.src))?; + object.set("dst", BigInt::from(self.dst))?; + object.set("ord", BigInt::from(self.ord))?; + Ok(object) + } +} + +/// A date, as days from 1970-01-01. +/// +/// A count rather than a set of fields, which is what the engine +/// stores and what makes two dates compare as two numbers. `Temporal` +/// reached Stage 4 in March 2026 but is unflagged only in Node 26 and +/// the newest browsers, and Node 24 is still the active LTS, so this +/// is the stable type and the runtimes that have `Temporal` reach it +/// through the opt-in rather than through a type that changes shape +/// under them. +#[napi] +pub struct ZuDate { + pub days: i32, +} + +#[napi] +impl ZuDate { + #[napi(constructor)] + pub fn new(days: i32) -> ZuDate { + ZuDate { days } + } + + /// The same thing as a plain object, for the reason [`ZuNode::to_json`] + /// gives. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("days", self.days)?; + Ok(object) + } +} + +/// A time of day, as nanoseconds from midnight, with the offset from +/// UTC in minutes for a zoned one and `null` for a local one. +/// +/// The offset is minutes and never an IANA name, which is the engine's +/// decision and worth repeating here: a name is a rule that changes +/// under a stored value when the zone database is updated, and a value +/// that means something different tomorrow is not a value. +#[napi] +pub struct ZuTime { + nanos: i64, + pub offset: Option, +} + +#[napi] +impl ZuTime { + #[napi(constructor)] + pub fn new(nanos: BigInt, offset: Option) -> ZuTime { + ZuTime { + nanos: nanos.get_i64().0, + offset, + } + } + + #[napi(getter)] + pub fn nanos(&self) -> BigInt { + BigInt::from(self.nanos) + } + + /// The same thing as a plain object, for the reason [`ZuNode::to_json`] + /// gives. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("nanos", BigInt::from(self.nanos))?; + object.set("offset", self.offset)?; + Ok(object) + } +} + +/// A date and a time together, as nanoseconds from the epoch, with the +/// offset from UTC in minutes for a zoned one and `null` for a local +/// one. +/// +/// A zoned one holds the instant in UTC and the offset separately, so +/// two of them compare as instants while each still prints in the zone +/// it was written in. +#[napi] +pub struct ZuTimestamp { + nanos: i64, + pub offset: Option, +} + +#[napi] +impl ZuTimestamp { + #[napi(constructor)] + pub fn new(nanos: BigInt, offset: Option) -> ZuTimestamp { + ZuTimestamp { + nanos: nanos.get_i64().0, + offset, + } + } + + #[napi(getter)] + pub fn nanos(&self) -> BigInt { + BigInt::from(self.nanos) + } + + /// The same thing as a plain object, for the reason [`ZuNode::to_json`] + /// gives. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("nanos", BigInt::from(self.nanos))?; + object.set("offset", self.offset)?; + Ok(object) + } +} + +/// A duration, which is a count of months or a count of nanoseconds +/// and never both. +/// +/// The two kinds do not mix, because no number of days is a month: a +/// value holding both would have to invent an answer for one month +/// after 31 January, and the standard does not ask for the invention. +/// `kind` says which one this is, and the other count is zero. +#[napi] +pub struct ZuDuration { + kind: &'static str, + months: i64, + nanos: i64, +} + +#[napi] +impl ZuDuration { + /// A duration of `months` months, which is the year-month kind. + #[napi(factory, js_name = "ofMonths")] + pub fn of_months(months: BigInt) -> ZuDuration { + year_month(months.get_i64().0) + } + + /// A duration of `nanos` nanoseconds, which is the day-time kind. + #[napi(factory, js_name = "ofNanos")] + pub fn of_nanos(nanos: BigInt) -> ZuDuration { + day_time(nanos.get_i64().0) + } + + /// Which of the two kinds this is: `yearMonth` or `dayTime`. + #[napi(getter)] + pub fn kind(&self) -> &str { + self.kind + } + + /// The months, which is zero for a day-time duration. + #[napi(getter)] + pub fn months(&self) -> BigInt { + BigInt::from(self.months) + } + + /// The nanoseconds, which is zero for a year-month duration. + #[napi(getter)] + pub fn nanos(&self) -> BigInt { + BigInt::from(self.nanos) + } + + /// The same thing as a plain object, for the reason [`ZuNode::to_json`] + /// gives. + #[napi(js_name = "toJSON")] + pub fn to_json<'env>(&self, env: &'env Env) -> Result> { + let mut object = Object::new(env)?; + object.set("kind", self.kind)?; + object.set("months", BigInt::from(self.months))?; + object.set("nanos", BigInt::from(self.nanos))?; + Ok(object) + } +} + +fn year_month(months: i64) -> ZuDuration { + ZuDuration { + kind: "yearMonth", + months, + nanos: 0, + } +} + +fn day_time(nanos: i64) -> ZuDuration { + ZuDuration { + kind: "dayTime", + months: 0, + nanos, + } +} + +/// Turns an engine value into the JavaScript value it is. +pub fn to_js<'env>(env: &'env Env, value: &Value, names: &Names) -> Result> { + match value { + Value::Null => Null.into_unknown(env), + Value::Bool(b) => (*b).into_unknown(env), + Value::Int(n) => BigInt::from(*n).into_unknown(env), + Value::Float(f) => (*f).into_unknown(env), + Value::Str(s) => s.as_str().into_unknown(env), + Value::Node { table, offset } => node(*table, *offset, names) + .into_instance(env)? + .into_unknown(env), + Value::Rel { + table, + src, + dst, + ord, + } => rel(*table, *src, *dst, *ord, names) + .into_instance(env)? + .into_unknown(env), + Value::List(items) => { + let mut array = env.create_array(items.len() as u32)?; + for (ix, item) in items.iter().enumerate() { + array.set(ix as u32, to_js(env, item, names)?)?; + } + array.into_unknown(env) + } + Value::Record(fields) => { + let mut object = Object::new(env)?; + for (name, field) in fields { + object.set(name.as_str(), to_js(env, field, names)?)?; + } + object.into_unknown(env) + } + Value::Temporal(t) => temporal(env, *t), + Value::Path(walk) => path(env, walk, names), + // The three the executor keeps to itself. A chain is settled + // into an edge list before any value leaves the pipeline, and a + // graph or a binding table is a handle to something that has no + // spelling outside the engine, so none of these reaches a + // result and the arm is here to be exhaustive rather than to + // run. + Value::Chain(_) | Value::Graph(_) | Value::BindingTable(_) => Null.into_unknown(env), + } +} + +fn node(table: u32, offset: u64, names: &Names) -> ZuNode { + ZuNode { + table: names.node(table), + offset, + } +} + +fn rel(table: u32, src: u64, dst: u64, ord: u64, names: &Names) -> ZuRel { + ZuRel { + table: names.rel(table), + src, + dst, + ord, + } +} + +/// A walk through the graph: nodes and edges, alternating, a node at +/// each end, given back as `{ nodes, rels }`. +/// +/// The engine hands one over as the walk itself: node, edge, node, +/// edge, node. The two lists are kept apart rather than interleaved, +/// because the question a caller asks is almost always about one of +/// them and picking one out of an alternating list means writing the +/// stride by hand. Splitting it here rather than in JavaScript is one +/// pass instead of two, and it is the difference between `path.nodes` +/// and `path.items.filter((_, i) => i % 2 === 0)`. A path of one node +/// has no edges and is the shortest there is. +fn path<'env>(env: &'env Env, walk: &[Value], names: &Names) -> Result> { + let mut nodes = env.create_array(0)?; + let mut rels = env.create_array(0)?; + for step in walk { + match step { + Value::Node { table, offset } => nodes.insert(node(*table, *offset, names))?, + Value::Rel { + table, + src, + dst, + ord, + } => rels.insert(rel(*table, *src, *dst, *ord, names))?, + // A path holds nodes and edges and the engine builds one + // through a constructor that checks the shape, so anything + // else here would be a bug in the engine rather than in the + // caller's statement. Dropping it keeps the two lists + // aligned, which is what a reader of them relies on. + _ => {} + } + } + let mut object = Object::new(env)?; + object.set("nodes", nodes)?; + object.set("rels", rels)?; + object.into_unknown(env) +} + +fn temporal(env: &Env, value: Temporal) -> Result> { + match value { + Temporal::Date(days) => ZuDate { days }.into_instance(env)?.into_unknown(env), + Temporal::LocalTime(nanos) => ZuTime { + nanos, + offset: None, + } + .into_instance(env)? + .into_unknown(env), + Temporal::ZonedTime { nanos, offset } => ZuTime { + nanos, + offset: Some(i32::from(offset)), + } + .into_instance(env)? + .into_unknown(env), + Temporal::LocalDatetime(nanos) => ZuTimestamp { + nanos, + offset: None, + } + .into_instance(env)? + .into_unknown(env), + Temporal::ZonedDatetime { nanos, offset } => ZuTimestamp { + nanos, + offset: Some(i32::from(offset)), + } + .into_instance(env)? + .into_unknown(env), + Temporal::Duration(DurationKind::YearMonth, months) => { + year_month(months).into_instance(env)?.into_unknown(env) + } + Temporal::Duration(DurationKind::DayTime, nanos) => { + day_time(nanos).into_instance(env)?.into_unknown(env) + } + } +} + +/// Turns a JavaScript value into the engine value it binds as. +/// +/// The refusals are the point of this function. A parameter that +/// cannot be bound is told so by name and by type, on the call that +/// passed it, rather than becoming a null the statement then compares +/// against and answers nothing for. +/// +/// A refusal is an `InvalidArg`, which is how [`crate::conn`] tells one +/// apart from a boundary failure and turns it into the rejection the +/// caller is already awaiting. +pub fn from_js(env: &Env, name: &str, value: Unknown<'_>) -> Result { + match value.get_type()? { + ValueType::Null | ValueType::Undefined => Ok(Value::Null), + ValueType::Boolean => Ok(Value::Bool(bool::from_unknown(value)?)), + ValueType::String => Ok(Value::Str(String::from_unknown(value)?)), + ValueType::BigInt => { + let big = BigInt::from_unknown(value)?; + let (n, lossless) = big.get_i64(); + if !lossless { + return Err(Error::new( + Status::InvalidArg, + format!( + "parameter {name} is a bigint outside what INT64 holds, \ + which is -2^63 up to 2^63 - 1" + ), + )); + } + Ok(Value::Int(n)) + } + // A whole number binds as an integer and anything else as a + // float. The alternative, binding every number as a float, + // makes `{ id: 1 }` fail to match a row whose id is an integer, + // which is the first thing anybody writes. + ValueType::Number => { + let n = f64::from_unknown(value)?; + if n.fract() == 0.0 && n.abs() <= 9_007_199_254_740_992.0 { + Ok(Value::Int(n as i64)) + } else { + Ok(Value::Float(n)) + } + } + ValueType::Object => from_object(env, name, value), + other => Err(Error::new( + Status::InvalidArg, + format!("parameter {name} is a {other}, which is not a value a statement can hold"), + )), + } +} + +fn from_object(env: &Env, name: &str, value: Unknown<'_>) -> Result { + if let Some(temporal) = temporal_from(env, &value)? { + return Ok(Value::Temporal(temporal)); + } + let object = Object::from_unknown(value)?; + if object.is_array()? { + let len = object.get_array_length()?; + let mut items = Vec::with_capacity(len as usize); + for ix in 0..len { + let item: Unknown<'_> = object.get_element(ix)?; + items.push(from_js(env, name, item)?); + } + return Ok(Value::List(items)); + } + // A plain object is a record, which is the one mapping that reads + // the same in both directions: a record comes back as an object + // with the same field names. + let mut fields = Vec::new(); + for key in Object::keys(&object)? { + let field: Unknown<'_> = object.get_named_property(key.as_str())?; + fields.push((key, from_js(env, name, field)?)); + } + Ok(Value::record(fields)) +} + +/// The four value classes, recognized by asking each one whether this +/// is one of its own rather than by reading a tag off the object. +/// A plain object shaped like a date is a record, and only an instance +/// is a date. +fn temporal_from(env: &Env, value: &Unknown<'_>) -> Result> { + if ZuDate::instance_of(env, value)? { + let days: i32 = Object::from_unknown(*value)?.get_named_property("days")?; + return Ok(Some(Temporal::Date(days))); + } + if ZuTime::instance_of(env, value)? { + let (nanos, offset) = parts(value)?; + return Ok(Some(match offset { + Some(offset) => Temporal::ZonedTime { nanos, offset }, + None => Temporal::LocalTime(nanos), + })); + } + if ZuTimestamp::instance_of(env, value)? { + let (nanos, offset) = parts(value)?; + return Ok(Some(match offset { + Some(offset) => Temporal::ZonedDatetime { nanos, offset }, + None => Temporal::LocalDatetime(nanos), + })); + } + if ZuDuration::instance_of(env, value)? { + let object = Object::from_unknown(*value)?; + let kind: String = object.get_named_property("kind")?; + return Ok(Some(if kind == "yearMonth" { + let months: BigInt = object.get_named_property("months")?; + Temporal::Duration(DurationKind::YearMonth, months.get_i64().0) + } else { + let nanos: BigInt = object.get_named_property("nanos")?; + Temporal::Duration(DurationKind::DayTime, nanos.get_i64().0) + })); + } + Ok(None) +} + +/// The nanosecond count and the offset a time or a timestamp carries. +/// The offset is what tells a zoned one from a local one, so it stays +/// an option all the way rather than becoming a zero that would read +/// as UTC. +fn parts(value: &Unknown<'_>) -> Result<(i64, Option)> { + let object = Object::from_unknown(*value)?; + let nanos: BigInt = object.get_named_property("nanos")?; + let offset: Option = object.get_named_property("offset")?; + Ok((nanos.get_i64().0, offset.map(|minutes| minutes as i16))) +} diff --git a/test/connect.test.mjs b/test/connect.test.mjs new file mode 100644 index 0000000..52f7804 --- /dev/null +++ b/test/connect.test.mjs @@ -0,0 +1,114 @@ +import assert from 'node:assert/strict' +import { mkdtemp, rm, stat } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import test from 'node:test' + +import { abiVersion, connect, version } from '../index.js' +import { fresh, twoPeople } from './helper.mjs' + +test('the client says which version it is and which ABI it implements', () => { + assert.match(version(), /^\d+\.\d+\.\d+$/) + assert.match(abiVersion(), /^\d+\.\d+$/) +}) + +test('connecting to a path that holds nothing makes a database there', async (t) => { + const dir = await mkdtemp(join(tmpdir(), 'zu-node-')) + t.after(() => rm(dir, { recursive: true, force: true })) + const path = join(dir, 'made.zu1') + + const conn = await connect(path) + t.after(() => conn.close()) + + assert.equal(conn.path, path) + assert.equal(conn.readOnly, false) + assert.equal(conn.open, true) + assert.ok((await stat(path)).size > 0, 'a database that was made is a file with a header in it') +}) + +test('connecting twice to the same path gives two connections to one database', async (t) => { + const { path, conn } = await twoPeople(t) + const second = await connect(path) + t.after(() => second.close()) + + assert.equal(second.path, conn.path) + assert.equal((await second.query('MATCH (p:person) RETURN p.name AS name')).length, 2) +}) + +test('a connection is closed once and closing it again does nothing', async (t) => { + const { conn } = await fresh(t) + + assert.equal(conn.open, true) + conn.close() + assert.equal(conn.open, false) + conn.close() + assert.equal(conn.open, false) +}) + +test('a statement on a closed connection is refused rather than crashing', async (t) => { + const { conn } = await twoPeople(t) + conn.close() + + await assert.rejects(() => conn.query('MATCH (p:person) RETURN p.name AS name'), (err) => { + assert.equal(err.name, 'ZuUsageError') + assert.match(err.message, /closed/) + // A mistake this client caught carries no GQLSTATUS, because the + // engine never saw the statement and there is no condition to + // report. A caller mapping codes to branches has to be able to tell + // that from a code it does not recognize. + assert.equal(err.code, undefined) + assert.equal(err.retryable, false) + return true + }) + await assert.rejects(() => conn.exec("INSERT (p:person {id: 3, name: 'iris'})"), { + name: 'ZuUsageError', + }) +}) + +test('await using closes the connection at the end of the block', async (t) => { + const dir = await mkdtemp(join(tmpdir(), 'zu-node-')) + t.after(() => rm(dir, { recursive: true, force: true })) + let escaped + { + await using conn = await connect(join(dir, 'scoped.zu1')) + escaped = conn + assert.equal(conn.open, true) + } + assert.equal(escaped.open, false) +}) + +test('a read-only connection refuses to write and never makes a database', async (t) => { + const { path, conn } = await twoPeople(t) + conn.close() + + const reader = await connect(path, { readOnly: true }) + t.after(() => reader.close()) + assert.equal(reader.readOnly, true) + await assert.rejects(() => reader.exec("INSERT (p:person {id: 3, name: 'iris'})"), { + name: 'ZuUsageError', + }) + + const missing = join(path, '..', 'nothing-here.zu1') + await assert.rejects(() => connect(missing, { readOnly: true }), (err) => { + // A read-only open of a path that holds nothing fails as an open. An + // empty database made here would report the same typo three calls + // later, as a statement that matches nothing. + assert.equal(err.name, 'ZuConnectionError') + return true + }) + await assert.rejects(() => stat(missing), { code: 'ENOENT' }) +}) + +test('the executor takes the limits it was opened with', async (t) => { + const dir = await mkdtemp(join(tmpdir(), 'zu-node-')) + t.after(() => rm(dir, { recursive: true, force: true })) + + const conn = await connect(join(dir, 'limited.zu1'), { + memoryLimit: 256n * 1024n * 1024n, + threads: 2, + }) + t.after(() => conn.close()) + + await conn.exec("INSERT (p:person {id: 1, name: 'ada'})") + assert.equal((await conn.query('MATCH (p:person) RETURN p.name AS name')).length, 1) +}) diff --git a/test/errors.test.mjs b/test/errors.test.mjs new file mode 100644 index 0000000..90449e1 --- /dev/null +++ b/test/errors.test.mjs @@ -0,0 +1,99 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { fresh, twoPeople } from './helper.mjs' + +test('a failed statement rejects with an Error carrying the condition', async (t) => { + const { conn } = await twoPeople(t) + + await assert.rejects(() => conn.query('MATCH (p:person) WHERE RETURN p.id AS id'), (err) => { + // An ordinary Error, so every catch, logger and rejection handler + // already knows what to do with it. + assert.ok(err instanceof Error) + assert.equal(err.name, 'ZuSyntaxError') + assert.equal(err.code, '42001') + assert.equal(err.condition, 'syntax error or access rule violation, invalid syntax') + assert.equal(err.severity, 'exception') + assert.equal(err.docUrl, 'https://zu.dev/docs/errors/42001') + assert.equal(err.retryable, false) + assert.ok(err.message.length > 0) + assert.ok(err.stack.includes('ZuSyntaxError')) + return true + }) +}) + +test('a condition that happened somewhere says where, in numbers', async (t) => { + const { conn } = await twoPeople(t) + const statement = 'MATCH (p:person)\nRETURN p.name AS name, ? AS x' + + await assert.rejects(() => conn.query(statement), (err) => { + assert.equal(typeof err.line, 'number') + assert.equal(typeof err.column, 'number') + assert.equal(typeof err.offset, 'number') + assert.equal(err.line, 2) + // The column indexes into the excerpt, which is the whole line the + // position falls on, so a caller can underline the token without + // having kept the statement. + assert.equal(err.excerpt, 'RETURN p.name AS name, ? AS x') + assert.equal(err.excerpt[err.column - 1], '?') + // The offset indexes into the statement, so a caller who did keep it + // can point at the same character without counting lines. + assert.equal(statement[err.offset], '?') + return true + }) +}) + +test('the fields are read off the error and never parsed back out of the message', async (t) => { + const { conn } = await twoPeople(t) + + const err = await conn.query('MATCH (p:person) WHERE RETURN p.id AS id').then( + () => null, + (caught) => caught, + ) + + // Own properties, on the error itself, so a structured logger picks + // them up and a caller can destructure them. + assert.deepEqual( + Object.keys(err).sort(), + ['code', 'column', 'condition', 'docUrl', 'excerpt', 'line', 'name', 'offset', 'retryable', 'severity'], + ) +}) + +test('a mistake this client caught carries no code', async (t) => { + const { conn } = await fresh(t) + + await assert.rejects(() => conn.query('RETURN $v AS v', { v: () => {} }), (err) => { + assert.equal(err.name, 'ZuUsageError') + // Absent rather than a stand-in, so a caller mapping codes to + // branches can tell a condition with no code from one it does not + // recognize. + assert.equal(err.code, undefined) + assert.equal('code' in err, false) + assert.equal(err.retryable, false) + return true + }) +}) + +test('an engine failure and a client failure are told apart by name', async (t) => { + const { conn } = await twoPeople(t) + + const names = [] + for (const run of [ + () => conn.query('MATCH (p:person) WHERE RETURN p.id AS id'), + () => conn.query('RETURN $v AS v', { v: Symbol('nope') }), + () => conn.exec("INSERT (p:person {id: 3})"), + ]) { + names.push(await run().then(() => null, (err) => err.name)) + } + + assert.deepEqual(names, ['ZuSyntaxError', 'ZuUsageError', 'ZuUsageError']) +}) + +test('one failed statement does not close the connection', async (t) => { + const { conn } = await twoPeople(t) + + await assert.rejects(() => conn.query('MATCH (p:person) WHERE RETURN p.id AS id')) + + assert.equal(conn.open, true) + assert.equal((await conn.query('MATCH (p:person) RETURN p.id AS id')).length, 2) +}) diff --git a/test/helper.mjs b/test/helper.mjs new file mode 100644 index 0000000..8df30c4 --- /dev/null +++ b/test/helper.mjs @@ -0,0 +1,33 @@ +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +import { connect } from '../index.js' + +// A database of its own per test, in a directory of its own, removed +// when the test ends. Sharing one would make the order the tests run in +// part of what they assert, and the runner is free to change it. +export async function fresh(t, options) { + const dir = await mkdtemp(join(tmpdir(), 'zu-node-')) + t.after(() => rm(dir, { recursive: true, force: true })) + const path = join(dir, 'test.zu1') + const conn = await connect(path, options) + t.after(() => conn.close()) + return { conn, path, dir } +} + +// A database with two people in it, which is the smallest thing most of +// these tests can ask a question about. There is no statement that +// makes a table on its own yet, so the first insert declares one. +export async function twoPeople(t) { + const made = await fresh(t) + await made.conn.exec("INSERT (p:person {id: 1, name: 'ada'})") + await made.conn.exec("INSERT (p:person {id: 2, name: 'zoe'})") + return made +} + +// What a test asserts about an error it caught, in one place, since +// every one of them wants the same first two things. +export function isZuError(err, name) { + return err instanceof Error && err.name === name && typeof err.retryable === 'boolean' +} diff --git a/test/query.test.mjs b/test/query.test.mjs new file mode 100644 index 0000000..8f885e9 --- /dev/null +++ b/test/query.test.mjs @@ -0,0 +1,132 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { ZuNode } from '../index.js' +import { twoPeople } from './helper.mjs' + +test('the rows come back as an array of objects keyed by column', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query('MATCH (p:person) RETURN p.id AS id, p.name AS name ORDER BY id') + + assert.ok(Array.isArray(rows)) + assert.equal(rows.length, 2) + assert.deepEqual(rows[0], { id: 1n, name: 'ada' }) + assert.deepEqual([...rows], [ + { id: 1n, name: 'ada' }, + { id: 2n, name: 'zoe' }, + ]) +}) + +test('the projection, the condition and the notices ride along beside the rows', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query('MATCH (p:person) RETURN p.name AS name, p.id AS id') + + // In the order they were written, not the order they were read. + assert.deepEqual(rows.columns, ['name', 'id']) + assert.equal(rows.gqlstatus, '00000') + assert.deepEqual(rows.notices, []) + // Beside the elements rather than among them, so the array is still + // exactly as long as the statement had rows and still looks like an + // array to everything that walks one. + assert.equal(rows.length, 2) + assert.deepEqual(Object.keys(rows), ['0', '1']) + assert.deepEqual(JSON.parse(JSON.stringify(rows.map((row) => row.name))), ['ada', 'zoe']) + assert.equal(Object.keys({ ...rows }).length, 2) +}) + +test('a statement that matched nothing is a success with no rows', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query("MATCH (p:person) WHERE p.name = 'nobody' RETURN p.name AS name") + + assert.equal(rows.length, 0) + assert.deepEqual(rows.columns, ['name']) + // 00000 is successful completion and says nothing about how many rows + // there were. A caller asking whether anything matched asks the + // length. + assert.equal(rows.gqlstatus, '00000') +}) + +test('a statement with no projection completes with 00001', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query("INSERT (p:person {id: 3, name: 'iris'})") + + assert.equal(rows.length, 0) + assert.deepEqual(rows.columns, []) + assert.equal(rows.gqlstatus, '00001') +}) + +test('exec runs the statement and gives back nothing', async (t) => { + const { conn } = await twoPeople(t) + + assert.equal(await conn.exec("INSERT (p:person {id: 3, name: 'iris'})"), undefined) + assert.equal((await conn.query('MATCH (p:person) RETURN p.id AS id')).length, 3) +}) + +test('parameters are named, and a name the statement does not use is refused', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query('MATCH (p:person) WHERE p.name = $name RETURN p.id AS id', { + name: 'zoe', + }) + assert.deepEqual([...rows], [{ id: 2n }]) + + await assert.rejects( + () => conn.query('MATCH (p:person) WHERE p.name = $name RETURN p.id AS id', { nmae: 'zoe' }), + (err) => { + assert.match(err.message, /name/) + return true + }, + ) +}) + +test('a node comes back as a node, with the table named rather than numbered', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query('MATCH (p:person) RETURN p AS p ORDER BY p.id') + + assert.ok(rows[0].p instanceof ZuNode) + assert.equal(rows[0].p.table, 'person') + assert.equal(rows[0].p.offset, 0n) + assert.equal(rows[1].p.offset, 1n) + // The fields are getters, so that a 64-bit one can be a bigint, and a + // getter is invisible to JSON.stringify. This is what puts them back. + assert.deepEqual(rows[0].p.toJSON(), { table: 'person', offset: 0n }) +}) + +test('statements run one after another on one connection', async (t) => { + const { conn } = await twoPeople(t) + + // Started together and awaited together. The connection runs them in + // order behind its lock, so what this asserts is that the second one + // waits rather than corrupting the first. + const [first, second, third] = await Promise.all([ + conn.query('MATCH (p:person) RETURN p.id AS id ORDER BY id'), + conn.query("MATCH (p:person) WHERE p.name = 'ada' RETURN p.name AS name"), + conn.query('MATCH (p:person) RETURN count(*) AS n'), + ]) + + assert.deepEqual([...first], [{ id: 1n }, { id: 2n }]) + assert.deepEqual([...second], [{ name: 'ada' }]) + assert.deepEqual([...third], [{ n: 2n }]) +}) + +test('a statement runs off the event loop', async (t) => { + const { conn } = await twoPeople(t) + + let ticked = false + const running = conn.query('MATCH (p:person) RETURN count(*) AS n') + // The promise was handed back before the statement finished, so this + // callback gets to run first. A synchronous native call would have + // finished the statement before this line was reached. + queueMicrotask(() => { + ticked = true + }) + const rows = await running + + assert.equal(ticked, true) + assert.deepEqual([...rows], [{ n: 2n }]) +}) diff --git a/test/values.test.mjs b/test/values.test.mjs new file mode 100644 index 0000000..968c66e --- /dev/null +++ b/test/values.test.mjs @@ -0,0 +1,147 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { ZuDate, ZuDuration, ZuTime, ZuTimestamp } from '../index.js' +import { fresh, twoPeople } from './helper.mjs' + +// What a parameter binds as is what comes back, so one statement that +// returns what it was given tests both directions at once. +async function roundTrip(conn, value) { + const rows = await conn.query('RETURN $v AS v', { v: value }) + return rows[0].v +} + +test('an INT64 is a bigint, going out and coming back', async (t) => { + const { conn } = await twoPeople(t) + + const rows = await conn.query('MATCH (p:person) RETURN p.id AS id ORDER BY id') + assert.equal(typeof rows[0].id, 'bigint') + assert.equal(rows[0].id, 1n) + + // 2^53 + 1, which is the first integer a JavaScript number cannot + // tell from its neighbour and the reason this is a bigint at all. + assert.equal(await roundTrip(conn, 9007199254740993n), 9007199254740993n) + assert.equal(await roundTrip(conn, -9223372036854775808n), -9223372036854775808n) + assert.equal(await roundTrip(conn, 9223372036854775807n), 9223372036854775807n) +}) + +test('a bigint outside what INT64 holds is refused by name', async (t) => { + const { conn } = await fresh(t) + + await assert.rejects(() => roundTrip(conn, 9223372036854775808n), (err) => { + assert.equal(err.name, 'ZuUsageError') + assert.match(err.message, /outside what INT64 holds/) + // Named, so a caller with twenty parameters knows which one. + assert.match(err.message, /\bv\b/) + return true + }) +}) + +test('a whole number binds as an integer and a fractional one as a float', async (t) => { + const { conn } = await fresh(t) + + // `{ id: 1 }` is what anybody writes, and binding it as a float would + // make it fail to match a row whose id is an integer. + assert.equal(await roundTrip(conn, 1), 1n) + assert.equal(typeof (await roundTrip(conn, 1)), 'bigint') + assert.equal(await roundTrip(conn, 1.5), 1.5) + assert.equal(typeof (await roundTrip(conn, 1.5)), 'number') +}) + +test('a string, a boolean, a null and an undefined bind as themselves', async (t) => { + const { conn } = await fresh(t) + + assert.equal(await roundTrip(conn, 'ada'), 'ada') + assert.equal(await roundTrip(conn, ''), '') + assert.equal(await roundTrip(conn, true), true) + assert.equal(await roundTrip(conn, false), false) + assert.equal(await roundTrip(conn, null), null) + // A field that is not there and a field that is null are the same + // thing to a statement, which is what makes an optional field of a + // plain object pass straight through. + assert.equal(await roundTrip(conn, undefined), null) +}) + +test('a list and a record bind as a list and a record, however deep', async (t) => { + const { conn } = await fresh(t) + + assert.deepEqual(await roundTrip(conn, [1n, 2n, 3n]), [1n, 2n, 3n]) + assert.deepEqual(await roundTrip(conn, []), []) + assert.deepEqual(await roundTrip(conn, { a: 1n, b: 'x' }), { a: 1n, b: 'x' }) + assert.deepEqual(await roundTrip(conn, { a: [1n, { b: null }] }), { a: [1n, { b: null }] }) +}) + +test('a parameter of a type nothing can bind is refused on the call that passed it', async (t) => { + const { conn } = await fresh(t) + + for (const value of [() => {}, Symbol('nope')]) { + await assert.rejects(() => roundTrip(conn, value), (err) => { + assert.equal(err.name, 'ZuUsageError') + assert.match(err.message, /\bv\b/) + return true + }) + } +}) + +test('a date is days from the epoch, both ways', async (t) => { + const { conn } = await fresh(t) + + const back = await roundTrip(conn, new ZuDate(19723)) + assert.ok(back instanceof ZuDate) + assert.equal(back.days, 19723) + assert.deepEqual(back.toJSON(), { days: 19723 }) + + const literal = (await conn.query("RETURN DATE '2024-01-01' AS d"))[0].d + assert.ok(literal instanceof ZuDate) + assert.equal(literal.days, 19723) +}) + +test('a time keeps its offset, and a local one keeps not having any', async (t) => { + const { conn } = await fresh(t) + + const local = await roundTrip(conn, new ZuTime(3600000000000n, null)) + assert.ok(local instanceof ZuTime) + assert.equal(local.nanos, 3600000000000n) + // Not zero. Zero is UTC, which is a zone, and a local time is in none. + assert.equal(local.offset, null) + + const zoned = await roundTrip(conn, new ZuTime(3600000000000n, 120)) + assert.equal(zoned.offset, 120) + assert.deepEqual(zoned.toJSON(), { nanos: 3600000000000n, offset: 120 }) +}) + +test('a timestamp keeps its instant to the nanosecond', async (t) => { + const { conn } = await fresh(t) + + const local = await roundTrip(conn, new ZuTimestamp(1700000000123456789n, null)) + assert.ok(local instanceof ZuTimestamp) + assert.equal(local.nanos, 1700000000123456789n) + assert.equal(local.offset, null) + + const zoned = await roundTrip(conn, new ZuTimestamp(1700000000123456789n, -480)) + assert.equal(zoned.offset, -480) +}) + +test('a duration is months or nanoseconds and never both', async (t) => { + const { conn } = await fresh(t) + + const months = await roundTrip(conn, ZuDuration.ofMonths(14n)) + assert.ok(months instanceof ZuDuration) + assert.equal(months.kind, 'yearMonth') + assert.equal(months.months, 14n) + assert.equal(months.nanos, 0n) + + const nanos = await roundTrip(conn, ZuDuration.ofNanos(90000000000n)) + assert.equal(nanos.kind, 'dayTime') + assert.equal(nanos.months, 0n) + assert.equal(nanos.nanos, 90000000000n) +}) + +test('a plain object shaped like a date is a record, not a date', async (t) => { + const { conn } = await fresh(t) + + const back = await roundTrip(conn, { days: 19723 }) + + assert.ok(!(back instanceof ZuDate)) + assert.deepEqual(back, { days: 19723n }) +}) diff --git a/types/header.d.ts b/types/header.d.ts new file mode 100644 index 0000000..4d8b19e --- /dev/null +++ b/types/header.d.ts @@ -0,0 +1,126 @@ +/* auto-generated by NAPI-RS */ +/* eslint-disable */ + +/** + * A value a statement can hold, going out. + * + * INT64 is `bigint` and FLOAT is `number`, which is the one rule worth + * learning before anything else here: a JavaScript number stops being + * exact at 2^53 and zu's integers go to 2^63, so a count that came back + * as a number would be a count you cannot trust. + */ +export type ZuValue = + | null + | boolean + | number + | bigint + | string + | ZuNode + | ZuRel + | ZuPath + | ZuDate + | ZuTime + | ZuTimestamp + | ZuDuration + | ZuValue[] + | { [field: string]: ZuValue } + +/** + * A value a statement can be given, coming in. + * + * Wider than what comes out, because a `number` that is whole binds as + * INT64 and `undefined` binds as null, which is what makes an optional + * field of a plain object pass straight through. + */ +export type ZuParam = + | null + | undefined + | boolean + | number + | bigint + | string + | ZuDate + | ZuTime + | ZuTimestamp + | ZuDuration + | ZuParam[] + | { [field: string]: ZuParam } + +/** + * A walk through the graph: nodes and edges, alternating, a node at + * each end. + * + * The two lists are kept apart rather than interleaved, because the + * question a caller asks is almost always about one of them. A path of + * one node has no edges and is the shortest there is. + */ +export interface ZuPath { + readonly nodes: ZuNode[] + readonly rels: ZuRel[] +} + +/** + * Something the engine wants to say about a statement that ran anyway. + * + * A notice is a completion condition of its own, so it carries a + * GQLSTATUS the same way a failure does. The one raised today is + * `01G11`, which says an aggregate ignored a null. + */ +export interface ZuNotice { + readonly code: string + readonly condition: string + readonly message: string + readonly docUrl: string +} + +/** + * The rows a statement gave back. + * + * An array, so iterating it is `for (const row of rows)` and nothing + * else. What a wrapper object would have carried is carried as + * properties beside the elements: `columns` for the projection in the + * order it was written, `gqlstatus` for the condition the statement + * completed with, and `notices` for what it wanted to say on the way. + */ +export interface ZuRows> extends Array { + readonly columns: string[] + readonly gqlstatus: string + readonly notices: ZuNotice[] +} + +/** + * What a failed call throws. + * + * An ordinary `Error`, so every `catch`, logger and unhandled rejection + * handler already knows what to do with it. What makes it a zu error is + * the fields, and none of them has to be parsed back out of the + * message: `code` picks the branch, `line` and `column` underline the + * token, `retryable` decides whether a retry loop goes round again. + */ +export interface ZuError extends Error { + /** + * The condition's class, written out: `ZuSyntaxError`, + * `ZuDataError`, `ZuTransactionError`, `ZuConnectionError`, + * `ZuInterrupted`, `ZuUsageError`, `ZuInternalError`, or `ZuError` + * for a condition in a class none of those name. + */ + readonly name: string + /** Whether running the same statement again could succeed. */ + readonly retryable: boolean + /** + * The GQLSTATUS, five characters. Absent on a mistake this client + * caught before the engine saw it, which is why a caller mapping + * codes to branches has to tell a missing one from an unknown one. + */ + readonly code?: string + /** The standard's own words for that code. */ + readonly condition?: string + readonly severity?: 'success' | 'noData' | 'warning' | 'informational' | 'exception' + readonly docUrl?: string + /** Where in the statement, for a condition that happened somewhere. */ + readonly line?: number + readonly column?: number + readonly offset?: number + /** The whole line `column` indexes into, for underlining it. */ + readonly excerpt?: string +}