@workglow/indexeddb 0.2.28

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 (37) hide show
  1. package/dist/job-queue/IndexedDbQueueStorage.d.ts +167 -0
  2. package/dist/job-queue/IndexedDbQueueStorage.d.ts.map +1 -0
  3. package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts +79 -0
  4. package/dist/job-queue/IndexedDbRateLimiterStorage.d.ts.map +1 -0
  5. package/dist/job-queue/browser.d.ts +7 -0
  6. package/dist/job-queue/browser.d.ts.map +1 -0
  7. package/dist/job-queue/browser.js +1195 -0
  8. package/dist/job-queue/browser.js.map +12 -0
  9. package/dist/job-queue/bun.d.ts +7 -0
  10. package/dist/job-queue/bun.d.ts.map +1 -0
  11. package/dist/job-queue/common.d.ts +8 -0
  12. package/dist/job-queue/common.d.ts.map +1 -0
  13. package/dist/job-queue/node.d.ts +7 -0
  14. package/dist/job-queue/node.d.ts.map +1 -0
  15. package/dist/job-queue/node.js +1195 -0
  16. package/dist/job-queue/node.js.map +12 -0
  17. package/dist/storage/IndexedDbKvStorage.d.ts +26 -0
  18. package/dist/storage/IndexedDbKvStorage.d.ts.map +1 -0
  19. package/dist/storage/IndexedDbTable.d.ts +40 -0
  20. package/dist/storage/IndexedDbTable.d.ts.map +1 -0
  21. package/dist/storage/IndexedDbTabularStorage.d.ts +198 -0
  22. package/dist/storage/IndexedDbTabularStorage.d.ts.map +1 -0
  23. package/dist/storage/IndexedDbVectorStorage.d.ts +52 -0
  24. package/dist/storage/IndexedDbVectorStorage.d.ts.map +1 -0
  25. package/dist/storage/browser.d.ts +7 -0
  26. package/dist/storage/browser.d.ts.map +1 -0
  27. package/dist/storage/browser.js +1182 -0
  28. package/dist/storage/browser.js.map +13 -0
  29. package/dist/storage/bun.d.ts +7 -0
  30. package/dist/storage/bun.d.ts.map +1 -0
  31. package/dist/storage/common.d.ts +10 -0
  32. package/dist/storage/common.d.ts.map +1 -0
  33. package/dist/storage/node.d.ts +7 -0
  34. package/dist/storage/node.d.ts.map +1 -0
  35. package/dist/storage/node.js +1182 -0
  36. package/dist/storage/node.js.map +13 -0
  37. package/package.json +74 -0
