@ixo/data-store 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.eslintrc.js +9 -0
  2. package/.prettierignore +3 -0
  3. package/.prettierrc.js +4 -0
  4. package/.turbo/turbo-build.log +4 -0
  5. package/CHANGELOG.md +7 -0
  6. package/README.md +276 -0
  7. package/dist/airtable-store/index.d.ts +42 -0
  8. package/dist/airtable-store/index.d.ts.map +1 -0
  9. package/dist/airtable-store/index.js +79 -0
  10. package/dist/chroma/chroma-data-store.d.ts +74 -0
  11. package/dist/chroma/chroma-data-store.d.ts.map +1 -0
  12. package/dist/chroma/chroma-data-store.js +196 -0
  13. package/dist/chroma/chroma-data-store.test.d.ts +2 -0
  14. package/dist/chroma/chroma-data-store.test.d.ts.map +1 -0
  15. package/dist/chroma/chroma-data-store.test.js +176 -0
  16. package/dist/chroma/chroma.types.d.ts +24 -0
  17. package/dist/chroma/chroma.types.d.ts.map +1 -0
  18. package/dist/chroma/chroma.types.js +2 -0
  19. package/dist/chroma/embedding-function.d.ts +14 -0
  20. package/dist/chroma/embedding-function.d.ts.map +1 -0
  21. package/dist/chroma/embedding-function.js +26 -0
  22. package/dist/chroma/index.d.ts +3 -0
  23. package/dist/chroma/index.d.ts.map +1 -0
  24. package/dist/chroma/index.js +18 -0
  25. package/dist/chroma/utils.d.ts +6 -0
  26. package/dist/chroma/utils.d.ts.map +1 -0
  27. package/dist/chroma/utils.js +31 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +19 -0
  31. package/dist/types/index.d.ts +3 -0
  32. package/dist/types/index.d.ts.map +1 -0
  33. package/dist/types/index.js +18 -0
  34. package/dist/types/structured-data-store.d.ts +24 -0
  35. package/dist/types/structured-data-store.d.ts.map +1 -0
  36. package/dist/types/structured-data-store.js +2 -0
  37. package/dist/types/vector-db-data-store.d.ts +58 -0
  38. package/dist/types/vector-db-data-store.d.ts.map +1 -0
  39. package/dist/types/vector-db-data-store.js +10 -0
  40. package/dist/utils/index.d.ts +2 -0
  41. package/dist/utils/index.d.ts.map +1 -0
  42. package/dist/utils/index.js +17 -0
  43. package/dist/utils/with-report-error.d.ts +5 -0
  44. package/dist/utils/with-report-error.d.ts.map +1 -0
  45. package/dist/utils/with-report-error.js +21 -0
  46. package/jest.config.js +6 -0
  47. package/package.json +49 -0
  48. package/src/airtable-store/index.ts +141 -0
  49. package/src/chroma/chroma-data-store.test.ts +210 -0
  50. package/src/chroma/chroma-data-store.ts +254 -0
  51. package/src/chroma/chroma.types.ts +28 -0
  52. package/src/chroma/embedding-function.ts +26 -0
  53. package/src/chroma/index.ts +2 -0
  54. package/src/chroma/utils.ts +40 -0
  55. package/src/index.ts +4 -0
  56. package/src/types/index.ts +2 -0
  57. package/src/types/structured-data-store.ts +34 -0
  58. package/src/types/vector-db-data-store.ts +78 -0
  59. package/src/utils/index.ts +1 -0
  60. package/src/utils/with-report-error.ts +18 -0
  61. package/tsconfig.json +7 -0
