@rvoh/psychic-workers 2.4.1 → 2.7.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/dist/cjs/src/background/BaseBackgroundedModel.js +112 -19
  2. package/dist/cjs/src/background/BaseBackgroundedService.js +73 -9
  3. package/dist/cjs/src/background/BaseScheduledService.js +59 -1
  4. package/dist/cjs/src/background/index.js +418 -105
  5. package/dist/cjs/src/error/background/DebouncedJobOutpacesRateLimit.js +64 -0
  6. package/dist/cjs/src/error/background/DebouncedJobRequiresMinimumDelay.js +52 -0
  7. package/dist/cjs/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.js +31 -0
  8. package/dist/cjs/src/error/background/RateLimitedPsychicJob.js +82 -0
  9. package/dist/cjs/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.js +51 -0
  10. package/dist/cjs/src/package-exports/errors.js +7 -0
  11. package/dist/cjs/src/test-utils/WorkerTestUtils.js +79 -14
  12. package/dist/esm/src/background/BaseBackgroundedModel.js +112 -19
  13. package/dist/esm/src/background/BaseBackgroundedService.js +73 -9
  14. package/dist/esm/src/background/BaseScheduledService.js +59 -1
  15. package/dist/esm/src/background/index.js +418 -105
  16. package/dist/esm/src/error/background/DebouncedJobOutpacesRateLimit.js +64 -0
  17. package/dist/esm/src/error/background/DebouncedJobRequiresMinimumDelay.js +52 -0
  18. package/dist/esm/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.js +31 -0
  19. package/dist/esm/src/error/background/RateLimitedPsychicJob.js +82 -0
  20. package/dist/esm/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.js +51 -0
  21. package/dist/esm/src/package-exports/errors.js +7 -0
  22. package/dist/esm/src/test-utils/WorkerTestUtils.js +79 -14
  23. package/dist/types/src/background/BaseBackgroundedModel.d.ts +97 -20
  24. package/dist/types/src/background/BaseBackgroundedService.d.ts +61 -10
  25. package/dist/types/src/background/BaseScheduledService.d.ts +54 -1
  26. package/dist/types/src/background/index.d.ts +137 -5
  27. package/dist/types/src/error/background/DebouncedJobOutpacesRateLimit.d.ts +31 -0
  28. package/dist/types/src/error/background/DebouncedJobRequiresMinimumDelay.d.ts +23 -0
  29. package/dist/types/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.d.ts +16 -0
  30. package/dist/types/src/error/background/RateLimitedPsychicJob.d.ts +73 -0
  31. package/dist/types/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.d.ts +16 -0
  32. package/dist/types/src/package-exports/errors.d.ts +1 -0
  33. package/dist/types/src/psychic-app-workers/index.d.ts +102 -53
  34. package/dist/types/src/test-utils/WorkerTestUtils.d.ts +32 -1
  35. package/dist/types/src/types/background.d.ts +100 -4
  36. package/package.json +9 -8
@@ -1,4 +1,5 @@
1
1
  import { Dream } from '@rvoh/dream';
2
+ import { mergeBackgroundWithOptsIntoJobConfig } from './BaseBackgroundedService.js';
2
3
  import background from './index.js';
3
4
  import durationToSeconds from '../helpers/durationToSeconds.js';
