@forzalabs/remora 0.0.12

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.
Files changed (85) hide show
  1. package/Constants.js +8 -0
  2. package/actions/automap.js +73 -0
  3. package/actions/compile.js +57 -0
  4. package/actions/debug.js +61 -0
  5. package/actions/deploy.js +95 -0
  6. package/actions/discover.js +36 -0
  7. package/actions/init.js +78 -0
  8. package/actions/run.js +51 -0
  9. package/auth/AdminManager.js +48 -0
  10. package/auth/ApiKeysManager.js +45 -0
  11. package/auth/JWTManager.js +56 -0
  12. package/core/Affirm.js +42 -0
  13. package/core/Algo.js +155 -0
  14. package/core/dste/DSTE.js +113 -0
  15. package/core/logger/DebugLogService.js +48 -0
  16. package/core/logger/DevelopmentLogService.js +70 -0
  17. package/core/logger/LocalLogService.js +70 -0
  18. package/core/logger/Logger.js +54 -0
  19. package/database/DatabaseEngine.js +119 -0
  20. package/database/DatabaseInitializer.js +80 -0
  21. package/database/DatabaseStructure.js +27 -0
  22. package/definitions/agents/DestinationDriver.js +2 -0
  23. package/definitions/agents/SourceDriver.js +2 -0
  24. package/definitions/cli.js +2 -0
  25. package/definitions/database/ApiKeys.js +2 -0
  26. package/definitions/database/Stored.js +7 -0
  27. package/definitions/database/UsageStat.js +2 -0
  28. package/definitions/database/User.js +2 -0
  29. package/definitions/json_schemas/consumer-schema.json +423 -0
  30. package/definitions/json_schemas/producer-schema.json +236 -0
  31. package/definitions/json_schemas/project-schema.json +59 -0
  32. package/definitions/json_schemas/source-schema.json +109 -0
  33. package/definitions/requests/ConsumerRequest.js +2 -0
  34. package/definitions/requests/Developer.js +2 -0
  35. package/definitions/requests/Mapping.js +2 -0
  36. package/definitions/requests/ProducerRequest.js +2 -0
  37. package/definitions/requests/Request.js +2 -0
  38. package/definitions/resources/Compiled.js +2 -0
  39. package/definitions/resources/Consumer.js +2 -0
  40. package/definitions/resources/Environment.js +2 -0
  41. package/definitions/resources/Library.js +2 -0
  42. package/definitions/resources/Producer.js +2 -0
  43. package/definitions/resources/Project.js +2 -0
  44. package/definitions/resources/Schema.js +2 -0
  45. package/definitions/resources/Source.js +2 -0
  46. package/documentation/README.md +123 -0
  47. package/documentation/default_resources/consumer.json +52 -0
  48. package/documentation/default_resources/producer.json +32 -0
  49. package/documentation/default_resources/project.json +14 -0
  50. package/documentation/default_resources/schema.json +36 -0
  51. package/documentation/default_resources/source.json +15 -0
  52. package/drivers/DriverFactory.js +56 -0
  53. package/drivers/LocalDriver.js +122 -0
  54. package/drivers/RedshiftDriver.js +179 -0
  55. package/drivers/S3Driver.js +47 -0
  56. package/drivers/S3SourceDriver.js +127 -0
  57. package/engines/CryptoEngine.js +46 -0
  58. package/engines/Environment.js +139 -0
  59. package/engines/ParseManager.js +38 -0
  60. package/engines/ProducerEngine.js +150 -0
  61. package/engines/UsageManager.js +61 -0
  62. package/engines/UserManager.js +43 -0
  63. package/engines/Validator.js +154 -0
  64. package/engines/ai/AutoMapperEngine.js +37 -0
  65. package/engines/ai/DeveloperEngine.js +70 -0
  66. package/engines/ai/LLM.js +299 -0
  67. package/engines/consumer/ConsumerEngine.js +204 -0
  68. package/engines/consumer/ConsumerManager.js +155 -0
  69. package/engines/consumer/PostProcessor.js +143 -0
  70. package/engines/deployment/DeploymentPlanner.js +46 -0
  71. package/engines/execution/ExecutionEnvironment.js +114 -0
  72. package/engines/execution/ExecutionPlanner.js +92 -0
  73. package/engines/execution/RequestExecutor.js +100 -0
  74. package/engines/file/FileCompiler.js +28 -0
  75. package/engines/file/FileExporter.js +116 -0
  76. package/engines/schema/SchemaEngine.js +33 -0
  77. package/engines/schema/SchemaValidator.js +67 -0
  78. package/engines/sql/SQLBuilder.js +96 -0
  79. package/engines/sql/SQLCompiler.js +140 -0
  80. package/engines/sql/SQLUtils.js +22 -0
  81. package/engines/validation/Validator.js +151 -0
  82. package/helper/Helper.js +64 -0
  83. package/helper/Settings.js +13 -0
  84. package/index.js +63 -0
  85. package/package.json +77 -0