@@ -0,0 +1,1195 @@
1
+ // src/job-queue/IndexedDbQueueStorage.ts
2
+ import { createServiceToken, deepEqual as deepEqual2, makeFingerprint, uuid4 } from "@workglow/util";
3
+ import { HybridSubscriptionManager } from "@workglow/storage";
4
+
5
+ // src/storage/IndexedDbTable.ts
6
+ import { deepEqual } from "@workglow/util";
7
+ var METADATA_STORE_NAME = "__schema_metadata__";
8
+ async function saveSchemaMetadata(db, tableName, snapshot) {
9
+ return new Promise((resolve, reject) => {
10
+ try {
11
+ const transaction = db.transaction(METADATA_STORE_NAME, "readwrite");
12
+ const store = transaction.objectStore(METADATA_STORE_NAME);
13
+ const request = store.put({ ...snapshot, tableName }, tableName);
14
+ request.onsuccess = () => resolve();
15
+ request.onerror = () => reject(request.error);
16
+ transaction.onerror = () => reject(transaction.error);
17
+ } catch (err) {
18
+ resolve();
19
+ }
20
+ });
21
+ }
22
+ async function openIndexedDbTable(tableName, version, upgradeNeededCallback) {
23
+ return new Promise((resolve, reject) => {
24
+ const openRequest = indexedDB.open(tableName, version);
25
+ openRequest.onsuccess = (event) => {
26
+ const db = event.target.result;
27
+ db.onversionchange = () => {
28
+ db.close();
29
+ };
30
+ resolve(db);
31
+ };
32
+ openRequest.onupgradeneeded = (event) => {
33
+ if (upgradeNeededCallback) {
34
+ upgradeNeededCallback(event);
35
+ }
36
+ };
37
+ openRequest.onerror = () => {
38
+ const error = openRequest.error;
39
+ if (error && error.name === "VersionError") {
40
+ reject(new Error(`Database ${tableName} exists at a higher version. Cannot open at version ${version || "current"}.`));
41
+ } else {
42
+ reject(error);
43
+ }
44
+ };
45
+ openRequest.onblocked = () => {
46
+ reject(new Error(`Database ${tableName} is blocked. Close all other tabs using this database.`));
47
+ };
48
+ });
49
+ }
50
+ async function deleteIndexedDbTable(tableName) {
51
+ return new Promise((resolve, reject) => {
52
+ const deleteRequest = indexedDB.deleteDatabase(tableName);
53
+ deleteRequest.onsuccess = () => resolve();
54
+ deleteRequest.onerror = () => reject(deleteRequest.error);
55
+ deleteRequest.onblocked = () => {
56
+ reject(new Error(`Cannot delete database ${tableName}. Close all other tabs using this database.`));
57
+ };
58
+ });
59
+ }
60
+ function compareSchemas(store, expectedPrimaryKey, expectedIndexes) {
61
+ const diff = {
62
+ indexesToAdd: [],
63
+ indexesToRemove: [],
64
+ indexesToModify: [],
65
+ primaryKeyChanged: false,
66
+ needsObjectStoreRecreation: false
67
+ };
68
+ const actualKeyPath = store.keyPath;
69
+ const normalizedExpected = Array.isArray(expectedPrimaryKey) ? expectedPrimaryKey : expectedPrimaryKey;
70
+ const normalizedActual = Array.isArray(actualKeyPath) ? actualKeyPath : actualKeyPath;
71
+ if (!deepEqual(normalizedExpected, normalizedActual)) {
72
+ diff.primaryKeyChanged = true;
73
+ diff.needsObjectStoreRecreation = true;
74
+ return diff;
75
+ }
76
+ const existingIndexes = new Map;
77
+ for (let i = 0;i < store.indexNames.length; i++) {
78
+ const indexName = store.indexNames[i];
79
+ existingIndexes.set(indexName, store.index(indexName));
80
+ }
81
+ for (const expectedIdx of expectedIndexes) {
82
+ const existingIdx = existingIndexes.get(expectedIdx.name);
83
+ if (!existingIdx) {
84
+ diff.indexesToAdd.push(expectedIdx);
85
+ } else {
86
+ const expectedKeyPath = Array.isArray(expectedIdx.keyPath) ? expectedIdx.keyPath : [expectedIdx.keyPath];
87
+ const actualKeyPath2 = Array.isArray(existingIdx.keyPath) ? existingIdx.keyPath : [existingIdx.keyPath];
88
+ const keyPathChanged = !deepEqual(expectedKeyPath, actualKeyPath2);
89
+ const uniqueChanged = existingIdx.unique !== (expectedIdx.options?.unique ?? false);
90
+ const multiEntryChanged = existingIdx.multiEntry !== (expectedIdx.options?.multiEntry ?? false);
91
+ if (keyPathChanged || uniqueChanged || multiEntryChanged) {
92
+ diff.indexesToModify.push(expectedIdx);
93
+ }
94
+ existingIndexes.delete(expectedIdx.name);
95
+ }
96
+ }
97
+ diff.indexesToRemove = Array.from(existingIndexes.keys());
98
+ return diff;
99
+ }
100
+ async function readAllData(store) {
101
+ return new Promise((resolve, reject) => {
102
+ const request = store.getAll();
103
+ request.onsuccess = () => resolve(request.result || []);
104
+ request.onerror = () => reject(request.error);
105
+ });
106
+ }
107
+ async function performIncrementalMigration(db, tableName, diff, options = {}) {
108
+ const currentVersion = db.version;
109
+ const newVersion = currentVersion + 1;
110
+ db.close();
111
+ options.onMigrationProgress?.(`Migrating ${tableName} from version ${currentVersion} to ${newVersion}...`, 0);
112
+ return openIndexedDbTable(tableName, newVersion, (event) => {
113
+ const transaction = event.target.transaction;
114
+ const store = transaction.objectStore(tableName);
115
+ for (const indexName of diff.indexesToRemove) {
116
+ options.onMigrationProgress?.(`Removing index: ${indexName}`, 0.2);
117
+ store.deleteIndex(indexName);
118
+ }
119
+ for (const indexDef of diff.indexesToModify) {
120
+ options.onMigrationProgress?.(`Updating index: ${indexDef.name}`, 0.4);
121
+ if (store.indexNames.contains(indexDef.name)) {
122
+ store.deleteIndex(indexDef.name);
123
+ }
124
+ store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);
125
+ }
126
+ for (const indexDef of diff.indexesToAdd) {
127
+ options.onMigrationProgress?.(`Adding index: ${indexDef.name}`, 0.6);
128
+ store.createIndex(indexDef.name, indexDef.keyPath, indexDef.options);
129
+ }
130
+ options.onMigrationProgress?.(`Migration complete`, 1);
131
+ });
132
+ }
133
+ async function performDestructiveMigration(db, tableName, primaryKey, expectedIndexes, options = {}, autoIncrement = false) {
134
+ if (!options.allowDestructiveMigration) {
135
+ throw new Error(`Destructive migration required for ${tableName} but not allowed. ` + `Primary key has changed. Set allowDestructiveMigration=true to proceed with data loss, ` + `or provide a dataTransformer to migrate data.`);
136
+ }
137
+ const currentVersion = db.version;
138
+ const newVersion = currentVersion + 1;
139
+ options.onMigrationProgress?.(`Performing destructive migration of ${tableName}. Reading existing data...`, 0);
140
+ let existingData = [];
141
+ try {
142
+ const transaction = db.transaction(tableName, "readonly");
143
+ const store = transaction.objectStore(tableName);
144
+ existingData = await readAllData(store);
145
+ options.onMigrationProgress?.(`Read ${existingData.length} records`, 0.3);
146
+ } catch (err) {
147
+ options.onMigrationWarning?.(`Failed to read existing data during migration: ${err}`, err);
148
+ }
149
+ db.close();
150
+ if (options.dataTransformer && existingData.length > 0) {
151
+ options.onMigrationProgress?.(`Transforming ${existingData.length} records...`, 0.4);
152
+ try {
153
+ const transformed = [];
154
+ for (let i = 0;i < existingData.length; i++) {
155
+ const record = existingData[i];
156
+ const transformedRecord = await options.dataTransformer(record);
157
+ if (transformedRecord !== undefined && transformedRecord !== null) {
158
+ transformed.push(transformedRecord);
159
+ }
160
+ if (i % 100 === 0) {
161
+ options.onMigrationProgress?.(`Transformed ${i}/${existingData.length} records`, 0.4 + i / existingData.length * 0.3);
162
+ }
163
+ }
164
+ existingData = transformed;
165
+ options.onMigrationProgress?.(`Transformation complete: ${existingData.length} records`, 0.7);
166
+ } catch (err) {
167
+ options.onMigrationWarning?.(`Data transformation failed: ${err}. Some data may be lost.`, err);
168
+ existingData = [];
169
+ }
170
+ }
171
+ options.onMigrationProgress?.(`Recreating object store...`, 0.75);
172
+ const newDb = await openIndexedDbTable(tableName, newVersion, (event) => {
173
+ const db2 = event.target.result;
174
+ if (db2.objectStoreNames.contains(tableName)) {
175
+ db2.deleteObjectStore(tableName);
176
+ }
177
+ const store = db2.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });
178
+ for (const idx of expectedIndexes) {
179
+ store.createIndex(idx.name, idx.keyPath, idx.options);
180
+ }
181
+ if (existingData.length > 0) {
182
+ options.onMigrationProgress?.(`Restoring ${existingData.length} records...`, 0.8);
183
+ for (const record of existingData) {
184
+ try {
185
+ store.put(record);
186
+ } catch (err) {
187
+ options.onMigrationWarning?.(`Failed to restore record: ${err}`, err);
188
+ }
189
+ }
190
+ }
191
+ });
192
+ options.onMigrationProgress?.(`Destructive migration complete`, 1);
193
+ return newDb;
194
+ }
195
+ async function createNewDatabase(tableName, primaryKey, expectedIndexes, options = {}, autoIncrement = false) {
196
+ options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);
197
+ try {
198
+ await deleteIndexedDbTable(tableName);
199
+ await new Promise((resolve) => setTimeout(resolve, 50));
200
+ } catch (err) {}
201
+ const version = 1;
202
+ const db = await openIndexedDbTable(tableName, version, (event) => {
203
+ const db2 = event.target.result;
204
+ if (!db2.objectStoreNames.contains(METADATA_STORE_NAME)) {
205
+ db2.createObjectStore(METADATA_STORE_NAME, { keyPath: "tableName" });
206
+ }
207
+ const store = db2.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });
208
+ for (const idx of expectedIndexes) {
209
+ store.createIndex(idx.name, idx.keyPath, idx.options);
210
+ }
211
+ });
212
+ const snapshot = {
213
+ version: db.version,
214
+ primaryKey,
215
+ indexes: expectedIndexes,
216
+ recordCount: 0,
217
+ timestamp: Date.now()
218
+ };
219
+ await saveSchemaMetadata(db, tableName, snapshot);
220
+ options.onMigrationProgress?.(`Database created successfully`, 1);
221
+ return db;
222
+ }
223
+ async function ensureIndexedDbTable(tableName, primaryKey, expectedIndexes = [], options = {}, autoIncrement = false) {
224
+ try {
225
+ let db;
226
+ let wasJustCreated = false;
227
+ try {
228
+ db = await openIndexedDbTable(tableName);
229
+ if (db.version === 1 && !db.objectStoreNames.contains(tableName)) {
230
+ wasJustCreated = true;
231
+ db.close();
232
+ }
233
+ } catch (err) {
234
+ options.onMigrationProgress?.(`Database ${tableName} does not exist or has version conflict, creating...`, 0);
235
+ return await createNewDatabase(tableName, primaryKey, expectedIndexes, options, autoIncrement);
236
+ }
237
+ if (wasJustCreated) {
238
+ options.onMigrationProgress?.(`Creating new database: ${tableName}`, 0);
239
+ try {
240
+ await deleteIndexedDbTable(tableName);
241
+ await new Promise((resolve) => setTimeout(resolve, 50));
242
+ } catch (err) {}
243
+ db = await openIndexedDbTable(tableName, 1, (event) => {
244
+ const db2 = event.target.result;
245
+ if (!db2.objectStoreNames.contains(METADATA_STORE_NAME)) {
246
+ db2.createObjectStore(METADATA_STORE_NAME, { keyPath: "tableName" });
247
+ }
248
+ const store2 = db2.createObjectStore(tableName, { keyPath: primaryKey, autoIncrement });
249
+ for (const idx of expectedIndexes) {
250
+ store2.createIndex(idx.name, idx.keyPath, idx.options);
251
+ }
252
+ });
253
+ const snapshot2 = {
254
+ version: db.version,
255
+ primaryKey,
256
+ indexes: expectedIndexes,
257
+ recordCount: 0,
258
+ timestamp: Date.now()
259
+ };
260
+ await saveSchemaMetadata(db, tableName, snapshot2);
261
+ options.onMigrationProgress?.(`Database created successfully`, 1);
262
+ return db;
263
+ }
264
+ if (!db.objectStoreNames.contains(METADATA_STORE_NAME)) {
265
+ const currentVersion = db.version;
266
+ db.close();
267
+ db = await openIndexedDbTable(tableName, currentVersion + 1, (event) => {
268
+ const db2 = event.target.result;
269
+ if (!db2.objectStoreNames.contains(METADATA_STORE_NAME)) {
270
+ db2.createObjectStore(METADATA_STORE_NAME, { keyPath: "tableName" });
271
+ }
272
+ });
273
+ }
274
+ if (!db.objectStoreNames.contains(tableName)) {
275
+ options.onMigrationProgress?.(`Object store ${tableName} does not exist, creating...`, 0);
276
+ db.close();
277
+ return await createNewDatabase(tableName, primaryKey, expectedIndexes, options, autoIncrement);
278
+ }
279
+ const transaction = db.transaction(tableName, "readonly");
280
+ const store = transaction.objectStore(tableName);
281
+ const diff = compareSchemas(store, primaryKey, expectedIndexes);
282
+ await new Promise((resolve) => {
283
+ transaction.oncomplete = () => resolve();
284
+ transaction.onerror = () => resolve();
285
+ });
286
+ const needsMigration = diff.indexesToAdd.length > 0 || diff.indexesToRemove.length > 0 || diff.indexesToModify.length > 0 || diff.needsObjectStoreRecreation;
287
+ if (!needsMigration) {
288
+ options.onMigrationProgress?.(`Schema for ${tableName} is up to date`, 1);
289
+ const snapshot2 = {
290
+ version: db.version,
291
+ primaryKey,
292
+ indexes: expectedIndexes,
293
+ timestamp: Date.now()
294
+ };
295
+ await saveSchemaMetadata(db, tableName, snapshot2);
296
+ return db;
297
+ }
298
+ if (diff.needsObjectStoreRecreation) {
299
+ options.onMigrationProgress?.(`Schema change requires object store recreation for ${tableName}`, 0);
300
+ db = await performDestructiveMigration(db, tableName, primaryKey, expectedIndexes, options, autoIncrement);
301
+ } else {
302
+ options.onMigrationProgress?.(`Performing incremental migration for ${tableName}`, 0);
303
+ db = await performIncrementalMigration(db, tableName, diff, options);
304
+ }
305
+ const snapshot = {
306
+ version: db.version,
307
+ primaryKey,
308
+ indexes: expectedIndexes,
309
+ timestamp: Date.now()
310
+ };
311
+ await saveSchemaMetadata(db, tableName, snapshot);
312
+ return db;
313
+ } catch (err) {
314
+ options.onMigrationWarning?.(`Migration failed for ${tableName}: ${err}`, err);
315
+ throw err;
316
+ }
317
+ }
318
+
319
+ // src/job-queue/IndexedDbQueueStorage.ts
320
+ import { JobStatus } from "@workglow/job-queue";
321
+ var INDEXED_DB_QUEUE_STORAGE = createServiceToken("jobqueue.storage.indexedDb");
322
+
323
+ class IndexedDbQueueStorage {
324
+ queueName;
325
+ scope = "process";
326
+ db;
327
+ tableName;
328
+ migrationOptions;
329
+ prefixes;
330
+ prefixValues;
331
+ hybridManager = null;
332
+ hybridOptions;
333
+ constructor(queueName, options = {}) {
334
+ this.queueName = queueName;
335
+ this.migrationOptions = options;
336
+ this.prefixes = options.prefixes ?? [];
337
+ this.prefixValues = options.prefixValues ?? {};
338
+ this.hybridOptions = {
339
+ useBroadcastChannel: options.useBroadcastChannel ?? true,
340
+ backupPollingIntervalMs: options.backupPollingIntervalMs ?? 5000
341
+ };
342
+ if (this.prefixes.length > 0) {
343
+ const prefixNames = this.prefixes.map((p) => p.name).join("_");
344
+ this.tableName = `jobs_${prefixNames}`;
345
+ } else {
346
+ this.tableName = "jobs";
347
+ }
348
+ }
349
+ getPrefixColumnNames() {
350
+ return this.prefixes.map((p) => p.name);
351
+ }
352
+ matchesPrefixes(job) {
353
+ for (const [key, value] of Object.entries(this.prefixValues)) {
354
+ if (job[key] !== value) {
355
+ return false;
356
+ }
357
+ }
358
+ return true;
359
+ }
360
+ getPrefixKeyValues() {
361
+ return this.prefixes.map((p) => this.prefixValues[p.name]);
362
+ }
363
+ async getDb() {
364
+ if (this.db)
365
+ return this.db;
366
+ await this.setupDatabase();
367
+ return this.db;
368
+ }
369
+ async setupDatabase() {
370
+ const prefixColumnNames = this.getPrefixColumnNames();
371
+ const buildKeyPath = (basePath) => {
372
+ return [...prefixColumnNames, ...basePath];
373
+ };
374
+ const expectedIndexes = [
375
+ {
376
+ name: "queue_status",
377
+ keyPath: buildKeyPath(["queue", "status"]),
378
+ options: { unique: false }
379
+ },
380
+ {
381
+ name: "queue_status_run_after",
382
+ keyPath: buildKeyPath(["queue", "status", "run_after"]),
383
+ options: { unique: false }
384
+ },
385
+ {
386
+ name: "queue_job_run_id",
387
+ keyPath: buildKeyPath(["queue", "job_run_id"]),
388
+ options: { unique: false }
389
+ },
390
+ {
391
+ name: "queue_fingerprint_status",
392
+ keyPath: buildKeyPath(["queue", "fingerprint", "status"]),
393
+ options: { unique: false }
394
+ }
395
+ ];
396
+ this.db = await ensureIndexedDbTable(this.tableName, "id", expectedIndexes, this.migrationOptions);
397
+ }
398
+ async add(job) {
399
+ const db = await this.getDb();
400
+ const now = new Date().toISOString();
401
+ const jobWithPrefixes = job;
402
+ jobWithPrefixes.id = jobWithPrefixes.id ?? uuid4();
403
+ jobWithPrefixes.job_run_id = jobWithPrefixes.job_run_id ?? uuid4();
404
+ jobWithPrefixes.queue = this.queueName;
405
+ jobWithPrefixes.fingerprint = await makeFingerprint(jobWithPrefixes.input);
406
+ jobWithPrefixes.status = JobStatus.PENDING;
407
+ jobWithPrefixes.progress = 0;
408
+ jobWithPrefixes.progress_message = "";
409
+ jobWithPrefixes.progress_details = null;
410
+ jobWithPrefixes.created_at = now;
411
+ jobWithPrefixes.run_after = now;
412
+ for (const [key, value] of Object.entries(this.prefixValues)) {
413
+ jobWithPrefixes[key] = value;
414
+ }
415
+ const tx = db.transaction(this.tableName, "readwrite");
416
+ const store = tx.objectStore(this.tableName);
417
+ return new Promise((resolve, reject) => {
418
+ const request = store.add(jobWithPrefixes);
419
+ tx.oncomplete = () => {
420
+ this.hybridManager?.notifyLocalChange();
421
+ resolve(jobWithPrefixes.id);
422
+ };
423
+ tx.onerror = () => reject(tx.error);
424
+ request.onerror = () => reject(request.error);
425
+ });
426
+ }
427
+ async get(id) {
428
+ const db = await this.getDb();
429
+ const tx = db.transaction(this.tableName, "readonly");
430
+ const store = tx.objectStore(this.tableName);
431
+ const request = store.get(id);
432
+ return new Promise((resolve, reject) => {
433
+ request.onsuccess = () => {
434
+ const job = request.result;
435
+ if (job && job.queue === this.queueName && this.matchesPrefixes(job)) {
436
+ resolve(job);
437
+ } else {
438
+ resolve(undefined);
439
+ }
440
+ };
441
+ request.onerror = () => reject(request.error);
442
+ tx.onerror = () => reject(tx.error);
443
+ });
444
+ }
445
+ async peek(status = JobStatus.PENDING, num = 100) {
446
+ const db = await this.getDb();
447
+ const tx = db.transaction(this.tableName, "readonly");
448
+ const store = tx.objectStore(this.tableName);
449
+ const index = store.index("queue_status_run_after");
450
+ const prefixKeyValues = this.getPrefixKeyValues();
451
+ return new Promise((resolve, reject) => {
452
+ const ret = new Map;
453
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, this.queueName, status, ""], [...prefixKeyValues, this.queueName, status, "￿"]);
454
+ const cursorRequest = index.openCursor(keyRange);
455
+ const handleCursor = (e) => {
456
+ const cursor = e.target.result;
457
+ if (!cursor || ret.size >= num) {
458
+ resolve(Array.from(ret.values()));
459
+ return;
460
+ }
461
+ const job = cursor.value;
462
+ if (this.matchesPrefixes(job)) {
463
+ ret.set(cursor.value.id, cursor.value);
464
+ }
465
+ cursor.continue();
466
+ };
467
+ cursorRequest.onsuccess = handleCursor;
468
+ cursorRequest.onerror = () => reject(cursorRequest.error);
469
+ tx.onerror = () => reject(tx.error);
470
+ });
471
+ }
472
+ async next(workerId) {
473
+ const db = await this.getDb();
474
+ const tx = db.transaction(this.tableName, "readwrite");
475
+ const store = tx.objectStore(this.tableName);
476
+ const index = store.index("queue_status_run_after");
477
+ const now = new Date().toISOString();
478
+ const prefixKeyValues = this.getPrefixKeyValues();
479
+ const claimToken = workerId;
480
+ const jobToReturn = await new Promise((resolve, reject) => {
481
+ const cursorRequest = index.openCursor(IDBKeyRange.bound([...prefixKeyValues, this.queueName, JobStatus.PENDING, ""], [...prefixKeyValues, this.queueName, JobStatus.PENDING, now], false, false));
482
+ let claimedJob;
483
+ let cursorStopped = false;
484
+ cursorRequest.onsuccess = (e) => {
485
+ const cursor = e.target.result;
486
+ if (!cursor) {
487
+ return;
488
+ }
489
+ if (cursorStopped) {
490
+ return;
491
+ }
492
+ const job = cursor.value;
493
+ if (job.queue !== this.queueName || job.status !== JobStatus.PENDING || !this.matchesPrefixes(job)) {
494
+ cursor.continue();
495
+ return;
496
+ }
497
+ job.status = JobStatus.PROCESSING;
498
+ job.last_ran_at = now;
499
+ job.worker_id = claimToken;
500
+ try {
501
+ const updateRequest = store.put(job);
502
+ updateRequest.onsuccess = () => {
503
+ claimedJob = job;
504
+ cursorStopped = true;
505
+ };
506
+ updateRequest.onerror = (err) => {
507
+ console.error("Failed to update job status:", err);
508
+ cursor.continue();
509
+ };
510
+ } catch (err) {
511
+ console.error("Error updating job:", err);
512
+ cursor.continue();
513
+ }
514
+ };
515
+ cursorRequest.onerror = () => reject(cursorRequest.error);
516
+ tx.oncomplete = () => {
517
+ if (claimedJob) {
518
+ this.hybridManager?.notifyLocalChange();
519
+ }
520
+ resolve(claimedJob);
521
+ };
522
+ tx.onerror = () => reject(tx.error);
523
+ });
524
+ if (!jobToReturn) {
525
+ return;
526
+ }
527
+ const verifiedJob = await this.get(jobToReturn.id);
528
+ if (!verifiedJob) {
529
+ return;
530
+ }
531
+ if (verifiedJob.worker_id !== claimToken) {
532
+ return;
533
+ }
534
+ if (verifiedJob.status !== JobStatus.PROCESSING) {
535
+ return;
536
+ }
537
+ return verifiedJob;
538
+ }
539
+ async size(status = JobStatus.PENDING) {
540
+ const db = await this.getDb();
541
+ const prefixKeyValues = this.getPrefixKeyValues();
542
+ return new Promise((resolve, reject) => {
543
+ const tx = db.transaction(this.tableName, "readonly");
544
+ const store = tx.objectStore(this.tableName);
545
+ const index = store.index("queue_status");
546
+ const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, status]);
547
+ const request = index.count(keyRange);
548
+ request.onsuccess = () => resolve(request.result);
549
+ request.onerror = () => reject(request.error);
550
+ tx.onerror = () => reject(tx.error);
551
+ });
552
+ }
553
+ async complete(job) {
554
+ const db = await this.getDb();
555
+ const tx = db.transaction(this.tableName, "readwrite");
556
+ const store = tx.objectStore(this.tableName);
557
+ return new Promise((resolve, reject) => {
558
+ const getReq = store.get(job.id);
559
+ getReq.onsuccess = () => {
560
+ const existing = getReq.result;
561
+ if (!existing || existing.queue !== this.queueName || !this.matchesPrefixes(existing)) {
562
+ reject(new Error(`Job ${job.id} not found or does not belong to queue ${this.queueName}`));
563
+ return;
564
+ }
565
+ const currentAttempts = existing.run_attempts ?? 0;
566
+ job.run_attempts = currentAttempts + 1;
567
+ job.queue = this.queueName;
568
+ const jobWithPrefixes = job;
569
+ for (const [key, value] of Object.entries(this.prefixValues)) {
570
+ jobWithPrefixes[key] = value;
571
+ }
572
+ const putReq = store.put(jobWithPrefixes);
573
+ putReq.onsuccess = () => {};
574
+ putReq.onerror = () => reject(putReq.error);
575
+ };
576
+ getReq.onerror = () => reject(getReq.error);
577
+ tx.oncomplete = () => {
578
+ this.hybridManager?.notifyLocalChange();
579
+ resolve();
580
+ };
581
+ tx.onerror = () => reject(tx.error);
582
+ });
583
+ }
584
+ async release(id) {
585
+ const job = await this.get(id);
586
+ if (!job)
587
+ return;
588
+ job.status = JobStatus.PENDING;
589
+ job.worker_id = null;
590
+ job.progress = 0;
591
+ job.progress_message = "";
592
+ job.progress_details = null;
593
+ await this.put(job);
594
+ }
595
+ async abort(id) {
596
+ const job = await this.get(id);
597
+ if (!job)
598
+ return;
599
+ job.status = JobStatus.ABORTING;
600
+ await this.complete(job);
601
+ }
602
+ async getByRunId(job_run_id) {
603
+ const db = await this.getDb();
604
+ const tx = db.transaction(this.tableName, "readonly");
605
+ const store = tx.objectStore(this.tableName);
606
+ const index = store.index("queue_job_run_id");
607
+ const prefixKeyValues = this.getPrefixKeyValues();
608
+ const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, job_run_id]);
609
+ const request = index.getAll(keyRange);
610
+ return new Promise((resolve, reject) => {
611
+ request.onsuccess = () => {
612
+ const results = (request.result || []).filter((job) => this.matchesPrefixes(job));
613
+ resolve(results);
614
+ };
615
+ request.onerror = () => reject(request.error);
616
+ tx.onerror = () => reject(tx.error);
617
+ });
618
+ }
619
+ async deleteAll() {
620
+ const db = await this.getDb();
621
+ const tx = db.transaction(this.tableName, "readwrite");
622
+ const store = tx.objectStore(this.tableName);
623
+ const index = store.index("queue_status");
624
+ const prefixKeyValues = this.getPrefixKeyValues();
625
+ return new Promise((resolve, reject) => {
626
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, this.queueName, ""], [...prefixKeyValues, this.queueName, "￿"]);
627
+ const request = index.openCursor(keyRange);
628
+ request.onsuccess = (event) => {
629
+ const cursor = event.target.result;
630
+ if (cursor) {
631
+ const job = cursor.value;
632
+ if (job.queue === this.queueName && this.matchesPrefixes(job)) {
633
+ const deleteRequest = cursor.delete();
634
+ deleteRequest.onsuccess = () => {
635
+ cursor.continue();
636
+ };
637
+ deleteRequest.onerror = () => {
638
+ cursor.continue();
639
+ };
640
+ } else {
641
+ cursor.continue();
642
+ }
643
+ }
644
+ };
645
+ tx.oncomplete = () => {
646
+ this.hybridManager?.notifyLocalChange();
647
+ resolve();
648
+ };
649
+ tx.onerror = () => reject(tx.error);
650
+ request.onerror = () => reject(request.error);
651
+ });
652
+ }
653
+ async outputForInput(input) {
654
+ const fingerprint = await makeFingerprint(input);
655
+ const db = await this.getDb();
656
+ const tx = db.transaction(this.tableName, "readonly");
657
+ const store = tx.objectStore(this.tableName);
658
+ const index = store.index("queue_fingerprint_status");
659
+ const prefixKeyValues = this.getPrefixKeyValues();
660
+ const request = index.get([
661
+ ...prefixKeyValues,
662
+ this.queueName,
663
+ fingerprint,
664
+ JobStatus.COMPLETED
665
+ ]);
666
+ return new Promise((resolve, reject) => {
667
+ request.onsuccess = () => {
668
+ const job = request.result;
669
+ if (job && this.matchesPrefixes(job)) {
670
+ resolve(job.output ?? null);
671
+ } else {
672
+ resolve(null);
673
+ }
674
+ };
675
+ request.onerror = () => reject(request.error);
676
+ tx.onerror = () => reject(tx.error);
677
+ });
678
+ }
679
+ async saveProgress(id, progress, message, details) {
680
+ const job = await this.get(id);
681
+ if (!job)
682
+ throw new Error(`Job ${id} not found`);
683
+ job.progress = progress;
684
+ job.progress_message = message;
685
+ job.progress_details = details;
686
+ await this.put(job);
687
+ }
688
+ async put(job) {
689
+ const db = await this.getDb();
690
+ const tx = db.transaction(this.tableName, "readwrite");
691
+ const store = tx.objectStore(this.tableName);
692
+ job.queue = this.queueName;
693
+ const jobWithPrefixes = job;
694
+ for (const [key, value] of Object.entries(this.prefixValues)) {
695
+ jobWithPrefixes[key] = value;
696
+ }
697
+ return new Promise((resolve, reject) => {
698
+ const putReq = store.put(jobWithPrefixes);
699
+ putReq.onerror = () => reject(putReq.error);
700
+ tx.oncomplete = () => {
701
+ this.hybridManager?.notifyLocalChange();
702
+ resolve();
703
+ };
704
+ tx.onerror = () => reject(tx.error);
705
+ });
706
+ }
707
+ async delete(id) {
708
+ const job = await this.get(id);
709
+ if (!job)
710
+ return;
711
+ const db = await this.getDb();
712
+ const tx = db.transaction(this.tableName, "readwrite");
713
+ const store = tx.objectStore(this.tableName);
714
+ const request = store.delete(id);
715
+ return new Promise((resolve, reject) => {
716
+ request.onsuccess = () => resolve();
717
+ request.onerror = () => reject(request.error);
718
+ tx.oncomplete = () => {
719
+ this.hybridManager?.notifyLocalChange();
720
+ };
721
+ tx.onerror = () => reject(tx.error);
722
+ });
723
+ }
724
+ async deleteJobsByStatusAndAge(status, olderThanMs) {
725
+ const db = await this.getDb();
726
+ const tx = db.transaction(this.tableName, "readwrite");
727
+ const store = tx.objectStore(this.tableName);
728
+ const index = store.index("queue_status");
729
+ const cutoffDate = new Date(Date.now() - olderThanMs).toISOString();
730
+ const prefixKeyValues = this.getPrefixKeyValues();
731
+ const keyRange = IDBKeyRange.only([...prefixKeyValues, this.queueName, status]);
732
+ return new Promise((resolve, reject) => {
733
+ const request = index.openCursor(keyRange);
734
+ request.onsuccess = (event) => {
735
+ const cursor = event.target.result;
736
+ if (cursor) {
737
+ const job = cursor.value;
738
+ if (job.queue === this.queueName && this.matchesPrefixes(job) && job.status === status && job.completed_at && job.completed_at <= cutoffDate) {
739
+ cursor.delete();
740
+ }
741
+ cursor.continue();
742
+ }
743
+ };
744
+ tx.oncomplete = () => {
745
+ this.hybridManager?.notifyLocalChange();
746
+ resolve();
747
+ };
748
+ tx.onerror = () => reject(tx.error);
749
+ request.onerror = () => reject(request.error);
750
+ });
751
+ }
752
+ async getAllJobs() {
753
+ const db = await this.getDb();
754
+ const tx = db.transaction(this.tableName, "readonly");
755
+ const store = tx.objectStore(this.tableName);
756
+ const index = store.index("queue_status");
757
+ const prefixKeyValues = this.getPrefixKeyValues();
758
+ return new Promise((resolve, reject) => {
759
+ const jobs = [];
760
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, this.queueName, ""], [...prefixKeyValues, this.queueName, "￿"]);
761
+ const request = index.openCursor(keyRange);
762
+ request.onsuccess = (event) => {
763
+ const cursor = event.target.result;
764
+ if (cursor) {
765
+ const job = cursor.value;
766
+ if (job.queue === this.queueName && this.matchesPrefixes(job)) {
767
+ jobs.push(job);
768
+ }
769
+ cursor.continue();
770
+ }
771
+ };
772
+ tx.oncomplete = () => resolve(jobs);
773
+ tx.onerror = () => reject(tx.error);
774
+ request.onerror = () => reject(request.error);
775
+ });
776
+ }
777
+ async getAllJobsWithFilter(prefixFilter) {
778
+ const db = await this.getDb();
779
+ const tx = db.transaction(this.tableName, "readonly");
780
+ const store = tx.objectStore(this.tableName);
781
+ return new Promise((resolve, reject) => {
782
+ const jobs = [];
783
+ const request = store.openCursor();
784
+ request.onsuccess = (event) => {
785
+ const cursor = event.target.result;
786
+ if (cursor) {
787
+ const job = cursor.value;
788
+ if (job.queue !== this.queueName) {
789
+ cursor.continue();
790
+ return;
791
+ }
792
+ if (Object.keys(prefixFilter).length === 0) {
793
+ jobs.push(job);
794
+ } else {
795
+ let matches = true;
796
+ for (const [key, value] of Object.entries(prefixFilter)) {
797
+ if (job[key] !== value) {
798
+ matches = false;
799
+ break;
800
+ }
801
+ }
802
+ if (matches) {
803
+ jobs.push(job);
804
+ }
805
+ }
806
+ cursor.continue();
807
+ }
808
+ };
809
+ tx.oncomplete = () => resolve(jobs);
810
+ tx.onerror = () => reject(tx.error);
811
+ request.onerror = () => reject(request.error);
812
+ });
813
+ }
814
+ isCustomPrefixFilter(prefixFilter) {
815
+ if (prefixFilter === undefined) {
816
+ return false;
817
+ }
818
+ if (Object.keys(prefixFilter).length === 0) {
819
+ return true;
820
+ }
821
+ const instanceKeys = Object.keys(this.prefixValues);
822
+ const filterKeys = Object.keys(prefixFilter);
823
+ if (instanceKeys.length !== filterKeys.length) {
824
+ return true;
825
+ }
826
+ for (const key of instanceKeys) {
827
+ if (this.prefixValues[key] !== prefixFilter[key]) {
828
+ return true;
829
+ }
830
+ }
831
+ return false;
832
+ }
833
+ getHybridManager() {
834
+ if (!this.hybridManager) {
835
+ const channelName = `indexeddb-queue-${this.tableName}-${this.queueName}`;
836
+ this.hybridManager = new HybridSubscriptionManager(channelName, async () => {
837
+ const jobs = await this.getAllJobs();
838
+ return new Map(jobs.map((j) => [j.id, j]));
839
+ }, (a, b) => deepEqual2(a, b), {
840
+ insert: (item) => ({ type: "INSERT", new: item }),
841
+ update: (oldItem, newItem) => ({ type: "UPDATE", old: oldItem, new: newItem }),
842
+ delete: (item) => ({ type: "DELETE", old: item })
843
+ }, {
844
+ defaultIntervalMs: 1000,
845
+ useBroadcastChannel: this.hybridOptions.useBroadcastChannel,
846
+ backupPollingIntervalMs: this.hybridOptions.backupPollingIntervalMs
847
+ });
848
+ }
849
+ return this.hybridManager;
850
+ }
851
+ subscribeWithCustomPrefixFilter(callback, prefixFilter, intervalMs) {
852
+ let lastKnownJobs = new Map;
853
+ let cancelled = false;
854
+ const poll = async () => {
855
+ if (cancelled)
856
+ return;
857
+ try {
858
+ const currentJobs = await this.getAllJobsWithFilter(prefixFilter);
859
+ if (cancelled)
860
+ return;
861
+ const currentMap = new Map(currentJobs.map((j) => [j.id, j]));
862
+ for (const [id, job] of currentMap) {
863
+ const old = lastKnownJobs.get(id);
864
+ if (!old) {
865
+ callback({ type: "INSERT", new: job });
866
+ } else if (!deepEqual2(old, job)) {
867
+ callback({ type: "UPDATE", old, new: job });
868
+ }
869
+ }
870
+ for (const [id, job] of lastKnownJobs) {
871
+ if (!currentMap.has(id)) {
872
+ callback({ type: "DELETE", old: job });
873
+ }
874
+ }
875
+ lastKnownJobs = currentMap;
876
+ } catch {}
877
+ };
878
+ const intervalId = setInterval(poll, intervalMs);
879
+ poll();
880
+ return () => {
881
+ cancelled = true;
882
+ clearInterval(intervalId);
883
+ };
884
+ }
885
+ subscribeToChanges(callback, options) {
886
+ const intervalMs = options?.pollingIntervalMs ?? 1000;
887
+ if (this.isCustomPrefixFilter(options?.prefixFilter)) {
888
+ return this.subscribeWithCustomPrefixFilter(callback, options.prefixFilter, intervalMs);
889
+ }
890
+ const manager = this.getHybridManager();
891
+ return manager.subscribe(callback, { intervalMs });
892
+ }
893
+ destroy() {
894
+ if (this.hybridManager) {
895
+ this.hybridManager.destroy();
896
+ this.hybridManager = null;
897
+ }
898
+ }
899
+ }
900
+ // src/job-queue/IndexedDbRateLimiterStorage.ts
901
+ import { createServiceToken as createServiceToken2 } from "@workglow/util";
902
+ var INDEXED_DB_RATE_LIMITER_STORAGE = createServiceToken2("ratelimiter.storage.indexedDb");
903
+
904
+ class IndexedDbRateLimiterStorage {
905
+ scope = "process";
906
+ executionDb;
907
+ nextAvailableDb;
908
+ executionTableName;
909
+ nextAvailableTableName;
910
+ migrationOptions;
911
+ prefixes;
912
+ prefixValues;
913
+ constructor(options = {}) {
914
+ this.migrationOptions = options;
915
+ this.prefixes = options.prefixes ?? [];
916
+ this.prefixValues = options.prefixValues ?? {};
917
+ if (this.prefixes.length > 0) {
918
+ const prefixNames = this.prefixes.map((p) => p.name).join("_");
919
+ this.executionTableName = `rate_limit_executions_${prefixNames}`;
920
+ this.nextAvailableTableName = `rate_limit_next_available_${prefixNames}`;
921
+ } else {
922
+ this.executionTableName = "rate_limit_executions";
923
+ this.nextAvailableTableName = "rate_limit_next_available";
924
+ }
925
+ }
926
+ getPrefixColumnNames() {
927
+ return this.prefixes.map((p) => p.name);
928
+ }
929
+ matchesPrefixes(record) {
930
+ for (const [key, value] of Object.entries(this.prefixValues)) {
931
+ if (record[key] !== value) {
932
+ return false;
933
+ }
934
+ }
935
+ return true;
936
+ }
937
+ getPrefixKeyValues() {
938
+ return this.prefixes.map((p) => this.prefixValues[p.name]);
939
+ }
940
+ async getExecutionDb() {
941
+ if (this.executionDb)
942
+ return this.executionDb;
943
+ await this.setupDatabase();
944
+ return this.executionDb;
945
+ }
946
+ async getNextAvailableDb() {
947
+ if (this.nextAvailableDb)
948
+ return this.nextAvailableDb;
949
+ await this.setupDatabase();
950
+ return this.nextAvailableDb;
951
+ }
952
+ async setupDatabase() {
953
+ const prefixColumnNames = this.getPrefixColumnNames();
954
+ const buildKeyPath = (basePath) => {
955
+ return [...prefixColumnNames, ...basePath];
956
+ };
957
+ const executionIndexes = [
958
+ {
959
+ name: "queue_executed_at",
960
+ keyPath: buildKeyPath(["queue_name", "executed_at"]),
961
+ options: { unique: false }
962
+ }
963
+ ];
964
+ this.executionDb = await ensureIndexedDbTable(this.executionTableName, "id", executionIndexes, this.migrationOptions);
965
+ const nextAvailableIndexes = [
966
+ {
967
+ name: "queue_name",
968
+ keyPath: buildKeyPath(["queue_name"]),
969
+ options: { unique: true }
970
+ }
971
+ ];
972
+ this.nextAvailableDb = await ensureIndexedDbTable(this.nextAvailableTableName, buildKeyPath(["queue_name"]).join("_"), nextAvailableIndexes, this.migrationOptions);
973
+ }
974
+ async tryReserveExecution(queueName, maxExecutions, windowMs) {
975
+ const nextIso = await this.getNextAvailableTime(queueName);
976
+ if (nextIso && new Date(nextIso).getTime() > Date.now()) {
977
+ return null;
978
+ }
979
+ const execDb = await this.getExecutionDb();
980
+ const prefixKeyValues = this.getPrefixKeyValues();
981
+ const windowStartIso = new Date(Date.now() - windowMs).toISOString();
982
+ const execTx = execDb.transaction(this.executionTableName, "readwrite");
983
+ const execStore = execTx.objectStore(this.executionTableName);
984
+ const insertedId = crypto.randomUUID();
985
+ return new Promise((resolve, reject) => {
986
+ let liveCount = 0;
987
+ let didInsert = false;
988
+ const liveRange = IDBKeyRange.bound([...prefixKeyValues, queueName, windowStartIso], [...prefixKeyValues, queueName, "￿"], true, false);
989
+ const cursorReq = execStore.index("queue_executed_at").openCursor(liveRange);
990
+ cursorReq.onsuccess = (event) => {
991
+ const cursor = event.target.result;
992
+ if (cursor) {
993
+ const record2 = cursor.value;
994
+ if (this.matchesPrefixes(record2)) {
995
+ liveCount++;
996
+ }
997
+ cursor.continue();
998
+ return;
999
+ }
1000
+ if (liveCount >= maxExecutions) {
1001
+ execTx.abort();
1002
+ return;
1003
+ }
1004
+ const record = {
1005
+ id: insertedId,
1006
+ queue_name: queueName,
1007
+ executed_at: new Date().toISOString()
1008
+ };
1009
+ for (const [k, v] of Object.entries(this.prefixValues)) {
1010
+ record[k] = v;
1011
+ }
1012
+ const addReq = execStore.add(record);
1013
+ didInsert = true;
1014
+ addReq.onerror = () => {
1015
+ try {
1016
+ execTx.abort();
1017
+ } catch {}
1018
+ reject(addReq.error);
1019
+ };
1020
+ };
1021
+ cursorReq.onerror = () => reject(cursorReq.error);
1022
+ execTx.oncomplete = () => resolve(didInsert ? insertedId : null);
1023
+ execTx.onerror = () => reject(execTx.error);
1024
+ execTx.onabort = () => resolve(null);
1025
+ });
1026
+ }
1027
+ async releaseExecution(queueName, token) {
1028
+ if (token === null || token === undefined)
1029
+ return;
1030
+ const db = await this.getExecutionDb();
1031
+ const tx = db.transaction(this.executionTableName, "readwrite");
1032
+ const store = tx.objectStore(this.executionTableName);
1033
+ return new Promise((resolve, reject) => {
1034
+ const req = store.delete(token);
1035
+ req.onerror = () => reject(req.error);
1036
+ tx.oncomplete = () => resolve();
1037
+ tx.onerror = () => reject(tx.error);
1038
+ });
1039
+ }
1040
+ async recordExecution(queueName) {
1041
+ const db = await this.getExecutionDb();
1042
+ const tx = db.transaction(this.executionTableName, "readwrite");
1043
+ const store = tx.objectStore(this.executionTableName);
1044
+ const record = {
1045
+ id: crypto.randomUUID(),
1046
+ queue_name: queueName,
1047
+ executed_at: new Date().toISOString()
1048
+ };
1049
+ for (const [key, value] of Object.entries(this.prefixValues)) {
1050
+ record[key] = value;
1051
+ }
1052
+ return new Promise((resolve, reject) => {
1053
+ const request = store.add(record);
1054
+ tx.oncomplete = () => resolve();
1055
+ tx.onerror = () => reject(tx.error);
1056
+ request.onerror = () => reject(request.error);
1057
+ });
1058
+ }
1059
+ async getExecutionCount(queueName, windowStartTime) {
1060
+ const db = await this.getExecutionDb();
1061
+ const tx = db.transaction(this.executionTableName, "readonly");
1062
+ const store = tx.objectStore(this.executionTableName);
1063
+ const index = store.index("queue_executed_at");
1064
+ const prefixKeyValues = this.getPrefixKeyValues();
1065
+ return new Promise((resolve, reject) => {
1066
+ let count = 0;
1067
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, queueName, windowStartTime], [...prefixKeyValues, queueName, "￿"], true, false);
1068
+ const request = index.openCursor(keyRange);
1069
+ request.onsuccess = (event) => {
1070
+ const cursor = event.target.result;
1071
+ if (cursor) {
1072
+ const record = cursor.value;
1073
+ if (this.matchesPrefixes(record)) {
1074
+ count++;
1075
+ }
1076
+ cursor.continue();
1077
+ }
1078
+ };
1079
+ tx.oncomplete = () => resolve(count);
1080
+ tx.onerror = () => reject(tx.error);
1081
+ request.onerror = () => reject(request.error);
1082
+ });
1083
+ }
1084
+ async getOldestExecutionAtOffset(queueName, offset) {
1085
+ const db = await this.getExecutionDb();
1086
+ const tx = db.transaction(this.executionTableName, "readonly");
1087
+ const store = tx.objectStore(this.executionTableName);
1088
+ const index = store.index("queue_executed_at");
1089
+ const prefixKeyValues = this.getPrefixKeyValues();
1090
+ return new Promise((resolve, reject) => {
1091
+ const executions = [];
1092
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, queueName, ""], [...prefixKeyValues, queueName, "￿"]);
1093
+ const request = index.openCursor(keyRange);
1094
+ request.onsuccess = (event) => {
1095
+ const cursor = event.target.result;
1096
+ if (cursor) {
1097
+ const record = cursor.value;
1098
+ if (this.matchesPrefixes(record)) {
1099
+ executions.push(record.executed_at);
1100
+ }
1101
+ cursor.continue();
1102
+ }
1103
+ };
1104
+ tx.oncomplete = () => {
1105
+ executions.sort();
1106
+ resolve(executions[offset]);
1107
+ };
1108
+ tx.onerror = () => reject(tx.error);
1109
+ request.onerror = () => reject(request.error);
1110
+ });
1111
+ }
1112
+ async getNextAvailableTime(queueName) {
1113
+ const db = await this.getNextAvailableDb();
1114
+ const tx = db.transaction(this.nextAvailableTableName, "readonly");
1115
+ const store = tx.objectStore(this.nextAvailableTableName);
1116
+ const prefixKeyValues = this.getPrefixKeyValues();
1117
+ const key = [...prefixKeyValues, queueName].join("_");
1118
+ return new Promise((resolve, reject) => {
1119
+ const request = store.get(key);
1120
+ request.onsuccess = () => {
1121
+ const record = request.result;
1122
+ if (record && this.matchesPrefixes(record)) {
1123
+ resolve(record.next_available_at);
1124
+ } else {
1125
+ resolve(undefined);
1126
+ }
1127
+ };
1128
+ request.onerror = () => reject(request.error);
1129
+ tx.onerror = () => reject(tx.error);
1130
+ });
1131
+ }
1132
+ async setNextAvailableTime(queueName, nextAvailableAt) {
1133
+ const db = await this.getNextAvailableDb();
1134
+ const tx = db.transaction(this.nextAvailableTableName, "readwrite");
1135
+ const store = tx.objectStore(this.nextAvailableTableName);
1136
+ const prefixKeyValues = this.getPrefixKeyValues();
1137
+ const key = [...prefixKeyValues, queueName].join("_");
1138
+ const record = {
1139
+ queue_name: queueName,
1140
+ next_available_at: nextAvailableAt
1141
+ };
1142
+ for (const [k, value] of Object.entries(this.prefixValues)) {
1143
+ record[k] = value;
1144
+ }
1145
+ record[this.getPrefixColumnNames().concat(["queue_name"]).join("_")] = key;
1146
+ return new Promise((resolve, reject) => {
1147
+ const request = store.put(record);
1148
+ tx.oncomplete = () => resolve();
1149
+ tx.onerror = () => reject(tx.error);
1150
+ request.onerror = () => reject(request.error);
1151
+ });
1152
+ }
1153
+ async clear(queueName) {
1154
+ const execDb = await this.getExecutionDb();
1155
+ const execTx = execDb.transaction(this.executionTableName, "readwrite");
1156
+ const execStore = execTx.objectStore(this.executionTableName);
1157
+ const execIndex = execStore.index("queue_executed_at");
1158
+ const prefixKeyValues = this.getPrefixKeyValues();
1159
+ await new Promise((resolve, reject) => {
1160
+ const keyRange = IDBKeyRange.bound([...prefixKeyValues, queueName, ""], [...prefixKeyValues, queueName, "￿"]);
1161
+ const request = execIndex.openCursor(keyRange);
1162
+ request.onsuccess = (event) => {
1163
+ const cursor = event.target.result;
1164
+ if (cursor) {
1165
+ const record = cursor.value;
1166
+ if (this.matchesPrefixes(record)) {
1167
+ cursor.delete();
1168
+ }
1169
+ cursor.continue();
1170
+ }
1171
+ };
1172
+ execTx.oncomplete = () => resolve();
1173
+ execTx.onerror = () => reject(execTx.error);
1174
+ request.onerror = () => reject(request.error);
1175
+ });
1176
+ const nextDb = await this.getNextAvailableDb();
1177
+ const nextTx = nextDb.transaction(this.nextAvailableTableName, "readwrite");
1178
+ const nextStore = nextTx.objectStore(this.nextAvailableTableName);
1179
+ const key = [...prefixKeyValues, queueName].join("_");
1180
+ await new Promise((resolve, reject) => {
1181
+ const request = nextStore.delete(key);
1182
+ nextTx.oncomplete = () => resolve();
1183
+ nextTx.onerror = () => reject(nextTx.error);
1184
+ request.onerror = () => reject(request.error);
1185
+ });
1186
+ }
1187
+ }
1188
+ export {
1189
+ IndexedDbRateLimiterStorage,
1190
+ IndexedDbQueueStorage,
1191
+ INDEXED_DB_RATE_LIMITER_STORAGE,
1192
+ INDEXED_DB_QUEUE_STORAGE
1193
+ };
1194
+
1195
+ //# debugId=57ACA1D439B2BC0064756E2164756E21