4
5
  export default class BaseBackgroundedModel extends Dream {
@@ -11,7 +12,7 @@ export default class BaseBackgroundedModel extends Dream {
11
12
  * @returns {object} config - the background job config
12
13
  * @returns {string} config.priority - 'default' | 'urgent' | 'not_urgent' | 'last'
13
14
  * @returns {string} config.workstream - a workstream name. This would be the name of a workstream, as defined in conf/workers.ts
14
- * @returns {string} config.queueId - the id of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
15
+ * @returns {string} config.queue - the name of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
15
16
  * @returns {string} config.groupId - the groupId of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
16
17
  */
17
18
  static get backgroundJobConfig() {
@@ -56,22 +57,30 @@ export default class BaseBackgroundedModel extends Dream {
56
57
  }
57
58
  /**
58
59
  * runs the specified method in a background queue, driven by BullMQ,
59
- * sending in the provided args, including a delay in seconds, which
60
- * can be used to hold off the job for a certain amount of time after
61
- * it is entered into the queue.
60
+ * sending in the provided args, along with a delay object — one or more of
61
+ * `{ seconds, minutes, hours, days }` — which holds the job off for that much
62
+ * time after it is entered into the queue.
63
+ *
64
+ * The delay object also accepts an optional `jobId`, which turns the delay
65
+ * into a **debounce**: repeated calls carrying the same `jobId` collapse into
66
+ * a single execution, which runs once the delay has elapsed without another
67
+ * call arriving. `jobId` is a deduplication key rather than a BullMQ job id.
68
+ *
69
+ * See {@link DelayedJobOpts.jobId}.
62
70
  *
63
71
  * ```ts
64
- * await User.backgroundWithDelay('myMethod', 'abc', 123)
72
+ * await User.backgroundWithDelay({ minutes: 5 }, 'myMethod', 'abc', 123)
65
73
  * ```
66
- * though calling background must be awaited, the resolution of the promise
67
- * is an indication that the job was put in the queue, not that it has
68
- * completed.
74
+ * though calling backgroundWithDelay must be awaited, the resolution of the promise
75
+ * is an indication that a run is pending, not that it has completed.
69
76
  *
70
77
  * NOTE: in test environments, psychic will immediately invoke the underlying
71
78
  * method, preventing you from needing to explicitly wait for queues to flush
72
79
  * before making assertions.
73
80
  *
74
- * @param delaySeconds - the amount of time (in seconds) you want to hold off before allowing the job to run
81
+ * @deprecated use `backgroundWith({ delay }, methodName, ...args)` instead. This method will be removed in a future major version.
82
+ *
83
+ * @param delay - how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
75
84
  * @param methodName - the name of the static method you wish to run in the background
76
85
  * @param args - a variadic list of arguments to be sent to your method
77
86
  */
@@ -85,6 +94,44 @@ export default class BaseBackgroundedModel extends Dream {
85
94
  jobConfig: safeThis.backgroundJobConfig,
86
95
  });
87
96
  }
97
+ /**
98
+ * runs the specified method in a background queue, driven by BullMQ,
99
+ * sending in the provided args, along with an options object which can
100
+ * be used to delay the job and/or override the priority provided by
101
+ * the `backgroundJobConfig` getter on this model.
102
+ *
103
+ * Adding a `jobId` to the delay turns it into a **debounce**: repeated calls
104
+ * carrying the same `jobId` collapse into a single execution, which runs once
105
+ * the delay has elapsed without another call arriving. `jobId` is a
106
+ * deduplication key rather than a BullMQ job id.
107
+ *
108
+ * See {@link DelayedJobOpts.jobId}.
109
+ *
110
+ * ```ts
111
+ * await User.backgroundWith({ delay: { seconds: 30, jobId: 'my-unique-job-id' }, priority: 'urgent' }, 'myMethod', 'abc', 123)
112
+ * ```
113
+ * though calling backgroundWith must be awaited, the resolution of the promise
114
+ * is an indication that a run is pending, not that it has completed.
115
+ *
116
+ * NOTE: in test environments, psychic will immediately invoke the underlying
117
+ * method, preventing you from needing to explicitly wait for queues to flush
118
+ * before making assertions.
119
+ *
120
+ * @param opts - options for backgrounding this job
121
+ * @param opts.delay - (optional) how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
122
+ * @param opts.priority - (optional) a priority which, when provided, overrides the priority provided by `backgroundJobConfig`
123
+ * @param methodName - the name of the static method you wish to run in the background
124
+ * @param args - a variadic list of arguments to be sent to your method
125
+ */
126
+ static async backgroundWith(opts, methodName, ...args) {
127
+ const safeThis = this;
128
+ return await background.staticMethod(safeThis, methodName, {
129
+ globalName: safeThis.globalName,
130
+ ...(opts.delay ? { delaySeconds: durationToSeconds(opts.delay), jobId: opts.delay.jobId } : {}),
131
+ args,
132
+ jobConfig: mergeBackgroundWithOptsIntoJobConfig(safeThis.backgroundJobConfig, opts),
133
+ });
134
+ }
88
135
  /**
89
136
  * types composed by psychic must be provided, since psychic-workers leverages
90
137
  * the sync command in psychic to read your backgroundable services and extract
@@ -111,7 +158,7 @@ export default class BaseBackgroundedModel extends Dream {
111
158
  * method, preventing you from needing to explicitly wait for queues to flush
112
159
  * before making assertions.
113
160
  *
114
- * @param methodName - the name of the static method you wish to run in the background
161
+ * @param methodName - the name of the instance method you wish to run in the background
115
162
  * @param args - a variadic list of arguments to be sent to your method
116
163
  */
117
164
  async background(methodName, ...args) {
@@ -123,24 +170,32 @@ export default class BaseBackgroundedModel extends Dream {
123
170
  }
124
171
  /**
125
172
  * runs the specified method in a background queue, driven by BullMQ,
126
- * sending in the provided args, including a delay in seconds, which
127
- * can be used to hold off the job for a certain amount of time after
128
- * it is entered into the queue.
173
+ * sending in the provided args, along with a delay object — one or more of
174
+ * `{ seconds, minutes, hours, days }` — which holds the job off for that much
175
+ * time after it is entered into the queue.
176
+ *
177
+ * The delay object also accepts an optional `jobId`, which turns the delay
178
+ * into a **debounce**: repeated calls carrying the same `jobId` collapse into
179
+ * a single execution, which runs once the delay has elapsed without another
180
+ * call arriving. `jobId` is a deduplication key rather than a BullMQ job id.
181
+ *
182
+ * See {@link DelayedJobOpts.jobId}.
129
183
  *
130
184
  * ```ts
131
185
  * const user = await User.lastOrFail()
132
- * await user.backgroundWithDelay('myMethod', 'abc', 123)
186
+ * await user.backgroundWithDelay({ minutes: 5 }, 'myMethod', 'abc', 123)
133
187
  * ```
134
- * though calling background must be awaited, the resolution of the promise
135
- * is an indication that the job was put in the queue, not that it has
136
- * completed.
188
+ * though calling backgroundWithDelay must be awaited, the resolution of the promise
189
+ * is an indication that a run is pending, not that it has completed.
137
190
  *
138
191
  * NOTE: in test environments, psychic will immediately invoke the underlying
139
192
  * method, preventing you from needing to explicitly wait for queues to flush
140
193
  * before making assertions.
141
194
  *
142
- * @param delaySeconds - the amount of time (in seconds) you want to hold off before allowing the job to run
143
- * @param methodName - the name of the static method you wish to run in the background
195
+ * @deprecated use `backgroundWith({ delay }, methodName, ...args)` instead. This method will be removed in a future major version.
196
+ *
197
+ * @param delay - how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
198
+ * @param methodName - the name of the instance method you wish to run in the background
144
199
  * @param args - a variadic list of arguments to be sent to your method
145
200
  */
146
201
  async backgroundWithDelay(delay, methodName, ...args) {
@@ -152,4 +207,42 @@ export default class BaseBackgroundedModel extends Dream {
152
207
  jobConfig: safeThis.backgroundJobConfig,
153
208
  });
154
209
  }
210
+ /**
211
+ * runs the specified method in a background queue, driven by BullMQ,
212
+ * sending in the provided args, along with an options object which can
213
+ * be used to delay the job and/or override the priority provided by
214
+ * the `backgroundJobConfig` getter on this model.
215
+ *
216
+ * Adding a `jobId` to the delay turns it into a **debounce**: repeated calls
217
+ * carrying the same `jobId` collapse into a single execution, which runs once
218
+ * the delay has elapsed without another call arriving. `jobId` is a
219
+ * deduplication key rather than a BullMQ job id.
220
+ *
221
+ * See {@link DelayedJobOpts.jobId}.
222
+ *
223
+ * ```ts
224
+ * const user = await User.lastOrFail()
225
+ * await user.backgroundWith({ delay: { seconds: 30, jobId: 'my-unique-job-id' }, priority: 'urgent' }, 'myMethod', 'abc', 123)
226
+ * ```
227
+ * though calling backgroundWith must be awaited, the resolution of the promise
228
+ * is an indication that a run is pending, not that it has completed.
229
+ *
230
+ * NOTE: in test environments, psychic will immediately invoke the underlying
231
+ * method, preventing you from needing to explicitly wait for queues to flush
232
+ * before making assertions.
233
+ *
234
+ * @param opts - options for backgrounding this job
235
+ * @param opts.delay - (optional) how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
236
+ * @param opts.priority - (optional) a priority which, when provided, overrides the priority provided by `backgroundJobConfig`
237
+ * @param methodName - the name of the instance method you wish to run in the background
238
+ * @param args - a variadic list of arguments to be sent to your method
239
+ */
240
+ async backgroundWith(opts, methodName, ...args) {
241
+ const safeThis = this;
242
+ return await background.modelInstanceMethod(safeThis, methodName, {
243
+ args,
244
+ ...(opts.delay ? { delaySeconds: durationToSeconds(opts.delay), jobId: opts.delay.jobId } : {}),
245
+ jobConfig: mergeBackgroundWithOptsIntoJobConfig(safeThis.backgroundJobConfig, opts),
246
+ });
247
+ }
155
248
  }
