ti2 1.0.95 → 1.0.96

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.
package/worker/index.js CHANGED
@@ -2,137 +2,209 @@ const throng = require('throng');
2
2
  require('util').inspect.defaultOptions.depth = null;
3
3
  const R = require('ramda');
4
4
  const { queue, saveResult } = require('./queue');
5
+ const bookingsControllerFactory = require('../controllers/bookings');
6
+ const mainAxios = require('axios'); // Generic axios instance for direct calls
5
7
 
6
8
  const workers = process.env.WEB_CONCURRENCY || 2;
7
9
  const maxJobsPerWorker = process.env.MAX_JOBS_PER_WORKER || 1;
8
10
 
9
- const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
10
- console.log(`Started worker ${id}`);
11
+ const worker = ({ plugins: pluginsParam }) => { // pluginsParam are instantiated plugins
12
+ // Initialize bookingsCtrl once per worker instance using the passed instantiated plugins.
13
+ const bookingsCtrl = bookingsControllerFactory(pluginsParam);
11
14
 
12
- const bye = () => {
13
- console.log(`Worker ${id} exiting`);
14
- disconnect();
15
- process.exit(0);
16
- };
15
+ return (id, disconnect) => {
16
+ console.log(`Started worker ${id}`);
17
+
18
+ const bye = () => {
19
+ console.log(`Worker ${id} exiting`);
20
+ disconnect();
21
+ // Graceful shutdown of the queue connection
22
+ queue.close().then(() => process.exit(0)).catch(() => process.exit(1));
23
+ };
24
+
25
+ process.on('SIGTERM', bye);
26
+ process.on('SIGUSR2', bye);
27
+ process.on('SIGINT', bye); // Good for local development
28
+
29
+ queue.process(maxJobsPerWorker, async job => {
30
+ const {
31
+ data, // Full job.data object
32
+ id: jobId,
33
+ } = job;
17
34
 
18
- process.on('SIGTERM', bye);
19
- process.on('SIGUSR2', bye);
20
- process.on('SIGINT', bye);
21
-
22
- queue.process(maxJobsPerWorker, async job => {
23
- const {
24
- data: {
25
- type = 'plugin',
26
- pluginName,
27
- inTesting,
28
- payload = {}
29
- },
30
- id: jobId,
31
- data: params,
32
- } = job;
33
- let resultValue;
34
- const jobLog = msg => console.log(`job ${jobId} > `, msg)
35
- const jobErr = msg => console.error(`job ${jobId} > `, msg)
36
- jobLog(`of type ${type} started`);
37
- if (type === 'api') {
35
+ // Destructure from data for clarity and to match existing patterns
38
36
  const {
39
- method,
40
- url,
41
- payload,
42
- headers,
43
- } = params;
44
- jobLog(`running method ${method} ${url}`)
45
-
46
- let code, result, server;
47
- const http = require('http');
48
- const axios = require('axios');
37
+ type = 'plugin', // Default job type if not specified
38
+ pluginName, // Name of the plugin context for the job
39
+ inTesting, // Flag if running in a testing environment
40
+ payload: jobDataPayload, // This is job.data.payload (e.g., { methodName: '...', args: {...} } or other plugin args)
41
+ method: jobDataMethod, // This is job.data.method (e.g., 'searchProducts' for generic plugin calls)
42
+ // Note: 'token' can also be a top-level property in data (data.token)
43
+ } = data;
44
+
45
+ let resultValue; // This will be set by all job type handlers
46
+ const jobLog = msg => console.log(`job ${jobId} (type: ${type}, plugin: ${pluginName || 'N/A'}) > `, msg);
47
+ const jobErr = msg => console.error(`job ${jobId} (type: ${type}, plugin: ${pluginName || 'N/A'}) > `, msg);
48
+ jobLog(`started`);
49
+
49
50
  try {
50
- const app = await require('../index')({
51
- pluginsInstantiated: pluginsParam,
52
- startServer: false,
53
- worker: false,
54
- });
55
-
56
- // Manually start server on random port
57
- server = http.createServer(app);
58
- await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
59
- const { address, port } = server.address();
60
- const baseURL = `http://${address}:${port}`;
61
- jobLog(`Worker internal server for job ${jobId} listening on: ${baseURL}`);
62
-
63
- // Extract token and body from payload
64
- const { token, body } = payload || {};
65
- const requestHeaders = {
66
- ...R.omit(['content-length'], headers),
67
- ...(token ? { Authorization: `Bearer ${token}` } : {})
68
- };
69
-
70
- // Use axios to make the request
71
- const response = await axios({
72
- method: method.toLowerCase(),
73
- url: `${baseURL}${url}`,
74
- data: R.omit(['backgroundJob'], payload),
75
- headers: requestHeaders,
76
- validateStatus: () => true, // Prevent axios from throwing on non-2xx status
77
- });
78
-
79
- code = response.status;
80
- result = response.data;
81
- jobLog(`Worker internal request for job ${jobId} completed with code ${code}`);
82
- } catch (error) {
83
- // Handle errors during app init or axios request
84
- jobErr(`Worker internal request error for job ${jobId}:`, error.message);
85
- // If axios error has response, use its status
86
- code = R.pathOr(500, ['response', 'status'], error);
87
- result = R.pathOr({ message: error.message }, ['response', 'data'], error);
88
- } finally {
89
- // Ensure server is closed if it was created
90
- if (server) {
91
- await new Promise(resolve => server.close(resolve));
92
- jobLog(`Worker internal server for job ${jobId} closed.`);
51
+ if (type === 'api') {
52
+ // params is job.data, so data.method, data.url etc. are correct here
53
+ jobLog(`running method ${data.method} ${data.url}`);
54
+ let code, result, server;
55
+ const http = require('http');
56
+ const axios = require('axios'); // Local axios for API call type
57
+ try {
58
+ const app = await require('../index')({
59
+ pluginsInstantiated: pluginsParam, // Pass the already instantiated plugins
60
+ startServer: false,
61
+ worker: false,
62
+ });
63
+
64
+ server = http.createServer(app);
65
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
66
+ const { address, port } = server.address();
67
+ const baseURL = `http://${address}:${port}`;
68
+ jobLog(`Worker internal server for job ${jobId} listening on: ${baseURL}`);
69
+
70
+ // data.payload is the payload for the API call job type
71
+ const { token: jwtTokenForHeader, ...requestBodyForWorker } = data.payload || {};
72
+
73
+ const requestHeaders = {
74
+ ...R.omit(['content-length', 'host', 'connection', 'accept-encoding'], data.headers),
75
+ ...(jwtTokenForHeader ? { Authorization: `Bearer ${jwtTokenForHeader}` } : {})
76
+ };
77
+
78
+ const response = await axios({
79
+ method: data.method.toLowerCase(),
80
+ url: `${baseURL}${data.url}`,
81
+ data: R.omit(['backgroundJob'], requestBodyForWorker),
82
+ headers: requestHeaders,
83
+ validateStatus: () => true,
84
+ });
85
+
86
+ code = response.status;
87
+ result = response.data;
88
+ jobLog(`Worker internal request for job ${jobId} completed with code ${code}`);
89
+ } catch (error) {
90
+ jobErr(`Worker internal request error for job ${jobId}: ${error.message}`);
91
+ code = R.pathOr(500, ['response', 'status'], error);
92
+ result = R.pathOr({ message: error.message }, ['response', 'data'], error);
93
+ } finally {
94
+ if (server) {
95
+ await new Promise(resolve => server.close(resolve));
96
+ jobLog(`Worker internal server for job ${jobId} closed.`);
97
+ }
98
+ }
99
+ resultValue = { code, result, success: code >= 200 && code < 300 };
100
+
101
+ } else if (type === 'callback') {
102
+ const { callbackUrl, request, result } = jobDataPayload; // jobDataPayload is job.data.payload
103
+ jobLog(`sending callback to ${callbackUrl}`);
104
+ await require('../lib/callback').sendCallback({
105
+ callbackUrl,
106
+ request,
107
+ result,
108
+ });
109
+ resultValue = { success: true };
110
+
111
+ } else if (type === 'plugin') {
112
+ // jobDataPayload is job.data.payload
113
+ // jobDataMethod is job.data.method (for generic plugin calls)
114
+
115
+ // Check for our specific controller-level background task
116
+ if (jobDataPayload && jobDataPayload.methodName === '$bookingsProductSearchInternal') {
117
+ jobLog(`Processing via bookingsCtrl.$bookingsProductSearchInternal`);
118
+
119
+ const controllerArgs = jobDataPayload.args;
120
+ if (!controllerArgs) {
121
+ throw new Error('$bookingsProductSearchInternal job is missing "args" in its payload');
122
+ }
123
+
124
+ // Ensure appKey from args matches pluginName from job data if pluginName is used for context
125
+ if (pluginName && controllerArgs.appKey !== pluginName) {
126
+ jobErr(`Job data pluginName (${pluginName}) mismatch with args.appKey (${controllerArgs.appKey}). Using args.appKey for plugin context.`);
127
+ }
128
+ // If this job type implies a forced refresh, `forceRefresh: true` should be part of `controllerArgs`.
129
+ // The `backgroundJob` flag is removed from $bookingsProductSearch.
130
+ const executionPayload = {
131
+ ...controllerArgs, // Contains appKey, userId, hint, payload (for $bps), headers
132
+ // Potentially `forceRefresh: true` if needed for this job type.
133
+ axios: mainAxios, // Provide the worker's generic axios instance
134
+ requestId: jobId, // Use job ID for tracing
135
+ // plugins are already available to bookingsCtrl via its factory initialization
136
+ };
137
+
138
+ resultValue = await bookingsCtrl.$bookingsProductSearch(executionPayload);
139
+ jobLog(`bookingsCtrl.$bookingsProductSearchInternal completed.`);
140
+
141
+ } else {
142
+ // Handle generic plugin method call
143
+ jobLog(`Processing plugin method: ${jobDataMethod} for plugin ${pluginName}`);
144
+
145
+ const currentJobPlugins = await (async () => {
146
+ if (inTesting) {
147
+ const fakePlugin = require('../test/plugin');
148
+ const FakePluginClass = fakePlugin.default || fakePlugin;
149
+ return [...pluginsParam, new FakePluginClass({ name: pluginName })];
150
+ }
151
+ return pluginsParam;
152
+ })();
153
+
154
+ const thePlugin = currentJobPlugins.find(({ name }) => name === pluginName);
155
+
156
+ if (!thePlugin) {
157
+ throw new Error(`Plugin ${pluginName} not found for job ${jobId}.`);
158
+ }
159
+ if (!jobDataMethod || typeof thePlugin[jobDataMethod] !== 'function') {
160
+ throw new Error(`Method ${jobDataMethod} not found or not a function on plugin ${pluginName} for job ${jobId}.`);
161
+ }
162
+
163
+ // Execute the plugin method
164
+ const pluginMethodResult = await thePlugin[jobDataMethod]({
165
+ ...(jobDataPayload || {}), // Spread job.data.payload (e.g., contains inner 'payload' and 'userId')
166
+ token: data.token, // Pass job.data.token
167
+ plugins: currentJobPlugins,
168
+ typeDefsAndQueries: bookingsControllerFactory.typeDefsAndQueries, // As per existing pattern
169
+ axios: mainAxios, // Inject worker's axios instance
170
+ requestId: jobId, // Inject job ID as requestId
171
+ });
172
+
173
+ jobLog(`Plugin method ${jobDataMethod} for ${pluginName} completed.`);
174
+ resultValue = pluginMethodResult; // The primary result of the job is the plugin's output
175
+
176
+ // Post-processing step
177
+ if (data.postProcess) {
178
+ jobLog(`Initiating post-processing step: ${data.postProcess.controller}.${data.postProcess.action}`);
179
+ if (data.postProcess.controller === 'bookings' && bookingsCtrl[data.postProcess.action]) {
180
+ await bookingsCtrl[data.postProcess.action]({
181
+ ...(data.postProcess.args || {}), // Spread static args from job data
182
+ pluginResult: pluginMethodResult, // Pass the dynamic result from the plugin
183
+ requestId: jobId, // Pass job ID for tracing in post-process
184
+ // 'plugins' are already part of bookingsCtrl via its factory
185
+ });
186
+ jobLog(`Post-processing step ${data.postProcess.action} completed for job ${jobId}.`);
187
+ } else {
188
+ jobErr(`Post-processing action ${data.postProcess.action} on controller ${data.postProcess.controller} not found or controller not supported for job ${jobId}.`);
189
+ // Depending on requirements, this could throw an error to fail the job
190
+ }
191
+ }
192
+ }
193
+ } else {
194
+ jobErr(`Unknown job type: ${type} for job ${jobId}`);
195
+ throw new Error(`Unknown job type: ${type} for job ${jobId}`);
93
196
  }
197
+
198
+ jobLog(`completed`);
199
+ await saveResult({ id: jobId, resultValue });
200
+
201
+ } catch (error) {
202
+ jobErr(`Processing failed: ${error.message}`);
203
+ if (error.stack) jobErr(error.stack); // Log full stack for debugging
204
+ throw new Error(error.message) // Re-throw to mark job as failed in Bull (use a summarized mesage)
94
205
  }
95
- resultValue = {
96
- code,
97
- result,
98
- success: code >= 200 && code < 300,
99
- };
100
- } else if (type === 'callback') {
101
- const {
102
- callbackUrl,
103
- request,
104
- result,
105
- } = payload;
106
- jobLog(`job ${jobId} > sending callback to ${callbackUrl}`);
107
- await require('../lib/callback').sendCallback({
108
- callbackUrl,
109
- request,
110
- result,
111
- });
112
- resultValue = { success: true };
113
- } else if (type === 'plugin') {
114
- const {
115
- method,
116
- payload,
117
- } = params;
118
- const plugins = await (async () => {
119
- if (inTesting) {
120
- const fakePlugin = require('../test/plugin');
121
- return [...pluginsParam, await new fakePlugin({ name: pluginName })];
122
- }
123
- return pluginsParam;
124
- })();
125
- const thePlugin = plugins.find(({ name }) => name === pluginName);
126
- resultValue = await thePlugin[method]({
127
- ...payload,
128
- token: params.token,
129
- plugins,
130
- typeDefsAndQueries: require('../controllers/bookings').typeDefsAndQueries,
131
- });
132
- }
133
- jobLog(`job ${jobId} > completed`);
134
- await saveResult({ id: jobId, resultValue });
135
- });
206
+ });
207
+ };
136
208
  };
137
209
 
138
210
  module.exports = async args => throng({
package/worker/queue.js CHANGED
@@ -141,6 +141,7 @@ const jobStatus = async ({ jobId }) => {
141
141
  };
142
142
  }
143
143
  };
144
+
144
145
  module.exports = {
145
146
  addJob,
146
147
  allDone,