@workglow/storage 0.2.25 → 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 (65) hide show
  1. package/dist/browser.js +512 -9
  2. package/dist/browser.js.map +23 -21
  3. package/dist/bun.js +1087 -21
  4. package/dist/bun.js.map +30 -27
  5. package/dist/common-server.d.ts +1 -0
  6. package/dist/common-server.d.ts.map +1 -1
  7. package/dist/common.d.ts +3 -0
  8. package/dist/common.d.ts.map +1 -1
  9. package/dist/node.js +1087 -21
  10. package/dist/node.js.map +30 -27
  11. package/dist/queue/IQueueStorage.d.ts +25 -2
  12. package/dist/queue/IQueueStorage.d.ts.map +1 -1
  13. package/dist/queue/InMemoryQueueStorage.d.ts +1 -0
  14. package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
  15. package/dist/queue/IndexedDbQueueStorage.d.ts +1 -0
  16. package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
  17. package/dist/queue/PostgresQueueStorage.d.ts +24 -3
  18. package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
  19. package/dist/queue/SqliteQueueStorage.d.ts +8 -0
  20. package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
  21. package/dist/queue/SupabaseQueueStorage.d.ts +1 -0
  22. package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
  23. package/dist/queue/TelemetryQueueStorage.d.ts +2 -1
  24. package/dist/queue/TelemetryQueueStorage.d.ts.map +1 -1
  25. package/dist/queue-limiter/IRateLimiterStorage.d.ts +46 -0
  26. package/dist/queue-limiter/IRateLimiterStorage.d.ts.map +1 -1
  27. package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts +12 -1
  28. package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts.map +1 -1
  29. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts +28 -1
  30. package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
  31. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts +4 -1
  32. package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts.map +1 -1
  33. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts +10 -1
  34. package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts.map +1 -1
  35. package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts +6 -1
  36. package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts.map +1 -1
  37. package/dist/tabular/BaseTabularStorage.d.ts +15 -1
  38. package/dist/tabular/BaseTabularStorage.d.ts.map +1 -1
  39. package/dist/tabular/CachedTabularStorage.d.ts +2 -1
  40. package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
  41. package/dist/tabular/CoveringIndexMissingError.d.ts +14 -0
  42. package/dist/tabular/CoveringIndexMissingError.d.ts.map +1 -0
  43. package/dist/tabular/FsFolderTabularStorage.d.ts +6 -1
  44. package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
  45. package/dist/tabular/ITabularStorage.d.ts +19 -1
  46. package/dist/tabular/ITabularStorage.d.ts.map +1 -1
  47. package/dist/tabular/InMemoryTabularStorage.d.ts +7 -1
  48. package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
  49. package/dist/tabular/IndexedDbTabularStorage.d.ts +10 -1
  50. package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
  51. package/dist/tabular/PostgresTabularStorage.d.ts +11 -1
  52. package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
  53. package/dist/tabular/SharedInMemoryTabularStorage.d.ts +2 -1
  54. package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
  55. package/dist/tabular/SqliteTabularStorage.d.ts +11 -1
  56. package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
  57. package/dist/tabular/SupabaseTabularStorage.d.ts +2 -1
  58. package/dist/tabular/SupabaseTabularStorage.d.ts.map +1 -1
  59. package/dist/tabular/TelemetryTabularStorage.d.ts +2 -1
  60. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  61. package/dist/tabular/coveringIndexPicker.d.ts +37 -0
  62. package/dist/tabular/coveringIndexPicker.d.ts.map +1 -0
  63. package/dist/vector/IVectorStorage.d.ts +3 -1
  64. package/dist/vector/IVectorStorage.d.ts.map +1 -1
  65. package/package.json +3 -3
package/dist/browser.js CHANGED
@@ -45,6 +45,21 @@ class StorageUnsupportedError extends StorageError {
45
45
  }
46
46
  }
47
47
 
