@devrev/airsync-sdk 2.0.0-beta.0 → 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.
Files changed (157) hide show
  1. package/README.md +0 -4
  2. package/dist/airsync-sdk.d.ts +1802 -0
  3. package/dist/attachments-streaming/attachments-streaming-pool.d.ts +7 -0
  4. package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -1
  5. package/dist/attachments-streaming/attachments-streaming-pool.js +44 -4
  6. package/dist/attachments-streaming/attachments-streaming-pool.test.js +259 -10
  7. package/dist/common/constants.d.ts +16 -5
  8. package/dist/common/constants.d.ts.map +1 -1
  9. package/dist/common/constants.js +20 -6
  10. package/dist/common/control-protocol.d.ts +2 -2
  11. package/dist/common/control-protocol.d.ts.map +1 -1
  12. package/dist/common/control-protocol.js +1 -5
  13. package/dist/common/helpers.d.ts +8 -0
  14. package/dist/common/helpers.d.ts.map +1 -1
  15. package/dist/common/helpers.js +15 -0
  16. package/dist/common/install-initial-domain-mapping.d.ts +2 -2
  17. package/dist/common/install-initial-domain-mapping.test.js +7 -3
  18. package/dist/common/test-utils.d.ts +19 -0
  19. package/dist/common/test-utils.d.ts.map +1 -0
  20. package/dist/common/test-utils.js +101 -0
  21. package/dist/common/time-value-resolver.d.ts +47 -0
  22. package/dist/common/time-value-resolver.d.ts.map +1 -0
  23. package/dist/common/time-value-resolver.js +141 -0
  24. package/dist/common/time-value-resolver.test.d.ts +2 -0
  25. package/dist/common/time-value-resolver.test.d.ts.map +1 -0
  26. package/dist/common/time-value-resolver.test.js +268 -0
  27. package/dist/http/axios-client-internal.d.ts.map +1 -1
  28. package/dist/http/axios-client-internal.js +7 -0
  29. package/dist/http/axios-client-internal.test.js +27 -6
  30. package/dist/http/axios-client.d.ts.map +1 -1
  31. package/dist/http/axios-client.js +6 -3
  32. package/dist/http/index.d.ts +0 -1
  33. package/dist/http/index.d.ts.map +1 -1
  34. package/dist/http/index.js +0 -1
  35. package/dist/index.d.ts +9 -8
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +12 -10
  38. package/dist/logger/logger.d.ts +1 -17
  39. package/dist/logger/logger.d.ts.map +1 -1
  40. package/dist/logger/logger.interfaces.d.ts +2 -2
  41. package/dist/logger/logger.js +35 -27
  42. package/dist/logger/logger.test.js +66 -19
  43. package/dist/mappers/mappers.interface.d.ts +5 -5
  44. package/dist/mappers/mappers.interface.js +3 -3
  45. package/dist/mappers/mappers.test.js +8 -10
  46. package/dist/mock-server/mock-server.d.ts +47 -0
  47. package/dist/mock-server/mock-server.d.ts.map +1 -0
  48. package/dist/mock-server/mock-server.interfaces.d.ts +73 -0
  49. package/dist/mock-server/mock-server.interfaces.d.ts.map +1 -0
  50. package/dist/mock-server/mock-server.interfaces.js +4 -0
  51. package/dist/mock-server/mock-server.js +278 -0
  52. package/dist/multithreading/create-worker.test.js +36 -17
  53. package/dist/multithreading/process-task.d.ts.map +1 -1
  54. package/dist/multithreading/process-task.js +46 -44
  55. package/dist/multithreading/process-task.test.d.ts +2 -0
  56. package/dist/multithreading/process-task.test.d.ts.map +1 -0
  57. package/dist/multithreading/process-task.test.js +146 -0
  58. package/dist/multithreading/spawn/spawn.d.ts +4 -3
  59. package/dist/multithreading/spawn/spawn.d.ts.map +1 -1
  60. package/dist/multithreading/spawn/spawn.helpers.d.ts.map +1 -1
  61. package/dist/multithreading/spawn/spawn.helpers.js +6 -14
  62. package/dist/multithreading/spawn/spawn.helpers.test.js +8 -8
  63. package/dist/multithreading/spawn/spawn.js +41 -31
  64. package/dist/multithreading/spawn/spawn.test.d.ts +2 -0
  65. package/dist/multithreading/spawn/spawn.test.d.ts.map +1 -0
  66. package/dist/multithreading/spawn/spawn.test.js +232 -0
  67. package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +49 -1
  68. package/dist/multithreading/worker-adapter/worker-adapter.d.ts +11 -5
  69. package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -1
  70. package/dist/multithreading/worker-adapter/worker-adapter.emit.test.d.ts +2 -0
  71. package/dist/multithreading/worker-adapter/worker-adapter.emit.test.d.ts.map +1 -0
  72. package/dist/multithreading/worker-adapter/worker-adapter.emit.test.js +390 -0
  73. package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.d.ts +2 -0
  74. package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.d.ts.map +1 -0
  75. package/dist/multithreading/worker-adapter/worker-adapter.extraction.test.js +799 -0
  76. package/dist/multithreading/worker-adapter/worker-adapter.js +233 -134
  77. package/dist/multithreading/worker-adapter/worker-adapter.loading.test.d.ts +2 -0
  78. package/dist/multithreading/worker-adapter/worker-adapter.loading.test.d.ts.map +1 -0
  79. package/dist/multithreading/worker-adapter/worker-adapter.loading.test.js +596 -0
  80. package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.d.ts +2 -0
  81. package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.d.ts.map +1 -0
  82. package/dist/multithreading/worker-adapter/worker-adapter.serialization.test.js +71 -0
  83. package/dist/repo/repo.d.ts.map +1 -1
  84. package/dist/repo/repo.interfaces.d.ts +4 -2
  85. package/dist/repo/repo.interfaces.d.ts.map +1 -1
  86. package/dist/repo/repo.js +4 -3
  87. package/dist/repo/repo.test.js +60 -7
  88. package/dist/state/state.d.ts +7 -1
  89. package/dist/state/state.d.ts.map +1 -1
  90. package/dist/state/state.extract-window.test.d.ts +2 -0
  91. package/dist/state/state.extract-window.test.d.ts.map +1 -0
  92. package/dist/state/state.extract-window.test.js +163 -0
  93. package/dist/state/state.interfaces.d.ts +25 -8
  94. package/dist/state/state.interfaces.d.ts.map +1 -1
  95. package/dist/state/state.interfaces.js +4 -2
  96. package/dist/state/state.js +111 -12
  97. package/dist/state/state.pending-boundaries.test.d.ts +2 -0
  98. package/dist/state/state.pending-boundaries.test.d.ts.map +1 -0
  99. package/dist/state/state.pending-boundaries.test.js +189 -0
  100. package/dist/state/state.post-state.test.d.ts +2 -0
  101. package/dist/state/state.post-state.test.d.ts.map +1 -0
  102. package/dist/state/state.post-state.test.js +77 -0
  103. package/dist/state/state.test.js +117 -73
  104. package/dist/state/state.time-value-resolution.test.d.ts +2 -0
  105. package/dist/state/state.time-value-resolution.test.d.ts.map +1 -0
  106. package/dist/state/state.time-value-resolution.test.js +175 -0
  107. package/dist/tsdoc-metadata.json +11 -0
  108. package/dist/types/common.d.ts +0 -25
  109. package/dist/types/common.d.ts.map +1 -1
  110. package/dist/types/common.js +1 -11
  111. package/dist/types/external-domain-metadata.d.ts +338 -0
  112. package/dist/types/external-domain-metadata.d.ts.map +1 -0
  113. package/dist/types/extraction.d.ts +89 -218
  114. package/dist/types/extraction.d.ts.map +1 -1
  115. package/dist/types/extraction.js +56 -124
  116. package/dist/types/extraction.test.js +69 -43
  117. package/dist/types/index.d.ts +4 -2
  118. package/dist/types/index.d.ts.map +1 -1
  119. package/dist/types/index.js +6 -3
  120. package/dist/types/loading.d.ts +4 -24
  121. package/dist/types/loading.d.ts.map +1 -1
  122. package/dist/types/loading.js +1 -22
  123. package/dist/types/workers.d.ts +31 -14
  124. package/dist/types/workers.d.ts.map +1 -1
  125. package/dist/types/workers.js +1 -0
  126. package/dist/uploader/uploader.d.ts +1 -0
  127. package/dist/uploader/uploader.d.ts.map +1 -1
  128. package/dist/uploader/uploader.helpers.test.js +0 -11
  129. package/dist/uploader/uploader.interfaces.d.ts +3 -3
  130. package/dist/uploader/uploader.js +53 -20
  131. package/dist/uploader/uploader.test.js +22 -10
  132. package/package.json +12 -11
  133. package/dist/common/event-type-translation.d.ts +0 -24
  134. package/dist/common/event-type-translation.d.ts.map +0 -1
  135. package/dist/common/event-type-translation.js +0 -117
  136. package/dist/deprecated/adapter/index.d.ts +0 -62
  137. package/dist/deprecated/adapter/index.d.ts.map +0 -1
  138. package/dist/deprecated/adapter/index.js +0 -151
  139. package/dist/deprecated/common/helpers.d.ts +0 -7
  140. package/dist/deprecated/common/helpers.d.ts.map +0 -1
  141. package/dist/deprecated/common/helpers.js +0 -47
  142. package/dist/deprecated/demo-extractor/external_domain_metadata.json +0 -38
  143. package/dist/deprecated/demo-extractor/index.d.ts +0 -18
  144. package/dist/deprecated/demo-extractor/index.d.ts.map +0 -1
  145. package/dist/deprecated/demo-extractor/index.js +0 -161
  146. package/dist/deprecated/http/client.d.ts +0 -22
  147. package/dist/deprecated/http/client.d.ts.map +0 -1
  148. package/dist/deprecated/http/client.js +0 -161
  149. package/dist/deprecated/uploader/index.d.ts +0 -35
  150. package/dist/deprecated/uploader/index.d.ts.map +0 -1
  151. package/dist/deprecated/uploader/index.js +0 -161
  152. package/dist/http/types.d.ts +0 -17
  153. package/dist/http/types.d.ts.map +0 -1
  154. package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts +0 -2
  155. package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts.map +0 -1
  156. package/dist/multithreading/worker-adapter/worker-adapter.test.js +0 -483
  157. /package/dist/{http/types.js → types/external-domain-metadata.js} +0 -0
