0z2i6v3u5t 1.0.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 (211) hide show
  1. package/.devcontainer/devcontainer.json +4 -0
  2. package/.devcontainer/setup.sh +11 -0
  3. package/.dockerignore +2 -0
  4. package/.github/CONTRIBUTING.md +52 -0
  5. package/.github/FUNDING.yml +3 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +43 -0
  9. package/.github/dependabot.yml +17 -0
  10. package/.github/workflows/codeql.yml +76 -0
  11. package/.github/workflows/publish_docs.yml +25 -0
  12. package/.github/workflows/test.yml +78 -0
  13. package/.nvmrc +1 -0
  14. package/.prettierignore +1 -0
  15. package/.prettierrc +1 -0
  16. package/.vscode/launch.json +42 -0
  17. package/CODE_OF_CONDUCT.md +76 -0
  18. package/Dockerfile +17 -0
  19. package/LICENSE +21 -0
  20. package/README.md +3 -0
  21. package/SECURITY.md +5 -0
  22. package/__tests__/actions/cacheTest.ts +58 -0
  23. package/__tests__/actions/randomNumber.ts +26 -0
  24. package/__tests__/actions/recursiveAction.ts +16 -0
  25. package/__tests__/actions/sleepTest.ts +24 -0
  26. package/__tests__/actions/status.ts +17 -0
  27. package/__tests__/actions/swagger.ts +76 -0
  28. package/__tests__/actions/validationTest.ts +63 -0
  29. package/__tests__/cli/cli.ts +126 -0
  30. package/__tests__/core/api.ts +632 -0
  31. package/__tests__/core/cache.ts +400 -0
  32. package/__tests__/core/chatRoom.ts +589 -0
  33. package/__tests__/core/cli.ts +349 -0
  34. package/__tests__/core/cluster.ts +132 -0
  35. package/__tests__/core/config.ts +78 -0
  36. package/__tests__/core/errors.ts +112 -0
  37. package/__tests__/core/log.ts +23 -0
  38. package/__tests__/core/middleware.ts +427 -0
  39. package/__tests__/core/plugins/partialPlugin.ts +94 -0
  40. package/__tests__/core/plugins/withPlugin.ts +88 -0
  41. package/__tests__/core/plugins/withoutPlugin.ts +81 -0
  42. package/__tests__/core/process.ts +42 -0
  43. package/__tests__/core/specHelper.ts +330 -0
  44. package/__tests__/core/staticFile/compression.ts +99 -0
  45. package/__tests__/core/staticFile/staticFile.ts +180 -0
  46. package/__tests__/core/tasks/customQueueFunction.ts +67 -0
  47. package/__tests__/core/tasks/fullWorkerFlow.ts +199 -0
  48. package/__tests__/core/tasks/tasks.ts +605 -0
  49. package/__tests__/integration/browser.ts +133 -0
  50. package/__tests__/integration/ioredis-mock.ts +194 -0
  51. package/__tests__/integration/sendBuffer.ts +97 -0
  52. package/__tests__/integration/sendFile.ts +24 -0
  53. package/__tests__/integration/sharedFingerprint.ts +82 -0
  54. package/__tests__/integration/taskFlow.ts +110 -0
  55. package/__tests__/jest.ts +5 -0
  56. package/__tests__/modules/action.ts +103 -0
  57. package/__tests__/modules/config.ts +19 -0
  58. package/__tests__/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +24 -0
  59. package/__tests__/servers/web/allowedRequestHosts.ts +88 -0
  60. package/__tests__/servers/web/enableMultiples.ts +83 -0
  61. package/__tests__/servers/web/fileUpload.ts +79 -0
  62. package/__tests__/servers/web/jsonp.ts +57 -0
  63. package/__tests__/servers/web/nonMultiples.ts +83 -0
  64. package/__tests__/servers/web/rawBody.ts +208 -0
  65. package/__tests__/servers/web/returnErrorCodes.ts +55 -0
  66. package/__tests__/servers/web/routes/deepRoutes.ts +96 -0
  67. package/__tests__/servers/web/routes/routes.ts +579 -0
  68. package/__tests__/servers/web/routes/veryDeepRoutes.ts +92 -0
  69. package/__tests__/servers/web/web.ts +1031 -0
  70. package/__tests__/servers/websocket.ts +795 -0
  71. package/__tests__/tasks/runAction.ts +37 -0
  72. package/__tests__/template.ts.example +20 -0
  73. package/__tests__/testCliCommands/hello.ts +44 -0
  74. package/__tests__/testPlugin/public/plugin.html +1 -0
  75. package/__tests__/testPlugin/src/actions/pluginAction.ts +14 -0
  76. package/__tests__/testPlugin/src/bin/hello.ts +22 -0
  77. package/__tests__/testPlugin/src/initializers/pluginInitializer.ts +17 -0
  78. package/__tests__/testPlugin/src/tasks/pluginTask.ts +15 -0
  79. package/__tests__/testPlugin/tsconfig.json +10 -0
  80. package/__tests__/utils/utils.ts +492 -0
  81. package/app.json +23 -0
  82. package/bin/deploy-docs +39 -0
  83. package/client/ActionheroWebsocketClient.js +277 -0
  84. package/docker-compose.yml +73 -0
  85. package/package.json +24 -0
  86. package/public/chat.html +194 -0
  87. package/public/css/cosmo.css +12 -0
  88. package/public/favicon.ico +0 -0
  89. package/public/index.html +115 -0
  90. package/public/javascript/.gitkeep +0 -0
  91. package/public/linkedSession.html +80 -0
  92. package/public/logo/actionhero-small.png +0 -0
  93. package/public/logo/actionhero.png +0 -0
  94. package/public/pixel.gif +0 -0
  95. package/public/simple.html +2 -0
  96. package/public/swagger.html +32 -0
  97. package/public/websocketLoadTest.html +322 -0
  98. package/src/actions/cacheTest.ts +58 -0
  99. package/src/actions/createChatRoom.ts +20 -0
  100. package/src/actions/randomNumber.ts +17 -0
  101. package/src/actions/recursiveAction.ts +13 -0
  102. package/src/actions/sendFile.ts +12 -0
  103. package/src/actions/sleepTest.ts +40 -0
  104. package/src/actions/status.ts +73 -0
  105. package/src/actions/swagger.ts +155 -0
  106. package/src/actions/validationTest.ts +36 -0
  107. package/src/bin/actionhero.ts +225 -0
  108. package/src/bin/methods/actions/list.ts +30 -0
  109. package/src/bin/methods/console.ts +26 -0
  110. package/src/bin/methods/generate/action.ts +58 -0
  111. package/src/bin/methods/generate/cli.ts +51 -0
  112. package/src/bin/methods/generate/initializer.ts +54 -0
  113. package/src/bin/methods/generate/plugin.ts +57 -0
  114. package/src/bin/methods/generate/server.ts +38 -0
  115. package/src/bin/methods/generate/task.ts +68 -0
  116. package/src/bin/methods/generate.ts +176 -0
  117. package/src/bin/methods/task/enqueue.ts +35 -0
  118. package/src/classes/action.ts +98 -0
  119. package/src/classes/actionProcessor.ts +463 -0
  120. package/src/classes/api.ts +51 -0
  121. package/src/classes/cli.ts +67 -0
  122. package/src/classes/config.ts +15 -0
  123. package/src/classes/connection.ts +321 -0
  124. package/src/classes/exceptionReporter.ts +9 -0
  125. package/src/classes/initializer.ts +59 -0
  126. package/src/classes/initializers.ts +5 -0
  127. package/src/classes/input.ts +9 -0
  128. package/src/classes/inputs.ts +34 -0
  129. package/src/classes/process/actionheroVersion.ts +15 -0
  130. package/src/classes/process/env.ts +16 -0
  131. package/src/classes/process/id.ts +34 -0
  132. package/src/classes/process/pid.ts +32 -0
  133. package/src/classes/process/projectRoot.ts +16 -0
  134. package/src/classes/process/typescript.ts +47 -0
  135. package/src/classes/process.ts +479 -0
  136. package/src/classes/server.ts +251 -0
  137. package/src/classes/task.ts +87 -0
  138. package/src/config/api.ts +107 -0
  139. package/src/config/errors.ts +162 -0
  140. package/src/config/logger.ts +113 -0
  141. package/src/config/plugins.ts +37 -0
  142. package/src/config/redis.ts +78 -0
  143. package/src/config/routes.ts +44 -0
  144. package/src/config/tasks.ts +84 -0
  145. package/src/config/web.ts +136 -0
  146. package/src/config/websocket.ts +62 -0
  147. package/src/index.ts +46 -0
  148. package/src/initializers/actions.ts +125 -0
  149. package/src/initializers/chatRoom.ts +214 -0
  150. package/src/initializers/connections.ts +124 -0
  151. package/src/initializers/exceptions.ts +155 -0
  152. package/src/initializers/params.ts +52 -0
  153. package/src/initializers/redis.ts +191 -0
  154. package/src/initializers/resque.ts +248 -0
  155. package/src/initializers/routes.ts +229 -0
  156. package/src/initializers/servers.ts +134 -0
  157. package/src/initializers/specHelper.ts +195 -0
  158. package/src/initializers/staticFile.ts +253 -0
  159. package/src/initializers/tasks.ts +188 -0
  160. package/src/modules/action.ts +89 -0
  161. package/src/modules/cache.ts +326 -0
  162. package/src/modules/chatRoom.ts +321 -0
  163. package/src/modules/config.ts +246 -0
  164. package/src/modules/log.ts +62 -0
  165. package/src/modules/redis.ts +93 -0
  166. package/src/modules/route.ts +59 -0
  167. package/src/modules/specHelper.ts +182 -0
  168. package/src/modules/task.ts +527 -0
  169. package/src/modules/utils/argv.ts +3 -0
  170. package/src/modules/utils/arrayStartingMatch.ts +21 -0
  171. package/src/modules/utils/arrayUnique.ts +15 -0
  172. package/src/modules/utils/collapseObjectToArray.ts +33 -0
  173. package/src/modules/utils/deepCopy.ts +3 -0
  174. package/src/modules/utils/ensureNoTsHeaderOrSpecFiles.ts +19 -0
  175. package/src/modules/utils/eventLoopDelay.ts +34 -0
  176. package/src/modules/utils/fileUtils.ts +119 -0
  177. package/src/modules/utils/filterObjectForLogging.ts +51 -0
  178. package/src/modules/utils/filterResponseForLogging.ts +53 -0
  179. package/src/modules/utils/getExternalIPAddress.ts +17 -0
  180. package/src/modules/utils/hashMerge.ts +63 -0
  181. package/src/modules/utils/isPlainObject.ts +45 -0
  182. package/src/modules/utils/isRunning.ts +7 -0
  183. package/src/modules/utils/parseCookies.ts +20 -0
  184. package/src/modules/utils/parseHeadersForClientAddress.ts +53 -0
  185. package/src/modules/utils/parseIPv6URI.ts +24 -0
  186. package/src/modules/utils/replaceDistWithSrc.ts +9 -0
  187. package/src/modules/utils/safeGlob.ts +6 -0
  188. package/src/modules/utils/sleep.ts +8 -0
  189. package/src/modules/utils/sortGlobalMiddleware.ts +17 -0
  190. package/src/modules/utils/sourceRelativeLinkPath.ts +29 -0
  191. package/src/modules/utils.ts +66 -0
  192. package/src/server.ts +20 -0
  193. package/src/servers/web.ts +894 -0
  194. package/src/servers/websocket.ts +304 -0
  195. package/src/tasks/runAction.ts +29 -0
  196. package/tea.yaml +9 -0
  197. package/templates/README.md.template +17 -0
  198. package/templates/action.ts.template +15 -0
  199. package/templates/boot.js.template +9 -0
  200. package/templates/cli.ts.template +15 -0
  201. package/templates/gitignore.template +23 -0
  202. package/templates/initializer.ts.template +17 -0
  203. package/templates/package-plugin.json.template +12 -0
  204. package/templates/package.json.template +45 -0
  205. package/templates/projectMap.txt +39 -0
  206. package/templates/projectServer.ts.template +20 -0
  207. package/templates/server.ts.template +37 -0
  208. package/templates/task.ts.template +16 -0
  209. package/templates/test/action.ts.template +13 -0
  210. package/templates/test/task.ts.template +20 -0
  211. package/tsconfig.json +11 -0