@@ -11,7 +11,7 @@ export default class BaseBackgroundedService {
11
11
  * @returns {object} config - the background job config
12
12
  * @returns {string} config.priority - 'default' | 'urgent' | 'not_urgent' | 'last'
13
13
  * @returns {string} config.workstream - a workstream name. This would be the name of a workstream, as defined in conf/workers.ts
14
- * @returns {string} config.queueId - the id of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
14
+ * @returns {string} config.queue - the name of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
15
15
  * @returns {string} config.groupId - the groupId of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
16
16
  */
17
17
  static get backgroundJobConfig() {
@@ -68,22 +68,30 @@ export default class BaseBackgroundedService {
68
68
  }
69
69
  /**
70
70
  * runs the specified method in a background queue, driven by BullMQ,
71
- * sending in the provided args, including a delay in seconds, which
72
- * can be used to hold off the job for a certain amount of time after
73
- * it is entered into the queue.
71
+ * sending in the provided args, along with a delay object — one or more of
72
+ * `{ seconds, minutes, hours, days }` — which holds the job off for that much
73
+ * time after it is entered into the queue.
74
+ *
75
+ * The delay object also accepts an optional `jobId`, which turns the delay
76
+ * into a **debounce**: repeated calls carrying the same `jobId` collapse into
77
+ * a single execution, which runs once the delay has elapsed without another
78
+ * call arriving. `jobId` is a deduplication key rather than a BullMQ job id.
79
+ *
80
+ * See {@link DelayedJobOpts.jobId}.
74
81
  *
75
82
  * ```ts
76
- * await MyBackgroundableClass.backgroundWithDelay('myMethod', 'abc', 123)
83
+ * await MyBackgroundableClass.backgroundWithDelay({ minutes: 5 }, 'myMethod', 'abc', 123)
77
84
  * ```
78
- * though calling background must be awaited, the resolution of the promise
79
- * is an indication that the job was put in the queue, not that it has
80
- * completed.
85
+ * though calling backgroundWithDelay must be awaited, the resolution of the promise
86
+ * is an indication that a run is pending, not that it has completed.
81
87
  *
82
88
  * NOTE: in test environments, psychic will immediately invoke the underlying
83
89
  * method, preventing you from needing to explicitly wait for queues to flush
84
90
  * before making assertions.
85
91
  *
86
- * @param delaySeconds - the amount of time (in seconds) you want to hold off before allowing the job to run
92
+ * @deprecated use `backgroundWith({ delay }, methodName, ...args)` instead. This method will be removed in a future major version.
93
+ *
94
+ * @param delay - how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
87
95
  * @param methodName - the name of the static method you wish to run in the background
88
96
  * @param args - a variadic list of arguments to be sent to your method
89
97
  */
@@ -97,6 +105,49 @@ export default class BaseBackgroundedService {
97
105
  jobConfig: safeThis.backgroundJobConfig,
98
106
  });
99
107
  }
