@workglow/storage 0.2.22 → 0.2.24

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 (35) hide show
  1. package/dist/browser.js +284 -55
  2. package/dist/browser.js.map +12 -12
  3. package/dist/bun.js +331 -55
  4. package/dist/bun.js.map +16 -16
  5. package/dist/node.js +331 -55
  6. package/dist/node.js.map +16 -16
  7. package/dist/queue/IQueueStorage.d.ts +9 -0
  8. package/dist/queue/IQueueStorage.d.ts.map +1 -1
  9. package/dist/queue/InMemoryQueueStorage.d.ts +5 -0
  10. package/dist/queue/InMemoryQueueStorage.d.ts.map +1 -1
  11. package/dist/queue/IndexedDbQueueStorage.d.ts +4 -0
  12. package/dist/queue/IndexedDbQueueStorage.d.ts.map +1 -1
  13. package/dist/queue/PostgresQueueStorage.d.ts +5 -0
  14. package/dist/queue/PostgresQueueStorage.d.ts.map +1 -1
  15. package/dist/queue/SqliteQueueStorage.d.ts +5 -0
  16. package/dist/queue/SqliteQueueStorage.d.ts.map +1 -1
  17. package/dist/queue/SupabaseQueueStorage.d.ts +4 -0
  18. package/dist/queue/SupabaseQueueStorage.d.ts.map +1 -1
  19. package/dist/queue/TelemetryQueueStorage.d.ts +1 -0
  20. package/dist/queue/TelemetryQueueStorage.d.ts.map +1 -1
  21. package/dist/tabular/BaseTabularStorage.d.ts +5 -0
  22. package/dist/tabular/BaseTabularStorage.d.ts.map +1 -1
  23. package/dist/tabular/ITabularStorage.d.ts +9 -1
  24. package/dist/tabular/ITabularStorage.d.ts.map +1 -1
  25. package/dist/tabular/IndexedDbTabularStorage.d.ts +40 -0
  26. package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
  27. package/dist/tabular/PostgresTabularStorage.d.ts +4 -0
  28. package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
  29. package/dist/tabular/SqliteTabularStorage.d.ts +4 -0
  30. package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
  31. package/dist/tabular/SupabaseTabularStorage.d.ts +9 -0
  32. package/dist/tabular/SupabaseTabularStorage.d.ts.map +1 -1
  33. package/dist/tabular/TelemetryTabularStorage.d.ts +1 -0
  34. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  35. package/package.json +3 -3
