@forzalabs/remora 1.0.21 → 1.1.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.
- package/actions/automap.js +26 -42
- package/actions/compile.js +27 -43
- package/actions/create_consumer.js +24 -40
- package/actions/create_producer.js +16 -32
- package/actions/debug.js +18 -34
- package/actions/deploy.js +30 -46
- package/actions/discover.js +13 -29
- package/actions/init.js +29 -45
- package/actions/mock.js +16 -32
- package/actions/run.js +34 -52
- package/actions/sample.js +42 -58
- package/index.js +38 -43
- package/package.json +4 -4
- package/workers/ExecutorWorker.js +18 -32
- package/Constants.js +0 -34
- package/core/Affirm.js +0 -42
- package/core/Algo.js +0 -160
- package/core/dste/DSTE.js +0 -113
- package/core/logger/DebugLogService.js +0 -48
- package/core/logger/DevelopmentLogService.js +0 -70
- package/core/logger/LocalLogService.js +0 -70
- package/core/logger/Logger.js +0 -54
- package/database/DatabaseEngine.js +0 -149
- package/database/DatabaseStructure.js +0 -27
- package/definitions/DatasetDefinitions.js +0 -2
- package/definitions/ExecutorDefinitions.js +0 -2
- package/definitions/ProcessENV.js +0 -2
- package/definitions/agents/DestinationDriver.js +0 -2
- package/definitions/agents/SourceDriver.js +0 -2
- package/definitions/cli.js +0 -2
- package/definitions/database/ApiKeys.js +0 -2
- package/definitions/database/Stored.js +0 -7
- package/definitions/database/UsageStat.js +0 -2
- package/definitions/database/User.js +0 -2
- package/definitions/json_schemas/consumer-schema.json +0 -1226
- package/definitions/json_schemas/producer-schema.json +0 -308
- package/definitions/json_schemas/project-schema.json +0 -100
- package/definitions/json_schemas/source-schema.json +0 -249
- package/definitions/requests/ConsumerRequest.js +0 -2
- package/definitions/requests/Developer.js +0 -2
- package/definitions/requests/Mapping.js +0 -2
- package/definitions/requests/ProducerRequest.js +0 -2
- package/definitions/requests/Request.js +0 -2
- package/definitions/resources/Compiled.js +0 -2
- package/definitions/resources/Consumer.js +0 -2
- package/definitions/resources/Environment.js +0 -2
- package/definitions/resources/Library.js +0 -2
- package/definitions/resources/Producer.js +0 -2
- package/definitions/resources/Project.js +0 -2
- package/definitions/resources/Schema.js +0 -2
- package/definitions/resources/Source.js +0 -2
- package/definitions/temp.js +0 -2
- package/definitions/transform/Transformations.js +0 -2
- package/drivers/DeltaShareDriver.js +0 -186
- package/drivers/DriverFactory.js +0 -72
- package/drivers/DriverHelper.js +0 -248
- package/drivers/HttpApiDriver.js +0 -208
- package/drivers/RedshiftDriver.js +0 -184
- package/drivers/files/LocalDestinationDriver.js +0 -146
- package/drivers/files/LocalSourceDriver.js +0 -405
- package/drivers/s3/S3DestinationDriver.js +0 -197
- package/drivers/s3/S3SourceDriver.js +0 -495
- package/engines/CryptoEngine.js +0 -75
- package/engines/Environment.js +0 -170
- package/engines/ProcessENVManager.js +0 -83
- package/engines/RandomEngine.js +0 -47
- package/engines/SecretManager.js +0 -23
- package/engines/UserManager.js +0 -66
- package/engines/ai/AutoMapperEngine.js +0 -37
- package/engines/ai/DeveloperEngine.js +0 -497
- package/engines/ai/LLM.js +0 -255
- package/engines/consumer/ConsumerManager.js +0 -218
- package/engines/consumer/ConsumerOnFinishManager.js +0 -202
- package/engines/dataset/Dataset.js +0 -824
- package/engines/dataset/DatasetManager.js +0 -211
- package/engines/dataset/DatasetRecord.js +0 -120
- package/engines/dataset/DatasetRecordPool.js +0 -77
- package/engines/execution/RequestExecutor.js +0 -67
- package/engines/parsing/CSVParser.js +0 -60
- package/engines/parsing/LineParser.js +0 -71
- package/engines/parsing/ParseCompression.js +0 -101
- package/engines/parsing/ParseHelper.js +0 -18
- package/engines/parsing/ParseManager.js +0 -54
- package/engines/parsing/XLSParser.js +0 -87
- package/engines/parsing/XMLParser.js +0 -115
- package/engines/producer/ProducerEngine.js +0 -127
- package/engines/producer/ProducerManager.js +0 -43
- package/engines/scheduler/CronScheduler.js +0 -222
- package/engines/scheduler/QueueManager.js +0 -314
- package/engines/schema/SchemaValidator.js +0 -67
- package/engines/transform/JoinEngine.js +0 -232
- package/engines/transform/TransformationEngine.js +0 -277
- package/engines/transform/TypeCaster.js +0 -59
- package/engines/usage/DataframeManager.js +0 -55
- package/engines/usage/UsageDataManager.js +0 -151
- package/engines/usage/UsageManager.js +0 -65
- package/engines/validation/Validator.js +0 -216
- package/executors/ConsumerExecutor.js +0 -280
- package/executors/Executor.js +0 -177
- package/executors/ExecutorOrchestrator.js +0 -331
- package/executors/ExecutorPerformance.js +0 -17
- package/executors/ExecutorProgress.js +0 -54
- package/executors/ExecutorScope.js +0 -52
- package/executors/OutputExecutor.js +0 -118
- package/executors/ProducerExecutor.js +0 -108
- package/helper/Helper.js +0 -149
- package/helper/Logger.js +0 -84
- package/helper/Runtime.js +0 -20
- package/helper/Settings.js +0 -13
- package/licencing/LicenceManager.js +0 -64
- package/settings.js +0 -12
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const DSTE_1 = __importDefault(require("../../core/dste/DSTE"));
|
|
16
|
-
const DatabaseEngine_1 = __importDefault(require("../../database/DatabaseEngine"));
|
|
17
|
-
const DataframeManager_1 = __importDefault(require("./DataframeManager"));
|
|
18
|
-
class UsageDataManager {
|
|
19
|
-
getUsageDetails() {
|
|
20
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
const now = DSTE_1.default.now();
|
|
22
|
-
const from = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
23
|
-
const prevMonthFrom = new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000);
|
|
24
|
-
const yearAgo = new Date(now.getFullYear(), now.getMonth() - 11, 1);
|
|
25
|
-
const collection = 'usage';
|
|
26
|
-
// Aggregate status counts for current and previous month
|
|
27
|
-
const getStatusCounts = (start, end) => __awaiter(this, void 0, void 0, function* () {
|
|
28
|
-
const results = yield DatabaseEngine_1.default.aggregate(collection, [
|
|
29
|
-
{ $match: { startedAt: { $gte: start, $lte: end } } },
|
|
30
|
-
{ $group: { _id: '$status', count: { $sum: 1 } } }
|
|
31
|
-
]);
|
|
32
|
-
let success = 0, failed = 0, total = 0;
|
|
33
|
-
results.forEach(r => {
|
|
34
|
-
total += r.count;
|
|
35
|
-
if (r._id === 'success')
|
|
36
|
-
success = r.count;
|
|
37
|
-
if (r._id === 'failed')
|
|
38
|
-
failed = r.count;
|
|
39
|
-
});
|
|
40
|
-
return { total, success, failed };
|
|
41
|
-
});
|
|
42
|
-
const statusesRequests = yield getStatusCounts(from, now);
|
|
43
|
-
const prevStatusesRequests = yield getStatusCounts(prevMonthFrom, from);
|
|
44
|
-
// Monthly success and fails for last 12 months
|
|
45
|
-
const monthlySuccessPipeline = [
|
|
46
|
-
{ $match: { status: 'success', startedAt: { $gte: yearAgo, $lte: now } } },
|
|
47
|
-
{ $addFields: { year: { $year: '$startedAt' }, month: { $month: '$startedAt' } } },
|
|
48
|
-
{ $group: { _id: { year: '$year', month: '$month' }, count: { $sum: 1 } } },
|
|
49
|
-
{ $project: { _id: 0, x: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] }, y: '$count' } },
|
|
50
|
-
{ $sort: { x: 1 } }
|
|
51
|
-
];
|
|
52
|
-
const monthlyFailsPipeline = [
|
|
53
|
-
{ $match: { status: 'failed', startedAt: { $gte: yearAgo, $lte: now } } },
|
|
54
|
-
{ $addFields: { year: { $year: '$startedAt' }, month: { $month: '$startedAt' } } },
|
|
55
|
-
{ $group: { _id: { year: '$year', month: '$month' }, count: { $sum: 1 } } },
|
|
56
|
-
{ $project: { _id: 0, x: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] }, y: '$count' } },
|
|
57
|
-
{ $sort: { x: 1 } }
|
|
58
|
-
];
|
|
59
|
-
const rawMonthlySuccess = yield DatabaseEngine_1.default.aggregate(collection, monthlySuccessPipeline);
|
|
60
|
-
const rawMonthlyFails = yield DatabaseEngine_1.default.aggregate(collection, monthlyFailsPipeline);
|
|
61
|
-
// Top lines per month for last 12 months
|
|
62
|
-
const topLinesPipeline = [
|
|
63
|
-
{ $match: { startedAt: { $gte: yearAgo, $lte: now } } },
|
|
64
|
-
{ $addFields: { year: { $year: '$startedAt' }, month: { $month: '$startedAt' } } },
|
|
65
|
-
{ $group: { _id: { year: '$year', month: '$month' }, itemsCount: { $max: '$itemsCount' } } },
|
|
66
|
-
{ $project: { _id: 0, x: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] }, y: '$itemsCount' } },
|
|
67
|
-
{ $sort: { x: 1 } }
|
|
68
|
-
];
|
|
69
|
-
const topLines = yield DatabaseEngine_1.default.aggregate(collection, topLinesPipeline);
|
|
70
|
-
// Top times per month for last 12 months
|
|
71
|
-
const topTimePipeline = [
|
|
72
|
-
{ $match: { startedAt: { $gte: yearAgo, $lte: now } } },
|
|
73
|
-
{ $addFields: { durationMs: { $subtract: ['$finishedAt', '$startedAt'] }, year: { $year: '$startedAt' }, month: { $month: '$startedAt' } } },
|
|
74
|
-
{ $group: { _id: { year: '$year', month: '$month' }, maxDuration: { $max: '$durationMs' } } },
|
|
75
|
-
{ $project: { _id: 0, x: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] }, y: '$maxDuration' } },
|
|
76
|
-
{ $sort: { x: 1 } }
|
|
77
|
-
];
|
|
78
|
-
const topTime = yield DatabaseEngine_1.default.aggregate(collection, topTimePipeline);
|
|
79
|
-
// Monthly consumers: for each consumer, per month count
|
|
80
|
-
const consumerPipeline = [
|
|
81
|
-
{ $match: { startedAt: { $gte: yearAgo, $lte: now } } },
|
|
82
|
-
{ $addFields: { year: { $year: '$startedAt' }, month: { $month: '$startedAt' } } },
|
|
83
|
-
{ $group: { _id: { consumer: '$consumer', year: '$year', month: '$month' }, count: { $sum: 1 } } },
|
|
84
|
-
{ $project: { _id: 0, consumer: '$_id.consumer', x: { $concat: [{ $toString: '$_id.year' }, '-', { $toString: '$_id.month' }] }, y: '$count' } },
|
|
85
|
-
{ $sort: { consumer: 1, x: 1 } }
|
|
86
|
-
];
|
|
87
|
-
const consumersData = yield DatabaseEngine_1.default.aggregate(collection, consumerPipeline);
|
|
88
|
-
// transform to consumer array
|
|
89
|
-
const consumerMap = {};
|
|
90
|
-
consumersData.forEach(r => {
|
|
91
|
-
consumerMap[r.consumer] = consumerMap[r.consumer] || [];
|
|
92
|
-
consumerMap[r.consumer].push({ x: r.x, y: r.y });
|
|
93
|
-
});
|
|
94
|
-
const consumers = Object.entries(consumerMap).map(([name, data]) => ({ name, data: DataframeManager_1.default.fill(data !== null && data !== void 0 ? data : [], yearAgo, now) }));
|
|
95
|
-
// Recent executions
|
|
96
|
-
const recentExecution = yield DatabaseEngine_1.default.query(collection, { startedAt: { $gte: from, $lte: now } }, { sort: { startedAt: -1 }, limit: 10 });
|
|
97
|
-
return {
|
|
98
|
-
statusesRequests,
|
|
99
|
-
prevStatusesRequests,
|
|
100
|
-
monthlySuccess: DataframeManager_1.default.fill(rawMonthlySuccess !== null && rawMonthlySuccess !== void 0 ? rawMonthlySuccess : [], yearAgo, now),
|
|
101
|
-
monthlyFails: DataframeManager_1.default.fill(rawMonthlyFails !== null && rawMonthlyFails !== void 0 ? rawMonthlyFails : [], yearAgo, now),
|
|
102
|
-
consumers: consumers,
|
|
103
|
-
topLine: DataframeManager_1.default.fill(topLines !== null && topLines !== void 0 ? topLines : [], yearAgo, now),
|
|
104
|
-
topTime: DataframeManager_1.default.fill(topTime !== null && topTime !== void 0 ? topTime : [], yearAgo, now),
|
|
105
|
-
recentExecution
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
getFilteredUsageDetails(from, to, filters) {
|
|
110
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
-
const now = DSTE_1.default.now();
|
|
112
|
-
const fromDate = from || new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
113
|
-
const toDate = to || now;
|
|
114
|
-
const collection = 'usage';
|
|
115
|
-
// Build match criteria
|
|
116
|
-
const matchCriteria = {
|
|
117
|
-
startedAt: { $gte: fromDate, $lte: toDate }
|
|
118
|
-
};
|
|
119
|
-
if (filters === null || filters === void 0 ? void 0 : filters.consumer) {
|
|
120
|
-
matchCriteria.consumer = filters.consumer;
|
|
121
|
-
}
|
|
122
|
-
if (filters === null || filters === void 0 ? void 0 : filters.user) {
|
|
123
|
-
matchCriteria['executedBy.name'] = filters.user;
|
|
124
|
-
}
|
|
125
|
-
if (filters === null || filters === void 0 ? void 0 : filters.status) {
|
|
126
|
-
matchCriteria.status = filters.status;
|
|
127
|
-
}
|
|
128
|
-
// Query with filters and sorting
|
|
129
|
-
const usageDetails = yield DatabaseEngine_1.default.query(collection, matchCriteria, {
|
|
130
|
-
sort: { startedAt: -1 },
|
|
131
|
-
limit: 1000 // Reasonable limit to prevent large result sets
|
|
132
|
-
});
|
|
133
|
-
return usageDetails;
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
getUsageById(id) {
|
|
137
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
-
const collection = 'usage';
|
|
139
|
-
try {
|
|
140
|
-
const usageDetail = yield DatabaseEngine_1.default.get(collection, id);
|
|
141
|
-
return usageDetail;
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
// If document not found or invalid ID, return null
|
|
145
|
-
console.error('Error fetching usage by ID:', error);
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
exports.default = new UsageDataManager();
|
|
@@ -1,65 +0,0 @@
|
|
|
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 Affirm_1 = __importDefault(require("../../core/Affirm"));
|
|
7
|
-
const DSTE_1 = __importDefault(require("../../core/dste/DSTE"));
|
|
8
|
-
const DatabaseEngine_1 = __importDefault(require("../../database/DatabaseEngine"));
|
|
9
|
-
const Helper_1 = __importDefault(require("../../helper/Helper"));
|
|
10
|
-
const Settings_1 = __importDefault(require("../../helper/Settings"));
|
|
11
|
-
class UsageManagerClass {
|
|
12
|
-
constructor() {
|
|
13
|
-
/**
|
|
14
|
-
* TODO: I need to group the usage stats into a bucket daily. When and how I do it is still a question...
|
|
15
|
-
*/
|
|
16
|
-
this.getTodayBucketId = (consumer) => {
|
|
17
|
-
(0, Affirm_1.default)(consumer, `Invalid consumer`);
|
|
18
|
-
const now = DSTE_1.default.now();
|
|
19
|
-
return `${consumer.name}_${now.getUTCFullYear()}_${now.getUTCMonth()}_${now.getUTCDate()}`.toLowerCase();
|
|
20
|
-
};
|
|
21
|
-
this.startUsage = (consumer, details) => {
|
|
22
|
-
const { user, invokedBy } = details;
|
|
23
|
-
const newUsage = {
|
|
24
|
-
_id: Helper_1.default.uuid(),
|
|
25
|
-
consumer: consumer.name,
|
|
26
|
-
startedAt: DSTE_1.default.now(),
|
|
27
|
-
executedBy: user,
|
|
28
|
-
itemsCount: -1,
|
|
29
|
-
status: 'started',
|
|
30
|
-
invokedBy: invokedBy !== null && invokedBy !== void 0 ? invokedBy : 'UNKNOWN',
|
|
31
|
-
_signature: '',
|
|
32
|
-
stats: null
|
|
33
|
-
};
|
|
34
|
-
if (Helper_1.default.isDev())
|
|
35
|
-
return { usageId: newUsage._id, usage: Promise.resolve(newUsage) };
|
|
36
|
-
const updateRes = DatabaseEngine_1.default.upsert(Settings_1.default.db.collections.usage, newUsage._id, newUsage);
|
|
37
|
-
return { usageId: newUsage._id, usage: updateRes };
|
|
38
|
-
};
|
|
39
|
-
this.endUsage = (usageId, itemsCount, stats) => {
|
|
40
|
-
const update = {
|
|
41
|
-
itemsCount: itemsCount,
|
|
42
|
-
status: 'success',
|
|
43
|
-
finishedAt: DSTE_1.default.now(),
|
|
44
|
-
stats
|
|
45
|
-
};
|
|
46
|
-
if (Helper_1.default.isDev())
|
|
47
|
-
return { usageId: null, usage: Promise.resolve(update) };
|
|
48
|
-
const updateRes = DatabaseEngine_1.default.upsert(Settings_1.default.db.collections.usage, usageId, update);
|
|
49
|
-
return { usageId: usageId, usage: updateRes };
|
|
50
|
-
};
|
|
51
|
-
this.failUsage = (usageId, error) => {
|
|
52
|
-
const update = {
|
|
53
|
-
status: 'failed',
|
|
54
|
-
error: error,
|
|
55
|
-
finishedAt: DSTE_1.default.now()
|
|
56
|
-
};
|
|
57
|
-
if (Helper_1.default.isDev())
|
|
58
|
-
return { usageId: null, usage: Promise.resolve(update) };
|
|
59
|
-
const updateRes = DatabaseEngine_1.default.upsert(Settings_1.default.db.collections.usage, usageId, update);
|
|
60
|
-
return { usageId: usageId, usage: updateRes };
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const UsageManager = new UsageManagerClass();
|
|
65
|
-
exports.default = UsageManager;
|
|
@@ -1,216 +0,0 @@
|
|
|
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 Affirm_1 = __importDefault(require("../../core/Affirm"));
|
|
7
|
-
const Algo_1 = __importDefault(require("../../core/Algo"));
|
|
8
|
-
const ConsumerManager_1 = __importDefault(require("../consumer/ConsumerManager"));
|
|
9
|
-
const Environment_1 = __importDefault(require("../Environment"));
|
|
10
|
-
class ValidatorClass {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.validateSources = (sources) => {
|
|
13
|
-
(0, Affirm_1.default)(sources, 'Invalid sources');
|
|
14
|
-
const errors = [];
|
|
15
|
-
try {
|
|
16
|
-
const dupes = Algo_1.default.duplicatesObject(sources, 'name');
|
|
17
|
-
if (dupes.length > 0)
|
|
18
|
-
errors.push(`Duplicate name(s) found in sources: "${dupes.map(x => x.name).join(', ')}"`);
|
|
19
|
-
for (let i = 0; i < sources.length; i++) {
|
|
20
|
-
const source = sources[i];
|
|
21
|
-
if (source.engine === 'local' && !source.authentication.path)
|
|
22
|
-
errors.push(`For source ${source.name}, the path has not been configured`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
if (errors.length === 0)
|
|
27
|
-
errors.push(`There was an error in the validation Sources. (error: ${e})`);
|
|
28
|
-
}
|
|
29
|
-
return errors;
|
|
30
|
-
};
|
|
31
|
-
this.validateProducers = (producers) => {
|
|
32
|
-
(0, Affirm_1.default)(producers, 'Invalid producers');
|
|
33
|
-
const errors = [];
|
|
34
|
-
try {
|
|
35
|
-
const dupes = Algo_1.default.duplicatesObject(producers, 'name');
|
|
36
|
-
if (dupes.length > 0)
|
|
37
|
-
errors.push(`Duplicate name(s) found in producers: "${dupes.map(x => x.name).join(', ')}"`);
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
if (errors.length === 0)
|
|
41
|
-
errors.push(`There was an error in the validation Producers. (error: ${e})`);
|
|
42
|
-
}
|
|
43
|
-
return errors;
|
|
44
|
-
};
|
|
45
|
-
this.validateProducer = (producer) => {
|
|
46
|
-
(0, Affirm_1.default)(producer, 'Invalid producer');
|
|
47
|
-
const errors = [];
|
|
48
|
-
try {
|
|
49
|
-
if (!producer.source || producer.source.length === 0)
|
|
50
|
-
errors.push(`Missing parameter "source" in producer`);
|
|
51
|
-
if (producer.dimensions.some(x => x.name.includes('{') || x.name.includes('[')))
|
|
52
|
-
errors.push(`Invalid dimension name found in producer "${producer.name}": can't use characters "{" or "[" in dimension names`);
|
|
53
|
-
// Validate sourceFilename dimension usage
|
|
54
|
-
const sourceFilenameDimensions = producer.dimensions.filter(x => x.sourceFilename === true);
|
|
55
|
-
if (sourceFilenameDimensions.length > 1) {
|
|
56
|
-
errors.push(`Producer "${producer.name}" has multiple dimensions with sourceFilename=true. Only one dimension can have this flag.`);
|
|
57
|
-
}
|
|
58
|
-
if (sourceFilenameDimensions.length > 0) {
|
|
59
|
-
const source = Environment_1.default.getSource(producer.source);
|
|
60
|
-
if (source) {
|
|
61
|
-
const validEngines = ['local', 'aws-s3'];
|
|
62
|
-
if (!validEngines.includes(source.engine)) {
|
|
63
|
-
errors.push(`Producer "${producer.name}" has a dimension with sourceFilename=true but the source engine "${source.engine}" doesn't support this feature. Only "local" and "aws-s3" sources support sourceFilename.`);
|
|
64
|
-
}
|
|
65
|
-
if (!producer.settings.fileKey && !producer.settings.fileType) {
|
|
66
|
-
errors.push(`Producer "${producer.name}" has a dimension with sourceFilename=true but is not a file-based producer. sourceFilename requires fileKey and fileType to be set.`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch (e) {
|
|
72
|
-
if (errors.length === 0)
|
|
73
|
-
errors.push(`There was an error in the validation Producer. (error: ${e})`);
|
|
74
|
-
}
|
|
75
|
-
return errors;
|
|
76
|
-
};
|
|
77
|
-
this.validateConsumers = (consumers) => {
|
|
78
|
-
(0, Affirm_1.default)(consumers, 'Invalid consumers');
|
|
79
|
-
const errors = [];
|
|
80
|
-
try {
|
|
81
|
-
const dupes = Algo_1.default.duplicatesObject(consumers, 'name');
|
|
82
|
-
if (dupes.length > 0)
|
|
83
|
-
errors.push(`Duplicate name(s) found in consumers: "${dupes.map(x => x.name).join(', ')}"`);
|
|
84
|
-
}
|
|
85
|
-
catch (e) {
|
|
86
|
-
if (errors.length === 0)
|
|
87
|
-
errors.push(`There was an error in the validation Consumers. (error: ${e})`);
|
|
88
|
-
}
|
|
89
|
-
return errors;
|
|
90
|
-
};
|
|
91
|
-
this.validateConsumer = (consumer) => {
|
|
92
|
-
(0, Affirm_1.default)(consumer, 'Invalid consumer');
|
|
93
|
-
const errors = [];
|
|
94
|
-
try {
|
|
95
|
-
// TODO: check that a consumer doesn't consume hitself
|
|
96
|
-
const allFieldsWithNoFrom = consumer.fields.filter(x => x.key === '*' && !x.from);
|
|
97
|
-
if (allFieldsWithNoFrom.length > 0 && consumer.producers.length > 1)
|
|
98
|
-
errors.push(`Field with key "*" was used without specifying the "from" producer and multiple producers were found.`);
|
|
99
|
-
// Validation on producers
|
|
100
|
-
if (consumer.producers.length === 0)
|
|
101
|
-
errors.push(`Consumer must have at least 1 producer.`);
|
|
102
|
-
const producers = consumer.producers.map(x => Environment_1.default.getProducer(x.name));
|
|
103
|
-
if (producers.length === 0)
|
|
104
|
-
errors.push('No producers found');
|
|
105
|
-
if (producers.some(x => !x))
|
|
106
|
-
errors.push(`Invalid producer found in consumer "${consumer.name}"`);
|
|
107
|
-
if (consumer.producers.length > 0) {
|
|
108
|
-
const withJoins = consumer.producers.filter(x => (Algo_1.default.hasVal(x.joins) && x.joins.length > 0) || !x.union);
|
|
109
|
-
const withUnions = consumer.producers.filter(x => x.union === true);
|
|
110
|
-
if (withJoins.length > 0 && withUnions.length)
|
|
111
|
-
errors.push(`Multiple producers in consumer have mixed "joins" and "union": you can either have multiple producers with "joins" or multiple producers with "union", but not both (joins: ${withJoins.map(x => x.name).join(', ')}; unions: ${withUnions.map(x => x.name).join(', ')})`);
|
|
112
|
-
}
|
|
113
|
-
// Validation on sources
|
|
114
|
-
const sources = producers.map(x => Environment_1.default.getSource(x.source));
|
|
115
|
-
if (sources.length === 0)
|
|
116
|
-
errors.push('No sources found');
|
|
117
|
-
if (sources.some(x => !x))
|
|
118
|
-
errors.push(`Invalid source found in consumer "${consumer.name}"`);
|
|
119
|
-
// For now we only support connecting producers of the same engine type to a consumer, so we give an error if we detect different ones
|
|
120
|
-
const uniqEngines = Algo_1.default.uniqBy(sources, 'engine');
|
|
121
|
-
if (uniqEngines.length !== 1)
|
|
122
|
-
errors.push(`Sources with different engines are used in the consumer "${consumer.name}" (${uniqEngines.join(', ')})`);
|
|
123
|
-
// For now we also only support consumers that have producers ALL having the same exact source
|
|
124
|
-
const uniqNames = Algo_1.default.uniqBy(sources, 'name');
|
|
125
|
-
if (uniqNames.length !== 1)
|
|
126
|
-
errors.push(`Producers with different sources are used in the consumer "${consumer.name}" (${uniqNames.join(', ')})`);
|
|
127
|
-
if (consumer.filters && consumer.filters.length > 0) {
|
|
128
|
-
if (consumer.filters.some(x => x.sql && x.rule))
|
|
129
|
-
errors.push(`A single consumer can't have both filters based on SQL and filters based on rules.`);
|
|
130
|
-
const [source] = ConsumerManager_1.default.getSource(consumer);
|
|
131
|
-
const engineClass = this.getEngineClass(source.engine);
|
|
132
|
-
if (engineClass === 'file' && consumer.filters.some(x => x.sql))
|
|
133
|
-
errors.push(`Filters based on SQL are only valid for SQL based sources. (source: ${source.name})`);
|
|
134
|
-
if (engineClass === 'sql' && consumer.filters.some(x => x.rule))
|
|
135
|
-
errors.push(`Filters based on rules are only valid for non-SQL based sources. (source: ${source.name})`);
|
|
136
|
-
// Check that the members of the rules are present in the consumer
|
|
137
|
-
const allFields = consumer.fields;
|
|
138
|
-
const ruleBasedFilters = consumer.filters.filter(x => x.rule);
|
|
139
|
-
const rulesWithMatchingFields = ruleBasedFilters.map(x => ({
|
|
140
|
-
match: allFields.find(k => { var _a; return ((_a = k.alias) !== null && _a !== void 0 ? _a : k.key) === x.rule.member; }),
|
|
141
|
-
rule: x
|
|
142
|
-
}));
|
|
143
|
-
if (rulesWithMatchingFields.some(x => !x.match)) {
|
|
144
|
-
const missingRules = rulesWithMatchingFields.filter(x => !x.match);
|
|
145
|
-
errors.push(`Filter(s) on member(s) "${missingRules.map(x => x.rule.rule.member).join(', ')}" is invalid since the member specified is not present in the consumer. Check the member value or add the missing field to the consumer.`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const validateTransformations = (fields) => {
|
|
149
|
-
var _a;
|
|
150
|
-
const errors = [];
|
|
151
|
-
const trxsFields = fields.filter(x => x.transform);
|
|
152
|
-
for (const field of trxsFields) {
|
|
153
|
-
const trxToValidate = [];
|
|
154
|
-
if (Array.isArray(field.transform))
|
|
155
|
-
trxToValidate.push(...field.transform);
|
|
156
|
-
else
|
|
157
|
-
trxToValidate.push(field.transform);
|
|
158
|
-
for (const trans of trxToValidate) {
|
|
159
|
-
if ('combine_fields' in trans) {
|
|
160
|
-
const { combine_fields } = trans;
|
|
161
|
-
if (!combine_fields.fields || combine_fields.fields.length === 0)
|
|
162
|
-
errors.push(`The "combine_field" transformation is missing the "fields" property ("${field.key}").`);
|
|
163
|
-
const missingFieldsInConsumer = combine_fields.fields
|
|
164
|
-
.map(x => ({ field: x, found: fields.find(k => { var _a; return ((_a = k.alias) !== null && _a !== void 0 ? _a : k.key) === x; }) }))
|
|
165
|
-
.filter(x => !x.found);
|
|
166
|
-
if (missingFieldsInConsumer.length > 0)
|
|
167
|
-
errors.push(`The requested field(s) for a transformation is missing in the consumer -> missing field(s): "${missingFieldsInConsumer.map(x => x.field).join(', ')}"; field transformation: "${(_a = field.alias) !== null && _a !== void 0 ? _a : field.key}";`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return errors;
|
|
172
|
-
};
|
|
173
|
-
errors.push(...validateTransformations(consumer.fields));
|
|
174
|
-
// Validation outputs
|
|
175
|
-
const duplicatesOutputs = Algo_1.default.duplicatesObject(consumer.outputs, 'format');
|
|
176
|
-
if (duplicatesOutputs.length > 0) {
|
|
177
|
-
const duplicatesTypes = Algo_1.default.uniq(duplicatesOutputs.map(x => x.format));
|
|
178
|
-
errors.push(`There are outputs with the same type. (duplicates type: ${duplicatesTypes.join(' and ')})`);
|
|
179
|
-
}
|
|
180
|
-
for (const output of consumer.outputs) {
|
|
181
|
-
const format = output.format.toUpperCase();
|
|
182
|
-
if (format === 'SQL' && output.accelerated && output.direct)
|
|
183
|
-
errors.push(`An output SQL cannot be both direct and accelerated (output: ${format})`);
|
|
184
|
-
if ((format === 'CSV' || format === 'JSON' || format === 'PARQUET')) {
|
|
185
|
-
if (!output.exportDestination)
|
|
186
|
-
errors.push(`A static file output must have an export destination set (${format})`);
|
|
187
|
-
else if (!Environment_1.default.getSource(output.exportDestination))
|
|
188
|
-
errors.push(`The export destination "${output.exportDestination}" was not found in the sources.`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// Validate distinct
|
|
192
|
-
if (consumer.options) {
|
|
193
|
-
if (Algo_1.default.hasVal(consumer.options.distinct) && Algo_1.default.hasVal(consumer.options.distinctOn))
|
|
194
|
-
errors.push(`Can't specify a "distinct" and a "distinctOn" clause on the same consumer (${consumer.name}); use one or the other.`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (e) {
|
|
198
|
-
if (errors.length === 0)
|
|
199
|
-
errors.push(`There was an error in the validation Consumer. (error: ${e})`);
|
|
200
|
-
}
|
|
201
|
-
return errors;
|
|
202
|
-
};
|
|
203
|
-
this.getEngineClass = (engine) => {
|
|
204
|
-
switch (engine) {
|
|
205
|
-
case 'aws-dynamodb': return 'no-sql';
|
|
206
|
-
case 'aws-redshift':
|
|
207
|
-
case 'postgres': return 'sql';
|
|
208
|
-
case 'delta-share':
|
|
209
|
-
case 'aws-s3': return 'file';
|
|
210
|
-
case 'local': return 'local';
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const Validator = new ValidatorClass();
|
|
216
|
-
exports.default = Validator;
|