@h-ai/vecdb 1.0.0-beta.26 → 1.0.0-beta.28
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.en.md +13 -0
- package/README.ja.md +13 -0
- package/README.md +13 -0
- package/dist/index.d.ts +64 -2
- package/dist/index.js +141 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -79,6 +79,19 @@ Insert/upsert take collection/documents, delete IDs, yielding void; search takes
|
|
|
79
79
|
| `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
|
|
80
80
|
| `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
|
|
81
81
|
| `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
|
|
82
|
+
| `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
|
|
83
|
+
|
|
84
|
+
Use `scan` to page through document IDs for reconciliation or cleanup. `collection` is the collection name. `options.limit` defaults to 64 and accepts 1 through 128. Omit `options.cursor` for the first page. Each page contains `{ id }` items. If `nextCursor` is present, continue with that cursor on the same collection and backend even when `items` is empty. Concurrent writes or deletions can affect pagination, so reconciliation jobs should run repeated sweeps. Qdrant returns only points that map back to framework document IDs.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// Input: collection name and page size. Output: HaiResult<VectorScanPage>.
|
|
88
|
+
const page = await vecdb.vector.scan('documents', { limit: 32 })
|
|
89
|
+
// On success, pass the cursor and page size to the same collection when another page exists.
|
|
90
|
+
if (page.success && page.data.nextCursor !== undefined) {
|
|
91
|
+
const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
|
|
92
|
+
// On success, nextPage.data.items contains the next IDs. An absent nextCursor marks the last page.
|
|
93
|
+
}
|
|
94
|
+
```
|
|
82
95
|
|
|
83
96
|
### Production shared state
|
|
84
97
|
|
package/README.ja.md
CHANGED
|
@@ -79,6 +79,19 @@ insert/upsert は集合と文書配列、delete は ID 配列から void。searc
|
|
|
79
79
|
| `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
|
|
80
80
|
| `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
|
|
81
81
|
| `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
|
|
82
|
+
| `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
|
|
83
|
+
|
|
84
|
+
`scan` は照合やクリーンアップのために文書 ID をページ単位で取得します。`collection` はコレクション名です。`options.limit` の既定値は 64、範囲は 1〜128 です。最初のページでは `options.cursor` を省略します。各ページの `items` には `{ id }` が入ります。`items` が空でも `nextCursor` があれば、同じバックエンドとコレクションにそのカーソルを渡して続けます。同時書き込みや削除でページの内容が変わるため、照合処理はスキャンを繰り返します。Qdrant はフレームワークの文書 ID に対応するポイントだけを返します。
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// 入力はコレクション名とページサイズ、出力は HaiResult<VectorScanPage>。
|
|
88
|
+
const page = await vecdb.vector.scan('documents', { limit: 32 })
|
|
89
|
+
// 成功して次のページがある場合は、同じコレクションにカーソルとページサイズを渡す。
|
|
90
|
+
if (page.success && page.data.nextCursor !== undefined) {
|
|
91
|
+
const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
|
|
92
|
+
// 成功時の nextPage.data.items は次ページの ID。nextCursor のないページが最後。
|
|
93
|
+
}
|
|
94
|
+
```
|
|
82
95
|
|
|
83
96
|
### 本番の共有状態
|
|
84
97
|
|
package/README.md
CHANGED
|
@@ -79,6 +79,19 @@ if (!closeResult.success) {
|
|
|
79
79
|
| `vecdb.vector.delete` | collection, ids | `void` | `await vecdb.vector.delete('documents', ['doc-1'])` |
|
|
80
80
|
| `vecdb.vector.search` | collection, vector, options | `VectorSearchResult[]` | `await vecdb.vector.search('documents', queryVector, { topK: 5 })` |
|
|
81
81
|
| `vecdb.vector.count` | collection | `number` | `await vecdb.vector.count('documents')` |
|
|
82
|
+
| `vecdb.vector.scan` | collection, options | `VectorScanPage` | `await vecdb.vector.scan('documents', { limit: 32 })` |
|
|
83
|
+
|
|
84
|
+
`scan` 分页返回文档 ID,可用于核对或批量清理集合。`collection` 是集合名,`options.limit` 默认为 64,取值为 1 到 128,`options.cursor` 在首页省略。每页的 `items` 只包含 `{ id }`。有 `nextCursor` 时,即使本页为空也继续读取,并将游标传给同一后端的同一集合。并发写入或删除可能改变分页结果,清理任务应重复扫描。Qdrant 只返回能映射回框架文档的记录。
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// 输入集合名与页大小,输出 HaiResult<VectorScanPage>。
|
|
88
|
+
const page = await vecdb.vector.scan('documents', { limit: 32 })
|
|
89
|
+
// 成功且存在下一页时,将上一页游标与页大小传入同一集合。
|
|
90
|
+
if (page.success && page.data.nextCursor !== undefined) {
|
|
91
|
+
const nextPage = await vecdb.vector.scan('documents', { cursor: page.data.nextCursor, limit: 32 })
|
|
92
|
+
// nextPage 成功时 data.items 是下一页文档 ID,data.nextCursor 缺失时已到末页。
|
|
93
|
+
}
|
|
94
|
+
```
|
|
82
95
|
|
|
83
96
|
### 生产共享状态
|
|
84
97
|
|
package/dist/index.d.ts
CHANGED
|
@@ -631,6 +631,45 @@ interface VectorOperations {
|
|
|
631
631
|
* Qdrant 使用精确 count,不依赖集合信息的近似统计。
|
|
632
632
|
*/
|
|
633
633
|
count: (collection: string) => Promise<HaiResult<number>>;
|
|
634
|
+
/**
|
|
635
|
+
* 分页读取文档 ID,仅返回可以传给本模块删除操作的 ID。
|
|
636
|
+
*
|
|
637
|
+
* @param collection - 集合名称
|
|
638
|
+
* @param options - 扫描游标与每页条数
|
|
639
|
+
* @returns 本页 ID 和后续游标。扫描不提供跨页快照。
|
|
640
|
+
* @example await vecdb.vector.scan('docs', { limit: 32 })
|
|
641
|
+
*/
|
|
642
|
+
scan?: (collection: string, options?: VectorScanOptions) => Promise<HaiResult<VectorScanPage>>;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* 文档 ID 扫描选项。
|
|
646
|
+
* @example
|
|
647
|
+
* ```ts
|
|
648
|
+
* import type { VectorScanOptions } from '@h-ai/vecdb'
|
|
649
|
+
* const options: VectorScanOptions = { limit: 32 }
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
interface VectorScanOptions {
|
|
653
|
+
/** 上一页返回的游标,首次扫描省略。游标仅用于同一后端的同一个集合。 */
|
|
654
|
+
cursor?: string;
|
|
655
|
+
/** 每页最多返回的文档数量,默认 64,允许 1 到 128。 */
|
|
656
|
+
limit?: number;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* 一页文档 ID。
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* import type { VectorScanPage } from '@h-ai/vecdb'
|
|
663
|
+
* const page: VectorScanPage = { items: [{ id: 'doc-1' }] }
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
666
|
+
interface VectorScanPage {
|
|
667
|
+
/** 文档 ID。Qdrant 只包含可映射回框架文档的记录。 */
|
|
668
|
+
items: Array<{
|
|
669
|
+
id: string;
|
|
670
|
+
}>;
|
|
671
|
+
/** 下一页游标。即使当前页为空,只要存在游标就应继续扫描。 */
|
|
672
|
+
nextCursor?: string;
|
|
634
673
|
}
|
|
635
674
|
/**
|
|
636
675
|
* 向量数据库服务接口(通过 `vecdb` 对象访问)
|
|
@@ -706,6 +745,27 @@ interface VecdbConnection {
|
|
|
706
745
|
path?: string;
|
|
707
746
|
}
|
|
708
747
|
|
|
748
|
+
/**
|
|
749
|
+
* @h-ai/vecdb — Provider 共享基础层
|
|
750
|
+
*
|
|
751
|
+
* 将各 Provider 的共性逻辑抽取为统一实现:连接守卫、运行时异常捕获、HaiResult 包装。
|
|
752
|
+
*
|
|
753
|
+
* 各 Provider 只需提供 CollectionDriver / VectorDriver(原始操作适配器),
|
|
754
|
+
* base 层统一处理初始化检查与 catch-all 安全网。
|
|
755
|
+
* @module vecdb-provider-base
|
|
756
|
+
*/
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* 内置后端全部支持文档 ID 扫描。
|
|
760
|
+
* @example 模块内部上下文
|
|
761
|
+
* ```ts
|
|
762
|
+
* function firstPage(vector: BuiltinVectorOperations, name: string) {
|
|
763
|
+
* return vector.scan(name)
|
|
764
|
+
* }
|
|
765
|
+
* ```
|
|
766
|
+
*/
|
|
767
|
+
type BuiltinVectorOperations = VectorOperations & Required<Pick<VectorOperations, 'scan'>>;
|
|
768
|
+
|
|
709
769
|
/**
|
|
710
770
|
* @h-ai/vecdb — 向量数据库服务主入口
|
|
711
771
|
*
|
|
@@ -739,7 +799,9 @@ interface VecdbConnection {
|
|
|
739
799
|
* }
|
|
740
800
|
* ```
|
|
741
801
|
*/
|
|
742
|
-
declare const vecdb: VecdbFunctions
|
|
802
|
+
declare const vecdb: VecdbFunctions & {
|
|
803
|
+
readonly vector: BuiltinVectorOperations;
|
|
804
|
+
};
|
|
743
805
|
|
|
744
806
|
export { DistanceMetricSchema, HaiVecdbError, VecdbConfigSchema, VecdbOperationLogConfigSchema, vecdb };
|
|
745
|
-
export type { ChromaConfig, CollectionCreateOptions, CollectionInfo, CollectionOperations, DistanceMetric, LancedbConfig, PgvectorConfig, QdrantConfig, VecdbConfig, VecdbConfigInput, VecdbConnection, VecdbFunctions, VecdbOperationLogConfig, VecdbType, VectorDocument, VectorOperations, VectorSearchOptions, VectorSearchResult };
|
|
807
|
+
export type { ChromaConfig, CollectionCreateOptions, CollectionInfo, CollectionOperations, DistanceMetric, LancedbConfig, PgvectorConfig, QdrantConfig, VecdbConfig, VecdbConfigInput, VecdbConnection, VecdbFunctions, VecdbOperationLogConfig, VecdbType, VectorDocument, VectorOperations, VectorScanOptions, VectorScanPage, VectorSearchOptions, VectorSearchResult };
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,8 @@ var en_US_default = {
|
|
|
34
34
|
vecdb_indexBuildFailed: "Failed to build index: {error}",
|
|
35
35
|
vecdb_serializationFailed: "Serialization/deserialization failed: {error}",
|
|
36
36
|
vecdb_invalidDimension: "Invalid vector dimension: {dimension}, must be a positive integer",
|
|
37
|
+
vecdb_invalidScanLimit: "The vector scan page size must be an integer between 1 and 128",
|
|
38
|
+
vecdb_invalidScanCursor: "Invalid vector scan cursor. Use the cursor returned by the previous page",
|
|
37
39
|
vecdb_configPathRequired: "Database storage path is required",
|
|
38
40
|
vecdb_configDatabaseRequired: "Database name is required",
|
|
39
41
|
vecdb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
|
|
@@ -68,6 +70,8 @@ var ja_JP_default = {
|
|
|
68
70
|
vecdb_indexBuildFailed: "\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u69CB\u7BC9\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
69
71
|
vecdb_serializationFailed: "\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u307E\u305F\u306F\u30C7\u30B7\u30EA\u30A2\u30E9\u30A4\u30BA\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
70
72
|
vecdb_invalidDimension: "\u30D9\u30AF\u30C8\u30EB\u306E\u6B21\u5143\u304C\u7121\u52B9\u3067\u3059: {dimension}\u3002\u6B63\u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
73
|
+
vecdb_invalidScanLimit: "\u30D9\u30AF\u30C8\u30EB\u30B9\u30AD\u30E3\u30F3\u306E\u30DA\u30FC\u30B8\u30B5\u30A4\u30BA\u306B\u306F 1 \u304B\u3089 128 \u306E\u6574\u6570\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
74
|
+
vecdb_invalidScanCursor: "\u30D9\u30AF\u30C8\u30EB\u30B9\u30AD\u30E3\u30F3\u306E\u30AB\u30FC\u30BD\u30EB\u304C\u7121\u52B9\u3067\u3059\u3002\u524D\u306E\u30DA\u30FC\u30B8\u304B\u3089\u8FD4\u3055\u308C\u305F\u30AB\u30FC\u30BD\u30EB\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
71
75
|
vecdb_configPathRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u4FDD\u5B58\u30D1\u30B9\u306F\u5FC5\u9808\u3067\u3059",
|
|
72
76
|
vecdb_configDatabaseRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
|
|
73
77
|
vecdb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
|
|
@@ -102,6 +106,8 @@ var zh_CN_default = {
|
|
|
102
106
|
vecdb_indexBuildFailed: "\u6784\u5EFA\u7D22\u5F15\u5931\u8D25: {error}",
|
|
103
107
|
vecdb_serializationFailed: "\u5E8F\u5217\u5316/\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {error}",
|
|
104
108
|
vecdb_invalidDimension: "\u65E0\u6548\u7684\u5411\u91CF\u7EF4\u5EA6: {dimension}\uFF0C\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
|
|
109
|
+
vecdb_invalidScanLimit: "\u5411\u91CF\u626B\u63CF\u6BCF\u9875\u6761\u6570\u5FC5\u987B\u4E3A 1 \u5230 128 \u7684\u6574\u6570",
|
|
110
|
+
vecdb_invalidScanCursor: "\u5411\u91CF\u626B\u63CF\u6E38\u6807\u65E0\u6548\uFF0C\u8BF7\u4F7F\u7528\u4E0A\u4E00\u9875\u8FD4\u56DE\u7684\u6E38\u6807",
|
|
105
111
|
vecdb_configPathRequired: "\u6570\u636E\u5E93\u5B58\u50A8\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A",
|
|
106
112
|
vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
|
|
107
113
|
vecdb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
|
|
@@ -383,6 +389,18 @@ function createBaseVectorOps(ctx, driver) {
|
|
|
383
389
|
HaiVecdbError.QUERY_FAILED,
|
|
384
390
|
"Failed to count vectors"
|
|
385
391
|
);
|
|
392
|
+
},
|
|
393
|
+
scan: (collection, options) => {
|
|
394
|
+
const limit = options?.limit ?? 64;
|
|
395
|
+
const cursor = options?.cursor;
|
|
396
|
+
logVecdbOperation(ctx, "read", "vector.scan", { limit, hasCursor: cursor !== void 0 });
|
|
397
|
+
return wrapOp(ctx, async () => {
|
|
398
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 128)
|
|
399
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanLimit"));
|
|
400
|
+
if (cursor !== void 0 && typeof cursor !== "string")
|
|
401
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
|
|
402
|
+
return driver.scan(collection, limit, cursor);
|
|
403
|
+
}, HaiVecdbError.QUERY_FAILED, "Failed to scan vector IDs");
|
|
386
404
|
}
|
|
387
405
|
};
|
|
388
406
|
}
|
|
@@ -751,6 +769,26 @@ function createChromaProvider() {
|
|
|
751
769
|
logger.debug("Counting vectors", { collection });
|
|
752
770
|
const target = await getCollection(collection);
|
|
753
771
|
return ok(await target.count());
|
|
772
|
+
},
|
|
773
|
+
async scan(collection, limit, cursor) {
|
|
774
|
+
const offset = cursor === void 0 ? 0 : Number(cursor);
|
|
775
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || cursor !== void 0 && String(offset) !== cursor)
|
|
776
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
|
|
777
|
+
try {
|
|
778
|
+
const target = await getCollection(collection);
|
|
779
|
+
const { ids } = await target.get({ offset, limit: limit + 1, include: [] });
|
|
780
|
+
return ok({
|
|
781
|
+
items: ids.slice(0, limit).map((id) => ({ id })),
|
|
782
|
+
nextCursor: ids.length > limit ? String(offset + limit) : void 0
|
|
783
|
+
});
|
|
784
|
+
} catch (error) {
|
|
785
|
+
if (error instanceof Error && ["ChromaNotFoundError", "InvalidCollectionError"].includes(error.name)) {
|
|
786
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
787
|
+
params: { name: collection }
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
754
792
|
}
|
|
755
793
|
};
|
|
756
794
|
return {
|
|
@@ -1113,6 +1151,19 @@ function createLancedbProvider() {
|
|
|
1113
1151
|
const count = await table.countRows();
|
|
1114
1152
|
return ok(count);
|
|
1115
1153
|
});
|
|
1154
|
+
},
|
|
1155
|
+
async scan(collection, limit, cursor) {
|
|
1156
|
+
return withCollection(collection, async (table) => {
|
|
1157
|
+
const query = table.query().select(["id"]).orderBy({ columnName: "id", ascending: true }).limit(limit + 1);
|
|
1158
|
+
if (cursor !== void 0)
|
|
1159
|
+
query.where(`id > '${escapeLanceSqlValue(cursor)}'`);
|
|
1160
|
+
const rows = await query.toArray();
|
|
1161
|
+
const items = rows.slice(0, limit).map((row) => ({ id: row.id }));
|
|
1162
|
+
return ok({
|
|
1163
|
+
items,
|
|
1164
|
+
nextCursor: rows.length > limit ? items.at(-1)?.id : void 0
|
|
1165
|
+
});
|
|
1166
|
+
});
|
|
1116
1167
|
}
|
|
1117
1168
|
};
|
|
1118
1169
|
return {
|
|
@@ -1505,6 +1556,25 @@ function createPgvectorProvider() {
|
|
|
1505
1556
|
logger3.debug("Counting vectors", { collection });
|
|
1506
1557
|
const result = await pool.query(`SELECT COUNT(*) as count FROM ${quoteIdent(table)}`);
|
|
1507
1558
|
return ok(Number.parseInt(String(result.rows[0].count), 10));
|
|
1559
|
+
},
|
|
1560
|
+
async scan(collection, limit, cursor) {
|
|
1561
|
+
const table = quoteIdent(tableName(collection));
|
|
1562
|
+
const request = cursor === void 0 ? pool.query(`SELECT id FROM ${table} ORDER BY id LIMIT $1`, [limit + 1]) : pool.query(`SELECT id FROM ${table} WHERE id > $1 ORDER BY id LIMIT $2`, [cursor, limit + 1]);
|
|
1563
|
+
const page = await request.catch((error) => {
|
|
1564
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "42P01")
|
|
1565
|
+
return void 0;
|
|
1566
|
+
throw error;
|
|
1567
|
+
});
|
|
1568
|
+
if (!page) {
|
|
1569
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
1570
|
+
params: { name: collection }
|
|
1571
|
+
}));
|
|
1572
|
+
}
|
|
1573
|
+
const items = page.rows.slice(0, limit).map((row) => ({ id: row.id }));
|
|
1574
|
+
return ok({
|
|
1575
|
+
items,
|
|
1576
|
+
nextCursor: page.rows.length > limit ? items.at(-1)?.id : void 0
|
|
1577
|
+
});
|
|
1508
1578
|
}
|
|
1509
1579
|
};
|
|
1510
1580
|
return {
|
|
@@ -1573,6 +1643,21 @@ function createPgvectorProvider() {
|
|
|
1573
1643
|
};
|
|
1574
1644
|
}
|
|
1575
1645
|
var MAX_RESPONSE_BYTES = 16 * 1024 * 1024;
|
|
1646
|
+
var MAX_QDRANT_POINT_ID = 18446744073709551615n;
|
|
1647
|
+
var scrollIdSchema = z.union([
|
|
1648
|
+
z.guid(),
|
|
1649
|
+
z.bigint().min(0n).max(MAX_QDRANT_POINT_ID)
|
|
1650
|
+
]);
|
|
1651
|
+
var parseWithSource = JSON.parse;
|
|
1652
|
+
var rawJSON = JSON.rawJSON;
|
|
1653
|
+
function parseScrollJson(text) {
|
|
1654
|
+
return parseWithSource(text, (key, value, context) => {
|
|
1655
|
+
const source = context?.source;
|
|
1656
|
+
if ((key === "id" || key === "next_page_offset") && typeof value === "number" && source && /^\d{1,20}$/.test(source))
|
|
1657
|
+
return BigInt(source);
|
|
1658
|
+
return value;
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1576
1661
|
var pointSchema = z.object({
|
|
1577
1662
|
id: z.union([
|
|
1578
1663
|
z.string(),
|
|
@@ -1606,7 +1691,7 @@ var QdrantRestClient = class {
|
|
|
1606
1691
|
}
|
|
1607
1692
|
apiKey;
|
|
1608
1693
|
base;
|
|
1609
|
-
async perform(path, method, schema, body) {
|
|
1694
|
+
async perform(path, method, schema, body, parseJson = JSON.parse) {
|
|
1610
1695
|
const url = new URL(this.base);
|
|
1611
1696
|
const [pathname, query] = path.split("?");
|
|
1612
1697
|
url.pathname = `${url.pathname.replace(/\/$/, "")}${pathname}`;
|
|
@@ -1652,7 +1737,7 @@ var QdrantRestClient = class {
|
|
|
1652
1737
|
}
|
|
1653
1738
|
let bodyJson;
|
|
1654
1739
|
try {
|
|
1655
|
-
bodyJson =
|
|
1740
|
+
bodyJson = parseJson(new TextDecoder().decode(bytes));
|
|
1656
1741
|
} catch {
|
|
1657
1742
|
throw new Error("Qdrant returned invalid JSON");
|
|
1658
1743
|
}
|
|
@@ -1664,9 +1749,9 @@ var QdrantRestClient = class {
|
|
|
1664
1749
|
throw new Error("Qdrant returned an invalid response");
|
|
1665
1750
|
return envelope.data.result;
|
|
1666
1751
|
}
|
|
1667
|
-
async request(path, method, schema, body) {
|
|
1752
|
+
async request(path, method, schema, body, parseJson) {
|
|
1668
1753
|
try {
|
|
1669
|
-
return await this.perform(path, method, schema, body);
|
|
1754
|
+
return await this.perform(path, method, schema, body, parseJson);
|
|
1670
1755
|
} catch (error) {
|
|
1671
1756
|
if (error instanceof QdrantHttpError)
|
|
1672
1757
|
throw error;
|
|
@@ -1709,6 +1794,22 @@ var QdrantRestClient = class {
|
|
|
1709
1794
|
}))
|
|
1710
1795
|
}), options);
|
|
1711
1796
|
}
|
|
1797
|
+
scroll(name, limit, offset) {
|
|
1798
|
+
return this.request(`/collections/${encodeURIComponent(name)}/points/scroll`, "POST", z.object({
|
|
1799
|
+
points: z.array(z.object({
|
|
1800
|
+
id: scrollIdSchema,
|
|
1801
|
+
payload: z.object({
|
|
1802
|
+
_id: z.unknown().optional()
|
|
1803
|
+
}).nullable().optional()
|
|
1804
|
+
})).max(limit),
|
|
1805
|
+
next_page_offset: scrollIdSchema.nullish()
|
|
1806
|
+
}), {
|
|
1807
|
+
limit,
|
|
1808
|
+
offset: typeof offset === "bigint" ? rawJSON(String(offset)) : offset,
|
|
1809
|
+
with_payload: ["_id"],
|
|
1810
|
+
with_vector: false
|
|
1811
|
+
}, parseScrollJson);
|
|
1812
|
+
}
|
|
1712
1813
|
count(name, options) {
|
|
1713
1814
|
return this.request(`/collections/${encodeURIComponent(name)}/points/count`, "POST", z.object({
|
|
1714
1815
|
count: z.number()
|
|
@@ -1962,6 +2063,40 @@ function createQdrantProvider() {
|
|
|
1962
2063
|
logger4.debug("Counting vectors", { collection });
|
|
1963
2064
|
const count = await client.count(collection, { exact: true });
|
|
1964
2065
|
return ok(count.count);
|
|
2066
|
+
},
|
|
2067
|
+
async scan(collection, limit, cursor) {
|
|
2068
|
+
let offset;
|
|
2069
|
+
if (cursor !== void 0) {
|
|
2070
|
+
if (/^n:(?:0|[1-9]\d{0,19})$/.test(cursor) && BigInt(cursor.slice(2)) <= MAX_QDRANT_POINT_ID)
|
|
2071
|
+
offset = BigInt(cursor.slice(2));
|
|
2072
|
+
else if (/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i.test(cursor))
|
|
2073
|
+
offset = cursor.toLowerCase();
|
|
2074
|
+
else
|
|
2075
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidScanCursor"));
|
|
2076
|
+
}
|
|
2077
|
+
const page = await client.scroll(collection, limit, offset).catch((error) => {
|
|
2078
|
+
if (isQdrantNotFoundError(error))
|
|
2079
|
+
return void 0;
|
|
2080
|
+
throw error;
|
|
2081
|
+
});
|
|
2082
|
+
if (!page) {
|
|
2083
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", {
|
|
2084
|
+
params: { name: collection }
|
|
2085
|
+
}));
|
|
2086
|
+
}
|
|
2087
|
+
const items = page.points.flatMap(({ id, payload }) => {
|
|
2088
|
+
const documentId = payload?._id;
|
|
2089
|
+
return typeof documentId === "string" && hashToUuid(documentId) === id ? [
|
|
2090
|
+
{ id: documentId }
|
|
2091
|
+
] : [];
|
|
2092
|
+
});
|
|
2093
|
+
const next = page.next_page_offset ?? void 0;
|
|
2094
|
+
if (next !== void 0 && next === offset)
|
|
2095
|
+
throw new Error("Qdrant scroll cursor did not advance");
|
|
2096
|
+
return ok({
|
|
2097
|
+
items,
|
|
2098
|
+
nextCursor: typeof next === "bigint" ? `n:${next}` : next
|
|
2099
|
+
});
|
|
1965
2100
|
}
|
|
1966
2101
|
};
|
|
1967
2102
|
return {
|
|
@@ -2092,7 +2227,8 @@ var vectorOps = {
|
|
|
2092
2227
|
upsert: (collection, documents) => currentProvider && !closingPromise ? currentProvider.vector.upsert(collection, documents) : notInitializedVector.upsert(collection, documents),
|
|
2093
2228
|
delete: (collection, ids) => currentProvider && !closingPromise ? currentProvider.vector.delete(collection, ids) : notInitializedVector.delete(collection, ids),
|
|
2094
2229
|
search: (collection, vector, options) => currentProvider && !closingPromise ? currentProvider.vector.search(collection, vector, options) : notInitializedVector.search(collection, vector, options),
|
|
2095
|
-
count: (collection) => currentProvider && !closingPromise ? currentProvider.vector.count(collection) : notInitializedVector.count(collection)
|
|
2230
|
+
count: (collection) => currentProvider && !closingPromise ? currentProvider.vector.count(collection) : notInitializedVector.count(collection),
|
|
2231
|
+
scan: (collection, options) => currentProvider && !closingPromise ? currentProvider.vector.scan(collection, options) : notInitializedVector.scan(collection, options)
|
|
2096
2232
|
};
|
|
2097
2233
|
var vecdb = {
|
|
2098
2234
|
/**
|