package/dist/browser.js CHANGED
@@ -161,6 +161,13 @@ class BaseTabularStorage {
161
161
  waitOn(name) {
162
162
  return this.events.waitOn(name);
163
163
  }
164
+ async count(criteria) {
165
+ if (!criteria || Object.keys(criteria).length === 0) {
166
+ return await this.size();
167
+ }
168
+ const entities = await this.query(criteria);
169
+ return entities?.length ?? 0;
170
+ }
164
171
  async* records(pageSize = 100) {
165
172
  if (pageSize <= 0) {
166
173
  throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);
@@ -1188,6 +1195,9 @@ class TelemetryTabularStorage {
1188
1195
  size() {
1189
1196
  return traced("workglow.storage.tabular.size", this.storageName, () => this.inner.size());
1190
1197
  }
1198
+ count(criteria) {
1199
+ return traced("workglow.storage.tabular.count", this.storageName, () => this.inner.count(criteria));
1200
+ }
1191
1201
  deleteSearch(criteria) {
1192
1202
  return traced("workglow.storage.tabular.deleteSearch", this.storageName, () => this.inner.deleteSearch(criteria));
1193
1203
  }
@@ -1549,6 +1559,19 @@ class InMemoryQueueStorage {
1549
1559
  this.events.emit("change", { type: "UPDATE", old: existing, new: jobWithPrefixes });
1550
1560
  }
1551
1561
  }
1562
+ async release(id) {
1563
+ await sleep(0);
1564
+ const job = this.jobQueue.find((j) => j.id === id && this.matchesPrefixes(j));
1565
+ if (job) {
1566
+ const oldJob = { ...job };
1567
+ job.status = JobStatus.PENDING;
1568
+ job.worker_id = null;
1569
+ job.progress = 0;
1570
+ job.progress_message = "";
1571
+ job.progress_details = null;
1572
+ this.events.emit("change", { type: "UPDATE", old: oldJob, new: job });
1573
+ }
1574
+ }
1552
1575
  async abort(id) {
1553
1576
  await sleep(0);
1554
1577
  const job = this.jobQueue.find((j) => j.id === id && this.matchesPrefixes(j));
@@ -1648,6 +1671,9 @@ class TelemetryQueueStorage {
1648
1671
  complete(job) {
1649
1672
  return traced("workglow.storage.queue.complete", this.storageName, () => this.inner.complete(job));
1650
1673
  }
1674
+ release(id) {
1675
+ return traced("workglow.storage.queue.release", this.storageName, () => this.inner.release(id));
1676
+ }
1651
1677
  deleteAll() {
1652
1678
  return traced("workglow.storage.queue.deleteAll", this.storageName, () => this.inner.deleteAll());
1653
1679
  }
@@ -2615,6 +2641,7 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
2615
2641
  migrationOptions;
2616
2642
  hybridManager = null;
2617
2643
  hybridOptions;
2644
+ cursorSafeIndexes;
2618
2645
  constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], migrationOptions = {}, clientProvidedKeys = "if-missing") {
2619
2646
  super(schema, primaryKeyNames, indexes, clientProvidedKeys);
2620
2647
  this.table = table;
@@ -2840,6 +2867,83 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
2840
2867
  request.onsuccess = () => resolve(request.result);
2841
2868
  });
2842
2869
  }
2870
+ getCursorSafeIndexes() {
2871
+ if (this.cursorSafeIndexes)
2872
+ return this.cursorSafeIndexes;
2873
+ const required = new Set(this.schema.required ?? []);
2874
+ this.cursorSafeIndexes = this.indexes.filter((columns) => columns.every((column) => required.has(String(column))));
2875
+ return this.cursorSafeIndexes;
2876
+ }
2877
+ createIndexedRange(store, criteria) {
2878
+ const criteriaColumns = Object.keys(criteria);
2879
+ if (criteriaColumns.length === 0)
2880
+ return;
2881
+ let best;
2882
+ for (const indexColumns of this.getCursorSafeIndexes()) {
2883
+ const prefixValues = [];
2884
+ for (const column of indexColumns) {
2885
+ const value = this.getEqualityCriterionValue(criteria, column);
2886
+ if (value === undefined)
2887
+ break;
2888
+ prefixValues.push(value);
2889
+ }
2890
+ if (prefixValues.length === 0)
2891
+ continue;
2892
+ const indexedPrefix = indexColumns.slice(0, prefixValues.length);
2893
+ const coversCriteria = criteriaColumns.every((column) => indexedPrefix.includes(column));
2894
+ const better = !best || coversCriteria && !best.coversCriteria || coversCriteria === best.coversCriteria && prefixValues.length > best.prefixValues.length;
2895
+ if (better) {
2896
+ best = {
2897
+ indexName: indexColumns.map((column) => String(column)).join("_"),
2898
+ prefixValues,
2899
+ fullMatch: prefixValues.length === indexColumns.length,
2900
+ coversCriteria
2901
+ };
2902
+ }
2903
+ }
2904
+ if (!best)
2905
+ return;
2906
+ const range = best.fullMatch ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues) : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);
2907
+ return {
2908
+ source: store.index(best.indexName),
2909
+ range,
2910
+ coversCriteria: best.coversCriteria
2911
+ };
2912
+ }
2913
+ async count(criteria) {
2914
+ if (!criteria || Object.keys(criteria).length === 0) {
2915
+ return await this.size();
2916
+ }
2917
+ this.validateQueryParams(criteria);
2918
+ const db = await this.getDb();
2919
+ return new Promise((resolve, reject) => {
2920
+ const transaction = db.transaction(this.table, "readonly");
2921
+ const store = transaction.objectStore(this.table);
2922
+ const plan = this.createIndexedRange(store, criteria);
2923
+ if (plan?.coversCriteria) {
2924
+ const request2 = plan.source.count(plan.range);
2925
+ request2.onerror = () => reject(request2.error);
2926
+ request2.onsuccess = () => resolve(request2.result);
2927
+ return;
2928
+ }
2929
+ const source = plan?.source ?? store;
2930
+ const range = plan?.range;
2931
+ let count = 0;
2932
+ const request = source.openCursor(range);
2933
+ request.onerror = () => reject(request.error);
2934
+ request.onsuccess = () => {
2935
+ const cursor = request.result;
2936
+ if (!cursor) {
2937
+ resolve(count);
2938
+ return;
2939
+ }
2940
+ if (this.matchesCriteria(cursor.value, criteria)) {
2941
+ count += 1;
2942
+ }
2943
+ cursor.continue();
2944
+ };
2945
+ });
2946
+ }
2843
2947
  async getBulk(offset, limit) {
2844
2948
  if (offset < 0) {
2845
2949
  throw new RangeError(`offset must be non-negative, got ${offset}`);
@@ -2961,48 +3065,135 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
2961
3065
  }
2962
3066
  });
