@workglow/storage 0.2.26 → 0.2.27

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 (38) hide show
  1. package/dist/browser.js +218 -12
  2. package/dist/browser.js.map +13 -13
  3. package/dist/bun.js +703 -24
  4. package/dist/bun.js.map +18 -17
  5. package/dist/common-server.d.ts +1 -0
  6. package/dist/common-server.d.ts.map +1 -1
  7. package/dist/node.js +703 -24
  8. package/dist/node.js.map +18 -17
  9. package/dist/queue/IQueueStorage.d.ts +25 -2
  10. package/dist/queue/IQueueStorage.d.ts.map +1 -1
  11. package/dist/queue/InMemoryQueueStorage.d.ts +1 -0
  12. package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
  13. package/dist/queue/IndexedDbQueueStorage.d.ts +1 -0
  14. package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
  15. package/dist/queue/PostgresQueueStorage.d.ts +24 -3
  16. package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
  17. package/dist/queue/SqliteQueueStorage.d.ts +8 -0
  18. package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
  19. package/dist/queue/SupabaseQueueStorage.d.ts +1 -0
  20. package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
  21. package/dist/queue/TelemetryQueueStorage.d.ts +2 -1
  22. package/dist/queue/TelemetryQueueStorage.d.ts.map +1 -1
  23. package/dist/queue-limiter/IRateLimiterStorage.d.ts +46 -0
  24. package/dist/queue-limiter/IRateLimiterStorage.d.ts.map +1 -1
  25. package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts +12 -1
  26. package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts.map +1 -1
  27. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts +28 -1
  28. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
  29. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts +4 -1
  30. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts.map +1 -1
  31. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts +10 -1
  32. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts.map +1 -1
  33. package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts +6 -1
  34. package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts.map +1 -1
  35. package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
  36. package/dist/tabular/SharedInMemoryTabularStorage.d.ts +2 -1
  37. package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
  38. package/package.json +3 -3
package/dist/browser.js CHANGED
@@ -1574,6 +1574,7 @@ var IN_MEMORY_QUEUE_STORAGE = createServiceToken9("jobqueue.storage.inMemory");
1574
1574
 
