@lancedb/lancedb 0.5.1 → 0.7.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/Cargo.toml +3 -3
- package/biome.json +19 -3
- package/dist/arrow.d.ts +42 -7
- package/dist/arrow.js +6 -5
- package/dist/connection.d.ts +55 -29
- package/dist/connection.js +22 -74
- package/dist/embedding/embedding_function.d.ts +11 -3
- package/dist/embedding/embedding_function.js +36 -12
- package/dist/embedding/openai.d.ts +6 -5
- package/dist/embedding/openai.js +4 -2
- package/dist/embedding/registry.d.ts +10 -11
- package/dist/embedding/registry.js +4 -0
- package/dist/index.d.ts +51 -3
- package/dist/index.js +28 -4
- package/dist/merge.d.ts +54 -0
- package/dist/merge.js +64 -0
- package/dist/native.d.ts +34 -7
- package/dist/native.js +26 -9
- package/dist/query.d.ts +51 -16
- package/dist/query.js +122 -21
- package/dist/remote/client.d.ts +28 -0
- package/dist/remote/client.js +172 -0
- package/dist/remote/connection.d.ts +25 -0
- package/dist/remote/connection.js +110 -0
- package/dist/remote/index.d.ts +3 -0
- package/dist/remote/index.js +9 -0
- package/dist/remote/table.d.ts +42 -0
- package/dist/remote/table.js +179 -0
- package/dist/sanitize.d.ts +3 -2
- package/dist/sanitize.js +55 -1
- package/dist/table.d.ts +116 -25
- package/dist/table.js +117 -233
- package/dist/util.d.ts +14 -0
- package/dist/util.js +65 -0
- package/examples/ann_indexes.ts +49 -0
- package/examples/basic.ts +149 -0
- package/examples/embedding.ts +83 -0
- package/examples/filtering.ts +34 -0
- package/examples/jsconfig.json +27 -0
- package/examples/package-lock.json +79 -0
- package/examples/package.json +18 -0
- package/examples/search.ts +37 -0
- package/lancedb/arrow.ts +87 -24
- package/lancedb/connection.ts +115 -92
- package/lancedb/embedding/embedding_function.ts +48 -16
- package/lancedb/embedding/openai.ts +11 -6
- package/lancedb/embedding/registry.ts +38 -22
- package/lancedb/index.ts +101 -2
- package/lancedb/merge.ts +70 -0
- package/lancedb/query.ts +168 -39
- package/lancedb/remote/client.ts +221 -0
- package/lancedb/remote/connection.ts +201 -0
- package/lancedb/remote/index.ts +3 -0
- package/lancedb/remote/table.ts +226 -0
- package/lancedb/sanitize.ts +73 -1
- package/lancedb/table.ts +344 -101
- package/lancedb/util.ts +69 -0
- package/native.d.ts +208 -0
- package/nodejs-artifacts/arrow.d.ts +42 -7
- package/nodejs-artifacts/arrow.js +6 -5
- package/nodejs-artifacts/connection.d.ts +55 -29
- package/nodejs-artifacts/connection.js +22 -74
- package/nodejs-artifacts/embedding/embedding_function.d.ts +11 -3
- package/nodejs-artifacts/embedding/embedding_function.js +36 -12
- package/nodejs-artifacts/embedding/openai.d.ts +6 -5
- package/nodejs-artifacts/embedding/openai.js +4 -2
- package/nodejs-artifacts/embedding/registry.d.ts +10 -11
- package/nodejs-artifacts/embedding/registry.js +4 -0
- package/nodejs-artifacts/index.d.ts +51 -3
- package/nodejs-artifacts/index.js +28 -4
- package/nodejs-artifacts/merge.d.ts +54 -0
- package/nodejs-artifacts/merge.js +64 -0
- package/nodejs-artifacts/native.d.ts +34 -7
- package/nodejs-artifacts/native.js +26 -9
- package/nodejs-artifacts/query.d.ts +51 -16
- package/nodejs-artifacts/query.js +122 -21
- package/nodejs-artifacts/remote/client.d.ts +28 -0
- package/nodejs-artifacts/remote/client.js +172 -0
- package/nodejs-artifacts/remote/connection.d.ts +25 -0
- package/nodejs-artifacts/remote/connection.js +110 -0
- package/nodejs-artifacts/remote/index.d.ts +3 -0
- package/nodejs-artifacts/remote/index.js +9 -0
- package/nodejs-artifacts/remote/table.d.ts +42 -0
- package/nodejs-artifacts/remote/table.js +179 -0
- package/nodejs-artifacts/sanitize.d.ts +3 -2
- package/nodejs-artifacts/sanitize.js +55 -1
- package/nodejs-artifacts/table.d.ts +116 -25
- package/nodejs-artifacts/table.js +117 -233
- package/nodejs-artifacts/util.d.ts +14 -0
- package/nodejs-artifacts/util.js +65 -0
- package/package.json +25 -11
package/lancedb/merge.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Data, fromDataToBuffer } from "./arrow";
|
|
2
|
+
import { NativeMergeInsertBuilder } from "./native";
|
|
3
|
+
|
|
4
|
+
/** A builder used to create and run a merge insert operation */
|
|
5
|
+
export class MergeInsertBuilder {
|
|
6
|
+
#native: NativeMergeInsertBuilder;
|
|
7
|
+
|
|
8
|
+
/** Construct a MergeInsertBuilder. __Internal use only.__ */
|
|
9
|
+
constructor(native: NativeMergeInsertBuilder) {
|
|
10
|
+
this.#native = native;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Rows that exist in both the source table (new data) and
|
|
15
|
+
* the target table (old data) will be updated, replacing
|
|
16
|
+
* the old row with the corresponding matching row.
|
|
17
|
+
*
|
|
18
|
+
* If there are multiple matches then the behavior is undefined.
|
|
19
|
+
* Currently this causes multiple copies of the row to be created
|
|
20
|
+
* but that behavior is subject to change.
|
|
21
|
+
*
|
|
22
|
+
* An optional condition may be specified. If it is, then only
|
|
23
|
+
* matched rows that satisfy the condtion will be updated. Any
|
|
24
|
+
* rows that do not satisfy the condition will be left as they
|
|
25
|
+
* are. Failing to satisfy the condition does not cause a
|
|
26
|
+
* "matched row" to become a "not matched" row.
|
|
27
|
+
*
|
|
28
|
+
* The condition should be an SQL string. Use the prefix
|
|
29
|
+
* target. to refer to rows in the target table (old data)
|
|
30
|
+
* and the prefix source. to refer to rows in the source
|
|
31
|
+
* table (new data).
|
|
32
|
+
*
|
|
33
|
+
* For example, "target.last_update < source.last_update"
|
|
34
|
+
*/
|
|
35
|
+
whenMatchedUpdateAll(options?: { where: string }): MergeInsertBuilder {
|
|
36
|
+
return new MergeInsertBuilder(
|
|
37
|
+
this.#native.whenMatchedUpdateAll(options?.where),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Rows that exist only in the source table (new data) should
|
|
42
|
+
* be inserted into the target table.
|
|
43
|
+
*/
|
|
44
|
+
whenNotMatchedInsertAll(): MergeInsertBuilder {
|
|
45
|
+
return new MergeInsertBuilder(this.#native.whenNotMatchedInsertAll());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Rows that exist only in the target table (old data) will be
|
|
49
|
+
* deleted. An optional condition can be provided to limit what
|
|
50
|
+
* data is deleted.
|
|
51
|
+
*
|
|
52
|
+
* @param options.where - An optional condition to limit what data is deleted
|
|
53
|
+
*/
|
|
54
|
+
whenNotMatchedBySourceDelete(options?: {
|
|
55
|
+
where: string;
|
|
56
|
+
}): MergeInsertBuilder {
|
|
57
|
+
return new MergeInsertBuilder(
|
|
58
|
+
this.#native.whenNotMatchedBySourceDelete(options?.where),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Executes the merge insert operation
|
|
63
|
+
*
|
|
64
|
+
* Nothing is returned but the `Table` is updated
|
|
65
|
+
*/
|
|
66
|
+
async execute(data: Data): Promise<void> {
|
|
67
|
+
const buffer = await fromDataToBuffer(data);
|
|
68
|
+
await this.#native.execute(buffer);
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lancedb/query.ts
CHANGED
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
Table as ArrowTable,
|
|
17
|
+
type IntoVector,
|
|
18
|
+
RecordBatch,
|
|
19
|
+
tableFromIPC,
|
|
20
|
+
} from "./arrow";
|
|
16
21
|
import { type IvfPqOptions } from "./indices";
|
|
17
22
|
import {
|
|
18
23
|
RecordBatchIterator as NativeBatchIterator,
|
|
@@ -50,16 +55,60 @@ export class RecordBatchIterator implements AsyncIterator<RecordBatch> {
|
|
|
50
55
|
}
|
|
51
56
|
/* eslint-enable */
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
export class QueryBase<
|
|
58
|
+
class RecordBatchIterable<
|
|
55
59
|
NativeQueryType extends NativeQuery | NativeVectorQuery,
|
|
56
|
-
QueryType,
|
|
57
60
|
> implements AsyncIterable<RecordBatch>
|
|
58
61
|
{
|
|
59
|
-
|
|
62
|
+
private inner: NativeQueryType;
|
|
63
|
+
private options?: QueryExecutionOptions;
|
|
64
|
+
|
|
65
|
+
constructor(inner: NativeQueryType, options?: QueryExecutionOptions) {
|
|
66
|
+
this.inner = inner;
|
|
67
|
+
this.options = options;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// biome-ignore lint/suspicious/noExplicitAny: skip
|
|
71
|
+
[Symbol.asyncIterator](): AsyncIterator<RecordBatch<any>, any, undefined> {
|
|
72
|
+
return new RecordBatchIterator(
|
|
73
|
+
this.inner.execute(this.options?.maxBatchLength),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Options that control the behavior of a particular query execution
|
|
80
|
+
*/
|
|
81
|
+
export interface QueryExecutionOptions {
|
|
82
|
+
/**
|
|
83
|
+
* The maximum number of rows to return in a single batch
|
|
84
|
+
*
|
|
85
|
+
* Batches may have fewer rows if the underlying data is stored
|
|
86
|
+
* in smaller chunks.
|
|
87
|
+
*/
|
|
88
|
+
maxBatchLength?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Common methods supported by all query types */
|
|
92
|
+
export class QueryBase<NativeQueryType extends NativeQuery | NativeVectorQuery>
|
|
93
|
+
implements AsyncIterable<RecordBatch>
|
|
94
|
+
{
|
|
95
|
+
protected constructor(
|
|
96
|
+
protected inner: NativeQueryType | Promise<NativeQueryType>,
|
|
97
|
+
) {
|
|
60
98
|
// intentionally empty
|
|
61
99
|
}
|
|
62
100
|
|
|
101
|
+
// call a function on the inner (either a promise or the actual object)
|
|
102
|
+
protected doCall(fn: (inner: NativeQueryType) => void) {
|
|
103
|
+
if (this.inner instanceof Promise) {
|
|
104
|
+
this.inner = this.inner.then((inner) => {
|
|
105
|
+
fn(inner);
|
|
106
|
+
return inner;
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
fn(this.inner);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
63
112
|
/**
|
|
64
113
|
* A filter statement to be applied to this query.
|
|
65
114
|
*
|
|
@@ -72,9 +121,17 @@ export class QueryBase<
|
|
|
72
121
|
* Filtering performance can often be improved by creating a scalar index
|
|
73
122
|
* on the filter column(s).
|
|
74
123
|
*/
|
|
75
|
-
where(predicate: string):
|
|
76
|
-
this.inner.onlyIf(predicate);
|
|
77
|
-
return this
|
|
124
|
+
where(predicate: string): this {
|
|
125
|
+
this.doCall((inner: NativeQueryType) => inner.onlyIf(predicate));
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A filter statement to be applied to this query.
|
|
130
|
+
* @alias where
|
|
131
|
+
* @deprecated Use `where` instead
|
|
132
|
+
*/
|
|
133
|
+
filter(predicate: string): this {
|
|
134
|
+
return this.where(predicate);
|
|
78
135
|
}
|
|
79
136
|
|
|
80
137
|
/**
|
|
@@ -108,9 +165,12 @@ export class QueryBase<
|
|
|
108
165
|
* object insertion order is easy to get wrong and `Map` is more foolproof.
|
|
109
166
|
*/
|
|
110
167
|
select(
|
|
111
|
-
columns: string[] | Map<string, string> | Record<string, string
|
|
112
|
-
):
|
|
168
|
+
columns: string[] | Map<string, string> | Record<string, string> | string,
|
|
169
|
+
): this {
|
|
113
170
|
let columnTuples: [string, string][];
|
|
171
|
+
if (typeof columns === "string") {
|
|
172
|
+
columns = [columns];
|
|
173
|
+
}
|
|
114
174
|
if (Array.isArray(columns)) {
|
|
115
175
|
columnTuples = columns.map((c) => [c, c]);
|
|
116
176
|
} else if (columns instanceof Map) {
|
|
@@ -118,8 +178,10 @@ export class QueryBase<
|
|
|
118
178
|
} else {
|
|
119
179
|
columnTuples = Object.entries(columns);
|
|
120
180
|
}
|
|
121
|
-
this.inner
|
|
122
|
-
|
|
181
|
+
this.doCall((inner: NativeQueryType) => {
|
|
182
|
+
inner.select(columnTuples);
|
|
183
|
+
});
|
|
184
|
+
return this;
|
|
123
185
|
}
|
|
124
186
|
|
|
125
187
|
/**
|
|
@@ -128,13 +190,19 @@ export class QueryBase<
|
|
|
128
190
|
* By default, a plain search has no limit. If this method is not
|
|
129
191
|
* called then every valid row from the table will be returned.
|
|
130
192
|
*/
|
|
131
|
-
limit(limit: number):
|
|
132
|
-
this.inner.limit(limit);
|
|
133
|
-
return this
|
|
193
|
+
limit(limit: number): this {
|
|
194
|
+
this.doCall((inner: NativeQueryType) => inner.limit(limit));
|
|
195
|
+
return this;
|
|
134
196
|
}
|
|
135
197
|
|
|
136
|
-
protected nativeExecute(
|
|
137
|
-
|
|
198
|
+
protected nativeExecute(
|
|
199
|
+
options?: Partial<QueryExecutionOptions>,
|
|
200
|
+
): Promise<NativeBatchIterator> {
|
|
201
|
+
if (this.inner instanceof Promise) {
|
|
202
|
+
return this.inner.then((inner) => inner.execute(options?.maxBatchLength));
|
|
203
|
+
} else {
|
|
204
|
+
return this.inner.execute(options?.maxBatchLength);
|
|
205
|
+
}
|
|
138
206
|
}
|
|
139
207
|
|
|
140
208
|
/**
|
|
@@ -148,8 +216,10 @@ export class QueryBase<
|
|
|
148
216
|
* single query)
|
|
149
217
|
*
|
|
150
218
|
*/
|
|
151
|
-
protected execute(
|
|
152
|
-
|
|
219
|
+
protected execute(
|
|
220
|
+
options?: Partial<QueryExecutionOptions>,
|
|
221
|
+
): RecordBatchIterator {
|
|
222
|
+
return new RecordBatchIterator(this.nativeExecute(options));
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
// biome-ignore lint/suspicious/noExplicitAny: skip
|
|
@@ -159,21 +229,48 @@ export class QueryBase<
|
|
|
159
229
|
}
|
|
160
230
|
|
|
161
231
|
/** Collect the results as an Arrow @see {@link ArrowTable}. */
|
|
162
|
-
async toArrow(): Promise<ArrowTable> {
|
|
232
|
+
async toArrow(options?: Partial<QueryExecutionOptions>): Promise<ArrowTable> {
|
|
163
233
|
const batches = [];
|
|
164
|
-
|
|
234
|
+
let inner;
|
|
235
|
+
if (this.inner instanceof Promise) {
|
|
236
|
+
inner = await this.inner;
|
|
237
|
+
} else {
|
|
238
|
+
inner = this.inner;
|
|
239
|
+
}
|
|
240
|
+
for await (const batch of new RecordBatchIterable(inner, options)) {
|
|
165
241
|
batches.push(batch);
|
|
166
242
|
}
|
|
167
243
|
return new ArrowTable(batches);
|
|
168
244
|
}
|
|
169
245
|
|
|
170
246
|
/** Collect the results as an array of objects. */
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
247
|
+
// biome-ignore lint/suspicious/noExplicitAny: arrow.toArrow() returns any[]
|
|
248
|
+
async toArray(options?: Partial<QueryExecutionOptions>): Promise<any[]> {
|
|
249
|
+
const tbl = await this.toArrow(options);
|
|
175
250
|
return tbl.toArray();
|
|
176
251
|
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generates an explanation of the query execution plan.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* import * as lancedb from "@lancedb/lancedb"
|
|
258
|
+
* const db = await lancedb.connect("./.lancedb");
|
|
259
|
+
* const table = await db.createTable("my_table", [
|
|
260
|
+
* { vector: [1.1, 0.9], id: "1" },
|
|
261
|
+
* ]);
|
|
262
|
+
* const plan = await table.query().nearestTo([0.5, 0.2]).explainPlan();
|
|
263
|
+
*
|
|
264
|
+
* @param verbose - If true, provides a more detailed explanation. Defaults to false.
|
|
265
|
+
* @returns A Promise that resolves to a string containing the query execution plan explanation.
|
|
266
|
+
*/
|
|
267
|
+
async explainPlan(verbose = false): Promise<string> {
|
|
268
|
+
if (this.inner instanceof Promise) {
|
|
269
|
+
return this.inner.then((inner) => inner.explainPlan(verbose));
|
|
270
|
+
} else {
|
|
271
|
+
return this.inner.explainPlan(verbose);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
177
274
|
}
|
|
178
275
|
|
|
179
276
|
/**
|
|
@@ -188,8 +285,8 @@ export interface ExecutableQuery {}
|
|
|
188
285
|
*
|
|
189
286
|
* This builder can be reused to execute the query many times.
|
|
190
287
|
*/
|
|
191
|
-
export class VectorQuery extends QueryBase<NativeVectorQuery
|
|
192
|
-
constructor(inner: NativeVectorQuery) {
|
|
288
|
+
export class VectorQuery extends QueryBase<NativeVectorQuery> {
|
|
289
|
+
constructor(inner: NativeVectorQuery | Promise<NativeVectorQuery>) {
|
|
193
290
|
super(inner);
|
|
194
291
|
}
|
|
195
292
|
|
|
@@ -216,7 +313,8 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
216
313
|
* you the desired recall.
|
|
217
314
|
*/
|
|
218
315
|
nprobes(nprobes: number): VectorQuery {
|
|
219
|
-
|
|
316
|
+
super.doCall((inner) => inner.nprobes(nprobes));
|
|
317
|
+
|
|
220
318
|
return this;
|
|
221
319
|
}
|
|
222
320
|
|
|
@@ -230,7 +328,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
230
328
|
* whose data type is a fixed-size-list of floats.
|
|
231
329
|
*/
|
|
232
330
|
column(column: string): VectorQuery {
|
|
233
|
-
|
|
331
|
+
super.doCall((inner) => inner.column(column));
|
|
234
332
|
return this;
|
|
235
333
|
}
|
|
236
334
|
|
|
@@ -248,8 +346,10 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
248
346
|
*
|
|
249
347
|
* By default "l2" is used.
|
|
250
348
|
*/
|
|
251
|
-
distanceType(
|
|
252
|
-
|
|
349
|
+
distanceType(
|
|
350
|
+
distanceType: Required<IvfPqOptions>["distanceType"],
|
|
351
|
+
): VectorQuery {
|
|
352
|
+
super.doCall((inner) => inner.distanceType(distanceType));
|
|
253
353
|
return this;
|
|
254
354
|
}
|
|
255
355
|
|
|
@@ -283,7 +383,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
283
383
|
* distance between the query vector and the actual uncompressed vector.
|
|
284
384
|
*/
|
|
285
385
|
refineFactor(refineFactor: number): VectorQuery {
|
|
286
|
-
|
|
386
|
+
super.doCall((inner) => inner.refineFactor(refineFactor));
|
|
287
387
|
return this;
|
|
288
388
|
}
|
|
289
389
|
|
|
@@ -308,7 +408,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
308
408
|
* factor can often help restore some of the results lost by post filtering.
|
|
309
409
|
*/
|
|
310
410
|
postfilter(): VectorQuery {
|
|
311
|
-
|
|
411
|
+
super.doCall((inner) => inner.postfilter());
|
|
312
412
|
return this;
|
|
313
413
|
}
|
|
314
414
|
|
|
@@ -322,13 +422,13 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
|
|
|
322
422
|
* calculate your recall to select an appropriate value for nprobes.
|
|
323
423
|
*/
|
|
324
424
|
bypassVectorIndex(): VectorQuery {
|
|
325
|
-
|
|
425
|
+
super.doCall((inner) => inner.bypassVectorIndex());
|
|
326
426
|
return this;
|
|
327
427
|
}
|
|
328
428
|
}
|
|
329
429
|
|
|
330
430
|
/** A builder for LanceDB queries. */
|
|
331
|
-
export class Query extends QueryBase<NativeQuery
|
|
431
|
+
export class Query extends QueryBase<NativeQuery> {
|
|
332
432
|
constructor(tbl: NativeTable) {
|
|
333
433
|
super(tbl.query());
|
|
334
434
|
}
|
|
@@ -370,9 +470,38 @@ export class Query extends QueryBase<NativeQuery, Query> {
|
|
|
370
470
|
* Vector searches always have a `limit`. If `limit` has not been called then
|
|
371
471
|
* a default `limit` of 10 will be used. @see {@link Query#limit}
|
|
372
472
|
*/
|
|
373
|
-
nearestTo(vector:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
473
|
+
nearestTo(vector: IntoVector): VectorQuery {
|
|
474
|
+
if (this.inner instanceof Promise) {
|
|
475
|
+
const nativeQuery = this.inner.then(async (inner) => {
|
|
476
|
+
if (vector instanceof Promise) {
|
|
477
|
+
const arr = await vector.then((v) => Float32Array.from(v));
|
|
478
|
+
return inner.nearestTo(arr);
|
|
479
|
+
} else {
|
|
480
|
+
return inner.nearestTo(Float32Array.from(vector));
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return new VectorQuery(nativeQuery);
|
|
484
|
+
}
|
|
485
|
+
if (vector instanceof Promise) {
|
|
486
|
+
const res = (async () => {
|
|
487
|
+
try {
|
|
488
|
+
const v = await vector;
|
|
489
|
+
const arr = Float32Array.from(v);
|
|
490
|
+
//
|
|
491
|
+
// biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
|
|
492
|
+
const value: any = this.nearestTo(arr);
|
|
493
|
+
const inner = value.inner as
|
|
494
|
+
| NativeVectorQuery
|
|
495
|
+
| Promise<NativeVectorQuery>;
|
|
496
|
+
return inner;
|
|
497
|
+
} catch (e) {
|
|
498
|
+
return Promise.reject(e);
|
|
499
|
+
}
|
|
500
|
+
})();
|
|
501
|
+
return new VectorQuery(res);
|
|
502
|
+
} else {
|
|
503
|
+
const vectorQuery = this.inner.nearestTo(Float32Array.from(vector));
|
|
504
|
+
return new VectorQuery(vectorQuery);
|
|
505
|
+
}
|
|
377
506
|
}
|
|
378
507
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Copyright 2023 LanceDB Developers.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import axios, {
|
|
16
|
+
AxiosError,
|
|
17
|
+
type AxiosResponse,
|
|
18
|
+
type ResponseType,
|
|
19
|
+
} from "axios";
|
|
20
|
+
import { Table as ArrowTable } from "../arrow";
|
|
21
|
+
import { tableFromIPC } from "../arrow";
|
|
22
|
+
import { VectorQuery } from "../query";
|
|
23
|
+
|
|
24
|
+
export class RestfulLanceDBClient {
|
|
25
|
+
#dbName: string;
|
|
26
|
+
#region: string;
|
|
27
|
+
#apiKey: string;
|
|
28
|
+
#hostOverride?: string;
|
|
29
|
+
#closed: boolean = false;
|
|
30
|
+
#connectionTimeout: number = 12 * 1000; // 12 seconds;
|
|
31
|
+
#readTimeout: number = 30 * 1000; // 30 seconds;
|
|
32
|
+
#session?: import("axios").AxiosInstance;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
dbName: string,
|
|
36
|
+
apiKey: string,
|
|
37
|
+
region: string,
|
|
38
|
+
hostOverride?: string,
|
|
39
|
+
connectionTimeout?: number,
|
|
40
|
+
readTimeout?: number,
|
|
41
|
+
) {
|
|
42
|
+
this.#dbName = dbName;
|
|
43
|
+
this.#apiKey = apiKey;
|
|
44
|
+
this.#region = region;
|
|
45
|
+
this.#hostOverride = hostOverride ?? this.#hostOverride;
|
|
46
|
+
this.#connectionTimeout = connectionTimeout ?? this.#connectionTimeout;
|
|
47
|
+
this.#readTimeout = readTimeout ?? this.#readTimeout;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// todo: cache the session.
|
|
51
|
+
get session(): import("axios").AxiosInstance {
|
|
52
|
+
if (this.#session !== undefined) {
|
|
53
|
+
return this.#session;
|
|
54
|
+
} else {
|
|
55
|
+
return axios.create({
|
|
56
|
+
baseURL: this.url,
|
|
57
|
+
headers: {
|
|
58
|
+
// biome-ignore lint: external API
|
|
59
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
60
|
+
},
|
|
61
|
+
transformResponse: decodeErrorData,
|
|
62
|
+
timeout: this.#connectionTimeout,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get url(): string {
|
|
68
|
+
return (
|
|
69
|
+
this.#hostOverride ??
|
|
70
|
+
`https://${this.#dbName}.${this.#region}.api.lancedb.com`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get headers(): { [key: string]: string } {
|
|
75
|
+
const headers: { [key: string]: string } = {
|
|
76
|
+
"x-api-key": this.#apiKey,
|
|
77
|
+
"x-request-id": "na",
|
|
78
|
+
};
|
|
79
|
+
if (this.#region == "local") {
|
|
80
|
+
headers["Host"] = `${this.#dbName}.${this.#region}.api.lancedb.com`;
|
|
81
|
+
}
|
|
82
|
+
if (this.#hostOverride) {
|
|
83
|
+
headers["x-lancedb-database"] = this.#dbName;
|
|
84
|
+
}
|
|
85
|
+
return headers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
isOpen(): boolean {
|
|
89
|
+
return !this.#closed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private checkNotClosed(): void {
|
|
93
|
+
if (this.#closed) {
|
|
94
|
+
throw new Error("Connection is closed");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
close(): void {
|
|
99
|
+
this.#session = undefined;
|
|
100
|
+
this.#closed = true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
104
|
+
async get(uri: string, params?: Record<string, any>): Promise<any> {
|
|
105
|
+
this.checkNotClosed();
|
|
106
|
+
uri = new URL(uri, this.url).toString();
|
|
107
|
+
let response;
|
|
108
|
+
try {
|
|
109
|
+
response = await this.session.get(uri, {
|
|
110
|
+
headers: this.headers,
|
|
111
|
+
params,
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {
|
|
114
|
+
if (e instanceof AxiosError) {
|
|
115
|
+
response = e.response;
|
|
116
|
+
} else {
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
RestfulLanceDBClient.checkStatus(response!);
|
|
122
|
+
return response!.data;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// biome-ignore lint/suspicious/noExplicitAny: api response
|
|
126
|
+
async post(uri: string, body?: any): Promise<any>;
|
|
127
|
+
async post(
|
|
128
|
+
uri: string,
|
|
129
|
+
// biome-ignore lint/suspicious/noExplicitAny: api request
|
|
130
|
+
body: any,
|
|
131
|
+
additional: {
|
|
132
|
+
config?: { responseType: "arraybuffer" };
|
|
133
|
+
headers?: Record<string, string>;
|
|
134
|
+
params?: Record<string, string>;
|
|
135
|
+
},
|
|
136
|
+
): Promise<Buffer>;
|
|
137
|
+
async post(
|
|
138
|
+
uri: string,
|
|
139
|
+
// biome-ignore lint/suspicious/noExplicitAny: api request
|
|
140
|
+
body?: any,
|
|
141
|
+
additional?: {
|
|
142
|
+
config?: { responseType: ResponseType };
|
|
143
|
+
headers?: Record<string, string>;
|
|
144
|
+
params?: Record<string, string>;
|
|
145
|
+
},
|
|
146
|
+
// biome-ignore lint/suspicious/noExplicitAny: api response
|
|
147
|
+
): Promise<any> {
|
|
148
|
+
this.checkNotClosed();
|
|
149
|
+
uri = new URL(uri, this.url).toString();
|
|
150
|
+
additional = Object.assign(
|
|
151
|
+
{ config: { responseType: "json" } },
|
|
152
|
+
additional,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const headers = { ...this.headers, ...additional.headers };
|
|
156
|
+
|
|
157
|
+
if (!headers["Content-Type"]) {
|
|
158
|
+
headers["Content-Type"] = "application/json";
|
|
159
|
+
}
|
|
160
|
+
let response;
|
|
161
|
+
try {
|
|
162
|
+
response = await this.session.post(uri, body, {
|
|
163
|
+
headers,
|
|
164
|
+
responseType: additional!.config!.responseType,
|
|
165
|
+
params: new Map(Object.entries(additional.params ?? {})),
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
if (e instanceof AxiosError) {
|
|
169
|
+
response = e.response;
|
|
170
|
+
} else {
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
RestfulLanceDBClient.checkStatus(response!);
|
|
175
|
+
if (additional!.config!.responseType === "arraybuffer") {
|
|
176
|
+
return response!.data;
|
|
177
|
+
} else {
|
|
178
|
+
return JSON.parse(response!.data);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async listTables(limit = 10, pageToken = ""): Promise<string[]> {
|
|
183
|
+
const json = await this.get("/v1/table", { limit, pageToken });
|
|
184
|
+
return json.tables;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async query(tableName: string, query: VectorQuery): Promise<ArrowTable> {
|
|
188
|
+
const tbl = await this.post(`/v1/table/${tableName}/query`, query, {
|
|
189
|
+
config: {
|
|
190
|
+
responseType: "arraybuffer",
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
return tableFromIPC(tbl);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static checkStatus(response: AxiosResponse): void {
|
|
197
|
+
if (response.status === 404) {
|
|
198
|
+
throw new Error(`Not found: ${response.data}`);
|
|
199
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Bad Request: ${response.status}, error: ${response.data}`,
|
|
202
|
+
);
|
|
203
|
+
} else if (response.status >= 500 && response.status < 600) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`Internal Server Error: ${response.status}, error: ${response.data}`,
|
|
206
|
+
);
|
|
207
|
+
} else if (response.status !== 200) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Unknown Error: ${response.status}, error: ${response.data}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function decodeErrorData(data: unknown) {
|
|
216
|
+
if (Buffer.isBuffer(data)) {
|
|
217
|
+
const decoded = data.toString("utf-8");
|
|
218
|
+
return decoded;
|
|
219
|
+
}
|
|
220
|
+
return data;
|
|
221
|
+
}
|