@lancedb/lancedb 0.5.2 → 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.
Files changed (89) hide show
  1. package/Cargo.toml +3 -3
  2. package/biome.json +19 -3
  3. package/dist/arrow.d.ts +41 -8
  4. package/dist/arrow.js +4 -4
  5. package/dist/connection.d.ts +49 -29
  6. package/dist/connection.js +21 -73
  7. package/dist/embedding/embedding_function.d.ts +9 -1
  8. package/dist/embedding/embedding_function.js +6 -0
  9. package/dist/embedding/openai.d.ts +6 -5
  10. package/dist/embedding/openai.js +4 -2
  11. package/dist/embedding/registry.d.ts +6 -11
  12. package/dist/index.d.ts +51 -3
  13. package/dist/index.js +28 -4
  14. package/dist/merge.d.ts +54 -0
  15. package/dist/merge.js +64 -0
  16. package/dist/native.d.ts +29 -3
  17. package/dist/native.js +26 -9
  18. package/dist/query.d.ts +33 -10
  19. package/dist/query.js +100 -13
  20. package/dist/remote/client.d.ts +28 -0
  21. package/dist/remote/client.js +172 -0
  22. package/dist/remote/connection.d.ts +25 -0
  23. package/dist/remote/connection.js +110 -0
  24. package/dist/remote/index.d.ts +3 -0
  25. package/dist/remote/index.js +9 -0
  26. package/dist/remote/table.d.ts +42 -0
  27. package/dist/remote/table.js +179 -0
  28. package/dist/sanitize.d.ts +3 -2
  29. package/dist/sanitize.js +55 -1
  30. package/dist/table.d.ts +105 -30
  31. package/dist/table.js +94 -237
  32. package/dist/util.d.ts +14 -0
  33. package/dist/util.js +65 -0
  34. package/examples/ann_indexes.ts +49 -0
  35. package/examples/basic.ts +149 -0
  36. package/examples/embedding.ts +83 -0
  37. package/examples/filtering.ts +34 -0
  38. package/examples/jsconfig.json +27 -0
  39. package/examples/package-lock.json +79 -0
  40. package/examples/package.json +18 -0
  41. package/examples/search.ts +37 -0
  42. package/lancedb/arrow.ts +80 -23
  43. package/lancedb/connection.ts +107 -92
  44. package/lancedb/embedding/embedding_function.ts +12 -1
  45. package/lancedb/embedding/openai.ts +11 -6
  46. package/lancedb/embedding/registry.ts +34 -22
  47. package/lancedb/index.ts +101 -2
  48. package/lancedb/merge.ts +70 -0
  49. package/lancedb/query.ts +114 -28
  50. package/lancedb/remote/client.ts +221 -0
  51. package/lancedb/remote/connection.ts +201 -0
  52. package/lancedb/remote/index.ts +3 -0
  53. package/lancedb/remote/table.ts +226 -0
  54. package/lancedb/sanitize.ts +73 -1
  55. package/lancedb/table.ts +320 -132
  56. package/lancedb/util.ts +69 -0
  57. package/native.d.ts +208 -0
  58. package/nodejs-artifacts/arrow.d.ts +41 -8
  59. package/nodejs-artifacts/arrow.js +4 -4
  60. package/nodejs-artifacts/connection.d.ts +49 -29
  61. package/nodejs-artifacts/connection.js +21 -73
  62. package/nodejs-artifacts/embedding/embedding_function.d.ts +9 -1
  63. package/nodejs-artifacts/embedding/embedding_function.js +6 -0
  64. package/nodejs-artifacts/embedding/openai.d.ts +6 -5
  65. package/nodejs-artifacts/embedding/openai.js +4 -2
  66. package/nodejs-artifacts/embedding/registry.d.ts +6 -11
  67. package/nodejs-artifacts/index.d.ts +51 -3
  68. package/nodejs-artifacts/index.js +28 -4
  69. package/nodejs-artifacts/merge.d.ts +54 -0
  70. package/nodejs-artifacts/merge.js +64 -0
  71. package/nodejs-artifacts/native.d.ts +29 -3
  72. package/nodejs-artifacts/native.js +26 -9
  73. package/nodejs-artifacts/query.d.ts +33 -10
  74. package/nodejs-artifacts/query.js +100 -13
  75. package/nodejs-artifacts/remote/client.d.ts +28 -0
  76. package/nodejs-artifacts/remote/client.js +172 -0
  77. package/nodejs-artifacts/remote/connection.d.ts +25 -0
  78. package/nodejs-artifacts/remote/connection.js +110 -0
  79. package/nodejs-artifacts/remote/index.d.ts +3 -0
  80. package/nodejs-artifacts/remote/index.js +9 -0
  81. package/nodejs-artifacts/remote/table.d.ts +42 -0
  82. package/nodejs-artifacts/remote/table.js +179 -0
  83. package/nodejs-artifacts/sanitize.d.ts +3 -2
  84. package/nodejs-artifacts/sanitize.js +55 -1
  85. package/nodejs-artifacts/table.d.ts +105 -30
  86. package/nodejs-artifacts/table.js +94 -237
  87. package/nodejs-artifacts/util.d.ts +14 -0
  88. package/nodejs-artifacts/util.js +65 -0
  89. package/package.json +25 -11
