@forzalabs/remora 0.0.44-nasco.3 → 0.0.46-nasco.3

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/Constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const CONSTANTS = {
4
- cliVersion: '0.0.40-nasco',
4
+ cliVersion: '0.0.46-nasco',
5
5
  lambdaVersion: 1,
6
6
  port: 5069,
7
7
  defaults: {
@@ -173,6 +173,10 @@
173
173
  },
174
174
  "default": {
175
175
  "description": "Default value of the field if it is missing (or on error if specified)"
176
+ },
177
+ "hidden": {
178
+ "type": "boolean",
179
+ "description": "If set, the field is kept and used during processing, but omitted when exporting the data"
176
180
  }
177
181
  },
178
182
  "required": [
@@ -428,6 +432,10 @@
428
432
  },
429
433
  "default": {
430
434
  "description": "Default value of the field if it is missing (or on error if specified)"
435
+ },
436
+ "hidden": {
437
+ "type": "boolean",
438
+ "description": "If set, the field is kept and used during processing, but omitted when exporting the data"
431
439
  }
432
440
  },
433
441
  "required": [
@@ -732,6 +740,36 @@
732
740
  },
733
741
  "required": ["append"],
734
742
  "additionalProperties": false
743
+ },
744
+ {
745
+ "type": "object",
746
+ "properties": {
747
+ "combine_fields": {
748
+ "type": "object",
749
+ "properties": {
750
+ "fields": {
751
+ "type": "array",
752
+ "items": {
753
+ "type": "string"
754
+ },
755
+ "description": "Array of field names to combine",
756
+ "minItems": 1
757
+ },
758
+ "separator": {
759
+ "type": "string",
760
+ "description": "Optional separator between fields (default: empty string)"
761
+ },
762
+ "template": {
763
+ "type": "string",
764
+ "description": "Optional template string with placeholders like '{field1} - {field2}'"
765
+ }
766
+ },
767
+ "required": ["fields"],
768
+ "additionalProperties": false
769
+ }
770
+ },
771
+ "required": ["combine_fields"],
772
+ "additionalProperties": false
735
773
  }
736
774
  ]
737
775
  }
@@ -70,7 +70,8 @@
70
70
  "mask",
71
71
  "crypt",
72
72
  "random",
73
- "seeded-random"
73
+ "seeded-random",
74
+ "none"
74
75
  ],
75
76
  "description": "Masking type to apply to this dimension. 'hash' replaces with a hashed value. 'mask' replaces characters with a mask character. 'crypt' encrypts the value. 'random' replaces with a random value. 'seeded-random' replaces with a random value generated from a seed. You can use environment variables by using the {your-env-var} notation",
