@trayio/cdk-runtime 2.9.0 → 2.11.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.
@@ -21,6 +21,7 @@ export declare class HttpOperationExecution<AUTH extends OperationHandlerAuth<un
21
21
  private parseBodyAsJson;
22
22
  private parseEmptyBody;
23
23
  private parseBodyAsFile;
24
- private parseeBodyAsText;
24
+ private parseBodyAsText;
25
+ private generateUploadOptions;
25
26
  }
26
27
  //# sourceMappingURL=HttpOperationExecution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HttpOperationExecution.d.ts","sourceRoot":"","sources":["../../../src/connector/operation/HttpOperationExecution.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EAGtB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAEN,oBAAoB,EAKpB,MAAM,0DAA0D,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAe7D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI1D,qBAAa,sBAAsB,CAClC,IAAI,SAAS,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,EACnD,EAAE,EACF,GAAG,CACF,YAAW,kBAAkB,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC;IAE7C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,WAAW,CAAc;gBAGhC,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,oBAAoB,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC;IAQvC,OAAO,CACZ,GAAG,EAAE,uBAAuB,CAAC,IAAI,CAAC,EAClC,KAAK,EAAE,EAAE,GACP,OAAO,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAiDvC,OAAO,CAAC,6BAA6B;IA2BrC,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,oBAAoB;IAc5B,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IA2CvB,OAAO,CAAC,gBAAgB;CAexB"}
1
+ {"version":3,"file":"HttpOperationExecution.d.ts","sourceRoot":"","sources":["../../../src/connector/operation/HttpOperationExecution.ts"],"names":[],"mappings":"AASA,OAAO,EACN,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EAGtB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAGN,oBAAoB,EAKpB,MAAM,0DAA0D,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAW7D,OAAO,EAAQ,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO1D,qBAAa,sBAAsB,CAClC,IAAI,SAAS,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,EACnD,EAAE,EACF,GAAG,CACF,YAAW,kBAAkB,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC;IAE7C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,WAAW,CAAc;gBAGhC,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,oBAAoB,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC;IAQvC,OAAO,CACZ,GAAG,EAAE,uBAAuB,CAAC,IAAI,CAAC,EAClC,KAAK,EAAE,EAAE,GACP,OAAO,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAiDvC,OAAO,CAAC,6BAA6B;IA+BrC,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,oBAAoB;IAyD5B,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,oBAAoB;IAc5B,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IA2CvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,qBAAqB;CAwC7B"}
@@ -33,17 +33,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
35
  exports.HttpOperationExecution = void 0;
36
- const OperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandler");
37
- const HttpOperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/HttpOperationHandler");
38
- const Http_1 = require("@trayio/commons/http/Http");
39
- const uuid_1 = require("uuid");
40
- const JsonSerialization_1 = require("@trayio/commons/serialization/JsonSerialization");
41
36
  const TE = __importStar(require("fp-ts/TaskEither"));
42
37
  const E = __importStar(require("fp-ts/Either"));
43
38
  const O = __importStar(require("fp-ts/Option"));
44
39
  const function_1 = require("fp-ts/function");
40
+ const Array_1 = require("fp-ts/Array");
41
+ const uuid_1 = require("uuid");
42
+ const OperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandler");
43
+ const HttpOperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/HttpOperationHandler");
44
+ const Http_1 = require("@trayio/commons/http/Http");
45
+ const JsonSerialization_1 = require("@trayio/commons/serialization/JsonSerialization");
45
46
  const BufferExtensions_1 = require("@trayio/commons/buffer/BufferExtensions");
46
47
  const mime = require('mime');