2963
3067
  }
3068
+ getEqualityCriterionValue(criteria, column) {
3069
+ const criterion = criteria[column];
3070
+ if (criterion === undefined)
3071
+ return;
3072
+ if (isSearchCondition(criterion)) {
3073
+ return criterion.operator === "=" ? criterion.value : undefined;
3074
+ }
3075
+ return criterion;
3076
+ }
3077
+ compareByOrder(a, b, options) {
3078
+ if (!options?.orderBy)
3079
+ return 0;
3080
+ for (const { column, direction } of options.orderBy) {
3081
+ const aVal = a[column];
3082
+ const bVal = b[column];
3083
+ if (aVal == null && bVal == null)
3084
+ continue;
3085
+ if (aVal == null)
3086
+ return direction === "ASC" ? -1 : 1;
3087
+ if (bVal == null)
3088
+ return direction === "ASC" ? 1 : -1;
3089
+ if (aVal < bVal)
3090
+ return direction === "ASC" ? -1 : 1;
3091
+ if (aVal > bVal)
3092
+ return direction === "ASC" ? 1 : -1;
3093
+ }
3094
+ return 0;
3095
+ }
3096
+ createIndexedQuery(store, criteria, options) {
3097
+ const orderBy = options?.orderBy ?? [];
3098
+ let best;
3099
+ for (const indexColumns of this.getCursorSafeIndexes()) {
3100
+ const prefixValues = [];
3101
+ for (const column of indexColumns) {
3102
+ const value = this.getEqualityCriterionValue(criteria, column);
3103
+ if (value === undefined)
3104
+ break;
3105
+ prefixValues.push(value);
3106
+ }
3107
+ if (prefixValues.length === 0)
3108
+ continue;
3109
+ const remainingColumns = indexColumns.slice(prefixValues.length);
3110
+ let redundantOrderPrefixLength = 0;
3111
+ while (redundantOrderPrefixLength < orderBy.length && redundantOrderPrefixLength < prefixValues.length && orderBy[redundantOrderPrefixLength]?.column === indexColumns[redundantOrderPrefixLength]) {
3112
+ redundantOrderPrefixLength++;
3113
+ }
3114
+ const normalizedOrderBy = orderBy.slice(redundantOrderPrefixLength);
3115
+ const satisfiesOrder = normalizedOrderBy.length === 0 || normalizedOrderBy.length <= remainingColumns.length && normalizedOrderBy.every((order, index) => order.column === remainingColumns[index]) && orderBy.every((order) => order.direction === orderBy[0]?.direction);
3116
+ if (!satisfiesOrder && best)
3117
+ continue;
3118
+ if (!best || satisfiesOrder && !best.satisfiesOrder || prefixValues.length > best.prefixValues.length) {
3119
+ best = {
3120
+ indexName: indexColumns.map((column) => String(column)).join("_"),
3121
+ prefixValues,
3122
+ fullMatch: prefixValues.length === indexColumns.length,
3123
+ satisfiesOrder,
3124
+ direction: orderBy[0]?.direction === "DESC" ? "prev" : "next"
3125
+ };
3126
+ }
3127
+ }
3128
+ const appliedLimit = Boolean(best?.satisfiesOrder && options?.limit !== undefined);
3129
+ const appliedOffset = Boolean(best?.satisfiesOrder && options?.offset !== undefined);
3130
+ if (!best) {
3131
+ return {
3132
+ source: store,
3133
+ range: undefined,
3134
+ direction: orderBy[0]?.direction === "DESC" ? "prev" : "next",
3135
+ satisfiesOrder: false,
3136
+ appliedLimit: false,
3137
+ appliedOffset: false,
3138
+ skipRemaining: 0
3139
+ };
3140
+ }
3141
+ const source = store.index(best.indexName);
3142
+ const keyRange = best.fullMatch ? IDBKeyRange.only(best.prefixValues.length === 1 ? best.prefixValues[0] : best.prefixValues) : IDBKeyRange.bound(best.prefixValues, [...best.prefixValues, []]);
3143
+ return {
3144
+ source,
3145
+ range: keyRange,
3146
+ direction: best.direction,
3147
+ satisfiesOrder: best.satisfiesOrder,
3148
+ appliedLimit,
3149
+ appliedOffset,
3150
+ skipRemaining: appliedOffset ? options?.offset ?? 0 : 0
3151
+ };
3152
+ }
2964
3153
  async query(criteria, options) {
2965
3154
  this.validateQueryParams(criteria, options);
2966
3155
  const db = await this.getDb();
2967
3156
  return new Promise((resolve, reject) => {
2968
3157
  const transaction = db.transaction(this.table, "readonly");
2969
3158
  const store = transaction.objectStore(this.table);
2970
- const getAllRequest = store.getAll();
2971
- getAllRequest.onsuccess = () => {
2972
- const allRecords = getAllRequest.result;
2973
- let results = allRecords.filter((record) => this.matchesCriteria(record, criteria));
2974
- if (options?.orderBy && options.orderBy.length > 0) {
2975
- results.sort((a, b) => {
2976
- for (const { column, direction } of options.orderBy) {
2977
- const aVal = a[column];
2978
- const bVal = b[column];
2979
- if (aVal == null && bVal == null)
2980
- continue;
2981
- if (aVal == null)
2982
- return direction === "ASC" ? -1 : 1;
2983
- if (bVal == null)
2984
- return direction === "ASC" ? 1 : -1;
2985
- if (aVal < bVal)
2986
- return direction === "ASC" ? -1 : 1;
2987
- if (aVal > bVal)
2988
- return direction === "ASC" ? 1 : -1;
2989
- }
2990
- return 0;
2991
- });
2992
- }
2993
- if (options?.offset !== undefined) {
2994
- results = results.slice(options.offset);
3159
+ const indexedQuery = this.createIndexedQuery(store, criteria, options);
3160
+ const results = [];
3161
+ const request = indexedQuery.source.openCursor(indexedQuery.range, indexedQuery.direction);
3162
+ request.onsuccess = () => {
3163
+ const cursor = request.result;
3164
+ if (!cursor) {
3165
+ let finalResults = results;
3166
+ if (!indexedQuery.satisfiesOrder && options?.orderBy && options.orderBy.length > 0) {
3167
+ finalResults = [...finalResults].sort((a, b) => this.compareByOrder(a, b, options));
3168
+ }
3169
+ if (!indexedQuery.appliedOffset && options?.offset !== undefined) {
3170
+ finalResults = finalResults.slice(options.offset);
3171
+ }
3172
+ if (!indexedQuery.appliedLimit && options?.limit !== undefined) {
3173
+ finalResults = finalResults.slice(0, options.limit);
3174
+ }
3175
+ const result = finalResults.length > 0 ? finalResults : undefined;
3176
+ this.events.emit("query", criteria, result);
3177
+ resolve(result);
3178
+ return;
2995
3179
  }
2996
- if (options?.limit !== undefined) {
2997
- results = results.slice(0, options.limit);
3180
+ const record = cursor.value;
3181
+ if (this.matchesCriteria(record, criteria)) {
3182
+ if (indexedQuery.skipRemaining > 0) {
3183
+ indexedQuery.skipRemaining -= 1;
3184
+ } else {
3185
+ results.push(record);
3186
+ if (indexedQuery.appliedLimit && results.length === options?.limit) {
3187
+ const result = results.length > 0 ? results : undefined;
3188
+ this.events.emit("query", criteria, result);
3189
+ resolve(result);
3190
+ return;
3191
+ }
3192
+ }
2998
3193
  }
2999
- const result = results.length > 0 ? results : undefined;
3000
- this.events.emit("query", criteria, result);
3001
- resolve(result);
3002
- };
3003
- getAllRequest.onerror = () => {
3004
- reject(getAllRequest.error);
3194
+ cursor.continue();
3005
3195
  };
3196
+ request.onerror = () => reject(request.error);
3006
3197
  });
3007
3198
  }
3008
3199
  getHybridManager() {
@@ -3783,16 +3974,9 @@ class SupabaseTabularStorage extends BaseSqlTabularStorage {
3783
3974
  }
3784
3975
  return data;
3785
3976
  }
3786
- async deleteSearch(criteria) {
3787
- const criteriaKeys = Object.keys(criteria);
3788
- if (criteriaKeys.length === 0) {
3789
- return;
3790
- }
3791
- let query = this.client.from(this.table).delete();
3792
- for (const column of criteriaKeys) {
3793
- if (!(column in this.schema.properties)) {
3794
- throw new Error(`Schema must have a ${String(column)} field to use deleteSearch`);
3795
- }
3977
+ applyCriteriaToFilter(query, criteria) {
3978
+ let q = query;
3979
+ for (const column of Object.keys(criteria)) {
3796
3980
  const criterion = criteria[column];
3797
3981
  let operator = "=";
3798
3982
  let value;
@@ -3804,32 +3988,45 @@ class SupabaseTabularStorage extends BaseSqlTabularStorage {
3804
3988
  }
3805
3989
  switch (operator) {
3806
3990
  case "=":
3807
- query = query.eq(String(column), value);
3991
+ q = q.eq(String(column), value);
3808
3992
  break;
3809
3993
  case "<":
3810
- query = query.lt(String(column), value);
3994
+ q = q.lt(String(column), value);
3811
3995
  break;
3812
3996
  case "<=":
3813
- query = query.lte(String(column), value);
3997
+ q = q.lte(String(column), value);
3814
3998
  break;
3815
3999
  case ">":
3816
- query = query.gt(String(column), value);
4000
+ q = q.gt(String(column), value);
3817
4001
  break;
3818
4002
  case ">=":
3819
- query = query.gte(String(column), value);
4003
+ q = q.gte(String(column), value);
3820
4004
  break;
3821
4005
  }
3822
4006
  }
3823
- const { error } = await query;
4007
+ return q;
4008
+ }
4009
+ async count(criteria) {
4010
+ if (!criteria || Object.keys(criteria).length === 0) {
4011
+ return await this.size();
4012
+ }
4013
+ this.validateQueryParams(criteria);
4014
+ const query = this.applyCriteriaToFilter(this.client.from(this.table).select("*", { count: "exact", head: true }), criteria);
4015
+ const { count, error } = await query;
3824
4016
  if (error)
3825
4017
  throw error;
3826
- this.events.emit("delete", criteriaKeys[0]);
4018
+ return count ?? 0;
3827
4019
  }
3828
- async query(criteria, options) {
3829
- this.validateQueryParams(criteria, options);
4020
+ async deleteSearch(criteria) {
3830
4021
  const criteriaKeys = Object.keys(criteria);
3831
- let query = this.client.from(this.table).select("*");
4022
+ if (criteriaKeys.length === 0) {
4023
+ return;
4024
+ }
4025
+ let query = this.client.from(this.table).delete();
3832
4026
  for (const column of criteriaKeys) {
4027
+ if (!(column in this.schema.properties)) {
4028
+ throw new Error(`Schema must have a ${String(column)} field to use deleteSearch`);
4029
+ }
3833
4030
  const criterion = criteria[column];
3834
4031
  let operator = "=";
3835
4032
  let value;
@@ -3857,6 +4054,14 @@ class SupabaseTabularStorage extends BaseSqlTabularStorage {
3857
4054
  break;
3858
4055
  }
3859
4056
  }
4057
+ const { error } = await query;
4058
+ if (error)
4059
+ throw error;
4060
+ this.events.emit("delete", criteriaKeys[0]);
4061
+ }
4062
+ async query(criteria, options) {
4063
+ this.validateQueryParams(criteria, options);
4064
+ let query = this.applyCriteriaToFilter(this.client.from(this.table).select("*"), criteria);
3860
4065
  if (options?.orderBy) {
3861
4066
  for (const { column, direction } of options.orderBy) {
3862
4067
  query = query.order(String(column), { ascending: direction === "ASC" });
@@ -4113,7 +4318,7 @@ class IndexedDbQueueStorage {
4113
4318
  const prefixKeyValues = this.getPrefixKeyValues();
4114
4319
  const claimToken = workerId;
4115
4320
  const jobToReturn = await new Promise((resolve, reject) => {
4116
- const cursorRequest = index.openCursor(IDBKeyRange.bound([...prefixKeyValues, this.queueName, JobStatus.PENDING, ""], [...prefixKeyValues, this.queueName, JobStatus.PENDING, now], false, true));
4321
+ const cursorRequest = index.openCursor(IDBKeyRange.bound([...prefixKeyValues, this.queueName, JobStatus.PENDING, ""], [...prefixKeyValues, this.queueName, JobStatus.PENDING, now], false, false));
4117
4322
  let claimedJob;
4118
4323
  let cursorStopped = false;
4119
4324
  cursorRequest.onsuccess = (e) => {
@@ -4216,6 +4421,17 @@ class IndexedDbQueueStorage {
4216
4421
  tx.onerror = () => reject(tx.error);
4217
4422
  });
4218
4423
  }
4424
+ async release(id) {
4425
+ const job = await this.get(id);
4426
+ if (!job)
4427
+ return;
4428
+ job.status = JobStatus.PENDING;
4429
+ job.worker_id = null;
4430
+ job.progress = 0;
4431
+ job.progress_message = "";
4432
+ job.progress_details = null;
4433
+ await this.put(job);
4434
+ }
4219
4435
  async abort(id) {
4220
4436
  const job = await this.get(id);
4221
4437
  if (!job)
@@ -4839,6 +5055,19 @@ class SupabaseQueueStorage {
4839
5055
  if (error)
4840
5056
  throw error;
4841
5057
  }
5058
+ async release(jobId) {
5059
+ let query = this.client.from(this.tableName).update({
5060
+ status: JobStatus.PENDING,
5061
+ worker_id: null,
5062
+ progress: 0,
5063
+ progress_message: "",
5064
+ progress_details: null
5065
+ }).eq("id", jobId).eq("queue", this.queueName);
5066
+ query = this.applyPrefixFilters(query);
5067
+ const { error } = await query;
5068
+ if (error)
5069
+ throw error;
5070
+ }
4842
5071
  async deleteAll() {
4843
5072
  let query = this.client.from(this.tableName).delete().eq("queue", this.queueName);
4844
5073
  query = this.applyPrefixFilters(query);
@@ -5577,4 +5806,4 @@ export {
5577
5806
  BaseTabularStorage
5578
5807
  };
5579
5808
 
5580
- //# debugId=BBB7B06FA3B7568C64756E2164756E21
5809
+ //# debugId=4C0D1FA7EA7C0F0564756E2164756E21