@capgo/capacitor-fast-sql 8.0.36 → 8.0.38
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.md +57 -0
- package/android/src/main/java/app/capgo/capacitor/fastsql/CapgoCapacitorFastSqlPlugin.java +1 -1
- package/dist/esm/fast-sql.d.ts +15 -0
- package/dist/esm/fast-sql.d.ts.map +1 -1
- package/dist/esm/fast-sql.js +51 -1
- package/dist/esm/helpers.d.ts +2 -0
- package/dist/esm/helpers.d.ts.map +1 -1
- package/dist/esm/helpers.js +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/vector-store.d.ts +128 -0
- package/dist/esm/vector-store.d.ts.map +1 -0
- package/dist/esm/vector-store.js +377 -0
- package/dist/plugin.cjs.js +429 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +429 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/CapgoCapacitorFastSqlPlugin.swift +1 -1
- package/ios/Sources/CapgoCapacitorFastSqlPlugin/SQLDatabase.swift +9 -10
- package/package.json +1 -1
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { FastSQL } from './fast-sql';
|
|
2
|
+
const DEFAULT_STORE = 'default';
|
|
3
|
+
const VECTOR_TABLE = '__fast_sql_vector_store';
|
|
4
|
+
const VECTOR_META_TABLE = '__fast_sql_vector_store_meta';
|
|
5
|
+
/**
|
|
6
|
+
* SQLite-backed VectorStore for local RAG usage.
|
|
7
|
+
*
|
|
8
|
+
* The helper is optional and dependency-free: pass your own embeddings provider,
|
|
9
|
+
* or use precomputed embeddings with `embeddingDimension`. Multiple stores can
|
|
10
|
+
* share one database by choosing different `store` names.
|
|
11
|
+
*/
|
|
12
|
+
export class FastSQLVectorStore {
|
|
13
|
+
constructor(connection, options = {}) {
|
|
14
|
+
var _a;
|
|
15
|
+
this.initialized = false;
|
|
16
|
+
this.ownsConnection = false;
|
|
17
|
+
this.shouldDisconnectConnection = false;
|
|
18
|
+
this.closed = false;
|
|
19
|
+
this.connection = connection;
|
|
20
|
+
this.store = (_a = options.store) !== null && _a !== void 0 ? _a : DEFAULT_STORE;
|
|
21
|
+
this.embeddings = options.embeddings;
|
|
22
|
+
this.configuredEmbeddingDim = options.embeddingDimension;
|
|
23
|
+
this.embeddingDim = options.embeddingDimension;
|
|
24
|
+
}
|
|
25
|
+
static retainConnection(database) {
|
|
26
|
+
var _a;
|
|
27
|
+
this.connectionRefs.set(database, ((_a = this.connectionRefs.get(database)) !== null && _a !== void 0 ? _a : 0) + 1);
|
|
28
|
+
}
|
|
29
|
+
static releaseConnection(database) {
|
|
30
|
+
var _a;
|
|
31
|
+
const refs = (_a = this.connectionRefs.get(database)) !== null && _a !== void 0 ? _a : 0;
|
|
32
|
+
if (refs > 1) {
|
|
33
|
+
this.connectionRefs.set(database, refs - 1);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
this.connectionRefs.delete(database);
|
|
37
|
+
this.ownedConnections.delete(database);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Open a vector store from database connection options.
|
|
42
|
+
*/
|
|
43
|
+
static async open(options) {
|
|
44
|
+
const existing = FastSQL.getConnection(options.database);
|
|
45
|
+
const ownsExistingVectorConnection = existing && FastSQLVectorStore.connectionRefs.has(options.database);
|
|
46
|
+
const connection = ownsExistingVectorConnection ? existing : await FastSQL.connect(options);
|
|
47
|
+
const vectorStore = new FastSQLVectorStore(connection, options);
|
|
48
|
+
vectorStore.ownsConnection = true;
|
|
49
|
+
vectorStore.shouldDisconnectConnection = !existing || FastSQLVectorStore.ownedConnections.has(options.database);
|
|
50
|
+
FastSQLVectorStore.retainConnection(connection.getDatabaseName());
|
|
51
|
+
FastSQL.retainConnection(connection.getDatabaseName());
|
|
52
|
+
if (vectorStore.shouldDisconnectConnection) {
|
|
53
|
+
FastSQLVectorStore.ownedConnections.add(connection.getDatabaseName());
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await vectorStore.load();
|
|
57
|
+
return vectorStore;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await vectorStore.unload();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a vector store from an existing SQL connection.
|
|
66
|
+
* The caller keeps ownership of the connection.
|
|
67
|
+
*/
|
|
68
|
+
static async fromConnection(connection, options = {}) {
|
|
69
|
+
const vectorStore = new FastSQLVectorStore(connection, options);
|
|
70
|
+
await vectorStore.load();
|
|
71
|
+
return vectorStore;
|
|
72
|
+
}
|
|
73
|
+
getStoreName() {
|
|
74
|
+
return this.store;
|
|
75
|
+
}
|
|
76
|
+
getDatabaseName() {
|
|
77
|
+
return this.connection.getDatabaseName();
|
|
78
|
+
}
|
|
79
|
+
async load() {
|
|
80
|
+
var _a, _b;
|
|
81
|
+
this.assertOpen();
|
|
82
|
+
if (this.initialized) {
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
await ((_b = (_a = this.embeddings) === null || _a === void 0 ? void 0 : _a.load) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
86
|
+
await this.ensureSchema();
|
|
87
|
+
await this.ensureStoreDimension();
|
|
88
|
+
this.initialized = true;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
async unload() {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
if (this.closed) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await ((_b = (_a = this.embeddings) === null || _a === void 0 ? void 0 : _a.unload) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
97
|
+
if (this.ownsConnection) {
|
|
98
|
+
const database = this.connection.getDatabaseName();
|
|
99
|
+
const releasedLastVectorStore = FastSQLVectorStore.releaseConnection(database);
|
|
100
|
+
const closedFromPendingDisconnect = await FastSQL.releaseConnection(database);
|
|
101
|
+
if (releasedLastVectorStore &&
|
|
102
|
+
this.shouldDisconnectConnection &&
|
|
103
|
+
!FastSQL.isConnectionShared(database) &&
|
|
104
|
+
!closedFromPendingDisconnect) {
|
|
105
|
+
await FastSQL.disconnect(database);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this.initialized = false;
|
|
109
|
+
this.closed = true;
|
|
110
|
+
}
|
|
111
|
+
async close() {
|
|
112
|
+
await this.unload();
|
|
113
|
+
}
|
|
114
|
+
async add(params) {
|
|
115
|
+
await this.ensureReady();
|
|
116
|
+
const { id = this.createId(), document, embedding, metadata } = params;
|
|
117
|
+
if (document === undefined && embedding === undefined) {
|
|
118
|
+
throw new Error('document and embedding cannot both be undefined');
|
|
119
|
+
}
|
|
120
|
+
const existing = await this.connection.query(`SELECT 1 FROM ${VECTOR_TABLE} WHERE store = ? AND id = ? LIMIT 1`, [
|
|
121
|
+
this.store,
|
|
122
|
+
id,
|
|
123
|
+
]);
|
|
124
|
+
if (existing.length > 0) {
|
|
125
|
+
throw new Error(`id already exists: ${id}`);
|
|
126
|
+
}
|
|
127
|
+
const resolvedEmbedding = embedding !== null && embedding !== void 0 ? embedding : (await this.embedDocument(document));
|
|
128
|
+
await this.ensureEmbeddingDimension(resolvedEmbedding);
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
await this.connection.run(`INSERT INTO ${VECTOR_TABLE} (store, id, document, embedding, metadata, created_at, updated_at)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
132
|
+
this.store,
|
|
133
|
+
id,
|
|
134
|
+
document !== null && document !== void 0 ? document : '',
|
|
135
|
+
this.encodeEmbedding(resolvedEmbedding),
|
|
136
|
+
this.encodeMetadata(metadata),
|
|
137
|
+
now,
|
|
138
|
+
now,
|
|
139
|
+
]);
|
|
140
|
+
return id;
|
|
141
|
+
}
|
|
142
|
+
async update(params) {
|
|
143
|
+
var _a, _b;
|
|
144
|
+
await this.ensureReady();
|
|
145
|
+
const rows = await this.connection.query(`SELECT id, document, embedding, metadata FROM ${VECTOR_TABLE} WHERE store = ? AND id = ? LIMIT 1`, [this.store, params.id]);
|
|
146
|
+
if (rows.length === 0) {
|
|
147
|
+
throw new Error(`id not found: ${params.id}`);
|
|
148
|
+
}
|
|
149
|
+
const current = this.rowToGetResult(rows[0]);
|
|
150
|
+
const document = (_a = params.document) !== null && _a !== void 0 ? _a : current.document;
|
|
151
|
+
const embedding = (_b = params.embedding) !== null && _b !== void 0 ? _b : (params.document !== undefined ? await this.embedDocument(params.document) : current.embedding);
|
|
152
|
+
const metadata = params.metadata === undefined ? current.metadata : params.metadata;
|
|
153
|
+
await this.ensureEmbeddingDimension(embedding);
|
|
154
|
+
await this.connection.run(`UPDATE ${VECTOR_TABLE}
|
|
155
|
+
SET document = ?, embedding = ?, metadata = ?, updated_at = ?
|
|
156
|
+
WHERE store = ? AND id = ?`, [document, this.encodeEmbedding(embedding), this.encodeMetadata(metadata), Date.now(), this.store, params.id]);
|
|
157
|
+
}
|
|
158
|
+
async delete(params) {
|
|
159
|
+
await this.ensureReady();
|
|
160
|
+
const rows = await this.getStoreRows();
|
|
161
|
+
const ids = rows
|
|
162
|
+
.map((row) => this.rowToGetResult(row))
|
|
163
|
+
.filter(params.predicate)
|
|
164
|
+
.map((row) => row.id);
|
|
165
|
+
for (const id of ids) {
|
|
166
|
+
await this.connection.run(`DELETE FROM ${VECTOR_TABLE} WHERE store = ? AND id = ?`, [this.store, id]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async query(params) {
|
|
170
|
+
await this.ensureReady();
|
|
171
|
+
const { queryText, queryEmbedding, nResults, predicate } = params;
|
|
172
|
+
if (queryText === undefined && queryEmbedding === undefined) {
|
|
173
|
+
throw new Error('queryText and queryEmbedding cannot both be undefined');
|
|
174
|
+
}
|
|
175
|
+
if (nResults !== undefined && nResults < 0) {
|
|
176
|
+
throw new Error('nResults cannot be negative');
|
|
177
|
+
}
|
|
178
|
+
const searchEmbedding = queryEmbedding !== null && queryEmbedding !== void 0 ? queryEmbedding : (await this.embedDocument(queryText));
|
|
179
|
+
await this.ensureEmbeddingDimension(searchEmbedding);
|
|
180
|
+
const results = (await this.getStoreRows())
|
|
181
|
+
.map((row) => {
|
|
182
|
+
const item = this.rowToGetResult(row);
|
|
183
|
+
return Object.assign(Object.assign({}, item), { similarity: this.cosineSimilarity(item.embedding, searchEmbedding) });
|
|
184
|
+
})
|
|
185
|
+
.filter(predicate !== null && predicate !== void 0 ? predicate : (() => true))
|
|
186
|
+
.sort((a, b) => b.similarity - a.similarity);
|
|
187
|
+
return nResults === undefined ? results : results.slice(0, nResults);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Delete only this logical vector store. Other stores in the same database stay intact.
|
|
191
|
+
*/
|
|
192
|
+
async deleteVectorStore() {
|
|
193
|
+
await this.ensureReady();
|
|
194
|
+
await this.connection.run(`DELETE FROM ${VECTOR_TABLE} WHERE store = ?`, [this.store]);
|
|
195
|
+
await this.connection.run(`DELETE FROM ${VECTOR_META_TABLE} WHERE store = ?`, [this.store]);
|
|
196
|
+
this.embeddingDim = this.configuredEmbeddingDim;
|
|
197
|
+
if (this.embeddingDim !== undefined) {
|
|
198
|
+
await this.saveEmbeddingDimension();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async ensureReady() {
|
|
202
|
+
this.assertOpen();
|
|
203
|
+
if (!this.initialized) {
|
|
204
|
+
await this.load();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
assertOpen() {
|
|
208
|
+
if (this.closed) {
|
|
209
|
+
throw new Error('Vector store is closed');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async ensureSchema() {
|
|
213
|
+
await this.connection.run(`CREATE TABLE IF NOT EXISTS ${VECTOR_TABLE} (
|
|
214
|
+
store TEXT NOT NULL,
|
|
215
|
+
id TEXT NOT NULL,
|
|
216
|
+
document TEXT NOT NULL,
|
|
217
|
+
embedding BLOB NOT NULL,
|
|
218
|
+
metadata TEXT DEFAULT NULL,
|
|
219
|
+
created_at INTEGER NOT NULL,
|
|
220
|
+
updated_at INTEGER NOT NULL,
|
|
221
|
+
PRIMARY KEY (store, id)
|
|
222
|
+
)`);
|
|
223
|
+
await this.connection.run(`CREATE INDEX IF NOT EXISTS ${VECTOR_TABLE}_store_idx ON ${VECTOR_TABLE} (store)`);
|
|
224
|
+
await this.connection.run(`CREATE TABLE IF NOT EXISTS ${VECTOR_META_TABLE} (
|
|
225
|
+
store TEXT PRIMARY KEY,
|
|
226
|
+
embedding_dim INTEGER NOT NULL
|
|
227
|
+
)`);
|
|
228
|
+
}
|
|
229
|
+
async ensureStoreDimension() {
|
|
230
|
+
const rows = await this.connection.query(`SELECT embedding_dim FROM ${VECTOR_META_TABLE} WHERE store = ? LIMIT 1`, [
|
|
231
|
+
this.store,
|
|
232
|
+
]);
|
|
233
|
+
const storedDim = this.parseStoredDimension(rows[0]);
|
|
234
|
+
if (storedDim !== undefined && this.embeddingDim !== undefined && storedDim !== this.embeddingDim) {
|
|
235
|
+
throw new Error(`embedding dimension ${this.embeddingDim} does not match existing store dimension ${storedDim}`);
|
|
236
|
+
}
|
|
237
|
+
if (this.embeddingDim === undefined) {
|
|
238
|
+
this.embeddingDim = storedDim !== null && storedDim !== void 0 ? storedDim : (this.embeddings ? (await this.embedDocument('dummy')).length : undefined);
|
|
239
|
+
}
|
|
240
|
+
if (this.embeddingDim !== undefined) {
|
|
241
|
+
await this.saveEmbeddingDimension();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
parseStoredDimension(row) {
|
|
245
|
+
if (!row) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
const value = row.embedding_dim;
|
|
249
|
+
if (typeof value === 'number') {
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
if (typeof value === 'string') {
|
|
253
|
+
const parsed = Number(value);
|
|
254
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
async saveEmbeddingDimension() {
|
|
259
|
+
if (this.embeddingDim === undefined) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
await this.connection.run(`INSERT OR REPLACE INTO ${VECTOR_META_TABLE} (store, embedding_dim) VALUES (?, ?)`, [
|
|
263
|
+
this.store,
|
|
264
|
+
this.embeddingDim,
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
async ensureEmbeddingDimension(embedding) {
|
|
268
|
+
this.validateEmbedding(embedding);
|
|
269
|
+
if (this.embeddingDim === undefined) {
|
|
270
|
+
this.embeddingDim = embedding.length;
|
|
271
|
+
await this.saveEmbeddingDimension();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (embedding.length !== this.embeddingDim) {
|
|
275
|
+
throw new Error(`embedding dimension ${embedding.length} does not match vector store dimension ${this.embeddingDim}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async embedDocument(document) {
|
|
279
|
+
if (document === undefined) {
|
|
280
|
+
throw new Error('document is required when embedding is not provided');
|
|
281
|
+
}
|
|
282
|
+
if (!this.embeddings) {
|
|
283
|
+
throw new Error('embeddings provider is required for text operations');
|
|
284
|
+
}
|
|
285
|
+
return this.embeddings.embed(document);
|
|
286
|
+
}
|
|
287
|
+
validateEmbedding(embedding) {
|
|
288
|
+
if (!Array.isArray(embedding) || embedding.length === 0) {
|
|
289
|
+
throw new Error('embedding must be a non-empty number array');
|
|
290
|
+
}
|
|
291
|
+
for (const value of embedding) {
|
|
292
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
293
|
+
throw new Error('embedding values must be finite numbers');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async getStoreRows() {
|
|
298
|
+
return this.connection.query(`SELECT id, document, embedding, metadata FROM ${VECTOR_TABLE} WHERE store = ? ORDER BY updated_at DESC`, [this.store]);
|
|
299
|
+
}
|
|
300
|
+
rowToGetResult(row) {
|
|
301
|
+
const result = {
|
|
302
|
+
id: String(row.id),
|
|
303
|
+
document: row.document === null || row.document === undefined ? '' : String(row.document),
|
|
304
|
+
embedding: this.decodeEmbedding(row.embedding),
|
|
305
|
+
};
|
|
306
|
+
const metadata = this.decodeMetadata(row.metadata);
|
|
307
|
+
if (metadata !== undefined) {
|
|
308
|
+
result.metadata = metadata;
|
|
309
|
+
}
|
|
310
|
+
return result;
|
|
311
|
+
}
|
|
312
|
+
encodeEmbedding(embedding) {
|
|
313
|
+
const bytes = new Uint8Array(embedding.length * Float32Array.BYTES_PER_ELEMENT);
|
|
314
|
+
const view = new DataView(bytes.buffer);
|
|
315
|
+
embedding.forEach((value, index) => view.setFloat32(index * Float32Array.BYTES_PER_ELEMENT, value, true));
|
|
316
|
+
return bytes;
|
|
317
|
+
}
|
|
318
|
+
decodeEmbedding(value) {
|
|
319
|
+
if (!(value instanceof Uint8Array)) {
|
|
320
|
+
throw new Error('stored embedding is not binary data');
|
|
321
|
+
}
|
|
322
|
+
if (value.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0) {
|
|
323
|
+
throw new Error('stored embedding has invalid byte length');
|
|
324
|
+
}
|
|
325
|
+
const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
|
|
326
|
+
const embedding = [];
|
|
327
|
+
for (let offset = 0; offset < value.byteLength; offset += Float32Array.BYTES_PER_ELEMENT) {
|
|
328
|
+
embedding.push(view.getFloat32(offset, true));
|
|
329
|
+
}
|
|
330
|
+
return embedding;
|
|
331
|
+
}
|
|
332
|
+
encodeMetadata(metadata) {
|
|
333
|
+
return metadata ? JSON.stringify(metadata) : null;
|
|
334
|
+
}
|
|
335
|
+
decodeMetadata(value) {
|
|
336
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const parsed = JSON.parse(value);
|
|
341
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
|
|
342
|
+
}
|
|
343
|
+
catch (_a) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
cosineSimilarity(a, b) {
|
|
348
|
+
if (a.length !== b.length) {
|
|
349
|
+
throw new Error(`embedding dimension ${b.length} does not match stored embedding dimension ${a.length}`);
|
|
350
|
+
}
|
|
351
|
+
let dot = 0;
|
|
352
|
+
let normA = 0;
|
|
353
|
+
let normB = 0;
|
|
354
|
+
for (let i = 0; i < a.length; i++) {
|
|
355
|
+
dot += a[i] * b[i];
|
|
356
|
+
normA += a[i] * a[i];
|
|
357
|
+
normB += b[i] * b[i];
|
|
358
|
+
}
|
|
359
|
+
if (normA === 0 || normB === 0) {
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
363
|
+
}
|
|
364
|
+
createId() {
|
|
365
|
+
const cryptoApi = globalThis.crypto;
|
|
366
|
+
if (typeof (cryptoApi === null || cryptoApi === void 0 ? void 0 : cryptoApi.randomUUID) === 'function') {
|
|
367
|
+
return cryptoApi.randomUUID();
|
|
368
|
+
}
|
|
369
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
|
370
|
+
const value = Math.floor(Math.random() * 16);
|
|
371
|
+
const nibble = char === 'x' ? value : (value & 0x3) | 0x8;
|
|
372
|
+
return nibble.toString(16);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
FastSQLVectorStore.connectionRefs = new Map();
|
|
377
|
+
FastSQLVectorStore.ownedConnections = new Set();
|