diff --git a/.github/workflows/build-js-bindings.yml b/.github/workflows/build-js-bindings.yml index ec10224..f852b26 100644 --- a/.github/workflows/build-js-bindings.yml +++ b/.github/workflows/build-js-bindings.yml @@ -1,6 +1,6 @@ # Build, pack and upload seekdb JS bindings for multiple platforms to S3 # -# Platforms: linux-x64, linux-arm64, darwin-arm64 (macOS x64 not supported) +# Platforms: linux-x64, linux-arm64, darwin-arm64, win32-x64 (macOS x64 not supported) # S3: s3://oceanbase-seekdb-builds/js-bindings/all_commits//seekdb-js-bindings-.zip # name: Build JS bindings @@ -138,6 +138,59 @@ jobs: name: seekdb-js-bindings-${{ matrix.platform }} path: seekdb-js-bindings-${{ matrix.platform }}.zip + # ---------- Build JS bindings on Windows ---------- + build-windows: + name: Build JS bindings (${{ matrix.platform }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - platform: win32-x64 + runner: windows-2022 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build bindings (Windows) + working-directory: packages/bindings + run: pnpm run build + + - name: Pack artifact (Windows) + working-directory: packages/bindings/pkgs/js-bindings + shell: pwsh + run: | + $root = (Resolve-Path ../../../..).Path + $zip = Join-Path $root "seekdb-js-bindings-${{ matrix.platform }}.zip" + if (Test-Path $zip) { Remove-Item $zip -Force } + Compress-Archive -Path * -DestinationPath $zip -Force + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: seekdb-js-bindings-${{ matrix.platform }} + path: seekdb-js-bindings-${{ matrix.platform }}.zip + # ---------- Collect artifacts and upload to S3 ---------- release-artifacts: name: Collect artifacts and upload to S3 @@ -145,6 +198,7 @@ jobs: needs: - build-linux - build-macos + - build-windows steps: - name: Download all artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 479778d..8da3473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,14 +97,13 @@ jobs: docker logs seekdb-server - name: Run server tests - working-directory: packages/seekdb env: SEEKDB_HOST: 127.0.0.1 SEEKDB_PORT: 2881 SEEKDB_USER: root SEEKDB_PASSWORD: "" SEEKDB_DATABASE: test - run: pnpm exec vitest run --exclude 'tests/embedded/**' + run: pnpm run test:server # Embedded-mode tests on multiple platforms (requires native bindings build per OS; Docker per job) test-embedded: @@ -121,6 +120,8 @@ jobs: runner: ubuntu-22.04-arm - platform: darwin-arm64 runner: macos-14 + - platform: windows-x64 + runner: windows-latest steps: - name: Checkout uses: actions/checkout@v4 @@ -149,7 +150,7 @@ jobs: - name: Build packages run: pnpm run build - # macOS has no Docker; skip container and exclude mode-consistency (needs seekdb server) + # Docker seekdb server is Linux-only; macOS/Windows skip container and exclude mode-consistency (needs server) - name: Start seekdb container if: runner.os == 'Linux' shell: bash @@ -159,7 +160,7 @@ jobs: docker logs seekdb-server - name: Run embedded tests - working-directory: packages/seekdb + shell: bash env: SEEKDB_HOST: 127.0.0.1 SEEKDB_PORT: 2881 @@ -168,9 +169,9 @@ jobs: SEEKDB_DATABASE: test run: | if [ "$RUNNER_OS" = "Linux" ]; then - pnpm exec vitest run tests/embedded/ 2>&1 | tee /tmp/vitest.log + pnpm run test:embedded 2>&1 | tee /tmp/vitest.log else - pnpm exec vitest run tests/embedded/ --exclude '**/mode-consistency.test.ts' 2>&1 | tee /tmp/vitest.log + pnpm run test:embedded -- --exclude '**/mode-consistency.test.ts' 2>&1 | tee /tmp/vitest.log fi exit_code=$? if [ $exit_code -ne 0 ]; then exit $exit_code; fi diff --git a/.gitignore b/.gitignore index 2647447..e376908 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ coverage/ examples/seekdb-prisma/generated/** spec/ .vscode +.codegraph \ No newline at end of file diff --git a/DEVELOP.md b/DEVELOP.md index 00cb5af..546111a 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -96,22 +96,24 @@ pnpm build The project uses Vitest. From the project root: ```bash -# Run all tests (seekdb + @seekdb/prisma-adapter; runs once and exits) +# Full suite: seekdb server tests + embedded tests + @seekdb/prisma-adapter (each runs once) pnpm test -# Run only seekdb package tests -pnpm --filter seekdb run test +# seekdb only — server-mode tests (paths outside tests/embedded/; need seekdb/OceanBase on SEEKDB_*) +pnpm run test:server + +# seekdb only — embedded-mode tests (under tests/embedded/; native addon required) +pnpm run test:embedded # Watch mode for seekdb (during development) pnpm --filter seekdb exec vitest # Run only @seekdb/prisma-adapter tests pnpm --filter @seekdb/prisma-adapter run test - -# Run only embedded-mode tests (no server required) -pnpm --filter seekdb exec vitest run tests/embedded/ ``` +From `packages/seekdb`, the same split is available as `pnpm run test:server` and `pnpm run test:embedded`. + **Tests and running mode**: - **Embedded-mode tests** live under `packages/seekdb/tests/embedded/` and use a temporary database path per test file. They do not require a seekdb/OceanBase server. diff --git a/README.md b/README.md index ce356bf..6784ef2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ For complete usage, see the official documentation. [Packages](#packages)
[Installation](#installation)
[Running Modes](#running-modes)
+[Embedded mode platform support](#embedded-mode-platform-support)
[Quick Start](#quick-start)
[Usage Guide](#usage-guide)
[Examples](#examples)
@@ -69,6 +70,19 @@ The SDK supports two modes; the constructor arguments to `SeekdbClient` determin - **SeekdbClient**: Pass `path` for embedded mode, or `host` (and port, user, password, etc.) for server mode. - **SeekdbAdminClient()**: For admin operations only; pass `path` for embedded or `host` for server. In embedded mode you do not specify a database name. +### Embedded mode platform support + +Embedded mode uses the native package **`@seekdb/js-bindings`**. Prebuilt binaries are supported on: + +| Platform key | OS / CPU | +| -------------- | --------------------------- | +| `linux-x64` | Linux x86_64 | +| `linux-arm64` | Linux ARM64 | +| `darwin-arm64` | macOS Apple Silicon (ARM64) | +| `win32-x64` | Windows x86_64 | + +Optional environment variables for the loader: `SEEKDB_BINDINGS_BASE_URL` (artifact directory URL), `SEEKDB_BINDINGS_CACHE_DIR` (download cache). See [`packages/bindings/README.md`](./packages/bindings/README.md). + ## Quick Start **Embedded mode** (local file, no server): diff --git a/package.json b/package.json index 80ae822..2b25867 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "build": "pnpm --filter seekdb run build && pnpm --filter '@seekdb/*' run build", "build:seekdb": "pnpm --filter seekdb run build", "build:embeddings": "pnpm --filter '@seekdb/*' run build", - "test": "pnpm --filter seekdb run test && pnpm --filter @seekdb/prisma-adapter run test", + "bindings:clean": "pnpm --filter @seekdb/js-bindings-build run clean", + "test": "pnpm run test:server && pnpm run test:embedded && pnpm --filter @seekdb/prisma-adapter run test", + "test:server": "pnpm --filter seekdb run test:server", + "test:embedded": "pnpm --filter seekdb run test:embedded", "lint": "pnpm -r run lint", "type-check": "pnpm -r run type-check", "prettier": "prettier --write .", diff --git a/packages/bindings/README.md b/packages/bindings/README.md index cc4721b..27b67a0 100644 --- a/packages/bindings/README.md +++ b/packages/bindings/README.md @@ -13,7 +13,7 @@ The native addon is structured in three layers: 2. **JavaScript Wrapper** (`pkgs/js-bindings/seekdb.js`) - Loads native `.node` from same dir (npm package / local build) or on-demand download (Node fetch + adm-zip) - - Supports Linux (x64/arm64) and macOS (arm64 only). **Native bindings are not on npm**; built by CI and hosted on S3. + - Supports Linux (x64/arm64), macOS (arm64), and Windows (x64). **Native bindings are not on npm**; built by CI and hosted on S3. 3. **TypeScript API Layer** (`../seekdb/src/client-embedded.ts`) - High-level TypeScript API @@ -22,7 +22,7 @@ The native addon is structured in three layers: ## Distribution (S3, not npm) -Native bindings are **not** published to npm. They are built by [`.github/workflows/build-js-bindings.yml`](../../.github/workflows/build-js-bindings.yml) and uploaded to S3. Each set of artifacts lives in a directory that contains `seekdb-js-bindings-.zip` for each platform (e.g. linux-x64, linux-arm64, darwin-arm64). +Native bindings are **not** published to npm. They are built by [`.github/workflows/build-js-bindings.yml`](../../.github/workflows/build-js-bindings.yml) and uploaded to S3. Each set of artifacts lives in a directory that contains `seekdb-js-bindings-.zip` for each platform (e.g. linux-x64, linux-arm64, darwin-arm64, win32-x64). **Usage**: When embedded mode is first used, the loader uses same-dir `seekdb.node` (npm package or local build) or downloads bindings on demand. Optional env: @@ -45,7 +45,7 @@ This will: 1. Fetch the libseekdb library for your platform (Python scripts invoked by `binding.gyp`) 2. If the archive contains a `libs/` directory, copy it to `pkgs/js-bindings/libs/` (e.g. macOS runtime deps) -3. Compile the C++ bindings with node-gyp and copy `seekdb.node` and `libseekdb.so`/`libseekdb.dylib` into `pkgs/js-bindings/` +3. Compile the C++ bindings with node-gyp and copy `seekdb.node` and `libseekdb.so` / `libseekdb.dylib` / `seekdb.dll` (as appropriate for the platform) into `pkgs/js-bindings/` ## Platform Support @@ -54,12 +54,13 @@ The bindings support the following platforms: - Linux x64 - Linux arm64 - macOS arm64 (Apple Silicon) +- Windows x64 -Note: macOS x64 and Windows are not currently supported. +Note: macOS x64 (Intel) is not currently supported in CI; local builds may still be possible. ## C API Integration -The bindings use the seekdb C API (see `seekdb.h` in `libseekdb/` after fetch) and link against `libseekdb.so` / `libseekdb.dylib`. The native library is downloaded and extracted by platform-specific Python scripts in `scripts/` (invoked from `binding.gyp`); see `scripts/README.md` for details. +The bindings use the seekdb C API (see `seekdb.h` in `libseekdb/` after fetch) and link against `libseekdb.so`, `libseekdb.dylib`, or `seekdb.dll` / import lib on Windows. The native library is downloaded and extracted by platform-specific Python scripts in `scripts/` (invoked from `binding.gyp`); see `scripts/README.md` for details. ### Current Implementation diff --git a/packages/bindings/binding.gyp b/packages/bindings/binding.gyp index 31e2d36..511be82 100644 --- a/packages/bindings/binding.gyp +++ b/packages/bindings/binding.gyp @@ -7,8 +7,8 @@ ['OS=="linux" and target_arch=="x64"', { 'actions': [{ 'action_name': 'run_fetch_libseekdb_script', - 'message': 'Fetching and extracting libseekdb', - 'inputs': [], + 'message': 'Ensuring libseekdb is present', + 'inputs': ['<(module_root_dir)/libseekdb/libseekdb.so'], 'action': ['sh', '-c', 'cd "<(module_root_dir)" && python3 scripts/fetch_libseekdb_linux_x64.py'], 'outputs': ['<(module_root_dir)/libseekdb/libseekdb.so'], }], @@ -16,8 +16,8 @@ ['OS=="linux" and target_arch=="arm64"', { 'actions': [{ 'action_name': 'run_fetch_libseekdb_script', - 'message': 'Fetching and extracting libseekdb', - 'inputs': [], + 'message': 'Ensuring libseekdb is present', + 'inputs': ['<(module_root_dir)/libseekdb/libseekdb.so'], 'action': ['sh', '-c', 'cd "<(module_root_dir)" && python3 scripts/fetch_libseekdb_linux_arm64.py'], 'outputs': ['<(module_root_dir)/libseekdb/libseekdb.so'], }], @@ -25,12 +25,21 @@ ['OS=="mac" and target_arch=="arm64"', { 'actions': [{ 'action_name': 'run_fetch_libseekdb_script', - 'message': 'Fetching and extracting libseekdb', - 'inputs': [], + 'message': 'Ensuring libseekdb is present', + 'inputs': ['<(module_root_dir)/libseekdb/libseekdb.dylib'], 'action': ['sh', '-c', 'cd "<(module_root_dir)" && python3 scripts/fetch_libseekdb_darwin_arm64.py'], 'outputs': ['<(module_root_dir)/libseekdb/libseekdb.dylib'], }], }], + ['OS=="win" and target_arch=="x64"', { + 'actions': [{ + 'action_name': 'run_fetch_libseekdb_script', + 'message': 'Ensuring libseekdb is present', + 'inputs': ['<(module_root_dir)/libseekdb/seekdb.dll'], + 'action': ['python3', '<(module_root_dir)/scripts/fetch_libseekdb_windows_x64.py'], + 'outputs': ['<(module_root_dir)/libseekdb/seekdb.dll'], + }], + }], ], }, { @@ -91,6 +100,20 @@ }, ], }], + ['OS=="win" and target_arch=="x64"', { + 'win_delay_load_hook': 'true', + 'link_settings': { + 'libraries': [ + '<(module_root_dir)/libseekdb/seekdb.lib', + ], + }, + 'copies': [ + { + 'files': ['<(module_root_dir)/libseekdb/seekdb.dll'], + 'destination': '<(module_root_dir)/pkgs/js-bindings', + }, + ], + }], ], }, { @@ -122,6 +145,14 @@ }, ], }], + ['OS=="win" and target_arch=="x64"', { + 'copies': [ + { + 'files': ['<(module_root_dir)/build/Release/seekdb.node'], + 'destination': '<(module_root_dir)/pkgs/js-bindings', + }, + ], + }], ], }, ], diff --git a/packages/bindings/package.json b/packages/bindings/package.json index ad04da8..a7f4738 100644 --- a/packages/bindings/package.json +++ b/packages/bindings/package.json @@ -11,7 +11,7 @@ "clean": "npm run clean:gyp && npm run clean:libseekdb && npm run clean:package", "clean:gyp": "node-gyp clean", "clean:libseekdb": "rimraf libseekdb", - "clean:package": "rimraf pkgs/js-bindings*/**/*.node pkgs/js-bindings*/**/*.so pkgs/js-bindings*/**/*.dylib pkgs/js-bindings*/**/*.dll", + "clean:package": "rimraf pkgs/js-bindings*/libs pkgs/js-bindings*/*.node pkgs/js-bindings*/*.so pkgs/js-bindings*/*.dylib pkgs/js-bindings*/*.dll pkgs/js-bindings*/**/*.node pkgs/js-bindings*/**/*.so pkgs/js-bindings*/**/*.dylib pkgs/js-bindings*/**/*.dll", "test": "echo \"Tests not yet implemented\"" }, "dependencies": { diff --git a/packages/bindings/pkgs/js-bindings/download.js b/packages/bindings/pkgs/js-bindings/download.js index d7f607a..6c8a451 100644 --- a/packages/bindings/pkgs/js-bindings/download.js +++ b/packages/bindings/pkgs/js-bindings/download.js @@ -6,9 +6,14 @@ const fs = require("fs"); const os = require("os"); const AdmZip = require("adm-zip"); -const SUPPORTED_PLATFORMS = ["darwin-arm64", "linux-x64", "linux-arm64"]; +const SUPPORTED_PLATFORMS = [ + "darwin-arm64", + "linux-x64", + "linux-arm64", + "win32-x64", +]; const DEFAULT_BASE_URL = - "https://oceanbase-seekdb-builds.s3.ap-southeast-1.amazonaws.com/js-bindings/all_commits/7548fd4ac9bb9d8a06621dfb1ade3924a95145d6"; + "https://oceanbase-seekdb-builds.s3.ap-southeast-1.amazonaws.com/js-bindings/all_commits/491beee0a7e67ba004502a83ea2da484f5fd9cdc"; function getPlatformArch() { const key = `${process.platform}-${process.arch === "arm64" ? "arm64" : "x64"}`; diff --git a/packages/bindings/scripts/README.md b/packages/bindings/scripts/README.md index d5a494f..0c39c5b 100644 --- a/packages/bindings/scripts/README.md +++ b/packages/bindings/scripts/README.md @@ -32,8 +32,9 @@ These scripts download libseekdb files for specific platforms. They are automati - `fetch_libseekdb_linux_x64.py` - Linux x64 - `fetch_libseekdb_linux_arm64.py` - Linux arm64 - `fetch_libseekdb_darwin_arm64.py` - macOS arm64 (Apple Silicon) +- `fetch_libseekdb_windows_x64.py` - Windows x64 -Note: Windows and macOS x64 (Intel Silicon) is not currently supported. +Note: macOS x64 (Intel) fetch/build is not wired in CI; Windows x64 is built in `build-js-bindings.yml`. **Manual usage (if needed):** diff --git a/packages/bindings/scripts/fetch_libseekdb.py b/packages/bindings/scripts/fetch_libseekdb.py index 8d2a540..269f039 100644 --- a/packages/bindings/scripts/fetch_libseekdb.py +++ b/packages/bindings/scripts/fetch_libseekdb.py @@ -36,7 +36,8 @@ def _ensure_output_dir_valid(output_dir): return has_lib = ( os.path.isfile(os.path.join(output_dir, "libseekdb.dylib")) or - os.path.isfile(os.path.join(output_dir, "libseekdb.so"))) + os.path.isfile(os.path.join(output_dir, "libseekdb.so")) or + os.path.isfile(os.path.join(output_dir, "seekdb.dll"))) if not has_lib: shutil.rmtree(output_dir) @@ -45,8 +46,13 @@ def fetch_libseekdb(zip_url, output_dir, local_zip_name): """ Download zip from zip_url and extract all contents into output_dir. local_zip_name: filename for the local zip (e.g. libseekdb-darwin-arm64.zip). + Skips download when the native library is already present (node-gyp / pnpm install). """ _ensure_output_dir_valid(output_dir) + if not _need_fetch(output_dir): + print("libseekdb native library already present in " + output_dir + ", skipping fetch") + copy_libs_to_package(os.path.dirname(output_dir)) + return if not os.path.exists(output_dir): os.makedirs(output_dir) @@ -114,7 +120,8 @@ def _need_fetch(output_dir): return True has_lib = ( os.path.isfile(os.path.join(output_dir, "libseekdb.dylib")) or - os.path.isfile(os.path.join(output_dir, "libseekdb.so"))) + os.path.isfile(os.path.join(output_dir, "libseekdb.so")) or + os.path.isfile(os.path.join(output_dir, "seekdb.dll"))) return not has_lib @@ -135,6 +142,8 @@ def fetch_if_empty(module_root): arch = "arm64" if machine in ("arm64", "aarch64") else "x64" if system == "darwin": zip_name = "libseekdb-darwin-arm64.zip" if arch == "arm64" else "libseekdb-darwin-x64.zip" + elif system == "windows" or sys.platform == "win32": + zip_name = "libseekdb-windows-%s.zip" % arch else: zip_name = "libseekdb-linux-%s.zip" % arch from libseekdb_url_config import get_zip_url diff --git a/packages/bindings/scripts/fetch_libseekdb_windows_x64.py b/packages/bindings/scripts/fetch_libseekdb_windows_x64.py new file mode 100644 index 0000000..268f67b --- /dev/null +++ b/packages/bindings/scripts/fetch_libseekdb_windows_x64.py @@ -0,0 +1,10 @@ +import os + +from fetch_libseekdb import fetch_libseekdb +from libseekdb_url_config import get_zip_url + +ZIP_NAME = "libseekdb-windows-x64.zip" +zip_url = get_zip_url(ZIP_NAME) +output_dir = os.path.join(os.path.dirname(__file__), "..", "libseekdb") + +fetch_libseekdb(zip_url, output_dir, "libseekdb.zip") diff --git a/packages/bindings/scripts/libseekdb_url_config.py b/packages/bindings/scripts/libseekdb_url_config.py index 9c0b17b..86a0985 100644 --- a/packages/bindings/scripts/libseekdb_url_config.py +++ b/packages/bindings/scripts/libseekdb_url_config.py @@ -1,6 +1,6 @@ # libseekdb zip download URL config -LIBSEEKDB_URL_PREFIX = "https://oceanbase-seekdb-builds.s3.ap-southeast-1.amazonaws.com/libseekdb/all_commits/c1a508a4efed701b88d369c7bdcf2aa2ea3480bd/" +LIBSEEKDB_URL_PREFIX = "https://oceanbase-seekdb-builds.s3.ap-southeast-1.amazonaws.com/libseekdb/all_commits/101093eafaccca19fdb063b6d2cfae57d8d26d95/" # LIBSEEKDB_URL_PREFIX = "https://github.com/oceanbase/seekdb/releases/download/v1.1.0/" diff --git a/packages/bindings/src/seekdb_js_bindings.cpp b/packages/bindings/src/seekdb_js_bindings.cpp index 7bc8937..7f74b21 100644 --- a/packages/bindings/src/seekdb_js_bindings.cpp +++ b/packages/bindings/src/seekdb_js_bindings.cpp @@ -620,7 +620,7 @@ class ExecuteWorker : public Napi::AsyncWorker { size_t str_len = seekdb_row_get_string_len(row, j); const size_t max_safe_len = 10 * 1024 * 1024; // 10MB cap to avoid OOM const size_t fallback_buf_size = 2 * 1024 * 1024; // 2MB when length unknown - if (str_len != static_cast(-1) && str_len <= max_safe_len) { + if (str_len != static_cast(-1) && str_len > 0 && str_len <= max_safe_len) { std::vector buf(str_len + 1, 0); int ret = seekdb_row_get_string(row, j, buf.data(), buf.size()); if (ret == SEEKDB_SUCCESS) { @@ -628,8 +628,8 @@ class ExecuteWorker : public Napi::AsyncWorker { } else { row_obj.Set(j, env.Null()); } - } else if (str_len == static_cast(-1)) { - // Length unknown (e.g. long TEXT/BLOB): try large buffer so long document/metadata not truncated + } else if (str_len == 0 || str_len == static_cast(-1)) { + // C ABI may report len 0/-1 for out-of-row TEXT; try large buffer before giving up. std::vector buf(fallback_buf_size, 0); int ret = seekdb_row_get_string(row, j, buf.data(), buf.size()); if (ret == SEEKDB_SUCCESS) { diff --git a/packages/seekdb/package.json b/packages/seekdb/package.json index 2b3d828..ae45093 100644 --- a/packages/seekdb/package.json +++ b/packages/seekdb/package.json @@ -23,6 +23,8 @@ "build:bindings": "cd ../bindings && node-gyp configure && node-gyp build", "dev": "tsup --watch", "test": "vitest", + "test:server": "vitest run --exclude \"tests/embedded/**\"", + "test:embedded": "bash -c 'set -o pipefail; vitest run tests/embedded 2>&1 | grep -v alloc_log_item'", "type-check": "tsc --noEmit", "prettier": "prettier --write .", "docs": "typedoc --entryPoints src/index.ts --out docs/v2" diff --git a/packages/seekdb/tests/README.md b/packages/seekdb/tests/README.md index cb0bc1f..8a88ceb 100644 --- a/packages/seekdb/tests/README.md +++ b/packages/seekdb/tests/README.md @@ -47,19 +47,20 @@ tests/ ## Running tests ```bash -# All tests (from repo root) +# Full monorepo test script (from repo root): server + embedded + prisma-adapter pnpm test -# From packages/seekdb -pnpm exec vitest run +# seekdb — server-mode vs embedded (from repo root or from packages/seekdb) +pnpm run test:server # excludes tests/embedded/** +pnpm run test:embedded # only tests/embedded/ -# Specific area -pnpm exec vitest run tests/collection/ +# Watch / ad-hoc (from packages/seekdb) +pnpm exec vitest -# Embedded only (requires native addon) -pnpm exec vitest run tests/embedded/ +# Specific area (from packages/seekdb) +pnpm exec vitest run tests/collection/ -# Unit tests only (fastest) +# Unit tests only (fastest; from packages/seekdb) pnpm exec vitest run tests/unit/ ``` diff --git a/packages/seekdb/tests/embedded/collection/collection-query.test.ts b/packages/seekdb/tests/embedded/collection/collection-query.test.ts index 0ea1ed3..7227244 100644 --- a/packages/seekdb/tests/embedded/collection/collection-query.test.ts +++ b/packages/seekdb/tests/embedded/collection/collection-query.test.ts @@ -122,6 +122,7 @@ describe("Embedded Mode - Collection Query Operations", () => { { category: "AI", score: 85, tag: "neural" }, ], }); + await collection.refresh_index(); }); afterAll(async () => { @@ -390,6 +391,7 @@ describe("Embedded Mode - Collection Query Operations", () => { ids: ["ef1", "ef2"], documents: ["test document 1", "test document 2"], }); + await collectionWithEF.refresh_index(); const results = await collectionWithEF.query({ queryTexts: "test document", diff --git a/packages/seekdb/tests/embedded/collection/complex-queries.test.ts b/packages/seekdb/tests/embedded/collection/complex-queries.test.ts index ef806a1..9cd10d1 100644 --- a/packages/seekdb/tests/embedded/collection/complex-queries.test.ts +++ b/packages/seekdb/tests/embedded/collection/complex-queries.test.ts @@ -50,6 +50,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { { nested: { key: "value1" }, score: 95 }, ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -82,6 +83,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { { category: "A", score: 95 }, ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -116,6 +118,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { { tags: ["tag1", "tag3"] }, ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -156,6 +159,8 @@ describe("Embedded Mode - Complex Query Scenarios", () => { await l2Collection.add(testData); await cosineCollection.add(testData); + await l2Collection.refresh_index(); + await cosineCollection.refresh_index(); const queryVector = [[1, 0, 0]]; @@ -194,6 +199,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { documents: ["test"], metadatas: [{ key: "value" }], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -222,6 +228,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { documents: ["test"], metadatas: [{ key: "value" }], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -254,6 +261,7 @@ describe("Embedded Mode - Complex Query Scenarios", () => { [7, 8, 9], ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [ diff --git a/packages/seekdb/tests/embedded/collection/query-approximate.test.ts b/packages/seekdb/tests/embedded/collection/query-approximate.test.ts index c45635d..93069e1 100644 --- a/packages/seekdb/tests/embedded/collection/query-approximate.test.ts +++ b/packages/seekdb/tests/embedded/collection/query-approximate.test.ts @@ -44,6 +44,7 @@ describe("Embedded Mode - Query Approximate Parameter", () => { [7, 8, 9], ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], @@ -103,6 +104,7 @@ describe("Embedded Mode - Query Approximate Parameter", () => { [4, 5, 6], ], }); + await collection.refresh_index(); const results = await collection.query({ queryEmbeddings: [[1, 2, 3]], diff --git a/packages/seekdb/tests/embedded/edge-cases/edge-cases-and-errors.test.ts b/packages/seekdb/tests/embedded/edge-cases/edge-cases-and-errors.test.ts index 1358242..38e9b1f 100644 --- a/packages/seekdb/tests/embedded/edge-cases/edge-cases-and-errors.test.ts +++ b/packages/seekdb/tests/embedded/edge-cases/edge-cases-and-errors.test.ts @@ -275,7 +275,6 @@ describe("Embedded Mode - Edge Cases and Error Handling", () => { await client.deleteCollection(collectionName); }); - // C ABI: metadata with newlines/quotes may be truncated or corrupted, or C layer may throw "Invalid JSON text". test("handles special characters in metadata", async () => { const collectionName = generateCollectionName("test_special_chars"); const collection = await client.createCollection({ @@ -295,25 +294,18 @@ describe("Embedded Mode - Edge Cases and Error Handling", () => { "key\nwith\nnewlines": "value", }; - try { - await collection.add({ - ids: ["id_special"], - embeddings: [[1, 2, 3]], - metadatas: [specialMetadata], - }); - const results = await collection.get({ ids: ["id_special"] }); - expect(results.metadatas).toBeDefined(); - expect(results.metadatas![0]).toEqual(specialMetadata); - } catch (e: any) { - // Embedded: C/engine may throw "Invalid JSON text" when metadata contains special chars; accept as known limitation. - const msg = String(e?.message ?? e ?? "").toLowerCase(); - expect(msg).toMatch(/invalid json|json text/); - } finally { - await client.deleteCollection(collectionName).catch(() => {}); - } + await collection.add({ + ids: ["id_special"], + embeddings: [[1, 2, 3]], + metadatas: [specialMetadata], + }); + + const results = await collection.get({ ids: ["id_special"] }); + expect(results.metadatas![0]).toEqual(specialMetadata); + + await client.deleteCollection(collectionName); }); - // Embedded: 100KB supported via STRING→MEDIUMTEXT; session ob_default_lob_inrow_threshold set on connect so LOB in-row; C ABI read_lob_data for out-of-row. test("handles very long document", async () => { const collectionName = generateCollectionName("test_long_doc"); const collection = await client.createCollection({ @@ -330,9 +322,8 @@ describe("Embedded Mode - Edge Cases and Error Handling", () => { }); const results = await collection.get({ ids: ["id_long"] }); - expect(results.documents).toBeDefined(); expect(results.documents![0]).toBe(longDoc); - expect((results.documents![0] as string).length).toBe(100000); + expect(results.documents![0].length).toBe(100000); await client.deleteCollection(collectionName); }); diff --git a/packages/seekdb/tests/embedded/embedding/collection-embedding-function.test.ts b/packages/seekdb/tests/embedded/embedding/collection-embedding-function.test.ts index 8e2761f..d0481c6 100644 --- a/packages/seekdb/tests/embedded/embedding/collection-embedding-function.test.ts +++ b/packages/seekdb/tests/embedded/embedding/collection-embedding-function.test.ts @@ -314,6 +314,7 @@ describe("Embedded Mode - Collection Embedding Function Tests", () => { ids: ["ef_q1", "ef_q2"], documents: ["Document about AI", "Document about Python"], }); + await collection.refresh_index(); const results = await collection.query({ queryTexts: "AI", diff --git a/packages/seekdb/tests/embedded/embedding/default-embedding-function.test.ts b/packages/seekdb/tests/embedded/embedding/default-embedding-function.test.ts index 6229fe8..a010576 100644 --- a/packages/seekdb/tests/embedded/embedding/default-embedding-function.test.ts +++ b/packages/seekdb/tests/embedded/embedding/default-embedding-function.test.ts @@ -55,6 +55,7 @@ describe("Embedded Mode - Default Embedding Function Tests", () => { documents: testDocuments, metadatas: testMetadatas, }); + await collection.refresh_index(); // Test query with queryTexts (using the default embedding function) const results = await collection.query({ diff --git a/packages/seekdb/tests/embedded/examples/official-example.test.ts b/packages/seekdb/tests/embedded/examples/official-example.test.ts index 2d37e99..aaea87e 100644 --- a/packages/seekdb/tests/embedded/examples/official-example.test.ts +++ b/packages/seekdb/tests/embedded/examples/official-example.test.ts @@ -77,6 +77,7 @@ describe("Embedded Mode - Official Example", () => { documents: PRODUCT_DOCUMENTS, metadatas: PRODUCT_METADATA, }); + await collection.refresh_index(); // Step 3: Query with queryTexts const queryResults = await collection.query({ diff --git a/packages/seekdb/tests/embedded/mode-consistency.test.ts b/packages/seekdb/tests/embedded/mode-consistency.test.ts index 672674c..4004fe5 100644 --- a/packages/seekdb/tests/embedded/mode-consistency.test.ts +++ b/packages/seekdb/tests/embedded/mode-consistency.test.ts @@ -285,6 +285,7 @@ describe("Mode Consistency Tests", () => { ], documents: ["doc1", "doc2"], }); + await embeddedCollection.refresh_index(); // Query both modes const serverResults = await serverCollection.query({