semola 0.5.4 → 0.6.0

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 (36) hide show
  1. package/README.md +18 -45
  2. package/dist/chunk-CKQMccvm.cjs +28 -0
  3. package/dist/lib/api/index.cjs +29 -15
  4. package/dist/lib/api/index.mjs +30 -16
  5. package/dist/lib/cache/index.cjs +47 -22
  6. package/dist/lib/cache/index.d.cts +3 -24
  7. package/dist/lib/cache/index.d.mts +3 -24
  8. package/dist/lib/cache/index.mjs +48 -23
  9. package/dist/lib/cron/index.cjs +117 -117
  10. package/dist/lib/cron/index.mjs +118 -118
  11. package/dist/lib/errors/index.d.cts +12 -1
  12. package/dist/lib/errors/index.d.mts +12 -1
  13. package/dist/lib/logging/index.cjs +1 -0
  14. package/dist/lib/orm/index.cjs +1642 -0
  15. package/dist/lib/orm/index.d.cts +402 -0
  16. package/dist/lib/orm/index.d.mts +402 -0
  17. package/dist/lib/orm/index.mjs +1630 -0
  18. package/dist/lib/prompts/index.cjs +89 -89
  19. package/dist/lib/prompts/index.d.cts +12 -33
  20. package/dist/lib/prompts/index.d.mts +12 -33
  21. package/dist/lib/prompts/index.mjs +89 -90
  22. package/dist/lib/pubsub/index.cjs +43 -19
  23. package/dist/lib/pubsub/index.d.cts +3 -18
  24. package/dist/lib/pubsub/index.d.mts +3 -18
  25. package/dist/lib/pubsub/index.mjs +44 -20
  26. package/dist/lib/queue/index.cjs +40 -10
  27. package/dist/lib/queue/index.d.cts +11 -4
  28. package/dist/lib/queue/index.d.mts +11 -4
  29. package/dist/lib/queue/index.mjs +39 -11
  30. package/dist/lib/workflow/index.cjs +285 -282
  31. package/dist/lib/workflow/index.d.cts +76 -11
  32. package/dist/lib/workflow/index.d.mts +76 -11
  33. package/dist/lib/workflow/index.mjs +278 -284
  34. package/package.json +11 -1
  35. package/dist/index-BhGNDjPq.d.mts +0 -13
  36. package/dist/index-DxSbeGP-.d.cts +0 -13
@@ -1,5 +1,31 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_lib_errors_index = require("../errors/index.cjs");
3
+ //#region src/lib/pubsub/errors.ts
4
+ var UnsubscribeError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "UnsubscribeError";
8
+ }
9
+ };
10
+ var SerializationError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "SerializationError";
14
+ }
15
+ };
16
+ var PublishError = class extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "PublishError";
20
+ }
21
+ };
22
+ var SubscribeError = class extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "SubscribeError";
26
+ }
27
+ };
28
+ //#endregion
3
29
  //#region src/lib/pubsub/index.ts