76
77
  "examples": [
@@ -18,6 +18,8 @@ class CryptoEngineClass {
18
18
  throw new Error('Not implemented yet');
19
19
  case 'mask':
20
20
  throw new Error('Not implemented yet');
21
+ case 'none':
22
+ return `${fieldReference} AS "${fieldName}"`;
21
23
  default:
22
24
  throw new Error('This type is not ');
23
25
  }
@@ -55,6 +57,8 @@ class CryptoEngineClass {
55
57
  throw new Error('Not implemented yet');
56
58
  case 'mask':
57
59
  throw new Error('Not implemented yet');
60
+ case 'none':
61
+ return value;
58
62
  default:
59
63
  throw new Error(`This type doesn't exist`);
60
64
  }
@@ -34,7 +34,7 @@ class PostProcessorClass {
34
34
  (0, Affirm_1.default)(dataset, 'Invalid dataset');
35
35
  const fields = ConsumerManager_1.default.getExpandedFields(consumer);
36
36
  let newDataset = yield this.dropDimensions(dataset, consumer);
37
- newDataset = this.renameDimensions(newDataset, consumer);
37
+ newDataset = this.updateDimensions(newDataset, consumer);
38
38
  newDataset = yield this.reorderDimensions(newDataset, consumer);
39
39
  newDataset = yield newDataset.map(record => {
40
40
  var _a, _b;
@@ -53,16 +53,15 @@ class PostProcessorClass {
53
53
  });
54
54
  return newDataset;
55
55
  });
56
- this.renameDimensions = (dataset, consumer) => {
56
+ this.updateDimensions = (dataset, consumer) => {
57
57
  const dimensions = dataset.getDimensions();
58
58
  const fields = ConsumerManager_1.default.getExpandedFields(consumer);
59
59
  for (const dim of dimensions) {
60
60
  // This dimension is wanted by the consumer, check if it needs renaming
61
61
  const consumerField = fields.find(x => x.cField.key === dim.name);
62
62
  if (consumerField) {
63
- const { cField: { key, alias } } = consumerField;
64
- if (key !== alias && Algo_1.default.hasVal(alias) && alias.length > 0)
65
- dataset.renameDimension(key, alias);
63
+ const { cField: { key, alias, hidden } } = consumerField;
64
+ dataset.updateDimension(key, { name: alias, hidden: hidden });
66
65
  }
67
66
  }
68
67
  return dataset;
@@ -206,7 +205,8 @@ class PostProcessorClass {
206
205
  const newDimensions = expectedFieldNames.map((key, index) => ({
207
206
  name: key,
208
207
  key: key,
209
- index: index
208
+ index: index,
209
+ hidden: null
210
210
  }));
211
211
  // Create the row string
212
212
  const values = newDimensions.map(dim => {
@@ -227,7 +227,8 @@ class PostProcessorClass {
227
227
  const newDimensions = columns.map((col, index) => ({
228
228
  name: col.nameInProducer,
229
229
  key: col.nameInProducer,
230
- index: index
230
+ index: index,
231
+ hidden: null
231
232
  }));
232
233
  // Update the dataset dimensions
233
234
  resDataset['_dimensions'] = newDimensions;
@@ -678,12 +678,17 @@ class Dataset {
678
678
  return this;
679
679
  });
680
680
  this.getDimensions = () => this._dimensions;
681
- this.renameDimension = (currentName, newName) => {
682
- const dimension = this._dimensions.find(x => x.name === currentName);
683
- (0, Affirm_1.default)(dimension, `Trying to rename the dataset dimension "${currentName}", but none was found (${this._dimensions.map(x => x.name).join(', ')})`);
684
- this._startOperation('rename-dimension');
685
- dimension.name = newName;
686
- this._finishOperation('rename-dimension');
681
+ this.updateDimension = (dimensionName, newValues) => {
682
+ const dimension = this._dimensions.find(x => x.name === dimensionName);
683
+ (0, Affirm_1.default)(dimension, `Trying to update the dataset dimension "${dimensionName}", but none was found (${this._dimensions.map(x => x.name).join(', ')})`);
684
+ this._startOperation('update-dimension');
685
+ const { hidden, name } = newValues;
686
+ if (name && name.length > 0)
687
+ dimension.name = name;
688
+ if (hidden)
689
+ dimension.hidden = hidden;
690
+ this._finishOperation('update-dimension');
691
+ return this;
687
692
  };
688
693
  this.dropDimensions = (dimensionNames) => __awaiter(this, void 0, void 0, function* () {
689
694
  if (dimensionNames.length === 0)
@@ -49,7 +49,7 @@ class DatasetManagerClass {
49
49
  const headerLine = firstLine;
50
50
  const rawDimensions = ParseManager_1.default._extractHeader(headerLine, delimiterChar, producer, discover);
51
51
  return {
52
- dimensions: rawDimensions.map(x => ({ key: x.name, name: x.saveAs, index: x.index })),
52
+ dimensions: rawDimensions.map(x => ({ key: x.name, name: x.saveAs, index: x.index, hidden: null })),
53
53
  delimiter: delimiterChar
54
54
  };
55
55
  }
@@ -64,7 +64,7 @@ class DatasetManagerClass {
64
64
  const columnKey = (_b = pColumn.aliasInProducer) !== null && _b !== void 0 ? _b : pColumn.nameInProducer;
65
65
  const csvColumnIndex = keys.findIndex(x => x === columnKey);
66
66
  (0, Affirm_1.default)(csvColumnIndex > -1, `The column "${pColumn.nameInProducer}" (with key "${columnKey}") of producer "${producer.name}" doesn't exist in the underlying dataset.`);
67
- dimensions.push({ index: csvColumnIndex, key: columnKey, name: pColumn.nameInProducer });
67
+ dimensions.push({ index: csvColumnIndex, key: columnKey, name: pColumn.nameInProducer, hidden: null });
68
68
  }
69
69
  const delimiterChar = (_c = file.delimiter) !== null && _c !== void 0 ? _c : ',';
70
70
  return { dimensions, delimiter: delimiterChar };
@@ -76,7 +76,7 @@ class DatasetManagerClass {
76
76
  const source = Environment_1.default.getSource(producer.source);
77
77
  const columns = FileCompiler_1.default.compileProducer(producer, source);
78
78
  return {
79
- dimensions: columns.map((x, i) => { var _a; return ({ key: (_a = x.aliasInProducer) !== null && _a !== void 0 ? _a : x.nameInProducer, name: x.nameInProducer, index: i }); }),
79
+ dimensions: columns.map((x, i) => { var _a; return ({ key: (_a = x.aliasInProducer) !== null && _a !== void 0 ? _a : x.nameInProducer, name: x.nameInProducer, index: i, hidden: null }); }),
80
80
  delimiter: delimiterChar
81
81
  };
82
82
  }
@@ -84,7 +84,7 @@ class DatasetManagerClass {
84
84
  const delimiterChar = (_e = producer.settings.delimiter) !== null && _e !== void 0 ? _e : ',';
85
85
  const rawDimensions = ParseManager_1.default._extractHeader(firstLine, delimiterChar, producer, discover);
86
86
  return {
87
- dimensions: rawDimensions.map(x => ({ key: x.name, name: x.saveAs, index: x.index })),
87
+ dimensions: rawDimensions.map(x => ({ key: x.name, name: x.saveAs, index: x.index, hidden: null })),
88
88
  delimiter: delimiterChar
89
89
  };
90
90
  }
@@ -46,10 +46,25 @@ class DatasetRecord {
46
46
  this._value = newValue;
47
47
  return this;
48
48
  };
49
- this.toJSON = () => JSON.stringify(this._value);
49
+ this.toJSON = () => {
50
+ if (this._dimensions.some(x => x.hidden)) {
51
+ // remove the not wanted dimension
52
+ const clonedValue = structuredClone(this._value);
53
+ for (const dim of this._dimensions) {
54
+ if (dim.hidden)
55
+ delete clonedValue[dim.name];
56
+ }
57
+ return JSON.stringify(clonedValue);
58
+ }
59
+ else {
60
+ return JSON.stringify(this._value);
61
+ }
62
+ };
50
63
  this.toCSV = (delimiter) => {
51
64
  const myDelimtier = delimiter !== null && delimiter !== void 0 ? delimiter : this._delimiter;
65
+ // remove the not wanted dimension
52
66
  const line = this._dimensions
67
+ .filter(x => !x.hidden)
53
68
  .map(x => { var _a, _b; return `"${Algo_1.default.replaceAll((_b = (_a = this._value[x.name]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '', '"', '""')}"`; })
54
69
  .join(myDelimtier);
55
70
  return line;
@@ -123,6 +123,7 @@ class ExecutionEnvironment {
123
123
  }
124
124
  case 'export-file': {
125
125
  (0, Affirm_1.default)(planStep.output, `Invalid output in export-file step`);
126
+ (0, Affirm_1.default)(this._resultingDataset, 'Invalid resulting dataset in export-file step');
126
127
  const res = yield FileExporter_1.default.export(this._consumer, planStep.output, this._resultingDataset);
127
128
  result.fileUri = res;
128
129
  break;
@@ -21,7 +21,6 @@ class RequestExecutorClass {
21
21
  this.execute = (dataset, request) => __awaiter(this, void 0, void 0, function* () {
22
22
  (0, Affirm_1.default)(dataset, 'Invalid data');
23
23
  (0, Affirm_1.default)(request, 'Invalid request');
24
- (0, Affirm_1.default)(Array.isArray(dataset), `Invalid data type: should be an array`);
25
24
  if (request.filters)
26
25
  dataset = yield this.applyFilters(dataset, request.filters);
27
26
  if (request.order)
@@ -16,7 +16,8 @@ class ProducerManagerClass {
16
16
  'mask',
17
17
  'crypt',
18
18
  'random',
19
- 'seeded-random'
19
+ 'seeded-random',
20
+ 'none'
20
21
  ];
21
22
  (0, Affirm_1.default)(allowedMaskValues.includes(mask), `Mask value "${mask}" in dimension "${dimension.name}" is not a valid value.`);
22
23
  return mask;
@@ -103,7 +103,8 @@ class JoinEngineClass {
103
103
  const resultDimensions = consumerColumns.map((col, index) => ({
104
104
  name: col.consumerAlias || col.consumerKey,
105
105
  key: col.consumerAlias || col.consumerKey,
106
- index
106
+ index,
107
+ hidden: null
107
108
  }));
108
109
  // Initialize the result dataset with proper dimensions
109
110
  resultDataset.getDimensions().length = 0;
@@ -31,10 +31,13 @@ class TransformationEngineClass {
31
31
  const value = record.getValue(fieldKey);
32
32
  if (!Algo_1.default.hasVal(value) && Algo_1.default.hasVal(field.default))
33
33
  record.setValue(fieldKey, field.default);
34
+ else if (!Algo_1.default.hasVal(value) && this.isFieldCombinationTransformation(field.transform))
35
+ // For field combination transformations, we don't skip null values as they might combine with other fields
36
+ continue;
34
37
  else if (!Algo_1.default.hasVal(value))
35
38
  continue;
36
39
  try {
37
- record.setValue(fieldKey, this.applyTransformations(value, field.transform, field));
40
+ record.setValue(fieldKey, this.applyTransformations(value, field.transform, field, record));
38
41
  }
39
42
  catch (error) {
40
43
  switch (field.onError) {
@@ -52,13 +55,19 @@ class TransformationEngineClass {
52
55
  return record;
53
56
  });
54
57
  });
55
- this.applyTransformations = (value, transformations, field) => {
58
+ this.isFieldCombinationTransformation = (transformation) => {
59
+ if (Array.isArray(transformation)) {
60
+ return transformation.some(t => this.isFieldCombinationTransformation(t));
61
+ }
62
+ return 'combine_fields' in transformation;
63
+ };
64
+ this.applyTransformations = (value, transformations, field, record) => {
56
65
  var _a;
57
66
  if (Array.isArray(transformations)) {
58
67
  // Process array transformations without creating intermediate arrays
59
68
  let result = value;
60
69
  for (const transform of transformations) {
61
- result = this.applyTransformations(result, transform, field);
70
+ result = this.applyTransformations(result, transform, field, record);
62
71
  }
63
72
  return result;
64
73
  }
@@ -190,6 +199,30 @@ class TransformationEngineClass {
190
199
  return transformations.prepend + TypeCaster_1.default.cast(value, 'string');
191
200
  if ('append' in transformations)
192
201
  return TypeCaster_1.default.cast(value, 'string') + transformations.append;
202
+ if ('combine_fields' in transformations) {
203
+ if (!record) {
204
+ throw new Error(`Cannot apply combine_fields transformation without record context in field '${field.key}'`);
205
+ }
206
+ const { fields, separator = '', template } = transformations.combine_fields;
207
+ // Get values from the specified fields
208
+ const fieldValues = fields.map(fieldName => {
209
+ const fieldValue = record.getValue(fieldName);
210
+ return fieldValue !== null && fieldValue !== undefined ? String(fieldValue) : '';
211
+ });
212
+ // If template is provided, use it for formatting
213
+ if (template) {
214
+ let result = template;
215
+ for (let i = 0; i < fields.length; i++) {
216
+ const placeholder = `{${fields[i]}}`;
217
+ result = result.replace(new RegExp(placeholder, 'g'), fieldValues[i]);
218
+ }
219
+ return result;
220
+ }
221
+ else {
222
+ // Otherwise, join with separator
223
+ return fieldValues.join(separator);
224
+ }
225
+ }
193
226
  if ('conditional' in transformations) {
194
227
  for (const clause of transformations.conditional.clauses) {
195
228
  if (this.evaluateCondition(value, clause.if)) {
@@ -138,6 +138,7 @@ class ValidatorClass {
138
138
  return errors;
139
139
  };
140
140
  const validateTransformations = (fields) => {
141
+ var _a;
141
142
  const errors = [];
142
143
  const trxsFields = fields.filter(x => x.transform);
143
144
  for (const field of trxsFields) {
@@ -149,7 +150,17 @@ class ValidatorClass {
149
150
  for (const trans of trxToValidate) {
150
151
  const trxKeys = Object.keys(trans);
151
152
  if (trxKeys.length !== 1)
152
- errors.push(`There can only be 1 transformation type in your transformation pipeline. Field "${field.key}" got ${trxKeys.length}`);
153
+ errors.push(`There can only be 1 transformation type in your transformation pipeline. Field "${field.key}" got ${trxKeys.length}.`);
154
+ if ('combine_fields' in trans) {
155
+ const { combine_fields } = trans;
156
+ if (!combine_fields.fields || combine_fields.fields.length === 0)
157
+ errors.push(`The "combine_field" transformation is missing the "fields" property ("${field.key}").`);
158
+ const missingFieldsInConsumer = combine_fields.fields
159
+ .map(x => ({ field: x, found: fields.find(k => { var _a; return ((_a = k.alias) !== null && _a !== void 0 ? _a : k.key) === x; }) }))
160
+ .filter(x => !x.found);
161
+ if (missingFieldsInConsumer.length > 0)
162
+ 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}";`);
163
+ }
153
164
  }
154
165
  }
155
166
  return errors;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forzalabs/remora",
3
- "version": "0.0.44-nasco.3",
3
+ "version": "0.0.46-nasco.3",
4
4
  "description": "A powerful CLI tool for seamless data translation.",
5
5
  "main": "index.js",
6
6
  "private": false,