@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,155 @@
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 Environment_1 = __importDefault(require("../Environment"));
9
+ class ConsumerManagerClass {
10
+ constructor() {
11
+ this.getConsumerFlatFields = (consumer) => {
12
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
13
+ return this.getFlatFields(consumer.fields);
14
+ };
15
+ this.getFlatFields = (list) => {
16
+ let result = [...list];
17
+ for (let i = 0; i < list.length; i++) {
18
+ const field = list[i];
19
+ if (field.grouping && field.grouping.subFields && field.grouping.subFields.length > 0)
20
+ result = [...result, ...this.getFlatFields(field.grouping.subFields)];
21
+ }
22
+ return result;
23
+ };
24
+ /**
25
+ * Returns the full list of fields that are used by a consumer, while keeping the nested structure of fields.
26
+ * If there are *, then replace them with the actual fields found in the underlying producer/consumer
27
+ */
28
+ this.getExpandedFields = (consumer) => {
29
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
30
+ const availableColumns = this.getAvailableColumns(consumer);
31
+ const convertedFields = this.convertFields(consumer.fields);
32
+ const expandedFields = convertedFields.flatMap(x => this.expandField(consumer, x, availableColumns));
33
+ return expandedFields;
34
+ };
35
+ this.convertFields = (fieldsToConvert) => {
36
+ (0, Affirm_1.default)(fieldsToConvert, 'Invalid fields');
37
+ const convertedFields = fieldsToConvert.map(x => ({
38
+ cField: x
39
+ }));
40
+ return convertedFields;
41
+ };
42
+ /**
43
+ * Return all the available columns (dimensions and measures) to the consumer given its producers
44
+ */
45
+ this.getAvailableColumns = (consumer) => {
46
+ const availableColumns = consumer.producers.flatMap(cProd => {
47
+ var _a, _b;
48
+ const producer = Environment_1.default.getProducer(cProd.name);
49
+ if (!producer) {
50
+ const subConsumer = Environment_1.default.getConsumer(cProd.name);
51
+ (0, Affirm_1.default)(subConsumer, `No producer found with name "${cProd.name}"`);
52
+ return this.getAvailableColumns(subConsumer);
53
+ }
54
+ else {
55
+ const dims = producer.dimensions.map(x => ({
56
+ consumerAlias: null,
57
+ consumerKey: null,
58
+ nameInProducer: x.name,
59
+ aliasInProducer: x.alias,
60
+ dimension: x,
61
+ owner: cProd.name
62
+ }));
63
+ const meas = (_b = (_a = producer.measures) === null || _a === void 0 ? void 0 : _a.map(x => ({
64
+ consumerAlias: null,
65
+ consumerKey: null,
66
+ nameInProducer: x.name,
67
+ aliasInProducer: x.name,
68
+ measure: x,
69
+ owner: cProd.name
70
+ }))) !== null && _b !== void 0 ? _b : [];
71
+ return [...dims, ...meas];
72
+ }
73
+ });
74
+ return availableColumns;
75
+ };
76
+ /**
77
+ * If the field is '*' then replace them with the actual fields found in the underlying producer/consumer
78
+ */
79
+ this.expandField = (consumer, field, availableColumns) => {
80
+ var _a;
81
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
82
+ (0, Affirm_1.default)(field, 'Invalid consumer field');
83
+ const expandedFields = [];
84
+ if (field.cField.key === '*') {
85
+ const from = (_a = field.cField.from) !== null && _a !== void 0 ? _a : (consumer.producers.length === 1 ? consumer.producers[0].name : null);
86
+ availableColumns.filter(x => x.owner === from).forEach(col => {
87
+ expandedFields.push({
88
+ cField: {
89
+ key: col.nameInProducer,
90
+ alias: col.nameInProducer,
91
+ from: col.owner
92
+ },
93
+ dimension: col.dimension,
94
+ measure: col.measure
95
+ });
96
+ });
97
+ }
98
+ else if (field.cField.grouping) {
99
+ expandedFields.push({
100
+ cField: {
101
+ key: field.cField.key,
102
+ alias: field.cField.alias,
103
+ from: field.cField.from,
104
+ grouping: {
105
+ groupingKey: field.cField.grouping.groupingKey,
106
+ subFields: field.cField.grouping.subFields.flatMap(x => this.expandField(consumer, { cField: x }, availableColumns)).map(x => x.cField)
107
+ }
108
+ },
109
+ dimension: field.dimension,
110
+ measure: field.measure
111
+ });
112
+ }
113
+ else {
114
+ const col = ConsumerManager.searchFieldInColumns(field.cField, availableColumns, consumer);
115
+ (0, Affirm_1.default)(col, `Consumer "${consumer.name}" misconfiguration: the requested field "${field.cField.key}" is not found in any of the specified producers ("${consumer.producers.map(x => x.name).join(', ')}")`);
116
+ expandedFields.push(field);
117
+ }
118
+ return expandedFields;
119
+ };
120
+ this.searchFieldInColumns = (field, columns, consumer) => {
121
+ (0, Affirm_1.default)(field, 'Invalid field');
122
+ (0, Affirm_1.default)(columns, 'Invalid columns');
123
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
124
+ if (field.from) {
125
+ return columns.find(x => x.owner === field.from && x.nameInProducer === field.key);
126
+ }
127
+ else if (consumer.producers.length === 1 && !field.from) {
128
+ return columns.find(x => x.nameInProducer === field.key);
129
+ }
130
+ else {
131
+ const matches = columns.filter(x => x.nameInProducer === field.key);
132
+ (0, Affirm_1.default)(matches.length > 0, `Consumer "${consumer.name}" misconfiguration: the field "${field.key}" is not found in any of the included producers (${consumer.producers.map(x => x.name).join(', ')})`);
133
+ (0, Affirm_1.default)(matches.length === 1, `Consumer "${consumer.name}" misconfiguration: the field "${field.key}" is ambiguos between the fields with same name from the producers: ${matches.map(x => x.owner).join(', ')}`);
134
+ return matches[0];
135
+ }
136
+ };
137
+ this.getSource = (consumer) => {
138
+ const producers = consumer.producers.map(x => Environment_1.default.getProducer(x.name));
139
+ (0, Affirm_1.default)(producers.length > 0, 'No producers found');
140
+ (0, Affirm_1.default)(producers.every(x => x), `Invalid producer found in consumer "${consumer.name}"`);
141
+ const sources = producers.map(x => Environment_1.default.getSource(x.source));
142
+ (0, Affirm_1.default)(sources.length > 0, 'No sources found');
143
+ (0, Affirm_1.default)(sources.every(x => x), `Invalid source found in consumer "${consumer.name}"`);
144
+ // 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
145
+ const uniqEngines = Algo_1.default.uniqBy(sources, 'engine');
146
+ (0, Affirm_1.default)(uniqEngines.length === 1, `Sources with different engines were used in the consumer "${consumer.name}" (${uniqEngines.join(', ')})`);
147
+ // For now we also only support consumers that have producers ALL having the same exact source
148
+ const uniqNames = Algo_1.default.uniqBy(sources, 'name');
149
+ (0, Affirm_1.default)(uniqNames.length === 1, `Producers with different sources were used in the consumer "${consumer.name}" (${uniqNames.join(', ')})`);
150
+ return [sources[0], producers[0]];
151
+ };
152
+ }
153
+ }
154
+ const ConsumerManager = new ConsumerManagerClass();
155
+ exports.default = ConsumerManager;
@@ -0,0 +1,143 @@
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 CryptoEngine_1 = __importDefault(require("../CryptoEngine"));
9
+ const Environment_1 = __importDefault(require("../Environment"));
10
+ const FileCompiler_1 = __importDefault(require("../file/FileCompiler"));
11
+ const ConsumerManager_1 = __importDefault(require("./ConsumerManager"));
12
+ class PostProcessorClass {
13
+ constructor() {
14
+ /**
15
+ * Get an array of objects and projects it to another array of objects but with different shape
16
+ * Basically does a field mapping with some extra logic (like grouping and creating nested fields)
17
+ */
18
+ this.doProjection = (consumer, data) => {
19
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
20
+ (0, Affirm_1.default)(data, 'Invalid data');
21
+ const fields = ConsumerManager_1.default.getExpandedFields(consumer);
22
+ const subProjection = (allFields, items) => {
23
+ (0, Affirm_1.default)(allFields, 'Invalid fields');
24
+ (0, Affirm_1.default)(Array.isArray(allFields), 'Invalid fields type, must be an array');
25
+ (0, Affirm_1.default)(items, 'Invalid items');
26
+ (0, Affirm_1.default)(Array.isArray(items), 'Invalid items type, must be an array');
27
+ if (allFields.some(x => x.cField.grouping)) {
28
+ (0, Affirm_1.default)(allFields.filter(x => x.cField.grouping).length === 1, `Mutliple fields at the same level were found having "grouping" attributes.`);
29
+ const groupingRule = allFields.find(x => x.cField.grouping).cField.grouping;
30
+ const groups = Algo_1.default.groupBy(items, groupingRule.groupingKey);
31
+ const projections = [];
32
+ groups.forEach(gItems => {
33
+ var _a;
34
+ const projected = {};
35
+ const first = gItems[0];
36
+ for (const field of allFields) {
37
+ const { key, alias, grouping } = field.cField;
38
+ const fieldKey = alias !== null && alias !== void 0 ? alias : key;
39
+ const maskType = (_a = field.dimension) === null || _a === void 0 ? void 0 : _a.mask;
40
+ if (!field.cField.grouping) {
41
+ projected[fieldKey] = CryptoEngine_1.default.hashValue(maskType, first[fieldKey]);
42
+ }
43
+ else {
44
+ const { subFields } = grouping;
45
+ const convertedSubFields = ConsumerManager_1.default.convertFields(subFields);
46
+ projected[fieldKey] = subProjection(convertedSubFields, gItems);
47
+ }
48
+ }
49
+ projections.push(projected);
50
+ });
51
+ return projections;
52
+ }
53
+ else {
54
+ return items.map(x => {
55
+ var _a;
56
+ const projected = {};
57
+ for (const field of allFields) {
58
+ const { key, alias } = field.cField;
59
+ const fieldKey = alias !== null && alias !== void 0 ? alias : key;
60
+ const maskType = (_a = field.dimension) === null || _a === void 0 ? void 0 : _a.mask;
61
+ projected[fieldKey] = CryptoEngine_1.default.hashValue(maskType, x[fieldKey]);
62
+ }
63
+ return projected;
64
+ });
65
+ }
66
+ };
67
+ return subProjection(fields, data);
68
+ };
69
+ /**
70
+ * Gets an array of objects (with potentially nested fields) and unpacks them to an array of objects with no nested fields
71
+ * If some nested keys are lists, then a logic similar to a SQL JOIN is used and rows are duplicated
72
+ */
73
+ this.unpack = (data, producer) => {
74
+ (0, Affirm_1.default)(data, 'Invalid data');
75
+ (0, Affirm_1.default)(Array.isArray(data), 'Invalid data type, must be an array');
76
+ (0, Affirm_1.default)(producer, 'Invalid producer');
77
+ console.log('BBBBBBBBB');
78
+ const source = Environment_1.default.getSource(producer.source);
79
+ (0, Affirm_1.default)(source, `No source found for producer "${producer.name}" with name "${producer.source}"`);
80
+ const columns = FileCompiler_1.default.compileProducer(producer, source);
81
+ (0, Affirm_1.default)(columns, `Invalid columns from compilation for producer "${producer.name}"`);
82
+ const unpackDimension = (item, dimension) => {
83
+ var _a;
84
+ const { nameInProducer, aliasInProducer } = dimension;
85
+ const maskType = (_a = dimension.dimension.mask) !== null && _a !== void 0 ? _a : undefined;
86
+ const keys = aliasInProducer.split('.');
87
+ let prevValue = item;
88
+ for (const key of keys) {
89
+ if (key.includes('{')) {
90
+ const cleanedKey = key.replace('{', '').replace('}', '');
91
+ if (Array.isArray(prevValue))
92
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue.map((x) => x[cleanedKey]);
93
+ else
94
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue[cleanedKey];
95
+ }
96
+ else if (key.includes('[')) {
97
+ const cleanedKey = key.replace('[', '').replace(']', '');
98
+ if (Array.isArray(prevValue))
99
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue.flatMap((x) => x[cleanedKey]);
100
+ else
101
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue[cleanedKey];
102
+ }
103
+ else {
104
+ if (Array.isArray(prevValue))
105
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue.map((x) => x[key]);
106
+ else
107
+ prevValue = prevValue === null || prevValue === void 0 ? void 0 : prevValue[key];
108
+ }
109
+ }
110
+ prevValue = CryptoEngine_1.default.hashValue(maskType, prevValue);
111
+ const res = { [nameInProducer]: prevValue };
112
+ return res;
113
+ };
114
+ const splitArrayFields = (item) => {
115
+ const keysWithArrayValues = Object.keys(item).filter(key => Array.isArray(item[key]));
116
+ if (keysWithArrayValues.length === 0)
117
+ return [item];
118
+ const key = keysWithArrayValues[0];
119
+ const values = item[key];
120
+ const remainingItem = Object.assign({}, item);
121
+ delete remainingItem[key];
122
+ const splitRemaining = splitArrayFields(remainingItem);
123
+ return values.flatMap((value) => {
124
+ return splitRemaining.map((remaining) => {
125
+ return Object.assign(Object.assign({}, remaining), { [key]: value });
126
+ });
127
+ });
128
+ };
129
+ const unpackSingle = (item) => {
130
+ const unpackedItem = {};
131
+ for (const column of columns) {
132
+ const value = unpackDimension(item, column);
133
+ Object.assign(unpackedItem, value);
134
+ }
135
+ return splitArrayFields(unpackedItem);
136
+ };
137
+ const unpackedData = data.flatMap(x => unpackSingle(x));
138
+ return unpackedData;
139
+ };
140
+ }
141
+ }
142
+ const PostProcessor = new PostProcessorClass();
143
+ exports.default = PostProcessor;
@@ -0,0 +1,46 @@
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 ConsumerManager_1 = __importDefault(require("../consumer/ConsumerManager"));
7
+ const Environment_1 = __importDefault(require("../Environment"));
8
+ class DeploymentPlannerClass {
9
+ constructor() {
10
+ this.planConsumer = (consumer) => {
11
+ const [source, producer] = ConsumerManager_1.default.getSource(consumer);
12
+ const plan = [];
13
+ for (let i = 0; i < consumer.outputs.length; i++) {
14
+ const output = consumer.outputs[i];
15
+ switch (output.format) {
16
+ // csv, json, parquet outputs do not need to generate anything at deploy
17
+ case 'SQL': {
18
+ if (output.accellerated && !output.direct)
19
+ plan.push({ type: 'create-materialized-view', output: output });
20
+ else if (!output.direct)
21
+ plan.push({ type: 'create-view', output: output });
22
+ break;
23
+ }
24
+ case 'API': {
25
+ throw new Error(`Invalid consumer "${consumer.name}" format "${output.format}": not implemented yet.`);
26
+ }
27
+ }
28
+ }
29
+ return plan;
30
+ };
31
+ this.planProducer = (producer) => {
32
+ const source = Environment_1.default.getSource(producer.source);
33
+ const plan = [];
34
+ switch (source.engine) {
35
+ case 'aws-redshift': {
36
+ if (!producer.settings.direct)
37
+ plan.push({ type: 'create-view', producer: producer });
38
+ break;
39
+ }
40
+ }
41
+ return plan;
42
+ };
43
+ }
44
+ }
45
+ const DeploymentPlanner = new DeploymentPlannerClass();
46
+ exports.default = DeploymentPlanner;
@@ -0,0 +1,114 @@
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 Affirm_1 = __importDefault(require("../../core/Affirm"));
16
+ const DriverFactory_1 = __importDefault(require("../../drivers/DriverFactory"));
17
+ const ConsumerEngine_1 = __importDefault(require("../consumer/ConsumerEngine"));
18
+ const PostProcessor_1 = __importDefault(require("../consumer/PostProcessor"));
19
+ const FileExporter_1 = __importDefault(require("../file/FileExporter"));
20
+ const ParseManager_1 = __importDefault(require("../ParseManager"));
21
+ const ProducerEngine_1 = __importDefault(require("../ProducerEngine"));
22
+ const SQLBuilder_1 = __importDefault(require("../sql/SQLBuilder"));
23
+ const SQLCompiler_1 = __importDefault(require("../sql/SQLCompiler"));
24
+ const ExecutionPlanner_1 = __importDefault(require("./ExecutionPlanner"));
25
+ const RequestExecutor_1 = __importDefault(require("./RequestExecutor"));
26
+ class ExecutionEnvironment {
27
+ constructor(consumer) {
28
+ this.run = (options) => __awaiter(this, void 0, void 0, function* () {
29
+ (0, Affirm_1.default)(this._consumer, 'Invalid consumer');
30
+ const plan = ExecutionPlanner_1.default.plan(this._consumer, options);
31
+ (0, Affirm_1.default)(plan, `Invalid execution plan`);
32
+ (0, Affirm_1.default)(plan.length > 0, `Empty execution plan`);
33
+ const result = { shape: ConsumerEngine_1.default.getOutputShape(this._consumer) };
34
+ for (const planStep of plan) {
35
+ switch (planStep.type) {
36
+ case 'compile-consumer-to-SQL': {
37
+ const sql = SQLCompiler_1.default.getConsumerReference(this._consumer);
38
+ this._data.consumerSQL = sql;
39
+ this._data.finalSQL = sql;
40
+ break;
41
+ }
42
+ case 'compile-execution-request-to-SQL': {
43
+ const sql = SQLBuilder_1.default.buildConsumerQuery(options);
44
+ this._data.executionRequestSQL = sql;
45
+ this._data.finalSQL = `WITH consumer AS (${this._data.consumerSQL})\nSELECT * FROM consumer${this._data.executionRequestSQL}`;
46
+ break;
47
+ }
48
+ case 'execute-SQL': {
49
+ (0, Affirm_1.default)(planStep.source, `Invalid source in execute-SQL step`);
50
+ const driver = yield DriverFactory_1.default.instantiateSource(planStep.source);
51
+ this._fetchedData = (yield driver.query(this._data.finalSQL)).rows;
52
+ this._fetchedDataType = 'array-of-json';
53
+ break;
54
+ }
55
+ case 'read-file-whole': {
56
+ (0, Affirm_1.default)(planStep.producer, `Invalid producer in read-file-whole step`);
57
+ const fileData = yield ProducerEngine_1.default.readFile(planStep.producer, { readmode: 'all' });
58
+ this._fetchedData = fileData.data;
59
+ this._fetchedDataType = fileData.dataType;
60
+ break;
61
+ }
62
+ case 'read-file-lines': {
63
+ (0, Affirm_1.default)(planStep.lines, `Invalid lines in read-file-lines step`);
64
+ (0, Affirm_1.default)(planStep.producer, `Invalid producer in read-file-lines step`);
65
+ const { producer, lines: { from, to } } = planStep;
66
+ const fileData = yield ProducerEngine_1.default.readFile(producer, { readmode: 'lines', lines: { from, to } });
67
+ this._fetchedData = fileData.data;
68
+ this._fetchedDataType = fileData.dataType;
69
+ break;
70
+ }
71
+ case 'nested-field-unpacking': {
72
+ (0, Affirm_1.default)(planStep.producer, `Invalid producer in nested-field-unpacking step`);
73
+ this._fetchedData = PostProcessor_1.default.unpack(this._fetchedData, planStep.producer);
74
+ break;
75
+ }
76
+ case 'post-process-json': {
77
+ this._fetchedData = PostProcessor_1.default.doProjection(this._consumer, this._fetchedData);
78
+ break;
79
+ }
80
+ case 'csv-to-json': {
81
+ (0, Affirm_1.default)(this._fetchedData, 'Invalid data');
82
+ (0, Affirm_1.default)(Array.isArray(this._fetchedData), 'Invalid data type, must be an array');
83
+ (0, Affirm_1.default)(planStep.producer, `Invalid producer in csv-to-json step`);
84
+ const csv = this._fetchedData[0];
85
+ this._fetchedData = ParseManager_1.default.csvToJson(csv, planStep.producer);
86
+ break;
87
+ }
88
+ case 'export-file': {
89
+ (0, Affirm_1.default)(planStep.output, `Invalid output in export-file step`);
90
+ const res = yield FileExporter_1.default.export(this._consumer, planStep.output, this._fetchedData);
91
+ result.fileUri = res;
92
+ break;
93
+ }
94
+ case 'apply-execution-request-to-result': {
95
+ this._fetchedData = RequestExecutor_1.default.execute(this._fetchedData, options);
96
+ break;
97
+ }
98
+ case 'apply-consumer-filters-on-JSON': {
99
+ this._fetchedData = RequestExecutor_1.default._applyFilters(this._fetchedData, this._consumer.filters.map(x => x.rule));
100
+ break;
101
+ }
102
+ default: throw new Error(`Invalid execution plan step type "${planStep.type}"`);
103
+ }
104
+ }
105
+ result.data = this._fetchedData;
106
+ return result;
107
+ });
108
+ this._consumer = consumer;
109
+ this._data = { consumerSQL: null, executionRequestSQL: null, finalSQL: null };
110
+ this._fetchedData = [];
111
+ this._fetchedDataType = null;
112
+ }
113
+ }
114
+ exports.default = ExecutionEnvironment;
@@ -0,0 +1,92 @@
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
+ class ExecutionPlannerClas {
10
+ constructor() {
11
+ this.getEngineClass = (engine) => {
12
+ switch (engine) {
13
+ case 'aws-dynamodb': return 'no-sql';
14
+ case 'aws-redshift':
15
+ case 'postgres': return 'sql';
16
+ case 'aws-s3': return 'file';
17
+ case 'local': return 'local';
18
+ }
19
+ };
20
+ this.plan = (consumer, options) => {
21
+ var _a;
22
+ (0, Affirm_1.default)(consumer, 'Invalid consumer');
23
+ const [source, producer] = ConsumerManager_1.default.getSource(consumer);
24
+ const producerEngine = source.engine;
25
+ const plan = [];
26
+ switch (producerEngine) {
27
+ case 'postgres':
28
+ case 'aws-redshift': {
29
+ plan.push({ type: 'compile-consumer-to-SQL' });
30
+ if (Algo_1.default.hasVal(options))
31
+ plan.push({ type: 'compile-execution-request-to-SQL' });
32
+ plan.push({ type: 'execute-SQL', source: source });
33
+ break;
34
+ }
35
+ case 'local':
36
+ case 'aws-s3': {
37
+ const prod = producer;
38
+ if (Algo_1.default.hasVal(options) && (options.limit || options.offset))
39
+ plan.push({ type: 'read-file-lines', producer: prod, lines: { from: (_a = options.offset) !== null && _a !== void 0 ? _a : 0, to: options.limit ? (options.offset + options.limit) : undefined } });
40
+ else
41
+ plan.push({ type: 'read-file-whole', producer: prod });
42
+ if (prod.settings.fileType.toUpperCase() === 'CSV') {
43
+ plan.push({ type: 'csv-to-json', producer: prod });
44
+ }
45
+ if (prod.dimensions.some(x => { var _a, _b; return ((_a = x.alias) === null || _a === void 0 ? void 0 : _a.includes('{')) || ((_b = x.alias) === null || _b === void 0 ? void 0 : _b.includes('[')); }))
46
+ plan.push({ type: 'nested-field-unpacking', producer: prod });
47
+ plan.push({ type: 'post-process-json' });
48
+ if (consumer.filters && consumer.filters.length > 0)
49
+ plan.push({ type: 'apply-consumer-filters-on-JSON' });
50
+ break;
51
+ }
52
+ default: throw new Error(`Engine "${producerEngine}" not supported`);
53
+ }
54
+ // at this point I have the data loaded in memory
55
+ // TODO: can I handle streaming data? (e.g. a file that is too big to fit in memory)
56
+ // TODO: how to handle pagination of SQL results?
57
+ const engineClass = this.getEngineClass(producerEngine);
58
+ for (const output of consumer.outputs) {
59
+ switch (output.format) {
60
+ case 'JSON': {
61
+ if (engineClass === 'file' && Algo_1.default.hasVal(options))
62
+ plan.push({ type: 'apply-execution-request-to-result' });
63
+ // TODO: test if it is need ed and if it doesn't break soething else
64
+ if (engineClass === 'sql')
65
+ plan.push({ type: 'post-process-json' });
66
+ plan.push({ type: 'export-file', output });
67
+ break;
68
+ }
69
+ case 'CSV':
70
+ case 'PARQUET': {
71
+ plan.push({ type: 'export-file', output });
72
+ break;
73
+ }
74
+ case 'API': {
75
+ if (engineClass === 'file' && Algo_1.default.hasVal(options))
76
+ plan.push({ type: 'apply-execution-request-to-result' });
77
+ break;
78
+ }
79
+ case 'SQL': {
80
+ // TODO: what should I do here?? do I need to do anything?
81
+ break;
82
+ }
83
+ default:
84
+ throw new Error(`Output format "${output.format}" not supported`);
85
+ }
86
+ }
87
+ return plan;
88
+ };
89
+ }
90
+ }
91
+ const ExecutionPlanner = new ExecutionPlannerClas();
92
+ exports.default = ExecutionPlanner;
@@ -0,0 +1,100 @@
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 RequestExecutorClass {
8
+ constructor() {
9
+ /**
10
+ * Applies the filters, limit, offset and order on the in-memory-data
11
+ */
12
+ this.execute = (data, request) => {
13
+ (0, Affirm_1.default)(data, 'Invalid data');
14
+ (0, Affirm_1.default)(request, 'Invalid request');
15
+ (0, Affirm_1.default)(Array.isArray(data), `Invalid data type: should be an array`);
16
+ if (request.filters)
17
+ data = this._applyFilters(data, request.filters);
18
+ if (request.order)
19
+ data = this._applyOrdering(data, request.order);
20
+ return data;
21
+ };
22
+ this._applyFilters = (data, filters) => {
23
+ return data.filter((item) => {
24
+ return filters.every(filter => this._evaluateFilter(item, filter));
25
+ });
26
+ };
27
+ this._evaluateFilter = (item, filter) => {
28
+ const evaluate = (baseItem, baseFilter) => {
29
+ const { member, operator, values } = baseFilter;
30
+ const value = baseItem[member];
31
+ const singleValue = values[0];
32
+ switch (operator) {
33
+ case 'equals':
34
+ return value === singleValue;
35
+ case 'notEquals':
36
+ return value !== singleValue;
37
+ case 'contains':
38
+ return typeof value === 'string' && value.includes(singleValue);
39
+ case 'notContains':
40
+ return typeof value === 'string' && !value.includes(singleValue);
41
+ case 'startsWith':
42
+ return typeof value === 'string' && value.startsWith(singleValue);
43
+ case 'endsWith':
44
+ return typeof value === 'string' && value.endsWith(singleValue);
45
+ case 'greaterThan':
46
+ return typeof value === 'number' && value > Number(singleValue);
47
+ case 'greaterThanOrEquals':
48
+ return typeof value === 'number' && value >= Number(singleValue);
49
+ case 'lessThan':
50
+ return typeof value === 'number' && value < Number(singleValue);
51
+ case 'lessThanOrEquals':
52
+ return typeof value === 'number' && value <= Number(singleValue);
53
+ case 'in':
54
+ return values.includes(value);
55
+ case 'notIn':
56
+ return !values.includes(value);
57
+ case 'between':
58
+ return values.length === 2 && value >= values[0] && value <= values[1];
59
+ case 'notBetween':
60
+ return values.length === 2 && (value < values[0] || value > values[1]);
61
+ case 'isNull':
62
+ return value === null || value === undefined;
63
+ case 'isNotNull':
64
+ return value !== null && value !== undefined;
65
+ case 'true':
66
+ return value === true;
67
+ case 'false':
68
+ return value === false;
69
+ case 'matches':
70
+ return typeof value === 'string' && new RegExp(singleValue).test(value);
71
+ case 'notMatches':
72
+ return typeof value === 'string' && !new RegExp(singleValue).test(value);
73
+ default:
74
+ throw new Error(`Unsupported filter operator: ${operator}`);
75
+ }
76
+ };
77
+ const { and, or } = filter;
78
+ const baseResult = evaluate(item, filter);
79
+ if (and)
80
+ return baseResult && and.every(subFilter => this._evaluateFilter(item, subFilter));
81
+ if (or)
82
+ return baseResult || or.some(subFilter => this._evaluateFilter(item, subFilter));
83
+ else
84
+ return baseResult;
85
+ };
86
+ this._applyOrdering = (data, order) => {
87
+ return data.sort((a, b) => {
88
+ for (const [field, direction] of order) {
89
+ if (a[field] < b[field])
90
+ return direction === 'asc' ? -1 : 1;
91
+ if (a[field] > b[field])
92
+ return direction === 'asc' ? 1 : -1;
93
+ }
94
+ return 0;
95
+ });
96
+ };
97
+ }
98
+ }
99
+ const RequestExecutor = new RequestExecutorClass();
100
+ exports.default = RequestExecutor;