4
30
  var PubSub = class {
5
31
  options;
@@ -22,33 +48,32 @@ var PubSub = class {
22
48
  const inFlightUnsubscribe = this.unsubscribeInFlight;
23
49
  if (inFlightUnsubscribe) await inFlightUnsubscribe;
24
50
  const handler = this.handlers.get(handlerId);
25
- if (!handler) return require_lib_errors_index.err("UnsubscribeError", "Not subscribed");
51
+ if (!handler) throw new UnsubscribeError("Not subscribed");
26
52
  this.handlers.delete(handlerId);
27
- if (this.handlers.size > 0) return require_lib_errors_index.ok(true);
53
+ if (this.handlers.size > 0) return;
28
54
  this.isSubscribed = false;
29
55
  this.unsubscribeInFlight = require_lib_errors_index.mightThrow(this.options.subscriber.unsubscribe(this.options.channel));
30
56
  const unsubscribeInFlight = this.unsubscribeInFlight;
31
57
  if (!unsubscribeInFlight) {
32
58
  this.handlers.set(handlerId, handler);
33
59
  this.isSubscribed = true;
34
- return require_lib_errors_index.err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
60
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
35
61
  }
36
62
  const [unsubscribeError] = await unsubscribeInFlight;
37
63
  if (unsubscribeError) {
38
64
  this.handlers.set(handlerId, handler);
39
65
  this.isSubscribed = true;
40
66
  this.unsubscribeInFlight = null;
41
- return require_lib_errors_index.err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
67
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
42
68
  }
43
69
  this.unsubscribeInFlight = null;
44
- return require_lib_errors_index.ok(true);
45
70
  }
46
71
  async publish(message) {
47
72
  const [stringifyError, stringified] = require_lib_errors_index.mightThrowSync(() => JSON.stringify(message));
48
- if (stringifyError || !stringified) return require_lib_errors_index.err("SerializationError", "Unable to serialize message");
73
+ if (stringifyError || !stringified) throw new SerializationError("Unable to serialize message");
49
74
  const [publishError, count] = await require_lib_errors_index.mightThrow(this.options.publisher.publish(this.options.channel, stringified));
50
- if (publishError) return require_lib_errors_index.err("PublishError", `Unable to publish to ${this.options.channel}`);
51
- return require_lib_errors_index.ok(count);
75
+ if (publishError) throw new PublishError(`Unable to publish to ${this.options.channel}`);
76
+ return count;
52
77
  }
53
78
  async subscribe(handler) {
54
79
  const inFlightUnsubscribe = this.unsubscribeInFlight;
@@ -56,39 +81,39 @@ var PubSub = class {
56
81
  const handlerId = this.nextHandlerId;
57
82
  this.nextHandlerId += 1;
58
83
  this.handlers.set(handlerId, handler);
59
- if (this.isActive()) return require_lib_errors_index.ok(() => this.unsubscribeHandler(handlerId));
84
+ if (this.isActive()) return () => this.unsubscribeHandler(handlerId);
60
85
  const inFlightSubscribe = this.subscribeInFlight;
61
86
  if (inFlightSubscribe) {
62
87
  const [inFlightError] = await inFlightSubscribe;
63
88
  if (inFlightError || !this.isSubscribed) {
64
89
  this.handlers.delete(handlerId);
65
- return require_lib_errors_index.err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
90
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
66
91
  }
67
- return require_lib_errors_index.ok(() => this.unsubscribeHandler(handlerId));
92
+ return () => this.unsubscribeHandler(handlerId);
68
93
  }
69
94
  this.subscribeInFlight = require_lib_errors_index.mightThrow(this.options.subscriber.subscribe(this.options.channel, async (message, channel) => this.onMessage(message, channel)));
70
95
  const subscribeInFlight = this.subscribeInFlight;
71
96
  if (!subscribeInFlight) {
72
97
  this.handlers.delete(handlerId);
73
- return require_lib_errors_index.err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
98
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
74
99
  }
75
100
  const [subscribeError, count] = await subscribeInFlight;
76
101
  this.subscribeInFlight = null;
77
102
  if (subscribeError) {
78
103
  this.handlers.delete(handlerId);
79
- return require_lib_errors_index.err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
104
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
80
105
  }
81
106
  if (!count) {
82
107
  this.handlers.delete(handlerId);
83
- return require_lib_errors_index.err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
108
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
84
109
  }
85
110
  this.isSubscribed = true;
86
- return require_lib_errors_index.ok(() => this.unsubscribeHandler(handlerId));
111
+ return () => this.unsubscribeHandler(handlerId);
87
112
  }
88
113
  async unsubscribe() {
89
114
  const inFlightUnsubscribe = this.unsubscribeInFlight;
90
115
  if (inFlightUnsubscribe) await inFlightUnsubscribe;
91
- if (!this.isActive()) return require_lib_errors_index.err("UnsubscribeError", "Not subscribed");
116
+ if (!this.isActive()) throw new UnsubscribeError("Not subscribed");
92
117
  const handlers = new Map(this.handlers);
93
118
  this.handlers.clear();
94
119
  this.isSubscribed = false;
@@ -97,17 +122,16 @@ var PubSub = class {
97
122
  if (!unsubscribeInFlight) {
98
123
  this.handlers = handlers;
99
124
  this.isSubscribed = true;
100
- return require_lib_errors_index.err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
125
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
101
126
  }
102
127
  const [unsubscribeError] = await unsubscribeInFlight;
103
128
  if (unsubscribeError) {
104
129
  this.handlers = handlers;
105
130
  this.isSubscribed = true;
106
131
  this.unsubscribeInFlight = null;
107
- return require_lib_errors_index.err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
132
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
108
133
  }
109
134
  this.unsubscribeInFlight = null;
110
- return require_lib_errors_index.ok(true);
111
135
  }
112
136
  isActive() {
113
137
  return this.handlers.size > 0 && this.isSubscribed;
@@ -17,24 +17,9 @@ declare class PubSub<T extends Record<string, unknown>> {
17
17
  constructor(options: PubSubOptions);
18
18
  private onMessage;
19
19
  private unsubscribeHandler;
20
- publish(message: T): Promise<readonly [{
21
- readonly type: "SerializationError";
22
- readonly message: string;
23
- }, null] | readonly [{
24
- readonly type: "PublishError";
25
- readonly message: string;
26
- }, null] | readonly [null, number]>;
27
- subscribe(handler: MessageHandler<T>): Promise<readonly [null, () => Promise<readonly [{
28
- readonly type: "UnsubscribeError";
29
- readonly message: string;
30
- }, null] | readonly [null, boolean]>] | readonly [{
31
- readonly type: "SubscribeError";
32
- readonly message: string;
33
- }, null]>;
34
- unsubscribe(): Promise<readonly [{
35
- readonly type: "UnsubscribeError";
36
- readonly message: string;
37
- }, null] | readonly [null, boolean]>;
20
+ publish(message: T): Promise<number>;
21
+ subscribe(handler: MessageHandler<T>): Promise<() => Promise<void>>;
22
+ unsubscribe(): Promise<void>;
38
23
  isActive(): boolean;
39
24
  }
40
25
  //#endregion
@@ -17,24 +17,9 @@ declare class PubSub<T extends Record<string, unknown>> {
17
17
  constructor(options: PubSubOptions);
18
18
  private onMessage;
19
19
  private unsubscribeHandler;
20
- publish(message: T): Promise<readonly [{
21
- readonly type: "SerializationError";
22
- readonly message: string;
23
- }, null] | readonly [{
24
- readonly type: "PublishError";
25
- readonly message: string;
26
- }, null] | readonly [null, number]>;
27
- subscribe(handler: MessageHandler<T>): Promise<readonly [null, () => Promise<readonly [{
28
- readonly type: "UnsubscribeError";
29
- readonly message: string;
30
- }, null] | readonly [null, boolean]>] | readonly [{
31
- readonly type: "SubscribeError";
32
- readonly message: string;
33
- }, null]>;
34
- unsubscribe(): Promise<readonly [{
35
- readonly type: "UnsubscribeError";
36
- readonly message: string;
37
- }, null] | readonly [null, boolean]>;
20
+ publish(message: T): Promise<number>;
21
+ subscribe(handler: MessageHandler<T>): Promise<() => Promise<void>>;
22
+ unsubscribe(): Promise<void>;
38
23
  isActive(): boolean;
39
24
  }
40
25
  //#endregion
@@ -1,4 +1,30 @@
1
- import { err, mightThrow, mightThrowSync, ok } from "../errors/index.mjs";
1
+ import { mightThrow, mightThrowSync } from "../errors/index.mjs";
2
+ //#region src/lib/pubsub/errors.ts
3
+ var UnsubscribeError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "UnsubscribeError";
7
+ }
8
+ };
9
+ var SerializationError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "SerializationError";
13
+ }
14
+ };
15
+ var PublishError = class extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = "PublishError";
19
+ }
20
+ };
21
+ var SubscribeError = class extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "SubscribeError";
25
+ }
26
+ };
27
+ //#endregion
2
28
  //#region src/lib/pubsub/index.ts