@@ -0,0 +1,527 @@
1
+ import { api, config, utils, log } from "./../index";
2
+ import { Task, TaskInputs } from "./../classes/task";
3
+ import { ErrorPayload } from "node-resque";
4
+
5
+ export namespace task {
6
+ /**
7
+ * An example middleware
8
+ * ```js
9
+ * const middleware = {
10
+ * name: 'timer',
11
+ * global: true,
12
+ * priority: 90,
13
+ * preProcessor: async function () {
14
+ * const worker = this.worker
15
+ * worker.startTime = process.hrtime()
16
+ * },
17
+ * postProcessor: async function () {
18
+ * const worker = this.worker
19
+ * const elapsed = process.hrtime(worker.startTime)
20
+ * const seconds = elapsed[0]
21
+ * const millis = elapsed[1] / 1000000
22
+ * log(worker.job.class + ' done in ' + seconds + ' s and ' + millis + ' ms.', 'info')
23
+ * },
24
+ * preEnqueue: async function () {
25
+ * return true // returning `false` will prevent the task from enqueueing
26
+ * },
27
+ * postEnqueue: async function () {
28
+ * log("Task successfully enqueued!")
29
+ * }
30
+ * }
31
+ * api.tasks.addMiddleware(middleware)
32
+ ```
33
+ */
34
+ export interface TaskMiddleware {
35
+ /**Unique name for the middleware. */
36
+ name: string;
37
+ /**Is this middleware applied to all tasks? */
38
+ global: boolean;
39
+ /**Module load order. Defaults to `api.config.general.defaultMiddlewarePriority`. */
40
+ priority?: number;
41
+ /**Called berore the task runs. Has access to all params, before sanitization. Can modify the data object for use in tasks. */
42
+ preProcessor?: () => boolean;
43
+ /**Called after the task runs.*/
44
+ postProcessor?: () => boolean;
45
+ /**Called before a task using this middleware is enqueued. */
46
+ preEnqueue?: () => Promise<boolean>;
47
+ /**Called after a task using this middleware is enqueued. */
48
+ postEnqueue?: () => boolean;
49
+ }
50
+
51
+ /**
52
+ * Enqueue a task to be performed in the background.
53
+ * Will throw an error if redis cannot be reached.
54
+ */
55
+ export async function enqueue(
56
+ taskName: string,
57
+ inputs?: TaskInputs,
58
+ queue: string = api.tasks.tasks[taskName].queue,
59
+ ) {
60
+ await validateInput(taskName, inputs);
61
+ return api.resque.queue.enqueue(queue, taskName, [inputs]);
62
+ }
63
+
64
+ /**
65
+ * Enqueue a task to be performed in the background, at a certain time in the future.
66
+ * Will throw an error if redis cannot be reached.
67
+ *
68
+ * Inputs:
69
+ * * taskName: The name of the task.
70
+ * * inputs: inputs to pass to the task.
71
+ * * queue: (Optional) Which queue/priority to run this instance of the task on.
72
+ * * suppressDuplicateTaskError: (optional) Suppress errors when the same task with the same arguments are double-enqueued for the same time
73
+ */
74
+ export async function enqueueAt(
75
+ timestamp: number,
76
+ taskName: string,
77
+ inputs?: TaskInputs,
78
+ queue: string = api.tasks.tasks[taskName].queue,
79
+ suppressDuplicateTaskError = false,
80
+ ) {
81
+ await validateInput(taskName, inputs);
82
+ return api.resque.queue.enqueueAt(
83
+ timestamp,
84
+ queue,
85
+ taskName,
86
+ [inputs],
87
+ suppressDuplicateTaskError,
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Enqueue a task to be performed in the background, at a certain number of ms from now.
93
+ * Will throw an error if redis cannot be reached.
94
+ *
95
+ * Inputs:
96
+ * * timestamp: At what time the task is able to be run. Does not guarantee that the task will be run at this time. (in ms)
97
+ * * taskName: The name of the task.
98
+ * * inputs: inputs to pass to the task.
99
+ * * queue: (Optional) Which queue/priority to run this instance of the task on.
100
+ * * suppressDuplicateTaskError: (optional) Suppress errors when the same task with the same arguments are double-enqueued for the same time
101
+ */
102
+ export async function enqueueIn(
103
+ time: number,
104
+ taskName: string,
105
+ inputs?: TaskInputs,
106
+ queue: string = api.tasks.tasks[taskName].queue,
107
+ suppressDuplicateTaskError = false,
108
+ ) {
109
+ await validateInput(taskName, inputs);
110
+ return api.resque.queue.enqueueIn(
111
+ time,
112
+ queue,
113
+ taskName,
114
+ [inputs],
115
+ suppressDuplicateTaskError,
116
+ );
117
+ }
118
+
119
+ /**
120
+ * Delete a previously enqueued task, which hasn't been run yet, from a queue.
121
+ * Will throw an error if redis cannot be reached.
122
+ *
123
+ * Inputs:
124
+ * * q: Which queue/priority is the task stored on?
125
+ * * taskName: The name of the job, likely to be the same name as a tak.
126
+ * * args: The arguments of the job. Note, arguments passed to a Task initially may be modified when enqueuing. It is best to read job properties first via `api.tasks.queued` or similar method.
127
+ * * count: Of the jobs that match q, taskName, and args, up to what position should we delete? (Default 0; this command is 0-indexed)
128
+ */
129
+ export async function del(
130
+ q: string,
131
+ taskName: string,
132
+ args?: TaskInputs,
133
+ count?: number,
134
+ ) {
135
+ return api.resque.queue.del(q, taskName, [args], count);
136
+ }
137
+
138
+ /**
139
+ * * will delete all jobs in the given queue of the named function/class
140
+ * * will not prevent new jobs from being added as this method is running
141
+ * * will not delete jobs in the delayed queues
142
+ *
143
+ * Inputs:
144
+ * * q: Which queue/priority is to run on?
145
+ * * taskName: The name of the job, likely to be the same name as a tak.
146
+ * * start? - starting position of task count to remove
147
+ * * stop? - stop position of task count to remove
148
+ */
149
+ export async function delByFunction(
150
+ q: string,
151
+ taskName: string,
152
+ start?: number,
153
+ stop?: number,
154
+ ) {
155
+ return api.resque.queue.delByFunction(q, taskName, start, stop);
156
+ }
157
+
158
+ /**
159
+ * Delete all previously enqueued tasks, which haven't been run yet, from all possible delayed timestamps.
160
+ * Will throw an error if redis cannot be reached.
161
+ *
162
+ * Inputs:
163
+ * * q: Which queue/priority is to run on?
164
+ * * taskName: The name of the job, likely to be the same name as a tak.
165
+ * * inputs The arguments of the job. Note, arguments passed to a Task initially may be modified when enqueuing. It is best to read job properties first via `api.tasks.delayedAt` or similar method.
166
+ */
167
+ export async function delDelayed(
168
+ q: string,
169
+ taskName: string,
170
+ inputs?: TaskInputs,
171
+ ) {
172
+ return api.resque.queue.delDelayed(q, taskName, [inputs]);
173
+ }
174
+
175
+ /**
176
+ * Return the timestamps a task is scheduled for.
177
+ * Will throw an error if redis cannot be reached.
178
+ *
179
+ * Inputs:
180
+ * * q: Which queue/priority is to run on?
181
+ * * taskName: The name of the job, likely to be the same name as a tak.
182
+ * * inputs: The arguments of the job. Note, arguments passed to a Task initially may be modified when enqueuing. It is best to read job properties first via `api.tasks.delayedAt` or similar method.
183
+ */
184
+ export async function scheduledAt(
185
+ q: string,
186
+ taskName: string,
187
+ inputs: TaskInputs,
188
+ ): Promise<Array<number>> {
189
+ return api.resque.queue.scheduledAt(q, taskName, [inputs]);
190
+ }
191
+
192
+ /**
193
+ * Return all resque stats for this namespace (how jobs failed, jobs succeeded, etc)
194
+ * Will throw an error if redis cannot be reached.
195
+ */
196
+ export async function stats() {
197
+ return api.resque.queue.stats();
198
+ }
199
+
200
+ /**
201
+ * Retrieve the details of jobs enqueued on a certain queue between start and stop (0-indexed)
202
+ * Will throw an error if redis cannot be reached.
203
+ *
204
+ * Inputs:
205
+ * * q The name of the queue.
206
+ * * start The index of the first job to return.
207
+ * * stop The index of the last job to return.
208
+ */
209
+ export async function queued(
210
+ q: string,
211
+ start: number,
212
+ stop: number,
213
+ ): Promise<Array<TaskInputs>> {
214
+ return api.resque.queue.queued(q, start, stop);
215
+ }
216
+
217
+ /**
218
+ * Delete a queue in redis, and all jobs stored on it.
219
+ * Will throw an error if redis cannot be reached.
220
+ */
221
+ export async function delQueue(q: string) {
222
+ return api.resque.queue.delQueue(q);
223
+ }
224
+
225
+ /**
226
+ * Return any locks, as created by resque plugins or task middleware, in this redis namespace.
227
+ * Will contain locks with keys like `resque:lock:{job}` and `resque:workerslock:{workerId}`
228
+ * Will throw an error if redis cannot be reached.
229
+ */
230
+ export async function locks() {
231
+ return api.resque.queue.locks();
232
+ }
233
+
234
+ /**
235
+ * Delete a lock on a job or worker. Locks can be found via `api.tasks.locks`
236
+ * Will throw an error if redis cannot be reached.
237
+ */
238
+ export async function delLock(lock: string) {
239
+ return api.resque.queue.delLock(lock);
240
+ }
241
+
242
+ /**
243
+ * List all timestamps for which tasks are enqueued in the future, via `api.tasks.enqueueIn` or `api.tasks.enqueueAt`
244
+ * Will throw an error if redis cannot be reached.
245
+ */
246
+ export async function timestamps(): Promise<Array<number>> {
247
+ return api.resque.queue.timestamps();
248
+ }
249
+
250
+ /**
251
+ * Return all jobs which have been enqueued to run at a certain timestamp.
252
+ * Will throw an error if redis cannot be reached.
253
+ */
254
+ export async function delayedAt(timestamp: number): Promise<any> {
255
+ return api.resque.queue.delayedAt(timestamp);
256
+ }
257
+
258
+ /**
259
+ * Return all delayed jobs, organized by the timestamp at where they are to run at.
260
+ * Note: This is a very slow command.
261
+ * Will throw an error if redis cannot be reached.
262
+ */
263
+ export async function allDelayed(): Promise<{ [timestamp: string]: any[] }> {
264
+ return api.resque.queue.allDelayed();
265
+ }
266
+
267
+ /**
268
+ * Return all workers registered by all members of this cluster.
269
+ * Note: MultiWorker processors each register as a unique worker.
270
+ * Will throw an error if redis cannot be reached.
271
+ */
272
+ export async function workers() {
273
+ return api.resque.queue.workers();
274
+ }
275
+
276
+ /**
277
+ * What is a given worker working on? If the worker is idle, 'started' will be returned.
278
+ * Will throw an error if redis cannot be reached.
279
+ */
280
+ export async function workingOn(
281
+ workerName: string,
282
+ queues: string,
283
+ ): Promise<any> {
284
+ return api.resque.queue.workingOn(workerName, queues);
285
+ }
286
+
287
+ /**
288
+ * Return all workers and what job they might be working on.
289
+ * Will throw an error if redis cannot be reached.
290
+ */
291
+ export async function allWorkingOn() {
292
+ return api.resque.queue.allWorkingOn();
293
+ }
294
+
295
+ /**
296
+ * How many jobs are in the failed queue.
297
+ * Will throw an error if redis cannot be reached.
298
+ */
299
+ export async function failedCount(): Promise<number> {
300
+ return api.resque.queue.failedCount();
301
+ }
302
+
303
+ /**
304
+ * Retrieve the details of failed jobs between start and stop (0-indexed).
305
+ * Will throw an error if redis cannot be reached.
306
+ */
307
+ export async function failed(start: number, stop: number) {
308
+ return api.resque.queue.failed(start, stop);
309
+ }
310
+
311
+ /**
312
+ * Remove a specific job from the failed queue.
313
+ * Will throw an error if redis cannot be reached.
314
+ */
315
+ export async function removeFailed(failedJob: ErrorPayload) {
316
+ return api.resque.queue.removeFailed(failedJob);
317
+ }
318
+
319
+ /**
320
+ * Remove a specific job from the failed queue, and retry it by placing it back into its original queue.
321
+ * Will throw an error if redis cannot be reached.
322
+ */
323
+ export async function retryAndRemoveFailed(failedJob: ErrorPayload) {
324
+ return api.resque.queue.retryAndRemoveFailed(failedJob);
325
+ }
326
+
327
+ /**
328
+ * If a worker process crashes, it will leave its state in redis as "working".
329
+ * You can remove workers from redis you know to be over, by specificizing an age which would make them too old to exist.
330
+ * This method will remove the data created by a 'stuck' worker and move the payload to the error queue.
331
+ * However, it will not actually remove any processes which may be running. A job *may* be running that you have removed.
332
+ * Will throw an error if redis cannot be reached.
333
+ */
334
+ export async function cleanOldWorkers(age: number) {
335
+ return api.resque.queue.cleanOldWorkers(age);
336
+ }
337
+
338
+ /**
339
+ * Ensures that a task which has a frequency is either running, or already enqueued.
340
+ * This is run automatically at boot for all tasks which have a frequency, via `api.tasks.enqueueAllRecurrentTasks`.
341
+ * Will throw an error if redis cannot be reached.
342
+ */
343
+ export async function enqueueRecurrentTask(taskName: string) {
344
+ const thisTask = api.tasks.tasks[taskName];
345
+
346
+ if (thisTask.frequency > 0) {
347
+ await task.del(thisTask.queue, taskName);
348
+ await task.delDelayed(thisTask.queue, taskName);
349
+ await task.enqueueIn(thisTask.frequency, taskName, {}, undefined, true);
350
+ log(
351
+ `re-enqueued recurrent job ${taskName}`,
352
+ config.tasks.schedulerLogging.reEnqueue,
353
+ );
354
+ }
355
+ }
356
+
357
+ /**
358
+ * This is run automatically at boot for all tasks which have a frequency, calling `api.tasks.enqueueRecurrentTask`
359
+ * Will throw an error if redis cannot be reached.
360
+ */
361
+ export async function enqueueAllRecurrentTasks() {
362
+ const enqueuedTasks: string[] = [];
363
+
364
+ for (const thisTask of Object.values(api.tasks.tasks)) {
365
+ if (thisTask.frequency > 0) {
366
+ try {
367
+ const toRun = await task.enqueue(thisTask.name, {});
368
+ if (toRun === true) {
369
+ log(
370
+ `enqueuing periodic task: ${thisTask.name}`,
371
+ config.tasks.schedulerLogging.enqueue,
372
+ );
373
+ enqueuedTasks.push(thisTask.name);
374
+ }
375
+ } catch (error) {
376
+ checkForRepeatRecurringTaskEnqueue(thisTask.name, error);
377
+ }
378
+ }
379
+ }
380
+
381
+ return enqueuedTasks;
382
+ }
383
+
384
+ /**
385
+ * Stop a task with a frequency by removing it from all possible queues.
386
+ * Will throw an error if redis cannot be reached.
387
+ */
388
+ export async function stopRecurrentTask(taskName: string): Promise<number> {
389
+ // find the jobs in either the normal queue or delayed queues
390
+ const thisTask = api.tasks.tasks[taskName];
391
+ if (thisTask.frequency > 0) {
392
+ let removedCount = 0;
393
+ const count = await task.del(thisTask.queue, thisTask.name, null, 1);
394
+ removedCount = removedCount + count;
395
+ const timestamps = await task.delDelayed(
396
+ thisTask.queue,
397
+ thisTask.name,
398
+ null,
399
+ );
400
+ removedCount = removedCount + timestamps.length;
401
+ return removedCount;
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Return wholistic details about the task system, including failures, queues, and workers.
407
+ * Will throw an error if redis cannot be reached.
408
+ */
409
+ export async function details() {
410
+ const details: {
411
+ queues: { [key: string]: any };
412
+ workers: { [key: string]: any };
413
+ stats: { [key: string]: any };
414
+ leader: string;
415
+ } = { queues: {}, workers: {}, stats: null, leader: null };
416
+
417
+ details.workers = await task.allWorkingOn();
418
+ details.stats = await task.stats();
419
+ const queues = await api.resque.queue.queues();
420
+
421
+ for (const i in queues) {
422
+ const queue = queues[i];
423
+ const length = await api.resque.queue.length(queue);
424
+ details.queues[queue] = { length: length };
425
+ }
426
+
427
+ details.leader = await api.resque.queue.leader();
428
+
429
+ return details;
430
+ }
431
+
432
+ export async function addMiddleware(middleware: TaskMiddleware) {
433
+ if (!middleware.name) {
434
+ throw new Error("middleware.name is required");
435
+ }
436
+ if (!middleware.priority) {
437
+ middleware.priority = config.general.defaultMiddlewarePriority;
438
+ }
439
+ middleware.priority = Number(middleware.priority);
440
+ api.tasks.middleware[middleware.name] = middleware;
441
+ if (middleware.global === true) {
442
+ api.tasks.globalMiddleware.push(middleware.name);
443
+ utils.sortGlobalMiddleware(
444
+ api.tasks.globalMiddleware,
445
+ api.tasks.middleware,
446
+ );
447
+ }
448
+ await api.tasks.loadTasks(true);
449
+ }
450
+
451
+ async function validateInput(taskName: string, inputs: TaskInputs) {
452
+ const task: Task = api.tasks.tasks[taskName];
453
+
454
+ if (!task) {
455
+ throw new Error(`task ${taskName} not found`);
456
+ }
457
+
458
+ for (const key in task.inputs) {
459
+ // default
460
+ if (inputs[key] === undefined && task.inputs[key].default !== undefined) {
461
+ if (typeof task.inputs[key].default === "function") {
462
+ inputs[key] = await task.inputs[key].default.call(
463
+ api,
464
+ inputs[key],
465
+ this,
466
+ );
467
+ } else {
468
+ inputs[key] = task.inputs[key].default;
469
+ }
470
+ }
471
+
472
+ // validator
473
+ if (
474
+ inputs[key] !== undefined &&
475
+ task.inputs[key].validator !== undefined
476
+ ) {
477
+ let validatorResponse;
478
+ if (typeof task.inputs[key].validator === "function") {
479
+ // allowed to throw too
480
+ const method = task.inputs[key].validator as Function;
481
+ validatorResponse = await method.call(api, inputs[key], this);
482
+ } else {
483
+ const method = this.prepareStringMethod(task.inputs[key].validator);
484
+ validatorResponse = await method.call(api, inputs[key], this);
485
+ }
486
+
487
+ // validator function returned nothing; assume param is OK
488
+ if (validatorResponse === null || validatorResponse === undefined) {
489
+ // ok
490
+ }
491
+
492
+ // validator returned something that was not `true`
493
+ else if (validatorResponse !== true) {
494
+ if (validatorResponse === false) {
495
+ throw new Error(
496
+ `${inputs[key]} is not a valid value for ${key} in task ${taskName}`,
497
+ );
498
+ } else {
499
+ throw new Error(validatorResponse);
500
+ }
501
+ }
502
+ }
503
+
504
+ // required
505
+ if (task.inputs[key].required === true) {
506
+ if (config.general.missingParamChecks.indexOf(inputs[key]) >= 0) {
507
+ throw new Error(`${key} is a required input for task ${taskName}`);
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ function checkForRepeatRecurringTaskEnqueue(
514
+ taskName: string,
515
+ error: NodeJS.ErrnoException,
516
+ ) {
517
+ if (error.toString().match(/already enqueued at this time/)) {
518
+ // this is OK, the job was enqueued by another process as this method was running
519
+ log(
520
+ `not enqueuing periodic task ${taskName} - error.toString()`,
521
+ "warning",
522
+ );
523
+ } else {
524
+ throw error;
525
+ }
526
+ }
527
+ }
@@ -0,0 +1,3 @@
1
+ const yargs = require("yargs/yargs"); // cannot be partially imported
2
+ const { hideBin } = require("yargs/helpers");
3
+ export const argv: { [key: string]: string } = yargs(hideBin(process.argv));
@@ -0,0 +1,21 @@
1
+ /**
2
+ Compare the first n elements of an array with another, longer array
3
+ */
4
+ export function arrayStartingMatch(a: Array<any>, b: Array<any>): boolean {
5
+ if (a.length === 0) {
6
+ return false;
7
+ }
8
+ if (b.length === 0) {
9
+ return false;
10
+ }
11
+
12
+ let matching = true;
13
+ let i = 0;
14
+ while (i < a.length) {
15
+ if (a[i] !== b[i]) {
16
+ matching = false;
17
+ }
18
+ i++;
19
+ }
20
+ return matching;
21
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Return only the unique values in an Array.
3
+ */
4
+ export function arrayUnique(arr: Array<any>): Array<any> {
5
+ const a = [];
6
+ for (let i = 0; i < arr.length; i++) {
7
+ for (let j = i + 1; j < arr.length; j++) {
8
+ if (arr[i] === arr[j]) {
9
+ j = ++i;
10
+ }
11
+ }
12
+ a.push(arr[i]);
13
+ }
14
+ return a;
15
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Collapses an Object with numerical keys (like `arguments` in a function) to an Array
3
+ */
4
+ export function collapseObjectToArray(
5
+ obj: Record<string, any>,
6
+ ): Array<any> | boolean {
7
+ try {
8
+ const keys = Object.keys(obj);
9
+ if (keys.length < 1) {
10
+ return false;
11
+ }
12
+ if (keys[0] !== "0") {
13
+ return false;
14
+ }
15
+ if (keys[keys.length - 1] !== String(keys.length - 1)) {
16
+ return false;
17
+ }
18
+
19
+ const arr = [];
20
+ for (const i in keys) {
21
+ const key = keys[i];
22
+ if (String(parseInt(key)) !== key) {
23
+ return false;
24
+ } else {
25
+ arr.push(obj[key]);
26
+ }
27
+ }
28
+
29
+ return arr;
30
+ } catch (e) {
31
+ return false;
32
+ }
33
+ }
@@ -0,0 +1,3 @@
1
+ export function deepCopy<T>(obj: T) {
2
+ return JSON.parse(JSON.stringify(obj)) as T;
3
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ *swap out d.ts files for the JS versions when running with ts-node
3
+ * also filter out *.test. and *.spec. js|ts files
4
+ */
5
+ export function ensureNoTsHeaderOrSpecFiles(
6
+ files: Array<string>,
7
+ ): Array<string> {
8
+ return files.filter((f) => {
9
+ if (f.match(/.*\.d\.ts$/)) {
10
+ return false;
11
+ }
12
+
13
+ if (f.match(/.*\.(?:spec|test)\.[tj]s$/)) {
14
+ return false;
15
+ }
16
+
17
+ return true;
18
+ });
19
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Returns the average delay between a tick of the node.js event loop, as measured for N calls of `process.nextTick`
3
+ */
4
+ export async function eventLoopDelay(
5
+ iterations: number = 10000,
6
+ ): Promise<number> {
7
+ const jobs: Array<() => Promise<number>> = [];
8
+
9
+ async function sleepyFunc(): Promise<number> {
10
+ return new Promise((resolve) => {
11
+ const start = process.hrtime();
12
+ process.nextTick(() => {
13
+ const delta = process.hrtime(start);
14
+ const ms = delta[0] * 1000 + delta[1] / 1000000;
15
+ resolve(ms);
16
+ });
17
+ });
18
+ }
19
+
20
+ let i = 0;
21
+ while (i < iterations) {
22
+ jobs.push(sleepyFunc);
23
+ i++;
24
+ }
25
+
26
+ const results = [];
27
+ for (const job of jobs) results.push(await job());
28
+
29
+ let sum = 0;
30
+ results.forEach((t) => (sum += t));
31
+
32
+ const avg = Math.round((sum / results.length) * 10000) / 1000;
33
+ return avg;
34
+ }