@@ -0,0 +1,58 @@
1
+ import { type IEmbeddingFunction, type Metadata } from 'chromadb';
2
+ /**
3
+ * Represents a document in the vector store.
4
+ */
5
+ export interface IVectorStoreDocument {
6
+ /**
7
+ * Unique identifier for the document.
8
+ */
9
+ id: string;
10
+ /**
11
+ * The content or data of the document.
12
+ */
13
+ content: string;
14
+ /**
15
+ * Optional metadata associated with the document.
16
+ */
17
+ metadata?: Metadata;
18
+ /**
19
+ * The score of the document.
20
+ */
21
+ score?: number;
22
+ }
23
+ export interface IVectorStoreDocumentWithEmbeddings extends IVectorStoreDocument {
24
+ embedding: number[];
25
+ }
26
+ /**
27
+ * Options for querying the vector store.
28
+ */
29
+ export interface IVectorStoreQueryOptions<Filters extends Record<string, unknown> = Record<string, unknown>> {
30
+ /**
31
+ * The number of top results to return.
32
+ */
33
+ topK?: number;
34
+ /**
35
+ * Filters to apply based on document metadata.
36
+ */
37
+ filters?: Filters;
38
+ }
39
+ export interface IVectorStoreOptions {
40
+ collectionName: string;
41
+ url: string;
42
+ embeddingFunction?: IEmbeddingFunction;
43
+ }
44
+ export declare abstract class VectorDBDataStore {
45
+ protected readonly options: IVectorStoreOptions;
46
+ constructor(options: IVectorStoreOptions);
47
+ abstract upsert(documents: IVectorStoreDocument[]): Promise<void>;
48
+ abstract delete(ids: string[]): Promise<void>;
49
+ abstract queryByVector(vector: number[], options?: IVectorStoreQueryOptions): Promise<IVectorStoreDocument[]>;
50
+ abstract query(query: string, options?: IVectorStoreQueryOptions): Promise<IVectorStoreDocument[]>;
51
+ abstract getById(id: string): Promise<IVectorStoreDocument | null>;
52
+ abstract queryWithSimilarity(query: string, options?: IVectorStoreQueryOptions & {
53
+ similarityThreshold: number;
54
+ }): Promise<IVectorStoreDocument[]>;
55
+ abstract addDocumentsWithEmbeddings(documents: IVectorStoreDocumentWithEmbeddings[]): Promise<void>;
56
+ abstract init(): Promise<void>;
57
+ }
58
+ //# sourceMappingURL=vector-db-data-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-db-data-store.d.ts","sourceRoot":"","sources":["../../src/types/vector-db-data-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kCACf,SAAQ,oBAAoB;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CACvC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjE;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;CACxC;AAED,8BAAsB,iBAAiB;IACzB,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB;gBAA5B,OAAO,EAAE,mBAAmB;IAE3D,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACjE,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7C,QAAQ,CAAC,aAAa,CACpB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,CACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAClE,QAAQ,CAAC,mBAAmB,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,wBAAwB,GAAG;QAAE,mBAAmB,EAAE,MAAM,CAAA;KAAE,GACnE,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAClC,QAAQ,CAAC,0BAA0B,CACjC,SAAS,EAAE,kCAAkC,EAAE,GAC9C,OAAO,CAAC,IAAI,CAAC;IAChB,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VectorDBDataStore = void 0;
4
+ class VectorDBDataStore {
5
+ options;
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ }
10
+ exports.VectorDBDataStore = VectorDBDataStore;
@@ -0,0 +1,2 @@
1
+ export * from './with-report-error';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./with-report-error"), exports);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * should report using the logger and throw the error if the promise is rejected
3
+ */
4
+ export declare const withReportError: <T>(promise: Promise<T>) => Promise<T>;
5
+ //# sourceMappingURL=with-report-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-report-error.d.ts","sourceRoot":"","sources":["../../src/utils/with-report-error.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAYvE,CAAC"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withReportError = void 0;
4
+ const logger_1 = require("@ixo/logger");
5
+ /**
6
+ * should report using the logger and throw the error if the promise is rejected
7
+ */
8
+ const withReportError = async (promise) => {
9
+ try {
10
+ const res = await promise;
11
+ return res;
12
+ }
13
+ catch (error) {
14
+ const errorMessage = error instanceof Error
15
+ ? error.message
16
+ : 'Something went wrong in AirtableDataStore';
17
+ logger_1.Logger.error(errorMessage, error);
18
+ throw error;
19
+ }
20
+ };
21
+ exports.withReportError = withReportError;
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ const baseConfig = require('@ixo/jest-config/nest');
2
+
3
+ module.exports = {
4
+ ...baseConfig,
5
+ displayName: 'data-store',
6
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@ixo/data-store",
3
+ "version": "1.0.1",
4
+ "private": false,
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "module": "./dist/index.js",
11
+ "exports": {
12
+ ".": {
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ },
17
+ "./*": {
18
+ "require": "./dist/*",
19
+ "import": "./dist/*",
20
+ "types": "./dist/*"
21
+ }
22
+ },
23
+ "devDependencies": {
24
+ "@types/jest": "^29.5.14",
25
+ "@types/node": "^22.7.3",
26
+ "jest": "^29.7.0",
27
+ "ts-jest": "^29.2.5",
28
+ "typescript": "^5.3.3",
29
+ "@ixo/eslint-config": "1.0.0",
30
+ "@ixo/jest-config": "0.0.0",
31
+ "@ixo/typescript-config": "1.0.0"
32
+ },
33
+ "dependencies": {
34
+ "@langchain/community": "^1.0.3",
35
+ "@langchain/core": "^1.0.5",
36
+ "@langchain/openai": "^1.0.0",
37
+ "airtable": "^0.12.2",
38
+ "chromadb": "^1.10.4",
39
+ "chromadb-default-embed": "^2.13.2",
40
+ "neo4j-driver": "^5.28.1",
41
+ "openai": "^6.9.1",
42
+ "zod": "^4.1.12",
43
+ "@ixo/logger": "0.0.2"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "test": "jest"
48
+ }
49
+ }
@@ -0,0 +1,141 @@
1
+ import AirtableBase, { type FieldSet } from 'airtable';
2
+ import { type QueryParams } from 'airtable/lib/query_params';
3
+ import { type IDataStore } from '../types';
4
+ import { withReportError } from '../utils';
5
+
6
+ /**
7
+ * AirtableDataStore class to interact with Airtable API implementing IDataStore interface
8
+ *
9
+ * Basic CRUD operations
10
+ */
11
+ export class AirtableDataStore<TFields extends FieldSet>
12
+ implements IDataStore<TFields>
13
+ {
14
+ private readonly base: AirtableBase.Base;
15
+
16
+ constructor() {
17
+ if (!process.env.AIRTABLE_API_KEY) {
18
+ throw new Error('AIRTABLE_API_KEY is required');
19
+ }
20
+
21
+ if (!process.env.AIRTABLE_BASE_ID) {
22
+ throw new Error('AIRTABLE_BASE_ID is required');
23
+ }
24
+
25
+ this.base = new AirtableBase({ apiKey: process.env.AIRTABLE_API_KEY }).base(
26
+ process.env.AIRTABLE_BASE_ID,
27
+ );
28
+ }
29
+
30
+ private static withId<TFields extends FieldSet>(
31
+ record: AirtableBase.Record<TFields>,
32
+ ): TFields & { id: string } {
33
+ return {
34
+ ...record.fields,
35
+ id: record.id,
36
+ };
37
+ }
38
+
39
+ async getAllRecords(
40
+ tableName: string,
41
+ selectOptions?: QueryParams<TFields>,
42
+ ): Promise<(TFields & { id: string })[]> {
43
+ const records = await withReportError(
44
+ this.base<TFields>(tableName).select(selectOptions).all(),
45
+ );
46
+
47
+ return records.map((record) => AirtableDataStore.withId(record));
48
+ }
49
+
50
+ async getRecord(
51
+ tableName: string,
52
+ recordId: string,
53
+ ): Promise<TFields & { id: string }> {
54
+ const record = await withReportError(
55
+ this.base<TFields>(tableName).find(recordId),
56
+ );
57
+
58
+ return AirtableDataStore.withId(record);
59
+ }
60
+
61
+ async createRecord(
62
+ tableName: string,
63
+ recordData: TFields,
64
+ ): Promise<TFields & { id: string }> {
65
+ const record = await withReportError(
66
+ this.base<TFields>(tableName).create(recordData),
67
+ );
68
+ return AirtableDataStore.withId(record);
69
+ }
70
+
71
+ async updateRecord(
72
+ tableName: string,
73
+ recordId: string,
74
+ recordData: Partial<TFields>,
75
+ ): Promise<TFields & { id: string }> {
76
+ const record = await withReportError(
77
+ this.base<TFields>(tableName).update([
78
+ {
79
+ id: recordId,
80
+ fields: recordData,
81
+ },
82
+ ]),
83
+ );
84
+ if (!record[0]) {
85
+ // this error will never be thrown as the airtable sdk will throw an error but to satisfy typescript
86
+ throw new Error('Record not found');
87
+ }
88
+ return AirtableDataStore.withId(record[0]);
89
+ }
90
+
91
+ async batchUpdateRecords(
92
+ tableName: string,
93
+ records: {
94
+ id: string;
95
+ fields: Partial<
96
+ TFields & {
97
+ batch?: boolean;
98
+ }
99
+ >;
100
+ }[],
101
+ ): Promise<(TFields & { id: string })[]> {
102
+ const updatedRecords = await withReportError(
103
+ this.base<TFields>(tableName).update(records),
104
+ );
105
+ return updatedRecords.map((record) => AirtableDataStore.withId(record));
106
+ }
107
+
108
+ async deleteRecord(
109
+ tableName: string,
110
+ recordId: string,
111
+ ): Promise<TFields & { id: string }> {
112
+ const record = await withReportError(
113
+ this.base<TFields>(tableName).destroy(recordId),
114
+ );
115
+ return AirtableDataStore.withId(record);
116
+ }
117
+
118
+ async getRecordByField(
119
+ tableName: string,
120
+ fieldName: string,
121
+ fieldValue: string,
122
+ ): Promise<(TFields & { id: string })[]> {
123
+ const records = await withReportError(
124
+ this.base<TFields>(tableName)
125
+ .select({
126
+ filterByFormula: `{${fieldName}} = "${fieldValue}"`,
127
+ })
128
+ .all(),
129
+ );
130
+ return records.map((record) => AirtableDataStore.withId(record));
131
+ }
132
+
133
+ public getLinkToRecord(recordId: string): string {
134
+ if (!process.env.AITABLE_BASE_TABLE_LINK) {
135
+ throw new Error('AITABLE_BASE_TABLE_LINK is required');
136
+ }
137
+ return `${process.env.AITABLE_BASE_TABLE_LINK}/${recordId}`;
138
+ }
139
+ }
140
+
141
+ export type { FieldSet };
@@ -0,0 +1,210 @@
1
+ /* eslint-disable jest/unbound-method -- ignore this for now */
2
+ import { ChromaClient, Collection, type IEmbeddingFunction } from 'chromadb';
3
+ import { ChromaDataStore } from './chroma-data-store';
4
+
5
+ jest.mock('chromadb');
6
+ const embeddingFunction: IEmbeddingFunction = {
7
+ generate: jest.fn(),
8
+ };
9
+ jest
10
+ .mocked(ChromaClient.prototype.getOrCreateCollection)
11
+ .mockResolvedValue(
12
+ new Collection(
13
+ 'test',
14
+ 'test',
15
+ jest.fn() as unknown as ChromaClient,
16
+ embeddingFunction,
17
+ ),
18
+ );
19
+
20
+ describe('ChromaDataStore', () => {
21
+ beforeAll(() => {
22
+ process.env.OPENAI_API_KEY = 'test';
23
+ });
24
+
25
+ afterAll(() => {
26
+ process.env.OPENAI_API_KEY = undefined;
27
+ });
28
+
29
+ it('should be defined', () => {
30
+ expect(ChromaDataStore).toBeDefined();
31
+ });
32
+
33
+ describe('Initialization', () => {
34
+ it('should construct and initialize without errors', async () => {
35
+ // Should not throw during construction
36
+ const chromaDataStore = new ChromaDataStore({
37
+ collectionName: 'test',
38
+ url: 'http://localhost:8000',
39
+ });
40
+ expect(chromaDataStore).toBeInstanceOf(ChromaDataStore);
41
+
42
+ // Should not throw during initialization
43
+ await expect(chromaDataStore.init()).resolves.not.toThrow();
44
+ });
45
+
46
+ it('should throw error when OPENAI_API_KEY is not set and no embedding function is provided', () => {
47
+ // Temporarily remove API key
48
+ const originalKey = process.env.OPENAI_API_KEY;
49
+ delete process.env.OPENAI_API_KEY;
50
+
51
+ // Should throw during construction
52
+ expect(
53
+ () =>
54
+ new ChromaDataStore({
55
+ collectionName: 'test',
56
+ url: 'http://localhost:8000',
57
+ }),
58
+ ).toThrow('OPENAI_API_KEY is not set');
59
+
60
+ // Restore API key
61
+ process.env.OPENAI_API_KEY = originalKey;
62
+ });
63
+
64
+ it('should create collection or use existing collection', async () => {
65
+ jest.spyOn(ChromaClient.prototype, 'getOrCreateCollection');
66
+ const chromaDataStore = new ChromaDataStore({
67
+ collectionName: 'test',
68
+ url: 'http://localhost:8000',
69
+ embeddingFunction,
70
+ });
71
+ await chromaDataStore.init();
72
+ expect(ChromaClient.prototype.getOrCreateCollection).toHaveBeenCalledWith(
73
+ {
74
+ name: 'test',
75
+ embeddingFunction,
76
+ },
77
+ );
78
+ });
79
+ });
80
+
81
+ describe('Query', () => {
82
+ let chromaDataStore: ChromaDataStore;
83
+ beforeAll(async () => {
84
+ chromaDataStore = new ChromaDataStore({
85
+ collectionName: 'test',
86
+ url: 'http://localhost:8000',
87
+ embeddingFunction,
88
+ });
89
+ await chromaDataStore.init();
90
+
91
+ await chromaDataStore.init();
92
+ jest.mocked(Collection.prototype.query).mockResolvedValue({
93
+ ids: [['1', '2']],
94
+ embeddings: null,
95
+ documents: [['Hello, world!', 'Hello, world! 2']],
96
+ metadatas: [[{ title: 'Hello, world!' }, { title: 'Hello, world! 2' }]],
97
+ distances: [[3.3435163194430493e-16, 0.30934086831133445]],
98
+ included: [],
99
+ });
100
+ });
101
+
102
+ it('should query documents', async () => {
103
+ const documents = await chromaDataStore.query('Hello, world!');
104
+ expect(documents).toHaveLength(2);
105
+ expect(documents[0]?.id).toBe('1');
106
+ expect(documents[0]?.content).toBe('Hello, world!');
107
+ });
108
+
109
+ it('should query documents by vector', async () => {
110
+ const vector = [1, 2, 3];
111
+ const documents = await chromaDataStore.queryByVector(vector);
112
+ expect(documents).toHaveLength(2);
113
+ expect(documents[0]?.id).toBe('1');
114
+ expect(documents[0]?.content).toBe('Hello, world!');
115
+ });
116
+
117
+ it('should query documents by similarity', async () => {
118
+ const documents = await chromaDataStore.queryWithSimilarity(
119
+ 'Hello, world!',
120
+ { similarityThreshold: 0.9 },
121
+ );
122
+ expect(documents).toHaveLength(1);
123
+ expect(documents[0]?.id).toBe('1');
124
+ expect(documents[0]?.content).toBe('Hello, world!');
125
+ });
126
+
127
+ it('should get document by id', async () => {
128
+ jest.spyOn(Collection.prototype, 'get');
129
+ jest.mocked(Collection.prototype.get).mockResolvedValue({
130
+ ids: ['1'],
131
+ embeddings: null,
132
+ documents: ['Hello, world!'],
133
+ metadatas: [{ title: 'Hello, world!' }],
134
+ included: [],
135
+ });
136
+ const document = await chromaDataStore.getById('1');
137
+ expect(Collection.prototype.get).toHaveBeenCalledWith({
138
+ ids: ['1'],
139
+ });
140
+ expect(document?.id).toBe('1');
141
+ expect(document?.content).toBe('Hello, world!');
142
+ });
143
+
144
+ it('should handle empty results', async () => {
145
+ jest.mocked(Collection.prototype.query).mockResolvedValue({
146
+ ids: [],
147
+ embeddings: null,
148
+ documents: [],
149
+ metadatas: [],
150
+ distances: [],
151
+ included: [],
152
+ });
153
+ const documents = await chromaDataStore.query('Hello, world!');
154
+ expect(documents).toHaveLength(0);
155
+ });
156
+ });
157
+
158
+ describe('Upsert', () => {
159
+ let chromaDataStore: ChromaDataStore;
160
+ beforeAll(async () => {
161
+ chromaDataStore = new ChromaDataStore({
162
+ collectionName: 'test',
163
+ url: 'http://localhost:8000',
164
+ embeddingFunction,
165
+ });
166
+ await chromaDataStore.init();
167
+ });
168
+
169
+ it('should upsert documents', async () => {
170
+ jest.spyOn(Collection.prototype, 'upsert');
171
+ await chromaDataStore.upsert([
172
+ {
173
+ id: '1',
174
+ content: 'Hello, world!',
175
+ metadata: { title: 'Hello, world!' },
176
+ },
177
+ {
178
+ id: '2',
179
+ content: 'Hello, world! 2',
180
+ metadata: { title: 'Hello, world! 2' },
181
+ },
182
+ ]);
183
+ expect(Collection.prototype.upsert).toHaveBeenCalledWith({
184
+ ids: ['1', '2'],
185
+ documents: ['Hello, world!', 'Hello, world! 2'],
186
+ metadatas: [{ title: 'Hello, world!' }, { title: 'Hello, world! 2' }],
187
+ });
188
+ });
189
+ });
190
+
191
+ describe('Delete', () => {
192
+ let chromaDataStore: ChromaDataStore;
193
+ beforeAll(async () => {
194
+ chromaDataStore = new ChromaDataStore({
195
+ collectionName: 'test',
196
+ url: 'http://localhost:8000',
197
+ embeddingFunction,
198
+ });
199
+ await chromaDataStore.init();
200
+ });
201
+
202
+ it('should delete documents', async () => {
203
+ jest.spyOn(Collection.prototype, 'delete');
204
+ await chromaDataStore.delete(['1', '2']);
205
+ expect(Collection.prototype.delete).toHaveBeenCalledWith({
206
+ ids: ['1', '2'],
207
+ });
208
+ });
209
+ });
210
+ });