package/lancedb/query.ts CHANGED
@@ -89,15 +89,26 @@ export interface QueryExecutionOptions {
89
89
  }
90
90
 
91
91
  /** Common methods supported by all query types */
92
- export class QueryBase<
93
- NativeQueryType extends NativeQuery | NativeVectorQuery,
94
- QueryType,
95
- > implements AsyncIterable<RecordBatch>
92
+ export class QueryBase<NativeQueryType extends NativeQuery | NativeVectorQuery>
93
+ implements AsyncIterable<RecordBatch>
96
94
  {
97
- protected constructor(protected inner: NativeQueryType) {
95
+ protected constructor(
96
+ protected inner: NativeQueryType | Promise<NativeQueryType>,
97
+ ) {
98
98
  // intentionally empty
99
99
  }
100
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
+ }
101
112
  /**
102
113
  * A filter statement to be applied to this query.
103
114
  *
@@ -110,9 +121,17 @@ export class QueryBase<
110
121
  * Filtering performance can often be improved by creating a scalar index
111
122
  * on the filter column(s).
112
123
  */
113
- where(predicate: string): QueryType {
114
- this.inner.onlyIf(predicate);
115
- return this as unknown as QueryType;
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);
116
135
  }
117
136
 
118
137
  /**
@@ -147,7 +166,7 @@ export class QueryBase<
147
166
  */
148
167
  select(
149
168
  columns: string[] | Map<string, string> | Record<string, string> | string,
150
- ): QueryType {
169
+ ): this {
151
170
  let columnTuples: [string, string][];
152
171
  if (typeof columns === "string") {
153
172
  columns = [columns];
@@ -159,8 +178,10 @@ export class QueryBase<
159
178
  } else {
160
179
  columnTuples = Object.entries(columns);
161
180
  }
162
- this.inner.select(columnTuples);
163
- return this as unknown as QueryType;
181
+ this.doCall((inner: NativeQueryType) => {
182
+ inner.select(columnTuples);
183
+ });
184
+ return this;
164
185
  }
165
186
 
166
187
  /**
@@ -169,15 +190,19 @@ export class QueryBase<
169
190
  * By default, a plain search has no limit. If this method is not
170
191
  * called then every valid row from the table will be returned.
171
192
  */
172
- limit(limit: number): QueryType {
173
- this.inner.limit(limit);
174
- return this as unknown as QueryType;
193
+ limit(limit: number): this {
194
+ this.doCall((inner: NativeQueryType) => inner.limit(limit));
195
+ return this;
175
196
  }
176
197
 
177
198
  protected nativeExecute(
178
199
  options?: Partial<QueryExecutionOptions>,
179
200
  ): Promise<NativeBatchIterator> {
180
- return this.inner.execute(options?.maxBatchLength);
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
+ }
181
206
  }
