vectlite 0.1.3 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -45
- package/index.d.ts +26 -3
- package/index.js +100 -4
- package/native/Cargo.toml +3 -3
- package/native/vectlite-core/Cargo.toml +3 -3
- package/package.json +6 -5
- package/prebuilds/darwin-arm64/vectlite.node +0 -0
- package/prebuilds/darwin-x64/vectlite.node +0 -0
- package/prebuilds/linux-x64-gnu/vectlite.node +0 -0
- package/prebuilds/win32-x64-msvc/vectlite.node +0 -0
- package/scripts/collect-prebuilds.mjs +27 -0
- package/scripts/install-addon.mjs +53 -0
- package/scripts/stage-prebuilt.mjs +56 -0
package/README.md
CHANGED
|
@@ -1,75 +1,203 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vectlite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/vectlite)
|
|
4
|
+
[](https://www.npmjs.com/package/vectlite)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Embedded vector store for local-first AI applications.
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
- JavaScript wrapper and TypeScript declarations included
|
|
9
|
-
- local smoke test available in `bindings/node/tests`
|
|
10
|
-
- npm package can be published as a source-build package
|
|
11
|
-
- installing the npm package requires a working Rust toolchain on the target machine
|
|
9
|
+
**vectlite** is a single-file, zero-dependency vector database written in Rust with Node.js bindings. It gives you dense + sparse hybrid search, HNSW indexing, metadata filtering, transactions, and crash-safe persistence in a single `.vdb` file -- no server, no Docker, no network calls.
|
|
12
10
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
From the repository root:
|
|
11
|
+
## Installation
|
|
16
12
|
|
|
17
13
|
```bash
|
|
18
|
-
|
|
19
|
-
npm run build
|
|
14
|
+
npm install vectlite
|
|
20
15
|
```
|
|
21
16
|
|
|
22
|
-
|
|
17
|
+
Requires Node.js 18+. Pre-built binaries are available for macOS (x86_64, arm64), Linux (x86_64), and Windows (x86_64). Other platforms fall back to compiling from source (requires Rust/Cargo).
|
|
23
18
|
|
|
24
|
-
##
|
|
19
|
+
## Quick Start
|
|
25
20
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
```js
|
|
22
|
+
const vectlite = require('vectlite')
|
|
23
|
+
|
|
24
|
+
// Create or open a database
|
|
25
|
+
const db = vectlite.open('knowledge.vdb', { dimension: 384 })
|
|
26
|
+
|
|
27
|
+
// Insert records with vectors, metadata, and sparse terms
|
|
28
|
+
db.upsert('doc1', embedding, { source: 'blog', title: 'Auth Guide' })
|
|
29
|
+
db.upsert('doc2', embedding2, { source: 'notes', title: 'Billing' })
|
|
30
|
+
|
|
31
|
+
// Search with filters
|
|
32
|
+
const results = db.search(embeddingQuery, { k: 5, filter: { source: 'blog' } })
|
|
33
|
+
|
|
34
|
+
// Clean up
|
|
35
|
+
db.compact()
|
|
29
36
|
```
|
|
30
37
|
|
|
31
|
-
##
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
### Core
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
- **Single-file storage** -- one `.vdb` file per database, portable and easy to back up
|
|
43
|
+
- **Dense vectors** -- cosine similarity with automatic HNSW indexing for large collections
|
|
44
|
+
- **Sparse vectors** -- BM25-scored inverted index for keyword retrieval
|
|
45
|
+
- **Hybrid search** -- dense + sparse fusion with linear or RRF strategies
|
|
46
|
+
- **Rich metadata** -- string, number, boolean, null, array, and nested object values
|
|
47
|
+
- **Crash-safe WAL** -- writes land in a write-ahead log first, then checkpoint with `compact()`
|
|
48
|
+
- **Transactions** -- atomic batched writes with `db.transaction()`
|
|
49
|
+
- **File locking** -- advisory locks prevent corruption from concurrent access
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
- `install` compiles the addon with Cargo on the target machine
|
|
37
|
-
- the published tarball does not ship prebuilt binaries yet
|
|
51
|
+
### Search & Retrieval
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
- **Metadata filters** -- MongoDB-style operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$contains`, `$exists`, `$and`, `$or`, `$not`
|
|
54
|
+
- **Nested filters** -- dot-path traversal (`author.name`), `$elemMatch`, `$size` on arrays and objects
|
|
55
|
+
- **Named vectors** -- multiple vector spaces per record (`vectors: { title: [...], body: [...] }`)
|
|
56
|
+
- **Multi-vector queries** -- weighted search across vector spaces in a single call
|
|
57
|
+
- **MMR diversification** -- `mmrLambda` controls relevance vs. diversity trade-off
|
|
58
|
+
- **Namespaces** -- logical isolation with per-namespace or cross-namespace search
|
|
59
|
+
- **Observability** -- `searchWithStats()` returns timings, BM25 term scores, ANN stats, and per-result explain payloads
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
61
|
+
### Data Management
|
|
62
|
+
|
|
63
|
+
- **Physical collections** -- `vectlite.openStore()` manages a directory of independent databases
|
|
64
|
+
- **Bulk ingestion** -- `bulkIngest()` with deferred index rebuilds for fast imports
|
|
65
|
+
- **Snapshots** -- `db.snapshot(path)` creates a self-contained copy
|
|
66
|
+
- **Backup / Restore** -- `db.backup(dir)` and `vectlite.restore(dir, path)` for full roundtrips
|
|
67
|
+
- **Read-only mode** -- `vectlite.open(path, { readOnly: true })` for safe concurrent readers
|
|
44
68
|
|
|
45
69
|
## Usage
|
|
46
70
|
|
|
71
|
+
### Hybrid Search
|
|
72
|
+
|
|
47
73
|
```js
|
|
48
|
-
const
|
|
74
|
+
const vectlite = require('vectlite')
|
|
49
75
|
|
|
50
|
-
const db = open('knowledge.vdb', { dimension: 384 })
|
|
51
|
-
db.upsert('doc1', embedding, { source: 'notes', title: 'Auth Guide' })
|
|
76
|
+
const db = vectlite.open('knowledge.vdb', { dimension: 384 })
|
|
52
77
|
|
|
78
|
+
// Upsert with dense + sparse vectors
|
|
79
|
+
db.upsert(
|
|
80
|
+
'doc1',
|
|
81
|
+
denseEmbedding,
|
|
82
|
+
{ source: 'docs', title: 'Auth Setup', text: 'How to configure SSO...' },
|
|
83
|
+
{ sparse: vectlite.sparseTerms('How to configure SSO authentication') },
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// Hybrid search
|
|
53
87
|
const results = db.search(queryEmbedding, {
|
|
54
|
-
k:
|
|
55
|
-
sparse: sparseTerms('
|
|
56
|
-
|
|
88
|
+
k: 10,
|
|
89
|
+
sparse: vectlite.sparseTerms('SSO authentication'),
|
|
90
|
+
fusion: 'rrf',
|
|
91
|
+
filter: { source: 'docs' },
|
|
92
|
+
explain: true,
|
|
57
93
|
})
|
|
94
|
+
|
|
95
|
+
for (const result of results) {
|
|
96
|
+
console.log(result.id, result.score)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Collections
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const store = vectlite.openStore('./my_collections')
|
|
104
|
+
const products = store.createCollection('products', 384)
|
|
105
|
+
products.upsert('p1', embedding, { name: 'Widget', price: 9.99 })
|
|
106
|
+
|
|
107
|
+
const logs = store.openOrCreateCollection('logs', 128)
|
|
108
|
+
console.log(store.collections()) // ["logs", "products"]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Transactions
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const tx = db.transaction()
|
|
115
|
+
try {
|
|
116
|
+
tx.upsert('doc1', emb1, { source: 'a' })
|
|
117
|
+
tx.upsert('doc2', emb2, { source: 'b' })
|
|
118
|
+
tx.delete('old_doc')
|
|
119
|
+
tx.commit() // All operations commit atomically
|
|
120
|
+
} catch (err) {
|
|
121
|
+
tx.rollback() // Roll back on error
|
|
122
|
+
throw err
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Text Helpers
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
async function run() {
|
|
130
|
+
// embedFn can be sync or async
|
|
131
|
+
await vectlite.upsertText(db, 'doc1', 'Auth setup guide', embedFn, { source: 'docs' })
|
|
132
|
+
const results = await vectlite.searchText(db, 'how to authenticate', embedFn, { k: 5 })
|
|
133
|
+
}
|
|
58
134
|
```
|
|
59
135
|
|
|
60
|
-
|
|
136
|
+
### Snapshots & Backup
|
|
61
137
|
|
|
62
|
-
|
|
138
|
+
```js
|
|
139
|
+
db.snapshot('/backups/knowledge_2024.vdb') // Self-contained copy
|
|
140
|
+
db.backup('/backups/full/') // Full backup with ANN sidecars
|
|
141
|
+
|
|
142
|
+
const restored = vectlite.restore('/backups/full/', 'restored.vdb')
|
|
143
|
+
```
|
|
63
144
|
|
|
64
|
-
-
|
|
65
|
-
- `insert`, `upsert`, `get`, `delete`
|
|
66
|
-
- batch writes and bulk ingest
|
|
67
|
-
- snapshots, backup, compact, flush
|
|
68
|
-
- namespaces and collections
|
|
69
|
-
- dense, sparse, and hybrid search
|
|
70
|
-
- search stats and text helpers
|
|
145
|
+
### Read-Only Mode
|
|
71
146
|
|
|
72
|
-
|
|
147
|
+
```js
|
|
148
|
+
const ro = vectlite.open('knowledge.vdb', { readOnly: true })
|
|
149
|
+
const results = ro.search(query, { k: 5 }) // Reads work
|
|
150
|
+
ro.upsert(...) // Throws VectLiteError
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Search Diagnostics
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const outcome = db.searchWithStats(query, {
|
|
157
|
+
k: 5,
|
|
158
|
+
sparse: terms,
|
|
159
|
+
explain: true,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
console.log(outcome.stats.timings) // { dense_us: 120, sparse_us: 45, ... }
|
|
163
|
+
console.log(outcome.stats.used_ann) // true
|
|
164
|
+
console.log(outcome.results[0].explain) // Detailed scoring breakdown
|
|
165
|
+
```
|
|
73
166
|
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
## Filter Operators
|
|
168
|
+
|
|
169
|
+
| Operator | Example | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `$eq` | `{ field: { $eq: 'value' } }` | Equal (also `{ field: 'value' }`) |
|
|
172
|
+
| `$ne` | `{ field: { $ne: 'value' } }` | Not equal |
|
|
173
|
+
| `$gt` / `$gte` | `{ field: { $gt: 5 } }` | Greater than (or equal) |
|
|
174
|
+
| `$lt` / `$lte` | `{ field: { $lt: 20 } }` | Less than (or equal) |
|
|
175
|
+
| `$in` / `$nin` | `{ field: { $in: ['a', 'b'] } }` | In / not in set |
|
|
176
|
+
| `$contains` | `{ field: { $contains: 'auth' } }` | Substring match |
|
|
177
|
+
| `$exists` | `{ field: { $exists: true } }` | Field presence |
|
|
178
|
+
| `$and` / `$or` | `{ $and: [{...}, {...}] }` | Logical combinators |
|
|
179
|
+
| `$not` | `{ $not: {...} }` | Logical negation |
|
|
180
|
+
| `$elemMatch` | `{ tags: { $elemMatch: { $eq: 'rust' } } }` | Match array elements |
|
|
181
|
+
| `$size` | `{ tags: { $size: 3 } }` | Array length |
|
|
182
|
+
| dot-path | `{ 'author.name': 'Alice' }` | Nested field access |
|
|
183
|
+
|
|
184
|
+
## How It Works
|
|
185
|
+
|
|
186
|
+
- Records are stored in a compact binary `.vdb` snapshot file
|
|
187
|
+
- Writes go through a crash-safe WAL (`.wal`) before being applied in memory
|
|
188
|
+
- `compact()` folds the WAL into the snapshot and persists HNSW sidecar files
|
|
189
|
+
- Dense search uses HNSW indexes (auto-built for collections above ~128 records)
|
|
190
|
+
- Sparse search uses an inverted index with BM25 scoring
|
|
191
|
+
- Hybrid fusion combines dense + sparse via linear combination or reciprocal rank fusion
|
|
192
|
+
- Advisory file locks (`flock`) prevent concurrent write corruption
|
|
193
|
+
|
|
194
|
+
## Links
|
|
195
|
+
|
|
196
|
+
- [Official Documentation](https://vectlite.mcsedition.org/)
|
|
197
|
+
- [GitHub Repository](https://github.com/mcsedition-hub/vectlite)
|
|
198
|
+
- [Issue Tracker](https://github.com/mcsedition-hub/vectlite/issues)
|
|
199
|
+
- [PyPI Package](https://pypi.org/project/vectlite/)
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
package/index.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export type Metadata = { [key: string]: MetadataValue }
|
|
|
10
10
|
export type SparseVector = { [term: string]: number }
|
|
11
11
|
export type NamedVectors = { [name: string]: number[] }
|
|
12
12
|
export type Filter = { [key: string]: unknown }
|
|
13
|
+
export type TextEmbedding = ArrayLike<number>
|
|
14
|
+
export type TextEmbeddingResult = TextEmbedding | Promise<TextEmbedding>
|
|
15
|
+
export type TextEmbedder = (text: string) => TextEmbeddingResult
|
|
13
16
|
|
|
14
17
|
export interface Record {
|
|
15
18
|
namespace: string
|
|
@@ -164,19 +167,39 @@ export function upsertText(
|
|
|
164
167
|
db: Database,
|
|
165
168
|
id: string,
|
|
166
169
|
text: string,
|
|
167
|
-
embed: (text: string) =>
|
|
170
|
+
embed: (text: string) => TextEmbedding,
|
|
168
171
|
metadata?: Metadata | null,
|
|
169
172
|
options?: WriteOptions,
|
|
170
173
|
): void
|
|
174
|
+
export function upsertText(
|
|
175
|
+
db: Database,
|
|
176
|
+
id: string,
|
|
177
|
+
text: string,
|
|
178
|
+
embed: (text: string) => Promise<TextEmbedding>,
|
|
179
|
+
metadata?: Metadata | null,
|
|
180
|
+
options?: WriteOptions,
|
|
181
|
+
): Promise<void>
|
|
171
182
|
export function searchText(
|
|
172
183
|
db: Database,
|
|
173
184
|
text: string,
|
|
174
|
-
embed: (text: string) =>
|
|
185
|
+
embed: (text: string) => TextEmbedding,
|
|
175
186
|
options?: SearchOptions,
|
|
176
187
|
): SearchResult[]
|
|
188
|
+
export function searchText(
|
|
189
|
+
db: Database,
|
|
190
|
+
text: string,
|
|
191
|
+
embed: (text: string) => Promise<TextEmbedding>,
|
|
192
|
+
options?: SearchOptions,
|
|
193
|
+
): Promise<SearchResult[]>
|
|
177
194
|
export function searchTextWithStats(
|
|
178
195
|
db: Database,
|
|
179
196
|
text: string,
|
|
180
|
-
embed: (text: string) =>
|
|
197
|
+
embed: (text: string) => TextEmbedding,
|
|
181
198
|
options?: SearchOptions,
|
|
182
199
|
): SearchResponse
|
|
200
|
+
export function searchTextWithStats(
|
|
201
|
+
db: Database,
|
|
202
|
+
text: string,
|
|
203
|
+
embed: (text: string) => Promise<TextEmbedding>,
|
|
204
|
+
options?: SearchOptions,
|
|
205
|
+
): Promise<SearchResponse>
|
package/index.js
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
function linuxLibc() {
|
|
7
|
+
if (process.platform !== 'linux') {
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const report = process.report?.getReport?.()
|
|
12
|
+
return report?.header?.glibcVersionRuntime ? 'gnu' : 'musl'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function runtimePrebuildTag() {
|
|
16
|
+
switch (process.platform) {
|
|
17
|
+
case 'darwin':
|
|
18
|
+
if (process.arch === 'x64') return 'darwin-x64'
|
|
19
|
+
if (process.arch === 'arm64') return 'darwin-arm64'
|
|
20
|
+
return null
|
|
21
|
+
case 'linux': {
|
|
22
|
+
const libc = linuxLibc()
|
|
23
|
+
if (process.arch === 'x64' && libc === 'gnu') return 'linux-x64-gnu'
|
|
24
|
+
if (process.arch === 'arm64' && libc === 'gnu') return 'linux-arm64-gnu'
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
case 'win32':
|
|
28
|
+
if (process.arch === 'x64') return 'win32-x64-msvc'
|
|
29
|
+
if (process.arch === 'arm64') return 'win32-arm64-msvc'
|
|
30
|
+
return null
|
|
31
|
+
default:
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function loadNative() {
|
|
37
|
+
const candidates = []
|
|
38
|
+
const prebuildTag = runtimePrebuildTag()
|
|
39
|
+
if (prebuildTag != null) {
|
|
40
|
+
candidates.push(path.join(__dirname, 'prebuilds', prebuildTag, 'vectlite.node'))
|
|
41
|
+
}
|
|
42
|
+
candidates.push(path.join(__dirname, 'vectlite.node'))
|
|
43
|
+
|
|
44
|
+
const errors = []
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
if (!fs.existsSync(candidate)) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return require(candidate)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
errors.push(`${candidate}: ${error?.message ?? String(error)}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const detail = errors.length === 0 ? 'No compatible prebuilt binary was found.' : errors.join('\n')
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unable to load the vectlite native addon.\n${detail}\n` +
|
|
59
|
+
'If this platform is not covered by prebuilt binaries, install Rust/Cargo and reinstall the package.',
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const native = loadNative()
|
|
4
64
|
|
|
5
65
|
const TOKEN_RE = /[a-z0-9]+/g
|
|
6
66
|
|
|
@@ -25,6 +85,15 @@ function wrapError(fn) {
|
|
|
25
85
|
}
|
|
26
86
|
}
|
|
27
87
|
|
|
88
|
+
function wrapAsync(value) {
|
|
89
|
+
return Promise.resolve(value).catch((error) => {
|
|
90
|
+
if (error instanceof VectLiteError) {
|
|
91
|
+
throw error
|
|
92
|
+
}
|
|
93
|
+
throw new VectLiteError(error?.message ?? String(error), error)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
28
97
|
function encode(value) {
|
|
29
98
|
return value == null ? null : JSON.stringify(value)
|
|
30
99
|
}
|
|
@@ -37,6 +106,18 @@ function asArray(values) {
|
|
|
37
106
|
return Array.from(values)
|
|
38
107
|
}
|
|
39
108
|
|
|
109
|
+
function isPromiseLike(value) {
|
|
110
|
+
return value != null && typeof value.then === 'function'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function embedText(text, embed) {
|
|
114
|
+
const embedded = wrapError(() => embed(text))
|
|
115
|
+
if (isPromiseLike(embedded)) {
|
|
116
|
+
return wrapAsync(embedded).then((vector) => asArray(vector))
|
|
117
|
+
}
|
|
118
|
+
return asArray(embedded)
|
|
119
|
+
}
|
|
120
|
+
|
|
40
121
|
function normalizeWriteOptions(options = {}) {
|
|
41
122
|
return {
|
|
42
123
|
namespace: options.namespace ?? null,
|
|
@@ -258,15 +339,30 @@ function upsertText(db, id, text, embed, metadata = null, options = {}) {
|
|
|
258
339
|
if (payload.text === undefined) {
|
|
259
340
|
payload.text = text
|
|
260
341
|
}
|
|
261
|
-
|
|
342
|
+
const vector = embedText(text, embed)
|
|
343
|
+
const writeOptions = { ...options, sparse: sparseTerms(text) }
|
|
344
|
+
if (isPromiseLike(vector)) {
|
|
345
|
+
return wrapAsync(vector.then((resolved) => db.upsert(id, resolved, payload, writeOptions)))
|
|
346
|
+
}
|
|
347
|
+
return db.upsert(id, vector, payload, writeOptions)
|
|
262
348
|
}
|
|
263
349
|
|
|
264
350
|
function searchText(db, text, embed, options = {}) {
|
|
265
|
-
|
|
351
|
+
const vector = embedText(text, embed)
|
|
352
|
+
const searchOptions = { ...options, sparse: sparseTerms(text) }
|
|
353
|
+
if (isPromiseLike(vector)) {
|
|
354
|
+
return wrapAsync(vector.then((resolved) => db.search(resolved, searchOptions)))
|
|
355
|
+
}
|
|
356
|
+
return db.search(vector, searchOptions)
|
|
266
357
|
}
|
|
267
358
|
|
|
268
359
|
function searchTextWithStats(db, text, embed, options = {}) {
|
|
269
|
-
|
|
360
|
+
const vector = embedText(text, embed)
|
|
361
|
+
const searchOptions = { ...options, sparse: sparseTerms(text) }
|
|
362
|
+
if (isPromiseLike(vector)) {
|
|
363
|
+
return wrapAsync(vector.then((resolved) => db.searchWithStats(resolved, searchOptions)))
|
|
364
|
+
}
|
|
365
|
+
return db.searchWithStats(vector, searchOptions)
|
|
270
366
|
}
|
|
271
367
|
|
|
272
368
|
module.exports = {
|
package/native/Cargo.toml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "vectlite-node"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.8"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
description = "Node.js bindings for vectlite."
|
|
7
|
-
homepage = "https://
|
|
7
|
+
homepage = "https://vectlite.mcsedition.org/"
|
|
8
8
|
repository = "https://github.com/mcsedition-hub/vectlite"
|
|
9
|
-
documentation = "https://
|
|
9
|
+
documentation = "https://vectlite.mcsedition.org/"
|
|
10
10
|
build = "build.rs"
|
|
11
11
|
|
|
12
12
|
[lib]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "vectlite-core"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.8"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
description = "Core storage engine for vectlite."
|
|
7
|
-
homepage = "https://
|
|
7
|
+
homepage = "https://vectlite.mcsedition.org/"
|
|
8
8
|
repository = "https://github.com/mcsedition-hub/vectlite"
|
|
9
|
-
documentation = "https://
|
|
9
|
+
documentation = "https://vectlite.mcsedition.org/"
|
|
10
10
|
|
|
11
11
|
[lib]
|
|
12
12
|
name = "vectlite"
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vectlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Embedded vector store for local-first AI applications.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
|
-
"homepage": "https://
|
|
7
|
+
"homepage": "https://vectlite.mcsedition.org/",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/mcsedition-hub/vectlite.git",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"index.d.ts",
|
|
29
29
|
"README.md",
|
|
30
30
|
"scripts",
|
|
31
|
-
"native"
|
|
31
|
+
"native",
|
|
32
|
+
"prebuilds"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
|
34
35
|
"build": "node ./scripts/build-addon.mjs",
|
|
35
|
-
"test": "npm run build && node --test tests/smoke.test.cjs",
|
|
36
|
-
"install": "node ./scripts/
|
|
36
|
+
"test": "npm run build && node --test tests/smoke.test.cjs && node --test tests/prebuild-loader.test.cjs",
|
|
37
|
+
"install": "node ./scripts/install-addon.mjs",
|
|
37
38
|
"prepack": "node ./scripts/prepare-package.mjs",
|
|
38
39
|
"postpack": "node ./scripts/clean-package.mjs"
|
|
39
40
|
},
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import { dirname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const packageRoot = resolve(__dirname, '..')
|
|
8
|
+
const sourceRoot = resolve(process.argv[2] ?? join(packageRoot, '..', '..', 'dist', 'node-prebuilds'))
|
|
9
|
+
const destRoot = join(packageRoot, 'prebuilds')
|
|
10
|
+
|
|
11
|
+
for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
12
|
+
if (!entry.isDirectory() || !entry.name.startsWith('prebuild-')) {
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const prebuildTag = entry.name.slice('prebuild-'.length)
|
|
17
|
+
const source = join(sourceRoot, entry.name, 'vectlite.node')
|
|
18
|
+
if (!existsSync(source)) {
|
|
19
|
+
console.error(`Missing prebuilt artifact for ${prebuildTag}: ${source}`)
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const destDir = join(destRoot, prebuildTag)
|
|
24
|
+
mkdirSync(destDir, { recursive: true })
|
|
25
|
+
cpSync(source, join(destDir, 'vectlite.node'))
|
|
26
|
+
console.log(`Collected ${prebuildTag}`)
|
|
27
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import { dirname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const packageRoot = resolve(__dirname, '..')
|
|
9
|
+
|
|
10
|
+
function linuxLibc() {
|
|
11
|
+
if (process.platform !== 'linux') {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const report = process.report?.getReport?.()
|
|
16
|
+
return report?.header?.glibcVersionRuntime ? 'gnu' : 'musl'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runtimePrebuildTag() {
|
|
20
|
+
switch (process.platform) {
|
|
21
|
+
case 'darwin':
|
|
22
|
+
if (process.arch === 'x64') return 'darwin-x64'
|
|
23
|
+
if (process.arch === 'arm64') return 'darwin-arm64'
|
|
24
|
+
return null
|
|
25
|
+
case 'linux': {
|
|
26
|
+
const libc = linuxLibc()
|
|
27
|
+
if (process.arch === 'x64' && libc === 'gnu') return 'linux-x64-gnu'
|
|
28
|
+
if (process.arch === 'arm64' && libc === 'gnu') return 'linux-arm64-gnu'
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
case 'win32':
|
|
32
|
+
if (process.arch === 'x64') return 'win32-x64-msvc'
|
|
33
|
+
if (process.arch === 'arm64') return 'win32-arm64-msvc'
|
|
34
|
+
return null
|
|
35
|
+
default:
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const prebuildTag = runtimePrebuildTag()
|
|
41
|
+
const prebuiltPath =
|
|
42
|
+
prebuildTag == null ? null : join(packageRoot, 'prebuilds', prebuildTag, 'vectlite.node')
|
|
43
|
+
|
|
44
|
+
if (prebuiltPath != null && existsSync(prebuiltPath)) {
|
|
45
|
+
console.log(`Using prebuilt addon: ${prebuiltPath}`)
|
|
46
|
+
process.exit(0)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = spawnSync(process.execPath, [join(__dirname, 'build-addon.mjs')], {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
process.exit(result.status ?? 1)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import { dirname } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const packageRoot = resolve(__dirname, '..')
|
|
8
|
+
|
|
9
|
+
function linuxLibc() {
|
|
10
|
+
if (process.platform !== 'linux') {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const report = process.report?.getReport?.()
|
|
15
|
+
return report?.header?.glibcVersionRuntime ? 'gnu' : 'musl'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runtimePrebuildTag() {
|
|
19
|
+
switch (process.platform) {
|
|
20
|
+
case 'darwin':
|
|
21
|
+
if (process.arch === 'x64') return 'darwin-x64'
|
|
22
|
+
if (process.arch === 'arm64') return 'darwin-arm64'
|
|
23
|
+
return null
|
|
24
|
+
case 'linux': {
|
|
25
|
+
const libc = linuxLibc()
|
|
26
|
+
if (process.arch === 'x64' && libc === 'gnu') return 'linux-x64-gnu'
|
|
27
|
+
if (process.arch === 'arm64' && libc === 'gnu') return 'linux-arm64-gnu'
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
case 'win32':
|
|
31
|
+
if (process.arch === 'x64') return 'win32-x64-msvc'
|
|
32
|
+
if (process.arch === 'arm64') return 'win32-arm64-msvc'
|
|
33
|
+
return null
|
|
34
|
+
default:
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const prebuildTag = process.env.VECTLITE_PREBUILD_TAG ?? runtimePrebuildTag()
|
|
40
|
+
if (prebuildTag == null) {
|
|
41
|
+
console.error('Unable to determine a prebuild tag for this platform.')
|
|
42
|
+
process.exit(1)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const source = join(packageRoot, 'vectlite.node')
|
|
46
|
+
if (!existsSync(source)) {
|
|
47
|
+
console.error(`Missing built addon at ${source}. Run the build first.`)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const destDir = join(packageRoot, 'prebuilds', prebuildTag)
|
|
52
|
+
const dest = join(destDir, 'vectlite.node')
|
|
53
|
+
|
|
54
|
+
mkdirSync(destDir, { recursive: true })
|
|
55
|
+
cpSync(source, dest)
|
|
56
|
+
console.log(`Staged prebuilt ${prebuildTag}: ${dest}`)
|