@forzalabs/remora 2.0.1 → 2.0.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/CHANGELOG.md +24 -1
- package/index.js +458 -243
- package/package.json +2 -2
- package/workers/ExecutorWorker.js +385 -204
package/index.js
CHANGED
|
@@ -7759,15 +7759,15 @@ var require_index_cjs = __commonJS({
|
|
|
7759
7759
|
};
|
|
7760
7760
|
}
|
|
7761
7761
|
function wrapConversion(toModel, graph2) {
|
|
7762
|
-
const
|
|
7762
|
+
const path30 = [graph2[toModel].parent, toModel];
|
|
7763
7763
|
let fn = convert$1[graph2[toModel].parent][toModel];
|
|
7764
7764
|
let cur = graph2[toModel].parent;
|
|
7765
7765
|
while (graph2[cur].parent) {
|
|
7766
|
-
|
|
7766
|
+
path30.unshift(graph2[cur].parent);
|
|
7767
7767
|
fn = link(convert$1[graph2[cur].parent][cur], fn);
|
|
7768
7768
|
cur = graph2[cur].parent;
|
|
7769
7769
|
}
|
|
7770
|
-
fn.conversion =
|
|
7770
|
+
fn.conversion = path30;
|
|
7771
7771
|
return fn;
|
|
7772
7772
|
}
|
|
7773
7773
|
function route(fromModel) {
|
|
@@ -8397,7 +8397,7 @@ var require_node2 = __commonJS({
|
|
|
8397
8397
|
var require_tail_file = __commonJS({
|
|
8398
8398
|
"../../node_modules/winston/lib/winston/tail-file.js"(exports2, module2) {
|
|
8399
8399
|
"use strict";
|
|
8400
|
-
var
|
|
8400
|
+
var fs33 = require("fs");
|
|
8401
8401
|
var { StringDecoder } = require("string_decoder");
|
|
8402
8402
|
var { Stream } = require_readable();
|
|
8403
8403
|
function noop() {
|
|
@@ -8418,7 +8418,7 @@ var require_tail_file = __commonJS({
|
|
|
8418
8418
|
stream.emit("end");
|
|
8419
8419
|
stream.emit("close");
|
|
8420
8420
|
};
|
|
8421
|
-
|
|
8421
|
+
fs33.open(options.file, "a+", "0644", (err2, fd) => {
|
|
8422
8422
|
if (err2) {
|
|
8423
8423
|
if (!iter) {
|
|
8424
8424
|
stream.emit("error", err2);
|
|
@@ -8430,10 +8430,10 @@ var require_tail_file = __commonJS({
|
|
|
8430
8430
|
}
|
|
8431
8431
|
(function read() {
|
|
8432
8432
|
if (stream.destroyed) {
|
|
8433
|
-
|
|
8433
|
+
fs33.close(fd, noop);
|
|
8434
8434
|
return;
|
|
8435
8435
|
}
|
|
8436
|
-
return
|
|
8436
|
+
return fs33.read(fd, buffer, 0, buffer.length, pos, (error, bytes) => {
|
|
8437
8437
|
if (error) {
|
|
8438
8438
|
if (!iter) {
|
|
8439
8439
|
stream.emit("error", error);
|
|
@@ -8492,8 +8492,8 @@ var require_tail_file = __commonJS({
|
|
|
8492
8492
|
var require_file = __commonJS({
|
|
8493
8493
|
"../../node_modules/winston/lib/winston/transports/file.js"(exports2, module2) {
|
|
8494
8494
|
"use strict";
|
|
8495
|
-
var
|
|
8496
|
-
var
|
|
8495
|
+
var fs33 = require("fs");
|
|
8496
|
+
var path30 = require("path");
|
|
8497
8497
|
var asyncSeries = require_series();
|
|
8498
8498
|
var zlib3 = require("zlib");
|
|
8499
8499
|
var { MESSAGE } = require_triple_beam();
|
|
@@ -8523,14 +8523,14 @@ var require_file = __commonJS({
|
|
|
8523
8523
|
this._onError = this._onError.bind(this);
|
|
8524
8524
|
if (options.filename || options.dirname) {
|
|
8525
8525
|
throwIf("filename or dirname", "stream");
|
|
8526
|
-
this._basename = this.filename = options.filename ?
|
|
8527
|
-
this.dirname = options.dirname ||
|
|
8526
|
+
this._basename = this.filename = options.filename ? path30.basename(options.filename) : "winston.log";
|
|
8527
|
+
this.dirname = options.dirname || path30.dirname(options.filename);
|
|
8528
8528
|
this.options = options.options || { flags: "a" };
|
|
8529
8529
|
} else if (options.stream) {
|
|
8530
8530
|
console.warn("options.stream will be removed in winston@4. Use winston.transports.Stream");
|
|
8531
8531
|
throwIf("stream", "filename", "maxsize");
|
|
8532
8532
|
this._dest = this._stream.pipe(this._setupStream(options.stream));
|
|
8533
|
-
this.dirname =
|
|
8533
|
+
this.dirname = path30.dirname(this._dest.path);
|
|
8534
8534
|
} else {
|
|
8535
8535
|
throw new Error("Cannot log to file without filename or stream.");
|
|
8536
8536
|
}
|
|
@@ -8693,11 +8693,11 @@ var require_file = __commonJS({
|
|
|
8693
8693
|
options = {};
|
|
8694
8694
|
}
|
|
8695
8695
|
options = normalizeQuery(options);
|
|
8696
|
-
const file =
|
|
8696
|
+
const file = path30.join(this.dirname, this.filename);
|
|
8697
8697
|
let buff = "";
|
|
8698
8698
|
let results = [];
|
|
8699
8699
|
let row = 0;
|
|
8700
|
-
const stream =
|
|
8700
|
+
const stream = fs33.createReadStream(file, {
|
|
8701
8701
|
encoding: "utf8"
|
|
8702
8702
|
});
|
|
8703
8703
|
stream.on("error", (err2) => {
|
|
@@ -8798,7 +8798,7 @@ var require_file = __commonJS({
|
|
|
8798
8798
|
* TODO: Refactor me.
|
|
8799
8799
|
*/
|
|
8800
8800
|
stream(options = {}) {
|
|
8801
|
-
const file =
|
|
8801
|
+
const file = path30.join(this.dirname, this.filename);
|
|
8802
8802
|
const stream = new Stream();
|
|
8803
8803
|
const tail = {
|
|
8804
8804
|
file,
|
|
@@ -8848,8 +8848,8 @@ var require_file = __commonJS({
|
|
|
8848
8848
|
*/
|
|
8849
8849
|
stat(callback) {
|
|
8850
8850
|
const target = this._getFile();
|
|
8851
|
-
const fullpath =
|
|
8852
|
-
|
|
8851
|
+
const fullpath = path30.join(this.dirname, target);
|
|
8852
|
+
fs33.stat(fullpath, (err2, stat) => {
|
|
8853
8853
|
if (err2 && err2.code === "ENOENT") {
|
|
8854
8854
|
debug("ENOENT\xA0ok", fullpath);
|
|
8855
8855
|
this.filename = target;
|
|
@@ -8952,9 +8952,9 @@ var require_file = __commonJS({
|
|
|
8952
8952
|
* @returns {WritableStream} Stream that writes to disk for the active file.
|
|
8953
8953
|
*/
|
|
8954
8954
|
_createStream(source) {
|
|
8955
|
-
const fullpath =
|
|
8955
|
+
const fullpath = path30.join(this.dirname, this.filename);
|
|
8956
8956
|
debug("create stream start", fullpath, this.options);
|
|
8957
|
-
const dest =
|
|
8957
|
+
const dest = fs33.createWriteStream(fullpath, this.options).on("error", (err2) => debug(err2)).on("close", () => debug("close", dest.path, dest.bytesWritten)).on("open", () => {
|
|
8958
8958
|
debug("file open ok", fullpath);
|
|
8959
8959
|
this.emit("open", fullpath);
|
|
8960
8960
|
source.pipe(dest);
|
|
@@ -8977,16 +8977,16 @@ var require_file = __commonJS({
|
|
|
8977
8977
|
*/
|
|
8978
8978
|
_incFile(callback) {
|
|
8979
8979
|
debug("_incFile", this.filename);
|
|
8980
|
-
const ext =
|
|
8981
|
-
const basename =
|
|
8980
|
+
const ext = path30.extname(this._basename);
|
|
8981
|
+
const basename = path30.basename(this._basename, ext);
|
|
8982
8982
|
const tasks = [];
|
|
8983
8983
|
if (this.zippedArchive) {
|
|
8984
8984
|
tasks.push(
|
|
8985
8985
|
function(cb) {
|
|
8986
8986
|
const num = this._created > 0 && !this.tailable ? this._created : "";
|
|
8987
8987
|
this._compressFile(
|
|
8988
|
-
|
|
8989
|
-
|
|
8988
|
+
path30.join(this.dirname, `${basename}${num}${ext}`),
|
|
8989
|
+
path30.join(this.dirname, `${basename}${num}${ext}.gz`),
|
|
8990
8990
|
cb
|
|
8991
8991
|
);
|
|
8992
8992
|
}.bind(this)
|
|
@@ -9011,8 +9011,8 @@ var require_file = __commonJS({
|
|
|
9011
9011
|
* @private
|
|
9012
9012
|
*/
|
|
9013
9013
|
_getFile() {
|
|
9014
|
-
const ext =
|
|
9015
|
-
const basename =
|
|
9014
|
+
const ext = path30.extname(this._basename);
|
|
9015
|
+
const basename = path30.basename(this._basename, ext);
|
|
9016
9016
|
const isRotation = this.rotationFormat ? this.rotationFormat() : this._created;
|
|
9017
9017
|
return !this.tailable && this._created ? `${basename}${isRotation}${ext}` : `${basename}${ext}`;
|
|
9018
9018
|
}
|
|
@@ -9032,8 +9032,8 @@ var require_file = __commonJS({
|
|
|
9032
9032
|
const isOldest = oldest !== 0 ? oldest : "";
|
|
9033
9033
|
const isZipped = this.zippedArchive ? ".gz" : "";
|
|
9034
9034
|
const filePath = `${basename}${isOldest}${ext}${isZipped}`;
|
|
9035
|
-
const target =
|
|
9036
|
-
|
|
9035
|
+
const target = path30.join(this.dirname, filePath);
|
|
9036
|
+
fs33.unlink(target, callback);
|
|
9037
9037
|
}
|
|
9038
9038
|
/**
|
|
9039
9039
|
* Roll files forward based on integer, up to maxFiles. e.g. if base if
|
|
@@ -9055,20 +9055,20 @@ var require_file = __commonJS({
|
|
|
9055
9055
|
for (let x2 = this.maxFiles - 1; x2 > 1; x2--) {
|
|
9056
9056
|
tasks.push(function(i6, cb) {
|
|
9057
9057
|
let fileName = `${basename}${i6 - 1}${ext}${isZipped}`;
|
|
9058
|
-
const tmppath =
|
|
9059
|
-
|
|
9058
|
+
const tmppath = path30.join(this.dirname, fileName);
|
|
9059
|
+
fs33.exists(tmppath, (exists) => {
|
|
9060
9060
|
if (!exists) {
|
|
9061
9061
|
return cb(null);
|
|
9062
9062
|
}
|
|
9063
9063
|
fileName = `${basename}${i6}${ext}${isZipped}`;
|
|
9064
|
-
|
|
9064
|
+
fs33.rename(tmppath, path30.join(this.dirname, fileName), cb);
|
|
9065
9065
|
});
|
|
9066
9066
|
}.bind(this, x2));
|
|
9067
9067
|
}
|
|
9068
9068
|
asyncSeries(tasks, () => {
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9069
|
+
fs33.rename(
|
|
9070
|
+
path30.join(this.dirname, `${basename}${ext}${isZipped}`),
|
|
9071
|
+
path30.join(this.dirname, `${basename}1${ext}${isZipped}`),
|
|
9072
9072
|
callback
|
|
9073
9073
|
);
|
|
9074
9074
|
});
|
|
@@ -9082,22 +9082,22 @@ var require_file = __commonJS({
|
|
|
9082
9082
|
* @private
|
|
9083
9083
|
*/
|
|
9084
9084
|
_compressFile(src, dest, callback) {
|
|
9085
|
-
|
|
9085
|
+
fs33.access(src, fs33.F_OK, (err2) => {
|
|
9086
9086
|
if (err2) {
|
|
9087
9087
|
return callback();
|
|
9088
9088
|
}
|
|
9089
9089
|
var gzip = zlib3.createGzip();
|
|
9090
|
-
var inp =
|
|
9091
|
-
var out =
|
|
9090
|
+
var inp = fs33.createReadStream(src);
|
|
9091
|
+
var out = fs33.createWriteStream(dest);
|
|
9092
9092
|
out.on("finish", () => {
|
|
9093
|
-
|
|
9093
|
+
fs33.unlink(src, callback);
|
|
9094
9094
|
});
|
|
9095
9095
|
inp.pipe(gzip).pipe(out);
|
|
9096
9096
|
});
|
|
9097
9097
|
}
|
|
9098
9098
|
_createLogDirIfNotExist(dirPath) {
|
|
9099
|
-
if (!
|
|
9100
|
-
|
|
9099
|
+
if (!fs33.existsSync(dirPath)) {
|
|
9100
|
+
fs33.mkdirSync(dirPath, { recursive: true });
|
|
9101
9101
|
}
|
|
9102
9102
|
}
|
|
9103
9103
|
};
|
|
@@ -9181,9 +9181,9 @@ var require_http = __commonJS({
|
|
|
9181
9181
|
};
|
|
9182
9182
|
const auth = options.params.auth || null;
|
|
9183
9183
|
delete options.params.auth;
|
|
9184
|
-
const
|
|
9184
|
+
const path30 = options.params.path || null;
|
|
9185
9185
|
delete options.params.path;
|
|
9186
|
-
this._request(options, auth,
|
|
9186
|
+
this._request(options, auth, path30, (err2, res, body) => {
|
|
9187
9187
|
if (res && res.statusCode !== 200) {
|
|
9188
9188
|
err2 = new Error(`Invalid HTTP Status Code: ${res.statusCode}`);
|
|
9189
9189
|
}
|
|
@@ -9211,12 +9211,12 @@ var require_http = __commonJS({
|
|
|
9211
9211
|
method: "stream",
|
|
9212
9212
|
params: options
|
|
9213
9213
|
};
|
|
9214
|
-
const
|
|
9214
|
+
const path30 = options.params.path || null;
|
|
9215
9215
|
delete options.params.path;
|
|
9216
9216
|
const auth = options.params.auth || null;
|
|
9217
9217
|
delete options.params.auth;
|
|
9218
9218
|
let buff = "";
|
|
9219
|
-
const req = this._request(options, auth,
|
|
9219
|
+
const req = this._request(options, auth, path30);
|
|
9220
9220
|
stream.destroy = () => req.destroy();
|
|
9221
9221
|
req.on("data", (data) => {
|
|
9222
9222
|
data = (buff + data).split(/\n+/);
|
|
@@ -9242,14 +9242,14 @@ var require_http = __commonJS({
|
|
|
9242
9242
|
* @param {string} path - request path
|
|
9243
9243
|
* @param {function} callback - Continuation to respond to when complete.
|
|
9244
9244
|
*/
|
|
9245
|
-
_request(options, auth,
|
|
9245
|
+
_request(options, auth, path30, callback) {
|
|
9246
9246
|
options = options || {};
|
|
9247
9247
|
auth = auth || this.auth;
|
|
9248
|
-
|
|
9248
|
+
path30 = path30 || this.path || "";
|
|
9249
9249
|
if (this.batch) {
|
|
9250
|
-
this._doBatch(options, callback, auth,
|
|
9250
|
+
this._doBatch(options, callback, auth, path30);
|
|
9251
9251
|
} else {
|
|
9252
|
-
this._doRequest(options, callback, auth,
|
|
9252
|
+
this._doRequest(options, callback, auth, path30);
|
|
9253
9253
|
}
|
|
9254
9254
|
}
|
|
9255
9255
|
/**
|
|
@@ -9259,18 +9259,18 @@ var require_http = __commonJS({
|
|
|
9259
9259
|
* @param {Object?} auth - authentication options
|
|
9260
9260
|
* @param {string} path - request path
|
|
9261
9261
|
*/
|
|
9262
|
-
_doBatch(options, callback, auth,
|
|
9262
|
+
_doBatch(options, callback, auth, path30) {
|
|
9263
9263
|
this.batchOptions.push(options);
|
|
9264
9264
|
if (this.batchOptions.length === 1) {
|
|
9265
9265
|
const me3 = this;
|
|
9266
9266
|
this.batchCallback = callback;
|
|
9267
9267
|
this.batchTimeoutID = setTimeout(function() {
|
|
9268
9268
|
me3.batchTimeoutID = -1;
|
|
9269
|
-
me3._doBatchRequest(me3.batchCallback, auth,
|
|
9269
|
+
me3._doBatchRequest(me3.batchCallback, auth, path30);
|
|
9270
9270
|
}, this.batchInterval);
|
|
9271
9271
|
}
|
|
9272
9272
|
if (this.batchOptions.length === this.batchCount) {
|
|
9273
|
-
this._doBatchRequest(this.batchCallback, auth,
|
|
9273
|
+
this._doBatchRequest(this.batchCallback, auth, path30);
|
|
9274
9274
|
}
|
|
9275
9275
|
}
|
|
9276
9276
|
/**
|
|
@@ -9279,14 +9279,14 @@ var require_http = __commonJS({
|
|
|
9279
9279
|
* @param {Object?} auth - authentication options
|
|
9280
9280
|
* @param {string} path - request path
|
|
9281
9281
|
*/
|
|
9282
|
-
_doBatchRequest(callback, auth,
|
|
9282
|
+
_doBatchRequest(callback, auth, path30) {
|
|
9283
9283
|
if (this.batchTimeoutID > 0) {
|
|
9284
9284
|
clearTimeout(this.batchTimeoutID);
|
|
9285
9285
|
this.batchTimeoutID = -1;
|
|
9286
9286
|
}
|
|
9287
9287
|
const batchOptionsCopy = this.batchOptions.slice();
|
|
9288
9288
|
this.batchOptions = [];
|
|
9289
|
-
this._doRequest(batchOptionsCopy, callback, auth,
|
|
9289
|
+
this._doRequest(batchOptionsCopy, callback, auth, path30);
|
|
9290
9290
|
}
|
|
9291
9291
|
/**
|
|
9292
9292
|
* Make a request to a winstond server or any http server which can
|
|
@@ -9296,7 +9296,7 @@ var require_http = __commonJS({
|
|
|
9296
9296
|
* @param {Object?} auth - authentication options
|
|
9297
9297
|
* @param {string} path - request path
|
|
9298
9298
|
*/
|
|
9299
|
-
_doRequest(options, callback, auth,
|
|
9299
|
+
_doRequest(options, callback, auth, path30) {
|
|
9300
9300
|
const headers = Object.assign({}, this.headers);
|
|
9301
9301
|
if (auth && auth.bearer) {
|
|
9302
9302
|
headers.Authorization = `Bearer ${auth.bearer}`;
|
|
@@ -9306,7 +9306,7 @@ var require_http = __commonJS({
|
|
|
9306
9306
|
method: "POST",
|
|
9307
9307
|
host: this.host,
|
|
9308
9308
|
port: this.port,
|
|
9309
|
-
path: `/${
|
|
9309
|
+
path: `/${path30.replace(/^\//, "")}`,
|
|
9310
9310
|
headers,
|
|
9311
9311
|
auth: auth && auth.username && auth.password ? `${auth.username}:${auth.password}` : "",
|
|
9312
9312
|
agent: this.agent
|
|
@@ -11527,7 +11527,7 @@ var CONSTANTS, Constants_default;
|
|
|
11527
11527
|
var init_Constants = __esm({
|
|
11528
11528
|
"../../packages/constants/src/Constants.ts"() {
|
|
11529
11529
|
CONSTANTS = {
|
|
11530
|
-
cliVersion: "2.0.
|
|
11530
|
+
cliVersion: "2.0.3",
|
|
11531
11531
|
backendVersion: 1,
|
|
11532
11532
|
backendPort: 5088,
|
|
11533
11533
|
workerVersion: 2,
|
|
@@ -11584,7 +11584,12 @@ var init_Constants = __esm({
|
|
|
11584
11584
|
* whole run, so the entire work list is submitted at once and needs an upper bound. A queued
|
|
11585
11585
|
* task holds references, not copies, so this bounds the work list rather than memory.
|
|
11586
11586
|
*/
|
|
11587
|
-
MAX_WORKER_QUEUE_SIZE: 1e5
|
|
11587
|
+
MAX_WORKER_QUEUE_SIZE: 1e5,
|
|
11588
|
+
/**
|
|
11589
|
+
* Rows a sample may ask for out of the pipeline. A sample is a look at the data, never an
|
|
11590
|
+
* export of it — `remora sample -s` and the preview's Data tab are both held to this.
|
|
11591
|
+
*/
|
|
11592
|
+
MAX_SAMPLE_ROWS: 100
|
|
11588
11593
|
}
|
|
11589
11594
|
};
|
|
11590
11595
|
Constants_default = CONSTANTS;
|
|
@@ -12733,7 +12738,7 @@ var init_Validator = __esm({
|
|
|
12733
12738
|
*/
|
|
12734
12739
|
this.detectOnFinishRecursion = (consumer) => {
|
|
12735
12740
|
const errors = [];
|
|
12736
|
-
const
|
|
12741
|
+
const path30 = [];
|
|
12737
12742
|
const targetsOf = (cons) => {
|
|
12738
12743
|
const names = [];
|
|
12739
12744
|
for (const output of cons.outputs ?? []) {
|
|
@@ -12745,17 +12750,17 @@ var init_Validator = __esm({
|
|
|
12745
12750
|
return names;
|
|
12746
12751
|
};
|
|
12747
12752
|
const walk = (name) => {
|
|
12748
|
-
if (
|
|
12749
|
-
errors.push(`Consumer "${consumer.name}" has a cyclic run-consumer (onSuccess/onError) chain: ${[...
|
|
12753
|
+
if (path30.some((x2) => x2.toLowerCase() === name.toLowerCase())) {
|
|
12754
|
+
errors.push(`Consumer "${consumer.name}" has a cyclic run-consumer (onSuccess/onError) chain: ${[...path30, name].join(" -> ")}`);
|
|
12750
12755
|
return true;
|
|
12751
12756
|
}
|
|
12752
12757
|
const cons = Environment_default.getConsumer(name);
|
|
12753
12758
|
if (!cons) return false;
|
|
12754
|
-
|
|
12759
|
+
path30.push(name);
|
|
12755
12760
|
for (const target of targetsOf(cons)) {
|
|
12756
12761
|
if (walk(target)) return true;
|
|
12757
12762
|
}
|
|
12758
|
-
|
|
12763
|
+
path30.pop();
|
|
12759
12764
|
return false;
|
|
12760
12765
|
};
|
|
12761
12766
|
walk(consumer.name);
|
|
@@ -14241,8 +14246,8 @@ var init_HttpClient = __esm({
|
|
|
14241
14246
|
throw new Error(err2.message ?? "An error occurred on GET.");
|
|
14242
14247
|
}
|
|
14243
14248
|
};
|
|
14244
|
-
this.getWorkerUrl = (
|
|
14245
|
-
const cleanPath =
|
|
14249
|
+
this.getWorkerUrl = (path30) => {
|
|
14250
|
+
const cleanPath = path30.startsWith("/") ? path30.substring(1) : path30;
|
|
14246
14251
|
return `${this.workerHost}/${cleanPath}`;
|
|
14247
14252
|
};
|
|
14248
14253
|
this.workerHost = ProcessENVManager_default.getEnvVariable("REMORA_WORKER_HOST") || "http://worker:5069";
|
|
@@ -14685,14 +14690,14 @@ var init_readFile = __esm({
|
|
|
14685
14690
|
import_promises3 = require("fs/promises");
|
|
14686
14691
|
filePromises = {};
|
|
14687
14692
|
fileIntercept = {};
|
|
14688
|
-
readFile2 = (
|
|
14689
|
-
if (fileIntercept[
|
|
14690
|
-
return fileIntercept[
|
|
14693
|
+
readFile2 = (path30, options) => {
|
|
14694
|
+
if (fileIntercept[path30] !== void 0) {
|
|
14695
|
+
return fileIntercept[path30];
|
|
14691
14696
|
}
|
|
14692
|
-
if (!filePromises[
|
|
14693
|
-
filePromises[
|
|
14697
|
+
if (!filePromises[path30] || options?.ignoreCache) {
|
|
14698
|
+
filePromises[path30] = (0, import_promises3.readFile)(path30, "utf8");
|
|
14694
14699
|
}
|
|
14695
|
-
return filePromises[
|
|
14700
|
+
return filePromises[path30];
|
|
14696
14701
|
};
|
|
14697
14702
|
}
|
|
14698
14703
|
});
|
|
@@ -14804,8 +14809,8 @@ var init_externalDataInterceptor = __esm({
|
|
|
14804
14809
|
getFileRecord() {
|
|
14805
14810
|
return fileIntercept;
|
|
14806
14811
|
},
|
|
14807
|
-
interceptFile(
|
|
14808
|
-
fileIntercept[
|
|
14812
|
+
interceptFile(path30, contents) {
|
|
14813
|
+
fileIntercept[path30] = Promise.resolve(contents);
|
|
14809
14814
|
},
|
|
14810
14815
|
getTokenRecord() {
|
|
14811
14816
|
return tokenIntercept;
|
|
@@ -17885,8 +17890,8 @@ var init_createConfigValueProvider = __esm({
|
|
|
17885
17890
|
return endpoint.url.href;
|
|
17886
17891
|
}
|
|
17887
17892
|
if ("hostname" in endpoint) {
|
|
17888
|
-
const { protocol, hostname, port, path:
|
|
17889
|
-
return `${protocol}//${hostname}${port ? ":" + port : ""}${
|
|
17893
|
+
const { protocol, hostname, port, path: path30 } = endpoint;
|
|
17894
|
+
return `${protocol}//${hostname}${port ? ":" + port : ""}${path30}`;
|
|
17890
17895
|
}
|
|
17891
17896
|
}
|
|
17892
17897
|
return endpoint;
|
|
@@ -18261,18 +18266,18 @@ var getAttrPathList;
|
|
|
18261
18266
|
var init_getAttrPathList = __esm({
|
|
18262
18267
|
"../../node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttrPathList.js"() {
|
|
18263
18268
|
init_types2();
|
|
18264
|
-
getAttrPathList = (
|
|
18265
|
-
const parts =
|
|
18269
|
+
getAttrPathList = (path30) => {
|
|
18270
|
+
const parts = path30.split(".");
|
|
18266
18271
|
const pathList = [];
|
|
18267
18272
|
for (const part of parts) {
|
|
18268
18273
|
const squareBracketIndex = part.indexOf("[");
|
|
18269
18274
|
if (squareBracketIndex !== -1) {
|
|
18270
18275
|
if (part.indexOf("]") !== part.length - 1) {
|
|
18271
|
-
throw new EndpointError(`Path: '${
|
|
18276
|
+
throw new EndpointError(`Path: '${path30}' does not end with ']'`);
|
|
18272
18277
|
}
|
|
18273
18278
|
const arrayIndex = part.slice(squareBracketIndex + 1, -1);
|
|
18274
18279
|
if (Number.isNaN(parseInt(arrayIndex))) {
|
|
18275
|
-
throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${
|
|
18280
|
+
throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path30}'`);
|
|
18276
18281
|
}
|
|
18277
18282
|
if (squareBracketIndex !== 0) {
|
|
18278
18283
|
pathList.push(part.slice(0, squareBracketIndex));
|
|
@@ -18293,9 +18298,9 @@ var init_getAttr = __esm({
|
|
|
18293
18298
|
"../../node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttr.js"() {
|
|
18294
18299
|
init_types2();
|
|
18295
18300
|
init_getAttrPathList();
|
|
18296
|
-
getAttr = (value,
|
|
18301
|
+
getAttr = (value, path30) => getAttrPathList(path30).reduce((acc, index) => {
|
|
18297
18302
|
if (typeof acc !== "object") {
|
|
18298
|
-
throw new EndpointError(`Index '${index}' in '${
|
|
18303
|
+
throw new EndpointError(`Index '${index}' in '${path30}' not found in '${JSON.stringify(value)}'`);
|
|
18299
18304
|
} else if (Array.isArray(acc)) {
|
|
18300
18305
|
const i6 = parseInt(index);
|
|
18301
18306
|
return acc[i6 < 0 ? acc.length + i6 : i6];
|
|
@@ -18356,8 +18361,8 @@ var init_parseURL = __esm({
|
|
|
18356
18361
|
return value;
|
|
18357
18362
|
}
|
|
18358
18363
|
if (typeof value === "object" && "hostname" in value) {
|
|
18359
|
-
const { hostname: hostname2, port, protocol: protocol2 = "", path:
|
|
18360
|
-
const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${
|
|
18364
|
+
const { hostname: hostname2, port, protocol: protocol2 = "", path: path30 = "", query = {} } = value;
|
|
18365
|
+
const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path30}`);
|
|
18361
18366
|
url.search = Object.entries(query).map(([k7, v3]) => `${k7}=${v3}`).join("&");
|
|
18362
18367
|
return url;
|
|
18363
18368
|
}
|
|
@@ -20701,11 +20706,11 @@ var init_HttpBindingProtocol = __esm({
|
|
|
20701
20706
|
const opTraits = translateTraits(operationSchema.traits);
|
|
20702
20707
|
if (opTraits.http) {
|
|
20703
20708
|
request.method = opTraits.http[0];
|
|
20704
|
-
const [
|
|
20709
|
+
const [path30, search] = opTraits.http[1].split("?");
|
|
20705
20710
|
if (request.path == "/") {
|
|
20706
|
-
request.path =
|
|
20711
|
+
request.path = path30;
|
|
20707
20712
|
} else {
|
|
20708
|
-
request.path +=
|
|
20713
|
+
request.path += path30;
|
|
20709
20714
|
}
|
|
20710
20715
|
const traitSearchParams = new URLSearchParams(search ?? "");
|
|
20711
20716
|
for (const [key, value] of traitSearchParams) {
|
|
@@ -24418,12 +24423,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
24418
24423
|
const password = request.password ?? "";
|
|
24419
24424
|
auth = `${username}:${password}`;
|
|
24420
24425
|
}
|
|
24421
|
-
let
|
|
24426
|
+
let path30 = request.path;
|
|
24422
24427
|
if (queryString) {
|
|
24423
|
-
|
|
24428
|
+
path30 += `?${queryString}`;
|
|
24424
24429
|
}
|
|
24425
24430
|
if (request.fragment) {
|
|
24426
|
-
|
|
24431
|
+
path30 += `#${request.fragment}`;
|
|
24427
24432
|
}
|
|
24428
24433
|
let hostname = request.hostname ?? "";
|
|
24429
24434
|
if (hostname[0] === "[" && hostname.endsWith("]")) {
|
|
@@ -24435,7 +24440,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
24435
24440
|
headers: request.headers,
|
|
24436
24441
|
host: hostname,
|
|
24437
24442
|
method: request.method,
|
|
24438
|
-
path:
|
|
24443
|
+
path: path30,
|
|
24439
24444
|
port: request.port,
|
|
24440
24445
|
agent,
|
|
24441
24446
|
auth
|
|
@@ -25344,10 +25349,10 @@ ${longDate}
|
|
|
25344
25349
|
${credentialScope}
|
|
25345
25350
|
${toHex(hashedRequest)}`;
|
|
25346
25351
|
}
|
|
25347
|
-
getCanonicalPath({ path:
|
|
25352
|
+
getCanonicalPath({ path: path30 }) {
|
|
25348
25353
|
if (this.uriEscapePath) {
|
|
25349
25354
|
const normalizedPathSegments = [];
|
|
25350
|
-
for (const pathSegment of
|
|
25355
|
+
for (const pathSegment of path30.split("/")) {
|
|
25351
25356
|
if (pathSegment?.length === 0)
|
|
25352
25357
|
continue;
|
|
25353
25358
|
if (pathSegment === ".")
|
|
@@ -25358,11 +25363,11 @@ ${toHex(hashedRequest)}`;
|
|
|
25358
25363
|
normalizedPathSegments.push(pathSegment);
|
|
25359
25364
|
}
|
|
25360
25365
|
}
|
|
25361
|
-
const normalizedPath = `${
|
|
25366
|
+
const normalizedPath = `${path30?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path30?.endsWith("/") ? "/" : ""}`;
|
|
25362
25367
|
const doubleEncoded = escapeUri(normalizedPath);
|
|
25363
25368
|
return doubleEncoded.replace(/%2F/g, "/");
|
|
25364
25369
|
}
|
|
25365
|
-
return
|
|
25370
|
+
return path30;
|
|
25366
25371
|
}
|
|
25367
25372
|
validateResolvedCredentials(credentials) {
|
|
25368
25373
|
if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") {
|
|
@@ -42569,32 +42574,32 @@ var init_DriverHelper = __esm({
|
|
|
42569
42574
|
},
|
|
42570
42575
|
quickReadFile: async (filePath, lineCount) => {
|
|
42571
42576
|
const fileStream = (0, import_fs9.createReadStream)(filePath);
|
|
42572
|
-
const
|
|
42577
|
+
const rl7 = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
|
|
42573
42578
|
const lines = [];
|
|
42574
42579
|
let counter = 0;
|
|
42575
|
-
for await (const line of
|
|
42580
|
+
for await (const line of rl7) {
|
|
42576
42581
|
lines.push(line);
|
|
42577
42582
|
counter++;
|
|
42578
42583
|
if (counter >= lineCount) {
|
|
42579
42584
|
break;
|
|
42580
42585
|
}
|
|
42581
42586
|
}
|
|
42582
|
-
|
|
42587
|
+
rl7.close();
|
|
42583
42588
|
fileStream.close();
|
|
42584
42589
|
return lines;
|
|
42585
42590
|
},
|
|
42586
42591
|
quickReadStream: async (stream, lineCount) => {
|
|
42587
|
-
const
|
|
42592
|
+
const rl7 = (0, import_readline.createInterface)({ input: stream, crlfDelay: Infinity });
|
|
42588
42593
|
const lines = [];
|
|
42589
42594
|
let counter = 0;
|
|
42590
|
-
for await (const line of
|
|
42595
|
+
for await (const line of rl7) {
|
|
42591
42596
|
lines.push(line);
|
|
42592
42597
|
counter++;
|
|
42593
42598
|
if (counter >= lineCount) {
|
|
42594
42599
|
break;
|
|
42595
42600
|
}
|
|
42596
42601
|
}
|
|
42597
|
-
|
|
42602
|
+
rl7.close();
|
|
42598
42603
|
return lines;
|
|
42599
42604
|
}
|
|
42600
42605
|
};
|
|
@@ -43368,7 +43373,7 @@ var init_S3DestinationDriver = __esm({
|
|
|
43368
43373
|
});
|
|
43369
43374
|
|
|
43370
43375
|
// ../../packages/drivers/src/DriverFactory.ts
|
|
43371
|
-
var DriverFactoryClass, DriverFactory, DriverFactory_default;
|
|
43376
|
+
var STAGEABLE_ENGINES, DriverFactoryClass, DriverFactory, DriverFactory_default;
|
|
43372
43377
|
var init_DriverFactory = __esm({
|
|
43373
43378
|
"../../packages/drivers/src/DriverFactory.ts"() {
|
|
43374
43379
|
init_RedshiftDriver();
|
|
@@ -43378,8 +43383,11 @@ var init_DriverFactory = __esm({
|
|
|
43378
43383
|
init_LocalDestinationDriver();
|
|
43379
43384
|
init_S3SourceDriver();
|
|
43380
43385
|
init_S3DestinationDriver();
|
|
43386
|
+
STAGEABLE_ENGINES = ["local", "aws-s3", "delta-share"];
|
|
43381
43387
|
DriverFactoryClass = class {
|
|
43382
43388
|
constructor() {
|
|
43389
|
+
/** Whether a run can stage a producer from this engine — see `STAGEABLE_ENGINES`. */
|
|
43390
|
+
this.canStage = (engine) => STAGEABLE_ENGINES.includes(engine);
|
|
43383
43391
|
this.instantiateSource = async (source) => {
|
|
43384
43392
|
switch (source.engine) {
|
|
43385
43393
|
case "aws-redshift": {
|
|
@@ -44132,6 +44140,7 @@ var init_ProducerManager = __esm({
|
|
|
44132
44140
|
init_src4();
|
|
44133
44141
|
init_src3();
|
|
44134
44142
|
init_src6();
|
|
44143
|
+
init_CryptoEngine();
|
|
44135
44144
|
ProducerManagerClass = class {
|
|
44136
44145
|
constructor() {
|
|
44137
44146
|
this.getColumns = (producer) => {
|
|
@@ -44186,6 +44195,46 @@ var init_ProducerManager = __esm({
|
|
|
44186
44195
|
return true;
|
|
44187
44196
|
return FixedWidthParser_default.matchesSelector(line, resolved.selector, resolved.range);
|
|
44188
44197
|
};
|
|
44198
|
+
/**
|
|
44199
|
+
* The post-parse work a producer implies for every record it produces: which keys carry the
|
|
44200
|
+
* source file name, and which carry a mask.
|
|
44201
|
+
*
|
|
44202
|
+
* Resolved once per chunk, never per line — `getMask` resolves a secret and validates the value,
|
|
44203
|
+
* and neither depends on the record. Empty for almost every producer, which is the case worth
|
|
44204
|
+
* keeping fast.
|
|
44205
|
+
*/
|
|
44206
|
+
this.compileFills = (dimensions) => {
|
|
44207
|
+
Affirm_default(dimensions, "Invalid producer dimensions");
|
|
44208
|
+
const fills = [];
|
|
44209
|
+
for (const dimension of dimensions) {
|
|
44210
|
+
const { prodDimension } = dimension;
|
|
44211
|
+
const mask = this.getMask(prodDimension);
|
|
44212
|
+
const sourceFilename = prodDimension.sourceFilename === true;
|
|
44213
|
+
if (!sourceFilename && !Algo_default.hasVal(mask))
|
|
44214
|
+
continue;
|
|
44215
|
+
fills.push({ key: dimension.name, mask, sourceFilename, type: prodDimension.type });
|
|
44216
|
+
}
|
|
44217
|
+
return fills;
|
|
44218
|
+
};
|
|
44219
|
+
/**
|
|
44220
|
+
* Applies the compiled fills to one parsed record, in place.
|
|
44221
|
+
*
|
|
44222
|
+
* The one place a producer dimension's mask is ever applied, on purpose: a run reaches it through
|
|
44223
|
+
* `ProducerExecutor.processLine` and a sample through `ProducerEngine`, and a masked dimension
|
|
44224
|
+
* that came back in the clear from one of the two would be a `hash` the project declared and the
|
|
44225
|
+
* screen ignored — a disclosure, not a display bug.
|
|
44226
|
+
*/
|
|
44227
|
+
this.applyFills = (record, fills, sourceFilename) => {
|
|
44228
|
+
if (!fills?.length)
|
|
44229
|
+
return record;
|
|
44230
|
+
for (const fill2 of fills) {
|
|
44231
|
+
if (fill2.sourceFilename)
|
|
44232
|
+
record[fill2.key] = sourceFilename;
|
|
44233
|
+
if (Algo_default.hasVal(fill2.mask))
|
|
44234
|
+
record[fill2.key] = CryptoEngine_default.hashValue(fill2.mask, record[fill2.key]?.toString(), fill2.type);
|
|
44235
|
+
}
|
|
44236
|
+
return record;
|
|
44237
|
+
};
|
|
44189
44238
|
}
|
|
44190
44239
|
};
|
|
44191
44240
|
ProducerManager = new ProducerManagerClass();
|
|
@@ -44600,7 +44649,8 @@ var init_ProducerEngine = __esm({
|
|
|
44600
44649
|
}
|
|
44601
44650
|
};
|
|
44602
44651
|
}
|
|
44603
|
-
const
|
|
44652
|
+
const headerLines = ParseHelper_default.hasHeaderLine(effectiveProducer.settings.fileType, effectiveProducer.settings.hasHeaderRow) ? 1 : 0;
|
|
44653
|
+
const fileData = await this.readFile(effectiveProducer, { readmode: "lines", lines: { from: 0, to: sampleSize + headerLines } }, effectiveSource);
|
|
44604
44654
|
rawData = fileData.data;
|
|
44605
44655
|
} finally {
|
|
44606
44656
|
if (scope)
|
|
@@ -44619,13 +44669,15 @@ var init_ProducerEngine = __esm({
|
|
|
44619
44669
|
if (!rawData || rawData.length === 0) return [];
|
|
44620
44670
|
const firstItem = rawData[0];
|
|
44621
44671
|
const isLineData = typeof firstItem === "string";
|
|
44672
|
+
const fills = discover2 ? [] : ProducerManager_default.compileFills(this._fillDimensions(producer));
|
|
44673
|
+
const sourceFilename = import_path18.default.basename(producer.settings.fileKey ?? producer.settings.sqlTable ?? "");
|
|
44622
44674
|
if (isLineData) {
|
|
44623
44675
|
const lines = rawData;
|
|
44624
44676
|
const dimensions = this.deriveDimensions(lines[0], producer, discover2);
|
|
44625
44677
|
const { fileType, hasHeaderRow } = producer.settings;
|
|
44626
44678
|
const dataLines = ParseHelper_default.hasHeaderLine(fileType, hasHeaderRow) ? lines.slice(1) : lines;
|
|
44627
44679
|
const recordSelector = ProducerManager_default.resolveRecordSelector(producer);
|
|
44628
|
-
return dataLines.filter((line) => line.trim().length > 0).filter((line) => ProducerManager_default.shouldReadLine(line, recordSelector)).flatMap((line) => LineParser_default.parseAll(line, producer, dimensions, noopTracker));
|
|
44680
|
+
return dataLines.filter((line) => line.trim().length > 0).filter((line) => ProducerManager_default.shouldReadLine(line, recordSelector)).flatMap((line) => LineParser_default.parseAll(line, producer, dimensions, noopTracker)).map((record) => ProducerManager_default.applyFills(record, fills, sourceFilename));
|
|
44629
44681
|
}
|
|
44630
44682
|
if (discover2) {
|
|
44631
44683
|
return rawData.map((item) => ({ ...item }));
|
|
@@ -44636,9 +44688,15 @@ var init_ProducerEngine = __esm({
|
|
|
44636
44688
|
const key = dim.alias ?? dim.name;
|
|
44637
44689
|
record[dim.name] = TypeCaster_default.cast(item[key], dim.type, dim.format);
|
|
44638
44690
|
}
|
|
44639
|
-
return record;
|
|
44691
|
+
return ProducerManager_default.applyFills(record, fills, sourceFilename);
|
|
44640
44692
|
});
|
|
44641
44693
|
};
|
|
44694
|
+
/**
|
|
44695
|
+
* The producer's declared dimensions in the shape `compileFills` reads them in. Declaration
|
|
44696
|
+
* order, because a fill is addressed by key and never by position — unlike the executor
|
|
44697
|
+
* dimensions of a run, whose index is reconciled against the file's own header.
|
|
44698
|
+
*/
|
|
44699
|
+
this._fillDimensions = (producer) => producer.dimensions.map((prodDimension, index) => ({ index, name: prodDimension.name, prodDimension }));
|
|
44642
44700
|
/**
|
|
44643
44701
|
* Builds the executor dimensions of a producer. There are two shapes of source: a FIXED file has
|
|
44644
44702
|
* no header, so its layout comes from the positions declared on the dimensions; every other file
|
|
@@ -47106,9 +47164,10 @@ var init_MockDataFactory = __esm({
|
|
|
47106
47164
|
if (type === "number") {
|
|
47107
47165
|
const first = bounds?.min ?? 1;
|
|
47108
47166
|
const last = first + poolSize - 1;
|
|
47167
|
+
const ceiling = bounds?.max;
|
|
47109
47168
|
Affirm_default(
|
|
47110
|
-
|
|
47111
|
-
`Key "${norm}" has a synth range of ${first}-${
|
|
47169
|
+
ceiling === void 0 || ceiling >= last,
|
|
47170
|
+
`Key "${norm}" has a synth range of ${first}-${ceiling}, which holds fewer than the ${poolSize} distinct values it has to generate; widen it or generate fewer records`
|
|
47112
47171
|
);
|
|
47113
47172
|
for (let i6 = 0; i6 < poolSize; i6++) pool.push(first + i6);
|
|
47114
47173
|
return pool;
|
|
@@ -48449,23 +48508,23 @@ var init_Diagnostics = __esm({
|
|
|
48449
48508
|
return [.../* @__PURE__ */ new Set([...inputs, ...chained])];
|
|
48450
48509
|
};
|
|
48451
48510
|
this._walk = (start, next, onCycle) => {
|
|
48452
|
-
const
|
|
48511
|
+
const path30 = [];
|
|
48453
48512
|
const onPath = /* @__PURE__ */ new Set();
|
|
48454
48513
|
const stack = [{ name: start, done: false }];
|
|
48455
48514
|
const visited = /* @__PURE__ */ new Set();
|
|
48456
48515
|
while (stack.length > 0) {
|
|
48457
48516
|
const frame = stack.pop();
|
|
48458
48517
|
if (frame.done) {
|
|
48459
|
-
onPath.delete(
|
|
48518
|
+
onPath.delete(path30.pop());
|
|
48460
48519
|
continue;
|
|
48461
48520
|
}
|
|
48462
48521
|
if (onPath.has(frame.name)) {
|
|
48463
|
-
|
|
48522
|
+
path30.slice(path30.indexOf(frame.name)).forEach((name) => onCycle.add(name));
|
|
48464
48523
|
continue;
|
|
48465
48524
|
}
|
|
48466
48525
|
if (visited.has(frame.name)) continue;
|
|
48467
48526
|
visited.add(frame.name);
|
|
48468
|
-
|
|
48527
|
+
path30.push(frame.name);
|
|
48469
48528
|
onPath.add(frame.name);
|
|
48470
48529
|
stack.push({ name: frame.name, done: true });
|
|
48471
48530
|
const successors = next.get(frame.name) ?? [];
|
|
@@ -49143,8 +49202,8 @@ var init_ResourceView = __esm({
|
|
|
49143
49202
|
if (!source || origin.path.length < 2) return { value: "declared here", muted: true };
|
|
49144
49203
|
return { value: `${source.resource}.${source.field}`, link: source.node };
|
|
49145
49204
|
};
|
|
49146
|
-
this._pathText = (
|
|
49147
|
-
return
|
|
49205
|
+
this._pathText = (path30) => {
|
|
49206
|
+
return path30.map((step) => `${step.resource}.${step.field}`).join(" \u2192 ");
|
|
49148
49207
|
};
|
|
49149
49208
|
this._consumerOutputsTable = (env2, consumer) => {
|
|
49150
49209
|
const declared = new Set((env2.sources ?? []).map((x2) => x2.name));
|
|
@@ -49886,17 +49945,30 @@ var init_LocalUsageDB = __esm({
|
|
|
49886
49945
|
}
|
|
49887
49946
|
};
|
|
49888
49947
|
/**
|
|
49889
|
-
* The recorded runs, newest first
|
|
49948
|
+
* The recorded runs, newest first — the newest `limit` of the rows `keep` accepts.
|
|
49949
|
+
*
|
|
49950
|
+
* Decoded from the end and stopped as soon as that many are held: a reader wants the recent
|
|
49890
49951
|
* executions, and decoding ten thousand rows to show ten of them is work nobody asked for.
|
|
49891
49952
|
*
|
|
49953
|
+
* `keep` is applied BEFORE the limit, which is the only way "the newest N runs of this kind" can
|
|
49954
|
+
* mean it: filtering afterwards lets the rows a caller does not want displace the ones it does,
|
|
49955
|
+
* so one sample recorded here would silently push a real run out of a history someone is reading.
|
|
49956
|
+
*
|
|
49892
49957
|
* Never throws. A missing file reads as no history, and so does a half-written one — this store is
|
|
49893
49958
|
* appended to while it is being read, so failing on that would make it unreadable during a run.
|
|
49894
49959
|
*/
|
|
49895
|
-
this.list = (limit = MAX_LISTED_ROWS) => {
|
|
49960
|
+
this.list = (limit = MAX_LISTED_ROWS, keep) => {
|
|
49896
49961
|
try {
|
|
49897
49962
|
if (!(0, import_fs16.existsSync)(CLI_DB_PATH))
|
|
49898
49963
|
return [];
|
|
49899
|
-
|
|
49964
|
+
const rows = this._getRows();
|
|
49965
|
+
const wanted = Math.max(1, limit);
|
|
49966
|
+
const found = [];
|
|
49967
|
+
for (let index = rows.length - 1; index >= 0 && found.length < wanted; index--) {
|
|
49968
|
+
const usage = this._fromLine(rows[index]);
|
|
49969
|
+
if (!keep || keep(usage)) found.push(usage);
|
|
49970
|
+
}
|
|
49971
|
+
return found;
|
|
49900
49972
|
} catch (error) {
|
|
49901
49973
|
this._handleError("list", error);
|
|
49902
49974
|
return [];
|
|
@@ -50077,9 +50149,10 @@ var init_UsageManager = __esm({
|
|
|
50077
50149
|
});
|
|
50078
50150
|
|
|
50079
50151
|
// ../../packages/engines/src/ops/RunOverlay.ts
|
|
50080
|
-
var MAX_RUNS, MAX_OPERATIONS, RunOverlayClass, RunOverlay, RunOverlay_default;
|
|
50152
|
+
var import_crypto5, MAX_RUNS, MAX_OPERATIONS, RunOverlayClass, RunOverlay, RunOverlay_default;
|
|
50081
50153
|
var init_RunOverlay = __esm({
|
|
50082
50154
|
"../../packages/engines/src/ops/RunOverlay.ts"() {
|
|
50155
|
+
import_crypto5 = __toESM(require("crypto"), 1);
|
|
50083
50156
|
init_src();
|
|
50084
50157
|
init_LineageEngine();
|
|
50085
50158
|
init_LocalUsageDB();
|
|
@@ -50088,6 +50161,8 @@ var init_RunOverlay = __esm({
|
|
|
50088
50161
|
MAX_OPERATIONS = 40;
|
|
50089
50162
|
RunOverlayClass = class {
|
|
50090
50163
|
constructor() {
|
|
50164
|
+
/** The last store token read, and the overlay token it resolved to. */
|
|
50165
|
+
this._signed = { store: null, overlay: "" };
|
|
50091
50166
|
/**
|
|
50092
50167
|
* The recent executions and their aggregates. ONE read of the store answers both, so a page
|
|
50093
50168
|
* showing a graph, a run list and a timing breakdown costs one file read rather than three.
|
|
@@ -50098,20 +50173,40 @@ var init_RunOverlay = __esm({
|
|
|
50098
50173
|
this.read = (limit = MAX_RUNS) => {
|
|
50099
50174
|
Affirm_default(limit > 0, "Run overlay limit must be greater than zero");
|
|
50100
50175
|
if (!UsageManager_default.recordsLocally()) return { runs: [], metrics: {} };
|
|
50101
|
-
const stats = LocalUsageDB_default.list(limit
|
|
50176
|
+
const stats = LocalUsageDB_default.list(limit, (stat) => !!stat?.consumer && stat.invokedBy !== "SAMPLE");
|
|
50102
50177
|
return { runs: stats.map((stat) => this._run(stat)), metrics: this._metrics(stats) };
|
|
50103
50178
|
};
|
|
50104
50179
|
/**
|
|
50105
|
-
* A token that changes when the
|
|
50180
|
+
* A token that changes when the OVERLAY does. Empty when there is nothing to read.
|
|
50106
50181
|
*
|
|
50107
50182
|
* A run finishing changes no configuration file, so the config hash cannot notice it — this is
|
|
50108
|
-
* the second half of what the preview polls
|
|
50109
|
-
*
|
|
50183
|
+
* the second half of what the preview polls.
|
|
50184
|
+
*
|
|
50185
|
+
* It signs the painted runs rather than the store, because the two do not move together: a
|
|
50186
|
+
* sample is recorded like any other execution and then filtered out of `read`, so the store's
|
|
50187
|
+
* own token changes while the picture does not. Signing the store there announced a project
|
|
50188
|
+
* change for every sample — and a page told its resource changed drops the sample it is showing,
|
|
50189
|
+
* which is exactly the rows someone had just asked for.
|
|
50190
|
+
*
|
|
50191
|
+
* Still cheap to poll: the store's mtime/size gates the read, so the rows are decoded only after
|
|
50192
|
+
* something has actually been written, never once a second.
|
|
50110
50193
|
*/
|
|
50111
50194
|
this.signature = () => {
|
|
50112
50195
|
if (!UsageManager_default.recordsLocally()) return "";
|
|
50113
|
-
|
|
50196
|
+
const store = LocalUsageDB_default.signature();
|
|
50197
|
+
if (store === this._signed.store) return this._signed.overlay;
|
|
50198
|
+
const overlay = this._token(this.read());
|
|
50199
|
+
this._signed = { store, overlay };
|
|
50200
|
+
return overlay;
|
|
50114
50201
|
};
|
|
50202
|
+
/**
|
|
50203
|
+
* A token over the whole overlay, runs and metrics alike.
|
|
50204
|
+
*
|
|
50205
|
+
* Everything it carries, not the newest row and a count: `endUsage` updates a row in place and a
|
|
50206
|
+
* finish action writes runs of its own, so a narrower token would miss a transition the graph
|
|
50207
|
+
* exists to show.
|
|
50208
|
+
*/
|
|
50209
|
+
this._token = (overlay) => import_crypto5.default.createHash("sha256").update(JSON.stringify(overlay)).digest("hex");
|
|
50115
50210
|
this._run = (stat) => {
|
|
50116
50211
|
const startedAt = this._iso(stat.startedAt);
|
|
50117
50212
|
return {
|
|
@@ -50358,64 +50453,6 @@ var init_PushdownEngine = __esm({
|
|
|
50358
50453
|
}
|
|
50359
50454
|
});
|
|
50360
50455
|
|
|
50361
|
-
// ../../packages/engines/src/producer/SampleEngine.ts
|
|
50362
|
-
var MAX_CHAIN_DEPTH2, SampleEngineClass, SampleEngine, SampleEngine_default;
|
|
50363
|
-
var init_SampleEngine = __esm({
|
|
50364
|
-
"../../packages/engines/src/producer/SampleEngine.ts"() {
|
|
50365
|
-
init_src();
|
|
50366
|
-
init_src4();
|
|
50367
|
-
init_ProducerEngine();
|
|
50368
|
-
MAX_CHAIN_DEPTH2 = 20;
|
|
50369
|
-
SampleEngineClass = class {
|
|
50370
|
-
constructor() {
|
|
50371
|
-
/**
|
|
50372
|
-
* @param resourceName a producer or consumer name, as declared.
|
|
50373
|
-
* @throws when the name is neither, or when the source cannot be read.
|
|
50374
|
-
*/
|
|
50375
|
-
this.read = async (resourceName, size = 10) => {
|
|
50376
|
-
Affirm_default(resourceName, "Missing resource name to sample");
|
|
50377
|
-
Affirm_default(size > 0, "Sample size must be greater than 0");
|
|
50378
|
-
const producer = Environment_default.getProducer(resourceName);
|
|
50379
|
-
if (producer)
|
|
50380
|
-
return { kind: "producer", name: resourceName, records: await ProducerEngine_default.readSampleData(producer, size, false) };
|
|
50381
|
-
const consumer = Environment_default.getConsumer(resourceName);
|
|
50382
|
-
Affirm_default(consumer, `Resource "${resourceName}" not found. Please check if it exists as a producer or consumer.`);
|
|
50383
|
-
return { kind: "consumer", name: resourceName, records: await this._readConsumer(consumer, size, []) };
|
|
50384
|
-
};
|
|
50385
|
-
/**
|
|
50386
|
-
* A consumer has no data of its own until it runs, so what it shows is its FIRST input, projected
|
|
50387
|
-
* through its field list — near enough to answer "what will come out of this" without a run.
|
|
50388
|
-
*/
|
|
50389
|
-
this._readConsumer = async (consumer, size, visited) => {
|
|
50390
|
-
Affirm_default(!visited.includes(consumer.name), `Consumer "${consumer.name}" reads itself through ${visited.join(" \u2192 ")}`);
|
|
50391
|
-
Affirm_default(visited.length < MAX_CHAIN_DEPTH2, `Consumer chain from "${visited[0]}" is more than ${MAX_CHAIN_DEPTH2} deep`);
|
|
50392
|
-
const input = consumer.producers[0];
|
|
50393
|
-
Affirm_default(input, `Consumer "${consumer.name}" has no producers configured`);
|
|
50394
|
-
const producer = Environment_default.getProducer(input.name);
|
|
50395
|
-
if (!producer) {
|
|
50396
|
-
const upstream = Environment_default.getConsumer(input.name);
|
|
50397
|
-
Affirm_default(upstream, `Producer or consumer "${input.name}" not found for consumer "${consumer.name}"`);
|
|
50398
|
-
return this._readConsumer(upstream, size, [...visited, consumer.name]);
|
|
50399
|
-
}
|
|
50400
|
-
const records = await ProducerEngine_default.readSampleData(producer, size, false);
|
|
50401
|
-
if (consumer.fields.some((field) => field.key === "*")) return records;
|
|
50402
|
-
return records.map((record) => this._project(consumer, record));
|
|
50403
|
-
};
|
|
50404
|
-
this._project = (consumer, record) => {
|
|
50405
|
-
const projected = {};
|
|
50406
|
-
for (const field of consumer.fields) {
|
|
50407
|
-
if (field.key === "*") continue;
|
|
50408
|
-
projected[field.alias || field.key] = record[field.key];
|
|
50409
|
-
}
|
|
50410
|
-
return projected;
|
|
50411
|
-
};
|
|
50412
|
-
}
|
|
50413
|
-
};
|
|
50414
|
-
SampleEngine = new SampleEngineClass();
|
|
50415
|
-
SampleEngine_default = SampleEngine;
|
|
50416
|
-
}
|
|
50417
|
-
});
|
|
50418
|
-
|
|
50419
50456
|
// ../../packages/engines/src/transform/LookupRegistry.ts
|
|
50420
50457
|
var LookupRegistryClass, LookupRegistry, LookupRegistry_default;
|
|
50421
50458
|
var init_LookupRegistry = __esm({
|
|
@@ -50452,12 +50489,12 @@ var init_LookupRegistry = __esm({
|
|
|
50452
50489
|
});
|
|
50453
50490
|
|
|
50454
50491
|
// ../../packages/engines/src/transform/LookupKey.ts
|
|
50455
|
-
var
|
|
50492
|
+
var import_crypto6, DELIMITER, idFor, LookupKey, LookupKey_default;
|
|
50456
50493
|
var init_LookupKey = __esm({
|
|
50457
50494
|
"../../packages/engines/src/transform/LookupKey.ts"() {
|
|
50458
|
-
|
|
50495
|
+
import_crypto6 = __toESM(require("crypto"), 1);
|
|
50459
50496
|
DELIMITER = String.fromCharCode(0);
|
|
50460
|
-
idFor = (ref) =>
|
|
50497
|
+
idFor = (ref) => import_crypto6.default.createHash("sha1").update([ref.consumer, ref.key, ref.value].join(DELIMITER)).digest("hex").slice(0, 16);
|
|
50461
50498
|
LookupKey = { idFor };
|
|
50462
50499
|
LookupKey_default = LookupKey;
|
|
50463
50500
|
}
|
|
@@ -51094,7 +51131,6 @@ var init_src10 = __esm({
|
|
|
51094
51131
|
init_ProducerEngine();
|
|
51095
51132
|
init_ProducerManager();
|
|
51096
51133
|
init_PushdownEngine();
|
|
51097
|
-
init_SampleEngine();
|
|
51098
51134
|
init_TransformationEngine();
|
|
51099
51135
|
init_TypeCaster();
|
|
51100
51136
|
init_LookupRegistry();
|
|
@@ -51264,14 +51300,14 @@ var init_OutputExecutor = __esm({
|
|
|
51264
51300
|
});
|
|
51265
51301
|
|
|
51266
51302
|
// ../../packages/executors/src/ConsumerExecutor.ts
|
|
51267
|
-
var import_path23, import_fs17, import_readline6, import_promises13,
|
|
51303
|
+
var import_path23, import_fs17, import_readline6, import_promises13, import_crypto7, LIMIT_SCAN_BYTES, NEWLINE_BYTE, ConsumerExecutorClass, ConsumerExecutor, ConsumerExecutor_default;
|
|
51268
51304
|
var init_ConsumerExecutor = __esm({
|
|
51269
51305
|
"../../packages/executors/src/ConsumerExecutor.ts"() {
|
|
51270
51306
|
import_path23 = __toESM(require("path"));
|
|
51271
51307
|
import_fs17 = __toESM(require("fs"));
|
|
51272
51308
|
import_readline6 = __toESM(require("readline"));
|
|
51273
51309
|
import_promises13 = __toESM(require("fs/promises"));
|
|
51274
|
-
|
|
51310
|
+
import_crypto7 = __toESM(require("crypto"));
|
|
51275
51311
|
init_src10();
|
|
51276
51312
|
init_src10();
|
|
51277
51313
|
init_src10();
|
|
@@ -51510,7 +51546,7 @@ var init_ConsumerExecutor = __esm({
|
|
|
51510
51546
|
let newLineCount = 0;
|
|
51511
51547
|
const seen = /* @__PURE__ */ new Set();
|
|
51512
51548
|
for await (const line of lineReader) {
|
|
51513
|
-
const hash =
|
|
51549
|
+
const hash = import_crypto7.default.createHash("sha1").update(line).digest("base64");
|
|
51514
51550
|
if (!seen.has(hash)) {
|
|
51515
51551
|
seen.add(hash);
|
|
51516
51552
|
if (!writer.write(line + "\n"))
|
|
@@ -51786,8 +51822,6 @@ var init_ProducerExecutor = __esm({
|
|
|
51786
51822
|
init_src8();
|
|
51787
51823
|
init_src4();
|
|
51788
51824
|
init_src10();
|
|
51789
|
-
init_src();
|
|
51790
|
-
init_src10();
|
|
51791
51825
|
init_src10();
|
|
51792
51826
|
import_path24 = __toESM(require("path"));
|
|
51793
51827
|
init_src2();
|
|
@@ -51818,20 +51852,14 @@ var init_ProducerExecutor = __esm({
|
|
|
51818
51852
|
* the source file name or a mask, and what that file name is. None of it depends on the line, so
|
|
51819
51853
|
* it is compiled once per chunk instead of rebuilt per line — including the mask's own
|
|
51820
51854
|
* validation, which used to run once per dimension per line.
|
|
51855
|
+
*
|
|
51856
|
+
* The fills themselves come from `ProducerManager`, which is also where the sample path gets
|
|
51857
|
+
* them: one producer stage, reached from two places.
|
|
51821
51858
|
*/
|
|
51822
51859
|
this.compileLine = (dimensions, chunk) => {
|
|
51823
51860
|
Affirm_default(dimensions, "Invalid producer dimensions");
|
|
51824
51861
|
Affirm_default(chunk, "Invalid chunk");
|
|
51825
|
-
|
|
51826
|
-
for (const dimension of dimensions) {
|
|
51827
|
-
const { prodDimension } = dimension;
|
|
51828
|
-
const mask = ProducerManager_default.getMask(prodDimension);
|
|
51829
|
-
const sourceFilename = prodDimension.sourceFilename === true;
|
|
51830
|
-
if (!sourceFilename && !Algo_default.hasVal(mask))
|
|
51831
|
-
continue;
|
|
51832
|
-
fills.push({ key: dimension.name, mask, sourceFilename, type: prodDimension.type });
|
|
51833
|
-
}
|
|
51834
|
-
return { fills, sourceFilename: import_path24.default.basename(chunk.fileUri) };
|
|
51862
|
+
return { fills: ProducerManager_default.compileFills(dimensions), sourceFilename: import_path24.default.basename(chunk.fileUri) };
|
|
51835
51863
|
};
|
|
51836
51864
|
/**
|
|
51837
51865
|
* The record(s) one source line produces. Almost always exactly one — a producer with a group
|
|
@@ -51852,14 +51880,8 @@ var init_ProducerExecutor = __esm({
|
|
|
51852
51880
|
if (plan.fills.length === 0)
|
|
51853
51881
|
return records;
|
|
51854
51882
|
counter = performance.now();
|
|
51855
|
-
for (const record of records)
|
|
51856
|
-
|
|
51857
|
-
if (fill2.sourceFilename)
|
|
51858
|
-
record[fill2.key] = plan.sourceFilename;
|
|
51859
|
-
if (Algo_default.hasVal(fill2.mask))
|
|
51860
|
-
record[fill2.key] = CryptoEngine_default.hashValue(fill2.mask, record[fill2.key]?.toString(), fill2.type);
|
|
51861
|
-
}
|
|
51862
|
-
}
|
|
51883
|
+
for (const record of records)
|
|
51884
|
+
ProducerManager_default.applyFills(record, plan.fills, plan.sourceFilename);
|
|
51863
51885
|
tracker.measure("process-line:dimensions-filename-mask", performance.now() - counter);
|
|
51864
51886
|
return records;
|
|
51865
51887
|
};
|
|
@@ -52627,7 +52649,8 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
52627
52649
|
} catch (error) {
|
|
52628
52650
|
Logger_default.log(`[${usageId}] Consumer "${consumer.name}" failed: ${Helper_default.asError(error).message} | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
52629
52651
|
Logger_default.error(error);
|
|
52630
|
-
|
|
52652
|
+
if (!request.skipExport)
|
|
52653
|
+
await ConsumerOnFinishManager_default.onConsumerError(consumer, usageId, this.makeOnFinishRunner(consumer, request, usageId));
|
|
52631
52654
|
Logger_default.log(`[${usageId}] Running cleanup after failure`);
|
|
52632
52655
|
await this.performCleanupOperations(scope, tracker);
|
|
52633
52656
|
UsageManager_default.failUsage(usageId, Helper_default.asError(error).message);
|
|
@@ -53826,6 +53849,164 @@ init_src4();
|
|
|
53826
53849
|
init_OutputExecutor();
|
|
53827
53850
|
init_ProducerExecutor();
|
|
53828
53851
|
|
|
53852
|
+
// ../../packages/executors/src/SampleRunner.ts
|
|
53853
|
+
var import_fs21 = __toESM(require("fs"));
|
|
53854
|
+
var import_promises18 = __toESM(require("fs/promises"));
|
|
53855
|
+
var import_path29 = __toESM(require("path"));
|
|
53856
|
+
var import_readline10 = __toESM(require("readline"));
|
|
53857
|
+
init_src();
|
|
53858
|
+
init_src3();
|
|
53859
|
+
init_src4();
|
|
53860
|
+
init_src8();
|
|
53861
|
+
init_src10();
|
|
53862
|
+
init_src5();
|
|
53863
|
+
init_src2();
|
|
53864
|
+
init_ExecutorOrchestrator();
|
|
53865
|
+
var MAX_WALK_DEPTH = 20;
|
|
53866
|
+
var SAMPLE_USER = { user: { _id: "sample", name: "sample", type: "licence" } };
|
|
53867
|
+
var SampleRunnerClass = class {
|
|
53868
|
+
constructor() {
|
|
53869
|
+
/**
|
|
53870
|
+
* @param resourceName a producer or consumer name, as declared.
|
|
53871
|
+
* @param rows the most rows to return, capped at `MAX_SAMPLE_ROWS`.
|
|
53872
|
+
* @throws when the name is neither, when a source cannot be read, or when the pipeline itself
|
|
53873
|
+
* fails — a consumer whose transformation throws is a sample that reports the reason.
|
|
53874
|
+
*/
|
|
53875
|
+
this.read = async (resourceName, rows = 10, details) => {
|
|
53876
|
+
Affirm_default(resourceName, "Missing resource name to sample");
|
|
53877
|
+
Affirm_default(rows > 0, "Sample size must be greater than 0");
|
|
53878
|
+
const size = Math.min(rows, Constants_default.defaults.MAX_SAMPLE_ROWS);
|
|
53879
|
+
const producer = Environment_default.getProducer(resourceName);
|
|
53880
|
+
if (producer) {
|
|
53881
|
+
const records = await ProducerEngine_default.readSampleData(producer, size, false);
|
|
53882
|
+
return {
|
|
53883
|
+
kind: "producer",
|
|
53884
|
+
name: resourceName,
|
|
53885
|
+
records,
|
|
53886
|
+
note: `${records.length} row(s) read from the source now, with this producer's dimensions, types and masks applied.`
|
|
53887
|
+
};
|
|
53888
|
+
}
|
|
53889
|
+
const consumer = Environment_default.getConsumer(resourceName);
|
|
53890
|
+
Affirm_default(consumer, `Resource "${resourceName}" not found. Please check if it exists as a producer or consumer.`);
|
|
53891
|
+
return this._runConsumer(consumer, size, details);
|
|
53892
|
+
};
|
|
53893
|
+
/**
|
|
53894
|
+
* Runs the consumer for real and reads back what it produced.
|
|
53895
|
+
*
|
|
53896
|
+
* The output is captured instead of exported, so nothing reaches the consumer's destination and
|
|
53897
|
+
* no onSuccess action fires: looking at data must never be indistinguishable from publishing it.
|
|
53898
|
+
*/
|
|
53899
|
+
this._runConsumer = async (consumer, size, details) => {
|
|
53900
|
+
this._assertStageable(consumer);
|
|
53901
|
+
const capturePath = import_path29.default.resolve(import_path29.default.join(ExecutorScope_default.getBasePath(), `sample_${consumer.name}_${Helper_default.uuid()}.jsonl`));
|
|
53902
|
+
ExecutorScope_default.ensurePath(capturePath);
|
|
53903
|
+
try {
|
|
53904
|
+
const result = await ExecutorOrchestrator_default.launch({
|
|
53905
|
+
consumer,
|
|
53906
|
+
// `SAMPLE`, so the run it records is distinguishable from one the project did. The
|
|
53907
|
+
// executor treats it like any other invocation; only `RunOverlay` reads the marker.
|
|
53908
|
+
details: { ...details ?? SAMPLE_USER, invokedBy: "SAMPLE" },
|
|
53909
|
+
// The rows asked for, bounded the way `remora run -l` bounds them — pushed to the
|
|
53910
|
+
// source and stopped early per worker wherever nothing downstream can drop a record.
|
|
53911
|
+
options: { limit: size },
|
|
53912
|
+
// Nothing reaches the destination and no finish action fires; the merged dataset
|
|
53913
|
+
// comes back through the same capture the lookup and consumer-input runs use.
|
|
53914
|
+
skipExport: true,
|
|
53915
|
+
captureMainDatasetTo: capturePath,
|
|
53916
|
+
captureFormat: "JSONL"
|
|
53917
|
+
});
|
|
53918
|
+
const captured = await this._readCapture(capturePath, size);
|
|
53919
|
+
const records = captured.map((record) => this._inFieldOrder(consumer, record));
|
|
53920
|
+
Logger_default.log(`[sample] Consumer "${consumer.name}": ${records.length} row(s) from ${result.inputCount} input line(s)`);
|
|
53921
|
+
return {
|
|
53922
|
+
kind: "consumer",
|
|
53923
|
+
name: consumer.name,
|
|
53924
|
+
records,
|
|
53925
|
+
note: `${records.length} row(s) from the real pipeline \u2014 filters, transformations, masking and validation all applied, exactly as in a run. Nothing was written to the destination.`
|
|
53926
|
+
};
|
|
53927
|
+
} finally {
|
|
53928
|
+
await import_promises18.default.rm(capturePath, { force: true });
|
|
53929
|
+
}
|
|
53930
|
+
};
|
|
53931
|
+
/**
|
|
53932
|
+
* Refuses, naming the source, when a run could not read one of this consumer's producers.
|
|
53933
|
+
*
|
|
53934
|
+
* `RedshiftDriver.ready` and `HttpApiSourceDriver.ready` are unimplemented, so the pipeline
|
|
53935
|
+
* cannot reach those sources at all. Checked up front because the alternative is a run that
|
|
53936
|
+
* spins up a worker pool and then surfaces "Not implemented yet" from three layers down, which
|
|
53937
|
+
* names neither the producer nor the source that caused it.
|
|
53938
|
+
*/
|
|
53939
|
+
this._assertStageable = (consumer) => {
|
|
53940
|
+
for (const producer of this._inputProducers(consumer)) {
|
|
53941
|
+
const source = Environment_default.getSource(producer.source);
|
|
53942
|
+
Affirm_default(source, `Producer "${producer.name}" of consumer "${consumer.name}" has no source "${producer.source}"`);
|
|
53943
|
+
if (!DriverFactory_default.canStage(source.engine))
|
|
53944
|
+
throw new Error(`Cannot sample consumer "${consumer.name}": its producer "${producer.name}" reads source "${source.name}", and a "${source.engine}" source cannot be run through the pipeline yet. Sample the producer "${producer.name}" instead to see its rows.`);
|
|
53945
|
+
}
|
|
53946
|
+
};
|
|
53947
|
+
/**
|
|
53948
|
+
* Every producer a run of this consumer would stage — its own, plus those of the consumers it
|
|
53949
|
+
* reads as input or consults as a code_lookup table, since each of those is executed too.
|
|
53950
|
+
*/
|
|
53951
|
+
this._inputProducers = (consumer, visited = []) => {
|
|
53952
|
+
Affirm_default(!visited.includes(consumer.name), `Consumer "${consumer.name}" reads itself through ${visited.join(" \u2192 ")}`);
|
|
53953
|
+
Affirm_default(visited.length < MAX_WALK_DEPTH, `Consumer chain from "${visited[0]}" is more than ${MAX_WALK_DEPTH} deep`);
|
|
53954
|
+
const seen = [...visited, consumer.name];
|
|
53955
|
+
const upstream = [
|
|
53956
|
+
...consumer.producers.map((entry) => entry.name),
|
|
53957
|
+
...ConsumerManager_default.collectLookupRefs(ConsumerManager_default.getExpandedFields(consumer)).map((ref) => ref.consumer)
|
|
53958
|
+
];
|
|
53959
|
+
return upstream.flatMap((name) => {
|
|
53960
|
+
const producer = Environment_default.getProducer(name);
|
|
53961
|
+
if (producer) return [producer];
|
|
53962
|
+
const nested = Environment_default.getConsumer(name);
|
|
53963
|
+
Affirm_default(nested, `Producer or consumer "${name}" not found for consumer "${consumer.name}"`);
|
|
53964
|
+
return this._inputProducers(nested, seen);
|
|
53965
|
+
});
|
|
53966
|
+
};
|
|
53967
|
+
/**
|
|
53968
|
+
* One row's keys in the order the consumer DECLARES its fields.
|
|
53969
|
+
*
|
|
53970
|
+
* A record comes out of a worker keyed in the order the producer's dimensions were read, which is
|
|
53971
|
+
* the source's layout and not the consumer's — so a consumer declaring country, plan, is_active
|
|
53972
|
+
* over a producer that stores is_active first would be shown its own columns in someone else's
|
|
53973
|
+
* order. Invisible in an exported JSON object; the whole shape of the table here.
|
|
53974
|
+
*
|
|
53975
|
+
* Anything the record carries that the field list does not name is appended rather than dropped:
|
|
53976
|
+
* a column that turns up unexpectedly is exactly the one worth seeing.
|
|
53977
|
+
*/
|
|
53978
|
+
this._inFieldOrder = (consumer, record) => {
|
|
53979
|
+
const declared = ConsumerManager_default.getExpandedFields(consumer).filter((field) => !field.cField.hidden).map((field) => field.finalKey);
|
|
53980
|
+
const ordered = {};
|
|
53981
|
+
for (const key of declared) {
|
|
53982
|
+
if (key in record) ordered[key] = record[key];
|
|
53983
|
+
}
|
|
53984
|
+
for (const key of Object.keys(record)) {
|
|
53985
|
+
if (!(key in ordered)) ordered[key] = record[key];
|
|
53986
|
+
}
|
|
53987
|
+
return ordered;
|
|
53988
|
+
};
|
|
53989
|
+
/** The captured dataset, which `captureFormat: 'JSONL'` guarantees is one JSON object per line. */
|
|
53990
|
+
this._readCapture = async (capturePath, size) => {
|
|
53991
|
+
if (!import_fs21.default.existsSync(capturePath)) return [];
|
|
53992
|
+
const records = [];
|
|
53993
|
+
const lines = import_readline10.default.createInterface({ input: import_fs21.default.createReadStream(capturePath), crlfDelay: Infinity });
|
|
53994
|
+
try {
|
|
53995
|
+
for await (const line of lines) {
|
|
53996
|
+
if (!line.length) continue;
|
|
53997
|
+
records.push(JSON.parse(line));
|
|
53998
|
+
if (records.length >= size) break;
|
|
53999
|
+
}
|
|
54000
|
+
} finally {
|
|
54001
|
+
lines.close();
|
|
54002
|
+
}
|
|
54003
|
+
return records;
|
|
54004
|
+
};
|
|
54005
|
+
}
|
|
54006
|
+
};
|
|
54007
|
+
var SampleRunner = new SampleRunnerClass();
|
|
54008
|
+
var SampleRunner_default = SampleRunner;
|
|
54009
|
+
|
|
53829
54010
|
// src/actions/run.ts
|
|
53830
54011
|
init_src4();
|
|
53831
54012
|
init_src2();
|
|
@@ -54018,6 +54199,7 @@ init_src5();
|
|
|
54018
54199
|
init_src2();
|
|
54019
54200
|
var WATCH_INTERVAL_MS = 1e3;
|
|
54020
54201
|
var SAMPLEABLE_KINDS = ["producer", "consumer"];
|
|
54202
|
+
var PIPELINE_SAMPLE_KINDS = ["consumer"];
|
|
54021
54203
|
var EDITABLE_KINDS = {
|
|
54022
54204
|
source: "source",
|
|
54023
54205
|
producer: "producer",
|
|
@@ -54054,11 +54236,15 @@ var PreviewProject = class {
|
|
|
54054
54236
|
this._files = [];
|
|
54055
54237
|
this._hash = null;
|
|
54056
54238
|
/**
|
|
54057
|
-
*
|
|
54239
|
+
* What this page is currently executing, or null — a run, or a consumer sample.
|
|
54058
54240
|
*
|
|
54059
|
-
* One at a time, deliberately: `remora run` runs consumers sequentially, and two concurrent
|
|
54060
|
-
* would each spin up a worker pool in this one process. Refusing the second with the
|
|
54061
|
-
* first is a better answer than thrashing the machine.
|
|
54241
|
+
* One at a time, deliberately: `remora run` runs consumers sequentially, and two concurrent
|
|
54242
|
+
* executions would each spin up a worker pool in this one process. Refusing the second with the
|
|
54243
|
+
* name of the first is a better answer than thrashing the machine.
|
|
54244
|
+
*
|
|
54245
|
+
* A sample takes the same lock because it IS an execution: bounded at the input and exporting
|
|
54246
|
+
* nothing, but a worker pool either way. That also means a sample and a run cannot overlap, which
|
|
54247
|
+
* is what stops a click on one panel from slowing the run someone is watching on another.
|
|
54062
54248
|
*/
|
|
54063
54249
|
this._running = null;
|
|
54064
54250
|
/** @returns true when the project changed and the snapshot was rebuilt. */
|
|
@@ -54104,18 +54290,47 @@ var PreviewProject = class {
|
|
|
54104
54290
|
]
|
|
54105
54291
|
};
|
|
54106
54292
|
},
|
|
54107
|
-
sample: async (nodeId, rows) =>
|
|
54108
|
-
if (!this._has(nodeId)) return null;
|
|
54109
|
-
const { kind, name } = LineageEngine_default.parseNodeId(nodeId);
|
|
54110
|
-
if (!SAMPLEABLE_KINDS.includes(kind)) return null;
|
|
54111
|
-
const { records } = await SampleEngine_default.read(name, rows);
|
|
54112
|
-
return ResourceView_default.buildSampleTable(records);
|
|
54113
|
-
},
|
|
54293
|
+
sample: async (nodeId, rows) => this._sample(nodeId, rows),
|
|
54114
54294
|
run: async (nodeId) => this._run(nodeId),
|
|
54115
54295
|
file: (nodeId) => this._file(nodeId),
|
|
54116
54296
|
save: async (nodeId, content) => this._save(nodeId, content),
|
|
54117
54297
|
logs: (lines) => this._logs(lines)
|
|
54118
54298
|
});
|
|
54299
|
+
/**
|
|
54300
|
+
* The rows a resource actually holds, as a table.
|
|
54301
|
+
*
|
|
54302
|
+
* A producer answers from a bounded read of its source. A consumer answers by RUNNING — every
|
|
54303
|
+
* filter, transformation, mask, union, distinct, pivot and validation it declares, over a
|
|
54304
|
+
* bounded slice of each producer's input, exporting nothing and recording nothing. Which of the
|
|
54305
|
+
* two happened, and over how much input, is the note `SampleRunner` returns; it becomes the
|
|
54306
|
+
* table's title, so the panel states what it is showing instead of leaving it to be assumed.
|
|
54307
|
+
*
|
|
54308
|
+
* Awaited, unlike a run: a sample is bounded, and its ANSWER is the rows — there is nothing for
|
|
54309
|
+
* the run overlay to show, because a sample deliberately leaves no trace on it.
|
|
54310
|
+
*/
|
|
54311
|
+
this._sample = async (nodeId, rows) => {
|
|
54312
|
+
if (!this._has(nodeId)) return null;
|
|
54313
|
+
const { kind, name } = LineageEngine_default.parseNodeId(nodeId);
|
|
54314
|
+
if (!SAMPLEABLE_KINDS.includes(kind)) return null;
|
|
54315
|
+
const pipeline5 = PIPELINE_SAMPLE_KINDS.includes(kind);
|
|
54316
|
+
if (pipeline5 && this._running)
|
|
54317
|
+
throw new Error(`"${this._running}" is still running; one execution at a time`);
|
|
54318
|
+
const blocking = pipeline5 ? this.snapshot.diagnostics.find((x2) => x2.node === nodeId && x2.severity === "error") : null;
|
|
54319
|
+
if (blocking) throw new Error(`Refusing to sample "${name}": ${blocking.message}`);
|
|
54320
|
+
if (pipeline5) this._running = name;
|
|
54321
|
+
try {
|
|
54322
|
+
const licence = LicenceManager_default.validate(ProcessENVManager_default.getEnvVariable("REMORA_LICENCE_KEY"));
|
|
54323
|
+
const { records, note } = await SampleRunner_default.read(name, rows, {
|
|
54324
|
+
// A sample from the preview is a sample from the CLI. It records no usage, so this
|
|
54325
|
+
// only ever names the run in the log.
|
|
54326
|
+
invokedBy: "CLI",
|
|
54327
|
+
user: { _id: licence.customer, name: licence.customer, type: "licence" }
|
|
54328
|
+
});
|
|
54329
|
+
return ResourceView_default.buildSampleTable(records, note);
|
|
54330
|
+
} finally {
|
|
54331
|
+
if (pipeline5) this._running = null;
|
|
54332
|
+
}
|
|
54333
|
+
};
|
|
54119
54334
|
/**
|
|
54120
54335
|
* Executes a consumer, and answers as soon as it has STARTED.
|
|
54121
54336
|
*
|
|
@@ -54127,7 +54342,7 @@ var PreviewProject = class {
|
|
|
54127
54342
|
if (!this._has(nodeId)) return { started: false, message: `No resource "${nodeId}" in this project` };
|
|
54128
54343
|
const { kind, name } = LineageEngine_default.parseNodeId(nodeId);
|
|
54129
54344
|
if (kind !== "consumer") return { started: false, message: `Only a consumer can be run; "${name}" is a ${kind}` };
|
|
54130
|
-
if (this._running) return { started: false, message: `"${this._running}" is still running; one
|
|
54345
|
+
if (this._running) return { started: false, message: `"${this._running}" is still running; one execution at a time` };
|
|
54131
54346
|
const blocking = this.snapshot.diagnostics.find((x2) => x2.node === nodeId && x2.severity === "error");
|
|
54132
54347
|
if (blocking) return { started: false, message: `Refusing to run "${name}": ${blocking.message}` };
|
|
54133
54348
|
const consumer = Environment_default.getConsumer(name);
|
|
@@ -54251,22 +54466,22 @@ var stopOnSignal = (server, watch) => {
|
|
|
54251
54466
|
|
|
54252
54467
|
// src/actions/create_producer.ts
|
|
54253
54468
|
var import_chalk11 = __toESM(require("chalk"));
|
|
54254
|
-
var
|
|
54255
|
-
var
|
|
54469
|
+
var import_fs22 = __toESM(require("fs"));
|
|
54470
|
+
var import_path30 = __toESM(require("path"));
|
|
54256
54471
|
init_src2();
|
|
54257
54472
|
var create_producer = async (name) => {
|
|
54258
54473
|
try {
|
|
54259
|
-
if (!
|
|
54474
|
+
if (!import_fs22.default.existsSync("./remora/producers")) {
|
|
54260
54475
|
throw new Error(import_chalk11.default.red("Missing directory: ") + import_chalk11.default.yellow("./remora/producers"));
|
|
54261
54476
|
}
|
|
54262
|
-
const defaultProducerTemplate =
|
|
54263
|
-
|
|
54477
|
+
const defaultProducerTemplate = import_fs22.default.readFileSync(
|
|
54478
|
+
import_path30.default.join(DOCUMENTATION_DIR, "default_resources/producer.json"),
|
|
54264
54479
|
"utf-8"
|
|
54265
54480
|
);
|
|
54266
54481
|
const defaultProducer = JSON.parse(defaultProducerTemplate);
|
|
54267
54482
|
defaultProducer.name = name;
|
|
54268
|
-
const producerPath =
|
|
54269
|
-
|
|
54483
|
+
const producerPath = import_path30.default.join("remora/producers", `${name}.json`);
|
|
54484
|
+
import_fs22.default.writeFileSync(producerPath, JSON.stringify(defaultProducer, null, 4));
|
|
54270
54485
|
console.log(import_chalk11.default.green(`\u2705 Created producer config at ${producerPath}`));
|
|
54271
54486
|
console.log(import_chalk11.default.blue("Remember to:"));
|
|
54272
54487
|
console.log(import_chalk11.default.blue("1. Set the correct source name"));
|
|
@@ -54281,26 +54496,26 @@ var create_producer = async (name) => {
|
|
|
54281
54496
|
|
|
54282
54497
|
// src/actions/create_consumer.ts
|
|
54283
54498
|
var import_chalk12 = __toESM(require("chalk"));
|
|
54284
|
-
var
|
|
54285
|
-
var
|
|
54499
|
+
var import_fs23 = __toESM(require("fs"));
|
|
54500
|
+
var import_path31 = __toESM(require("path"));
|
|
54286
54501
|
init_src2();
|
|
54287
54502
|
var create_consumer = async (name, producerName) => {
|
|
54288
54503
|
try {
|
|
54289
|
-
if (!
|
|
54504
|
+
if (!import_fs23.default.existsSync("./remora/consumers")) {
|
|
54290
54505
|
throw new Error(import_chalk12.default.red("Missing directory: ") + import_chalk12.default.yellow("./remora/consumers"));
|
|
54291
54506
|
}
|
|
54292
|
-
const defaultConsumerTemplate =
|
|
54293
|
-
|
|
54507
|
+
const defaultConsumerTemplate = import_fs23.default.readFileSync(
|
|
54508
|
+
import_path31.default.join(DOCUMENTATION_DIR, "default_resources/consumer.json"),
|
|
54294
54509
|
"utf-8"
|
|
54295
54510
|
);
|
|
54296
54511
|
const defaultConsumer = JSON.parse(defaultConsumerTemplate);
|
|
54297
54512
|
defaultConsumer.name = name;
|
|
54298
54513
|
if (producerName) {
|
|
54299
|
-
const producerPath =
|
|
54300
|
-
if (!
|
|
54514
|
+
const producerPath = import_path31.default.join("remora/producers", `${producerName}.json`);
|
|
54515
|
+
if (!import_fs23.default.existsSync(producerPath)) {
|
|
54301
54516
|
throw new Error(import_chalk12.default.red("Producer not found: ") + import_chalk12.default.yellow(producerPath));
|
|
54302
54517
|
}
|
|
54303
|
-
const producerConfig = JSON.parse(
|
|
54518
|
+
const producerConfig = JSON.parse(import_fs23.default.readFileSync(producerPath, "utf-8"));
|
|
54304
54519
|
defaultConsumer.producers = [{ name: producerName }];
|
|
54305
54520
|
defaultConsumer.fields = producerConfig.dimensions.map((dim) => ({
|
|
54306
54521
|
key: dim.name,
|
|
@@ -54319,8 +54534,8 @@ var create_consumer = async (name, producerName) => {
|
|
|
54319
54534
|
defaultConsumer.filters = void 0;
|
|
54320
54535
|
defaultConsumer.metadata = void 0;
|
|
54321
54536
|
}
|
|
54322
|
-
const consumerPath =
|
|
54323
|
-
|
|
54537
|
+
const consumerPath = import_path31.default.join("remora/consumers", `${name}.json`);
|
|
54538
|
+
import_fs23.default.writeFileSync(consumerPath, JSON.stringify(defaultConsumer, null, 4));
|
|
54324
54539
|
console.log(import_chalk12.default.green(`\u2705 Created consumer config at ${consumerPath}`));
|
|
54325
54540
|
if (!producerName) {
|
|
54326
54541
|
console.log(import_chalk12.default.blue("Remember to:"));
|
|
@@ -54348,8 +54563,8 @@ init_src5();
|
|
|
54348
54563
|
var import_chalk13 = __toESM(require("chalk"));
|
|
54349
54564
|
var import_ora5 = __toESM(require("ora"));
|
|
54350
54565
|
init_src10();
|
|
54351
|
-
var
|
|
54352
|
-
var
|
|
54566
|
+
var import_fs24 = __toESM(require("fs"));
|
|
54567
|
+
var import_path32 = __toESM(require("path"));
|
|
54353
54568
|
init_src4();
|
|
54354
54569
|
init_src10();
|
|
54355
54570
|
init_src2();
|
|
@@ -54378,14 +54593,14 @@ var automap = async (producerName, schemaNames) => {
|
|
|
54378
54593
|
const mapResult = await AutoMapperEngine_default.map(sampleStrings, schemas, producer.settings.fileKey, [source]);
|
|
54379
54594
|
for (const producer2 of mapResult.producers) {
|
|
54380
54595
|
const producerPath = Environment_default.resolveResourceFilePath("producers", producer2.name);
|
|
54381
|
-
|
|
54382
|
-
|
|
54596
|
+
import_fs24.default.mkdirSync(import_path32.default.dirname(producerPath), { recursive: true });
|
|
54597
|
+
import_fs24.default.writeFileSync(producerPath, JSON.stringify(producer2, null, 4));
|
|
54383
54598
|
console.log(import_chalk13.default.blue(`Created producer: ${producer2.name}`));
|
|
54384
54599
|
}
|
|
54385
54600
|
for (const consumer of mapResult.consumers) {
|
|
54386
54601
|
const consumerPath = Environment_default.resolveResourceFilePath("consumers", consumer.name);
|
|
54387
|
-
|
|
54388
|
-
|
|
54602
|
+
import_fs24.default.mkdirSync(import_path32.default.dirname(consumerPath), { recursive: true });
|
|
54603
|
+
import_fs24.default.writeFileSync(consumerPath, JSON.stringify(consumer, null, 4));
|
|
54389
54604
|
console.log(import_chalk13.default.blue(`Created consumer: ${consumer.name}`));
|
|
54390
54605
|
}
|
|
54391
54606
|
spinner.succeed("Producer has been successfully mapped");
|
|
@@ -54405,7 +54620,6 @@ var automap = async (producerName, schemaNames) => {
|
|
|
54405
54620
|
// src/actions/sample.ts
|
|
54406
54621
|
var import_chalk14 = __toESM(require("chalk"));
|
|
54407
54622
|
var import_ora6 = __toESM(require("ora"));
|
|
54408
|
-
init_src10();
|
|
54409
54623
|
init_src5();
|
|
54410
54624
|
init_src2();
|
|
54411
54625
|
var sample = async (resourceName, sampleSize = 10) => {
|
|
@@ -54413,8 +54627,9 @@ var sample = async (resourceName, sampleSize = 10) => {
|
|
|
54413
54627
|
compile();
|
|
54414
54628
|
console.log();
|
|
54415
54629
|
const spinner = (0, import_ora6.default)(import_chalk14.default.blue(`Sampling "${resourceName}"...`)).start();
|
|
54416
|
-
const { kind, records } = await
|
|
54630
|
+
const { kind, records, note } = await SampleRunner_default.read(resourceName, sampleSize);
|
|
54417
54631
|
spinner.succeed(import_chalk14.default.green(`Sample data retrieved from ${kind} "${resourceName}"`));
|
|
54632
|
+
console.log(import_chalk14.default.gray(` ${note}`));
|
|
54418
54633
|
if (records.length === 0) {
|
|
54419
54634
|
console.log(import_chalk14.default.yellow("No data found in the dataset."));
|
|
54420
54635
|
return;
|
|
@@ -54619,7 +54834,7 @@ program.command("deploy").description("Deploy the application to the specified e
|
|
|
54619
54834
|
program.command("run [name]").description("Execute consumers. Optionally specify a single consumer name or use --project to run all consumers in a project.").option("-p, --project <name>", "Run all consumers belonging to the specified project").option("-l, --limit <number>", "Limit the number of output records", parseInt).action((name, options) => run(name, options));
|
|
54620
54835
|
program.command("discover").description("Discover the data shape of a producer and automatically create the resource for it.").argument("<producer>", "The producer to discover").action(discover);
|
|
54621
54836
|
program.command("graph").description("Print the project lineage (what feeds what) to stdout as a diagram or as JSON").option("-f, --format <format>", "Output format: mermaid, dot or json", "mermaid").action((options) => graph(options));
|
|
54622
|
-
program.command("ui-preview").description("Open a
|
|
54837
|
+
program.command("ui-preview").description("Open a visual preview of the project: the lineage graph, and a table inspector for every resource").option("-p, --port <number>", "Port to serve the preview on (default: 5070, or the next free one)").option("--no-open", "Do not open a browser window").action((options) => ui_preview(options));
|
|
54623
54838
|
program.command("create-producer <name>").description("Create a new producer configuration with default settings").action(create_producer);
|
|
54624
54839
|
program.command("automap").description("Automatically map a producer to consumers using specified schemas.").argument("<producer>", "The producer to analyze").argument("<schemas...>", "One or more schema names to map against").action(automap);
|
|
54625
54840
|
program.command("create-consumer <name>").description("Create a new consumer configuration with default settings").option("-p, --producer <name>", "Producer to create a one-to-one mapping from").action((name, options) => create_consumer(name, options.producer));
|