@workglow/storage 0.0.126 → 0.1.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 (36) hide show
  1. package/dist/browser.js +58 -23
  2. package/dist/browser.js.map +18 -18
  3. package/dist/bun.js +104 -42
  4. package/dist/bun.js.map +24 -24
  5. package/dist/node.js +104 -42
  6. package/dist/node.js.map +24 -24
  7. package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
  8. package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
  9. package/dist/queue/SupabaseQueueStorage.d.ts +11 -0
  10. package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
  11. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
  12. package/dist/sqlite/browser.d.ts.map +1 -1
  13. package/dist/sqlite/browser.js.map +2 -2
  14. package/dist/sqlite/bun.d.ts.map +1 -1
  15. package/dist/sqlite/bun.js.map +2 -2
  16. package/dist/tabular/BaseSqlTabularStorage.d.ts.map +1 -1
  17. package/dist/tabular/CachedTabularStorage.d.ts +1 -0
  18. package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
  19. package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
  20. package/dist/tabular/HuggingFaceTabularStorage.d.ts.map +1 -1
  21. package/dist/tabular/InMemoryTabularStorage.d.ts +2 -0
  22. package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
  23. package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
  24. package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
  25. package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
  26. package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
  27. package/dist/tabular/StorageError.d.ts.map +1 -1
  28. package/dist/tabular/SupabaseTabularStorage.d.ts.map +1 -1
  29. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  30. package/dist/util/HybridSubscriptionManager.d.ts.map +1 -1
  31. package/dist/util/PollingSubscriptionManager.d.ts +2 -0
  32. package/dist/util/PollingSubscriptionManager.d.ts.map +1 -1
  33. package/dist/vector/PostgresVectorStorage.d.ts +0 -12
  34. package/dist/vector/PostgresVectorStorage.d.ts.map +1 -1
  35. package/dist/vector/SqliteAiVectorStorage.d.ts.map +1 -1
  36. package/package.json +7 -7