48
+ // src/tabular/CoveringIndexMissingError.ts
49
+ class CoveringIndexMissingError extends StorageError {
50
+ static type = "CoveringIndexMissingError";
51
+ table;
52
+ requiredColumns;
53
+ registeredIndexes;
54
+ constructor(table, requiredColumns, registeredIndexes) {
55
+ const indexList = registeredIndexes.map((cols) => `[${cols.join(", ")}]`).join(", ");
56
+ super(`No covering index for table "${table}". ` + `Required columns: [${requiredColumns.join(", ")}]. ` + `Registered indexes: ${indexList || "(none)"}.`);
57
+ this.table = table;
58
+ this.requiredColumns = requiredColumns;
59
+ this.registeredIndexes = registeredIndexes;
60
+ }
61
+ }
62
+
48
63
  // src/tabular/BaseTabularStorage.ts
49
64
  var TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository");
50
65
 
@@ -168,6 +183,15 @@ class BaseTabularStorage {
168
183
  const entities = await this.query(criteria);
169
184
  return entities?.length ?? 0;
170
185
  }
186
+ queryIndex(criteria, options) {
187
+ this.validateSelect(options);
188
+ const requiredColumns = [
189
+ ...Object.keys(criteria),
190
+ ...(options.orderBy ?? []).map((o) => String(o.column)),
191
+ ...options.select.map(String)
192
+ ];
193
+ throw new CoveringIndexMissingError(this.constructor.name, requiredColumns, []);
194
+ }
171
195
  async* records(pageSize = 100) {
172
196
  if (pageSize <= 0) {
173
197
  throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);
@@ -263,6 +287,18 @@ class BaseTabularStorage {
263
287
  }
264
288
  }
265
289
  }
