@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
@@ -0,0 +1,596 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const state_1 = require("../../state/state");
4
+ const jest_setup_1 = require("../../tests/jest.setup");
5
+ const test_utils_1 = require("../../common/test-utils");
6
+ const types_1 = require("../../types");
7
+ const loading_1 = require("../../types/loading");
8
+ const worker_adapter_1 = require("./worker-adapter");
9
+ jest.mock('../../common/control-protocol', () => ({
10
+ emit: jest.fn().mockResolvedValue({}),
11
+ }));
12
+ jest.mock('../../mappers/mappers');
13
+ jest.mock('../../uploader/uploader');
14
+ jest.mock('../../repo/repo');
15
+ jest.mock('node:worker_threads', () => ({
16
+ parentPort: { postMessage: jest.fn() },
17
+ }));
18
+ jest.mock('../../attachments-streaming/attachments-streaming-pool', () => ({
19
+ AttachmentsStreamingPool: jest.fn().mockImplementation(() => ({
20
+ streamAll: jest.fn().mockResolvedValue(undefined),
21
+ })),
22
+ }));
23
+ function makeAdapter(eventType) {
24
+ const event = (0, test_utils_1.createMockEvent)(jest_setup_1.mockServer.baseUrl, {
25
+ payload: { event_type: eventType },
26
+ });
27
+ const initialState = {
28
+ attachments: { completed: false },
29
+ snapInVersionId: '',
30
+ toDevRev: {
31
+ attachmentsMetadata: {
32
+ artifactIds: [],
33
+ lastProcessed: 0,
34
+ lastProcessedAttachmentsIdsList: [],
35
+ },
36
+ },
37
+ };
38
+ const adapterState = new state_1.State({ event, initialState });
39
+ const adapter = new worker_adapter_1.WorkerAdapter({ event, adapterState });
40
+ return { adapter, event, adapterState };
41
+ }
42
+ function makeLoaderItem(devrevId = 'dev-1') {
43
+ return {
44
+ id: { devrev: devrevId, external: 'ext-1' },
45
+ created_date: '',
46
+ modified_date: '',
47
+ data: {},
48
+ };
49
+ }
50
+ function setupLoaderFile(adapter, items, itemType = 'tasks') {
51
+ adapter['adapterState'].state.fromDevRev = {
52
+ filesToLoad: [
53
+ {
54
+ id: 'artifact-1',
55
+ file_name: 'file.json',
56
+ itemType,
57
+ count: items.length,
58
+ lineToProcess: 0,
59
+ completed: false,
60
+ },
61
+ ],
62
+ };
63
+ adapter['uploader'].getJsonObjectByArtifactId = jest
64
+ .fn()
65
+ .mockResolvedValue({ response: items });
66
+ }
67
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadItemTypes — timeout and unexpected errors`, () => {
68
+ let adapter;
69
+ let exitSpy;
70
+ let emitSpy;
71
+ beforeEach(() => {
72
+ jest.clearAllMocks();
73
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingData));
74
+ exitSpy = jest
75
+ .spyOn(process, 'exit')
76
+ .mockImplementation(() => undefined);
77
+ emitSpy = jest.spyOn(adapter, 'emit').mockResolvedValue();
78
+ });
79
+ afterEach(() => {
80
+ exitSpy.mockRestore();
81
+ jest.restoreAllMocks();
82
+ });
83
+ it('should emit DataLoadingProgress and exit on timeout', async () => {
84
+ // Arrange
85
+ const items = [makeLoaderItem('dev-1'), makeLoaderItem('dev-2')];
86
+ setupLoaderFile(adapter, items);
87
+ adapter.isTimeout = true;
88
+ const itemTypesToLoad = [
89
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
90
+ ];
91
+ // Act
92
+ await adapter.loadItemTypes({ itemTypesToLoad });
93
+ // Assert
94
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.DataLoadingProgress);
95
+ expect(exitSpy).toHaveBeenCalledWith(0);
96
+ });
97
+ it('should emit DataLoadingProgress mid-loop when timeout arrives between items', async () => {
98
+ // Arrange
99
+ const items = [
100
+ makeLoaderItem('dev-1'),
101
+ makeLoaderItem('dev-2'),
102
+ makeLoaderItem('dev-3'),
103
+ ];
104
+ setupLoaderFile(adapter, items);
105
+ exitSpy.mockRestore();
106
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => {
107
+ throw new Error('process.exit');
108
+ }));
109
+ let loadItemCallCount = 0;
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/require-await
111
+ jest.spyOn(adapter, 'loadItem').mockImplementation(async () => {
112
+ loadItemCallCount++;
113
+ if (loadItemCallCount === 1) {
114
+ adapter.isTimeout = true;
115
+ }
116
+ return { report: { item_type: 'tasks', updated: 1 } };
117
+ });
118
+ const itemTypesToLoad = [
119
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
120
+ ];
121
+ // Act & Assert
122
+ await expect(adapter.loadItemTypes({ itemTypesToLoad })).rejects.toThrow('process.exit');
123
+ expect(loadItemCallCount).toBe(1);
124
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.DataLoadingProgress);
125
+ });
126
+ it('should emit DataLoadingError and exit(1) on unexpected error', async () => {
127
+ // Arrange
128
+ adapter['adapterState'].state.fromDevRev = {
129
+ filesToLoad: [
130
+ {
131
+ id: 'artifact-1',
132
+ file_name: 'file1.json',
133
+ itemType: 'tasks',
134
+ count: 1,
135
+ lineToProcess: 0,
136
+ completed: false,
137
+ },
138
+ ],
139
+ };
140
+ adapter['uploader'].getJsonObjectByArtifactId = jest
141
+ .fn()
142
+ .mockRejectedValue(new Error('Unexpected network failure'));
143
+ const itemTypesToLoad = [
144
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
145
+ ];
146
+ // Act
147
+ await adapter.loadItemTypes({ itemTypesToLoad });
148
+ // Assert
149
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.DataLoadingError, expect.objectContaining({
150
+ error: expect.objectContaining({
151
+ message: expect.stringContaining('Error during data loading'),
152
+ }),
153
+ }));
154
+ expect(exitSpy).toHaveBeenCalledWith(1);
155
+ });
156
+ });
157
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadItemTypes — loadItem branch coverage via public API`, () => {
158
+ let adapter;
159
+ let emitSpy;
160
+ let exitSpy;
161
+ const itemTypesToLoad = [
162
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
163
+ ];
164
+ beforeEach(() => {
165
+ jest.clearAllMocks();
166
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingData));
167
+ emitSpy = jest.spyOn(adapter, 'emit').mockResolvedValue();
168
+ exitSpy = jest
169
+ .spyOn(process, 'exit')
170
+ .mockImplementation(() => undefined);
171
+ itemTypesToLoad[0].create = jest.fn();
172
+ itemTypesToLoad[0].update = jest.fn();
173
+ });
174
+ afterEach(() => {
175
+ exitSpy.mockRestore();
176
+ jest.restoreAllMocks();
177
+ });
178
+ it('should accumulate an UPDATED report when the connector updates the item and the mapper sync succeeds', async () => {
179
+ // Arrange
180
+ setupLoaderFile(adapter, [makeLoaderItem('dev-1')]);
181
+ adapter['_mappers'].getByTargetId = jest.fn().mockResolvedValue({
182
+ data: { sync_mapper_record: { id: 'smr-1' } },
183
+ });
184
+ adapter['_mappers'].update = jest.fn().mockResolvedValue({ data: {} });
185
+ itemTypesToLoad[0].update = jest
186
+ .fn()
187
+ .mockResolvedValue({ id: 'ext-updated-1' });
188
+ // Act
189
+ const { reports } = await adapter.loadItemTypes({ itemTypesToLoad });
190
+ // Assert
191
+ expect(reports).toEqual(expect.arrayContaining([
192
+ expect.objectContaining({
193
+ item_type: 'tasks',
194
+ [loading_1.ActionType.UPDATED]: 1,
195
+ }),
196
+ ]));
197
+ expect(emitSpy).not.toHaveBeenCalled();
198
+ });
199
+ it('should fall back to create and accumulate a CREATED report when the mapper record does not exist (404)', async () => {
200
+ // Arrange
201
+ setupLoaderFile(adapter, [makeLoaderItem('dev-2')]);
202
+ const axiosError = { isAxiosError: true, response: { status: 404 } };
203
+ adapter['_mappers'].getByTargetId = jest.fn().mockRejectedValue(axiosError);
204
+ adapter['_mappers'].create = jest.fn().mockResolvedValue({ data: {} });
205
+ itemTypesToLoad[0].create = jest
206
+ .fn()
207
+ .mockResolvedValue({ id: 'new-ext-id' });
208
+ // Act
209
+ const { reports } = await adapter.loadItemTypes({ itemTypesToLoad });
210
+ // Assert
211
+ expect(reports).toEqual(expect.arrayContaining([
212
+ expect.objectContaining({
213
+ item_type: 'tasks',
214
+ [loading_1.ActionType.CREATED]: 1,
215
+ }),
216
+ ]));
217
+ expect(emitSpy).not.toHaveBeenCalled();
218
+ });
219
+ it('should emit DataLoadingDelayed and stop processing when the connector signals a rate-limit delay', async () => {
220
+ // Arrange
221
+ setupLoaderFile(adapter, [makeLoaderItem('dev-3')]);
222
+ adapter['_mappers'].getByTargetId = jest.fn().mockResolvedValue({
223
+ data: { sync_mapper_record: { id: 'smr-1' } },
224
+ });
225
+ itemTypesToLoad[0].update = jest.fn().mockResolvedValue({ delay: 15 });
226
+ // Act
227
+ await adapter.loadItemTypes({ itemTypesToLoad });
228
+ // Assert
229
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.DataLoadingDelayed, expect.objectContaining({ delay: 15 }));
230
+ });
231
+ it('should count the item as FAILED when the update succeeds but the mapper sync throws', async () => {
232
+ // Arrange
233
+ setupLoaderFile(adapter, [makeLoaderItem('dev-4')]);
234
+ adapter['_mappers'].getByTargetId = jest.fn().mockResolvedValue({
235
+ data: { sync_mapper_record: { id: 'smr-1' } },
236
+ });
237
+ adapter['_mappers'].update = jest
238
+ .fn()
239
+ .mockRejectedValue(new Error('mapper down'));
240
+ itemTypesToLoad[0].update = jest.fn().mockResolvedValue({ id: 'ext-id' });
241
+ // Act
242
+ const { reports } = await adapter.loadItemTypes({ itemTypesToLoad });
243
+ // Assert
244
+ expect(emitSpy).not.toHaveBeenCalled();
245
+ expect(reports).toBeDefined();
246
+ });
247
+ it('should not emit for a non-404 Axios error from the mapper (recorded as item-level error)', async () => {
248
+ // Arrange
249
+ setupLoaderFile(adapter, [makeLoaderItem('dev-5')]);
250
+ const axiosError = {
251
+ isAxiosError: true,
252
+ message: 'internal server error',
253
+ response: { status: 500 },
254
+ };
255
+ adapter['_mappers'].getByTargetId = jest.fn().mockRejectedValue(axiosError);
256
+ // Act
257
+ await adapter.loadItemTypes({ itemTypesToLoad });
258
+ // Assert
259
+ expect(emitSpy).not.toHaveBeenCalled();
260
+ });
261
+ it('should handle a null sync_mapper_record gracefully and continue loading', async () => {
262
+ // Arrange
263
+ setupLoaderFile(adapter, [makeLoaderItem('dev-6')]);
264
+ adapter['_mappers'].getByTargetId = jest
265
+ .fn()
266
+ .mockResolvedValue({ data: null });
267
+ // Act
268
+ const { reports } = await adapter.loadItemTypes({ itemTypesToLoad });
269
+ // Assert
270
+ expect(emitSpy).not.toHaveBeenCalled();
271
+ expect(reports).toBeDefined();
272
+ });
273
+ });
274
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadItemTypes — additional branches`, () => {
275
+ let adapter;
276
+ let emitSpy;
277
+ beforeEach(() => {
278
+ jest.clearAllMocks();
279
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingData));
280
+ emitSpy = jest.spyOn(adapter, 'emit').mockResolvedValue();
281
+ });
282
+ afterEach(() => {
283
+ jest.restoreAllMocks();
284
+ });
285
+ it('should return immediately with empty reports when filesToLoad is empty', async () => {
286
+ // Arrange
287
+ adapter['adapterState'].state.fromDevRev = { filesToLoad: [] };
288
+ // Act
289
+ const result = await adapter.loadItemTypes({
290
+ itemTypesToLoad: [
291
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
292
+ ],
293
+ });
294
+ // Assert
295
+ expect(result.reports).toEqual([]);
296
+ expect(emitSpy).not.toHaveBeenCalled();
297
+ });
298
+ it('should emit DataLoadingError when a file references an item type not in itemTypesToLoad', async () => {
299
+ // Arrange
300
+ adapter['adapterState'].state.fromDevRev = {
301
+ filesToLoad: [
302
+ {
303
+ id: 'art-1',
304
+ file_name: 'file.json',
305
+ itemType: 'unknown-type',
306
+ count: 1,
307
+ lineToProcess: 0,
308
+ completed: false,
309
+ },
310
+ ],
311
+ };
312
+ adapter['uploader'].getJsonObjectByArtifactId = jest
313
+ .fn()
314
+ .mockResolvedValue({ response: [makeLoaderItem()] });
315
+ // Act
316
+ await adapter.loadItemTypes({
317
+ itemTypesToLoad: [
318
+ { itemType: 'tasks', create: jest.fn(), update: jest.fn() },
319
+ ],
320
+ });
321
+ // Assert
322
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.DataLoadingError, expect.objectContaining({
323
+ error: expect.objectContaining({
324
+ message: expect.stringContaining('unknown-type'),
325
+ }),
326
+ }));
327
+ });
328
+ });
329
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadAttachments — timeout, transformer errors, unexpected errors`, () => {
330
+ let adapter;
331
+ let exitSpy;
332
+ let emitSpy;
333
+ function setupFilesToLoad(a, items) {
334
+ a['adapterState'].state.fromDevRev = {
335
+ filesToLoad: [
336
+ {
337
+ id: 'artifact-1',
338
+ file_name: 'attachments.json',
339
+ itemType: 'attachment',
340
+ count: items.length,
341
+ lineToProcess: 0,
342
+ completed: false,
343
+ },
344
+ ],
345
+ };
346
+ a['uploader'].getJsonObjectByArtifactId = jest
347
+ .fn()
348
+ .mockResolvedValue({ response: items });
349
+ }
350
+ beforeEach(() => {
351
+ jest.clearAllMocks();
352
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingAttachments));
353
+ exitSpy = jest
354
+ .spyOn(process, 'exit')
355
+ .mockImplementation(() => undefined);
356
+ emitSpy = jest.spyOn(adapter, 'emit').mockResolvedValue();
357
+ });
358
+ afterEach(() => {
359
+ exitSpy.mockRestore();
360
+ jest.restoreAllMocks();
361
+ });
362
+ it('should emit AttachmentLoadingProgress and exit on timeout', async () => {
363
+ // Arrange
364
+ const items = [
365
+ {
366
+ reference_id: 'ref-1',
367
+ parent_type: 'task',
368
+ parent_reference_id: 'parent-1',
369
+ file_name: 'file.pdf',
370
+ file_type: 'application/pdf',
371
+ file_size: 100,
372
+ url: 'https://example.com/file.pdf',
373
+ valid_until: '',
374
+ created_by_id: 'user-1',
375
+ created_date: '',
376
+ modified_by_id: 'user-1',
377
+ modified_date: '',
378
+ },
379
+ ];
380
+ setupFilesToLoad(adapter, items);
381
+ adapter.isTimeout = true;
382
+ // Act
383
+ await adapter.loadAttachments({
384
+ create: jest.fn(),
385
+ });
386
+ // Assert
387
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.AttachmentLoadingProgress);
388
+ expect(exitSpy).toHaveBeenCalledWith(0);
389
+ });
390
+ it('should emit AttachmentLoadingError on transformer file error', async () => {
391
+ // Arrange
392
+ adapter['adapterState'].state.fromDevRev = {
393
+ filesToLoad: [
394
+ {
395
+ id: 'bad-artifact',
396
+ file_name: 'attachments.json',
397
+ itemType: 'attachment',
398
+ count: 1,
399
+ lineToProcess: 0,
400
+ completed: false,
401
+ },
402
+ ],
403
+ };
404
+ adapter['uploader'].getJsonObjectByArtifactId = jest
405
+ .fn()
406
+ .mockResolvedValue({
407
+ response: null,
408
+ error: new Error('Artifact not found'),
409
+ });
410
+ // Act
411
+ await adapter.loadAttachments({
412
+ create: jest.fn(),
413
+ });
414
+ // Assert
415
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.AttachmentLoadingError, expect.objectContaining({
416
+ error: expect.objectContaining({
417
+ message: expect.stringContaining('Transformer file not found'),
418
+ }),
419
+ }));
420
+ });
421
+ it('should emit AttachmentLoadingError and exit(1) on unexpected error', async () => {
422
+ // Arrange
423
+ const items = [
424
+ {
425
+ reference_id: 'ref-1',
426
+ parent_type: 'task',
427
+ parent_reference_id: 'parent-1',
428
+ file_name: 'file.pdf',
429
+ file_type: 'application/pdf',
430
+ file_size: 100,
431
+ url: 'https://example.com/file.pdf',
432
+ valid_until: '',
433
+ created_by_id: 'user-1',
434
+ created_date: '',
435
+ modified_by_id: 'user-1',
436
+ modified_date: '',
437
+ },
438
+ ];
439
+ setupFilesToLoad(adapter, items);
440
+ const mockCreate = jest
441
+ .fn()
442
+ .mockRejectedValue(new Error('Unexpected API failure'));
443
+ // Act
444
+ await adapter.loadAttachments({ create: mockCreate });
445
+ // Assert
446
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.AttachmentLoadingError, expect.objectContaining({
447
+ error: expect.objectContaining({
448
+ message: expect.stringContaining('Error during attachment loading'),
449
+ }),
450
+ }));
451
+ expect(exitSpy).toHaveBeenCalledWith(1);
452
+ });
453
+ });
454
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadAttachments — additional branches`, () => {
455
+ let adapter;
456
+ let emitSpy;
457
+ beforeEach(() => {
458
+ jest.clearAllMocks();
459
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingAttachments));
460
+ emitSpy = jest.spyOn(adapter, 'emit').mockResolvedValue();
461
+ });
462
+ afterEach(() => {
463
+ jest.restoreAllMocks();
464
+ });
465
+ it('should return immediately with empty reports when fromDevRev is not set', async () => {
466
+ // Arrange
467
+ adapter['adapterState'].state.fromDevRev = undefined;
468
+ // Act
469
+ const result = await adapter.loadAttachments({ create: jest.fn() });
470
+ // Assert
471
+ expect(result.reports).toEqual([]);
472
+ expect(emitSpy).not.toHaveBeenCalled();
473
+ });
474
+ it('should emit AttachmentLoadingDelayed and stop the loop when the connector signals a rate-limit delay', async () => {
475
+ // Arrange
476
+ adapter['adapterState'].state.fromDevRev = {
477
+ filesToLoad: [
478
+ {
479
+ id: 'art-1',
480
+ file_name: 'attachments.json',
481
+ itemType: 'attachment',
482
+ count: 1,
483
+ lineToProcess: 0,
484
+ completed: false,
485
+ },
486
+ ],
487
+ };
488
+ adapter['uploader'].getJsonObjectByArtifactId = jest
489
+ .fn()
490
+ .mockResolvedValue({
491
+ response: [
492
+ {
493
+ reference_id: 'ref-1',
494
+ parent_type: 'task',
495
+ parent_reference_id: 'parent-1',
496
+ file_name: 'file.pdf',
497
+ file_type: 'application/pdf',
498
+ file_size: 100,
499
+ url: 'https://example.com/file.pdf',
500
+ valid_until: '',
501
+ created_by_id: 'user-1',
502
+ created_date: '',
503
+ modified_by_id: 'user-1',
504
+ modified_date: '',
505
+ },
506
+ ],
507
+ });
508
+ jest
509
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
510
+ .spyOn(adapter, 'loadAttachment')
511
+ .mockResolvedValue({ rateLimit: { delay: 20 } });
512
+ // Act
513
+ await adapter.loadAttachments({ create: jest.fn() });
514
+ // Assert
515
+ expect(emitSpy).toHaveBeenCalledWith(types_1.LoaderEventType.AttachmentLoadingDelayed, expect.objectContaining({ delay: 20 }));
516
+ });
517
+ });
518
+ describe(`${worker_adapter_1.WorkerAdapter.name}.loadAttachment`, () => {
519
+ let adapter;
520
+ beforeEach(() => {
521
+ jest.clearAllMocks();
522
+ ({ adapter } = makeAdapter(types_1.EventType.ContinueLoadingAttachments));
523
+ });
524
+ afterEach(() => {
525
+ jest.restoreAllMocks();
526
+ });
527
+ function makeAttachment() {
528
+ return {
529
+ reference_id: 'ref-1',
530
+ parent_type: 'task',
531
+ parent_reference_id: 'parent-1',
532
+ file_name: 'file.pdf',
533
+ file_type: 'application/pdf',
534
+ file_size: 100,
535
+ url: 'https://example.com/file.pdf',
536
+ valid_until: '',
537
+ created_by_id: 'user-1',
538
+ created_date: '',
539
+ modified_by_id: 'user-1',
540
+ modified_date: '',
541
+ };
542
+ }
543
+ it('should return a CREATED report when create succeeds', async () => {
544
+ var _a, _b;
545
+ // Arrange
546
+ adapter['_mappers'].create = jest.fn().mockResolvedValue({ data: {} });
547
+ const create = jest.fn().mockResolvedValue({ id: 'att-ext-1' });
548
+ // Act
549
+ const result = await adapter['loadAttachment']({
550
+ item: makeAttachment(),
551
+ create,
552
+ });
553
+ // Assert
554
+ expect((_a = result.report) === null || _a === void 0 ? void 0 : _a.item_type).toBe('attachment');
555
+ expect((_b = result.report) === null || _b === void 0 ? void 0 : _b[loading_1.ActionType.CREATED]).toBe(1);
556
+ });
557
+ it('should still return CREATED even when mapper create fails — attachment loading is resilient', async () => {
558
+ var _a;
559
+ // Arrange
560
+ adapter['_mappers'].create = jest
561
+ .fn()
562
+ .mockRejectedValue(new Error('mapper failed'));
563
+ const create = jest.fn().mockResolvedValue({ id: 'att-ext-1' });
564
+ // Act
565
+ const result = await adapter['loadAttachment']({
566
+ item: makeAttachment(),
567
+ create,
568
+ });
569
+ // Assert
570
+ expect((_a = result.report) === null || _a === void 0 ? void 0 : _a[loading_1.ActionType.CREATED]).toBe(1);
571
+ });
572
+ it('should propagate rate-limit delay when the connector signals one', async () => {
573
+ var _a;
574
+ // Arrange
575
+ const create = jest.fn().mockResolvedValue({ delay: 30 });
576
+ // Act
577
+ const result = await adapter['loadAttachment']({
578
+ item: makeAttachment(),
579
+ create,
580
+ });
581
+ // Assert
582
+ expect((_a = result.rateLimit) === null || _a === void 0 ? void 0 : _a.delay).toBe(30);
583
+ });
584
+ it('should return a FAILED report when create returns neither id nor delay', async () => {
585
+ var _a;
586
+ // Arrange
587
+ const create = jest.fn().mockResolvedValue({ id: null, delay: null });
588
+ // Act
589
+ const result = await adapter['loadAttachment']({
590
+ item: makeAttachment(),
591
+ create,
592
+ });
593
+ // Assert
594
+ expect((_a = result.report) === null || _a === void 0 ? void 0 : _a[loading_1.ActionType.FAILED]).toBe(1);
595
+ });
596
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker-adapter.serialization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-adapter.serialization.test.d.ts","sourceRoot":"","sources":["../../../src/multithreading/worker-adapter/worker-adapter.serialization.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const js_jsonl_1 = require("js-jsonl");
4
+ // Pin the serialization contract for items that reach the uploader.
5
+ //
6
+ // The SDK uploads items via Uploader.upload(), which calls jsonl.stringify() on
7
+ // the input. That means user `create`/`update` callbacks and normalizers can
8
+ // silently produce inputs that fail or lose information at the wire boundary:
9
+ //
10
+ // - Circular references throw "Converting circular structure to JSON"
11
+ // - BigInt values throw "Do not know how to serialize a BigInt"
12
+ // - Date objects are converted to ISO strings (information loss: no Date on
13
+ // the other side, just a string)
14
+ // - undefined fields are dropped (null fields are preserved)
15
+ //
16
+ // These tests exist to catch regressions in the serialization layer (e.g.,
17
+ // silently switching to a different serializer that masks BigInt or mangles
18
+ // Dates) before they reach production.
19
+ describe('serialization boundary for items uploaded via jsonl', () => {
20
+ it('throws when an item contains a circular reference', () => {
21
+ // Arrange
22
+ const item = { id: 'a' };
23
+ item.self = item;
24
+ // Act & Assert
25
+ expect(() => js_jsonl_1.jsonl.stringify([item])).toThrow(/circular/i);
26
+ });
27
+ it('throws when an item contains a BigInt field', () => {
28
+ // Arrange
29
+ const item = { id: 'a', counter: BigInt(1) };
30
+ // Act & Assert
31
+ expect(() => js_jsonl_1.jsonl.stringify([item])).toThrow(/BigInt/i);
32
+ });
33
+ it('serializes Date instances to ISO strings (information loss — consumer receives a string)', () => {
34
+ // Arrange
35
+ const item = {
36
+ id: 'a',
37
+ created: new Date('2025-01-01T00:00:00.000Z'),
38
+ };
39
+ // Act
40
+ const output = js_jsonl_1.jsonl.stringify([item]);
41
+ const parsed = JSON.parse(output);
42
+ // Assert
43
+ expect(parsed.created).toBe('2025-01-01T00:00:00.000Z');
44
+ expect(typeof parsed.created).toBe('string');
45
+ });
46
+ it('drops undefined fields but preserves null fields', () => {
47
+ // Arrange
48
+ const item = {
49
+ id: 'a',
50
+ present: null,
51
+ missing: undefined,
52
+ };
53
+ // Act
54
+ const output = js_jsonl_1.jsonl.stringify([item]);
55
+ const parsed = JSON.parse(output);
56
+ // Assert
57
+ expect(parsed).toEqual({ id: 'a', present: null });
58
+ expect(parsed).not.toHaveProperty('missing');
59
+ });
60
+ it('emits one newline-terminated line per item (jsonl format)', () => {
61
+ // Arrange
62
+ const items = [{ id: 'a' }, { id: 'b' }, { id: 'c' }];
63
+ // Act
64
+ const output = js_jsonl_1.jsonl.stringify(items);
65
+ const lines = output.split('\n').filter((l) => l.length > 0);
66
+ // Assert
67
+ expect(lines).toHaveLength(3);
68
+ expect(JSON.parse(lines[0])).toEqual({ id: 'a' });
69
+ expect(JSON.parse(lines[2])).toEqual({ id: 'c' });
70
+ });
71
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/repo/repo.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAG3D,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,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;gBAEzB,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;IAqCxB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAuC5C"}
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;AAE3B,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;gBAEzB,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;IAqCxB,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAyC5C"}