package/dist/node.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
- try {
691
- const all = await this.durable.getAll();
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 index = this.jobQueue.findIndex((j) => j.id === job.id);
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
- job.run_attempts = currentAttempts + 1;
1540
- this.jobQueue[index] = job;
1541
- this.events.emit("change", { type: "UPDATE", old: existing, new: job });
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.initAndPoll(subscribers, subscription);
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(subscribers, newSubscription) {
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 "node:fs/promises";
2222
2243
  import path from "node: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 sleep3(1);
2279
- await writeFile(filePath, JSON.stringify(entity));
2280
- } catch (error2) {
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 allEntities = await Promise.all(jsonFiles.map(async (file) => {
2381
+ const results = await Promise.allSettled(jsonFiles.map(async (file) => {
2361
2382
  const filePath = path.join(this.folderPath, file);
2362
- const content = await readFile(filePath, "utf8");
2363
- return JSON.parse(content);
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];
@@ -4675,7 +4709,7 @@ class SqliteQueueStorage {
4675
4709
  return result;
4676
4710
  }
4677
4711
  async peek(status = JobStatus.PENDING, num = 100) {
4678
- num = Number(num) || 100;
4712
+ num = Math.max(1, Math.min(1e4, Math.floor(Number(num) || 100)));
4679
4713
  const prefixConditions = this.buildPrefixWhereClause();
4680
4714
  const prefixParams = this.getPrefixParamValues();
4681
4715
  const FutureJobQuery = `
@@ -4930,12 +4964,24 @@ class SupabaseQueueStorage {
4930
4964
  const conditions = this.prefixes.map((p) => {
4931
4965
  const value = this.prefixValues[p.name];
4932
4966
  if (p.type === "uuid") {
4933
- return `${p.name} = '${this.escapeSqlString(String(value))}'`;
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}`);
4934
4973
  }
4935
- return `${p.name} = ${Number(value ?? 0)}`;
4974
+ return `${p.name} = ${numValue}`;
4936
4975
  }).join(" AND ");
4937
4976
  return " AND " + conditions;
4938
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
+ }
4939
4985
  escapeSqlString(value) {
4940
4986
  return value.replace(/'/g, "''");
4941
4987
  }
@@ -5042,8 +5088,10 @@ class SupabaseQueueStorage {
5042
5088
  }
5043
5089
  async next(workerId) {
5044
5090
  const prefixConditions = this.buildPrefixWhereSql();
5045
- const escapedQueueName = this.escapeSqlString(this.queueName);
5046
- const escapedWorkerId = this.escapeSqlString(workerId);
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);
5047
5095
  const sql = `
5048
5096
  UPDATE ${this.tableName}
5049
5097
  SET status = '${JobStatus.PROCESSING}', last_ran_at = NOW() AT TIME ZONE 'UTC', worker_id = '${escapedWorkerId}'
@@ -5804,6 +5852,8 @@ class SupabaseRateLimiterStorage {
5804
5852
  }
5805
5853
  // src/vector/PostgresVectorStorage.ts
5806
5854
  import { cosineSimilarity as cosineSimilarity2 } from "@workglow/util/schema";
5855
+ var SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
5856
+
5807
5857
  class PostgresVectorStorage extends PostgresTabularStorage {
5808
5858
  vectorDimensions;
5809
5859
  VectorType;
@@ -5840,6 +5890,9 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5840
5890
  if (filter && Object.keys(filter).length > 0 && metadataCol) {
5841
5891
  const conditions = [];
5842
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
+ }
5843
5896
  conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
5844
5897
  params.push(String(value));
5845
5898
  paramIndex++;
@@ -5868,7 +5921,10 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5868
5921
  }
5869
5922
  return results;
5870
5923
  } catch (error) {
5871
- console.warn("pgvector query failed, falling back to in-memory search:", error);
5924
+ if (error instanceof StorageValidationError) {
5925
+ throw error;
5926
+ }
5927
+ console.error("pgvector query failed, falling back to in-memory search:", error);
5872
5928
  return this.searchFallback(query, options);
5873
5929
  }
5874
5930
  }
@@ -5879,7 +5935,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5879
5935
  }
5880
5936
  try {
5881
5937
  const queryVector = `[${Array.from(query).join(",")}]`;
5882
- const tsQuery = textQuery.split(/\s+/).join(" & ");
5938
+ const tsQueryText = textQuery;
5883
5939
  const vectorCol = String(this.vectorPropertyName);
5884
5940
  const metadataCol = this.metadataPropertyName ? String(this.metadataPropertyName) : null;
5885
5941
  let sql = `
@@ -5887,15 +5943,18 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5887
5943
  *,
5888
5944
  (
5889
5945
  $2 * (1 - (${vectorCol} <=> $1::vector)) +
5890
- $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), to_tsquery('english', $4))
5946
+ $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
5891
5947
  ) as score
5892
5948
  FROM "${this.table}"
5893
5949
  `;
5894
- const params = [queryVector, vectorWeight, 1 - vectorWeight, tsQuery];
5950
+ const params = [queryVector, vectorWeight, 1 - vectorWeight, tsQueryText];
5895
5951
  let paramIndex = 5;
5896
5952
  if (filter && Object.keys(filter).length > 0 && metadataCol) {
5897
5953
  const conditions = [];
5898
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
+ }
5899
5958
  conditions.push(`${metadataCol}->>'${key}' = $${paramIndex}`);
5900
5959
  params.push(String(value));
5901
5960
  paramIndex++;
@@ -5906,7 +5965,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5906
5965
  sql += filter ? " AND" : " WHERE";
5907
5966
  sql += ` (
5908
5967
  $2 * (1 - (${vectorCol} <=> $1::vector)) +
5909
- $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), to_tsquery('english', $4))
5968
+ $3 * ts_rank(to_tsvector('english', ${metadataCol || "''"}::text), plainto_tsquery('english', $4))
5910
5969
  ) >= $${paramIndex}`;
5911
5970
  params.push(scoreThreshold);
5912
5971
  paramIndex++;
@@ -5927,7 +5986,10 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5927
5986
  }
5928
5987
  return results;
5929
5988
  } catch (error) {
5930
- console.warn("pgvector hybrid query failed, falling back to in-memory search:", error);
5989
+ if (error instanceof StorageValidationError) {
5990
+ throw error;
5991
+ }
5992
+ console.error("pgvector hybrid query failed, falling back to in-memory search:", error);
5931
5993
  return this.hybridSearchFallback(query, options);
5932
5994
  }
5933
5995
  }
@@ -5963,7 +6025,7 @@ class PostgresVectorStorage extends PostgresTabularStorage {
5963
6025
  continue;
5964
6026
  }
5965
6027
  const vectorScore = cosineSimilarity2(query, vector);
5966
- const metadataText = JSON.stringify(metadata).toLowerCase();
6028
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
5967
6029
  let textScore = 0;
5968
6030
  if (queryWords.length > 0) {
5969
6031
  let matches = 0;
@@ -6074,7 +6136,7 @@ class SqliteVectorStorage extends SqliteTabularStorage {
6074
6136
  continue;
6075
6137
  }
6076
6138
  const vectorScore = cosineSimilarity3(query, vector);
6077
- const metadataText = JSON.stringify(metadata).toLowerCase();
6139
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
6078
6140
  let textScore = 0;
6079
6141
  if (queryWords.length > 0) {
6080
6142
  let matches = 0;
@@ -6408,7 +6470,7 @@ class SqliteAiVectorStorage extends SqliteTabularStorage {
6408
6470
  if (filter && !matchesFilter3(metadata, filter)) {
6409
6471
  continue;
6410
6472
  }
6411
- const metadataText = JSON.stringify(metadata).toLowerCase();
6473
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
6412
6474
  let textScore = 0;
6413
6475
  if (queryWords.length > 0) {
6414
6476
  let matches = 0;
@@ -6463,7 +6525,7 @@ class SqliteAiVectorStorage extends SqliteTabularStorage {
6463
6525
  continue;
6464
6526
  }
6465
6527
  const vectorScore = cosineSimilarity4(query, vector);
6466
- const metadataText = JSON.stringify(metadata).toLowerCase();
6528
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
6467
6529
  let textScore = 0;
6468
6530
  if (queryWords.length > 0) {
6469
6531
  let matches = 0;
@@ -8258,4 +8320,4 @@ export {
8258
8320
  BaseTabularStorage
8259
8321
  };
8260
8322
 
8261
- //# debugId=CB06EB0A7CC1A40D64756E2164756E21
8323
+ //# debugId=120AE6AECCC1B17664756E2164756E21