@devrev/airsync-sdk 2.0.0-beta.1 → 2.0.0-beta.2
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/README.md +0 -4
- package/dist/airsync-sdk.d.ts +1802 -0
- package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -1
- package/dist/attachments-streaming/attachments-streaming-pool.js +13 -2
- package/dist/attachments-streaming/attachments-streaming-pool.test.js +201 -6
- package/dist/common/constants.d.ts +16 -5
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +20 -6
- package/dist/common/control-protocol.d.ts +2 -2
- package/dist/common/control-protocol.d.ts.map +1 -1
- package/dist/common/control-protocol.js +1 -5
- package/dist/common/helpers.d.ts +8 -0
- package/dist/common/helpers.d.ts.map +1 -1
- package/dist/common/helpers.js +15 -0
- package/dist/common/install-initial-domain-mapping.d.ts +2 -2
- package/dist/common/install-initial-domain-mapping.test.js +5 -2
- package/dist/common/test-utils.d.ts +19 -0
- package/dist/common/test-utils.d.ts.map +1 -0
- package/dist/common/test-utils.js +101 -0
- package/dist/common/time-value-resolver.d.ts +47 -0
- package/dist/common/time-value-resolver.d.ts.map +1 -0
- package/dist/common/time-value-resolver.js +141 -0
- package/dist/common/time-value-resolver.test.d.ts +2 -0
- package/dist/common/time-value-resolver.test.d.ts.map +1 -0
- package/dist/common/time-value-resolver.test.js +268 -0
- package/dist/http/axios-client-internal.d.ts.map +1 -1
- package/dist/http/axios-client-internal.js +7 -0
- package/dist/http/axios-client-internal.test.js +21 -0
- package/dist/http/axios-client.d.ts.map +1 -1
- package/dist/http/axios-client.js +6 -3
- package/dist/http/index.d.ts +0 -1
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +0 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/logger/logger.d.ts +1 -17
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.interfaces.d.ts +2 -2
- package/dist/logger/logger.js +35 -27
- package/dist/logger/logger.test.js +66 -19
- package/dist/mappers/mappers.interface.d.ts +5 -5
- package/dist/mappers/mappers.interface.js +3 -3
- package/dist/mappers/mappers.test.js +8 -10
- package/dist/mock-server/mock-server.d.ts +47 -0
- package/dist/mock-server/mock-server.d.ts.map +1 -0
- package/dist/mock-server/mock-server.interfaces.d.ts +73 -0
- package/dist/mock-server/mock-server.interfaces.d.ts.map +1 -0
- package/dist/mock-server/mock-server.interfaces.js +4 -0
- package/dist/mock-server/mock-server.js +278 -0
- package/dist/multithreading/create-worker.test.js +36 -17
- package/dist/multithreading/process-task.d.ts.map +1 -1
- package/dist/multithreading/process-task.js +15 -48
- package/dist/multithreading/process-task.test.d.ts +2 -0
- package/dist/multithreading/process-task.test.d.ts.map +1 -0
- package/dist/multithreading/process-task.test.js +146 -0
- package/dist/multithreading/spawn/spawn.d.ts +3 -3
- package/dist/multithreading/spawn/spawn.d.ts.map +1 -1
- package/dist/multithreading/spawn/spawn.helpers.d.ts.map +1 -1
- package/dist/multithreading/spawn/spawn.helpers.js +6 -14
- package/dist/multithreading/spawn/spawn.helpers.test.js +8 -8
- package/dist/multithreading/spawn/spawn.js +20 -25
- package/dist/multithreading/spawn/spawn.test.d.ts +2 -0
- package/dist/multithreading/spawn/spawn.test.d.ts.map +1 -0
- package/dist/multithreading/spawn/spawn.test.js +232 -0
- package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +49 -1
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts +11 -5
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.emit.test.d.ts +2 -0
- package/dist/multithreading/worker-adapter/worker-adapter.emit.test.d.ts.map +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.emit.test.js +390 -0
- package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.d.ts +2 -0
- package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.d.ts.map +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.js +799 -0
- package/dist/multithreading/worker-adapter/worker-adapter.js +232 -131
- package/dist/multithreading/worker-adapter/worker-adapter.loading.test.d.ts +2 -0
- package/dist/multithreading/worker-adapter/worker-adapter.loading.test.d.ts.map +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.loading.test.js +596 -0
- package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.d.ts +2 -0
- package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.d.ts.map +1 -0
- package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.js +71 -0
- package/dist/repo/repo.d.ts.map +1 -1
- package/dist/repo/repo.interfaces.d.ts +4 -2
- package/dist/repo/repo.interfaces.d.ts.map +1 -1
- package/dist/repo/repo.js +4 -3
- package/dist/repo/repo.test.js +60 -7
- package/dist/state/state.d.ts +7 -1
- package/dist/state/state.d.ts.map +1 -1
- package/dist/state/state.extract-window.test.d.ts +2 -0
- package/dist/state/state.extract-window.test.d.ts.map +1 -0
- package/dist/state/state.extract-window.test.js +163 -0
- package/dist/state/state.interfaces.d.ts +17 -7
- package/dist/state/state.interfaces.d.ts.map +1 -1
- package/dist/state/state.interfaces.js +4 -2
- package/dist/state/state.js +111 -12
- package/dist/state/state.pending-boundaries.test.d.ts +2 -0
- package/dist/state/state.pending-boundaries.test.d.ts.map +1 -0
- package/dist/state/state.pending-boundaries.test.js +189 -0
- package/dist/state/state.post-state.test.d.ts +2 -0
- package/dist/state/state.post-state.test.d.ts.map +1 -0
- package/dist/state/state.post-state.test.js +77 -0
- package/dist/state/state.test.js +117 -73
- package/dist/state/state.time-value-resolution.test.d.ts +2 -0
- package/dist/state/state.time-value-resolution.test.d.ts.map +1 -0
- package/dist/state/state.time-value-resolution.test.js +175 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/types/common.d.ts +0 -25
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/common.js +1 -11
- package/dist/types/external-domain-metadata.d.ts +20 -1
- package/dist/types/external-domain-metadata.d.ts.map +1 -1
- package/dist/types/extraction.d.ts +89 -218
- package/dist/types/extraction.d.ts.map +1 -1
- package/dist/types/extraction.js +56 -124
- package/dist/types/extraction.test.js +69 -43
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -3
- package/dist/types/loading.d.ts +4 -24
- package/dist/types/loading.d.ts.map +1 -1
- package/dist/types/loading.js +1 -22
- package/dist/types/workers.d.ts +31 -14
- package/dist/types/workers.d.ts.map +1 -1
- package/dist/types/workers.js +1 -0
- package/dist/uploader/uploader.d.ts +1 -0
- package/dist/uploader/uploader.d.ts.map +1 -1
- package/dist/uploader/uploader.helpers.test.js +0 -11
- package/dist/uploader/uploader.interfaces.d.ts +3 -3
- package/dist/uploader/uploader.js +53 -20
- package/dist/uploader/uploader.test.js +22 -10
- package/package.json +10 -13
- package/dist/common/event-type-translation.d.ts +0 -24
- package/dist/common/event-type-translation.d.ts.map +0 -1
- package/dist/common/event-type-translation.js +0 -117
- package/dist/deprecated/adapter/index.d.ts +0 -62
- package/dist/deprecated/adapter/index.d.ts.map +0 -1
- package/dist/deprecated/adapter/index.js +0 -151
- package/dist/deprecated/common/helpers.d.ts +0 -7
- package/dist/deprecated/common/helpers.d.ts.map +0 -1
- package/dist/deprecated/common/helpers.js +0 -47
- package/dist/deprecated/demo-extractor/external_domain_metadata.json +0 -38
- package/dist/deprecated/demo-extractor/index.d.ts +0 -18
- package/dist/deprecated/demo-extractor/index.d.ts.map +0 -1
- package/dist/deprecated/demo-extractor/index.js +0 -161
- package/dist/deprecated/http/client.d.ts +0 -22
- package/dist/deprecated/http/client.d.ts.map +0 -1
- package/dist/deprecated/http/client.js +0 -161
- package/dist/deprecated/uploader/index.d.ts +0 -35
- package/dist/deprecated/uploader/index.d.ts.map +0 -1
- package/dist/deprecated/uploader/index.js +0 -161
- package/dist/http/types.d.ts +0 -17
- package/dist/http/types.d.ts.map +0 -1
- package/dist/http/types.js +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.test.js +0 -483
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MockServer = void 0;
|
|
4
|
+
const http_1 = require("http");
|
|
5
|
+
const mock_server_interfaces_1 = require("./mock-server.interfaces");
|
|
6
|
+
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10mb
|
|
7
|
+
/**
|
|
8
|
+
* Parses the JSON body from an incoming request.
|
|
9
|
+
*/
|
|
10
|
+
async function parseJsonBody(req) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let size = 0;
|
|
14
|
+
req.on('data', (chunk) => {
|
|
15
|
+
size += chunk.length;
|
|
16
|
+
if (size > MAX_BODY_SIZE) {
|
|
17
|
+
reject(new Error('Request body too large'));
|
|
18
|
+
req.destroy();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
chunks.push(chunk);
|
|
22
|
+
});
|
|
23
|
+
req.on('end', () => {
|
|
24
|
+
if (chunks.length === 0) {
|
|
25
|
+
resolve(undefined);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
30
|
+
resolve(JSON.parse(raw));
|
|
31
|
+
}
|
|
32
|
+
catch (_a) {
|
|
33
|
+
resolve(undefined);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
req.on('error', reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wraps a ServerResponse with helper methods (status, json, set, send).
|
|
41
|
+
*/
|
|
42
|
+
function wrapResponse(res) {
|
|
43
|
+
const mock = res;
|
|
44
|
+
let statusCode = 200;
|
|
45
|
+
mock.set = (headers) => {
|
|
46
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
47
|
+
res.setHeader(key, value);
|
|
48
|
+
}
|
|
49
|
+
return mock;
|
|
50
|
+
};
|
|
51
|
+
mock.status = (code) => {
|
|
52
|
+
statusCode = code;
|
|
53
|
+
return mock;
|
|
54
|
+
};
|
|
55
|
+
mock.json = (data) => {
|
|
56
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
57
|
+
res.end(JSON.stringify(data));
|
|
58
|
+
};
|
|
59
|
+
mock.send = () => {
|
|
60
|
+
res.writeHead(statusCode);
|
|
61
|
+
res.end();
|
|
62
|
+
};
|
|
63
|
+
return mock;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* MockServer used in tests to mock internal AirSync endpoints.
|
|
67
|
+
* This is a simple mock server that listens on a port and responds to requests.
|
|
68
|
+
* Supports per-test route configuration to simulate different response scenarios.
|
|
69
|
+
*/
|
|
70
|
+
class MockServer {
|
|
71
|
+
constructor(port = mock_server_interfaces_1.DEFAULT_MOCK_SERVER_PORT) {
|
|
72
|
+
this.server = null;
|
|
73
|
+
this.routeHandlers = new Map();
|
|
74
|
+
this.requests = [];
|
|
75
|
+
this.requestCounts = new Map();
|
|
76
|
+
this.internalPort = port;
|
|
77
|
+
this.port = port;
|
|
78
|
+
this.baseUrl = `http://localhost:${this.port}`;
|
|
79
|
+
}
|
|
80
|
+
async start() {
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
this.server = (0, http_1.createServer)((req, res) => {
|
|
83
|
+
this.handleRequest(req, res).catch(() => {
|
|
84
|
+
res.writeHead(500);
|
|
85
|
+
res.end();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
this.server.listen(this.internalPort, () => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const actualPort = (_b = (_a = this.server) === null || _a === void 0 ? void 0 : _a.address()) === null || _b === void 0 ? void 0 : _b.port;
|
|
91
|
+
if (actualPort) {
|
|
92
|
+
this.port = actualPort;
|
|
93
|
+
this.baseUrl = `http://localhost:${this.port}`;
|
|
94
|
+
}
|
|
95
|
+
console.log(`Mock server running on http://localhost:${this.port}.`);
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async stop() {
|
|
101
|
+
if (!this.server) {
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
}
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
this.server.close((err) => {
|
|
106
|
+
if (err) {
|
|
107
|
+
reject(err);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log('Mock server stopped.');
|
|
111
|
+
this.server = null;
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async handleRequest(raw, rawRes) {
|
|
118
|
+
const req = raw;
|
|
119
|
+
const res = wrapResponse(rawRes);
|
|
120
|
+
const urlPath = (req.url || '/').split('?')[0];
|
|
121
|
+
req.path = urlPath;
|
|
122
|
+
req.body = await parseJsonBody(req);
|
|
123
|
+
const requestInfo = Object.assign({ method: req.method || 'GET', url: req.url || urlPath }, (req.body !== undefined && req.body !== null
|
|
124
|
+
? { body: req.body }
|
|
125
|
+
: {}));
|
|
126
|
+
this.requests.push(requestInfo);
|
|
127
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
128
|
+
const key = `${method}:${urlPath}`;
|
|
129
|
+
const customHandler = this.routeHandlers.get(key);
|
|
130
|
+
if (customHandler) {
|
|
131
|
+
customHandler(req, res);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this.defaultRouteHandler(req, res);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Default route handler for the mock server. Returns { success: true } for
|
|
139
|
+
* routes that are not explicitly set.
|
|
140
|
+
*/
|
|
141
|
+
defaultRouteHandler(req, res) {
|
|
142
|
+
if (req.method === 'GET' && req.path === '/worker_data_url.get') {
|
|
143
|
+
res.status(200).json({
|
|
144
|
+
state: JSON.stringify({}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else if (req.method === 'GET' && req.path === '/internal/snap-ins.get') {
|
|
148
|
+
res.status(200).json({
|
|
149
|
+
snap_in: {
|
|
150
|
+
imports: [{ name: 'test_import_slug' }],
|
|
151
|
+
snap_in_version: { slug: 'test_snap_in_slug' },
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
else if (req.method === 'GET' &&
|
|
156
|
+
req.path === '/internal/airdrop.artifacts.upload-url') {
|
|
157
|
+
const artifactId = `artifact-${Date.now()}-${Math.random()
|
|
158
|
+
.toString(36)
|
|
159
|
+
.substr(2, 9)}`;
|
|
160
|
+
res.status(200).json({
|
|
161
|
+
upload_url: `${this.baseUrl}/file-upload-url`,
|
|
162
|
+
artifact_id: artifactId,
|
|
163
|
+
form_data: [],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
res.status(200).json({ success: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
getRouteKey(method, path) {
|
|
171
|
+
return `${method.toUpperCase()}:${path}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Configures a route to return a specific status code and optional response body.
|
|
175
|
+
*/
|
|
176
|
+
setRoute(config) {
|
|
177
|
+
const { path, method, status, body, retry, headers, delay } = config;
|
|
178
|
+
const key = this.getRouteKey(method, path);
|
|
179
|
+
if (retry) {
|
|
180
|
+
this.requestCounts.set(key, 0);
|
|
181
|
+
}
|
|
182
|
+
this.routeHandlers.set(key, (req, res) => {
|
|
183
|
+
const sendResponse = (responseDelay) => {
|
|
184
|
+
const send = () => {
|
|
185
|
+
var _a, _b;
|
|
186
|
+
if (retry) {
|
|
187
|
+
const currentCount = this.requestCounts.get(key) || 0;
|
|
188
|
+
const failureCount = (_a = retry.failureCount) !== null && _a !== void 0 ? _a : 4;
|
|
189
|
+
const errorStatus = (_b = retry.errorStatus) !== null && _b !== void 0 ? _b : 500;
|
|
190
|
+
if (currentCount < failureCount) {
|
|
191
|
+
this.requestCounts.set(key, currentCount + 1);
|
|
192
|
+
const sendFailure = () => {
|
|
193
|
+
if (retry.headers) {
|
|
194
|
+
res.set(retry.headers);
|
|
195
|
+
}
|
|
196
|
+
if (retry.errorBody !== undefined) {
|
|
197
|
+
res.status(errorStatus).json(retry.errorBody);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
res.status(errorStatus).send();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
if (retry.delay) {
|
|
204
|
+
setTimeout(sendFailure, retry.delay);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
sendFailure();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.requestCounts.set(key, currentCount + 1);
|
|
212
|
+
if (headers) {
|
|
213
|
+
res.set(headers);
|
|
214
|
+
}
|
|
215
|
+
if (body !== undefined) {
|
|
216
|
+
res.status(status).json(body);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
this.defaultRouteHandler(req, res);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
if (headers) {
|
|
225
|
+
res.set(headers);
|
|
226
|
+
}
|
|
227
|
+
if (body !== undefined) {
|
|
228
|
+
res.status(status).json(body);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
res.status(status).send();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
if (responseDelay) {
|
|
236
|
+
setTimeout(send, responseDelay);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
send();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
sendResponse(delay);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Resets all custom route handlers, restoring all default handlers.
|
|
247
|
+
* Also clears request tracking data.
|
|
248
|
+
*/
|
|
249
|
+
resetRoutes() {
|
|
250
|
+
this.routeHandlers.clear();
|
|
251
|
+
this.requestCounts.clear();
|
|
252
|
+
this.requests = [];
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Returns the most recent request or undefined if no requests exist.
|
|
256
|
+
*/
|
|
257
|
+
getLastRequest() {
|
|
258
|
+
if (this.requests.length === 0) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
return this.requests[this.requests.length - 1];
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Gets the number of requests made to a specific endpoint.
|
|
265
|
+
*/
|
|
266
|
+
getRequestCount(method, path) {
|
|
267
|
+
return this.getRequests(method, path).length;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Gets all requests made to a specific endpoint.
|
|
271
|
+
*/
|
|
272
|
+
getRequests(method, path) {
|
|
273
|
+
const pathWithoutQuery = path.split('?')[0];
|
|
274
|
+
return this.requests.filter((req) => req.method.toUpperCase() === method.toUpperCase() &&
|
|
275
|
+
req.url.split('?')[0] === pathWithoutQuery);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.MockServer = MockServer;
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const worker_threads_1 = require("worker_threads");
|
|
4
|
-
const
|
|
4
|
+
const jest_setup_1 = require("../tests/jest.setup");
|
|
5
|
+
const test_utils_1 = require("../common/test-utils");
|
|
5
6
|
const extraction_1 = require("../types/extraction");
|
|
6
7
|
const create_worker_1 = require("./create-worker");
|
|
7
8
|
describe(create_worker_1.createWorker.name, () => {
|
|
8
9
|
it('should create a Worker instance when valid parameters are provided', async () => {
|
|
10
|
+
// Arrange
|
|
9
11
|
const workerPath = __dirname + '../tests/dummy-worker.ts';
|
|
12
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
13
|
+
payload: { event_type: extraction_1.EventType.StartExtractingExternalSyncUnits },
|
|
14
|
+
});
|
|
15
|
+
// Act
|
|
10
16
|
const worker = worker_threads_1.isMainThread
|
|
11
17
|
? await (0, create_worker_1.createWorker)({
|
|
12
|
-
event
|
|
13
|
-
eventType: extraction_1.EventType.ExtractionExternalSyncUnitsStart,
|
|
14
|
-
}),
|
|
18
|
+
event,
|
|
15
19
|
initialState: {},
|
|
16
20
|
workerPath,
|
|
17
21
|
})
|
|
18
22
|
: null;
|
|
23
|
+
// Assert
|
|
19
24
|
expect(worker).not.toBeNull();
|
|
20
25
|
expect(worker).toBeInstanceOf(worker_threads_1.Worker);
|
|
21
26
|
if (worker) {
|
|
@@ -23,36 +28,43 @@ describe(create_worker_1.createWorker.name, () => {
|
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
30
|
it('should throw error when not in main thread', async () => {
|
|
31
|
+
// Arrange
|
|
26
32
|
const originalIsMainThread = worker_threads_1.isMainThread;
|
|
27
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
34
|
worker_threads_1.isMainThread = false;
|
|
29
35
|
const workerPath = __dirname + '../tests/dummy-worker.ts';
|
|
36
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
37
|
+
payload: { event_type: extraction_1.EventType.StartExtractingExternalSyncUnits },
|
|
38
|
+
});
|
|
39
|
+
// Act & Assert
|
|
30
40
|
await expect((0, create_worker_1.createWorker)({
|
|
31
|
-
event
|
|
32
|
-
eventType: extraction_1.EventType.ExtractionExternalSyncUnitsStart,
|
|
33
|
-
}),
|
|
41
|
+
event,
|
|
34
42
|
initialState: {},
|
|
35
43
|
workerPath,
|
|
36
44
|
})).rejects.toThrow('Worker threads can not start more worker threads.');
|
|
37
|
-
// Restore original value
|
|
38
45
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
46
|
worker_threads_1.isMainThread = originalIsMainThread;
|
|
40
47
|
});
|
|
41
48
|
it('[edge] should handle worker creation with minimal valid data', async () => {
|
|
49
|
+
// Arrange
|
|
42
50
|
const workerPath = __dirname + '../tests/dummy-worker.ts';
|
|
51
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
52
|
+
payload: { event_type: extraction_1.EventType.StartExtractingExternalSyncUnits },
|
|
53
|
+
});
|
|
43
54
|
if (worker_threads_1.isMainThread) {
|
|
55
|
+
// Act
|
|
44
56
|
const worker = await (0, create_worker_1.createWorker)({
|
|
45
|
-
event
|
|
46
|
-
eventType: extraction_1.EventType.ExtractionExternalSyncUnitsStart,
|
|
47
|
-
}),
|
|
57
|
+
event,
|
|
48
58
|
initialState: {},
|
|
49
59
|
workerPath,
|
|
50
60
|
});
|
|
61
|
+
// Assert
|
|
51
62
|
expect(worker).toBeInstanceOf(worker_threads_1.Worker);
|
|
52
63
|
await worker.terminate();
|
|
53
64
|
}
|
|
54
65
|
});
|
|
55
66
|
it('[edge] should handle worker creation with complex initial state', async () => {
|
|
67
|
+
// Arrange
|
|
56
68
|
const workerPath = __dirname + '../tests/dummy-worker.ts';
|
|
57
69
|
const complexState = {
|
|
58
70
|
nested: {
|
|
@@ -60,28 +72,35 @@ describe(create_worker_1.createWorker.name, () => {
|
|
|
60
72
|
config: { enabled: true },
|
|
61
73
|
},
|
|
62
74
|
};
|
|
75
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
76
|
+
payload: { event_type: extraction_1.EventType.StartExtractingData },
|
|
77
|
+
});
|
|
63
78
|
if (worker_threads_1.isMainThread) {
|
|
79
|
+
// Act
|
|
64
80
|
const worker = await (0, create_worker_1.createWorker)({
|
|
65
|
-
event
|
|
66
|
-
eventType: extraction_1.EventType.ExtractionDataStart,
|
|
67
|
-
}),
|
|
81
|
+
event,
|
|
68
82
|
initialState: complexState,
|
|
69
83
|
workerPath,
|
|
70
84
|
});
|
|
85
|
+
// Assert
|
|
71
86
|
expect(worker).toBeInstanceOf(worker_threads_1.Worker);
|
|
72
87
|
await worker.terminate();
|
|
73
88
|
}
|
|
74
89
|
});
|
|
75
90
|
it('[edge] should handle different event types', async () => {
|
|
91
|
+
// Arrange
|
|
76
92
|
const workerPath = __dirname + '../tests/dummy-worker.ts';
|
|
93
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
94
|
+
payload: { event_type: extraction_1.EventType.StartExtractingMetadata },
|
|
95
|
+
});
|
|
77
96
|
if (worker_threads_1.isMainThread) {
|
|
97
|
+
// Act
|
|
78
98
|
const worker = await (0, create_worker_1.createWorker)({
|
|
79
|
-
event
|
|
80
|
-
eventType: extraction_1.EventType.ExtractionMetadataStart,
|
|
81
|
-
}),
|
|
99
|
+
event,
|
|
82
100
|
initialState: {},
|
|
83
101
|
workerPath,
|
|
84
102
|
});
|
|
103
|
+
// Assert
|
|
85
104
|
expect(worker).toBeInstanceOf(worker_threads_1.Worker);
|
|
86
105
|
await worker.terminate();
|
|
87
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-task.d.ts","sourceRoot":"","sources":["../../src/multithreading/process-task.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"process-task.d.ts","sourceRoot":"","sources":["../../src/multithreading/process-task.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,oBAAoB,EAGrB,MAAM,kBAAkB,CAAC;AAG1B,wBAAgB,WAAW,CAAC,cAAc,EAAE,EAC1C,IAAI,EACJ,SAAS,GACV,EAAE,oBAAoB,CAAC,cAAc,CAAC,QAwDtC"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.processTask = processTask;
|
|
4
4
|
const node_worker_threads_1 = require("node:worker_threads");
|
|
5
|
-
const event_type_translation_1 = require("../common/event-type-translation");
|
|
6
5
|
const logger_1 = require("../logger/logger");
|
|
7
6
|
const logger_context_1 = require("../logger/logger.context");
|
|
8
7
|
const state_1 = require("../state/state");
|
|
@@ -16,8 +15,6 @@ function processTask({ task, onTimeout, }) {
|
|
|
16
15
|
await (0, logger_context_1.runWithSdkLogContext)(async () => {
|
|
17
16
|
try {
|
|
18
17
|
const event = node_worker_threads_1.workerData.event;
|
|
19
|
-
// TODO: Remove when the old types are completely phased out
|
|
20
|
-
event.payload.event_type = (0, event_type_translation_1.translateIncomingEventType)(event.payload.event_type);
|
|
21
18
|
const initialState = node_worker_threads_1.workerData.initialState;
|
|
22
19
|
const initialDomainMapping = node_worker_threads_1.workerData.initialDomainMapping;
|
|
23
20
|
const options = node_worker_threads_1.workerData.options;
|
|
@@ -34,59 +31,29 @@ function processTask({ task, onTimeout, }) {
|
|
|
34
31
|
adapterState,
|
|
35
32
|
options,
|
|
36
33
|
});
|
|
37
|
-
// Timeout handling flow:
|
|
38
|
-
// The task() runs in the main flow below. Meanwhile, the parent thread may send
|
|
39
|
-
// a timeout message at any point. When that happens, the message handler signals
|
|
40
|
-
// the adapter to stop (handleTimeout), waits for task() to finish, then runs onTimeout().
|
|
41
|
-
// If task() completes before any timeout message arrives, the worker exits normally.
|
|
42
|
-
// isTimeoutReceived prevents both flows from calling process.exit.
|
|
43
|
-
let isTimeoutReceived = false;
|
|
44
|
-
let taskExecution;
|
|
45
34
|
node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.on(workers_1.WorkerEvent.WorkerMessage, (message) => {
|
|
46
35
|
if (message.subject !== workers_1.WorkerMessageSubject.WorkerMessageExit) {
|
|
47
36
|
return;
|
|
48
37
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
console.log('Timeout received. Waiting for the task to finish.');
|
|
53
|
-
adapter.handleTimeout();
|
|
54
|
-
try {
|
|
55
|
-
await taskExecution;
|
|
56
|
-
}
|
|
57
|
-
catch (taskError) {
|
|
58
|
-
console.warn('Task error during timeout:', (0, logger_1.serializeError)(taskError));
|
|
59
|
-
}
|
|
60
|
-
console.log('Task finished. Running onTimeout handler.');
|
|
61
|
-
await (0, logger_context_1.runWithUserLogContext)(async () => onTimeout({ adapter }));
|
|
62
|
-
console.log('onTimeout handler complete. Exiting worker.');
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
catch (onTimeoutError) {
|
|
66
|
-
console.error('Error in onTimeout handler:', (0, logger_1.serializeError)(onTimeoutError));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
38
|
+
console.log('Timeout received. Waiting for the task to finish.');
|
|
39
|
+
adapter.isTimeout = true;
|
|
70
40
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
await
|
|
74
|
-
}
|
|
75
|
-
catch (taskError) {
|
|
76
|
-
if (isTimeoutReceived) {
|
|
77
|
-
console.log('Task threw during timeout. Letting timeout handler finish.');
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
throw taskError;
|
|
81
|
-
}
|
|
82
|
-
if (!isTimeoutReceived) {
|
|
83
|
-
console.log('Task completed. Exiting worker.');
|
|
84
|
-
process.exit(0);
|
|
41
|
+
await (0, logger_context_1.runWithUserLogContext)(async () => task({ adapter }));
|
|
42
|
+
if (adapter.isTimeout && !adapter.hasWorkerEmitted) {
|
|
43
|
+
await (0, logger_context_1.runWithUserLogContext)(async () => onTimeout({ adapter }));
|
|
85
44
|
}
|
|
45
|
+
process.exit(0);
|
|
86
46
|
}
|
|
87
47
|
catch (error) {
|
|
88
|
-
|
|
89
|
-
|
|
48
|
+
(0, logger_context_1.runWithUserLogContext)(() => {
|
|
49
|
+
const errorMessage = `Error while processing task. ${(0, logger_1.serializeError)(error)}`;
|
|
50
|
+
console.error(errorMessage);
|
|
51
|
+
node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
|
|
52
|
+
subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
|
|
53
|
+
payload: { message: errorMessage },
|
|
54
|
+
});
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
90
57
|
}
|
|
91
58
|
});
|
|
92
59
|
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-task.test.d.ts","sourceRoot":"","sources":["../../src/multithreading/process-task.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const extraction_1 = require("../types/extraction");
|
|
4
|
+
const workers_1 = require("../types/workers");
|
|
5
|
+
// These tests cover logic that is NOT exercised by the end-to-end integration
|
|
6
|
+
// tests under src/tests/timeout-handling/:
|
|
7
|
+
// - translation of legacy wire event types into the new enum (mutates event in place)
|
|
8
|
+
// - the hasWorkerEmitted guard that prevents onTimeout from firing after a
|
|
9
|
+
// successful emit (integration tests only exercise the positive case)
|
|
10
|
+
// - the error branch that posts WorkerMessageFailed and exits(1)
|
|
11
|
+
// - the WorkerMessage handler's guard that only flips isTimeout on
|
|
12
|
+
// WorkerMessageExit (integration tests can't cleanly target non-Exit messages)
|
|
13
|
+
//
|
|
14
|
+
// Tests for the happy path, timeout-signal-reaches-worker behavior, and main-thread
|
|
15
|
+
// early return were removed — they either duplicate what the integration suite
|
|
16
|
+
// already exercises or assert mocked behavior with little signal.
|
|
17
|
+
const mockParentPortPostMessage = jest.fn();
|
|
18
|
+
const mockParentPortOn = jest.fn();
|
|
19
|
+
let mockIsMainThread = false;
|
|
20
|
+
jest.mock('node:worker_threads', () => ({
|
|
21
|
+
get isMainThread() {
|
|
22
|
+
return mockIsMainThread;
|
|
23
|
+
},
|
|
24
|
+
get parentPort() {
|
|
25
|
+
return {
|
|
26
|
+
postMessage: mockParentPortPostMessage,
|
|
27
|
+
on: mockParentPortOn,
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
get workerData() {
|
|
31
|
+
var _a;
|
|
32
|
+
return (_a = global.__workerData__) !== null && _a !== void 0 ? _a : {};
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
jest.mock('../logger/logger', () => ({
|
|
36
|
+
Logger: jest.fn().mockImplementation(() => ({
|
|
37
|
+
log: jest.fn(),
|
|
38
|
+
warn: jest.fn(),
|
|
39
|
+
error: jest.fn(),
|
|
40
|
+
info: jest.fn(),
|
|
41
|
+
logFn: jest.fn(),
|
|
42
|
+
})),
|
|
43
|
+
serializeError: jest.fn((e) => String(e)),
|
|
44
|
+
}));
|
|
45
|
+
jest.mock('../logger/logger.context', () => ({
|
|
46
|
+
runWithSdkLogContext: jest.fn((fn) => fn()),
|
|
47
|
+
runWithUserLogContext: jest.fn((fn) => fn()),
|
|
48
|
+
}));
|
|
49
|
+
jest.mock('../state/state', () => ({
|
|
50
|
+
createAdapterState: jest.fn(),
|
|
51
|
+
}));
|
|
52
|
+
jest.mock('./worker-adapter/worker-adapter', () => ({
|
|
53
|
+
WorkerAdapter: jest.fn().mockImplementation(() => ({
|
|
54
|
+
isTimeout: false,
|
|
55
|
+
hasWorkerEmitted: false,
|
|
56
|
+
})),
|
|
57
|
+
}));
|
|
58
|
+
const process_task_1 = require("./process-task");
|
|
59
|
+
const state_1 = require("../state/state");
|
|
60
|
+
const worker_adapter_1 = require("./worker-adapter/worker-adapter");
|
|
61
|
+
const test_utils_1 = require("../common/test-utils");
|
|
62
|
+
function setWorkerData(data) {
|
|
63
|
+
global.__workerData__ = data;
|
|
64
|
+
}
|
|
65
|
+
function makeEvent(eventType = extraction_1.EventType.StartExtractingData) {
|
|
66
|
+
return (0, test_utils_1.createMockEvent)('http://localhost:0', {
|
|
67
|
+
payload: { event_type: eventType },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Flush the microtask queue enough to let the async IIFE inside processTask run.
|
|
71
|
+
const flush = async () => new Promise((r) => setTimeout(r, 0));
|
|
72
|
+
describe(process_task_1.processTask.name, () => {
|
|
73
|
+
let processExitSpy;
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
jest.clearAllMocks();
|
|
76
|
+
mockIsMainThread = false;
|
|
77
|
+
processExitSpy = jest
|
|
78
|
+
.spyOn(process, 'exit')
|
|
79
|
+
.mockImplementation((() => { }));
|
|
80
|
+
state_1.createAdapterState.mockResolvedValue({});
|
|
81
|
+
});
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
processExitSpy.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
it('should NOT call onTimeout when the worker already emitted before timeout check', async () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const event = makeEvent();
|
|
88
|
+
setWorkerData({ event, initialState: {}, options: {} });
|
|
89
|
+
// Both flags true: a timeout arrived but the worker had already emitted —
|
|
90
|
+
// onTimeout must be skipped. This is the guard the integration suite cannot
|
|
91
|
+
// target cleanly because it requires a precise race between emit and timeout.
|
|
92
|
+
const mockAdapter = { isTimeout: true, hasWorkerEmitted: true };
|
|
93
|
+
worker_adapter_1.WorkerAdapter.mockImplementation(() => mockAdapter);
|
|
94
|
+
const task = jest.fn().mockResolvedValue(undefined);
|
|
95
|
+
const onTimeout = jest.fn().mockResolvedValue(undefined);
|
|
96
|
+
// Act
|
|
97
|
+
(0, process_task_1.processTask)({ task, onTimeout });
|
|
98
|
+
await flush();
|
|
99
|
+
// Assert
|
|
100
|
+
expect(onTimeout).not.toHaveBeenCalled();
|
|
101
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
102
|
+
});
|
|
103
|
+
it('should NOT flip adapter.isTimeout when a non-Exit WorkerMessage arrives', async () => {
|
|
104
|
+
// Arrange
|
|
105
|
+
const event = makeEvent();
|
|
106
|
+
setWorkerData({ event, initialState: {}, options: {} });
|
|
107
|
+
const mockAdapter = { isTimeout: false, hasWorkerEmitted: false };
|
|
108
|
+
worker_adapter_1.WorkerAdapter.mockImplementation(() => mockAdapter);
|
|
109
|
+
const task = jest.fn().mockResolvedValue(undefined);
|
|
110
|
+
const onTimeout = jest.fn().mockResolvedValue(undefined);
|
|
111
|
+
// Act
|
|
112
|
+
(0, process_task_1.processTask)({ task, onTimeout });
|
|
113
|
+
await flush();
|
|
114
|
+
// Grab the handler registered for WorkerMessage events and invoke it with
|
|
115
|
+
// subjects that must NOT flip isTimeout (log messages, unknown subjects).
|
|
116
|
+
const messageHandlerCall = mockParentPortOn.mock.calls.find(([eventName]) => eventName === workers_1.WorkerEvent.WorkerMessage);
|
|
117
|
+
expect(messageHandlerCall).toBeDefined();
|
|
118
|
+
const handler = messageHandlerCall[1];
|
|
119
|
+
handler({ subject: workers_1.WorkerMessageSubject.WorkerMessageLog });
|
|
120
|
+
handler({ subject: 'NONSENSE_SUBJECT' });
|
|
121
|
+
// Assert
|
|
122
|
+
expect(mockAdapter.isTimeout).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
it('should post WorkerMessageFailed with the error message and exit(1) when task throws', async () => {
|
|
125
|
+
// Arrange
|
|
126
|
+
const event = makeEvent();
|
|
127
|
+
setWorkerData({ event, initialState: {}, options: {} });
|
|
128
|
+
const mockAdapter = { isTimeout: false, hasWorkerEmitted: false };
|
|
129
|
+
worker_adapter_1.WorkerAdapter.mockImplementation(() => mockAdapter);
|
|
130
|
+
const taskError = new Error('task boom');
|
|
131
|
+
const task = jest.fn().mockRejectedValue(taskError);
|
|
132
|
+
const onTimeout = jest.fn().mockResolvedValue(undefined);
|
|
133
|
+
// Act
|
|
134
|
+
(0, process_task_1.processTask)({ task, onTimeout });
|
|
135
|
+
await flush();
|
|
136
|
+
// Assert
|
|
137
|
+
expect(mockParentPortPostMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
138
|
+
subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
|
|
139
|
+
payload: expect.objectContaining({
|
|
140
|
+
message: expect.stringContaining('task boom'),
|
|
141
|
+
}),
|
|
142
|
+
}));
|
|
143
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
144
|
+
expect(onTimeout).not.toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -5,13 +5,12 @@ import { SpawnFactoryInterface, SpawnInterface } from '../../types/workers';
|
|
|
5
5
|
* The class provides utilities to emit control events to the platform and exit the worker gracefully.
|
|
6
6
|
* In case of lambda timeout, the class emits a lambda timeout event to the platform.
|
|
7
7
|
* @param {SpawnFactoryInterface} options - The options to create a new instance of Spawn class
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {AirSyncEvent} options.event - The event object received from the platform
|
|
9
9
|
* @param {object} options.initialState - The initial state of the adapter
|
|
10
|
-
* @param {string} [options.workerPath] Remove getWorkerPath function and use baseWorkerPath: __dirname instead of workerPath
|
|
11
10
|
* @param {string} [options.baseWorkerPath] - The base path for the worker files, usually `__dirname`
|
|
12
11
|
* @returns {Promise<Spawn>} - A new instance of Spawn class
|
|
13
12
|
*/
|
|
14
|
-
export declare function spawn<ConnectorState>({ event, initialState,
|
|
13
|
+
export declare function spawn<ConnectorState>({ event, initialState, initialDomainMapping, options, baseWorkerPath, }: SpawnFactoryInterface<ConnectorState>): Promise<void>;
|
|
15
14
|
export declare class Spawn {
|
|
16
15
|
private event;
|
|
17
16
|
private alreadyEmitted;
|
|
@@ -24,6 +23,7 @@ export declare class Spawn {
|
|
|
24
23
|
private resolve;
|
|
25
24
|
private originalConsole;
|
|
26
25
|
private logger;
|
|
26
|
+
private workerFailedMessage;
|
|
27
27
|
constructor({ event, worker, options, resolve, originalConsole, }: SpawnInterface);
|
|
28
28
|
private clearTimeouts;
|
|
29
29
|
private exitFromMainThread;
|