@devrev/ts-adaas 1.20.2-beta.3 → 1.20.2-beta.5
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/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +0 -1
- package/dist/common/event-type-translation.d.ts.map +1 -1
- package/dist/common/event-type-translation.js +0 -1
- package/dist/common/event-type-translation.test.js +0 -1
- package/dist/mock-server/mock-server.d.ts +0 -4
- package/dist/mock-server/mock-server.d.ts.map +1 -1
- package/dist/mock-server/mock-server.interfaces.d.ts +0 -9
- package/dist/mock-server/mock-server.interfaces.d.ts.map +1 -1
- package/dist/mock-server/mock-server.js +6 -39
- package/dist/multithreading/process-task.d.ts.map +1 -1
- package/dist/multithreading/process-task.js +6 -21
- package/dist/multithreading/process-task.test.js +1 -28
- 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 +0 -9
- package/dist/multithreading/spawn/spawn.helpers.test.js +0 -34
- package/dist/multithreading/spawn/spawn.js +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts +0 -13
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.emit.test.js +5 -51
- package/dist/multithreading/worker-adapter/worker-adapter.js +79 -131
- package/dist/repo/repo.d.ts +2 -1
- package/dist/repo/repo.d.ts.map +1 -1
- package/dist/repo/repo.js +12 -6
- package/dist/repo/repo.test.js +1 -32
- package/dist/state/state.d.ts.map +1 -1
- package/dist/state/state.js +34 -4
- package/dist/state/state.time-value-resolution.test.js +151 -0
- package/dist/types/extraction.d.ts +9 -7
- package/dist/types/extraction.d.ts.map +1 -1
- package/dist/types/extraction.js +0 -7
- package/package.json +1 -1
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.upload-failure.test.js +0 -135
|
@@ -22,7 +22,6 @@ const workers_1 = require("../../types/workers");
|
|
|
22
22
|
const uploader_1 = require("../../uploader/uploader");
|
|
23
23
|
const event_type_translation_1 = require("../../common/event-type-translation");
|
|
24
24
|
const helpers_1 = require("../../common/helpers");
|
|
25
|
-
const spawn_helpers_1 = require("../spawn/spawn.helpers");
|
|
26
25
|
function createWorkerAdapter({ event, adapterState, options, }) {
|
|
27
26
|
return new WorkerAdapter({
|
|
28
27
|
event,
|
|
@@ -160,49 +159,93 @@ class WorkerAdapter {
|
|
|
160
159
|
*/
|
|
161
160
|
async emit(newEventType, data) {
|
|
162
161
|
return (0, logger_context_1.runWithSdkLogContext)(async () => {
|
|
163
|
-
var _a;
|
|
162
|
+
var _a, _b;
|
|
164
163
|
newEventType = (0, event_type_translation_1.translateOutgoingEventType)(newEventType);
|
|
165
164
|
if (this.hasWorkerEmitted) {
|
|
166
165
|
console.warn(`Trying to emit event with event type: ${newEventType}. Ignoring emit request because it has already been emitted.`);
|
|
167
166
|
return;
|
|
168
167
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
batchSize: 25000,
|
|
182
|
-
skipConfirmation: true,
|
|
183
|
-
},
|
|
168
|
+
// If the event is ExternalSyncUnitExtractionDone, upload external sync units via a Repo before emitting
|
|
169
|
+
// TODO: Remove in v2.0.0
|
|
170
|
+
if (newEventType === extraction_1.ExtractorEventType.ExternalSyncUnitExtractionDone &&
|
|
171
|
+
(data === null || data === void 0 ? void 0 : data.external_sync_units) &&
|
|
172
|
+
data.external_sync_units.length > 0) {
|
|
173
|
+
console.log(`Uploading ${data.external_sync_units.length} external sync units via repo before emitting event.`);
|
|
174
|
+
this.initializeRepos([
|
|
175
|
+
{
|
|
176
|
+
itemType: constants_1.AirSyncDefaultItemTypes.EXTERNAL_SYNC_UNITS,
|
|
177
|
+
overridenOptions: {
|
|
178
|
+
batchSize: 25000,
|
|
179
|
+
skipConfirmation: true,
|
|
184
180
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
await ((_a = this.getRepo(constants_1.AirSyncDefaultItemTypes.EXTERNAL_SYNC_UNITS)) === null || _a === void 0 ? void 0 : _a.push(data.external_sync_units));
|
|
184
|
+
// Remove inline external_sync_units from data to avoid SQS size issues
|
|
185
|
+
delete data.external_sync_units;
|
|
186
|
+
}
|
|
187
|
+
// Upload all repos before emitting the event
|
|
188
|
+
console.log(`Uploading all repos before emitting event with event type: ${newEventType}.`);
|
|
189
|
+
try {
|
|
190
|
+
await this.uploadAllRepos();
|
|
192
191
|
}
|
|
193
192
|
catch (error) {
|
|
194
|
-
console.error('Error while
|
|
195
|
-
|
|
193
|
+
console.error('Error while uploading repos', error);
|
|
194
|
+
node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
|
|
195
|
+
this.hasWorkerEmitted = true;
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
198
|
+
// If the extraction is done, we want to save the timestamp of the last successful sync
|
|
199
|
+
if (newEventType === extraction_1.ExtractorEventType.AttachmentExtractionDone) {
|
|
200
|
+
console.log(`Overwriting lastSuccessfulSyncStarted with lastSyncStarted (${this.state.lastSyncStarted}).`);
|
|
201
|
+
this.state.lastSuccessfulSyncStarted = this.state.lastSyncStarted;
|
|
202
|
+
this.state.lastSyncStarted = '';
|
|
203
|
+
// Clear pending extraction boundaries now that the cycle is complete
|
|
204
|
+
this.state.pendingWorkersOldest = '';
|
|
205
|
+
this.state.pendingWorkersNewest = '';
|
|
206
|
+
// Update workersOldest and workersNewest boundaries from resolved extraction timestamps.
|
|
207
|
+
// Expand boundaries: workersOldest gets the earliest timestamp, workersNewest gets the latest.
|
|
208
|
+
const extractionStart = this.event.payload.event_context.extract_from;
|
|
209
|
+
const extractionEnd = this.event.payload.event_context.extract_to;
|
|
210
|
+
if (extractionStart &&
|
|
211
|
+
(!this.state.workersOldest ||
|
|
212
|
+
extractionStart < this.state.workersOldest)) {
|
|
213
|
+
console.log(`Updating workersOldest from '${this.state.workersOldest}' to '${extractionStart}'.`);
|
|
214
|
+
this.state.workersOldest = extractionStart;
|
|
215
|
+
}
|
|
216
|
+
if (extractionEnd &&
|
|
217
|
+
(!this.state.workersNewest ||
|
|
218
|
+
extractionEnd > this.state.workersNewest)) {
|
|
219
|
+
console.log(`Updating workersNewest from '${this.state.workersNewest}' to '${extractionEnd}'.`);
|
|
220
|
+
this.state.workersNewest = extractionEnd;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// We want to save the state every time we emit an event, except for the start and delete events
|
|
224
|
+
if (!constants_1.STATELESS_EVENT_TYPES.includes(this.event.payload.event_type)) {
|
|
225
|
+
console.log(`Saving state before emitting event with event type: ${newEventType}.`);
|
|
226
|
+
try {
|
|
227
|
+
await this.adapterState.postState(this.state);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error('Error while posting state', error);
|
|
231
|
+
node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(workers_1.WorkerMessageSubject.WorkerMessageExit);
|
|
232
|
+
this.hasWorkerEmitted = true;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
198
236
|
try {
|
|
237
|
+
// Always prune error messages to make them shorter before emit
|
|
238
|
+
if ((_b = data === null || data === void 0 ? void 0 : data.error) === null || _b === void 0 ? void 0 : _b.message) {
|
|
239
|
+
data.error.message = (0, helpers_1.truncateMessage)(data.error.message);
|
|
240
|
+
}
|
|
199
241
|
const isExtractionEvent = Object.values(extraction_1.ExtractorEventType).includes(newEventType);
|
|
242
|
+
const isLoaderEvent = Object.values(loading_1.LoaderEventType).includes(newEventType);
|
|
200
243
|
const progressData = {};
|
|
201
244
|
if (isExtractionEvent &&
|
|
202
|
-
(newEventType
|
|
203
|
-
newEventType
|
|
204
|
-
newEventType
|
|
205
|
-
newEventType
|
|
245
|
+
(newEventType == extraction_1.ExtractorEventType.DataExtractionDone ||
|
|
246
|
+
newEventType == extraction_1.ExtractorEventType.DataExtractionProgress ||
|
|
247
|
+
newEventType == extraction_1.ExtractorEventType.AttachmentExtractionDone ||
|
|
248
|
+
newEventType == extraction_1.ExtractorEventType.AttachmentExtractionProgress)) {
|
|
206
249
|
const repo = this.lastExtractedItemType
|
|
207
250
|
? this.repos.find((r) => r.itemType === this.lastExtractedItemType)
|
|
208
251
|
: undefined;
|
|
@@ -217,7 +260,9 @@ class WorkerAdapter {
|
|
|
217
260
|
await (0, control_protocol_1.emit)({
|
|
218
261
|
eventType: newEventType,
|
|
219
262
|
event: this.event,
|
|
220
|
-
data:
|
|
263
|
+
data: Object.assign(Object.assign(Object.assign({}, data), (isExtractionEvent ? { artifacts: this.artifacts } : {})), (isLoaderEvent
|
|
264
|
+
? { reports: this.reports, processed_files: this.processedFiles }
|
|
265
|
+
: {})),
|
|
221
266
|
worker_metadata: Object.assign({}, progressData),
|
|
222
267
|
});
|
|
223
268
|
const message = {
|
|
@@ -235,111 +280,14 @@ class WorkerAdapter {
|
|
|
235
280
|
}
|
|
236
281
|
});
|
|
237
282
|
}
|
|
238
|
-
/**
|
|
239
|
-
* Emits a phase-specific error after task or pre-emit execution fails. This
|
|
240
|
-
* path avoids flushing repos again, so artifacts uploaded before the failure
|
|
241
|
-
* are still reported without retrying the failed batch.
|
|
242
|
-
*/
|
|
243
|
-
async emitError(error) {
|
|
244
|
-
return (0, logger_context_1.runWithSdkLogContext)(async () => {
|
|
245
|
-
if (this.hasWorkerEmitted) {
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
// Include artifacts uploaded before a failure so they are not orphaned
|
|
249
|
-
// and can be associated with the emitted phase error.
|
|
250
|
-
for (const repo of this.repos) {
|
|
251
|
-
this.artifacts = [...this.artifacts, ...repo.uploadedArtifacts];
|
|
252
|
-
}
|
|
253
|
-
const { eventType } = (0, spawn_helpers_1.getTimeoutErrorEventType)(this.event.payload.event_type);
|
|
254
|
-
try {
|
|
255
|
-
await (0, control_protocol_1.emit)({
|
|
256
|
-
eventType,
|
|
257
|
-
event: this.event,
|
|
258
|
-
data: this.buildEmitPayload(eventType, {
|
|
259
|
-
error: { message: (0, logger_1.serializeError)(error) },
|
|
260
|
-
}),
|
|
261
|
-
});
|
|
262
|
-
const message = {
|
|
263
|
-
subject: workers_1.WorkerMessageSubject.WorkerMessageEmitted,
|
|
264
|
-
payload: { eventType },
|
|
265
|
-
};
|
|
266
|
-
this.artifacts = [];
|
|
267
|
-
node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage(message);
|
|
268
|
-
this.hasWorkerEmitted = true;
|
|
269
|
-
}
|
|
270
|
-
catch (emitError) {
|
|
271
|
-
console.error(`Error while emitting error event with event type: ${eventType}.`, (0, logger_1.serializeError)(emitError));
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
283
|
async uploadAllRepos() {
|
|
276
284
|
for (const repo of this.repos) {
|
|
277
|
-
await repo.upload();
|
|
285
|
+
const error = await repo.upload();
|
|
278
286
|
this.artifacts.push(...repo.uploadedArtifacts);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Pre-emit hook: uploads all repos and updates extraction boundaries/state.
|
|
283
|
-
* Throws if uploading repos or persisting state fails. The caller emits a
|
|
284
|
-
* compensating error event with whatever artifacts did upload.
|
|
285
|
-
*/
|
|
286
|
-
async beforeEmit(eventType) {
|
|
287
|
-
// Upload all repos before emitting the event.
|
|
288
|
-
console.log(`Uploading all repos before emitting event with event type: ${eventType}.`);
|
|
289
|
-
await this.uploadAllRepos();
|
|
290
|
-
// If the extraction is done, save the timestamp of the last successful sync.
|
|
291
|
-
if (eventType === extraction_1.ExtractorEventType.AttachmentExtractionDone) {
|
|
292
|
-
console.log(`Overwriting lastSuccessfulSyncStarted with lastSyncStarted (${this.state.lastSyncStarted}).`);
|
|
293
|
-
this.state.lastSuccessfulSyncStarted = this.state.lastSyncStarted;
|
|
294
|
-
this.state.lastSyncStarted = '';
|
|
295
|
-
// Clear pending extraction boundaries now that the cycle is complete.
|
|
296
|
-
this.state.pendingWorkersOldest = '';
|
|
297
|
-
this.state.pendingWorkersNewest = '';
|
|
298
|
-
// Update workersOldest and workersNewest boundaries from resolved extraction timestamps.
|
|
299
|
-
const extractionStart = this.event.payload.event_context.extract_from;
|
|
300
|
-
const extractionEnd = this.event.payload.event_context.extract_to;
|
|
301
|
-
if (extractionStart &&
|
|
302
|
-
(!this.state.workersOldest ||
|
|
303
|
-
extractionStart < this.state.workersOldest)) {
|
|
304
|
-
console.log(`Updating workersOldest from '${this.state.workersOldest}' to '${extractionStart}'.`);
|
|
305
|
-
this.state.workersOldest = extractionStart;
|
|
306
|
-
}
|
|
307
|
-
if (extractionEnd &&
|
|
308
|
-
(!this.state.workersNewest || extractionEnd > this.state.workersNewest)) {
|
|
309
|
-
console.log(`Updating workersNewest from '${this.state.workersNewest}' to '${extractionEnd}'.`);
|
|
310
|
-
this.state.workersNewest = extractionEnd;
|
|
287
|
+
if (error) {
|
|
288
|
+
throw error;
|
|
311
289
|
}
|
|
312
290
|
}
|
|
313
|
-
if (constants_1.STATELESS_EVENT_TYPES.includes(this.event.payload.event_type)) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
let stateSizeKb = 0;
|
|
317
|
-
try {
|
|
318
|
-
stateSizeKb =
|
|
319
|
-
Buffer.byteLength(JSON.stringify(this.state), 'utf8') / 1024;
|
|
320
|
-
}
|
|
321
|
-
catch (_a) {
|
|
322
|
-
stateSizeKb = 0;
|
|
323
|
-
}
|
|
324
|
-
console.log(`Saving ${stateSizeKb.toFixed(2)} KB state before emitting event with event type: ${eventType}. Current state`, (0, logger_1.getPrintableState)(this.state));
|
|
325
|
-
try {
|
|
326
|
-
await this.adapterState.postState(this.state);
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
console.error('Error while posting state.', (0, logger_1.serializeError)(error));
|
|
330
|
-
throw error;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
buildEmitPayload(eventType, data) {
|
|
334
|
-
var _a;
|
|
335
|
-
if ((_a = data === null || data === void 0 ? void 0 : data.error) === null || _a === void 0 ? void 0 : _a.message) {
|
|
336
|
-
data.error.message = (0, helpers_1.truncateMessage)(data.error.message);
|
|
337
|
-
}
|
|
338
|
-
const isExtractionEvent = Object.values(extraction_1.ExtractorEventType).includes(eventType);
|
|
339
|
-
const isLoaderEvent = Object.values(loading_1.LoaderEventType).includes(eventType);
|
|
340
|
-
return Object.assign(Object.assign(Object.assign({}, data), (isExtractionEvent ? { artifacts: this.artifacts } : {})), (isLoaderEvent
|
|
341
|
-
? { reports: this.reports, processed_files: this.processedFiles }
|
|
342
|
-
: {}));
|
|
343
291
|
}
|
|
344
292
|
async loadItemTypes({ itemTypesToLoad, }) {
|
|
345
293
|
return (0, logger_context_1.runWithSdkLogContext)(async () => {
|
package/dist/repo/repo.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Item } from '../repo/repo.interfaces';
|
|
2
|
+
import { ErrorRecord } from '../types/common';
|
|
2
3
|
import { Artifact } from '../uploader/uploader.interfaces';
|
|
3
4
|
import { NormalizedAttachment, NormalizedItem, RepoFactoryInterface } from './repo.interfaces';
|
|
4
5
|
export declare class Repo {
|
|
@@ -21,7 +22,7 @@ export declare class Repo {
|
|
|
21
22
|
};
|
|
22
23
|
constructor({ event, itemType, normalize, onUpload, options, }: RepoFactoryInterface);
|
|
23
24
|
getItems(): (NormalizedItem | NormalizedAttachment | Item)[];
|
|
24
|
-
upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void>;
|
|
25
|
+
upload(batch?: (NormalizedItem | NormalizedAttachment | Item)[]): Promise<void | ErrorRecord>;
|
|
25
26
|
push(items: Item[]): Promise<boolean>;
|
|
26
27
|
}
|
|
27
28
|
//# sourceMappingURL=repo.d.ts.map
|
package/dist/repo/repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/repo/repo.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/repo/repo.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAI3D,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAG3B,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAmD;IAChE,OAAO,CAAC,SAAS,CAAC,CAAwD;IAC1E,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,OAAO,CAAC,CAAuB;IAChC,iBAAiB,EAAE,QAAQ,EAAE,CAAC;IAC9B,UAAU,EAAE;QACjB,YAAY,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACnD,YAAY,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACpD,CAGC;gBAEU,EACV,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,OAAO,GACR,EAAE,oBAAoB;IAUvB,QAAQ,IAAI,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE;IAItD,MAAM,CACV,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAAC,EAAE,GACvD,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC;IAsDxB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAyC5C"}
|
package/dist/repo/repo.js
CHANGED
|
@@ -23,7 +23,6 @@ class Repo {
|
|
|
23
23
|
return this.items;
|
|
24
24
|
}
|
|
25
25
|
async upload(batch) {
|
|
26
|
-
var _a;
|
|
27
26
|
const itemsToUpload = batch || this.items;
|
|
28
27
|
if (itemsToUpload.length > 0) {
|
|
29
28
|
for (const item of itemsToUpload) {
|
|
@@ -45,8 +44,8 @@ class Repo {
|
|
|
45
44
|
console.log(`Uploading ${itemsToUpload.length} items of type ${this.itemType}. `);
|
|
46
45
|
const { artifact, error } = await this.uploader.upload(this.itemType, itemsToUpload);
|
|
47
46
|
if (error || !artifact) {
|
|
48
|
-
console.error(
|
|
49
|
-
|
|
47
|
+
console.error('Error while uploading batch', error);
|
|
48
|
+
return error;
|
|
50
49
|
}
|
|
51
50
|
this.onUpload(artifact);
|
|
52
51
|
this.uploadedArtifacts.push(artifact);
|
|
@@ -81,9 +80,16 @@ class Repo {
|
|
|
81
80
|
// Upload in batches while the number of items exceeds the batch size
|
|
82
81
|
const batchSize = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.batchSize) || constants_1.ARTIFACT_BATCH_SIZE;
|
|
83
82
|
while (this.items.length >= batchSize) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// Slice out a batch of batchSize items to upload
|
|
84
|
+
const batch = this.items.splice(0, batchSize);
|
|
85
|
+
try {
|
|
86
|
+
// Upload the batch
|
|
87
|
+
await this.upload(batch);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error('Error while uploading batch', error);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
87
93
|
}
|
|
88
94
|
return true;
|
|
89
95
|
}
|
package/dist/repo/repo.test.js
CHANGED
|
@@ -313,7 +313,7 @@ describe(repo_1.Repo.name, () => {
|
|
|
313
313
|
error: new Error('fail'),
|
|
314
314
|
artifact: null,
|
|
315
315
|
});
|
|
316
|
-
await
|
|
316
|
+
await repo.upload([itemWithDate('1', '2022-01-01T00:00:00.000Z')]);
|
|
317
317
|
expect(repo.dateRanges.creationDate.oldest).toBe(ts('2022-01-01T00:00:00.000Z'));
|
|
318
318
|
expect(repo.dateRanges.creationDate.newest).toBe(ts('2022-01-01T00:00:00.000Z'));
|
|
319
319
|
});
|
|
@@ -343,35 +343,4 @@ describe(repo_1.Repo.name, () => {
|
|
|
343
343
|
expect(repo.dateRanges.modifiedDate.newest).toBe(ts('2023-01-01T00:00:00.000Z'));
|
|
344
344
|
});
|
|
345
345
|
});
|
|
346
|
-
it('should throw when upload fails', async () => {
|
|
347
|
-
mockUploadFn.mockResolvedValueOnce({
|
|
348
|
-
error: { message: 'upload failed' },
|
|
349
|
-
});
|
|
350
|
-
await expect(repo.upload((0, test_helpers_1.createItems)(1))).rejects.toThrow('upload failed');
|
|
351
|
-
});
|
|
352
|
-
it('should retain items in repo when batch upload fails during push', async () => {
|
|
353
|
-
repo = new repo_1.Repo({
|
|
354
|
-
event: (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
355
|
-
payload: { event_type: types_1.EventType.ExtractionDataStart },
|
|
356
|
-
}),
|
|
357
|
-
itemType: 'test_item_type',
|
|
358
|
-
normalize,
|
|
359
|
-
onUpload: jest.fn(),
|
|
360
|
-
options: { batchSize: 10 },
|
|
361
|
-
});
|
|
362
|
-
const items = (0, test_helpers_1.createItems)(20);
|
|
363
|
-
mockUploadFn
|
|
364
|
-
.mockResolvedValueOnce({
|
|
365
|
-
artifact: {
|
|
366
|
-
id: 'artifact-1',
|
|
367
|
-
item_type: 'test_item_type',
|
|
368
|
-
item_count: 10,
|
|
369
|
-
},
|
|
370
|
-
})
|
|
371
|
-
.mockResolvedValueOnce({
|
|
372
|
-
error: { message: 'second batch failed' },
|
|
373
|
-
});
|
|
374
|
-
await expect(repo.push(items)).rejects.toThrow('second batch failed');
|
|
375
|
-
expect(repo.getItems().length).toBe(10);
|
|
376
|
-
});
|
|
377
346
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state/state.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,YAAY,EAIZ,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,EACvD,KAAK,EACL,YAAY,EACZ,oBAAoB,EACpB,OAAO,GACR,EAAE,cAAc,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state/state.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,YAAY,EAIZ,cAAc,EACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,EACvD,KAAK,EACL,YAAY,EACZ,oBAAoB,EACpB,OAAO,GACR,EAAE,cAAc,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAqLjE;AAED,qBAAa,KAAK,CAAC,cAAc;IAC/B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,eAAe,CAAW;IAClC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,cAAc,CAAC,cAAc,CAAC;IAenE,IAAI,KAAK,IAAI,YAAY,CAAC,cAAc,CAAC,CAExC;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,cAAc,CAAC,EAE5C;IAED,IAAI,eAAe,IAAI,eAAe,CAErC;IAED;;;;OAIG;IACG,IAAI,CAAC,YAAY,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDvD;;;OAGG;IACG,SAAS,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,cAAc,CAAC;IAqDpD;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAqBjE"}
|
package/dist/state/state.js
CHANGED
|
@@ -65,6 +65,12 @@ async function createAdapterState({ event, initialState, initialDomainMapping, o
|
|
|
65
65
|
// On StartExtractingMetadata: resolve fresh from TimeValue objects and store in pending state (always overwrite).
|
|
66
66
|
// On all other events: reuse the pending values cached during StartExtractingMetadata.
|
|
67
67
|
const eventContext = event.payload.event_context;
|
|
68
|
+
const overrideExtractFrom = (value) => {
|
|
69
|
+
if (eventContext.extract_from !== undefined) {
|
|
70
|
+
eventContext.platform_extract_from = eventContext.extract_from;
|
|
71
|
+
}
|
|
72
|
+
eventContext.extract_from = value;
|
|
73
|
+
};
|
|
68
74
|
if (event.payload.event_type === extraction_1.EventType.StartExtractingMetadata) {
|
|
69
75
|
const timeFields = [
|
|
70
76
|
{
|
|
@@ -83,7 +89,12 @@ async function createAdapterState({ event, initialState, initialDomainMapping, o
|
|
|
83
89
|
if (timeValue && timeValue.type) {
|
|
84
90
|
try {
|
|
85
91
|
const resolved = (0, time_value_resolver_1.resolveTimeValue)(timeValue, as.state);
|
|
86
|
-
|
|
92
|
+
if (target === 'extract_from') {
|
|
93
|
+
overrideExtractFrom(resolved);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
eventContext[target] = resolved;
|
|
97
|
+
}
|
|
87
98
|
as.state[pending] = resolved;
|
|
88
99
|
console.log(`Resolved ${target} to ${resolved}. Stored in ${pending}.`);
|
|
89
100
|
}
|
|
@@ -97,13 +108,32 @@ async function createAdapterState({ event, initialState, initialDomainMapping, o
|
|
|
97
108
|
process.exit(1);
|
|
98
109
|
}
|
|
99
110
|
}
|
|
111
|
+
else if (target === 'extract_from' &&
|
|
112
|
+
as.state.lastSuccessfulSyncStarted) {
|
|
113
|
+
overrideExtractFrom(as.state.lastSuccessfulSyncStarted);
|
|
114
|
+
as.state.pendingWorkersOldest = as.state.lastSuccessfulSyncStarted;
|
|
115
|
+
console.log(`Using lastSuccessfulSyncStarted as extract_from: ${as.state.lastSuccessfulSyncStarted}.`);
|
|
116
|
+
}
|
|
117
|
+
else if (target === 'extract_from' &&
|
|
118
|
+
!as.state.lastSuccessfulSyncStarted &&
|
|
119
|
+
eventContext.extract_from !== undefined) {
|
|
120
|
+
// CPv1.1 supplies the resolved boundary directly. Keep it pending so
|
|
121
|
+
// later extraction phases expose the same event_context value.
|
|
122
|
+
as.state.pendingWorkersOldest = eventContext.extract_from;
|
|
123
|
+
console.log(`Using platform-provided extract_from: ${eventContext.extract_from}. Stored in pendingWorkersOldest.`);
|
|
124
|
+
}
|
|
100
125
|
}
|
|
101
126
|
}
|
|
102
127
|
else {
|
|
103
128
|
// Non-StartExtractingMetadata events: reuse pending values from state
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
const pendingExtractFrom = as.state.pendingWorkersOldest || as.state.lastSuccessfulSyncStarted;
|
|
130
|
+
if (pendingExtractFrom &&
|
|
131
|
+
(eventContext.extract_from === undefined ||
|
|
132
|
+
eventContext.extract_from !== pendingExtractFrom)) {
|
|
133
|
+
overrideExtractFrom(pendingExtractFrom);
|
|
134
|
+
console.log(`Using ${as.state.pendingWorkersOldest
|
|
135
|
+
? 'pendingWorkersOldest'
|
|
136
|
+
: 'lastSuccessfulSyncStarted'} as extract_from: ${pendingExtractFrom}.`);
|
|
107
137
|
}
|
|
108
138
|
else {
|
|
109
139
|
console.log('pendingWorkersOldest is not set in state. extract_from will not be populated for this invocation.');
|
|
@@ -73,6 +73,98 @@ describe('State — TimeValue resolution', () => {
|
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
describe('Backwards compatibility - missing TimeValue type', () => {
|
|
76
|
+
it('should prefer lastSuccessfulSyncStarted over the platform extract_from when no CPv2 start time is provided', async () => {
|
|
77
|
+
// Arrange: CPv1.1 sends a concrete extract_from instead of a TimeValue.
|
|
78
|
+
const platformExtractFrom = '2025-04-01T00:00:00.000Z';
|
|
79
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
80
|
+
context: {
|
|
81
|
+
snap_in_version_id: 'test_snap_in_version_id',
|
|
82
|
+
},
|
|
83
|
+
payload: {
|
|
84
|
+
event_type: extraction_1.EventType.StartExtractingMetadata,
|
|
85
|
+
event_context: {
|
|
86
|
+
extract_from: platformExtractFrom,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
fetchStateSpy.mockResolvedValue({
|
|
91
|
+
state: JSON.stringify({
|
|
92
|
+
snapInVersionId: 'test_snap_in_version_id',
|
|
93
|
+
lastSuccessfulSyncStarted: '2025-05-01T00:00:00.000Z',
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
// Act
|
|
97
|
+
const state = await (0, state_1.createAdapterState)({
|
|
98
|
+
event,
|
|
99
|
+
initialState: {},
|
|
100
|
+
initialDomainMapping: {},
|
|
101
|
+
});
|
|
102
|
+
// Assert: the successful sync boundary takes precedence and is retained
|
|
103
|
+
// for later phases.
|
|
104
|
+
expect(event.payload.event_context.extract_from).toBe('2025-05-01T00:00:00.000Z');
|
|
105
|
+
expect(event.payload.event_context.platform_extract_from).toBe(platformExtractFrom);
|
|
106
|
+
expect(state.state.pendingWorkersOldest).toBe('2025-05-01T00:00:00.000Z');
|
|
107
|
+
});
|
|
108
|
+
it('should preserve the platform extract_from when resolving a CPv2 start time', async () => {
|
|
109
|
+
const platformExtractFrom = '2025-04-01T00:00:00.000Z';
|
|
110
|
+
const resolvedExtractFrom = '2025-06-01T00:00:00.000Z';
|
|
111
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
112
|
+
context: {
|
|
113
|
+
snap_in_version_id: 'test_snap_in_version_id',
|
|
114
|
+
},
|
|
115
|
+
payload: {
|
|
116
|
+
event_type: extraction_1.EventType.StartExtractingMetadata,
|
|
117
|
+
event_context: {
|
|
118
|
+
extraction_start_time: {
|
|
119
|
+
type: extraction_1.TimeValueType.ABSOLUTE_TIME,
|
|
120
|
+
value: resolvedExtractFrom,
|
|
121
|
+
},
|
|
122
|
+
extract_from: platformExtractFrom,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
fetchStateSpy.mockResolvedValue({
|
|
127
|
+
state: JSON.stringify({
|
|
128
|
+
snapInVersionId: 'test_snap_in_version_id',
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
await (0, state_1.createAdapterState)({
|
|
132
|
+
event,
|
|
133
|
+
initialState: {},
|
|
134
|
+
initialDomainMapping: {},
|
|
135
|
+
});
|
|
136
|
+
expect(event.payload.event_context.extract_from).toBe(resolvedExtractFrom);
|
|
137
|
+
expect(event.payload.event_context.platform_extract_from).toBe(platformExtractFrom);
|
|
138
|
+
});
|
|
139
|
+
it('should use the platform extract_from when lastSuccessfulSyncStarted is not set', async () => {
|
|
140
|
+
// Arrange: CPv1.1 sends a concrete extract_from instead of a TimeValue.
|
|
141
|
+
const platformExtractFrom = '2025-04-01T00:00:00.000Z';
|
|
142
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
143
|
+
context: {
|
|
144
|
+
snap_in_version_id: 'test_snap_in_version_id',
|
|
145
|
+
},
|
|
146
|
+
payload: {
|
|
147
|
+
event_type: extraction_1.EventType.StartExtractingMetadata,
|
|
148
|
+
event_context: {
|
|
149
|
+
extract_from: platformExtractFrom,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
fetchStateSpy.mockResolvedValue({
|
|
154
|
+
state: JSON.stringify({
|
|
155
|
+
snapInVersionId: 'test_snap_in_version_id',
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
// Act
|
|
159
|
+
const state = await (0, state_1.createAdapterState)({
|
|
160
|
+
event,
|
|
161
|
+
initialState: {},
|
|
162
|
+
initialDomainMapping: {},
|
|
163
|
+
});
|
|
164
|
+
// Assert: the platform value is used and retained for later phases.
|
|
165
|
+
expect(event.payload.event_context.extract_from).toBe(platformExtractFrom);
|
|
166
|
+
expect(state.state.pendingWorkersOldest).toBe(platformExtractFrom);
|
|
167
|
+
});
|
|
76
168
|
it('should skip resolution when extraction_start_time has no type', async () => {
|
|
77
169
|
// Arrange: platform sends extraction_start_time without a type field (old platform version)
|
|
78
170
|
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
@@ -106,6 +198,65 @@ describe('State — TimeValue resolution', () => {
|
|
|
106
198
|
expect(event.payload.event_context.extract_to).toBe('2025-06-01T00:00:00.000Z');
|
|
107
199
|
expect(state.state.pendingWorkersNewest).toBe('2025-06-01T00:00:00.000Z');
|
|
108
200
|
});
|
|
201
|
+
it('should use lastSuccessfulSyncStarted for extract_from when extraction_start_time has no type', async () => {
|
|
202
|
+
// Arrange: platform sends an empty extraction_start_time on a subsequent sync
|
|
203
|
+
const lastSuccessfulSyncStarted = '2025-05-01T00:00:00.000Z';
|
|
204
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
205
|
+
context: {
|
|
206
|
+
snap_in_version_id: 'test_snap_in_version_id',
|
|
207
|
+
},
|
|
208
|
+
payload: {
|
|
209
|
+
event_type: extraction_1.EventType.StartExtractingMetadata,
|
|
210
|
+
event_context: {
|
|
211
|
+
extraction_start_time: {},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
fetchStateSpy.mockResolvedValue({
|
|
216
|
+
state: JSON.stringify({
|
|
217
|
+
snapInVersionId: 'test_snap_in_version_id',
|
|
218
|
+
lastSuccessfulSyncStarted,
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
// Act
|
|
222
|
+
const state = await (0, state_1.createAdapterState)({
|
|
223
|
+
event,
|
|
224
|
+
initialState: {},
|
|
225
|
+
initialDomainMapping: {},
|
|
226
|
+
});
|
|
227
|
+
// Assert: the fallback is also stored for the following data phase
|
|
228
|
+
expect(event.payload.event_context.extract_from).toBe(lastSuccessfulSyncStarted);
|
|
229
|
+
expect(state.state.pendingWorkersOldest).toBe(lastSuccessfulSyncStarted);
|
|
230
|
+
});
|
|
231
|
+
it('should override platform extract_from on StartExtractingData and preserve its original value', async () => {
|
|
232
|
+
const platformExtractFrom = '2026-08-24T13:26:15.388Z';
|
|
233
|
+
const lastSuccessfulSyncStarted = '2026-08-25T13:26:15.388Z';
|
|
234
|
+
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
235
|
+
context: {
|
|
236
|
+
snap_in_version_id: 'test_snap_in_version_id',
|
|
237
|
+
},
|
|
238
|
+
payload: {
|
|
239
|
+
event_type: extraction_1.EventType.StartExtractingData,
|
|
240
|
+
event_context: {
|
|
241
|
+
extract_from: platformExtractFrom,
|
|
242
|
+
extraction_start_time: {},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
fetchStateSpy.mockResolvedValue({
|
|
247
|
+
state: JSON.stringify({
|
|
248
|
+
snapInVersionId: 'test_snap_in_version_id',
|
|
249
|
+
lastSuccessfulSyncStarted,
|
|
250
|
+
}),
|
|
251
|
+
});
|
|
252
|
+
await (0, state_1.createAdapterState)({
|
|
253
|
+
event,
|
|
254
|
+
initialState: {},
|
|
255
|
+
initialDomainMapping: {},
|
|
256
|
+
});
|
|
257
|
+
expect(event.payload.event_context.extract_from).toBe(lastSuccessfulSyncStarted);
|
|
258
|
+
expect(event.payload.event_context.platform_extract_from).toBe(platformExtractFrom);
|
|
259
|
+
});
|
|
109
260
|
it('should skip resolution when extraction_end_time has no type', async () => {
|
|
110
261
|
// Arrange: platform sends extraction_end_time without a type field
|
|
111
262
|
const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
|
|
@@ -56,8 +56,7 @@ export declare enum EventType {
|
|
|
56
56
|
StartDeletingExtractorState = "START_DELETING_EXTRACTOR_STATE",
|
|
57
57
|
StartExtractingAttachments = "START_EXTRACTING_ATTACHMENTS",
|
|
58
58
|
ContinueExtractingAttachments = "CONTINUE_EXTRACTING_ATTACHMENTS",
|
|
59
|
-
StartDeletingExtractorAttachmentsState = "START_DELETING_EXTRACTOR_ATTACHMENTS_STATE"
|
|
60
|
-
ContinueExtractingMetadata = "CONTINUE_EXTRACTING_METADATA"
|
|
59
|
+
StartDeletingExtractorAttachmentsState = "START_DELETING_EXTRACTOR_ATTACHMENTS_STATE"
|
|
61
60
|
}
|
|
62
61
|
/**
|
|
63
62
|
* ExtractorEventType is an enum that defines the different types of events that can be sent from the external extractor to ADaaS.
|
|
@@ -144,9 +143,7 @@ export declare enum ExtractorEventType {
|
|
|
144
143
|
AttachmentExtractionDone = "ATTACHMENT_EXTRACTION_DONE",
|
|
145
144
|
AttachmentExtractionError = "ATTACHMENT_EXTRACTION_ERROR",
|
|
146
145
|
ExtractorAttachmentsStateDeletionDone = "EXTRACTOR_ATTACHMENTS_STATE_DELETION_DONE",
|
|
147
|
-
ExtractorAttachmentsStateDeletionError = "EXTRACTOR_ATTACHMENTS_STATE_DELETION_ERROR"
|
|
148
|
-
MetadataExtractionProgress = "METADATA_EXTRACTION_PROGRESS",
|
|
149
|
-
MetadataExtractionDelayed = "METADATA_EXTRACTION_DELAYED"
|
|
146
|
+
ExtractorAttachmentsStateDeletionError = "EXTRACTOR_ATTACHMENTS_STATE_DELETION_ERROR"
|
|
150
147
|
}
|
|
151
148
|
/**
|
|
152
149
|
* @deprecated
|
|
@@ -297,11 +294,16 @@ export interface EventContext {
|
|
|
297
294
|
external_system_name: string;
|
|
298
295
|
external_system_type: string;
|
|
299
296
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
297
|
+
* Extraction start timestamp (ISO 8601 format), supplied directly by CPv1.1
|
|
298
|
+
* or resolved by the SDK from extraction_start_time and worker state for CPv2.
|
|
302
299
|
* This is the field developers should read to know when to start extracting from.
|
|
303
300
|
*/
|
|
304
301
|
extract_from?: string;
|
|
302
|
+
/**
|
|
303
|
+
* Original extraction start timestamp supplied by the platform before the SDK
|
|
304
|
+
* overrides extract_from.
|
|
305
|
+
*/
|
|
306
|
+
platform_extract_from?: string;
|
|
305
307
|
import_slug: string;
|
|
306
308
|
initial_sync_scope?: InitialSyncScope;
|
|
307
309
|
mode: string;
|