@trayio/commons 1.8.0 → 1.9.1

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.
@@ -2,7 +2,7 @@ import * as TE from 'fp-ts/lib/TaskEither';
2
2
  import { File, FileStorage } from './File';
3
3
  export declare class NodeFsFileStorage implements FileStorage {
4
4
  private basePath;
5
- constructor(basePath: string);
5
+ constructor(basePath?: string);
6
6
  write(file: File): TE.TaskEither<Error, undefined>;
7
7
  read(key: string): TE.TaskEither<Error, File>;
8
8
  delete(key: string): TE.TaskEither<Error, undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"NodeFsFileStorage.d.ts","sourceRoot":"","sources":["../../src/file/NodeFsFileStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAG3C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAG3C,qBAAa,iBAAkB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAEpC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC;IAalD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC;IAkB7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC;CAWpD"}
1
+ {"version":3,"file":"NodeFsFileStorage.d.ts","sourceRoot":"","sources":["../../src/file/NodeFsFileStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAG3C,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAG3C,qBAAa,iBAAkB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ;gBAAR,QAAQ,GAAE,MAAe;IAE7C,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC;IAalD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC;IAkB7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC;CAWpD"}
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const FileStorage_abstract_test_1 = require("@trayio/commons/src/file/FileStorage.abstract.test");
4
4
  const NodeFsFileStorage_1 = require("./NodeFsFileStorage");
5
- (0, FileStorage_abstract_test_1.fileStorageTest)(new NodeFsFileStorage_1.NodeFsFileStorage('/tmp'));
5
+ (0, FileStorage_abstract_test_1.fileStorageTest)(new NodeFsFileStorage_1.NodeFsFileStorage());
@@ -29,7 +29,7 @@ const fs = __importStar(require("fs/promises"));
29
29
  const function_1 = require("fp-ts/lib/function");
30
30
  const Task_1 = require("../task/Task");
31
31
  class NodeFsFileStorage {
32
- constructor(basePath) {
32
+ constructor(basePath = '/tmp') {
33
33
  this.basePath = basePath;
34
34
  }
35
35
  write(file) {
@@ -1 +1 @@
1
- {"version":3,"file":"HttpClient.abstract.test.d.ts","sourceRoot":"","sources":["../../src/http/HttpClient.abstract.test.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,QA0QpD"}
1
+ {"version":3,"file":"HttpClient.abstract.test.d.ts","sourceRoot":"","sources":["../../src/http/HttpClient.abstract.test.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK1C,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,QAgdpD"}
@@ -31,6 +31,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
31
31
  step((generator = generator.apply(thisArg, _arguments || [])).next());
32
32
  });
33
33
  };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
34
37
  Object.defineProperty(exports, "__esModule", { value: true });
35
38
  exports.httpClientTest = void 0;
36
39
  const E = __importStar(require("fp-ts/Either"));
@@ -39,12 +42,15 @@ const t = __importStar(require("io-ts"));
39
42
  const http_1 = require("http");
40
43
  const util_1 = require("util");
41
44
  const TE = __importStar(require("fp-ts/TaskEither"));
45
+ const formidable_1 = __importDefault(require("formidable"));
42
46
  const Task_1 = require("../task/Task");
43
47
  const JsonSerialization_1 = require("../serialization/JsonSerialization");
44
48
  const TypeCodec_1 = require("../codec/TypeCodec");
45
49
  const Http_1 = require("./Http");
50
+ const NodeFsFileStorage_1 = require("../file/NodeFsFileStorage");
46
51
  function httpClientTest(httpClient) {
47
52
  const serialization = new JsonSerialization_1.JsonSerialization();
53
+ const fileStorage = new NodeFsFileStorage_1.NodeFsFileStorage();
48
54
  const testErrorResponseCodec = TypeCodec_1.TypeCodec.fromDescriptor(t.type({
49
55
  message: t.string,
50
56
  }));
@@ -52,6 +58,13 @@ function httpClientTest(httpClient) {
52
58
  strField: t.string,
53
59
  intField: t.number,
54
60
  }));
61
+ const testMultiPartBodyTypeCodec = TypeCodec_1.TypeCodec.fromDescriptor(t.type({
62
+ fields: t.record(t.string, t.string),
63
+ files: t.record(t.string, t.type({
64
+ key: t.string,
65
+ size: t.number,
66
+ })),
67
+ }));
55
68
  const testInputCodec = TypeCodec_1.TypeCodec.fromDescriptor(t.type({
56
69
  inputField: t.number,
57
70
  }));
@@ -59,18 +72,76 @@ function httpClientTest(httpClient) {
59
72
  const serializedResponse = (0, function_1.pipe)(E.right(responseCodec.encode(response)), E.map((encodedResponse) => serialization.serialize(encodedResponse)), E.getOrElse(() => (new util_1.TextEncoder().encode('Error serializing value').buffer)));
60
73
  httpResponse.end(Buffer.from(serializedResponse));
61
74
  }
62
- function processHttpRequest(requestCodec, responseCodec, httpRequest, httpResponse, process) {
63
- const body = [];
64
- httpRequest.on('data', (chunk) => {
65
- body.push(chunk);
66
- });
67
- httpRequest.on('end', () => {
68
- const buffer = Buffer.concat(body);
69
- const response = (0, function_1.pipe)(serialization.deserialize(buffer), E.chain((encodedRequest) => requestCodec.decode(encodedRequest)), E.map(process), E.map((result) => responseCodec.encode(result)), E.map((encodedResult) => serialization.serialize(encodedResult)), E.getOrElse(() => (new util_1.TextEncoder().encode('Error serializing value').buffer)));
70
- httpResponse.end(Buffer.from(response));
75
+ function processHttpRequest(requestCodec, responseCodec, httpRequest, httpResponse, process, isMultiPartBody = false) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ if (isMultiPartBody) {
78
+ const form = (0, formidable_1.default)({});
79
+ let fields;
80
+ let files;
81
+ try {
82
+ [fields, files] = yield form.parse(httpRequest);
83
+ }
84
+ catch (err) {
85
+ console.error(err);
86
+ return;
87
+ }
88
+ const input = {
89
+ fields: flattenFields(fields),
90
+ files: flattenFiles(files),
91
+ };
92
+ const response = (0, function_1.pipe)(requestCodec.decode(input), E.map(process), E.map((result) => new util_1.TextEncoder().encode(JSON.stringify(responseCodec.encode(result))).buffer), E.getOrElse(() => new util_1.TextEncoder().encode('Error serializing value').buffer));
93
+ httpResponse.end(Buffer.from(response));
94
+ }
95
+ const body = [];
96
+ httpRequest.on('data', (chunk) => {
97
+ body.push(chunk);
98
+ });
99
+ httpRequest.on('end', () => {
100
+ const buffer = Buffer.concat(body);
101
+ const response = (0, function_1.pipe)(serialization.deserialize(buffer), E.chain((encodedRequest) => requestCodec.decode(encodedRequest)), E.map(process), E.map((result) => responseCodec.encode(result)), E.map((encodedResult) => serialization.serialize(encodedResult)), E.getOrElse(() => (new util_1.TextEncoder().encode('Error serializing value').buffer)));
102
+ httpResponse.end(Buffer.from(response));
103
+ });
71
104
  });
72
105
  }
73
- const requestListener = (req, res) => {
106
+ const flattenFields = (fields) => {
107
+ const flattenedFields = {};
108
+ Object.entries(fields).forEach(([key, value]) => {
109
+ if (Array.isArray(value) && value.length > 1) {
110
+ throw new Error(`Field ${key} has more than one value and is current not supported`);
111
+ }
112
+ if (Array.isArray(value)) {
113
+ flattenedFields[key] = value.join(',');
114
+ }
115
+ else {
116
+ flattenedFields[key] = value !== null && value !== void 0 ? value : '';
117
+ }
118
+ });
119
+ return flattenedFields;
120
+ };
121
+ const flattenFiles = (files) => {
122
+ const flattenedFiles = {};
123
+ Object.entries(files)
124
+ .filter(([key, file]) => file !== null)
125
+ .forEach(([key, file]) => {
126
+ if (Array.isArray(file) && file.length > 1) {
127
+ throw new Error(`Field ${key} has more than one file and is currently not supported`);
128
+ }
129
+ if (Array.isArray(file)) {
130
+ const fileContent = convertFormidableFileToTrayFile(file[0]);
131
+ flattenedFiles[key] = fileContent;
132
+ }
133
+ });
134
+ return flattenedFiles;
135
+ };
136
+ const convertFormidableFileToTrayFile = (file) => {
137
+ var _a;
138
+ return ({
139
+ key: (_a = file.originalFilename) !== null && _a !== void 0 ? _a : '',
140
+ size: file.size,
141
+ });
142
+ };
143
+ const requestListener = (req, res) => __awaiter(this, void 0, void 0, function* () {
144
+ var _a;
74
145
  if (req.method === 'GET' && typeof req.headers['error'] === 'string') {
75
146
  const errorCode = +req.headers['error'];
76
147
  res.writeHead(errorCode, {
@@ -90,15 +161,20 @@ function httpClientTest(httpClient) {
90
161
  };
91
162
  sendHttpResponse(res, response, testResponseCodec);
92
163
  }
164
+ else if (req.method === 'POST' &&
165
+ ((_a = req.headers['content-type']) === null || _a === void 0 ? void 0 : _a.includes(Http_1.HttpContentType.MultipartRequestBody))) {
166
+ res.writeHead(200, { [Http_1.HttpHeader.ContentType]: Http_1.HttpContentType.Json });
167
+ yield processHttpRequest(testMultiPartBodyTypeCodec, testMultiPartBodyTypeCodec, req, res, (input) => input, true);
168
+ }
93
169
  else {
94
170
  const customHeader = req.headers['x-custom-header'];
95
171
  res.writeHead(200, { [Http_1.HttpHeader.ContentType]: Http_1.HttpContentType.Json });
96
- processHttpRequest(testInputCodec, testResponseCodec, req, res, (input) => ({
172
+ yield processHttpRequest(testInputCodec, testResponseCodec, req, res, (input) => ({
97
173
  strField: customHeader,
98
174
  intField: input.inputField,
99
175
  }));
100
176
  }
101
- };
177
+ });
102
178
  const host = 'localhost';
103
179
  const port = 8000; // TODO: Get random port
104
180
  const baseUrl = `http://${host}:${port}`;
@@ -140,7 +216,9 @@ function httpClientTest(httpClient) {
140
216
  inputField: 321,
141
217
  };
142
218
  const response = yield (0, function_1.pipe)((0, Task_1.createTaskEitherFromEither)(E.right(testInputCodec.encode(testInput))), TE.map((encoded) => serialization.serialize(encoded)), TE.chain((serializedInput) => httpClient.execute(Http_1.HttpMethod.Post, baseUrl, {
143
- headers: { 'x-custom-header': 'customVal' },
219
+ headers: {
220
+ 'x-custom-header': 'customVal',
221
+ },
144
222
  pathParams: {},
145
223
  queryString: {},
146
224
  body: serializedInput,
@@ -152,6 +230,69 @@ function httpClientTest(httpClient) {
152
230
  const result = (0, function_1.pipe)(response, E.chain((httpResponse) => serialization.deserialize(httpResponse.body)), E.chain((encoded) => testResponseCodec.decode(encoded)));
153
231
  expect(result).toStrictEqual(E.right(testValue));
154
232
  }));
233
+ test('should send a POST request with multipart body', () => __awaiter(this, void 0, void 0, function* () {
234
+ yield fileStorage.delete('file1')();
235
+ yield fileStorage.delete('file2')();
236
+ const file1 = yield fileStorage.write({
237
+ metadata: {
238
+ key: 'file1',
239
+ size: 123,
240
+ },
241
+ content: Buffer.from('file 1 content'),
242
+ })();
243
+ const file2 = yield fileStorage.write({
244
+ metadata: {
245
+ key: 'file2',
246
+ size: 456,
247
+ },
248
+ content: Buffer.from('file 2 content'),
249
+ })();
250
+ if (file1._tag === 'Left') {
251
+ throw file1.left;
252
+ }
253
+ if (file2._tag === 'Left') {
254
+ throw file2.left;
255
+ }
256
+ const testInput = {
257
+ fields: {
258
+ field1: 'field1',
259
+ field2: 'field2',
260
+ },
261
+ files: {
262
+ file1: {
263
+ key: 'file1',
264
+ size: 123,
265
+ },
266
+ file2: {
267
+ key: 'file2',
268
+ size: 456,
269
+ },
270
+ },
271
+ };
272
+ const response = yield httpClient.execute(Http_1.HttpMethod.Post, baseUrl, {
273
+ headers: {
274
+ 'x-custom-header': 'customVal',
275
+ 'content-type': Http_1.HttpContentType.MultipartRequestBody,
276
+ },
277
+ pathParams: {},
278
+ queryString: {},
279
+ body: testInput,
280
+ })();
281
+ const result = (0, function_1.pipe)(response, E.chain((httpResponse) => serialization.deserialize(httpResponse.body)), E.chain((res) => testMultiPartBodyTypeCodec.decode(res)));
282
+ expect(result).toEqual(E.right(expect.objectContaining({
283
+ fields: testInput.fields,
284
+ files: expect.objectContaining({
285
+ file1: expect.objectContaining({
286
+ key: 'file1',
287
+ size: expect.any(Number),
288
+ }),
289
+ file2: expect.objectContaining({
290
+ key: 'file2',
291
+ size: expect.any(Number),
292
+ }),
293
+ }),
294
+ })));
295
+ }));
155
296
  test('should parse an internal error response', () => __awaiter(this, void 0, void 0, function* () {
156
297
  const response = yield httpClient.execute(Http_1.HttpMethod.Get, baseUrl, {
157
298
  headers: { error: '500' },
@@ -0,0 +1,6 @@
1
+ export interface StringConversionsInterface {
2
+ toBoolean: (str: string) => boolean;
3
+ toNumber: (str: string) => number;
4
+ }
5
+ export declare const StringConversions: StringConversionsInterface;
6
+ //# sourceMappingURL=StringConversions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StringConversions.d.ts","sourceRoot":"","sources":["../../src/string/StringConversions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAClC;AAED,eAAO,MAAM,iBAAiB,EAAE,0BAY/B,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StringConversions = void 0;
4
+ exports.StringConversions = {
5
+ toBoolean: (string) => {
6
+ switch (string.toLowerCase().trim()) {
7
+ case 'true':
8
+ return true;
9
+ case 'false':
10
+ return false;
11
+ default:
12
+ return Boolean(string);
13
+ }
14
+ },
15
+ toNumber: (string) => parseInt(string, 10),
16
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=StringConversions.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StringConversions.unit.test.d.ts","sourceRoot":"","sources":["../../src/string/StringConversions.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const StringConversions_1 = require("./StringConversions");
4
+ describe('StringConversions', () => {
5
+ describe('toBoolean', () => {
6
+ it('should convert "true" string to boolean true', () => {
7
+ expect(StringConversions_1.StringConversions.toBoolean('true')).toBe(true);
8
+ });
9
+ it('should convert "false" string to boolean false', () => {
10
+ expect(StringConversions_1.StringConversions.toBoolean('false')).toBe(false);
11
+ });
12
+ it('should convert non-boolean string to boolean', () => {
13
+ expect(StringConversions_1.StringConversions.toBoolean('hello')).toBe(true);
14
+ expect(StringConversions_1.StringConversions.toBoolean('')).toBe(false);
15
+ });
16
+ it('should handle case insensitivity', () => {
17
+ expect(StringConversions_1.StringConversions.toBoolean('TrUe')).toBe(true);
18
+ expect(StringConversions_1.StringConversions.toBoolean('FaLsE')).toBe(false);
19
+ });
20
+ it('should handle leading/trailing spaces', () => {
21
+ expect(StringConversions_1.StringConversions.toBoolean(' true ')).toBe(true);
22
+ expect(StringConversions_1.StringConversions.toBoolean(' false ')).toBe(false);
23
+ });
24
+ });
25
+ describe('toNumber', () => {
26
+ it('should convert string to number', () => {
27
+ expect(StringConversions_1.StringConversions.toNumber('123')).toBe(123);
28
+ });
29
+ it('should handle non-numeric string', () => {
30
+ expect(StringConversions_1.StringConversions.toNumber('hello')).toBeNaN();
31
+ });
32
+ it('should handle empty string', () => {
33
+ expect(StringConversions_1.StringConversions.toNumber('')).toBeNaN();
34
+ });
35
+ });
36
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trayio/commons",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Extensions to the standard/core libraries and basic features",
5
5
  "exports": {
6
6
  "./*": "./dist/*.js"
@@ -14,8 +14,10 @@
14
14
  "access": "public"
15
15
  },
16
16
  "dependencies": {
17
+ "@types/formidable": "3.4.4",
17
18
  "@types/node": "18.11.9",
18
19
  "@types/uuid": "9.0.2",
20
+ "formidable": "^3.5.1",
19
21
  "fp-ts": "2.12.3",
20
22
  "io-ts": "2.2.19",
21
23
  "io-ts-types": "0.5.19",