@forzalabs/remora 0.0.12 → 0.0.13
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/documentation/default_resources/consumer.json +1 -0
- package/documentation/default_resources/producer.json +1 -0
- package/documentation/default_resources/project.json +1 -0
- package/documentation/default_resources/schema.json +3 -3
- package/documentation/default_resources/source.json +1 -0
- package/drivers/RedshiftDriver.js +5 -3
- package/drivers/S3Driver.js +5 -2
- package/drivers/S3SourceDriver.js +5 -2
- package/engines/Environment.js +1 -3
- package/engines/ProducerEngine.js +2 -2
- package/engines/SecretManager.js +14 -0
- package/engines/Validator.js +6 -5
- package/engines/consumer/ConsumerEngine.js +12 -12
- package/engines/execution/ExecutionPlanner.js +1 -1
- package/engines/file/FileExporter.js +1 -1
- package/engines/sql/SQLCompiler.js +4 -4
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "
|
|
3
|
-
"title": "<schema
|
|
4
|
-
"type": "
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "<example schema>",
|
|
4
|
+
"type": "object",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"description": "<schema description>",
|
|
7
7
|
"properties": {
|
|
@@ -16,6 +16,7 @@ const client_redshift_data_1 = require("@aws-sdk/client-redshift-data");
|
|
|
16
16
|
const Affirm_1 = __importDefault(require("../core/Affirm"));
|
|
17
17
|
const Algo_1 = __importDefault(require("../core/Algo"));
|
|
18
18
|
const Environment_1 = __importDefault(require("../engines/Environment"));
|
|
19
|
+
const SecretManager_1 = __importDefault(require("../engines/SecretManager"));
|
|
19
20
|
class RedshiftDriver {
|
|
20
21
|
constructor() {
|
|
21
22
|
this.init = (source) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -23,12 +24,13 @@ class RedshiftDriver {
|
|
|
23
24
|
this._SQL_MAX_QUERY_ROWS = parseInt(Environment_1.default.get('SQL_MAX_QUERY_ROWS'));
|
|
24
25
|
this._dbName = source.authentication['database'];
|
|
25
26
|
this._workgroup = source.authentication['workgroup'];
|
|
27
|
+
const sessionToken = SecretManager_1.default.replaceSecret(source.authentication['sessionToken']);
|
|
26
28
|
const config = {
|
|
27
29
|
region: source.authentication['region'],
|
|
28
30
|
credentials: {
|
|
29
|
-
accessKeyId: source.authentication['accessKey'],
|
|
30
|
-
secretAccessKey: source.authentication['secretKey']
|
|
31
|
-
|
|
31
|
+
accessKeyId: SecretManager_1.default.replaceSecret(source.authentication['accessKey']),
|
|
32
|
+
secretAccessKey: SecretManager_1.default.replaceSecret(source.authentication['secretKey']),
|
|
33
|
+
sessionToken: sessionToken ? sessionToken : undefined
|
|
32
34
|
}
|
|
33
35
|
};
|
|
34
36
|
try {
|
package/drivers/S3Driver.js
CHANGED
|
@@ -14,15 +14,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
16
16
|
const Affirm_1 = __importDefault(require("../core/Affirm"));
|
|
17
|
+
const SecretManager_1 = __importDefault(require("../engines/SecretManager"));
|
|
17
18
|
class S3Driver {
|
|
18
19
|
constructor() {
|
|
19
20
|
this.init = (source) => __awaiter(this, void 0, void 0, function* () {
|
|
20
21
|
this._bucketName = source.authentication['bucketName'];
|
|
22
|
+
const sessionToken = SecretManager_1.default.replaceSecret(source.authentication['sessionToken']);
|
|
21
23
|
const config = {
|
|
22
24
|
region: source.authentication['region'],
|
|
23
25
|
credentials: {
|
|
24
|
-
accessKeyId: source.authentication['accessKey'],
|
|
25
|
-
secretAccessKey: source.authentication['secretKey']
|
|
26
|
+
accessKeyId: SecretManager_1.default.replaceSecret(source.authentication['accessKey']),
|
|
27
|
+
secretAccessKey: SecretManager_1.default.replaceSecret(source.authentication['secretKey']),
|
|
28
|
+
sessionToken: sessionToken ? sessionToken : undefined
|
|
26
29
|
}
|
|
27
30
|
};
|
|
28
31
|
this._client = new client_s3_1.S3Client(config);
|
|
@@ -22,15 +22,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
23
23
|
const readline_1 = __importDefault(require("readline"));
|
|
24
24
|
const Affirm_1 = __importDefault(require("../core/Affirm"));
|
|
25
|
+
const SecretManager_1 = __importDefault(require("../engines/SecretManager"));
|
|
25
26
|
class S3SourceDriver {
|
|
26
27
|
constructor() {
|
|
27
28
|
this.init = (source) => __awaiter(this, void 0, void 0, function* () {
|
|
28
29
|
this._bucketName = source.authentication['bucketName'];
|
|
30
|
+
const sessionToken = SecretManager_1.default.replaceSecret(source.authentication['sessionToken']);
|
|
29
31
|
const config = {
|
|
30
32
|
region: source.authentication['region'],
|
|
31
33
|
credentials: {
|
|
32
|
-
accessKeyId: source.authentication['accessKey'],
|
|
33
|
-
secretAccessKey: source.authentication['secretKey']
|
|
34
|
+
accessKeyId: SecretManager_1.default.replaceSecret(source.authentication['accessKey']),
|
|
35
|
+
secretAccessKey: SecretManager_1.default.replaceSecret(source.authentication['secretKey']),
|
|
36
|
+
sessionToken: sessionToken ? sessionToken : undefined
|
|
34
37
|
}
|
|
35
38
|
};
|
|
36
39
|
this._client = new client_s3_1.S3Client(config);
|
package/engines/Environment.js
CHANGED
|
@@ -73,9 +73,7 @@ class EnvironmentClass {
|
|
|
73
73
|
});
|
|
74
74
|
// Initialize environment
|
|
75
75
|
this.init({
|
|
76
|
-
settings: new Map(Object.entries({
|
|
77
|
-
SQL_MAX_QUERY_ROWS: projectConfig.settings.SQL_MAX_QUERY_ROWS.toString()
|
|
78
|
-
})),
|
|
76
|
+
settings: new Map(Object.entries(Object.assign({}, projectConfig.settings)).map(([key, value]) => [key, String(value)])),
|
|
79
77
|
sources,
|
|
80
78
|
producers,
|
|
81
79
|
consumers,
|
|
@@ -51,8 +51,8 @@ class ProducerEngineClass {
|
|
|
51
51
|
for (const planStep of plan) {
|
|
52
52
|
switch (planStep.type) {
|
|
53
53
|
case 'create-view': {
|
|
54
|
-
const internalSchema = Environment_1.default.get('
|
|
55
|
-
(0, Affirm_1.default)(internalSchema, `
|
|
54
|
+
const internalSchema = Environment_1.default.get('REMORA_SCHEMA');
|
|
55
|
+
(0, Affirm_1.default)(internalSchema, `Missing "REMORA_SCHEMA" on project settings (needed due to "${producer.name}" wanting to create a view)`);
|
|
56
56
|
const sql = SQLCompiler_1.default.compileProducer(producer, source);
|
|
57
57
|
const vSQL = `CREATE OR REPLACE VIEW "${internalSchema}"."${SQLUtils_1.default.viewName(producer.name)}" AS ${sql}`;
|
|
58
58
|
yield driver.execute(vSQL);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class SecretManagerClass {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.replaceSecret = (value) => {
|
|
6
|
+
if (!value || value.length <= 2 || !value.startsWith('{') || !value.endsWith('}'))
|
|
7
|
+
return value;
|
|
8
|
+
const parsedValue = value.slice(1, value.length - 1);
|
|
9
|
+
return process.env[parsedValue];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const SecretManager = new SecretManagerClass();
|
|
14
|
+
exports.default = SecretManager;
|
package/engines/Validator.js
CHANGED
|
@@ -121,7 +121,7 @@ class ValidatorClass {
|
|
|
121
121
|
const groupingFields = fields.filter(x => x.grouping);
|
|
122
122
|
if (groupingFields.length > 1)
|
|
123
123
|
errors.push(`There can't be 2 fields with grouping defined at the same level (${groupingFields.map(x => x.key).join(', ')}). Level: ${level}`);
|
|
124
|
-
groupingFields.forEach(field => {
|
|
124
|
+
groupingFields.forEach((field) => {
|
|
125
125
|
if (field.grouping)
|
|
126
126
|
errors = [...errors, ...validateGroupingLevels(field.grouping.subFields, level + 1)];
|
|
127
127
|
});
|
|
@@ -136,10 +136,11 @@ class ValidatorClass {
|
|
|
136
136
|
}
|
|
137
137
|
for (let i = 0; i < consumer.outputs.length; i++) {
|
|
138
138
|
const output = consumer.outputs[i];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
const format = output.format.toUpperCase();
|
|
140
|
+
if (format === 'SQL' && output.accellerated && output.direct)
|
|
141
|
+
errors.push(`An output SQL cannot be both direct and accelerated (output: ${format})`);
|
|
142
|
+
if ((format === 'CSV' || format === 'JSON' || format === 'PARQUET') && !output.exportDestination)
|
|
143
|
+
errors.push(`A static file output must have an export destination set (${format})`);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
catch (e) {
|
|
@@ -28,7 +28,7 @@ class ConsumerEngineClass {
|
|
|
28
28
|
this.compile = (consumer) => {
|
|
29
29
|
var _a, _b;
|
|
30
30
|
(0, Affirm_1.default)(consumer, `Invalid consumer`);
|
|
31
|
-
const availableColumns = consumer.producers.flatMap(
|
|
31
|
+
const availableColumns = consumer.producers.flatMap(cProd => {
|
|
32
32
|
var _a, _b;
|
|
33
33
|
const producer = Environment_1.default.getProducer(cProd.name);
|
|
34
34
|
if (!producer) {
|
|
@@ -63,7 +63,7 @@ class ConsumerEngineClass {
|
|
|
63
63
|
// TODO: replace with the new funcitons in the consumermanager to reduce diplicate code
|
|
64
64
|
if (field.key === '*') {
|
|
65
65
|
const from = (_a = field.from) !== null && _a !== void 0 ? _a : (consumer.producers.length === 1 ? consumer.producers[0].name : null);
|
|
66
|
-
availableColumns.filter(x => x.owner === from).forEach(
|
|
66
|
+
availableColumns.filter(x => x.owner === from).forEach(col => {
|
|
67
67
|
col.consumerKey = col.nameInProducer;
|
|
68
68
|
col.consumerAlias = col.nameInProducer;
|
|
69
69
|
selectedColumns.push(col);
|
|
@@ -104,8 +104,8 @@ class ConsumerEngineClass {
|
|
|
104
104
|
case 'create-materialized-view': {
|
|
105
105
|
const sql = SQLCompiler_1.default.compileConsumer(consumer);
|
|
106
106
|
(0, Affirm_1.default)(sql, `Invalid SQL from deployment compilation for consumer "${consumer.name}"`);
|
|
107
|
-
const internalSchema = Environment_1.default.get('
|
|
108
|
-
(0, Affirm_1.default)(internalSchema, `
|
|
107
|
+
const internalSchema = Environment_1.default.get('REMORA_SCHEMA');
|
|
108
|
+
(0, Affirm_1.default)(internalSchema, `Missing "REMORA_SCHEMA" on project settings (needed due to "${consumer.name}" wanting to create a view)`);
|
|
109
109
|
// TODO When I want to update a materialize view there is no way except killing it and recreating it. The problem is that: 1) it is not said that it can be deleted since that materialize view could have some dependencies 2) we should find a way to update it without it going completely offline.
|
|
110
110
|
const mvSQL = `
|
|
111
111
|
DROP MATERIALIZED VIEW IF EXISTS "${internalSchema}"."${SQLUtils_1.default.acceleratedViewName(consumer.name)}";
|
|
@@ -116,8 +116,8 @@ class ConsumerEngineClass {
|
|
|
116
116
|
case 'create-view': {
|
|
117
117
|
const sql = SQLCompiler_1.default.compileConsumer(consumer);
|
|
118
118
|
(0, Affirm_1.default)(sql, `Invalid SQL from deployment compilation for consumer "${consumer.name}"`);
|
|
119
|
-
const internalSchema = Environment_1.default.get('
|
|
120
|
-
(0, Affirm_1.default)(internalSchema, `
|
|
119
|
+
const internalSchema = Environment_1.default.get('REMORA_SCHEMA');
|
|
120
|
+
(0, Affirm_1.default)(internalSchema, `Missing "REMORA_SCHEMA" on project settings (needed due to "${consumer.name}" wanting to create a view)`);
|
|
121
121
|
const vSQL = `CREATE OR REPLACE VIEW "${internalSchema}"."${SQLUtils_1.default.sanitizeName(consumer.name)}" AS ${sql}`;
|
|
122
122
|
yield driver.execute(vSQL);
|
|
123
123
|
break;
|
|
@@ -145,14 +145,14 @@ class ConsumerEngineClass {
|
|
|
145
145
|
(0, Affirm_1.default)(consumer, `Invalid consumer`);
|
|
146
146
|
const compiled = this.compile(consumer);
|
|
147
147
|
const outDimensions = compiled.map(x => {
|
|
148
|
-
var _a;
|
|
148
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
149
149
|
return ({
|
|
150
150
|
name: (_a = x.consumerAlias) !== null && _a !== void 0 ? _a : x.consumerKey,
|
|
151
|
-
type: x.dimension.type,
|
|
152
|
-
classification: x.dimension.classification,
|
|
153
|
-
description: x.dimension.description,
|
|
154
|
-
mask: x.dimension.mask,
|
|
155
|
-
pk: x.dimension.pk
|
|
151
|
+
type: (_b = x.dimension) === null || _b === void 0 ? void 0 : _b.type,
|
|
152
|
+
classification: (_c = x.dimension) === null || _c === void 0 ? void 0 : _c.classification,
|
|
153
|
+
description: (_e = (_d = x.dimension) === null || _d === void 0 ? void 0 : _d.description) !== null && _e !== void 0 ? _e : (_f = x.measure) === null || _f === void 0 ? void 0 : _f.description,
|
|
154
|
+
mask: (_g = x.dimension) === null || _g === void 0 ? void 0 : _g.mask,
|
|
155
|
+
pk: (_h = x.dimension) === null || _h === void 0 ? void 0 : _h.pk
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
158
|
return {
|
|
@@ -56,7 +56,7 @@ class ExecutionPlannerClas {
|
|
|
56
56
|
// TODO: how to handle pagination of SQL results?
|
|
57
57
|
const engineClass = this.getEngineClass(producerEngine);
|
|
58
58
|
for (const output of consumer.outputs) {
|
|
59
|
-
switch (output.format) {
|
|
59
|
+
switch (output.format.toUpperCase()) {
|
|
60
60
|
case 'JSON': {
|
|
61
61
|
if (engineClass === 'file' && Algo_1.default.hasVal(options))
|
|
62
62
|
plan.push({ type: 'apply-execution-request-to-result' });
|
|
@@ -60,7 +60,7 @@ class FileExporterClass {
|
|
|
60
60
|
let exportData = null;
|
|
61
61
|
let extension = null;
|
|
62
62
|
// build the actual file in the requested format
|
|
63
|
-
switch (output.format) {
|
|
63
|
+
switch (output.format.toUpperCase()) {
|
|
64
64
|
case 'CSV': {
|
|
65
65
|
const lines = [];
|
|
66
66
|
const keys = Object.keys(data[0]);
|
|
@@ -53,8 +53,8 @@ class SQLCompilerClass {
|
|
|
53
53
|
return sql;
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
|
-
const internalSchema = Environment_1.default.get('
|
|
57
|
-
(0, Affirm_1.default)(internalSchema, `
|
|
56
|
+
const internalSchema = Environment_1.default.get('REMORA_SCHEMA');
|
|
57
|
+
(0, Affirm_1.default)(internalSchema, `Missing "REMORA_SCHEMA" on project settings (needed due to "${producer.name}" wanting to create a view)`);
|
|
58
58
|
return `CREATE OR REPLACE VIEW "${internalSchema}"."${producer.name}" AS ${sql}`;
|
|
59
59
|
}
|
|
60
60
|
};
|
|
@@ -70,8 +70,8 @@ class SQLCompilerClass {
|
|
|
70
70
|
return this.compileProducer(producer, source);
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
const internalSchema = Environment_1.default.get('
|
|
74
|
-
(0, Affirm_1.default)(internalSchema, `
|
|
73
|
+
const internalSchema = Environment_1.default.get('REMORA_SCHEMA');
|
|
74
|
+
(0, Affirm_1.default)(internalSchema, `Missing "REMORA_SCHEMA" on project settings (needed due to "${producer.name}" wanting to create a view)`);
|
|
75
75
|
return `SELECT * FROM "${internalSchema}"."${producer.name}"`;
|
|
76
76
|
}
|
|
77
77
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forzalabs/remora",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.13",
|
|
4
|
+
"description": "A powerful CLI tool for seamless data translation.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"bin": {
|