@workglow/storage 0.0.125 → 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/README.md +15 -6
- package/dist/browser.d.ts +18 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +58 -23
- package/dist/browser.js.map +13 -13
- package/dist/{types.d.ts → bun.d.ts} +1 -1
- package/dist/bun.d.ts.map +1 -0
- package/dist/bun.js +106 -45
- package/dist/bun.js.map +22 -22
- package/dist/node.d.ts +7 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +106 -45
- package/dist/node.js.map +22 -22
- package/dist/postgres/browser.d.ts +32 -0
- package/dist/postgres/browser.d.ts.map +1 -0
- package/dist/postgres/browser.js +150 -0
- package/dist/postgres/browser.js.map +11 -0
- package/dist/postgres/node-bun.d.ts +26 -0
- package/dist/postgres/node-bun.d.ts.map +1 -0
- package/dist/postgres/node-bun.js +41 -0
- package/dist/postgres/node-bun.js.map +10 -0
- package/dist/postgres/pglite-pool.d.ts +21 -0
- package/dist/postgres/pglite-pool.d.ts.map +1 -0
- package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
- package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
- package/dist/queue/PostgresQueueStorage.d.ts +1 -1
- package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
- package/dist/queue/SqliteQueueStorage.d.ts +1 -1
- package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
- package/dist/queue/SupabaseQueueStorage.d.ts +11 -0
- package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
- package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts +1 -1
- package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts +1 -1
- package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts.map +1 -1
- package/dist/sqlite/browser.d.ts +37 -0
- package/dist/sqlite/browser.d.ts.map +1 -0
- package/dist/sqlite/browser.js +125 -0
- package/dist/sqlite/browser.js.map +10 -0
- package/dist/sqlite/bun.d.ts +32 -0
- package/dist/sqlite/bun.d.ts.map +1 -0
- package/dist/sqlite/bun.js +84 -0
- package/dist/sqlite/bun.js.map +10 -0
- package/dist/sqlite/canonical-api.d.ts +34 -0
- package/dist/sqlite/canonical-api.d.ts.map +1 -0
- package/dist/sqlite/node.d.ts +34 -0
- package/dist/sqlite/node.d.ts.map +1 -0
- package/dist/sqlite/node.js +65 -0
- package/dist/sqlite/node.js.map +10 -0
- package/dist/tabular/CachedTabularStorage.d.ts +1 -0
- package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
- package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
- package/dist/tabular/InMemoryTabularStorage.d.ts +2 -0
- package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
- package/dist/tabular/PostgresTabularStorage.d.ts +1 -1
- package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
- package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
- package/dist/tabular/SqliteTabularStorage.d.ts +1 -1
- package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
- package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
- package/dist/util/HybridSubscriptionManager.d.ts.map +1 -1
- package/dist/util/PollingSubscriptionManager.d.ts +2 -0
- package/dist/util/PollingSubscriptionManager.d.ts.map +1 -1
- package/dist/vector/PostgresVectorStorage.d.ts +1 -13
- package/dist/vector/PostgresVectorStorage.d.ts.map +1 -1
- package/dist/vector/SqliteAiVectorStorage.d.ts +1 -1
- package/dist/vector/SqliteAiVectorStorage.d.ts.map +1 -1
- package/dist/vector/SqliteVectorStorage.d.ts +1 -1
- package/dist/vector/SqliteVectorStorage.d.ts.map +1 -1
- package/package.json +85 -21
- package/src/kv/README.md +4 -3
- package/src/queue/README.md +1 -1
- package/src/tabular/README.md +8 -1
- package/src/vector/README.md +6 -6
- package/dist/types.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,iBAAiB,CAAC"}
|
package/dist/bun.js
CHANGED
|
@@ -394,6 +394,7 @@ var MEMORY_TABULAR_REPOSITORY = createServiceToken2("storage.tabularRepository.i
|
|
|
394
394
|
class InMemoryTabularStorage extends BaseTabularStorage {
|
|
395
395
|
values = new Map;
|
|
396
396
|
autoIncrementCounter = 0;
|
|
397
|
+
_lastPutWasInsert = false;
|
|
397
398
|
constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
398
399
|
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
399
400
|
}
|
|
@@ -429,6 +430,7 @@ class InMemoryTabularStorage extends BaseTabularStorage {
|
|
|
429
430
|
}
|
|
430
431
|
const { key } = this.separateKeyValueFromCombined(entityToStore);
|
|
431
432
|
const id = await makeFingerprint2(key);
|
|
433
|
+
this._lastPutWasInsert = !this.values.has(id);
|
|
432
434
|
this.values.set(id, entityToStore);
|
|
433
435
|
this.events.emit("put", entityToStore);
|
|
434
436
|
return entityToStore;
|
|
@@ -625,7 +627,7 @@ class InMemoryTabularStorage extends BaseTabularStorage {
|
|
|
625
627
|
}
|
|
626
628
|
subscribeToChanges(callback, options) {
|
|
627
629
|
const handlePut = (entity) => {
|
|
628
|
-
callback({ type: "UPDATE", new: entity });
|
|
630
|
+
callback({ type: this._lastPutWasInsert ? "INSERT" : "UPDATE", new: entity });
|
|
629
631
|
};
|
|
630
632
|
const handleDelete = (_key) => {
|
|
631
633
|
callback({ type: "DELETE" });
|
|
@@ -654,6 +656,7 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
654
656
|
cache;
|
|
655
657
|
durable;
|
|
656
658
|
cacheInitialized = false;
|
|
659
|
+
cacheInitPromise = null;
|
|
657
660
|
constructor(durable, cache, schema, primaryKeyNames, indexes, clientProvidedKeys = "if-missing") {
|
|
658
661
|
if (!schema || !primaryKeyNames) {
|
|
659
662
|
throw new Error("Schema and primaryKeyNames must be provided when creating CachedTabularStorage");
|
|
@@ -687,16 +690,23 @@ class CachedTabularStorage extends BaseTabularStorage {
|
|
|
687
690
|
async initializeCache() {
|
|
688
691
|
if (this.cacheInitialized)
|
|
689
692
|
return;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (all && all.length > 0) {
|
|
693
|
-
await this.cache.putBulk(all);
|
|
694
|
-
}
|
|
695
|
-
this.cacheInitialized = true;
|
|
696
|
-
} catch (error) {
|
|
697
|
-
getLogger().warn("Failed to initialize cache from durable repository:", { error });
|
|
698
|
-
this.cacheInitialized = true;
|
|
693
|
+
if (this.cacheInitPromise) {
|
|
694
|
+
return this.cacheInitPromise;
|
|
699
695
|
}
|
|
696
|
+
this.cacheInitPromise = (async () => {
|
|
697
|
+
try {
|
|
698
|
+
const all = await this.durable.getAll();
|
|
699
|
+
if (all && all.length > 0) {
|
|
700
|
+
await this.cache.putBulk(all);
|
|
701
|
+
}
|
|
702
|
+
this.cacheInitialized = true;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
getLogger().warn("Failed to initialize cache from durable repository:", { error });
|
|
705
|
+
} finally {
|
|
706
|
+
this.cacheInitPromise = null;
|
|
707
|
+
}
|
|
708
|
+
})();
|
|
709
|
+
return this.cacheInitPromise;
|
|
700
710
|
}
|
|
701
711
|
async put(value) {
|
|
702
712
|
await this.initializeCache();
|
|
@@ -1532,13 +1542,17 @@ class InMemoryQueueStorage {
|
|
|
1532
1542
|
}
|
|
1533
1543
|
async complete(job) {
|
|
1534
1544
|
await sleep(0);
|
|
1535
|
-
const
|
|
1545
|
+
const jobWithPrefixes = job;
|
|
1546
|
+
const index = this.jobQueue.findIndex((j) => j.id === job.id && this.matchesPrefixes(j));
|
|
1536
1547
|
if (index !== -1) {
|
|
1537
1548
|
const existing = this.jobQueue[index];
|
|
1538
1549
|
const currentAttempts = existing?.run_attempts ?? 0;
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1550
|
+
jobWithPrefixes.run_attempts = currentAttempts + 1;
|
|
1551
|
+
for (const [key, value] of Object.entries(this.prefixValues)) {
|
|
1552
|
+
jobWithPrefixes[key] = value;
|
|
1553
|
+
}
|
|
1554
|
+
this.jobQueue[index] = jobWithPrefixes;
|
|
1555
|
+
this.events.emit("change", { type: "UPDATE", old: existing, new: jobWithPrefixes });
|
|
1542
1556
|
}
|
|
1543
1557
|
}
|
|
1544
1558
|
async abort(id) {
|
|
@@ -1893,6 +1907,7 @@ class PollingSubscriptionManager {
|
|
|
1893
1907
|
intervals = new Map;
|
|
1894
1908
|
lastKnownState = new Map;
|
|
1895
1909
|
initialized = false;
|
|
1910
|
+
initializing = false;
|
|
1896
1911
|
fetchState;
|
|
1897
1912
|
compareItems;
|
|
1898
1913
|
payloadFactory;
|
|
@@ -1917,7 +1932,8 @@ class PollingSubscriptionManager {
|
|
|
1917
1932
|
this.intervals.set(interval, intervalGroup);
|
|
1918
1933
|
if (!this.initialized) {
|
|
1919
1934
|
this.initialized = true;
|
|
1920
|
-
this.
|
|
1935
|
+
this.initializing = true;
|
|
1936
|
+
this.initAndPoll(subscription);
|
|
1921
1937
|
} else {
|
|
1922
1938
|
this.pollForNewSubscriber(subscription);
|
|
1923
1939
|
}
|
|
@@ -1936,7 +1952,7 @@ class PollingSubscriptionManager {
|
|
|
1936
1952
|
}
|
|
1937
1953
|
};
|
|
1938
1954
|
}
|
|
1939
|
-
async initAndPoll(
|
|
1955
|
+
async initAndPoll(newSubscription) {
|
|
1940
1956
|
try {
|
|
1941
1957
|
this.lastKnownState = await this.fetchState();
|
|
1942
1958
|
for (const [, item] of this.lastKnownState) {
|
|
@@ -1945,7 +1961,9 @@ class PollingSubscriptionManager {
|
|
|
1945
1961
|
newSubscription.callback(payload);
|
|
1946
1962
|
} catch {}
|
|
1947
1963
|
}
|
|
1948
|
-
} catch {}
|
|
1964
|
+
} catch {} finally {
|
|
1965
|
+
this.initializing = false;
|
|
1966
|
+
}
|
|
1949
1967
|
}
|
|
1950
1968
|
pollForNewSubscriber(subscription) {
|
|
1951
1969
|
for (const [, item] of this.lastKnownState) {
|
|
@@ -1958,6 +1976,8 @@ class PollingSubscriptionManager {
|
|
|
1958
1976
|
async poll(subscribers) {
|
|
1959
1977
|
if (subscribers.size === 0)
|
|
1960
1978
|
return;
|
|
1979
|
+
if (this.initializing)
|
|
1980
|
+
return;
|
|
1961
1981
|
try {
|
|
1962
1982
|
const currentState = await this.fetchState();
|
|
1963
1983
|
const changes = [];
|
|
@@ -2001,6 +2021,7 @@ class PollingSubscriptionManager {
|
|
|
2001
2021
|
this.intervals.clear();
|
|
2002
2022
|
this.lastKnownState.clear();
|
|
2003
2023
|
this.initialized = false;
|
|
2024
|
+
this.initializing = false;
|
|
2004
2025
|
}
|
|
2005
2026
|
}
|
|
2006
2027
|
// src/vector/InMemoryVectorStorage.ts
|
|
@@ -2105,7 +2126,7 @@ class InMemoryVectorStorage extends InMemoryTabularStorage {
|
|
|
2105
2126
|
continue;
|
|
2106
2127
|
}
|
|
2107
2128
|
const vectorScore = cosineSimilarity(query, vector);
|
|
2108
|
-
const metadataText = Object.values(metadata).join(" ").toLowerCase();
|
|
2129
|
+
const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
|
|
2109
2130
|
const textScore = textRelevance(metadataText, textQuery);
|
|
2110
2131
|
const combinedScore = vectorWeight * vectorScore + (1 - vectorWeight) * textScore;
|
|
2111
2132
|
if (combinedScore < scoreThreshold) {
|
|
@@ -2217,7 +2238,7 @@ class EncryptedKvCredentialStore {
|
|
|
2217
2238
|
}
|
|
2218
2239
|
}
|
|
2219
2240
|
// src/tabular/FsFolderTabularStorage.ts
|
|
2220
|
-
import { createServiceToken as createServiceToken12, makeFingerprint as makeFingerprint5, sleep as sleep3, uuid4 as uuid43 } from "@workglow/util";
|
|
2241
|
+
import { createServiceToken as createServiceToken12, getLogger as getLogger3, makeFingerprint as makeFingerprint5, sleep as sleep3, uuid4 as uuid43 } from "@workglow/util";
|
|
2221
2242
|
import { mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
|
2222
2243
|
import path from "path";
|
|
2223
2244
|
var FS_FOLDER_TABULAR_REPOSITORY = createServiceToken12("storage.tabularRepository.fsFolder");
|
|
@@ -2274,11 +2295,11 @@ class FsFolderTabularStorage extends BaseTabularStorage {
|
|
|
2274
2295
|
try {
|
|
2275
2296
|
await writeFile(filePath, JSON.stringify(entityToStore));
|
|
2276
2297
|
} catch (error) {
|
|
2298
|
+
await sleep3(1);
|
|
2277
2299
|
try {
|
|
2278
|
-
await
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
console.error("Error writing file", filePath, error2);
|
|
2300
|
+
await writeFile(filePath, JSON.stringify(entityToStore));
|
|
2301
|
+
} catch (retryError) {
|
|
2302
|
+
throw new Error(`Failed to write file "${filePath}" after retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`, { cause: retryError });
|
|
2282
2303
|
}
|
|
2283
2304
|
}
|
|
2284
2305
|
this.events.emit("put", entityToStore);
|
|
@@ -2357,11 +2378,24 @@ class FsFolderTabularStorage extends BaseTabularStorage {
|
|
|
2357
2378
|
if (jsonFiles.length === 0) {
|
|
2358
2379
|
return;
|
|
2359
2380
|
}
|
|
2360
|
-
const
|
|
2381
|
+
const results = await Promise.allSettled(jsonFiles.map(async (file) => {
|
|
2361
2382
|
const filePath = path.join(this.folderPath, file);
|
|
2362
|
-
|
|
2363
|
-
|
|
2383
|
+
try {
|
|
2384
|
+
const content = await readFile(filePath, "utf8");
|
|
2385
|
+
return JSON.parse(content);
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2388
|
+
throw new Error(`Failed to read or parse "${filePath}": ${message}`);
|
|
2389
|
+
}
|
|
2364
2390
|
}));
|
|
2391
|
+
const allEntities = [];
|
|
2392
|
+
for (const result of results) {
|
|
2393
|
+
if (result.status === "fulfilled") {
|
|
2394
|
+
allEntities.push(result.value);
|
|
2395
|
+
} else {
|
|
2396
|
+
getLogger3().warn(`Skipping corrupted file in getBulk: ${result.reason?.message ?? result.reason}`);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2365
2399
|
allEntities.sort((a, b) => {
|
|
2366
2400
|
for (const key of this.primaryKeyNames) {
|
|
2367
2401
|
const aVal = a[key];
|
|
@@ -3108,10 +3142,9 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
3108
3142
|
}
|
|
3109
3143
|
}
|
|
3110
3144
|
// src/tabular/SqliteTabularStorage.ts
|
|
3111
|
-
import { Sqlite } from "@workglow/sqlite";
|
|
3145
|
+
import { Sqlite } from "@workglow/storage/sqlite";
|
|
3112
3146
|
import { createServiceToken as createServiceToken14, uuid4 as uuid44 } from "@workglow/util";
|
|
3113
3147
|
var SQLITE_TABULAR_REPOSITORY = createServiceToken14("storage.tabularRepository.sqlite");
|
|
3114
|
-
var Database = Sqlite.Database;
|
|
3115
3148
|
|
|
3116
3149
|
class SqliteTabularStorage extends BaseSqlTabularStorage {
|
|
3117
3150
|
db;
|
|
@@ -3121,7 +3154,7 @@ class SqliteTabularStorage extends BaseSqlTabularStorage {
|
|
|
3121
3154
|
constructor(dbOrPath, table = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
3122
3155
|
super(table, schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
3123
3156
|
if (typeof dbOrPath === "string") {
|
|
3124
|
-
this.db = new Database(dbOrPath);
|
|
3157
|
+
this.db = new Sqlite.Database(dbOrPath);
|
|
3125
3158
|
} else {
|
|
3126
3159
|
this.db = dbOrPath;
|
|
3127
3160
|
}
|
|
@@ -4676,7 +4709,7 @@ class SqliteQueueStorage {
|
|
|
4676
4709
|
return result;
|
|
4677
4710
|
}
|
|
4678
4711
|
async peek(status = JobStatus.PENDING, num = 100) {
|
|
4679
|
-
num = Number(num) || 100;
|
|
4712
|
+
num = Math.max(1, Math.min(1e4, Math.floor(Number(num) || 100)));
|
|
4680
4713
|
const prefixConditions = this.buildPrefixWhereClause();
|
|
4681
4714
|
const prefixParams = this.getPrefixParamValues();
|
|
4682
4715
|
const FutureJobQuery = `
|
|
@@ -4931,12 +4964,24 @@ class SupabaseQueueStorage {
|
|
|
4931
4964
|
const conditions = this.prefixes.map((p) => {
|
|
4932
4965
|
const value = this.prefixValues[p.name];
|
|
4933
4966
|
if (p.type === "uuid") {
|
|
4934
|
-
|
|
4967
|
+
const validated = this.validateSqlValue(String(value), `prefix "${p.name}"`);
|
|
4968
|
+
return `${p.name} = '${this.escapeSqlString(validated)}'`;
|
|
4969
|
+
}
|
|
4970
|
+
const numValue = Number(value ?? 0);
|
|
4971
|
+
if (!Number.isFinite(numValue)) {
|
|
4972
|
+
throw new Error(`Invalid numeric prefix value for "${p.name}": ${value}`);
|
|
4935
4973
|
}
|
|
4936
|
-
return `${p.name} = ${
|
|
4974
|
+
return `${p.name} = ${numValue}`;
|
|
4937
4975
|
}).join(" AND ");
|
|
4938
4976
|
return " AND " + conditions;
|
|
4939
4977
|
}
|
|
4978
|
+
static SAFE_SQL_VALUE_RE = /^[a-zA-Z0-9_\-.:]+$/;
|
|
4979
|
+
validateSqlValue(value, context) {
|
|
4980
|
+
if (!SupabaseQueueStorage.SAFE_SQL_VALUE_RE.test(value)) {
|
|
4981
|
+
throw new Error(`Unsafe value for ${context}: "${value}". Values must match /^[a-zA-Z0-9_\\-.:]+$/.`);
|
|
4982
|
+
}
|
|
4983
|
+
return value;
|
|
4984
|
+
}
|
|
4940
4985
|
escapeSqlString(value) {
|
|
4941
4986
|
return value.replace(/'/g, "''");
|
|
4942
4987
|
}
|
|
@@ -5043,8 +5088,10 @@ class SupabaseQueueStorage {
|
|
|
5043
5088
|
}
|
|
5044
5089
|
async next(workerId) {
|
|
5045
5090
|
const prefixConditions = this.buildPrefixWhereSql();
|
|
5046
|
-
const
|
|
5047
|
-
const
|
|
5091
|
+
const validatedQueueName = this.validateSqlValue(this.queueName, "queueName");
|
|
5092
|
+
const validatedWorkerId = this.validateSqlValue(workerId, "workerId");
|
|
5093
|
+
const escapedQueueName = this.escapeSqlString(validatedQueueName);
|
|
5094
|
+
const escapedWorkerId = this.escapeSqlString(validatedWorkerId);
|
|
5048
5095
|
const sql = `
|
|
5049
5096
|
UPDATE ${this.tableName}
|
|
5050
5097
|
SET status = '${JobStatus.PROCESSING}', last_ran_at = NOW() AT TIME ZONE 'UTC', worker_id = '${escapedWorkerId}'
|
|
@@ -5805,6 +5852,8 @@ class SupabaseRateLimiterStorage {
|
|
|
5805
5852
|
}
|
|
5806
5853
|
// src/vector/PostgresVectorStorage.ts
|
|
5807
5854
|
import { cosineSimilarity as cosineSimilarity2 } from "@workglow/util/schema";
|
|
5855
|
+
var SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
5856
|
+
|
|
5808
5857
|
class PostgresVectorStorage extends PostgresTabularStorage {
|
|
5809
5858
|
vectorDimensions;
|
|
5810
5859
|
VectorType;
|
|
@@ -5841,6 +5890,9 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5841
5890
|
if (filter && Object.keys(filter).length > 0 && metadataCol) {
|
|
5842
5891
|
const conditions = [];
|
|
5843
5892
|
for (const [key, value] of Object.entries(filter)) {
|
|
5893
|
+
if (!SAFE_IDENTIFIER_RE.test(key)) {
|
|
5894
|
+
throw new StorageValidationError(`Invalid metadata filter key: "${key}". Keys must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`);
|
|
5895
|
+
}
|
|
5844
5896
|
conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
|
|
5845
5897
|
params.push(String(value));
|
|
5846
5898
|
paramIndex++;
|
|
@@ -5869,7 +5921,10 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5869
5921
|
}
|
|
5870
5922
|
return results;
|
|
5871
5923
|
} catch (error) {
|
|
5872
|
-
|
|
5924
|
+
if (error instanceof StorageValidationError) {
|
|
5925
|
+
throw error;
|
|
5926
|
+
}
|
|
5927
|
+
console.error("pgvector query failed, falling back to in-memory search:", error);
|
|
5873
5928
|
return this.searchFallback(query, options);
|
|
5874
5929
|
}
|
|
5875
5930
|
}
|
|
@@ -5880,7 +5935,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5880
5935
|
}
|
|
5881
5936
|
try {
|
|
5882
5937
|
const queryVector = `[${Array.from(query).join(",")}]`;
|
|
5883
|
-
const
|
|
5938
|
+
const tsQueryText = textQuery;
|
|
5884
5939
|
const vectorCol = String(this.vectorPropertyName);
|
|
5885
5940
|
const metadataCol = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
|
|
5886
5941
|
let sql = `
|
|
@@ -5888,15 +5943,18 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5888
5943
|
*,
|
|
5889
5944
|
(
|
|
5890
5945
|
$2 * (1 - (${vectorCol} <=> $1::vector)) +
|
|
5891
|
-
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text),
|
|
5946
|
+
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
|
|
5892
5947
|
) as score
|
|
5893
5948
|
FROM "${this.table}"
|
|
5894
5949
|
`;
|
|
5895
|
-
const params = [queryVector, vectorWeight, 1 - vectorWeight,
|
|
5950
|
+
const params = [queryVector, vectorWeight, 1 - vectorWeight, tsQueryText];
|
|
5896
5951
|
let paramIndex = 5;
|
|
5897
5952
|
if (filter && Object.keys(filter).length > 0 && metadataCol) {
|
|
5898
5953
|
const conditions = [];
|
|
5899
5954
|
for (const [key, value] of Object.entries(filter)) {
|
|
5955
|
+
if (!SAFE_IDENTIFIER_RE.test(key)) {
|
|
5956
|
+
throw new StorageValidationError(`Invalid metadata filter key: "${key}". Keys must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`);
|
|
5957
|
+
}
|
|
5900
5958
|
conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
|
|
5901
5959
|
params.push(String(value));
|
|
5902
5960
|
paramIndex++;
|
|
@@ -5907,7 +5965,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5907
5965
|
sql += filter ? " AND" : " WHERE";
|
|
5908
5966
|
sql += ` (
|
|
5909
5967
|
$2 * (1 - (${vectorCol} <=> $1::vector)) +
|
|
5910
|
-
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text),
|
|
5968
|
+
$3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
|
|
5911
5969
|
) >= $${paramIndex}`;
|
|
5912
5970
|
params.push(scoreThreshold);
|
|
5913
5971
|
paramIndex++;
|
|
@@ -5928,7 +5986,10 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5928
5986
|
}
|
|
5929
5987
|
return results;
|
|
5930
5988
|
} catch (error) {
|
|
5931
|
-
|
|
5989
|
+
if (error instanceof StorageValidationError) {
|
|
5990
|
+
throw error;
|
|
5991
|
+
}
|
|
5992
|
+
console.error("pgvector hybrid query failed, falling back to in-memory search:", error);
|
|
5932
5993
|
return this.hybridSearchFallback(query, options);
|
|
5933
5994
|
}
|
|
5934
5995
|
}
|
|
@@ -5964,7 +6025,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
|
|
|
5964
6025
|
continue;
|
|
5965
6026
|
}
|
|
5966
6027
|
const vectorScore = cosineSimilarity2(query, vector);
|
|
5967
|
-
const metadataText =
|
|
6028
|
+
const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
|
|
5968
6029
|
let textScore = 0;
|
|
5969
6030
|
if (queryWords.length > 0) {
|
|
5970
6031
|
let matches = 0;
|
|
@@ -6075,7 +6136,7 @@ class SqliteVectorStorage extends SqliteTabularStorage {
|
|
|
6075
6136
|
continue;
|
|
6076
6137
|
}
|
|
6077
6138
|
const vectorScore = cosineSimilarity3(query, vector);
|
|
6078
|
-
const metadataText =
|
|
6139
|
+
const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
|
|
6079
6140
|
let textScore = 0;
|
|
6080
6141
|
if (queryWords.length > 0) {
|
|
6081
6142
|
let matches = 0;
|
|
@@ -6409,7 +6470,7 @@ class SqliteAiVectorStorage extends SqliteTabularStorage {
|
|
|
6409
6470
|
if (filter && !matchesFilter3(metadata, filter)) {
|
|
6410
6471
|
continue;
|
|
6411
6472
|
}
|
|
6412
|
-
const metadataText =
|
|
6473
|
+
const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
|
|
6413
6474
|
let textScore = 0;
|
|
6414
6475
|
if (queryWords.length > 0) {
|
|
6415
6476
|
let matches = 0;
|
|
@@ -6464,7 +6525,7 @@ class SqliteAiVectorStorage extends SqliteTabularStorage {
|
|
|
6464
6525
|
continue;
|
|
6465
6526
|
}
|
|
6466
6527
|
const vectorScore = cosineSimilarity4(query, vector);
|
|
6467
|
-
const metadataText =
|
|
6528
|
+
const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
|
|
6468
6529
|
let textScore = 0;
|
|
6469
6530
|
if (queryWords.length > 0) {
|
|
6470
6531
|
let matches = 0;
|
|
@@ -8259,4 +8320,4 @@ export {
|
|
|
8259
8320
|
BaseTabularStorage
|
|
8260
8321
|
};
|
|
8261
8322
|
|
|
8262
|
-
//# debugId=
|
|
8323
|
+
//# debugId=962404C01663B8E764756E2164756E21
|