@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/index.js CHANGED
@@ -7759,15 +7759,15 @@ var require_index_cjs = __commonJS({
7759
7759
  };
7760
7760
  }
7761
7761
  function wrapConversion(toModel, graph2) {
7762
- const path29 = [graph2[toModel].parent, toModel];
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
- path29.unshift(graph2[cur].parent);
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 = path29;
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 fs32 = require("fs");
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
- fs32.open(options.file, "a+", "0644", (err2, fd) => {
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
- fs32.close(fd, noop);
8433
+ fs33.close(fd, noop);
8434
8434
  return;
8435
8435
  }
8436
- return fs32.read(fd, buffer, 0, buffer.length, pos, (error, bytes) => {
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 fs32 = require("fs");
8496
- var path29 = require("path");
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 ? path29.basename(options.filename) : "winston.log";
8527
- this.dirname = options.dirname || path29.dirname(options.filename);
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 = path29.dirname(this._dest.path);
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 = path29.join(this.dirname, this.filename);
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 = fs32.createReadStream(file, {
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 = path29.join(this.dirname, this.filename);
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 = path29.join(this.dirname, target);
8852
- fs32.stat(fullpath, (err2, stat) => {
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 = path29.join(this.dirname, this.filename);
8955
+ const fullpath = path30.join(this.dirname, this.filename);
8956
8956
  debug("create stream start", fullpath, this.options);
8957
- const dest = fs32.createWriteStream(fullpath, this.options).on("error", (err2) => debug(err2)).on("close", () => debug("close", dest.path, dest.bytesWritten)).on("open", () => {
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 = path29.extname(this._basename);
8981
- const basename = path29.basename(this._basename, ext);
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
- path29.join(this.dirname, `${basename}${num}${ext}`),
8989
- path29.join(this.dirname, `${basename}${num}${ext}.gz`),
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 = path29.extname(this._basename);
9015
- const basename = path29.basename(this._basename, ext);
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 = path29.join(this.dirname, filePath);
9036
- fs32.unlink(target, callback);
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 = path29.join(this.dirname, fileName);
9059
- fs32.exists(tmppath, (exists) => {
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
- fs32.rename(tmppath, path29.join(this.dirname, fileName), cb);
9064
+ fs33.rename(tmppath, path30.join(this.dirname, fileName), cb);
9065
9065
  });
9066
9066
  }.bind(this, x2));
9067
9067
  }
9068
9068
  asyncSeries(tasks, () => {
9069
- fs32.rename(
9070
- path29.join(this.dirname, `${basename}${ext}${isZipped}`),
9071
- path29.join(this.dirname, `${basename}1${ext}${isZipped}`),
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
- fs32.access(src, fs32.F_OK, (err2) => {
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 = fs32.createReadStream(src);
9091
- var out = fs32.createWriteStream(dest);
9090
+ var inp = fs33.createReadStream(src);
9091
+ var out = fs33.createWriteStream(dest);
9092
9092
  out.on("finish", () => {
9093
- fs32.unlink(src, callback);
9093
+ fs33.unlink(src, callback);
9094
9094
  });
9095
9095
  inp.pipe(gzip).pipe(out);
9096
9096
  });
9097
9097
  }
9098
9098
  _createLogDirIfNotExist(dirPath) {
9099
- if (!fs32.existsSync(dirPath)) {
9100
- fs32.mkdirSync(dirPath, { recursive: true });
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 path29 = options.params.path || null;
9184
+ const path30 = options.params.path || null;
9185
9185
  delete options.params.path;
9186
- this._request(options, auth, path29, (err2, res, body) => {
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 path29 = options.params.path || null;
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, path29);
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, path29, callback) {
9245
+ _request(options, auth, path30, callback) {
9246
9246
  options = options || {};
9247
9247
  auth = auth || this.auth;
9248
- path29 = path29 || this.path || "";
9248
+ path30 = path30 || this.path || "";
9249
9249
  if (this.batch) {
9250
- this._doBatch(options, callback, auth, path29);
9250
+ this._doBatch(options, callback, auth, path30);
9251
9251
  } else {
9252
- this._doRequest(options, callback, auth, path29);
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, path29) {
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, path29);
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, path29);
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, path29) {
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, path29);
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, path29) {
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: `/${path29.replace(/^\//, "")}`,
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.1",
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 path29 = [];
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 (path29.some((x2) => x2.toLowerCase() === name.toLowerCase())) {
12749
- errors.push(`Consumer "${consumer.name}" has a cyclic run-consumer (onSuccess/onError) chain: ${[...path29, name].join(" -> ")}`);
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
- path29.push(name);
12759
+ path30.push(name);
12755
12760
  for (const target of targetsOf(cons)) {
12756
12761
  if (walk(target)) return true;
12757
12762
  }
12758
- path29.pop();
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 = (path29) => {
14245
- const cleanPath = path29.startsWith("/") ? path29.substring(1) : path29;
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 = (path29, options) => {
14689
- if (fileIntercept[path29] !== void 0) {
14690
- return fileIntercept[path29];
14693
+ readFile2 = (path30, options) => {
14694
+ if (fileIntercept[path30] !== void 0) {
14695
+ return fileIntercept[path30];
14691
14696
  }
14692
- if (!filePromises[path29] || options?.ignoreCache) {
14693
- filePromises[path29] = (0, import_promises3.readFile)(path29, "utf8");
14697
+ if (!filePromises[path30] || options?.ignoreCache) {
14698
+ filePromises[path30] = (0, import_promises3.readFile)(path30, "utf8");
14694
14699
  }
14695
- return filePromises[path29];
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(path29, contents) {
14808
- fileIntercept[path29] = Promise.resolve(contents);
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: path29 } = endpoint;
17889
- return `${protocol}//${hostname}${port ? ":" + port : ""}${path29}`;
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 = (path29) => {
18265
- const parts = path29.split(".");
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: '${path29}' does not end with ']'`);
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: '${path29}'`);
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, path29) => getAttrPathList(path29).reduce((acc, index) => {
18301
+ getAttr = (value, path30) => getAttrPathList(path30).reduce((acc, index) => {
18297
18302
  if (typeof acc !== "object") {
18298
- throw new EndpointError(`Index '${index}' in '${path29}' not found in '${JSON.stringify(value)}'`);
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: path29 = "", query = {} } = value;
18360
- const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path29}`);
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 [path29, search] = opTraits.http[1].split("?");
20709
+ const [path30, search] = opTraits.http[1].split("?");
20705
20710
  if (request.path == "/") {
20706
- request.path = path29;
20711
+ request.path = path30;
20707
20712
  } else {
20708
- request.path += path29;
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 path29 = request.path;
24426
+ let path30 = request.path;
24422
24427
  if (queryString) {
24423
- path29 += `?${queryString}`;
24428
+ path30 += `?${queryString}`;
24424
24429
  }
24425
24430
  if (request.fragment) {
24426
- path29 += `#${request.fragment}`;
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: path29,
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: path29 }) {
25352
+ getCanonicalPath({ path: path30 }) {
25348
25353
  if (this.uriEscapePath) {
25349
25354
  const normalizedPathSegments = [];
25350
- for (const pathSegment of path29.split("/")) {
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 = `${path29?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path29?.endsWith("/") ? "/" : ""}`;
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 path29;
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 rl6 = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
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 rl6) {
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
- rl6.close();
42587
+ rl7.close();
42583
42588
  fileStream.close();
42584
42589
  return lines;
42585
42590
  },
42586
42591
  quickReadStream: async (stream, lineCount) => {
42587
- const rl6 = (0, import_readline.createInterface)({ input: stream, crlfDelay: Infinity });
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 rl6) {
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
- rl6.close();
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 fileData = await this.readFile(effectiveProducer, { readmode: "lines", lines: { from: 0, to: sampleSize } }, effectiveSource);
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
- bounds?.max === void 0 || bounds.max >= last,
47111
- `Key "${norm}" has a synth range of ${first}-${bounds.max}, which holds fewer than the ${poolSize} distinct values it has to generate; widen it or generate fewer records`
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 path29 = [];
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(path29.pop());
48518
+ onPath.delete(path30.pop());
48460
48519
  continue;
48461
48520
  }
48462
48521
  if (onPath.has(frame.name)) {
48463
- path29.slice(path29.indexOf(frame.name)).forEach((name) => onCycle.add(name));
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
- path29.push(frame.name);
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 = (path29) => {
49147
- return path29.map((step) => `${step.resource}.${step.field}`).join(" \u2192 ");
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. Only the tail of the file is decoded: a reader wants the recent
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
- return this._getRows().slice(-Math.max(1, limit)).map((line) => this._fromLine(line)).reverse();
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).filter((stat) => stat?.consumer);
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 run store does. Empty when there is nothing to read.
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, and it is cheap for the same reason the hash is
50109
- * affordable: it reads metadata, not records.
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
- return LocalUsageDB_default.signature();
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 import_crypto5, DELIMITER, idFor, LookupKey, LookupKey_default;
50492
+ var import_crypto6, DELIMITER, idFor, LookupKey, LookupKey_default;
50456
50493
  var init_LookupKey = __esm({
50457
50494
  "../../packages/engines/src/transform/LookupKey.ts"() {
50458
- import_crypto5 = __toESM(require("crypto"), 1);
50495
+ import_crypto6 = __toESM(require("crypto"), 1);
50459
50496
  DELIMITER = String.fromCharCode(0);
50460
- idFor = (ref) => import_crypto5.default.createHash("sha1").update([ref.consumer, ref.key, ref.value].join(DELIMITER)).digest("hex").slice(0, 16);
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, import_crypto6, LIMIT_SCAN_BYTES, NEWLINE_BYTE, ConsumerExecutorClass, ConsumerExecutor, ConsumerExecutor_default;
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
- import_crypto6 = __toESM(require("crypto"));
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 = import_crypto6.default.createHash("sha1").update(line).digest("base64");
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
- const fills = [];
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
- for (const fill2 of plan.fills) {
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
- await ConsumerOnFinishManager_default.onConsumerError(consumer, usageId, this.makeOnFinishRunner(consumer, request, usageId));
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
- * The consumer a run started from this page is executing, or null.
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 runs
54060
- * would each spin up a worker pool in this one process. Refusing the second with the name of 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 run at a time` };
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 import_fs21 = __toESM(require("fs"));
54255
- var import_path29 = __toESM(require("path"));
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 (!import_fs21.default.existsSync("./remora/producers")) {
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 = import_fs21.default.readFileSync(
54263
- import_path29.default.join(DOCUMENTATION_DIR, "default_resources/producer.json"),
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 = import_path29.default.join("remora/producers", `${name}.json`);
54269
- import_fs21.default.writeFileSync(producerPath, JSON.stringify(defaultProducer, null, 4));
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 import_fs22 = __toESM(require("fs"));
54285
- var import_path30 = __toESM(require("path"));
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 (!import_fs22.default.existsSync("./remora/consumers")) {
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 = import_fs22.default.readFileSync(
54293
- import_path30.default.join(DOCUMENTATION_DIR, "default_resources/consumer.json"),
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 = import_path30.default.join("remora/producers", `${producerName}.json`);
54300
- if (!import_fs22.default.existsSync(producerPath)) {
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(import_fs22.default.readFileSync(producerPath, "utf-8"));
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 = import_path30.default.join("remora/consumers", `${name}.json`);
54323
- import_fs22.default.writeFileSync(consumerPath, JSON.stringify(defaultConsumer, null, 4));
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 import_fs23 = __toESM(require("fs"));
54352
- var import_path31 = __toESM(require("path"));
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
- import_fs23.default.mkdirSync(import_path31.default.dirname(producerPath), { recursive: true });
54382
- import_fs23.default.writeFileSync(producerPath, JSON.stringify(producer2, null, 4));
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
- import_fs23.default.mkdirSync(import_path31.default.dirname(consumerPath), { recursive: true });
54388
- import_fs23.default.writeFileSync(consumerPath, JSON.stringify(consumer, null, 4));
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 SampleEngine_default.read(resourceName, sampleSize);
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 read-only 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));
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));