290
+ validateSelect(options) {
291
+ if (!options.select || options.select.length === 0) {
292
+ throw new StorageValidationError("queryIndex requires a non-empty select array");
293
+ }
294
+ const schemaProps = Object.keys(this.schema.properties);
295
+ for (const col of options.select) {
296
+ const colStr = String(col);
297
+ if (!schemaProps.includes(colStr)) {
298
+ throw new StorageValidationError(`queryIndex select column "${colStr}" is not in schema`);
299
+ }
300
+ }
301
+ }
266
302
  primaryKeyColumns() {
267
303
  const columns = [];
268
304
  for (const key of Object.keys(this.primaryKeySchema.properties)) {
@@ -383,6 +419,61 @@ import { createServiceToken as createServiceToken3, getLogger } from "@workglow/
383
419
 
384
420
  // src/tabular/InMemoryTabularStorage.ts
385
421
  import { createServiceToken as createServiceToken2, makeFingerprint as makeFingerprint2, uuid4 } from "@workglow/util";
422
+
423
+ // src/tabular/coveringIndexPicker.ts
424
+ function pickCoveringIndex(input) {
425
+ const { table, indexes, criteriaColumns, orderByColumns, selectColumns, primaryKeyColumns } = input;
426
+ const required = uniqueColumns([
427
+ ...criteriaColumns,
428
+ ...orderByColumns.map((o) => o.column),
429
+ ...selectColumns
430
+ ]);
431
+ const pkSet = new Set(primaryKeyColumns);
432
+ for (const idx of indexes) {
433
+ const fit = tryFitIndex(idx, criteriaColumns, orderByColumns, selectColumns, pkSet);
434
+ if (fit !== undefined) {
435
+ return { name: idx.name, keyPath: idx.keyPath, reverseDirection: fit.reverseDirection };
436
+ }
437
+ }
438
+ throw new CoveringIndexMissingError(table, required, indexes.map((i) => i.keyPath));
439
+ }
440
+ function tryFitIndex(index, criteria, orderBy, select, pkSet) {
441
+ const keyPath = index.keyPath;
442
+ const criteriaSet = new Set(criteria);
443
+ if (criteria.length > keyPath.length)
444
+ return;
445
+ for (let i = 0;i < criteria.length; i++) {
446
+ if (!criteriaSet.has(keyPath[i]))
447
+ return;
448
+ }
449
+ let reverseDirection = false;
450
+ if (orderBy.length > 0) {
451
+ const start = criteria.length;
452
+ if (start + orderBy.length > keyPath.length)
453
+ return;
454
+ for (let i = 0;i < orderBy.length; i++) {
455
+ if (keyPath[start + i] !== orderBy[i].column)
456
+ return;
457
+ }
458
+ const allDesc = orderBy.every((o) => o.direction === "DESC");
459
+ const allAsc = orderBy.every((o) => o.direction === "ASC");
460
+ if (allDesc)
461
+ reverseDirection = true;
462
+ else if (!allAsc)
463
+ return;
464
+ }
465
+ const keyPathSet = new Set(keyPath);
466
+ for (const col of select) {
467
+ if (!keyPathSet.has(col) && !pkSet.has(col))
468
+ return;
469
+ }
470
+ return { reverseDirection };
471
+ }
472
+ function uniqueColumns(cols) {
473
+ return Array.from(new Set(cols));
474
+ }
475
+
476
+ // src/tabular/InMemoryTabularStorage.ts
386
477
  var MEMORY_TABULAR_REPOSITORY = createServiceToken2("storage.tabularRepository.inMemory");
387
478
 
388
479
  class InMemoryTabularStorage extends BaseTabularStorage {
@@ -625,6 +716,38 @@ class InMemoryTabularStorage extends BaseTabularStorage {
625
716
  this.events.emit("query", criteria, result);
626
717
  return result;
627
718
  }
719
+ async queryIndex(criteria, options) {
720
+ this.validateSelect(options);
721
+ this.validateQueryParams(criteria, options);
722
+ const registered = this.indexes.map((cols, i) => ({
723
+ name: `idx_${i}`,
724
+ keyPath: cols
725
+ }));
726
+ pickCoveringIndex({
727
+ table: "InMemoryTabularStorage",
728
+ indexes: registered,
729
+ criteriaColumns: Object.keys(criteria),
730
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
731
+ column: String(o.column),
732
+ direction: o.direction
733
+ })),
734
+ selectColumns: options.select.map(String),
735
+ primaryKeyColumns: this.primaryKeyNames.map(String)
736
+ });
737
+ const full = await this.query(criteria, {
738
+ orderBy: options.orderBy,
739
+ limit: options.limit,
740
+ offset: options.offset
741
+ });
742
+ if (!full)
743
+ return [];
744
+ return full.map((row) => {
745
+ const out = {};
746
+ for (const k of options.select)
747
+ out[k] = row[k];
748
+ return out;
749
+ });
750
+ }
628
751
  subscribeToChanges(callback, options) {
629
752
  const handlePut = (entity) => {
630
753
  callback({ type: this._lastPutWasInsert ? "INSERT" : "UPDATE", new: entity });
@@ -767,6 +890,10 @@ class CachedTabularStorage extends BaseTabularStorage {
767
890
  await this.initializeCache();
768
891
  return await this.cache.query(criteria, options);
769
892
  }
893
+ async queryIndex(criteria, options) {
894
+ await this.initializeCache();
895
+ return await this.cache.queryIndex(criteria, options);
896
+ }
770
897
  async deleteSearch(criteria) {
771
898
  await this.initializeCache();
772
899
  await this.durable.deleteSearch(criteria);
@@ -1207,6 +1334,9 @@ class TelemetryTabularStorage {
1207
1334
  query(criteria, options) {
1208
1335
  return traced("workglow.storage.tabular.query", this.storageName, () => this.inner.query(criteria, options));
1209
1336
  }
1337
+ queryIndex(criteria, options) {
1338
+ return traced("workglow.storage.tabular.queryIndex", this.storageName, () => this.inner.queryIndex(criteria, options));
1339
+ }
1210
1340
  records(pageSize) {
1211
1341
  return this.inner.records(pageSize);
1212
1342
  }
@@ -1444,6 +1574,7 @@ var IN_MEMORY_QUEUE_STORAGE = createServiceToken9("jobqueue.storage.inMemory");
1444
1574
 
1445
1575
  class InMemoryQueueStorage {
1446
1576
  queueName;
1577
+ scope = "process";
1447
1578
  prefixValues;
1448
1579
  events = new EventEmitter3;
1449
1580
  constructor(queueName, options) {
@@ -1653,6 +1784,9 @@ class TelemetryQueueStorage {
1653
1784
  this.storageName = storageName;
1654
1785
  this.inner = inner;
1655
1786
  }
1787
+ get scope() {
1788
+ return this.inner.scope;
1789
+ }
1656
1790
  add(job) {
1657
1791
  return traced("workglow.storage.queue.add", this.storageName, () => this.inner.add(job));
1658
1792
  }
@@ -1703,13 +1837,15 @@ class TelemetryQueueStorage {
1703
1837
  }
1704
1838
  }
1705
1839
  // src/queue-limiter/InMemoryRateLimiterStorage.ts
1706
- import { createServiceToken as createServiceToken10, sleep as sleep2 } from "@workglow/util";
1840
+ import { createServiceToken as createServiceToken10, sleep as sleep2, uuid4 as uuid43 } from "@workglow/util";
1707
1841
  var IN_MEMORY_RATE_LIMITER_STORAGE = createServiceToken10("ratelimiter.storage.inMemory");
1708
1842
 
1709
1843
  class InMemoryRateLimiterStorage {
1844
+ scope = "process";
1710
1845
  prefixValues;
1711
1846
  executions = new Map;
1712
1847
  nextAvailableTimes = new Map;
1848
+ reserveChains = new Map;
1713
1849
  constructor(options) {
1714
1850
  this.prefixValues = options?.prefixValues ?? {};
1715
1851
  }
@@ -1718,11 +1854,67 @@ class InMemoryRateLimiterStorage {
1718
1854
  return prefixPart ? `${prefixPart}|${queueName}` : queueName;
1719
1855
  }
1720
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
+ }
1721
1912
  async recordExecution(queueName) {
1722
1913
  await sleep2(0);
1723
1914
  const key = this.makeKey(queueName);
1724
1915
  const executions = this.executions.get(key) ?? [];
1725
1916
  executions.push({
1917
+ id: uuid43(),
1726
1918
  queueName,
1727
1919
  executedAt: new Date
1728
1920
  });
@@ -2304,7 +2496,7 @@ class LazyEncryptedCredentialStore {
2304
2496
  }
2305
2497
  }
2306
2498
  // src/tabular/IndexedDbTabularStorage.ts
2307
- 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";
2308
2500
 
2309
2501
  // src/util/IndexedDbTable.ts
2310
2502
  import { deepEqual } from "@workglow/util";
@@ -2695,7 +2887,7 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
2695
2887
  }
2696
2888
  generateKeyValue(columnName, strategy) {
2697
2889
  if (strategy === "uuid") {
2698
- return uuid43();
2890
+ return uuid44();
2699
2891
  }
2700
2892
  throw new Error(`IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`);
2701
2893
  }
@@ -3196,6 +3388,107 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
3196
3388
  request.onerror = () => reject(request.error);
3197
3389
  });
3198
3390
  }
3391
+ async queryIndex(criteria, options) {
3392
+ this.validateSelect(options);
3393
+ this.validateQueryParams(criteria, options);
3394
+ const registered = this.indexes.map((cols) => {
3395
+ const cs = Array.isArray(cols) ? cols : [cols];
3396
+ return { name: cs.join("_"), keyPath: cs };
3397
+ });
3398
+ const picked = pickCoveringIndex({
3399
+ table: this.table,
3400
+ indexes: registered,
3401
+ criteriaColumns: Object.keys(criteria),
3402
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
3403
+ column: String(o.column),
3404
+ direction: o.direction
3405
+ })),
3406
+ selectColumns: options.select.map(String),
3407
+ primaryKeyColumns: this.primaryKeyColumns().map(String)
3408
+ });
3409
+ const db = await this.getDb();
3410
+ return new Promise((resolve, reject) => {
3411
+ const tx = db.transaction(this.table, "readonly");
3412
+ const store = tx.objectStore(this.table);
3413
+ const idx = store.index(picked.name);
3414
+ const prefix = [];
3415
+ for (const col of picked.keyPath) {
3416
+ const c = criteria[col];
3417
+ if (c === undefined && !(col in criteria))
3418
+ break;
3419
+ if (isSearchCondition(c)) {
3420
+ if (c.operator !== "=")
3421
+ break;
3422
+ prefix.push(c.value);
3423
+ } else {
3424
+ prefix.push(c);
3425
+ }
3426
+ }
3427
+ const range = prefix.length === 0 ? undefined : prefix.length === picked.keyPath.length ? IDBKeyRange.only(prefix.length === 1 ? prefix[0] : prefix) : IDBKeyRange.bound(prefix, [...prefix, []]);
3428
+ const direction = picked.reverseDirection ? "prev" : "next";
3429
+ const request = idx.openKeyCursor(range, direction);
3430
+ const out = [];
3431
+ let toSkip = options.offset ?? 0;
3432
+ const keyPathPositions = new Map;
3433
+ picked.keyPath.forEach((col, i) => keyPathPositions.set(col, i));
3434
+ const pkCols = this.primaryKeyColumns().map(String);
3435
+ const pkPositions = new Map;
3436
+ pkCols.forEach((col, i) => pkPositions.set(col, i));
3437
+ request.onsuccess = () => {
3438
+ const cursor = request.result;
3439
+ if (!cursor) {
3440
+ resolve(out);
3441
+ return;
3442
+ }
3443
+ const key = cursor.key;
3444
+ const row = {};
3445
+ for (const col of options.select) {
3446
+ const colStr = String(col);
3447
+ const pos = keyPathPositions.get(colStr);
3448
+ if (pos !== undefined) {
3449
+ row[colStr] = Array.isArray(key) ? key[pos] : key;
3450
+ } else {
3451
+ if (pkCols.length === 1 && colStr === pkCols[0]) {
3452
+ row[colStr] = cursor.primaryKey;
3453
+ } else {
3454
+ const pkPos = pkPositions.get(colStr);
3455
+ if (pkPos !== undefined) {
3456
+ row[colStr] = Array.isArray(cursor.primaryKey) ? cursor.primaryKey[pkPos] : cursor.primaryKey;
3457
+ }
3458
+ }
3459
+ }
3460
+ }
3461
+ let matches = true;
3462
+ for (const [col, crit] of Object.entries(criteria)) {
3463
+ const pos = keyPathPositions.get(col);
3464
+ if (pos === undefined)
3465
+ continue;
3466
+ if (pos < prefix.length)
3467
+ continue;
3468
+ const valFromKey = Array.isArray(key) ? key[pos] : key;
3469
+ const op = isSearchCondition(crit) ? crit.operator : "=";
3470
+ const val = isSearchCondition(crit) ? crit.value : crit;
3471
+ if (!compareWithOperator(valFromKey, op, val)) {
3472
+ matches = false;
3473
+ break;
3474
+ }
3475
+ }
3476
+ if (matches) {
3477
+ if (toSkip > 0) {
3478
+ toSkip -= 1;
3479
+ } else {
3480
+ out.push(row);
3481
+ if (options.limit !== undefined && out.length >= options.limit) {
3482
+ resolve(out);
3483
+ return;
3484
+ }
3485
+ }
3486
+ }
3487
+ cursor.continue();
3488
+ };
3489
+ request.onerror = () => reject(request.error);
3490
+ });
3491
+ }
3199
3492
  getHybridManager() {
3200
3493
  if (!this.hybridManager) {
3201
3494
  const channelName = `indexeddb-tabular-${this.table}`;
@@ -3233,6 +3526,22 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
3233
3526
  this.db?.close();
3234
3527
  }
3235
3528
  }
