@trayio/commons 1.8.0 → 1.9.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.
- package/dist/file/NodeFsFileStorage.d.ts +1 -1
- package/dist/file/NodeFsFileStorage.d.ts.map +1 -1
- package/dist/file/NodeFsFileStorage.integration.test.js +1 -1
- package/dist/file/NodeFsFileStorage.js +1 -1
- package/dist/http/HttpClient.abstract.test.d.ts.map +1 -1
- package/dist/http/HttpClient.abstract.test.js +154 -13
- package/dist/string/StringConversions.d.ts +6 -0
- package/dist/string/StringConversions.d.ts.map +1 -0
- package/dist/string/StringConversions.js +16 -0
- package/dist/string/StringConversions.unit.test.d.ts +2 -0
- package/dist/string/StringConversions.unit.test.d.ts.map +1 -0
- package/dist/string/StringConversions.unit.test.js +36 -0
- package/package.json +3 -1
|
@@ -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
|
|
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,
|
|
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(
|
|
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":"
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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: {
|
|
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 @@
|
|
|
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 @@
|
|
|
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.
|
|
3
|
+
"version": "1.9.0",
|
|
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",
|