@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.
Files changed (76) hide show
  1. package/README.md +15 -6
  2. package/dist/browser.d.ts +18 -0
  3. package/dist/browser.d.ts.map +1 -0
  4. package/dist/browser.js +58 -23
  5. package/dist/browser.js.map +13 -13
  6. package/dist/{types.d.ts → bun.d.ts} +1 -1
  7. package/dist/bun.d.ts.map +1 -0
  8. package/dist/bun.js +106 -45
  9. package/dist/bun.js.map +22 -22
  10. package/dist/node.d.ts +7 -0
  11. package/dist/node.d.ts.map +1 -0
  12. package/dist/node.js +106 -45
  13. package/dist/node.js.map +22 -22
  14. package/dist/postgres/browser.d.ts +32 -0
  15. package/dist/postgres/browser.d.ts.map +1 -0
  16. package/dist/postgres/browser.js +150 -0
  17. package/dist/postgres/browser.js.map +11 -0
  18. package/dist/postgres/node-bun.d.ts +26 -0
  19. package/dist/postgres/node-bun.d.ts.map +1 -0
  20. package/dist/postgres/node-bun.js +41 -0
  21. package/dist/postgres/node-bun.js.map +10 -0
  22. package/dist/postgres/pglite-pool.d.ts +21 -0
  23. package/dist/postgres/pglite-pool.d.ts.map +1 -0
  24. package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
  25. package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
  26. package/dist/queue/PostgresQueueStorage.d.ts +1 -1
  27. package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
  28. package/dist/queue/SqliteQueueStorage.d.ts +1 -1
  29. package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
  30. package/dist/queue/SupabaseQueueStorage.d.ts +11 -0
  31. package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
  32. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
  33. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts +1 -1
  34. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts.map +1 -1
  35. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts +1 -1
  36. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts.map +1 -1
  37. package/dist/sqlite/browser.d.ts +37 -0
  38. package/dist/sqlite/browser.d.ts.map +1 -0
  39. package/dist/sqlite/browser.js +125 -0
  40. package/dist/sqlite/browser.js.map +10 -0
  41. package/dist/sqlite/bun.d.ts +32 -0
  42. package/dist/sqlite/bun.d.ts.map +1 -0
  43. package/dist/sqlite/bun.js +84 -0
  44. package/dist/sqlite/bun.js.map +10 -0
  45. package/dist/sqlite/canonical-api.d.ts +34 -0
  46. package/dist/sqlite/canonical-api.d.ts.map +1 -0
  47. package/dist/sqlite/node.d.ts +34 -0
  48. package/dist/sqlite/node.d.ts.map +1 -0
  49. package/dist/sqlite/node.js +65 -0
  50. package/dist/sqlite/node.js.map +10 -0
  51. package/dist/tabular/CachedTabularStorage.d.ts +1 -0
  52. package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
  53. package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
  54. package/dist/tabular/InMemoryTabularStorage.d.ts +2 -0
  55. package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
  56. package/dist/tabular/PostgresTabularStorage.d.ts +1 -1
  57. package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
  58. package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
  59. package/dist/tabular/SqliteTabularStorage.d.ts +1 -1
  60. package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
  61. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  62. package/dist/util/HybridSubscriptionManager.d.ts.map +1 -1
  63. package/dist/util/PollingSubscriptionManager.d.ts +2 -0
  64. package/dist/util/PollingSubscriptionManager.d.ts.map +1 -1
  65. package/dist/vector/PostgresVectorStorage.d.ts +1 -13
  66. package/dist/vector/PostgresVectorStorage.d.ts.map +1 -1
  67. package/dist/vector/SqliteAiVectorStorage.d.ts +1 -1
  68. package/dist/vector/SqliteAiVectorStorage.d.ts.map +1 -1
  69. package/dist/vector/SqliteVectorStorage.d.ts +1 -1
  70. package/dist/vector/SqliteVectorStorage.d.ts.map +1 -1
  71. package/package.json +85 -21
  72. package/src/kv/README.md +4 -3
  73. package/src/queue/README.md +1 -1
  74. package/src/tabular/README.md +8 -1
  75. package/src/vector/README.md +6 -6
  76. package/dist/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -142,6 +142,10 @@ The package uses conditional exports, so importing from `@workglow/storage` auto
