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,191 @@
1
+ import * as IORedis from "ioredis";
2
+ import * as dotProp from "dot-prop";
3
+ import { api, config, id, log, Initializer, redis, utils } from "../index";
4
+ import * as RedisModule from "./../modules/redis";
5
+
6
+ export interface RedisApi {
7
+ clients: {
8
+ [key: string]: IORedis.Redis;
9
+ };
10
+ subscriptionHandlers: {
11
+ [key: string]: Function;
12
+ };
13
+ rpcCallbacks: {
14
+ [key: string]: any;
15
+ };
16
+ status: {
17
+ subscribed: boolean;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Redis helpers and connections.
23
+ */
24
+ export class RedisInitializer extends Initializer {
25
+ constructor() {
26
+ super();
27
+ this.name = "redis";
28
+ this.loadPriority = 200;
29
+ this.startPriority = 101;
30
+ this.stopPriority = 99999;
31
+ }
32
+
33
+ async initialize() {
34
+ api.redis = {
35
+ clients: {},
36
+ subscriptionHandlers: {},
37
+ rpcCallbacks: {},
38
+ status: {
39
+ subscribed: false,
40
+ },
41
+ };
42
+
43
+ api.redis.subscriptionHandlers.do = async (
44
+ message: RedisModule.redis.PubSubMessage,
45
+ ) => {
46
+ if (
47
+ !message.connectionId ||
48
+ (api.connections && api.connections.connections[message.connectionId])
49
+ ) {
50
+ const cmdParts = message.method.split(".");
51
+ const cmd = cmdParts.shift();
52
+ if (cmd !== "api") {
53
+ throw new Error(
54
+ "cannot operate on a method outside of the api object",
55
+ );
56
+ }
57
+
58
+ const callableApi = Object.assign(api, { log });
59
+
60
+ const method: Function = dotProp.get(callableApi, cmdParts.join("."));
61
+ let args = message.args;
62
+ if (args === null) {
63
+ args = [];
64
+ }
65
+ if (!Array.isArray(args)) {
66
+ args = [args];
67
+ }
68
+ if (method) {
69
+ const response = await method.apply(null, args);
70
+ await redis.respondCluster(message.messageId, response);
71
+ } else {
72
+ log("RPC method `" + cmdParts.join(".") + "` not found", "crit");
73
+ }
74
+ }
75
+ };
76
+
77
+ api.redis.subscriptionHandlers.doResponse = function (
78
+ message: RedisModule.redis.PubSubMessage,
79
+ ) {
80
+ if (api.redis.rpcCallbacks[message.messageId]) {
81
+ const { resolve, timer } = api.redis.rpcCallbacks[message.messageId];
82
+ clearTimeout(timer);
83
+ resolve(message.response);
84
+ delete api.redis.rpcCallbacks[message.messageId];
85
+ }
86
+ };
87
+
88
+ const connectionNames = ["client", "subscriber", "tasks"] as const;
89
+ for (const r of connectionNames) {
90
+ if (config.redis[r].buildNew === true) {
91
+ api.redis.clients[r] = new config.redis[r].konstructor(
92
+ ...(config.redis[r].args ?? []),
93
+ );
94
+
95
+ api.redis.clients[r].on("error", (error) => {
96
+ log(`Redis connection \`${r}\` error`, "alert", error);
97
+ });
98
+
99
+ api.redis.clients[r].on("connect", () => {
100
+ log(`Redis connection \`${r}\` connected`, "debug");
101
+ });
102
+
103
+ api.redis.clients[r].on("ready", () => {
104
+ log(`Redis connection \`${r}\` ready`, "debug");
105
+ });
106
+
107
+ api.redis.clients[r].on("close", () => {
108
+ log(`Redis connection \`${r}\` closed`, "debug");
109
+ });
110
+
111
+ api.redis.clients[r].on("end", () => {
112
+ log(`Redis connection \`${r}\` ended`, "debug");
113
+ });
114
+
115
+ api.redis.clients[r].on("reconnecting", () => {
116
+ log(`Redis connection \`${r}\` reconnecting`, "info");
117
+ });
118
+ } else {
119
+ api.redis.clients[r] = config.redis[r].konstructor(
120
+ config.redis[r].args,
121
+ );
122
+ api.redis.clients[r].on("error", (error) => {
123
+ log(`Redis connection \`${r}\` error`, "alert", error);
124
+ });
125
+ log(`Redis connection \`${r}\` connected`, "debug");
126
+ }
127
+
128
+ if (r !== "subscriber") await api.redis.clients[r].get("_test");
129
+ }
130
+
131
+ if (!api.redis.status.subscribed) {
132
+ await api.redis.clients.subscriber.subscribe(config.general.channel);
133
+ api.redis.status.subscribed = true;
134
+
135
+ const messageHandler = async (
136
+ messageChannel: string,
137
+ stringifiedMessage: string,
138
+ ) => {
139
+ let message: RedisModule.redis.PubSubMessage;
140
+ try {
141
+ message = JSON.parse(stringifiedMessage);
142
+ } catch (e) {
143
+ message = {};
144
+ }
145
+
146
+ if (
147
+ messageChannel === config.general.channel &&
148
+ message.serverToken === config.general.serverToken
149
+ ) {
150
+ if (api.redis.subscriptionHandlers[message.messageType]) {
151
+ await api.redis.subscriptionHandlers[message.messageType](message);
152
+ }
153
+ }
154
+ };
155
+
156
+ api.redis.clients.subscriber.on("message", messageHandler);
157
+ }
158
+ }
159
+
160
+ async start() {
161
+ await redis.doCluster("api.log", [
162
+ `actionhero member ${id} has joined the cluster`,
163
+ ]);
164
+ }
165
+
166
+ async stop() {
167
+ await api.redis.clients.subscriber.unsubscribe();
168
+ api.redis.status.subscribed = false;
169
+ await redis.doCluster("api.log", [
170
+ `actionhero member ${id} has left the cluster`,
171
+ ]);
172
+
173
+ await utils.sleep(config.redis.stopTimeout); // allow some time for the goodbye message to propagate
174
+
175
+ const keys = Object.keys(api.redis.clients);
176
+ for (const i in keys) {
177
+ const client = api.redis.clients[keys[i]];
178
+ //@ts-ignore
179
+ if (typeof client.end === "function") {
180
+ //@ts-ignore
181
+ await client.end();
182
+ } else if (typeof client.quit === "function") {
183
+ await client.quit();
184
+ } else if (typeof client.disconnect === "function") {
185
+ await client.disconnect();
186
+ }
187
+ }
188
+
189
+ await utils.sleep(config.redis.stopTimeout); // allow some time for the connection to close
190
+ }
191
+ }
@@ -0,0 +1,248 @@
1
+ import { Queue, Scheduler, MultiWorker, ParsedJob } from "node-resque";
2
+ import { api, config, log, utils, Initializer } from "../index";
3
+
4
+ export interface ResqueApi {
5
+ connectionDetails: {
6
+ [key: string]: any;
7
+ };
8
+ queue: Queue;
9
+ scheduler: Scheduler;
10
+ multiWorker: MultiWorker;
11
+ startQueue: ResqueInitializer["startQueue"];
12
+ stopQueue: ResqueInitializer["stopQueue"];
13
+ startScheduler: ResqueInitializer["startScheduler"];
14
+ stopScheduler: ResqueInitializer["stopScheduler"];
15
+ startMultiWorker: ResqueInitializer["startMultiWorker"];
16
+ stopMultiWorker: ResqueInitializer["stopMultiWorker"];
17
+ }
18
+
19
+ /**
20
+ * The node-resque workers and scheduler which process tasks.
21
+ * see https://github.com/actionhero/node-resque
22
+ */
23
+ export class ResqueInitializer extends Initializer {
24
+ constructor() {
25
+ super();
26
+ this.name = "resque";
27
+ this.loadPriority = 600;
28
+ this.startPriority = 950;
29
+ this.stopPriority = 100;
30
+ }
31
+
32
+ startQueue = async () => {
33
+ api.resque.queue = new Queue(
34
+ { connection: api.resque.connectionDetails },
35
+ api.tasks.jobs,
36
+ );
37
+
38
+ api.resque.queue.on("error", (error) => {
39
+ log(error.toString(), "error", "[api.resque.queue]");
40
+ });
41
+
42
+ await api.resque.queue.connect();
43
+ };
44
+
45
+ stopQueue = () => {
46
+ if (api.resque.queue) {
47
+ return api.resque.queue.end();
48
+ }
49
+ };
50
+
51
+ startScheduler = async () => {
52
+ if (config.tasks.scheduler === true) {
53
+ api.resque.scheduler = new Scheduler({
54
+ connection: api.resque.connectionDetails,
55
+ timeout: config.tasks.timeout,
56
+ stuckWorkerTimeout: config.tasks.stuckWorkerTimeout,
57
+ retryStuckJobs: config.tasks.retryStuckJobs,
58
+ });
59
+
60
+ api.resque.scheduler.on("error", (error) => {
61
+ log(error.toString(), "error", "[api.resque.scheduler]");
62
+ });
63
+
64
+ await api.resque.scheduler.connect();
65
+ api.resque.scheduler.on("start", () => {
66
+ log("resque scheduler started", config.tasks.schedulerLogging.start);
67
+ });
68
+ api.resque.scheduler.on("end", () => {
69
+ log("resque scheduler ended", config.tasks.schedulerLogging.end);
70
+ });
71
+ api.resque.scheduler.on("poll", () => {
72
+ log("resque scheduler polling", config.tasks.schedulerLogging.poll);
73
+ });
74
+ api.resque.scheduler.on("leader", () => {
75
+ log("This node is now the Resque scheduler leader", "notice");
76
+ });
77
+ api.resque.scheduler.on(
78
+ "cleanStuckWorker",
79
+ (workerName, errorPayload, delta) => {
80
+ log("cleaned stuck worker", "warning", {
81
+ workerName,
82
+ errorPayload,
83
+ delta,
84
+ });
85
+ },
86
+ );
87
+
88
+ api.resque.scheduler.start();
89
+ }
90
+ };
91
+
92
+ stopScheduler = async () => {
93
+ return api.resque.scheduler?.end();
94
+ };
95
+
96
+ startMultiWorker = async () => {
97
+ api.resque.multiWorker = new MultiWorker(
98
+ {
99
+ connection: api.resque.connectionDetails,
100
+ queues: Array.isArray(config.tasks.queues)
101
+ ? config.tasks.queues
102
+ : await config.tasks.queues(),
103
+ timeout: config.tasks.timeout,
104
+ checkTimeout: config.tasks.checkTimeout,
105
+ minTaskProcessors: config.tasks.minTaskProcessors,
106
+ maxTaskProcessors: config.tasks.maxTaskProcessors,
107
+ maxEventLoopDelay: config.tasks.maxEventLoopDelay,
108
+ },
109
+ api.tasks.jobs,
110
+ );
111
+
112
+ // normal worker emitters
113
+ api.resque.multiWorker.on("start", (workerId) => {
114
+ log("[ worker ] started", config.tasks.workerLogging.start, {
115
+ workerId,
116
+ });
117
+ });
118
+ api.resque.multiWorker.on("end", (workerId) => {
119
+ log("[ worker ] ended", config.tasks.workerLogging.end, {
120
+ workerId,
121
+ });
122
+ });
123
+ api.resque.multiWorker.on("cleaning_worker", (workerId, worker, pid) => {
124
+ log(
125
+ `[ worker ] cleaning old worker ${worker}, (${pid})`,
126
+ config.tasks.workerLogging.cleaning_worker,
127
+ );
128
+ });
129
+ api.resque.multiWorker.on("poll", (workerId, queue) => {
130
+ log(`[ worker ] polling ${queue}`, config.tasks.workerLogging.poll, {
131
+ workerId,
132
+ });
133
+ });
134
+ api.resque.multiWorker.on("job", (workerId, queue, job: ParsedJob) => {
135
+ log(`[ worker ] working job ${queue}`, config.tasks.workerLogging.job, {
136
+ workerId,
137
+ class: job.class,
138
+ queue: job.queue,
139
+ args: JSON.stringify(utils.filterObjectForLogging(job.args[0])),
140
+ });
141
+ });
142
+ api.resque.multiWorker.on(
143
+ "reEnqueue",
144
+ (workerId, queue, job: ParsedJob, plugin) => {
145
+ log("[ worker ] reEnqueue task", config.tasks.workerLogging.reEnqueue, {
146
+ workerId,
147
+ plugin: JSON.stringify(plugin),
148
+ class: job.class,
149
+ queue: job.queue,
150
+ });
151
+ },
152
+ );
153
+ api.resque.multiWorker.on("pause", (workerId) => {
154
+ log("[ worker ] paused", config.tasks.workerLogging.pause, {
155
+ workerId,
156
+ });
157
+ });
158
+
159
+ api.resque.multiWorker.on("failure", (workerId, queue, job, failure) => {
160
+ api.exceptionHandlers.task(failure, queue, job, workerId);
161
+ });
162
+ api.resque.multiWorker.on("error", (error, workerId, queue, job) => {
163
+ api.exceptionHandlers.task(error, queue, job, workerId);
164
+ });
165
+
166
+ api.resque.multiWorker.on(
167
+ "success",
168
+ (workerId, queue, job: ParsedJob, result, duration) => {
169
+ const payload = {
170
+ workerId,
171
+ class: job.class,
172
+ queue: job.queue,
173
+ args: JSON.stringify(utils.filterObjectForLogging(job.args[0])),
174
+ result,
175
+ duration,
176
+ };
177
+
178
+ log(
179
+ "[ worker ] task success",
180
+ config.tasks.workerLogging.success,
181
+ payload,
182
+ );
183
+ },
184
+ );
185
+
186
+ // multiWorker emitters
187
+ api.resque.multiWorker.on("multiWorkerAction", (verb, delay) => {
188
+ log(
189
+ `[ multiworker ] checked for worker status: ${verb} (event loop delay: ${delay}ms)`,
190
+ config.tasks.workerLogging.multiWorkerAction,
191
+ );
192
+ });
193
+
194
+ if (config.tasks.minTaskProcessors > 0) {
195
+ api.resque.multiWorker.start();
196
+ }
197
+ };
198
+
199
+ stopMultiWorker = async () => {
200
+ if (api.resque.multiWorker && config.tasks.minTaskProcessors > 0) {
201
+ return api.resque.multiWorker.stop();
202
+ }
203
+ };
204
+
205
+ async initialize() {
206
+ api.resque = {
207
+ queue: null,
208
+ multiWorker: null,
209
+ scheduler: null,
210
+ connectionDetails: Object.assign(
211
+ {},
212
+ config.tasks.connectionOptions.tasks,
213
+ {
214
+ redis: api.redis.clients.tasks,
215
+ pkg: ["RedisMock", "_RedisMock"].includes(
216
+ api.redis.clients.tasks?.constructor?.name,
217
+ )
218
+ ? "ioredis-mock"
219
+ : "ioredis",
220
+ },
221
+ ),
222
+ startQueue: this.startQueue,
223
+ stopQueue: this.stopQueue,
224
+ startScheduler: this.startScheduler,
225
+ stopScheduler: this.stopScheduler,
226
+ startMultiWorker: this.startMultiWorker,
227
+ stopMultiWorker: this.stopMultiWorker,
228
+ };
229
+ }
230
+
231
+ async start() {
232
+ if (
233
+ config.tasks.minTaskProcessors === 0 &&
234
+ config.tasks.maxTaskProcessors > 0
235
+ ) {
236
+ config.tasks.minTaskProcessors = 1;
237
+ }
238
+
239
+ await api.resque.startScheduler();
240
+ await api.resque.startMultiWorker();
241
+ }
242
+
243
+ async stop() {
244
+ await api.resque.stopScheduler();
245
+ await api.resque.stopMultiWorker();
246
+ await api.resque.stopQueue();
247
+ }
248
+ }
@@ -0,0 +1,229 @@
1
+ import * as path from "path";
2
+ import {
3
+ api,
4
+ config,
5
+ log,
6
+ utils,
7
+ route,
8
+ Initializer,
9
+ RouteType,
10
+ Connection,
11
+ RouteMethod,
12
+ } from "../index";
13
+ import { routerMethods } from "../modules/route";
14
+
15
+ export interface RoutesApi {
16
+ routes: { [method in RouteMethod]: RouteType[] };
17
+ processRoute: RoutesInitializer["processRoute"];
18
+ matchURL: RoutesInitializer["matchURL"];
19
+ loadRoutes: RoutesInitializer["loadRoutes"];
20
+ }
21
+
22
+ /**
23
+ * Contains routing options for web clients. Can associate routes with actions or files.
24
+ */
25
+ export class RoutesInitializer extends Initializer {
26
+ constructor() {
27
+ super();
28
+ this.name = "routes";
29
+ this.loadPriority = 500;
30
+ }
31
+
32
+ processRoute = (connection: Connection, pathParts: string[]) => {
33
+ if (
34
+ connection.params.action === undefined ||
35
+ api.actions.actions[connection.params.action] === undefined
36
+ ) {
37
+ let method = connection.rawConnection.method.toLowerCase() as RouteMethod;
38
+ if (method === "head" && !api.routes.routes.head) method = "get";
39
+
40
+ for (const i in api.routes.routes[method]) {
41
+ const route = api.routes.routes[method][i];
42
+ const match = api.routes.matchURL(
43
+ pathParts,
44
+ route.path,
45
+ route.matchTrailingPathParts,
46
+ );
47
+ if (match.match) {
48
+ if (route.apiVersion) {
49
+ connection.params.apiVersion ||= route.apiVersion;
50
+ }
51
+
52
+ for (const param in match.params) {
53
+ try {
54
+ const decodedName = decodeURIComponent(param.replace(/\+/g, " "));
55
+ const decodedValue = decodeURIComponent(
56
+ match.params[param].replace(/\+/g, " "),
57
+ );
58
+ connection.params[decodedName] = decodedValue;
59
+ } catch (e) {
60
+ // malformed URL
61
+ }
62
+ }
63
+
64
+ connection.matchedRoute = route;
65
+
66
+ if (route.dir) {
67
+ const requestedFile =
68
+ connection.rawConnection.parsedURL.pathname.substring(
69
+ route.path.length,
70
+ connection.rawConnection.parsedURL.pathname.length,
71
+ );
72
+ connection.params.file = path.normalize(
73
+ route.dir + "/" + requestedFile,
74
+ );
75
+ } else {
76
+ connection.params.action = route.action;
77
+ }
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ };
83
+
84
+ matchURL = (
85
+ pathParts: string[],
86
+ match: string,
87
+ matchTrailingPathParts: boolean,
88
+ ) => {
89
+ const response: { match: boolean; params: { [key: string]: any } } = {
90
+ match: false,
91
+ params: {},
92
+ };
93
+ const matchParts = match.split("/");
94
+ let regexpMatch: string = null;
95
+ let variable = "";
96
+
97
+ if (matchParts[0] === "") matchParts.splice(0, 1);
98
+ if (matchParts[matchParts.length - 1] === "") matchParts.pop();
99
+
100
+ if (matchParts.length !== pathParts.length && !matchTrailingPathParts) {
101
+ return response;
102
+ }
103
+
104
+ for (const i in matchParts) {
105
+ const matchPart = matchParts[i];
106
+ let pathPart = pathParts[i];
107
+
108
+ if (matchTrailingPathParts && parseInt(i) === matchParts.length - 1) {
109
+ for (const j in pathParts) {
110
+ if (j > i) pathPart = pathPart + "/" + pathParts[j];
111
+ }
112
+ }
113
+
114
+ if (!pathPart) return response;
115
+
116
+ if (matchPart.includes(":")) {
117
+ const trimmedMatchParts = matchPart.split(":");
118
+ const trimmedMatchPart =
119
+ trimmedMatchParts[trimmedMatchParts.length - 1];
120
+ const replacement = trimmedMatchParts[trimmedMatchParts.length - 2];
121
+ if (replacement) {
122
+ if (!pathPart.includes(replacement)) return response;
123
+ pathPart = pathPart.replace(replacement, "");
124
+ }
125
+
126
+ if (!trimmedMatchPart.includes("(")) {
127
+ variable = trimmedMatchPart;
128
+ response.params[variable] = pathPart;
129
+ } else {
130
+ variable = trimmedMatchPart.replace(":", "").split("(")[0];
131
+ regexpMatch = trimmedMatchPart.substring(
132
+ trimmedMatchPart.indexOf("(") + 1,
133
+ trimmedMatchPart.length - 1,
134
+ );
135
+ const matches = pathPart.match(new RegExp(regexpMatch, "g"));
136
+ if (matches) {
137
+ response.params[variable] = pathPart;
138
+ } else {
139
+ return response;
140
+ }
141
+ }
142
+ } else {
143
+ if (
144
+ pathPart === null ||
145
+ pathPart === undefined ||
146
+ pathParts[i].toLowerCase() !== matchPart.toLowerCase()
147
+ ) {
148
+ return response;
149
+ }
150
+ }
151
+ }
152
+
153
+ response.match = true;
154
+ return response;
155
+ };
156
+
157
+ loadRoutes = (rawRoutes?: (typeof config)["routes"]) => {
158
+ let counter = 0;
159
+
160
+ if (!rawRoutes) if (config.routes) rawRoutes = config.routes;
161
+
162
+ for (const [method, collection] of Object.entries(rawRoutes)) {
163
+ for (const configRoute of collection as RouteType[]) {
164
+ if (method === "all") {
165
+ for (const verb of routerMethods) {
166
+ route.registerRoute(
167
+ verb as RouteMethod,
168
+ configRoute.path,
169
+ configRoute.action,
170
+ configRoute.apiVersion,
171
+ configRoute.matchTrailingPathParts,
172
+ configRoute.dir,
173
+ );
174
+ }
175
+ } else {
176
+ route.registerRoute(
177
+ method as RouteMethod,
178
+ configRoute.path,
179
+ configRoute.action,
180
+ configRoute.apiVersion,
181
+ configRoute.matchTrailingPathParts,
182
+ configRoute.dir,
183
+ );
184
+ }
185
+ counter++;
186
+ }
187
+ }
188
+
189
+ api.params.postVariables = utils.arrayUnique(api.params.postVariables);
190
+
191
+ if (config.web && Array.isArray(config.web.automaticRoutes)) {
192
+ config.web.automaticRoutes.forEach((verb: RouteMethod) => {
193
+ if (!routerMethods.includes(verb)) {
194
+ throw new Error(`${verb} is not an HTTP verb`);
195
+ }
196
+
197
+ log(
198
+ `creating routes automatically for all actions responding to ${verb.toUpperCase()} HTTP verb`,
199
+ );
200
+
201
+ for (const action in api.actions.actions) {
202
+ route.registerRoute(verb, "/" + action, action, null);
203
+ }
204
+ });
205
+ }
206
+
207
+ log("routes:", "debug", api.routes.routes);
208
+ return counter;
209
+ };
210
+
211
+ async initialize() {
212
+ api.routes = {
213
+ routes: {
214
+ all: [],
215
+ head: [],
216
+ get: [],
217
+ patch: [],
218
+ post: [],
219
+ put: [],
220
+ delete: [],
221
+ },
222
+ processRoute: this.processRoute,
223
+ matchURL: this.matchURL,
224
+ loadRoutes: this.loadRoutes,
225
+ };
226
+
227
+ api.routes.loadRoutes();
228
+ }
229
+ }