108
+ /**
109
+ * runs the specified method in a background queue, driven by BullMQ,
110
+ * sending in the provided args, along with an options object which can
111
+ * be used to delay the job and/or override the priority provided by
112
+ * the `backgroundJobConfig` getter on this service.
113
+ *
114
+ * Adding a `jobId` to the delay turns it into a **debounce**: repeated calls
115
+ * carrying the same `jobId` collapse into a single execution, which runs once
116
+ * the delay has elapsed without another call arriving. `jobId` is a
117
+ * deduplication key rather than a BullMQ job id.
118
+ *
119
+ * See {@link DelayedJobOpts.jobId}.
120
+ *
121
+ * ```ts
122
+ * await MyBackgroundableClass.backgroundWith(
123
+ * { delay: { seconds: 30, jobId: 'my-unique-job-id' }, priority: 'urgent' },
124
+ * 'myMethod',
125
+ * 'abc',
126
+ * 123,
127
+ * )
128
+ * ```
129
+ * though calling backgroundWith must be awaited, the resolution of the promise
130
+ * is an indication that a run is pending, not that it has completed.
131
+ *
132
+ * NOTE: in test environments, psychic will immediately invoke the underlying
133
+ * method, preventing you from needing to explicitly wait for queues to flush
134
+ * before making assertions.
135
+ *
136
+ * @param opts - options for backgrounding this job
137
+ * @param opts.delay - (optional) how long you want to hold off before allowing the job to run, given as at least one of `seconds`, `minutes`, `hours` or `days`, plus an optional `jobId` (a deduplication key) which debounces repeated calls into a single run
138
+ * @param opts.priority - (optional) a priority which, when provided, overrides the priority provided by `backgroundJobConfig`
139
+ * @param methodName - the name of the static method you wish to run in the background
140
+ * @param args - a variadic list of arguments to be sent to your method
141
+ */
142
+ static async backgroundWith(opts, methodName, ...args) {
143
+ const safeThis = this;
144
+ return await background.staticMethod(safeThis, methodName, {
145
+ globalName: safeThis.globalName,
146
+ ...(opts.delay ? { delaySeconds: durationToSeconds(opts.delay), jobId: opts.delay.jobId } : {}),
147
+ args,
148
+ jobConfig: mergeBackgroundWithOptsIntoJobConfig(safeThis.backgroundJobConfig, opts),
149
+ });
150
+ }
100
151
  /**
101
152
  * types composed by psychic must be provided, since psychic-workers leverages
102
153
  * the sync command in psychic to read your backgroundable services and extract
@@ -108,3 +159,16 @@ export default class BaseBackgroundedService {
108
159
  throw new Error('Must define psychicWorkerTypes getter in ApplicationBackgroundedService class within your application');
109
160
  }
110
161
  }
162
+ /**
163
+ * @internal
164
+ *
165
+ * returns a copy of the provided job config, with the priority
166
+ * replaced by the priority found in the `backgroundWith` opts,
167
+ * if one was provided.
168
+ */
169
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
170
+ export function mergeBackgroundWithOptsIntoJobConfig(jobConfig, opts) {
171
+ if (!opts.priority)
172
+ return jobConfig;
173
+ return { ...jobConfig, priority: opts.priority };
174
+ }
@@ -10,7 +10,7 @@ export default class BaseScheduledService {
10
10
  * @returns {object} config - the background job config
11
11
  * @returns {string} config.priority - 'default' | 'urgent' | 'not_urgent' | 'last'
12
12
  * @returns {string} config.workstream - a workstream name. This would be the name of a workstream, as defined in conf/workers.ts
13
- * @returns {string} config.queueId - the id of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
13
+ * @returns {string} config.queue - the name of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
14
14
  * @returns {string} config.groupId - the groupId of the BullMQ queue you wish to connect to. This can only be provided if workstream is not provided.
15
15
  */
16
16
  static get backgroundJobConfig() {
@@ -66,6 +66,64 @@ export default class BaseScheduledService {
66
66
  jobConfig: safeThis.backgroundJobConfig,
67
67
  });
68
68
  }
