@devrev/airsync-sdk 2.0.0-beta.0

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 (180) hide show
  1. package/README.md +31 -0
  2. package/dist/attachments-streaming/attachments-streaming-pool.d.ts +16 -0
  3. package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -0
  4. package/dist/attachments-streaming/attachments-streaming-pool.interfaces.d.ts +9 -0
  5. package/dist/attachments-streaming/attachments-streaming-pool.interfaces.d.ts.map +1 -0
  6. package/dist/attachments-streaming/attachments-streaming-pool.interfaces.js +2 -0
  7. package/dist/attachments-streaming/attachments-streaming-pool.js +97 -0
  8. package/dist/attachments-streaming/attachments-streaming-pool.test.d.ts +2 -0
  9. package/dist/attachments-streaming/attachments-streaming-pool.test.d.ts.map +1 -0
  10. package/dist/attachments-streaming/attachments-streaming-pool.test.js +267 -0
  11. package/dist/common/constants.d.ts +25 -0
  12. package/dist/common/constants.d.ts.map +1 -0
  13. package/dist/common/constants.js +58 -0
  14. package/dist/common/control-protocol.d.ts +10 -0
  15. package/dist/common/control-protocol.d.ts.map +1 -0
  16. package/dist/common/control-protocol.js +31 -0
  17. package/dist/common/errors.d.ts +6 -0
  18. package/dist/common/errors.d.ts.map +1 -0
  19. package/dist/common/errors.js +4 -0
  20. package/dist/common/event-type-translation.d.ts +24 -0
  21. package/dist/common/event-type-translation.d.ts.map +1 -0
  22. package/dist/common/event-type-translation.js +117 -0
  23. package/dist/common/helpers.d.ts +41 -0
  24. package/dist/common/helpers.d.ts.map +1 -0
  25. package/dist/common/helpers.js +124 -0
  26. package/dist/common/install-initial-domain-mapping.d.ts +4 -0
  27. package/dist/common/install-initial-domain-mapping.d.ts.map +1 -0
  28. package/dist/common/install-initial-domain-mapping.js +58 -0
  29. package/dist/common/install-initial-domain-mapping.test.d.ts +2 -0
  30. package/dist/common/install-initial-domain-mapping.test.d.ts.map +1 -0
  31. package/dist/common/install-initial-domain-mapping.test.js +207 -0
  32. package/dist/deprecated/adapter/index.d.ts +62 -0
  33. package/dist/deprecated/adapter/index.d.ts.map +1 -0
  34. package/dist/deprecated/adapter/index.js +151 -0
  35. package/dist/deprecated/common/helpers.d.ts +7 -0
  36. package/dist/deprecated/common/helpers.d.ts.map +1 -0
  37. package/dist/deprecated/common/helpers.js +47 -0
  38. package/dist/deprecated/demo-extractor/external_domain_metadata.json +38 -0
  39. package/dist/deprecated/demo-extractor/index.d.ts +18 -0
  40. package/dist/deprecated/demo-extractor/index.d.ts.map +1 -0
  41. package/dist/deprecated/demo-extractor/index.js +161 -0
  42. package/dist/deprecated/http/client.d.ts +22 -0
  43. package/dist/deprecated/http/client.d.ts.map +1 -0
  44. package/dist/deprecated/http/client.js +161 -0
  45. package/dist/deprecated/uploader/index.d.ts +35 -0
  46. package/dist/deprecated/uploader/index.d.ts.map +1 -0
  47. package/dist/deprecated/uploader/index.js +161 -0
  48. package/dist/http/axios-client-internal.d.ts +3 -0
  49. package/dist/http/axios-client-internal.d.ts.map +1 -0
  50. package/dist/http/axios-client-internal.js +66 -0
  51. package/dist/http/axios-client-internal.test.d.ts +2 -0
  52. package/dist/http/axios-client-internal.test.d.ts.map +1 -0
  53. package/dist/http/axios-client-internal.test.js +154 -0
  54. package/dist/http/axios-client.d.ts +27 -0
  55. package/dist/http/axios-client.d.ts.map +1 -0
  56. package/dist/http/axios-client.js +57 -0
  57. package/dist/http/constants.d.ts +4 -0
  58. package/dist/http/constants.d.ts.map +1 -0
  59. package/dist/http/constants.js +6 -0
  60. package/dist/http/index.d.ts +3 -0
  61. package/dist/http/index.d.ts.map +1 -0
  62. package/dist/http/index.js +18 -0
  63. package/dist/http/types.d.ts +17 -0
  64. package/dist/http/types.d.ts.map +1 -0
  65. package/dist/http/types.js +2 -0
  66. package/dist/index.d.ts +14 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +34 -0
  69. package/dist/logger/logger.constants.d.ts +6 -0
  70. package/dist/logger/logger.constants.d.ts.map +1 -0
  71. package/dist/logger/logger.constants.js +13 -0
  72. package/dist/logger/logger.context.d.ts +58 -0
  73. package/dist/logger/logger.context.d.ts.map +1 -0
  74. package/dist/logger/logger.context.js +86 -0
  75. package/dist/logger/logger.d.ts +89 -0
  76. package/dist/logger/logger.d.ts.map +1 -0
  77. package/dist/logger/logger.interfaces.d.ts +43 -0
  78. package/dist/logger/logger.interfaces.d.ts.map +1 -0
  79. package/dist/logger/logger.interfaces.js +9 -0
  80. package/dist/logger/logger.js +196 -0
  81. package/dist/logger/logger.test.d.ts +2 -0
  82. package/dist/logger/logger.test.d.ts.map +1 -0
  83. package/dist/logger/logger.test.js +490 -0
  84. package/dist/mappers/mappers.d.ts +52 -0
  85. package/dist/mappers/mappers.d.ts.map +1 -0
  86. package/dist/mappers/mappers.interface.d.ts +294 -0
  87. package/dist/mappers/mappers.interface.d.ts.map +1 -0
  88. package/dist/mappers/mappers.interface.js +48 -0
  89. package/dist/mappers/mappers.js +83 -0
  90. package/dist/mappers/mappers.test.d.ts +2 -0
  91. package/dist/mappers/mappers.test.d.ts.map +1 -0
  92. package/dist/mappers/mappers.test.js +107 -0
  93. package/dist/multithreading/create-worker.d.ts +5 -0
  94. package/dist/multithreading/create-worker.d.ts.map +1 -0
  95. package/dist/multithreading/create-worker.js +28 -0
  96. package/dist/multithreading/create-worker.test.d.ts +2 -0
  97. package/dist/multithreading/create-worker.test.d.ts.map +1 -0
  98. package/dist/multithreading/create-worker.test.js +89 -0
  99. package/dist/multithreading/process-task.d.ts +3 -0
  100. package/dist/multithreading/process-task.d.ts.map +1 -0
  101. package/dist/multithreading/process-task.js +58 -0
  102. package/dist/multithreading/spawn/spawn.d.ts +30 -0
  103. package/dist/multithreading/spawn/spawn.d.ts.map +1 -0
  104. package/dist/multithreading/spawn/spawn.helpers.d.ts +21 -0
  105. package/dist/multithreading/spawn/spawn.helpers.d.ts.map +1 -0
  106. package/dist/multithreading/spawn/spawn.helpers.js +114 -0
  107. package/dist/multithreading/spawn/spawn.helpers.test.d.ts +2 -0
  108. package/dist/multithreading/spawn/spawn.helpers.test.d.ts.map +1 -0
  109. package/dist/multithreading/spawn/spawn.helpers.test.js +293 -0
  110. package/dist/multithreading/spawn/spawn.js +249 -0
  111. package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.d.ts +2 -0
  112. package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.d.ts.map +1 -0
  113. package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +127 -0
  114. package/dist/multithreading/worker-adapter/worker-adapter.d.ts +91 -0
  115. package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +1 -0
  116. package/dist/multithreading/worker-adapter/worker-adapter.helpers.d.ts +22 -0
  117. package/dist/multithreading/worker-adapter/worker-adapter.helpers.d.ts.map +1 -0
  118. package/dist/multithreading/worker-adapter/worker-adapter.helpers.js +64 -0
  119. package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.d.ts +2 -0
  120. package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.d.ts.map +1 -0
  121. package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.js +514 -0
  122. package/dist/multithreading/worker-adapter/worker-adapter.js +747 -0
  123. package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts +2 -0
  124. package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts.map +1 -0
  125. package/dist/multithreading/worker-adapter/worker-adapter.test.js +483 -0
  126. package/dist/multithreading/worker.d.ts +2 -0
  127. package/dist/multithreading/worker.d.ts.map +1 -0
  128. package/dist/multithreading/worker.js +9 -0
  129. package/dist/repo/repo.d.ts +18 -0
  130. package/dist/repo/repo.d.ts.map +1 -0
  131. package/dist/repo/repo.interfaces.d.ts +46 -0
  132. package/dist/repo/repo.interfaces.d.ts.map +1 -0
  133. package/dist/repo/repo.interfaces.js +2 -0
  134. package/dist/repo/repo.js +75 -0
  135. package/dist/repo/repo.test.d.ts +2 -0
  136. package/dist/repo/repo.test.d.ts.map +1 -0
  137. package/dist/repo/repo.test.js +131 -0
  138. package/dist/state/state.d.ts +30 -0
  139. package/dist/state/state.d.ts.map +1 -0
  140. package/dist/state/state.interfaces.d.ts +51 -0
  141. package/dist/state/state.interfaces.d.ts.map +1 -0
  142. package/dist/state/state.interfaces.js +21 -0
  143. package/dist/state/state.js +166 -0
  144. package/dist/state/state.test.d.ts +2 -0
  145. package/dist/state/state.test.d.ts.map +1 -0
  146. package/dist/state/state.test.js +224 -0
  147. package/dist/types/common.d.ts +50 -0
  148. package/dist/types/common.d.ts.map +1 -0
  149. package/dist/types/common.js +25 -0
  150. package/dist/types/extraction.d.ts +417 -0
  151. package/dist/types/extraction.d.ts.map +1 -0
  152. package/dist/types/extraction.js +170 -0
  153. package/dist/types/extraction.test.d.ts +2 -0
  154. package/dist/types/extraction.test.d.ts.map +1 -0
  155. package/dist/types/extraction.test.js +70 -0
  156. package/dist/types/index.d.ts +9 -0
  157. package/dist/types/index.d.ts.map +1 -0
  158. package/dist/types/index.js +18 -0
  159. package/dist/types/loading.d.ts +147 -0
  160. package/dist/types/loading.d.ts.map +1 -0
  161. package/dist/types/loading.js +48 -0
  162. package/dist/types/workers.d.ts +161 -0
  163. package/dist/types/workers.d.ts.map +1 -0
  164. package/dist/types/workers.js +22 -0
  165. package/dist/uploader/uploader.d.ts +92 -0
  166. package/dist/uploader/uploader.d.ts.map +1 -0
  167. package/dist/uploader/uploader.helpers.d.ts +33 -0
  168. package/dist/uploader/uploader.helpers.d.ts.map +1 -0
  169. package/dist/uploader/uploader.helpers.js +139 -0
  170. package/dist/uploader/uploader.helpers.test.d.ts +2 -0
  171. package/dist/uploader/uploader.helpers.test.d.ts.map +1 -0
  172. package/dist/uploader/uploader.helpers.test.js +267 -0
  173. package/dist/uploader/uploader.interfaces.d.ts +95 -0
  174. package/dist/uploader/uploader.interfaces.d.ts.map +1 -0
  175. package/dist/uploader/uploader.interfaces.js +2 -0
  176. package/dist/uploader/uploader.js +305 -0
  177. package/dist/uploader/uploader.test.d.ts +2 -0
  178. package/dist/uploader/uploader.test.d.ts.map +1 -0
  179. package/dist/uploader/uploader.test.js +589 -0
  180. package/package.json +60 -0
