@things-factory/integration-base 6.0.64 → 6.0.68
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-server/engine/task/csv-readline.js +66 -0
- package/dist-server/engine/task/csv-readline.js.map +1 -0
- package/dist-server/engine/task/data-mapper.js +37 -0
- package/dist-server/engine/task/data-mapper.js.map +1 -0
- package/dist-server/engine/task/empty-check.js +3 -2
- package/dist-server/engine/task/empty-check.js.map +1 -1
- package/dist-server/engine/task/graphql-query.js.map +1 -1
- package/dist-server/engine/task/index.js +2 -0
- package/dist-server/engine/task/index.js.map +1 -1
- package/dist-server/engine/task/map-data.js +37 -0
- package/dist-server/engine/task/map-data.js.map +1 -0
- package/dist-server/routers/scenario-schedule-callback-router.js +50 -0
- package/dist-server/routers/scenario-schedule-callback-router.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/helps/integration/task/csv-readline.md +10 -0
- package/helps/integration/task/data-mapper.md +14 -0
- package/helps/integration/task/empty-check.md +10 -0
- package/package.json +10 -6
- package/server/engine/task/csv-readline.ts +83 -0
- package/server/engine/task/data-mapper.ts +44 -0
- package/server/engine/task/empty-check.ts +3 -2
- package/server/engine/task/graphql-query.ts +1 -0
- package/server/engine/task/index.ts +2 -0
- package/translations/en.json +1 -1
- package/translations/ko.json +1 -1
- package/translations/ms.json +1 -1
- package/translations/zh.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
5
|
+
const task_registry_1 = require("../task-registry");
|
|
6
|
+
async function CsvReadline(step, context) {
|
|
7
|
+
var { params: { path } } = step;
|
|
8
|
+
var { logger, __csv_resources, closures } = context;
|
|
9
|
+
if (!path) {
|
|
10
|
+
throw new Error('csv filepath should be given');
|
|
11
|
+
}
|
|
12
|
+
if (!__csv_resources) {
|
|
13
|
+
__csv_resources = context.__csv_resources = [];
|
|
14
|
+
}
|
|
15
|
+
if (!__csv_resources[path]) {
|
|
16
|
+
const stream = fs_1.default.createReadStream(path);
|
|
17
|
+
if (!stream) {
|
|
18
|
+
throw new Error('csv file loading failed');
|
|
19
|
+
}
|
|
20
|
+
const readline = require('readline').createInterface({
|
|
21
|
+
input: stream,
|
|
22
|
+
crlfDelay: Infinity
|
|
23
|
+
});
|
|
24
|
+
const iterator = readline[Symbol.asyncIterator]();
|
|
25
|
+
const { value: header } = await iterator.next();
|
|
26
|
+
__csv_resources[path] = {
|
|
27
|
+
stream,
|
|
28
|
+
iterator,
|
|
29
|
+
header: header ? header.split(',').map(h => h.replace(/"/g, '')) : []
|
|
30
|
+
};
|
|
31
|
+
closures.push(async () => {
|
|
32
|
+
try {
|
|
33
|
+
stream && (await stream.close());
|
|
34
|
+
logger.info(`csv file(${path}) is released before ending the scenario.`);
|
|
35
|
+
delete __csv_resources[path];
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
logger.error(e);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const { iterator, header } = __csv_resources[path];
|
|
43
|
+
const data = {};
|
|
44
|
+
const { value: line } = await iterator.next();
|
|
45
|
+
if (!line) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const lineData = line.split(',');
|
|
49
|
+
for (let i = 0; i < header.length; i++) {
|
|
50
|
+
data[header[i]] = lineData[i];
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
data
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
CsvReadline.parameterSpec = [
|
|
57
|
+
{
|
|
58
|
+
type: 'string',
|
|
59
|
+
name: 'path',
|
|
60
|
+
label: 'path'
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
CsvReadline.connectorFree = true;
|
|
64
|
+
CsvReadline.help = 'integration/task/csv-readline';
|
|
65
|
+
task_registry_1.TaskRegistry.registerTaskHandler('csv-readline', CsvReadline);
|
|
66
|
+
//# sourceMappingURL=csv-readline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv-readline.js","sourceRoot":"","sources":["../../../server/engine/task/csv-readline.ts"],"names":[],"mappings":";;;AAAA,oDAAmB;AAEnB,oDAA+C;AAE/C,KAAK,UAAU,WAAW,CAAC,IAAI,EAAE,OAAO;IACtC,IAAI,EACF,MAAM,EAAE,EAAE,IAAI,EAAE,EACjB,GAAG,IAAI,CAAA;IAER,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAEnD,IAAI,CAAC,IAAI,EAAE;QACT,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;KAChD;IAED,IAAI,CAAC,eAAe,EAAE;QACpB,eAAe,GAAG,OAAO,CAAC,eAAe,GAAG,EAAqB,CAAA;KAClE;IAED,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE;QAC1B,MAAM,MAAM,GAAG,YAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAExC,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;SAC3C;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC;YACnD,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAA;QAEF,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAA;QACjD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAE/C,eAAe,CAAC,IAAI,CAAC,GAAG;YACtB,MAAM;YACN,QAAQ;YACR,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;SACtE,CAAA;QAED,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;YACvB,IAAI;gBACF,MAAM,IAAI,CAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;gBAChC,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,2CAA2C,CAAC,CAAA;gBACxE,OAAO,eAAe,CAAC,IAAI,CAAC,CAAA;aAC7B;YAAC,OAAO,CAAC,EAAE;gBACV,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;aAChB;QACH,CAAC,CAAC,CAAA;KACH;IAED,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IAElD,MAAM,IAAI,GAAG,EAA+B,CAAA;IAC5C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAE7C,IAAI,CAAC,IAAI,EAAE;QACT,OAAM;KACP;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAEhC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACtC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;KAC9B;IAED,OAAO;QACL,IAAI;KACL,CAAA;AACH,CAAC;AAED,WAAW,CAAC,aAAa,GAAG;IAC1B;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;KACd;CACF,CAAA;AAED,WAAW,CAAC,aAAa,GAAG,IAAI,CAAA;AAChC,WAAW,CAAC,IAAI,GAAG,+BAA+B,CAAA;AAElD,4BAAY,CAAC,mBAAmB,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA","sourcesContent":["import fs from 'fs'\n\nimport { TaskRegistry } from '../task-registry'\n\nasync function CsvReadline(step, context) {\n var {\n params: { path }\n } = step\n\n var { logger, __csv_resources, closures } = context\n\n if (!path) {\n throw new Error('csv filepath should be given')\n }\n\n if (!__csv_resources) {\n __csv_resources = context.__csv_resources = [] as fs.ReadStream[]\n }\n\n if (!__csv_resources[path]) {\n const stream = fs.createReadStream(path)\n\n if (!stream) {\n throw new Error('csv file loading failed')\n }\n\n const readline = require('readline').createInterface({\n input: stream,\n crlfDelay: Infinity\n })\n\n const iterator = readline[Symbol.asyncIterator]()\n const { value: header } = await iterator.next()\n\n __csv_resources[path] = {\n stream,\n iterator,\n header: header ? header.split(',').map(h => h.replace(/\"/g, '')) : []\n }\n\n closures.push(async () => {\n try {\n stream && (await stream.close())\n logger.info(`csv file(${path}) is released before ending the scenario.`)\n delete __csv_resources[path]\n } catch (e) {\n logger.error(e)\n }\n })\n }\n\n const { iterator, header } = __csv_resources[path]\n\n const data = {} as { [key: string]: string }\n const { value: line } = await iterator.next()\n\n if (!line) {\n return\n }\n\n const lineData = line.split(',')\n\n for (let i = 0; i < header.length; i++) {\n data[header[i]] = lineData[i]\n }\n\n return {\n data\n }\n}\n\nCsvReadline.parameterSpec = [\n {\n type: 'string',\n name: 'path',\n label: 'path'\n }\n]\n\nCsvReadline.connectorFree = true\nCsvReadline.help = 'integration/task/csv-readline'\n\nTaskRegistry.registerTaskHandler('csv-readline', CsvReadline)\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@things-factory/utils");
|
|
4
|
+
const task_registry_1 = require("../task-registry");
|
|
5
|
+
function mapping(rule, input) {
|
|
6
|
+
if (Array.isArray(input)) {
|
|
7
|
+
return input.map(i => mapping(rule, i));
|
|
8
|
+
}
|
|
9
|
+
return Object.keys(rule).reduce((sum, key) => {
|
|
10
|
+
sum[key] = input[rule[key]];
|
|
11
|
+
return sum;
|
|
12
|
+
}, {});
|
|
13
|
+
}
|
|
14
|
+
async function DataMapper(step, { logger, data }) {
|
|
15
|
+
var { params: { accessor, mappingRule } } = step;
|
|
16
|
+
const input = (0, utils_1.access)(accessor, data);
|
|
17
|
+
const output = mapping(mappingRule || {}, input);
|
|
18
|
+
return {
|
|
19
|
+
data: output
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
DataMapper.parameterSpec = [
|
|
23
|
+
{
|
|
24
|
+
type: 'scenario-step-input',
|
|
25
|
+
name: 'accessor',
|
|
26
|
+
label: 'accessor'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'key-values',
|
|
30
|
+
name: 'mappingRule',
|
|
31
|
+
label: 'mapping-rule'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
DataMapper.connectorFree = true;
|
|
35
|
+
DataMapper.help = 'integration/task/data-mapper';
|
|
36
|
+
task_registry_1.TaskRegistry.registerTaskHandler('data-mapper', DataMapper);
|
|
37
|
+
//# sourceMappingURL=data-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-mapper.js","sourceRoot":"","sources":["../../../server/engine/task/data-mapper.ts"],"names":[],"mappings":";;AAAA,iDAA8C;AAC9C,oDAA+C;AAE/C,SAAS,OAAO,CAAC,IAAI,EAAE,KAAK;IAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACxB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;KACxC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3B,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,EAAS,CAAC,CAAA;AACf,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9C,IAAI,EACF,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,EAClC,GAAG,IAAI,CAAA;IAER,MAAM,KAAK,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,EAAE,KAAK,CAAC,CAAA;IAEhD,OAAO;QACL,IAAI,EAAE,MAAM;KACb,CAAA;AACH,CAAC;AAED,UAAU,CAAC,aAAa,GAAG;IACzB;QACE,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;KAClB;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,cAAc;KACtB;CACF,CAAA;AAED,UAAU,CAAC,aAAa,GAAG,IAAI,CAAA;AAC/B,UAAU,CAAC,IAAI,GAAG,8BAA8B,CAAA;AAEhD,4BAAY,CAAC,mBAAmB,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA","sourcesContent":["import { access } from '@things-factory/utils'\nimport { TaskRegistry } from '../task-registry'\n\nfunction mapping(rule, input) {\n if (Array.isArray(input)) {\n return input.map(i => mapping(rule, i))\n }\n\n return Object.keys(rule).reduce((sum, key) => {\n sum[key] = input[rule[key]]\n return sum\n }, {} as any)\n}\n\nasync function DataMapper(step, { logger, data }) {\n var {\n params: { accessor, mappingRule }\n } = step\n\n const input = access(accessor, data)\n const output = mapping(mappingRule || {}, input)\n\n return {\n data: output\n }\n}\n\nDataMapper.parameterSpec = [\n {\n type: 'scenario-step-input',\n name: 'accessor',\n label: 'accessor'\n },\n {\n type: 'key-values',\n name: 'mappingRule',\n label: 'mapping-rule'\n }\n]\n\nDataMapper.connectorFree = true\nDataMapper.help = 'integration/task/data-mapper'\n\nTaskRegistry.registerTaskHandler('data-mapper', DataMapper)\n"]}
|
|
@@ -5,7 +5,7 @@ const utils_1 = require("@things-factory/utils");
|
|
|
5
5
|
async function EmptyCheck(step, { logger, data }) {
|
|
6
6
|
var { params: { accessor, goto } } = step;
|
|
7
7
|
const value = (0, utils_1.access)(accessor, data);
|
|
8
|
-
if ([null, undefined, NaN, ''].
|
|
8
|
+
if ([null, undefined, NaN, ''].indexOf(value) !== -1) {
|
|
9
9
|
return {
|
|
10
10
|
next: goto
|
|
11
11
|
};
|
|
@@ -19,11 +19,12 @@ EmptyCheck.parameterSpec = [
|
|
|
19
19
|
label: 'accessor'
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
type: '
|
|
22
|
+
type: 'scenario-step-input',
|
|
23
23
|
name: 'goto',
|
|
24
24
|
label: 'goto'
|
|
25
25
|
}
|
|
26
26
|
];
|
|
27
27
|
EmptyCheck.connectorFree = true;
|
|
28
|
+
EmptyCheck.help = 'integration/task/empty-check';
|
|
28
29
|
task_registry_1.TaskRegistry.registerTaskHandler('empty-check', EmptyCheck);
|
|
29
30
|
//# sourceMappingURL=empty-check.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empty-check.js","sourceRoot":"","sources":["../../../server/engine/task/empty-check.ts"],"names":[],"mappings":";;AAAA,oDAA+C;AAC/C,iDAA8C;AAE9C,KAAK,UAAU,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9C,IAAI,EACF,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAC3B,GAAG,IAAI,CAAA;IAER,MAAM,KAAK,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"empty-check.js","sourceRoot":"","sources":["../../../server/engine/task/empty-check.ts"],"names":[],"mappings":";;AAAA,oDAA+C;AAC/C,iDAA8C;AAE9C,KAAK,UAAU,UAAU,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9C,IAAI,EACF,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAC3B,GAAG,IAAI,CAAA;IAER,MAAM,KAAK,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE;QACpD,OAAO;YACL,IAAI,EAAE,IAAI;SACX,CAAA;KACF;IAED,OAAO,EAAE,CAAA;AACX,CAAC;AAED,UAAU,CAAC,aAAa,GAAG;IACzB;QACE,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;KAClB;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;KACd;CACF,CAAA;AAED,UAAU,CAAC,aAAa,GAAG,IAAI,CAAA;AAC/B,UAAU,CAAC,IAAI,GAAG,8BAA8B,CAAA;AAEhD,4BAAY,CAAC,mBAAmB,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA","sourcesContent":["import { TaskRegistry } from '../task-registry'\nimport { access } from '@things-factory/utils'\n\nasync function EmptyCheck(step, { logger, data }) {\n var {\n params: { accessor, goto }\n } = step\n\n const value = access(accessor, data)\n if ([null, undefined, NaN, ''].indexOf(value) !== -1) {\n return {\n next: goto\n }\n }\n\n return {}\n}\n\nEmptyCheck.parameterSpec = [\n {\n type: 'scenario-step-input',\n name: 'accessor',\n label: 'accessor'\n },\n {\n type: 'scenario-step-input',\n name: 'goto',\n label: 'goto'\n }\n]\n\nEmptyCheck.connectorFree = true\nEmptyCheck.help = 'integration/task/empty-check'\n\nTaskRegistry.registerTaskHandler('empty-check', EmptyCheck)\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql-query.js","sourceRoot":"","sources":["../../../server/engine/task/graphql-query.ts"],"names":[],"mappings":";;;AAAA,sEAA6B;AAC7B,6BAAwB;AAExB,iDAA8C;AAE9C,8DAAyD;AACzD,oDAA+C;AAE/C,KAAK,UAAU,YAAY,CAAC,IAAI,EAAE,OAAO;IACvC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;IAC9D,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,WAAW,IAAI,EAAE,CAAA;IAClE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAEzC,MAAM,EAAE,GAAG,IAAI,QAAE,CAAC;QAChB,OAAO,EAAE;YACP,IAAI;YACJ,SAAS;SACV;KACF,CAAC,CAAA;IAEF,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC,CAAA;IAEjC,IAAI,MAAM,GAAG,sCAAiB,CAAC,2BAA2B,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAElF,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE;QACvF,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAA;QAC1C,SAAS,CAAC,GAAG,CAAC,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QACvC,OAAO,SAAS,CAAA;IAClB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAC7C,KAAK,EAAE,IAAA,qBAAG,EAAA;QACN,KAAK;KACR;QACD,SAAS,EAAE,cAAc;KAC1B,CAAC,CAAA;IAEF,OAAO;QACL,IAAI,EAAE,WAAW;KAClB,CAAA;AACH,CAAC;AAED,YAAY,CAAC,aAAa,GAAG;IAC3B;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;KACf;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,WAAW;QAClB,QAAQ,EAAE;YACR,OAAO,EAAE,EAAE,CAAC,uBAAuB;SACpC;KACF;CACF,CAAA;
|
|
1
|
+
{"version":3,"file":"graphql-query.js","sourceRoot":"","sources":["../../../server/engine/task/graphql-query.ts"],"names":[],"mappings":";;;AAAA,sEAA6B;AAC7B,6BAAwB;AAExB,iDAA8C;AAE9C,8DAAyD;AACzD,oDAA+C;AAE/C,KAAK,UAAU,YAAY,CAAC,IAAI,EAAE,OAAO;IACvC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;IAC9D,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,WAAW,IAAI,EAAE,CAAA;IAClE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAEzC,MAAM,EAAE,GAAG,IAAI,QAAE,CAAC;QAChB,OAAO,EAAE;YACP,IAAI;YACJ,SAAS;SACV;KACF,CAAC,CAAA;IAEF,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,GAAG,GAAG,CAAC,CAAA;IAEjC,IAAI,MAAM,GAAG,sCAAiB,CAAC,2BAA2B,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAElF,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE;QACvF,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAA;QAC1C,SAAS,CAAC,GAAG,CAAC,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QACvC,OAAO,SAAS,CAAA;IAClB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;QAC7C,KAAK,EAAE,IAAA,qBAAG,EAAA;QACN,KAAK;KACR;QACD,SAAS,EAAE,cAAc;KAC1B,CAAC,CAAA;IAEF,OAAO;QACL,IAAI,EAAE,WAAW;KAClB,CAAA;AACH,CAAC;AAED,YAAY,CAAC,aAAa,GAAG;IAC3B;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;KACf;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,WAAW;QAClB,QAAQ,EAAE;YACR,OAAO,EAAE,EAAE,CAAC,uBAAuB;SACpC;KACF;CACF,CAAA;AAED,YAAY,CAAC,IAAI,GAAG,gCAAgC,CAAA;AAEpD,4BAAY,CAAC,mBAAmB,CAAC,eAAe,EAAE,YAAY,CAAC,CAAA","sourcesContent":["import gql from 'graphql-tag'\nimport { VM } from 'vm2'\n\nimport { access } from '@things-factory/utils'\n\nimport { ConnectionManager } from '../connection-manager'\nimport { TaskRegistry } from '../task-registry'\n\nasync function GraphqlQuery(step, context) {\n var { connection: connectionName, params: stepOptions } = step\n var { query, variables: variablesAccessorMap } = stepOptions || {}\n var { domain, data, variables } = context\n\n const vm = new VM({\n sandbox: {\n data,\n variables\n }\n })\n\n query = vm.run('`' + query + '`')\n\n var client = ConnectionManager.getConnectionInstanceByName(domain, connectionName)\n\n const queryVariables = Object.keys(variablesAccessorMap || {}).reduce((variables, key) => {\n const accessor = variablesAccessorMap[key]\n variables[key] = access(accessor, data)\n return variables\n }, {})\n\n var { data: queryResult } = await client.query({\n query: gql`\n ${query}\n `,\n variables: queryVariables\n })\n\n return {\n data: queryResult\n }\n}\n\nGraphqlQuery.parameterSpec = [\n {\n type: 'graphql',\n name: 'query',\n label: 'query'\n },\n {\n type: 'key-values',\n name: 'variables',\n label: 'variables',\n property: {\n options: [] //'scenario-step-input'\n }\n }\n]\n\nGraphqlQuery.help = 'integration/task/graphql-query'\n\nTaskRegistry.registerTaskHandler('graphql-query', GraphqlQuery)\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,uBAAoB;AACpB,0BAAuB;AACvB,mBAAgB;AAChB,iBAAc;AACd,qBAAkB;AAClB,sBAAmB;AACnB,uBAAoB;AACpB,2BAAwB;AACxB,4BAAyB;AACzB,iCAA8B;AAC9B,kCAA+B;AAC/B,0BAAuB;AACvB,8BAA2B;AAC3B,mCAAgC;AAChC,iCAA8B;AAC9B,yBAAsB;AACtB,kBAAe;AACf,iBAAc;AACd,yBAAsB;AACtB,+BAA4B;AAC5B,6BAA0B;AAC1B,mCAAgC;AAChC,wBAAqB;AACrB,8BAA2B;AAC3B,oBAAiB;AACjB,4BAAyB;AACzB,4BAAyB;AACzB,0BAAuB;AACvB,mBAAgB;AAChB,uBAAoB;AACpB,4BAAyB;AACzB,6BAA0B;AAC1B,oBAAiB","sourcesContent":["import './echo-send'\nimport './echo-receive'\nimport './sleep'\nimport './log'\nimport './publish'\nimport './http-get'\nimport './http-post'\nimport './graphql-query'\nimport './graphql-mutate'\nimport './local-graphql-query'\nimport './local-graphql-mutate'\nimport './sub-scenario'\nimport './book-up-scenario'\nimport './pick-pending-scenario'\nimport './reset-pending-queue'\nimport './empty-check'\nimport './goto'\nimport './end'\nimport './switch-goto'\nimport './switch-range-goto'\nimport './switch-scenario'\nimport './switch-range-scenario'\nimport './switch-set'\nimport './switch-range-set'\nimport './script'\nimport './database-query'\nimport './mqtt-subscribe'\nimport './mqtt-publish'\nimport './throw'\nimport './variables'\nimport './floating-point'\nimport './socket-listener'\nimport './random'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA,uBAAoB;AACpB,0BAAuB;AACvB,mBAAgB;AAChB,iBAAc;AACd,qBAAkB;AAClB,sBAAmB;AACnB,uBAAoB;AACpB,2BAAwB;AACxB,4BAAyB;AACzB,iCAA8B;AAC9B,kCAA+B;AAC/B,0BAAuB;AACvB,8BAA2B;AAC3B,mCAAgC;AAChC,iCAA8B;AAC9B,yBAAsB;AACtB,kBAAe;AACf,iBAAc;AACd,yBAAsB;AACtB,+BAA4B;AAC5B,6BAA0B;AAC1B,mCAAgC;AAChC,wBAAqB;AACrB,8BAA2B;AAC3B,oBAAiB;AACjB,4BAAyB;AACzB,4BAAyB;AACzB,0BAAuB;AACvB,mBAAgB;AAChB,uBAAoB;AACpB,4BAAyB;AACzB,6BAA0B;AAC1B,oBAAiB;AACjB,0BAAuB;AACvB,yBAAsB","sourcesContent":["import './echo-send'\nimport './echo-receive'\nimport './sleep'\nimport './log'\nimport './publish'\nimport './http-get'\nimport './http-post'\nimport './graphql-query'\nimport './graphql-mutate'\nimport './local-graphql-query'\nimport './local-graphql-mutate'\nimport './sub-scenario'\nimport './book-up-scenario'\nimport './pick-pending-scenario'\nimport './reset-pending-queue'\nimport './empty-check'\nimport './goto'\nimport './end'\nimport './switch-goto'\nimport './switch-range-goto'\nimport './switch-scenario'\nimport './switch-range-scenario'\nimport './switch-set'\nimport './switch-range-set'\nimport './script'\nimport './database-query'\nimport './mqtt-subscribe'\nimport './mqtt-publish'\nimport './throw'\nimport './variables'\nimport './floating-point'\nimport './socket-listener'\nimport './random'\nimport './csv-readline'\nimport './data-mapper'\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@things-factory/utils");
|
|
4
|
+
const task_registry_1 = require("../task-registry");
|
|
5
|
+
function map(rule, input) {
|
|
6
|
+
if (Array.isArray(input)) {
|
|
7
|
+
return input.map(i => map(rule, i));
|
|
8
|
+
}
|
|
9
|
+
return Object.keys(rule).reduce((sum, key) => {
|
|
10
|
+
sum[key] = input[rule[key]];
|
|
11
|
+
return sum;
|
|
12
|
+
}, {});
|
|
13
|
+
}
|
|
14
|
+
async function MapData(step, { logger, data }) {
|
|
15
|
+
var { params: { accessor, mappingRule } } = step;
|
|
16
|
+
const input = (0, utils_1.access)(accessor, data);
|
|
17
|
+
const output = map(mappingRule || {}, input);
|
|
18
|
+
return {
|
|
19
|
+
data: output
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
MapData.parameterSpec = [
|
|
23
|
+
{
|
|
24
|
+
type: 'scenario-step-input',
|
|
25
|
+
name: 'accessor',
|
|
26
|
+
label: 'accessor'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'key-values',
|
|
30
|
+
name: 'mappingRule',
|
|
31
|
+
label: 'mapping-rule'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
MapData.connectorFree = true;
|
|
35
|
+
MapData.help = 'integration/task/map-data';
|
|
36
|
+
task_registry_1.TaskRegistry.registerTaskHandler('map-data', MapData);
|
|
37
|
+
//# sourceMappingURL=map-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map-data.js","sourceRoot":"","sources":["../../../server/engine/task/map-data.ts"],"names":[],"mappings":";;AAAA,iDAA8C;AAC9C,oDAA+C;AAE/C,SAAS,GAAG,CAAC,IAAI,EAAE,KAAK;IACtB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QACxB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;KACpC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3B,OAAO,GAAG,CAAA;IACZ,CAAC,EAAE,EAAS,CAAC,CAAA;AACf,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3C,IAAI,EACF,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,EAClC,GAAG,IAAI,CAAA;IAER,MAAM,KAAK,GAAG,IAAA,cAAM,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACpC,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,EAAE,KAAK,CAAC,CAAA;IAE5C,OAAO;QACL,IAAI,EAAE,MAAM;KACb,CAAA;AACH,CAAC;AAED,OAAO,CAAC,aAAa,GAAG;IACtB;QACE,IAAI,EAAE,qBAAqB;QAC3B,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;KAClB;IACD;QACE,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,cAAc;KACtB;CACF,CAAA;AAED,OAAO,CAAC,aAAa,GAAG,IAAI,CAAA;AAC5B,OAAO,CAAC,IAAI,GAAG,2BAA2B,CAAA;AAE1C,4BAAY,CAAC,mBAAmB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA","sourcesContent":["import { access } from '@things-factory/utils'\nimport { TaskRegistry } from '../task-registry'\n\nfunction map(rule, input) {\n if (Array.isArray(input)) {\n return input.map(i => map(rule, i))\n }\n\n return Object.keys(rule).reduce((sum, key) => {\n sum[key] = input[rule[key]]\n return sum\n }, {} as any)\n}\n\nasync function MapData(step, { logger, data }) {\n var {\n params: { accessor, mappingRule }\n } = step\n\n const input = access(accessor, data)\n const output = map(mappingRule || {}, input)\n\n return {\n data: output\n }\n}\n\nMapData.parameterSpec = [\n {\n type: 'scenario-step-input',\n name: 'accessor',\n label: 'accessor'\n },\n {\n type: 'key-values',\n name: 'mappingRule',\n label: 'mapping-rule'\n }\n]\n\nMapData.connectorFree = true\nMapData.help = 'integration/task/map-data'\n\nTaskRegistry.registerTaskHandler('map-data', MapData)\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scenarioScheduleCallbackRouter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const koa_router_1 = tslib_1.__importDefault(require("koa-router"));
|
|
6
|
+
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const scenario_type_1 = require("../service/scenario/scenario-type");
|
|
8
|
+
const scenario_engine_1 = require("../engine/scenario-engine");
|
|
9
|
+
exports.scenarioScheduleCallbackRouter = new koa_router_1.default();
|
|
10
|
+
/* When a callback occurs from the scheduler when a scheduled scenario is on schedule. */
|
|
11
|
+
exports.scenarioScheduleCallbackRouter.post('/callback-schedule-for-scenario', async (context, next) => {
|
|
12
|
+
const { client } = context.request.body;
|
|
13
|
+
// application: Application,
|
|
14
|
+
// group: `${domain.id}`,
|
|
15
|
+
// type: 'scenario',
|
|
16
|
+
// key: scenario.id,
|
|
17
|
+
// operation: 'start'
|
|
18
|
+
const { domain } = context.state;
|
|
19
|
+
if (!domain) {
|
|
20
|
+
throw new Error('cannot identify the current domain.');
|
|
21
|
+
}
|
|
22
|
+
if (!client || typeof client !== 'object') {
|
|
23
|
+
throw new Error('client property should be a part of callback body.');
|
|
24
|
+
}
|
|
25
|
+
const { group: domainId, key: scenarioId, operation, type } = client;
|
|
26
|
+
if (!domainId || !scenarioId) {
|
|
27
|
+
throw new Error(`group(${domainId}) and key(${scenarioId}) properties should not be empty`);
|
|
28
|
+
}
|
|
29
|
+
if (domainId != domain.id) {
|
|
30
|
+
throw new Error('client property(group) is not matched with the current domain.');
|
|
31
|
+
}
|
|
32
|
+
if (type != 'scenario') {
|
|
33
|
+
throw new Error('client property(type) is not matched with the required value.');
|
|
34
|
+
}
|
|
35
|
+
if (operation != 'start') {
|
|
36
|
+
throw new Error('client property(operation) is not matched with the required value.');
|
|
37
|
+
}
|
|
38
|
+
const scenario = await (0, shell_1.getRepository)(scenario_type_1.Scenario).findOne({
|
|
39
|
+
where: { domain: { id: domainId }, id: scenarioId },
|
|
40
|
+
relations: ['domain', 'steps', 'creator']
|
|
41
|
+
});
|
|
42
|
+
if (!scenario) {
|
|
43
|
+
throw new Error(`Scenario having given scenarioId(${domain === null || domain === void 0 ? void 0 : domain.subdomain}:${scenarioId}) not found`);
|
|
44
|
+
}
|
|
45
|
+
// TODO prepare the secure authentication method
|
|
46
|
+
context.state.user = scenario.creator;
|
|
47
|
+
scenario_engine_1.ScenarioEngine.load(scenario.name, scenario, context);
|
|
48
|
+
context.status = 200;
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=scenario-schedule-callback-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-schedule-callback-router.js","sourceRoot":"","sources":["../../server/routers/scenario-schedule-callback-router.ts"],"names":[],"mappings":";;;;AAAA,oEAA+B;AAE/B,iDAAqD;AAGrD,qEAA4D;AAC5D,+DAA0D;AAE7C,QAAA,8BAA8B,GAAG,IAAI,oBAAM,EAAE,CAAA;AAE1D,yFAAyF;AACzF,sCAA8B,CAAC,IAAI,CAAC,iCAAiC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;IAC7F,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,IAA+B,CAAA;IAElE,4BAA4B;IAC5B,yBAAyB;IACzB,oBAAoB;IACpB,oBAAoB;IACpB,qBAAqB;IAErB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;IAEhC,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;KACvD;IAED,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;KACtE;IAED,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,MAAM,CAAA;IAEpE,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,SAAS,QAAQ,aAAa,UAAU,kCAAkC,CAAC,CAAA;KAC5F;IAED,IAAI,QAAQ,IAAI,MAAM,CAAC,EAAE,EAAE;QACzB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;KAClF;IAED,IAAI,IAAI,IAAI,UAAU,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAA;KACjF;IAED,IAAI,SAAS,IAAI,OAAO,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAA;KACtF;IAED,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAa,EAAC,wBAAQ,CAAC,CAAC,OAAO,CAAC;QACrD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;QACnD,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC;KAC1C,CAAC,CAAA;IAEF,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,SAAS,IAAI,UAAU,aAAa,CAAC,CAAA;KAClG;IAED,gDAAgD;IAChD,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAA;IAErC,gCAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAErD,OAAO,CAAC,MAAM,GAAG,GAAG,CAAA;AACtB,CAAC,CAAC,CAAA","sourcesContent":["import Router from 'koa-router'\n\nimport { getRepository } from '@things-factory/shell'\nimport { ScheduleRegisterRequest } from '@things-factory/scheduler-client'\n\nimport { Scenario } from '../service/scenario/scenario-type'\nimport { ScenarioEngine } from '../engine/scenario-engine'\n\nexport const scenarioScheduleCallbackRouter = new Router()\n\n/* When a callback occurs from the scheduler when a scheduled scenario is on schedule. */\nscenarioScheduleCallbackRouter.post('/callback-schedule-for-scenario', async (context, next) => {\n const { client } = context.request.body as ScheduleRegisterRequest\n\n // application: Application,\n // group: `${domain.id}`,\n // type: 'scenario',\n // key: scenario.id,\n // operation: 'start'\n\n const { domain } = context.state\n\n if (!domain) {\n throw new Error('cannot identify the current domain.')\n }\n\n if (!client || typeof client !== 'object') {\n throw new Error('client property should be a part of callback body.')\n }\n\n const { group: domainId, key: scenarioId, operation, type } = client\n\n if (!domainId || !scenarioId) {\n throw new Error(`group(${domainId}) and key(${scenarioId}) properties should not be empty`)\n }\n\n if (domainId != domain.id) {\n throw new Error('client property(group) is not matched with the current domain.')\n }\n\n if (type != 'scenario') {\n throw new Error('client property(type) is not matched with the required value.')\n }\n\n if (operation != 'start') {\n throw new Error('client property(operation) is not matched with the required value.')\n }\n\n const scenario = await getRepository(Scenario).findOne({\n where: { domain: { id: domainId }, id: scenarioId },\n relations: ['domain', 'steps', 'creator']\n })\n\n if (!scenario) {\n throw new Error(`Scenario having given scenarioId(${domain?.subdomain}:${scenarioId}) not found`)\n }\n\n // TODO prepare the secure authentication method\n context.state.user = scenario.creator\n\n ScenarioEngine.load(scenario.name, scenario, context)\n\n context.status = 200\n})\n"]}
|