182
207
 
183
208
  /**
@@ -206,7 +231,13 @@ export class QueryBase<
206
231
  /** Collect the results as an Arrow @see {@link ArrowTable}. */
207
232
  async toArrow(options?: Partial<QueryExecutionOptions>): Promise<ArrowTable> {
208
233
  const batches = [];
209
- for await (const batch of new RecordBatchIterable(this.inner, options)) {
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)) {
210
241
  batches.push(batch);
211
242
  }
212
243
  return new ArrowTable(batches);
@@ -218,6 +249,28 @@ export class QueryBase<
218
249
  const tbl = await this.toArrow(options);
219
250
  return tbl.toArray();
220
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
+ }
221
274
  }
222
275
 
223
276
  /**
@@ -232,8 +285,8 @@ export interface ExecutableQuery {}
232
285
  *
233
286
  * This builder can be reused to execute the query many times.
234
287
  */
235
- export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
236
- constructor(inner: NativeVectorQuery) {
288
+ export class VectorQuery extends QueryBase<NativeVectorQuery> {
289
+ constructor(inner: NativeVectorQuery | Promise<NativeVectorQuery>) {
237
290
  super(inner);
238
291
  }
239
292
 
@@ -260,7 +313,8 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
260
313
  * you the desired recall.
261
314
  */
262
315
  nprobes(nprobes: number): VectorQuery {
263
- this.inner.nprobes(nprobes);
316
+ super.doCall((inner) => inner.nprobes(nprobes));
317
+
264
318
  return this;
265
319
  }
266
320
 
@@ -274,7 +328,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
274
328
  * whose data type is a fixed-size-list of floats.
275
329
  */
276
330
  column(column: string): VectorQuery {
277
- this.inner.column(column);
331
+ super.doCall((inner) => inner.column(column));
278
332
  return this;
279
333
  }
280
334
 
@@ -292,8 +346,10 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
292
346
  *
293
347
  * By default "l2" is used.
294
348
  */
295
- distanceType(distanceType: string): VectorQuery {
296
- this.inner.distanceType(distanceType);
349
+ distanceType(
350
+ distanceType: Required<IvfPqOptions>["distanceType"],
351
+ ): VectorQuery {
352
+ super.doCall((inner) => inner.distanceType(distanceType));
297
353
  return this;
298
354
  }
299
355
 
@@ -327,7 +383,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
327
383
  * distance between the query vector and the actual uncompressed vector.
328
384
  */
329
385
  refineFactor(refineFactor: number): VectorQuery {
330
- this.inner.refineFactor(refineFactor);
386
+ super.doCall((inner) => inner.refineFactor(refineFactor));
331
387
  return this;
332
388
  }
333
389
 
@@ -352,7 +408,7 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
352
408
  * factor can often help restore some of the results lost by post filtering.
353
409
  */
354
410
  postfilter(): VectorQuery {
355
- this.inner.postfilter();
411
+ super.doCall((inner) => inner.postfilter());
356
412
  return this;
357
413
  }
358
414
 
@@ -366,13 +422,13 @@ export class VectorQuery extends QueryBase<NativeVectorQuery, VectorQuery> {
366
422
  * calculate your recall to select an appropriate value for nprobes.
367
423
  */
368
424
  bypassVectorIndex(): VectorQuery {
369
- this.inner.bypassVectorIndex();
425
+ super.doCall((inner) => inner.bypassVectorIndex());
370
426
  return this;
371
427
  }
372
428
  }
373
429
 
374
430
  /** A builder for LanceDB queries. */
375
- export class Query extends QueryBase<NativeQuery, Query> {
431
+ export class Query extends QueryBase<NativeQuery> {
376
432
  constructor(tbl: NativeTable) {
377
433
  super(tbl.query());
378
434
  }
@@ -415,7 +471,37 @@ export class Query extends QueryBase<NativeQuery, Query> {
415
471
  * a default `limit` of 10 will be used. @see {@link Query#limit}
416
472
  */
417
473
  nearestTo(vector: IntoVector): VectorQuery {
418
- const vectorQuery = this.inner.nearestTo(Float32Array.from(vector));
419
- return new VectorQuery(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
+ }
420
506
  }
421
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
+ }
@@ -0,0 +1,201 @@
1
+ import { Schema } from "apache-arrow";
2
+ import {
3
+ Data,
4
+ SchemaLike,
5
+ fromTableToStreamBuffer,
6
+ makeEmptyTable,
7
+ } from "../arrow";
8
+ import {
9
+ Connection,
10
+ CreateTableOptions,
11
+ OpenTableOptions,
12
+ TableNamesOptions,
13
+ } from "../connection";
14
+ import { Table } from "../table";
15
+ import { TTLCache } from "../util";
16
+ import { RestfulLanceDBClient } from "./client";
17
+ import { RemoteTable } from "./table";
18
+
19
+ export interface RemoteConnectionOptions {
20
+ apiKey?: string;
21
+ region?: string;
22
+ hostOverride?: string;
23
+ connectionTimeout?: number;
24
+ readTimeout?: number;
25
+ }
26
+
27
+ export class RemoteConnection extends Connection {
28
+ #dbName: string;
29
+ #apiKey: string;
30
+ #region: string;
31
+ #client: RestfulLanceDBClient;
32
+ #tableCache = new TTLCache(300_000);
33
+
34
+ constructor(
35
+ url: string,
36
+ {
37
+ apiKey,
38
+ region,
39
+ hostOverride,
40
+ connectionTimeout,
41
+ readTimeout,
42
+ }: RemoteConnectionOptions,
43
+ ) {
44
+ super();
45
+ apiKey = apiKey ?? process.env.LANCEDB_API_KEY;
46
+ region = region ?? process.env.LANCEDB_REGION;
47
+
48
+ if (!apiKey) {
49
+ throw new Error("apiKey is required when connecting to LanceDB Cloud");
50
+ }
51
+
52
+ if (!region) {
53
+ throw new Error("region is required when connecting to LanceDB Cloud");
54
+ }
55
+
56
+ const parsed = new URL(url);
57
+ if (parsed.protocol !== "db:") {
58
+ throw new Error(
59
+ `invalid protocol: ${parsed.protocol}, only accepts db://`,
60
+ );
61
+ }
62
+
63
+ this.#dbName = parsed.hostname;
64
+ this.#apiKey = apiKey;
65
+ this.#region = region;
66
+ this.#client = new RestfulLanceDBClient(
67
+ this.#dbName,
68
+ this.#apiKey,
69
+ this.#region,
70
+ hostOverride,
71
+ connectionTimeout,
72
+ readTimeout,
73
+ );
74
+ }
75
+
76
+ isOpen(): boolean {
77
+ return this.#client.isOpen();
78
+ }
79
+ close(): void {
80
+ return this.#client.close();
81
+ }
82
+
83
+ display(): string {
84
+ return `RemoteConnection(${this.#dbName})`;
85
+ }
86
+
87
+ async tableNames(options?: Partial<TableNamesOptions>): Promise<string[]> {
88
+ const response = await this.#client.get("/v1/table/", {
89
+ limit: options?.limit ?? 10,
90
+ // biome-ignore lint/style/useNamingConvention: <explanation>
91
+ page_token: options?.startAfter ?? "",
92
+ });
93
+ const body = await response.body();
94
+ for (const table of body.tables) {
95
+ this.#tableCache.set(table, true);
96
+ }
97
+ return body.tables;
98
+ }
99
+
100
+ async openTable(
101
+ name: string,
102
+ _options?: Partial<OpenTableOptions> | undefined,
103
+ ): Promise<Table> {
104
+ if (this.#tableCache.get(name) === undefined) {
105
+ await this.#client.post(
106
+ `/v1/table/${encodeURIComponent(name)}/describe/`,
107
+ );
108
+ this.#tableCache.set(name, true);
109
+ }
110
+ return new RemoteTable(this.#client, name, this.#dbName);
111
+ }
112
+
113
+ async createTable(
114
+ nameOrOptions:
115
+ | string
116
+ | ({ name: string; data: Data } & Partial<CreateTableOptions>),
117
+ data?: Data,
118
+ options?: Partial<CreateTableOptions> | undefined,
119
+ ): Promise<Table> {
120
+ if (typeof nameOrOptions !== "string" && "name" in nameOrOptions) {
121
+ const { name, data, ...options } = nameOrOptions;
122
+ return this.createTable(name, data, options);
123
+ }
124
+ if (data === undefined) {
125
+ throw new Error("data is required");
126
+ }
127
+ if (options?.mode) {
128
+ console.warn(
129
+ "option 'mode' is not supported in LanceDB Cloud",
130
+ "LanceDB Cloud only supports the default 'create' mode.",
131
+ "If the table already exists, an error will be thrown.",
132
+ );
133
+ }
134
+ if (options?.embeddingFunction) {
135
+ console.warn(
136
+ "embedding_functions is not yet supported on LanceDB Cloud.",
137
+ "Please vote https://github.com/lancedb/lancedb/issues/626 ",
138
+ "for this feature.",
139
+ );
140
+ }
141
+
142
+ const { buf } = await Table.parseTableData(
143
+ data,
144
+ options,
145
+ true /** streaming */,
146
+ );
147
+
148
+ await this.#client.post(
149
+ `/v1/table/${encodeURIComponent(nameOrOptions)}/create/`,
150
+ buf,
151
+ {
152
+ config: {
153
+ responseType: "arraybuffer",
154
+ },
155
+ headers: { "Content-Type": "application/vnd.apache.arrow.stream" },
156
+ },
157
+ );
158
+ this.#tableCache.set(nameOrOptions, true);
159
+ return new RemoteTable(this.#client, nameOrOptions, this.#dbName);
160
+ }
161
+
162
+ async createEmptyTable(
163
+ name: string,
164
+ schema: SchemaLike,
165
+ options?: Partial<CreateTableOptions> | undefined,
166
+ ): Promise<Table> {
167
+ if (options?.mode) {
168
+ console.warn(`mode is not supported on LanceDB Cloud`);
169
+ }
170
+
171
+ if (options?.embeddingFunction) {
172
+ console.warn(
173
+ "embeddingFunction is not yet supported on LanceDB Cloud.",
174
+ "Please vote https://github.com/lancedb/lancedb/issues/626 ",
175
+ "for this feature.",
176
+ );
177
+ }
178
+ const emptyTable = makeEmptyTable(schema);
179
+ const buf = await fromTableToStreamBuffer(emptyTable);
180
+
181
+ await this.#client.post(
182
+ `/v1/table/${encodeURIComponent(name)}/create/`,
183
+ buf,
184
+ {
185
+ config: {
186
+ responseType: "arraybuffer",
187
+ },
188
+ headers: { "Content-Type": "application/vnd.apache.arrow.stream" },
189
+ },
190
+ );
191
+
192
+ this.#tableCache.set(name, true);
193
+ return new RemoteTable(this.#client, name, this.#dbName);
194
+ }
195
+
196
+ async dropTable(name: string): Promise<void> {
197
+ await this.#client.post(`/v1/table/${encodeURIComponent(name)}/drop/`);
198
+
199
+ this.#tableCache.delete(name);
200
+ }
201
+ }
@@ -0,0 +1,3 @@
1
+ export { RestfulLanceDBClient } from "./client";
2
+ export { type RemoteConnectionOptions, RemoteConnection } from "./connection";
3
+ export { RemoteTable } from "./table";