@@ -0,0 +1,589 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const form_data_1 = __importDefault(require("form-data"));
7
+ const js_jsonl_1 = require("js-jsonl");
8
+ const zlib_1 = __importDefault(require("zlib"));
9
+ const axios_client_internal_1 = require("../http/axios-client-internal");
10
+ const test_helpers_1 = require("../tests/test-helpers");
11
+ const uploader_helpers_1 = require("./uploader.helpers");
12
+ const uploader_1 = require("./uploader");
13
+ jest.mock('../http/axios-client-internal');
14
+ jest.mock('./uploader.helpers', () => (Object.assign(Object.assign({}, jest.requireActual('./uploader.helpers')), { downloadToLocal: jest.fn(), compressGzip: jest.fn(jest.requireActual('./uploader.helpers').compressGzip) })));
15
+ const mockedAxiosClient = jest.mocked(axios_client_internal_1.axiosClient);
16
+ const mockedDownloadToLocal = jest.mocked(uploader_helpers_1.downloadToLocal);
17
+ const mockedCompressGzip = jest.mocked(uploader_helpers_1.compressGzip);
18
+ describe(uploader_1.Uploader.name, () => {
19
+ const mockEvent = (0, test_helpers_1.createEvent)();
20
+ let uploader;
21
+ beforeEach(() => {
22
+ uploader = new uploader_1.Uploader({ event: mockEvent });
23
+ });
24
+ afterEach(() => {
25
+ jest.clearAllMocks();
26
+ });
27
+ describe(uploader_1.Uploader.prototype.upload.name, () => {
28
+ const mockArtifactUploadUrlResponse = {
29
+ data: (0, test_helpers_1.createArtifact)(),
30
+ };
31
+ it('should return artifact info when upload flow succeeds', async () => {
32
+ // Arrange
33
+ const itemType = 'tasks';
34
+ const fetchedObjects = [
35
+ { id: 1, name: 'Task 1' },
36
+ { id: 2, name: 'Task 2' },
37
+ ];
38
+ mockedAxiosClient.get.mockResolvedValueOnce(mockArtifactUploadUrlResponse);
39
+ mockedAxiosClient.post.mockResolvedValue((0, test_helpers_1.createAxiosResponse)());
40
+ // Act
41
+ const result = await uploader.upload(itemType, fetchedObjects);
42
+ // Assert
43
+ expect(result).toEqual({
44
+ artifact: {
45
+ id: 'art_123',
46
+ item_type: itemType,
47
+ item_count: 2,
48
+ },
49
+ });
50
+ expect(result.error).toBeUndefined();
51
+ });
52
+ it('should report item_count as 1 when uploading single object', async () => {
53
+ var _a;
54
+ // Arrange
55
+ const itemType = 'metadata';
56
+ const fetchedObject = { key: 'value' };
57
+ mockedAxiosClient.get.mockResolvedValueOnce(mockArtifactUploadUrlResponse);
58
+ mockedAxiosClient.post.mockResolvedValue((0, test_helpers_1.createAxiosResponse)());
59
+ // Act
60
+ const result = await uploader.upload(itemType, fetchedObject);
61
+ // Assert
62
+ expect((_a = result.artifact) === null || _a === void 0 ? void 0 : _a.item_count).toBe(1);
63
+ expect(result.error).toBeUndefined();
64
+ });
65
+ it('should call downloadToLocal when isLocalDevelopment is true', async () => {
66
+ // Arrange
67
+ const localUploader = new uploader_1.Uploader({
68
+ event: mockEvent,
69
+ options: { isLocalDevelopment: true },
70
+ });
71
+ const itemType = 'tasks';
72
+ const fetchedObjects = [{ id: 1 }];
73
+ mockedAxiosClient.get.mockResolvedValueOnce(mockArtifactUploadUrlResponse);
74
+ mockedAxiosClient.post.mockResolvedValue((0, test_helpers_1.createAxiosResponse)());
75
+ // Act
76
+ await localUploader.upload(itemType, fetchedObjects);
77
+ // Assert
78
+ expect(mockedDownloadToLocal).toHaveBeenCalledWith(itemType, fetchedObjects);
79
+ });
80
+ it('should return error when getArtifactUploadUrl fails', async () => {
81
+ // Arrange
82
+ const itemType = 'tasks';
83
+ const fetchedObjects = [{ id: 1 }];
84
+ mockedAxiosClient.get.mockRejectedValueOnce(new Error('Get artifact upload URL failed'));
85
+ // Act
86
+ const result = await uploader.upload(itemType, fetchedObjects);
87
+ // Assert
88
+ expect(result.error).toBeInstanceOf(Error);
89
+ expect(result.artifact).toBeUndefined();
90
+ });
91
+ it('should return error when uploadArtifact fails', async () => {
92
+ // Arrange
93
+ const itemType = 'tasks';
94
+ const fetchedObjects = [{ id: 1 }];
95
+ mockedAxiosClient.get.mockResolvedValueOnce(mockArtifactUploadUrlResponse);
96
+ mockedAxiosClient.post.mockRejectedValueOnce(new Error('Upload artifact failed'));
97
+ // Act
98
+ const result = await uploader.upload(itemType, fetchedObjects);
99
+ // Assert
100
+ expect(result.error).toBeInstanceOf(Error);
101
+ expect(result.artifact).toBeUndefined();
102
+ });
103
+ it('should return error when confirmArtifactUpload fails', async () => {
104
+ // Arrange
105
+ const itemType = 'tasks';
106
+ const fetchedObjects = [{ id: 1 }];
107
+ mockedAxiosClient.get.mockResolvedValueOnce(mockArtifactUploadUrlResponse);
108
+ mockedAxiosClient.post
109
+ .mockResolvedValueOnce((0, test_helpers_1.createAxiosResponse)())
110
+ .mockRejectedValueOnce(new Error('Confirm artifact upload failed'));
111
+ // Act
112
+ const result = await uploader.upload(itemType, fetchedObjects);
113
+ // Assert
114
+ expect(result.error).toBeInstanceOf(Error);
115
+ expect(result.artifact).toBeUndefined();
116
+ });
117
+ it('should return error when compression fails', async () => {
118
+ // Arrange
119
+ const itemType = 'tasks';
120
+ const fetchedObjects = [{ id: 1 }];
121
+ mockedCompressGzip.mockReturnValueOnce({ error: 'Mock error' });
122
+ // Act
123
+ const result = await uploader.upload(itemType, fetchedObjects);
124
+ // Assert
125
+ expect(result.error).toBeInstanceOf(Error);
126
+ expect(result.artifact).toBeUndefined();
127
+ });
128
+ });
129
+ describe(uploader_1.Uploader.prototype.getArtifactUploadUrl.name, () => {
130
+ it('should return artifact upload info when API call succeeds', async () => {
131
+ // Arrange
132
+ const filename = 'test-file.jsonl.gz';
133
+ const fileType = 'application/x-gzip';
134
+ const fileSize = 1024;
135
+ const expectedArtifact = (0, test_helpers_1.createArtifact)();
136
+ mockedAxiosClient.get.mockResolvedValueOnce({
137
+ data: expectedArtifact,
138
+ });
139
+ // Act
140
+ const result = await uploader.getArtifactUploadUrl(filename, fileType, fileSize);
141
+ // Assert
142
+ expect(result.response).toEqual(expectedArtifact);
143
+ expect(mockedAxiosClient.get).toHaveBeenCalledWith(expect.stringContaining('/internal/airdrop.artifacts.upload-url'), expect.objectContaining({
144
+ params: expect.objectContaining({
145
+ file_type: fileType,
146
+ file_name: filename,
147
+ file_size: fileSize,
148
+ }),
149
+ }));
150
+ });
151
+ it('should return undefined when API call fails', async () => {
152
+ // Arrange
153
+ const filename = 'test-file.jsonl.gz';
154
+ const fileType = 'application/x-gzip';
155
+ mockedAxiosClient.get.mockRejectedValueOnce(new Error('Get artifact upload URL failed'));
156
+ // Act
157
+ const result = await uploader.getArtifactUploadUrl(filename, fileType);
158
+ // Assert
159
+ expect(result.response).toBeUndefined();
160
+ expect(result.error).toBeInstanceOf(Error);
161
+ expect(mockedAxiosClient.get).toHaveBeenCalled();
162
+ });
163
+ });
164
+ describe(uploader_1.Uploader.prototype.uploadArtifact.name, () => {
165
+ it('should return response when posting file as multipart form data', async () => {
166
+ // Arrange
167
+ const artifact = (0, test_helpers_1.createArtifact)();
168
+ const file = (0, test_helpers_1.createFileBuffer)();
169
+ const mockResponse = (0, test_helpers_1.createAxiosResponse)();
170
+ mockedAxiosClient.post.mockResolvedValueOnce(mockResponse);
171
+ // Act
172
+ const result = await uploader.uploadArtifact(artifact, file);
173
+ // Assert
174
+ expect(result.response).toBe(mockResponse);
175
+ expect(mockedAxiosClient.post).toHaveBeenCalledWith(artifact.upload_url, expect.any(form_data_1.default), expect.objectContaining({
176
+ headers: expect.any(Object),
177
+ }));
178
+ });
179
+ it('should append form_data fields to the multipart form', async () => {
180
+ // Arrange
181
+ // form_data is typed as array but implementation iterates with for...in
182
+ const formDataFields = { key: 'test-key', 'x-amz-credential': 'cred123' };
183
+ const artifact = {
184
+ artifact_id: 'art_123',
185
+ upload_url: 'https://s3.example.com/upload',
186
+ form_data: formDataFields,
187
+ };
188
+ const file = (0, test_helpers_1.createFileBuffer)();
189
+ const appendSpy = jest.spyOn(form_data_1.default.prototype, 'append');
190
+ mockedAxiosClient.post.mockResolvedValueOnce({ status: 200 });
191
+ // Act
192
+ await uploader.uploadArtifact(artifact, file);
193
+ // Assert
194
+ expect(appendSpy).toHaveBeenCalledWith('key', 'test-key');
195
+ expect(appendSpy).toHaveBeenCalledWith('x-amz-credential', 'cred123');
196
+ expect(appendSpy).toHaveBeenCalledWith('file', file);
197
+ appendSpy.mockRestore();
198
+ });
199
+ it('should return undefined when upload fails', async () => {
200
+ // Arrange
201
+ const artifact = (0, test_helpers_1.createArtifact)();
202
+ const file = (0, test_helpers_1.createFileBuffer)();
203
+ mockedAxiosClient.post.mockRejectedValueOnce(new Error('Upload artifact failed'));
204
+ // Act
205
+ const result = await uploader.uploadArtifact(artifact, file);
206
+ // Assert
207
+ expect(result.response).toBeUndefined();
208
+ expect(result.error).toBeInstanceOf(Error);
209
+ });
210
+ });
211
+ describe(uploader_1.Uploader.prototype.confirmArtifactUpload.name, () => {
212
+ it('should return response when confirming artifact upload', async () => {
213
+ // Arrange
214
+ const artifactId = 'art_123';
215
+ const mockResponse = (0, test_helpers_1.createAxiosResponse)();
216
+ mockedAxiosClient.post.mockResolvedValueOnce(mockResponse);
217
+ // Act
218
+ const result = await uploader.confirmArtifactUpload(artifactId);
219
+ // Assert
220
+ expect(result.response).toBe(mockResponse);
221
+ expect(mockedAxiosClient.post).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
222
+ artifact_id: artifactId,
223
+ }), expect.any(Object));
224
+ });
225
+ it('should return undefined when confirmation fails', async () => {
226
+ // Arrange
227
+ const artifactId = 'art_123';
228
+ mockedAxiosClient.post.mockRejectedValueOnce(new Error('Confirmation failed'));
229
+ // Act
230
+ const result = await uploader.confirmArtifactUpload(artifactId);
231
+ // Assert
232
+ expect(result.response).toBeUndefined();
233
+ expect(result.error).toBeInstanceOf(Error);
234
+ });
235
+ });
236
+ describe(uploader_1.Uploader.prototype.streamArtifact.name, () => {
237
+ it('should return response when streaming file to upload URL', async () => {
238
+ // Arrange
239
+ const artifact = (0, test_helpers_1.createArtifact)();
240
+ const destroyFn = jest.fn();
241
+ const fileStream = (0, test_helpers_1.createFileStream)({
242
+ contentLength: 1024,
243
+ destroyFn,
244
+ });
245
+ const mockResponse = (0, test_helpers_1.createAxiosResponse)();
246
+ mockedAxiosClient.post.mockResolvedValueOnce(mockResponse);
247
+ // Act
248
+ const result = await uploader.streamArtifact(artifact, fileStream);
249
+ // Assert
250
+ expect(result.response).toBe(mockResponse);
251
+ expect(axios_client_internal_1.axiosClient.post).toHaveBeenCalledWith(artifact.upload_url, expect.any(form_data_1.default), expect.objectContaining({
252
+ headers: expect.any(Object),
253
+ maxRedirects: 0,
254
+ validateStatus: expect.any(Function),
255
+ }));
256
+ expect(destroyFn).toHaveBeenCalled();
257
+ });
258
+ it('should append form_data fields to the multipart form', async () => {
259
+ // Arrange
260
+ const formDataFields = { key: 'test-key', 'x-amz-credential': 'cred123' };
261
+ const artifact = {
262
+ artifact_id: 'art_123',
263
+ upload_url: 'https://s3.example.com/upload',
264
+ form_data: formDataFields,
265
+ };
266
+ const fileStream = (0, test_helpers_1.createFileStream)({ contentLength: 1024 });
267
+ const appendSpy = jest.spyOn(form_data_1.default.prototype, 'append');
268
+ mockedAxiosClient.post.mockResolvedValueOnce({ status: 200 });
269
+ // Act
270
+ await uploader.streamArtifact(artifact, fileStream);
271
+ // Assert
272
+ expect(appendSpy).toHaveBeenCalledWith('key', 'test-key');
273
+ expect(appendSpy).toHaveBeenCalledWith('x-amz-credential', 'cred123');
274
+ appendSpy.mockRestore();
275
+ });
276
+ it('should use validateStatus that accepts 2xx and 3xx status codes', async () => {
277
+ // Arrange
278
+ const artifact = (0, test_helpers_1.createArtifact)();
279
+ const fileStream = (0, test_helpers_1.createFileStream)({ contentLength: 1024 });
280
+ mockedAxiosClient.post.mockResolvedValueOnce((0, test_helpers_1.createAxiosResponse)());
281
+ // Act
282
+ await uploader.streamArtifact(artifact, fileStream);
283
+ // Assert
284
+ const callArgs = mockedAxiosClient.post.mock.calls[0];
285
+ const config = callArgs[2];
286
+ const validateStatus = config === null || config === void 0 ? void 0 : config.validateStatus;
287
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(200)).toBe(true);
288
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(201)).toBe(true);
289
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(299)).toBe(true);
290
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(300)).toBe(true);
291
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(301)).toBe(true);
292
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(399)).toBe(true);
293
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(400)).toBe(false);
294
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(404)).toBe(false);
295
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(500)).toBe(false);
296
+ expect(validateStatus === null || validateStatus === void 0 ? void 0 : validateStatus(199)).toBe(false);
297
+ });
298
+ it('should set Content-Length header when missing from file stream', async () => {
299
+ // Arrange
300
+ const artifact = (0, test_helpers_1.createArtifact)();
301
+ const fileStream = (0, test_helpers_1.createFileStream)({ includeContentLength: false });
302
+ mockedAxiosClient.post.mockResolvedValueOnce((0, test_helpers_1.createAxiosResponse)());
303
+ // Act
304
+ await uploader.streamArtifact(artifact, fileStream);
305
+ // Assert
306
+ expect(mockedAxiosClient.post).toHaveBeenCalledWith(artifact.upload_url, expect.any(form_data_1.default), expect.objectContaining({
307
+ headers: expect.objectContaining({
308
+ 'Content-Length': expect.any(Number),
309
+ }),
310
+ }));
311
+ });
312
+ it('should destroy stream and return undefined when streaming fails', async () => {
313
+ // Arrange
314
+ const artifact = (0, test_helpers_1.createArtifact)();
315
+ const destroyFn = jest.fn();
316
+ const fileStream = (0, test_helpers_1.createFileStream)({ destroyFn });
317
+ mockedAxiosClient.post.mockRejectedValueOnce(new Error('Streaming failed'));
318
+ // Act
319
+ const result = await uploader.streamArtifact(artifact, fileStream);
320
+ // Assert
321
+ expect(result.response).toBeUndefined();
322
+ expect(result.error).toBeInstanceOf(Error);
323
+ expect(destroyFn).toHaveBeenCalled();
324
+ });
325
+ });
326
+ describe('Uploader.destroyStream', () => {
327
+ it('should call destroy when stream has destroy method', () => {
328
+ // Arrange
329
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
330
+ const destroyFn = jest.fn();
331
+ const fileStream = {
332
+ data: { destroy: destroyFn },
333
+ };
334
+ // Act
335
+ destroyStream(fileStream);
336
+ // Assert
337
+ expect(destroyFn).toHaveBeenCalled();
338
+ });
339
+ it('should call close when stream has close but no destroy method', () => {
340
+ // Arrange
341
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
342
+ const closeFn = jest.fn();
343
+ const fileStream = {
344
+ data: { close: closeFn },
345
+ };
346
+ // Act
347
+ destroyStream(fileStream);
348
+ // Assert
349
+ expect(closeFn).toHaveBeenCalled();
350
+ });
351
+ it('should prefer destroy over close when both are available', () => {
352
+ // Arrange
353
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
354
+ const destroyFn = jest.fn();
355
+ const closeFn = jest.fn();
356
+ const fileStream = {
357
+ data: { destroy: destroyFn, close: closeFn },
358
+ };
359
+ // Act
360
+ destroyStream(fileStream);
361
+ // Assert
362
+ expect(destroyFn).toHaveBeenCalled();
363
+ expect(closeFn).not.toHaveBeenCalled();
364
+ });
365
+ it('[edge] should handle stream with no destroy or close methods', () => {
366
+ // Arrange
367
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
368
+ const fileStream = {
369
+ data: {},
370
+ };
371
+ // Act & Assert - should not throw
372
+ expect(() => destroyStream(fileStream)).not.toThrow();
373
+ });
374
+ it('[edge] should handle null/undefined data gracefully', () => {
375
+ // Arrange
376
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
377
+ const fileStreamNullData = {
378
+ data: null,
379
+ };
380
+ const fileStreamUndefinedData = {
381
+ data: undefined,
382
+ };
383
+ // Act & Assert - should not throw
384
+ expect(() => destroyStream(fileStreamNullData)).not.toThrow();
385
+ expect(() => destroyStream(fileStreamUndefinedData)).not.toThrow();
386
+ });
387
+ it('[edge] should warn when destroy throws an error', () => {
388
+ // Arrange
389
+ const destroyStream = (0, test_helpers_1.callPrivateMethod)()(uploader, 'destroyStream');
390
+ const destroyFn = jest.fn().mockImplementation(() => {
391
+ throw new Error('Destroy failed');
392
+ });
393
+ const fileStream = {
394
+ data: { destroy: destroyFn },
395
+ };
396
+ // Act & Assert - should not throw
397
+ expect(() => destroyStream(fileStream)).not.toThrow();
398
+ });
399
+ });
400
+ describe('Uploader.getArtifactDownloadUrl', () => {
401
+ it('should return download URL when API call succeeds', async () => {
402
+ // Arrange
403
+ const getArtifactDownloadUrl = (0, test_helpers_1.callPrivateMethod)()(uploader, 'getArtifactDownloadUrl');
404
+ const artifactId = 'art_123';
405
+ const expectedDownloadUrl = 'https://s3.example.com/download/art_123';
406
+ mockedAxiosClient.get.mockResolvedValueOnce({
407
+ data: { download_url: expectedDownloadUrl },
408
+ });
409
+ // Act
410
+ const result = await getArtifactDownloadUrl(artifactId);
411
+ // Assert
412
+ expect(result.response).toBe(expectedDownloadUrl);
413
+ expect(result.error).toBeUndefined();
414
+ expect(mockedAxiosClient.get).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
415
+ params: expect.objectContaining({
416
+ artifact_id: artifactId,
417
+ }),
418
+ }));
419
+ });
420
+ it('should return undefined when API call fails', async () => {
421
+ // Arrange
422
+ const getArtifactDownloadUrl = (0, test_helpers_1.callPrivateMethod)()(uploader, 'getArtifactDownloadUrl');
423
+ const artifactId = 'art_123';
424
+ mockedAxiosClient.get.mockRejectedValueOnce(new Error('API error'));
425
+ // Act
426
+ const result = await getArtifactDownloadUrl(artifactId);
427
+ // Assert
428
+ expect(result.response).toBeUndefined();
429
+ expect(result.error).toBeInstanceOf(Error);
430
+ });
431
+ });
432
+ describe(uploader_1.Uploader.prototype.getAttachmentsFromArtifactId.name, () => {
433
+ it('should return attachments when download and parse succeeds', async () => {
434
+ // Arrange
435
+ const artifactId = 'art_123';
436
+ const mockAttachments = [
437
+ { id: '1', url: 'https://example.com/1', file_name: 'file1.txt' },
438
+ { id: '2', url: 'https://example.com/2', file_name: 'file2.txt' },
439
+ ];
440
+ const gzippedData = zlib_1.default.gzipSync(js_jsonl_1.jsonl.stringify(mockAttachments));
441
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
442
+ mockedAxiosClient.get.mockResolvedValueOnce({
443
+ data: gzippedData,
444
+ });
445
+ // Act
446
+ const result = await uploader.getAttachmentsFromArtifactId({
447
+ artifact: artifactId,
448
+ });
449
+ // Assert
450
+ expect(result.attachments).toEqual(mockAttachments);
451
+ expect(result.error).toBeUndefined();
452
+ });
453
+ it('should return error when getArtifactDownloadUrl fails', async () => {
454
+ // Arrange
455
+ const artifactId = 'art_123';
456
+ (0, test_helpers_1.spyOnPrivateMethod)(uploader, 'getArtifactDownloadUrl').mockResolvedValueOnce({ error: new Error('API error') });
457
+ // Act
458
+ const result = await uploader.getAttachmentsFromArtifactId({
459
+ artifact: artifactId,
460
+ });
461
+ // Assert
462
+ expect(result.error).toBeInstanceOf(Error);
463
+ expect(result.attachments).toBeUndefined();
464
+ });
465
+ it('should return error when downloadArtifact fails', async () => {
466
+ // Arrange
467
+ const artifactId = 'art_123';
468
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
469
+ mockedAxiosClient.get.mockRejectedValueOnce(new Error('Download error'));
470
+ // Act
471
+ const result = await uploader.getAttachmentsFromArtifactId({
472
+ artifact: artifactId,
473
+ });
474
+ // Assert
475
+ expect(result.error).toBeInstanceOf(Error);
476
+ expect(result.attachments).toBeUndefined();
477
+ });
478
+ it('should return error when decompression fails', async () => {
479
+ // Arrange
480
+ const artifactId = 'art_123';
481
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
482
+ mockedAxiosClient.get.mockResolvedValueOnce({
483
+ data: Buffer.from('not valid gzip data'),
484
+ });
485
+ // Act
486
+ const result = await uploader.getAttachmentsFromArtifactId({
487
+ artifact: artifactId,
488
+ });
489
+ // Assert
490
+ expect(result.error).toBeInstanceOf(Error);
491
+ expect(result.attachments).toBeUndefined();
492
+ });
493
+ it('should return error when JSONL parsing fails', async () => {
494
+ // Arrange
495
+ const artifactId = 'art_123';
496
+ const gzippedInvalidJsonl = zlib_1.default.gzipSync('not valid jsonl {{{');
497
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
498
+ mockedAxiosClient.get.mockResolvedValueOnce({
499
+ data: gzippedInvalidJsonl,
500
+ });
501
+ // Act
502
+ const result = await uploader.getAttachmentsFromArtifactId({
503
+ artifact: artifactId,
504
+ });
505
+ // Assert
506
+ expect(result.error).toBeInstanceOf(Error);
507
+ expect(result.attachments).toBeUndefined();
508
+ });
509
+ });
510
+ describe(uploader_1.Uploader.prototype.getJsonObjectByArtifactId.name, () => {
511
+ it('should return parsed data when downloading non-gzipped artifact', async () => {
512
+ // Arrange
513
+ const artifactId = 'art_123';
514
+ const mockData = [
515
+ { id: 1, name: 'Item 1' },
516
+ { id: 2, name: 'Item 2' },
517
+ ];
518
+ const jsonlData = js_jsonl_1.jsonl.stringify(mockData);
519
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
520
+ mockedAxiosClient.get.mockResolvedValueOnce({
521
+ data: Buffer.from(jsonlData),
522
+ });
523
+ // Act
524
+ const result = await uploader.getJsonObjectByArtifactId({
525
+ artifactId,
526
+ isGzipped: false,
527
+ });
528
+ // Assert
529
+ expect(result.response).toEqual(mockData);
530
+ });
531
+ it('should return parsed data when downloading gzipped artifact', async () => {
532
+ // Arrange
533
+ const artifactId = 'art_123';
534
+ const mockData = [
535
+ { id: 1, name: 'Item 1' },
536
+ { id: 2, name: 'Item 2' },
537
+ ];
538
+ const gzippedData = zlib_1.default.gzipSync(js_jsonl_1.jsonl.stringify(mockData));
539
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
540
+ mockedAxiosClient.get.mockResolvedValueOnce({
541
+ data: gzippedData,
542
+ });
543
+ // Act
544
+ const result = await uploader.getJsonObjectByArtifactId({
545
+ artifactId,
546
+ isGzipped: true,
547
+ });
548
+ // Assert
549
+ expect(result.response).toEqual(mockData);
550
+ });
551
+ it('[edge] should return error when getArtifactDownloadUrl fails', async () => {
552
+ // Arrange
553
+ const artifactId = 'art_123';
554
+ (0, test_helpers_1.spyOnPrivateMethod)(uploader, 'getArtifactDownloadUrl').mockResolvedValueOnce({ error: new Error('API error') });
555
+ // Act
556
+ const result = await uploader.getJsonObjectByArtifactId({ artifactId });
557
+ // Assert
558
+ expect(result.response).toBeUndefined();
559
+ expect(result.error).toBeInstanceOf(Error);
560
+ });
561
+ it('[edge] should return error when downloadArtifact fails', async () => {
562
+ // Arrange
563
+ const artifactId = 'art_123';
564
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
565
+ mockedAxiosClient.get.mockRejectedValueOnce(new Error('Download error'));
566
+ // Act
567
+ const result = await uploader.getJsonObjectByArtifactId({ artifactId });
568
+ // Assert
569
+ expect(result.response).toBeUndefined();
570
+ expect(result.error).toBeInstanceOf(Error);
571
+ });
572
+ it('[edge] should return error when decompression fails for gzipped artifact', async () => {
573
+ // Arrange
574
+ const artifactId = 'art_123';
575
+ mockedAxiosClient.get.mockResolvedValueOnce((0, test_helpers_1.createDownloadUrlResponse)());
576
+ mockedAxiosClient.get.mockResolvedValueOnce({
577
+ data: Buffer.from('not valid gzip'),
578
+ });
579
+ // Act
580
+ const result = await uploader.getJsonObjectByArtifactId({
581
+ artifactId,
582
+ isGzipped: true,
583
+ });
584
+ // Assert
585
+ expect(result.response).toBeUndefined();
586
+ expect(result.error).toBeDefined();
587
+ });
588
+ });
589
+ });
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@devrev/airsync-sdk",
3
+ "version": "2.0.0-beta.0",
4
+ "description": "TypeScript SDK for building AirSync snap-ins on the DevRev platform.",
5
+ "type": "commonjs",
6
+ "main": "./dist/index.js",
7
+ "typings": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc -p ./tsconfig.json",
10
+ "prepare": "npm run build",
11
+ "start": "ts-node src/index.ts",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix",
14
+ "test": "jest --forceExit --coverage --runInBand --selectProjects default --",
15
+ "test:backwards-compatibility": "jest --forceExit --runInBand --selectProjects backwards-compatibility --"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/devrev/adaas-sdk.git"
20
+ },
21
+ "publishConfig": {
22
+ "registry": "https://npm.pkg.github.com"
23
+ },
24
+ "keywords": [],
25
+ "author": "devrev",
26
+ "license": "ISC",
27
+ "devDependencies": {
28
+ "@microsoft/api-extractor": "^7.52.11",
29
+ "@microsoft/api-extractor-model": "^7.30.7",
30
+ "@types/express": "^5.0.3",
31
+ "@types/jest": "^29.5.14",
32
+ "@types/node": "^22.18.0",
33
+ "@types/yargs": "^17.0.33",
34
+ "@typescript-eslint/eslint-plugin": "^8.46.0",
35
+ "@typescript-eslint/parser": "^8.46.0",
36
+ "eslint": "9.32.0",
37
+ "eslint-config-prettier": "^9.1.2",
38
+ "eslint-plugin-prettier": "4.0.0",
39
+ "jest": "^29.7.0",
40
+ "jiti": "^2.6.1",
41
+ "prettier": "^2.8.3",
42
+ "ts-jest": "^29.4.4",
43
+ "typescript": "^5.3.3",
44
+ "typescript-eslint": "^8.46.1"
45
+ },
46
+ "dependencies": {
47
+ "@devrev/typescript-sdk": "^1.1.59",
48
+ "axios": "^1.12.1",
49
+ "axios-retry": "^4.5.0",
50
+ "express": "^5.2.1",
51
+ "form-data": "^4.0.4",
52
+ "js-jsonl": "^1.1.1",
53
+ "ts-node": "^10.9.2",
54
+ "yargs": "^17.7.2"
55
+ },
56
+ "files": [
57
+ "dist/**/*",
58
+ "!dist/tests/**/*"
59
+ ]
60
+ }