@h-ai/vecdb 0.1.0-alpha.67 → 0.1.0-alpha.68
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 +38 -2
- package/README.ja.md +38 -2
- package/README.md +38 -2
- package/dist/index.d.ts +212 -132
- package/dist/index.js +434 -263
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { core,
|
|
2
|
+
import { core, err, ok } from '@h-ai/core';
|
|
3
|
+
import process from 'process';
|
|
3
4
|
import { spawn } from 'child_process';
|
|
4
|
-
import { setTimeout } from 'timers/promises';
|
|
5
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
5
6
|
import { createRequire } from 'module';
|
|
6
7
|
import { createHash } from 'crypto';
|
|
7
8
|
|
|
@@ -20,6 +21,11 @@ var en_US_default = {
|
|
|
20
21
|
vecdb_connectionFailed: "Failed to connect to vector database: {error}",
|
|
21
22
|
vecdb_collectionNotFound: "Collection not found: {name}",
|
|
22
23
|
vecdb_collectionAlreadyExists: "Collection already exists: {name}",
|
|
24
|
+
vecdb_invalidCollectionSchema: "Collection has no valid persisted vector dimension or distance metric: {name}",
|
|
25
|
+
vecdb_atomicUpsertUnavailable: "LanceDB collection or driver lacks safe atomic merge primary-key support; no writes were performed. Rebuild the collection or use a supported driver",
|
|
26
|
+
vecdb_duplicateDocumentIds: "Vector document IDs must be unique within a batch; no writes were performed",
|
|
27
|
+
vecdb_jsonMetadataRequired: "LanceDB collection requires native JSON metadata; rebuild legacy string metadata collections before writing or filtering",
|
|
28
|
+
vecdb_invalidMetadataFilter: "LanceDB metadata filter must be a plain object of top-level string, finite number, boolean or null values; nested objects, arrays and undefined are unsupported",
|
|
23
29
|
vecdb_dimensionMismatch: "Vector dimension mismatch: expected {expected}, actual {actual}",
|
|
24
30
|
vecdb_insertFailed: "Failed to insert vectors: {error}",
|
|
25
31
|
vecdb_deleteFailed: "Failed to delete vectors: {error}",
|
|
@@ -29,7 +35,43 @@ var en_US_default = {
|
|
|
29
35
|
vecdb_serializationFailed: "Serialization/deserialization failed: {error}",
|
|
30
36
|
vecdb_invalidDimension: "Invalid vector dimension: {dimension}, must be a positive integer",
|
|
31
37
|
vecdb_configPathRequired: "Database storage path is required",
|
|
32
|
-
vecdb_configDatabaseRequired: "Database name is required"
|
|
38
|
+
vecdb_configDatabaseRequired: "Database name is required",
|
|
39
|
+
vecdb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
|
|
40
|
+
vecdb_chromaEndpointInvalid: "Chroma requires an HTTP(S) root endpoint without credentials, path prefix, query or fragment",
|
|
41
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer token must contain only printable ASCII characters without whitespace"
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// messages/ja-JP.json
|
|
45
|
+
var ja_JP_default = {
|
|
46
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
47
|
+
vecdb_notInitialized: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bvecdb.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
48
|
+
vecdb_initFailed: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
49
|
+
vecdb_closeFailed: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u7D42\u4E86\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
50
|
+
vecdb_configError: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
51
|
+
vecdb_initInProgress: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u521D\u671F\u5316\u306F\u65E2\u306B\u5B9F\u884C\u4E2D\u3067\u3059",
|
|
52
|
+
vecdb_unsupportedType: "\u672A\u5BFE\u5FDC\u306E\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u7A2E\u985E\u3067\u3059: {type}",
|
|
53
|
+
vecdb_driverNotFound: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u30C9\u30E9\u30A4\u30D0\u30FC\u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u307E\u305B\u3093: {driver}\u3002pnpm add {driver}\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
54
|
+
vecdb_connectionFailed: "\u30D9\u30AF\u30C8\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
55
|
+
vecdb_collectionNotFound: "\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {name}",
|
|
56
|
+
vecdb_collectionAlreadyExists: "\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059: {name}",
|
|
57
|
+
vecdb_invalidCollectionSchema: "\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306B\u6709\u52B9\u306A\u6C38\u7D9A\u5316\u6E08\u307F\u306E\u30D9\u30AF\u30C8\u30EB\u6B21\u5143\u307E\u305F\u306F\u8DDD\u96E2\u30E1\u30C8\u30EA\u30C3\u30AF\u304C\u3042\u308A\u307E\u305B\u3093\uFF1A{name}",
|
|
58
|
+
vecdb_atomicUpsertUnavailable: "LanceDB \u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u307E\u305F\u306F\u30C9\u30E9\u30A4\u30D0\u30FC\u306B\u5B89\u5168\u306A\u4E3B\u30AD\u30FC\u306B\u3088\u308B\u30A2\u30C8\u30DF\u30C3\u30AF\u306A merge \u6A5F\u80FD\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u66F8\u304D\u8FBC\u307F\u306F\u884C\u3063\u3066\u3044\u307E\u305B\u3093\u3002\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u3092\u518D\u4F5C\u6210\u3059\u308B\u304B\u3001\u5BFE\u5FDC\u30C9\u30E9\u30A4\u30D0\u30FC\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
59
|
+
vecdb_duplicateDocumentIds: "\u540C\u4E00\u30D0\u30C3\u30C1\u306E\u30D9\u30AF\u30C8\u30EB\u6587\u66F8 ID \u306F\u4E00\u610F\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u66F8\u304D\u8FBC\u307F\u306F\u884C\u3063\u3066\u3044\u307E\u305B\u3093",
|
|
60
|
+
vecdb_jsonMetadataRequired: "LanceDB \u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306B\u306F\u30CD\u30A4\u30C6\u30A3\u30D6 JSON \u30E1\u30BF\u30C7\u30FC\u30BF\u304C\u5FC5\u8981\u3067\u3059\u3002\u65E7\u5F62\u5F0F\u306E\u6587\u5B57\u5217\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u4F7F\u7528\u3059\u308B\u30B3\u30EC\u30AF\u30B7\u30E7\u30F3\u306F\u3001\u66F8\u304D\u8FBC\u307F\u3084\u30D5\u30A3\u30EB\u30BF\u30FC\u51E6\u7406\u306E\u524D\u306B\u518D\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
61
|
+
vecdb_invalidMetadataFilter: "LanceDB \u30E1\u30BF\u30C7\u30FC\u30BF\u30D5\u30A3\u30EB\u30BF\u30FC\u306F\u901A\u5E38\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3068\u3057\u3001\u6700\u4E0A\u4F4D\u306E\u5024\u306B\u306F\u6587\u5B57\u5217\u3001\u6709\u9650\u6570\u3001\u771F\u507D\u5024\u3001null \u306E\u307F\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002\u5165\u308C\u5B50\u306E\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3001\u914D\u5217\u3001undefined \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
62
|
+
vecdb_dimensionMismatch: "\u30D9\u30AF\u30C8\u30EB\u306E\u6B21\u5143\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093: \u671F\u5F85\u5024{expected}\u3001\u5B9F\u969B\u306E\u5024{actual}",
|
|
63
|
+
vecdb_insertFailed: "\u30D9\u30AF\u30C8\u30EB\u306E\u633F\u5165\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
64
|
+
vecdb_deleteFailed: "\u30D9\u30AF\u30C8\u30EB\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
65
|
+
vecdb_updateFailed: "\u30D9\u30AF\u30C8\u30EB\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
66
|
+
vecdb_queryFailed: "\u30D9\u30AF\u30C8\u30EB\u306E\u691C\u7D22\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
67
|
+
vecdb_indexBuildFailed: "\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u69CB\u7BC9\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
|
|
68
|
+
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}",
|
|
69
|
+
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",
|
|
70
|
+
vecdb_configPathRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u4FDD\u5B58\u30D1\u30B9\u306F\u5FC5\u9808\u3067\u3059",
|
|
71
|
+
vecdb_configDatabaseRequired: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u540D\u306F\u5FC5\u9808\u3067\u3059",
|
|
72
|
+
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",
|
|
73
|
+
vecdb_chromaEndpointInvalid: "Chroma \u306B\u306F HTTP(S) \u306E\u30EB\u30FC\u30C8\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u8A8D\u8A3C\u60C5\u5831\u3001\u30D1\u30B9\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u3001\u30AF\u30A8\u30EA\u3001\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093",
|
|
74
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer \u30C8\u30FC\u30AF\u30F3\u306B\u306F\u7A7A\u767D\u3092\u542B\u307E\u306A\u3044\u8868\u793A\u53EF\u80FD\u306A ASCII \u6587\u5B57\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059"
|
|
33
75
|
};
|
|
34
76
|
|
|
35
77
|
// messages/zh-CN.json
|
|
@@ -45,6 +87,11 @@ var zh_CN_default = {
|
|
|
45
87
|
vecdb_connectionFailed: "\u8FDE\u63A5\u5411\u91CF\u6570\u636E\u5E93\u5931\u8D25: {error}",
|
|
46
88
|
vecdb_collectionNotFound: "\u96C6\u5408\u4E0D\u5B58\u5728: {name}",
|
|
47
89
|
vecdb_collectionAlreadyExists: "\u96C6\u5408\u5DF2\u5B58\u5728: {name}",
|
|
90
|
+
vecdb_invalidCollectionSchema: "\u96C6\u5408\u7F3A\u5C11\u6709\u6548\u7684\u6301\u4E45\u5316\u5411\u91CF\u7EF4\u5EA6\u6216\u8DDD\u79BB\u5EA6\u91CF\u5B9A\u4E49: {name}",
|
|
91
|
+
vecdb_atomicUpsertUnavailable: "LanceDB \u96C6\u5408\u6216\u9A71\u52A8\u7F3A\u5C11\u5B89\u5168\u7684\u539F\u5B50 merge \u4E3B\u952E\u80FD\u529B\uFF0C\u672A\u6267\u884C\u5199\u5165\uFF0C\u8BF7\u91CD\u5EFA\u96C6\u5408\u6216\u4F7F\u7528\u53D7\u652F\u6301\u7684\u9A71\u52A8",
|
|
92
|
+
vecdb_duplicateDocumentIds: "\u540C\u4E00\u6279\u5411\u91CF\u6587\u6863\u7684 ID \u4E0D\u5F97\u91CD\u590D\uFF0C\u672A\u6267\u884C\u5199\u5165",
|
|
93
|
+
vecdb_jsonMetadataRequired: "LanceDB \u96C6\u5408\u987B\u4F7F\u7528\u539F\u751F JSON \u5143\u6570\u636E\uFF0C\u65E7\u5B57\u7B26\u4E32\u5143\u6570\u636E\u96C6\u5408\u9700\u91CD\u5EFA\u540E\u624D\u80FD\u5199\u5165\u6216\u8FC7\u6EE4",
|
|
94
|
+
vecdb_invalidMetadataFilter: "LanceDB \u5143\u6570\u636E\u8FC7\u6EE4\u987B\u4E3A\u666E\u901A\u5BF9\u8C61\uFF0C\u503C\u4EC5\u652F\u6301\u9876\u5C42\u5B57\u7B26\u4E32\u3001\u6709\u9650\u6570\u503C\u3001\u5E03\u5C14\u6216 null\uFF1B\u4E0D\u652F\u6301\u5D4C\u5957\u5BF9\u8C61\u3001\u6570\u7EC4\u548C undefined",
|
|
48
95
|
vecdb_dimensionMismatch: "\u5411\u91CF\u7EF4\u5EA6\u4E0D\u5339\u914D: \u671F\u671B {expected}\uFF0C\u5B9E\u9645 {actual}",
|
|
49
96
|
vecdb_insertFailed: "\u63D2\u5165\u5411\u91CF\u5931\u8D25: {error}",
|
|
50
97
|
vecdb_deleteFailed: "\u5220\u9664\u5411\u91CF\u5931\u8D25: {error}",
|
|
@@ -54,13 +101,17 @@ var zh_CN_default = {
|
|
|
54
101
|
vecdb_serializationFailed: "\u5E8F\u5217\u5316/\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {error}",
|
|
55
102
|
vecdb_invalidDimension: "\u65E0\u6548\u7684\u5411\u91CF\u7EF4\u5EA6: {dimension}\uFF0C\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
|
|
56
103
|
vecdb_configPathRequired: "\u6570\u636E\u5E93\u5B58\u50A8\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A",
|
|
57
|
-
vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A"
|
|
104
|
+
vecdb_configDatabaseRequired: "\u6570\u636E\u5E93\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
|
|
105
|
+
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",
|
|
106
|
+
vecdb_chromaEndpointInvalid: "Chroma \u4EC5\u652F\u6301 HTTP(S) \u6839\u7AEF\u70B9\uFF0C\u4E0D\u5F97\u5305\u542B\u51ED\u636E\u3001\u8DEF\u5F84\u524D\u7F00\u3001query \u6216 fragment",
|
|
107
|
+
vecdb_chromaTokenInvalid: "Chroma Bearer token \u4EC5\u652F\u6301\u65E0\u7A7A\u767D\u7684\u53EF\u6253\u5370 ASCII \u5B57\u7B26"
|
|
58
108
|
};
|
|
59
109
|
|
|
60
110
|
// src/vecdb-i18n.ts
|
|
61
111
|
var vecdbM = core.i18n.createMessageGetter({
|
|
62
112
|
"zh-CN": zh_CN_default,
|
|
63
|
-
"en-US": en_US_default
|
|
113
|
+
"en-US": en_US_default,
|
|
114
|
+
"ja-JP": ja_JP_default
|
|
64
115
|
});
|
|
65
116
|
|
|
66
117
|
// src/vecdb-config.ts
|
|
@@ -76,9 +127,9 @@ var OperationLogConfigFieldSchema = {
|
|
|
76
127
|
};
|
|
77
128
|
var LancedbConfigSchema = z.object({
|
|
78
129
|
type: z.literal("lancedb"),
|
|
79
|
-
/**
|
|
130
|
+
/** 持久化本地目录;独立节点的同名路径不会自动共享,分布式部署优先使用服务端后端。 */
|
|
80
131
|
path: z.string().min(1, vecdbM("vecdb_configPathRequired")),
|
|
81
|
-
/**
|
|
132
|
+
/** 新建集合的默认距离度量;已有集合使用 Arrow schema 中的持久定义。 */
|
|
82
133
|
metric: DistanceMetricSchema.optional(),
|
|
83
134
|
...OperationLogConfigFieldSchema
|
|
84
135
|
});
|
|
@@ -119,14 +170,21 @@ var ChromaConfigSchema = z.object({
|
|
|
119
170
|
type: z.literal("chroma"),
|
|
120
171
|
/** 本地持久化路径(嵌入式模式:提供且无 url 时自动拉起本地服务) */
|
|
121
172
|
path: z.string().optional(),
|
|
122
|
-
/**
|
|
123
|
-
url: z.string().url().
|
|
173
|
+
/** Chroma HTTP(S) 根端点;不可包含凭据、路径前缀、query 或 fragment;提供时优先于 host/port。 */
|
|
174
|
+
url: z.string().url().refine((value) => {
|
|
175
|
+
try {
|
|
176
|
+
const endpoint = new URL(value);
|
|
177
|
+
return ["http:", "https:"].includes(endpoint.protocol) && !endpoint.username && !endpoint.password && endpoint.pathname === "/" && !endpoint.search && !endpoint.hash;
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}, { error: () => vecdbM("vecdb_chromaEndpointInvalid") }).optional(),
|
|
124
182
|
/** 主机(默认 localhost) */
|
|
125
183
|
host: z.string().default("localhost"),
|
|
126
184
|
/** 端口(默认 8000) */
|
|
127
185
|
port: z.number().int().min(1).max(65535).default(8e3),
|
|
128
|
-
/**
|
|
129
|
-
apiKey: z.string().optional(),
|
|
186
|
+
/** 可选 Bearer token,发送 Authorization 请求头;用于采用此协议的自托管服务/网关,不是 Chroma Cloud 配置。空串表示未配置。 */
|
|
187
|
+
apiKey: z.string().regex(/^[\x21-\x7E]*$/, { error: () => vecdbM("vecdb_chromaTokenInvalid") }).optional(),
|
|
130
188
|
/** 拉起本地服务的可执行命令(默认 'chroma',可指定绝对路径) */
|
|
131
189
|
serverCommand: z.string().default("chroma"),
|
|
132
190
|
/** 拉起本地服务的就绪等待超时(毫秒,默认 30000) */
|
|
@@ -365,14 +423,15 @@ function buildWhere(filter) {
|
|
|
365
423
|
const entries = Object.entries(filter);
|
|
366
424
|
if (entries.length === 0)
|
|
367
425
|
return void 0;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return { $and: entries.map(([key, value]) => ({ [key]: value })) };
|
|
426
|
+
const where = entries.length === 1 ? { [entries[0][0]]: entries[0][1] } : { $and: entries.map(([key, value]) => ({ [key]: value })) };
|
|
427
|
+
return where;
|
|
371
428
|
}
|
|
372
429
|
function createChromaProvider() {
|
|
373
430
|
let client = null;
|
|
374
431
|
let config = null;
|
|
375
432
|
let serverProcess = null;
|
|
433
|
+
let spawnFailed = false;
|
|
434
|
+
let closing = null;
|
|
376
435
|
function isMissingModuleError(error) {
|
|
377
436
|
const message = error instanceof Error ? error.message : String(error);
|
|
378
437
|
return message.includes("chromadb");
|
|
@@ -394,24 +453,93 @@ function createChromaProvider() {
|
|
|
394
453
|
throw error;
|
|
395
454
|
}
|
|
396
455
|
}
|
|
397
|
-
function startEmbeddedServer(chromaConfig) {
|
|
456
|
+
async function startEmbeddedServer(chromaConfig) {
|
|
398
457
|
const args = ["run", "--path", chromaConfig.path ?? "./data/chroma", "--host", chromaConfig.host, "--port", String(chromaConfig.port)];
|
|
399
458
|
logger.info("Starting embedded Chroma server", { command: chromaConfig.serverCommand, host: chromaConfig.host, port: chromaConfig.port });
|
|
459
|
+
spawnFailed = false;
|
|
400
460
|
const proc = spawn(chromaConfig.serverCommand, args, { stdio: "ignore" });
|
|
401
461
|
proc.on("error", (error) => {
|
|
462
|
+
spawnFailed = proc.pid === void 0;
|
|
402
463
|
logger.error("Chroma server process error", { error: error.message });
|
|
403
464
|
});
|
|
404
465
|
serverProcess = proc;
|
|
466
|
+
await new Promise((resolve, reject) => {
|
|
467
|
+
proc.once("spawn", resolve);
|
|
468
|
+
proc.once("error", reject);
|
|
469
|
+
});
|
|
405
470
|
}
|
|
406
|
-
function stopEmbeddedServer() {
|
|
407
|
-
|
|
471
|
+
async function stopEmbeddedServer() {
|
|
472
|
+
const owned = serverProcess;
|
|
473
|
+
if (!owned)
|
|
408
474
|
return;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
475
|
+
const hasExited = () => owned.exitCode !== null || owned.signalCode !== null || spawnFailed && owned.pid === void 0;
|
|
476
|
+
if (!hasExited()) {
|
|
477
|
+
const child = owned;
|
|
478
|
+
await new Promise((resolve, reject) => {
|
|
479
|
+
let timer;
|
|
480
|
+
let settled = false;
|
|
481
|
+
function cleanup() {
|
|
482
|
+
clearTimeout(timer);
|
|
483
|
+
child.removeListener("exit", exited);
|
|
484
|
+
child.removeListener("error", failed);
|
|
485
|
+
}
|
|
486
|
+
function exited() {
|
|
487
|
+
if (settled)
|
|
488
|
+
return;
|
|
489
|
+
settled = true;
|
|
490
|
+
cleanup();
|
|
491
|
+
resolve();
|
|
492
|
+
}
|
|
493
|
+
function failed(cause) {
|
|
494
|
+
if (settled)
|
|
495
|
+
return;
|
|
496
|
+
if (hasExited()) {
|
|
497
|
+
exited();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
settled = true;
|
|
501
|
+
cleanup();
|
|
502
|
+
reject(cause);
|
|
503
|
+
}
|
|
504
|
+
function signal(name) {
|
|
505
|
+
try {
|
|
506
|
+
if (!child.kill(name))
|
|
507
|
+
failed(new Error("Chroma child termination signal was rejected"));
|
|
508
|
+
} catch (cause) {
|
|
509
|
+
failed(cause instanceof Error ? cause : new Error(String(cause)));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
child.once("exit", exited);
|
|
513
|
+
child.once("error", failed);
|
|
514
|
+
timer = setTimeout(() => {
|
|
515
|
+
timer = setTimeout(() => failed(new Error("Chroma child exit was not confirmed")), 1500);
|
|
516
|
+
signal("SIGKILL");
|
|
517
|
+
}, 1500);
|
|
518
|
+
if (hasExited())
|
|
519
|
+
exited();
|
|
520
|
+
else signal("SIGTERM");
|
|
521
|
+
});
|
|
413
522
|
}
|
|
414
|
-
serverProcess
|
|
523
|
+
if (serverProcess === owned)
|
|
524
|
+
serverProcess = null;
|
|
525
|
+
}
|
|
526
|
+
async function closeResources() {
|
|
527
|
+
if (!closing) {
|
|
528
|
+
client = null;
|
|
529
|
+
config = null;
|
|
530
|
+
closing = (async () => {
|
|
531
|
+
try {
|
|
532
|
+
await stopEmbeddedServer();
|
|
533
|
+
logger.info("Chroma connection closed");
|
|
534
|
+
return ok(void 0);
|
|
535
|
+
} catch (cause) {
|
|
536
|
+
return err({ ...HaiVecdbError.CONNECTION_FAILED, message: vecdbM("vecdb_closeFailed", { params: { error: cause instanceof Error ? cause.message : String(cause) } }), cause, ext: { cleanupPending: true } });
|
|
537
|
+
}
|
|
538
|
+
})().finally(() => {
|
|
539
|
+
closing = null;
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return await closing;
|
|
415
543
|
}
|
|
416
544
|
async function waitForHeartbeat(chromaClient, timeout) {
|
|
417
545
|
const deadline = Date.now() + timeout;
|
|
@@ -422,7 +550,7 @@ function createChromaProvider() {
|
|
|
422
550
|
return;
|
|
423
551
|
} catch (error) {
|
|
424
552
|
lastError = error;
|
|
425
|
-
await setTimeout(HEARTBEAT_POLL_INTERVAL);
|
|
553
|
+
await setTimeout$1(HEARTBEAT_POLL_INTERVAL);
|
|
426
554
|
}
|
|
427
555
|
}
|
|
428
556
|
throw new Error(`Chroma heartbeat timed out after ${timeout}ms: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
@@ -447,7 +575,7 @@ function createChromaProvider() {
|
|
|
447
575
|
async create(name, options) {
|
|
448
576
|
logger.debug("Creating collection", { name, dimension: options.dimension, metric: options.metric });
|
|
449
577
|
const existing = await client.listCollections();
|
|
450
|
-
const exists = existing.some((item) =>
|
|
578
|
+
const exists = existing.some((item) => item.name === name);
|
|
451
579
|
if (exists)
|
|
452
580
|
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
|
|
453
581
|
const metric = options.metric ?? config?.metric ?? "cosine";
|
|
@@ -462,7 +590,7 @@ function createChromaProvider() {
|
|
|
462
590
|
async drop(name) {
|
|
463
591
|
logger.debug("Dropping collection", { name });
|
|
464
592
|
const existing = await client.listCollections();
|
|
465
|
-
const exists = existing.some((item) =>
|
|
593
|
+
const exists = existing.some((item) => item.name === name);
|
|
466
594
|
if (!exists)
|
|
467
595
|
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
|
|
468
596
|
await client.deleteCollection({ name });
|
|
@@ -472,12 +600,12 @@ function createChromaProvider() {
|
|
|
472
600
|
async exists(name) {
|
|
473
601
|
logger.debug("Checking collection exists", { name });
|
|
474
602
|
const existing = await client.listCollections();
|
|
475
|
-
return ok(existing.some((item) =>
|
|
603
|
+
return ok(existing.some((item) => item.name === name));
|
|
476
604
|
},
|
|
477
605
|
async info(name) {
|
|
478
606
|
logger.debug("Getting collection info", { name });
|
|
479
607
|
const existing = await client.listCollections();
|
|
480
|
-
if (!existing.some((item) =>
|
|
608
|
+
if (!existing.some((item) => item.name === name))
|
|
481
609
|
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
|
|
482
610
|
const collection = await getCollection(name);
|
|
483
611
|
const count = await collection.count();
|
|
@@ -491,14 +619,16 @@ function createChromaProvider() {
|
|
|
491
619
|
async list() {
|
|
492
620
|
logger.debug("Listing collections");
|
|
493
621
|
const existing = await client.listCollections();
|
|
494
|
-
return ok(existing.map((item) =>
|
|
622
|
+
return ok(existing.map((item) => item.name));
|
|
495
623
|
}
|
|
496
624
|
};
|
|
497
625
|
function toWriteParams(documents) {
|
|
498
626
|
return {
|
|
499
627
|
ids: documents.map((doc) => doc.id),
|
|
500
628
|
embeddings: documents.map((doc) => doc.vector),
|
|
501
|
-
documents: documents.map((doc) => doc.content ??
|
|
629
|
+
documents: documents.map((doc) => doc.content ?? ""),
|
|
630
|
+
// 统一 API 的 metadata 更宽;SDK 在 add/upsert 中先执行 validateMetadata,再发送请求。
|
|
631
|
+
// 使用上游 Metadata 类型跨越此边界,避免复制其标量、数组及稀疏向量规则;原生拒绝会由 base 转为 HaiResult。
|
|
502
632
|
metadatas: documents.map((doc) => ({ ...doc.metadata, _content: doc.content ?? "" }))
|
|
503
633
|
};
|
|
504
634
|
}
|
|
@@ -577,35 +707,49 @@ function createChromaProvider() {
|
|
|
577
707
|
async connect(cfg) {
|
|
578
708
|
if (cfg.type !== "chroma")
|
|
579
709
|
return err(HaiVecdbError.UNSUPPORTED_TYPE, vecdbM("vecdb_unsupportedType", { params: { type: cfg.type } }));
|
|
710
|
+
if (serverProcess || client || closing) {
|
|
711
|
+
const cleaned = await closeResources();
|
|
712
|
+
if (!cleaned.success)
|
|
713
|
+
return cleaned;
|
|
714
|
+
}
|
|
580
715
|
const chromaConfig = cfg;
|
|
581
716
|
const embedded = !!chromaConfig.path && !chromaConfig.url;
|
|
582
717
|
const loadResult = await loadChromaModule();
|
|
583
718
|
if (!loadResult.success)
|
|
584
719
|
return loadResult;
|
|
720
|
+
const startupAbort = new AbortController();
|
|
721
|
+
const startupTimeout = embedded ? chromaConfig.startupTimeout : 5e3;
|
|
722
|
+
const startupTimer = setTimeout(() => startupAbort.abort(), startupTimeout);
|
|
585
723
|
try {
|
|
586
724
|
if (embedded)
|
|
587
|
-
startEmbeddedServer(chromaConfig);
|
|
588
|
-
const
|
|
725
|
+
await startEmbeddedServer(chromaConfig);
|
|
726
|
+
const endpoint = chromaConfig.url ? new URL(chromaConfig.url) : void 0;
|
|
727
|
+
const clientParams = endpoint ? { host: endpoint.hostname, port: Number(endpoint.port || (endpoint.protocol === "https:" ? 443 : 80)), ssl: endpoint.protocol === "https:" } : { host: chromaConfig.host, port: chromaConfig.port, ssl: false };
|
|
589
728
|
if (chromaConfig.apiKey)
|
|
590
|
-
clientParams.
|
|
729
|
+
clientParams.headers = { Authorization: `Bearer ${chromaConfig.apiKey}` };
|
|
730
|
+
clientParams.fetchOptions = { signal: startupAbort.signal };
|
|
591
731
|
const chromaClient = new loadResult.data.ChromaClient(clientParams);
|
|
592
|
-
await waitForHeartbeat(chromaClient,
|
|
732
|
+
await waitForHeartbeat(chromaClient, startupTimeout);
|
|
733
|
+
await chromaClient.countCollections();
|
|
734
|
+
startupAbort.signal.throwIfAborted();
|
|
735
|
+
if (embedded && (spawnFailed || !serverProcess || serverProcess.exitCode !== null || serverProcess.signalCode !== null))
|
|
736
|
+
throw new Error("Chroma child exited before readiness was confirmed");
|
|
593
737
|
client = chromaClient;
|
|
594
738
|
config = chromaConfig;
|
|
595
|
-
logger.info("Chroma connected", { embedded, host:
|
|
739
|
+
logger.info("Chroma connected", { embedded, host: clientParams.host, port: clientParams.port, ssl: clientParams.ssl });
|
|
596
740
|
return ok(void 0);
|
|
597
741
|
} catch (error) {
|
|
598
|
-
|
|
742
|
+
startupAbort.abort();
|
|
743
|
+
const cleanup = await closeResources();
|
|
599
744
|
logger.error("Failed to connect to Chroma", { error: error instanceof Error ? error.message : String(error) });
|
|
600
|
-
|
|
745
|
+
const failure = err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: String(error) } }), error);
|
|
746
|
+
return !failure.success && !cleanup.success ? err({ ...failure.error, ext: { cleanupPending: true, cleanupError: cleanup.error } }) : failure;
|
|
747
|
+
} finally {
|
|
748
|
+
clearTimeout(startupTimer);
|
|
601
749
|
}
|
|
602
750
|
},
|
|
603
751
|
async close() {
|
|
604
|
-
|
|
605
|
-
client = null;
|
|
606
|
-
config = null;
|
|
607
|
-
logger.info("Chroma connection closed");
|
|
608
|
-
return ok(void 0);
|
|
752
|
+
return await closeResources();
|
|
609
753
|
},
|
|
610
754
|
isConnected() {
|
|
611
755
|
return client !== null;
|
|
@@ -616,16 +760,29 @@ function createChromaProvider() {
|
|
|
616
760
|
}
|
|
617
761
|
var require2 = createRequire(import.meta.url);
|
|
618
762
|
var logger2 = core.logger.child({ module: "vecdb", scope: "lancedb" });
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
763
|
+
var COLLECTION_METRIC_KEY = "hai.vecdb.metric";
|
|
764
|
+
var PRIMARY_KEY_FLAG = "lance-schema:unenforced-primary-key";
|
|
765
|
+
var PRIMARY_KEY_POSITION = "lance-schema:unenforced-primary-key:position";
|
|
622
766
|
function escapeLanceSqlValue(value) {
|
|
623
767
|
return value.replace(/'/g, "''");
|
|
624
768
|
}
|
|
769
|
+
function buildMetadataPredicate(filter) {
|
|
770
|
+
if (typeof filter !== "object" || filter === null || Array.isArray(filter) || ![Object.prototype, null].includes(Object.getPrototypeOf(filter)) || Reflect.ownKeys(filter).some((key) => typeof key !== "string")) {
|
|
771
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidMetadataFilter"));
|
|
772
|
+
}
|
|
773
|
+
const predicates = [];
|
|
774
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
775
|
+
if (value !== null && typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value))) {
|
|
776
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidMetadataFilter"));
|
|
777
|
+
}
|
|
778
|
+
const extracted = `json_extract(json_get(metadata, '${escapeLanceSqlValue(key)}'), '$')`;
|
|
779
|
+
predicates.push(typeof value === "number" ? `TRY_CAST(${extracted} AS DOUBLE) = CAST(${value} AS DOUBLE)` : `${extracted} = '${escapeLanceSqlValue(JSON.stringify(value))}'`);
|
|
780
|
+
}
|
|
781
|
+
return ok(predicates.join(" AND "));
|
|
782
|
+
}
|
|
625
783
|
function createLancedbProvider() {
|
|
626
784
|
let connection = null;
|
|
627
785
|
let config = null;
|
|
628
|
-
const collectionMetas = /* @__PURE__ */ new Map();
|
|
629
786
|
function loadLancedb() {
|
|
630
787
|
try {
|
|
631
788
|
const mod = require2("@lancedb/lancedb");
|
|
@@ -635,22 +792,30 @@ function createLancedbProvider() {
|
|
|
635
792
|
return err(HaiVecdbError.DRIVER_NOT_FOUND, vecdbM("vecdb_driverNotFound", { params: { driver: "@lancedb/lancedb" } }), error);
|
|
636
793
|
}
|
|
637
794
|
}
|
|
638
|
-
async function
|
|
639
|
-
if (!connection)
|
|
640
|
-
return
|
|
795
|
+
async function withCollection(name, action) {
|
|
796
|
+
if (!(await connection.tableNames()).includes(name))
|
|
797
|
+
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
|
|
798
|
+
const table = await connection.openTable(name);
|
|
641
799
|
try {
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
800
|
+
const schema = await table.schema();
|
|
801
|
+
const vectorType = schema.fields.find((field) => field.name === "vector")?.type;
|
|
802
|
+
const metric = DistanceMetricSchema.safeParse(schema.metadata.get(COLLECTION_METRIC_KEY) ?? null);
|
|
803
|
+
if (!vectorType || !("listSize" in vectorType) || typeof vectorType.listSize !== "number" || !Number.isInteger(vectorType.listSize) || vectorType.listSize <= 0 || !metric.success) {
|
|
804
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
|
|
805
|
+
}
|
|
806
|
+
const primaryKeys = schema.fields.filter((field) => field.metadata.has(PRIMARY_KEY_POSITION) || ["true", "1", "yes"].includes(field.metadata.get(PRIMARY_KEY_FLAG)?.toLowerCase() ?? ""));
|
|
807
|
+
const idField = primaryKeys[0];
|
|
808
|
+
const atomicWrites = primaryKeys.length === 1 && idField.name === "id" && !idField.nullable && ["true", "1", "yes"].includes(idField.metadata.get(PRIMARY_KEY_FLAG)?.toLowerCase() ?? "") && idField.metadata.get(PRIMARY_KEY_POSITION) === "1" && typeof table.mergeInsert === "function";
|
|
809
|
+
const driver = loadLancedb();
|
|
810
|
+
if (!driver.success)
|
|
811
|
+
return driver;
|
|
812
|
+
const jsonMetadata = schema.fields.find((field) => field.name === "metadata")?.metadata.get("ARROW:extension:name") === "lance.json";
|
|
813
|
+
const inputSchema = jsonMetadata ? schema.assign([driver.data.makeJsonField("metadata", false)]) : void 0;
|
|
814
|
+
return await action(table, { dimension: vectorType.listSize, metric: metric.data, atomicWrites, inputSchema });
|
|
815
|
+
} finally {
|
|
816
|
+
table.close();
|
|
649
817
|
}
|
|
650
818
|
}
|
|
651
|
-
function getCollectionDimension(name) {
|
|
652
|
-
return collectionMetas.get(name)?.dimension ?? null;
|
|
653
|
-
}
|
|
654
819
|
function createDimensionMismatchResult(expected, actual) {
|
|
655
820
|
return err(
|
|
656
821
|
HaiVecdbError.DIMENSION_MISMATCH,
|
|
@@ -659,18 +824,13 @@ function createLancedbProvider() {
|
|
|
659
824
|
})
|
|
660
825
|
);
|
|
661
826
|
}
|
|
662
|
-
function validateVectorDimension(
|
|
663
|
-
|
|
664
|
-
if (expected === null || vector.length === expected) {
|
|
827
|
+
function validateVectorDimension(expected, vector) {
|
|
828
|
+
if (vector.length === expected) {
|
|
665
829
|
return null;
|
|
666
830
|
}
|
|
667
831
|
return createDimensionMismatchResult(expected, vector.length);
|
|
668
832
|
}
|
|
669
|
-
function validateDocumentsDimension(
|
|
670
|
-
const expected = getCollectionDimension(collection);
|
|
671
|
-
if (expected === null) {
|
|
672
|
-
return null;
|
|
673
|
-
}
|
|
833
|
+
function validateDocumentsDimension(expected, documents) {
|
|
674
834
|
for (const document of documents) {
|
|
675
835
|
if (document.vector.length !== expected) {
|
|
676
836
|
return createDimensionMismatchResult(expected, document.vector.length);
|
|
@@ -678,6 +838,15 @@ function createLancedbProvider() {
|
|
|
678
838
|
}
|
|
679
839
|
return null;
|
|
680
840
|
}
|
|
841
|
+
function validateAtomicWrite(meta, documents) {
|
|
842
|
+
if (!meta.inputSchema)
|
|
843
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_jsonMetadataRequired"));
|
|
844
|
+
if (!meta.atomicWrites)
|
|
845
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_atomicUpsertUnavailable"));
|
|
846
|
+
if (new Set(documents.map((document) => document.id)).size !== documents.length)
|
|
847
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_duplicateDocumentIds"));
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
681
850
|
const ctx = { isConnected: () => connection !== null, logger: logger2, operationLog: () => config?.operationLog };
|
|
682
851
|
const collectionDriver = {
|
|
683
852
|
async create(name, options) {
|
|
@@ -687,16 +856,22 @@ function createLancedbProvider() {
|
|
|
687
856
|
return err(HaiVecdbError.COLLECTION_ALREADY_EXISTS, vecdbM("vecdb_collectionAlreadyExists", { params: { name } }));
|
|
688
857
|
}
|
|
689
858
|
const dimension = options.dimension;
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
859
|
+
const parsedMetric = DistanceMetricSchema.safeParse(options.metric ?? config?.metric);
|
|
860
|
+
if (!parsedMetric.success)
|
|
861
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
|
|
862
|
+
const metric = parsedMetric.data;
|
|
863
|
+
const driver = loadLancedb();
|
|
864
|
+
if (!driver.success)
|
|
865
|
+
return driver;
|
|
866
|
+
const inferred = driver.data.makeArrowTable([{
|
|
867
|
+
id: "",
|
|
693
868
|
vector: Array.from({ length: dimension }).fill(0),
|
|
694
869
|
content: "",
|
|
695
870
|
metadata: "{}"
|
|
696
|
-
};
|
|
697
|
-
const
|
|
698
|
-
await
|
|
699
|
-
|
|
871
|
+
}], {}, /* @__PURE__ */ new Map([[COLLECTION_METRIC_KEY, metric]])).schema;
|
|
872
|
+
const schema = inferred.assign(inferred.fields.map((field) => field.name === "id" ? field.clone({ nullable: false, metadata: new Map([...field.metadata, [PRIMARY_KEY_FLAG, "true"], [PRIMARY_KEY_POSITION, "1"]]) }) : field.name === "metadata" ? driver.data.makeJsonField("metadata", false) : field));
|
|
873
|
+
const table = await connection.createEmptyTable(name, schema);
|
|
874
|
+
table.close();
|
|
700
875
|
logger2.info("Collection created", { name, dimension, metric });
|
|
701
876
|
return ok(void 0);
|
|
702
877
|
},
|
|
@@ -707,7 +882,6 @@ function createLancedbProvider() {
|
|
|
707
882
|
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
|
|
708
883
|
}
|
|
709
884
|
await connection.dropTable(name);
|
|
710
|
-
collectionMetas.delete(name);
|
|
711
885
|
logger2.info("Collection dropped", { name });
|
|
712
886
|
return ok(void 0);
|
|
713
887
|
},
|
|
@@ -718,18 +892,7 @@ function createLancedbProvider() {
|
|
|
718
892
|
},
|
|
719
893
|
async info(name) {
|
|
720
894
|
logger2.debug("Getting collection info", { name });
|
|
721
|
-
|
|
722
|
-
if (!table) {
|
|
723
|
-
return err(HaiVecdbError.COLLECTION_NOT_FOUND, vecdbM("vecdb_collectionNotFound", { params: { name } }));
|
|
724
|
-
}
|
|
725
|
-
const count = await table.countRows();
|
|
726
|
-
const meta = collectionMetas.get(name);
|
|
727
|
-
return ok({
|
|
728
|
-
name,
|
|
729
|
-
dimension: meta?.dimension ?? 0,
|
|
730
|
-
metric: meta?.metric ?? config?.metric ?? "cosine",
|
|
731
|
-
count
|
|
732
|
-
});
|
|
895
|
+
return withCollection(name, async (table, meta) => ok({ name, dimension: meta.dimension, metric: meta.metric, count: await table.countRows() }));
|
|
733
896
|
},
|
|
734
897
|
async list() {
|
|
735
898
|
logger2.debug("Listing collections");
|
|
@@ -740,105 +903,100 @@ function createLancedbProvider() {
|
|
|
740
903
|
const vectorDriver = {
|
|
741
904
|
async insert(collection, documents) {
|
|
742
905
|
logger2.debug("Inserting vectors", { collection, count: documents.length });
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
906
|
+
return withCollection(collection, async (table, meta) => {
|
|
907
|
+
const atomicValidation = validateAtomicWrite(meta, documents);
|
|
908
|
+
if (atomicValidation)
|
|
909
|
+
return atomicValidation;
|
|
910
|
+
const dimensionValidation = validateDocumentsDimension(meta.dimension, documents);
|
|
911
|
+
if (dimensionValidation) {
|
|
912
|
+
return dimensionValidation;
|
|
913
|
+
}
|
|
914
|
+
const records = documents.map((doc) => ({
|
|
915
|
+
id: doc.id,
|
|
916
|
+
vector: doc.vector,
|
|
917
|
+
content: doc.content ?? "",
|
|
918
|
+
metadata: JSON.stringify(doc.metadata ?? {})
|
|
919
|
+
}));
|
|
920
|
+
const driver = loadLancedb();
|
|
921
|
+
if (!driver.success)
|
|
922
|
+
return driver;
|
|
923
|
+
await table.mergeInsert("id").whenNotMatchedInsertAll().execute(driver.data.makeArrowTable(records, { schema: meta.inputSchema }));
|
|
924
|
+
logger2.info("Vectors inserted", { collection, count: documents.length });
|
|
925
|
+
return ok(void 0);
|
|
926
|
+
});
|
|
760
927
|
},
|
|
761
928
|
async upsert(collection, documents) {
|
|
762
929
|
logger2.debug("Upserting vectors", { collection, count: documents.length });
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
logger2.info("Vectors upserted", { collection, count: documents.length });
|
|
786
|
-
return ok(void 0);
|
|
930
|
+
return withCollection(collection, async (table, meta) => {
|
|
931
|
+
const atomicValidation = validateAtomicWrite(meta, documents);
|
|
932
|
+
if (atomicValidation)
|
|
933
|
+
return atomicValidation;
|
|
934
|
+
const dimensionValidation = validateDocumentsDimension(meta.dimension, documents);
|
|
935
|
+
if (dimensionValidation) {
|
|
936
|
+
return dimensionValidation;
|
|
937
|
+
}
|
|
938
|
+
const records = documents.map((doc) => ({
|
|
939
|
+
id: doc.id,
|
|
940
|
+
vector: doc.vector,
|
|
941
|
+
content: doc.content ?? "",
|
|
942
|
+
metadata: JSON.stringify(doc.metadata ?? {})
|
|
943
|
+
}));
|
|
944
|
+
const driver = loadLancedb();
|
|
945
|
+
if (!driver.success)
|
|
946
|
+
return driver;
|
|
947
|
+
await table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(driver.data.makeArrowTable(records, { schema: meta.inputSchema }));
|
|
948
|
+
logger2.info("Vectors upserted", { collection, count: documents.length });
|
|
949
|
+
return ok(void 0);
|
|
950
|
+
});
|
|
787
951
|
},
|
|
788
952
|
async delete(collection, ids) {
|
|
789
953
|
logger2.debug("Deleting vectors", { collection, count: ids.length });
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
logger2.info("Vectors deleted", { collection, count: ids.length });
|
|
797
|
-
return ok(void 0);
|
|
954
|
+
return withCollection(collection, async (table) => {
|
|
955
|
+
const idList = ids.map((id) => `'${escapeLanceSqlValue(id)}'`).join(", ");
|
|
956
|
+
await table.delete(`id IN (${idList})`);
|
|
957
|
+
logger2.info("Vectors deleted", { collection, count: ids.length });
|
|
958
|
+
return ok(void 0);
|
|
959
|
+
});
|
|
798
960
|
},
|
|
799
961
|
async search(collection, vector, options) {
|
|
800
962
|
logger2.debug("Searching vectors", { collection, topK: options?.topK, hasFilter: !!options?.filter });
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const dimensionValidation = validateVectorDimension(collection, vector);
|
|
806
|
-
if (dimensionValidation) {
|
|
807
|
-
return dimensionValidation;
|
|
808
|
-
}
|
|
809
|
-
const topK = options?.topK ?? 10;
|
|
810
|
-
const minScore = options?.minScore ?? 0;
|
|
811
|
-
let query = table.search(vector).limit(topK);
|
|
812
|
-
if (options?.filter) {
|
|
813
|
-
const filterParts = [];
|
|
814
|
-
for (const [key, value] of Object.entries(options.filter)) {
|
|
815
|
-
filterParts.push(`metadata LIKE '%"${escapeLanceFilterValue(key)}":"${escapeLanceFilterValue(String(value))}"%'`);
|
|
963
|
+
return withCollection(collection, async (table, meta) => {
|
|
964
|
+
const dimensionValidation = validateVectorDimension(meta.dimension, vector);
|
|
965
|
+
if (dimensionValidation) {
|
|
966
|
+
return dimensionValidation;
|
|
816
967
|
}
|
|
817
|
-
|
|
818
|
-
|
|
968
|
+
const topK = options?.topK ?? 10;
|
|
969
|
+
const minScore = options?.minScore ?? 0;
|
|
970
|
+
let query = table.vectorSearch(vector).distanceType(meta.metric === "euclidean" ? "l2" : meta.metric).limit(topK);
|
|
971
|
+
if (options?.filter !== void 0) {
|
|
972
|
+
if (!meta.inputSchema)
|
|
973
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_jsonMetadataRequired"));
|
|
974
|
+
const predicate = buildMetadataPredicate(options.filter);
|
|
975
|
+
if (!predicate.success)
|
|
976
|
+
return predicate;
|
|
977
|
+
if (predicate.data)
|
|
978
|
+
query = query.where(predicate.data);
|
|
819
979
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
|
|
980
|
+
const results = await query.toArray();
|
|
981
|
+
const searchResults = results.map((row) => {
|
|
982
|
+
const distance = row._distance ?? 0;
|
|
983
|
+
const score = meta.metric === "cosine" ? Math.max(0, Math.min(1, 1 - distance / 2)) : meta.metric === "dot" ? 1 / (1 + Math.exp(distance - 1)) : 1 / (1 + Math.max(0, distance));
|
|
984
|
+
return {
|
|
985
|
+
id: row.id,
|
|
986
|
+
score,
|
|
987
|
+
content: row.content || void 0,
|
|
988
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
989
|
+
};
|
|
990
|
+
}).filter((r) => r.score >= minScore);
|
|
991
|
+
return ok(searchResults);
|
|
992
|
+
});
|
|
833
993
|
},
|
|
834
994
|
async count(collection) {
|
|
835
995
|
logger2.debug("Counting vectors", { collection });
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return
|
|
839
|
-
}
|
|
840
|
-
const count = await table.countRows();
|
|
841
|
-
return ok(count);
|
|
996
|
+
return withCollection(collection, async (table) => {
|
|
997
|
+
const count = await table.countRows();
|
|
998
|
+
return ok(count);
|
|
999
|
+
});
|
|
842
1000
|
}
|
|
843
1001
|
};
|
|
844
1002
|
return {
|
|
@@ -853,36 +1011,22 @@ function createLancedbProvider() {
|
|
|
853
1011
|
return loadResult;
|
|
854
1012
|
const lancedb = loadResult.data;
|
|
855
1013
|
try {
|
|
856
|
-
connection = await lancedb.connect(lanceConfig.path);
|
|
1014
|
+
connection = await lancedb.connect(lanceConfig.path, { readConsistencyInterval: 0 });
|
|
857
1015
|
config = lanceConfig;
|
|
858
|
-
const tableNames = await connection.tableNames();
|
|
859
|
-
await Promise.allSettled(tableNames.map(async (name) => {
|
|
860
|
-
try {
|
|
861
|
-
const table = await connection.openTable(name);
|
|
862
|
-
const count = await table.countRows();
|
|
863
|
-
if (count === 0)
|
|
864
|
-
return;
|
|
865
|
-
const rows = await table.search(Array.from({ length: 1 }).fill(0)).limit(1).toArray();
|
|
866
|
-
if (rows.length > 0 && rows[0].vector) {
|
|
867
|
-
collectionMetas.set(name, {
|
|
868
|
-
dimension: rows[0].vector.length,
|
|
869
|
-
metric: lanceConfig.metric ?? "cosine"
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
} catch {
|
|
873
|
-
}
|
|
874
|
-
}));
|
|
875
1016
|
logger2.info("LanceDB connected", { path: lanceConfig.path });
|
|
876
1017
|
return ok(void 0);
|
|
877
1018
|
} catch (error) {
|
|
1019
|
+
connection?.close();
|
|
1020
|
+
connection = null;
|
|
1021
|
+
config = null;
|
|
878
1022
|
logger2.error("Failed to connect to LanceDB", { error });
|
|
879
1023
|
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_connectionFailed", { params: { error: String(error) } }), error);
|
|
880
1024
|
}
|
|
881
1025
|
},
|
|
882
1026
|
async close() {
|
|
1027
|
+
connection?.close();
|
|
883
1028
|
connection = null;
|
|
884
1029
|
config = null;
|
|
885
|
-
collectionMetas.clear();
|
|
886
1030
|
logger2.info("LanceDB connection closed");
|
|
887
1031
|
return ok(void 0);
|
|
888
1032
|
},
|
|
@@ -1257,18 +1401,6 @@ function createPgvectorProvider() {
|
|
|
1257
1401
|
vector: createBaseVectorOps(ctx, vectorDriver)
|
|
1258
1402
|
};
|
|
1259
1403
|
}
|
|
1260
|
-
function sanitizeUrl(url) {
|
|
1261
|
-
try {
|
|
1262
|
-
const parsed = new URL(url);
|
|
1263
|
-
if (parsed.username)
|
|
1264
|
-
parsed.username = "***";
|
|
1265
|
-
if (parsed.password)
|
|
1266
|
-
parsed.password = "***";
|
|
1267
|
-
return parsed.toString();
|
|
1268
|
-
} catch {
|
|
1269
|
-
return "(invalid url)";
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
1404
|
var logger4 = core.logger.child({ module: "vecdb", scope: "qdrant" });
|
|
1273
1405
|
function isQdrantModule(value) {
|
|
1274
1406
|
if (typeof value !== "object" || value === null) {
|
|
@@ -1289,8 +1421,6 @@ function toQdrantDistance(metric) {
|
|
|
1289
1421
|
return "Euclid";
|
|
1290
1422
|
case "dot":
|
|
1291
1423
|
return "Dot";
|
|
1292
|
-
default:
|
|
1293
|
-
return "Cosine";
|
|
1294
1424
|
}
|
|
1295
1425
|
}
|
|
1296
1426
|
function createQdrantProvider() {
|
|
@@ -1318,12 +1448,7 @@ function createQdrantProvider() {
|
|
|
1318
1448
|
}
|
|
1319
1449
|
}
|
|
1320
1450
|
function isQdrantNotFoundError(error) {
|
|
1321
|
-
|
|
1322
|
-
if (getCollectionErrorCtor && error instanceof getCollectionErrorCtor) {
|
|
1323
|
-
return error.getActualType?.().status === 404;
|
|
1324
|
-
}
|
|
1325
|
-
const maybeError = error;
|
|
1326
|
-
return maybeError.status === 404 || maybeError.response?.status === 404 || maybeError.$metadata?.httpStatusCode === 404;
|
|
1451
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
1327
1452
|
}
|
|
1328
1453
|
async function getCollectionInfo(name) {
|
|
1329
1454
|
try {
|
|
@@ -1335,12 +1460,22 @@ function createQdrantProvider() {
|
|
|
1335
1460
|
throw error;
|
|
1336
1461
|
}
|
|
1337
1462
|
}
|
|
1463
|
+
function readVectorDefinition(info) {
|
|
1464
|
+
const vectors = info.config.params.vectors;
|
|
1465
|
+
if (!vectors || !("size" in vectors) || typeof vectors.size !== "number" || !Number.isSafeInteger(vectors.size) || vectors.size <= 0)
|
|
1466
|
+
return void 0;
|
|
1467
|
+
if (!("distance" in vectors))
|
|
1468
|
+
return void 0;
|
|
1469
|
+
const metric = vectors.distance === "Cosine" ? "cosine" : vectors.distance === "Euclid" ? "euclidean" : vectors.distance === "Dot" ? "dot" : void 0;
|
|
1470
|
+
return metric ? { dimension: vectors.size, metric } : void 0;
|
|
1471
|
+
}
|
|
1338
1472
|
async function getCollectionDimension(name) {
|
|
1339
1473
|
const infoResult = await getCollectionInfo(name);
|
|
1340
1474
|
if (!infoResult.success) {
|
|
1341
1475
|
return infoResult;
|
|
1342
1476
|
}
|
|
1343
|
-
|
|
1477
|
+
const definition = readVectorDefinition(infoResult.data);
|
|
1478
|
+
return definition ? ok(definition.dimension) : err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
|
|
1344
1479
|
}
|
|
1345
1480
|
function validateVectorDimension(expected, actual) {
|
|
1346
1481
|
if (expected === actual) {
|
|
@@ -1401,14 +1536,12 @@ function createQdrantProvider() {
|
|
|
1401
1536
|
return infoResult;
|
|
1402
1537
|
}
|
|
1403
1538
|
const collectionInfo = infoResult.data;
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const metric = qdrantDistance === "Euclid" ? "euclidean" : qdrantDistance === "Dot" ? "dot" : "cosine";
|
|
1539
|
+
const definition = readVectorDefinition(collectionInfo);
|
|
1540
|
+
if (!definition)
|
|
1541
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_invalidCollectionSchema", { params: { name } }));
|
|
1408
1542
|
return ok({
|
|
1409
1543
|
name,
|
|
1410
|
-
|
|
1411
|
-
metric,
|
|
1544
|
+
...definition,
|
|
1412
1545
|
count: collectionInfo.points_count ?? 0
|
|
1413
1546
|
});
|
|
1414
1547
|
},
|
|
@@ -1441,7 +1574,7 @@ function createQdrantProvider() {
|
|
|
1441
1574
|
_content: doc.content ?? ""
|
|
1442
1575
|
}
|
|
1443
1576
|
}));
|
|
1444
|
-
await client.upsert(collection, { points });
|
|
1577
|
+
await client.upsert(collection, { points, wait: true });
|
|
1445
1578
|
logger4.info("Vectors inserted", { collection, count: documents.length });
|
|
1446
1579
|
return ok(void 0);
|
|
1447
1580
|
},
|
|
@@ -1466,14 +1599,14 @@ function createQdrantProvider() {
|
|
|
1466
1599
|
_content: doc.content ?? ""
|
|
1467
1600
|
}
|
|
1468
1601
|
}));
|
|
1469
|
-
await client.upsert(collection, { points });
|
|
1602
|
+
await client.upsert(collection, { points, wait: true });
|
|
1470
1603
|
logger4.info("Vectors upserted", { collection, count: documents.length });
|
|
1471
1604
|
return ok(void 0);
|
|
1472
1605
|
},
|
|
1473
1606
|
async delete(collection, ids) {
|
|
1474
1607
|
logger4.debug("Deleting vectors", { collection, count: ids.length });
|
|
1475
1608
|
const uuids = ids.map(hashToUuid);
|
|
1476
|
-
await client.delete(collection, { points: uuids });
|
|
1609
|
+
await client.delete(collection, { points: uuids, wait: true });
|
|
1477
1610
|
logger4.info("Vectors deleted", { collection, count: ids.length });
|
|
1478
1611
|
return ok(void 0);
|
|
1479
1612
|
},
|
|
@@ -1508,9 +1641,9 @@ function createQdrantProvider() {
|
|
|
1508
1641
|
const payload = point.payload ?? {};
|
|
1509
1642
|
const { _id, _content, ...metadata } = payload;
|
|
1510
1643
|
return {
|
|
1511
|
-
id: _id
|
|
1644
|
+
id: typeof _id === "string" ? _id : "",
|
|
1512
1645
|
score: point.score,
|
|
1513
|
-
content: _content
|
|
1646
|
+
content: typeof _content === "string" && _content.length > 0 ? _content : void 0,
|
|
1514
1647
|
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
1515
1648
|
};
|
|
1516
1649
|
});
|
|
@@ -1518,11 +1651,8 @@ function createQdrantProvider() {
|
|
|
1518
1651
|
},
|
|
1519
1652
|
async count(collection) {
|
|
1520
1653
|
logger4.debug("Counting vectors", { collection });
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
return infoResult;
|
|
1524
|
-
}
|
|
1525
|
-
return ok(infoResult.data.points_count ?? 0);
|
|
1654
|
+
const count = await client.count(collection, { exact: true });
|
|
1655
|
+
return ok(count.count);
|
|
1526
1656
|
}
|
|
1527
1657
|
};
|
|
1528
1658
|
return {
|
|
@@ -1545,7 +1675,7 @@ function createQdrantProvider() {
|
|
|
1545
1675
|
await qdrantClient.getCollections();
|
|
1546
1676
|
client = qdrantClient;
|
|
1547
1677
|
config = qdrantConfig;
|
|
1548
|
-
logger4.info("Qdrant connected", { url: sanitizeUrl(qdrantConfig.url) });
|
|
1678
|
+
logger4.info("Qdrant connected", { url: core.sanitize.sanitizeUrl(qdrantConfig.url) });
|
|
1549
1679
|
return ok(void 0);
|
|
1550
1680
|
} catch (error) {
|
|
1551
1681
|
logger4.error("Failed to connect to Qdrant", { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -1571,6 +1701,38 @@ var logger5 = core.logger.child({ module: "vecdb", scope: "main" });
|
|
|
1571
1701
|
var currentProvider = null;
|
|
1572
1702
|
var currentConfig = null;
|
|
1573
1703
|
var initInProgress = false;
|
|
1704
|
+
var pendingProvider = null;
|
|
1705
|
+
var initCompletion = null;
|
|
1706
|
+
var closingPromise = null;
|
|
1707
|
+
async function closeOwnedProvider() {
|
|
1708
|
+
if (currentProvider) {
|
|
1709
|
+
pendingProvider = currentProvider;
|
|
1710
|
+
currentProvider = null;
|
|
1711
|
+
}
|
|
1712
|
+
currentConfig = null;
|
|
1713
|
+
if (!pendingProvider)
|
|
1714
|
+
return ok(void 0);
|
|
1715
|
+
try {
|
|
1716
|
+
const result = await pendingProvider.close();
|
|
1717
|
+
if (!result.success)
|
|
1718
|
+
return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true } });
|
|
1719
|
+
pendingProvider = null;
|
|
1720
|
+
return result;
|
|
1721
|
+
} catch (cause) {
|
|
1722
|
+
return err({ ...HaiVecdbError.CONNECTION_FAILED, message: vecdbM("vecdb_closeFailed", { params: { error: cause instanceof Error ? cause.message : String(cause) } }), cause, ext: { cleanupPending: true } });
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
async function failInitialization(result) {
|
|
1726
|
+
const cleanup = await closeOwnedProvider();
|
|
1727
|
+
if (result.success)
|
|
1728
|
+
return result;
|
|
1729
|
+
if (!cleanup.success)
|
|
1730
|
+
return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } });
|
|
1731
|
+
const ext = { ...result.error.ext };
|
|
1732
|
+
delete ext.cleanupPending;
|
|
1733
|
+
delete ext.cleanupError;
|
|
1734
|
+
return err({ ...result.error, ext: Object.keys(ext).length ? ext : void 0 });
|
|
1735
|
+
}
|
|
1574
1736
|
function createProvider(config) {
|
|
1575
1737
|
switch (config.type) {
|
|
1576
1738
|
case "lancedb":
|
|
@@ -1589,6 +1751,20 @@ var notInitialized = core.module.createNotInitializedKit(
|
|
|
1589
1751
|
);
|
|
1590
1752
|
var notInitializedCollection = notInitialized.proxy();
|
|
1591
1753
|
var notInitializedVector = notInitialized.proxy();
|
|
1754
|
+
var collectionOps = {
|
|
1755
|
+
create: (name, options) => currentProvider && !closingPromise ? currentProvider.collection.create(name, options) : notInitializedCollection.create(name, options),
|
|
1756
|
+
drop: (name) => currentProvider && !closingPromise ? currentProvider.collection.drop(name) : notInitializedCollection.drop(name),
|
|
1757
|
+
exists: (name) => currentProvider && !closingPromise ? currentProvider.collection.exists(name) : notInitializedCollection.exists(name),
|
|
1758
|
+
info: (name) => currentProvider && !closingPromise ? currentProvider.collection.info(name) : notInitializedCollection.info(name),
|
|
1759
|
+
list: () => currentProvider && !closingPromise ? currentProvider.collection.list() : notInitializedCollection.list()
|
|
1760
|
+
};
|
|
1761
|
+
var vectorOps = {
|
|
1762
|
+
insert: (collection, documents) => currentProvider && !closingPromise ? currentProvider.vector.insert(collection, documents) : notInitializedVector.insert(collection, documents),
|
|
1763
|
+
upsert: (collection, documents) => currentProvider && !closingPromise ? currentProvider.vector.upsert(collection, documents) : notInitializedVector.upsert(collection, documents),
|
|
1764
|
+
delete: (collection, ids) => currentProvider && !closingPromise ? currentProvider.vector.delete(collection, ids) : notInitializedVector.delete(collection, ids),
|
|
1765
|
+
search: (collection, vector, options) => currentProvider && !closingPromise ? currentProvider.vector.search(collection, vector, options) : notInitializedVector.search(collection, vector, options),
|
|
1766
|
+
count: (collection) => currentProvider && !closingPromise ? currentProvider.vector.count(collection) : notInitializedVector.count(collection)
|
|
1767
|
+
};
|
|
1592
1768
|
var vecdb = {
|
|
1593
1769
|
/**
|
|
1594
1770
|
* 初始化向量数据库连接
|
|
@@ -1597,19 +1773,19 @@ var vecdb = {
|
|
|
1597
1773
|
* @returns 初始化结果,失败时包含错误信息
|
|
1598
1774
|
*/
|
|
1599
1775
|
async init(config) {
|
|
1600
|
-
if (initInProgress) {
|
|
1776
|
+
if (initInProgress || closingPromise) {
|
|
1601
1777
|
logger5.warn("Vecdb init already in progress, skipping concurrent call");
|
|
1602
1778
|
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_initInProgress"));
|
|
1603
1779
|
}
|
|
1604
1780
|
initInProgress = true;
|
|
1781
|
+
let resolveInit;
|
|
1782
|
+
initCompletion = new Promise((resolve) => {
|
|
1783
|
+
resolveInit = resolve;
|
|
1784
|
+
});
|
|
1605
1785
|
try {
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
if (!closeResult.success) {
|
|
1610
|
-
return closeResult;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1786
|
+
const cleanup = await closeOwnedProvider();
|
|
1787
|
+
if (!cleanup.success)
|
|
1788
|
+
return cleanup;
|
|
1613
1789
|
logger5.info("Initializing vecdb module");
|
|
1614
1790
|
const parseResult = VecdbConfigSchema.safeParse(config);
|
|
1615
1791
|
if (!parseResult.success) {
|
|
@@ -1617,26 +1793,32 @@ var vecdb = {
|
|
|
1617
1793
|
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
|
|
1618
1794
|
}
|
|
1619
1795
|
const parsed = parseResult.data;
|
|
1796
|
+
if (core.module.requiresSharedState(process.env) && (parsed.type === "lancedb" || parsed.type === "chroma" && !!parsed.path && !parsed.url))
|
|
1797
|
+
return err(HaiVecdbError.CONFIG_ERROR, vecdbM("vecdb_sharedBackendRequired"));
|
|
1620
1798
|
try {
|
|
1621
1799
|
const provider = createProvider(parsed);
|
|
1800
|
+
pendingProvider = provider;
|
|
1622
1801
|
const connectResult = await provider.connect(parsed);
|
|
1623
1802
|
if (!connectResult.success) {
|
|
1624
1803
|
logger5.error("Vecdb module initialization failed", {
|
|
1625
1804
|
code: connectResult.error.code,
|
|
1626
1805
|
message: connectResult.error.message
|
|
1627
1806
|
});
|
|
1628
|
-
return connectResult;
|
|
1807
|
+
return await failInitialization(connectResult);
|
|
1629
1808
|
}
|
|
1809
|
+
pendingProvider = null;
|
|
1630
1810
|
currentProvider = provider;
|
|
1631
1811
|
currentConfig = parsed;
|
|
1632
1812
|
logger5.info("Vecdb module initialized", { type: parsed.type });
|
|
1633
1813
|
return ok(void 0);
|
|
1634
1814
|
} catch (error) {
|
|
1635
1815
|
logger5.error("Vecdb module initialization failed", { error });
|
|
1636
|
-
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
|
|
1816
|
+
return await failInitialization(err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error));
|
|
1637
1817
|
}
|
|
1638
1818
|
} finally {
|
|
1639
1819
|
initInProgress = false;
|
|
1820
|
+
initCompletion = null;
|
|
1821
|
+
resolveInit();
|
|
1640
1822
|
}
|
|
1641
1823
|
},
|
|
1642
1824
|
/**
|
|
@@ -1645,7 +1827,7 @@ var vecdb = {
|
|
|
1645
1827
|
* 未初始化时返回占位对象(所有调用返回 NOT_INITIALIZED)。
|
|
1646
1828
|
*/
|
|
1647
1829
|
get collection() {
|
|
1648
|
-
return
|
|
1830
|
+
return collectionOps;
|
|
1649
1831
|
},
|
|
1650
1832
|
/**
|
|
1651
1833
|
* 获取向量操作接口
|
|
@@ -1653,15 +1835,15 @@ var vecdb = {
|
|
|
1653
1835
|
* 未初始化时返回占位对象(所有调用返回 NOT_INITIALIZED)。
|
|
1654
1836
|
*/
|
|
1655
1837
|
get vector() {
|
|
1656
|
-
return
|
|
1838
|
+
return vectorOps;
|
|
1657
1839
|
},
|
|
1658
1840
|
/** 获取当前脱敏配置快照(未初始化时为 null) */
|
|
1659
1841
|
get config() {
|
|
1660
|
-
return currentConfig ? core.sanitize.sanitizeSensitiveFields(currentConfig) : null;
|
|
1842
|
+
return currentConfig && !closingPromise ? core.sanitize.sanitizeSensitiveFields(currentConfig) : null;
|
|
1661
1843
|
},
|
|
1662
1844
|
/** 获取底层后端的原始连接信息(含凭证,未初始化时为 null;仅供同进程内集成使用) */
|
|
1663
1845
|
get rawConnection() {
|
|
1664
|
-
if (!currentConfig)
|
|
1846
|
+
if (!currentConfig || closingPromise)
|
|
1665
1847
|
return null;
|
|
1666
1848
|
if (currentConfig.type === "qdrant")
|
|
1667
1849
|
return { type: "qdrant", url: currentConfig.url, apiKey: currentConfig.apiKey };
|
|
@@ -1690,7 +1872,7 @@ var vecdb = {
|
|
|
1690
1872
|
},
|
|
1691
1873
|
/** 检查是否已初始化 */
|
|
1692
1874
|
get isInitialized() {
|
|
1693
|
-
return currentProvider !== null && currentProvider.isConnected();
|
|
1875
|
+
return currentProvider !== null && !closingPromise && currentProvider.isConnected();
|
|
1694
1876
|
},
|
|
1695
1877
|
/**
|
|
1696
1878
|
* 关闭向量数据库连接
|
|
@@ -1698,27 +1880,16 @@ var vecdb = {
|
|
|
1698
1880
|
* 多次调用安全,未初始化时直接返回。
|
|
1699
1881
|
*/
|
|
1700
1882
|
async close() {
|
|
1701
|
-
if (!
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
if (!closeResult.success) {
|
|
1710
|
-
logger5.error("Vecdb module close failed", { code: closeResult.error.code, message: closeResult.error.message });
|
|
1711
|
-
return closeResult;
|
|
1712
|
-
}
|
|
1713
|
-
logger5.info("Vecdb module closed");
|
|
1714
|
-
return ok(void 0);
|
|
1715
|
-
} catch (error) {
|
|
1716
|
-
logger5.error("Vecdb module close failed", { error });
|
|
1717
|
-
return err(HaiVecdbError.CONNECTION_FAILED, vecdbM("vecdb_closeFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
|
|
1718
|
-
} finally {
|
|
1719
|
-
currentProvider = null;
|
|
1720
|
-
currentConfig = null;
|
|
1883
|
+
if (!closingPromise) {
|
|
1884
|
+
closingPromise = (async () => {
|
|
1885
|
+
if (initCompletion)
|
|
1886
|
+
await initCompletion;
|
|
1887
|
+
return closeOwnedProvider();
|
|
1888
|
+
})().finally(() => {
|
|
1889
|
+
closingPromise = null;
|
|
1890
|
+
});
|
|
1721
1891
|
}
|
|
1892
|
+
return await closingPromise;
|
|
1722
1893
|
}
|
|
1723
1894
|
};
|
|
1724
1895
|
|