@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.
- package/dist/browser.js +218 -12
- package/dist/browser.js.map +13 -13
- package/dist/bun.js +703 -24
- package/dist/bun.js.map +18 -17
- package/dist/common-server.d.ts +1 -0
- package/dist/common-server.d.ts.map +1 -1
- package/dist/node.js +703 -24
- package/dist/node.js.map +18 -17
- package/dist/queue/IQueueStorage.d.ts +25 -2
- package/dist/queue/IQueueStorage.d.ts.map +1 -1
- package/dist/queue/InMemoryQueueStorage.d.ts +1 -0
- package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
- package/dist/queue/IndexedDbQueueStorage.d.ts +1 -0
- package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
- package/dist/queue/PostgresQueueStorage.d.ts +24 -3
- package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
- package/dist/queue/SqliteQueueStorage.d.ts +8 -0
- package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
- package/dist/queue/SupabaseQueueStorage.d.ts +1 -0
- package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
- package/dist/queue/TelemetryQueueStorage.d.ts +2 -1
- package/dist/queue/TelemetryQueueStorage.d.ts.map +1 -1
- package/dist/queue-limiter/IRateLimiterStorage.d.ts +46 -0
- package/dist/queue-limiter/IRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts +12 -1
- package/dist/queue-limiter/InMemoryRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts +28 -1
- package/dist/queue-limiter/IndexedDbRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts +4 -1
- package/dist/queue-limiter/PostgresRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts +10 -1
- package/dist/queue-limiter/SqliteRateLimiterStorage.d.ts.map +1 -1
- package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts +6 -1
- package/dist/queue-limiter/SupabaseRateLimiterStorage.d.ts.map +1 -1
- package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
- package/dist/tabular/SharedInMemoryTabularStorage.d.ts +2 -1
- package/dist/tabular/SharedInMemoryTabularStorage.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/node.js
CHANGED
|
@@ -1577,6 +1577,7 @@ var IN_MEMORY_QUEUE_STORAGE = createServiceToken9("jobqueue.storage.inMemory");
|
|
|
1577
1577
|
|
|
1578
1578
|
class InMemoryQueueStorage {
|
|
1579
1579
|
queueName;
|
|
1580
|
+
scope = "process";
|
|
1580
1581
|
prefixValues;
|
|
1581
1582
|
events = new EventEmitter3;
|
|
1582
1583
|
constructor(queueName, options) {
|
|
@@ -1786,6 +1787,9 @@ class TelemetryQueueStorage {
|
|
|
1786
1787
|
this.storageName = storageName;
|
|
1787
1788
|
this.inner = inner;
|
|
1788
1789
|
}
|
|
1790
|
+
get scope() {
|
|
1791
|
+
return this.inner.scope;
|
|
1792
|
+
}
|
|
1789
1793
|
add(job) {
|
|
1790
1794
|
return traced("workglow.storage.queue.add", this.storageName, () => this.inner.add(job));
|
|
1791
1795
|
}
|
|
@@ -1836,13 +1840,15 @@ class TelemetryQueueStorage {
|
|
|
1836
1840
|
}
|
|
1837
1841
|
}
|
|
1838
1842
|
// src/queue-limiter/InMemoryRateLimiterStorage.ts
|
|
1839
|
-
import { createServiceToken as createServiceToken10, sleep as sleep2 } from "@workglow/util";
|
|
1843
|
+
import { createServiceToken as createServiceToken10, sleep as sleep2, uuid4 as uuid43 } from "@workglow/util";
|
|
1840
1844
|
var IN_MEMORY_RATE_LIMITER_STORAGE = createServiceToken10("ratelimiter.storage.inMemory");
|
|
1841
1845
|
|
|
1842
1846
|
class InMemoryRateLimiterStorage {
|
|
1847
|
+
scope = "process";
|
|
1843
1848
|
prefixValues;
|
|
1844
1849
|
executions = new Map;
|
|
1845
1850
|
nextAvailableTimes = new Map;
|
|
1851
|
+
reserveChains = new Map;
|
|
1846
1852
|
constructor(options) {
|
|
1847
1853
|
this.prefixValues = options?.prefixValues ?? {};
|
|
1848
1854
|
}
|
|
@@ -1851,11 +1857,67 @@ class InMemoryRateLimiterStorage {
|
|
|
1851
1857
|
return prefixPart ? `${prefixPart}|${queueName}` : queueName;
|
|
1852
1858
|
}
|
|
1853
1859
|
async setupDatabase() {}
|
|
1860
|
+
async withKeyLock(key, fn) {
|
|
1861
|
+
const previous = this.reserveChains.get(key) ?? Promise.resolve();
|
|
1862
|
+
let release;
|
|
1863
|
+
const next = new Promise((resolve) => {
|
|
1864
|
+
release = resolve;
|
|
1865
|
+
});
|
|
1866
|
+
this.reserveChains.set(key, next);
|
|
1867
|
+
try {
|
|
1868
|
+
await previous;
|
|
1869
|
+
return await fn();
|
|
1870
|
+
} finally {
|
|
1871
|
+
release(undefined);
|
|
1872
|
+
if (this.reserveChains.get(key) === next) {
|
|
1873
|
+
this.reserveChains.delete(key);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
async tryReserveExecution(queueName, maxExecutions, windowMs) {
|
|
1878
|
+
const key = this.makeKey(queueName);
|
|
1879
|
+
return this.withKeyLock(key, () => {
|
|
1880
|
+
const now = Date.now();
|
|
1881
|
+
const windowStart = new Date(now - windowMs);
|
|
1882
|
+
const executions = this.executions.get(key) ?? [];
|
|
1883
|
+
const live = executions.filter((e) => e.executedAt > windowStart);
|
|
1884
|
+
if (live.length >= maxExecutions) {
|
|
1885
|
+
if (live.length !== executions.length) {
|
|
1886
|
+
this.executions.set(key, live);
|
|
1887
|
+
}
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
const next = this.nextAvailableTimes.get(key);
|
|
1891
|
+
if (next && next.getTime() > now) {
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
const id = uuid43();
|
|
1895
|
+
live.push({ id, queueName, executedAt: new Date(now) });
|
|
1896
|
+
this.executions.set(key, live);
|
|
1897
|
+
return id;
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
async releaseExecution(queueName, token) {
|
|
1901
|
+
if (token === null || token === undefined)
|
|
1902
|
+
return;
|
|
1903
|
+
const key = this.makeKey(queueName);
|
|
1904
|
+
await this.withKeyLock(key, () => {
|
|
1905
|
+
const executions = this.executions.get(key);
|
|
1906
|
+
if (!executions || executions.length === 0)
|
|
1907
|
+
return;
|
|
1908
|
+
const idx = executions.findIndex((e) => e.id === token);
|
|
1909
|
+
if (idx === -1)
|
|
1910
|
+
return;
|
|
1911
|
+
executions.splice(idx, 1);
|
|
1912
|
+
this.executions.set(key, executions);
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1854
1915
|
async recordExecution(queueName) {
|
|
1855
1916
|
await sleep2(0);
|
|
1856
1917
|
const key = this.makeKey(queueName);
|
|
1857
1918
|
const executions = this.executions.get(key) ?? [];
|
|
1858
1919
|
executions.push({
|
|
1920
|
+
id: uuid43(),
|
|
1859
1921
|
queueName,
|
|
1860
1922
|
executedAt: new Date
|
|
1861
1923
|
});
|
|
@@ -2443,7 +2505,7 @@ import {
|
|
|
2443
2505
|
getLogger as getLogger3,
|
|
2444
2506
|
makeFingerprint as makeFingerprint5,
|
|
2445
2507
|
sleep as sleep3,
|
|
2446
|
-
uuid4 as
|
|
2508
|
+
uuid4 as uuid44
|
|
2447
2509
|
} from "@workglow/util";
|
|
2448
2510
|
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2449
2511
|
import path from "node:path";
|
|
@@ -2471,7 +2533,7 @@ class FsFolderTabularStorage extends BaseTabularStorage {
|
|
|
2471
2533
|
if (strategy === "autoincrement") {
|
|
2472
2534
|
return ++this.autoIncrementCounter;
|
|
2473
2535
|
} else {
|
|
2474
|
-
return
|
|
2536
|
+
return uuid44();
|
|
2475
2537
|
}
|
|
2476
2538
|
}
|
|
2477
2539
|
async put(entity) {
|
|
@@ -3423,7 +3485,7 @@ class PostgresTabularStorage extends BaseSqlTabularStorage {
|
|
|
3423
3485
|
}
|
|
3424
3486
|
// src/tabular/SqliteTabularStorage.ts
|
|
3425
3487
|
import { Sqlite } from "@workglow/storage/sqlite";
|
|
3426
|
-
import { createServiceToken as createServiceToken14, uuid4 as
|
|
3488
|
+
import { createServiceToken as createServiceToken14, uuid4 as uuid45 } from "@workglow/util";
|
|
3427
3489
|
var SQLITE_TABULAR_REPOSITORY = createServiceToken14("storage.tabularRepository.sqlite");
|
|
3428
3490
|
|
|
3429
3491
|
class SqliteTabularStorage extends BaseSqlTabularStorage {
|
|
@@ -3601,7 +3663,7 @@ class SqliteTabularStorage extends BaseSqlTabularStorage {
|
|
|
3601
3663
|
}
|
|
3602
3664
|
generateKeyValue(columnName, strategy) {
|
|
3603
3665
|
if (strategy === "uuid") {
|
|
3604
|
-
return
|
|
3666
|
+
return uuid45();
|
|
3605
3667
|
}
|
|
3606
3668
|
throw new Error(`SQLite autoincrement keys are generated by the database, not client-side. Column: ${columnName}`);
|
|
3607
3669
|
}
|
|
@@ -4641,13 +4703,15 @@ class SupabaseKvStorage extends KvViaTabularStorage {
|
|
|
4641
4703
|
}
|
|
4642
4704
|
}
|
|
4643
4705
|
// src/queue/PostgresQueueStorage.ts
|
|
4644
|
-
import {
|
|
4706
|
+
import { createHash } from "node:crypto";
|
|
4707
|
+
import { createServiceToken as createServiceToken21, getLogger as getLogger4, makeFingerprint as makeFingerprint6, uuid4 as uuid46 } from "@workglow/util";
|
|
4645
4708
|
var POSTGRES_QUEUE_STORAGE = createServiceToken21("jobqueue.storage.postgres");
|
|
4646
4709
|
var SAFE_IDENTIFIER = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
4647
4710
|
|
|
4648
4711
|
class PostgresQueueStorage {
|
|
4649
4712
|
db;
|
|
4650
4713
|
queueName;
|
|
4714
|
+
scope = "cluster";
|
|
4651
4715
|
prefixes;
|
|
4652
4716
|
prefixValues;
|
|
4653
4717
|
tableName;
|
|
@@ -4744,14 +4808,46 @@ class PostgresQueueStorage {
|
|
|
4744
4808
|
ON ${this.tableName} (${prefixIndexPrefix}queue, status, run_after)`;
|
|
4745
4809
|
await this.db.query(sql);
|
|
4746
4810
|
sql = `
|
|
4747
|
-
CREATE INDEX IF NOT EXISTS jobs_fingerprint${indexSuffix}_unique_idx
|
|
4811
|
+
CREATE INDEX IF NOT EXISTS jobs_fingerprint${indexSuffix}_unique_idx
|
|
4748
4812
|
ON ${this.tableName} (${prefixIndexPrefix}queue, fingerprint, status)`;
|
|
4749
4813
|
await this.db.query(sql);
|
|
4814
|
+
const fnName = `${this.tableName}_notify`;
|
|
4815
|
+
const trgName = `${this.tableName}_notify_trg`;
|
|
4816
|
+
try {
|
|
4817
|
+
await this.db.query(`
|
|
4818
|
+
CREATE OR REPLACE FUNCTION ${fnName}() RETURNS trigger AS $fn$
|
|
4819
|
+
DECLARE
|
|
4820
|
+
channel TEXT := 'wglw_q_' || md5('${this.tableName}' || COALESCE(NEW.queue, OLD.queue));
|
|
4821
|
+
payload TEXT;
|
|
4822
|
+
BEGIN
|
|
4823
|
+
payload := json_build_object(
|
|
4824
|
+
'op', TG_OP,
|
|
4825
|
+
'id', COALESCE(NEW.id, OLD.id),
|
|
4826
|
+
'queue', COALESCE(NEW.queue, OLD.queue),
|
|
4827
|
+
'status', COALESCE(NEW.status::text, OLD.status::text)
|
|
4828
|
+
)::text;
|
|
4829
|
+
PERFORM pg_notify(channel, payload);
|
|
4830
|
+
RETURN NULL;
|
|
4831
|
+
END;
|
|
4832
|
+
$fn$ LANGUAGE plpgsql;
|
|
4833
|
+
`);
|
|
4834
|
+
await this.db.query(`DROP TRIGGER IF EXISTS ${trgName} ON ${this.tableName}`);
|
|
4835
|
+
await this.db.query(`
|
|
4836
|
+
CREATE TRIGGER ${trgName}
|
|
4837
|
+
AFTER INSERT OR UPDATE ON ${this.tableName}
|
|
4838
|
+
FOR EACH ROW EXECUTE FUNCTION ${fnName}();
|
|
4839
|
+
`);
|
|
4840
|
+
} catch {}
|
|
4841
|
+
}
|
|
4842
|
+
notifyChannelName() {
|
|
4843
|
+
const tableAndQueue = `${this.tableName}${this.queueName}`;
|
|
4844
|
+
const hash = createHash("md5").update(tableAndQueue).digest("hex");
|
|
4845
|
+
return `wglw_q_${hash}`;
|
|
4750
4846
|
}
|
|
4751
4847
|
async add(job) {
|
|
4752
4848
|
const now = new Date().toISOString();
|
|
4753
4849
|
job.queue = this.queueName;
|
|
4754
|
-
job.job_run_id = job.job_run_id ??
|
|
4850
|
+
job.job_run_id = job.job_run_id ?? uuid46();
|
|
4755
4851
|
job.fingerprint = await makeFingerprint6(job.input);
|
|
4756
4852
|
job.status = JobStatus.PENDING;
|
|
4757
4853
|
job.progress = 0;
|
|
@@ -4990,17 +5086,138 @@ class PostgresQueueStorage {
|
|
|
4990
5086
|
AND completed_at <= $3${prefixConditions}`, [this.queueName, status, cutoffDate, ...prefixParams]);
|
|
4991
5087
|
}
|
|
4992
5088
|
subscribeToChanges(callback, options) {
|
|
4993
|
-
|
|
5089
|
+
const poolMaybe = this.db;
|
|
5090
|
+
if (typeof poolMaybe.connect !== "function") {
|
|
5091
|
+
throw new Error("PostgresQueueStorage.subscribeToChanges requires a pg.Pool (got a single-connection wrapper)");
|
|
5092
|
+
}
|
|
5093
|
+
const pool = poolMaybe;
|
|
5094
|
+
const channel = this.notifyChannelName();
|
|
5095
|
+
const effectivePrefixFilter = options?.prefixFilter === undefined ? this.prefixes.length > 0 ? this.prefixValues : null : Object.keys(options.prefixFilter).length === 0 ? null : options.prefixFilter;
|
|
5096
|
+
let unsubscribed = false;
|
|
5097
|
+
let activeClient = null;
|
|
5098
|
+
let reconnectTimer = null;
|
|
5099
|
+
let backoffMs = 250;
|
|
5100
|
+
const dispatch = (change) => {
|
|
5101
|
+
try {
|
|
5102
|
+
callback(change);
|
|
5103
|
+
} catch (err) {
|
|
5104
|
+
getLogger4().debug("PostgresQueueStorage subscribe callback threw", {
|
|
5105
|
+
channel,
|
|
5106
|
+
changeType: change.type,
|
|
5107
|
+
error: err
|
|
5108
|
+
});
|
|
5109
|
+
}
|
|
5110
|
+
};
|
|
5111
|
+
const matchesPrefix = (change, filter) => {
|
|
5112
|
+
const row = change.new ?? change.old;
|
|
5113
|
+
if (!row)
|
|
5114
|
+
return false;
|
|
5115
|
+
for (const [k, v] of Object.entries(filter)) {
|
|
5116
|
+
if (row[k] !== v)
|
|
5117
|
+
return false;
|
|
5118
|
+
}
|
|
5119
|
+
return true;
|
|
5120
|
+
};
|
|
5121
|
+
const hydrate = async (id) => {
|
|
5122
|
+
try {
|
|
5123
|
+
return await this.get(id);
|
|
5124
|
+
} catch {
|
|
5125
|
+
return;
|
|
5126
|
+
}
|
|
5127
|
+
};
|
|
5128
|
+
const handleNotification = (msg) => {
|
|
5129
|
+
if (msg.channel !== channel || !msg.payload)
|
|
5130
|
+
return;
|
|
5131
|
+
let parsed;
|
|
5132
|
+
try {
|
|
5133
|
+
parsed = JSON.parse(msg.payload);
|
|
5134
|
+
} catch {
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
(async () => {
|
|
5138
|
+
const op = parsed.op;
|
|
5139
|
+
const fallback = {
|
|
5140
|
+
id: parsed.id,
|
|
5141
|
+
queue: parsed.queue,
|
|
5142
|
+
status: parsed.status
|
|
5143
|
+
};
|
|
5144
|
+
const fullRow = op === "DELETE" ? undefined : await hydrate(parsed.id);
|
|
5145
|
+
const change = {
|
|
5146
|
+
type: op === "INSERT" ? "INSERT" : op === "DELETE" ? "DELETE" : "UPDATE",
|
|
5147
|
+
new: op === "DELETE" ? undefined : fullRow ?? fallback,
|
|
5148
|
+
old: op === "DELETE" ? fallback : undefined
|
|
5149
|
+
};
|
|
5150
|
+
if (effectivePrefixFilter && !matchesPrefix(change, effectivePrefixFilter))
|
|
5151
|
+
return;
|
|
5152
|
+
dispatch(change);
|
|
5153
|
+
})();
|
|
5154
|
+
};
|
|
5155
|
+
const connect = async () => {
|
|
5156
|
+
if (unsubscribed)
|
|
5157
|
+
return;
|
|
5158
|
+
try {
|
|
5159
|
+
const client = await pool.connect();
|
|
5160
|
+
if (unsubscribed) {
|
|
5161
|
+
client.release();
|
|
5162
|
+
return;
|
|
5163
|
+
}
|
|
5164
|
+
activeClient = client;
|
|
5165
|
+
client.on("notification", handleNotification);
|
|
5166
|
+
client.on("error", () => scheduleReconnect());
|
|
5167
|
+
await client.query(`LISTEN ${channel}`);
|
|
5168
|
+
backoffMs = 250;
|
|
5169
|
+
dispatch({ type: "RESYNC" });
|
|
5170
|
+
} catch {
|
|
5171
|
+
scheduleReconnect();
|
|
5172
|
+
}
|
|
5173
|
+
};
|
|
5174
|
+
const scheduleReconnect = () => {
|
|
5175
|
+
if (unsubscribed || reconnectTimer)
|
|
5176
|
+
return;
|
|
5177
|
+
const c = activeClient;
|
|
5178
|
+
activeClient = null;
|
|
5179
|
+
try {
|
|
5180
|
+
c?.removeAllListeners?.("notification");
|
|
5181
|
+
c?.removeAllListeners?.("error");
|
|
5182
|
+
c?.release();
|
|
5183
|
+
} catch {}
|
|
5184
|
+
const delay = Math.min(backoffMs, 30000);
|
|
5185
|
+
backoffMs = Math.min(backoffMs * 2, 30000);
|
|
5186
|
+
reconnectTimer = setTimeout(() => {
|
|
5187
|
+
reconnectTimer = null;
|
|
5188
|
+
connect();
|
|
5189
|
+
}, delay);
|
|
5190
|
+
};
|
|
5191
|
+
connect();
|
|
5192
|
+
return () => {
|
|
5193
|
+
unsubscribed = true;
|
|
5194
|
+
if (reconnectTimer) {
|
|
5195
|
+
clearTimeout(reconnectTimer);
|
|
5196
|
+
reconnectTimer = null;
|
|
5197
|
+
}
|
|
5198
|
+
const c = activeClient;
|
|
5199
|
+
activeClient = null;
|
|
5200
|
+
if (c) {
|
|
5201
|
+
c.query(`UNLISTEN ${channel}`).catch(() => {}).finally(() => {
|
|
5202
|
+
try {
|
|
5203
|
+
c.removeAllListeners?.("notification");
|
|
5204
|
+
c.removeAllListeners?.("error");
|
|
5205
|
+
c.release();
|
|
5206
|
+
} catch {}
|
|
5207
|
+
});
|
|
5208
|
+
}
|
|
5209
|
+
};
|
|
4994
5210
|
}
|
|
4995
5211
|
}
|
|
4996
5212
|
// src/queue/SqliteQueueStorage.ts
|
|
4997
|
-
import { createServiceToken as createServiceToken22, makeFingerprint as makeFingerprint7, sleep as sleep4, uuid4 as
|
|
5213
|
+
import { createServiceToken as createServiceToken22, makeFingerprint as makeFingerprint7, sleep as sleep4, uuid4 as uuid47 } from "@workglow/util";
|
|
4998
5214
|
var SQLITE_QUEUE_STORAGE = createServiceToken22("jobqueue.storage.sqlite");
|
|
4999
5215
|
|
|
5000
5216
|
class SqliteQueueStorage {
|
|
5001
5217
|
db;
|
|
5002
5218
|
queueName;
|
|
5003
5219
|
options;
|
|
5220
|
+
scope = "process";
|
|
5004
5221
|
prefixes;
|
|
5005
5222
|
prefixValues;
|
|
5006
5223
|
tableName;
|
|
@@ -5077,7 +5294,7 @@ class SqliteQueueStorage {
|
|
|
5077
5294
|
}
|
|
5078
5295
|
async add(job) {
|
|
5079
5296
|
const now = new Date().toISOString();
|
|
5080
|
-
job.job_run_id = job.job_run_id ??
|
|
5297
|
+
job.job_run_id = job.job_run_id ?? uuid47();
|
|
5081
5298
|
job.queue = this.queueName;
|
|
5082
5299
|
job.fingerprint = await makeFingerprint7(job.input);
|
|
5083
5300
|
job.status = JobStatus.PENDING;
|
|
@@ -5344,11 +5561,12 @@ class SqliteQueueStorage {
|
|
|
5344
5561
|
}
|
|
5345
5562
|
}
|
|
5346
5563
|
// src/queue/SupabaseQueueStorage.ts
|
|
5347
|
-
import { createServiceToken as createServiceToken23, deepEqual as deepEqual2, makeFingerprint as makeFingerprint8, uuid4 as
|
|
5564
|
+
import { createServiceToken as createServiceToken23, deepEqual as deepEqual2, makeFingerprint as makeFingerprint8, uuid4 as uuid48 } from "@workglow/util";
|
|
5348
5565
|
var SUPABASE_QUEUE_STORAGE = createServiceToken23("jobqueue.storage.supabase");
|
|
5349
5566
|
|
|
5350
5567
|
class SupabaseQueueStorage {
|
|
5351
5568
|
queueName;
|
|
5569
|
+
scope = "cluster";
|
|
5352
5570
|
client;
|
|
5353
5571
|
prefixes;
|
|
5354
5572
|
prefixValues;
|
|
@@ -5473,7 +5691,7 @@ class SupabaseQueueStorage {
|
|
|
5473
5691
|
async add(job) {
|
|
5474
5692
|
const now = new Date().toISOString();
|
|
5475
5693
|
job.queue = this.queueName;
|
|
5476
|
-
job.job_run_id = job.job_run_id ??
|
|
5694
|
+
job.job_run_id = job.job_run_id ?? uuid48();
|
|
5477
5695
|
job.fingerprint = await makeFingerprint8(job.input);
|
|
5478
5696
|
job.status = JobStatus.PENDING;
|
|
5479
5697
|
job.progress = 0;
|
|
@@ -5876,6 +6094,7 @@ var POSTGRES_RATE_LIMITER_STORAGE = createServiceToken24("ratelimiter.storage.po
|
|
|
5876
6094
|
|
|
5877
6095
|
class PostgresRateLimiterStorage {
|
|
5878
6096
|
db;
|
|
6097
|
+
scope = "cluster";
|
|
5879
6098
|
prefixes;
|
|
5880
6099
|
prefixValues;
|
|
5881
6100
|
executionTableName;
|
|
@@ -5942,6 +6161,80 @@ class PostgresRateLimiterStorage {
|
|
|
5942
6161
|
)
|
|
5943
6162
|
`);
|
|
5944
6163
|
}
|
|
6164
|
+
async tryReserveExecution(queueName, maxExecutions, windowMs) {
|
|
6165
|
+
const prefixColumnNames = this.getPrefixColumnNames();
|
|
6166
|
+
const prefixParamValues = this.getPrefixParamValues();
|
|
6167
|
+
const prefixCount = prefixColumnNames.length;
|
|
6168
|
+
const queueParam = `$${prefixCount + 1}`;
|
|
6169
|
+
const windowStartParam = `$${prefixCount + 2}`;
|
|
6170
|
+
const lockKeyParts = [`'${this.executionTableName}'`];
|
|
6171
|
+
for (let i = 0;i < prefixCount; i++) {
|
|
6172
|
+
lockKeyParts.push(`$${i + 1}::text`);
|
|
6173
|
+
}
|
|
6174
|
+
lockKeyParts.push(`${queueParam}::text`);
|
|
6175
|
+
const lockKeyExpr = `hashtextextended(${lockKeyParts.join(" || '|' || ")}, 0)`;
|
|
6176
|
+
const prefixWhere = prefixCount > 0 ? " AND " + prefixColumnNames.map((p, i) => `${p} = $${i + 1}`).join(" AND ") : "";
|
|
6177
|
+
const prefixInsertCols = prefixCount > 0 ? prefixColumnNames.join(", ") + ", " : "";
|
|
6178
|
+
const prefixInsertPlaceholders = prefixCount > 0 ? prefixColumnNames.map((_, i) => `$${i + 1}`).join(", ") + ", " : "";
|
|
6179
|
+
const windowStart = new Date(Date.now() - windowMs).toISOString();
|
|
6180
|
+
const supportsConnect = typeof this.db.connect === "function";
|
|
6181
|
+
if (!supportsConnect) {
|
|
6182
|
+
const dbAny = this.db;
|
|
6183
|
+
const ctorName = dbAny.constructor?.name;
|
|
6184
|
+
const looksLikePGlite = typeof dbAny.exec === "function" && dbAny.waitReady !== undefined;
|
|
6185
|
+
const looksLikePGLitePool = ctorName === "PGLitePool";
|
|
6186
|
+
if (!looksLikePGlite && !looksLikePGLitePool) {
|
|
6187
|
+
throw new Error(`PostgresRateLimiterStorage.tryReserveExecution requires a pg.Pool with connect() or a known single-connection wrapper (PGLitePool, PGlite); got ${ctorName ?? typeof this.db}. A multi-connection pool without connect() would dispatch the advisory lock and the INSERT to different sessions, breaking atomicity.`);
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
const conn = supportsConnect ? await this.db.connect() : { query: this.db.query.bind(this.db), release: () => {} };
|
|
6191
|
+
try {
|
|
6192
|
+
await conn.query("BEGIN");
|
|
6193
|
+
try {
|
|
6194
|
+
await conn.query(`SELECT pg_advisory_xact_lock(${lockKeyExpr})`, [...prefixParamValues, queueName]);
|
|
6195
|
+
const countResult = await conn.query(`
|
|
6196
|
+
SELECT COUNT(*)::int AS n
|
|
6197
|
+
FROM ${this.executionTableName}
|
|
6198
|
+
WHERE queue_name = ${queueParam} AND executed_at > ${windowStartParam}${prefixWhere}
|
|
6199
|
+
`, [...prefixParamValues, queueName, windowStart]);
|
|
6200
|
+
const n = countResult.rows[0]?.n ?? 0;
|
|
6201
|
+
if (n >= maxExecutions) {
|
|
6202
|
+
await conn.query("COMMIT");
|
|
6203
|
+
return null;
|
|
6204
|
+
}
|
|
6205
|
+
const naResult = await conn.query(`
|
|
6206
|
+
SELECT next_available_at
|
|
6207
|
+
FROM ${this.nextAvailableTableName}
|
|
6208
|
+
WHERE queue_name = ${queueParam}${prefixWhere}
|
|
6209
|
+
`, [...prefixParamValues, queueName]);
|
|
6210
|
+
const nextAvailableAt = naResult.rows[0]?.next_available_at ?? null;
|
|
6211
|
+
if (nextAvailableAt && new Date(nextAvailableAt).getTime() > Date.now()) {
|
|
6212
|
+
await conn.query("COMMIT");
|
|
6213
|
+
return null;
|
|
6214
|
+
}
|
|
6215
|
+
const insertResult = await conn.query(`
|
|
6216
|
+
INSERT INTO ${this.executionTableName} (${prefixInsertCols}queue_name)
|
|
6217
|
+
VALUES (${prefixInsertPlaceholders}${queueParam})
|
|
6218
|
+
RETURNING id
|
|
6219
|
+
`, [...prefixParamValues, queueName]);
|
|
6220
|
+
await conn.query("COMMIT");
|
|
6221
|
+
return insertResult.rows[0]?.id ?? null;
|
|
6222
|
+
} catch (err) {
|
|
6223
|
+
try {
|
|
6224
|
+
await conn.query("ROLLBACK");
|
|
6225
|
+
} catch {}
|
|
6226
|
+
throw err;
|
|
6227
|
+
}
|
|
6228
|
+
} finally {
|
|
6229
|
+
conn.release();
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
async releaseExecution(queueName, token) {
|
|
6233
|
+
if (token === null || token === undefined)
|
|
6234
|
+
return;
|
|
6235
|
+
const { conditions: prefixConditions, params: prefixParams } = this.buildPrefixWhereClause(3);
|
|
6236
|
+
await this.db.query(`DELETE FROM ${this.executionTableName} WHERE id = $1 AND queue_name = $2${prefixConditions}`, [token, queueName, ...prefixParams]);
|
|
6237
|
+
}
|
|
5945
6238
|
async recordExecution(queueName) {
|
|
5946
6239
|
const prefixColumnNames = this.getPrefixColumnNames();
|
|
5947
6240
|
const prefixColumnsInsert = prefixColumnNames.length > 0 ? prefixColumnNames.join(", ") + ", " : "";
|
|
@@ -6014,6 +6307,7 @@ var SQLITE_RATE_LIMITER_STORAGE = createServiceToken25("ratelimiter.storage.sqli
|
|
|
6014
6307
|
|
|
6015
6308
|
class SqliteRateLimiterStorage {
|
|
6016
6309
|
db;
|
|
6310
|
+
scope = "process";
|
|
6017
6311
|
prefixes;
|
|
6018
6312
|
prefixValues;
|
|
6019
6313
|
executionTableName;
|
|
@@ -6077,6 +6371,55 @@ class SqliteRateLimiterStorage {
|
|
|
6077
6371
|
);
|
|
6078
6372
|
`);
|
|
6079
6373
|
}
|
|
6374
|
+
async tryReserveExecution(queueName, maxExecutions, windowMs) {
|
|
6375
|
+
const prefixColumnNames = this.getPrefixColumnNames();
|
|
6376
|
+
const prefixParamValues = this.getPrefixParamValues();
|
|
6377
|
+
const prefixConditions = this.buildPrefixWhereClause();
|
|
6378
|
+
const prefixColumnsInsert = prefixColumnNames.length > 0 ? prefixColumnNames.join(", ") + ", " : "";
|
|
6379
|
+
const prefixPlaceholders = prefixColumnNames.length > 0 ? prefixColumnNames.map(() => "?").join(", ") + ", " : "";
|
|
6380
|
+
const windowStart = toSQLiteTimestamp(new Date(Date.now() - windowMs));
|
|
6381
|
+
let insertedId = null;
|
|
6382
|
+
const txn = this.db.transaction(() => {
|
|
6383
|
+
const countStmt = this.db.prepare(`
|
|
6384
|
+
SELECT COUNT(*) AS count
|
|
6385
|
+
FROM ${this.executionTableName}
|
|
6386
|
+
WHERE queue_name = ? AND executed_at > ?${prefixConditions}
|
|
6387
|
+
`);
|
|
6388
|
+
const countRow = countStmt.get(queueName, windowStart, ...prefixParamValues);
|
|
6389
|
+
if ((countRow?.count ?? 0) >= maxExecutions) {
|
|
6390
|
+
return;
|
|
6391
|
+
}
|
|
6392
|
+
const naStmt = this.db.prepare(`
|
|
6393
|
+
SELECT next_available_at
|
|
6394
|
+
FROM ${this.nextAvailableTableName}
|
|
6395
|
+
WHERE queue_name = ?${prefixConditions}
|
|
6396
|
+
`);
|
|
6397
|
+
const naRow = naStmt.get(queueName, ...prefixParamValues);
|
|
6398
|
+
if (naRow?.next_available_at) {
|
|
6399
|
+
const nextAvailable = new Date(naRow.next_available_at + "Z").getTime();
|
|
6400
|
+
if (nextAvailable > Date.now()) {
|
|
6401
|
+
return;
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
const insertStmt = this.db.prepare(`
|
|
6405
|
+
INSERT INTO ${this.executionTableName} (${prefixColumnsInsert}queue_name)
|
|
6406
|
+
VALUES (${prefixPlaceholders}?)
|
|
6407
|
+
`);
|
|
6408
|
+
const info = insertStmt.run(...prefixParamValues, queueName);
|
|
6409
|
+
insertedId = info.lastInsertRowid;
|
|
6410
|
+
});
|
|
6411
|
+
txn();
|
|
6412
|
+
if (insertedId == null)
|
|
6413
|
+
return null;
|
|
6414
|
+
return typeof insertedId === "bigint" ? Number(insertedId) : insertedId;
|
|
6415
|
+
}
|
|
6416
|
+
async releaseExecution(queueName, token) {
|
|
6417
|
+
if (token === null || token === undefined)
|
|
6418
|
+
return;
|
|
6419
|
+
const prefixConditions = this.buildPrefixWhereClause();
|
|
6420
|
+
const prefixParams = this.getPrefixParamValues();
|
|
6421
|
+
this.db.prepare(`DELETE FROM ${this.executionTableName} WHERE id = ? AND queue_name = ?${prefixConditions}`).run(token, queueName, ...prefixParams);
|
|
6422
|
+
}
|
|
6080
6423
|
async recordExecution(queueName) {
|
|
6081
6424
|
const prefixColumnNames = this.getPrefixColumnNames();
|
|
6082
6425
|
const prefixColumnsInsert = prefixColumnNames.length > 0 ? prefixColumnNames.join(", ") + ", " : "";
|
|
@@ -6152,6 +6495,7 @@ import { createServiceToken as createServiceToken26 } from "@workglow/util";
|
|
|
6152
6495
|
var SUPABASE_RATE_LIMITER_STORAGE = createServiceToken26("ratelimiter.storage.supabase");
|
|
6153
6496
|
|
|
6154
6497
|
class SupabaseRateLimiterStorage {
|
|
6498
|
+
scope = "cluster";
|
|
6155
6499
|
client;
|
|
6156
6500
|
prefixes;
|
|
6157
6501
|
prefixValues;
|
|
@@ -6234,6 +6578,75 @@ class SupabaseRateLimiterStorage {
|
|
|
6234
6578
|
if (nextTableError && nextTableError.code !== "42P07") {
|
|
6235
6579
|
throw nextTableError;
|
|
6236
6580
|
}
|
|
6581
|
+
const fnName = this.atomicReserveFunctionName();
|
|
6582
|
+
const prefixSig = this.prefixes.map((p) => `${p.name} ${this.getPrefixColumnType(p.type)}`).join(", ");
|
|
6583
|
+
const prefixSigPrefix = prefixSig ? prefixSig + ", " : "";
|
|
6584
|
+
const prefixWhere = this.prefixes.length > 0 ? " AND " + this.prefixes.map((p) => `${p.name} = _${p.name}`).join(" AND ") : "";
|
|
6585
|
+
const prefixInsertCols = this.prefixes.length > 0 ? this.prefixes.map((p) => p.name).join(", ") + ", " : "";
|
|
6586
|
+
const prefixInsertVals = this.prefixes.length > 0 ? this.prefixes.map((p) => `_${p.name}`).join(", ") + ", " : "";
|
|
6587
|
+
const lockKeyParts = [`'${this.executionTableName}'`, ...this.prefixes.map((p) => `_${p.name}::text`), `_queue_name::text`];
|
|
6588
|
+
const lockKeyExpr = `hashtextextended(${lockKeyParts.join(" || '|' || ")}, 0)`;
|
|
6589
|
+
const createFnSql = `
|
|
6590
|
+
CREATE OR REPLACE FUNCTION ${fnName}(
|
|
6591
|
+
${prefixSigPrefix}_queue_name TEXT, _window_start TIMESTAMPTZ, _max_exec INT
|
|
6592
|
+
) RETURNS BIGINT AS $fn$
|
|
6593
|
+
DECLARE
|
|
6594
|
+
_count INT;
|
|
6595
|
+
_next TIMESTAMPTZ;
|
|
6596
|
+
_new_id BIGINT;
|
|
6597
|
+
BEGIN
|
|
6598
|
+
PERFORM pg_advisory_xact_lock(${lockKeyExpr});
|
|
6599
|
+
SELECT COUNT(*) INTO _count FROM ${this.executionTableName}
|
|
6600
|
+
WHERE queue_name = _queue_name AND executed_at > _window_start${prefixWhere};
|
|
6601
|
+
IF _count >= _max_exec THEN RETURN NULL; END IF;
|
|
6602
|
+
SELECT next_available_at INTO _next FROM ${this.nextAvailableTableName}
|
|
6603
|
+
WHERE queue_name = _queue_name${prefixWhere};
|
|
6604
|
+
IF _next IS NOT NULL AND _next > NOW() THEN RETURN NULL; END IF;
|
|
6605
|
+
INSERT INTO ${this.executionTableName} (${prefixInsertCols}queue_name)
|
|
6606
|
+
VALUES (${prefixInsertVals}_queue_name)
|
|
6607
|
+
RETURNING id INTO _new_id;
|
|
6608
|
+
RETURN _new_id;
|
|
6609
|
+
END;
|
|
6610
|
+
$fn$ LANGUAGE plpgsql;
|
|
6611
|
+
`;
|
|
6612
|
+
const { error: fnError } = await this.client.rpc("exec_sql", { query: createFnSql });
|
|
6613
|
+
if (fnError) {
|
|
6614
|
+
throw fnError;
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
atomicReserveFunctionName() {
|
|
6618
|
+
return `${this.executionTableName}_try_reserve`.slice(0, 63);
|
|
6619
|
+
}
|
|
6620
|
+
async tryReserveExecution(queueName, maxExecutions, windowMs) {
|
|
6621
|
+
const args = {
|
|
6622
|
+
_queue_name: queueName,
|
|
6623
|
+
_window_start: new Date(Date.now() - windowMs).toISOString(),
|
|
6624
|
+
_max_exec: maxExecutions
|
|
6625
|
+
};
|
|
6626
|
+
for (const p of this.prefixes) {
|
|
6627
|
+
args[`_${p.name}`] = this.prefixValues[p.name];
|
|
6628
|
+
}
|
|
6629
|
+
const { data, error } = await this.client.rpc(this.atomicReserveFunctionName(), args);
|
|
6630
|
+
if (error)
|
|
6631
|
+
throw error;
|
|
6632
|
+
if (data === null || data === undefined)
|
|
6633
|
+
return null;
|
|
6634
|
+
if (Array.isArray(data)) {
|
|
6635
|
+
if (data.length === 0)
|
|
6636
|
+
return null;
|
|
6637
|
+
const first = Object.values(data[0])[0];
|
|
6638
|
+
return first ?? null;
|
|
6639
|
+
}
|
|
6640
|
+
return data;
|
|
6641
|
+
}
|
|
6642
|
+
async releaseExecution(queueName, token) {
|
|
6643
|
+
if (token === null || token === undefined)
|
|
6644
|
+
return;
|
|
6645
|
+
let del = this.client.from(this.executionTableName).delete().eq("id", token).eq("queue_name", queueName);
|
|
6646
|
+
del = this.applyPrefixFilters(del);
|
|
6647
|
+
const { error: delError } = await del;
|
|
6648
|
+
if (delError)
|
|
6649
|
+
throw delError;
|
|
6237
6650
|
}
|
|
6238
6651
|
async recordExecution(queueName) {
|
|
6239
6652
|
const prefixInsertValues = this.getPrefixInsertValues();
|
|
@@ -6999,7 +7412,7 @@ class SqliteAiVectorStorage extends SqliteTabularStorage {
|
|
|
6999
7412
|
import { createServiceToken as createServiceToken28 } from "@workglow/util";
|
|
7000
7413
|
|
|
7001
7414
|
// src/tabular/IndexedDbTabularStorage.ts
|
|
7002
|
-
import { createServiceToken as createServiceToken27, deepEqual as deepEqual4, makeFingerprint as makeFingerprint9, uuid4 as
|
|
7415
|
+
import { createServiceToken as createServiceToken27, deepEqual as deepEqual4, makeFingerprint as makeFingerprint9, uuid4 as uuid49 } from "@workglow/util";
|
|
7003
7416
|
|
|
7004
7417
|
// src/util/IndexedDbTable.ts
|
|
7005
7418
|
import { deepEqual as deepEqual3 } from "@workglow/util";
|
|
@@ -7390,7 +7803,7 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
|
|
|
7390
7803
|
}
|
|
7391
7804
|
generateKeyValue(columnName, strategy) {
|
|
7392
7805
|
if (strategy === "uuid") {
|
|
7393
|
-
return
|
|
7806
|
+
return uuid49();
|
|
7394
7807
|
}
|
|
7395
7808
|
throw new Error(`IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`);
|
|
7396
7809
|
}
|
|
@@ -7963,13 +8376,15 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
|
|
|
7963
8376
|
}
|
|
7964
8377
|
let matches = true;
|
|
7965
8378
|
for (const [col, crit] of Object.entries(criteria)) {
|
|
7966
|
-
if (!isSearchCondition(crit))
|
|
7967
|
-
continue;
|
|
7968
8379
|
const pos = keyPathPositions.get(col);
|
|
7969
8380
|
if (pos === undefined)
|
|
7970
8381
|
continue;
|
|
8382
|
+
if (pos < prefix.length)
|
|
8383
|
+
continue;
|
|
7971
8384
|
const valFromKey = Array.isArray(key) ? key[pos] : key;
|
|
7972
|
-
|
|
8385
|
+
const op = isSearchCondition(crit) ? crit.operator : "=";
|
|
8386
|
+
const val = isSearchCondition(crit) ? crit.value : crit;
|
|
8387
|
+
if (!compareWithOperator(valFromKey, op, val)) {
|
|
7973
8388
|
matches = false;
|
|
7974
8389
|
break;
|
|
7975
8390
|
}
|
|
@@ -8061,6 +8476,7 @@ import { createServiceToken as createServiceToken29 } from "@workglow/util";
|
|
|
8061
8476
|
var INDEXED_DB_RATE_LIMITER_STORAGE = createServiceToken29("ratelimiter.storage.indexedDb");
|
|
8062
8477
|
|
|
8063
8478
|
class IndexedDbRateLimiterStorage {
|
|
8479
|
+
scope = "process";
|
|
8064
8480
|
executionDb;
|
|
8065
8481
|
nextAvailableDb;
|
|
8066
8482
|
executionTableName;
|
|
@@ -8129,6 +8545,72 @@ class IndexedDbRateLimiterStorage {
|
|
|
8129
8545
|
];
|
|
8130
8546
|
this.nextAvailableDb = await ensureIndexedDbTable(this.nextAvailableTableName, buildKeyPath(["queue_name"]).join("_"), nextAvailableIndexes, this.migrationOptions);
|
|
8131
8547
|
}
|
|
8548
|
+
async tryReserveExecution(queueName, maxExecutions, windowMs) {
|
|
8549
|
+
const nextIso = await this.getNextAvailableTime(queueName);
|
|
8550
|
+
if (nextIso && new Date(nextIso).getTime() > Date.now()) {
|
|
8551
|
+
return null;
|
|
8552
|
+
}
|
|
8553
|
+
const execDb = await this.getExecutionDb();
|
|
8554
|
+
const prefixKeyValues = this.getPrefixKeyValues();
|
|
8555
|
+
const windowStartIso = new Date(Date.now() - windowMs).toISOString();
|
|
8556
|
+
const execTx = execDb.transaction(this.executionTableName, "readwrite");
|
|
8557
|
+
const execStore = execTx.objectStore(this.executionTableName);
|
|
8558
|
+
const insertedId = crypto.randomUUID();
|
|
8559
|
+
return new Promise((resolve, reject) => {
|
|
8560
|
+
let liveCount = 0;
|
|
8561
|
+
let didInsert = false;
|
|
8562
|
+
const liveRange = IDBKeyRange.bound([...prefixKeyValues, queueName, windowStartIso], [...prefixKeyValues, queueName, ""], true, false);
|
|
8563
|
+
const cursorReq = execStore.index("queue_executed_at").openCursor(liveRange);
|
|
8564
|
+
cursorReq.onsuccess = (event) => {
|
|
8565
|
+
const cursor = event.target.result;
|
|
8566
|
+
if (cursor) {
|
|
8567
|
+
const record2 = cursor.value;
|
|
8568
|
+
if (this.matchesPrefixes(record2)) {
|
|
8569
|
+
liveCount++;
|
|
8570
|
+
}
|
|
8571
|
+
cursor.continue();
|
|
8572
|
+
return;
|
|
8573
|
+
}
|
|
8574
|
+
if (liveCount >= maxExecutions) {
|
|
8575
|
+
execTx.abort();
|
|
8576
|
+
return;
|
|
8577
|
+
}
|
|
8578
|
+
const record = {
|
|
8579
|
+
id: insertedId,
|
|
8580
|
+
queue_name: queueName,
|
|
8581
|
+
executed_at: new Date().toISOString()
|
|
8582
|
+
};
|
|
8583
|
+
for (const [k, v] of Object.entries(this.prefixValues)) {
|
|
8584
|
+
record[k] = v;
|
|
8585
|
+
}
|
|
8586
|
+
const addReq = execStore.add(record);
|
|
8587
|
+
didInsert = true;
|
|
8588
|
+
addReq.onerror = () => {
|
|
8589
|
+
try {
|
|
8590
|
+
execTx.abort();
|
|
8591
|
+
} catch {}
|
|
8592
|
+
reject(addReq.error);
|
|
8593
|
+
};
|
|
8594
|
+
};
|
|
8595
|
+
cursorReq.onerror = () => reject(cursorReq.error);
|
|
8596
|
+
execTx.oncomplete = () => resolve(didInsert ? insertedId : null);
|
|
8597
|
+
execTx.onerror = () => reject(execTx.error);
|
|
8598
|
+
execTx.onabort = () => resolve(null);
|
|
8599
|
+
});
|
|
8600
|
+
}
|
|
8601
|
+
async releaseExecution(queueName, token) {
|
|
8602
|
+
if (token === null || token === undefined)
|
|
8603
|
+
return;
|
|
8604
|
+
const db = await this.getExecutionDb();
|
|
8605
|
+
const tx = db.transaction(this.executionTableName, "readwrite");
|
|
8606
|
+
const store = tx.objectStore(this.executionTableName);
|
|
8607
|
+
return new Promise((resolve, reject) => {
|
|
8608
|
+
const req = store.delete(token);
|
|
8609
|
+
req.onerror = () => reject(req.error);
|
|
8610
|
+
tx.oncomplete = () => resolve();
|
|
8611
|
+
tx.onerror = () => reject(tx.error);
|
|
8612
|
+
});
|
|
8613
|
+
}
|
|
8132
8614
|
async recordExecution(queueName) {
|
|
8133
8615
|
const db = await this.getExecutionDb();
|
|
8134
8616
|
const tx = db.transaction(this.executionTableName, "readwrite");
|
|
@@ -8278,11 +8760,12 @@ class IndexedDbRateLimiterStorage {
|
|
|
8278
8760
|
}
|
|
8279
8761
|
}
|
|
8280
8762
|
// src/queue/IndexedDbQueueStorage.ts
|
|
8281
|
-
import { createServiceToken as createServiceToken30, deepEqual as deepEqual5, makeFingerprint as makeFingerprint10, uuid4 as
|
|
8763
|
+
import { createServiceToken as createServiceToken30, deepEqual as deepEqual5, makeFingerprint as makeFingerprint10, uuid4 as uuid410 } from "@workglow/util";
|
|
8282
8764
|
var INDEXED_DB_QUEUE_STORAGE = createServiceToken30("jobqueue.storage.indexedDb");
|
|
8283
8765
|
|
|
8284
8766
|
class IndexedDbQueueStorage {
|
|
8285
8767
|
queueName;
|
|
8768
|
+
scope = "process";
|
|
8286
8769
|
db;
|
|
8287
8770
|
tableName;
|
|
8288
8771
|
migrationOptions;
|
|
@@ -8359,8 +8842,8 @@ class IndexedDbQueueStorage {
|
|
|
8359
8842
|
const db = await this.getDb();
|
|
8360
8843
|
const now = new Date().toISOString();
|
|
8361
8844
|
const jobWithPrefixes = job;
|
|
8362
|
-
jobWithPrefixes.id = jobWithPrefixes.id ??
|
|
8363
|
-
jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ??
|
|
8845
|
+
jobWithPrefixes.id = jobWithPrefixes.id ?? uuid410();
|
|
8846
|
+
jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ?? uuid410();
|
|
8364
8847
|
jobWithPrefixes.queue = this.queueName;
|
|
8365
8848
|
jobWithPrefixes.fingerprint = await makeFingerprint10(jobWithPrefixes.input);
|
|
8366
8849
|
jobWithPrefixes.status = JobStatus.PENDING;
|
|
@@ -8857,10 +9340,204 @@ class IndexedDbQueueStorage {
|
|
|
8857
9340
|
}
|
|
8858
9341
|
}
|
|
8859
9342
|
}
|
|
8860
|
-
// src/
|
|
9343
|
+
// src/tabular/SharedInMemoryTabularStorage.ts
|
|
8861
9344
|
import { createServiceToken as createServiceToken31 } from "@workglow/util";
|
|
9345
|
+
var SHARED_IN_MEMORY_TABULAR_REPOSITORY = createServiceToken31("storage.tabularRepository.sharedInMemory");
|
|
9346
|
+
var SYNC_TIMEOUT = 1000;
|
|
9347
|
+
var MAX_PENDING_MESSAGES = 1000;
|
|
9348
|
+
|
|
9349
|
+
class SharedInMemoryTabularStorage extends BaseTabularStorage {
|
|
9350
|
+
channel = null;
|
|
9351
|
+
channelName;
|
|
9352
|
+
inMemoryRepo;
|
|
9353
|
+
isInitialized = false;
|
|
9354
|
+
syncInProgress = false;
|
|
9355
|
+
pendingMessages = [];
|
|
9356
|
+
constructor(channelName = "tabular_store", schema, primaryKeyNames, indexes = [], clientProvidedKeys = "if-missing") {
|
|
9357
|
+
super(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
9358
|
+
this.channelName = channelName;
|
|
9359
|
+
this.inMemoryRepo = new InMemoryTabularStorage(schema, primaryKeyNames, indexes, clientProvidedKeys);
|
|
9360
|
+
this.setupEventForwarding();
|
|
9361
|
+
this.initializeBroadcastChannel();
|
|
9362
|
+
}
|
|
9363
|
+
isBroadcastChannelAvailable() {
|
|
9364
|
+
return typeof BroadcastChannel !== "undefined";
|
|
9365
|
+
}
|
|
9366
|
+
initializeBroadcastChannel() {
|
|
9367
|
+
if (!this.isBroadcastChannelAvailable()) {
|
|
9368
|
+
console.warn("BroadcastChannel is not available. Tab synchronization will not work.");
|
|
9369
|
+
return;
|
|
9370
|
+
}
|
|
9371
|
+
try {
|
|
9372
|
+
this.channel = new BroadcastChannel(this.channelName);
|
|
9373
|
+
this.channel.onmessage = (event) => {
|
|
9374
|
+
this.handleBroadcastMessage(event.data);
|
|
9375
|
+
};
|
|
9376
|
+
this.syncFromOtherTabs();
|
|
9377
|
+
} catch (error) {
|
|
9378
|
+
console.error("Failed to initialize BroadcastChannel:", error);
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
setupEventForwarding() {
|
|
9382
|
+
this.inMemoryRepo.on("put", (entity) => {
|
|
9383
|
+
this.events.emit("put", entity);
|
|
9384
|
+
});
|
|
9385
|
+
this.inMemoryRepo.on("get", (key, entity) => {
|
|
9386
|
+
this.events.emit("get", key, entity);
|
|
9387
|
+
});
|
|
9388
|
+
this.inMemoryRepo.on("query", (key, entities) => {
|
|
9389
|
+
this.events.emit("query", key, entities);
|
|
9390
|
+
});
|
|
9391
|
+
this.inMemoryRepo.on("delete", (key) => {
|
|
9392
|
+
this.events.emit("delete", key);
|
|
9393
|
+
});
|
|
9394
|
+
this.inMemoryRepo.on("clearall", () => {
|
|
9395
|
+
this.events.emit("clearall");
|
|
9396
|
+
});
|
|
9397
|
+
}
|
|
9398
|
+
async handleBroadcastMessage(message) {
|
|
9399
|
+
if (this.syncInProgress && message.type !== "SYNC_RESPONSE") {
|
|
9400
|
+
if (this.pendingMessages.length < MAX_PENDING_MESSAGES) {
|
|
9401
|
+
this.pendingMessages.push(message);
|
|
9402
|
+
}
|
|
9403
|
+
return;
|
|
9404
|
+
}
|
|
9405
|
+
switch (message.type) {
|
|
9406
|
+
case "SYNC_REQUEST":
|
|
9407
|
+
const all = await this.inMemoryRepo.getAll();
|
|
9408
|
+
if (this.channel && all) {
|
|
9409
|
+
this.channel.postMessage({
|
|
9410
|
+
type: "SYNC_RESPONSE",
|
|
9411
|
+
data: all
|
|
9412
|
+
});
|
|
9413
|
+
}
|
|
9414
|
+
break;
|
|
9415
|
+
case "SYNC_RESPONSE":
|
|
9416
|
+
if (message.data && Array.isArray(message.data)) {
|
|
9417
|
+
await this.copyDataFromArray(message.data);
|
|
9418
|
+
}
|
|
9419
|
+
this.syncInProgress = false;
|
|
9420
|
+
await this.drainPendingMessages();
|
|
9421
|
+
break;
|
|
9422
|
+
case "PUT":
|
|
9423
|
+
await this.inMemoryRepo.put(message.entity);
|
|
9424
|
+
break;
|
|
9425
|
+
case "PUT_BULK":
|
|
9426
|
+
await this.inMemoryRepo.putBulk(message.entities);
|
|
9427
|
+
break;
|
|
9428
|
+
case "DELETE":
|
|
9429
|
+
await this.inMemoryRepo.delete(message.key);
|
|
9430
|
+
break;
|
|
9431
|
+
case "DELETE_ALL":
|
|
9432
|
+
await this.inMemoryRepo.deleteAll();
|
|
9433
|
+
break;
|
|
9434
|
+
case "DELETE_SEARCH":
|
|
9435
|
+
await this.inMemoryRepo.deleteSearch(message.criteria);
|
|
9436
|
+
break;
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
async drainPendingMessages() {
|
|
9440
|
+
while (!this.syncInProgress && this.pendingMessages.length > 0) {
|
|
9441
|
+
const messages = this.pendingMessages;
|
|
9442
|
+
this.pendingMessages = [];
|
|
9443
|
+
for (const message of messages) {
|
|
9444
|
+
await this.handleBroadcastMessage(message);
|
|
9445
|
+
if (this.syncInProgress) {
|
|
9446
|
+
break;
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9449
|
+
}
|
|
9450
|
+
}
|
|
9451
|
+
syncFromOtherTabs() {
|
|
9452
|
+
if (!this.channel)
|
|
9453
|
+
return;
|
|
9454
|
+
this.syncInProgress = true;
|
|
9455
|
+
this.channel.postMessage({ type: "SYNC_REQUEST" });
|
|
9456
|
+
setTimeout(() => {
|
|
9457
|
+
if (this.syncInProgress) {
|
|
9458
|
+
this.syncInProgress = false;
|
|
9459
|
+
this.drainPendingMessages().catch((error) => {
|
|
9460
|
+
console.error("Failed to drain pending messages after sync timeout", error);
|
|
9461
|
+
});
|
|
9462
|
+
}
|
|
9463
|
+
}, SYNC_TIMEOUT);
|
|
9464
|
+
}
|
|
9465
|
+
async copyDataFromArray(entities) {
|
|
9466
|
+
if (entities.length === 0)
|
|
9467
|
+
return;
|
|
9468
|
+
await this.inMemoryRepo.deleteAll();
|
|
9469
|
+
await this.inMemoryRepo.putBulk(entities);
|
|
9470
|
+
}
|
|
9471
|
+
broadcast(message) {
|
|
9472
|
+
if (this.channel) {
|
|
9473
|
+
this.channel.postMessage(message);
|
|
9474
|
+
}
|
|
9475
|
+
}
|
|
9476
|
+
async setupDatabase() {
|
|
9477
|
+
if (this.isInitialized)
|
|
9478
|
+
return;
|
|
9479
|
+
this.isInitialized = true;
|
|
9480
|
+
await this.syncFromOtherTabs();
|
|
9481
|
+
}
|
|
9482
|
+
async put(value) {
|
|
9483
|
+
const result = await this.inMemoryRepo.put(value);
|
|
9484
|
+
this.broadcast({ type: "PUT", entity: result });
|
|
9485
|
+
return result;
|
|
9486
|
+
}
|
|
9487
|
+
async putBulk(values) {
|
|
9488
|
+
const result = await this.inMemoryRepo.putBulk(values);
|
|
9489
|
+
this.broadcast({ type: "PUT_BULK", entities: result });
|
|
9490
|
+
return result;
|
|
9491
|
+
}
|
|
9492
|
+
async get(key) {
|
|
9493
|
+
return await this.inMemoryRepo.get(key);
|
|
9494
|
+
}
|
|
9495
|
+
async delete(value) {
|
|
9496
|
+
await this.inMemoryRepo.delete(value);
|
|
9497
|
+
const { key } = this.separateKeyValueFromCombined(value);
|
|
9498
|
+
this.broadcast({ type: "DELETE", key });
|
|
9499
|
+
}
|
|
9500
|
+
async deleteAll() {
|
|
9501
|
+
await this.inMemoryRepo.deleteAll();
|
|
9502
|
+
this.broadcast({ type: "DELETE_ALL" });
|
|
9503
|
+
}
|
|
9504
|
+
async getAll(options) {
|
|
9505
|
+
return await this.inMemoryRepo.getAll(options);
|
|
9506
|
+
}
|
|
9507
|
+
async size() {
|
|
9508
|
+
return await this.inMemoryRepo.size();
|
|
9509
|
+
}
|
|
9510
|
+
async getBulk(offset, limit) {
|
|
9511
|
+
return await this.inMemoryRepo.getBulk(offset, limit);
|
|
9512
|
+
}
|
|
9513
|
+
async query(criteria, options) {
|
|
9514
|
+
return await this.inMemoryRepo.query(criteria, options);
|
|
9515
|
+
}
|
|
9516
|
+
async queryIndex(criteria, options) {
|
|
9517
|
+
return await this.inMemoryRepo.queryIndex(criteria, options);
|
|
9518
|
+
}
|
|
9519
|
+
async deleteSearch(criteria) {
|
|
9520
|
+
await this.inMemoryRepo.deleteSearch(criteria);
|
|
9521
|
+
this.broadcast({
|
|
9522
|
+
type: "DELETE_SEARCH",
|
|
9523
|
+
criteria
|
|
9524
|
+
});
|
|
9525
|
+
}
|
|
9526
|
+
subscribeToChanges(callback, options) {
|
|
9527
|
+
return this.inMemoryRepo.subscribeToChanges(callback, options);
|
|
9528
|
+
}
|
|
9529
|
+
destroy() {
|
|
9530
|
+
if (this.channel) {
|
|
9531
|
+
this.channel.close();
|
|
9532
|
+
this.channel = null;
|
|
9533
|
+
}
|
|
9534
|
+
this.inMemoryRepo.destroy();
|
|
9535
|
+
}
|
|
9536
|
+
}
|
|
9537
|
+
// src/vector/IndexedDbVectorStorage.ts
|
|
9538
|
+
import { createServiceToken as createServiceToken32 } from "@workglow/util";
|
|
8862
9539
|
import { cosineSimilarity as cosineSimilarity5 } from "@workglow/util/schema";
|
|
8863
|
-
var IDB_VECTOR_REPOSITORY =
|
|
9540
|
+
var IDB_VECTOR_REPOSITORY = createServiceToken32("storage.vectorRepository.indexedDb");
|
|
8864
9541
|
function matchesFilter4(metadata, filter) {
|
|
8865
9542
|
for (const [key, value] of Object.entries(filter)) {
|
|
8866
9543
|
if (metadata[key] !== value) {
|
|
@@ -8988,6 +9665,7 @@ export {
|
|
|
8988
9665
|
SqliteQueueStorage,
|
|
8989
9666
|
SqliteKvStorage,
|
|
8990
9667
|
SqliteAiVectorStorage,
|
|
9668
|
+
SharedInMemoryTabularStorage,
|
|
8991
9669
|
SUPABASE_TABULAR_REPOSITORY,
|
|
8992
9670
|
SUPABASE_RATE_LIMITER_STORAGE,
|
|
8993
9671
|
SUPABASE_QUEUE_STORAGE,
|
|
@@ -8996,6 +9674,7 @@ export {
|
|
|
8996
9674
|
SQLITE_RATE_LIMITER_STORAGE,
|
|
8997
9675
|
SQLITE_QUEUE_STORAGE,
|
|
8998
9676
|
SQLITE_KV_REPOSITORY,
|
|
9677
|
+
SHARED_IN_MEMORY_TABULAR_REPOSITORY,
|
|
8999
9678
|
RATE_LIMITER_STORAGE,
|
|
9000
9679
|
QUEUE_STORAGE,
|
|
9001
9680
|
PostgresVectorStorage,
|
|
@@ -9050,4 +9729,4 @@ export {
|
|
|
9050
9729
|
BaseTabularStorage
|
|
9051
9730
|
};
|
|
9052
9731
|
|
|
9053
|
-
//# debugId=
|
|
9732
|
+
//# debugId=7AF69C59EB644D3464756E2164756E21
|