142
142
  import { InMemoryKvStorage, SqliteTabularStorage } from "@workglow/storage";
143
143
  ```
144
144
 
145
+ ### SQLite setup (`@workglow/storage/sqlite`)
146
+
147
+ Before you open SQLite with a **file path** or construct **`new Sqlite.Database(...)`**, call **`await Sqlite.init()`** once per runtime (Node.js, Bun, or browser). Export is available from **`workglow`** and **`@workglow/storage/sqlite`**. The call is idempotent. On the browser it loads the SQLite WASM build; on Node.js it loads `better-sqlite3`; on Bun it resolves `bun:sqlite`.
148
+
145
149
  ## Storage Types
146
150
 
147
151
  ### Key-Value Storage
@@ -171,12 +175,15 @@ const browserStore = new IndexedDbKvRepository("my-app-storage");
171
175
 
172
176
  // Node.js/Bun (using SQLite)
173
177
  import { SqliteKvRepository } from "@workglow/storage";
174
- // Pass a file path or a Database instance (see @workglow/sqlite)
178
+ import { Sqlite } from "@workglow/storage/sqlite";
179
+
180
+ await Sqlite.init();
181
+ // Pass a file path or a Sqlite.Database instance (see @workglow/storage/sqlite)
175
182
  const sqliteStore = new SqliteKvRepository("./data.db", "config_table");
176
183
 
177
184
  // PostgreSQL (Node.js/Bun)
178
185
  import { PostgresKvRepository } from "@workglow/storage";
179
- import { Pool } from "pg";
186
+ import type { Pool } from "@workglow/storage/postgres";
180
187
  const pool = new Pool({ connectionString: "postgresql://..." });
181
188
  const pgStore = new PostgresKvRepository(pool, "settings");
182
189
 
@@ -390,7 +397,9 @@ await userRepo.deleteSearch({
390
397
  ```typescript
391
398
  // SQLite (Node.js/Bun)
392
399
  import { SqliteTabularStorage } from "@workglow/storage";
400
+ import { Sqlite } from "@workglow/storage/sqlite";
393
401
 