69
+ /**
70
+ * Returns the id that `schedule` registers a job under, which can be passed
71
+ * to {@link BaseScheduledService.unschedule} to remove that job.
72
+ *
73
+ * The id is derived from the service's global name and the method name. It
74
+ * performs no queue construction and no redis I/O, so it is safe to call
75
+ * anywhere, including from a console in development.
76
+ *
77
+ * The intended workflow for deleting a scheduled service is to read the id
78
+ * off the class before removing it:
79
+ *
80
+ * ```ts
81
+ * MyScheduledService.unscheduleId('myHourlyMethod')
82
+ * // => 'services/MyScheduledService:myHourlyMethod'
83
+ * ```
84
+ *
85
+ * then check that string into a seed or migration and call `unschedule` with
86
+ * it, which allows the class itself to be deleted in the same deploy.
87
+ *
88
+ * ```ts
89
+ * await ApplicationScheduledService.unschedule('services/MyScheduledService:myHourlyMethod')
90
+ * ```
91
+ *
92
+ * @param methodName - the name of the static method that was scheduled
93
+ * @returns the id `schedule` registered this method under
94
+ */
95
+ static unscheduleId(methodName) {
96
+ const safeThis = this;
97
+ return background.jobSchedulerId(safeThis.globalName, methodName);
98
+ }
99
+ /**
100
+ * Removes a scheduled job, preventing it from being run again.
101
+ *
102
+ * ```ts
103
+ * await MyScheduledService.unschedule(MyScheduledService.unscheduleId('myHourlyMethod'))
104
+ * ```
105
+ *
106
+ * Since the id is just a string, it can also be checked into a seed or
107
+ * migration, which allows the scheduled service class to be deleted in the
108
+ * same deploy that stops its job:
109
+ *
110
+ * ```ts
111
+ * await ApplicationScheduledService.unschedule('services/MyScheduledService:myHourlyMethod')
112
+ * ```
113
+ *
114
+ * Every queue in your application is checked, so this finds the job whether
115
+ * or not the service's workstream has changed since it was scheduled, and
116
+ * whether it landed in a current or a transitional queue.
117
+ *
118
+ * NOTE: unscheduling stops future runs. It does not cancel a run that has
119
+ * already been placed on a queue, so a final invocation may still happen.
120
+ *
121
+ * @param id - the id the job was scheduled under, from {@link BaseScheduledService.unscheduleId}
122
+ * @returns true if a scheduled job was removed, false if none was found
123
+ */
124
+ static async unschedule(id) {
125
+ return await background.unschedule(id);
126
+ }
69
127
  /**
70
128
  * types composed by psychic must be provided, since psychic-workers leverages
71
129
  * the sync command in psychic to read your backgroundable services and extract