@@ -0,0 +1,28 @@
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
+ class FileCompilerClass {
8
+ constructor() {
9
+ this.compileProducer = (producer, source) => {
10
+ (0, Affirm_1.default)(producer, `Invalid producer`);
11
+ (0, Affirm_1.default)(source, `Invalid source`);
12
+ (0, Affirm_1.default)(producer.settings.fileKey, `Missing required file key in producer settings`);
13
+ (0, Affirm_1.default)(producer.settings.fileType, `Missing required file type in producer settings`);
14
+ (0, Affirm_1.default)(!producer.measures || producer.measures.length === 0, `Cannot use "measure" with a producer linked to a file (only dimensions are allowed).`);
15
+ const columns = producer.dimensions.map(x => ({
16
+ aliasInProducer: x.alias,
17
+ nameInProducer: x.name,
18
+ consumerAlias: null,
19
+ consumerKey: null,
20
+ owner: x.name,
21
+ dimension: x
22
+ }));
23
+ return columns;
24
+ };
25
+ }
26
+ }
27
+ const FileCompiler = new FileCompilerClass();
28
+ exports.default = FileCompiler;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ const Affirm_1 = __importDefault(require("../../core/Affirm"));
49
+ const Algo_1 = __importDefault(require("../../core/Algo"));
50
+ const DSTE_1 = __importDefault(require("../../core/dste/DSTE"));
51
+ const DriverFactory_1 = __importDefault(require("../../drivers/DriverFactory"));
52
+ const Environment_1 = __importDefault(require("../Environment"));
53
+ const fs = __importStar(require("fs"));
54
+ class FileExporterClass {
55
+ constructor() {
56
+ this.export = (consumer, output, data) => __awaiter(this, void 0, void 0, function* () {
57
+ (0, Affirm_1.default)(consumer, `Invalid consumer`);
58
+ (0, Affirm_1.default)(output, `Invalid output`);
59
+ (0, Affirm_1.default)(data, `Invalid export data`);
60
+ let exportData = null;
61
+ let extension = null;
62
+ // build the actual file in the requested format
63
+ switch (output.format) {
64
+ case 'CSV': {
65
+ const lines = [];
66
+ const keys = Object.keys(data[0]);
67
+ lines.push(keys.map(x => `"${x}"`).join(','));
68
+ for (let i = 0; i < data.length; i++) {
69
+ const row = data[i];
70
+ const columns = keys.map(key => row[key]);
71
+ const rowValues = columns.map(c => Algo_1.default.replaceAll(c.toString(), '"', '""')).map(c => `"${c}"`).join(',');
72
+ lines.push(rowValues);
73
+ }
74
+ const csv = lines.join('\n');
75
+ exportData = csv;
76
+ extension = 'csv';
77
+ break;
78
+ }
79
+ case 'JSON': {
80
+ const lines = [];
81
+ for (let i = 0; i < data.length; i++) {
82
+ const row = data[i];
83
+ lines.push(JSON.stringify(row));
84
+ }
85
+ const jsonl = lines.join('\n');
86
+ exportData = jsonl;
87
+ extension = 'jsonl';
88
+ break;
89
+ }
90
+ case 'PARQUET': {
91
+ throw new Error(`Consumer output "${output.format}" not implemented yet`);
92
+ }
93
+ }
94
+ // export it where it needs to go
95
+ const source = Environment_1.default.getSource(output.exportDestination);
96
+ (0, Affirm_1.default)(source, `Invalid consumer "${consumer.name}" export location source. Make sure that the export location is an available source.`);
97
+ switch (source.engine) {
98
+ case 'local': {
99
+ const folder = source.authentication.path;
100
+ if (!fs.existsSync(folder))
101
+ fs.mkdirSync(folder);
102
+ const path = `${folder}/${consumer.name}_${Algo_1.default.replaceAll(DSTE_1.default.now().toJSON(), ':', '-')}.${extension}`;
103
+ fs.writeFileSync(path, exportData);
104
+ return path;
105
+ }
106
+ case 'aws-s3': {
107
+ const driver = yield DriverFactory_1.default.instantiateDestination(source);
108
+ const res = yield driver.uploadFile({ content: exportData, name: `${consumer.name}_${DSTE_1.default.now().toJSON()}` });
109
+ return res.key;
110
+ }
111
+ }
112
+ });
113
+ }
114
+ }
115
+ const FileExporter = new FileExporterClass();
116
+ exports.default = FileExporter;
@@ -0,0 +1,33 @@
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 SchemaValidator_1 = __importDefault(require("./SchemaValidator"));
8
+ const ConsumerEngine_1 = __importDefault(require("../consumer/ConsumerEngine"));
9
+ class SchemaEngineClass {
10
+ constructor() {
11
+ /**
12
+ * If the consumer has a ".schema" then checks that the output shape of the consumer complies with what the JSON schema says
13
+ */
14
+ this.enforceConsumerOutputSchema = (consumer) => {
15
+ var _a;
16
+ (0, Affirm_1.default)(consumer, `Invalid consumer`);
17
+ if (!consumer.schema)
18
+ return true;
19
+ const validator = SchemaValidator_1.default.getSchema(consumer.schema);
20
+ const schema = validator.schema;
21
+ // TODO: right now I just check that all the required properites are there...
22
+ // in the future this needs to be more fleshed (types, ...)
23
+ const properties = ConsumerEngine_1.default.compile(consumer);
24
+ const matches = (_a = schema.required) === null || _a === void 0 ? void 0 : _a.map(x => ({
25
+ exists: properties.find(k => k.consumerAlias === x) ? true : false,
26
+ property: x
27
+ }));
28
+ return matches.every(x => x.exists);
29
+ };
30
+ }
31
+ }
32
+ const SchemaEngine = new SchemaEngineClass();
33
+ exports.default = SchemaEngine;
@@ -0,0 +1,67 @@
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 ajv_1 = __importDefault(require("ajv"));
7
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const Affirm_1 = __importDefault(require("../../core/Affirm"));
11
+ class SchemaValidatorClass {
12
+ constructor() {
13
+ this.addSchema = (schema, schemaKey) => {
14
+ const isValid = this.ajv.validateSchema(schema);
15
+ (0, Affirm_1.default)(isValid, `Invalid JSON Schema: ${JSON.stringify(this.ajv.errors)}`);
16
+ this.ajv.addSchema(schema, schemaKey);
17
+ };
18
+ this.getSchema = (schemaKey) => {
19
+ return this.ajv.getSchema(schemaKey);
20
+ };
21
+ this.validate = (schemaKey, data) => {
22
+ var _a;
23
+ const validateFn = this.ajv.getSchema(schemaKey);
24
+ (0, Affirm_1.default)(validateFn, `Failed to get validator for schema '${schemaKey}'`);
25
+ const isValid = validateFn(data);
26
+ return { isValid, errors: (_a = validateFn.errors) !== null && _a !== void 0 ? _a : [], data };
27
+ };
28
+ this.validateAndReturn = (schemaKey, data) => {
29
+ const res = this.validate(schemaKey, data);
30
+ if (res.isValid)
31
+ return res.data;
32
+ else
33
+ return null;
34
+ };
35
+ this.unloadSchemas = () => {
36
+ const result = this.ajv.removeSchema();
37
+ (0, Affirm_1.default)(result, `Failed to remove schemas`);
38
+ this.loadInternalSchema();
39
+ return true;
40
+ };
41
+ this.loadInternalSchema = () => {
42
+ const internalSchemaDir = path_1.default.join(__dirname, '../../definitions/json_schemas');
43
+ const internalSchemas = [
44
+ 'project-schema',
45
+ 'source-schema',
46
+ 'producer-schema',
47
+ 'consumer-schema'
48
+ ];
49
+ internalSchemas.forEach(sn => {
50
+ const sp = path_1.default.join(internalSchemaDir, `${sn}.json`);
51
+ const parsed = JSON.parse(fs_1.default.readFileSync(sp, 'utf-8'));
52
+ this.addSchema(parsed, sn);
53
+ });
54
+ return true;
55
+ };
56
+ this.ajv = new ajv_1.default({
57
+ allErrors: true,
58
+ strict: true,
59
+ strictSchema: false,
60
+ validateFormats: true
61
+ });
62
+ (0, ajv_formats_1.default)(this.ajv);
63
+ this.loadInternalSchema();
64
+ }
65
+ }
66
+ const SchemaValidator = new SchemaValidatorClass();
67
+ exports.default = SchemaValidator;
@@ -0,0 +1,96 @@
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
+ class SQLBuilderClass {
8
+ constructor() {
9
+ this.getSQLOperator = (operator) => {
10
+ switch (operator) {
11
+ case 'equals': return '=';
12
+ case 'notEquals': return '!=';
13
+ case 'contains': return 'LIKE';
14
+ case 'notContains': return 'NOT LIKE';
15
+ case 'startsWith': return 'LIKE';
16
+ case 'endsWith': return 'LIKE';
17
+ case 'greaterThan': return '>';
18
+ case 'greaterThanOrEquals': return '>=';
19
+ case 'lessThan': return '<';
20
+ case 'lessThanOrEquals': return '<=';
21
+ case 'in': return 'IN';
22
+ case 'notIn': return 'NOT IN';
23
+ case 'between': return 'BETWEEN';
24
+ case 'notBetween': return 'NOT BETWEEN';
25
+ case 'isNull': return 'IS NULL';
26
+ case 'isNotNull': return 'IS NOT NULL';
27
+ case 'true': return '= TRUE';
28
+ case 'false': return '= FALSE';
29
+ case 'matches': return '~';
30
+ case 'notMatches': return '!~';
31
+ case 'sql': return ''; // Custom SQL should be handled separately
32
+ default: throw new Error(`Unsupported operator: ${operator}`);
33
+ }
34
+ };
35
+ this.buildFilterClause = (filter) => {
36
+ var _a, _b;
37
+ const operator = this.getSQLOperator(filter.operator);
38
+ let baseClause;
39
+ if (filter.operator === 'in' || filter.operator === 'notIn') {
40
+ baseClause = `${filter.member} ${operator} (${filter.values.map(value => `'${value}'`).join(', ')})`;
41
+ }
42
+ else if (filter.operator === 'between' || filter.operator === 'notBetween') {
43
+ baseClause = `${filter.member} ${operator} '${filter.values[0]}' AND '${filter.values[1]}'`;
44
+ }
45
+ else if (filter.operator === 'isNull' || filter.operator === 'isNotNull' || filter.operator === 'true' || filter.operator === 'false') {
46
+ baseClause = `${filter.member} ${operator}`;
47
+ }
48
+ else if (filter.operator === 'contains' || filter.operator === 'notContains') {
49
+ baseClause = `${filter.member} ${operator} '%${filter.values[0]}%'`;
50
+ }
51
+ else if (filter.operator === 'startsWith') {
52
+ baseClause = `${filter.member} ${operator} '${filter.values[0]}%'`;
53
+ }
54
+ else if (filter.operator === 'endsWith') {
55
+ baseClause = `${filter.member} ${operator} '%${filter.values[0]}'`;
56
+ }
57
+ else if (filter.operator === 'sql') {
58
+ baseClause = `(${filter.values[0]})`;
59
+ }
60
+ else {
61
+ baseClause = `${filter.member} ${operator} '${filter.values[0]}'`;
62
+ }
63
+ const orClauses = (_a = filter.or) === null || _a === void 0 ? void 0 : _a.map(this.buildFilterClause).join(' OR ');
64
+ const andClauses = (_b = filter.and) === null || _b === void 0 ? void 0 : _b.map(this.buildFilterClause).join(' AND ');
65
+ let combinedClause = baseClause;
66
+ if (orClauses) {
67
+ combinedClause = `(${combinedClause} OR ${orClauses})`;
68
+ }
69
+ if (andClauses) {
70
+ combinedClause = `(${combinedClause} AND ${andClauses})`;
71
+ }
72
+ return combinedClause;
73
+ };
74
+ this.buildConsumerQuery = (request) => {
75
+ (0, Affirm_1.default)(request, `Invalid build consumer request`);
76
+ let query = ``;
77
+ if (request.filters && request.filters.length > 0) {
78
+ const filterClauses = request.filters.map(this.buildFilterClause);
79
+ query += ` WHERE ${filterClauses.join(' AND ')}`;
80
+ }
81
+ if (request.order && request.order.length > 0) {
82
+ const orderClauses = request.order.map(([field, direction]) => `${field} ${direction}`);
83
+ query += ` ORDER BY ${orderClauses.join(', ')}`;
84
+ }
85
+ if (request.limit) {
86
+ query += ` LIMIT ${request.limit}`;
87
+ }
88
+ if (request.offset) {
89
+ query += ` OFFSET ${request.offset}`;
90
+ }
91
+ return query;
92
+ };
93
+ }
94
+ }
95
+ const SQLBuilder = new SQLBuilderClass();
96
+ exports.default = SQLBuilder;
@@ -0,0 +1,140 @@
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 ConsumerEngine_1 = __importDefault(require("../consumer/ConsumerEngine"));
9
+ const CryptoEngine_1 = __importDefault(require("../CryptoEngine"));
10
+ const Environment_1 = __importDefault(require("../Environment"));
11
+ const SQLUtils_1 = __importDefault(require("../sql/SQLUtils"));
12
+ class SQLCompilerClass {
13
+ constructor() {
14
+ this.compileProducer = (producer, source) => {
15
+ var _a, _b;
16
+ (0, Affirm_1.default)(producer, `Invalid producer`);
17
+ (0, Affirm_1.default)(source, `Invalid source`);
18
+ if (Algo_1.default.hasVal(producer.settings.sql)) {
19
+ const libraryItem = Environment_1.default.getSqlInLibrary(producer.settings.sql);
20
+ (0, Affirm_1.default)(libraryItem, `Misconfiguration for producer "${producer.name}": SQL library item is missing. Required SQL item named "${producer.settings.sql}" was not found.`);
21
+ return libraryItem.sql;
22
+ }
23
+ (0, Affirm_1.default)(producer.settings.sqlTable, `Misconfiguration for producer "${producer.name}": SQL table name is missing. Required since no override to the SQL was used.`);
24
+ const dimensions = producer.dimensions.map(x => {
25
+ var _a;
26
+ const maskType = x.mask;
27
+ const columnReference = (_a = x.alias) !== null && _a !== void 0 ? _a : x.name;
28
+ const fieldReference = `"${producer.settings.sqlTable}"."${columnReference}"`;
29
+ if (x.mask && x.mask === 'hash')
30
+ return CryptoEngine_1.default.hashQuery(maskType, fieldReference, x.name);
31
+ else
32
+ return `${fieldReference} AS "${x.name}"`;
33
+ });
34
+ const measures = (_b = (_a = producer.measures) === null || _a === void 0 ? void 0 : _a.map(measure => {
35
+ const thisProducer = `"${producer.settings.sqlTable}"`;
36
+ const sanitized = measure.sql.replace('P', thisProducer).replace('PROD', thisProducer).replace('PRODUCER', thisProducer)
37
+ .replace('${', '').replace('}', '');
38
+ return `${sanitized} AS "${measure.name}"`;
39
+ })) !== null && _b !== void 0 ? _b : [];
40
+ const columns = dimensions.concat(measures);
41
+ const sql = `SELECT ${columns.join(', ')} FROM "${source.authentication['schema']}"."${producer.settings.sqlTable}"`;
42
+ if (measures.length > 0) {
43
+ const groupBys = `GROUP BY ${dimensions.map((_, i) => i + 1).join(', ')}`;
44
+ return `${sql} ${groupBys}`;
45
+ }
46
+ return sql;
47
+ };
48
+ this.deployProducer = (producer, source) => {
49
+ (0, Affirm_1.default)(producer, `Invalid producer`);
50
+ (0, Affirm_1.default)(source, `Invalid source`);
51
+ const sql = this.compileProducer(producer, source);
52
+ if (producer.settings.direct) {
53
+ return sql;
54
+ }
55
+ else {
56
+ const internalSchema = Environment_1.default.get('schema');
57
+ (0, Affirm_1.default)(internalSchema, `Invalid schema set on the authentication for source "${source.name}"`);
58
+ return `CREATE OR REPLACE VIEW "${internalSchema}"."${producer.name}" AS ${sql}`;
59
+ }
60
+ };
61
+ /**
62
+ * Returns the SQL reference to this producer, used in FROM when constructing SQL statements of consumers (and others).
63
+ * This might easily just return a reference to a view or the underlying SQL of a producer if the view is not available.
64
+ */
65
+ this.getProducerReference = (producer) => {
66
+ (0, Affirm_1.default)(producer, 'Invalid producer');
67
+ const source = Environment_1.default.getSource(producer.source);
68
+ (0, Affirm_1.default)(source, `No source found for producer "${producer.name}"`);
69
+ if (producer.settings.direct) {
70
+ return this.compileProducer(producer, source);
71
+ }
72
+ else {
73
+ const internalSchema = Environment_1.default.get('schema');
74
+ (0, Affirm_1.default)(internalSchema, `Invalid schema set on the authentication for source "${source.name}"`);
75
+ return `SELECT * FROM "${internalSchema}"."${producer.name}"`;
76
+ }
77
+ };
78
+ this.getConsumerReference = (consumer) => {
79
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
80
+ if (consumer.outputs.some(x => x.format === 'SQL' && x.accellerated))
81
+ return `SELECT * FROM "av_remora_${SQLUtils_1.default.sanitizeName(consumer.name)}"`;
82
+ if (consumer.outputs.some(x => x.format === 'SQL' && !x.direct))
83
+ return `SELECT * FROM "v_remora_${SQLUtils_1.default.sanitizeName(consumer.name)}"`;
84
+ return `SELECT * FROM (${this.compileConsumer(consumer)})`;
85
+ };
86
+ this.compileConsumer = (consumer) => {
87
+ var _a;
88
+ (0, Affirm_1.default)(consumer, `Invalid consumer`);
89
+ (0, Affirm_1.default)(consumer.producers.length > 0, `Consumer has no producers to draw data from ("${consumer.name}")`);
90
+ const subqueries = consumer.producers.map(cProd => {
91
+ const producer = Environment_1.default.getProducer(cProd.name);
92
+ if (!producer) {
93
+ const consumer = Environment_1.default.getConsumer(cProd.name);
94
+ (0, Affirm_1.default)(consumer, `No producer found for consumer "${consumer.name}" with name "${cProd.name}"`);
95
+ return `"${cProd.name}" AS (${this.getConsumerReference(consumer)})`;
96
+ }
97
+ return `"${cProd.name}" AS (${this.getProducerReference(producer)})`;
98
+ });
99
+ const columns = ConsumerEngine_1.default.compile(consumer);
100
+ const sqlColumns = columns.map(x => { var _a, _b, _c; return `"${x.owner}"."${(_b = (_a = x.dimension) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : (_c = x.measure) === null || _c === void 0 ? void 0 : _c.name}" AS "${x.consumerAlias}"`; });
101
+ const columnQualifiers = sqlColumns.map(x => Algo_1.default.replaceAll(x.split('.')[1], '"', ''));
102
+ const dupes = Algo_1.default.duplicates(columnQualifiers);
103
+ (0, Affirm_1.default)(dupes.length === 0, `Wrong consumer configuration: duplicate columns where found for consumer "${consumer.name}" ("${dupes.join(', ')}")`);
104
+ const joins = consumer.producers.filter(x => x.joins && x.joins.length > 0).flatMap(cProd => {
105
+ return cProd.joins.map(x => {
106
+ const otherProd = consumer.producers.find(k => k.name === x.otherName);
107
+ (0, Affirm_1.default)(otherProd, `Invalid JOIN relantionship: the producer "${cProd.name}" is asking for a join with "${x.otherName}", but this one doesn't exists.`);
108
+ const starts = Algo_1.default.locations(x.sql, '${');
109
+ const ends = Algo_1.default.locations(x.sql, '}');
110
+ (0, Affirm_1.default)(starts.length === ends.length, `Invalid JOIN SQL: number of condition parameters does not match on consumer "${consumer.name}" producer "${cProd.name}" ("${x.sql}")`);
111
+ const params = starts.map((start, index) => x.sql.substring(start, ends[index] + 1));
112
+ const thisProducer = `"${cProd.name}"`;
113
+ const sqlParams = params.map(param => param.replace('P', thisProducer).replace('PROD', thisProducer).replace('PRODUCER', thisProducer)
114
+ .replace('${', '').replace('}', ''));
115
+ let sqlCondition = x.sql;
116
+ params.forEach((p, i) => sqlCondition = sqlCondition.replace(p, sqlParams[i]));
117
+ return `LEFT JOIN "${otherProd.name}" ON ${sqlCondition}`;
118
+ });
119
+ });
120
+ const filters = (_a = consumer.filters) === null || _a === void 0 ? void 0 : _a.map(x => {
121
+ const starts = Algo_1.default.locations(x.sql, '${');
122
+ const ends = Algo_1.default.locations(x.sql, '}');
123
+ (0, Affirm_1.default)(starts.length === ends.length, `Invalid filter SQL: number of condition parameters does not match on consumer "${consumer.name}" ("${x.sql}")`);
124
+ const params = starts.map((start, index) => x.sql.substring(start, ends[index] + 1));
125
+ const sqlParams = params.map(param => SQLUtils_1.default.findDimension(columns, param));
126
+ let sqlFilter = x.sql;
127
+ params.forEach((p, i) => sqlFilter = sqlFilter.replace(p, sqlParams[i].consumerAlias));
128
+ return sqlFilter;
129
+ });
130
+ let sql = `WITH ${subqueries.join(',\n')}\n\nSELECT ${sqlColumns.join(', ')} FROM "${consumer.producers[0].name}"`;
131
+ if (joins && joins.length > 0)
132
+ sql += ` ${joins.join(', ')}`;
133
+ if (filters && filters.length > 0)
134
+ sql += ` WHERE ${filters.join(', ')}`;
135
+ return sql;
136
+ };
137
+ }
138
+ }
139
+ const SQLCompiler = new SQLCompilerClass();
140
+ exports.default = SQLCompiler;
@@ -0,0 +1,22 @@
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 SQLUtils = {
9
+ viewName: (name) => 'v_remora_' + SQLUtils.sanitizeName(name),
10
+ acceleratedViewName: (name) => 'av_remora_' + SQLUtils.sanitizeName(name),
11
+ findDimension: (compiledDimensions, dimensionQualifier) => {
12
+ const cleanedQualifier = Algo_1.default.replaceAll(dimensionQualifier.split('.')[1], '}', '');
13
+ const hit = compiledDimensions.find(x => x.consumerAlias === cleanedQualifier);
14
+ (0, Affirm_1.default)(hit, `Unable to find the column referenced by "${dimensionQualifier}" in the compiled list of columns (${compiledDimensions.map(x => `"${x.consumerAlias}"`).join(', ')})`);
15
+ return hit;
16
+ },
17
+ sanitizeName: (string) => {
18
+ (0, Affirm_1.default)(string, 'Invalid string to sanitize');
19
+ return string.replace(/[^a-z0-9]/gi, '_');
20
+ }
21
+ };
22
+ exports.default = SQLUtils;
@@ -0,0 +1,151 @@
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
+ const ExecutionPlanner_1 = __importDefault(require("../execution/ExecutionPlanner"));
11
+ class ValidatorClass {
12
+ constructor() {
13
+ this.validateSources = (sources) => {
14
+ (0, Affirm_1.default)(sources, 'Invalid sources');
15
+ const errors = [];
16
+ try {
17
+ const dupes = Algo_1.default.duplicatesObject(sources, 'name');
18
+ if (dupes.length > 0)
19
+ errors.push(`Duplicate name(s) found in sources: "${dupes.map(x => x.name).join(', ')}"`);
20
+ for (let i = 0; i < sources.length; i++) {
21
+ const source = sources[i];
22
+ if (source.engine === 'local' && !source.authentication.path)
23
+ errors.push(`For source ${source.name}, the path has not been configured`);
24
+ }
25
+ }
26
+ catch (e) {
27
+ if (errors.length === 0)
28
+ errors.push(`There was an error in the validation Sources. (error: ${e})`);
29
+ }
30
+ return errors;
31
+ };
32
+ this.validateProducers = (producers) => {
33
+ (0, Affirm_1.default)(producers, 'Invalid producers');
34
+ const errors = [];
35
+ try {
36
+ const dupes = Algo_1.default.duplicatesObject(producers, 'name');
37
+ if (dupes.length > 0)
38
+ errors.push(`Duplicate name(s) found in producers: "${dupes.map(x => x.name).join(', ')}"`);
39
+ }
40
+ catch (e) {
41
+ if (errors.length === 0)
42
+ errors.push(`There was an error in the validation Producers. (error: ${e})`);
43
+ }
44
+ return errors;
45
+ };
46
+ this.validateProducer = (producer) => {
47
+ (0, Affirm_1.default)(producer, 'Invalid producer');
48
+ const errors = [];
49
+ try {
50
+ if (!producer.source || producer.source.length === 0)
51
+ errors.push(`Missing parameter "source" in producer`);
52
+ if (producer.dimensions.some(x => x.name.includes('{') || x.name.includes('[')))
53
+ errors.push(`Invalid dimension name found in producer "${producer.name}": can't use characters "{" or "[" in dimension names`);
54
+ }
55
+ catch (e) {
56
+ if (errors.length === 0)
57
+ errors.push(`There was an error in the validation Producer. (error: ${e})`);
58
+ }
59
+ return errors;
60
+ };
61
+ this.validateConsumers = (consumers) => {
62
+ (0, Affirm_1.default)(consumers, 'Invalid consumers');
63
+ const errors = [];
64
+ try {
65
+ const dupes = Algo_1.default.duplicatesObject(consumers, 'name');
66
+ if (dupes.length > 0)
67
+ errors.push(`Duplicate name(s) found in consumers: "${dupes.map(x => x.name).join(', ')}"`);
68
+ }
69
+ catch (e) {
70
+ if (errors.length === 0)
71
+ errors.push(`There was an error in the validation Consumers. (error: ${e})`);
72
+ }
73
+ return errors;
74
+ };
75
+ this.validateConsumer = (consumer) => {
76
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
77
+ const errors = [];
78
+ try {
79
+ // TODO: check that a consumer doens't consume himself
80
+ const allFieldsWithNoFrom = consumer.fields.filter(x => x.key === '*' && !x.from);
81
+ if (allFieldsWithNoFrom.length > 0 && consumer.producers.length > 1)
82
+ errors.push(`Field with key "*" was used without specifying the "from" producer and multiple producers were found.`);
83
+ if (consumer.fields.some(x => x.key === '*' && x.grouping))
84
+ errors.push(`Field with key "*" can't be used for "grouping". Either remove the grouping or change the key.`);
85
+ // Validation on producers
86
+ if (consumer.producers.length === 0)
87
+ errors.push(`Consumer must have at least 1 producer.`);
88
+ const producers = consumer.producers.map(x => Environment_1.default.getProducer(x.name));
89
+ if (producers.length === 0)
90
+ errors.push('No producers found');
91
+ if (producers.some(x => !x))
92
+ errors.push(`Invalid producer found in consumer "${consumer.name}"`);
93
+ // Validation on sources
94
+ const sources = producers.map(x => Environment_1.default.getSource(x.source));
95
+ if (sources.length === 0)
96
+ errors.push('No sources found');
97
+ if (sources.some(x => !x))
98
+ errors.push(`Invalid source found in consumer "${consumer.name}"`);
99
+ // 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
100
+ const uniqEngines = Algo_1.default.uniqBy(sources, 'engine');
101
+ if (uniqEngines.length !== 1)
102
+ errors.push(`Sources with different engines were used in the consumer "${consumer.name}" (${uniqEngines.join(', ')})`);
103
+ // For now we also only support consumers that have producers ALL having the same exact source
104
+ const uniqNames = Algo_1.default.uniqBy(sources, 'name');
105
+ if (uniqNames.length !== 1)
106
+ errors.push(`Producers with different sources were used in the consumer "${consumer.name}" (${uniqNames.join(', ')})`);
107
+ if (consumer.filters && consumer.filters.length > 0) {
108
+ if (consumer.filters.some(x => x.sql && x.rule))
109
+ errors.push(`A single consumer can't have both filters based on SQL and filters based on rules.`);
110
+ const [source] = ConsumerManager_1.default.getSource(consumer);
111
+ const engineClass = ExecutionPlanner_1.default.getEngineClass(source.engine);
112
+ if (engineClass === 'file' && consumer.filters.some(x => x.sql))
113
+ errors.push(`Filters based on SQL are only valid for SQL based sources. (source: ${source.name})`);
114
+ if (engineClass === 'sql' && consumer.filters.some(x => x.rule))
115
+ errors.push(`Filters based on rules are only valid for non-SQL based sources. (source: ${source.name})`);
116
+ }
117
+ // Validation on fields
118
+ const validateGroupingLevels = (fields, level = 0) => {
119
+ let errors = [];
120
+ const groupingFields = fields.filter(x => x.grouping);
121
+ if (groupingFields.length > 1)
122
+ errors.push(`There can't be 2 fields with grouping defined at the same level (${groupingFields.map(x => x.key).join(', ')}). Level: ${level}`);
123
+ groupingFields.forEach(field => {
124
+ if (field.grouping)
125
+ errors = [...errors, ...validateGroupingLevels(field.grouping.subFields, level + 1)];
126
+ });
127
+ return errors;
128
+ };
129
+ errors.push(...validateGroupingLevels(consumer.fields));
130
+ // Validation outputs
131
+ const duplicatesOutputs = Algo_1.default.duplicatesObject(consumer.outputs, 'format');
132
+ if (duplicatesOutputs.length > 0) {
133
+ const duplicatesTypes = Algo_1.default.uniq(duplicatesOutputs.map(x => x.format));
134
+ errors.push(`There are outputs with the same type. (duplicates type: ${duplicatesTypes.join(' and ')})`);
135
+ }
136
+ for (let i = 0; i < consumer.outputs.length; i++) {
137
+ const output = consumer.outputs[i];
138
+ if (output.format === 'SQL' && output.accellerated && output.direct)
139
+ errors.push(`An output SQL cannot be both direct and accelerated. (output: ${output.format})`);
140
+ }
141
+ }
142
+ catch (e) {
143
+ if (errors.length === 0)
144
+ errors.push(`There was an error in the validation Consumer. (error: ${e})`);
145
+ }
146
+ return errors;
147
+ };
148
+ }
149
+ }
150
+ const Validator = new ValidatorClass();
151
+ exports.default = Validator;