@wiscale/velesdb-sdk 1.14.4 → 1.17.0
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 +81 -6
- package/dist/index.d.mts +100 -11
- package/dist/index.d.ts +100 -11
- package/dist/index.js +166 -59
- package/dist/index.mjs +165 -59
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Official TypeScript SDK for [VelesDB](https://github.com/cyberlife-coder/VelesDB) -- the local-first vector database for AI and RAG. Sub-millisecond semantic search in Browser and Node.js.
|
|
4
4
|
|
|
5
|
-
**v1.
|
|
5
|
+
**v1.17.0** | Node.js >= 18 | Browser (WASM) | MIT License
|
|
6
6
|
|
|
7
|
-
## What's New in v1.
|
|
7
|
+
## What's New in v1.16.0
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **First-party embedding helper.** New `OpenAIEmbedder` (plus the `Embedder` interface and `OpenAIEmbedderOptions` type), exported from the package root. It calls any OpenAI-compatible `/embeddings` endpoint via the global `fetch` API — no extra runtime dependency — so you can go from text to vectors without hand-writing the request. See [Embedding helper](#embedding-helper) below. Works in Node.js ≥ 18, browsers, and Deno.
|
|
10
|
+
|
|
11
|
+
### Previous (v1.14.2)
|
|
12
|
+
|
|
13
|
+
- **No SDK source change.** v1.14.2 was a workspace patch focused on the Python Haystack `DocumentStore` (`DuplicatePolicy.SKIP` contract fix) and seven version-drift gaps in the release tooling. The TS SDK ships in lock-step with the workspace and was functionally identical to v1.14.1.
|
|
10
14
|
|
|
11
15
|
### Previous (v1.14.1)
|
|
12
16
|
|
|
@@ -125,6 +129,7 @@ await db.upsertBatch('documents', [
|
|
|
125
129
|
]);
|
|
126
130
|
|
|
127
131
|
// 5. Search for similar vectors
|
|
132
|
+
const queryVector = new Float32Array(768).fill(0.1);
|
|
128
133
|
const results = await db.search('documents', queryVector, { k: 5 });
|
|
129
134
|
console.log(results);
|
|
130
135
|
// [{ id: 'doc-1', score: 0.95, payload: { title: 'Hello World', ... } }, ...]
|
|
@@ -151,6 +156,7 @@ await db.init();
|
|
|
151
156
|
// Same API as WASM backend
|
|
152
157
|
await db.createCollection('products', { dimension: 1536 });
|
|
153
158
|
await db.upsert('products', { id: 1, vector: embedding });
|
|
159
|
+
const queryVector = new Float32Array(1536).fill(0.1);
|
|
154
160
|
const results = await db.search('products', queryVector, { k: 10 });
|
|
155
161
|
```
|
|
156
162
|
|
|
@@ -161,6 +167,39 @@ const results = await db.search('products', queryVector, { k: 10 });
|
|
|
161
167
|
> are accepted for backward compatibility but are deprecated and will be removed
|
|
162
168
|
> in a future major version. Always target `/v1/` in custom HTTP clients.
|
|
163
169
|
|
|
170
|
+
## Embedding helper
|
|
171
|
+
|
|
172
|
+
The SDK ships an optional `OpenAIEmbedder` so you can turn text into vectors without
|
|
173
|
+
writing the HTTP call yourself. It targets any OpenAI-compatible `/embeddings`
|
|
174
|
+
endpoint (OpenAI, Azure OpenAI, vLLM, …) using the global `fetch` API, so it adds
|
|
175
|
+
**no extra runtime dependency** and runs in Node.js ≥ 18, browsers, and Deno.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { VelesDB, OpenAIEmbedder } from '@wiscale/velesdb-sdk';
|
|
179
|
+
|
|
180
|
+
const embedder = new OpenAIEmbedder({
|
|
181
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
182
|
+
model: 'text-embedding-3-small', // default; pass `dimensions` to truncate
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const db = new VelesDB({ backend: 'wasm' });
|
|
186
|
+
await db.init();
|
|
187
|
+
|
|
188
|
+
// Embed first so the collection dimension matches the model output.
|
|
189
|
+
const vectors = await embedder.embed(['hello world', 'vector search']);
|
|
190
|
+
await db.createCollection('docs', { dimension: embedder.dimension, metric: 'cosine' });
|
|
191
|
+
await db.upsertBatch('docs', vectors.map((vector, i) => ({ id: `doc-${i}`, vector })));
|
|
192
|
+
|
|
193
|
+
const [query] = await embedder.embed(['greeting']);
|
|
194
|
+
const results = await db.search('docs', query, { k: 5 });
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`embedder.dimension` is `0` until the first `embed()` call (or until you pass
|
|
198
|
+
`dimensions` to the constructor), at which point it is inferred from the model's
|
|
199
|
+
output. To use a different provider, set `baseUrl`. Implement the `Embedder`
|
|
200
|
+
interface (`{ dimension: number; embed(texts: string[]): Promise<number[][]> }`)
|
|
201
|
+
to plug in any other embedding source.
|
|
202
|
+
|
|
164
203
|
## API Reference
|
|
165
204
|
|
|
166
205
|
### Client
|
|
@@ -312,7 +351,7 @@ Vector similarity search.
|
|
|
312
351
|
```typescript
|
|
313
352
|
const results = await db.search('docs', queryVector, {
|
|
314
353
|
k: 10,
|
|
315
|
-
filter: { category: 'tech' },
|
|
354
|
+
filter: { condition: { type: 'eq', field: 'category', value: 'tech' } },
|
|
316
355
|
includeVectors: true
|
|
317
356
|
});
|
|
318
357
|
// Returns: SearchResult[] = [{ id, score, payload?, vector? }, ...]
|
|
@@ -325,7 +364,7 @@ Execute multiple search queries in parallel.
|
|
|
325
364
|
```typescript
|
|
326
365
|
const batchResults = await db.searchBatch('docs', [
|
|
327
366
|
{ vector: queryA, k: 5 },
|
|
328
|
-
{ vector: queryB, k: 10, filter: { type: 'article' } },
|
|
367
|
+
{ vector: queryB, k: 10, filter: { condition: { type: 'eq', field: 'type', value: 'article' } } },
|
|
329
368
|
]);
|
|
330
369
|
// Returns: SearchResult[][] (one result array per query)
|
|
331
370
|
```
|
|
@@ -430,6 +469,37 @@ const results = await db.multiQuerySearch('docs', [emb1, emb2], {
|
|
|
430
469
|
|
|
431
470
|
> **Note:** WASM supports `rrf`, `average`, `maximum`. The `weighted` and `relative_score` strategies are REST-only.
|
|
432
471
|
|
|
472
|
+
#### Named sparse indexes — `sparseIndexName` vs `sparseSearchNamed()`
|
|
473
|
+
|
|
474
|
+
A collection can carry multiple sparse indexes (e.g. `splade_v2`, `bm25_titles`). The TypeScript SDK exposes two distinct APIs depending on whether you want a dense + sparse hybrid query or a pure sparse query against a named index.
|
|
475
|
+
|
|
476
|
+
| API | Shape | Use when |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| `db.search(coll, denseVec, { sparseVector, sparseIndexName })` | dense + sparse hybrid against the named sparse index | You already have a dense embedding and want to combine it with a specific named sparse index in a single search call. The dense vector drives the primary candidate set; the named sparse index re-ranks. |
|
|
479
|
+
| `db.sparseSearchNamed(coll, sparseVec, indexName, options?)` | pure sparse against a named index | You have only a sparse vector (e.g. SPLADE expansion, lexical query) and want to query a specific named sparse index directly. No dense component. |
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Hybrid: dense + sparse via the splade_v2 named index
|
|
483
|
+
const hybrid = await db.search('docs', denseEmbedding, {
|
|
484
|
+
k: 10,
|
|
485
|
+
sparseVector: { 42: 0.8, 99: 0.3 },
|
|
486
|
+
sparseIndexName: 'splade_v2',
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Pure sparse against the same named index
|
|
490
|
+
const sparseOnly = await db.sparseSearchNamed(
|
|
491
|
+
'docs',
|
|
492
|
+
{ 42: 0.8, 99: 0.3 },
|
|
493
|
+
'splade_v2',
|
|
494
|
+
{ k: 10 },
|
|
495
|
+
);
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
When the collection has only one (default) sparse index, omit `sparseIndexName` on `db.search()`; the server picks the default. For named indexes, both APIs require the explicit name.
|
|
499
|
+
|
|
500
|
+
> **Note:** Both APIs are REST-only when a named index is required.
|
|
501
|
+
> The WASM backend has no concept of named sparse indexes — `sparseIndexName` is silently ignored on `db.search()` (the collection's single sparse index is used), and `db.sparseSearchNamed()` throws `wasmNotSupported`. Tracked as a follow-up to either surface a `wasmNotSupported` throw on `sparseIndexName` or implement named-sparse support in WASM.
|
|
502
|
+
|
|
433
503
|
---
|
|
434
504
|
|
|
435
505
|
### Collection Utilities
|
|
@@ -507,7 +577,7 @@ const combined = await db.query('users',
|
|
|
507
577
|
|
|
508
578
|
// Fusion strategy
|
|
509
579
|
const fused = await db.query('docs',
|
|
510
|
-
"SELECT * FROM docs USING FUSION(strategy = 'rrf', k = 60)
|
|
580
|
+
"SELECT * FROM docs LIMIT 20 USING FUSION(strategy = 'rrf', k = 60)"
|
|
511
581
|
);
|
|
512
582
|
```
|
|
513
583
|
|
|
@@ -797,6 +867,11 @@ import {
|
|
|
797
867
|
VelesDB,
|
|
798
868
|
AgentMemoryClient,
|
|
799
869
|
|
|
870
|
+
// Embedding helper
|
|
871
|
+
OpenAIEmbedder,
|
|
872
|
+
type Embedder,
|
|
873
|
+
type OpenAIEmbedderOptions,
|
|
874
|
+
|
|
800
875
|
// Backends (advanced: use VelesDB client instead)
|
|
801
876
|
WasmBackend,
|
|
802
877
|
RestBackend,
|
package/dist/index.d.mts
CHANGED
|
@@ -376,6 +376,30 @@ declare const f: {
|
|
|
376
376
|
* @packageDocumentation
|
|
377
377
|
*/
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Options for `db.sparseSearchNamed()` — **pure sparse** query against a
|
|
381
|
+
* named sparse index (issue #380).
|
|
382
|
+
*
|
|
383
|
+
* Use this when you have only a sparse vector and want to query a specific
|
|
384
|
+
* named sparse index directly. The query carries no dense component.
|
|
385
|
+
*
|
|
386
|
+
* For **dense + sparse hybrid** against a named index, use
|
|
387
|
+
* `db.search(..., { sparseVector, sparseIndexName })` instead
|
|
388
|
+
* (see {@link SearchOptions.sparseIndexName}).
|
|
389
|
+
*
|
|
390
|
+
* **Backend support:** REST only. The WASM backend has no concept of named
|
|
391
|
+
* sparse indexes; this method throws `wasmNotSupported` on WASM.
|
|
392
|
+
*/
|
|
393
|
+
interface SparseSearchNamedOptions {
|
|
394
|
+
/** Number of results to return (default: 10) */
|
|
395
|
+
k?: number;
|
|
396
|
+
/** Filter expression */
|
|
397
|
+
filter?: FilterInput;
|
|
398
|
+
/** Optional dense vector to combine with sparse for hybrid named search */
|
|
399
|
+
vector?: number[] | Float32Array;
|
|
400
|
+
/** Search quality preset */
|
|
401
|
+
quality?: SearchQuality;
|
|
402
|
+
}
|
|
379
403
|
/** Search options */
|
|
380
404
|
interface SearchOptions {
|
|
381
405
|
/** Number of results to return (default: 10) */
|
|
@@ -386,6 +410,18 @@ interface SearchOptions {
|
|
|
386
410
|
includeVectors?: boolean;
|
|
387
411
|
/** Optional sparse vector for hybrid sparse+dense search */
|
|
388
412
|
sparseVector?: SparseVector;
|
|
413
|
+
/**
|
|
414
|
+
* Named sparse index to combine with the dense query for **hybrid** search
|
|
415
|
+
* (when the collection has multiple sparse indexes). When omitted, the
|
|
416
|
+
* default sparse index is used.
|
|
417
|
+
*
|
|
418
|
+
* For a **pure sparse** query against a named index (no dense vector),
|
|
419
|
+
* call `db.sparseSearchNamed()` instead — see {@link SparseSearchNamedOptions}.
|
|
420
|
+
*
|
|
421
|
+
* **Backend support:** REST only. The WASM backend silently ignores this
|
|
422
|
+
* field and uses the collection's single sparse index regardless.
|
|
423
|
+
*/
|
|
424
|
+
sparseIndexName?: string;
|
|
389
425
|
/** Search quality preset (default: 'balanced'). */
|
|
390
426
|
quality?: SearchQuality;
|
|
391
427
|
}
|
|
@@ -612,8 +648,8 @@ interface ColumnStatsDetail {
|
|
|
612
648
|
name: string;
|
|
613
649
|
nullCount: number;
|
|
614
650
|
distinctCount: number;
|
|
615
|
-
minValue: unknown
|
|
616
|
-
maxValue: unknown
|
|
651
|
+
minValue: unknown;
|
|
652
|
+
maxValue: unknown;
|
|
617
653
|
avgSizeBytes: number;
|
|
618
654
|
histogramBuckets: number | null;
|
|
619
655
|
histogramStale: boolean | null;
|
|
@@ -1152,6 +1188,17 @@ interface IVelesDBBackend {
|
|
|
1152
1188
|
traverseParallel(collection: string, request: TraverseParallelRequest): Promise<TraverseResponse>;
|
|
1153
1189
|
/** Get the in-degree and out-degree of a node */
|
|
1154
1190
|
getNodeDegree(collection: string, nodeId: number): Promise<DegreeResponse>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Search a named sparse index (issue #380).
|
|
1193
|
+
*
|
|
1194
|
+
* Sends `sparse_vectors: { [indexName]: query }` and `sparse_index: indexName`
|
|
1195
|
+
* to the `/search` endpoint. When `options.vector` is provided, the request
|
|
1196
|
+
* also includes a dense vector for hybrid sparse+dense search against the
|
|
1197
|
+
* named index.
|
|
1198
|
+
*
|
|
1199
|
+
* WASM backend: not supported (throws `VelesDB-WASM-NOT-SUPPORTED`).
|
|
1200
|
+
*/
|
|
1201
|
+
sparseSearchNamed(collection: string, query: SparseVector, indexName: string, options?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1155
1202
|
/** Train Product Quantization on a collection */
|
|
1156
1203
|
trainPq(collection: string, options?: PqTrainOptions): Promise<string>;
|
|
1157
1204
|
/** Stream-insert documents with backpressure support */
|
|
@@ -1322,6 +1369,13 @@ declare class VelesDB {
|
|
|
1322
1369
|
filter?: FilterInput;
|
|
1323
1370
|
}): Promise<SearchResult[]>;
|
|
1324
1371
|
multiQuerySearch(collection: string, vectors: Array<number[] | Float32Array>, options?: MultiQuerySearchOptions): Promise<SearchResult[]>;
|
|
1372
|
+
/**
|
|
1373
|
+
* Pure sparse search against a named sparse index.
|
|
1374
|
+
*
|
|
1375
|
+
* @see {@link SparseSearchNamedOptions} for the full pure-sparse vs hybrid comparison.
|
|
1376
|
+
* @see {@link VelesDB.search} for dense + sparse hybrid against a named index.
|
|
1377
|
+
*/
|
|
1378
|
+
sparseSearchNamed(collection: string, query: SparseVector, indexName: string, options?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1325
1379
|
query(collection: string, queryString: string, params?: Record<string, unknown>, options?: QueryOptions): Promise<QueryApiResponse>;
|
|
1326
1380
|
queryExplain(queryString: string, params?: Record<string, unknown>, options?: {
|
|
1327
1381
|
analyze?: boolean;
|
|
@@ -1454,16 +1508,9 @@ declare class WasmBackend implements IVelesDBBackend {
|
|
|
1454
1508
|
getNodePayload(c: string, id: number): Promise<NodePayloadResponse>;
|
|
1455
1509
|
upsertNodePayload(c: string, id: number, p: Record<string, unknown>): Promise<void>;
|
|
1456
1510
|
graphSearch(c: string, r: GraphSearchRequest): Promise<GraphSearchResponse>;
|
|
1511
|
+
sparseSearchNamed(c: string, q: SparseVector, idx: string, o?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1457
1512
|
}
|
|
1458
1513
|
|
|
1459
|
-
/**
|
|
1460
|
-
* REST Backend for VelesDB
|
|
1461
|
-
*
|
|
1462
|
-
* Connects to VelesDB server via REST API.
|
|
1463
|
-
* This is the composition root that delegates to focused backend modules.
|
|
1464
|
-
* HTTP infrastructure lives in rest-http.ts.
|
|
1465
|
-
*/
|
|
1466
|
-
|
|
1467
1514
|
/**
|
|
1468
1515
|
* REST Backend
|
|
1469
1516
|
*
|
|
@@ -1521,6 +1568,7 @@ declare class RestBackend implements IVelesDBBackend {
|
|
|
1521
1568
|
id: number;
|
|
1522
1569
|
score: number;
|
|
1523
1570
|
}>>;
|
|
1571
|
+
sparseSearchNamed(c: string, q: SparseVector, idx: string, o?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1524
1572
|
query(c: string, q: string, p?: Record<string, unknown>, o?: QueryOptions): Promise<QueryApiResponse>;
|
|
1525
1573
|
queryExplain(q: string, p?: Record<string, unknown>, o?: {
|
|
1526
1574
|
analyze?: boolean;
|
|
@@ -1998,4 +2046,45 @@ type VelesErrorCode = (typeof VELES_ERROR_CODES)[number];
|
|
|
1998
2046
|
*/
|
|
1999
2047
|
declare function parseVelesError(code: string | null | undefined, message: string): VelesError;
|
|
2000
2048
|
|
|
2001
|
-
|
|
2049
|
+
/**
|
|
2050
|
+
* Optional embedding helpers for the VelesDB TypeScript SDK.
|
|
2051
|
+
*
|
|
2052
|
+
* A thin {@link Embedder} interface plus an adapter for OpenAI-compatible
|
|
2053
|
+
* endpoints. The adapter uses the global `fetch` API (Node ≥ 18, browsers,
|
|
2054
|
+
* Deno) and has no additional runtime dependencies.
|
|
2055
|
+
*
|
|
2056
|
+
* @example
|
|
2057
|
+
* ```typescript
|
|
2058
|
+
* import { VelesDB, OpenAIEmbedder } from '@wiscale/velesdb-sdk';
|
|
2059
|
+
*
|
|
2060
|
+
* const embedder = new OpenAIEmbedder({ apiKey: process.env.OPENAI_API_KEY! });
|
|
2061
|
+
* const db = new VelesDB({ backend: 'wasm' });
|
|
2062
|
+
* await db.init();
|
|
2063
|
+
* await db.createCollection('docs', { dimension: embedder.dimension ?? 1536 });
|
|
2064
|
+
* const vectors = await embedder.embed(['hello world', 'vector search']);
|
|
2065
|
+
* ```
|
|
2066
|
+
*/
|
|
2067
|
+
interface Embedder {
|
|
2068
|
+
/** Embedding dimension, or `0` if not yet known (determined after first call). */
|
|
2069
|
+
readonly dimension: number;
|
|
2070
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
2071
|
+
}
|
|
2072
|
+
interface OpenAIEmbedderOptions {
|
|
2073
|
+
model?: string;
|
|
2074
|
+
apiKey: string;
|
|
2075
|
+
/** Override the base URL for Azure OpenAI, vLLM, or any compatible endpoint. */
|
|
2076
|
+
baseUrl?: string;
|
|
2077
|
+
/** Request a specific output dimension (requires a model that supports it). */
|
|
2078
|
+
dimensions?: number;
|
|
2079
|
+
}
|
|
2080
|
+
declare class OpenAIEmbedder implements Embedder {
|
|
2081
|
+
private readonly model;
|
|
2082
|
+
private readonly apiKey;
|
|
2083
|
+
private readonly baseUrl;
|
|
2084
|
+
private readonly requestedDimensions;
|
|
2085
|
+
dimension: number;
|
|
2086
|
+
constructor(options: OpenAIEmbedderOptions);
|
|
2087
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
export { type ActualStats, type AddEdgeRequest, AgentMemoryClient, type AgentMemoryConfig, type AggregateQueryOptions, type AggregateResponse, type AggregationQueryResponse, AllocationFailedError, type AsyncIndexBuilderOptions, type BackendType, BackpressureError, type CapabilityMap, type Collection, type CollectionConfig, type CollectionConfigResponse, CollectionExistsError, CollectionNotFoundError, type CollectionSanityChecks, type CollectionSanityDiagnostics, type CollectionSanityResponse, type CollectionStatsResponse, type CollectionType, type ColumnStatsDetail, ColumnStoreError, type CompareOp, type Condition, ConfigError, ConnectionError, type CreateIndexOptions, DatabaseLockedError, type DeferredIndexerOptions, type DegreeResponse, DimensionMismatchError, type DistanceMetric, EdgeExistsError, EdgeNotFoundError, type EdgesResponse, type Embedder, type EpisodicEvent, EpochMismatchError, type ExplainCost, type ExplainFeatures, type ExplainPlanStep, type ExplainResponse, type Filter, type FilterInput, type FusionOptions, type FusionStrategy, type GetEdgesOptions, type GetNodeEdgesOptions, GpuError, type GraphCollectionConfig, type GraphEdge, GraphNotSupportedError, type GraphSchemaMode, type GraphSearchRequest, type GraphSearchResponse, type GraphSearchResultItem, GuardRailError, type GuardRailsConfigResponse, type GuardRailsUpdateRequest, type HnswParams, type IVelesDBBackend, IncompatibleSchemaVersionError, IndexCorruptedError, IndexError, type IndexInfo, type IndexType, InternalError, InvalidCollectionNameError, InvalidDimensionError, InvalidEdgeLabelError, InvalidQuantizerConfigError, InvalidVectorError, IoError, type JsonValue, type ListNodesResponse, type MatchQueryOptions, type MatchQueryResponse, type MatchQueryResultItem, type MultiQuerySearchOptions, type NearVectorOptions, NodeNotFoundError, type NodePayloadResponse, type NodeStats, NotFoundError, OpenAIEmbedder, type OpenAIEmbedderOptions, OverflowError, PointNotFoundError, type PqTrainOptions, type ProceduralPattern, type QueryApiResponse, QueryError, type QueryOptions, type QueryResponse, type QueryResult, type QueryStats, REST_CAPABILITIES, type RebuildIndexResponse, type RelDirection, type RelOptions, RestBackend, type RestPointId, SchemaValidationError, type ScrollRequest, type ScrollResponse, SearchNotSupportedError, type SearchOptions, type SearchQuality, type SearchQualityWire, type SearchResult, type SemanticEntry, SerializationError, SnapshotBuildFailedError, SparseIndexError, type SparseSearchNamedOptions, type SparseVector, StorageError, type StorageMode, type StreamUpsertResponse, TrainingFailedError, type TraversalResultItem, type TraversalStats, type TraverseParallelRequest, type TraverseRequest, type TraverseResponse, VELES_ERROR_CODES, ValidationError, type VectorDocument, VectorNotAllowedError, VectorRequiredError, VelesDB, type VelesDBConfig, VelesDBError, VelesError, type VelesErrorCode, VelesQLBuilder, WASM_CAPABILITIES, WasmBackend, f, isTypedFilter, normalizeFilter, parseVelesError, searchQualityToMode, velesql };
|
package/dist/index.d.ts
CHANGED
|
@@ -376,6 +376,30 @@ declare const f: {
|
|
|
376
376
|
* @packageDocumentation
|
|
377
377
|
*/
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Options for `db.sparseSearchNamed()` — **pure sparse** query against a
|
|
381
|
+
* named sparse index (issue #380).
|
|
382
|
+
*
|
|
383
|
+
* Use this when you have only a sparse vector and want to query a specific
|
|
384
|
+
* named sparse index directly. The query carries no dense component.
|
|
385
|
+
*
|
|
386
|
+
* For **dense + sparse hybrid** against a named index, use
|
|
387
|
+
* `db.search(..., { sparseVector, sparseIndexName })` instead
|
|
388
|
+
* (see {@link SearchOptions.sparseIndexName}).
|
|
389
|
+
*
|
|
390
|
+
* **Backend support:** REST only. The WASM backend has no concept of named
|
|
391
|
+
* sparse indexes; this method throws `wasmNotSupported` on WASM.
|
|
392
|
+
*/
|
|
393
|
+
interface SparseSearchNamedOptions {
|
|
394
|
+
/** Number of results to return (default: 10) */
|
|
395
|
+
k?: number;
|
|
396
|
+
/** Filter expression */
|
|
397
|
+
filter?: FilterInput;
|
|
398
|
+
/** Optional dense vector to combine with sparse for hybrid named search */
|
|
399
|
+
vector?: number[] | Float32Array;
|
|
400
|
+
/** Search quality preset */
|
|
401
|
+
quality?: SearchQuality;
|
|
402
|
+
}
|
|
379
403
|
/** Search options */
|
|
380
404
|
interface SearchOptions {
|
|
381
405
|
/** Number of results to return (default: 10) */
|
|
@@ -386,6 +410,18 @@ interface SearchOptions {
|
|
|
386
410
|
includeVectors?: boolean;
|
|
387
411
|
/** Optional sparse vector for hybrid sparse+dense search */
|
|
388
412
|
sparseVector?: SparseVector;
|
|
413
|
+
/**
|
|
414
|
+
* Named sparse index to combine with the dense query for **hybrid** search
|
|
415
|
+
* (when the collection has multiple sparse indexes). When omitted, the
|
|
416
|
+
* default sparse index is used.
|
|
417
|
+
*
|
|
418
|
+
* For a **pure sparse** query against a named index (no dense vector),
|
|
419
|
+
* call `db.sparseSearchNamed()` instead — see {@link SparseSearchNamedOptions}.
|
|
420
|
+
*
|
|
421
|
+
* **Backend support:** REST only. The WASM backend silently ignores this
|
|
422
|
+
* field and uses the collection's single sparse index regardless.
|
|
423
|
+
*/
|
|
424
|
+
sparseIndexName?: string;
|
|
389
425
|
/** Search quality preset (default: 'balanced'). */
|
|
390
426
|
quality?: SearchQuality;
|
|
391
427
|
}
|
|
@@ -612,8 +648,8 @@ interface ColumnStatsDetail {
|
|
|
612
648
|
name: string;
|
|
613
649
|
nullCount: number;
|
|
614
650
|
distinctCount: number;
|
|
615
|
-
minValue: unknown
|
|
616
|
-
maxValue: unknown
|
|
651
|
+
minValue: unknown;
|
|
652
|
+
maxValue: unknown;
|
|
617
653
|
avgSizeBytes: number;
|
|
618
654
|
histogramBuckets: number | null;
|
|
619
655
|
histogramStale: boolean | null;
|
|
@@ -1152,6 +1188,17 @@ interface IVelesDBBackend {
|
|
|
1152
1188
|
traverseParallel(collection: string, request: TraverseParallelRequest): Promise<TraverseResponse>;
|
|
1153
1189
|
/** Get the in-degree and out-degree of a node */
|
|
1154
1190
|
getNodeDegree(collection: string, nodeId: number): Promise<DegreeResponse>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Search a named sparse index (issue #380).
|
|
1193
|
+
*
|
|
1194
|
+
* Sends `sparse_vectors: { [indexName]: query }` and `sparse_index: indexName`
|
|
1195
|
+
* to the `/search` endpoint. When `options.vector` is provided, the request
|
|
1196
|
+
* also includes a dense vector for hybrid sparse+dense search against the
|
|
1197
|
+
* named index.
|
|
1198
|
+
*
|
|
1199
|
+
* WASM backend: not supported (throws `VelesDB-WASM-NOT-SUPPORTED`).
|
|
1200
|
+
*/
|
|
1201
|
+
sparseSearchNamed(collection: string, query: SparseVector, indexName: string, options?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1155
1202
|
/** Train Product Quantization on a collection */
|
|
1156
1203
|
trainPq(collection: string, options?: PqTrainOptions): Promise<string>;
|
|
1157
1204
|
/** Stream-insert documents with backpressure support */
|
|
@@ -1322,6 +1369,13 @@ declare class VelesDB {
|
|
|
1322
1369
|
filter?: FilterInput;
|
|
1323
1370
|
}): Promise<SearchResult[]>;
|
|
1324
1371
|
multiQuerySearch(collection: string, vectors: Array<number[] | Float32Array>, options?: MultiQuerySearchOptions): Promise<SearchResult[]>;
|
|
1372
|
+
/**
|
|
1373
|
+
* Pure sparse search against a named sparse index.
|
|
1374
|
+
*
|
|
1375
|
+
* @see {@link SparseSearchNamedOptions} for the full pure-sparse vs hybrid comparison.
|
|
1376
|
+
* @see {@link VelesDB.search} for dense + sparse hybrid against a named index.
|
|
1377
|
+
*/
|
|
1378
|
+
sparseSearchNamed(collection: string, query: SparseVector, indexName: string, options?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1325
1379
|
query(collection: string, queryString: string, params?: Record<string, unknown>, options?: QueryOptions): Promise<QueryApiResponse>;
|
|
1326
1380
|
queryExplain(queryString: string, params?: Record<string, unknown>, options?: {
|
|
1327
1381
|
analyze?: boolean;
|
|
@@ -1454,16 +1508,9 @@ declare class WasmBackend implements IVelesDBBackend {
|
|
|
1454
1508
|
getNodePayload(c: string, id: number): Promise<NodePayloadResponse>;
|
|
1455
1509
|
upsertNodePayload(c: string, id: number, p: Record<string, unknown>): Promise<void>;
|
|
1456
1510
|
graphSearch(c: string, r: GraphSearchRequest): Promise<GraphSearchResponse>;
|
|
1511
|
+
sparseSearchNamed(c: string, q: SparseVector, idx: string, o?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1457
1512
|
}
|
|
1458
1513
|
|
|
1459
|
-
/**
|
|
1460
|
-
* REST Backend for VelesDB
|
|
1461
|
-
*
|
|
1462
|
-
* Connects to VelesDB server via REST API.
|
|
1463
|
-
* This is the composition root that delegates to focused backend modules.
|
|
1464
|
-
* HTTP infrastructure lives in rest-http.ts.
|
|
1465
|
-
*/
|
|
1466
|
-
|
|
1467
1514
|
/**
|
|
1468
1515
|
* REST Backend
|
|
1469
1516
|
*
|
|
@@ -1521,6 +1568,7 @@ declare class RestBackend implements IVelesDBBackend {
|
|
|
1521
1568
|
id: number;
|
|
1522
1569
|
score: number;
|
|
1523
1570
|
}>>;
|
|
1571
|
+
sparseSearchNamed(c: string, q: SparseVector, idx: string, o?: SparseSearchNamedOptions): Promise<SearchResult[]>;
|
|
1524
1572
|
query(c: string, q: string, p?: Record<string, unknown>, o?: QueryOptions): Promise<QueryApiResponse>;
|
|
1525
1573
|
queryExplain(q: string, p?: Record<string, unknown>, o?: {
|
|
1526
1574
|
analyze?: boolean;
|
|
@@ -1998,4 +2046,45 @@ type VelesErrorCode = (typeof VELES_ERROR_CODES)[number];
|
|
|
1998
2046
|
*/
|
|
1999
2047
|
declare function parseVelesError(code: string | null | undefined, message: string): VelesError;
|
|
2000
2048
|
|
|
2001
|
-
|
|
2049
|
+
/**
|
|
2050
|
+
* Optional embedding helpers for the VelesDB TypeScript SDK.
|
|
2051
|
+
*
|
|
2052
|
+
* A thin {@link Embedder} interface plus an adapter for OpenAI-compatible
|
|
2053
|
+
* endpoints. The adapter uses the global `fetch` API (Node ≥ 18, browsers,
|
|
2054
|
+
* Deno) and has no additional runtime dependencies.
|
|
2055
|
+
*
|
|
2056
|
+
* @example
|
|
2057
|
+
* ```typescript
|
|
2058
|
+
* import { VelesDB, OpenAIEmbedder } from '@wiscale/velesdb-sdk';
|
|
2059
|
+
*
|
|
2060
|
+
* const embedder = new OpenAIEmbedder({ apiKey: process.env.OPENAI_API_KEY! });
|
|
2061
|
+
* const db = new VelesDB({ backend: 'wasm' });
|
|
2062
|
+
* await db.init();
|
|
2063
|
+
* await db.createCollection('docs', { dimension: embedder.dimension ?? 1536 });
|
|
2064
|
+
* const vectors = await embedder.embed(['hello world', 'vector search']);
|
|
2065
|
+
* ```
|
|
2066
|
+
*/
|
|
2067
|
+
interface Embedder {
|
|
2068
|
+
/** Embedding dimension, or `0` if not yet known (determined after first call). */
|
|
2069
|
+
readonly dimension: number;
|
|
2070
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
2071
|
+
}
|
|
2072
|
+
interface OpenAIEmbedderOptions {
|
|
2073
|
+
model?: string;
|
|
2074
|
+
apiKey: string;
|
|
2075
|
+
/** Override the base URL for Azure OpenAI, vLLM, or any compatible endpoint. */
|
|
2076
|
+
baseUrl?: string;
|
|
2077
|
+
/** Request a specific output dimension (requires a model that supports it). */
|
|
2078
|
+
dimensions?: number;
|
|
2079
|
+
}
|
|
2080
|
+
declare class OpenAIEmbedder implements Embedder {
|
|
2081
|
+
private readonly model;
|
|
2082
|
+
private readonly apiKey;
|
|
2083
|
+
private readonly baseUrl;
|
|
2084
|
+
private readonly requestedDimensions;
|
|
2085
|
+
dimension: number;
|
|
2086
|
+
constructor(options: OpenAIEmbedderOptions);
|
|
2087
|
+
embed(texts: string[]): Promise<number[][]>;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
export { type ActualStats, type AddEdgeRequest, AgentMemoryClient, type AgentMemoryConfig, type AggregateQueryOptions, type AggregateResponse, type AggregationQueryResponse, AllocationFailedError, type AsyncIndexBuilderOptions, type BackendType, BackpressureError, type CapabilityMap, type Collection, type CollectionConfig, type CollectionConfigResponse, CollectionExistsError, CollectionNotFoundError, type CollectionSanityChecks, type CollectionSanityDiagnostics, type CollectionSanityResponse, type CollectionStatsResponse, type CollectionType, type ColumnStatsDetail, ColumnStoreError, type CompareOp, type Condition, ConfigError, ConnectionError, type CreateIndexOptions, DatabaseLockedError, type DeferredIndexerOptions, type DegreeResponse, DimensionMismatchError, type DistanceMetric, EdgeExistsError, EdgeNotFoundError, type EdgesResponse, type Embedder, type EpisodicEvent, EpochMismatchError, type ExplainCost, type ExplainFeatures, type ExplainPlanStep, type ExplainResponse, type Filter, type FilterInput, type FusionOptions, type FusionStrategy, type GetEdgesOptions, type GetNodeEdgesOptions, GpuError, type GraphCollectionConfig, type GraphEdge, GraphNotSupportedError, type GraphSchemaMode, type GraphSearchRequest, type GraphSearchResponse, type GraphSearchResultItem, GuardRailError, type GuardRailsConfigResponse, type GuardRailsUpdateRequest, type HnswParams, type IVelesDBBackend, IncompatibleSchemaVersionError, IndexCorruptedError, IndexError, type IndexInfo, type IndexType, InternalError, InvalidCollectionNameError, InvalidDimensionError, InvalidEdgeLabelError, InvalidQuantizerConfigError, InvalidVectorError, IoError, type JsonValue, type ListNodesResponse, type MatchQueryOptions, type MatchQueryResponse, type MatchQueryResultItem, type MultiQuerySearchOptions, type NearVectorOptions, NodeNotFoundError, type NodePayloadResponse, type NodeStats, NotFoundError, OpenAIEmbedder, type OpenAIEmbedderOptions, OverflowError, PointNotFoundError, type PqTrainOptions, type ProceduralPattern, type QueryApiResponse, QueryError, type QueryOptions, type QueryResponse, type QueryResult, type QueryStats, REST_CAPABILITIES, type RebuildIndexResponse, type RelDirection, type RelOptions, RestBackend, type RestPointId, SchemaValidationError, type ScrollRequest, type ScrollResponse, SearchNotSupportedError, type SearchOptions, type SearchQuality, type SearchQualityWire, type SearchResult, type SemanticEntry, SerializationError, SnapshotBuildFailedError, SparseIndexError, type SparseSearchNamedOptions, type SparseVector, StorageError, type StorageMode, type StreamUpsertResponse, TrainingFailedError, type TraversalResultItem, type TraversalStats, type TraverseParallelRequest, type TraverseRequest, type TraverseResponse, VELES_ERROR_CODES, ValidationError, type VectorDocument, VectorNotAllowedError, VectorRequiredError, VelesDB, type VelesDBConfig, VelesDBError, VelesError, type VelesErrorCode, VelesQLBuilder, WASM_CAPABILITIES, WasmBackend, f, isTypedFilter, normalizeFilter, parseVelesError, searchQualityToMode, velesql };
|
package/dist/index.js
CHANGED
|
@@ -105,6 +105,7 @@ __export(index_exports, {
|
|
|
105
105
|
IoError: () => IoError,
|
|
106
106
|
NodeNotFoundError: () => NodeNotFoundError,
|
|
107
107
|
NotFoundError: () => NotFoundError,
|
|
108
|
+
OpenAIEmbedder: () => OpenAIEmbedder,
|
|
108
109
|
OverflowError: () => OverflowError,
|
|
109
110
|
PointNotFoundError: () => PointNotFoundError,
|
|
110
111
|
QueryError: () => QueryError,
|
|
@@ -725,49 +726,49 @@ var VELES_ERROR_CODES = [
|
|
|
725
726
|
"VELES-035",
|
|
726
727
|
"VELES-036"
|
|
727
728
|
];
|
|
728
|
-
var CODE_TO_CLASS =
|
|
729
|
-
"VELES-001"
|
|
730
|
-
"VELES-002"
|
|
731
|
-
"VELES-003"
|
|
732
|
-
"VELES-004"
|
|
733
|
-
"VELES-005"
|
|
734
|
-
"VELES-006"
|
|
735
|
-
"VELES-007"
|
|
736
|
-
"VELES-008"
|
|
737
|
-
"VELES-009"
|
|
738
|
-
"VELES-010"
|
|
739
|
-
"VELES-011"
|
|
740
|
-
"VELES-012"
|
|
741
|
-
"VELES-013"
|
|
742
|
-
"VELES-014"
|
|
743
|
-
"VELES-015"
|
|
744
|
-
"VELES-016"
|
|
745
|
-
"VELES-017"
|
|
746
|
-
"VELES-018"
|
|
747
|
-
"VELES-019"
|
|
748
|
-
"VELES-020"
|
|
749
|
-
"VELES-021"
|
|
750
|
-
"VELES-022"
|
|
751
|
-
"VELES-023"
|
|
752
|
-
"VELES-024"
|
|
753
|
-
"VELES-025"
|
|
754
|
-
"VELES-026"
|
|
755
|
-
"VELES-027"
|
|
756
|
-
"VELES-028"
|
|
757
|
-
"VELES-029"
|
|
758
|
-
"VELES-030"
|
|
759
|
-
"VELES-031"
|
|
760
|
-
"VELES-032"
|
|
761
|
-
"VELES-033"
|
|
762
|
-
"VELES-034"
|
|
763
|
-
"VELES-035"
|
|
764
|
-
"VELES-036"
|
|
765
|
-
|
|
729
|
+
var CODE_TO_CLASS = /* @__PURE__ */ new Map([
|
|
730
|
+
["VELES-001", CollectionExistsError],
|
|
731
|
+
["VELES-002", CollectionNotFoundError],
|
|
732
|
+
["VELES-003", PointNotFoundError],
|
|
733
|
+
["VELES-004", DimensionMismatchError],
|
|
734
|
+
["VELES-005", InvalidVectorError],
|
|
735
|
+
["VELES-006", StorageError],
|
|
736
|
+
["VELES-007", IndexError],
|
|
737
|
+
["VELES-008", IndexCorruptedError],
|
|
738
|
+
["VELES-009", ConfigError],
|
|
739
|
+
["VELES-010", QueryError],
|
|
740
|
+
["VELES-011", IoError],
|
|
741
|
+
["VELES-012", SerializationError],
|
|
742
|
+
["VELES-013", InternalError],
|
|
743
|
+
["VELES-014", VectorNotAllowedError],
|
|
744
|
+
["VELES-015", SearchNotSupportedError],
|
|
745
|
+
["VELES-016", VectorRequiredError],
|
|
746
|
+
["VELES-017", SchemaValidationError],
|
|
747
|
+
["VELES-018", GraphNotSupportedError],
|
|
748
|
+
["VELES-019", EdgeExistsError],
|
|
749
|
+
["VELES-020", EdgeNotFoundError],
|
|
750
|
+
["VELES-021", InvalidEdgeLabelError],
|
|
751
|
+
["VELES-022", NodeNotFoundError],
|
|
752
|
+
["VELES-023", OverflowError],
|
|
753
|
+
["VELES-024", ColumnStoreError],
|
|
754
|
+
["VELES-025", GpuError],
|
|
755
|
+
["VELES-026", EpochMismatchError],
|
|
756
|
+
["VELES-027", GuardRailError],
|
|
757
|
+
["VELES-028", InvalidQuantizerConfigError],
|
|
758
|
+
["VELES-029", TrainingFailedError],
|
|
759
|
+
["VELES-030", SparseIndexError],
|
|
760
|
+
["VELES-031", DatabaseLockedError],
|
|
761
|
+
["VELES-032", InvalidDimensionError],
|
|
762
|
+
["VELES-033", AllocationFailedError],
|
|
763
|
+
["VELES-034", InvalidCollectionNameError],
|
|
764
|
+
["VELES-035", SnapshotBuildFailedError],
|
|
765
|
+
["VELES-036", IncompatibleSchemaVersionError]
|
|
766
|
+
]);
|
|
766
767
|
function parseVelesError(code, message) {
|
|
767
768
|
if (code === null || code === void 0) {
|
|
768
769
|
return new VelesError(message, "VELES-UNKNOWN");
|
|
769
770
|
}
|
|
770
|
-
const Cls = CODE_TO_CLASS
|
|
771
|
+
const Cls = CODE_TO_CLASS.get(code);
|
|
771
772
|
if (Cls !== void 0) {
|
|
772
773
|
return new Cls(message);
|
|
773
774
|
}
|
|
@@ -957,6 +958,9 @@ async function wasmMatchProceduralPatterns(_collection, _embedding, _k) {
|
|
|
957
958
|
}
|
|
958
959
|
|
|
959
960
|
// src/backends/wasm-wave4-stubs.ts
|
|
961
|
+
function wasmSparseSearchNamed(_c, _q, _idx, _o) {
|
|
962
|
+
return Promise.resolve(wasmNotSupported("Named sparse index search"));
|
|
963
|
+
}
|
|
960
964
|
function wasmRebuildIndex(_c) {
|
|
961
965
|
return Promise.resolve(wasmNotSupported("Index rebuild"));
|
|
962
966
|
}
|
|
@@ -1404,6 +1408,10 @@ var WasmBackend = class {
|
|
|
1404
1408
|
this.ensureInitialized();
|
|
1405
1409
|
return wasmGraphSearch(c, r);
|
|
1406
1410
|
}
|
|
1411
|
+
async sparseSearchNamed(c, q, idx, o) {
|
|
1412
|
+
this.ensureInitialized();
|
|
1413
|
+
return wasmSparseSearchNamed(c, q, idx, o);
|
|
1414
|
+
}
|
|
1407
1415
|
};
|
|
1408
1416
|
|
|
1409
1417
|
// src/backends/crud-backend.ts
|
|
@@ -1977,6 +1985,9 @@ async function search(transport, collection, query3, options) {
|
|
|
1977
1985
|
if (options?.sparseVector) {
|
|
1978
1986
|
body.sparse_vector = transport.sparseToRest(options.sparseVector);
|
|
1979
1987
|
}
|
|
1988
|
+
if (options?.sparseIndexName) {
|
|
1989
|
+
body.sparse_index = options.sparseIndexName;
|
|
1990
|
+
}
|
|
1980
1991
|
const response = await transport.requestJson(
|
|
1981
1992
|
"POST",
|
|
1982
1993
|
`${collectionPath(collection)}/search`,
|
|
@@ -2042,12 +2053,33 @@ async function multiQuerySearch(transport, collection, vectors, options) {
|
|
|
2042
2053
|
avg_weight: options?.fusionParams?.avgWeight,
|
|
2043
2054
|
max_weight: options?.fusionParams?.maxWeight,
|
|
2044
2055
|
hit_weight: options?.fusionParams?.hitWeight,
|
|
2056
|
+
dense_weight: options?.fusionParams?.denseWeight,
|
|
2057
|
+
sparse_weight: options?.fusionParams?.sparseWeight,
|
|
2045
2058
|
filter: options?.filter
|
|
2046
2059
|
}
|
|
2047
2060
|
);
|
|
2048
2061
|
throwOnError(response, `Collection '${collection}'`);
|
|
2049
2062
|
return response.data?.results ?? [];
|
|
2050
2063
|
}
|
|
2064
|
+
async function sparseSearchNamed(transport, collection, query3, indexName, options) {
|
|
2065
|
+
const body = {
|
|
2066
|
+
sparse_vectors: { [indexName]: transport.sparseToRest(query3) },
|
|
2067
|
+
sparse_index: indexName,
|
|
2068
|
+
top_k: options?.k ?? 10,
|
|
2069
|
+
filter: options?.filter,
|
|
2070
|
+
...searchQualityToMode(options?.quality)
|
|
2071
|
+
};
|
|
2072
|
+
if (options?.vector) {
|
|
2073
|
+
body.vector = Array.from(options.vector);
|
|
2074
|
+
}
|
|
2075
|
+
const response = await transport.requestJson(
|
|
2076
|
+
"POST",
|
|
2077
|
+
`${collectionPath(collection)}/search`,
|
|
2078
|
+
body
|
|
2079
|
+
);
|
|
2080
|
+
throwOnError(response, `Collection '${collection}'`);
|
|
2081
|
+
return response.data?.results ?? [];
|
|
2082
|
+
}
|
|
2051
2083
|
async function searchIds(transport, collection, query3, options) {
|
|
2052
2084
|
const queryVector = toNumberArray(query3);
|
|
2053
2085
|
const response = await transport.requestJson(
|
|
@@ -2735,6 +2767,10 @@ var RestBackend = class {
|
|
|
2735
2767
|
this.ensureInitialized();
|
|
2736
2768
|
return searchIds(buildSearchTransport(this.httpConfig), c, q, o);
|
|
2737
2769
|
}
|
|
2770
|
+
async sparseSearchNamed(c, q, idx, o) {
|
|
2771
|
+
this.ensureInitialized();
|
|
2772
|
+
return sparseSearchNamed(buildSearchTransport(this.httpConfig), c, q, idx, o);
|
|
2773
|
+
}
|
|
2738
2774
|
// Query
|
|
2739
2775
|
async query(c, q, p, o) {
|
|
2740
2776
|
this.ensureInitialized();
|
|
@@ -2904,7 +2940,8 @@ function validateDocsBatch(docs, validateDoc) {
|
|
|
2904
2940
|
}
|
|
2905
2941
|
}
|
|
2906
2942
|
function validateDocument(doc, config) {
|
|
2907
|
-
|
|
2943
|
+
const id = doc.id;
|
|
2944
|
+
if (id === void 0 || id === null) {
|
|
2908
2945
|
throw new ValidationError("Document ID is required");
|
|
2909
2946
|
}
|
|
2910
2947
|
requireVector(doc.vector, "Vector");
|
|
@@ -2941,6 +2978,11 @@ function hybridSearch2(backend, collection, vector, textQuery, options) {
|
|
|
2941
2978
|
requireNonEmptyString(textQuery, "Text query");
|
|
2942
2979
|
return backend.hybridSearch(collection, vector, textQuery, options);
|
|
2943
2980
|
}
|
|
2981
|
+
function sparseSearchNamed2(backend, collection, query3, indexName, options) {
|
|
2982
|
+
requireNonEmptyString(collection, "Collection name");
|
|
2983
|
+
requireNonEmptyString(indexName, "Index name");
|
|
2984
|
+
return backend.sparseSearchNamed(collection, query3, indexName, options);
|
|
2985
|
+
}
|
|
2944
2986
|
function multiQuerySearch2(backend, collection, vectors, options) {
|
|
2945
2987
|
if (!Array.isArray(vectors) || vectors.length === 0) {
|
|
2946
2988
|
throw new ValidationError("Vectors must be a non-empty array");
|
|
@@ -2954,11 +2996,15 @@ function trainPq2(backend, collection, options) {
|
|
|
2954
2996
|
return backend.trainPq(collection, options);
|
|
2955
2997
|
}
|
|
2956
2998
|
function streamInsert2(backend, config, collection, docs) {
|
|
2957
|
-
validateDocsBatch(docs, (doc) =>
|
|
2999
|
+
validateDocsBatch(docs, (doc) => {
|
|
3000
|
+
validateDocument(doc, config);
|
|
3001
|
+
});
|
|
2958
3002
|
return backend.streamInsert(collection, docs);
|
|
2959
3003
|
}
|
|
2960
3004
|
function streamUpsertPoints2(backend, config, collection, docs) {
|
|
2961
|
-
validateDocsBatch(docs, (doc) =>
|
|
3005
|
+
validateDocsBatch(docs, (doc) => {
|
|
3006
|
+
validateDocument(doc, config);
|
|
3007
|
+
});
|
|
2962
3008
|
return backend.streamUpsertPoints(collection, docs);
|
|
2963
3009
|
}
|
|
2964
3010
|
function scroll2(backend, collection, request2) {
|
|
@@ -3091,13 +3137,14 @@ var VelesDB = class {
|
|
|
3091
3137
|
this.backend = this.createBackend(config);
|
|
3092
3138
|
}
|
|
3093
3139
|
validateConfig(config) {
|
|
3094
|
-
|
|
3140
|
+
const backend = config.backend;
|
|
3141
|
+
if (!backend) {
|
|
3095
3142
|
throw new ValidationError("Backend type is required");
|
|
3096
3143
|
}
|
|
3097
|
-
if (
|
|
3098
|
-
throw new ValidationError(`Invalid backend type: ${
|
|
3144
|
+
if (backend !== "wasm" && backend !== "rest") {
|
|
3145
|
+
throw new ValidationError(`Invalid backend type: ${backend}. Use 'wasm' or 'rest'`);
|
|
3099
3146
|
}
|
|
3100
|
-
if (
|
|
3147
|
+
if (backend === "rest" && !config.url) {
|
|
3101
3148
|
throw new ValidationError("URL is required for REST backend");
|
|
3102
3149
|
}
|
|
3103
3150
|
}
|
|
@@ -3105,10 +3152,14 @@ var VelesDB = class {
|
|
|
3105
3152
|
switch (config.backend) {
|
|
3106
3153
|
case "wasm":
|
|
3107
3154
|
return new WasmBackend();
|
|
3108
|
-
case "rest":
|
|
3155
|
+
case "rest": {
|
|
3156
|
+
if (!config.url) {
|
|
3157
|
+
throw new ValidationError("URL is required for REST backend");
|
|
3158
|
+
}
|
|
3109
3159
|
return new RestBackend(config.url, config.apiKey, config.timeout);
|
|
3160
|
+
}
|
|
3110
3161
|
default:
|
|
3111
|
-
throw new ValidationError(`Unknown backend: ${config.backend}`);
|
|
3162
|
+
throw new ValidationError(`Unknown backend: ${String(config.backend)}`);
|
|
3112
3163
|
}
|
|
3113
3164
|
}
|
|
3114
3165
|
/** Initialize the client. Must be called before any other operations. */
|
|
@@ -3167,7 +3218,9 @@ var VelesDB = class {
|
|
|
3167
3218
|
}
|
|
3168
3219
|
async upsertBatch(collection, docs) {
|
|
3169
3220
|
this.ensureInitialized();
|
|
3170
|
-
validateDocsBatch(docs, (doc) =>
|
|
3221
|
+
validateDocsBatch(docs, (doc) => {
|
|
3222
|
+
validateDocument(doc, this.config);
|
|
3223
|
+
});
|
|
3171
3224
|
await this.backend.upsertBatch(collection, docs);
|
|
3172
3225
|
}
|
|
3173
3226
|
async delete(collection, id) {
|
|
@@ -3217,6 +3270,16 @@ var VelesDB = class {
|
|
|
3217
3270
|
this.ensureInitialized();
|
|
3218
3271
|
return multiQuerySearch2(this.backend, collection, vectors, options);
|
|
3219
3272
|
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Pure sparse search against a named sparse index.
|
|
3275
|
+
*
|
|
3276
|
+
* @see {@link SparseSearchNamedOptions} for the full pure-sparse vs hybrid comparison.
|
|
3277
|
+
* @see {@link VelesDB.search} for dense + sparse hybrid against a named index.
|
|
3278
|
+
*/
|
|
3279
|
+
async sparseSearchNamed(collection, query3, indexName, options) {
|
|
3280
|
+
this.ensureInitialized();
|
|
3281
|
+
return sparseSearchNamed2(this.backend, collection, query3, indexName, options);
|
|
3282
|
+
}
|
|
3220
3283
|
async query(collection, queryString, params, options) {
|
|
3221
3284
|
this.ensureInitialized();
|
|
3222
3285
|
return query2(this.backend, collection, queryString, params, options);
|
|
@@ -3673,13 +3736,14 @@ var VelesQLBuilder = class _VelesQLBuilder {
|
|
|
3673
3736
|
return `*${min}..${max}`;
|
|
3674
3737
|
}
|
|
3675
3738
|
buildWhereClause() {
|
|
3676
|
-
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3739
|
+
let result = "";
|
|
3740
|
+
for (const [idx, clause] of this.state.whereClauses.entries()) {
|
|
3741
|
+
if (idx === 0) {
|
|
3742
|
+
if (!clause) return "";
|
|
3743
|
+
result = clause;
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
3746
|
+
const operator = this.state.whereOperators[idx - 1] ?? "AND";
|
|
3683
3747
|
if (clause) {
|
|
3684
3748
|
result += ` ${operator} ${clause}`;
|
|
3685
3749
|
}
|
|
@@ -3693,13 +3757,14 @@ function velesql() {
|
|
|
3693
3757
|
|
|
3694
3758
|
// src/filter.ts
|
|
3695
3759
|
function isTypedFilter(input) {
|
|
3696
|
-
|
|
3760
|
+
const value = input;
|
|
3761
|
+
if (typeof value !== "object" || value === null) {
|
|
3697
3762
|
return false;
|
|
3698
3763
|
}
|
|
3699
|
-
if (!("condition" in
|
|
3764
|
+
if (!("condition" in value)) {
|
|
3700
3765
|
return false;
|
|
3701
3766
|
}
|
|
3702
|
-
const cond =
|
|
3767
|
+
const cond = value.condition;
|
|
3703
3768
|
return typeof cond === "object" && cond !== null;
|
|
3704
3769
|
}
|
|
3705
3770
|
function normalizeFilter(input) {
|
|
@@ -3834,6 +3899,47 @@ var f = {
|
|
|
3834
3899
|
return { condition: { type: "not", condition: filter.condition } };
|
|
3835
3900
|
}
|
|
3836
3901
|
};
|
|
3902
|
+
|
|
3903
|
+
// src/embed.ts
|
|
3904
|
+
var OpenAIEmbedder = class {
|
|
3905
|
+
constructor(options) {
|
|
3906
|
+
this.model = options.model ?? "text-embedding-3-small";
|
|
3907
|
+
this.apiKey = options.apiKey;
|
|
3908
|
+
this.baseUrl = options.baseUrl?.replace(/\/$/, "") ?? "https://api.openai.com/v1";
|
|
3909
|
+
this.requestedDimensions = options.dimensions;
|
|
3910
|
+
this.dimension = options.dimensions ?? 0;
|
|
3911
|
+
}
|
|
3912
|
+
async embed(texts) {
|
|
3913
|
+
if (texts.length === 0) return [];
|
|
3914
|
+
const body = { model: this.model, input: texts };
|
|
3915
|
+
if (this.requestedDimensions !== void 0) {
|
|
3916
|
+
body["dimensions"] = this.requestedDimensions;
|
|
3917
|
+
}
|
|
3918
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
3919
|
+
method: "POST",
|
|
3920
|
+
headers: {
|
|
3921
|
+
"Content-Type": "application/json",
|
|
3922
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3923
|
+
},
|
|
3924
|
+
body: JSON.stringify(body)
|
|
3925
|
+
});
|
|
3926
|
+
if (!response.ok) {
|
|
3927
|
+
const text = await response.text().catch(() => "");
|
|
3928
|
+
throw new Error(
|
|
3929
|
+
`OpenAI embeddings request failed: ${response.status} ${response.statusText} \u2014 ${text.slice(0, 500)}`
|
|
3930
|
+
);
|
|
3931
|
+
}
|
|
3932
|
+
const json = await response.json();
|
|
3933
|
+
const vectors = json.data.map((item) => item.embedding);
|
|
3934
|
+
if (this.dimension === 0) {
|
|
3935
|
+
for (const vec of vectors) {
|
|
3936
|
+
this.dimension = vec.length;
|
|
3937
|
+
break;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
return vectors;
|
|
3941
|
+
}
|
|
3942
|
+
};
|
|
3837
3943
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3838
3944
|
0 && (module.exports = {
|
|
3839
3945
|
AgentMemoryClient,
|
|
@@ -3864,6 +3970,7 @@ var f = {
|
|
|
3864
3970
|
IoError,
|
|
3865
3971
|
NodeNotFoundError,
|
|
3866
3972
|
NotFoundError,
|
|
3973
|
+
OpenAIEmbedder,
|
|
3867
3974
|
OverflowError,
|
|
3868
3975
|
PointNotFoundError,
|
|
3869
3976
|
QueryError,
|
package/dist/index.mjs
CHANGED
|
@@ -587,49 +587,49 @@ var VELES_ERROR_CODES = [
|
|
|
587
587
|
"VELES-035",
|
|
588
588
|
"VELES-036"
|
|
589
589
|
];
|
|
590
|
-
var CODE_TO_CLASS =
|
|
591
|
-
"VELES-001"
|
|
592
|
-
"VELES-002"
|
|
593
|
-
"VELES-003"
|
|
594
|
-
"VELES-004"
|
|
595
|
-
"VELES-005"
|
|
596
|
-
"VELES-006"
|
|
597
|
-
"VELES-007"
|
|
598
|
-
"VELES-008"
|
|
599
|
-
"VELES-009"
|
|
600
|
-
"VELES-010"
|
|
601
|
-
"VELES-011"
|
|
602
|
-
"VELES-012"
|
|
603
|
-
"VELES-013"
|
|
604
|
-
"VELES-014"
|
|
605
|
-
"VELES-015"
|
|
606
|
-
"VELES-016"
|
|
607
|
-
"VELES-017"
|
|
608
|
-
"VELES-018"
|
|
609
|
-
"VELES-019"
|
|
610
|
-
"VELES-020"
|
|
611
|
-
"VELES-021"
|
|
612
|
-
"VELES-022"
|
|
613
|
-
"VELES-023"
|
|
614
|
-
"VELES-024"
|
|
615
|
-
"VELES-025"
|
|
616
|
-
"VELES-026"
|
|
617
|
-
"VELES-027"
|
|
618
|
-
"VELES-028"
|
|
619
|
-
"VELES-029"
|
|
620
|
-
"VELES-030"
|
|
621
|
-
"VELES-031"
|
|
622
|
-
"VELES-032"
|
|
623
|
-
"VELES-033"
|
|
624
|
-
"VELES-034"
|
|
625
|
-
"VELES-035"
|
|
626
|
-
"VELES-036"
|
|
627
|
-
|
|
590
|
+
var CODE_TO_CLASS = /* @__PURE__ */ new Map([
|
|
591
|
+
["VELES-001", CollectionExistsError],
|
|
592
|
+
["VELES-002", CollectionNotFoundError],
|
|
593
|
+
["VELES-003", PointNotFoundError],
|
|
594
|
+
["VELES-004", DimensionMismatchError],
|
|
595
|
+
["VELES-005", InvalidVectorError],
|
|
596
|
+
["VELES-006", StorageError],
|
|
597
|
+
["VELES-007", IndexError],
|
|
598
|
+
["VELES-008", IndexCorruptedError],
|
|
599
|
+
["VELES-009", ConfigError],
|
|
600
|
+
["VELES-010", QueryError],
|
|
601
|
+
["VELES-011", IoError],
|
|
602
|
+
["VELES-012", SerializationError],
|
|
603
|
+
["VELES-013", InternalError],
|
|
604
|
+
["VELES-014", VectorNotAllowedError],
|
|
605
|
+
["VELES-015", SearchNotSupportedError],
|
|
606
|
+
["VELES-016", VectorRequiredError],
|
|
607
|
+
["VELES-017", SchemaValidationError],
|
|
608
|
+
["VELES-018", GraphNotSupportedError],
|
|
609
|
+
["VELES-019", EdgeExistsError],
|
|
610
|
+
["VELES-020", EdgeNotFoundError],
|
|
611
|
+
["VELES-021", InvalidEdgeLabelError],
|
|
612
|
+
["VELES-022", NodeNotFoundError],
|
|
613
|
+
["VELES-023", OverflowError],
|
|
614
|
+
["VELES-024", ColumnStoreError],
|
|
615
|
+
["VELES-025", GpuError],
|
|
616
|
+
["VELES-026", EpochMismatchError],
|
|
617
|
+
["VELES-027", GuardRailError],
|
|
618
|
+
["VELES-028", InvalidQuantizerConfigError],
|
|
619
|
+
["VELES-029", TrainingFailedError],
|
|
620
|
+
["VELES-030", SparseIndexError],
|
|
621
|
+
["VELES-031", DatabaseLockedError],
|
|
622
|
+
["VELES-032", InvalidDimensionError],
|
|
623
|
+
["VELES-033", AllocationFailedError],
|
|
624
|
+
["VELES-034", InvalidCollectionNameError],
|
|
625
|
+
["VELES-035", SnapshotBuildFailedError],
|
|
626
|
+
["VELES-036", IncompatibleSchemaVersionError]
|
|
627
|
+
]);
|
|
628
628
|
function parseVelesError(code, message) {
|
|
629
629
|
if (code === null || code === void 0) {
|
|
630
630
|
return new VelesError(message, "VELES-UNKNOWN");
|
|
631
631
|
}
|
|
632
|
-
const Cls = CODE_TO_CLASS
|
|
632
|
+
const Cls = CODE_TO_CLASS.get(code);
|
|
633
633
|
if (Cls !== void 0) {
|
|
634
634
|
return new Cls(message);
|
|
635
635
|
}
|
|
@@ -819,6 +819,9 @@ async function wasmMatchProceduralPatterns(_collection, _embedding, _k) {
|
|
|
819
819
|
}
|
|
820
820
|
|
|
821
821
|
// src/backends/wasm-wave4-stubs.ts
|
|
822
|
+
function wasmSparseSearchNamed(_c, _q, _idx, _o) {
|
|
823
|
+
return Promise.resolve(wasmNotSupported("Named sparse index search"));
|
|
824
|
+
}
|
|
822
825
|
function wasmRebuildIndex(_c) {
|
|
823
826
|
return Promise.resolve(wasmNotSupported("Index rebuild"));
|
|
824
827
|
}
|
|
@@ -1266,6 +1269,10 @@ var WasmBackend = class {
|
|
|
1266
1269
|
this.ensureInitialized();
|
|
1267
1270
|
return wasmGraphSearch(c, r);
|
|
1268
1271
|
}
|
|
1272
|
+
async sparseSearchNamed(c, q, idx, o) {
|
|
1273
|
+
this.ensureInitialized();
|
|
1274
|
+
return wasmSparseSearchNamed(c, q, idx, o);
|
|
1275
|
+
}
|
|
1269
1276
|
};
|
|
1270
1277
|
|
|
1271
1278
|
// src/backends/crud-backend.ts
|
|
@@ -1839,6 +1846,9 @@ async function search(transport, collection, query3, options) {
|
|
|
1839
1846
|
if (options?.sparseVector) {
|
|
1840
1847
|
body.sparse_vector = transport.sparseToRest(options.sparseVector);
|
|
1841
1848
|
}
|
|
1849
|
+
if (options?.sparseIndexName) {
|
|
1850
|
+
body.sparse_index = options.sparseIndexName;
|
|
1851
|
+
}
|
|
1842
1852
|
const response = await transport.requestJson(
|
|
1843
1853
|
"POST",
|
|
1844
1854
|
`${collectionPath(collection)}/search`,
|
|
@@ -1904,12 +1914,33 @@ async function multiQuerySearch(transport, collection, vectors, options) {
|
|
|
1904
1914
|
avg_weight: options?.fusionParams?.avgWeight,
|
|
1905
1915
|
max_weight: options?.fusionParams?.maxWeight,
|
|
1906
1916
|
hit_weight: options?.fusionParams?.hitWeight,
|
|
1917
|
+
dense_weight: options?.fusionParams?.denseWeight,
|
|
1918
|
+
sparse_weight: options?.fusionParams?.sparseWeight,
|
|
1907
1919
|
filter: options?.filter
|
|
1908
1920
|
}
|
|
1909
1921
|
);
|
|
1910
1922
|
throwOnError(response, `Collection '${collection}'`);
|
|
1911
1923
|
return response.data?.results ?? [];
|
|
1912
1924
|
}
|
|
1925
|
+
async function sparseSearchNamed(transport, collection, query3, indexName, options) {
|
|
1926
|
+
const body = {
|
|
1927
|
+
sparse_vectors: { [indexName]: transport.sparseToRest(query3) },
|
|
1928
|
+
sparse_index: indexName,
|
|
1929
|
+
top_k: options?.k ?? 10,
|
|
1930
|
+
filter: options?.filter,
|
|
1931
|
+
...searchQualityToMode(options?.quality)
|
|
1932
|
+
};
|
|
1933
|
+
if (options?.vector) {
|
|
1934
|
+
body.vector = Array.from(options.vector);
|
|
1935
|
+
}
|
|
1936
|
+
const response = await transport.requestJson(
|
|
1937
|
+
"POST",
|
|
1938
|
+
`${collectionPath(collection)}/search`,
|
|
1939
|
+
body
|
|
1940
|
+
);
|
|
1941
|
+
throwOnError(response, `Collection '${collection}'`);
|
|
1942
|
+
return response.data?.results ?? [];
|
|
1943
|
+
}
|
|
1913
1944
|
async function searchIds(transport, collection, query3, options) {
|
|
1914
1945
|
const queryVector = toNumberArray(query3);
|
|
1915
1946
|
const response = await transport.requestJson(
|
|
@@ -2597,6 +2628,10 @@ var RestBackend = class {
|
|
|
2597
2628
|
this.ensureInitialized();
|
|
2598
2629
|
return searchIds(buildSearchTransport(this.httpConfig), c, q, o);
|
|
2599
2630
|
}
|
|
2631
|
+
async sparseSearchNamed(c, q, idx, o) {
|
|
2632
|
+
this.ensureInitialized();
|
|
2633
|
+
return sparseSearchNamed(buildSearchTransport(this.httpConfig), c, q, idx, o);
|
|
2634
|
+
}
|
|
2600
2635
|
// Query
|
|
2601
2636
|
async query(c, q, p, o) {
|
|
2602
2637
|
this.ensureInitialized();
|
|
@@ -2766,7 +2801,8 @@ function validateDocsBatch(docs, validateDoc) {
|
|
|
2766
2801
|
}
|
|
2767
2802
|
}
|
|
2768
2803
|
function validateDocument(doc, config) {
|
|
2769
|
-
|
|
2804
|
+
const id = doc.id;
|
|
2805
|
+
if (id === void 0 || id === null) {
|
|
2770
2806
|
throw new ValidationError("Document ID is required");
|
|
2771
2807
|
}
|
|
2772
2808
|
requireVector(doc.vector, "Vector");
|
|
@@ -2803,6 +2839,11 @@ function hybridSearch2(backend, collection, vector, textQuery, options) {
|
|
|
2803
2839
|
requireNonEmptyString(textQuery, "Text query");
|
|
2804
2840
|
return backend.hybridSearch(collection, vector, textQuery, options);
|
|
2805
2841
|
}
|
|
2842
|
+
function sparseSearchNamed2(backend, collection, query3, indexName, options) {
|
|
2843
|
+
requireNonEmptyString(collection, "Collection name");
|
|
2844
|
+
requireNonEmptyString(indexName, "Index name");
|
|
2845
|
+
return backend.sparseSearchNamed(collection, query3, indexName, options);
|
|
2846
|
+
}
|
|
2806
2847
|
function multiQuerySearch2(backend, collection, vectors, options) {
|
|
2807
2848
|
if (!Array.isArray(vectors) || vectors.length === 0) {
|
|
2808
2849
|
throw new ValidationError("Vectors must be a non-empty array");
|
|
@@ -2816,11 +2857,15 @@ function trainPq2(backend, collection, options) {
|
|
|
2816
2857
|
return backend.trainPq(collection, options);
|
|
2817
2858
|
}
|
|
2818
2859
|
function streamInsert2(backend, config, collection, docs) {
|
|
2819
|
-
validateDocsBatch(docs, (doc) =>
|
|
2860
|
+
validateDocsBatch(docs, (doc) => {
|
|
2861
|
+
validateDocument(doc, config);
|
|
2862
|
+
});
|
|
2820
2863
|
return backend.streamInsert(collection, docs);
|
|
2821
2864
|
}
|
|
2822
2865
|
function streamUpsertPoints2(backend, config, collection, docs) {
|
|
2823
|
-
validateDocsBatch(docs, (doc) =>
|
|
2866
|
+
validateDocsBatch(docs, (doc) => {
|
|
2867
|
+
validateDocument(doc, config);
|
|
2868
|
+
});
|
|
2824
2869
|
return backend.streamUpsertPoints(collection, docs);
|
|
2825
2870
|
}
|
|
2826
2871
|
function scroll2(backend, collection, request2) {
|
|
@@ -2953,13 +2998,14 @@ var VelesDB = class {
|
|
|
2953
2998
|
this.backend = this.createBackend(config);
|
|
2954
2999
|
}
|
|
2955
3000
|
validateConfig(config) {
|
|
2956
|
-
|
|
3001
|
+
const backend = config.backend;
|
|
3002
|
+
if (!backend) {
|
|
2957
3003
|
throw new ValidationError("Backend type is required");
|
|
2958
3004
|
}
|
|
2959
|
-
if (
|
|
2960
|
-
throw new ValidationError(`Invalid backend type: ${
|
|
3005
|
+
if (backend !== "wasm" && backend !== "rest") {
|
|
3006
|
+
throw new ValidationError(`Invalid backend type: ${backend}. Use 'wasm' or 'rest'`);
|
|
2961
3007
|
}
|
|
2962
|
-
if (
|
|
3008
|
+
if (backend === "rest" && !config.url) {
|
|
2963
3009
|
throw new ValidationError("URL is required for REST backend");
|
|
2964
3010
|
}
|
|
2965
3011
|
}
|
|
@@ -2967,10 +3013,14 @@ var VelesDB = class {
|
|
|
2967
3013
|
switch (config.backend) {
|
|
2968
3014
|
case "wasm":
|
|
2969
3015
|
return new WasmBackend();
|
|
2970
|
-
case "rest":
|
|
3016
|
+
case "rest": {
|
|
3017
|
+
if (!config.url) {
|
|
3018
|
+
throw new ValidationError("URL is required for REST backend");
|
|
3019
|
+
}
|
|
2971
3020
|
return new RestBackend(config.url, config.apiKey, config.timeout);
|
|
3021
|
+
}
|
|
2972
3022
|
default:
|
|
2973
|
-
throw new ValidationError(`Unknown backend: ${config.backend}`);
|
|
3023
|
+
throw new ValidationError(`Unknown backend: ${String(config.backend)}`);
|
|
2974
3024
|
}
|
|
2975
3025
|
}
|
|
2976
3026
|
/** Initialize the client. Must be called before any other operations. */
|
|
@@ -3029,7 +3079,9 @@ var VelesDB = class {
|
|
|
3029
3079
|
}
|
|
3030
3080
|
async upsertBatch(collection, docs) {
|
|
3031
3081
|
this.ensureInitialized();
|
|
3032
|
-
validateDocsBatch(docs, (doc) =>
|
|
3082
|
+
validateDocsBatch(docs, (doc) => {
|
|
3083
|
+
validateDocument(doc, this.config);
|
|
3084
|
+
});
|
|
3033
3085
|
await this.backend.upsertBatch(collection, docs);
|
|
3034
3086
|
}
|
|
3035
3087
|
async delete(collection, id) {
|
|
@@ -3079,6 +3131,16 @@ var VelesDB = class {
|
|
|
3079
3131
|
this.ensureInitialized();
|
|
3080
3132
|
return multiQuerySearch2(this.backend, collection, vectors, options);
|
|
3081
3133
|
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Pure sparse search against a named sparse index.
|
|
3136
|
+
*
|
|
3137
|
+
* @see {@link SparseSearchNamedOptions} for the full pure-sparse vs hybrid comparison.
|
|
3138
|
+
* @see {@link VelesDB.search} for dense + sparse hybrid against a named index.
|
|
3139
|
+
*/
|
|
3140
|
+
async sparseSearchNamed(collection, query3, indexName, options) {
|
|
3141
|
+
this.ensureInitialized();
|
|
3142
|
+
return sparseSearchNamed2(this.backend, collection, query3, indexName, options);
|
|
3143
|
+
}
|
|
3082
3144
|
async query(collection, queryString, params, options) {
|
|
3083
3145
|
this.ensureInitialized();
|
|
3084
3146
|
return query2(this.backend, collection, queryString, params, options);
|
|
@@ -3535,13 +3597,14 @@ var VelesQLBuilder = class _VelesQLBuilder {
|
|
|
3535
3597
|
return `*${min}..${max}`;
|
|
3536
3598
|
}
|
|
3537
3599
|
buildWhereClause() {
|
|
3538
|
-
|
|
3539
|
-
const
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3600
|
+
let result = "";
|
|
3601
|
+
for (const [idx, clause] of this.state.whereClauses.entries()) {
|
|
3602
|
+
if (idx === 0) {
|
|
3603
|
+
if (!clause) return "";
|
|
3604
|
+
result = clause;
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3607
|
+
const operator = this.state.whereOperators[idx - 1] ?? "AND";
|
|
3545
3608
|
if (clause) {
|
|
3546
3609
|
result += ` ${operator} ${clause}`;
|
|
3547
3610
|
}
|
|
@@ -3555,13 +3618,14 @@ function velesql() {
|
|
|
3555
3618
|
|
|
3556
3619
|
// src/filter.ts
|
|
3557
3620
|
function isTypedFilter(input) {
|
|
3558
|
-
|
|
3621
|
+
const value = input;
|
|
3622
|
+
if (typeof value !== "object" || value === null) {
|
|
3559
3623
|
return false;
|
|
3560
3624
|
}
|
|
3561
|
-
if (!("condition" in
|
|
3625
|
+
if (!("condition" in value)) {
|
|
3562
3626
|
return false;
|
|
3563
3627
|
}
|
|
3564
|
-
const cond =
|
|
3628
|
+
const cond = value.condition;
|
|
3565
3629
|
return typeof cond === "object" && cond !== null;
|
|
3566
3630
|
}
|
|
3567
3631
|
function normalizeFilter(input) {
|
|
@@ -3696,6 +3760,47 @@ var f = {
|
|
|
3696
3760
|
return { condition: { type: "not", condition: filter.condition } };
|
|
3697
3761
|
}
|
|
3698
3762
|
};
|
|
3763
|
+
|
|
3764
|
+
// src/embed.ts
|
|
3765
|
+
var OpenAIEmbedder = class {
|
|
3766
|
+
constructor(options) {
|
|
3767
|
+
this.model = options.model ?? "text-embedding-3-small";
|
|
3768
|
+
this.apiKey = options.apiKey;
|
|
3769
|
+
this.baseUrl = options.baseUrl?.replace(/\/$/, "") ?? "https://api.openai.com/v1";
|
|
3770
|
+
this.requestedDimensions = options.dimensions;
|
|
3771
|
+
this.dimension = options.dimensions ?? 0;
|
|
3772
|
+
}
|
|
3773
|
+
async embed(texts) {
|
|
3774
|
+
if (texts.length === 0) return [];
|
|
3775
|
+
const body = { model: this.model, input: texts };
|
|
3776
|
+
if (this.requestedDimensions !== void 0) {
|
|
3777
|
+
body["dimensions"] = this.requestedDimensions;
|
|
3778
|
+
}
|
|
3779
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
3780
|
+
method: "POST",
|
|
3781
|
+
headers: {
|
|
3782
|
+
"Content-Type": "application/json",
|
|
3783
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3784
|
+
},
|
|
3785
|
+
body: JSON.stringify(body)
|
|
3786
|
+
});
|
|
3787
|
+
if (!response.ok) {
|
|
3788
|
+
const text = await response.text().catch(() => "");
|
|
3789
|
+
throw new Error(
|
|
3790
|
+
`OpenAI embeddings request failed: ${response.status} ${response.statusText} \u2014 ${text.slice(0, 500)}`
|
|
3791
|
+
);
|
|
3792
|
+
}
|
|
3793
|
+
const json = await response.json();
|
|
3794
|
+
const vectors = json.data.map((item) => item.embedding);
|
|
3795
|
+
if (this.dimension === 0) {
|
|
3796
|
+
for (const vec of vectors) {
|
|
3797
|
+
this.dimension = vec.length;
|
|
3798
|
+
break;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
return vectors;
|
|
3802
|
+
}
|
|
3803
|
+
};
|
|
3699
3804
|
export {
|
|
3700
3805
|
AgentMemoryClient,
|
|
3701
3806
|
AllocationFailedError,
|
|
@@ -3725,6 +3830,7 @@ export {
|
|
|
3725
3830
|
IoError,
|
|
3726
3831
|
NodeNotFoundError,
|
|
3727
3832
|
NotFoundError,
|
|
3833
|
+
OpenAIEmbedder,
|
|
3728
3834
|
OverflowError,
|
|
3729
3835
|
PointNotFoundError,
|
|
3730
3836
|
QueryError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wiscale/velesdb-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "VelesDB TypeScript SDK: The Local Vector Database for AI & RAG. Microsecond semantic search in Browser & Node.js.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"test": "vitest run",
|
|
23
23
|
"test:watch": "vitest",
|
|
24
24
|
"test:coverage": "vitest run --coverage",
|
|
25
|
-
"lint": "eslint src
|
|
26
|
-
"lint:fix": "eslint src --
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"lint:fix": "eslint src --fix",
|
|
27
27
|
"format": "prettier --write src",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"prepublishOnly": "npm run build"
|
|
@@ -54,15 +54,17 @@
|
|
|
54
54
|
"url": "https://github.com/cyberlife-coder/VelesDB/issues"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
+
"@eslint/js": "^10.0.1",
|
|
57
58
|
"@types/node": "^25.6.0",
|
|
58
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
59
|
-
"@typescript-eslint/parser": "^
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
60
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
60
61
|
"@vitest/coverage-v8": "^4.0.16",
|
|
61
|
-
"eslint": "^
|
|
62
|
-
"eslint-config-prettier": "^
|
|
62
|
+
"eslint": "^10.4.0",
|
|
63
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
64
|
"prettier": "^3.0.0",
|
|
64
65
|
"tsup": "^8.0.0",
|
|
65
|
-
"typescript": "^
|
|
66
|
+
"typescript": "^6.0.3",
|
|
67
|
+
"typescript-eslint": "^8.0.0",
|
|
66
68
|
"vitest": "^4.0.16"
|
|
67
69
|
},
|
|
68
70
|
"dependencies": {
|