48
+ const { AWS_LAMBDA_FUNCTION_MEMORY_SIZE, CONNECTOR_MAX_ALLOCATED_RAM_MB } = process.env;
47
49
  class HttpOperationExecution {
48
50
  constructor(httpClient, fileStorage, handler) {
49
51
  this.jsonSerialization = new JsonSerialization_1.JsonSerialization();
@@ -94,7 +96,40 @@ class HttpOperationExecution {
94
96
  return TE.right(BufferExtensions_1.BufferExtensions.arrayBufferToReadable(serializedBody));
95
97
  }
96
98
  serializeAsMultipart(body) {
97
- throw new Error('unimplemented');
99
+ const fileStreamsFromReferencesTasks = Object.keys(body.files).map((fileKey) => {
100
+ const fileRef = body.files[fileKey];
101
+ return (0, function_1.pipe)(this.httpClient.execute(Http_1.HttpMethod.Get, fileRef.url, {
102
+ headers: {
103
+ [Http_1.HttpHeader.ContentType]: Http_1.HttpContentType.OctetStream,
104
+ },
105
+ pathParams: {},
106
+ queryString: {},
107
+ body: BufferExtensions_1.BufferExtensions.arrayBufferToReadable(new ArrayBuffer(0)),
108
+ }), TE.chain((response) => {
109
+ if (response.statusCode >= 300) {
110
+ return TE.left(Error('error downloading file from source'));
111
+ }
112
+ const parsedSized = Number.parseInt(response.headers.ContentLength ||
113
+ response.headers['content-length'], 10);
114
+ return TE.right({
115
+ key: fileKey,
116
+ metadata: {
117
+ name: fileRef.name,
118
+ size: Number.isNaN(parsedSized) ? undefined : parsedSized,
119
+ contentType: fileRef.mime_type,
120
+ },
121
+ content: response.body,
122
+ });
123
+ }));
124
+ });
125
+ const filesTask = (0, Array_1.sequence)(TE.MonadTask)(fileStreamsFromReferencesTasks);
126
+ return (0, function_1.pipe)(filesTask, TE.map((files) => files.reduce((acc, file) => {
127
+ acc[file.key] = file;
128
+ return acc;
129
+ }, {})), TE.map((multipartFiles) => ({
130
+ fields: body.fields,
131
+ files: multipartFiles,
132
+ })));
98
133
  }
99
134
  serializeAsFile(operationRequest) {
100
135
  const downloadFilefromReference = this.httpClient.execute(Http_1.HttpMethod.Get, operationRequest.body.url, {
@@ -131,7 +166,7 @@ class HttpOperationExecution {
131
166
  case Http_1.HttpContentType.MultipartRequestBody:
132
167
  return TE.right(OperationHandler_1.OperationHandlerResult.failure(OperationHandler_1.OperationHandlerError.connectorError(`Invalid Response Type ${contentType}`)));
133
168
  case Http_1.HttpContentType.Text:
134
- return this.parseeBodyAsText(operationResponse);
169
+ return this.parseBodyAsText(operationResponse);
135
170
  default:
136
171
  return this.parseBodyAsJson(operationResponse);
137
172
  }
@@ -148,18 +183,19 @@ class HttpOperationExecution {
148
183
  return TE.right(operationResponse.responseParser(undefined));
149
184
  }
150
185
  parseBodyAsFile(operationResponse) {
151
- var _a;
152
186
  const contentType = (operationResponse.response.headers[Http_1.HttpHeader.ContentType.toLowerCase()]);
153
187
  const fileExtension = mime.getExtension(contentType);
188
+ const contentLength = (operationResponse.response.headers[Http_1.HttpHeader.ContentLength.toLowerCase()]);
189
+ const uploadOptions = this.generateUploadOptions(contentLength);
154
190
  return (0, function_1.pipe)(this.fileStorage.write({
155
191
  content: operationResponse.response.body,
156
192
  key: fileExtension ? `${(0, uuid_1.v4)()}.${fileExtension}` : (0, uuid_1.v4)(),
157
193
  metadata: {
158
194
  name: (0, uuid_1.v4)(),
159
- size: parseFloat((_a = (operationResponse.response.headers[Http_1.HttpHeader.ContentLength.toLowerCase()])) !== null && _a !== void 0 ? _a : undefined),
195
+ size: parseFloat(contentLength !== null && contentLength !== void 0 ? contentLength : undefined),
160
196
  contentType,
161
197
  },
162
- }, O.none), TE.chain((file) => (0, function_1.pipe)(this.fileStorage.getSharedUrl(file.key), TE.map((sharedUrl) => {
198
+ }, O.some(uploadOptions)), TE.chain((file) => (0, function_1.pipe)(this.fileStorage.getSharedUrl(file.key), TE.map((sharedUrl) => {
163
199
  var _a, _b;
164
200
  return operationResponse.responseParser({
165
201
  name: file.metadata.name,
@@ -169,12 +205,43 @@ class HttpOperationExecution {
169
205
  });
170
206
  }))));
171
207
  }
172
- parseeBodyAsText(operationResponse) {
208
+ parseBodyAsText(operationResponse) {
173
209
  const arrayBufferTE = BufferExtensions_1.BufferExtensions.readableToArrayBuffer(operationResponse.response.body);
174
210
  return (0, function_1.pipe)(arrayBufferTE, TE.chain((arrayBuffer) => {
175
211
  const deserializeBody = new TextDecoder().decode(arrayBuffer);
176
212
  return TE.right(operationResponse.responseParser(deserializeBody));
177
213
  }));
178
214
  }
215
+ // This method is used to respect lambda memory limits as per the old falfel implementation
216
+ generateUploadOptions(contentLength) {
217
+ const allocatedRam = AWS_LAMBDA_FUNCTION_MEMORY_SIZE || CONNECTOR_MAX_ALLOCATED_RAM_MB;
218
+ const availableRAM = allocatedRam ? parseInt(allocatedRam, 10) : 128; // 128MB default
219
+ const uploadMinSize = 1024 * 1024 * 8, // 8MB
220
+ maxRam = 1024 * 1024 * availableRAM;
221
+ /*
222
+ 128 / 16 = 8 (which is UPLOAD_MIN_SIZE)
223
+ TARGET_SIZE should either be 8MB (since default assumed RAM is 128MB) or
224
+ 1/16 of of the availableRAM, which ever is greater. The TARGET_SIZE, set
225
+ as `partSize`, will always be used in conjuction with `queueSize`.
226
+ */
227
+ const sixteenthOfRAM = Math.floor(maxRam / 16);
228
+ const targetSize = sixteenthOfRAM < uploadMinSize ? uploadMinSize : sixteenthOfRAM;
229
+ const uploadOptions = {
230
+ // 8MB (minimum) * 4 parallel = upto 32MB/s RAM usage
231
+ partSize: targetSize,
232
+ queueSize: 4,
233
+ acl: 'bucket-owner-full-control',
234
+ };
235
+ if (!contentLength) {
236
+ if (CONNECTOR_MAX_ALLOCATED_RAM_MB) {
237
+ uploadOptions.queueSize = 12;
238
+ }
239
+ }
240
+ else if (parseInt(contentLength, 10) > targetSize &&
241
+ parseInt(contentLength, 10) > targetSize * 8) {
242
+ uploadOptions.queueSize = 12;
243
+ }
244
+ return uploadOptions;
245
+ }
179
246
  }
180
247
  exports.HttpOperationExecution = HttpOperationExecution;
@@ -1 +1 @@
1
- {"version":3,"file":"OperationExecutionGateway.unit.test.d.ts","sourceRoot":"","sources":["../../../src/connector/operation/OperationExecutionGateway.unit.test.ts"],"names":[],"mappings":"AAOA,OAAO,8BAA8B,CAAC"}
1
+ {"version":3,"file":"OperationExecutionGateway.unit.test.d.ts","sourceRoot":"","sources":["../../../src/connector/operation/OperationExecutionGateway.unit.test.ts"],"names":[],"mappings":"AAmBA,OAAO,8BAA8B,CAAC"}
@@ -23,18 +23,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- const OperationHandlerSetup_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandlerSetup");
27
- const OperationHandlerTest_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandlerTest");
28
- const OperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandler");
29
- require("./OperationHandlerTestRunner");
26
+ const stream_1 = require("stream");
30
27
  const T = __importStar(require("fp-ts/Task"));
31
28
  const TE = __importStar(require("fp-ts/TaskEither"));
32
29
  const function_1 = require("fp-ts/function");
30
+ const OperationHandlerSetup_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandlerSetup");
31
+ const OperationHandlerTest_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandlerTest");
32
+ const OperationHandler_1 = require("@trayio/cdk-dsl/connector/operation/OperationHandler");
33
33
  const Http_1 = require("@trayio/commons/http/Http");
34
- const stream_1 = require("stream");
35
34
  const ExpressHttpServer_1 = require("@trayio/express/http/ExpressHttpServer");
36
35
  const ExpressHttpController_1 = require("@trayio/express/http/ExpressHttpController");
37
36
  const NodeFsFileStorage_1 = require("@trayio/commons/file/NodeFsFileStorage");
37
+ require("./OperationHandlerTestRunner");
38
38
  const fileStorage = new NodeFsFileStorage_1.NodeFsFileStorage('/tmp');
39
39
  class TestControllerHttp {
40
40
  constructor() {
@@ -80,6 +80,26 @@ class TestControllerHttp {
80
80
  })));
81
81
  },
82
82
  },
83
+ {
84
+ path: `/multipart-upload`,
85
+ method: Http_1.HttpMethod.Post,
86
+ execute: (request) => {
87
+ const { body } = request;
88
+ expect(body).toEqual(expect.objectContaining({
89
+ fields: expect.objectContaining({ test: 'Hello world!' }),
90
+ files: expect.objectContaining({
91
+ 'test-image': expect.any(Object),
92
+ }),
93
+ }));
94
+ return T.of({
95
+ body: stream_1.Readable.from(JSON.stringify({
96
+ success: true,
97
+ })),
98
+ headers: {},
99
+ statusCode: 200,
100
+ });
101
+ },
102
+ },
83
103
  ];
84
104
  }
85
105
  }
@@ -152,6 +172,37 @@ describe('OperationExecutionGateway', () => {
152
172
  .finallyDoNothing())
153
173
  .nothingAfterAll());
154
174
  });
