mirror of
https://github.com/lancedb/lancedb.git
synced 2026-09-01 11:08:55 +00:00
a87cada90e
The bindings are built, installed and published with pnpm everywhere,
but a parallel npm dependency graph was still being maintained beside
it. This removes it, raises the supported Node floor to the versions we
actually test, and gives Dependabot the npm coverage it was missing.
## Dropping npm
`nodejs/package-lock.json` was regenerated by `ci/update_lockfiles.sh`
on every release commit and read by nothing — no workflow runs `npm ci`
or `npm install` in `nodejs/`, and npm never publishes a lockfile in a
package tarball. It could not even agree with the real install, since
npm does not see pnpm's `overrides`. Because GitHub's dependency graph
parses `package-lock.json`, it was also reporting vulnerabilities for a
tree we neither install nor ship.
`docs/package.json`, `docs/package-lock.json` and `docs/tsconfig.json`
go too. They depend on `file:../node` and
`file:../node/node_modules/apache-arrow` — the `node/` directory was
removed long ago — the tsconfig compiles `src/*.ts` where no TypeScript
files exist, and nothing installs any of it. `docs.yml` only referenced
the lockfile to configure an npm cache for an install it never ran.
Two `workflow_dispatch` workflows for regenerating those lockfiles are
removed as well. Both were already broken: they `uses:` composite
actions at `.github/workflows/update_package_lock{,_nodejs}` that do not
exist, so dispatching either failed immediately.
The remaining `npx` calls become direct `node_modules/.bin/...`
invocations. These were already running locally installed binaries
rather than resolving anything, but naming the binary removes the npm
CLI from the loop and does not depend on which Node version is active.
`dev.yml`'s commitlint check was the last place doing real npm
dependency resolution — an unpinned `npm install
@commitlint/config-conventional` that also bypassed the
`minimumReleaseAge` hold configured for `nodejs/` — and is now a pinned
`pnpm dlx`.
## Node support
Node 18 and 20 both reached end-of-life, in April 2025 and April 2026.
The matrix moves to 22, 24 and 26, and `engines` rises from `>= 18` to
`>= 22` so the declared floor is one the matrix actually covers. Node 22
is LTS until April 2027; 24 is LTS; 26 is Current and becomes LTS in
October 2026.
This also removes the reason the workflows reached for `npx` in the
first place: pnpm 11 requires Node >= 22.13, which every matrix version
now satisfies.
The prebuilt-binary smoke test in `npm-publish.yml` moves from Node 20
to Node 22 — the floor, where a napi ABI problem would surface first —
rather than fanning out across all three, to keep the publish matrix
from tripling.
## Dependabot
There were no npm-ecosystem entries at all, which is why the advisories
behind #4073 went unnoticed. Both pnpm lockfiles are now watched —
`nodejs/` and `nodejs/examples/`, which is a separate install — using
the same `lockfile-only` strategy as the existing cargo and pip entries,
so version ranges in `package.json` are left alone.
## Pre-commit biome
The hook ran `npx @biomejs/biome@1.8.3` while `nodejs/package.json`
resolved 1.9.4. The two disagree about formatting, so the hook rejected
code that `pnpm lint` accepts, and failed on unmodified `main` for
anyone touching `nodejs/`. It now uses the pnpm-managed biome, which
fixes the drift with no source changes.
## Testing
`dev.yml`'s commitlint job does not check out the repo, so it runs in an
empty workspace, and I could not verify `pnpm/action-setup` there
locally. It triggers on `pull_request_target`, so this PR exercises it
directly — worth confirming green before merge. I did verify the `pnpm
dlx` invocation itself locally: it accepts a conventional title and
rejects a non-conventional one with exit 1.
Node 26 is new enough that the examples job may surface gaps in prebuilt
native binaries (`onnxruntime-node`, `sharp`) before their maintainers
publish for it.
## Not included
`nodejs/examples/` still pins `sharp: "0.33.5"` and has its own audit
findings. Raising the Node floor unblocks that work — sharp 0.35
requires Node >= 20.9, which the matrix now satisfies — but it is a
dependency bump rather than tooling cleanup, so it is left separate.
## Breaking changes
`@lancedb/lancedb` now requires Node >= 22; previously >= 18. The
`@types/node` peer range moves from `>=18` to `>=22` to match. Users on
Node 18 or 20 must upgrade their runtime; both have been end-of-life for
some time. Existing installs are unaffected, since `engines` is only
checked on install.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
158 lines
7.9 KiB
Markdown
158 lines
7.9 KiB
Markdown
LanceDB is a database designed for retrieval, including vector, full-text, and hybrid search.
|
|
It is a wrapper around Lance. There are two backends: local (in-process like SQLite) and
|
|
remote (against LanceDB Cloud).
|
|
|
|
The core of LanceDB is written in Rust. There are bindings in Python, Typescript, and Java.
|
|
|
|
Project layout:
|
|
|
|
* `rust/lancedb`: The LanceDB core Rust implementation.
|
|
* `python`: The Python bindings, using PyO3.
|
|
* `nodejs`: The Typescript bindings, using napi-rs
|
|
* `java`: The Java bindings
|
|
|
|
Common commands:
|
|
|
|
* Check for compiler errors: `cargo check --quiet --features remote --tests --examples`
|
|
* Run tests: `cargo test --quiet --features remote --tests`
|
|
* Run specific test: `cargo test --quiet --features remote -p <package_name> --test <test_name>`
|
|
* Lint: `cargo clippy --quiet --features remote --tests --examples`
|
|
* Format Rust: `cargo fmt --all`
|
|
* Use repository-defined Cargo profiles instead of ad hoc LTO overrides.
|
|
* Use `release-with-debug` for benchmarks and profiling so optimized builds keep debug symbols without a rebuild.
|
|
* Use `release-no-lto` only for local debugging, IO-bound benchmarks, or compile-time-sensitive performance investigation where LTO would not affect the measured bottleneck.
|
|
* Format Python: `ruff format .`
|
|
* Lint Python: `ruff check .`
|
|
* Bootstrap Python dev env: `cd python && uv run --extra tests --extra dev maturin develop --extras tests,dev`
|
|
* Run Python tests: `cd python && uv run --extra tests pytest python/tests -vv --durations=10 -m "not slow and not s3_test"`
|
|
* Run specific Python test: `cd python && uv run --extra tests pytest python/tests/<test_file>.py::<test_name> -q`
|
|
|
|
For Python validation, prefer the uv-managed environment declared by `python/uv.lock`.
|
|
Do not treat system `python`, global `pytest`, or missing editable-install errors as
|
|
final blockers; bootstrap or enter the uv environment instead. If `lancedb._lancedb`
|
|
is missing or stale, or if Rust/PyO3 binding code changed, rebuild the Python
|
|
extension with the bootstrap command above before running tests.
|
|
|
|
Before committing changes, run formatting for every language you touched. At minimum:
|
|
|
|
* Rust changes: run `cargo fmt --all`.
|
|
* Python changes: run `ruff format .` and `ruff check .` from the repository root,
|
|
and run targeted tests through `cd python && uv run ...`.
|
|
* TypeScript changes: run the relevant `pnpm` lint, format, build, and docs commands in `nodejs`.
|
|
|
|
Before creating a PR, the exact value passed to `gh pr create --title` must follow
|
|
Conventional Commits, such as `fix: support nested field paths in native index creation`
|
|
or `feat(python): add dataset multiprocessing support`. Do not use a plain natural
|
|
language summary like `Support nested field paths in native index creation` as the PR
|
|
title. The semantic-release check uses the PR title and body as the merge commit message,
|
|
so a non-conventional PR title will fail CI. After creating a PR, read the remote PR title
|
|
back and fix it immediately if it is not conventional.
|
|
|
|
## Coding tips
|
|
|
|
* When writing Rust doctests for things that require a connection or table reference,
|
|
write them as a function instead of a fully executable test. This allows type checking
|
|
to run but avoids needing a full test environment. For example:
|
|
```rust
|
|
/// ```
|
|
/// use lance_index::scalar::FullTextSearchQuery;
|
|
/// use lancedb::query::{QueryBase, ExecutableQuery};
|
|
///
|
|
/// # use lancedb::Table;
|
|
/// # async fn query(table: &Table) -> Result<(), Box<dyn std::error::Error>> {
|
|
/// let results = table.query()
|
|
/// .full_text_search(FullTextSearchQuery::new("hello world".into()))
|
|
/// .execute()
|
|
/// .await?;
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
```
|
|
|
|
## Example plan: adding a new method on Table
|
|
|
|
Adding a new method involves first adding it to the Rust core, then exposing it
|
|
in the Python and TypeScript bindings. There are both local and remote tables.
|
|
Remote tables are implemented via a HTTP API and require the `remote` cargo
|
|
feature flag to be enabled. Python has both sync and async methods.
|
|
|
|
Rust core changes:
|
|
|
|
1. Add method on `Table` struct in `rust/lancedb/src/table.rs` (calls `BaseTable` trait).
|
|
2. Add method to `BaseTable` trait in `rust/lancedb/src/table.rs`.
|
|
3. Implement new trait method on `NativeTable` in `rust/lancedb/src/table.rs`.
|
|
* Test with unit test in `rust/lancedb/src/table.rs`.
|
|
4. Implement new trait method on `RemoteTable` in `rust/lancedb/src/remote/table.rs`.
|
|
* Test with unit test in `rust/lancedb/src/remote/table.rs` against mocked endpoint.
|
|
|
|
Python bindings changes:
|
|
|
|
1. Add PyO3 method binding in `python/src/table.rs`. Run `make develop` to compile bindings.
|
|
2. Add types for PyO3 method in `python/python/lancedb/_lancedb.pyi`.
|
|
3. Add method to `AsyncTable` class in `python/python/lancedb/table.py`.
|
|
4. Add abstract method to `Table` abstract base class in `python/python/lancedb/table.py`.
|
|
5. Add concrete sync method to `LanceTable` class in `python/python/lancedb/table.py`.
|
|
* Should use `LOOP.run()` to call the corresponding `AsyncTable` method.
|
|
6. Add concrete sync method to `RemoteTable` class in `python/python/lancedb/remote/table.py`.
|
|
7. Add unit test in `python/tests/test_table.py`.
|
|
8. If you added a new public class or module-level function (not just a method on an
|
|
existing class), expose it in the API reference. See "Python API reference" below.
|
|
|
|
TypeScript bindings changes:
|
|
|
|
1. Add napi-rs method binding on `Table` in `nodejs/src/table.rs`.
|
|
2. Run `pnpm build` to generate TypeScript definitions.
|
|
3. Add typescript method on abstract class `Table` in `nodejs/src/table.ts`.
|
|
4. Add concrete method on `LocalTable` class in `nodejs/src/native_table.ts`.
|
|
* Note: despite the name, this class is also used for remote tables.
|
|
5. Add test in `nodejs/__test__/table.test.ts`.
|
|
6. Run `pnpm run docs` to generate TypeScript documentation.
|
|
|
|
## Python API reference
|
|
|
|
`docs/src/python/python.md` is the entire Python API reference. It is maintained by
|
|
hand, and anything not listed there is not rendered at all, so new public classes and
|
|
module-level functions have to be added explicitly. How depends on the module:
|
|
|
|
* `lancedb.index`, `lancedb.embeddings`, `lancedb.remote`, and `lancedb.rerankers` are
|
|
rendered by a single directive each, driven by the module's `__all__`. Add the new
|
|
name to `__all__` and it appears; forget, and it is silently omitted.
|
|
* Everything else (`lancedb`, `lancedb.table`, `lancedb.query`, `lancedb.db`, ...) is
|
|
listed symbol by symbol. Add a `::: lancedb.<module>.<Name>` line to the matching
|
|
section, and remember that the page separates synchronous and asynchronous APIs.
|
|
|
|
Deliberately undocumented: concrete implementations reached through an abstract base
|
|
(`LanceTable`, `LanceDBConnection`, `RemoteDBConnection`), query base classes already
|
|
covered by `inherited_members`, and internal helpers.
|
|
|
|
Cross-references in docstrings use mkdocstrings syntax, `[text][lancedb.table.Table]`.
|
|
Plain relative links such as `[Table](Table)` do not resolve. To check your work:
|
|
|
|
```shell
|
|
pip install -r docs/requirements.txt
|
|
cd docs && PYTHONPATH=. mkdocs build
|
|
```
|
|
|
|
The docs site only builds on pushes to `main`, so this is not covered by PR CI.
|
|
|
|
## Review Guidelines
|
|
|
|
Please consider the following when reviewing code contributions.
|
|
|
|
### Rust API design
|
|
* Design public APIs so they can be evolved easily in the future without breaking
|
|
changes. Often this means using builder patterns or options structs instead of
|
|
long argument lists.
|
|
* For public APIs, prefer inputs that use `Into<T>` or `AsRef<T>` traits to allow
|
|
more flexible inputs. For example, use `name: Into<String>` instead of `name: String`,
|
|
so we don't have to write `func("my_string".to_string())`.
|
|
|
|
### Testing
|
|
* Ensure all new public APIs have documentation and examples.
|
|
* Ensure that all bugfixes and features have corresponding tests. **We do not merge
|
|
code without tests.**
|
|
|
|
### Documentation
|
|
* New features must include updates to the rust documentation comments. Link to
|
|
relevant structs and methods to increase the value of documentation.
|