@forzalabs/remora 0.0.30 ā 0.0.31-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/LICENCE.md +31 -0
- package/README.md +62 -0
- package/drivers/LocalDriver.js +86 -28
- package/drivers/S3Driver.js +121 -45
- package/engines/ProducerEngine.js +3 -0
- package/helper/Helper.js +55 -0
- package/index.js +1 -8
- package/package.json +4 -5
- package/actions/automap.js +0 -77
package/LICENCE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Forza Labs LLC
|
|
6
|
+
|
|
7
|
+
Licensed Work: Remora
|
|
8
|
+
|
|
9
|
+
Additional Use Grant: You may make use of the Licensed Work for any non-commercial purpose, including personal, academic, research, or open source projects.
|
|
10
|
+
|
|
11
|
+
Change Date: 02/07/2025
|
|
12
|
+
|
|
13
|
+
Terms
|
|
14
|
+
|
|
15
|
+
The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make other use of the Licensed Work. The rights granted are subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
1. **Commercial Use**: Any use of the Licensed Work for or on behalf of a company, organization, or any commercial purpose requires a separate commercial license from the Licensor.
|
|
18
|
+
|
|
19
|
+
2. **Non-Commercial Use**: Non-commercial users may freely use, modify, and distribute the Licensed Work, provided that such use is not intended for commercial advantage or monetary compensation.
|
|
20
|
+
|
|
21
|
+
3. **Change Date**: On the Change Date, the terms of this license will change to the Change License specified above.
|
|
22
|
+
|
|
23
|
+
4. **Other Conditions**:
|
|
24
|
+
- This license does not grant trademark rights.
|
|
25
|
+
- You must include this license text with any distribution of the Licensed Work.
|
|
26
|
+
- You may not remove or modify this license notice.
|
|
27
|
+
|
|
28
|
+
Use Limitation: If your usage does not comply with the above terms (e.g., if it's for commercial purposes), you must contact the Licensor to purchase a commercial license.
|
|
29
|
+
|
|
30
|
+
To inquire about commercial licensing, contact: admin@forza-labs.com
|
|
31
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# š Remora CLI
|
|
2
|
+
|
|
3
|
+
A powerful CLI tool for seamless data translation and processing.
|
|
4
|
+
|
|
5
|
+
## š Overview
|
|
6
|
+
|
|
7
|
+
Remora is a comprehensive data processing CLI that enables efficient data transformation, translation, and management across various formats and sources. Whether you're working with CSV, JSON, XML, or other data formats, Remora provides the tools you need to process and transform your data with ease.
|
|
8
|
+
|
|
9
|
+
## š Features
|
|
10
|
+
|
|
11
|
+
- **Data Translation**: Convert between different data formats seamlessly
|
|
12
|
+
- **Multiple Data Sources**: Support for local files, S3, databases, and more
|
|
13
|
+
- **Flexible Processing**: Configure producers and consumers for custom data pipelines
|
|
14
|
+
- **Schema Validation**: Ensure data integrity with built-in schema validation
|
|
15
|
+
- **CLI Interface**: Easy-to-use command-line interface for all operations
|
|
16
|
+
|
|
17
|
+
## š¦ Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @forzalabs/remora
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## š ļø Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Initialize a new Remora project
|
|
27
|
+
remora init
|
|
28
|
+
|
|
29
|
+
# Run data processing
|
|
30
|
+
remora run
|
|
31
|
+
|
|
32
|
+
# Discover data sources
|
|
33
|
+
remora discover
|
|
34
|
+
|
|
35
|
+
# Compile configurations
|
|
36
|
+
remora compile
|
|
37
|
+
|
|
38
|
+
# Deploy to target environment
|
|
39
|
+
remora deploy
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## š·ļø NASCO Version
|
|
43
|
+
|
|
44
|
+
> **Important**: This specific version is for NASCO and has all AI features completely removed.
|
|
45
|
+
|
|
46
|
+
To manage different versions, we maintain a dedicated branch (called 'nasco') with a specific version of the package, which is the normal version with appended '-nasco.x'.
|
|
47
|
+
|
|
48
|
+
**Example**: `1.0.0-nasco.1`
|
|
49
|
+
|
|
50
|
+
**Publishing**: When publishing to npm, you must include the tag 'nasco' so the main version of the package is not overwritten:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm publish --tag nasco
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## š License
|
|
57
|
+
|
|
58
|
+
BSL
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
Built with ā¤ļø by Forza Labs
|
package/drivers/LocalDriver.js
CHANGED
|
@@ -60,6 +60,7 @@ const Affirm_1 = __importDefault(require("../core/Affirm"));
|
|
|
60
60
|
const Algo_1 = __importDefault(require("../core/Algo"));
|
|
61
61
|
const xlsx_1 = __importDefault(require("xlsx"));
|
|
62
62
|
const XMLParser_1 = __importDefault(require("../engines/parsing/XMLParser")); // Added XMLParser import
|
|
63
|
+
const Helper_1 = __importDefault(require("../helper/Helper"));
|
|
63
64
|
class LocalSourceDriver {
|
|
64
65
|
constructor() {
|
|
65
66
|
this.init = (source) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -75,19 +76,15 @@ class LocalSourceDriver {
|
|
|
75
76
|
(0, Affirm_1.default)(request, `Invalid download request`);
|
|
76
77
|
(0, Affirm_1.default)(request.fileKey, `Invalid file key for download request`);
|
|
77
78
|
(0, Affirm_1.default)(request.fileType, `Invalid file type for download request`);
|
|
78
|
-
const { fileKey
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
case 'XLSX':
|
|
88
|
-
return yield this._readExcelLines(fileUrl, options === null || options === void 0 ? void 0 : options.sheetName);
|
|
89
|
-
case 'XML':
|
|
90
|
-
return yield this._readXmlLines(fileUrl);
|
|
79
|
+
const { fileKey } = request;
|
|
80
|
+
if (fileKey.includes('%')) {
|
|
81
|
+
const allFileKeys = this.listFiles(fileKey);
|
|
82
|
+
const promises = allFileKeys.map(x => this._get(Object.assign(Object.assign({}, request), { fileKey: x })));
|
|
83
|
+
const results = yield Promise.all(promises);
|
|
84
|
+
return results.flat();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return yield this._get(request);
|
|
91
88
|
}
|
|
92
89
|
});
|
|
93
90
|
this.readLinesInRange = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -98,19 +95,15 @@ class LocalSourceDriver {
|
|
|
98
95
|
(0, Affirm_1.default)(request.options, `Invalid request options`);
|
|
99
96
|
Affirm_1.default.hasValue(request.options.lineFrom, `Invalid request options line from`);
|
|
100
97
|
Affirm_1.default.hasValue(request.options.lineTo, `Invalid request options line to`);
|
|
101
|
-
const { fileKey
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
case 'XLSX':
|
|
111
|
-
return yield this._readExcelLines(fileUrl, sheetName, lineFrom, lineTo);
|
|
112
|
-
case 'XML':
|
|
113
|
-
return yield this._readXmlLines(fileUrl, lineFrom, lineTo);
|
|
98
|
+
const { fileKey } = request;
|
|
99
|
+
if (fileKey.includes('%')) {
|
|
100
|
+
const allFileKeys = this.listFiles(fileKey);
|
|
101
|
+
const promises = allFileKeys.map(x => this._get(Object.assign(Object.assign({}, request), { fileKey: x })));
|
|
102
|
+
const results = yield Promise.all(promises);
|
|
103
|
+
return results.flat();
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return yield this._get(request);
|
|
114
107
|
}
|
|
115
108
|
});
|
|
116
109
|
this.exist = (producer) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -118,8 +111,14 @@ class LocalSourceDriver {
|
|
|
118
111
|
(0, Affirm_1.default)(producer, `Invalid producer`);
|
|
119
112
|
const fileKey = producer.settings.fileKey;
|
|
120
113
|
(0, Affirm_1.default)(fileKey, `Invalid file key for download request`);
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
if (fileKey.includes('%')) {
|
|
115
|
+
const allFileKeys = this.listFiles(fileKey);
|
|
116
|
+
return allFileKeys.length > 0;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const fileUrl = path_1.default.join(this._path, fileKey);
|
|
120
|
+
return fs.existsSync(fileUrl);
|
|
121
|
+
}
|
|
123
122
|
});
|
|
124
123
|
this._readLines = (fileUri, lineFrom, lineTo) => __awaiter(this, void 0, void 0, function* () {
|
|
125
124
|
var _a, e_1, _b, _c;
|
|
@@ -185,6 +184,65 @@ class LocalSourceDriver {
|
|
|
185
184
|
}
|
|
186
185
|
return lines;
|
|
187
186
|
});
|
|
187
|
+
this._get = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
188
|
+
const { fileKey, fileType, options } = request;
|
|
189
|
+
let lineFrom, lineTo, sheetName;
|
|
190
|
+
if (options) {
|
|
191
|
+
lineFrom = options.lineFrom;
|
|
192
|
+
lineTo = options.lineTo;
|
|
193
|
+
sheetName = options.sheetName;
|
|
194
|
+
}
|
|
195
|
+
const fileUrl = path_1.default.join(this._path, fileKey);
|
|
196
|
+
switch (fileType) {
|
|
197
|
+
case 'CSV':
|
|
198
|
+
case 'JSON':
|
|
199
|
+
case 'JSONL':
|
|
200
|
+
case 'TXT':
|
|
201
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
202
|
+
return yield this._readLines(fileUrl, lineFrom, lineTo);
|
|
203
|
+
else
|
|
204
|
+
return yield this._readLines(fileUrl);
|
|
205
|
+
case 'XLS':
|
|
206
|
+
case 'XLSX':
|
|
207
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
208
|
+
return yield this._readExcelLines(fileUrl, sheetName, lineFrom, lineTo);
|
|
209
|
+
else
|
|
210
|
+
return yield this._readExcelLines(fileUrl, sheetName);
|
|
211
|
+
case 'XML':
|
|
212
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
213
|
+
return yield this._readXmlLines(fileUrl, lineFrom, lineTo);
|
|
214
|
+
else
|
|
215
|
+
return yield this._readXmlLines(fileUrl);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
this.listFiles = (filekeyPattern) => {
|
|
219
|
+
(0, Affirm_1.default)(this._path, 'Path not initialized');
|
|
220
|
+
try {
|
|
221
|
+
// Get all files in the directory (recursively if needed)
|
|
222
|
+
const getAllFiles = (dirPath, basePath = '') => {
|
|
223
|
+
const files = [];
|
|
224
|
+
const items = fs.readdirSync(dirPath);
|
|
225
|
+
for (const item of items) {
|
|
226
|
+
const fullPath = path_1.default.join(dirPath, item);
|
|
227
|
+
const relativePath = basePath ? path_1.default.join(basePath, item) : item;
|
|
228
|
+
const stats = fs.statSync(fullPath);
|
|
229
|
+
if (stats.isDirectory()) {
|
|
230
|
+
// Recursively get files from subdirectories
|
|
231
|
+
files.push(...getAllFiles(fullPath, relativePath));
|
|
232
|
+
}
|
|
233
|
+
else if (stats.isFile()) {
|
|
234
|
+
files.push(relativePath);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return files;
|
|
238
|
+
};
|
|
239
|
+
const allFiles = getAllFiles(this._path);
|
|
240
|
+
return Helper_1.default.matchPattern(filekeyPattern, allFiles);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new Error(`Failed to list files in directory "${this._path}": ${error.message}`);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
188
246
|
}
|
|
189
247
|
}
|
|
190
248
|
exports.LocalSourceDriver = LocalSourceDriver;
|
package/drivers/S3Driver.js
CHANGED
|
@@ -27,6 +27,7 @@ const readline_1 = __importDefault(require("readline"));
|
|
|
27
27
|
const Algo_1 = __importDefault(require("../core/Algo"));
|
|
28
28
|
const xlsx_1 = __importDefault(require("xlsx"));
|
|
29
29
|
const XMLParser_1 = __importDefault(require("../engines/parsing/XMLParser")); // Added XMLParser import
|
|
30
|
+
const Helper_1 = __importDefault(require("../helper/Helper"));
|
|
30
31
|
class S3DestinationDriver {
|
|
31
32
|
constructor() {
|
|
32
33
|
this.init = (source) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -133,50 +134,31 @@ class S3SourceDriver {
|
|
|
133
134
|
(0, Affirm_1.default)(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
134
135
|
(0, Affirm_1.default)(request, `Invalid download request`);
|
|
135
136
|
(0, Affirm_1.default)(request.fileKey, `Invalid file key for download request`);
|
|
136
|
-
const { fileKey
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
case 'JSON':
|
|
147
|
-
case 'JSONL':
|
|
148
|
-
case 'TXT':
|
|
149
|
-
return yield this._readLines(stream);
|
|
150
|
-
case 'XLS':
|
|
151
|
-
case 'XLSX':
|
|
152
|
-
return yield this._readExcelLines(stream, options === null || options === void 0 ? void 0 : options.sheetName);
|
|
153
|
-
case 'XML':
|
|
154
|
-
return yield this._readXmlLines(stream);
|
|
137
|
+
const { fileKey } = request;
|
|
138
|
+
if (fileKey.includes('%')) {
|
|
139
|
+
const allFileKeys = yield this.listFiles(fileKey);
|
|
140
|
+
(0, Affirm_1.default)(allFileKeys.length < 50, `Pattern ${fileKey} of producer requested to S3 matches more than 50 files (${allFileKeys.length}), this is more than the S3 allowed limit. Please refine your pattern, remove some files or use a separate bucket.`);
|
|
141
|
+
const promises = allFileKeys.map(x => this._get(Object.assign(Object.assign({}, request), { fileKey: x })));
|
|
142
|
+
const results = yield Promise.all(promises);
|
|
143
|
+
return results.flat();
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
return yield this._get(request);
|
|
155
147
|
}
|
|
156
148
|
});
|
|
157
149
|
this.readLinesInRange = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
158
150
|
(0, Affirm_1.default)(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
159
151
|
(0, Affirm_1.default)(request, 'Invalid read request');
|
|
160
152
|
(0, Affirm_1.default)(request.options, 'Invalid read request options');
|
|
161
|
-
const { fileKey
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
case 'CSV':
|
|
171
|
-
case 'JSON':
|
|
172
|
-
case 'JSONL':
|
|
173
|
-
case 'TXT':
|
|
174
|
-
return yield this._readLines(stream, lineFrom, lineTo);
|
|
175
|
-
case 'XLS':
|
|
176
|
-
case 'XLSX':
|
|
177
|
-
return yield this._readExcelLines(stream, sheetName, lineFrom, lineTo);
|
|
178
|
-
case 'XML':
|
|
179
|
-
return yield this._readXmlLines(stream, lineFrom, lineTo);
|
|
153
|
+
const { fileKey } = request;
|
|
154
|
+
if (fileKey.includes('%')) {
|
|
155
|
+
const allFileKeys = yield this.listFiles(fileKey);
|
|
156
|
+
const promises = allFileKeys.map(x => this._get(Object.assign(Object.assign({}, request), { fileKey: x })));
|
|
157
|
+
const results = yield Promise.all(promises);
|
|
158
|
+
return results.flat();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
return yield this._get(request);
|
|
180
162
|
}
|
|
181
163
|
});
|
|
182
164
|
this.exist = (producer) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -186,14 +168,20 @@ class S3SourceDriver {
|
|
|
186
168
|
const bucket = this._bucketName;
|
|
187
169
|
const fileKey = producer.settings.fileKey;
|
|
188
170
|
(0, Affirm_1.default)(fileKey, `Invalid file key for download request`);
|
|
189
|
-
|
|
190
|
-
yield this.
|
|
191
|
-
return
|
|
171
|
+
if (fileKey.includes('%')) {
|
|
172
|
+
const allFileKeys = yield this.listFiles(fileKey);
|
|
173
|
+
return allFileKeys.length > 0;
|
|
192
174
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
175
|
+
else {
|
|
176
|
+
try {
|
|
177
|
+
yield this._client.send(new client_s3_1.HeadObjectCommand({ Bucket: bucket, Key: fileKey }));
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (((_a = error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 404 || error.name === 'NotFound')
|
|
182
|
+
return false;
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
197
185
|
}
|
|
198
186
|
});
|
|
199
187
|
this._readLines = (stream, lineFrom, lineTo) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -288,6 +276,94 @@ class S3SourceDriver {
|
|
|
288
276
|
}
|
|
289
277
|
return lines;
|
|
290
278
|
});
|
|
279
|
+
this._get = (request) => __awaiter(this, void 0, void 0, function* () {
|
|
280
|
+
const { fileKey, fileType, options } = request;
|
|
281
|
+
const bucket = this._bucketName;
|
|
282
|
+
let lineFrom, lineTo, sheetName;
|
|
283
|
+
if (options) {
|
|
284
|
+
lineFrom = options.lineFrom;
|
|
285
|
+
lineTo = options.lineTo;
|
|
286
|
+
sheetName = options.sheetName;
|
|
287
|
+
}
|
|
288
|
+
const response = yield this._client.send(new client_s3_1.GetObjectCommand({
|
|
289
|
+
Bucket: bucket,
|
|
290
|
+
Key: fileKey
|
|
291
|
+
}));
|
|
292
|
+
(0, Affirm_1.default)(response.Body, 'Failed to fetch object from S3');
|
|
293
|
+
const stream = response.Body;
|
|
294
|
+
switch (fileType) {
|
|
295
|
+
case 'CSV':
|
|
296
|
+
case 'JSON':
|
|
297
|
+
case 'JSONL':
|
|
298
|
+
case 'TXT':
|
|
299
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
300
|
+
return yield this._readLines(stream, lineFrom, lineTo);
|
|
301
|
+
else
|
|
302
|
+
return yield this._readLines(stream);
|
|
303
|
+
case 'XLS':
|
|
304
|
+
case 'XLSX':
|
|
305
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
306
|
+
return yield this._readExcelLines(stream, sheetName, lineFrom, lineTo);
|
|
307
|
+
else
|
|
308
|
+
return yield this._readExcelLines(stream, sheetName);
|
|
309
|
+
case 'XML':
|
|
310
|
+
if (Algo_1.default.hasVal(lineFrom) && Algo_1.default.hasVal(lineTo))
|
|
311
|
+
return yield this._readXmlLines(stream, lineFrom, lineTo);
|
|
312
|
+
else
|
|
313
|
+
return yield this._readXmlLines(stream);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
this._listFiles = (fileKeyPattern, maxKeys, continuationToken) => __awaiter(this, void 0, void 0, function* () {
|
|
317
|
+
var _a;
|
|
318
|
+
(0, Affirm_1.default)(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
319
|
+
// Convert SQL-like pattern to prefix and pattern parts for filtering
|
|
320
|
+
let prefix = '';
|
|
321
|
+
if (fileKeyPattern) {
|
|
322
|
+
if (fileKeyPattern.includes('%')) {
|
|
323
|
+
const parts = fileKeyPattern.split('%').filter(part => part.length > 0);
|
|
324
|
+
// If pattern starts with text before first %, use it as prefix for S3 optimization
|
|
325
|
+
if (!fileKeyPattern.startsWith('%') && parts[0]) {
|
|
326
|
+
prefix = parts[0];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// No wildcard, use the entire pattern as prefix
|
|
331
|
+
prefix = fileKeyPattern;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const listParams = {
|
|
335
|
+
Bucket: this._bucketName,
|
|
336
|
+
Prefix: prefix || undefined,
|
|
337
|
+
MaxKeys: maxKeys || 10000,
|
|
338
|
+
ContinuationToken: continuationToken
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
const response = yield this._client.send(new client_s3_1.ListObjectsV2Command(listParams));
|
|
342
|
+
const files = ((_a = response.Contents) === null || _a === void 0 ? void 0 : _a.map(obj => obj.Key).filter(key => key !== undefined)) || [];
|
|
343
|
+
const matchingFiles = Helper_1.default.matchPattern(fileKeyPattern, files);
|
|
344
|
+
return {
|
|
345
|
+
files: matchingFiles,
|
|
346
|
+
nextContinuationToken: response.NextContinuationToken
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
throw new Error(`Failed to list files in bucket "${this._bucketName}": ${error.message}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
this.listFiles = (fileKeyPattern, maxKeys) => __awaiter(this, void 0, void 0, function* () {
|
|
354
|
+
const allFiles = [];
|
|
355
|
+
let continuationToken = undefined;
|
|
356
|
+
do {
|
|
357
|
+
const result = yield this._listFiles(fileKeyPattern, maxKeys, continuationToken);
|
|
358
|
+
allFiles.push(...result.files);
|
|
359
|
+
continuationToken = result.nextContinuationToken;
|
|
360
|
+
// If maxKeys is specified and we've reached the limit, break
|
|
361
|
+
if (maxKeys && allFiles.length >= maxKeys) {
|
|
362
|
+
return allFiles.slice(0, maxKeys);
|
|
363
|
+
}
|
|
364
|
+
} while (continuationToken);
|
|
365
|
+
return allFiles;
|
|
366
|
+
});
|
|
291
367
|
}
|
|
292
368
|
}
|
|
293
369
|
exports.S3SourceDriver = S3SourceDriver;
|
|
@@ -62,6 +62,9 @@ class ProducerEngineClass {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Nobody is using this anymore... prefer readFile
|
|
67
|
+
*/
|
|
65
68
|
this.execute = (producer) => __awaiter(this, void 0, void 0, function* () {
|
|
66
69
|
(0, Affirm_1.default)(producer, 'Invalid producer');
|
|
67
70
|
const source = Environment_1.default.getSource(producer.source);
|
package/helper/Helper.js
CHANGED
|
@@ -66,6 +66,61 @@ const Helper = {
|
|
|
66
66
|
const year = date.getFullYear();
|
|
67
67
|
const month = ('0' + (date.getMonth() + 1)).slice(-2);
|
|
68
68
|
return `${year}-${month}`;
|
|
69
|
+
},
|
|
70
|
+
matchPattern: (pattern, items) => {
|
|
71
|
+
let patternParts = [];
|
|
72
|
+
let hasWildcard = false;
|
|
73
|
+
let result = [...items];
|
|
74
|
+
if (pattern) {
|
|
75
|
+
if (pattern.includes('%')) {
|
|
76
|
+
hasWildcard = true;
|
|
77
|
+
const parts = pattern.split('%').filter(part => part.length > 0);
|
|
78
|
+
// Store all non-empty parts for pattern matching
|
|
79
|
+
patternParts = parts;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Apply additional filtering for patterns with wildcards
|
|
83
|
+
if (hasWildcard && patternParts.length > 0) {
|
|
84
|
+
result = result.filter(key => {
|
|
85
|
+
// Create a function to check if the key matches the SQL-like pattern
|
|
86
|
+
const matchesPattern = (fileName, pattern) => {
|
|
87
|
+
if (!pattern.includes('%')) {
|
|
88
|
+
return fileName === pattern;
|
|
89
|
+
}
|
|
90
|
+
const parts = pattern.split('%');
|
|
91
|
+
let currentIndex = 0;
|
|
92
|
+
for (let i = 0; i < parts.length; i++) {
|
|
93
|
+
const part = parts[i];
|
|
94
|
+
if (part === '')
|
|
95
|
+
continue; // Skip empty parts from consecutive %% or leading/trailing %
|
|
96
|
+
if (i === 0 && !pattern.startsWith('%')) {
|
|
97
|
+
// First part must be at the beginning
|
|
98
|
+
if (!fileName.startsWith(part)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
currentIndex = part.length;
|
|
102
|
+
}
|
|
103
|
+
else if (i === parts.length - 1 && !pattern.endsWith('%')) {
|
|
104
|
+
// Last part must be at the end
|
|
105
|
+
if (!fileName.endsWith(part)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Middle parts must exist somewhere after the current position
|
|
111
|
+
const foundIndex = fileName.indexOf(part, currentIndex);
|
|
112
|
+
if (foundIndex === -1) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
currentIndex = foundIndex + part.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
};
|
|
120
|
+
return matchesPattern(key, pattern);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
69
124
|
}
|
|
70
125
|
};
|
|
71
126
|
exports.default = Helper;
|
package/index.js
CHANGED
|
@@ -11,11 +11,10 @@ const debug_1 = require("./actions/debug");
|
|
|
11
11
|
const deploy_1 = require("./actions/deploy");
|
|
12
12
|
const init_1 = require("./actions/init");
|
|
13
13
|
const run_1 = require("./actions/run");
|
|
14
|
-
const Constants_1 = __importDefault(require("./Constants"));
|
|
15
14
|
const discover_1 = require("./actions/discover");
|
|
16
|
-
const automap_1 = require("./actions/automap");
|
|
17
15
|
const create_producer_1 = require("./actions/create_producer");
|
|
18
16
|
const create_consumer_1 = require("./actions/create_consumer");
|
|
17
|
+
const Constants_1 = __importDefault(require("./Constants"));
|
|
19
18
|
const LicenceManager_1 = __importDefault(require("./licencing/LicenceManager"));
|
|
20
19
|
dotenv_1.default.configDotenv();
|
|
21
20
|
const program = new commander_1.Command();
|
|
@@ -63,12 +62,6 @@ program
|
|
|
63
62
|
.description('Discover the data shape of a producer and automatically create the resource for it.')
|
|
64
63
|
.argument('<producer>', 'The producer to discover')
|
|
65
64
|
.action(discover_1.discover);
|
|
66
|
-
program
|
|
67
|
-
.command('automap')
|
|
68
|
-
.description('Automatically map a producer to consumers using specified schemas.')
|
|
69
|
-
.argument('<producer>', 'The producer to analyze')
|
|
70
|
-
.argument('<schemas...>', 'One or more schema names to map against')
|
|
71
|
-
.action(automap_1.automap);
|
|
72
65
|
program
|
|
73
66
|
.command('create-producer <name>')
|
|
74
67
|
.description('Create a new producer configuration with default settings')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forzalabs/remora",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31-nasco.3",
|
|
4
4
|
"description": "A powerful CLI tool for seamless data translation.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"private": false,
|
|
@@ -17,11 +17,10 @@
|
|
|
17
17
|
"compile": "npx tsx ./src/index.ts compile",
|
|
18
18
|
"deploy": "npx tsx ./src/index.ts deploy",
|
|
19
19
|
"debug": "npx tsx ./src/index.ts debug",
|
|
20
|
-
"automap": "npx tsx ./src/index.ts automap",
|
|
21
20
|
"create-producer": "npx tsx ./src/index.ts create-producer",
|
|
22
21
|
"copy-static-file": "npx tsx ./scripts/CopyStaticFile.js",
|
|
23
22
|
"build": "npm i && npm run sync && tsc --outDir .build && npm run copy-static-file",
|
|
24
|
-
"upload": "npm run build && cd .build && npm publish --access=public"
|
|
23
|
+
"upload": "npm run build && cd .build && npm publish --tag nasco --access=public"
|
|
25
24
|
},
|
|
26
25
|
"keywords": [
|
|
27
26
|
"nextjs",
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
"typescript"
|
|
31
30
|
],
|
|
32
31
|
"author": "",
|
|
33
|
-
"license": "
|
|
32
|
+
"license": "BSL",
|
|
34
33
|
"dependencies": {
|
|
35
34
|
"@aws-sdk/client-redshift-data": "^3.699.0",
|
|
36
35
|
"@aws-sdk/client-s3": "^3.701.0",
|
|
@@ -51,7 +50,7 @@
|
|
|
51
50
|
"knex": "^2.4.2",
|
|
52
51
|
"mongodb": "^6.15.0",
|
|
53
52
|
"next": "^13.4.1",
|
|
54
|
-
|
|
53
|
+
|
|
55
54
|
"ora": "^5.4.1",
|
|
56
55
|
"react": "^18.2.0",
|
|
57
56
|
"react-dom": "^18.2.0",
|
package/actions/automap.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.automap = void 0;
|
|
16
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
-
const ora_1 = __importDefault(require("ora"));
|
|
18
|
-
const compile_1 = require("./compile");
|
|
19
|
-
const AutoMapperEngine_1 = __importDefault(require("../engines/ai/AutoMapperEngine"));
|
|
20
|
-
const fs_1 = __importDefault(require("fs"));
|
|
21
|
-
const path_1 = __importDefault(require("path"));
|
|
22
|
-
const Environment_1 = __importDefault(require("../engines/Environment"));
|
|
23
|
-
const ProducerEngine_1 = __importDefault(require("../engines/ProducerEngine"));
|
|
24
|
-
/**
|
|
25
|
-
* e.g. npm run automap -- myclaims Claim
|
|
26
|
-
*/
|
|
27
|
-
const automap = (producerName, schemaNames) => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
-
try {
|
|
29
|
-
(0, compile_1.compile)();
|
|
30
|
-
const spinner = (0, ora_1.default)(chalk_1.default.blue('Auto-mapping producer data...\n')).start();
|
|
31
|
-
// Get the producer
|
|
32
|
-
const producer = Environment_1.default.getProducer(producerName);
|
|
33
|
-
if (!producer) {
|
|
34
|
-
throw new Error(`Producer ${producerName} not found`);
|
|
35
|
-
}
|
|
36
|
-
const source = Environment_1.default.getSource(producer.source);
|
|
37
|
-
if (!source) {
|
|
38
|
-
throw new Error(`Source ${producer.source} not found`);
|
|
39
|
-
}
|
|
40
|
-
// Get the specified schemas
|
|
41
|
-
const schemas = [];
|
|
42
|
-
for (const schemaName of schemaNames) {
|
|
43
|
-
const schema = Environment_1.default.getSchema(schemaName);
|
|
44
|
-
if (!schema) {
|
|
45
|
-
throw new Error(`Schema ${schemaName} not found`);
|
|
46
|
-
}
|
|
47
|
-
schemas.push(schema);
|
|
48
|
-
}
|
|
49
|
-
// Read and convert sample data
|
|
50
|
-
const sampleData = yield ProducerEngine_1.default.readSampleData(producer);
|
|
51
|
-
// Convert sample data to strings for AutoMapperEngine
|
|
52
|
-
const sampleStrings = sampleData.map(item => JSON.stringify(item));
|
|
53
|
-
// Call the automapper
|
|
54
|
-
const mapResult = yield AutoMapperEngine_1.default.map(sampleStrings, schemas, producer.settings.fileKey, [source]);
|
|
55
|
-
// Create the producers based on the mapping
|
|
56
|
-
for (const producer of mapResult.producers) {
|
|
57
|
-
const producerPath = path_1.default.join('remora/producers', `${producer.name}.json`);
|
|
58
|
-
fs_1.default.writeFileSync(producerPath, JSON.stringify(producer, null, 4));
|
|
59
|
-
console.log(chalk_1.default.blue(`Created producer: ${producer.name}`));
|
|
60
|
-
}
|
|
61
|
-
// Create the consumers based on the mapping
|
|
62
|
-
for (const consumer of mapResult.consumers) {
|
|
63
|
-
const consumerPath = path_1.default.join('remora/consumers', `${consumer.name}.json`);
|
|
64
|
-
fs_1.default.writeFileSync(consumerPath, JSON.stringify(consumer, null, 4));
|
|
65
|
-
console.log(chalk_1.default.blue(`Created consumer: ${consumer.name}`));
|
|
66
|
-
}
|
|
67
|
-
spinner.succeed('Producer has been successfully mapped');
|
|
68
|
-
console.log(chalk_1.default.green(`\nā
Created ${mapResult.producers.length} producers!`));
|
|
69
|
-
console.log(chalk_1.default.green(`ā
Created ${mapResult.consumers.length} consumers!`));
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
console.error(chalk_1.default.red.bold('\nā Unexpected error during automapping:'), err instanceof Error ? err.message : String(err));
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
exports.automap = automap;
|