1575
1575
  class InMemoryQueueStorage {
1576
1576
  queueName;
1577
+ scope = "process";
1577
1578
  prefixValues;
1578
1579
  events = new EventEmitter3;
1579
1580
  constructor(queueName, options) {
@@ -1783,6 +1784,9 @@ class TelemetryQueueStorage {
1783
1784
  this.storageName = storageName;
1784
1785
  this.inner = inner;
1785
1786
  }
1787
+ get scope() {
1788
+ return this.inner.scope;
1789
+ }
1786
1790
  add(job) {
1787
1791
  return traced("workglow.storage.queue.add", this.storageName, () => this.inner.add(job));
1788
1792
  }
@@ -1833,13 +1837,15 @@ class TelemetryQueueStorage {
1833
1837
  }
1834
1838
  }
1835
1839
  // src/queue-limiter/InMemoryRateLimiterStorage.ts
1836
- import { createServiceToken as createServiceToken10, sleep as sleep2 } from "@workglow/util";
1840
+ import { createServiceToken as createServiceToken10, sleep as sleep2, uuid4 as uuid43 } from "@workglow/util";
1837
1841
  var IN_MEMORY_RATE_LIMITER_STORAGE = createServiceToken10("ratelimiter.storage.inMemory");
1838
1842
 
1839
1843
  class InMemoryRateLimiterStorage {
1844
+ scope = "process";
1840
1845
  prefixValues;
1841
1846
  executions = new Map;
1842
1847
  nextAvailableTimes = new Map;
1848
+ reserveChains = new Map;
1843
1849
  constructor(options) {
1844
1850
  this.prefixValues = options?.prefixValues ?? {};
1845
1851
  }
@@ -1848,11 +1854,67 @@ class InMemoryRateLimiterStorage {
1848
1854
  return prefixPart ? `${prefixPart}|${queueName}` : queueName;
1849
1855
  }
1850
1856
  async setupDatabase() {}
1857
+ async withKeyLock(key, fn) {
1858
+ const previous = this.reserveChains.get(key) ?? Promise.resolve();
1859
+ let release;
1860
+ const next = new Promise((resolve) => {
1861
+ release = resolve;
1862
+ });
1863
+ this.reserveChains.set(key, next);
1864
+ try {
1865
+ await previous;
1866
+ return await fn();
1867
+ } finally {
1868
+ release(undefined);
1869
+ if (this.reserveChains.get(key) === next) {
1870
+ this.reserveChains.delete(key);
1871
+ }
1872
+ }
1873
+ }
1874
+ async tryReserveExecution(queueName, maxExecutions, windowMs) {
1875
+ const key = this.makeKey(queueName);
1876
+ return this.withKeyLock(key, () => {
1877
+ const now = Date.now();
1878
+ const windowStart = new Date(now - windowMs);
1879
+ const executions = this.executions.get(key) ?? [];
1880
+ const live = executions.filter((e) => e.executedAt > windowStart);
1881
+ if (live.length >= maxExecutions) {
1882
+ if (live.length !== executions.length) {
1883
+ this.executions.set(key, live);
1884
+ }
1885
+ return null;
1886
+ }
1887
+ const next = this.nextAvailableTimes.get(key);
1888
+ if (next && next.getTime() > now) {
1889
+ return null;
1890
+ }
1891
+ const id = uuid43();
1892
+ live.push({ id, queueName, executedAt: new Date(now) });
1893
+ this.executions.set(key, live);
1894
+ return id;
1895
+ });
1896
+ }
1897
+ async releaseExecution(queueName, token) {
1898
+ if (token === null || token === undefined)
1899
+ return;
1900
+ const key = this.makeKey(queueName);
1901
+ await this.withKeyLock(key, () => {
1902
+ const executions = this.executions.get(key);
1903
+ if (!executions || executions.length === 0)
1904
+ return;
1905
+ const idx = executions.findIndex((e) => e.id === token);
1906
+ if (idx === -1)
1907
+ return;
1908
+ executions.splice(idx, 1);
1909
+ this.executions.set(key, executions);
1910
+ });
1911
+ }
1851
1912
  async recordExecution(queueName) {
1852
1913
  await sleep2(0);
1853
1914
  const key = this.makeKey(queueName);
1854
1915
  const executions = this.executions.get(key) ?? [];
1855
1916
  executions.push({
1917
+ id: uuid43(),
1856
1918
  queueName,
1857
1919
  executedAt: new Date
1858
1920
  });
@@ -2434,7 +2496,7 @@ class LazyEncryptedCredentialStore {
2434
2496
  }
2435
2497
  }
2436
2498
  // src/tabular/IndexedDbTabularStorage.ts
2437
- import { createServiceToken as createServiceToken12, deepEqual as deepEqual2, makeFingerprint as makeFingerprint5, uuid4 as uuid43 } from "@workglow/util";
2499
+ import { createServiceToken as createServiceToken12, deepEqual as deepEqual2, makeFingerprint as makeFingerprint5, uuid4 as uuid44 } from "@workglow/util";
2438
2500
 
2439
2501
  // src/util/IndexedDbTable.ts
2440
2502
  import { deepEqual } from "@workglow/util";
@@ -2825,7 +2887,7 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
2825
2887
  }
2826
2888
  generateKeyValue(columnName, strategy) {
2827
2889
  if (strategy === "uuid") {
2828
- return uuid43();
2890
+ return uuid44();
2829
2891
  }
2830
2892
  throw new Error(`IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`);
2831
2893
  }
@@ -3398,13 +3460,15 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
3398
3460
  }
3399
3461
  let matches = true;