@@ -6,12 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.State = void 0;
7
7
  exports.createAdapterState = createAdapterState;
8
8
  const axios_1 = __importDefault(require("axios"));
9
+ const node_worker_threads_1 = require("node:worker_threads");
9
10
  const constants_1 = require("../common/constants");
10
11
  const install_initial_domain_mapping_1 = require("../common/install-initial-domain-mapping");
12
+ const time_value_resolver_1 = require("../common/time-value-resolver");
11
13
  const axios_client_internal_1 = require("../http/axios-client-internal");
12
14
  const logger_1 = require("../logger/logger");
13
15
  const common_1 = require("../types/common");
14
16
  const extraction_1 = require("../types/extraction");
17
+ const workers_1 = require("../types/workers");
15
18
  const state_interfaces_1 = require("./state.interfaces");
16
19
  async function createAdapterState({ event, initialState, initialDomainMapping, options, }) {
17
20
  // Deep clone the initial state to avoid mutating the original state
@@ -43,21 +46,88 @@ async function createAdapterState({ event, initialState, initialDomainMapping, o
43
46
  }
44
47
  }
45
48
  catch (error) {
46
- console.error('Error while installing initial domain mapping.', (0, logger_1.serializeError)(error));
49
+ const errorMessage = `Error while installing initial domain mapping. ${(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
+ });
47
55
  process.exit(1);
48
56
  }
49
57
  }
50
- // Set lastSyncStarted if the event type is StartExtractingData
51
- if (event.payload.event_type === extraction_1.EventType.StartExtractingData &&
52
- !as.state.lastSyncStarted) {
53
- as.state.lastSyncStarted = new Date().toISOString();
54
- console.log(`Setting lastSyncStarted to ${as.state.lastSyncStarted}.`);
58
+ // Resolve extraction timestamps from TimeValue objects, or reuse pending values from a prior invocation.
59
+ // On StartExtractingMetadata: resolve fresh from TimeValue objects and store in pending state (always overwrite).
60
+ // On all other events: reuse the pending values cached during StartExtractingMetadata.
61
+ const eventContext = event.payload.event_context;
62
+ if (event.payload.event_type === extraction_1.EventType.StartExtractingMetadata) {
63
+ const timeFields = [
64
+ {
65
+ source: 'extraction_start_time',
66
+ target: 'extract_from',
67
+ pending: 'pendingWorkersOldest',
68
+ },
69
+ {
70
+ source: 'extraction_end_time',
71
+ target: 'extract_to',
72
+ pending: 'pendingWorkersNewest',
73
+ },
74
+ ];
75
+ for (const { source, target, pending } of timeFields) {
76
+ const timeValue = eventContext[source];
77
+ if (timeValue && timeValue.type) {
78
+ try {
79
+ const resolved = (0, time_value_resolver_1.resolveTimeValue)(timeValue, as.state);
80
+ eventContext[target] = resolved;
81
+ as.state[pending] = resolved;
82
+ console.log(`Resolved ${target} to ${resolved}. Stored in ${pending}.`);
83
+ }
84
+ catch (error) {
85
+ const errorMessage = `Failed to resolve ${source}: ${(0, logger_1.serializeError)(error)}`;
86
+ console.error(errorMessage);
87
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
88
+ subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
89
+ payload: { message: errorMessage },
90
+ });
91
+ process.exit(1);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ else {
97
+ // Non-StartExtractingMetadata events: reuse pending values from state
98
+ if (as.state.pendingWorkersOldest) {
99
+ eventContext.extract_from = as.state.pendingWorkersOldest;
100
+ console.log(`Reusing pendingWorkersOldest as extract_from: ${as.state.pendingWorkersOldest}.`);
101
+ }
102
+ else {
103
+ console.log('pendingWorkersOldest is not set in state. extract_from will not be populated for this invocation.');
104
+ }
105
+ if (as.state.pendingWorkersNewest) {
106
+ eventContext.extract_to = as.state.pendingWorkersNewest;
107
+ console.log(`Reusing pendingWorkersNewest as extract_to: ${as.state.pendingWorkersNewest}.`);
108
+ }
109
+ else {
110
+ console.log('pendingWorkersNewest is not set in state. extract_to will not be populated for this invocation.');
111
+ }
112
+ }
113
+ // Validate that extract_from is before extract_to
114
+ if (eventContext.extract_from && eventContext.extract_to) {
115
+ if (eventContext.extract_from >= eventContext.extract_to) {
116
+ const errorMessage = `Invalid extraction window: extract_from (${eventContext.extract_from}) must be older than extract_to (${eventContext.extract_to}). This indicates an error in the platform.`;
117
+ console.error(errorMessage);
118
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
119
+ subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
120
+ payload: { message: errorMessage },
121
+ });
122
+ process.exit(1);
123
+ }
55
124
  }
56
125
  }
57
126
  return as;
58
127
  }
59
128
  class State {
60
129
  constructor({ event, initialState }) {
130
+ this._extractionScope = {};
61
131
  this.initialSdkState =
62
132
  event.payload.event_context.mode === common_1.SyncMode.LOADING
63
133
  ? state_interfaces_1.loadingSdkState
@@ -74,6 +144,9 @@ class State {
74
144
  set state(value) {
75
145
  this._state = value;
76
146
  }
147
+ get extractionScope() {
148
+ return this._extractionScope;
149
+ }
77
150
  /**
78
151
  * Initializes the state for this adapter instance by fetching from API
79
152
  * or creating an initial state if none exists (404).
@@ -82,7 +155,7 @@ class State {
82
155
  async init(initialState) {
83
156
  var _a;
84
157
  try {
85
- const stringifiedState = await this.fetchState();
158
+ const { state: stringifiedState, objects } = await this.fetchState();
86
159
  if (!stringifiedState) {
87
160
  throw new Error('No state found in response.');
88
161
  }
@@ -95,6 +168,14 @@ class State {
95
168
  }
96
169
  this.state = parsedState;
97
170
  console.log('State fetched successfully. Current state', (0, logger_1.getPrintableState)(this.state));
171
+ if (objects) {
172
+ try {
173
+ this._extractionScope = JSON.parse(objects);
174
+ }
175
+ catch (error) {
176
+ console.warn(`Failed to parse extractionScope. ${error}`);
177
+ }
178
+ }
98
179
  }
99
180
  catch (error) {
100
181
  if (axios_1.default.isAxiosError(error) && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
@@ -104,7 +185,12 @@ class State {
104
185
  await this.postState(initialAdapterState);
105
186
  }
106
187
  else {
107
- console.error('Failed to init state.', (0, logger_1.serializeError)(error));
188
+ const errorMessage = `Failed to init state. ${(0, logger_1.serializeError)(error)}`;
189
+ console.error(errorMessage);
190
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
191
+ subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
192
+ payload: { message: errorMessage },
193
+ });
108
194
  process.exit(1);
109
195
  }
110
196
  }
@@ -121,7 +207,12 @@ class State {
121
207
  stringifiedState = JSON.stringify(this.state);
122
208
  }
123
209
  catch (error) {
124
- console.error('Failed to stringify state.', (0, logger_1.serializeError)(error));
210
+ const errorMessage = `Failed to stringify state. ${(0, logger_1.serializeError)(error)}`;
211
+ console.error(errorMessage);
212
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
213
+ subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
214
+ payload: { message: errorMessage },
215
+ });
125
216
  process.exit(1);
126
217
  }
127
218
  try {
@@ -139,7 +230,12 @@ class State {
139
230
  console.log('State updated successfully to', (0, logger_1.getPrintableState)(this.state));
140
231
  }
141
232
  catch (error) {
142
- console.error('Failed to update the state.', (0, logger_1.serializeError)(error));
233
+ const errorMessage = `Failed to update the state. ${(0, logger_1.serializeError)(error)}`;
234
+ console.error(errorMessage);
235
+ node_worker_threads_1.parentPort === null || node_worker_threads_1.parentPort === void 0 ? void 0 : node_worker_threads_1.parentPort.postMessage({
236
+ subject: workers_1.WorkerMessageSubject.WorkerMessageFailed,
237
+ payload: { message: errorMessage },
238
+ });
143
239
  process.exit(1);
144
240
  }
145
241
  }
@@ -148,7 +244,7 @@ class State {
148
244
  * @return The raw state data from API
149
245
  */
150
246
  async fetchState() {
151
- var _a;
247
+ var _a, _b;
152
248
  console.log(`Fetching state with sync unit id ${this.syncUnitId} and request id ${this.requestId}.`);
153
249
  const url = this.workerUrl + '.get';
154
250
  const response = await axios_client_internal_1.axiosClient.get(url, {
@@ -160,7 +256,10 @@ class State {
160
256
  request_id: this.requestId,
161
257
  },
162
258
  });
163
- return (_a = response.data) === null || _a === void 0 ? void 0 : _a.state;
259
+ return {
260
+ state: (_a = response.data) === null || _a === void 0 ? void 0 : _a.state,
261
+ objects: (_b = response.data) === null || _b === void 0 ? void 0 : _b.objects,
262
+ };
164
263
  }
165
264
  }
166
265
  exports.State = State;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state.pending-boundaries.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.pending-boundaries.test.d.ts","sourceRoot":"","sources":["../../src/state/state.pending-boundaries.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jest_setup_1 = require("../tests/jest.setup");
4
+ const test_utils_1 = require("../common/test-utils");
5
+ const extraction_1 = require("../types/extraction");
6
+ const state_1 = require("./state");
7
+ /* eslint-disable @typescript-eslint/no-require-imports */
8
+ const FIXED_NOW = '2026-03-26T10:00:00.000Z';
9
+ describe('State — pending extraction boundaries', () => {
10
+ let postStateSpy;
11
+ let fetchStateSpy;
12
+ let installInitialDomainMappingSpy;
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ jest.restoreAllMocks();
16
+ postStateSpy = jest.spyOn(state_1.State.prototype, 'postState');
17
+ fetchStateSpy = jest.spyOn(state_1.State.prototype, 'fetchState');
18
+ installInitialDomainMappingSpy = jest.spyOn(require('../common/install-initial-domain-mapping'), 'installInitialDomainMapping');
19
+ jest.spyOn(process, 'exit').mockImplementation(() => {
20
+ throw new Error('process.exit called');
21
+ });
22
+ jest.useFakeTimers();
23
+ jest.setSystemTime(new Date(FIXED_NOW));
24
+ });
25
+ afterEach(() => {
26
+ jest.useRealTimers();
27
+ });
28
+ it('should store resolved values in pendingWorkersOldest/pendingWorkersNewest on StartExtractingMetadata', async () => {
29
+ // Arrange
30
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
31
+ context: {
32
+ snap_in_version_id: '',
33
+ },
34
+ payload: {
35
+ event_type: extraction_1.EventType.StartExtractingMetadata,
36
+ event_context: {
37
+ extraction_start_time: {
38
+ type: extraction_1.TimeValueType.UNBOUNDED,
39
+ },
40
+ extraction_end_time: {
41
+ type: extraction_1.TimeValueType.CURRENT_TIME,
42
+ },
43
+ },
44
+ },
45
+ });
46
+ fetchStateSpy.mockRejectedValue({
47
+ isAxiosError: true,
48
+ response: { status: 404 },
49
+ });
50
+ installInitialDomainMappingSpy.mockResolvedValue({ success: true });
51
+ postStateSpy.mockResolvedValue({ success: true });
52
+ // Act
53
+ const state = await (0, state_1.createAdapterState)({
54
+ event,
55
+ initialState: {},
56
+ initialDomainMapping: {},
57
+ });
58
+ // Assert
59
+ expect(state.state.pendingWorkersOldest).toBe('1970-01-01T00:00:00.000Z');
60
+ expect(state.state.pendingWorkersNewest).toBe(FIXED_NOW);
61
+ expect(event.payload.event_context.extract_from).toBe('1970-01-01T00:00:00.000Z');
62
+ expect(event.payload.event_context.extract_to).toBe(FIXED_NOW);
63
+ });
64
+ it('should overwrite pending values on a retry (new StartExtractingMetadata after failure)', async () => {
65
+ // Arrange: state has stale pending values from a previous failed attempt
66
+ const staleOldest = '2026-03-25T08:00:00.000Z';
67
+ const staleNewest = '2026-03-25T09:00:00.000Z';
68
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
69
+ context: {
70
+ snap_in_version_id: 'test_snap_in_version_id',
71
+ },
72
+ payload: {
73
+ event_type: extraction_1.EventType.StartExtractingMetadata,
74
+ event_context: {
75
+ extraction_start_time: {
76
+ type: extraction_1.TimeValueType.UNBOUNDED,
77
+ },
78
+ extraction_end_time: {
79
+ type: extraction_1.TimeValueType.CURRENT_TIME,
80
+ },
81
+ },
82
+ },
83
+ });
84
+ const stringifiedState = JSON.stringify({
85
+ snapInVersionId: 'test_snap_in_version_id',
86
+ pendingWorkersOldest: staleOldest,
87
+ pendingWorkersNewest: staleNewest,
88
+ });
89
+ fetchStateSpy.mockResolvedValue({ state: stringifiedState });
90
+ // Act
91
+ const state = await (0, state_1.createAdapterState)({
92
+ event,
93
+ initialState: {},
94
+ initialDomainMapping: {},
95
+ });
96
+ // Assert: pending values are overwritten with fresh resolution, not stale values
97
+ expect(state.state.pendingWorkersOldest).toBe('1970-01-01T00:00:00.000Z');
98
+ expect(state.state.pendingWorkersNewest).toBe(FIXED_NOW);
99
+ expect(state.state.pendingWorkersNewest).not.toBe(staleNewest);
100
+ });
101
+ it('should reuse pending values from state on ContinueExtractingData instead of re-resolving', async () => {
102
+ // Arrange: state has pending values from a prior StartExtractingMetadata phase
103
+ const pendingOldest = '1970-01-01T00:00:00.000Z';
104
+ const pendingNewest = '2026-03-26T08:00:00.000Z'; // Earlier than FIXED_NOW
105
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
106
+ context: {
107
+ snap_in_version_id: 'test_snap_in_version_id',
108
+ },
109
+ payload: {
110
+ event_type: extraction_1.EventType.ContinueExtractingData,
111
+ event_context: {
112
+ // Platform still sends TimeValue objects, but they should be ignored
113
+ extraction_start_time: {
114
+ type: extraction_1.TimeValueType.CURRENT_TIME,
115
+ },
116
+ extraction_end_time: {
117
+ type: extraction_1.TimeValueType.CURRENT_TIME,
118
+ },
119
+ },
120
+ },
121
+ });
122
+ const stringifiedState = JSON.stringify({
123
+ snapInVersionId: 'test_snap_in_version_id',
124
+ pendingWorkersOldest: pendingOldest,
125
+ pendingWorkersNewest: pendingNewest,
126
+ });
127
+ fetchStateSpy.mockResolvedValue({ state: stringifiedState });
128
+ // Act
129
+ const state = await (0, state_1.createAdapterState)({
130
+ event,
131
+ initialState: {},
132
+ initialDomainMapping: {},
133
+ });
134
+ // Assert: uses cached pending values, NOT new Date() resolution
135
+ expect(event.payload.event_context.extract_from).toBe(pendingOldest);
136
+ expect(event.payload.event_context.extract_to).toBe(pendingNewest);
137
+ // Pending values in state remain unchanged
138
+ expect(state.state.pendingWorkersOldest).toBe(pendingOldest);
139
+ expect(state.state.pendingWorkersNewest).toBe(pendingNewest);
140
+ });
141
+ it('should not set extract_from/extract_to on ContinueExtractingData if no pending values exist', async () => {
142
+ // Arrange: state has no pending values (e.g. old state from before this feature)
143
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
144
+ context: {
145
+ snap_in_version_id: 'test_snap_in_version_id',
146
+ },
147
+ payload: { event_type: extraction_1.EventType.ContinueExtractingData },
148
+ });
149
+ const stringifiedState = JSON.stringify({
150
+ snapInVersionId: 'test_snap_in_version_id',
151
+ });
152
+ fetchStateSpy.mockResolvedValue({ state: stringifiedState });
153
+ // Act
154
+ await (0, state_1.createAdapterState)({
155
+ event,
156
+ initialState: {},
157
+ initialDomainMapping: {},
158
+ });
159
+ // Assert: no extraction timestamps are set
160
+ expect(event.payload.event_context.extract_from).toBeUndefined();
161
+ expect(event.payload.event_context.extract_to).toBeUndefined();
162
+ });
163
+ it('should reuse pending values on StartExtractingAttachments', async () => {
164
+ // Arrange: state has pending values from the StartExtractingMetadata phase
165
+ const pendingOldest = '1970-01-01T00:00:00.000Z';
166
+ const pendingNewest = '2026-03-26T08:00:00.000Z';
167
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
168
+ context: {
169
+ snap_in_version_id: 'test_snap_in_version_id',
170
+ },
171
+ payload: { event_type: extraction_1.EventType.StartExtractingAttachments },
172
+ });
173
+ const stringifiedState = JSON.stringify({
174
+ snapInVersionId: 'test_snap_in_version_id',
175
+ pendingWorkersOldest: pendingOldest,
176
+ pendingWorkersNewest: pendingNewest,
177
+ });
178
+ fetchStateSpy.mockResolvedValue({ state: stringifiedState });
179
+ // Act
180
+ await (0, state_1.createAdapterState)({
181
+ event,
182
+ initialState: {},
183
+ initialDomainMapping: {},
184
+ });
185
+ // Assert: pending values are reused
186
+ expect(event.payload.event_context.extract_from).toBe(pendingOldest);
187
+ expect(event.payload.event_context.extract_to).toBe(pendingNewest);
188
+ });
189
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state.post-state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.post-state.test.d.ts","sourceRoot":"","sources":["../../src/state/state.post-state.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jest_setup_1 = require("../tests/jest.setup");
4
+ const test_utils_1 = require("../common/test-utils");
5
+ const extraction_1 = require("../types/extraction");
6
+ const state_1 = require("./state");
7
+ /* eslint-disable @typescript-eslint/no-require-imports */
8
+ describe('State.postState', () => {
9
+ let postStateSpy;
10
+ let fetchStateSpy;
11
+ let processExitSpy;
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ jest.restoreAllMocks();
15
+ postStateSpy = jest.spyOn(state_1.State.prototype, 'postState');
16
+ fetchStateSpy = jest.spyOn(state_1.State.prototype, 'fetchState');
17
+ processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
18
+ throw new Error('process.exit called');
19
+ });
20
+ });
21
+ it('should POST the stringified state to the update endpoint', async () => {
22
+ // Arrange
23
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
24
+ context: {
25
+ snap_in_version_id: '1.0.0',
26
+ secrets: { service_account_token: 'test_token' },
27
+ },
28
+ payload: { event_type: extraction_1.EventType.StartExtractingData },
29
+ });
30
+ const stateToPost = { snapInVersionId: '1.0.0', foo: 'bar' };
31
+ fetchStateSpy.mockResolvedValue({
32
+ state: JSON.stringify({ snapInVersionId: '1.0.0' }),
33
+ });
34
+ postStateSpy.mockRestore();
35
+ const adapterState = await (0, state_1.createAdapterState)({
36
+ event,
37
+ initialState: {},
38
+ initialDomainMapping: {},
39
+ });
40
+ // Act
41
+ await adapterState.postState(stateToPost);
42
+ // Assert: the mock server records all incoming requests — inspect what was sent
43
+ const requests = jest_setup_1.mockServer.getRequests('POST', '/worker_data_url.update');
44
+ expect(requests).toHaveLength(1);
45
+ const body = requests[0].body;
46
+ // Body must contain the stringified state, preserving the original fields
47
+ expect(typeof body.state).toBe('string');
48
+ const parsed = JSON.parse(body.state);
49
+ expect(parsed.foo).toBe('bar');
50
+ expect(parsed.snapInVersionId).toBe('1.0.0');
51
+ });
52
+ it('should exit(1) when postState HTTP request fails', async () => {
53
+ // Arrange
54
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
55
+ context: { snap_in_version_id: '1.0.0' },
56
+ payload: { event_type: extraction_1.EventType.StartExtractingData },
57
+ });
58
+ fetchStateSpy.mockResolvedValue({
59
+ state: JSON.stringify({ snapInVersionId: '1.0.0' }),
60
+ });
61
+ postStateSpy.mockRestore();
62
+ const adapterState = await (0, state_1.createAdapterState)({
63
+ event,
64
+ initialState: {},
65
+ initialDomainMapping: {},
66
+ });
67
+ // Mock axiosClient.post directly to bypass the retry backoff
68
+ const axiosClientModule = require('../http/axios-client-internal');
69
+ const axiosPostSpy = jest
70
+ .spyOn(axiosClientModule.axiosClient, 'post')
71
+ .mockRejectedValue(new Error('network error'));
72
+ // Act & Assert
73
+ await expect(adapterState.postState()).rejects.toThrow('process.exit called');
74
+ expect(processExitSpy).toHaveBeenCalledWith(1);
75
+ axiosPostSpy.mockRestore();
76
+ });
77
+ });