File size: 4,257 Bytes
e3aec01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/********************************************************************* 
 * A simple vector store using IndexedDB for persistence.
 * Coded by Jason Mayes 2026. 
 *--------------------------------------------------------------------
 * Connect with me on social if aquestions or comments:
 *
 * LinkedIn: https://www.linkedin.com/in/webai/
 * Twitter / X: https://x.com/jason_mayes
 * Github: https://github.com/jasonmayes
 * CodePen: https://codepen.io/jasonmayes
 *********************************************************************/
export class VectorStore {
  /**
   * @param {string} dbName Name of the IndexedDB database.
   */
  constructor(dbName = 'unnamed') {
    this.dbName = dbName;
    this.db = null;
  }

  /**
   * Sets the database name to use for storage and retrieval.
   * @param {string} name The name of the database to use.
   */
  setDb(name) {
    if (this.dbName !== name) {
      this.dbName = name;
      if (this.db) {
        this.db.close();
        this.db = null;
      }
    }
  }

  /**
   * Initializes the IndexedDB database.
   * @return {!Promise<!IDBDatabase>}
   * @private
   */
  async initDb() {
    if (this.db) return this.db;

    return new Promise((resolve, reject) => {
      // Increment version to 2 to trigger upgrade if you already had version 1
      const request = indexedDB.open(this.dbName, 1);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        // Single store for both text and vectors
        if (!db.objectStoreNames.contains('embeddings')) {
          db.createObjectStore('embeddings', { keyPath: 'id', autoIncrement: true });
        }
      };

      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(this.db);
      };

      request.onerror = (e) => reject(e.target.error);
    });
  }


  /**
   * Stores an embedding and its associated text.
   * @param {!Array<number>} embedding The vector embedding.
   * @param {string} text The original text data.
   * @return {!Promise<void>}
   */
  async storeBatch(items) {
    const db = await this.initDb();
    const transaction = db.transaction(['embeddings'], 'readwrite');
    const store = transaction.objectStore('embeddings');

    return new Promise((resolve, reject) => {
      transaction.oncomplete = () => resolve();
      transaction.onerror = (e) => reject(e.target.error);

      for (const item of items) {
        // item.embedding should be a Float32Array for max speed
        store.add({
          text: item.text,
          embedding: item.embedding
        });
      }
    });
  }


  /**
   * Fetches all vectors from the database.
   * @return {!Promise<!Array<{id: number, embedding: !Array<number>}>>}
   */
  async getAllVectors() {
    const db = await this.initDb();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['embeddings'], 'readonly');
      const store = transaction.objectStore('embeddings');
      const vectors = [];

      // A cursor lets us step through records without loading 
      // the whole store into memory at once.
      const request = store.openCursor();

      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
          // We only grab the ID and the embedding
          vectors.push({
            id: cursor.value.id,
            embedding: cursor.value.embedding // Float32Array
          });
          cursor.continue();
        } else {
          // No more results
          resolve(vectors);
        }
      };

      request.onerror = (e) => reject(e.target.error);
    });
  }


  /**
   * Fetches the text for a given ID.
   * @param {number} id The ID of the text to fetch.
   * @return {!Promise<string>}
   */
  async getTextByID(id) {
    const db = await this.initDb();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(['embeddings'], 'readonly');
      const store = transaction.objectStore('embeddings');
      const request = store.get(id);

      request.onsuccess = () => {
        // Just return the text property
        resolve(request.result ? request.result.text : null);
      };
      request.onerror = (e) => reject(e.target.error);
    });
  }
}