3400
3462
  for (const [col, crit] of Object.entries(criteria)) {
3401
- if (!isSearchCondition(crit))
3402
- continue;
3403
3463
  const pos = keyPathPositions.get(col);
3404
3464
  if (pos === undefined)
3405
3465
  continue;
3466
+ if (pos < prefix.length)
3467
+ continue;
3406
3468
  const valFromKey = Array.isArray(key) ? key[pos] : key;
3407
- if (!compareWithOperator(valFromKey, crit.operator, crit.value)) {
3469
+ const op = isSearchCondition(crit) ? crit.operator : "=";
3470
+ const val = isSearchCondition(crit) ? crit.value : crit;
3471
+ if (!compareWithOperator(valFromKey, op, val)) {
3408
3472
  matches = false;
3409
3473
  break;
3410
3474
  }
@@ -3651,6 +3715,9 @@ class SharedInMemoryTabularStorage extends BaseTabularStorage {
3651
3715
  async query(criteria, options) {
3652
3716
  return await this.inMemoryRepo.query(criteria, options);
3653
3717
  }
3718
+ async queryIndex(criteria, options) {
3719
+ return await this.inMemoryRepo.queryIndex(criteria, options);
3720
+ }
3654
3721
  async deleteSearch(criteria) {
3655
3722
  await this.inMemoryRepo.deleteSearch(criteria);
3656
3723
  this.broadcast({
@@ -4453,11 +4520,12 @@ class SupabaseKvStorage extends KvViaTabularStorage {
4453
4520
  }
4454
4521
  }
4455
4522
  // src/queue/IndexedDbQueueStorage.ts
4456
- import { createServiceToken as createServiceToken17, deepEqual as deepEqual3, makeFingerprint as makeFingerprint6, uuid4 as uuid44 } from "@workglow/util";
4523
+ import { createServiceToken as createServiceToken17, deepEqual as deepEqual3, makeFingerprint as makeFingerprint6, uuid4 as uuid45 } from "@workglow/util";
4457
4524
  var INDEXED_DB_QUEUE_STORAGE = createServiceToken17("jobqueue.storage.indexedDb");
4458
4525
 
4459
4526
  class IndexedDbQueueStorage {
4460
4527
  queueName;
4528
+ scope = "process";
4461
4529
  db;
4462
4530
  tableName;
4463
4531
  migrationOptions;
@@ -4534,8 +4602,8 @@ class IndexedDbQueueStorage {
4534
4602
  const db = await this.getDb();
4535
4603
  const now = new Date().toISOString();
4536
4604
  const jobWithPrefixes = job;
4537
- jobWithPrefixes.id = jobWithPrefixes.id ?? uuid44();
4538
- jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ?? uuid44();
4605
+ jobWithPrefixes.id = jobWithPrefixes.id ?? uuid45();
4606
+ jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ?? uuid45();
4539
4607
  jobWithPrefixes.queue = this.queueName;
4540
4608
  jobWithPrefixes.fingerprint = await makeFingerprint6(jobWithPrefixes.input);
4541
4609
  jobWithPrefixes.status = JobStatus.PENDING;
@@ -5033,11 +5101,12 @@ class IndexedDbQueueStorage {
5033
5101
  }
5034
5102
  }
5035
5103
  // src/queue/SupabaseQueueStorage.ts
5036
- import { createServiceToken as createServiceToken18, deepEqual as deepEqual4, makeFingerprint as makeFingerprint7, uuid4 as uuid45 } from "@workglow/util";
5104
+ import { createServiceToken as createServiceToken18, deepEqual as deepEqual4, makeFingerprint as makeFingerprint7, uuid4 as uuid46 } from "@workglow/util";
5037
5105
  var SUPABASE_QUEUE_STORAGE = createServiceToken18("jobqueue.storage.supabase");
5038
5106
 
5039
5107
  class SupabaseQueueStorage {
5040
5108
  queueName;
5109
+ scope = "cluster";
5041
5110
  client;
5042
5111
  prefixes;
5043
5112
  prefixValues;
@@ -5162,7 +5231,7 @@ class SupabaseQueueStorage {
5162
5231
  async add(job) {
5163
5232
  const now = new Date().toISOString();
5164
5233
  job.queue = this.queueName;
5165
- job.job_run_id = job.job_run_id ?? uuid45();
5234
+ job.job_run_id = job.job_run_id ?? uuid46();
5166
5235
  job.fingerprint = await makeFingerprint7(job.input);
5167
5236
  job.status = JobStatus.PENDING;
5168
5237
  job.progress = 0;
@@ -5564,6 +5633,7 @@ import { createServiceToken as createServiceToken19 } from "@workglow/util";
5564
5633
  var INDEXED_DB_RATE_LIMITER_STORAGE = createServiceToken19("ratelimiter.storage.indexedDb");
5565
5634
 
5566
5635
  class IndexedDbRateLimiterStorage {
5636
+ scope = "process";
5567
5637
  executionDb;
5568
5638
  nextAvailableDb;
5569
5639
  executionTableName;
@@ -5632,6 +5702,72 @@ class IndexedDbRateLimiterStorage {
5632
5702
  ];
5633
5703
  this.nextAvailableDb = await ensureIndexedDbTable(this.nextAvailableTableName, buildKeyPath(["queue_name"]).join("_"), nextAvailableIndexes, this.migrationOptions);
5634
5704
  }
5705
+ async tryReserveExecution(queueName, maxExecutions, windowMs) {
5706
+ const nextIso = await this.getNextAvailableTime(queueName);
5707
+ if (nextIso && new Date(nextIso).getTime() > Date.now()) {
5708
+ return null;
5709
+ }
5710
+ const execDb = await this.getExecutionDb();
5711
+ const prefixKeyValues = this.getPrefixKeyValues();
5712
+ const windowStartIso = new Date(Date.now() - windowMs).toISOString();
5713
+ const execTx = execDb.transaction(this.executionTableName, "readwrite");
5714
+ const execStore = execTx.objectStore(this.executionTableName);
5715
+ const insertedId = crypto.randomUUID();
5716
+ return new Promise((resolve, reject) => {
5717
+ let liveCount = 0;
5718
+ let didInsert = false;
5719
+ const liveRange = IDBKeyRange.bound([...prefixKeyValues, queueName, windowStartIso], [...prefixKeyValues, queueName, "￿"], true, false);
5720
+ const cursorReq = execStore.index("queue_executed_at").openCursor(liveRange);
5721
+ cursorReq.onsuccess = (event) => {
5722
+ const cursor = event.target.result;
5723
+ if (cursor) {
5724
+ const record2 = cursor.value;
5725
+ if (this.matchesPrefixes(record2)) {
5726
+ liveCount++;
5727
+ }
5728
+ cursor.continue();
5729
+ return;
5730
+ }
5731
+ if (liveCount >= maxExecutions) {
5732
+ execTx.abort();
5733
+ return;
5734
+ }
5735
+ const record = {
5736
+ id: insertedId,
5737
+ queue_name: queueName,
5738
+ executed_at: new Date().toISOString()
5739
+ };
5740
+ for (const [k, v] of Object.entries(this.prefixValues)) {
5741
+ record[k] = v;
5742
+ }
5743
+ const addReq = execStore.add(record);
5744
+ didInsert = true;
5745
+ addReq.onerror = () => {
5746
+ try {
5747
+ execTx.abort();
5748
+ } catch {}
5749
+ reject(addReq.error);
5750
+ };
5751
+ };
5752
+ cursorReq.onerror = () => reject(cursorReq.error);
5753
+ execTx.oncomplete = () => resolve(didInsert ? insertedId : null);
5754
+ execTx.onerror = () => reject(execTx.error);
5755
+ execTx.onabort = () => resolve(null);
5756
+ });
5757
+ }
5758
+ async releaseExecution(queueName, token) {
5759
+ if (token === null || token === undefined)
5760
+ return;
5761
+ const db = await this.getExecutionDb();
5762
+ const tx = db.transaction(this.executionTableName, "readwrite");
5763
+ const store = tx.objectStore(this.executionTableName);
5764
+ return new Promise((resolve, reject) => {
5765
+ const req = store.delete(token);
5766
+ req.onerror = () => reject(req.error);
5767
+ tx.oncomplete = () => resolve();
5768
+ tx.onerror = () => reject(tx.error);
5769
+ });
5770
+ }
5635
5771
  async recordExecution(queueName) {
5636
5772
  const db = await this.getExecutionDb();
5637
5773
  const tx = db.transaction(this.executionTableName, "readwrite");
@@ -5785,6 +5921,7 @@ import { createServiceToken as createServiceToken20 } from "@workglow/util";
5785
5921
  var SUPABASE_RATE_LIMITER_STORAGE = createServiceToken20("ratelimiter.storage.supabase");
5786
5922
 
5787
5923
  class SupabaseRateLimiterStorage {
5924
+ scope = "cluster";
5788
5925
  client;
5789
5926
  prefixes;
5790
5927
  prefixValues;
@@ -5867,6 +6004,75 @@ class SupabaseRateLimiterStorage {
5867
6004
  if (nextTableError && nextTableError.code !== "42P07") {
5868
6005
  throw nextTableError;
5869
6006
  }
6007
+ const fnName = this.atomicReserveFunctionName();
6008
+ const prefixSig = this.prefixes.map((p) => `${p.name} ${this.getPrefixColumnType(p.type)}`).join(", ");
6009
+ const prefixSigPrefix = prefixSig ? prefixSig + ", " : "";
6010
+ const prefixWhere = this.prefixes.length > 0 ? " AND " + this.prefixes.map((p) => `${p.name} = _${p.name}`).join(" AND ") : "";
6011
+ const prefixInsertCols = this.prefixes.length > 0 ? this.prefixes.map((p) => p.name).join(", ") + ", " : "";
6012
+ const prefixInsertVals = this.prefixes.length > 0 ? this.prefixes.map((p) => `_${p.name}`).join(", ") + ", " : "";
6013
+ const lockKeyParts = [`'${this.executionTableName}'`, ...this.prefixes.map((p) => `_${p.name}::text`), `_queue_name::text`];
6014
+ const lockKeyExpr = `hashtextextended(${lockKeyParts.join(" || '|' || ")}, 0)`;
6015
+ const createFnSql = `
6016
+ CREATE OR REPLACE FUNCTION ${fnName}(
6017
+ ${prefixSigPrefix}_queue_name TEXT, _window_start TIMESTAMPTZ, _max_exec INT
6018
+ ) RETURNS BIGINT AS $fn$
6019
+ DECLARE
6020
+ _count INT;
6021
+ _next TIMESTAMPTZ;
6022
+ _new_id BIGINT;
6023
+ BEGIN
6024
+ PERFORM pg_advisory_xact_lock(${lockKeyExpr});
6025
+ SELECT COUNT(*) INTO _count FROM ${this.executionTableName}
6026
+ WHERE queue_name = _queue_name AND executed_at > _window_start${prefixWhere};
6027
+ IF _count >= _max_exec THEN RETURN NULL; END IF;
6028
+ SELECT next_available_at INTO _next FROM ${this.nextAvailableTableName}
6029
+ WHERE queue_name = _queue_name${prefixWhere};
6030
+ IF _next IS NOT NULL AND _next > NOW() THEN RETURN NULL; END IF;
6031
+ INSERT INTO ${this.executionTableName} (${prefixInsertCols}queue_name)
6032
+ VALUES (${prefixInsertVals}_queue_name)
6033
+ RETURNING id INTO _new_id;
6034
+ RETURN _new_id;
6035
+ END;
6036
+ $fn$ LANGUAGE plpgsql;
6037
+ `;
6038
+ const { error: fnError } = await this.client.rpc("exec_sql", { query: createFnSql });
6039
+ if (fnError) {
6040
+ throw fnError;
6041
+ }
6042
+ }
6043
+ atomicReserveFunctionName() {
6044
+ return `${this.executionTableName}_try_reserve`.slice(0, 63);
6045
+ }
6046
+ async tryReserveExecution(queueName, maxExecutions, windowMs) {
6047
+ const args = {
6048
+ _queue_name: queueName,
6049
+ _window_start: new Date(Date.now() - windowMs).toISOString(),
6050
+ _max_exec: maxExecutions
6051
+ };
6052
+ for (const p of this.prefixes) {
6053
+ args[`_${p.name}`] = this.prefixValues[p.name];
6054
+ }
6055
+ const { data, error } = await this.client.rpc(this.atomicReserveFunctionName(), args);
6056
+ if (error)
6057
+ throw error;
6058
+ if (data === null || data === undefined)
6059
+ return null;
6060
+ if (Array.isArray(data)) {
6061
+ if (data.length === 0)
6062
+ return null;
6063
+ const first = Object.values(data[0])[0];
6064
+ return first ?? null;
6065
+ }
6066
+ return data;
6067
+ }
6068
+ async releaseExecution(queueName, token) {
6069
+ if (token === null || token === undefined)
6070
+ return;
6071
+ let del = this.client.from(this.executionTableName).delete().eq("id", token).eq("queue_name", queueName);
6072
+ del = this.applyPrefixFilters(del);
6073
+ const { error: delError } = await del;
6074
+ if (delError)
6075
+ throw delError;
5870
6076
  }
5871
6077
  async recordExecution(queueName) {
5872
6078
  const prefixInsertValues = this.getPrefixInsertValues();
@@ -6103,4 +6309,4 @@ export {
6103
6309
  BaseTabularStorage
6104
6310
  };
6105
6311
 
6106
- //# debugId=2583E6527A9527C764756E2164756E21
6312
+ //# debugId=E2D716C3197417F164756E2164756E21