3529
+ function compareWithOperator(a, op, b) {
3530
+ const av = a;
3531
+ const bv = b;
3532
+ switch (op) {
3533
+ case "=":
3534
+ return av === bv;
3535
+ case "<":
3536
+ return av !== null && av !== undefined && av < bv;
3537
+ case "<=":
3538
+ return av !== null && av !== undefined && av <= bv;
3539
+ case ">":
3540
+ return av !== null && av !== undefined && av > bv;
3541
+ case ">=":
3542
+ return av !== null && av !== undefined && av >= bv;
3543
+ }
3544
+ }
3236
3545
  // src/tabular/SharedInMemoryTabularStorage.ts
3237
3546
  import { createServiceToken as createServiceToken13 } from "@workglow/util";
3238
3547
  var SHARED_IN_MEMORY_TABULAR_REPOSITORY = createServiceToken13("storage.tabularRepository.sharedInMemory");
@@ -3406,6 +3715,9 @@ class SharedInMemoryTabularStorage extends BaseTabularStorage {
3406
3715
  async query(criteria, options) {
3407
3716
  return await this.inMemoryRepo.query(criteria, options);
3408
3717
  }
3718
+ async queryIndex(criteria, options) {
3719
+ return await this.inMemoryRepo.queryIndex(criteria, options);
3720
+ }
3409
3721
  async deleteSearch(criteria) {
3410
3722
  await this.inMemoryRepo.deleteSearch(criteria);
3411
3723
  this.broadcast({
@@ -4093,6 +4405,56 @@ class SupabaseTabularStorage extends BaseSqlTabularStorage {
4093
4405
  this.events.emit("query", criteria, undefined);
4094
4406
  return;
4095
4407
  }
4408
+ async queryIndex(criteria, options) {
4409
+ this.validateSelect(options);
4410
+ this.validateQueryParams(criteria, options);
4411
+ const registered = this.indexes.map((cols, i) => {
4412
+ const cs = Array.isArray(cols) ? cols : [cols];
4413
+ return { name: `idx_${i}`, keyPath: cs };
4414
+ });
4415
+ pickCoveringIndex({
4416
+ table: this.table,
4417
+ indexes: registered,
4418
+ criteriaColumns: Object.keys(criteria),
4419
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
4420
+ column: String(o.column),
4421
+ direction: o.direction
4422
+ })),
4423
+ selectColumns: options.select.map(String),
4424
+ primaryKeyColumns: this.primaryKeyNames.map(String)
4425
+ });
4426
+ const colList = options.select.map(String).join(",");
4427
+ let q = this.applyCriteriaToFilter(this.client.from(this.table).select(colList), criteria);
4428
+ if (options.orderBy) {
4429
+ for (const { column, direction } of options.orderBy) {
4430
+ q = q.order(String(column), { ascending: direction === "ASC" });
4431
+ }
4432
+ }
4433
+ if (options.offset !== undefined && options.limit === undefined) {
4434
+ throw new StorageValidationError("queryIndex with offset requires limit (no implicit cap)");
4435
+ }
4436
+ if (options.offset !== undefined || options.limit !== undefined) {
4437
+ const start = options.offset ?? 0;
4438
+ if (options.limit !== undefined) {
4439
+ q = q.range(start, start + options.limit - 1);
4440
+ }
4441
+ }
4442
+ const { data, error } = await q;
4443
+ if (error)
4444
+ throw error;
4445
+ if (!data)
4446
+ return [];
4447
+ const rows = data;
4448
+ const sel = new Set(options.select.map(String));
4449
+ for (const row of rows) {
4450
+ for (const key of Object.keys(row)) {
4451
+ if (sel.has(key)) {
4452
+ row[key] = this.sqlToJsValue(key, row[key]);
4453
+ }
4454
+ }
4455
+ }
4456
+ return rows;
4457
+ }
4096
4458
  convertRealtimeRow(row) {
4097
4459
  const entity = { ...row };
4098
4460
  const record = entity;
@@ -4158,11 +4520,12 @@ class SupabaseKvStorage extends KvViaTabularStorage {
4158
4520
  }
4159
4521
  }
4160
4522
  // src/queue/IndexedDbQueueStorage.ts
4161
- 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";
4162
4524
  var INDEXED_DB_QUEUE_STORAGE = createServiceToken17("jobqueue.storage.indexedDb");
4163
4525
 
4164
4526
  class IndexedDbQueueStorage {
4165
4527
  queueName;
4528
+ scope = "process";
4166
4529
  db;
4167
4530
  tableName;
4168
4531
  migrationOptions;
@@ -4239,8 +4602,8 @@ class IndexedDbQueueStorage {
4239
4602
  const db = await this.getDb();
4240
4603
  const now = new Date().toISOString();
4241
4604
  const jobWithPrefixes = job;
4242
- jobWithPrefixes.id = jobWithPrefixes.id ?? uuid44();
4243
- 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();
4244
4607
  jobWithPrefixes.queue = this.queueName;
4245
4608
  jobWithPrefixes.fingerprint = await makeFingerprint6(jobWithPrefixes.input);
4246
4609
  jobWithPrefixes.status = JobStatus.PENDING;
@@ -4738,11 +5101,12 @@ class IndexedDbQueueStorage {
4738
5101
  }
4739
5102
  }
4740
5103
  // src/queue/SupabaseQueueStorage.ts
4741
- 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";
4742
5105
  var SUPABASE_QUEUE_STORAGE = createServiceToken18("jobqueue.storage.supabase");
4743
5106
 
4744
5107
  class SupabaseQueueStorage {
4745
5108
  queueName;
5109
+ scope = "cluster";
4746
5110
  client;
4747
5111
  prefixes;
4748
5112
  prefixValues;
@@ -4867,7 +5231,7 @@ class SupabaseQueueStorage {
4867
5231
  async add(job) {
4868
5232
  const now = new Date().toISOString();
4869
5233
  job.queue = this.queueName;
4870
- job.job_run_id = job.job_run_id ?? uuid45();
5234
+ job.job_run_id = job.job_run_id ?? uuid46();
4871
5235
  job.fingerprint = await makeFingerprint7(job.input);
4872
5236
  job.status = JobStatus.PENDING;
4873
5237
  job.progress = 0;
@@ -5269,6 +5633,7 @@ import { createServiceToken as createServiceToken19 } from "@workglow/util";
5269
5633
  var INDEXED_DB_RATE_LIMITER_STORAGE = createServiceToken19("ratelimiter.storage.indexedDb");
5270
5634
 
5271
5635
  class IndexedDbRateLimiterStorage {
5636
+ scope = "process";
5272
5637
  executionDb;
5273
5638
  nextAvailableDb;
5274
5639
  executionTableName;
@@ -5337,6 +5702,72 @@ class IndexedDbRateLimiterStorage {
5337
5702
  ];
5338
5703
  this.nextAvailableDb = await ensureIndexedDbTable(this.nextAvailableTableName, buildKeyPath(["queue_name"]).join("_"), nextAvailableIndexes, this.migrationOptions);
5339
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
+ }
5340
5771
  async recordExecution(queueName) {
5341
5772
  const db = await this.getExecutionDb();
5342
5773
  const tx = db.transaction(this.executionTableName, "readwrite");
@@ -5490,6 +5921,7 @@ import { createServiceToken as createServiceToken20 } from "@workglow/util";
5490
5921
  var SUPABASE_RATE_LIMITER_STORAGE = createServiceToken20("ratelimiter.storage.supabase");
5491
5922
 
5492
5923
  class SupabaseRateLimiterStorage {
5924
+ scope = "cluster";
5493
5925
  client;
5494
5926
  prefixes;
5495
5927
  prefixValues;
@@ -5572,6 +6004,75 @@ class SupabaseRateLimiterStorage {
5572
6004
  if (nextTableError && nextTableError.code !== "42P07") {
5573
6005
  throw nextTableError;
5574
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;
5575
6076
  }
5576
6077
  async recordExecution(queueName) {
5577
6078
  const prefixInsertValues = this.getPrefixInsertValues();
@@ -5739,6 +6240,7 @@ class IndexedDbVectorStorage extends IndexedDbTabularStorage {
5739
6240
  export {
5740
6241
  traced,
5741
6242
  registerTabularRepository,
6243
+ pickCoveringIndex,
5742
6244
  isSearchCondition,
5743
6245
  getVectorProperty,
5744
6246
  getTabularRepository,
@@ -5801,9 +6303,10 @@ export {
5801
6303
  EncryptedKvCredentialStore,
5802
6304
  DefaultKeyValueSchema,
5803
6305
  DefaultKeyValueKey,
6306
+ CoveringIndexMissingError,
5804
6307
  CachedTabularStorage,
5805
6308
  CACHED_TABULAR_REPOSITORY,
5806
6309
  BaseTabularStorage
5807
6310
  };
5808
6311
 
5809
- //# debugId=4C0D1FA7EA7C0F0564756E2164756E21
6312
+ //# debugId=E2D716C3197417F164756E2164756E21