175
+ describe('post multipart (with file)', () => {
176
+ const multipartOperation = OperationHandlerSetup_1.OperationHandlerSetup.configureHandler((handler) => handler.usingHttp((http) => http
177
+ .post('http://localhost:3000/multipart-upload')
178
+ .handleRequest((_ctx, _input, request) => request.withBodyAsMultipart({
179
+ fields: {
180
+ test: 'Hello world!',
181
+ },
182
+ files: {
183
+ 'test-image': {
184
+ name: 'testImage.png',
185
+ url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Wikipedia-logo-transparent.png/526px-Wikipedia-logo-transparent.png',
186
+ mime_type: 'image/png',
187
+ expires: 0,
188
+ },
189
+ },
190
+ }))
191
+ .handleResponse((_ctx, _input, response) => response.parseWithBodyAsJson())));
192
+ OperationHandlerTest_1.OperationHandlerTestSetup.configureHandlerTest(multipartOperation, (handlerTest) => handlerTest
193
+ .usingHandlerContext('test')
194
+ .nothingBeforeAll()
195
+ .testCase('should send a multipart body (both data and file)', (testCase) => testCase
196
+ .usingHandlerContext('test')
197
+ .givenNothing()
198
+ .when(() => ({}))
199
+ .then(({ output }) => {
200
+ const outputValue = OperationHandler_1.OperationHandlerResult.getSuccessfulValueOrFail(output);
201
+ expect(outputValue.success).toEqual(true);
202
+ })
203
+ .finallyDoNothing())
204
+ .nothingAfterAll());
205
+ });
155
206
  describe('upload an image', () => {
156
207
  const uploadImageOperation = OperationHandlerSetup_1.OperationHandlerSetup.configureHandler((handler) => handler.usingHttp((http) => http
157
208
  .post('http://localhost:3000/image-upload')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trayio/cdk-runtime",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "A Runtime that executes connector operations defined using the CDK DSL",
5
5
  "exports": {
6
6
  "./*": "./dist/*.js"
@@ -14,10 +14,10 @@
14
14
  "node": ">=18.x"
15
15
  },
16
16
  "dependencies": {
17
- "@trayio/axios": "2.9.0",
18
- "@trayio/cdk-dsl": "2.9.0",
19
- "@trayio/express": "2.9.0",
20
- "@trayio/winston": "2.9.0",
17
+ "@trayio/axios": "2.11.0",
18
+ "@trayio/cdk-dsl": "2.11.0",
19
+ "@trayio/express": "2.11.0",
20
+ "@trayio/winston": "2.11.0",
21
21
  "mime": "3.0.0",
22
22
  "uuid": "9.0.0"
23
23
  },