diff --git a/.github/actions/setup-jfrog/action.yml b/.github/actions/setup-jfrog/action.yml index b06eba62..a4a2c43c 100644 --- a/.github/actions/setup-jfrog/action.yml +++ b/.github/actions/setup-jfrog/action.yml @@ -34,3 +34,38 @@ runs: always-auth=true EOF echo "npm configured to use JFrog registry" + + # Rewrite lockfile `resolved:` URLs to JFrog. `npm ci` honors the + # lockfile's `resolved:` URL over the `.npmrc` registry; protected + # runners cannot reach `registry.npmjs.org` or the internal + # `npm-proxy.dev.databricks.com` directly, so without this rewrite + # npm hangs ~8 minutes on any package whose `resolved:` points + # elsewhere, then dies with "Exit handler never called". + # The committed lockfile is kept as-generated locally (mixed + # public-npm + dev-proxy URLs) so contributors can `npm ci` + # locally; this step adapts it for CI. + - name: Rewrite lockfile to JFrog registry + shell: bash + run: | + set -euo pipefail + if [ -f package-lock.json ]; then + # Rewrite both public npm and the internal dev-proxy (the + # latter is written by `npm install` when run on a dev + # workstation whose .npmrc points at npm-proxy.dev). + sed -i \ + -e 's|https://registry.npmjs.org|https://databricks.jfrog.io/artifactory/api/npm/db-npm|g' \ + -e 's|https://npm-proxy.dev.databricks.com|https://databricks.jfrog.io/artifactory/api/npm/db-npm|g' \ + package-lock.json + echo "package-lock.json resolved: URLs rewritten to JFrog" + echo "Resolved URL distribution after rewrite:" + grep -oE '"resolved": "https://[^/]+' package-lock.json | sort | uniq -c + # Fail loud if any non-JFrog host remains — a new registry hostname + # added to the tree would otherwise silently hang `npm ci` for 8min. + LEAKS=$(grep -oE '"resolved": "https://[^/]+' package-lock.json | grep -v 'databricks\.jfrog\.io' | sort -u || true) + if [ -n "$LEAKS" ]; then + echo "::error::JFrog rewrite incomplete; non-JFrog hosts remain: $LEAKS" + exit 1 + fi + else + echo "no package-lock.json found; skipping rewrite" + fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a49058c8..f8948e34 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,16 @@ jobs: labels: linux-ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Pin lockfileVersion to 2 + # See CONTRIBUTING.md "Dependency Pins". Modern npm writes v3 + # by default; catching drift here prevents silent format upgrades + # from sneaking in via `npm install`. + run: | + actual=$(jq -r '.lockfileVersion' package-lock.json) + if [ "$actual" != "2" ]; then + echo "::error::package-lock.json lockfileVersion is $actual; expected 2. Regenerate with 'npm install --lockfile-version=2'." + exit 1 + fi - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 20 @@ -49,10 +59,16 @@ jobs: runs-on: group: databricks-protected-runner-group labels: linux-ubuntu-latest + timeout-minutes: 20 strategy: + fail-fast: false matrix: - # only LTS versions starting from the lowest we support - node-version: ['14', '16', '18', '20'] + # LTS versions: 16/18/20 are the currently-supported floor; 22 + # is the active LTS and 24 is the new LTS. Node 14 was dropped + # because the modern npm ecosystem (e.g. @dabh/diagnostics@2.0.7+ + # via winston) ships ES2021 syntax (||=) that Node 14's V8 cannot + # parse. Node 14 has been EOL upstream since April 2023. + node-version: ['20', '22', '24', '26'] env: cache-name: cache-node-modules NYC_REPORT_DIR: coverage_unit_node${{ matrix.node-version }} @@ -62,11 +78,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - name: Set up Python 3.10 for Node 14 - if: ${{ matrix.node-version == '14' }} - uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c # v4 - with: - python-version: '3.10' - uses: ./.github/actions/setup-jfrog - name: Cache node modules uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -94,32 +105,49 @@ jobs: group: databricks-protected-runner-group labels: linux-ubuntu-latest environment: azure-prod + # Cap job time so a wedged matrix entry doesn't hold a runner + + # warehouse session for GitHub's 6-hour default. Tests historically + # complete in <15min; 30min leaves room for warehouse cold-start. + timeout-minutes: 30 + strategy: + # Run all matrix entries even if one fails so a Node-version-specific + # network/TLS regression doesn't hide other versions' results. + fail-fast: false + matrix: + # Matches the unit-test matrix so e2e catches Node-version-specific + # behaviors (TLS/cipher defaults, native fetch interaction, lz4 ABI, + # OAuth crypto APIs) that unit tests with mocked I/O can't surface. + node-version: ['20', '22', '24', '26'] env: E2E_HOST: ${{ secrets.DATABRICKS_HOST }} E2E_PATH: ${{ secrets.TEST_PECO_WAREHOUSE_HTTP_PATH }} E2E_ACCESS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} - E2E_TABLE_SUFFIX: ${{github.sha}} + # Include Node version in the suffix so parallel matrix entries + # don't collide on shared E2E table names. Use underscore (not + # hyphen) — hyphens aren't valid in SQL unquoted identifiers, + # and tests build identifiers as `..._${E2E_TABLE_SUFFIX}`. + E2E_TABLE_SUFFIX: ${{ github.sha }}_node${{ matrix.node-version }} E2E_CATALOG: peco E2E_SCHEMA: default E2E_VOLUME: e2etests cache-name: cache-node-modules - NYC_REPORT_DIR: coverage_e2e + NYC_REPORT_DIR: coverage_e2e_node${{ matrix.node-version }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 20 + node-version: ${{ matrix.node-version }} - uses: ./.github/actions/setup-jfrog - name: Cache node modules uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-${{ matrix.node-version }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + ${{ runner.os }}-${{ matrix.node-version }}-build-${{ env.cache-name }}- + ${{ runner.os }}-${{ matrix.node-version }}-build- + ${{ runner.os }}-${{ matrix.node-version }}- - name: Run e2e tests run: | npm ci diff --git a/CHANGELOG.md b/CHANGELOG.md index 58e90f3c..036c71a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Release History +## 2.0.0 + +**Breaking changes — completes the security cleanup that 1.17.0 could not do without breaking changes.** + +- **`engines.node` raised to `>=20.0.0`** (was `>=14`). Node 14/16/18 are dropped. Node 14 (EOL 2023-04), 16 (EOL 2023-09), and 18 (EOL 2025-04) are all past upstream end-of-life. The `>=20` floor is also the minimum for the patched `serialize-javascript` (dev) — see below — and comfortably clears the kernel backend's own Node 18 requirement. +- **`thrift` `^0.16.0` → `^0.23.0`** — clears GHSA-r67j-r569-jrwp (HIGH) and GHSA-526f-jxpj-jmg2. A direct runtime dep whose fix is a major bump. +- **`uuid` `^9.0.0` → `^11.1.1`** — clears GHSA-w5hq-g745-h8pq (HIGH 7.5). uuid 11 dual-publishes ESM+CJS; the driver's CJS `dist/` continues to `require('uuid')` unchanged. An `overrides` pin holds thrift's transitive uuid at 11 (uuid 13 is ESM-only and would break `require()`). + +**Security:** with the above plus the `overrides` block carried from 1.17.0 (and `serialize-javascript` pinned to the patched `^7.0.5`, which requires Node ≥20), OSV-Scanner reports **zero findings** — 0 CRITICAL / 0 HIGH / 0 MED / 0 LOW. + +Closes #370. + ## 1.17.0 **Security (non-breaking):** clears the non-breaking OSV-Scanner findings via a `package.json` `overrides` block plus a coherent dev-toolchain bump (`mocha`, `eslint`) — **2 CRITICAL** (`basic-ftp`, `form-data`) plus the transitively-fixable HIGH/MED/LOW (`ws`, `@75lb/deep-merge`, `ip-address`, and the mocha-tree deps `braces`/`micromatch`/`cross-spawn`/`minimatch`/`glob`/`@babel/*`/`js-yaml`/`path-to-regexp`). No engine or runtime-API changes — a drop-in patch so consumers can clear critical/high scanner alerts without adopting the breaking `2.0.0` bump. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d2304561..01968699 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,6 +107,20 @@ npm run lint:fix npm run type-check ``` +## Dependency Pins + +A few entries in `package.json` are pinned more tightly than usual. Don't relax these without understanding why. + +- **`typescript: "5.5.4"`** (exact, no caret). This pin has both a floor and a ceiling: + + - Floor (TS >= 5.0) is required because `uuid@11`'s shipped `.d.ts` uses `export type * from './types.js'`, a TS 5.0+ feature. + - Ceiling (TS < 5.6) is required because TS 5.6 changed how `@types/node`'s generic `Buffer` declarations get emitted into our published `dist/*.d.ts`. Allowing TS 5.6+ would leak `Buffer` into the published types, which fails to compile for consumers on stale `@types/node`. + - If you bump TS, run `npm run build` and `git diff dist/` and verify no `Buffer<...>` generics appear in any `.d.ts`. If they do, you need to either roll back or also bump `@types/node` consumer expectations (a customer-facing change). + +- **`overrides.uuid: "^11.1.1"`**. Forces `thrift@0.23.0`'s declared `uuid: ^13.0.0` (ESM-only) down to v11 (dual ESM+CJS). Without this override, the driver's CJS-compiled `dist/` would crash on `require('uuid')` at runtime. Remove this override only after migrating the driver to ESM or when `thrift` drops the uuid dep. + +- **`package-lock.json` is pinned to `lockfileVersion: 2`.** Modern npm writes v3 by default. To regenerate the lockfile, run `npm install --lockfile-version=2` so CI's lint step doesn't reject your PR. v2 is kept for compat with older toolchains; revisit when the team is ready to drop them. + ## Pull Request Process 1. Update the [CHANGELOG](CHANGELOG.md) with details of your changes, if applicable. diff --git a/README.md b/README.md index 9c72fd1a..5d31e138 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The Databricks SQL Driver for Node.js is a Javascript driver for applications th ## Requirements -- Node.js 14 or newer +- Node.js 20 or newer ## Installation diff --git a/lib/connection/auth/tokenProvider/FederationProvider.ts b/lib/connection/auth/tokenProvider/FederationProvider.ts index c3fc9091..417851ff 100644 --- a/lib/connection/auth/tokenProvider/FederationProvider.ts +++ b/lib/connection/auth/tokenProvider/FederationProvider.ts @@ -163,7 +163,12 @@ export default class FederationProvider implements ITokenProvider { 'Content-Type': 'application/x-www-form-urlencoded', }, body, - signal: controller.signal, + // node-fetch ships its own AbortSignal shim that differs slightly + // from the native AbortSignal (subtle `this`-typing mismatch on + // onabort handler). TS 4 didn't catch this; TS 5+ does. The two + // are runtime-compatible, so cast through `unknown` (rather than + // `any`) to keep the assertion narrow. + signal: controller.signal as unknown as import('node-fetch').RequestInit['signal'], }); if (!response.ok) { diff --git a/lib/result/ArrowResultHandler.ts b/lib/result/ArrowResultHandler.ts index 6d56b250..2b225a4c 100644 --- a/lib/result/ArrowResultHandler.ts +++ b/lib/result/ArrowResultHandler.ts @@ -27,7 +27,7 @@ export default class ArrowResultHandler implements IResultsProvider this.isLZ4Compressed = lz4Compressed ?? false; if (this.isLZ4Compressed && !LZ4()) { - throw new HiveDriverError('Cannot handle LZ4 compressed result: module `lz4` not installed'); + throw new HiveDriverError('Cannot handle LZ4 compressed result: module `lz4-napi` not installed'); } } diff --git a/lib/result/CloudFetchResultHandler.ts b/lib/result/CloudFetchResultHandler.ts index b9e89312..755f2a24 100644 --- a/lib/result/CloudFetchResultHandler.ts +++ b/lib/result/CloudFetchResultHandler.ts @@ -43,7 +43,7 @@ export default class CloudFetchResultHandler implements IResultsProvider napi.compressFrameSync(data), + decode: (data: Buffer): Buffer => napi.decompressFrameSync(data), + }; } -// The null is already tried resolving that failed -let resolvedModule: LZ4Module | null | undefined; +// The null means we already tried resolving and it failed. +let resolvedModule: LZ4Codec | null | undefined; -function getResolvedModule() { +function getResolvedModule(): LZ4Codec | undefined { if (resolvedModule === undefined) { resolvedModule = tryLoadLZ4Module() ?? null; } diff --git a/package-lock.json b/package-lock.json index 57b0a993..417060fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@databricks/sql", - "version": "1.17.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@databricks/sql", - "version": "1.17.0", + "version": "2.0.0", "license": "Apache-2.0", "dependencies": { "apache-arrow": "^13.0.0", @@ -17,21 +17,19 @@ "open": "^8.4.2", "openid-client": "^5.4.2", "proxy-agent": "^6.3.1", - "thrift": "^0.16.0", - "uuid": "^9.0.0", + "thrift": "^0.23.0", + "uuid": "^11.1.1", "winston": "^3.8.2" }, "devDependencies": { "@types/chai": "^4.3.14", "@types/http-proxy": "^1.17.14", - "@types/lz4": "^0.6.4", "@types/mocha": "^10.0.6", "@types/node": "^18.11.9", - "@types/node-fetch": "^2.6.4", + "@types/node-fetch": "^2.6.13", "@types/node-int64": "^0.4.29", "@types/sinon": "^17.0.3", "@types/thrift": "^0.10.11", - "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.44.0", "@typescript-eslint/parser": "^5.44.0", "chai": "^4.3.6", @@ -49,10 +47,10 @@ "prettier": "^2.8.4", "sinon": "^17.0.1", "ts-node": "^10.9.2", - "typescript": "^4.9.3" + "typescript": "5.5.4" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "optionalDependencies": { "@databricks/databricks-sql-kernel-darwin-arm64": "0.2.0", @@ -63,7 +61,7 @@ "@databricks/databricks-sql-kernel-linux-x64-musl": "0.2.0", "@databricks/databricks-sql-kernel-win32-arm64-msvc": "0.2.0", "@databricks/databricks-sql-kernel-win32-x64-msvc": "0.2.0", - "lz4": "^0.6.5" + "lz4-napi": "^2.9.0" } }, "node_modules/@75lb/deep-merge": { @@ -95,6 +93,213 @@ "node": ">=12.17" } }, + "node_modules/@antoniomuso/lz4-napi-android-arm-eabi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-android-arm-eabi/-/lz4-napi-android-arm-eabi-2.9.0.tgz", + "integrity": "sha512-aeT/9SoWq7rnmzssWuCKUPaxVt3fzE9q+xq/ZHbnUSmrm8/EhLOACMvQeCOnL0IZsmPh8EpuwIE1TZyM9iQPRA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-android-arm64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-android-arm64/-/lz4-napi-android-arm64-2.9.0.tgz", + "integrity": "sha512-ibQ0qiEvmljXAM97IgOZfh+PeiSQ0Rqf2HErJlZPVm2v4GVJoB67v21v1TUydqNNV5L8bwufVoZ90nheL8X9ZA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-darwin-arm64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-darwin-arm64/-/lz4-napi-darwin-arm64-2.9.0.tgz", + "integrity": "sha512-1su4K1MWa4bcWoZlHajv+luGmFDV1JwIsvjtDF+0HhUveSDPP+8A4Z34zOZidURIr08Sl7M7ViPth6ZQ9SqnAA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-darwin-x64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-darwin-x64/-/lz4-napi-darwin-x64-2.9.0.tgz", + "integrity": "sha512-8Lnbm2MkdJtiJ/nbcRS9zRyGp3G0sG6D+Y/x1vTP8nZs3/f8tBwYNsjxCQyyXNNyHcYWwVGbk68onP/pyDljOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-freebsd-x64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-freebsd-x64/-/lz4-napi-freebsd-x64-2.9.0.tgz", + "integrity": "sha512-k04EMVOjntKDPrdR4Tf8WyNseuk9PTtSGw8WHyp4CTjoR1s+YJxtp9SMnThe5o2q0TATwk8WGYb/Howrp5OMxw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-linux-arm-gnueabihf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm-gnueabihf/-/lz4-napi-linux-arm-gnueabihf-2.9.0.tgz", + "integrity": "sha512-H92F8zPZmgy2r8IhCWh3qIBfLp2BQ5cp18RoDXhtGFWwkh+5gVWrZp11IVznrsdgB0QeW0VR7dAMMHg3WLOPfA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-linux-arm64-gnu": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm64-gnu/-/lz4-napi-linux-arm64-gnu-2.9.0.tgz", + "integrity": "sha512-25crh0qs/3Rj3fMI8ulYD0DoaKsidUhMBki2aeO69ZK+F8bmQ/e2++FlgJ6f3EgMP5CNxJtnZXKhPOraQWjwAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-linux-arm64-musl": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm64-musl/-/lz4-napi-linux-arm64-musl-2.9.0.tgz", + "integrity": "sha512-eJtHp38zuLaYI0/cOV/BKcNQiXUBo4GPx53FTf0Y307yUjLsn48LNeN0vD28Ct9YrbUae3bQvMD5AD86She0ww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-linux-x64-gnu": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-x64-gnu/-/lz4-napi-linux-x64-gnu-2.9.0.tgz", + "integrity": "sha512-mDjS4dyjRKaZQcAP71SphkYH5r3kufB30ih/VETVu/br2toCfBk6Zr1xhL1r+L7FaVAFzF62B7h30CiqrN0Awg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-linux-x64-musl": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-x64-musl/-/lz4-napi-linux-x64-musl-2.9.0.tgz", + "integrity": "sha512-pvU7Z7qjkjn17NkddBtBQ7C2iRqjtZ7WJ3Jqrjtj4XxolY3Q0HaYMvWjkWhzb9AKGZbj5y+EHYtbVoZJ2TSQhQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-win32-arm64-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-arm64-msvc/-/lz4-napi-win32-arm64-msvc-2.9.0.tgz", + "integrity": "sha512-aioLlbpJl0QPEXLXhh2bzyitc3T7Jot3f1ap6WdKiRa+CIjMHXw1nxJXy07MLXif10r+qVZr86ic8dvwErgqEQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-win32-ia32-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-ia32-msvc/-/lz4-napi-win32-ia32-msvc-2.9.0.tgz", + "integrity": "sha512-VaF4XMTdYb59TsPsiqnWwsNaWKHhgxF33z5p4zg4n0tp20eWozl76hn8B+aXthSs40W0W1N97QhxxV4oXGd8cg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@antoniomuso/lz4-napi-win32-x64-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-x64-msvc/-/lz4-napi-win32-x64-msvc-2.9.0.tgz", + "integrity": "sha512-wfA8ShO3eGLxJ1LDwXJo87XL2D4NkMJV1pfHPvLZpD0MWb9u8VfgS+gKK5YhT7XKjzVdeIna9jgFdn2HBnZBxA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -861,6 +1066,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/triples": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.2.0.tgz", + "integrity": "sha512-HAPjR3bnCsdXBsATpDIP5WCrw0JcACwhhrwIAQhiR46n+jm+a2F8kBsfseAuWtSyQ+H3Yebt2k43B5dy+04yMA==", + "license": "MIT", + "optional": true + }, + "node_modules/@node-rs/helper": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.6.0.tgz", + "integrity": "sha512-2OTh/tokcLA1qom1zuCJm2gQzaZljCCbtX1YCrwRVd/toz7KxaDRFeLTAPwhs8m9hWgzrBn5rShRm6IaZofCPw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/triples": "^1.2.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1006,15 +1228,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/lz4": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@types/lz4/-/lz4-0.6.4.tgz", - "integrity": "sha512-vvxMIkowyHbY6zkCantyYwAK83N5E04bzFZSPJICG1SDpaL89L7lObs9DhLuhJhXHCQMkjzN3LYoAH6LjfwMFQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -1028,13 +1241,14 @@ "dev": true }, "node_modules/@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", - "form-data": "^3.0.0" + "form-data": "^4.0.4" } }, "node_modules/@types/node-int64": { @@ -1089,12 +1303,6 @@ "@types/q": "*" } }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", @@ -1620,26 +1828,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, "node_modules/baseline-browser-mapping": { "version": "2.10.41", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", @@ -1740,30 +1928,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -2142,12 +2306,6 @@ "node": ">= 8" } }, - "node_modules/cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", - "optional": true - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3761,26 +3919,6 @@ "node": ">= 14" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -4242,15 +4380,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -4531,20 +4660,32 @@ "node": ">=10" } }, - "node_modules/lz4": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/lz4/-/lz4-0.6.5.tgz", - "integrity": "sha512-KSZcJU49QZOlJSItaeIU3p8WoAvkTmD9fJqeahQXNu1iQ/kR0/mQLdbrK8JY9MY8f6AhJoMrihp1nu1xDbscSQ==", - "hasInstallScript": true, + "node_modules/lz4-napi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/lz4-napi/-/lz4-napi-2.9.0.tgz", + "integrity": "sha512-ZOWqxBMIK5768aD20tYn5B6Pp9WPM9UG/LHk8neG9p0gC1DtjdzhTtlkxhAjvTRpmJvMtnnqLKlT+COlqAt9cQ==", + "license": "MIT", "optional": true, "dependencies": { - "buffer": "^5.2.1", - "cuint": "^0.2.2", - "nan": "^2.13.2", - "xxhashjs": "^0.2.2" + "@node-rs/helper": "^1.3.3" }, "engines": { - "node": ">= 0.10" + "node": ">= 10" + }, + "optionalDependencies": { + "@antoniomuso/lz4-napi-android-arm-eabi": "2.9.0", + "@antoniomuso/lz4-napi-android-arm64": "2.9.0", + "@antoniomuso/lz4-napi-darwin-arm64": "2.9.0", + "@antoniomuso/lz4-napi-darwin-x64": "2.9.0", + "@antoniomuso/lz4-napi-freebsd-x64": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm-gnueabihf": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm64-gnu": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm64-musl": "2.9.0", + "@antoniomuso/lz4-napi-linux-x64-gnu": "2.9.0", + "@antoniomuso/lz4-napi-linux-x64-musl": "2.9.0", + "@antoniomuso/lz4-napi-win32-arm64-msvc": "2.9.0", + "@antoniomuso/lz4-napi-win32-ia32-msvc": "2.9.0", + "@antoniomuso/lz4-napi-win32-x64-msvc": "2.9.0" } }, "node_modules/make-dir": { @@ -4747,16 +4888,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -4778,12 +4909,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "optional": true - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5607,16 +5732,6 @@ } ] }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5822,6 +5937,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6213,14 +6338,16 @@ "dev": true }, "node_modules/thrift": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.16.0.tgz", - "integrity": "sha512-W8DpGyTPlIaK3f+e1XOCLxefaUWXtrOXAaVIDbfYhmVyriYeAKgsBVFNJUV1F9SQ2SPt2sG44AZQxSGwGj/3VA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.23.0.tgz", + "integrity": "sha512-j7F1ls8JogClU88Ta/pwD/OzYuiFeD6Z5GoWw7ip+jcDhcNYFKgfXYEsyLXYpiNfJO94fbLnITGxyfZ19YzA6Q==", + "license": "Apache-2.0", "dependencies": { "browser-or-node": "^1.2.1", "isomorphic-ws": "^4.0.1", "node-int64": "^0.4.0", "q": "^1.5.0", + "uuid": "^13.0.0", "ws": "^5.2.3" }, "engines": { @@ -6402,16 +6529,17 @@ } }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/typical": { @@ -6492,11 +6620,16 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { @@ -6671,15 +6804,6 @@ } } }, - "node_modules/xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "optional": true, - "dependencies": { - "cuint": "^0.2.2" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6800,6 +6924,84 @@ } } }, + "@antoniomuso/lz4-napi-android-arm-eabi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-android-arm-eabi/-/lz4-napi-android-arm-eabi-2.9.0.tgz", + "integrity": "sha512-aeT/9SoWq7rnmzssWuCKUPaxVt3fzE9q+xq/ZHbnUSmrm8/EhLOACMvQeCOnL0IZsmPh8EpuwIE1TZyM9iQPRA==", + "optional": true + }, + "@antoniomuso/lz4-napi-android-arm64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-android-arm64/-/lz4-napi-android-arm64-2.9.0.tgz", + "integrity": "sha512-ibQ0qiEvmljXAM97IgOZfh+PeiSQ0Rqf2HErJlZPVm2v4GVJoB67v21v1TUydqNNV5L8bwufVoZ90nheL8X9ZA==", + "optional": true + }, + "@antoniomuso/lz4-napi-darwin-arm64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-darwin-arm64/-/lz4-napi-darwin-arm64-2.9.0.tgz", + "integrity": "sha512-1su4K1MWa4bcWoZlHajv+luGmFDV1JwIsvjtDF+0HhUveSDPP+8A4Z34zOZidURIr08Sl7M7ViPth6ZQ9SqnAA==", + "optional": true + }, + "@antoniomuso/lz4-napi-darwin-x64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-darwin-x64/-/lz4-napi-darwin-x64-2.9.0.tgz", + "integrity": "sha512-8Lnbm2MkdJtiJ/nbcRS9zRyGp3G0sG6D+Y/x1vTP8nZs3/f8tBwYNsjxCQyyXNNyHcYWwVGbk68onP/pyDljOA==", + "optional": true + }, + "@antoniomuso/lz4-napi-freebsd-x64": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-freebsd-x64/-/lz4-napi-freebsd-x64-2.9.0.tgz", + "integrity": "sha512-k04EMVOjntKDPrdR4Tf8WyNseuk9PTtSGw8WHyp4CTjoR1s+YJxtp9SMnThe5o2q0TATwk8WGYb/Howrp5OMxw==", + "optional": true + }, + "@antoniomuso/lz4-napi-linux-arm-gnueabihf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm-gnueabihf/-/lz4-napi-linux-arm-gnueabihf-2.9.0.tgz", + "integrity": "sha512-H92F8zPZmgy2r8IhCWh3qIBfLp2BQ5cp18RoDXhtGFWwkh+5gVWrZp11IVznrsdgB0QeW0VR7dAMMHg3WLOPfA==", + "optional": true + }, + "@antoniomuso/lz4-napi-linux-arm64-gnu": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm64-gnu/-/lz4-napi-linux-arm64-gnu-2.9.0.tgz", + "integrity": "sha512-25crh0qs/3Rj3fMI8ulYD0DoaKsidUhMBki2aeO69ZK+F8bmQ/e2++FlgJ6f3EgMP5CNxJtnZXKhPOraQWjwAw==", + "optional": true + }, + "@antoniomuso/lz4-napi-linux-arm64-musl": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-arm64-musl/-/lz4-napi-linux-arm64-musl-2.9.0.tgz", + "integrity": "sha512-eJtHp38zuLaYI0/cOV/BKcNQiXUBo4GPx53FTf0Y307yUjLsn48LNeN0vD28Ct9YrbUae3bQvMD5AD86She0ww==", + "optional": true + }, + "@antoniomuso/lz4-napi-linux-x64-gnu": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-x64-gnu/-/lz4-napi-linux-x64-gnu-2.9.0.tgz", + "integrity": "sha512-mDjS4dyjRKaZQcAP71SphkYH5r3kufB30ih/VETVu/br2toCfBk6Zr1xhL1r+L7FaVAFzF62B7h30CiqrN0Awg==", + "optional": true + }, + "@antoniomuso/lz4-napi-linux-x64-musl": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-linux-x64-musl/-/lz4-napi-linux-x64-musl-2.9.0.tgz", + "integrity": "sha512-pvU7Z7qjkjn17NkddBtBQ7C2iRqjtZ7WJ3Jqrjtj4XxolY3Q0HaYMvWjkWhzb9AKGZbj5y+EHYtbVoZJ2TSQhQ==", + "optional": true + }, + "@antoniomuso/lz4-napi-win32-arm64-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-arm64-msvc/-/lz4-napi-win32-arm64-msvc-2.9.0.tgz", + "integrity": "sha512-aioLlbpJl0QPEXLXhh2bzyitc3T7Jot3f1ap6WdKiRa+CIjMHXw1nxJXy07MLXif10r+qVZr86ic8dvwErgqEQ==", + "optional": true + }, + "@antoniomuso/lz4-napi-win32-ia32-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-ia32-msvc/-/lz4-napi-win32-ia32-msvc-2.9.0.tgz", + "integrity": "sha512-VaF4XMTdYb59TsPsiqnWwsNaWKHhgxF33z5p4zg4n0tp20eWozl76hn8B+aXthSs40W0W1N97QhxxV4oXGd8cg==", + "optional": true + }, + "@antoniomuso/lz4-napi-win32-x64-msvc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@antoniomuso/lz4-napi-win32-x64-msvc/-/lz4-napi-win32-x64-msvc-2.9.0.tgz", + "integrity": "sha512-wfA8ShO3eGLxJ1LDwXJo87XL2D4NkMJV1pfHPvLZpD0MWb9u8VfgS+gKK5YhT7XKjzVdeIna9jgFdn2HBnZBxA==", + "optional": true + }, "@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -7306,6 +7508,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@napi-rs/triples": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.2.0.tgz", + "integrity": "sha512-HAPjR3bnCsdXBsATpDIP5WCrw0JcACwhhrwIAQhiR46n+jm+a2F8kBsfseAuWtSyQ+H3Yebt2k43B5dy+04yMA==", + "optional": true + }, + "@node-rs/helper": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.6.0.tgz", + "integrity": "sha512-2OTh/tokcLA1qom1zuCJm2gQzaZljCCbtX1YCrwRVd/toz7KxaDRFeLTAPwhs8m9hWgzrBn5rShRm6IaZofCPw==", + "optional": true, + "requires": { + "@napi-rs/triples": "^1.2.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7444,15 +7661,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/lz4": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@types/lz4/-/lz4-0.6.4.tgz", - "integrity": "sha512-vvxMIkowyHbY6zkCantyYwAK83N5E04bzFZSPJICG1SDpaL89L7lObs9DhLuhJhXHCQMkjzN3LYoAH6LjfwMFQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -7466,9 +7674,9 @@ "dev": true }, "@types/node-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", - "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dev": true, "requires": { "@types/node": "*", @@ -7527,12 +7735,6 @@ "@types/q": "*" } }, - "@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, "@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", @@ -7889,12 +8091,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "optional": true - }, "baseline-browser-mapping": { "version": "2.10.41", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", @@ -7955,16 +8151,6 @@ "update-browserslist-db": "^1.2.3" } }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "optional": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -8250,12 +8436,6 @@ "which": "^2.0.1" } }, - "cuint": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", - "optional": true - }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9398,12 +9578,6 @@ "debug": "4" } }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "optional": true - }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -9714,15 +9888,7 @@ "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - } + "uuid": "^11.1.1" } }, "istanbul-lib-report": { @@ -9946,16 +10112,26 @@ "yallist": "^4.0.0" } }, - "lz4": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/lz4/-/lz4-0.6.5.tgz", - "integrity": "sha512-KSZcJU49QZOlJSItaeIU3p8WoAvkTmD9fJqeahQXNu1iQ/kR0/mQLdbrK8JY9MY8f6AhJoMrihp1nu1xDbscSQ==", + "lz4-napi": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/lz4-napi/-/lz4-napi-2.9.0.tgz", + "integrity": "sha512-ZOWqxBMIK5768aD20tYn5B6Pp9WPM9UG/LHk8neG9p0gC1DtjdzhTtlkxhAjvTRpmJvMtnnqLKlT+COlqAt9cQ==", "optional": true, "requires": { - "buffer": "^5.2.1", - "cuint": "^0.2.2", - "nan": "^2.13.2", - "xxhashjs": "^0.2.2" + "@antoniomuso/lz4-napi-android-arm-eabi": "2.9.0", + "@antoniomuso/lz4-napi-android-arm64": "2.9.0", + "@antoniomuso/lz4-napi-darwin-arm64": "2.9.0", + "@antoniomuso/lz4-napi-darwin-x64": "2.9.0", + "@antoniomuso/lz4-napi-freebsd-x64": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm-gnueabihf": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm64-gnu": "2.9.0", + "@antoniomuso/lz4-napi-linux-arm64-musl": "2.9.0", + "@antoniomuso/lz4-napi-linux-x64-gnu": "2.9.0", + "@antoniomuso/lz4-napi-linux-x64-musl": "2.9.0", + "@antoniomuso/lz4-napi-win32-arm64-msvc": "2.9.0", + "@antoniomuso/lz4-napi-win32-ia32-msvc": "2.9.0", + "@antoniomuso/lz4-napi-win32-x64-msvc": "2.9.0", + "@node-rs/helper": "^1.3.3" } }, "make-dir": { @@ -10063,7 +10239,7 @@ "log-symbols": "^4.1.0", "minimatch": "^5.1.6", "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", + "serialize-javascript": "^7.0.5", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^6.5.1", @@ -10100,15 +10276,6 @@ "argparse": "^2.0.1" } }, - "serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -10125,12 +10292,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "optional": true - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10731,15 +10892,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -10875,6 +11027,12 @@ "lru-cache": "^6.0.0" } }, + "serialize-javascript": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", + "dev": true + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -11168,14 +11326,15 @@ "dev": true }, "thrift": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.16.0.tgz", - "integrity": "sha512-W8DpGyTPlIaK3f+e1XOCLxefaUWXtrOXAaVIDbfYhmVyriYeAKgsBVFNJUV1F9SQ2SPt2sG44AZQxSGwGj/3VA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.23.0.tgz", + "integrity": "sha512-j7F1ls8JogClU88Ta/pwD/OzYuiFeD6Z5GoWw7ip+jcDhcNYFKgfXYEsyLXYpiNfJO94fbLnITGxyfZ19YzA6Q==", "requires": { "browser-or-node": "^1.2.1", "isomorphic-ws": "^4.0.1", "node-int64": "^0.4.0", "q": "^1.5.0", + "uuid": "^11.1.1", "ws": "^8.18.0" } }, @@ -11302,9 +11461,9 @@ } }, "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true }, "typical": { @@ -11354,9 +11513,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==" }, "v8-compile-cache-lib": { "version": "3.0.1", @@ -11486,15 +11645,6 @@ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "requires": {} }, - "xxhashjs": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", - "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", - "optional": true, - "requires": { - "cuint": "^0.2.2" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index ebf41fd2..e963ac74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@databricks/sql", - "version": "1.17.0", + "version": "2.0.0", "description": "Driver for connection to Databricks SQL via Thrift API.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -9,7 +9,7 @@ "test": "tests" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "scripts": { "prepare": "npm run build", @@ -51,14 +51,12 @@ "devDependencies": { "@types/chai": "^4.3.14", "@types/http-proxy": "^1.17.14", - "@types/lz4": "^0.6.4", "@types/mocha": "^10.0.6", "@types/node": "^18.11.9", - "@types/node-fetch": "^2.6.4", + "@types/node-fetch": "^2.6.13", "@types/node-int64": "^0.4.29", "@types/sinon": "^17.0.3", "@types/thrift": "^0.10.11", - "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.44.0", "@typescript-eslint/parser": "^5.44.0", "chai": "^4.3.6", @@ -76,7 +74,7 @@ "prettier": "^2.8.4", "sinon": "^17.0.1", "ts-node": "^10.9.2", - "typescript": "^4.9.3" + "typescript": "5.5.4" }, "dependencies": { "apache-arrow": "^13.0.0", @@ -87,12 +85,12 @@ "open": "^8.4.2", "openid-client": "^5.4.2", "proxy-agent": "^6.3.1", - "thrift": "^0.16.0", - "uuid": "^9.0.0", + "thrift": "^0.23.0", + "uuid": "^11.1.1", "winston": "^3.8.2" }, "optionalDependencies": { - "lz4": "^0.6.5", + "lz4-napi": "^2.9.0", "@databricks/databricks-sql-kernel-linux-x64-gnu": "0.2.0", "@databricks/databricks-sql-kernel-linux-arm64-gnu": "0.2.0", "@databricks/databricks-sql-kernel-linux-x64-musl": "0.2.0", @@ -107,6 +105,8 @@ "@75lb/deep-merge": "^1.1.2", "ws": "^8.18.0", "ip-address": "^10.1.1", - "form-data": "^4.0.4" + "form-data": "^4.0.4", + "uuid": "^11.1.1", + "serialize-javascript": "^7.0.5" } } diff --git a/tests/e2e/.mocharc.js b/tests/e2e/.mocharc.js index f410891b..1790cbe4 100644 --- a/tests/e2e/.mocharc.js +++ b/tests/e2e/.mocharc.js @@ -7,4 +7,9 @@ const argvSpecs = process.argv.slice(4); module.exports = { spec: argvSpecs.length > 0 ? argvSpecs : allSpecs, timeout: '300000', + // See tests/unit/.mocharc.js — ts-node handles .ts on both the CJS + // (require) and ESM (import) load paths, so Node 24+'s built-in + // strip-types never intercepts specs using TS parameter properties. + require: 'ts-node/register', + loader: 'ts-node/esm', }; diff --git a/tests/unit/.mocharc.js b/tests/unit/.mocharc.js index 8dcbd98e..4b49ed01 100644 --- a/tests/unit/.mocharc.js +++ b/tests/unit/.mocharc.js @@ -6,4 +6,15 @@ const argvSpecs = process.argv.slice(4); module.exports = { spec: argvSpecs.length > 0 ? argvSpecs : allSpecs, + // Compile .ts specs with ts-node on every supported Node version. Both + // hooks are needed: mocha loads some specs via `require` (CJS) and others + // via `import()` (ESM), and on Node 24+ any .ts reaching the ESM path is + // grabbed by Node's built-in --experimental-strip-types, whose strip-only + // mode rejects TS parameter properties ("parameter property is not + // supported in strip-only mode"). `require: ts-node/register` covers the + // CJS path; `loader: ts-node/esm` covers the import() path. Verified on + // Node 20 and 24. (Avoid the --no-experimental-strip-types node flag — it + // doesn't exist before Node 22.6 and crashes older Node at startup.) + require: 'ts-node/register', + loader: 'ts-node/esm', }; diff --git a/tests/unit/.stubs/OAuth.ts b/tests/unit/.stubs/OAuth.ts index bd7026f2..1f892d46 100644 --- a/tests/unit/.stubs/OAuth.ts +++ b/tests/unit/.stubs/OAuth.ts @@ -139,6 +139,12 @@ export class OAuthCallbackServerStub< public unref() { return this; } + + // Required by @types/node >= 18.19.x (Node 20+ added Symbol.asyncDispose to Server). + // Cast through `any`: the project targets ES2018, whose lib predates + // Symbol.asyncDispose, so referencing it directly is a compile error even + // though the runtime (Node 20+) provides it. + public async [(Symbol as any).asyncDispose]() {} } export class AuthorizationCodeStub { diff --git a/tests/unit/result/ArrowResultConverter.test.ts b/tests/unit/result/ArrowResultConverter.test.ts index 44c9306c..5c7785fb 100644 --- a/tests/unit/result/ArrowResultConverter.test.ts +++ b/tests/unit/result/ArrowResultConverter.test.ts @@ -54,9 +54,19 @@ const sampleArrowBatch = [ ]), ]; +// Resolve from CWD (always the repo root when mocha runs) rather than +// __dirname. Node 22+'s ESM auto-detection loads .ts specs as ES modules, +// where CJS globals like __dirname are unavailable. +// Loud check for the CWD assumption: this file reads stub fixtures at +// import time, so a wrong CWD must fail clearly rather than producing +// an opaque ENOENT. +if (!fs.existsSync('package.json')) { + throw new Error(`Expected mocha to be invoked from repo root; CWD=${process.cwd()} has no package.json`); +} +const ARROW_STUBS_DIR = path.resolve('tests/unit/result/.stubs'); const arrowBatchAllNulls = [ - fs.readFileSync(path.join(__dirname, './.stubs/arrowSchemaAllNulls.arrow')), - fs.readFileSync(path.join(__dirname, './.stubs/dataAllNulls.arrow')), + fs.readFileSync(path.join(ARROW_STUBS_DIR, 'arrowSchemaAllNulls.arrow')), + fs.readFileSync(path.join(ARROW_STUBS_DIR, 'dataAllNulls.arrow')), ]; const emptyItem: ArrowBatch = { diff --git a/tests/unit/result/ArrowResultHandler.test.ts b/tests/unit/result/ArrowResultHandler.test.ts index 3003083f..a81209a6 100644 --- a/tests/unit/result/ArrowResultHandler.test.ts +++ b/tests/unit/result/ArrowResultHandler.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import Int64 from 'node-int64'; -import LZ4 from 'lz4'; +import { compressFrameSync } from 'lz4-napi'; import ArrowResultHandler from '../../../lib/result/ArrowResultHandler'; import ResultsProviderStub from '../.stubs/ResultsProviderStub'; import { TRowSet, TSparkArrowBatch, TStatusCode, TTableSchema } from '../../../thrift/TCLIService_types'; @@ -39,7 +39,7 @@ const sampleRowSet1LZ4Compressed: TRowSet = { rows: [], arrowBatches: sampleRowSet1.arrowBatches?.map((item) => ({ ...item, - batch: LZ4.encode(item.batch), + batch: compressFrameSync(item.batch), })), }; diff --git a/tests/unit/result/CloudFetchResultHandler.test.ts b/tests/unit/result/CloudFetchResultHandler.test.ts index da5911c6..93ab712a 100644 --- a/tests/unit/result/CloudFetchResultHandler.test.ts +++ b/tests/unit/result/CloudFetchResultHandler.test.ts @@ -1,7 +1,7 @@ import { expect, AssertionError } from 'chai'; import sinon, { SinonStub } from 'sinon'; import Int64 from 'node-int64'; -import LZ4 from 'lz4'; +import { compressFrameSync } from 'lz4-napi'; import { Request, Response } from 'node-fetch'; import { ShouldRetryResult } from '../../../lib/connection/contracts/IRetryPolicy'; import { HttpTransactionDetails } from '../../../lib/connection/contracts/IConnectionProvider'; @@ -372,7 +372,7 @@ describe('CloudFetchResultHandler', () => { context.invokeWithRetryStub.callsFake(async () => ({ request: new Request('localhost'), - response: new Response(LZ4.encode(expectedBatch), { status: 200 }), + response: new Response(compressFrameSync(expectedBatch), { status: 200 }), })); expect(await rowSetProvider.hasMore()).to.be.true; diff --git a/tests/unit/thrift-field-id-validation.test.ts b/tests/unit/thrift-field-id-validation.test.ts index 18609364..dde077b1 100644 --- a/tests/unit/thrift-field-id-validation.test.ts +++ b/tests/unit/thrift-field-id-validation.test.ts @@ -10,7 +10,19 @@ import { expect } from 'chai'; */ describe('Thrift Field ID Validation', () => { const MAX_ALLOWED_FIELD_ID = 3329; - const THRIFT_DIR = path.join(__dirname, '../../thrift'); + // Resolve from CWD (always the repo root when mocha runs) rather than + // __dirname. Node 22+'s ESM auto-detection loads .ts specs as ES + // modules, where CJS globals like __dirname are unavailable. + const THRIFT_DIR = path.resolve('thrift'); + + before(() => { + // Loud check for the CWD assumption above. Fails clearly when this + // file is executed from a non-repo-root CWD (e.g. running a single + // spec from a subdirectory) instead of producing an opaque ENOENT. + if (!fs.existsSync('package.json')) { + throw new Error(`Expected mocha to be invoked from repo root; CWD=${process.cwd()} has no package.json`); + } + }); it('should ensure all Thrift field IDs are within allowed range', () => { const violations: string[] = []; diff --git a/tests/unit/utils/lz4.test.ts b/tests/unit/utils/lz4.test.ts index bd10a88a..95713e47 100644 --- a/tests/unit/utils/lz4.test.ts +++ b/tests/unit/utils/lz4.test.ts @@ -1,20 +1,35 @@ import { expect } from 'chai'; import sinon from 'sinon'; - -describe('lz4 module loader', () => { - let moduleLoadStub: sinon.SinonStub | undefined; +import getLZ4 from '../../../lib/utils/lz4'; + +// Exercises the lz4 loader (lib/utils/lz4.ts), which now wraps `lz4-napi` +// and exposes a stable { encode, decode } frame codec with a +// load-failure-tolerant contract. +// +// This suite uses CJS-only primitives (Module._load, require.cache) to +// inject a fake/failed 'lz4-napi'. Node 22+ loads .ts specs via the ESM +// loader where those aren't available; skip there — the production loader +// works fine, only this test's interception mechanism is CJS-bound. +describe('lz4 module loader', function () { + let moduleLoadRestore: (() => void) | undefined; let consoleWarnStub: sinon.SinonStub; + before(function () { + if (typeof require === 'undefined') { + this.skip(); + } + }); + beforeEach(() => { consoleWarnStub = sinon.stub(console, 'warn'); }); afterEach(() => { consoleWarnStub.restore(); - if (moduleLoadStub) { - moduleLoadStub.restore(); + if (moduleLoadRestore) { + moduleLoadRestore(); + moduleLoadRestore = undefined; } - // Clear module cache Object.keys(require.cache).forEach((key) => { if (key.includes('lz4')) { delete require.cache[key]; @@ -22,29 +37,28 @@ describe('lz4 module loader', () => { }); }); - const mockModuleLoad = (lz4MockOrError: unknown): { restore: () => void; wasLz4LoadAttempted: () => boolean } => { + // Intercept require('lz4-napi') to return a mock or throw a given error. + const mockNapiLoad = (napiMockOrError: unknown): { wasLoadAttempted: () => boolean } => { // eslint-disable-next-line global-require const Module = require('module'); const originalLoad = Module._load; - let lz4LoadAttempted = false; + let loadAttempted = false; Module._load = (request: string, parent: unknown, isMain: boolean) => { - if (request === 'lz4') { - lz4LoadAttempted = true; - if (lz4MockOrError instanceof Error) { - throw lz4MockOrError; + if (request === 'lz4-napi') { + loadAttempted = true; + if (napiMockOrError instanceof Error) { + throw napiMockOrError; } - return lz4MockOrError; + return napiMockOrError; } return originalLoad.call(Module, request, parent, isMain); }; - return { - restore: () => { - Module._load = originalLoad; - }, - wasLz4LoadAttempted: () => lz4LoadAttempted, + moduleLoadRestore = () => { + Module._load = originalLoad; }; + return { wasLoadAttempted: () => loadAttempted }; }; const loadLz4Module = () => { @@ -53,104 +67,131 @@ describe('lz4 module loader', () => { return require('../../../lib/utils/lz4'); }; - it('should successfully load and use lz4 module when available', () => { - const fakeLz4 = { - encode: (buf: Buffer) => { - const compressed = Buffer.from(`compressed:${buf.toString()}`); - return compressed; - }, - decode: (buf: Buffer) => { - const decompressed = buf.toString().replace('compressed:', ''); - return Buffer.from(decompressed); - }, + it('exposes an { encode, decode } codec backed by lz4-napi frame APIs', () => { + // Fake lz4-napi: frame APIs the adapter calls. + const fakeNapi = { + compressFrameSync: (buf: Buffer) => Buffer.from(`frame:${buf.toString()}`), + decompressFrameSync: (buf: Buffer) => Buffer.from(buf.toString().replace('frame:', '')), }; - const { restore } = mockModuleLoad(fakeLz4); - const moduleExports = loadLz4Module(); - const lz4Module = moduleExports.default(); - restore(); + mockNapiLoad(fakeNapi); + const lz4Module = loadLz4Module().default(); expect(lz4Module).to.not.be.undefined; expect(lz4Module.encode).to.be.a('function'); expect(lz4Module.decode).to.be.a('function'); const testData = Buffer.from('Hello, World!'); - const compressed = lz4Module.encode(testData); - const decompressed = lz4Module.decode(compressed); - - expect(decompressed.toString()).to.equal('Hello, World!'); + const roundTripped = lz4Module.decode(lz4Module.encode(testData)); + expect(roundTripped.toString()).to.equal('Hello, World!'); expect(consoleWarnStub.called).to.be.false; }); - it('should return undefined when lz4 module fails to load with MODULE_NOT_FOUND', () => { - const err: NodeJS.ErrnoException = new Error("Cannot find module 'lz4'"); + it('returns undefined (no warning) when lz4-napi is not installed (MODULE_NOT_FOUND)', () => { + const err: NodeJS.ErrnoException = new Error("Cannot find module 'lz4-napi'"); err.code = 'MODULE_NOT_FOUND'; - const { restore } = mockModuleLoad(err); - const moduleExports = loadLz4Module(); - const lz4Module = moduleExports.default(); - restore(); + mockNapiLoad(err); + const lz4Module = loadLz4Module().default(); expect(lz4Module).to.be.undefined; expect(consoleWarnStub.called).to.be.false; }); - it('should return undefined and log warning when lz4 fails with ERR_DLOPEN_FAILED', () => { - const err: NodeJS.ErrnoException = new Error('Module did not self-register'); + it('returns undefined and warns on any other load failure', () => { + const err: NodeJS.ErrnoException = new Error('unexpected dlopen failure'); err.code = 'ERR_DLOPEN_FAILED'; - const { restore } = mockModuleLoad(err); - const moduleExports = loadLz4Module(); - const lz4Module = moduleExports.default(); - restore(); + mockNapiLoad(err); + const lz4Module = loadLz4Module().default(); expect(lz4Module).to.be.undefined; expect(consoleWarnStub.calledOnce).to.be.true; - expect(consoleWarnStub.firstCall.args[0]).to.include('Architecture or version mismatch'); + expect(consoleWarnStub.firstCall.args[0]).to.include('lz4-napi'); }); - it('should return undefined and log warning when lz4 fails with unknown error code', () => { - const err: NodeJS.ErrnoException = new Error('Some unknown error'); - err.code = 'UNKNOWN_ERROR'; - - const { restore } = mockModuleLoad(err); - const moduleExports = loadLz4Module(); - const lz4Module = moduleExports.default(); - restore(); - - expect(lz4Module).to.be.undefined; - expect(consoleWarnStub.calledOnce).to.be.true; - expect(consoleWarnStub.firstCall.args[0]).to.include('Unhandled error code'); - }); + it('does not attempt to load lz4-napi until the codec is requested', () => { + const fakeNapi = { + compressFrameSync: () => Buffer.from(''), + decompressFrameSync: () => Buffer.from(''), + }; - it('should return undefined and log warning when error has no code property', () => { - const err = new Error('Error without code'); + const { wasLoadAttempted } = mockNapiLoad(fakeNapi); - const { restore } = mockModuleLoad(err); - const moduleExports = loadLz4Module(); - const lz4Module = moduleExports.default(); - restore(); + // Import the module but do NOT call the default export (getResolvedModule). + loadLz4Module(); - expect(lz4Module).to.be.undefined; - expect(consoleWarnStub.calledOnce).to.be.true; - expect(consoleWarnStub.firstCall.args[0]).to.include('Invalid error object'); + expect(wasLoadAttempted()).to.be.false; + expect(consoleWarnStub.called).to.be.false; }); +}); - it('should not attempt to load lz4 module when getResolvedModule is not called', () => { - const fakeLz4 = { - encode: () => Buffer.from(''), - decode: () => Buffer.from(''), - }; - - const { restore, wasLz4LoadAttempted } = mockModuleLoad(fakeLz4); +// Real (unmocked) frame-format compatibility, exercised through the driver's +// own codec (lib/utils/lz4.ts) with the actual `lz4-napi`. This is the test +// that backs the "LZ4 frame compatible" claim: unlike the loader suite above +// it does NOT mock lz4-napi, and it runs on every supported Node version +// (import-based, so no CJS-only interception → no ESM skip). +describe('lz4 frame codec (real lz4-napi)', function () { + // The Databricks server sends LZ4 **frame** format (magic 0x184D2204). This + // golden frame was produced by the reference `lz4` CLI (an implementation + // independent of lz4-napi) from the known plaintext below — so decoding it + // proves the driver reads frames it did not itself produce, not merely that + // its own encode/decode round-trip agrees. + const goldenPlaintext = Buffer.from( + 'Databricks LZ4 frame golden test — the quick brown fox jumps over the lazy dog 0123456789', + ); + const goldenFrame = Buffer.from([ + 4, 34, 77, 24, 100, 64, 167, 91, 0, 0, 128, 68, 97, 116, 97, 98, 114, 105, 99, 107, 115, 32, 76, 90, 52, 32, 102, + 114, 97, 109, 101, 32, 103, 111, 108, 100, 101, 110, 32, 116, 101, 115, 116, 32, 226, 128, 148, 32, 116, 104, 101, + 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, + 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 0, 0, 0, 0, 153, 39, 109, 21, + ]); + + // lib/utils/lz4.ts is deliberately optional: it returns undefined when the + // native binding can't load, so the driver degrades gracefully instead of + // crashing. lz4-napi ships prebuilds for the mainstream platforms but not + // every arch (e.g. linux-ppc64le/s390x), and `npm ci --omit=optional` also + // yields undefined. So these cases test the CODEC when it is present rather + // than asserting the environment has it — a missing binding skips (with a + // visible warning) instead of failing the whole unit run. CI's linux-x64 + // matrix always has a prebuild, so the frame-compat path is always covered + // there. + let codec: ReturnType; + + before(function () { + codec = getLZ4(); + if (!codec) { + // eslint-disable-next-line no-console + console.warn( + 'lz4-napi is not available on this platform — skipping real LZ4 frame codec tests. ' + + 'This is expected on platforms without an lz4-napi prebuild or with --omit=optional.', + ); + this.skip(); + } + }); - // Load the module but don't call getResolvedModule - loadLz4Module(); - // Note: we're NOT calling .default() here + it('decodes a golden LZ4 frame produced by an independent encoder', () => { + // Frame magic 0x184D2204, little-endian. + expect(goldenFrame.readUInt32LE(0)).to.equal(0x184d2204); - restore(); + const decoded = codec!.decode(goldenFrame); + expect(decoded.equals(goldenPlaintext)).to.be.true; + }); - expect(wasLz4LoadAttempted()).to.be.false; - expect(consoleWarnStub.called).to.be.false; + it('round-trips arbitrary binary payloads (encode then decode)', () => { + for (const payload of [ + Buffer.alloc(0), + Buffer.from('short'), + Buffer.from('a'.repeat(100000)), // larger than a single block + Buffer.from(Array.from({ length: 4096 }, (_, i) => i % 256)), // non-repetitive binary + ]) { + const encoded = codec!.encode(payload); + // Non-empty inputs must carry the LZ4 frame magic. + if (payload.length > 0) { + expect(encoded.readUInt32LE(0)).to.equal(0x184d2204); + } + expect(codec!.decode(encoded).equals(payload)).to.be.true; + } }); });