402
+ await Sqlite.init();
394
403
  const sqliteUsers = new SqliteTabularStorage<typeof UserSchema, ["id"]>(
395
404
  "./users.db",
396
405
  "users",
@@ -401,7 +410,7 @@ const sqliteUsers = new SqliteTabularStorage<typeof UserSchema, ["id"]>(
401
410
 
402
411
  // PostgreSQL (Node.js/Bun)
403
412
  import { PostgresTabularStorage } from "@workglow/storage";
404
- import { Pool } from "pg";
413
+ import type { Pool } from "@workglow/storage/postgres";
405
414
 
406
415
  const pool = new Pool({ connectionString: "postgresql://..." });
407
416
  const pgUsers = new PostgresTabularStorage<typeof UserSchema, ["id"]>(
@@ -554,11 +563,11 @@ import {
554
563
  PostgresQueueStorage,
555
564
  SupabaseTabularStorage,
556
565
  } from "@workglow/storage";
557
-
558
- import { Database } from "bun:sqlite";
566
+ import { Sqlite } from "@workglow/storage/sqlite";
559
567
  import { createClient } from "@supabase/supabase-js";
560
568
 
561
- const db = new Database("./app.db");
569
+ await Sqlite.init();
570
+ const db = new Sqlite.Database("./app.db");
562
571
  const data = new SqliteTabularStorage(db, "items", ItemSchema, ["id"]);
563
572
 
564
573
  // Or use Supabase for cloud storage
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Steven Roussey <sroussey@gmail.com>
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export * from "./common";
7
+ export * from "./tabular/IndexedDbTabularStorage";
8
+ export * from "./tabular/SharedInMemoryTabularStorage";
9
+ export * from "./tabular/SupabaseTabularStorage";
10
+ export * from "./kv/IndexedDbKvStorage";
11
+ export * from "./kv/SupabaseKvStorage";
12
+ export * from "./queue/IndexedDbQueueStorage";
13
+ export * from "./queue/SupabaseQueueStorage";
14
+ export * from "./queue-limiter/IndexedDbRateLimiterStorage";
15
+ export * from "./queue-limiter/SupabaseRateLimiterStorage";
16
+ export * from "./vector/IndexedDbVectorStorage";
17
+ export * from "./util/IndexedDbTable";
18
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,UAAU,CAAC;AAEzB,cAAc,mCAAmC,CAAC;AAClD,cAAc,wCAAwC,CAAC;AACvD,cAAc,kCAAkC,CAAC;AAEjD,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAE7C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4CAA4C,CAAC;AAE3D,cAAc,iCAAiC,CAAC;AAEhD,cAAc,uBAAuB,CAAC"}
package/dist/browser.js CHANGED
@@ -391,6 +391,7 @@ var MEMORY_TABULAR_REPOSITORY = createServiceToken2("storage.tabularRepository.i
391
391
  class InMemoryTabularStorage extends BaseTabularStorage {
392
392
  values = new Map;
393
393
  autoIncrementCounter = 0;
394
+ _lastPutWasInsert = false;
394
395
  constructor(schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
395
396
  super(schema, primaryKeyNames, indexes, clientProvidedKeys);
396
397
  }
@@ -426,6 +427,7 @@ class InMemoryTabularStorage extends BaseTabularStorage {
426
427
  }
427
428
  const { key } = this.separateKeyValueFromCombined(entityToStore);
428
429
  const id = await makeFingerprint2(key);
430
+ this._lastPutWasInsert = !this.values.has(id);
429
431
  this.values.set(id, entityToStore);
430
432
  this.events.emit("put", entityToStore);
431
433
  return entityToStore;
@@ -622,7 +624,7 @@ class InMemoryTabularStorage extends BaseTabularStorage {
622
624
  }
623
625
  subscribeToChanges(callback, options) {
624
626
  const handlePut = (entity) => {
625
- callback({ type: "UPDATE", new: entity });
627
+ callback({ type: this._lastPutWasInsert ? "INSERT" : "UPDATE", new: entity });
626
628
  };
627
629
  const handleDelete = (_key) => {
628
630
  callback({ type: "DELETE" });
@@ -651,6 +653,7 @@ class CachedTabularStorage extends BaseTabularStorage {
651
653
  cache;
652
654
  durable;
653
655
  cacheInitialized = false;
656
+ cacheInitPromise = null;
654
657
  constructor(durable, cache, schema, primaryKeyNames, indexes, clientProvidedKeys = "if-missing") {
655
658
  if (!schema || !primaryKeyNames) {
656
659
  throw new Error("Schema and primaryKeyNames must be provided when creating CachedTabularStorage");
@@ -684,16 +687,23 @@ class CachedTabularStorage extends BaseTabularStorage {
684
687
  async initializeCache() {
685
688
  if (this.cacheInitialized)
686
689
  return;
687
- try {
688
- const all = await this.durable.getAll();
689
- if (all && all.length > 0) {
690
- await this.cache.putBulk(all);
691
- }
692
- this.cacheInitialized = true;
693
- } catch (error) {
694
- getLogger().warn("Failed to initialize cache from durable repository:", { error });
695
- this.cacheInitialized = true;
690
+ if (this.cacheInitPromise) {
691
+ return this.cacheInitPromise;
696
692
  }
693
+ this.cacheInitPromise = (async () => {
694
+ try {
695
+ const all = await this.durable.getAll();
696
+ if (all && all.length > 0) {
697
+ await this.cache.putBulk(all);
698
+ }
699
+ this.cacheInitialized = true;
700
+ } catch (error) {
701
+ getLogger().warn("Failed to initialize cache from durable repository:", { error });
702
+ } finally {
703
+ this.cacheInitPromise = null;
704
+ }
705
+ })();
706
+ return this.cacheInitPromise;
697
707
  }
698
708
  async put(value) {
699
709
  await this.initializeCache();
@@ -1529,13 +1539,17 @@ class InMemoryQueueStorage {
1529
1539
  }
1530
1540
  async complete(job) {
1531
1541
  await sleep(0);
1532
- const index = this.jobQueue.findIndex((j) => j.id === job.id);
1542
+ const jobWithPrefixes = job;
1543
+ const index = this.jobQueue.findIndex((j) => j.id === job.id && this.matchesPrefixes(j));
1533
1544
  if (index !== -1) {
1534
1545
  const existing = this.jobQueue[index];
1535
1546
  const currentAttempts = existing?.run_attempts ?? 0;
1536
- job.run_attempts = currentAttempts + 1;
1537
- this.jobQueue[index] = job;
1538
- this.events.emit("change", { type: "UPDATE", old: existing, new: job });
1547
+ jobWithPrefixes.run_attempts = currentAttempts + 1;
1548
+ for (const [key, value] of Object.entries(this.prefixValues)) {
1549
+ jobWithPrefixes[key] = value;
1550
+ }
1551
+ this.jobQueue[index] = jobWithPrefixes;
1552
+ this.events.emit("change", { type: "UPDATE", old: existing, new: jobWithPrefixes });
1539
1553
  }
1540
1554
  }
1541
1555
  async abort(id) {
@@ -1890,6 +1904,7 @@ class PollingSubscriptionManager {
1890
1904
  intervals = new Map;
1891
1905
  lastKnownState = new Map;
1892
1906
  initialized = false;
1907
+ initializing = false;
1893
1908
  fetchState;
1894
1909
  compareItems;
1895
1910
  payloadFactory;
@@ -1914,7 +1929,8 @@ class PollingSubscriptionManager {
1914
1929
  this.intervals.set(interval, intervalGroup);
1915
1930
  if (!this.initialized) {
1916
1931
  this.initialized = true;
1917
- this.initAndPoll(subscribers, subscription);
1932
+ this.initializing = true;
1933
+ this.initAndPoll(subscription);
1918
1934
  } else {
1919
1935
  this.pollForNewSubscriber(subscription);
1920
1936
  }
@@ -1933,7 +1949,7 @@ class PollingSubscriptionManager {
1933
1949
  }
1934
1950
  };
1935
1951
  }
1936
- async initAndPoll(subscribers, newSubscription) {
1952
+ async initAndPoll(newSubscription) {
1937
1953
  try {
1938
1954
  this.lastKnownState = await this.fetchState();
1939
1955
  for (const [, item] of this.lastKnownState) {
@@ -1942,7 +1958,9 @@ class PollingSubscriptionManager {
1942
1958
  newSubscription.callback(payload);
1943
1959
  } catch {}
1944
1960
  }
1945
- } catch {}
1961
+ } catch {} finally {
1962
+ this.initializing = false;
1963
+ }
1946
1964
  }
1947
1965
  pollForNewSubscriber(subscription) {
1948
1966
  for (const [, item] of this.lastKnownState) {
@@ -1955,6 +1973,8 @@ class PollingSubscriptionManager {
1955
1973
  async poll(subscribers) {
1956
1974
  if (subscribers.size === 0)
1957
1975
  return;
1976
+ if (this.initializing)
1977
+ return;
1958
1978
  try {
1959
1979
  const currentState = await this.fetchState();
1960
1980
  const changes = [];
@@ -1998,6 +2018,7 @@ class PollingSubscriptionManager {
1998
2018
  this.intervals.clear();
1999
2019
  this.lastKnownState.clear();
2000
2020
  this.initialized = false;
2021
+ this.initializing = false;
2001
2022
  }
2002
2023
  }
2003
2024
  // src/vector/InMemoryVectorStorage.ts
@@ -2102,7 +2123,7 @@ class InMemoryVectorStorage extends InMemoryTabularStorage {
2102
2123
  continue;
2103
2124
  }
2104
2125
  const vectorScore = cosineSimilarity(query, vector);
2105
- const metadataText = Object.values(metadata).join(" ").toLowerCase();
2126
+ const metadataText = Object.values(metadata ?? {}).join(" ").toLowerCase();
2106
2127
  const textScore = textRelevance(metadataText, textQuery);
2107
2128
  const combinedScore = vectorWeight * vectorScore + (1 - vectorWeight) * textScore;
2108
2129
  if (combinedScore < scoreThreshold) {
@@ -4484,12 +4505,24 @@ class SupabaseQueueStorage {
4484
4505
  const conditions = this.prefixes.map((p) => {
4485
4506
  const value = this.prefixValues[p.name];
4486
4507
  if (p.type === "uuid") {
4487
- return `${p.name} = '${this.escapeSqlString(String(value))}'`;
4508
+ const validated = this.validateSqlValue(String(value), `prefix "${p.name}"`);
4509
+ return `${p.name} = '${this.escapeSqlString(validated)}'`;
4510
+ }
4511
+ const numValue = Number(value ?? 0);
4512
+ if (!Number.isFinite(numValue)) {
4513
+ throw new Error(`Invalid numeric prefix value for "${p.name}": ${value}`);
4488
4514
  }
4489
- return `${p.name} = ${Number(value ?? 0)}`;
4515
+ return `${p.name} = ${numValue}`;
4490
4516
  }).join(" AND ");
4491
4517
  return " AND " + conditions;
4492
4518
  }
4519
+ static SAFE_SQL_VALUE_RE = /^[a-zA-Z0-9_\-.:]+$/;
4520
+ validateSqlValue(value, context) {
4521
+ if (!SupabaseQueueStorage.SAFE_SQL_VALUE_RE.test(value)) {
4522
+ throw new Error(`Unsafe value for ${context}: "${value}". Values must match /^[a-zA-Z0-9_\\-.:]+$/.`);
4523
+ }
4524
+ return value;
4525
+ }
4493
4526
  escapeSqlString(value) {
4494
4527
  return value.replace(/'/g, "''");
4495
4528
  }
@@ -4596,8 +4629,10 @@ class SupabaseQueueStorage {
4596
4629
  }
4597
4630
  async next(workerId) {
4598
4631
  const prefixConditions = this.buildPrefixWhereSql();
4599
- const escapedQueueName = this.escapeSqlString(this.queueName);
4600
- const escapedWorkerId = this.escapeSqlString(workerId);
4632
+ const validatedQueueName = this.validateSqlValue(this.queueName, "queueName");
4633
+ const validatedWorkerId = this.validateSqlValue(workerId, "workerId");
4634
+ const escapedQueueName = this.escapeSqlString(validatedQueueName);
4635
+ const escapedWorkerId = this.escapeSqlString(validatedWorkerId);
4601
4636
  const sql = `
4602
4637
  UPDATE ${this.tableName}
4603
4638
  SET status = '${JobStatus.PROCESSING}', last_ran_at = NOW() AT TIME ZONE 'UTC', worker_id = '${escapedWorkerId}'
@@ -5469,4 +5504,4 @@ export {
5469
5504
  BaseTabularStorage
5470
5505
  };
5471
5506
 
5472
- //# debugId=D02D6875010DA7A164756E2164756E21
5507
+ //# debugId=09B8497E3C06E04164756E2164756E21