@vectorstores/upstash 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) vectorstores contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,181 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var core = require('@vectorstores/core');
4
+ var vector = require('@upstash/vector');
5
+ var env = require('@vectorstores/env');
6
+
7
+ /**
8
+ * Provides support for writing and querying vector data in Upstash.
9
+ */ class UpstashVectorStore extends core.BaseVectorStore {
10
+ /**
11
+ * @param namespace namespace to use
12
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
13
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
14
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
19
+ * ```
20
+ */ constructor(params){
21
+ super(params), this.storesText = true;
22
+ this.namespace = params?.namespace ?? "";
23
+ this.maxBatchSize = params?.maxBatchSize ?? 1000;
24
+ const token = params?.token ?? env.getEnv("UPSTASH_VECTOR_REST_TOKEN");
25
+ const endpoint = params?.endpoint ?? env.getEnv("UPSTASH_VECTOR_REST_URL");
26
+ if (!token) {
27
+ throw new Error("Must specify UPSTASH_VECTOR_REST_TOKEN via env variable.");
28
+ }
29
+ if (!endpoint) {
30
+ throw new Error("Must specify UPSTASH_VECTOR_REST_URL via env variable.");
31
+ }
32
+ this.db = new vector.Index({
33
+ token,
34
+ url: endpoint
35
+ });
36
+ }
37
+ async getDb() {
38
+ if (!this.db) {
39
+ const { Index } = await import('@upstash/vector');
40
+ this.db = new Index();
41
+ }
42
+ return this.db;
43
+ }
44
+ /**
45
+ * Connects to the database specified in environment vars.
46
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
47
+ */ client() {
48
+ return this.getDb();
49
+ }
50
+ /**
51
+ * Adds vector record(s) to the table.
52
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
53
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
54
+ */ async add(embeddingResults) {
55
+ if (embeddingResults.length == 0) {
56
+ return [];
57
+ }
58
+ const nodes = embeddingResults.map(this.nodeToRecord);
59
+ const result = await this.upsertInBatches(nodes);
60
+ if (result != "OK") {
61
+ throw new Error("Failed to save chunk");
62
+ }
63
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
64
+ }
65
+ /**
66
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
67
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
68
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
69
+ */ async addPlainText(text) {
70
+ if (text.length == 0) {
71
+ return [];
72
+ }
73
+ const nodes = text.map(this.textNodeToRecord);
74
+ const result = await this.upsertInBatches(nodes);
75
+ if (result != "OK") {
76
+ throw new Error("Failed to save chunk");
77
+ }
78
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
79
+ }
80
+ async upsertInBatches(nodes) {
81
+ const promises = [];
82
+ for(let i = 0; i < nodes.length; i += this.maxBatchSize){
83
+ const batch = nodes.slice(i, i + this.maxBatchSize);
84
+ promises.push(this.db.upsert(batch, {
85
+ namespace: this.namespace
86
+ }));
87
+ }
88
+ const results = await Promise.all(promises);
89
+ return results.every((result)=>result === "OK") ? "OK" : "NOT-OK";
90
+ }
91
+ /**
92
+ * Deletes a single record from the database by id.
93
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
94
+ * @param refDocId Unique identifier for the record to delete.
95
+ * @returns Promise that resolves if the delete query did not throw an error.
96
+ */ async delete(refDocId) {
97
+ await this.db.namespace(this.namespace).delete(refDocId);
98
+ }
99
+ /**
100
+ * Deletes a single record from the database by id.
101
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
102
+ * @param refDocId Unique identifier for the record to delete.
103
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
104
+ * @returns Promise that resolves if the delete query did not throw an error.
105
+ */ async deleteMany(refDocId) {
106
+ await this.db.namespace(this.namespace).delete(refDocId);
107
+ }
108
+ /**
109
+ * Query the vector store for the closest matching data to the query embeddings
110
+ * @param query The VectorStoreQuery to be used
111
+ * @param options Required by VectorStore interface. Currently ignored.
112
+ * @returns Zero or more Document instances with data from the vector store.
113
+ */ async query(query, _options) {
114
+ const filter = this.toUpstashFilter(query.filters);
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ const defaultOptions = {
117
+ vector: query.queryEmbedding,
118
+ topK: query.similarityTopK,
119
+ includeVectors: true,
120
+ includeMetadata: true,
121
+ filter
122
+ };
123
+ const db = this.db;
124
+ const results = await db.query(defaultOptions, {
125
+ namespace: this.namespace
126
+ });
127
+ const nodes = results.map((result)=>{
128
+ const node = core.metadataDictToNode(result.metadata ?? {}, {
129
+ fallback: {
130
+ id: result.id,
131
+ metadata: result.metadata,
132
+ embedding: result.vector
133
+ }
134
+ });
135
+ return node;
136
+ });
137
+ const ret = {
138
+ nodes: nodes,
139
+ similarities: results.map((row)=>row.score || 999),
140
+ ids: results.map((row)=>String(row.id))
141
+ };
142
+ return ret;
143
+ }
144
+ toFilterString(filter) {
145
+ return `${filter.key} ${filter.operator} ${filter.value}`;
146
+ }
147
+ toUpstashFilter(stdFilters) {
148
+ if (!stdFilters?.filters) return;
149
+ for (const item of stdFilters.filters){
150
+ if (item.operator === "==") {
151
+ //@ts-expect-error Upstash equal operator uses only one equal sign, so we have to replace it.
152
+ item.operator = "=";
153
+ }
154
+ }
155
+ const filterStrings = stdFilters.filters.map(this.toFilterString);
156
+ if (filterStrings.length === 1) {
157
+ return filterStrings[0];
158
+ }
159
+ return filterStrings.join(` ${stdFilters.condition ?? "and"} `);
160
+ }
161
+ nodeToRecord(node) {
162
+ const id = node.id_.length ? node.id_ : null;
163
+ return {
164
+ // fixme: why id is possibly null?
165
+ id: id,
166
+ vector: node.getEmbedding(),
167
+ metadata: core.nodeToMetadata(node)
168
+ };
169
+ }
170
+ textNodeToRecord(node) {
171
+ const id = node.id_.length ? node.id_ : null;
172
+ return {
173
+ // fixme: why id is possibly null?
174
+ id: id,
175
+ data: node.text,
176
+ metadata: core.nodeToMetadata(node)
177
+ };
178
+ }
179
+ }
180
+
181
+ exports.UpstashVectorStore = UpstashVectorStore;
@@ -0,0 +1,85 @@
1
+ import { BaseVectorStore, VectorStoreBaseParams, BaseNode, Metadata, TextNode, VectorStoreQuery, VectorStoreQueryResult, MetadataFilter, MetadataFilters } from '@vectorstores/core';
2
+ import { Index } from '@upstash/vector';
3
+
4
+ type UpstashParams = {
5
+ namespace?: string;
6
+ token?: string;
7
+ endpoint?: string;
8
+ maxBatchSize?: number;
9
+ } & VectorStoreBaseParams;
10
+ /**
11
+ * Provides support for writing and querying vector data in Upstash.
12
+ */
13
+ declare class UpstashVectorStore extends BaseVectorStore {
14
+ storesText: boolean;
15
+ private db;
16
+ private maxBatchSize;
17
+ namespace: string;
18
+ /**
19
+ * @param namespace namespace to use
20
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
21
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
22
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
27
+ * ```
28
+ */
29
+ constructor(params?: UpstashParams);
30
+ private getDb;
31
+ /**
32
+ * Connects to the database specified in environment vars.
33
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
34
+ */
35
+ client(): Promise<Index>;
36
+ /**
37
+ * Adds vector record(s) to the table.
38
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
39
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
40
+ */
41
+ add(embeddingResults: BaseNode<Metadata>[]): Promise<string[]>;
42
+ /**
43
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
44
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
45
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
46
+ */
47
+ addPlainText(text: TextNode<Metadata>[]): Promise<string[]>;
48
+ private upsertInBatches;
49
+ /**
50
+ * Deletes a single record from the database by id.
51
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
52
+ * @param refDocId Unique identifier for the record to delete.
53
+ * @returns Promise that resolves if the delete query did not throw an error.
54
+ */
55
+ delete(refDocId: string): Promise<void>;
56
+ /**
57
+ * Deletes a single record from the database by id.
58
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
59
+ * @param refDocId Unique identifier for the record to delete.
60
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
61
+ * @returns Promise that resolves if the delete query did not throw an error.
62
+ */
63
+ deleteMany(refDocId: string[]): Promise<void>;
64
+ /**
65
+ * Query the vector store for the closest matching data to the query embeddings
66
+ * @param query The VectorStoreQuery to be used
67
+ * @param options Required by VectorStore interface. Currently ignored.
68
+ * @returns Zero or more Document instances with data from the vector store.
69
+ */
70
+ query(query: VectorStoreQuery, _options?: object): Promise<VectorStoreQueryResult>;
71
+ toFilterString(filter: MetadataFilter): string;
72
+ toUpstashFilter(stdFilters?: MetadataFilters): string | undefined;
73
+ nodeToRecord(node: BaseNode<Metadata>): {
74
+ id: string;
75
+ vector: number[];
76
+ metadata: Metadata;
77
+ };
78
+ textNodeToRecord(node: TextNode<Metadata>): {
79
+ id: string;
80
+ data: string;
81
+ metadata: Metadata;
82
+ };
83
+ }
84
+
85
+ export { UpstashVectorStore };
@@ -0,0 +1,85 @@
1
+ import { BaseVectorStore, VectorStoreBaseParams, BaseNode, Metadata, TextNode, VectorStoreQuery, VectorStoreQueryResult, MetadataFilter, MetadataFilters } from '@vectorstores/core';
2
+ import { Index } from '@upstash/vector';
3
+
4
+ type UpstashParams = {
5
+ namespace?: string;
6
+ token?: string;
7
+ endpoint?: string;
8
+ maxBatchSize?: number;
9
+ } & VectorStoreBaseParams;
10
+ /**
11
+ * Provides support for writing and querying vector data in Upstash.
12
+ */
13
+ declare class UpstashVectorStore extends BaseVectorStore {
14
+ storesText: boolean;
15
+ private db;
16
+ private maxBatchSize;
17
+ namespace: string;
18
+ /**
19
+ * @param namespace namespace to use
20
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
21
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
22
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
27
+ * ```
28
+ */
29
+ constructor(params?: UpstashParams);
30
+ private getDb;
31
+ /**
32
+ * Connects to the database specified in environment vars.
33
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
34
+ */
35
+ client(): Promise<Index>;
36
+ /**
37
+ * Adds vector record(s) to the table.
38
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
39
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
40
+ */
41
+ add(embeddingResults: BaseNode<Metadata>[]): Promise<string[]>;
42
+ /**
43
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
44
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
45
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
46
+ */
47
+ addPlainText(text: TextNode<Metadata>[]): Promise<string[]>;
48
+ private upsertInBatches;
49
+ /**
50
+ * Deletes a single record from the database by id.
51
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
52
+ * @param refDocId Unique identifier for the record to delete.
53
+ * @returns Promise that resolves if the delete query did not throw an error.
54
+ */
55
+ delete(refDocId: string): Promise<void>;
56
+ /**
57
+ * Deletes a single record from the database by id.
58
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
59
+ * @param refDocId Unique identifier for the record to delete.
60
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
61
+ * @returns Promise that resolves if the delete query did not throw an error.
62
+ */
63
+ deleteMany(refDocId: string[]): Promise<void>;
64
+ /**
65
+ * Query the vector store for the closest matching data to the query embeddings
66
+ * @param query The VectorStoreQuery to be used
67
+ * @param options Required by VectorStore interface. Currently ignored.
68
+ * @returns Zero or more Document instances with data from the vector store.
69
+ */
70
+ query(query: VectorStoreQuery, _options?: object): Promise<VectorStoreQueryResult>;
71
+ toFilterString(filter: MetadataFilter): string;
72
+ toUpstashFilter(stdFilters?: MetadataFilters): string | undefined;
73
+ nodeToRecord(node: BaseNode<Metadata>): {
74
+ id: string;
75
+ vector: number[];
76
+ metadata: Metadata;
77
+ };
78
+ textNodeToRecord(node: TextNode<Metadata>): {
79
+ id: string;
80
+ data: string;
81
+ metadata: Metadata;
82
+ };
83
+ }
84
+
85
+ export { UpstashVectorStore };
@@ -0,0 +1,85 @@
1
+ import { BaseVectorStore, VectorStoreBaseParams, BaseNode, Metadata, TextNode, VectorStoreQuery, VectorStoreQueryResult, MetadataFilter, MetadataFilters } from '@vectorstores/core';
2
+ import { Index } from '@upstash/vector';
3
+
4
+ type UpstashParams = {
5
+ namespace?: string;
6
+ token?: string;
7
+ endpoint?: string;
8
+ maxBatchSize?: number;
9
+ } & VectorStoreBaseParams;
10
+ /**
11
+ * Provides support for writing and querying vector data in Upstash.
12
+ */
13
+ declare class UpstashVectorStore extends BaseVectorStore {
14
+ storesText: boolean;
15
+ private db;
16
+ private maxBatchSize;
17
+ namespace: string;
18
+ /**
19
+ * @param namespace namespace to use
20
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
21
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
22
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
27
+ * ```
28
+ */
29
+ constructor(params?: UpstashParams);
30
+ private getDb;
31
+ /**
32
+ * Connects to the database specified in environment vars.
33
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
34
+ */
35
+ client(): Promise<Index>;
36
+ /**
37
+ * Adds vector record(s) to the table.
38
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
39
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
40
+ */
41
+ add(embeddingResults: BaseNode<Metadata>[]): Promise<string[]>;
42
+ /**
43
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
44
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
45
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
46
+ */
47
+ addPlainText(text: TextNode<Metadata>[]): Promise<string[]>;
48
+ private upsertInBatches;
49
+ /**
50
+ * Deletes a single record from the database by id.
51
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
52
+ * @param refDocId Unique identifier for the record to delete.
53
+ * @returns Promise that resolves if the delete query did not throw an error.
54
+ */
55
+ delete(refDocId: string): Promise<void>;
56
+ /**
57
+ * Deletes a single record from the database by id.
58
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
59
+ * @param refDocId Unique identifier for the record to delete.
60
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
61
+ * @returns Promise that resolves if the delete query did not throw an error.
62
+ */
63
+ deleteMany(refDocId: string[]): Promise<void>;
64
+ /**
65
+ * Query the vector store for the closest matching data to the query embeddings
66
+ * @param query The VectorStoreQuery to be used
67
+ * @param options Required by VectorStore interface. Currently ignored.
68
+ * @returns Zero or more Document instances with data from the vector store.
69
+ */
70
+ query(query: VectorStoreQuery, _options?: object): Promise<VectorStoreQueryResult>;
71
+ toFilterString(filter: MetadataFilter): string;
72
+ toUpstashFilter(stdFilters?: MetadataFilters): string | undefined;
73
+ nodeToRecord(node: BaseNode<Metadata>): {
74
+ id: string;
75
+ vector: number[];
76
+ metadata: Metadata;
77
+ };
78
+ textNodeToRecord(node: TextNode<Metadata>): {
79
+ id: string;
80
+ data: string;
81
+ metadata: Metadata;
82
+ };
83
+ }
84
+
85
+ export { UpstashVectorStore };
@@ -0,0 +1,179 @@
1
+ import { BaseVectorStore, metadataDictToNode, nodeToMetadata } from '@vectorstores/core';
2
+ import { Index } from '@upstash/vector';
3
+ import { getEnv } from '@vectorstores/env';
4
+
5
+ /**
6
+ * Provides support for writing and querying vector data in Upstash.
7
+ */ class UpstashVectorStore extends BaseVectorStore {
8
+ /**
9
+ * @param namespace namespace to use
10
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
11
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
12
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
17
+ * ```
18
+ */ constructor(params){
19
+ super(params), this.storesText = true;
20
+ this.namespace = params?.namespace ?? "";
21
+ this.maxBatchSize = params?.maxBatchSize ?? 1000;
22
+ const token = params?.token ?? getEnv("UPSTASH_VECTOR_REST_TOKEN");
23
+ const endpoint = params?.endpoint ?? getEnv("UPSTASH_VECTOR_REST_URL");
24
+ if (!token) {
25
+ throw new Error("Must specify UPSTASH_VECTOR_REST_TOKEN via env variable.");
26
+ }
27
+ if (!endpoint) {
28
+ throw new Error("Must specify UPSTASH_VECTOR_REST_URL via env variable.");
29
+ }
30
+ this.db = new Index({
31
+ token,
32
+ url: endpoint
33
+ });
34
+ }
35
+ async getDb() {
36
+ if (!this.db) {
37
+ const { Index } = await import('@upstash/vector');
38
+ this.db = new Index();
39
+ }
40
+ return this.db;
41
+ }
42
+ /**
43
+ * Connects to the database specified in environment vars.
44
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
45
+ */ client() {
46
+ return this.getDb();
47
+ }
48
+ /**
49
+ * Adds vector record(s) to the table.
50
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
51
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
52
+ */ async add(embeddingResults) {
53
+ if (embeddingResults.length == 0) {
54
+ return [];
55
+ }
56
+ const nodes = embeddingResults.map(this.nodeToRecord);
57
+ const result = await this.upsertInBatches(nodes);
58
+ if (result != "OK") {
59
+ throw new Error("Failed to save chunk");
60
+ }
61
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
62
+ }
63
+ /**
64
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
65
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
66
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
67
+ */ async addPlainText(text) {
68
+ if (text.length == 0) {
69
+ return [];
70
+ }
71
+ const nodes = text.map(this.textNodeToRecord);
72
+ const result = await this.upsertInBatches(nodes);
73
+ if (result != "OK") {
74
+ throw new Error("Failed to save chunk");
75
+ }
76
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
77
+ }
78
+ async upsertInBatches(nodes) {
79
+ const promises = [];
80
+ for(let i = 0; i < nodes.length; i += this.maxBatchSize){
81
+ const batch = nodes.slice(i, i + this.maxBatchSize);
82
+ promises.push(this.db.upsert(batch, {
83
+ namespace: this.namespace
84
+ }));
85
+ }
86
+ const results = await Promise.all(promises);
87
+ return results.every((result)=>result === "OK") ? "OK" : "NOT-OK";
88
+ }
89
+ /**
90
+ * Deletes a single record from the database by id.
91
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
92
+ * @param refDocId Unique identifier for the record to delete.
93
+ * @returns Promise that resolves if the delete query did not throw an error.
94
+ */ async delete(refDocId) {
95
+ await this.db.namespace(this.namespace).delete(refDocId);
96
+ }
97
+ /**
98
+ * Deletes a single record from the database by id.
99
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
100
+ * @param refDocId Unique identifier for the record to delete.
101
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
102
+ * @returns Promise that resolves if the delete query did not throw an error.
103
+ */ async deleteMany(refDocId) {
104
+ await this.db.namespace(this.namespace).delete(refDocId);
105
+ }
106
+ /**
107
+ * Query the vector store for the closest matching data to the query embeddings
108
+ * @param query The VectorStoreQuery to be used
109
+ * @param options Required by VectorStore interface. Currently ignored.
110
+ * @returns Zero or more Document instances with data from the vector store.
111
+ */ async query(query, _options) {
112
+ const filter = this.toUpstashFilter(query.filters);
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ const defaultOptions = {
115
+ vector: query.queryEmbedding,
116
+ topK: query.similarityTopK,
117
+ includeVectors: true,
118
+ includeMetadata: true,
119
+ filter
120
+ };
121
+ const db = this.db;
122
+ const results = await db.query(defaultOptions, {
123
+ namespace: this.namespace
124
+ });
125
+ const nodes = results.map((result)=>{
126
+ const node = metadataDictToNode(result.metadata ?? {}, {
127
+ fallback: {
128
+ id: result.id,
129
+ metadata: result.metadata,
130
+ embedding: result.vector
131
+ }
132
+ });
133
+ return node;
134
+ });
135
+ const ret = {
136
+ nodes: nodes,
137
+ similarities: results.map((row)=>row.score || 999),
138
+ ids: results.map((row)=>String(row.id))
139
+ };
140
+ return ret;
141
+ }
142
+ toFilterString(filter) {
143
+ return `${filter.key} ${filter.operator} ${filter.value}`;
144
+ }
145
+ toUpstashFilter(stdFilters) {
146
+ if (!stdFilters?.filters) return;
147
+ for (const item of stdFilters.filters){
148
+ if (item.operator === "==") {
149
+ //@ts-expect-error Upstash equal operator uses only one equal sign, so we have to replace it.
150
+ item.operator = "=";
151
+ }
152
+ }
153
+ const filterStrings = stdFilters.filters.map(this.toFilterString);
154
+ if (filterStrings.length === 1) {
155
+ return filterStrings[0];
156
+ }
157
+ return filterStrings.join(` ${stdFilters.condition ?? "and"} `);
158
+ }
159
+ nodeToRecord(node) {
160
+ const id = node.id_.length ? node.id_ : null;
161
+ return {
162
+ // fixme: why id is possibly null?
163
+ id: id,
164
+ vector: node.getEmbedding(),
165
+ metadata: nodeToMetadata(node)
166
+ };
167
+ }
168
+ textNodeToRecord(node) {
169
+ const id = node.id_.length ? node.id_ : null;
170
+ return {
171
+ // fixme: why id is possibly null?
172
+ id: id,
173
+ data: node.text,
174
+ metadata: nodeToMetadata(node)
175
+ };
176
+ }
177
+ }
178
+
179
+ export { UpstashVectorStore };
package/dist/index.js ADDED
@@ -0,0 +1,179 @@
1
+ import { BaseVectorStore, metadataDictToNode, nodeToMetadata } from '@vectorstores/core';
2
+ import { Index } from '@upstash/vector';
3
+ import { getEnv } from '@vectorstores/env';
4
+
5
+ /**
6
+ * Provides support for writing and querying vector data in Upstash.
7
+ */ class UpstashVectorStore extends BaseVectorStore {
8
+ /**
9
+ * @param namespace namespace to use
10
+ * @param token upstash vector token. if not set, `process.env.UPSTASH_VECTOR_REST_TOKEN` is used.
11
+ * @param endpoint upstash vector endpoint. If not set, `process.env.UPSTASH_VECTOR_REST_URL` is used.
12
+ * @param maxBatchSize maximum number of vectors upserted at once. Default is 1000.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const vectorStore = new UpstashVectorStore({ namespace: "my-namespace" })
17
+ * ```
18
+ */ constructor(params){
19
+ super(params), this.storesText = true;
20
+ this.namespace = params?.namespace ?? "";
21
+ this.maxBatchSize = params?.maxBatchSize ?? 1000;
22
+ const token = params?.token ?? getEnv("UPSTASH_VECTOR_REST_TOKEN");
23
+ const endpoint = params?.endpoint ?? getEnv("UPSTASH_VECTOR_REST_URL");
24
+ if (!token) {
25
+ throw new Error("Must specify UPSTASH_VECTOR_REST_TOKEN via env variable.");
26
+ }
27
+ if (!endpoint) {
28
+ throw new Error("Must specify UPSTASH_VECTOR_REST_URL via env variable.");
29
+ }
30
+ this.db = new Index({
31
+ token,
32
+ url: endpoint
33
+ });
34
+ }
35
+ async getDb() {
36
+ if (!this.db) {
37
+ const { Index } = await import('@upstash/vector');
38
+ this.db = new Index();
39
+ }
40
+ return this.db;
41
+ }
42
+ /**
43
+ * Connects to the database specified in environment vars.
44
+ * @returns A connection to the database, or the error encountered while connecting/setting up.
45
+ */ client() {
46
+ return this.getDb();
47
+ }
48
+ /**
49
+ * Adds vector record(s) to the table.
50
+ * @param embeddingResults The Nodes to be inserted, optionally including metadata tuples.
51
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
52
+ */ async add(embeddingResults) {
53
+ if (embeddingResults.length == 0) {
54
+ return [];
55
+ }
56
+ const nodes = embeddingResults.map(this.nodeToRecord);
57
+ const result = await this.upsertInBatches(nodes);
58
+ if (result != "OK") {
59
+ throw new Error("Failed to save chunk");
60
+ }
61
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
62
+ }
63
+ /**
64
+ * Adds plain text record(s) to the table. Upstash take cares of embedding conversion.
65
+ * @param text The Nodes to be inserted, optionally including metadata tuples.
66
+ * @returns ids of the embeddings (infered from the id_ field of embeddingResults objects)
67
+ */ async addPlainText(text) {
68
+ if (text.length == 0) {
69
+ return [];
70
+ }
71
+ const nodes = text.map(this.textNodeToRecord);
72
+ const result = await this.upsertInBatches(nodes);
73
+ if (result != "OK") {
74
+ throw new Error("Failed to save chunk");
75
+ }
76
+ return nodes.map((node)=>node.id).filter((id)=>!!id);
77
+ }
78
+ async upsertInBatches(nodes) {
79
+ const promises = [];
80
+ for(let i = 0; i < nodes.length; i += this.maxBatchSize){
81
+ const batch = nodes.slice(i, i + this.maxBatchSize);
82
+ promises.push(this.db.upsert(batch, {
83
+ namespace: this.namespace
84
+ }));
85
+ }
86
+ const results = await Promise.all(promises);
87
+ return results.every((result)=>result === "OK") ? "OK" : "NOT-OK";
88
+ }
89
+ /**
90
+ * Deletes a single record from the database by id.
91
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
92
+ * @param refDocId Unique identifier for the record to delete.
93
+ * @returns Promise that resolves if the delete query did not throw an error.
94
+ */ async delete(refDocId) {
95
+ await this.db.namespace(this.namespace).delete(refDocId);
96
+ }
97
+ /**
98
+ * Deletes a single record from the database by id.
99
+ * NOTE: Uses the collection property controlled by setCollection/getCollection.
100
+ * @param refDocId Unique identifier for the record to delete.
101
+ * @param deleteKwargs Required by VectorStore interface. Currently ignored.
102
+ * @returns Promise that resolves if the delete query did not throw an error.
103
+ */ async deleteMany(refDocId) {
104
+ await this.db.namespace(this.namespace).delete(refDocId);
105
+ }
106
+ /**
107
+ * Query the vector store for the closest matching data to the query embeddings
108
+ * @param query The VectorStoreQuery to be used
109
+ * @param options Required by VectorStore interface. Currently ignored.
110
+ * @returns Zero or more Document instances with data from the vector store.
111
+ */ async query(query, _options) {
112
+ const filter = this.toUpstashFilter(query.filters);
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ const defaultOptions = {
115
+ vector: query.queryEmbedding,
116
+ topK: query.similarityTopK,
117
+ includeVectors: true,
118
+ includeMetadata: true,
119
+ filter
120
+ };
121
+ const db = this.db;
122
+ const results = await db.query(defaultOptions, {
123
+ namespace: this.namespace
124
+ });
125
+ const nodes = results.map((result)=>{
126
+ const node = metadataDictToNode(result.metadata ?? {}, {
127
+ fallback: {
128
+ id: result.id,
129
+ metadata: result.metadata,
130
+ embedding: result.vector
131
+ }
132
+ });
133
+ return node;
134
+ });
135
+ const ret = {
136
+ nodes: nodes,
137
+ similarities: results.map((row)=>row.score || 999),
138
+ ids: results.map((row)=>String(row.id))
139
+ };
140
+ return ret;
141
+ }
142
+ toFilterString(filter) {
143
+ return `${filter.key} ${filter.operator} ${filter.value}`;
144
+ }
145
+ toUpstashFilter(stdFilters) {
146
+ if (!stdFilters?.filters) return;
147
+ for (const item of stdFilters.filters){
148
+ if (item.operator === "==") {
149
+ //@ts-expect-error Upstash equal operator uses only one equal sign, so we have to replace it.
150
+ item.operator = "=";
151
+ }
152
+ }
153
+ const filterStrings = stdFilters.filters.map(this.toFilterString);
154
+ if (filterStrings.length === 1) {
155
+ return filterStrings[0];
156
+ }
157
+ return filterStrings.join(` ${stdFilters.condition ?? "and"} `);
158
+ }
159
+ nodeToRecord(node) {
160
+ const id = node.id_.length ? node.id_ : null;
161
+ return {
162
+ // fixme: why id is possibly null?
163
+ id: id,
164
+ vector: node.getEmbedding(),
165
+ metadata: nodeToMetadata(node)
166
+ };
167
+ }
168
+ textNodeToRecord(node) {
169
+ const id = node.id_.length ? node.id_ : null;
170
+ return {
171
+ // fixme: why id is possibly null?
172
+ id: id,
173
+ data: node.text,
174
+ metadata: nodeToMetadata(node)
175
+ };
176
+ }
177
+ }
178
+
179
+ export { UpstashVectorStore };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@vectorstores/upstash",
3
+ "description": "Upstash Storage for vectorstores",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "edge-light": {
11
+ "types": "./dist/index.edge-light.d.ts",
12
+ "default": "./dist/index.edge-light.js"
13
+ },
14
+ "workerd": {
15
+ "types": "./dist/index.edge-light.d.ts",
16
+ "default": "./dist/index.edge-light.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ },
22
+ "import": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/schiesser/vectorstores.git",
34
+ "directory": "packages/providers/storage/upstash"
35
+ },
36
+ "devDependencies": {
37
+ "@vectorstores/core": "0.1.0",
38
+ "@vectorstores/env": "0.1.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@vectorstores/core": "0.1.0",
42
+ "@vectorstores/env": "0.1.0"
43
+ },
44
+ "dependencies": {
45
+ "@upstash/vector": "^1.1.5"
46
+ },
47
+ "scripts": {
48
+ "build": "bunchee",
49
+ "dev": "bunchee --watch"
50
+ }
51
+ }