3
29
  var PubSub = class {
4
30
  options;
@@ -21,33 +47,32 @@ var PubSub = class {
21
47
  const inFlightUnsubscribe = this.unsubscribeInFlight;
22
48
  if (inFlightUnsubscribe) await inFlightUnsubscribe;
23
49
  const handler = this.handlers.get(handlerId);
24
- if (!handler) return err("UnsubscribeError", "Not subscribed");
50
+ if (!handler) throw new UnsubscribeError("Not subscribed");
25
51
  this.handlers.delete(handlerId);
26
- if (this.handlers.size > 0) return ok(true);
52
+ if (this.handlers.size > 0) return;
27
53
  this.isSubscribed = false;
28
54
  this.unsubscribeInFlight = mightThrow(this.options.subscriber.unsubscribe(this.options.channel));
29
55
  const unsubscribeInFlight = this.unsubscribeInFlight;
30
56
  if (!unsubscribeInFlight) {
31
57
  this.handlers.set(handlerId, handler);
32
58
  this.isSubscribed = true;
33
- return err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
59
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
34
60
  }
35
61
  const [unsubscribeError] = await unsubscribeInFlight;
36
62
  if (unsubscribeError) {
37
63
  this.handlers.set(handlerId, handler);
38
64
  this.isSubscribed = true;
39
65
  this.unsubscribeInFlight = null;
40
- return err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
66
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
41
67
  }
42
68
  this.unsubscribeInFlight = null;
43
- return ok(true);
44
69
  }
45
70
  async publish(message) {
46
71
  const [stringifyError, stringified] = mightThrowSync(() => JSON.stringify(message));
47
- if (stringifyError || !stringified) return err("SerializationError", "Unable to serialize message");
72
+ if (stringifyError || !stringified) throw new SerializationError("Unable to serialize message");
48
73
  const [publishError, count] = await mightThrow(this.options.publisher.publish(this.options.channel, stringified));
49
- if (publishError) return err("PublishError", `Unable to publish to ${this.options.channel}`);
50
- return ok(count);
74
+ if (publishError) throw new PublishError(`Unable to publish to ${this.options.channel}`);
75
+ return count;
51
76
  }
52
77
  async subscribe(handler) {
53
78
  const inFlightUnsubscribe = this.unsubscribeInFlight;
@@ -55,39 +80,39 @@ var PubSub = class {
55
80
  const handlerId = this.nextHandlerId;
56
81
  this.nextHandlerId += 1;
57
82
  this.handlers.set(handlerId, handler);
58
- if (this.isActive()) return ok(() => this.unsubscribeHandler(handlerId));
83
+ if (this.isActive()) return () => this.unsubscribeHandler(handlerId);
59
84
  const inFlightSubscribe = this.subscribeInFlight;
60
85
  if (inFlightSubscribe) {
61
86
  const [inFlightError] = await inFlightSubscribe;
62
87
  if (inFlightError || !this.isSubscribed) {
63
88
  this.handlers.delete(handlerId);
64
- return err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
89
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
65
90
  }
66
- return ok(() => this.unsubscribeHandler(handlerId));
91
+ return () => this.unsubscribeHandler(handlerId);
67
92
  }
68
93
  this.subscribeInFlight = mightThrow(this.options.subscriber.subscribe(this.options.channel, async (message, channel) => this.onMessage(message, channel)));
69
94
  const subscribeInFlight = this.subscribeInFlight;
70
95
  if (!subscribeInFlight) {
71
96
  this.handlers.delete(handlerId);
72
- return err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
97
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
73
98
  }
74
99
  const [subscribeError, count] = await subscribeInFlight;
75
100
  this.subscribeInFlight = null;
76
101
  if (subscribeError) {
77
102
  this.handlers.delete(handlerId);
78
- return err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
103
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
79
104
  }
80
105
  if (!count) {
81
106
  this.handlers.delete(handlerId);
82
- return err("SubscribeError", `Unable to subscribe to ${this.options.channel}`);
107
+ throw new SubscribeError(`Unable to subscribe to ${this.options.channel}`);
83
108
  }
84
109
  this.isSubscribed = true;
85
- return ok(() => this.unsubscribeHandler(handlerId));
110
+ return () => this.unsubscribeHandler(handlerId);
86
111
  }
87
112
  async unsubscribe() {
88
113
  const inFlightUnsubscribe = this.unsubscribeInFlight;
89
114
  if (inFlightUnsubscribe) await inFlightUnsubscribe;
90
- if (!this.isActive()) return err("UnsubscribeError", "Not subscribed");
115
+ if (!this.isActive()) throw new UnsubscribeError("Not subscribed");
91
116
  const handlers = new Map(this.handlers);
92
117
  this.handlers.clear();
93
118
  this.isSubscribed = false;
@@ -96,17 +121,16 @@ var PubSub = class {
96
121
  if (!unsubscribeInFlight) {
97
122
  this.handlers = handlers;
98
123
  this.isSubscribed = true;
99
- return err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
124
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
100
125
  }
101
126
  const [unsubscribeError] = await unsubscribeInFlight;
102
127
  if (unsubscribeError) {
103
128
  this.handlers = handlers;
104
129
  this.isSubscribed = true;
105
130
  this.unsubscribeInFlight = null;
106
- return err("UnsubscribeError", `Unable to unsubscribe from ${this.options.channel}`);
131
+ throw new UnsubscribeError(`Unable to unsubscribe from ${this.options.channel}`);
107
132
  }
108
133
  this.unsubscribeInFlight = null;
109
- return ok(true);
110
134
  }
111
135
  isActive() {
112
136
  return this.handlers.size > 0 && this.isSubscribed;
@@ -1,5 +1,22 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_chunk = require("../../chunk-CKQMccvm.cjs");
2
3
  const require_lib_errors_index = require("../errors/index.cjs");
4
+ let node_assert = require("node:assert");
5
+ node_assert = require_chunk.__toESM(node_assert);
6
+ //#region src/lib/queue/errors.ts
7
+ var SerializationError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "SerializationError";
11
+ }
12
+ };
13
+ var EnqueueError = class extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "EnqueueError";
17
+ }
18
+ };
19
+ //#endregion
3
20
  //#region src/lib/queue/index.ts
4
21
  const toMinimalJob = (jobState) => ({
5
22
  id: jobState.id,
@@ -12,9 +29,9 @@ const DEFAULT_RETRIES = 3;
12
29
  const DEFAULT_TIMEOUT = 3e4;
13
30
  const DEFAULT_CONCURRENCY = 1;
14
31
  const DEFAULT_POLL_INTERVAL = 100;
15
- const MAX_BACKOFF_DELAY = 6e4;
16
- const BASE_BACKOFF_DELAY = 1e3;
17
- const BACKOFF_MULTIPLIER = 2;
32
+ const DEFAULT_RETRY_MAX_DELAY = 6e4;
33
+ const DEFAULT_RETRY_BASE_DELAY = 1e3;
34
+ const DEFAULT_RETRY_MULTIPLIER = 2;
18
35
  const SHUTDOWN_POLL_INTERVAL = 10;
19
36
  var Queue = class {
20
37
  options;
@@ -24,14 +41,26 @@ var Queue = class {
24
41
  timeout;
25
42
  concurrency;
26
43
  pollInterval;
44
+ retryBaseDelay;
45
+ retryMultiplier;
46
+ retryMaxDelay;
27
47
  constructor(options) {
28
48
  this.options = options;
29
49
  this.retries = options.retries ?? DEFAULT_RETRIES;
30
50
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
31
51
  this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
32
52
  this.pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;
53
+ this.retryBaseDelay = options.retryBackoff?.baseDelay ?? DEFAULT_RETRY_BASE_DELAY;
54
+ this.retryMultiplier = options.retryBackoff?.multiplier ?? DEFAULT_RETRY_MULTIPLIER;
55
+ this.retryMaxDelay = options.retryBackoff?.maxDelay ?? DEFAULT_RETRY_MAX_DELAY;
56
+ node_assert.default.ok(Number.isFinite(this.retryBaseDelay) && this.retryBaseDelay > 0, "Invalid retryBackoff.baseDelay: must be a positive finite number");
57
+ node_assert.default.ok(Number.isFinite(this.retryMultiplier) && this.retryMultiplier > 0, "Invalid retryBackoff.multiplier: must be a positive finite number");
58
+ node_assert.default.ok(Number.isFinite(this.retryMaxDelay) && this.retryMaxDelay > 0, "Invalid retryBackoff.maxDelay: must be a positive finite number");
33
59
  this.startWorkers();
34
60
  }
61
+ computeBackoffDelay(attempt) {
62
+ return Math.min(this.retryBaseDelay * this.retryMultiplier ** (attempt - 1), this.retryMaxDelay);
63
+ }
35
64
  async enqueue(data) {
36
65
  const job = {
37
66
  id: crypto.randomUUID(),
@@ -41,10 +70,10 @@ var Queue = class {
41
70
  createdAt: Date.now()
42
71
  };
43
72
  const [serializeError, serialized] = this.serializeJob(job);
44
- if (serializeError || !serialized) return require_lib_errors_index.err("QueueError", "Unable to serialize job data");
73
+ if (serializeError || !serialized) throw new SerializationError("Unable to serialize job data");
45
74
  const [enqueueError] = await this.enqueueJobData(serialized);
46
- if (enqueueError) return require_lib_errors_index.err("QueueError", "Unable to enqueue job");
47
- return require_lib_errors_index.ok(job.id);
75
+ if (enqueueError) throw new EnqueueError("Unable to enqueue job");
76
+ return job.id;
48
77
  }
49
78
  async stop() {
50
79
  this.running = false;
@@ -70,7 +99,8 @@ var Queue = class {
70
99
  return require_lib_errors_index.mightThrow(this.options.redis.lpush(deadLetterKey, deadLetterEntry));
71
100
  }
72
101
  formatErrorMessage(error) {
73
- return error instanceof Error ? error.message : String(error);
102
+ if (!error) return "Unknown error";
103
+ return error.message;
74
104
  }
75
105
  startWorkers() {
76
106
  for (let i = 0; i < this.concurrency; i++) this.processJobs();
@@ -131,13 +161,13 @@ var Queue = class {
131
161
  });
132
162
  if (job.attempts <= job.maxRetries) {
133
163
  if (this.options.onRetry) {
134
- const delay = Math.min(BASE_BACKOFF_DELAY * BACKOFF_MULTIPLIER ** (job.attempts - 1), MAX_BACKOFF_DELAY);
164
+ const delay = this.computeBackoffDelay(job.attempts);
135
165
  await require_lib_errors_index.mightThrow(Promise.resolve(this.options.onRetry({
136
166
  job: toMinimalJob(job),
137
167
  error: errorMsg,
138
168
  nextRetryDelayMs: delay,
139
169
  retriesRemaining: job.maxRetries - job.attempts,
140
- backoffMultiplier: BACKOFF_MULTIPLIER
170
+ backoffMultiplier: this.retryMultiplier
141
171
  })));
142
172
  }
143
173
  await this.retryJob(job);
@@ -162,7 +192,7 @@ var Queue = class {
162
192
  })));
163
193
  }
164
194
  async retryJob(job) {
165
- const delay = Math.min(BASE_BACKOFF_DELAY * BACKOFF_MULTIPLIER ** (job.attempts - 1), MAX_BACKOFF_DELAY);
195
+ const delay = this.computeBackoffDelay(job.attempts);
166
196
  await new Promise((resolve) => setTimeout(resolve, delay));
167
197
  const [stringifyError, serialized] = this.serializeJob(job);
168
198
  if (stringifyError || !serialized) {
@@ -29,6 +29,11 @@ type ParseErrorContext = {
29
29
  parseError: string;
30
30
  timestamp: number;
31
31
  };
32
+ type QueueRetryBackoff = {
33
+ baseDelay?: number;
34
+ multiplier?: number;
35
+ maxDelay?: number;
36
+ };
32
37
  type QueueOptions<T> = {
33
38
  name: string;
34
39
  redis: Bun.RedisClient;
@@ -38,6 +43,7 @@ type QueueOptions<T> = {
38
43
  onError?: (context: ErrorContext<T>) => void | Promise<void>;
39
44
  onParseError?: (context: ParseErrorContext) => void | Promise<void>;
40
45
  retries?: number;
46
+ retryBackoff?: QueueRetryBackoff;
41
47
  timeout?: number;
42
48
  concurrency?: number;
43
49
  pollInterval?: number;
@@ -52,11 +58,12 @@ declare class Queue<T> {
52
58
  private timeout;
53
59
  private concurrency;
54
60
  private pollInterval;
61
+ private retryBaseDelay;
62
+ private retryMultiplier;
63
+ private retryMaxDelay;
55
64
  constructor(options: QueueOptions<T>);
56
- enqueue(data: T): Promise<readonly [null, string] | readonly [{
57
- readonly type: "QueueError";
58
- readonly message: string;
59
- }, null]>;
65
+ private computeBackoffDelay;
66
+ enqueue(data: T): Promise<string>;
60
67
  stop(): Promise<void>;
61
68
  private waitForPollInterval;
62
69
  private serializeJob;
@@ -29,6 +29,11 @@ type ParseErrorContext = {
29
29
  parseError: string;
30
30
  timestamp: number;
31
31
  };
32
+ type QueueRetryBackoff = {
33
+ baseDelay?: number;
34
+ multiplier?: number;
35
+ maxDelay?: number;
36
+ };
32
37
  type QueueOptions<T> = {
33
38
  name: string;
34
39
  redis: Bun.RedisClient;
@@ -38,6 +43,7 @@ type QueueOptions<T> = {
38
43
  onError?: (context: ErrorContext<T>) => void | Promise<void>;
39
44
  onParseError?: (context: ParseErrorContext) => void | Promise<void>;
40
45
  retries?: number;
46
+ retryBackoff?: QueueRetryBackoff;
41
47
  timeout?: number;
42
48
  concurrency?: number;
43
49
  pollInterval?: number;
@@ -52,11 +58,12 @@ declare class Queue<T> {
52
58
  private timeout;
53
59
  private concurrency;
54
60
  private pollInterval;
61
+ private retryBaseDelay;
62
+ private retryMultiplier;
63
+ private retryMaxDelay;
55
64
  constructor(options: QueueOptions<T>);
56
- enqueue(data: T): Promise<readonly [null, string] | readonly [{
57
- readonly type: "QueueError";
58
- readonly message: string;
59
- }, null]>;
65
+ private computeBackoffDelay;
66
+ enqueue(data: T): Promise<string>;
60
67
  stop(): Promise<void>;
61
68
  private waitForPollInterval;
62
69
  private serializeJob;
@@ -1,4 +1,19 @@
1
- import { err, mightThrow, mightThrowSync, ok } from "../errors/index.mjs";
1
+ import { mightThrow, mightThrowSync } from "../errors/index.mjs";
2
+ import assert from "node:assert";
3
+ //#region src/lib/queue/errors.ts
4
+ var SerializationError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "SerializationError";
8
+ }
9
+ };
10
+ var EnqueueError = class extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "EnqueueError";
14
+ }
15
+ };
16
+ //#endregion
2
17
  //#region src/lib/queue/index.ts
3
18
  const toMinimalJob = (jobState) => ({
4
19
  id: jobState.id,
@@ -11,9 +26,9 @@ const DEFAULT_RETRIES = 3;
11
26
  const DEFAULT_TIMEOUT = 3e4;
12
27
  const DEFAULT_CONCURRENCY = 1;
13
28
  const DEFAULT_POLL_INTERVAL = 100;
14
- const MAX_BACKOFF_DELAY = 6e4;
15
- const BASE_BACKOFF_DELAY = 1e3;
16
- const BACKOFF_MULTIPLIER = 2;
29
+ const DEFAULT_RETRY_MAX_DELAY = 6e4;
30
+ const DEFAULT_RETRY_BASE_DELAY = 1e3;
31
+ const DEFAULT_RETRY_MULTIPLIER = 2;
17
32
  const SHUTDOWN_POLL_INTERVAL = 10;
18
33
  var Queue = class {
19
34
  options;
@@ -23,14 +38,26 @@ var Queue = class {
23
38
  timeout;
24
39
  concurrency;
25
40
  pollInterval;
41
+ retryBaseDelay;
42
+ retryMultiplier;
43
+ retryMaxDelay;
26
44
  constructor(options) {
27
45
  this.options = options;
28
46
  this.retries = options.retries ?? DEFAULT_RETRIES;
29
47
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
30
48
  this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
31
49
  this.pollInterval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;
50
+ this.retryBaseDelay = options.retryBackoff?.baseDelay ?? DEFAULT_RETRY_BASE_DELAY;
51
+ this.retryMultiplier = options.retryBackoff?.multiplier ?? DEFAULT_RETRY_MULTIPLIER;
52
+ this.retryMaxDelay = options.retryBackoff?.maxDelay ?? DEFAULT_RETRY_MAX_DELAY;
53
+ assert.ok(Number.isFinite(this.retryBaseDelay) && this.retryBaseDelay > 0, "Invalid retryBackoff.baseDelay: must be a positive finite number");
54
+ assert.ok(Number.isFinite(this.retryMultiplier) && this.retryMultiplier > 0, "Invalid retryBackoff.multiplier: must be a positive finite number");
55
+ assert.ok(Number.isFinite(this.retryMaxDelay) && this.retryMaxDelay > 0, "Invalid retryBackoff.maxDelay: must be a positive finite number");
32
56
  this.startWorkers();
33
57
  }
58
+ computeBackoffDelay(attempt) {
59
+ return Math.min(this.retryBaseDelay * this.retryMultiplier ** (attempt - 1), this.retryMaxDelay);
60
+ }
34
61
  async enqueue(data) {
35
62
  const job = {
36
63
  id: crypto.randomUUID(),
@@ -40,10 +67,10 @@ var Queue = class {
40
67
  createdAt: Date.now()
41
68
  };
42
69
  const [serializeError, serialized] = this.serializeJob(job);
43
- if (serializeError || !serialized) return err("QueueError", "Unable to serialize job data");
70
+ if (serializeError || !serialized) throw new SerializationError("Unable to serialize job data");
44
71
  const [enqueueError] = await this.enqueueJobData(serialized);
45
- if (enqueueError) return err("QueueError", "Unable to enqueue job");
46
- return ok(job.id);
72
+ if (enqueueError) throw new EnqueueError("Unable to enqueue job");
73
+ return job.id;
47
74
  }
48
75
  async stop() {
49
76
  this.running = false;
@@ -69,7 +96,8 @@ var Queue = class {
69
96
  return mightThrow(this.options.redis.lpush(deadLetterKey, deadLetterEntry));
70
97
  }
71
98
  formatErrorMessage(error) {
72
- return error instanceof Error ? error.message : String(error);
99
+ if (!error) return "Unknown error";
100
+ return error.message;
73
101
  }
74
102
  startWorkers() {
75
103
  for (let i = 0; i < this.concurrency; i++) this.processJobs();
@@ -130,13 +158,13 @@ var Queue = class {
130
158
  });
131
159
  if (job.attempts <= job.maxRetries) {
132
160
  if (this.options.onRetry) {
133
- const delay = Math.min(BASE_BACKOFF_DELAY * BACKOFF_MULTIPLIER ** (job.attempts - 1), MAX_BACKOFF_DELAY);
161
+ const delay = this.computeBackoffDelay(job.attempts);
134
162
  await mightThrow(Promise.resolve(this.options.onRetry({
135
163
  job: toMinimalJob(job),
136
164
  error: errorMsg,
137
165
  nextRetryDelayMs: delay,
138
166
  retriesRemaining: job.maxRetries - job.attempts,
139
- backoffMultiplier: BACKOFF_MULTIPLIER
167
+ backoffMultiplier: this.retryMultiplier
140
168
  })));
141
169
  }
142
170
  await this.retryJob(job);
@@ -161,7 +189,7 @@ var Queue = class {
161
189
  })));
162
190
  }
163
191
  async retryJob(job) {
164
- const delay = Math.min(BASE_BACKOFF_DELAY * BACKOFF_MULTIPLIER ** (job.attempts - 1), MAX_BACKOFF_DELAY);
192
+ const delay = this.computeBackoffDelay(job.attempts);
165
193
  await new Promise((resolve) => setTimeout(resolve, delay));
166
194
  const [stringifyError, serialized] = this.serializeJob(job);
167
195
  if (stringifyError || !serialized) {