@forzalabs/remora 2.1.1 → 2.2.0
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 +52 -0
- package/documentation/README.md +1 -2
- package/documentation/default_resources/project.json +0 -4
- package/index.js +2270 -923
- package/json_schemas/consumer-schema.json +19 -1
- package/json_schemas/producer-schema.json +3 -7
- package/json_schemas/project-schema.json +11 -10
- package/json_schemas/source-schema.json +57 -1
- package/package.json +1 -1
- package/workers/ExecutorWorker.js +2044 -761
|
@@ -72,6 +72,67 @@ var init_Affirm = __esm({
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// ../../packages/core/src/BatchWriter.ts
|
|
76
|
+
var FLUSH_AFTER_CHARS, BatchWriter, BatchWriter_default;
|
|
77
|
+
var init_BatchWriter = __esm({
|
|
78
|
+
"../../packages/core/src/BatchWriter.ts"() {
|
|
79
|
+
init_Affirm();
|
|
80
|
+
FLUSH_AFTER_CHARS = 1 << 18;
|
|
81
|
+
BatchWriter = class {
|
|
82
|
+
constructor(stream) {
|
|
83
|
+
/** How many times the stream's buffer filled and the caller had to wait for it. */
|
|
84
|
+
this.drains = 0;
|
|
85
|
+
this.buffer = "";
|
|
86
|
+
/** Queues one line and its newline. False means a batch went out and the stream wants a drain. */
|
|
87
|
+
this.write = (line) => {
|
|
88
|
+
this.buffer += line;
|
|
89
|
+
this.buffer += "\n";
|
|
90
|
+
if (this.buffer.length < FLUSH_AFTER_CHARS)
|
|
91
|
+
return true;
|
|
92
|
+
return this.flush();
|
|
93
|
+
};
|
|
94
|
+
this.waitForDrain = () => {
|
|
95
|
+
this.drains++;
|
|
96
|
+
return new Promise((resolve) => this.stream.once("drain", resolve));
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Writes whatever is still buffered, then waits for the stream to be fully closed.
|
|
100
|
+
*
|
|
101
|
+
* Waits for `close`, not `finish`: `finish` only means `end()` has flushed its buffer to the OS,
|
|
102
|
+
* while `close` means the descriptor is released. Callers that rename or unlink the file right
|
|
103
|
+
* after — which most of the passes that write a dataset do — need the descriptor gone, or the
|
|
104
|
+
* rename fails with EPERM on Windows. `close` is strictly later than `finish`, so waiting for it
|
|
105
|
+
* is safe for the callers that do not care.
|
|
106
|
+
*
|
|
107
|
+
* The flush above may hand the stream more than its high-water mark, which `write` would report
|
|
108
|
+
* by returning false. There is nothing to wait for here: `end()` flushes what is buffered before
|
|
109
|
+
* it emits `finish`, so the batch lands either way.
|
|
110
|
+
*/
|
|
111
|
+
this.close = async () => {
|
|
112
|
+
if (this.stream.destroyed)
|
|
113
|
+
return;
|
|
114
|
+
this.flush();
|
|
115
|
+
await new Promise((resolve, reject) => {
|
|
116
|
+
this.stream.once("close", resolve);
|
|
117
|
+
this.stream.once("error", reject);
|
|
118
|
+
this.stream.end();
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
this.flush = () => {
|
|
122
|
+
if (!this.buffer)
|
|
123
|
+
return true;
|
|
124
|
+
const batch = this.buffer;
|
|
125
|
+
this.buffer = "";
|
|
126
|
+
return this.stream.write(batch);
|
|
127
|
+
};
|
|
128
|
+
Affirm_default(stream, "Invalid write stream");
|
|
129
|
+
this.stream = stream;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
BatchWriter_default = BatchWriter;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
75
136
|
// ../../packages/core/src/Algo.ts
|
|
76
137
|
var algo, Algo_default;
|
|
77
138
|
var init_Algo = __esm({
|
|
@@ -233,6 +294,36 @@ var init_Algo = __esm({
|
|
|
233
294
|
}
|
|
234
295
|
});
|
|
235
296
|
|
|
297
|
+
// ../../packages/core/src/LineReader.ts
|
|
298
|
+
async function* readLines(input) {
|
|
299
|
+
Affirm_default(input, "Invalid input stream");
|
|
300
|
+
input.setEncoding("utf8");
|
|
301
|
+
let tail = "";
|
|
302
|
+
for await (const chunk of input) {
|
|
303
|
+
let text = tail + chunk;
|
|
304
|
+
let pendingReturn = "";
|
|
305
|
+
if (text.endsWith("\r")) {
|
|
306
|
+
pendingReturn = "\r";
|
|
307
|
+
text = text.slice(0, -1);
|
|
308
|
+
}
|
|
309
|
+
const parts = text.split(TERMINATOR);
|
|
310
|
+
tail = parts.pop() + pendingReturn;
|
|
311
|
+
for (const part of parts)
|
|
312
|
+
yield part;
|
|
313
|
+
}
|
|
314
|
+
if (tail.endsWith("\r"))
|
|
315
|
+
yield tail.slice(0, -1);
|
|
316
|
+
else if (tail.length > 0)
|
|
317
|
+
yield tail;
|
|
318
|
+
}
|
|
319
|
+
var TERMINATOR;
|
|
320
|
+
var init_LineReader = __esm({
|
|
321
|
+
"../../packages/core/src/LineReader.ts"() {
|
|
322
|
+
init_Affirm();
|
|
323
|
+
TERMINATOR = /\r\n|\n|\r/;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
236
327
|
// ../../packages/core/src/dste/DSTE.ts
|
|
237
328
|
var DeterministicSimulationTestingEngine, DSTE, DSTE_default;
|
|
238
329
|
var init_DSTE = __esm({
|
|
@@ -283,7 +374,9 @@ Tests summary:
|
|
|
283
374
|
var init_src = __esm({
|
|
284
375
|
"../../packages/core/src/index.ts"() {
|
|
285
376
|
init_Affirm();
|
|
377
|
+
init_BatchWriter();
|
|
286
378
|
init_Algo();
|
|
379
|
+
init_LineReader();
|
|
287
380
|
init_DSTE();
|
|
288
381
|
}
|
|
289
382
|
});
|
|
@@ -4203,14 +4296,14 @@ var require_from = __commonJS({
|
|
|
4203
4296
|
return (hint === "string" ? String : Number)(input);
|
|
4204
4297
|
}
|
|
4205
4298
|
var ERR_INVALID_ARG_TYPE = require_errors2().codes.ERR_INVALID_ARG_TYPE;
|
|
4206
|
-
function from(
|
|
4299
|
+
function from(Readable10, iterable, opts) {
|
|
4207
4300
|
var iterator;
|
|
4208
4301
|
if (iterable && typeof iterable.next === "function") {
|
|
4209
4302
|
iterator = iterable;
|
|
4210
4303
|
} else if (iterable && iterable[Symbol.asyncIterator]) iterator = iterable[Symbol.asyncIterator]();
|
|
4211
4304
|
else if (iterable && iterable[Symbol.iterator]) iterator = iterable[Symbol.iterator]();
|
|
4212
4305
|
else throw new ERR_INVALID_ARG_TYPE("iterable", ["Iterable"], iterable);
|
|
4213
|
-
var readable = new
|
|
4306
|
+
var readable = new Readable10(_objectSpread({
|
|
4214
4307
|
objectMode: true
|
|
4215
4308
|
}, opts));
|
|
4216
4309
|
var reading = false;
|
|
@@ -4250,9 +4343,9 @@ var require_from = __commonJS({
|
|
|
4250
4343
|
var require_stream_readable = __commonJS({
|
|
4251
4344
|
"../../node_modules/readable-stream/lib/_stream_readable.js"(exports2, module2) {
|
|
4252
4345
|
"use strict";
|
|
4253
|
-
module2.exports =
|
|
4346
|
+
module2.exports = Readable10;
|
|
4254
4347
|
var Duplex;
|
|
4255
|
-
|
|
4348
|
+
Readable10.ReadableState = ReadableState;
|
|
4256
4349
|
var EE = require("events").EventEmitter;
|
|
4257
4350
|
var EElistenerCount = function EElistenerCount2(emitter, type) {
|
|
4258
4351
|
return emitter.listeners(type).length;
|
|
@@ -4287,7 +4380,7 @@ var require_stream_readable = __commonJS({
|
|
|
4287
4380
|
var StringDecoder;
|
|
4288
4381
|
var createReadableStreamAsyncIterator;
|
|
4289
4382
|
var from;
|
|
4290
|
-
require_inherits()(
|
|
4383
|
+
require_inherits()(Readable10, Stream);
|
|
4291
4384
|
var errorOrDestroy = destroyImpl.errorOrDestroy;
|
|
4292
4385
|
var kProxyEvents = ["error", "close", "destroy", "pause", "resume"];
|
|
4293
4386
|
function prependListener(emitter, event, fn) {
|
|
@@ -4331,9 +4424,9 @@ var require_stream_readable = __commonJS({
|
|
|
4331
4424
|
this.encoding = options2.encoding;
|
|
4332
4425
|
}
|
|
4333
4426
|
}
|
|
4334
|
-
function
|
|
4427
|
+
function Readable10(options2) {
|
|
4335
4428
|
Duplex = Duplex || require_stream_duplex();
|
|
4336
|
-
if (!(this instanceof
|
|
4429
|
+
if (!(this instanceof Readable10)) return new Readable10(options2);
|
|
4337
4430
|
var isDuplex = this instanceof Duplex;
|
|
4338
4431
|
this._readableState = new ReadableState(options2, this, isDuplex);
|
|
4339
4432
|
this.readable = true;
|
|
@@ -4343,7 +4436,7 @@ var require_stream_readable = __commonJS({
|
|
|
4343
4436
|
}
|
|
4344
4437
|
Stream.call(this);
|
|
4345
4438
|
}
|
|
4346
|
-
Object.defineProperty(
|
|
4439
|
+
Object.defineProperty(Readable10.prototype, "destroyed", {
|
|
4347
4440
|
// making it explicit this property is not enumerable
|
|
4348
4441
|
// because otherwise some prototype manipulation in
|
|
4349
4442
|
// userland will fail
|
|
@@ -4361,12 +4454,12 @@ var require_stream_readable = __commonJS({
|
|
|
4361
4454
|
this._readableState.destroyed = value;
|
|
4362
4455
|
}
|
|
4363
4456
|
});
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4457
|
+
Readable10.prototype.destroy = destroyImpl.destroy;
|
|
4458
|
+
Readable10.prototype._undestroy = destroyImpl.undestroy;
|
|
4459
|
+
Readable10.prototype._destroy = function(err2, cb) {
|
|
4367
4460
|
cb(err2);
|
|
4368
4461
|
};
|
|
4369
|
-
|
|
4462
|
+
Readable10.prototype.push = function(chunk, encoding) {
|
|
4370
4463
|
var state2 = this._readableState;
|
|
4371
4464
|
var skipChunkCheck;
|
|
4372
4465
|
if (!state2.objectMode) {
|
|
@@ -4383,7 +4476,7 @@ var require_stream_readable = __commonJS({
|
|
|
4383
4476
|
}
|
|
4384
4477
|
return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);
|
|
4385
4478
|
};
|
|
4386
|
-
|
|
4479
|
+
Readable10.prototype.unshift = function(chunk) {
|
|
4387
4480
|
return readableAddChunk(this, chunk, null, true, false);
|
|
4388
4481
|
};
|
|
4389
4482
|
function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
|
|
@@ -4444,10 +4537,10 @@ var require_stream_readable = __commonJS({
|
|
|
4444
4537
|
}
|
|
4445
4538
|
return er2;
|
|
4446
4539
|
}
|
|
4447
|
-
|
|
4540
|
+
Readable10.prototype.isPaused = function() {
|
|
4448
4541
|
return this._readableState.flowing === false;
|
|
4449
4542
|
};
|
|
4450
|
-
|
|
4543
|
+
Readable10.prototype.setEncoding = function(enc) {
|
|
4451
4544
|
if (!StringDecoder) StringDecoder = require_string_decoder().StringDecoder;
|
|
4452
4545
|
var decoder = new StringDecoder(enc);
|
|
4453
4546
|
this._readableState.decoder = decoder;
|
|
@@ -4493,7 +4586,7 @@ var require_stream_readable = __commonJS({
|
|
|
4493
4586
|
}
|
|
4494
4587
|
return state2.length;
|
|
4495
4588
|
}
|
|
4496
|
-
|
|
4589
|
+
Readable10.prototype.read = function(n4) {
|
|
4497
4590
|
debug("read", n4);
|
|
4498
4591
|
n4 = parseInt(n4, 10);
|
|
4499
4592
|
var state2 = this._readableState;
|
|
@@ -4602,10 +4695,10 @@ var require_stream_readable = __commonJS({
|
|
|
4602
4695
|
}
|
|
4603
4696
|
state2.readingMore = false;
|
|
4604
4697
|
}
|
|
4605
|
-
|
|
4698
|
+
Readable10.prototype._read = function(n4) {
|
|
4606
4699
|
errorOrDestroy(this, new ERR_METHOD_NOT_IMPLEMENTED("_read()"));
|
|
4607
4700
|
};
|
|
4608
|
-
|
|
4701
|
+
Readable10.prototype.pipe = function(dest, pipeOpts) {
|
|
4609
4702
|
var src = this;
|
|
4610
4703
|
var state2 = this._readableState;
|
|
4611
4704
|
switch (state2.pipesCount) {
|
|
@@ -4708,7 +4801,7 @@ var require_stream_readable = __commonJS({
|
|
|
4708
4801
|
}
|
|
4709
4802
|
};
|
|
4710
4803
|
}
|
|
4711
|
-
|
|
4804
|
+
Readable10.prototype.unpipe = function(dest) {
|
|
4712
4805
|
var state2 = this._readableState;
|
|
4713
4806
|
var unpipeInfo = {
|
|
4714
4807
|
hasUnpiped: false
|
|
@@ -4742,7 +4835,7 @@ var require_stream_readable = __commonJS({
|
|
|
4742
4835
|
dest.emit("unpipe", this, unpipeInfo);
|
|
4743
4836
|
return this;
|
|
4744
4837
|
};
|
|
4745
|
-
|
|
4838
|
+
Readable10.prototype.on = function(ev, fn) {
|
|
4746
4839
|
var res = Stream.prototype.on.call(this, ev, fn);
|
|
4747
4840
|
var state2 = this._readableState;
|
|
4748
4841
|
if (ev === "data") {
|
|
@@ -4763,15 +4856,15 @@ var require_stream_readable = __commonJS({
|
|
|
4763
4856
|
}
|
|
4764
4857
|
return res;
|
|
4765
4858
|
};
|
|
4766
|
-
|
|
4767
|
-
|
|
4859
|
+
Readable10.prototype.addListener = Readable10.prototype.on;
|
|
4860
|
+
Readable10.prototype.removeListener = function(ev, fn) {
|
|
4768
4861
|
var res = Stream.prototype.removeListener.call(this, ev, fn);
|
|
4769
4862
|
if (ev === "readable") {
|
|
4770
4863
|
process.nextTick(updateReadableListening, this);
|
|
4771
4864
|
}
|
|
4772
4865
|
return res;
|
|
4773
4866
|
};
|
|
4774
|
-
|
|
4867
|
+
Readable10.prototype.removeAllListeners = function(ev) {
|
|
4775
4868
|
var res = Stream.prototype.removeAllListeners.apply(this, arguments);
|
|
4776
4869
|
if (ev === "readable" || ev === void 0) {
|
|
4777
4870
|
process.nextTick(updateReadableListening, this);
|
|
@@ -4791,7 +4884,7 @@ var require_stream_readable = __commonJS({
|
|
|
4791
4884
|
debug("readable nexttick read 0");
|
|
4792
4885
|
self2.read(0);
|
|
4793
4886
|
}
|
|
4794
|
-
|
|
4887
|
+
Readable10.prototype.resume = function() {
|
|
4795
4888
|
var state2 = this._readableState;
|
|
4796
4889
|
if (!state2.flowing) {
|
|
4797
4890
|
debug("resume");
|
|
@@ -4817,7 +4910,7 @@ var require_stream_readable = __commonJS({
|
|
|
4817
4910
|
flow(stream);
|
|
4818
4911
|
if (state2.flowing && !state2.reading) stream.read(0);
|
|
4819
4912
|
}
|
|
4820
|
-
|
|
4913
|
+
Readable10.prototype.pause = function() {
|
|
4821
4914
|
debug("call pause flowing=%j", this._readableState.flowing);
|
|
4822
4915
|
if (this._readableState.flowing !== false) {
|
|
4823
4916
|
debug("pause");
|
|
@@ -4832,7 +4925,7 @@ var require_stream_readable = __commonJS({
|
|
|
4832
4925
|
debug("flow", state2.flowing);
|
|
4833
4926
|
while (state2.flowing && stream.read() !== null) ;
|
|
4834
4927
|
}
|
|
4835
|
-
|
|
4928
|
+
Readable10.prototype.wrap = function(stream) {
|
|
4836
4929
|
var _this = this;
|
|
4837
4930
|
var state2 = this._readableState;
|
|
4838
4931
|
var paused = false;
|
|
@@ -4877,14 +4970,14 @@ var require_stream_readable = __commonJS({
|
|
|
4877
4970
|
return this;
|
|
4878
4971
|
};
|
|
4879
4972
|
if (typeof Symbol === "function") {
|
|
4880
|
-
|
|
4973
|
+
Readable10.prototype[Symbol.asyncIterator] = function() {
|
|
4881
4974
|
if (createReadableStreamAsyncIterator === void 0) {
|
|
4882
4975
|
createReadableStreamAsyncIterator = require_async_iterator();
|
|
4883
4976
|
}
|
|
4884
4977
|
return createReadableStreamAsyncIterator(this);
|
|
4885
4978
|
};
|
|
4886
4979
|
}
|
|
4887
|
-
Object.defineProperty(
|
|
4980
|
+
Object.defineProperty(Readable10.prototype, "readableHighWaterMark", {
|
|
4888
4981
|
// making it explicit this property is not enumerable
|
|
4889
4982
|
// because otherwise some prototype manipulation in
|
|
4890
4983
|
// userland will fail
|
|
@@ -4893,7 +4986,7 @@ var require_stream_readable = __commonJS({
|
|
|
4893
4986
|
return this._readableState.highWaterMark;
|
|
4894
4987
|
}
|
|
4895
4988
|
});
|
|
4896
|
-
Object.defineProperty(
|
|
4989
|
+
Object.defineProperty(Readable10.prototype, "readableBuffer", {
|
|
4897
4990
|
// making it explicit this property is not enumerable
|
|
4898
4991
|
// because otherwise some prototype manipulation in
|
|
4899
4992
|
// userland will fail
|
|
@@ -4902,7 +4995,7 @@ var require_stream_readable = __commonJS({
|
|
|
4902
4995
|
return this._readableState && this._readableState.buffer;
|
|
4903
4996
|
}
|
|
4904
4997
|
});
|
|
4905
|
-
Object.defineProperty(
|
|
4998
|
+
Object.defineProperty(Readable10.prototype, "readableFlowing", {
|
|
4906
4999
|
// making it explicit this property is not enumerable
|
|
4907
5000
|
// because otherwise some prototype manipulation in
|
|
4908
5001
|
// userland will fail
|
|
@@ -4916,8 +5009,8 @@ var require_stream_readable = __commonJS({
|
|
|
4916
5009
|
}
|
|
4917
5010
|
}
|
|
4918
5011
|
});
|
|
4919
|
-
|
|
4920
|
-
Object.defineProperty(
|
|
5012
|
+
Readable10._fromList = fromList;
|
|
5013
|
+
Object.defineProperty(Readable10.prototype, "readableLength", {
|
|
4921
5014
|
// making it explicit this property is not enumerable
|
|
4922
5015
|
// because otherwise some prototype manipulation in
|
|
4923
5016
|
// userland will fail
|
|
@@ -4963,11 +5056,11 @@ var require_stream_readable = __commonJS({
|
|
|
4963
5056
|
}
|
|
4964
5057
|
}
|
|
4965
5058
|
if (typeof Symbol === "function") {
|
|
4966
|
-
|
|
5059
|
+
Readable10.from = function(iterable, opts) {
|
|
4967
5060
|
if (from === void 0) {
|
|
4968
5061
|
from = require_from();
|
|
4969
5062
|
}
|
|
4970
|
-
return from(
|
|
5063
|
+
return from(Readable10, iterable, opts);
|
|
4971
5064
|
};
|
|
4972
5065
|
}
|
|
4973
5066
|
function indexOf(xs2, x2) {
|
|
@@ -4989,9 +5082,9 @@ var require_stream_duplex = __commonJS({
|
|
|
4989
5082
|
return keys2;
|
|
4990
5083
|
};
|
|
4991
5084
|
module2.exports = Duplex;
|
|
4992
|
-
var
|
|
5085
|
+
var Readable10 = require_stream_readable();
|
|
4993
5086
|
var Writable2 = require_stream_writable();
|
|
4994
|
-
require_inherits()(Duplex,
|
|
5087
|
+
require_inherits()(Duplex, Readable10);
|
|
4995
5088
|
{
|
|
4996
5089
|
keys = objectKeys(Writable2.prototype);
|
|
4997
5090
|
for (v3 = 0; v3 < keys.length; v3++) {
|
|
@@ -5004,7 +5097,7 @@ var require_stream_duplex = __commonJS({
|
|
|
5004
5097
|
var v3;
|
|
5005
5098
|
function Duplex(options2) {
|
|
5006
5099
|
if (!(this instanceof Duplex)) return new Duplex(options2);
|
|
5007
|
-
|
|
5100
|
+
Readable10.call(this, options2);
|
|
5008
5101
|
Writable2.call(this, options2);
|
|
5009
5102
|
this.allowHalfOpen = true;
|
|
5010
5103
|
if (options2) {
|
|
@@ -6374,14 +6467,14 @@ var require_series = __commonJS({
|
|
|
6374
6467
|
var require_stream_transform = __commonJS({
|
|
6375
6468
|
"../../node_modules/readable-stream/lib/_stream_transform.js"(exports2, module2) {
|
|
6376
6469
|
"use strict";
|
|
6377
|
-
module2.exports =
|
|
6470
|
+
module2.exports = Transform5;
|
|
6378
6471
|
var _require$codes = require_errors2().codes;
|
|
6379
6472
|
var ERR_METHOD_NOT_IMPLEMENTED = _require$codes.ERR_METHOD_NOT_IMPLEMENTED;
|
|
6380
6473
|
var ERR_MULTIPLE_CALLBACK = _require$codes.ERR_MULTIPLE_CALLBACK;
|
|
6381
6474
|
var ERR_TRANSFORM_ALREADY_TRANSFORMING = _require$codes.ERR_TRANSFORM_ALREADY_TRANSFORMING;
|
|
6382
6475
|
var ERR_TRANSFORM_WITH_LENGTH_0 = _require$codes.ERR_TRANSFORM_WITH_LENGTH_0;
|
|
6383
6476
|
var Duplex = require_stream_duplex();
|
|
6384
|
-
require_inherits()(
|
|
6477
|
+
require_inherits()(Transform5, Duplex);
|
|
6385
6478
|
function afterTransform(er2, data) {
|
|
6386
6479
|
var ts2 = this._transformState;
|
|
6387
6480
|
ts2.transforming = false;
|
|
@@ -6400,8 +6493,8 @@ var require_stream_transform = __commonJS({
|
|
|
6400
6493
|
this._read(rs2.highWaterMark);
|
|
6401
6494
|
}
|
|
6402
6495
|
}
|
|
6403
|
-
function
|
|
6404
|
-
if (!(this instanceof
|
|
6496
|
+
function Transform5(options2) {
|
|
6497
|
+
if (!(this instanceof Transform5)) return new Transform5(options2);
|
|
6405
6498
|
Duplex.call(this, options2);
|
|
6406
6499
|
this._transformState = {
|
|
6407
6500
|
afterTransform: afterTransform.bind(this),
|
|
@@ -6429,14 +6522,14 @@ var require_stream_transform = __commonJS({
|
|
|
6429
6522
|
done(this, null, null);
|
|
6430
6523
|
}
|
|
6431
6524
|
}
|
|
6432
|
-
|
|
6525
|
+
Transform5.prototype.push = function(chunk, encoding) {
|
|
6433
6526
|
this._transformState.needTransform = false;
|
|
6434
6527
|
return Duplex.prototype.push.call(this, chunk, encoding);
|
|
6435
6528
|
};
|
|
6436
|
-
|
|
6529
|
+
Transform5.prototype._transform = function(chunk, encoding, cb) {
|
|
6437
6530
|
cb(new ERR_METHOD_NOT_IMPLEMENTED("_transform()"));
|
|
6438
6531
|
};
|
|
6439
|
-
|
|
6532
|
+
Transform5.prototype._write = function(chunk, encoding, cb) {
|
|
6440
6533
|
var ts2 = this._transformState;
|
|
6441
6534
|
ts2.writecb = cb;
|
|
6442
6535
|
ts2.writechunk = chunk;
|
|
@@ -6446,7 +6539,7 @@ var require_stream_transform = __commonJS({
|
|
|
6446
6539
|
if (ts2.needTransform || rs2.needReadable || rs2.length < rs2.highWaterMark) this._read(rs2.highWaterMark);
|
|
6447
6540
|
}
|
|
6448
6541
|
};
|
|
6449
|
-
|
|
6542
|
+
Transform5.prototype._read = function(n4) {
|
|
6450
6543
|
var ts2 = this._transformState;
|
|
6451
6544
|
if (ts2.writechunk !== null && !ts2.transforming) {
|
|
6452
6545
|
ts2.transforming = true;
|
|
@@ -6455,7 +6548,7 @@ var require_stream_transform = __commonJS({
|
|
|
6455
6548
|
ts2.needTransform = true;
|
|
6456
6549
|
}
|
|
6457
6550
|
};
|
|
6458
|
-
|
|
6551
|
+
Transform5.prototype._destroy = function(err2, cb) {
|
|
6459
6552
|
Duplex.prototype._destroy.call(this, err2, function(err22) {
|
|
6460
6553
|
cb(err22);
|
|
6461
6554
|
});
|
|
@@ -6476,11 +6569,11 @@ var require_stream_passthrough = __commonJS({
|
|
|
6476
6569
|
"../../node_modules/readable-stream/lib/_stream_passthrough.js"(exports2, module2) {
|
|
6477
6570
|
"use strict";
|
|
6478
6571
|
module2.exports = PassThrough;
|
|
6479
|
-
var
|
|
6480
|
-
require_inherits()(PassThrough,
|
|
6572
|
+
var Transform5 = require_stream_transform();
|
|
6573
|
+
require_inherits()(PassThrough, Transform5);
|
|
6481
6574
|
function PassThrough(options2) {
|
|
6482
6575
|
if (!(this instanceof PassThrough)) return new PassThrough(options2);
|
|
6483
|
-
|
|
6576
|
+
Transform5.call(this, options2);
|
|
6484
6577
|
}
|
|
6485
6578
|
PassThrough.prototype._transform = function(chunk, encoding, cb) {
|
|
6486
6579
|
cb(null, chunk);
|
|
@@ -6546,7 +6639,7 @@ var require_pipeline = __commonJS({
|
|
|
6546
6639
|
if (typeof streams[streams.length - 1] !== "function") return noop;
|
|
6547
6640
|
return streams.pop();
|
|
6548
6641
|
}
|
|
6549
|
-
function
|
|
6642
|
+
function pipeline7() {
|
|
6550
6643
|
for (var _len = arguments.length, streams = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
6551
6644
|
streams[_key] = arguments[_key];
|
|
6552
6645
|
}
|
|
@@ -6569,7 +6662,7 @@ var require_pipeline = __commonJS({
|
|
|
6569
6662
|
});
|
|
6570
6663
|
return streams.reduce(pipe);
|
|
6571
6664
|
}
|
|
6572
|
-
module2.exports =
|
|
6665
|
+
module2.exports = pipeline7;
|
|
6573
6666
|
}
|
|
6574
6667
|
});
|
|
6575
6668
|
|
|
@@ -7807,15 +7900,15 @@ var require_index_cjs = __commonJS({
|
|
|
7807
7900
|
};
|
|
7808
7901
|
}
|
|
7809
7902
|
function wrapConversion(toModel, graph) {
|
|
7810
|
-
const
|
|
7903
|
+
const path20 = [graph[toModel].parent, toModel];
|
|
7811
7904
|
let fn = convert$1[graph[toModel].parent][toModel];
|
|
7812
7905
|
let cur = graph[toModel].parent;
|
|
7813
7906
|
while (graph[cur].parent) {
|
|
7814
|
-
|
|
7907
|
+
path20.unshift(graph[cur].parent);
|
|
7815
7908
|
fn = link(convert$1[graph[cur].parent][cur], fn);
|
|
7816
7909
|
cur = graph[cur].parent;
|
|
7817
7910
|
}
|
|
7818
|
-
fn.conversion =
|
|
7911
|
+
fn.conversion = path20;
|
|
7819
7912
|
return fn;
|
|
7820
7913
|
}
|
|
7821
7914
|
function route(fromModel) {
|
|
@@ -8445,7 +8538,7 @@ var require_node2 = __commonJS({
|
|
|
8445
8538
|
var require_tail_file = __commonJS({
|
|
8446
8539
|
"../../node_modules/winston/lib/winston/tail-file.js"(exports2, module2) {
|
|
8447
8540
|
"use strict";
|
|
8448
|
-
var
|
|
8541
|
+
var fs28 = require("fs");
|
|
8449
8542
|
var { StringDecoder } = require("string_decoder");
|
|
8450
8543
|
var { Stream } = require_readable();
|
|
8451
8544
|
function noop() {
|
|
@@ -8466,7 +8559,7 @@ var require_tail_file = __commonJS({
|
|
|
8466
8559
|
stream.emit("end");
|
|
8467
8560
|
stream.emit("close");
|
|
8468
8561
|
};
|
|
8469
|
-
|
|
8562
|
+
fs28.open(options2.file, "a+", "0644", (err2, fd) => {
|
|
8470
8563
|
if (err2) {
|
|
8471
8564
|
if (!iter) {
|
|
8472
8565
|
stream.emit("error", err2);
|
|
@@ -8478,10 +8571,10 @@ var require_tail_file = __commonJS({
|
|
|
8478
8571
|
}
|
|
8479
8572
|
(function read() {
|
|
8480
8573
|
if (stream.destroyed) {
|
|
8481
|
-
|
|
8574
|
+
fs28.close(fd, noop);
|
|
8482
8575
|
return;
|
|
8483
8576
|
}
|
|
8484
|
-
return
|
|
8577
|
+
return fs28.read(fd, buffer, 0, buffer.length, pos, (error, bytes) => {
|
|
8485
8578
|
if (error) {
|
|
8486
8579
|
if (!iter) {
|
|
8487
8580
|
stream.emit("error", error);
|
|
@@ -8540,8 +8633,8 @@ var require_tail_file = __commonJS({
|
|
|
8540
8633
|
var require_file = __commonJS({
|
|
8541
8634
|
"../../node_modules/winston/lib/winston/transports/file.js"(exports2, module2) {
|
|
8542
8635
|
"use strict";
|
|
8543
|
-
var
|
|
8544
|
-
var
|
|
8636
|
+
var fs28 = require("fs");
|
|
8637
|
+
var path20 = require("path");
|
|
8545
8638
|
var asyncSeries = require_series();
|
|
8546
8639
|
var zlib3 = require("zlib");
|
|
8547
8640
|
var { MESSAGE } = require_triple_beam();
|
|
@@ -8571,14 +8664,14 @@ var require_file = __commonJS({
|
|
|
8571
8664
|
this._onError = this._onError.bind(this);
|
|
8572
8665
|
if (options2.filename || options2.dirname) {
|
|
8573
8666
|
throwIf("filename or dirname", "stream");
|
|
8574
|
-
this._basename = this.filename = options2.filename ?
|
|
8575
|
-
this.dirname = options2.dirname ||
|
|
8667
|
+
this._basename = this.filename = options2.filename ? path20.basename(options2.filename) : "winston.log";
|
|
8668
|
+
this.dirname = options2.dirname || path20.dirname(options2.filename);
|
|
8576
8669
|
this.options = options2.options || { flags: "a" };
|
|
8577
8670
|
} else if (options2.stream) {
|
|
8578
8671
|
console.warn("options.stream will be removed in winston@4. Use winston.transports.Stream");
|
|
8579
8672
|
throwIf("stream", "filename", "maxsize");
|
|
8580
8673
|
this._dest = this._stream.pipe(this._setupStream(options2.stream));
|
|
8581
|
-
this.dirname =
|
|
8674
|
+
this.dirname = path20.dirname(this._dest.path);
|
|
8582
8675
|
} else {
|
|
8583
8676
|
throw new Error("Cannot log to file without filename or stream.");
|
|
8584
8677
|
}
|
|
@@ -8741,11 +8834,11 @@ var require_file = __commonJS({
|
|
|
8741
8834
|
options2 = {};
|
|
8742
8835
|
}
|
|
8743
8836
|
options2 = normalizeQuery(options2);
|
|
8744
|
-
const file =
|
|
8837
|
+
const file = path20.join(this.dirname, this.filename);
|
|
8745
8838
|
let buff = "";
|
|
8746
8839
|
let results = [];
|
|
8747
8840
|
let row = 0;
|
|
8748
|
-
const stream =
|
|
8841
|
+
const stream = fs28.createReadStream(file, {
|
|
8749
8842
|
encoding: "utf8"
|
|
8750
8843
|
});
|
|
8751
8844
|
stream.on("error", (err2) => {
|
|
@@ -8846,7 +8939,7 @@ var require_file = __commonJS({
|
|
|
8846
8939
|
* TODO: Refactor me.
|
|
8847
8940
|
*/
|
|
8848
8941
|
stream(options2 = {}) {
|
|
8849
|
-
const file =
|
|
8942
|
+
const file = path20.join(this.dirname, this.filename);
|
|
8850
8943
|
const stream = new Stream();
|
|
8851
8944
|
const tail = {
|
|
8852
8945
|
file,
|
|
@@ -8896,8 +8989,8 @@ var require_file = __commonJS({
|
|
|
8896
8989
|
*/
|
|
8897
8990
|
stat(callback) {
|
|
8898
8991
|
const target = this._getFile();
|
|
8899
|
-
const fullpath =
|
|
8900
|
-
|
|
8992
|
+
const fullpath = path20.join(this.dirname, target);
|
|
8993
|
+
fs28.stat(fullpath, (err2, stat) => {
|
|
8901
8994
|
if (err2 && err2.code === "ENOENT") {
|
|
8902
8995
|
debug("ENOENT\xA0ok", fullpath);
|
|
8903
8996
|
this.filename = target;
|
|
@@ -9000,9 +9093,9 @@ var require_file = __commonJS({
|
|
|
9000
9093
|
* @returns {WritableStream} Stream that writes to disk for the active file.
|
|
9001
9094
|
*/
|
|
9002
9095
|
_createStream(source) {
|
|
9003
|
-
const fullpath =
|
|
9096
|
+
const fullpath = path20.join(this.dirname, this.filename);
|
|
9004
9097
|
debug("create stream start", fullpath, this.options);
|
|
9005
|
-
const dest =
|
|
9098
|
+
const dest = fs28.createWriteStream(fullpath, this.options).on("error", (err2) => debug(err2)).on("close", () => debug("close", dest.path, dest.bytesWritten)).on("open", () => {
|
|
9006
9099
|
debug("file open ok", fullpath);
|
|
9007
9100
|
this.emit("open", fullpath);
|
|
9008
9101
|
source.pipe(dest);
|
|
@@ -9025,16 +9118,16 @@ var require_file = __commonJS({
|
|
|
9025
9118
|
*/
|
|
9026
9119
|
_incFile(callback) {
|
|
9027
9120
|
debug("_incFile", this.filename);
|
|
9028
|
-
const ext =
|
|
9029
|
-
const basename =
|
|
9121
|
+
const ext = path20.extname(this._basename);
|
|
9122
|
+
const basename = path20.basename(this._basename, ext);
|
|
9030
9123
|
const tasks = [];
|
|
9031
9124
|
if (this.zippedArchive) {
|
|
9032
9125
|
tasks.push(
|
|
9033
9126
|
function(cb) {
|
|
9034
9127
|
const num = this._created > 0 && !this.tailable ? this._created : "";
|
|
9035
9128
|
this._compressFile(
|
|
9036
|
-
|
|
9037
|
-
|
|
9129
|
+
path20.join(this.dirname, `${basename}${num}${ext}`),
|
|
9130
|
+
path20.join(this.dirname, `${basename}${num}${ext}.gz`),
|
|
9038
9131
|
cb
|
|
9039
9132
|
);
|
|
9040
9133
|
}.bind(this)
|
|
@@ -9059,8 +9152,8 @@ var require_file = __commonJS({
|
|
|
9059
9152
|
* @private
|
|
9060
9153
|
*/
|
|
9061
9154
|
_getFile() {
|
|
9062
|
-
const ext =
|
|
9063
|
-
const basename =
|
|
9155
|
+
const ext = path20.extname(this._basename);
|
|
9156
|
+
const basename = path20.basename(this._basename, ext);
|
|
9064
9157
|
const isRotation = this.rotationFormat ? this.rotationFormat() : this._created;
|
|
9065
9158
|
return !this.tailable && this._created ? `${basename}${isRotation}${ext}` : `${basename}${ext}`;
|
|
9066
9159
|
}
|
|
@@ -9080,8 +9173,8 @@ var require_file = __commonJS({
|
|
|
9080
9173
|
const isOldest = oldest !== 0 ? oldest : "";
|
|
9081
9174
|
const isZipped = this.zippedArchive ? ".gz" : "";
|
|
9082
9175
|
const filePath = `${basename}${isOldest}${ext}${isZipped}`;
|
|
9083
|
-
const target =
|
|
9084
|
-
|
|
9176
|
+
const target = path20.join(this.dirname, filePath);
|
|
9177
|
+
fs28.unlink(target, callback);
|
|
9085
9178
|
}
|
|
9086
9179
|
/**
|
|
9087
9180
|
* Roll files forward based on integer, up to maxFiles. e.g. if base if
|
|
@@ -9103,20 +9196,20 @@ var require_file = __commonJS({
|
|
|
9103
9196
|
for (let x2 = this.maxFiles - 1; x2 > 1; x2--) {
|
|
9104
9197
|
tasks.push(function(i6, cb) {
|
|
9105
9198
|
let fileName = `${basename}${i6 - 1}${ext}${isZipped}`;
|
|
9106
|
-
const tmppath =
|
|
9107
|
-
|
|
9199
|
+
const tmppath = path20.join(this.dirname, fileName);
|
|
9200
|
+
fs28.exists(tmppath, (exists) => {
|
|
9108
9201
|
if (!exists) {
|
|
9109
9202
|
return cb(null);
|
|
9110
9203
|
}
|
|
9111
9204
|
fileName = `${basename}${i6}${ext}${isZipped}`;
|
|
9112
|
-
|
|
9205
|
+
fs28.rename(tmppath, path20.join(this.dirname, fileName), cb);
|
|
9113
9206
|
});
|
|
9114
9207
|
}.bind(this, x2));
|
|
9115
9208
|
}
|
|
9116
9209
|
asyncSeries(tasks, () => {
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9210
|
+
fs28.rename(
|
|
9211
|
+
path20.join(this.dirname, `${basename}${ext}${isZipped}`),
|
|
9212
|
+
path20.join(this.dirname, `${basename}1${ext}${isZipped}`),
|
|
9120
9213
|
callback
|
|
9121
9214
|
);
|
|
9122
9215
|
});
|
|
@@ -9130,22 +9223,22 @@ var require_file = __commonJS({
|
|
|
9130
9223
|
* @private
|
|
9131
9224
|
*/
|
|
9132
9225
|
_compressFile(src, dest, callback) {
|
|
9133
|
-
|
|
9226
|
+
fs28.access(src, fs28.F_OK, (err2) => {
|
|
9134
9227
|
if (err2) {
|
|
9135
9228
|
return callback();
|
|
9136
9229
|
}
|
|
9137
9230
|
var gzip = zlib3.createGzip();
|
|
9138
|
-
var inp =
|
|
9139
|
-
var out =
|
|
9231
|
+
var inp = fs28.createReadStream(src);
|
|
9232
|
+
var out = fs28.createWriteStream(dest);
|
|
9140
9233
|
out.on("finish", () => {
|
|
9141
|
-
|
|
9234
|
+
fs28.unlink(src, callback);
|
|
9142
9235
|
});
|
|
9143
9236
|
inp.pipe(gzip).pipe(out);
|
|
9144
9237
|
});
|
|
9145
9238
|
}
|
|
9146
9239
|
_createLogDirIfNotExist(dirPath) {
|
|
9147
|
-
if (!
|
|
9148
|
-
|
|
9240
|
+
if (!fs28.existsSync(dirPath)) {
|
|
9241
|
+
fs28.mkdirSync(dirPath, { recursive: true });
|
|
9149
9242
|
}
|
|
9150
9243
|
}
|
|
9151
9244
|
};
|
|
@@ -9229,9 +9322,9 @@ var require_http = __commonJS({
|
|
|
9229
9322
|
};
|
|
9230
9323
|
const auth = options2.params.auth || null;
|
|
9231
9324
|
delete options2.params.auth;
|
|
9232
|
-
const
|
|
9325
|
+
const path20 = options2.params.path || null;
|
|
9233
9326
|
delete options2.params.path;
|
|
9234
|
-
this._request(options2, auth,
|
|
9327
|
+
this._request(options2, auth, path20, (err2, res, body) => {
|
|
9235
9328
|
if (res && res.statusCode !== 200) {
|
|
9236
9329
|
err2 = new Error(`Invalid HTTP Status Code: ${res.statusCode}`);
|
|
9237
9330
|
}
|
|
@@ -9259,12 +9352,12 @@ var require_http = __commonJS({
|
|
|
9259
9352
|
method: "stream",
|
|
9260
9353
|
params: options2
|
|
9261
9354
|
};
|
|
9262
|
-
const
|
|
9355
|
+
const path20 = options2.params.path || null;
|
|
9263
9356
|
delete options2.params.path;
|
|
9264
9357
|
const auth = options2.params.auth || null;
|
|
9265
9358
|
delete options2.params.auth;
|
|
9266
9359
|
let buff = "";
|
|
9267
|
-
const req = this._request(options2, auth,
|
|
9360
|
+
const req = this._request(options2, auth, path20);
|
|
9268
9361
|
stream.destroy = () => req.destroy();
|
|
9269
9362
|
req.on("data", (data) => {
|
|
9270
9363
|
data = (buff + data).split(/\n+/);
|
|
@@ -9290,14 +9383,14 @@ var require_http = __commonJS({
|
|
|
9290
9383
|
* @param {string} path - request path
|
|
9291
9384
|
* @param {function} callback - Continuation to respond to when complete.
|
|
9292
9385
|
*/
|
|
9293
|
-
_request(options2, auth,
|
|
9386
|
+
_request(options2, auth, path20, callback) {
|
|
9294
9387
|
options2 = options2 || {};
|
|
9295
9388
|
auth = auth || this.auth;
|
|
9296
|
-
|
|
9389
|
+
path20 = path20 || this.path || "";
|
|
9297
9390
|
if (this.batch) {
|
|
9298
|
-
this._doBatch(options2, callback, auth,
|
|
9391
|
+
this._doBatch(options2, callback, auth, path20);
|
|
9299
9392
|
} else {
|
|
9300
|
-
this._doRequest(options2, callback, auth,
|
|
9393
|
+
this._doRequest(options2, callback, auth, path20);
|
|
9301
9394
|
}
|
|
9302
9395
|
}
|
|
9303
9396
|
/**
|
|
@@ -9307,18 +9400,18 @@ var require_http = __commonJS({
|
|
|
9307
9400
|
* @param {Object?} auth - authentication options
|
|
9308
9401
|
* @param {string} path - request path
|
|
9309
9402
|
*/
|
|
9310
|
-
_doBatch(options2, callback, auth,
|
|
9403
|
+
_doBatch(options2, callback, auth, path20) {
|
|
9311
9404
|
this.batchOptions.push(options2);
|
|
9312
9405
|
if (this.batchOptions.length === 1) {
|
|
9313
9406
|
const me3 = this;
|
|
9314
9407
|
this.batchCallback = callback;
|
|
9315
9408
|
this.batchTimeoutID = setTimeout(function() {
|
|
9316
9409
|
me3.batchTimeoutID = -1;
|
|
9317
|
-
me3._doBatchRequest(me3.batchCallback, auth,
|
|
9410
|
+
me3._doBatchRequest(me3.batchCallback, auth, path20);
|
|
9318
9411
|
}, this.batchInterval);
|
|
9319
9412
|
}
|
|
9320
9413
|
if (this.batchOptions.length === this.batchCount) {
|
|
9321
|
-
this._doBatchRequest(this.batchCallback, auth,
|
|
9414
|
+
this._doBatchRequest(this.batchCallback, auth, path20);
|
|
9322
9415
|
}
|
|
9323
9416
|
}
|
|
9324
9417
|
/**
|
|
@@ -9327,14 +9420,14 @@ var require_http = __commonJS({
|
|
|
9327
9420
|
* @param {Object?} auth - authentication options
|
|
9328
9421
|
* @param {string} path - request path
|
|
9329
9422
|
*/
|
|
9330
|
-
_doBatchRequest(callback, auth,
|
|
9423
|
+
_doBatchRequest(callback, auth, path20) {
|
|
9331
9424
|
if (this.batchTimeoutID > 0) {
|
|
9332
9425
|
clearTimeout(this.batchTimeoutID);
|
|
9333
9426
|
this.batchTimeoutID = -1;
|
|
9334
9427
|
}
|
|
9335
9428
|
const batchOptionsCopy = this.batchOptions.slice();
|
|
9336
9429
|
this.batchOptions = [];
|
|
9337
|
-
this._doRequest(batchOptionsCopy, callback, auth,
|
|
9430
|
+
this._doRequest(batchOptionsCopy, callback, auth, path20);
|
|
9338
9431
|
}
|
|
9339
9432
|
/**
|
|
9340
9433
|
* Make a request to a winstond server or any http server which can
|
|
@@ -9344,7 +9437,7 @@ var require_http = __commonJS({
|
|
|
9344
9437
|
* @param {Object?} auth - authentication options
|
|
9345
9438
|
* @param {string} path - request path
|
|
9346
9439
|
*/
|
|
9347
|
-
_doRequest(options2, callback, auth,
|
|
9440
|
+
_doRequest(options2, callback, auth, path20) {
|
|
9348
9441
|
const headers = Object.assign({}, this.headers);
|
|
9349
9442
|
if (auth && auth.bearer) {
|
|
9350
9443
|
headers.Authorization = `Bearer ${auth.bearer}`;
|
|
@@ -9354,7 +9447,7 @@ var require_http = __commonJS({
|
|
|
9354
9447
|
method: "POST",
|
|
9355
9448
|
host: this.host,
|
|
9356
9449
|
port: this.port,
|
|
9357
|
-
path: `/${
|
|
9450
|
+
path: `/${path20.replace(/^\//, "")}`,
|
|
9358
9451
|
headers,
|
|
9359
9452
|
auth: auth && auth.username && auth.password ? `${auth.username}:${auth.password}` : "",
|
|
9360
9453
|
agent: this.agent
|
|
@@ -10272,7 +10365,7 @@ var require_profiler = __commonJS({
|
|
|
10272
10365
|
var require_logger = __commonJS({
|
|
10273
10366
|
"../../node_modules/winston/lib/winston/logger.js"(exports2, module2) {
|
|
10274
10367
|
"use strict";
|
|
10275
|
-
var { Stream, Transform:
|
|
10368
|
+
var { Stream, Transform: Transform5 } = require_readable();
|
|
10276
10369
|
var asyncForEach = require_forEach();
|
|
10277
10370
|
var { LEVEL, SPLAT } = require_triple_beam();
|
|
10278
10371
|
var isStream = require_is_stream();
|
|
@@ -10283,7 +10376,7 @@ var require_logger = __commonJS({
|
|
|
10283
10376
|
var { warn } = require_common();
|
|
10284
10377
|
var config = require_config2();
|
|
10285
10378
|
var formatRegExp = /%[scdjifoO%]/g;
|
|
10286
|
-
var Logger2 = class extends
|
|
10379
|
+
var Logger2 = class extends Transform5 {
|
|
10287
10380
|
/**
|
|
10288
10381
|
* Constructor function for the Logger object responsible for persisting log
|
|
10289
10382
|
* messages and metadata to one or more transports.
|
|
@@ -11682,7 +11775,7 @@ var CONSTANTS, Constants_default;
|
|
|
11682
11775
|
var init_Constants = __esm({
|
|
11683
11776
|
"../../packages/constants/src/Constants.ts"() {
|
|
11684
11777
|
CONSTANTS = {
|
|
11685
|
-
cliVersion: "2.
|
|
11778
|
+
cliVersion: "2.2.0",
|
|
11686
11779
|
backendVersion: 1,
|
|
11687
11780
|
backendPort: 5088,
|
|
11688
11781
|
workerVersion: 2,
|
|
@@ -11699,6 +11792,23 @@ var init_Constants = __esm({
|
|
|
11699
11792
|
* for the whole project, and once per dimension when a worker compiles its line plan.
|
|
11700
11793
|
*/
|
|
11701
11794
|
MASK_VALUES: ["hash", "mask", "crypt", "random", "seeded-random", "none"],
|
|
11795
|
+
/**
|
|
11796
|
+
* Where each kind of resource lives inside the `remora/` directory, as a path relative to it.
|
|
11797
|
+
*
|
|
11798
|
+
* Fixed, not configurable: project.json used to name these folders, which meant nothing outside
|
|
11799
|
+
* the loader could find a resource from its path alone, and two projects could disagree about
|
|
11800
|
+
* what a remora project looks like. `sources`, `producers` and `consumers` must exist; `schemas`
|
|
11801
|
+
* and `codeSets` are read only when the folder is there.
|
|
11802
|
+
*/
|
|
11803
|
+
RESOURCE_FOLDERS: {
|
|
11804
|
+
sources: "/sources",
|
|
11805
|
+
producers: "/producers",
|
|
11806
|
+
consumers: "/consumers",
|
|
11807
|
+
schemas: "/schemas",
|
|
11808
|
+
codeSets: "/code_sets"
|
|
11809
|
+
},
|
|
11810
|
+
/** project.json keys that used to name the resource folders, kept only to warn on and ignore. */
|
|
11811
|
+
LEGACY_PROJECT_PATH_KEYS: ["sources", "producers", "consumers", "schemas", "codeSets"],
|
|
11702
11812
|
defaults: {
|
|
11703
11813
|
REMORA_PATH: "./remora",
|
|
11704
11814
|
PRODUCER_TEMP_FOLDER: ".temp",
|
|
@@ -11777,6 +11887,19 @@ var init_Constants = __esm({
|
|
|
11777
11887
|
* than memory — a zip bomb stops with a named error instead of filling the scope folder.
|
|
11778
11888
|
*/
|
|
11779
11889
|
MAX_ARCHIVE_ENTRIES: 1e4,
|
|
11890
|
+
/**
|
|
11891
|
+
* Files one consumer's output may be split into. Generous for a country, a region or a year,
|
|
11892
|
+
* and low enough to catch "split by customer_id" before it exhausts the process's descriptor
|
|
11893
|
+
* table. The run fails mid-pass with a named error rather than producing a directory nobody
|
|
11894
|
+
* asked for.
|
|
11895
|
+
*/
|
|
11896
|
+
MAX_SPLIT_FILES: 1e3,
|
|
11897
|
+
/**
|
|
11898
|
+
* Write streams the split pass may hold open at once, independent of how many files it ends
|
|
11899
|
+
* up writing. Past this the least recently written bucket is closed and reopened in append
|
|
11900
|
+
* mode when its next line arrives — its byte counter lives in the bucket, not the stream.
|
|
11901
|
+
*/
|
|
11902
|
+
MAX_OPEN_SPLIT_STREAMS: 64,
|
|
11780
11903
|
/**
|
|
11781
11904
|
* Rows a sample may ask for out of the pipeline. A sample is a look at the data, never an
|
|
11782
11905
|
* export of it — `remora sample -s` and the preview's Data tab are both held to this.
|
|
@@ -12804,6 +12927,7 @@ var init_Validator = __esm({
|
|
|
12804
12927
|
}
|
|
12805
12928
|
}
|
|
12806
12929
|
errors.push(...this.validateOmitNull(consumer));
|
|
12930
|
+
errors.push(...this.validateSplit(consumer));
|
|
12807
12931
|
const dimensionFields = consumer.fields.filter((x2) => x2.key !== "*" && !x2.fixed && !x2.copyFrom && !x2.lineCount);
|
|
12808
12932
|
const dimensionKeys = dimensionFields.map((x2) => `${x2.from ?? "_default_"}::${x2.key}`);
|
|
12809
12933
|
const duplicateDimensionKeys = dimensionKeys.filter((k7, i6) => dimensionKeys.indexOf(k7) !== i6);
|
|
@@ -12860,6 +12984,42 @@ var init_Validator = __esm({
|
|
|
12860
12984
|
}
|
|
12861
12985
|
return errors;
|
|
12862
12986
|
};
|
|
12987
|
+
/**
|
|
12988
|
+
* Checks `split`, which breaks a consumer's output into several files — by the value of one or
|
|
12989
|
+
* more fields, by a size cap, or both.
|
|
12990
|
+
*
|
|
12991
|
+
* The alias-resolution and wildcard rules follow `distinctOn`: the keys name consumer fields as
|
|
12992
|
+
* the output writes them, and a consumer selecting `*` has no field list to check them against
|
|
12993
|
+
* until it reads its producers. A field it declares beside the `*` is still checked, since a
|
|
12994
|
+
* hidden one is hidden whatever the wildcard expands to.
|
|
12995
|
+
*/
|
|
12996
|
+
this.validateSplit = (consumer) => {
|
|
12997
|
+
const errors = [];
|
|
12998
|
+
const { split: split2 } = consumer;
|
|
12999
|
+
const positionalFormats = ["CSV", "TXT", "PARQUET"];
|
|
13000
|
+
if (!Algo_default.hasVal(split2)) return errors;
|
|
13001
|
+
if (Algo_default.hasVal(consumer.maximumFileSize))
|
|
13002
|
+
errors.push(`Consumer "${consumer.name}" sets both "maximumFileSize" and "split". They are the same cap: move the value to "split": { "maxFileSizeGb": ${consumer.maximumFileSize} } and remove "maximumFileSize".`);
|
|
13003
|
+
const keys = split2.by ?? [];
|
|
13004
|
+
if (keys.length === 0 && !Algo_default.hasVal(split2.maxFileSizeGb))
|
|
13005
|
+
errors.push(`The "split" of consumer "${consumer.name}" sets neither "by" nor "maxFileSizeGb", so it would split nothing. Remove it, or say how the output should be split.`);
|
|
13006
|
+
if (Algo_default.hasVal(split2.maxFileSizeGb) && !(split2.maxFileSizeGb > 0))
|
|
13007
|
+
errors.push(`The "split.maxFileSizeGb" of consumer "${consumer.name}" must be greater than zero (got ${split2.maxFileSizeGb}).`);
|
|
13008
|
+
const apiOutputs = (consumer.outputs ?? []).filter((x2) => x2.format.toUpperCase() === "API");
|
|
13009
|
+
if (apiOutputs.length > 0)
|
|
13010
|
+
errors.push(`Consumer "${consumer.name}" declares "split", but an API output is a response, not a file, and there is nothing to split it into.`);
|
|
13011
|
+
if (keys.length === 0) return errors;
|
|
13012
|
+
const fieldKeys = (consumer.fields ?? []).map((x2) => x2.alias ?? x2.key);
|
|
13013
|
+
const missingKeys = fieldKeys.includes("*") ? [] : keys.filter((key) => !fieldKeys.includes(key));
|
|
13014
|
+
if (missingKeys.length > 0)
|
|
13015
|
+
errors.push(`"split.by" references key(s) "${missingKeys.join(", ")}" that are not present in the consumer "${consumer.name}".`);
|
|
13016
|
+
const positional = (consumer.outputs ?? []).filter((x2) => positionalFormats.includes(x2.format.toUpperCase()));
|
|
13017
|
+
if (positional.length === 0) return errors;
|
|
13018
|
+
const hiddenKeys = keys.filter((key) => (consumer.fields ?? []).some((x2) => (x2.alias ?? x2.key) === key && x2.hidden));
|
|
13019
|
+
if (hiddenKeys.length > 0)
|
|
13020
|
+
errors.push(`"split.by" of consumer "${consumer.name}" references hidden field(s) "${hiddenKeys.join(", ")}", which the ${positional.map((x2) => x2.format.toUpperCase()).join("/")} output does not write \u2014 a hidden field can't be split on in a positional format.`);
|
|
13021
|
+
return errors;
|
|
13022
|
+
};
|
|
12863
13023
|
/**
|
|
12864
13024
|
* Checks `omitNull`, which drops a record's null fields on export. It only means anything where a
|
|
12865
13025
|
* record carries its own field names, and it is applied when the record is serialized — one record
|
|
@@ -12956,7 +13116,7 @@ var init_Validator = __esm({
|
|
|
12956
13116
|
*/
|
|
12957
13117
|
this.detectOnFinishRecursion = (consumer) => {
|
|
12958
13118
|
const errors = [];
|
|
12959
|
-
const
|
|
13119
|
+
const path20 = [];
|
|
12960
13120
|
const targetsOf = (cons) => {
|
|
12961
13121
|
const names = [];
|
|
12962
13122
|
for (const output of cons.outputs ?? []) {
|
|
@@ -12968,17 +13128,17 @@ var init_Validator = __esm({
|
|
|
12968
13128
|
return names;
|
|
12969
13129
|
};
|
|
12970
13130
|
const walk = (name) => {
|
|
12971
|
-
if (
|
|
12972
|
-
errors.push(`Consumer "${consumer.name}" has a cyclic run-consumer (onSuccess/onError) chain: ${[...
|
|
13131
|
+
if (path20.some((x2) => x2.toLowerCase() === name.toLowerCase())) {
|
|
13132
|
+
errors.push(`Consumer "${consumer.name}" has a cyclic run-consumer (onSuccess/onError) chain: ${[...path20, name].join(" -> ")}`);
|
|
12973
13133
|
return true;
|
|
12974
13134
|
}
|
|
12975
13135
|
const cons = Environment_default.getConsumer(name);
|
|
12976
13136
|
if (!cons) return false;
|
|
12977
|
-
|
|
13137
|
+
path20.push(name);
|
|
12978
13138
|
for (const target of targetsOf(cons)) {
|
|
12979
13139
|
if (walk(target)) return true;
|
|
12980
13140
|
}
|
|
12981
|
-
|
|
13141
|
+
path20.pop();
|
|
12982
13142
|
return false;
|
|
12983
13143
|
};
|
|
12984
13144
|
walk(consumer.name);
|
|
@@ -13563,7 +13723,7 @@ ${errors.map((x2) => ` - ${x2}`).join("\n")}`);
|
|
|
13563
13723
|
});
|
|
13564
13724
|
|
|
13565
13725
|
// ../../packages/common/src/Environment.ts
|
|
13566
|
-
var import_fs4, import_crypto, import_adm_zip, import_path4, import_client_s3, RESOURCE_SCHEMA_KEYS, INVALID_RESOURCE_MESSAGES, EnvironmentClass, Environment, Environment_default;
|
|
13726
|
+
var import_fs4, import_crypto, import_adm_zip, import_path4, import_client_s3, RESOURCE_SCHEMA_KEYS, INVALID_RESOURCE_MESSAGES, STAGING_DIRECTORY, EnvironmentClass, Environment, Environment_default;
|
|
13567
13727
|
var init_Environment = __esm({
|
|
13568
13728
|
"../../packages/common/src/Environment.ts"() {
|
|
13569
13729
|
import_fs4 = __toESM(require("fs"), 1);
|
|
@@ -13592,6 +13752,7 @@ var init_Environment = __esm({
|
|
|
13592
13752
|
codeSet: "Invalid code set configuration",
|
|
13593
13753
|
schema: "Invalid schema configuration"
|
|
13594
13754
|
};
|
|
13755
|
+
STAGING_DIRECTORY = ".remora-incoming";
|
|
13595
13756
|
EnvironmentClass = class {
|
|
13596
13757
|
constructor() {
|
|
13597
13758
|
this._env = null;
|
|
@@ -13671,31 +13832,72 @@ var init_Environment = __esm({
|
|
|
13671
13832
|
Bucket: bucketName,
|
|
13672
13833
|
Key: key
|
|
13673
13834
|
});
|
|
13835
|
+
let buffer;
|
|
13674
13836
|
try {
|
|
13675
13837
|
const response = await s3Client.send(command5);
|
|
13676
|
-
|
|
13838
|
+
buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
13677
13839
|
} catch (error) {
|
|
13678
|
-
throw new Error(`Error at bucket ${bucketName}, while
|
|
13840
|
+
throw new Error(`Error at bucket ${bucketName}, while obtaining ${key}, caused ${error}`);
|
|
13679
13841
|
}
|
|
13842
|
+
return this._saveConfigFile(buffer, url);
|
|
13680
13843
|
};
|
|
13681
|
-
|
|
13844
|
+
/**
|
|
13845
|
+
* Extract a downloaded configuration archive over the local remora path.
|
|
13846
|
+
*
|
|
13847
|
+
* The archive is fully read and extracted to a staging directory BEFORE anything live is
|
|
13848
|
+
* touched, because the destructive step is not reversible: a configuration that fails to
|
|
13849
|
+
* extract half way through would otherwise leave the process running against an empty
|
|
13850
|
+
* project directory until the next successful download.
|
|
13851
|
+
*/
|
|
13682
13852
|
this._saveConfigFile = (buffer, url) => {
|
|
13683
|
-
|
|
13853
|
+
Affirm_default(buffer?.length, `Empty configuration archive downloaded from ${url}`);
|
|
13854
|
+
const zip = this._readArchive(buffer, url);
|
|
13684
13855
|
const extractPath = import_path4.default.join(this._localPath, "remora");
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
}
|
|
13856
|
+
const stagingPath = import_path4.default.join(this._localPath, STAGING_DIRECTORY);
|
|
13857
|
+
import_fs4.default.rmSync(stagingPath, { recursive: true, force: true });
|
|
13858
|
+
try {
|
|
13859
|
+
zip.extractAllTo(stagingPath, true);
|
|
13860
|
+
this._clearConfigDirectory(extractPath);
|
|
13861
|
+
for (const entry of import_fs4.default.readdirSync(stagingPath))
|
|
13862
|
+
import_fs4.default.renameSync(import_path4.default.join(stagingPath, entry), import_path4.default.join(extractPath, entry));
|
|
13863
|
+
} finally {
|
|
13864
|
+
import_fs4.default.rmSync(stagingPath, { recursive: true, force: true });
|
|
13694
13865
|
}
|
|
13695
|
-
zip.extractAllTo(extractPath, true);
|
|
13696
13866
|
Logger_default.log(`Remote configuration downloaded and extracted from ${url}`);
|
|
13697
13867
|
return true;
|
|
13698
13868
|
};
|
|
13869
|
+
/**
|
|
13870
|
+
* Read the archive's entry table up front. adm-zip parses entries lazily, so without this the
|
|
13871
|
+
* first read of a malformed archive happens inside `extractAllTo` — after the live
|
|
13872
|
+
* configuration has already been removed.
|
|
13873
|
+
*/
|
|
13874
|
+
this._readArchive = (buffer, url) => {
|
|
13875
|
+
try {
|
|
13876
|
+
const zip = new import_adm_zip.default(buffer);
|
|
13877
|
+
Affirm_default.hasItems(zip.getEntries(), `Configuration archive from ${url} contains no files`);
|
|
13878
|
+
return zip;
|
|
13879
|
+
} catch (error) {
|
|
13880
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
13881
|
+
throw new Error(
|
|
13882
|
+
`Unreadable configuration archive from ${url}: ${reason}. Rebuild the archive from the project directory (\`remora package\`) instead of adding files to the existing one \u2014 appending leaves two entries under the same name, which is rejected because the two could hold different content.`
|
|
13883
|
+
);
|
|
13884
|
+
}
|
|
13885
|
+
};
|
|
13886
|
+
/** Remove everything the previous configuration left behind, keeping the run scratch directory. */
|
|
13887
|
+
this._clearConfigDirectory = (extractPath) => {
|
|
13888
|
+
if (!import_fs4.default.existsSync(extractPath)) {
|
|
13889
|
+
import_fs4.default.mkdirSync(extractPath, { recursive: true });
|
|
13890
|
+
return;
|
|
13891
|
+
}
|
|
13892
|
+
for (const file of import_fs4.default.readdirSync(extractPath)) {
|
|
13893
|
+
if (file === "temp") continue;
|
|
13894
|
+
const filePath = import_path4.default.join(extractPath, file);
|
|
13895
|
+
if (import_fs4.default.statSync(filePath).isDirectory())
|
|
13896
|
+
import_fs4.default.rmSync(filePath, { recursive: true, force: true });
|
|
13897
|
+
else
|
|
13898
|
+
import_fs4.default.unlinkSync(filePath);
|
|
13899
|
+
}
|
|
13900
|
+
};
|
|
13699
13901
|
/**
|
|
13700
13902
|
* Check if the configuration has changed and reload if so.
|
|
13701
13903
|
* For remote URIs, re-downloads using ETag caching (HTTP 304 avoids redundant downloads).
|
|
@@ -13758,25 +13960,26 @@ var init_Environment = __esm({
|
|
|
13758
13960
|
if (!projectConfig) return this._emptyInspection(problems);
|
|
13759
13961
|
SchemaValidator_default.unloadSchemas();
|
|
13760
13962
|
const read = (entries) => this._readConfigs(envPath, entries, remoraPath, problems);
|
|
13761
|
-
const
|
|
13762
|
-
const
|
|
13963
|
+
const optional = (folder) => import_fs4.default.existsSync(import_path4.default.join(envPath, folder)) ? [folder] : [];
|
|
13964
|
+
const folders = Constants_default.RESOURCE_FOLDERS;
|
|
13965
|
+
const schemaFiles = read(optional(folders.schemas));
|
|
13763
13966
|
const schemas = schemaFiles.map((x2) => x2.config);
|
|
13764
13967
|
schemas.forEach((schema) => this._registerSchema(schema, problems));
|
|
13765
|
-
const sourceFiles = read(
|
|
13968
|
+
const sourceFiles = read([folders.sources]);
|
|
13766
13969
|
const sources = sourceFiles.map((x2) => x2.config);
|
|
13767
13970
|
sourceFiles.forEach((source) => this._recordSchemaProblem(source, "source", problems));
|
|
13768
13971
|
this._checkApiQueueSource(projectConfig, sources, problems);
|
|
13769
|
-
const producerFiles = read(
|
|
13972
|
+
const producerFiles = read([folders.producers]);
|
|
13770
13973
|
const producers = producerFiles.map((x2) => x2.config);
|
|
13771
13974
|
producerFiles.forEach(({ config: producer, file }) => {
|
|
13772
13975
|
const schemaProblem = this._schemaProblem(producer, "producer", file);
|
|
13773
13976
|
this._expandProducer(producer, problems);
|
|
13774
13977
|
if (schemaProblem) problems.push(schemaProblem);
|
|
13775
13978
|
});
|
|
13776
|
-
const consumerFiles = read(
|
|
13979
|
+
const consumerFiles = read([folders.consumers]);
|
|
13777
13980
|
const consumers = consumerFiles.map((x2) => x2.config);
|
|
13778
13981
|
consumerFiles.forEach((consumer) => this._recordSchemaProblem(consumer, "consumer", problems));
|
|
13779
|
-
const codeSetFiles = read(
|
|
13982
|
+
const codeSetFiles = read(optional(folders.codeSets));
|
|
13780
13983
|
const codeSets = codeSetFiles.map((x2) => x2.config);
|
|
13781
13984
|
codeSetFiles.forEach((codeSet) => this._recordSchemaProblem(codeSet, "codeSet", problems));
|
|
13782
13985
|
this._checkCodeSetNames(codeSets, problems);
|
|
@@ -13826,6 +14029,7 @@ var init_Environment = __esm({
|
|
|
13826
14029
|
const { env: env2, projectName, projectSettings, problems } = this.inspect(remoraPath);
|
|
13827
14030
|
const fatal = problems.find((x2) => x2.severity === "error");
|
|
13828
14031
|
if (fatal) throw new Error(fatal.message);
|
|
14032
|
+
problems.filter((x2) => x2.severity === "warning").forEach((x2) => Logger_default.warn(x2.message));
|
|
13829
14033
|
this._projectName = projectName;
|
|
13830
14034
|
this._projectSettings = projectSettings;
|
|
13831
14035
|
this.init(env2);
|
|
@@ -13861,8 +14065,27 @@ var init_Environment = __esm({
|
|
|
13861
14065
|
message: `Invalid project configuration - ${SchemaErrorReporter_default.describe(validation.errors, projectConfig)}`
|
|
13862
14066
|
});
|
|
13863
14067
|
}
|
|
14068
|
+
this._checkLegacyPaths(projectConfig, projectPath, problems);
|
|
13864
14069
|
return projectConfig;
|
|
13865
14070
|
};
|
|
14071
|
+
/**
|
|
14072
|
+
* project.json used to name the folder each kind of resource was read from. It no longer does —
|
|
14073
|
+
* the folders are fixed — so the old keys are reported and ignored.
|
|
14074
|
+
*
|
|
14075
|
+
* A WARNING, not an error: a project written against an older CLI still runs, and the author is
|
|
14076
|
+
* told once, per load, what to delete. This is the one warning the loader raises.
|
|
14077
|
+
*/
|
|
14078
|
+
this._checkLegacyPaths = (projectConfig, projectPath, problems) => {
|
|
14079
|
+
const stale = Constants_default.LEGACY_PROJECT_PATH_KEYS.filter((key) => key in (projectConfig ?? {}));
|
|
14080
|
+
if (stale.length === 0) return;
|
|
14081
|
+
const folders = Object.values(Constants_default.RESOURCE_FOLDERS).join(", ");
|
|
14082
|
+
problems.push({
|
|
14083
|
+
severity: "warning",
|
|
14084
|
+
scope: "project",
|
|
14085
|
+
file: projectPath,
|
|
14086
|
+
message: `Project configuration: ${stale.map((x2) => `"${x2}"`).join(", ")} no longer decide where resources live and can be removed - the folders are fixed (${folders})`
|
|
14087
|
+
});
|
|
14088
|
+
};
|
|
13866
14089
|
/**
|
|
13867
14090
|
* Reads every JSON file the entries point at.
|
|
13868
14091
|
*
|
|
@@ -15028,14 +15251,14 @@ var init_readFile = __esm({
|
|
|
15028
15251
|
import_promises3 = require("fs/promises");
|
|
15029
15252
|
filePromises = {};
|
|
15030
15253
|
fileIntercept = {};
|
|
15031
|
-
readFile2 = (
|
|
15032
|
-
if (fileIntercept[
|
|
15033
|
-
return fileIntercept[
|
|
15254
|
+
readFile2 = (path20, options2) => {
|
|
15255
|
+
if (fileIntercept[path20] !== void 0) {
|
|
15256
|
+
return fileIntercept[path20];
|
|
15034
15257
|
}
|
|
15035
|
-
if (!filePromises[
|
|
15036
|
-
filePromises[
|
|
15258
|
+
if (!filePromises[path20] || options2?.ignoreCache) {
|
|
15259
|
+
filePromises[path20] = (0, import_promises3.readFile)(path20, "utf8");
|
|
15037
15260
|
}
|
|
15038
|
-
return filePromises[
|
|
15261
|
+
return filePromises[path20];
|
|
15039
15262
|
};
|
|
15040
15263
|
}
|
|
15041
15264
|
});
|
|
@@ -15147,8 +15370,8 @@ var init_externalDataInterceptor = __esm({
|
|
|
15147
15370
|
getFileRecord() {
|
|
15148
15371
|
return fileIntercept;
|
|
15149
15372
|
},
|
|
15150
|
-
interceptFile(
|
|
15151
|
-
fileIntercept[
|
|
15373
|
+
interceptFile(path20, contents) {
|
|
15374
|
+
fileIntercept[path20] = Promise.resolve(contents);
|
|
15152
15375
|
},
|
|
15153
15376
|
getTokenRecord() {
|
|
15154
15377
|
return tokenIntercept;
|
|
@@ -18228,8 +18451,8 @@ var init_createConfigValueProvider = __esm({
|
|
|
18228
18451
|
return endpoint.url.href;
|
|
18229
18452
|
}
|
|
18230
18453
|
if ("hostname" in endpoint) {
|
|
18231
|
-
const { protocol, hostname, port, path:
|
|
18232
|
-
return `${protocol}//${hostname}${port ? ":" + port : ""}${
|
|
18454
|
+
const { protocol, hostname, port, path: path20 } = endpoint;
|
|
18455
|
+
return `${protocol}//${hostname}${port ? ":" + port : ""}${path20}`;
|
|
18233
18456
|
}
|
|
18234
18457
|
}
|
|
18235
18458
|
return endpoint;
|
|
@@ -18604,18 +18827,18 @@ var getAttrPathList;
|
|
|
18604
18827
|
var init_getAttrPathList = __esm({
|
|
18605
18828
|
"../../node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttrPathList.js"() {
|
|
18606
18829
|
init_types2();
|
|
18607
|
-
getAttrPathList = (
|
|
18608
|
-
const parts =
|
|
18830
|
+
getAttrPathList = (path20) => {
|
|
18831
|
+
const parts = path20.split(".");
|
|
18609
18832
|
const pathList = [];
|
|
18610
18833
|
for (const part of parts) {
|
|
18611
18834
|
const squareBracketIndex = part.indexOf("[");
|
|
18612
18835
|
if (squareBracketIndex !== -1) {
|
|
18613
18836
|
if (part.indexOf("]") !== part.length - 1) {
|
|
18614
|
-
throw new EndpointError(`Path: '${
|
|
18837
|
+
throw new EndpointError(`Path: '${path20}' does not end with ']'`);
|
|
18615
18838
|
}
|
|
18616
18839
|
const arrayIndex = part.slice(squareBracketIndex + 1, -1);
|
|
18617
18840
|
if (Number.isNaN(parseInt(arrayIndex))) {
|
|
18618
|
-
throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${
|
|
18841
|
+
throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path20}'`);
|
|
18619
18842
|
}
|
|
18620
18843
|
if (squareBracketIndex !== 0) {
|
|
18621
18844
|
pathList.push(part.slice(0, squareBracketIndex));
|
|
@@ -18636,9 +18859,9 @@ var init_getAttr = __esm({
|
|
|
18636
18859
|
"../../node_modules/@smithy/core/dist-es/submodules/endpoints/util-endpoints/lib/getAttr.js"() {
|
|
18637
18860
|
init_types2();
|
|
18638
18861
|
init_getAttrPathList();
|
|
18639
|
-
getAttr = (value,
|
|
18862
|
+
getAttr = (value, path20) => getAttrPathList(path20).reduce((acc, index) => {
|
|
18640
18863
|
if (typeof acc !== "object") {
|
|
18641
|
-
throw new EndpointError(`Index '${index}' in '${
|
|
18864
|
+
throw new EndpointError(`Index '${index}' in '${path20}' not found in '${JSON.stringify(value)}'`);
|
|
18642
18865
|
} else if (Array.isArray(acc)) {
|
|
18643
18866
|
const i6 = parseInt(index);
|
|
18644
18867
|
return acc[i6 < 0 ? acc.length + i6 : i6];
|
|
@@ -18699,8 +18922,8 @@ var init_parseURL = __esm({
|
|
|
18699
18922
|
return value;
|
|
18700
18923
|
}
|
|
18701
18924
|
if (typeof value === "object" && "hostname" in value) {
|
|
18702
|
-
const { hostname: hostname2, port, protocol: protocol2 = "", path:
|
|
18703
|
-
const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${
|
|
18925
|
+
const { hostname: hostname2, port, protocol: protocol2 = "", path: path20 = "", query = {} } = value;
|
|
18926
|
+
const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path20}`);
|
|
18704
18927
|
url.search = Object.entries(query).map(([k7, v3]) => `${k7}=${v3}`).join("&");
|
|
18705
18928
|
return url;
|
|
18706
18929
|
}
|
|
@@ -21044,11 +21267,11 @@ var init_HttpBindingProtocol = __esm({
|
|
|
21044
21267
|
const opTraits = translateTraits(operationSchema.traits);
|
|
21045
21268
|
if (opTraits.http) {
|
|
21046
21269
|
request.method = opTraits.http[0];
|
|
21047
|
-
const [
|
|
21270
|
+
const [path20, search] = opTraits.http[1].split("?");
|
|
21048
21271
|
if (request.path == "/") {
|
|
21049
|
-
request.path =
|
|
21272
|
+
request.path = path20;
|
|
21050
21273
|
} else {
|
|
21051
|
-
request.path +=
|
|
21274
|
+
request.path += path20;
|
|
21052
21275
|
}
|
|
21053
21276
|
const traitSearchParams = new URLSearchParams(search ?? "");
|
|
21054
21277
|
for (const [key, value] of traitSearchParams) {
|
|
@@ -24761,12 +24984,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
24761
24984
|
const password = request.password ?? "";
|
|
24762
24985
|
auth = `${username}:${password}`;
|
|
24763
24986
|
}
|
|
24764
|
-
let
|
|
24987
|
+
let path20 = request.path;
|
|
24765
24988
|
if (queryString) {
|
|
24766
|
-
|
|
24989
|
+
path20 += `?${queryString}`;
|
|
24767
24990
|
}
|
|
24768
24991
|
if (request.fragment) {
|
|
24769
|
-
|
|
24992
|
+
path20 += `#${request.fragment}`;
|
|
24770
24993
|
}
|
|
24771
24994
|
let hostname = request.hostname ?? "";
|
|
24772
24995
|
if (hostname[0] === "[" && hostname.endsWith("]")) {
|
|
@@ -24778,7 +25001,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
24778
25001
|
headers: request.headers,
|
|
24779
25002
|
host: hostname,
|
|
24780
25003
|
method: request.method,
|
|
24781
|
-
path:
|
|
25004
|
+
path: path20,
|
|
24782
25005
|
port: request.port,
|
|
24783
25006
|
agent,
|
|
24784
25007
|
auth
|
|
@@ -25687,10 +25910,10 @@ ${longDate}
|
|
|
25687
25910
|
${credentialScope}
|
|
25688
25911
|
${toHex(hashedRequest)}`;
|
|
25689
25912
|
}
|
|
25690
|
-
getCanonicalPath({ path:
|
|
25913
|
+
getCanonicalPath({ path: path20 }) {
|
|
25691
25914
|
if (this.uriEscapePath) {
|
|
25692
25915
|
const normalizedPathSegments = [];
|
|
25693
|
-
for (const pathSegment of
|
|
25916
|
+
for (const pathSegment of path20.split("/")) {
|
|
25694
25917
|
if (pathSegment?.length === 0)
|
|
25695
25918
|
continue;
|
|
25696
25919
|
if (pathSegment === ".")
|
|
@@ -25701,11 +25924,11 @@ ${toHex(hashedRequest)}`;
|
|
|
25701
25924
|
normalizedPathSegments.push(pathSegment);
|
|
25702
25925
|
}
|
|
25703
25926
|
}
|
|
25704
|
-
const normalizedPath = `${
|
|
25927
|
+
const normalizedPath = `${path20?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path20?.endsWith("/") ? "/" : ""}`;
|
|
25705
25928
|
const doubleEncoded = escapeUri(normalizedPath);
|
|
25706
25929
|
return doubleEncoded.replace(/%2F/g, "/");
|
|
25707
25930
|
}
|
|
25708
|
-
return
|
|
25931
|
+
return path20;
|
|
25709
25932
|
}
|
|
25710
25933
|
validateResolvedCredentials(credentials) {
|
|
25711
25934
|
if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") {
|
|
@@ -33416,10 +33639,10 @@ var require_pend = __commonJS({
|
|
|
33416
33639
|
// ../../node_modules/yauzl/fd-slicer.js
|
|
33417
33640
|
var require_fd_slicer = __commonJS({
|
|
33418
33641
|
"../../node_modules/yauzl/fd-slicer.js"(exports2) {
|
|
33419
|
-
var
|
|
33642
|
+
var fs28 = require("fs");
|
|
33420
33643
|
var util = require("util");
|
|
33421
33644
|
var stream = require("stream");
|
|
33422
|
-
var
|
|
33645
|
+
var Readable10 = stream.Readable;
|
|
33423
33646
|
var PassThrough = stream.PassThrough;
|
|
33424
33647
|
var Pend = require_pend();
|
|
33425
33648
|
var EventEmitter = require("events").EventEmitter;
|
|
@@ -33436,7 +33659,7 @@ var require_fd_slicer = __commonJS({
|
|
|
33436
33659
|
FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
|
|
33437
33660
|
var self2 = this;
|
|
33438
33661
|
self2.pend.go(function(cb) {
|
|
33439
|
-
|
|
33662
|
+
fs28.read(self2.fd, buffer, offset, length, position, function(err2, bytesRead, buffer2) {
|
|
33440
33663
|
cb();
|
|
33441
33664
|
callback(err2, bytesRead, buffer2);
|
|
33442
33665
|
});
|
|
@@ -33453,7 +33676,7 @@ var require_fd_slicer = __commonJS({
|
|
|
33453
33676
|
self2.refCount -= 1;
|
|
33454
33677
|
if (self2.refCount < 0) throw new Error("invalid unref");
|
|
33455
33678
|
if (self2.refCount > 0) return;
|
|
33456
|
-
|
|
33679
|
+
fs28.close(self2.fd, onCloseDone);
|
|
33457
33680
|
function onCloseDone(err2) {
|
|
33458
33681
|
if (err2) {
|
|
33459
33682
|
self2.emit("error", err2);
|
|
@@ -33462,10 +33685,10 @@ var require_fd_slicer = __commonJS({
|
|
|
33462
33685
|
}
|
|
33463
33686
|
}
|
|
33464
33687
|
};
|
|
33465
|
-
util.inherits(ReadStream2,
|
|
33688
|
+
util.inherits(ReadStream2, Readable10);
|
|
33466
33689
|
function ReadStream2(context, options2) {
|
|
33467
33690
|
options2 = options2 || {};
|
|
33468
|
-
|
|
33691
|
+
Readable10.call(this, options2);
|
|
33469
33692
|
this.context = context;
|
|
33470
33693
|
this.context.ref();
|
|
33471
33694
|
this.start = options2.start || 0;
|
|
@@ -33485,7 +33708,7 @@ var require_fd_slicer = __commonJS({
|
|
|
33485
33708
|
}
|
|
33486
33709
|
self2.context.pend.go(function(cb) {
|
|
33487
33710
|
var buffer = Buffer.allocUnsafe(toRead);
|
|
33488
|
-
|
|
33711
|
+
fs28.read(self2.context.fd, buffer, 0, toRead, self2.pos, function(err2, bytesRead) {
|
|
33489
33712
|
if (err2) {
|
|
33490
33713
|
self2.destroy(err2);
|
|
33491
33714
|
} else if (bytesRead === 0) {
|
|
@@ -33845,12 +34068,12 @@ var require_crc32 = __commonJS({
|
|
|
33845
34068
|
// ../../node_modules/yauzl/index.js
|
|
33846
34069
|
var require_yauzl = __commonJS({
|
|
33847
34070
|
"../../node_modules/yauzl/index.js"(exports2) {
|
|
33848
|
-
var
|
|
34071
|
+
var fs28 = require("fs");
|
|
33849
34072
|
var zlib3 = require("zlib");
|
|
33850
34073
|
var fd_slicer = require_fd_slicer();
|
|
33851
34074
|
var util = require("util");
|
|
33852
34075
|
var EventEmitter = require("events").EventEmitter;
|
|
33853
|
-
var
|
|
34076
|
+
var Transform5 = require("stream").Transform;
|
|
33854
34077
|
var PassThrough = require("stream").PassThrough;
|
|
33855
34078
|
var Writable2 = require("stream").Writable;
|
|
33856
34079
|
var crc322 = typeof zlib3.crc32 === "function" ? zlib3.crc32 : require_crc32();
|
|
@@ -33870,9 +34093,9 @@ var require_yauzl = __commonJS({
|
|
|
33870
34093
|
exports2.Entry = Entry;
|
|
33871
34094
|
exports2.LocalFileHeader = LocalFileHeader;
|
|
33872
34095
|
exports2.RandomAccessReader = RandomAccessReader;
|
|
33873
|
-
function openPromise(
|
|
34096
|
+
function openPromise(path20, options2) {
|
|
33874
34097
|
return new Promise((resolve, reject) => {
|
|
33875
|
-
open(
|
|
34098
|
+
open(path20, { ...options2, lazyEntries: true }, function(err2, zipfile) {
|
|
33876
34099
|
if (err2) return reject(err2);
|
|
33877
34100
|
resolve(zipfile);
|
|
33878
34101
|
});
|
|
@@ -33902,7 +34125,7 @@ var require_yauzl = __commonJS({
|
|
|
33902
34125
|
});
|
|
33903
34126
|
});
|
|
33904
34127
|
}
|
|
33905
|
-
function open(
|
|
34128
|
+
function open(path20, options2, callback) {
|
|
33906
34129
|
if (typeof options2 === "function") {
|
|
33907
34130
|
callback = options2;
|
|
33908
34131
|
options2 = null;
|
|
@@ -33914,10 +34137,10 @@ var require_yauzl = __commonJS({
|
|
|
33914
34137
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
33915
34138
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
33916
34139
|
if (callback == null) callback = defaultCallback;
|
|
33917
|
-
|
|
34140
|
+
fs28.open(path20, "r", function(err2, fd) {
|
|
33918
34141
|
if (err2) return callback(err2);
|
|
33919
34142
|
fromFd(fd, options2, function(err3, zipfile) {
|
|
33920
|
-
if (err3)
|
|
34143
|
+
if (err3) fs28.close(fd, defaultCallback);
|
|
33921
34144
|
callback(err3, zipfile);
|
|
33922
34145
|
});
|
|
33923
34146
|
});
|
|
@@ -33934,7 +34157,7 @@ var require_yauzl = __commonJS({
|
|
|
33934
34157
|
if (options2.validateEntrySizes == null) options2.validateEntrySizes = true;
|
|
33935
34158
|
if (options2.strictFileNames == null) options2.strictFileNames = false;
|
|
33936
34159
|
if (callback == null) callback = defaultCallback;
|
|
33937
|
-
|
|
34160
|
+
fs28.fstat(fd, function(err2, stats) {
|
|
33938
34161
|
if (err2) return callback(err2);
|
|
33939
34162
|
var reader = new fd_slicer.FdSlicer(fd);
|
|
33940
34163
|
fromRandomAccessReader(reader, stats.size, options2, callback);
|
|
@@ -34544,9 +34767,9 @@ var require_yauzl = __commonJS({
|
|
|
34544
34767
|
callback();
|
|
34545
34768
|
});
|
|
34546
34769
|
}
|
|
34547
|
-
util.inherits(AssertByteCountStream,
|
|
34770
|
+
util.inherits(AssertByteCountStream, Transform5);
|
|
34548
34771
|
function AssertByteCountStream(byteCount) {
|
|
34549
|
-
|
|
34772
|
+
Transform5.call(this);
|
|
34550
34773
|
this.actualByteCount = 0;
|
|
34551
34774
|
this.expectedByteCount = byteCount;
|
|
34552
34775
|
}
|
|
@@ -38161,8 +38384,8 @@ var init_HttpClient = __esm({
|
|
|
38161
38384
|
throw new Error(err2.message ?? "An error occurred on GET.");
|
|
38162
38385
|
}
|
|
38163
38386
|
};
|
|
38164
|
-
this.getWorkerUrl = (
|
|
38165
|
-
const cleanPath =
|
|
38387
|
+
this.getWorkerUrl = (path20) => {
|
|
38388
|
+
const cleanPath = path20.startsWith("/") ? path20.substring(1) : path20;
|
|
38166
38389
|
return `${this.workerHost}/${cleanPath}`;
|
|
38167
38390
|
};
|
|
38168
38391
|
this.workerHost = ProcessENVManager_default.getEnvVariable("REMORA_WORKER_HOST") || "http://worker:5069";
|
|
@@ -44053,7 +44276,7 @@ var init_DeltaShareDriver = __esm({
|
|
|
44053
44276
|
* disagree on their columns still produces a rectangular dataset.
|
|
44054
44277
|
*/
|
|
44055
44278
|
this._writeDatasetFile = async (localPath, batches, delimiter) => {
|
|
44056
|
-
const
|
|
44279
|
+
const writer = new BatchWriter_default(fs9.createWriteStream(localPath));
|
|
44057
44280
|
let header = null;
|
|
44058
44281
|
let records = 0;
|
|
44059
44282
|
try {
|
|
@@ -44061,25 +44284,19 @@ var init_DeltaShareDriver = __esm({
|
|
|
44061
44284
|
for (const record of batch) {
|
|
44062
44285
|
if (!header) {
|
|
44063
44286
|
header = Object.keys(record);
|
|
44064
|
-
|
|
44287
|
+
if (!writer.write(CSVParser_default.stringifyRow(header, delimiter)))
|
|
44288
|
+
await writer.waitForDrain();
|
|
44065
44289
|
}
|
|
44066
|
-
|
|
44290
|
+
if (!writer.write(CSVParser_default.stringifyRow(header.map((column) => this._serializeDatasetValue(record[column])), delimiter)))
|
|
44291
|
+
await writer.waitForDrain();
|
|
44067
44292
|
records++;
|
|
44068
44293
|
}
|
|
44069
44294
|
}
|
|
44070
44295
|
} finally {
|
|
44071
|
-
await
|
|
44296
|
+
await writer.close();
|
|
44072
44297
|
}
|
|
44073
44298
|
return records;
|
|
44074
44299
|
};
|
|
44075
|
-
// A false from write() means the buffer is full; waiting for the drain just then keeps the queued
|
|
44076
|
-
// rows bounded instead of letting them pile up faster than the disk takes them.
|
|
44077
|
-
this._writeLine = async (stream, line) => {
|
|
44078
|
-
if (stream.write(`${line}
|
|
44079
|
-
`))
|
|
44080
|
-
return;
|
|
44081
|
-
await new Promise((resolve) => stream.once("drain", resolve));
|
|
44082
|
-
};
|
|
44083
44300
|
/**
|
|
44084
44301
|
* Every file the share reports for this table. `jsonPredicateHints` lets the server skip the
|
|
44085
44302
|
* files that cannot hold a matching row and `limitHint` the ones past the rows the caller wants;
|
|
@@ -44258,18 +44475,20 @@ var init_SqlDatasetWriter = __esm({
|
|
|
44258
44475
|
Affirm_default(localPath, "Invalid dataset path");
|
|
44259
44476
|
Affirm_default.hasItems(columns, "Invalid dataset columns");
|
|
44260
44477
|
Affirm_default(batches, "Invalid dataset batches");
|
|
44261
|
-
const
|
|
44478
|
+
const writer = new BatchWriter_default(import_fs9.default.createWriteStream(localPath));
|
|
44262
44479
|
let rows = 0;
|
|
44263
44480
|
try {
|
|
44264
|
-
|
|
44481
|
+
if (!writer.write(CSVParser_default.stringifyRow(columns, delimiter)))
|
|
44482
|
+
await writer.waitForDrain();
|
|
44265
44483
|
for await (const batch of batches) {
|
|
44266
44484
|
for (const row of batch) {
|
|
44267
|
-
|
|
44485
|
+
if (!writer.write(CSVParser_default.stringifyRow(row.map((value) => this.serialize(value)), delimiter)))
|
|
44486
|
+
await writer.waitForDrain();
|
|
44268
44487
|
rows++;
|
|
44269
44488
|
}
|
|
44270
44489
|
}
|
|
44271
44490
|
} finally {
|
|
44272
|
-
await
|
|
44491
|
+
await writer.close();
|
|
44273
44492
|
}
|
|
44274
44493
|
return rows;
|
|
44275
44494
|
};
|
|
@@ -44291,14 +44510,6 @@ var init_SqlDatasetWriter = __esm({
|
|
|
44291
44510
|
return value.toString("base64");
|
|
44292
44511
|
return JSON.stringify(value);
|
|
44293
44512
|
};
|
|
44294
|
-
// A false from write() means the buffer is full; waiting for the drain just then keeps the queued
|
|
44295
|
-
// rows bounded instead of letting them pile up faster than the disk takes them.
|
|
44296
|
-
this.writeLine = async (stream, line) => {
|
|
44297
|
-
if (stream.write(`${line}
|
|
44298
|
-
`))
|
|
44299
|
-
return;
|
|
44300
|
-
await new Promise((resolve) => stream.once("drain", resolve));
|
|
44301
|
-
};
|
|
44302
44513
|
}
|
|
44303
44514
|
};
|
|
44304
44515
|
SqlDatasetWriter = new SqlDatasetWriterClass();
|
|
@@ -44319,8 +44530,13 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44319
44530
|
*
|
|
44320
44531
|
* Columns are projected in the producer's own order and the caller writes the header from
|
|
44321
44532
|
* `columnKeys`, so the staged CSV never depends on how a source echoes column names back.
|
|
44533
|
+
*
|
|
44534
|
+
* `options` is what the run asked the source to do for it — a `WHERE` rendered by `SqlPredicate`
|
|
44535
|
+
* and a row limit, both resolved by `SqlPushdown`. Neither changes what the run produces: the
|
|
44536
|
+
* filters are applied per record and the limit enforced regardless, so a statement built without
|
|
44537
|
+
* them reads more rows and returns the same result.
|
|
44322
44538
|
*/
|
|
44323
|
-
this.buildProducerSelect = (producer, source,
|
|
44539
|
+
this.buildProducerSelect = (producer, source, options2) => {
|
|
44324
44540
|
Affirm_default(producer, "Invalid producer");
|
|
44325
44541
|
Affirm_default(source, "Invalid source");
|
|
44326
44542
|
const { schema, table } = this.resolveTable(producer, source);
|
|
@@ -44328,8 +44544,30 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44328
44544
|
Affirm_default.hasItems(columns, `Producer "${producer.name}" declares no dimensions, so there is nothing to select from "${table}"`);
|
|
44329
44545
|
const projection = columns.map((column) => this.quote(column, `column of producer "${producer.name}"`)).join(", ");
|
|
44330
44546
|
const from = schema ? `${this.quote(schema, `schema of producer "${producer.name}"`)}.${this.quote(table, `table of producer "${producer.name}"`)}` : this.quote(table, `table of producer "${producer.name}"`);
|
|
44331
|
-
const
|
|
44332
|
-
|
|
44547
|
+
const where = options2?.where ? ` WHERE ${options2.where}` : "";
|
|
44548
|
+
const limit = options2?.limit > 0 ? ` LIMIT ${Math.floor(options2.limit)}` : "";
|
|
44549
|
+
return `SELECT ${projection} FROM ${from}${where}${limit}`;
|
|
44550
|
+
};
|
|
44551
|
+
/**
|
|
44552
|
+
* The statement that asks a SQL source what columns a producer's table actually HAS.
|
|
44553
|
+
*
|
|
44554
|
+
* This is what `discover` reads, and it deliberately does not go through `buildProducerSelect`:
|
|
44555
|
+
* that statement is built FROM the producer's dimensions, so sampling it can only ever hand back
|
|
44556
|
+
* the columns the producer already declared — a producer naming one column of a fourteen-column
|
|
44557
|
+
* table discovered exactly that one and was told it had succeeded. The catalog is the only source
|
|
44558
|
+
* of the answer, so the catalog is what is read.
|
|
44559
|
+
*
|
|
44560
|
+
* `information_schema.columns` rather than a dialect catalog (`svv_columns`, `pg_attribute`)
|
|
44561
|
+
* because Redshift and Postgres both expose it with the same column names, which is the same
|
|
44562
|
+
* reason the rest of this builder is dialect-free. One known limit: a Redshift external
|
|
44563
|
+
* (Spectrum) or late-binding-view column is not in `information_schema` and so is not discovered.
|
|
44564
|
+
*/
|
|
44565
|
+
this.buildColumnSelect = (producer, source) => {
|
|
44566
|
+
Affirm_default(producer, "Invalid producer");
|
|
44567
|
+
Affirm_default(source, "Invalid source");
|
|
44568
|
+
const { schema, table } = this.resolveTable(producer, source);
|
|
44569
|
+
const schemaMatch = schema ? `table_schema = ${this.nameLiteral(schema, `schema of producer "${producer.name}"`)}` : "table_schema = current_schema()";
|
|
44570
|
+
return `SELECT column_name, data_type, is_nullable, ordinal_position FROM information_schema.columns WHERE ${schemaMatch} AND table_name = ${this.nameLiteral(table, `table of producer "${producer.name}"`)} ORDER BY ordinal_position`;
|
|
44333
44571
|
};
|
|
44334
44572
|
/**
|
|
44335
44573
|
* The source column behind each dimension, in declaration order: `alias` when the dimension
|
|
@@ -44345,12 +44583,14 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44345
44583
|
* Where the producer's rows live. The table comes from `settings.sqlTable`, optionally qualified
|
|
44346
44584
|
* as `schema.table`; an unqualified name falls back to the source's own `schema`, and with
|
|
44347
44585
|
* neither the statement is left unqualified for the connection's `search_path` to resolve.
|
|
44586
|
+
*
|
|
44587
|
+
* A table name is the only thing a producer contributes: there is no hand-written statement to
|
|
44588
|
+
* accept, so every statement sent to a source is one this builder emitted.
|
|
44348
44589
|
*/
|
|
44349
44590
|
this.resolveTable = (producer, source) => {
|
|
44350
44591
|
Affirm_default(producer, "Invalid producer");
|
|
44351
44592
|
Affirm_default(source, "Invalid source");
|
|
44352
|
-
const { sqlTable,
|
|
44353
|
-
Affirm_default(!sql, `Producer "${producer.name}" sets "sql", which is not supported on a SQL source yet \u2014 use "sqlTable"`);
|
|
44593
|
+
const { sqlTable, fileKey } = producer.settings;
|
|
44354
44594
|
Affirm_default(!fileKey || sqlTable, `Producer "${producer.name}" sets "fileKey", which a SQL source has no use for \u2014 use "sqlTable"`);
|
|
44355
44595
|
Affirm_default(sqlTable, `Producer "${producer.name}" reads from SQL source "${source.name}" but declares no "sqlTable"`);
|
|
44356
44596
|
const parts = sqlTable.split(".");
|
|
@@ -44359,6 +44599,12 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44359
44599
|
return { schema: parts[0], table: parts[1] };
|
|
44360
44600
|
return { schema: source.authentication.schema || void 0, table: parts[0] };
|
|
44361
44601
|
};
|
|
44602
|
+
/**
|
|
44603
|
+
* Whether a name is one `quote` would accept. For a caller that has to answer "can this be put in
|
|
44604
|
+
* a statement at all" without being thrown at — see `SqlPredicate`, where an unusable column is a
|
|
44605
|
+
* filter not pushed rather than a run that fails.
|
|
44606
|
+
*/
|
|
44607
|
+
this.isIdentifier = (identifier) => !!identifier && IDENTIFIER_PATTERN2.test(identifier);
|
|
44362
44608
|
/**
|
|
44363
44609
|
* One identifier, quoted. The pattern check is the whole defence against a name reaching the
|
|
44364
44610
|
* server as syntax rather than as a name — a producer's config is not a trusted statement — so
|
|
@@ -44369,6 +44615,23 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44369
44615
|
Affirm_default(IDENTIFIER_PATTERN2.test(identifier), `Invalid SQL identifier "${identifier}" for ${context}: expected letters, digits, "_" or "$", not starting with a digit`);
|
|
44370
44616
|
return `"${identifier}"`;
|
|
44371
44617
|
};
|
|
44618
|
+
/**
|
|
44619
|
+
* The same identifier as a string LITERAL, which is how the catalog stores it: in
|
|
44620
|
+
* `information_schema` a table name is data, not syntax, so it is compared rather than quoted.
|
|
44621
|
+
* It goes through the identical pattern check, which forbids a quote character outright, so
|
|
44622
|
+
* there is nothing here to escape — a name this accepts cannot close the literal.
|
|
44623
|
+
*
|
|
44624
|
+
* The comparison is EXACT, which matches the projection: `buildProducerSelect` quotes every
|
|
44625
|
+
* identifier, so `"Claim"` and `claim` are already two different tables to the server. Postgres
|
|
44626
|
+
* folds an unquoted name to lower case when the table is created, and Redshift folds it always,
|
|
44627
|
+
* so a `sqlTable` that does not match the stored spelling finds nothing — and the caller says so
|
|
44628
|
+
* naming what it looked for, rather than guessing at the case.
|
|
44629
|
+
*/
|
|
44630
|
+
this.nameLiteral = (identifier, context) => {
|
|
44631
|
+
Affirm_default(identifier, `Missing SQL identifier for ${context}`);
|
|
44632
|
+
Affirm_default(IDENTIFIER_PATTERN2.test(identifier), `Invalid SQL identifier "${identifier}" for ${context}: expected letters, digits, "_" or "$", not starting with a digit`);
|
|
44633
|
+
return `'${identifier}'`;
|
|
44634
|
+
};
|
|
44372
44635
|
}
|
|
44373
44636
|
};
|
|
44374
44637
|
SqlQueryBuilder = new SqlQueryBuilderClass();
|
|
@@ -44376,6 +44639,288 @@ var init_SqlQueryBuilder = __esm({
|
|
|
44376
44639
|
}
|
|
44377
44640
|
});
|
|
44378
44641
|
|
|
44642
|
+
// ../../packages/drivers/src/sql/SqlPredicate.ts
|
|
44643
|
+
var ISO_DATE_SHAPE, NUMERIC_LITERAL, isControlCharacter, MAX_LITERAL_CHARS, SqlPredicateClass, SqlPredicate, SqlPredicate_default;
|
|
44644
|
+
var init_SqlPredicate = __esm({
|
|
44645
|
+
"../../packages/drivers/src/sql/SqlPredicate.ts"() {
|
|
44646
|
+
init_SqlQueryBuilder();
|
|
44647
|
+
ISO_DATE_SHAPE = /^\d{4}-\d{2}-\d{2}/;
|
|
44648
|
+
NUMERIC_LITERAL = /^[+-]?(\d+\.?\d*|\.\d+)$/;
|
|
44649
|
+
isControlCharacter = (character) => {
|
|
44650
|
+
const code = character.charCodeAt(0);
|
|
44651
|
+
return code < 32 || code === 127;
|
|
44652
|
+
};
|
|
44653
|
+
MAX_LITERAL_CHARS = 1e3;
|
|
44654
|
+
SqlPredicateClass = class {
|
|
44655
|
+
constructor() {
|
|
44656
|
+
/**
|
|
44657
|
+
* The `WHERE` body for these filters, or `null` when none of them can be expressed.
|
|
44658
|
+
*
|
|
44659
|
+
* `columnTypes` is the type each column has in the SOURCE's own catalog, keyed by column name.
|
|
44660
|
+
* A filter whose column is missing from it, or whose dimension type the catalog disagrees with,
|
|
44661
|
+
* is not pushed: a producer is free to declare a `timestamptz` column as a `string`, and the
|
|
44662
|
+
* statement that compares it as one either prunes the wrong rows or fails outright.
|
|
44663
|
+
*/
|
|
44664
|
+
this.build = (filters, columnTypes) => {
|
|
44665
|
+
if (!filters || filters.length === 0)
|
|
44666
|
+
return null;
|
|
44667
|
+
if (!columnTypes || columnTypes.size === 0)
|
|
44668
|
+
return null;
|
|
44669
|
+
return this.all(filters.map((filter) => this.toNode(filter, columnTypes)).filter((x2) => !!x2));
|
|
44670
|
+
};
|
|
44671
|
+
this.toNode = (filter, columnTypes) => {
|
|
44672
|
+
const node = this.toComparison(filter, columnTypes);
|
|
44673
|
+
if (!node)
|
|
44674
|
+
return null;
|
|
44675
|
+
if (filter.and?.length) {
|
|
44676
|
+
const children = filter.and.map((x2) => this.toNode(x2, columnTypes)).filter((x2) => !!x2);
|
|
44677
|
+
return this.all([node, ...children]);
|
|
44678
|
+
}
|
|
44679
|
+
if (filter.or?.length) {
|
|
44680
|
+
const branches = filter.or.map((x2) => this.toNode(x2, columnTypes));
|
|
44681
|
+
return branches.every((x2) => !!x2) ? this.any([node, ...branches]) : null;
|
|
44682
|
+
}
|
|
44683
|
+
return node;
|
|
44684
|
+
};
|
|
44685
|
+
/** The filter's own rule as SQL, ignoring its `and`/`or` children. */
|
|
44686
|
+
this.toComparison = (filter, columnTypes) => {
|
|
44687
|
+
if (columnTypes.get(filter.column) !== filter.type)
|
|
44688
|
+
return null;
|
|
44689
|
+
if (!SqlQueryBuilder_default.isIdentifier(filter.column))
|
|
44690
|
+
return null;
|
|
44691
|
+
const column = SqlQueryBuilder_default.quote(filter.column, `pushdown column "${filter.column}"`);
|
|
44692
|
+
switch (filter.type) {
|
|
44693
|
+
case "string":
|
|
44694
|
+
return this.text(filter, column);
|
|
44695
|
+
case "number":
|
|
44696
|
+
return this.numeric(filter, column);
|
|
44697
|
+
default:
|
|
44698
|
+
return null;
|
|
44699
|
+
}
|
|
44700
|
+
};
|
|
44701
|
+
/**
|
|
44702
|
+
* A text column's rule. The LIKE patterns are built from a value with no backslash in it (see
|
|
44703
|
+
* `isPlainText`), so the only pattern characters a value can carry are `%` and `_` — both of
|
|
44704
|
+
* which match MORE than the literal they stand for, which is the safe direction.
|
|
44705
|
+
*/
|
|
44706
|
+
this.text = (filter, column) => {
|
|
44707
|
+
const values = filter.values ?? [];
|
|
44708
|
+
if (values.length === 0 || !values.every(this.isPlainText))
|
|
44709
|
+
return null;
|
|
44710
|
+
switch (filter.operator) {
|
|
44711
|
+
case "equals":
|
|
44712
|
+
return values.length === 1 ? `${column} = ${this.textLiteral(values[0])}` : null;
|
|
44713
|
+
case "in":
|
|
44714
|
+
return `${column} IN (${values.map(this.textLiteral).join(", ")})`;
|
|
44715
|
+
case "contains":
|
|
44716
|
+
return `${column} LIKE ${this.textLiteral(`%${values[0]}%`)}`;
|
|
44717
|
+
case "startsWith":
|
|
44718
|
+
return `${column} LIKE ${this.textLiteral(`${values[0]}%`)}`;
|
|
44719
|
+
case "endsWith":
|
|
44720
|
+
return `${column} LIKE ${this.textLiteral(`%${values[0]}`)}`;
|
|
44721
|
+
default:
|
|
44722
|
+
return null;
|
|
44723
|
+
}
|
|
44724
|
+
};
|
|
44725
|
+
/**
|
|
44726
|
+
* A numeric column's rule. The literal goes over as the digits the filter was given rather than
|
|
44727
|
+
* as the double they parse to, so `1.50` is compared as the server stores it.
|
|
44728
|
+
*
|
|
44729
|
+
* The one shape where the two sides can still disagree is a column holding more precision than a
|
|
44730
|
+
* double: the server compares it exactly and remora compares it rounded, so two values a record
|
|
44731
|
+
* reads as equal are two values to the server. A producer against such a column turns the lot
|
|
44732
|
+
* off with `settings.disablePushdown`.
|
|
44733
|
+
*/
|
|
44734
|
+
this.numeric = (filter, column) => {
|
|
44735
|
+
const values = (filter.values ?? []).map(this.numberLiteral);
|
|
44736
|
+
if (values.length === 0 || !values.every((x2) => x2 !== null))
|
|
44737
|
+
return null;
|
|
44738
|
+
switch (filter.operator) {
|
|
44739
|
+
case "equals":
|
|
44740
|
+
return values.length === 1 ? `${column} = ${values[0]}` : null;
|
|
44741
|
+
case "in":
|
|
44742
|
+
return `${column} IN (${values.join(", ")})`;
|
|
44743
|
+
case "greaterThan":
|
|
44744
|
+
return `${column} > ${values[0]}`;
|
|
44745
|
+
case "greaterThanOrEquals":
|
|
44746
|
+
return `${column} >= ${values[0]}`;
|
|
44747
|
+
case "lessThan":
|
|
44748
|
+
return `${column} < ${values[0]}`;
|
|
44749
|
+
case "lessThanOrEquals":
|
|
44750
|
+
return `${column} <= ${values[0]}`;
|
|
44751
|
+
case "between":
|
|
44752
|
+
return values.length === 2 ? `(${column} >= ${values[0]} AND ${column} <= ${values[1]})` : null;
|
|
44753
|
+
default:
|
|
44754
|
+
return null;
|
|
44755
|
+
}
|
|
44756
|
+
};
|
|
44757
|
+
/**
|
|
44758
|
+
* Whether this value can go into a statement as a string literal at all.
|
|
44759
|
+
*
|
|
44760
|
+
* A single quote is doubled, which is how SQL spells one and the only escape both engines read
|
|
44761
|
+
* the same way whatever `standard_conforming_strings` is set to. A BACKSLASH is refused rather
|
|
44762
|
+
* than escaped for that same reason — and because it is the default escape character of `LIKE`,
|
|
44763
|
+
* where it would make the pattern match less than the value it came from.
|
|
44764
|
+
*/
|
|
44765
|
+
this.isPlainText = (value) => {
|
|
44766
|
+
const text = String(value ?? "");
|
|
44767
|
+
if (text.includes("\\") || [...text].some(isControlCharacter))
|
|
44768
|
+
return false;
|
|
44769
|
+
if (ISO_DATE_SHAPE.test(text))
|
|
44770
|
+
return false;
|
|
44771
|
+
return text.length <= MAX_LITERAL_CHARS;
|
|
44772
|
+
};
|
|
44773
|
+
this.textLiteral = (value) => `'${String(value).replaceAll("'", "''")}'`;
|
|
44774
|
+
/** The value as a numeric literal, or `null` when it does not name a finite number. */
|
|
44775
|
+
this.numberLiteral = (value) => {
|
|
44776
|
+
const text = String(value ?? "").trim();
|
|
44777
|
+
if (!NUMERIC_LITERAL.test(text) || !Number.isFinite(Number(text)))
|
|
44778
|
+
return null;
|
|
44779
|
+
return text;
|
|
44780
|
+
};
|
|
44781
|
+
/** `null` when there is nothing to join, the node itself when there is one, a group otherwise. */
|
|
44782
|
+
this.all = (nodes5) => this.group("AND", nodes5);
|
|
44783
|
+
this.any = (nodes5) => this.group("OR", nodes5);
|
|
44784
|
+
this.group = (operator, nodes5) => {
|
|
44785
|
+
if (nodes5.length === 0)
|
|
44786
|
+
return null;
|
|
44787
|
+
return nodes5.length === 1 ? nodes5[0] : `(${nodes5.join(` ${operator} `)})`;
|
|
44788
|
+
};
|
|
44789
|
+
}
|
|
44790
|
+
};
|
|
44791
|
+
SqlPredicate = new SqlPredicateClass();
|
|
44792
|
+
SqlPredicate_default = SqlPredicate;
|
|
44793
|
+
}
|
|
44794
|
+
});
|
|
44795
|
+
|
|
44796
|
+
// ../../packages/drivers/src/sql/SqlSchemaReader.ts
|
|
44797
|
+
var NUMERIC_TYPES, TEMPORAL_TYPES, SqlSchemaReaderClass, SqlSchemaReader, SqlSchemaReader_default;
|
|
44798
|
+
var init_SqlSchemaReader = __esm({
|
|
44799
|
+
"../../packages/drivers/src/sql/SqlSchemaReader.ts"() {
|
|
44800
|
+
init_src();
|
|
44801
|
+
init_src2();
|
|
44802
|
+
init_SqlQueryBuilder();
|
|
44803
|
+
NUMERIC_TYPES = /* @__PURE__ */ new Set([
|
|
44804
|
+
"smallint",
|
|
44805
|
+
"integer",
|
|
44806
|
+
"bigint",
|
|
44807
|
+
"decimal",
|
|
44808
|
+
"numeric",
|
|
44809
|
+
"real",
|
|
44810
|
+
"double precision",
|
|
44811
|
+
// Redshift reports these spellings for the same types
|
|
44812
|
+
"int",
|
|
44813
|
+
"int2",
|
|
44814
|
+
"int4",
|
|
44815
|
+
"int8",
|
|
44816
|
+
"float",
|
|
44817
|
+
"float4",
|
|
44818
|
+
"float8"
|
|
44819
|
+
]);
|
|
44820
|
+
TEMPORAL_TYPES = /* @__PURE__ */ new Set(["date", "timestamp", "timestamptz", "timestamp without time zone", "timestamp with time zone"]);
|
|
44821
|
+
SqlSchemaReaderClass = class {
|
|
44822
|
+
constructor() {
|
|
44823
|
+
/**
|
|
44824
|
+
* Every column of the producer's table, in the table's own order.
|
|
44825
|
+
*
|
|
44826
|
+
* Goes through `driver.query`, which is already capped at `SQL_MAX_QUERY_ROWS` — a table with
|
|
44827
|
+
* more columns than that cap is not a table remora can read anyway. A table that matches nothing
|
|
44828
|
+
* fails here rather than producing a producer with no dimensions, because "this table has no
|
|
44829
|
+
* columns" is never the truth: it is a name that did not resolve.
|
|
44830
|
+
*/
|
|
44831
|
+
this.readColumns = async (driver, producer, source) => {
|
|
44832
|
+
Affirm_default(driver, `Invalid source driver for producer "${producer?.name}"`);
|
|
44833
|
+
Affirm_default(producer, "Invalid producer");
|
|
44834
|
+
Affirm_default(source, "Invalid source");
|
|
44835
|
+
const { schema, table } = SqlQueryBuilder_default.resolveTable(producer, source);
|
|
44836
|
+
const select = SqlQueryBuilder_default.buildColumnSelect(producer, source);
|
|
44837
|
+
Logger_default.log(`SQL schema read for producer "${producer.name}" with: ${select}`);
|
|
44838
|
+
const result = await driver.query(select);
|
|
44839
|
+
const rows = result?.rows ?? [];
|
|
44840
|
+
Affirm_default.hasItems(rows, `Table "${schema ? `${schema}.` : ""}${table}" of producer "${producer.name}" has no columns in the catalog of source "${source.name}" \u2014 check the name exists and is spelled with the same case the table was created with, and that this user may read it`);
|
|
44841
|
+
return rows.slice().sort((left, right) => Number(left.ordinal_position) - Number(right.ordinal_position)).map((row) => ({
|
|
44842
|
+
name: String(row.column_name),
|
|
44843
|
+
dataType: String(row.data_type ?? "").trim().toLowerCase(),
|
|
44844
|
+
nullable: String(row.is_nullable ?? "YES").trim().toUpperCase() !== "NO"
|
|
44845
|
+
}));
|
|
44846
|
+
};
|
|
44847
|
+
/**
|
|
44848
|
+
* The dimension type a SQL column reads as. Anything not recognised is a `string`, which is both
|
|
44849
|
+
* the safe answer and the true one for the types that reach a staged CSV as text anyway — `json`,
|
|
44850
|
+
* `uuid`, an array, a Redshift `super`.
|
|
44851
|
+
*/
|
|
44852
|
+
this.toDimensionType = (dataType) => {
|
|
44853
|
+
const type = (dataType ?? "").trim().toLowerCase();
|
|
44854
|
+
if (NUMERIC_TYPES.has(type)) return "number";
|
|
44855
|
+
if (type === "boolean" || type === "bool") return "boolean";
|
|
44856
|
+
if (TEMPORAL_TYPES.has(type)) return "datetime";
|
|
44857
|
+
return "string";
|
|
44858
|
+
};
|
|
44859
|
+
}
|
|
44860
|
+
};
|
|
44861
|
+
SqlSchemaReader = new SqlSchemaReaderClass();
|
|
44862
|
+
SqlSchemaReader_default = SqlSchemaReader;
|
|
44863
|
+
}
|
|
44864
|
+
});
|
|
44865
|
+
|
|
44866
|
+
// ../../packages/drivers/src/sql/SqlPushdown.ts
|
|
44867
|
+
var SqlPushdownClass, SqlPushdown, SqlPushdown_default;
|
|
44868
|
+
var init_SqlPushdown = __esm({
|
|
44869
|
+
"../../packages/drivers/src/sql/SqlPushdown.ts"() {
|
|
44870
|
+
init_src();
|
|
44871
|
+
init_src2();
|
|
44872
|
+
init_SqlPredicate();
|
|
44873
|
+
init_SqlSchemaReader();
|
|
44874
|
+
SqlPushdownClass = class {
|
|
44875
|
+
constructor() {
|
|
44876
|
+
/** What this producer's staging statement should carry. Empty when there is nothing to push. */
|
|
44877
|
+
this.resolve = async (request) => {
|
|
44878
|
+
Affirm_default(request, "Invalid SQL pushdown request");
|
|
44879
|
+
const { driver, producer, source, plan } = request;
|
|
44880
|
+
Affirm_default(driver, "Invalid source driver");
|
|
44881
|
+
Affirm_default(producer, "Invalid producer");
|
|
44882
|
+
Affirm_default(source, "Invalid source");
|
|
44883
|
+
if (!plan || producer.settings?.disablePushdown)
|
|
44884
|
+
return {};
|
|
44885
|
+
return {
|
|
44886
|
+
where: await this.buildWhere(driver, producer, source, plan),
|
|
44887
|
+
limit: plan.limit > 0 ? plan.limit : void 0
|
|
44888
|
+
};
|
|
44889
|
+
};
|
|
44890
|
+
this.buildWhere = async (driver, producer, source, plan) => {
|
|
44891
|
+
const filters = plan.filters ?? [];
|
|
44892
|
+
if (filters.length === 0)
|
|
44893
|
+
return null;
|
|
44894
|
+
const columnTypes = await this.readColumnTypes(driver, producer, source);
|
|
44895
|
+
const where = SqlPredicate_default.build(filters, columnTypes);
|
|
44896
|
+
if (!where && columnTypes)
|
|
44897
|
+
Logger_default.log(`SQL pushdown: none of the ${filters.length} filter(s) of producer "${producer.name}" can be expressed as a WHERE clause`);
|
|
44898
|
+
return where;
|
|
44899
|
+
};
|
|
44900
|
+
/**
|
|
44901
|
+
* The type the SOURCE gives each of the table's columns, keyed by column name, or `null` when
|
|
44902
|
+
* the catalog cannot be read.
|
|
44903
|
+
*
|
|
44904
|
+
* A table that is not in `information_schema` — a Redshift external (Spectrum) table, a
|
|
44905
|
+
* late-binding view — is a table nothing is pushed to rather than a run that fails: the staging
|
|
44906
|
+
* read itself does not go through the catalog and is unaffected.
|
|
44907
|
+
*/
|
|
44908
|
+
this.readColumnTypes = async (driver, producer, source) => {
|
|
44909
|
+
try {
|
|
44910
|
+
const columns = await SqlSchemaReader_default.readColumns(driver, producer, source);
|
|
44911
|
+
return new Map(columns.map((column) => [column.name, SqlSchemaReader_default.toDimensionType(column.dataType)]));
|
|
44912
|
+
} catch (error) {
|
|
44913
|
+
Logger_default.warn(`SQL pushdown: the columns of producer "${producer.name}" could not be read from the catalog of source "${source.name}", so its filters are applied per record instead: ${error}`);
|
|
44914
|
+
return null;
|
|
44915
|
+
}
|
|
44916
|
+
};
|
|
44917
|
+
}
|
|
44918
|
+
};
|
|
44919
|
+
SqlPushdown = new SqlPushdownClass();
|
|
44920
|
+
SqlPushdown_default = SqlPushdown;
|
|
44921
|
+
}
|
|
44922
|
+
});
|
|
44923
|
+
|
|
44379
44924
|
// ../../packages/drivers/src/RedshiftDriver.ts
|
|
44380
44925
|
var import_fs10, import_client_redshift_data, RedshiftDriver, RedshiftDriver_default;
|
|
44381
44926
|
var init_RedshiftDriver = __esm({
|
|
@@ -44389,6 +44934,7 @@ var init_RedshiftDriver = __esm({
|
|
|
44389
44934
|
init_src2();
|
|
44390
44935
|
init_AwsCredentials();
|
|
44391
44936
|
init_SqlDatasetWriter();
|
|
44937
|
+
init_SqlPushdown();
|
|
44392
44938
|
init_SqlQueryBuilder();
|
|
44393
44939
|
RedshiftDriver = class {
|
|
44394
44940
|
constructor() {
|
|
@@ -44403,7 +44949,11 @@ var init_RedshiftDriver = __esm({
|
|
|
44403
44949
|
Affirm_default(this._dbName, `Source "${source.name}" is missing authentication.database`);
|
|
44404
44950
|
Affirm_default(this._workgroup || this._clusterId, `Source "${source.name}" is missing authentication.workgroup (Redshift Serverless) or authentication.clusterId (a provisioned cluster)`);
|
|
44405
44951
|
const config = {
|
|
44406
|
-
|
|
44952
|
+
// Through replaceSecret like every other field: `region` is as much a `{ENV_VAR}`
|
|
44953
|
+
// candidate as the database name, and the raw reference reached the SDK as a literal
|
|
44954
|
+
// and failed as `region="{REDSHIFT_REGION}" is not a valid hostname component` — an
|
|
44955
|
+
// error naming neither the source nor the variable that was not resolved.
|
|
44956
|
+
region: SecretManager_default.replaceSecret(source.authentication["region"]),
|
|
44407
44957
|
credentials: resolveAwsCredentials(source.authentication)
|
|
44408
44958
|
};
|
|
44409
44959
|
try {
|
|
@@ -44441,7 +44991,8 @@ var init_RedshiftDriver = __esm({
|
|
|
44441
44991
|
Affirm_default(producer, "Invalid producer");
|
|
44442
44992
|
Affirm_default(scope, "Invalid executor scope");
|
|
44443
44993
|
const { table } = SqlQueryBuilder_default.resolveTable(producer, this._source);
|
|
44444
|
-
const
|
|
44994
|
+
const pushdown = await SqlPushdown_default.resolve({ driver: this, producer, source: this._source, plan: request.pushdown });
|
|
44995
|
+
const select = SqlQueryBuilder_default.buildProducerSelect(producer, this._source, pushdown);
|
|
44445
44996
|
const columns = SqlQueryBuilder_default.columnKeys(producer);
|
|
44446
44997
|
const localPath = ExecutorScope_default.getProducerPath(scope, producer, table);
|
|
44447
44998
|
ExecutorScope_default.ensurePath(localPath);
|
|
@@ -44454,7 +45005,7 @@ var init_RedshiftDriver = __esm({
|
|
|
44454
45005
|
const reported = statement.ResultRows >= 0 ? statement.ResultRows : null;
|
|
44455
45006
|
Affirm_default(reported === null || reported === rows, `Redshift ready: staged ${rows} row(s) for producer "${producer.name}" but the statement reported ${reported}`);
|
|
44456
45007
|
Logger_default.log(`Redshift ready: staged ${rows} row(s) (${fileSize} bytes) for producer "${producer.name}" from table "${table}"`);
|
|
44457
|
-
return { files: [{ fullUri: localPath, fileSize }] };
|
|
45008
|
+
return { files: [{ fullUri: localPath, fileSize }], filteredAtSource: !!pushdown.where };
|
|
44458
45009
|
};
|
|
44459
45010
|
this.execute = async (sql) => {
|
|
44460
45011
|
Affirm_default(sql, `Invalid SQL`);
|
|
@@ -44669,6 +45220,7 @@ var init_PostgresDriver = __esm({
|
|
|
44669
45220
|
init_src2();
|
|
44670
45221
|
init_AwsCredentials();
|
|
44671
45222
|
init_SqlDatasetWriter();
|
|
45223
|
+
init_SqlPushdown();
|
|
44672
45224
|
init_SqlQueryBuilder();
|
|
44673
45225
|
PostgresDriver = class {
|
|
44674
45226
|
constructor() {
|
|
@@ -44698,7 +45250,8 @@ var init_PostgresDriver = __esm({
|
|
|
44698
45250
|
Affirm_default(producer, "Invalid producer");
|
|
44699
45251
|
Affirm_default(scope, "Invalid executor scope");
|
|
44700
45252
|
const { table } = SqlQueryBuilder_default.resolveTable(producer, this._source);
|
|
44701
|
-
const
|
|
45253
|
+
const pushdown = await SqlPushdown_default.resolve({ driver: this, producer, source: this._source, plan: request.pushdown });
|
|
45254
|
+
const select = SqlQueryBuilder_default.buildProducerSelect(producer, this._source, pushdown);
|
|
44702
45255
|
const columns = SqlQueryBuilder_default.columnKeys(producer);
|
|
44703
45256
|
const localPath = ExecutorScope_default.getProducerPath(scope, producer, table);
|
|
44704
45257
|
ExecutorScope_default.ensurePath(localPath);
|
|
@@ -44714,7 +45267,7 @@ var init_PostgresDriver = __esm({
|
|
|
44714
45267
|
onProgress?.(1);
|
|
44715
45268
|
const fileSize = import_fs11.default.statSync(localPath).size;
|
|
44716
45269
|
Logger_default.log(`Postgres ready: staged ${rows} row(s) (${fileSize} bytes) for producer "${producer.name}" from table "${table}"`);
|
|
44717
|
-
return { files: [{ fullUri: localPath, fileSize }] };
|
|
45270
|
+
return { files: [{ fullUri: localPath, fileSize }], filteredAtSource: !!pushdown.where };
|
|
44718
45271
|
};
|
|
44719
45272
|
/**
|
|
44720
45273
|
* A bounded, ad-hoc read — the sample and discover paths, never a run. Capped at
|
|
@@ -44789,10 +45342,27 @@ var init_PostgresDriver = __esm({
|
|
|
44789
45342
|
try {
|
|
44790
45343
|
await client.connect();
|
|
44791
45344
|
} catch (error) {
|
|
44792
|
-
throw new Error(`Unable to connect to ${this.describeTarget()} for source "${this._source.name}": ${error
|
|
45345
|
+
throw new Error(`Unable to connect to ${this.describeTarget()} for source "${this._source.name}": ${this.describeConnectionError(error)}`);
|
|
44793
45346
|
}
|
|
44794
45347
|
return client;
|
|
44795
45348
|
};
|
|
45349
|
+
/**
|
|
45350
|
+
* Why a connection failed, in words. A refused or timed-out connection is the commonest thing to
|
|
45351
|
+
* go wrong with a SQL source and the one case where `error.message` is EMPTY: node resolves
|
|
45352
|
+
* localhost to both ::1 and 127.0.0.1, and when every address fails `pg` rejects with an
|
|
45353
|
+
* `AggregateError` whose message is blank and whose reasons are in `errors`. Reporting that
|
|
45354
|
+
* verbatim produced "Unable to connect to postgres://host:5432/db for source "x": " — the
|
|
45355
|
+
* message naming everything except the thing the operator needs. So the reason is taken from
|
|
45356
|
+
* whichever of the three carries one, and the address list is deduplicated because "refused on
|
|
45357
|
+
* ::1 and on 127.0.0.1" is one fact about one host, not two.
|
|
45358
|
+
*/
|
|
45359
|
+
this.describeConnectionError = (error) => {
|
|
45360
|
+
const failure = error;
|
|
45361
|
+
if (failure?.message) return failure.message;
|
|
45362
|
+
const reasons = [...new Set((failure?.errors ?? []).map((reason) => reason?.message).filter(Boolean))];
|
|
45363
|
+
if (reasons.length > 0) return `${failure.code ? `${failure.code}: ` : ""}${reasons.join("; ")}`;
|
|
45364
|
+
return failure?.code || "no reason reported by the driver";
|
|
45365
|
+
};
|
|
44796
45366
|
this.buildConfig = async () => {
|
|
44797
45367
|
const host = SecretManager_default.replaceSecret(this._auth.host);
|
|
44798
45368
|
const port = parseInt(SecretManager_default.replaceSecret(this._auth.port) || "5432");
|
|
@@ -45055,11 +45625,10 @@ var init_HttpApiDriver = __esm({
|
|
|
45055
45625
|
});
|
|
45056
45626
|
|
|
45057
45627
|
// ../../packages/drivers/src/DriverHelper.ts
|
|
45058
|
-
var import_stream2,
|
|
45628
|
+
var import_stream2, import_promises9, import_fs12, DriverHelper, DriverHelper_default;
|
|
45059
45629
|
var init_DriverHelper = __esm({
|
|
45060
45630
|
"../../packages/drivers/src/DriverHelper.ts"() {
|
|
45061
45631
|
import_stream2 = require("stream");
|
|
45062
|
-
import_readline = require("readline");
|
|
45063
45632
|
import_promises9 = require("stream/promises");
|
|
45064
45633
|
import_fs12 = require("fs");
|
|
45065
45634
|
init_src2();
|
|
@@ -45160,51 +45729,40 @@ var init_DriverHelper = __esm({
|
|
|
45160
45729
|
Affirm_default(options2, "Invalid options");
|
|
45161
45730
|
const { append, destinationPath, objects, delimiter } = options2;
|
|
45162
45731
|
const writeOptions = append ? { flags: "a" } : {};
|
|
45163
|
-
const
|
|
45164
|
-
const waitForDrain = () => new Promise((resolve) => writeStream.once("drain", resolve));
|
|
45732
|
+
const writer = new BatchWriter_default((0, import_fs12.createWriteStream)(destinationPath, writeOptions));
|
|
45165
45733
|
let lineCount = 0;
|
|
45166
45734
|
const keys = Object.keys(objects[0]);
|
|
45167
45735
|
for (const obj of objects) {
|
|
45168
|
-
|
|
45169
|
-
|
|
45170
|
-
await waitForDrain();
|
|
45736
|
+
if (!writer.write(keys.map((k7) => obj[k7]).join(delimiter)))
|
|
45737
|
+
await writer.waitForDrain();
|
|
45171
45738
|
lineCount++;
|
|
45172
45739
|
}
|
|
45173
|
-
|
|
45174
|
-
await new Promise((resolve, reject) => {
|
|
45175
|
-
writeStream.on("finish", resolve);
|
|
45176
|
-
writeStream.on("error", reject);
|
|
45177
|
-
});
|
|
45740
|
+
await writer.close();
|
|
45178
45741
|
return lineCount;
|
|
45179
45742
|
},
|
|
45180
45743
|
quickReadFile: async (filePath, lineCount) => {
|
|
45181
45744
|
const fileStream = (0, import_fs12.createReadStream)(filePath);
|
|
45182
|
-
const rl8 = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
|
|
45183
45745
|
const lines = [];
|
|
45184
45746
|
let counter = 0;
|
|
45185
|
-
for await (const line of
|
|
45747
|
+
for await (const line of readLines(fileStream)) {
|
|
45186
45748
|
lines.push(line);
|
|
45187
45749
|
counter++;
|
|
45188
45750
|
if (counter >= lineCount) {
|
|
45189
45751
|
break;
|
|
45190
45752
|
}
|
|
45191
45753
|
}
|
|
45192
|
-
rl8.close();
|
|
45193
|
-
fileStream.close();
|
|
45194
45754
|
return lines;
|
|
45195
45755
|
},
|
|
45196
45756
|
quickReadStream: async (stream, lineCount) => {
|
|
45197
|
-
const rl8 = (0, import_readline.createInterface)({ input: stream, crlfDelay: Infinity });
|
|
45198
45757
|
const lines = [];
|
|
45199
45758
|
let counter = 0;
|
|
45200
|
-
for await (const line of
|
|
45759
|
+
for await (const line of readLines(stream)) {
|
|
45201
45760
|
lines.push(line);
|
|
45202
45761
|
counter++;
|
|
45203
45762
|
if (counter >= lineCount) {
|
|
45204
45763
|
break;
|
|
45205
45764
|
}
|
|
45206
45765
|
}
|
|
45207
|
-
rl8.close();
|
|
45208
45766
|
return lines;
|
|
45209
45767
|
}
|
|
45210
45768
|
};
|
|
@@ -45213,12 +45771,11 @@ var init_DriverHelper = __esm({
|
|
|
45213
45771
|
});
|
|
45214
45772
|
|
|
45215
45773
|
// ../../packages/drivers/src/files/LocalSourceDriver.ts
|
|
45216
|
-
var fs13, import_path9,
|
|
45774
|
+
var fs13, import_path9, XLSX2, LocalSourceDriver;
|
|
45217
45775
|
var init_LocalSourceDriver = __esm({
|
|
45218
45776
|
"../../packages/drivers/src/files/LocalSourceDriver.ts"() {
|
|
45219
45777
|
fs13 = __toESM(require("fs"), 1);
|
|
45220
45778
|
import_path9 = __toESM(require("path"), 1);
|
|
45221
|
-
import_readline2 = __toESM(require("readline"), 1);
|
|
45222
45779
|
init_src();
|
|
45223
45780
|
init_src();
|
|
45224
45781
|
XLSX2 = __toESM(require("@e965/xlsx"), 1);
|
|
@@ -45293,10 +45850,9 @@ var init_LocalSourceDriver = __esm({
|
|
|
45293
45850
|
};
|
|
45294
45851
|
this._readLines = async (fileUri, lineFrom, lineTo) => {
|
|
45295
45852
|
const stream = fs13.createReadStream(fileUri);
|
|
45296
|
-
const reader = import_readline2.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
45297
45853
|
const lines = [];
|
|
45298
45854
|
let lineCounter = 0;
|
|
45299
|
-
for await (const line of
|
|
45855
|
+
for await (const line of readLines(stream)) {
|
|
45300
45856
|
if (Algo_default.hasVal(lineFrom) && Algo_default.hasVal(lineTo)) {
|
|
45301
45857
|
if (lineCounter >= lineFrom && lineCounter < lineTo) {
|
|
45302
45858
|
if (line && line.length > 0)
|
|
@@ -45309,8 +45865,6 @@ var init_LocalSourceDriver = __esm({
|
|
|
45309
45865
|
lines.push(line);
|
|
45310
45866
|
}
|
|
45311
45867
|
}
|
|
45312
|
-
reader.close();
|
|
45313
|
-
stream.close();
|
|
45314
45868
|
return lines;
|
|
45315
45869
|
};
|
|
45316
45870
|
this._readExcelLines = async (fileUri, sheetName, lineFrom, lineTo, startRow, startColumn) => {
|
|
@@ -45456,12 +46010,12 @@ var init_LocalSourceDriver = __esm({
|
|
|
45456
46010
|
});
|
|
45457
46011
|
|
|
45458
46012
|
// ../../packages/drivers/src/files/LocalDestinationDriver.ts
|
|
45459
|
-
var fs14,
|
|
46013
|
+
var fs14, import_crypto4, import_promises10, import_path10, LocalDestinationDriver;
|
|
45460
46014
|
var init_LocalDestinationDriver = __esm({
|
|
45461
46015
|
"../../packages/drivers/src/files/LocalDestinationDriver.ts"() {
|
|
45462
46016
|
fs14 = __toESM(require("fs"), 1);
|
|
46017
|
+
import_crypto4 = require("crypto");
|
|
45463
46018
|
import_promises10 = __toESM(require("fs/promises"), 1);
|
|
45464
|
-
import_readline3 = __toESM(require("readline"), 1);
|
|
45465
46019
|
import_path10 = __toESM(require("path"), 1);
|
|
45466
46020
|
init_src();
|
|
45467
46021
|
init_src2();
|
|
@@ -45492,37 +46046,57 @@ var init_LocalDestinationDriver = __esm({
|
|
|
45492
46046
|
const fileContent = await s3Driver.downloadFile(sourceFileKey);
|
|
45493
46047
|
await this.saveFile(destinationFileKey, fileContent);
|
|
45494
46048
|
};
|
|
46049
|
+
/**
|
|
46050
|
+
* A private path to build the export at, beside the file it will become.
|
|
46051
|
+
*
|
|
46052
|
+
* Two runs of one consumer share an export name — that is what a configured `exportName` means —
|
|
46053
|
+
* so they target the same path, and a run started by CRON while an API call is still exporting
|
|
46054
|
+
* would otherwise have both streams writing into one file at their own offsets. The result is a
|
|
46055
|
+
* file holding neither run: interleaved lines, and a torn line where they met.
|
|
46056
|
+
*
|
|
46057
|
+
* Building under a unique name and renaming into place makes the swap atomic, so concurrent runs
|
|
46058
|
+
* degrade to a clean last-writer-wins — one run's output, whole — instead of corruption. A
|
|
46059
|
+
* sibling of the destination on purpose: rename is only atomic within one filesystem.
|
|
46060
|
+
*/
|
|
46061
|
+
this._stagingPath = (toFilePath) => `${toFilePath}.${(0, import_crypto4.randomUUID)()}.partial`;
|
|
46062
|
+
/** Removes a staging file a failed export left behind; never masks the original error. */
|
|
46063
|
+
this._discardStaging = async (stagingPath) => {
|
|
46064
|
+
try {
|
|
46065
|
+
await import_promises10.default.unlink(stagingPath);
|
|
46066
|
+
} catch {
|
|
46067
|
+
}
|
|
46068
|
+
};
|
|
45495
46069
|
this.move = async (fromPath, toName) => {
|
|
46070
|
+
const toFilePath = import_path10.default.join(this._path, toName);
|
|
46071
|
+
const stagingPath = this._stagingPath(toFilePath);
|
|
45496
46072
|
try {
|
|
45497
|
-
|
|
45498
|
-
await import_promises10.default.
|
|
46073
|
+
await import_promises10.default.copyFile(fromPath, stagingPath);
|
|
46074
|
+
await import_promises10.default.rename(stagingPath, toFilePath);
|
|
45499
46075
|
await import_promises10.default.unlink(fromPath);
|
|
45500
46076
|
return { bucket: "", key: toFilePath, res: true };
|
|
45501
46077
|
} catch (error) {
|
|
45502
46078
|
Logger_default.error(error);
|
|
46079
|
+
await this._discardStaging(stagingPath);
|
|
45503
46080
|
return { bucket: "", key: "", res: false };
|
|
45504
46081
|
}
|
|
45505
46082
|
};
|
|
45506
46083
|
this.transformAndMove = async (fromPath, transform, toName) => {
|
|
46084
|
+
const toFilePath = import_path10.default.join(this._path, toName);
|
|
46085
|
+
const stagingPath = this._stagingPath(toFilePath);
|
|
45507
46086
|
try {
|
|
45508
|
-
const toFilePath = import_path10.default.join(this._path, toName);
|
|
45509
46087
|
const reader = fs14.createReadStream(fromPath);
|
|
45510
|
-
const
|
|
45511
|
-
const
|
|
45512
|
-
|
|
45513
|
-
|
|
45514
|
-
if (!writer.write(transform(line) + "\n"))
|
|
45515
|
-
await waitForDrain();
|
|
46088
|
+
const writer = new BatchWriter_default(fs14.createWriteStream(stagingPath));
|
|
46089
|
+
for await (const line of readLines(reader)) {
|
|
46090
|
+
if (!writer.write(transform(line)))
|
|
46091
|
+
await writer.waitForDrain();
|
|
45516
46092
|
}
|
|
45517
|
-
writer.
|
|
45518
|
-
await new Promise((resolve, reject) => {
|
|
45519
|
-
writer.on("finish", resolve);
|
|
45520
|
-
writer.on("error", reject);
|
|
45521
|
-
});
|
|
46093
|
+
await writer.close();
|
|
45522
46094
|
reader.close();
|
|
46095
|
+
await import_promises10.default.rename(stagingPath, toFilePath);
|
|
45523
46096
|
return { bucket: "", key: toFilePath, res: true };
|
|
45524
46097
|
} catch (error) {
|
|
45525
46098
|
Logger_default.error(error);
|
|
46099
|
+
await this._discardStaging(stagingPath);
|
|
45526
46100
|
return { bucket: "", key: "", res: false };
|
|
45527
46101
|
}
|
|
45528
46102
|
};
|
|
@@ -45531,89 +46105,24 @@ var init_LocalDestinationDriver = __esm({
|
|
|
45531
46105
|
}
|
|
45532
46106
|
});
|
|
45533
46107
|
|
|
45534
|
-
// ../../packages/drivers/src/
|
|
45535
|
-
var
|
|
45536
|
-
var
|
|
45537
|
-
"../../packages/drivers/src/
|
|
45538
|
-
import_client_s32 = require("@aws-sdk/client-s3");
|
|
46108
|
+
// ../../packages/drivers/src/StreamReader.ts
|
|
46109
|
+
var import_xlsx2, StreamReaderClass, StreamReader, StreamReader_default;
|
|
46110
|
+
var init_StreamReader = __esm({
|
|
46111
|
+
"../../packages/drivers/src/StreamReader.ts"() {
|
|
45539
46112
|
init_src();
|
|
45540
|
-
init_AwsCredentials();
|
|
45541
|
-
import_stream3 = require("stream");
|
|
45542
|
-
import_promises11 = require("stream/promises");
|
|
45543
|
-
import_readline4 = __toESM(require("readline"), 1);
|
|
45544
|
-
import_path11 = __toESM(require("path"), 1);
|
|
45545
|
-
import_fs13 = __toESM(require("fs"), 1);
|
|
45546
|
-
init_src();
|
|
45547
|
-
import_xlsx2 = __toESM(require("@e965/xlsx"), 1);
|
|
45548
46113
|
init_src5();
|
|
45549
|
-
|
|
45550
|
-
|
|
45551
|
-
init_src4();
|
|
45552
|
-
S3SourceDriver = class {
|
|
46114
|
+
import_xlsx2 = __toESM(require("@e965/xlsx"), 1);
|
|
46115
|
+
StreamReaderClass = class {
|
|
45553
46116
|
constructor() {
|
|
45554
|
-
|
|
45555
|
-
|
|
45556
|
-
|
|
45557
|
-
|
|
45558
|
-
|
|
45559
|
-
|
|
45560
|
-
this._client = new import_client_s32.S3Client(config);
|
|
45561
|
-
return this;
|
|
45562
|
-
};
|
|
45563
|
-
this.readAll = async (request) => {
|
|
45564
|
-
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
45565
|
-
Affirm_default(request, `Invalid download request`);
|
|
45566
|
-
Affirm_default(request.fileKey, `Invalid file key for download request`);
|
|
45567
|
-
const { fileKey } = request;
|
|
45568
|
-
if (fileKey.includes("%")) {
|
|
45569
|
-
const allFileKeys = await this.listFiles(fileKey);
|
|
45570
|
-
Affirm_default(allFileKeys.length < 50, `Pattern ${fileKey} of producer requested to S3 matches more than 50 files (${allFileKeys.length}), this is more than the S3 allowed limit. Please refine your pattern, remove some files or use a separate bucket.`);
|
|
45571
|
-
const promises2 = allFileKeys.map((x2, i6) => this._get({ ...request, fileKey: x2 }, i6));
|
|
45572
|
-
const results = await Promise.all(promises2);
|
|
45573
|
-
return results.flat();
|
|
45574
|
-
} else {
|
|
45575
|
-
return await this._get(request);
|
|
45576
|
-
}
|
|
45577
|
-
};
|
|
45578
|
-
this.readLinesInRange = async (request) => {
|
|
45579
|
-
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
45580
|
-
Affirm_default(request, "Invalid read request");
|
|
45581
|
-
Affirm_default(request.options, "Invalid read request options");
|
|
45582
|
-
const { fileKey } = request;
|
|
45583
|
-
if (fileKey.includes("%")) {
|
|
45584
|
-
const allFileKeys = await this.listFiles(fileKey);
|
|
45585
|
-
const promises2 = allFileKeys.map((x2, i6) => this._get({ ...request, fileKey: x2 }, i6));
|
|
45586
|
-
const results = await Promise.all(promises2);
|
|
45587
|
-
return results.flat();
|
|
45588
|
-
} else {
|
|
45589
|
-
return await this._get(request);
|
|
45590
|
-
}
|
|
45591
|
-
};
|
|
45592
|
-
this.exist = async (producer) => {
|
|
45593
|
-
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
45594
|
-
Affirm_default(producer, "Invalid read producer");
|
|
45595
|
-
const bucket = this._bucketName;
|
|
45596
|
-
const fileKey = producer.settings.fileKey;
|
|
45597
|
-
Affirm_default(fileKey, `Invalid file key for download request`);
|
|
45598
|
-
if (fileKey.includes("%")) {
|
|
45599
|
-
const allFileKeys = await this.listFiles(fileKey);
|
|
45600
|
-
return allFileKeys.length > 0;
|
|
45601
|
-
} else {
|
|
45602
|
-
try {
|
|
45603
|
-
await this._client.send(new import_client_s32.HeadObjectCommand({ Bucket: bucket, Key: fileKey }));
|
|
45604
|
-
return true;
|
|
45605
|
-
} catch (error) {
|
|
45606
|
-
if (error.$metadata?.httpStatusCode === 404 || error.name === "NotFound")
|
|
45607
|
-
return false;
|
|
45608
|
-
throw error;
|
|
45609
|
-
}
|
|
45610
|
-
}
|
|
45611
|
-
};
|
|
45612
|
-
this._readLines = async (stream, lineFrom, lineTo) => {
|
|
45613
|
-
const reader = import_readline4.default.createInterface({ input: stream, crlfDelay: Infinity });
|
|
46117
|
+
/**
|
|
46118
|
+
* Reads the stream as lines, optionally keeping only `[lineFrom, lineTo)`.
|
|
46119
|
+
* Empty lines are dropped, and the read stops as soon as the range is satisfied.
|
|
46120
|
+
*/
|
|
46121
|
+
this.readLines = async (stream, lineFrom, lineTo) => {
|
|
46122
|
+
Affirm_default(stream, "Invalid stream");
|
|
45614
46123
|
const lines = [];
|
|
45615
46124
|
let lineCounter = 0;
|
|
45616
|
-
for await (const line of
|
|
46125
|
+
for await (const line of readLines(stream)) {
|
|
45617
46126
|
if (Algo_default.hasVal(lineFrom) && Algo_default.hasVal(lineTo)) {
|
|
45618
46127
|
if (lineCounter >= lineFrom && lineCounter < lineTo) {
|
|
45619
46128
|
if (line && line.length > 0)
|
|
@@ -45626,10 +46135,11 @@ var init_S3SourceDriver = __esm({
|
|
|
45626
46135
|
lines.push(line);
|
|
45627
46136
|
}
|
|
45628
46137
|
}
|
|
45629
|
-
reader.close();
|
|
45630
46138
|
return lines;
|
|
45631
46139
|
};
|
|
45632
|
-
|
|
46140
|
+
/** Reads one sheet of a workbook as CSV lines. The whole workbook is buffered — XLSX has no streaming decode. */
|
|
46141
|
+
this.readExcelLines = async (stream, sheetName, lineFrom, lineTo, startRow, startColumn) => {
|
|
46142
|
+
Affirm_default(stream, "Invalid stream");
|
|
45633
46143
|
Affirm_default(sheetName, `Invalid sheetname`);
|
|
45634
46144
|
const chunks = [];
|
|
45635
46145
|
for await (const chunk of stream) {
|
|
@@ -45652,7 +46162,9 @@ var init_S3SourceDriver = __esm({
|
|
|
45652
46162
|
else
|
|
45653
46163
|
return lines;
|
|
45654
46164
|
};
|
|
45655
|
-
|
|
46165
|
+
/** Reads the stream as XML and flattens it to one JSON line per record. Buffered, like the Excel path. */
|
|
46166
|
+
this.readXmlLines = async (stream, lineFrom, lineTo) => {
|
|
46167
|
+
Affirm_default(stream, "Invalid stream");
|
|
45656
46168
|
const chunks = [];
|
|
45657
46169
|
for await (const chunk of stream) {
|
|
45658
46170
|
chunks.push(chunk);
|
|
@@ -45665,24 +46177,19 @@ var init_S3SourceDriver = __esm({
|
|
|
45665
46177
|
}
|
|
45666
46178
|
return lines;
|
|
45667
46179
|
};
|
|
45668
|
-
|
|
45669
|
-
|
|
45670
|
-
|
|
45671
|
-
|
|
45672
|
-
|
|
45673
|
-
|
|
45674
|
-
|
|
45675
|
-
|
|
45676
|
-
|
|
45677
|
-
|
|
45678
|
-
|
|
45679
|
-
}
|
|
45680
|
-
const
|
|
45681
|
-
Bucket: bucket,
|
|
45682
|
-
Key: fileKey
|
|
45683
|
-
}));
|
|
45684
|
-
Affirm_default(response.Body, "Failed to fetch object from S3");
|
|
45685
|
-
const stream = response.Body;
|
|
46180
|
+
/**
|
|
46181
|
+
* Reads a stream as the lines of `request.fileType`, honouring any line range in `request.options`.
|
|
46182
|
+
*
|
|
46183
|
+
* `index` is this file's position within a pattern match: anything but the first file drops its
|
|
46184
|
+
* header row, or the header would repeat in the middle of the unified dataset.
|
|
46185
|
+
*/
|
|
46186
|
+
this.read = async (stream, request, index) => {
|
|
46187
|
+
Affirm_default(stream, "Invalid stream");
|
|
46188
|
+
Affirm_default(request, "Invalid read request");
|
|
46189
|
+
Affirm_default(request.fileType, "Invalid file type for read request");
|
|
46190
|
+
const { fileType, options: options2 } = request;
|
|
46191
|
+
const { lineFrom, lineTo, sheetName, hasHeaderRow, startRow, startColumn } = options2 ?? {};
|
|
46192
|
+
const hasRange = Algo_default.hasVal(lineFrom) && Algo_default.hasVal(lineTo);
|
|
45686
46193
|
let lines = [];
|
|
45687
46194
|
switch (fileType) {
|
|
45688
46195
|
case "CSV":
|
|
@@ -45690,30 +46197,110 @@ var init_S3SourceDriver = __esm({
|
|
|
45690
46197
|
case "JSONL":
|
|
45691
46198
|
case "TXT":
|
|
45692
46199
|
case "FIXED":
|
|
45693
|
-
|
|
45694
|
-
lines = await this._readLines(stream, lineFrom, lineTo);
|
|
45695
|
-
else
|
|
45696
|
-
lines = await this._readLines(stream);
|
|
46200
|
+
lines = hasRange ? await this.readLines(stream, lineFrom, lineTo) : await this.readLines(stream);
|
|
45697
46201
|
break;
|
|
45698
46202
|
case "XLS":
|
|
45699
46203
|
case "XLSX":
|
|
45700
|
-
|
|
45701
|
-
lines = await this._readExcelLines(stream, sheetName, lineFrom, lineTo, startRow, startColumn);
|
|
45702
|
-
else
|
|
45703
|
-
lines = await this._readExcelLines(stream, sheetName, void 0, void 0, startRow, startColumn);
|
|
46204
|
+
lines = hasRange ? await this.readExcelLines(stream, sheetName, lineFrom, lineTo, startRow, startColumn) : await this.readExcelLines(stream, sheetName, void 0, void 0, startRow, startColumn);
|
|
45704
46205
|
break;
|
|
45705
46206
|
case "XML":
|
|
45706
|
-
|
|
45707
|
-
lines = await this._readXmlLines(stream, lineFrom, lineTo);
|
|
45708
|
-
else
|
|
45709
|
-
lines = await this._readXmlLines(stream);
|
|
46207
|
+
lines = hasRange ? await this.readXmlLines(stream, lineFrom, lineTo) : await this.readXmlLines(stream);
|
|
45710
46208
|
break;
|
|
45711
46209
|
}
|
|
45712
|
-
if (index > 0 && ParseHelper_default.shouldHaveHeader(fileType, hasHeaderRow))
|
|
46210
|
+
if (index > 0 && ParseHelper_default.shouldHaveHeader(fileType, hasHeaderRow))
|
|
45713
46211
|
lines = lines.slice(1);
|
|
45714
|
-
}
|
|
45715
46212
|
return lines;
|
|
45716
46213
|
};
|
|
46214
|
+
}
|
|
46215
|
+
};
|
|
46216
|
+
StreamReader = new StreamReaderClass();
|
|
46217
|
+
StreamReader_default = StreamReader;
|
|
46218
|
+
}
|
|
46219
|
+
});
|
|
46220
|
+
|
|
46221
|
+
// ../../packages/drivers/src/s3/S3SourceDriver.ts
|
|
46222
|
+
var import_client_s32, import_stream3, import_promises11, import_path11, import_fs13, S3SourceDriver;
|
|
46223
|
+
var init_S3SourceDriver = __esm({
|
|
46224
|
+
"../../packages/drivers/src/s3/S3SourceDriver.ts"() {
|
|
46225
|
+
import_client_s32 = require("@aws-sdk/client-s3");
|
|
46226
|
+
init_src();
|
|
46227
|
+
init_AwsCredentials();
|
|
46228
|
+
import_stream3 = require("stream");
|
|
46229
|
+
import_promises11 = require("stream/promises");
|
|
46230
|
+
import_path11 = __toESM(require("path"), 1);
|
|
46231
|
+
import_fs13 = __toESM(require("fs"), 1);
|
|
46232
|
+
init_src6();
|
|
46233
|
+
init_StreamReader();
|
|
46234
|
+
init_src4();
|
|
46235
|
+
S3SourceDriver = class {
|
|
46236
|
+
constructor() {
|
|
46237
|
+
this.init = async (source) => {
|
|
46238
|
+
this._bucketName = SecretManager_default.replaceSecret(source.authentication["bucket"]);
|
|
46239
|
+
const config = {
|
|
46240
|
+
region: SecretManager_default.replaceSecret(source.authentication["region"]),
|
|
46241
|
+
credentials: resolveAwsCredentials(source.authentication)
|
|
46242
|
+
};
|
|
46243
|
+
this._client = new import_client_s32.S3Client(config);
|
|
46244
|
+
return this;
|
|
46245
|
+
};
|
|
46246
|
+
this.readAll = async (request) => {
|
|
46247
|
+
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
46248
|
+
Affirm_default(request, `Invalid download request`);
|
|
46249
|
+
Affirm_default(request.fileKey, `Invalid file key for download request`);
|
|
46250
|
+
const { fileKey } = request;
|
|
46251
|
+
if (fileKey.includes("%")) {
|
|
46252
|
+
const allFileKeys = await this.listFiles(fileKey);
|
|
46253
|
+
Affirm_default(allFileKeys.length < 50, `Pattern ${fileKey} of producer requested to S3 matches more than 50 files (${allFileKeys.length}), this is more than the S3 allowed limit. Please refine your pattern, remove some files or use a separate bucket.`);
|
|
46254
|
+
const promises2 = allFileKeys.map((x2, i6) => this._get({ ...request, fileKey: x2 }, i6));
|
|
46255
|
+
const results = await Promise.all(promises2);
|
|
46256
|
+
return results.flat();
|
|
46257
|
+
} else {
|
|
46258
|
+
return await this._get(request);
|
|
46259
|
+
}
|
|
46260
|
+
};
|
|
46261
|
+
this.readLinesInRange = async (request) => {
|
|
46262
|
+
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
46263
|
+
Affirm_default(request, "Invalid read request");
|
|
46264
|
+
Affirm_default(request.options, "Invalid read request options");
|
|
46265
|
+
const { fileKey } = request;
|
|
46266
|
+
if (fileKey.includes("%")) {
|
|
46267
|
+
const allFileKeys = await this.listFiles(fileKey);
|
|
46268
|
+
const promises2 = allFileKeys.map((x2, i6) => this._get({ ...request, fileKey: x2 }, i6));
|
|
46269
|
+
const results = await Promise.all(promises2);
|
|
46270
|
+
return results.flat();
|
|
46271
|
+
} else {
|
|
46272
|
+
return await this._get(request);
|
|
46273
|
+
}
|
|
46274
|
+
};
|
|
46275
|
+
this.exist = async (producer) => {
|
|
46276
|
+
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
46277
|
+
Affirm_default(producer, "Invalid read producer");
|
|
46278
|
+
const bucket = this._bucketName;
|
|
46279
|
+
const fileKey = producer.settings.fileKey;
|
|
46280
|
+
Affirm_default(fileKey, `Invalid file key for download request`);
|
|
46281
|
+
if (fileKey.includes("%")) {
|
|
46282
|
+
const allFileKeys = await this.listFiles(fileKey);
|
|
46283
|
+
return allFileKeys.length > 0;
|
|
46284
|
+
} else {
|
|
46285
|
+
try {
|
|
46286
|
+
await this._client.send(new import_client_s32.HeadObjectCommand({ Bucket: bucket, Key: fileKey }));
|
|
46287
|
+
return true;
|
|
46288
|
+
} catch (error) {
|
|
46289
|
+
if (error.$metadata?.httpStatusCode === 404 || error.name === "NotFound")
|
|
46290
|
+
return false;
|
|
46291
|
+
throw error;
|
|
46292
|
+
}
|
|
46293
|
+
}
|
|
46294
|
+
};
|
|
46295
|
+
this._get = async (request, index) => {
|
|
46296
|
+
const { fileKey } = request;
|
|
46297
|
+
const response = await this._client.send(new import_client_s32.GetObjectCommand({
|
|
46298
|
+
Bucket: this._bucketName,
|
|
46299
|
+
Key: fileKey
|
|
46300
|
+
}));
|
|
46301
|
+
Affirm_default(response.Body, "Failed to fetch object from S3");
|
|
46302
|
+
return await StreamReader_default.read(response.Body, request, index);
|
|
46303
|
+
};
|
|
45717
46304
|
this._listFiles = async (fileKeyPattern, maxKeys, continuationToken) => {
|
|
45718
46305
|
Affirm_default(this._client, 'S3 client not yet initialized, call "connect()" first');
|
|
45719
46306
|
let prefix2 = "";
|
|
@@ -45843,22 +46430,22 @@ var init_S3SourceDriver = __esm({
|
|
|
45843
46430
|
});
|
|
45844
46431
|
|
|
45845
46432
|
// ../../packages/drivers/src/s3/S3DestinationDriver.ts
|
|
45846
|
-
var import_client_s33, import_path12, import_fs14,
|
|
46433
|
+
var import_client_s33, import_path12, import_fs14, S3DestinationDriver;
|
|
45847
46434
|
var init_S3DestinationDriver = __esm({
|
|
45848
46435
|
"../../packages/drivers/src/s3/S3DestinationDriver.ts"() {
|
|
45849
46436
|
import_client_s33 = require("@aws-sdk/client-s3");
|
|
45850
46437
|
init_src();
|
|
45851
46438
|
init_src2();
|
|
46439
|
+
init_src4();
|
|
45852
46440
|
init_AwsCredentials();
|
|
45853
46441
|
import_path12 = __toESM(require("path"), 1);
|
|
45854
46442
|
import_fs14 = __toESM(require("fs"), 1);
|
|
45855
|
-
import_readline5 = __toESM(require("readline"), 1);
|
|
45856
46443
|
S3DestinationDriver = class {
|
|
45857
46444
|
constructor() {
|
|
45858
46445
|
this.init = async (source) => {
|
|
45859
|
-
this._bucketName = source.authentication["bucket"];
|
|
46446
|
+
this._bucketName = SecretManager_default.replaceSecret(source.authentication["bucket"]);
|
|
45860
46447
|
const config = {
|
|
45861
|
-
region: source.authentication["region"],
|
|
46448
|
+
region: SecretManager_default.replaceSecret(source.authentication["region"]),
|
|
45862
46449
|
credentials: resolveAwsCredentials(source.authentication)
|
|
45863
46450
|
};
|
|
45864
46451
|
this._client = new import_client_s33.S3Client(config);
|
|
@@ -45898,9 +46485,8 @@ var init_S3DestinationDriver = __esm({
|
|
|
45898
46485
|
Affirm_default(toName, "Invalid destination name");
|
|
45899
46486
|
Affirm_default(import_fs14.default.existsSync(fromPath), `Source file does not exist: ${fromPath}`);
|
|
45900
46487
|
const reader = import_fs14.default.createReadStream(fromPath);
|
|
45901
|
-
const lineReader = import_readline5.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
45902
46488
|
const transformedLines = async function* () {
|
|
45903
|
-
for await (const line of
|
|
46489
|
+
for await (const line of readLines(reader)) {
|
|
45904
46490
|
yield transform(line) + "\n";
|
|
45905
46491
|
}
|
|
45906
46492
|
};
|
|
@@ -45983,6 +46569,382 @@ var init_S3DestinationDriver = __esm({
|
|
|
45983
46569
|
}
|
|
45984
46570
|
});
|
|
45985
46571
|
|
|
46572
|
+
// ../../packages/drivers/src/presigned/UrlMinter.ts
|
|
46573
|
+
var DEFAULT_TIMEOUT_MS, DEFAULT_URL_FIELD, DEFAULT_FILE_NAME_PARAM, MAX_ERROR_BODY_CHARS, UrlMinterClass, UrlMinter, UrlMinter_default;
|
|
46574
|
+
var init_UrlMinter = __esm({
|
|
46575
|
+
"../../packages/drivers/src/presigned/UrlMinter.ts"() {
|
|
46576
|
+
init_src();
|
|
46577
|
+
init_src4();
|
|
46578
|
+
init_src2();
|
|
46579
|
+
init_OidcTokenProvider();
|
|
46580
|
+
DEFAULT_TIMEOUT_MS = 3e4;
|
|
46581
|
+
DEFAULT_URL_FIELD = "url";
|
|
46582
|
+
DEFAULT_FILE_NAME_PARAM = "filename";
|
|
46583
|
+
MAX_ERROR_BODY_CHARS = 500;
|
|
46584
|
+
UrlMinterClass = class {
|
|
46585
|
+
constructor() {
|
|
46586
|
+
/**
|
|
46587
|
+
* Mints a signed URL for one file name.
|
|
46588
|
+
* Throws with the endpoint's own answer when it refuses — a mint that fails is a transfer that
|
|
46589
|
+
* must not be attempted.
|
|
46590
|
+
*/
|
|
46591
|
+
this.mint = async (auth, fileName) => {
|
|
46592
|
+
Affirm_default(auth, "Missing authentication config for the presigned-url source");
|
|
46593
|
+
Affirm_default(fileName, "Missing the file name to mint a signed URL for");
|
|
46594
|
+
const endpoint = SecretManager_default.replaceSecret(auth.url);
|
|
46595
|
+
Affirm_default(endpoint, "A presigned-url source requires its minting endpoint in authentication.url");
|
|
46596
|
+
const payload = await this._request(auth, endpoint, fileName);
|
|
46597
|
+
const urlField = auth.signedUrlField || DEFAULT_URL_FIELD;
|
|
46598
|
+
const url = this._resolve(payload, urlField);
|
|
46599
|
+
Affirm_default(url, `The minting endpoint ${this.safeUrl(endpoint)} returned no signed URL at "${urlField}". Set authentication.signedUrlField to where the URL is in its response.`);
|
|
46600
|
+
Affirm_default(typeof url === "string", `The minting endpoint returned a ${typeof url} at "${urlField}", not a URL string`);
|
|
46601
|
+
const headers = auth.signedHeadersField ? this._resolve(payload, auth.signedHeadersField) : void 0;
|
|
46602
|
+
Affirm_default(!headers || typeof headers === "object", `The minting endpoint returned a ${typeof headers} at "${auth.signedHeadersField}", not an object of headers`);
|
|
46603
|
+
Logger_default.log(`Minted a signed URL for "${fileName}" from ${this.safeUrl(endpoint)}: ${this.safeUrl(url)}`);
|
|
46604
|
+
return { url, headers: headers ?? {} };
|
|
46605
|
+
};
|
|
46606
|
+
/**
|
|
46607
|
+
* A URL with its query string removed, for logging. A signed URL carries its credentials there,
|
|
46608
|
+
* so logging one whole would put a working upload token in the log pipeline — the same rule
|
|
46609
|
+
* `StateEmitter` applies to every URL it emits.
|
|
46610
|
+
*/
|
|
46611
|
+
this.safeUrl = (url) => {
|
|
46612
|
+
try {
|
|
46613
|
+
const parsed = new URL(url);
|
|
46614
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
46615
|
+
} catch {
|
|
46616
|
+
return "(unparseable URL)";
|
|
46617
|
+
}
|
|
46618
|
+
};
|
|
46619
|
+
this._request = async (auth, endpoint, fileName) => {
|
|
46620
|
+
const method = auth.httpMethod || "POST";
|
|
46621
|
+
const headers = await this._authHeaders(auth);
|
|
46622
|
+
const fileNameParam = auth.fileNameParam || DEFAULT_FILE_NAME_PARAM;
|
|
46623
|
+
const url = new URL(endpoint);
|
|
46624
|
+
for (const [key, value] of Object.entries(auth.queryParams ?? {}))
|
|
46625
|
+
url.searchParams.append(key, SecretManager_default.replaceSecret(value));
|
|
46626
|
+
const options2 = {
|
|
46627
|
+
method,
|
|
46628
|
+
headers,
|
|
46629
|
+
signal: AbortSignal.timeout(auth.timeout || DEFAULT_TIMEOUT_MS)
|
|
46630
|
+
};
|
|
46631
|
+
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
46632
|
+
headers["Content-Type"] = headers["Content-Type"] || "application/json";
|
|
46633
|
+
options2.body = JSON.stringify({ [fileNameParam]: fileName });
|
|
46634
|
+
} else {
|
|
46635
|
+
url.searchParams.append(fileNameParam, fileName);
|
|
46636
|
+
}
|
|
46637
|
+
const response = await fetch(url.toString(), options2);
|
|
46638
|
+
if (!response.ok) {
|
|
46639
|
+
const body = await response.text().catch(() => "");
|
|
46640
|
+
const detail = body ? `: ${body.slice(0, MAX_ERROR_BODY_CHARS)}` : "";
|
|
46641
|
+
throw new Error(`The minting endpoint ${this.safeUrl(endpoint)} answered HTTP ${response.status} ${response.statusText} for "${fileName}"${detail}`);
|
|
46642
|
+
}
|
|
46643
|
+
return await response.json();
|
|
46644
|
+
};
|
|
46645
|
+
/**
|
|
46646
|
+
* The credentials for the MINT call. Every method here already exists elsewhere in the codebase —
|
|
46647
|
+
* `oidc` is the same client-credentials provider delta-share uses, which is what an API Gateway
|
|
46648
|
+
* fronted by M2M authentication wants, and its token is cached and refreshed across runs.
|
|
46649
|
+
*/
|
|
46650
|
+
this._authHeaders = async (auth) => {
|
|
46651
|
+
const headers = {};
|
|
46652
|
+
for (const [key, value] of Object.entries(auth.headers ?? {}))
|
|
46653
|
+
headers[key] = SecretManager_default.replaceSecret(value);
|
|
46654
|
+
switch (auth.method) {
|
|
46655
|
+
case "oidc": {
|
|
46656
|
+
headers["Authorization"] = `Bearer ${await OidcTokenProvider_default.getToken(this._oidcConfig(auth))}`;
|
|
46657
|
+
break;
|
|
46658
|
+
}
|
|
46659
|
+
case "bearer-token": {
|
|
46660
|
+
const token2 = SecretManager_default.replaceSecret(auth.bearerToken);
|
|
46661
|
+
Affirm_default(token2, 'Authentication method "bearer-token" requires authentication.bearerToken');
|
|
46662
|
+
headers["Authorization"] = `Bearer ${token2}`;
|
|
46663
|
+
break;
|
|
46664
|
+
}
|
|
46665
|
+
case "api-key": {
|
|
46666
|
+
const apiKey = SecretManager_default.replaceSecret(auth.apiKey);
|
|
46667
|
+
Affirm_default(apiKey, 'Authentication method "api-key" requires authentication.apiKey');
|
|
46668
|
+
headers[auth.apiKeyHeader || "X-API-Key"] = apiKey;
|
|
46669
|
+
break;
|
|
46670
|
+
}
|
|
46671
|
+
case "username-password": {
|
|
46672
|
+
const user = SecretManager_default.replaceSecret(auth.user);
|
|
46673
|
+
const password = SecretManager_default.replaceSecret(auth.password);
|
|
46674
|
+
Affirm_default(user, 'Authentication method "username-password" requires authentication.user');
|
|
46675
|
+
Affirm_default(password, 'Authentication method "username-password" requires authentication.password');
|
|
46676
|
+
headers["Authorization"] = `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
|
|
46677
|
+
break;
|
|
46678
|
+
}
|
|
46679
|
+
case "none":
|
|
46680
|
+
break;
|
|
46681
|
+
default:
|
|
46682
|
+
throw new Error(`Authentication method "${auth.method}" is not supported for a presigned-url source. Use oidc, bearer-token, api-key, username-password or none.`);
|
|
46683
|
+
}
|
|
46684
|
+
return headers;
|
|
46685
|
+
};
|
|
46686
|
+
this._oidcConfig = (auth) => {
|
|
46687
|
+
const config = {
|
|
46688
|
+
tokenEndpoint: SecretManager_default.replaceSecret(auth.tokenEndpoint),
|
|
46689
|
+
clientId: SecretManager_default.replaceSecret(auth.clientId),
|
|
46690
|
+
clientSecret: SecretManager_default.replaceSecret(auth.clientSecret),
|
|
46691
|
+
scope: SecretManager_default.replaceSecret(auth.scope),
|
|
46692
|
+
audience: SecretManager_default.replaceSecret(auth.audience),
|
|
46693
|
+
clientAuthMethod: auth.clientAuthMethod
|
|
46694
|
+
};
|
|
46695
|
+
Affirm_default(config.tokenEndpoint, 'Authentication method "oidc" requires authentication.tokenEndpoint');
|
|
46696
|
+
Affirm_default(config.clientId, 'Authentication method "oidc" requires authentication.clientId');
|
|
46697
|
+
Affirm_default(config.clientSecret, 'Authentication method "oidc" requires authentication.clientSecret');
|
|
46698
|
+
return config;
|
|
46699
|
+
};
|
|
46700
|
+
/** Walks a dot path (`data.uploadUrl`) into the response, answering undefined rather than throwing. */
|
|
46701
|
+
this._resolve = (payload, path20) => {
|
|
46702
|
+
let current = payload;
|
|
46703
|
+
for (const segment of path20.split(".")) {
|
|
46704
|
+
if (current === null || typeof current !== "object")
|
|
46705
|
+
return void 0;
|
|
46706
|
+
current = current[segment];
|
|
46707
|
+
}
|
|
46708
|
+
return current;
|
|
46709
|
+
};
|
|
46710
|
+
}
|
|
46711
|
+
};
|
|
46712
|
+
UrlMinter = new UrlMinterClass();
|
|
46713
|
+
UrlMinter_default = UrlMinter;
|
|
46714
|
+
}
|
|
46715
|
+
});
|
|
46716
|
+
|
|
46717
|
+
// ../../packages/drivers/src/presigned/PresignedUrlSourceDriver.ts
|
|
46718
|
+
var import_stream4, import_promises12, import_fs15, MAX_ERROR_BODY_CHARS2, PresignedUrlSourceDriver;
|
|
46719
|
+
var init_PresignedUrlSourceDriver = __esm({
|
|
46720
|
+
"../../packages/drivers/src/presigned/PresignedUrlSourceDriver.ts"() {
|
|
46721
|
+
init_src();
|
|
46722
|
+
init_src4();
|
|
46723
|
+
init_src2();
|
|
46724
|
+
import_stream4 = require("stream");
|
|
46725
|
+
import_promises12 = require("stream/promises");
|
|
46726
|
+
import_fs15 = __toESM(require("fs"), 1);
|
|
46727
|
+
init_StreamReader();
|
|
46728
|
+
init_UrlMinter();
|
|
46729
|
+
MAX_ERROR_BODY_CHARS2 = 500;
|
|
46730
|
+
PresignedUrlSourceDriver = class {
|
|
46731
|
+
constructor() {
|
|
46732
|
+
this.init = async (source) => {
|
|
46733
|
+
Affirm_default(source, "Invalid source");
|
|
46734
|
+
Affirm_default(source.authentication, `Missing authentication for source "${source.name}"`);
|
|
46735
|
+
Affirm_default(source.authentication.url, `Source "${source.name}" is a presigned-url source and needs its minting endpoint in authentication.url`);
|
|
46736
|
+
this._source = source;
|
|
46737
|
+
this._auth = source.authentication;
|
|
46738
|
+
Logger_default.log(`Presigned URL source "${source.name}" initialised: minting endpoint=${UrlMinter_default.safeUrl(SecretManager_default.replaceSecret(source.authentication.url))}`);
|
|
46739
|
+
return this;
|
|
46740
|
+
};
|
|
46741
|
+
this.execute = async (_sql) => {
|
|
46742
|
+
void _sql;
|
|
46743
|
+
throw new Error("PresignedUrlSourceDriver.execute is not supported: a signed URL addresses a file, not a SQL engine");
|
|
46744
|
+
};
|
|
46745
|
+
this.query = async (_sql, _values) => {
|
|
46746
|
+
void _sql;
|
|
46747
|
+
void _values;
|
|
46748
|
+
throw new Error("PresignedUrlSourceDriver.query is not supported: a signed URL addresses a file, not a SQL engine");
|
|
46749
|
+
};
|
|
46750
|
+
this.exist = async (producer) => {
|
|
46751
|
+
Affirm_default(producer, "Invalid producer");
|
|
46752
|
+
const fileKey = producer.settings?.fileKey;
|
|
46753
|
+
Affirm_default(fileKey, `Producer "${producer.name}" has no settings.fileKey to mint a signed URL for`);
|
|
46754
|
+
try {
|
|
46755
|
+
const minted = await UrlMinter_default.mint(this._auth, fileKey);
|
|
46756
|
+
const response = await fetch(minted.url, {
|
|
46757
|
+
method: "GET",
|
|
46758
|
+
headers: { ...minted.headers, range: "bytes=0-0" }
|
|
46759
|
+
});
|
|
46760
|
+
await response.body?.cancel();
|
|
46761
|
+
return response.ok;
|
|
46762
|
+
} catch (error) {
|
|
46763
|
+
Logger_default.log(`Existence check for "${fileKey}" on source "${this._source.name}" answered no: ${error}`);
|
|
46764
|
+
return false;
|
|
46765
|
+
}
|
|
46766
|
+
};
|
|
46767
|
+
this.readAll = async (request) => {
|
|
46768
|
+
Affirm_default(request, "Invalid download request");
|
|
46769
|
+
Affirm_default(request.fileKey, "Invalid file key for download request");
|
|
46770
|
+
Affirm_default(request.fileType, "Invalid file type for download request");
|
|
46771
|
+
return await this._read(request);
|
|
46772
|
+
};
|
|
46773
|
+
this.readLinesInRange = async (request) => {
|
|
46774
|
+
Affirm_default(request, "Invalid read request");
|
|
46775
|
+
Affirm_default(request.fileKey, "Invalid file key for read request");
|
|
46776
|
+
Affirm_default(request.fileType, "Invalid file type for read request");
|
|
46777
|
+
Affirm_default(request.options, "Invalid read request options");
|
|
46778
|
+
Affirm_default.hasValue(request.options.lineFrom, "Invalid read request option lineFrom");
|
|
46779
|
+
Affirm_default.hasValue(request.options.lineTo, "Invalid read request option lineTo");
|
|
46780
|
+
return await this._read(request);
|
|
46781
|
+
};
|
|
46782
|
+
/**
|
|
46783
|
+
* Stages the producer's file locally, which is all a run needs from this source: everything after
|
|
46784
|
+
* it reads the staged path and never learns where the bytes came from.
|
|
46785
|
+
*/
|
|
46786
|
+
this.ready = async (request) => {
|
|
46787
|
+
Affirm_default(request, "Invalid ready request");
|
|
46788
|
+
Affirm_default(request.producer, "Invalid producer");
|
|
46789
|
+
Affirm_default(request.scope, "Invalid executor scope");
|
|
46790
|
+
const { producer, scope, onProgress } = request;
|
|
46791
|
+
const fileKey = producer.settings?.fileKey;
|
|
46792
|
+
Affirm_default(fileKey, `Producer "${producer.name}" has no settings.fileKey to mint a signed URL for`);
|
|
46793
|
+
this._affirmSingleFile(fileKey);
|
|
46794
|
+
const localPath = ExecutorScope_default.getProducerPath(scope, producer, fileKey);
|
|
46795
|
+
ExecutorScope_default.ensurePath(localPath);
|
|
46796
|
+
const minted = await UrlMinter_default.mint(this._auth, fileKey);
|
|
46797
|
+
const response = await this._fetchSigned(minted, fileKey);
|
|
46798
|
+
const totalBytes = Number(response.headers.get("content-length") ?? 0);
|
|
46799
|
+
let stagedBytes = 0;
|
|
46800
|
+
const meter = new import_stream4.Transform({
|
|
46801
|
+
transform(chunk, _encoding, done) {
|
|
46802
|
+
stagedBytes += chunk.length;
|
|
46803
|
+
if (totalBytes > 0)
|
|
46804
|
+
onProgress?.(Math.min(1, stagedBytes / totalBytes));
|
|
46805
|
+
done(null, chunk);
|
|
46806
|
+
}
|
|
46807
|
+
});
|
|
46808
|
+
await (0, import_promises12.pipeline)(import_stream4.Readable.fromWeb(response.body), meter, import_fs15.default.createWriteStream(localPath));
|
|
46809
|
+
onProgress?.(1);
|
|
46810
|
+
const fileSize = import_fs15.default.statSync(localPath).size;
|
|
46811
|
+
Logger_default.info(`Staged "${fileKey}" from a signed URL to ${localPath} (${fileSize} bytes)`);
|
|
46812
|
+
return { files: [{ fullUri: localPath, fileSize }] };
|
|
46813
|
+
};
|
|
46814
|
+
this._read = async (request) => {
|
|
46815
|
+
this._affirmSingleFile(request.fileKey);
|
|
46816
|
+
const minted = await UrlMinter_default.mint(this._auth, request.fileKey);
|
|
46817
|
+
const response = await this._fetchSigned(minted, request.fileKey);
|
|
46818
|
+
return await StreamReader_default.read(import_stream4.Readable.fromWeb(response.body), request);
|
|
46819
|
+
};
|
|
46820
|
+
/**
|
|
46821
|
+
* A minting endpoint signs one named object, so there is nothing for a `%` pattern to expand
|
|
46822
|
+
* against — no listing exists on this side. Refused here rather than silently staging the one
|
|
46823
|
+
* file whose name happens to contain the pattern.
|
|
46824
|
+
*/
|
|
46825
|
+
this._affirmSingleFile = (fileKey) => {
|
|
46826
|
+
Affirm_default(
|
|
46827
|
+
!fileKey.includes("%"),
|
|
46828
|
+
`A presigned-url source cannot expand the file key pattern "${fileKey}": the minting endpoint signs one named file and this driver cannot list the others. Name the file exactly, or use a source that can list (aws-s3, local).`
|
|
46829
|
+
);
|
|
46830
|
+
};
|
|
46831
|
+
this._fetchSigned = async (minted, fileKey) => {
|
|
46832
|
+
const response = await fetch(minted.url, {
|
|
46833
|
+
method: this._auth.signedUrlMethod || "GET",
|
|
46834
|
+
headers: minted.headers
|
|
46835
|
+
});
|
|
46836
|
+
if (!response.ok) {
|
|
46837
|
+
const body = await response.text().catch(() => "");
|
|
46838
|
+
const detail = body ? `: ${body.slice(0, MAX_ERROR_BODY_CHARS2)}` : "";
|
|
46839
|
+
throw new Error(`The signed URL for "${fileKey}" answered HTTP ${response.status} ${response.statusText}${detail}`);
|
|
46840
|
+
}
|
|
46841
|
+
Affirm_default(response.body, `The signed URL for "${fileKey}" returned no body`);
|
|
46842
|
+
return response;
|
|
46843
|
+
};
|
|
46844
|
+
}
|
|
46845
|
+
};
|
|
46846
|
+
}
|
|
46847
|
+
});
|
|
46848
|
+
|
|
46849
|
+
// ../../packages/drivers/src/presigned/PresignedUrlDestinationDriver.ts
|
|
46850
|
+
var import_stream5, import_crypto5, import_promises13, import_fs16, MAX_ERROR_BODY_CHARS3, PresignedUrlDestinationDriver;
|
|
46851
|
+
var init_PresignedUrlDestinationDriver = __esm({
|
|
46852
|
+
"../../packages/drivers/src/presigned/PresignedUrlDestinationDriver.ts"() {
|
|
46853
|
+
init_src();
|
|
46854
|
+
init_src4();
|
|
46855
|
+
init_src2();
|
|
46856
|
+
import_stream5 = require("stream");
|
|
46857
|
+
import_crypto5 = require("crypto");
|
|
46858
|
+
import_promises13 = __toESM(require("fs/promises"), 1);
|
|
46859
|
+
import_fs16 = __toESM(require("fs"), 1);
|
|
46860
|
+
init_UrlMinter();
|
|
46861
|
+
MAX_ERROR_BODY_CHARS3 = 500;
|
|
46862
|
+
PresignedUrlDestinationDriver = class {
|
|
46863
|
+
constructor() {
|
|
46864
|
+
this.init = async (source) => {
|
|
46865
|
+
Affirm_default(source, "Invalid source");
|
|
46866
|
+
Affirm_default(source.authentication, `Missing authentication for source "${source.name}"`);
|
|
46867
|
+
Affirm_default(source.authentication.url, `Source "${source.name}" is a presigned-url destination and needs its minting endpoint in authentication.url`);
|
|
46868
|
+
this._source = source;
|
|
46869
|
+
this._auth = source.authentication;
|
|
46870
|
+
Logger_default.log(`Presigned URL destination "${source.name}" initialised: minting endpoint=${UrlMinter_default.safeUrl(SecretManager_default.replaceSecret(source.authentication.url))}`);
|
|
46871
|
+
return this;
|
|
46872
|
+
};
|
|
46873
|
+
this.move = async (fromPath, toName) => {
|
|
46874
|
+
Affirm_default(fromPath, "Invalid source path");
|
|
46875
|
+
Affirm_default(toName, "Invalid destination name");
|
|
46876
|
+
Affirm_default(import_fs16.default.existsSync(fromPath), `Source file does not exist: ${fromPath}`);
|
|
46877
|
+
try {
|
|
46878
|
+
return await this._upload(fromPath, toName);
|
|
46879
|
+
} catch (error) {
|
|
46880
|
+
Logger_default.error(`Presigned upload of "${toName}" from source "${this._source.name}" failed: ${error}`);
|
|
46881
|
+
return { res: false, key: "", bucket: "" };
|
|
46882
|
+
}
|
|
46883
|
+
};
|
|
46884
|
+
this.transformAndMove = async (fromPath, transform, toName) => {
|
|
46885
|
+
Affirm_default(fromPath, "Invalid source path");
|
|
46886
|
+
Affirm_default(transform, "Invalid transform function");
|
|
46887
|
+
Affirm_default(toName, "Invalid destination name");
|
|
46888
|
+
Affirm_default(import_fs16.default.existsSync(fromPath), `Source file does not exist: ${fromPath}`);
|
|
46889
|
+
const stagingPath = `${fromPath}.${(0, import_crypto5.randomUUID)()}.partial`;
|
|
46890
|
+
try {
|
|
46891
|
+
await this._stageTransformed(fromPath, transform, stagingPath);
|
|
46892
|
+
return await this._upload(stagingPath, toName);
|
|
46893
|
+
} catch (error) {
|
|
46894
|
+
Logger_default.error(`Presigned upload of "${toName}" from source "${this._source.name}" failed: ${error}`);
|
|
46895
|
+
return { res: false, key: "", bucket: "" };
|
|
46896
|
+
} finally {
|
|
46897
|
+
await this._discardStaging(stagingPath);
|
|
46898
|
+
}
|
|
46899
|
+
};
|
|
46900
|
+
this._upload = async (fromPath, toName) => {
|
|
46901
|
+
const minted = await UrlMinter_default.mint(this._auth, toName);
|
|
46902
|
+
const fileSize = import_fs16.default.statSync(fromPath).size;
|
|
46903
|
+
const options2 = {
|
|
46904
|
+
method: this._auth.signedUrlMethod || "PUT",
|
|
46905
|
+
headers: { ...minted.headers, "content-length": String(fileSize) },
|
|
46906
|
+
body: import_stream5.Readable.toWeb(import_fs16.default.createReadStream(fromPath)),
|
|
46907
|
+
duplex: "half"
|
|
46908
|
+
};
|
|
46909
|
+
const response = await fetch(minted.url, options2);
|
|
46910
|
+
if (!response.ok) {
|
|
46911
|
+
const body = await response.text().catch(() => "");
|
|
46912
|
+
const detail = body ? `: ${body.slice(0, MAX_ERROR_BODY_CHARS3)}` : "";
|
|
46913
|
+
throw new Error(`The signed URL for "${toName}" answered HTTP ${response.status} ${response.statusText}${detail}`);
|
|
46914
|
+
}
|
|
46915
|
+
Logger_default.info(`Uploaded "${toName}" (${fileSize} bytes) to ${UrlMinter_default.safeUrl(minted.url)}`);
|
|
46916
|
+
return { res: true, key: toName, bucket: this._host(minted.url) };
|
|
46917
|
+
};
|
|
46918
|
+
/** Writes the transformed lines to their own file, so the upload has a size to declare. */
|
|
46919
|
+
this._stageTransformed = async (fromPath, transform, stagingPath) => {
|
|
46920
|
+
const reader = import_fs16.default.createReadStream(fromPath);
|
|
46921
|
+
const writer = new BatchWriter_default(import_fs16.default.createWriteStream(stagingPath));
|
|
46922
|
+
for await (const line of readLines(reader)) {
|
|
46923
|
+
if (!writer.write(transform(line)))
|
|
46924
|
+
await writer.waitForDrain();
|
|
46925
|
+
}
|
|
46926
|
+
await writer.close();
|
|
46927
|
+
reader.close();
|
|
46928
|
+
};
|
|
46929
|
+
/** Removes a staging file the export left behind; never masks the error that is being reported. */
|
|
46930
|
+
this._discardStaging = async (stagingPath) => {
|
|
46931
|
+
try {
|
|
46932
|
+
await import_promises13.default.unlink(stagingPath);
|
|
46933
|
+
} catch {
|
|
46934
|
+
}
|
|
46935
|
+
};
|
|
46936
|
+
this._host = (url) => {
|
|
46937
|
+
try {
|
|
46938
|
+
return new URL(url).host;
|
|
46939
|
+
} catch {
|
|
46940
|
+
return "";
|
|
46941
|
+
}
|
|
46942
|
+
};
|
|
46943
|
+
}
|
|
46944
|
+
};
|
|
46945
|
+
}
|
|
46946
|
+
});
|
|
46947
|
+
|
|
45986
46948
|
// ../../packages/drivers/src/DriverFactory.ts
|
|
45987
46949
|
var STAGEABLE_ENGINES, SQL_ENGINES, DriverFactoryClass, DriverFactory, DriverFactory_default;
|
|
45988
46950
|
var init_DriverFactory = __esm({
|
|
@@ -45995,7 +46957,9 @@ var init_DriverFactory = __esm({
|
|
|
45995
46957
|
init_LocalDestinationDriver();
|
|
45996
46958
|
init_S3SourceDriver();
|
|
45997
46959
|
init_S3DestinationDriver();
|
|
45998
|
-
|
|
46960
|
+
init_PresignedUrlSourceDriver();
|
|
46961
|
+
init_PresignedUrlDestinationDriver();
|
|
46962
|
+
STAGEABLE_ENGINES = ["local", "aws-s3", "delta-share", "aws-redshift", "postgres", "presigned-url"];
|
|
45999
46963
|
SQL_ENGINES = ["aws-redshift", "postgres"];
|
|
46000
46964
|
DriverFactoryClass = class {
|
|
46001
46965
|
constructor() {
|
|
@@ -46035,6 +46999,11 @@ var init_DriverFactory = __esm({
|
|
|
46035
46999
|
await driver.init(source);
|
|
46036
47000
|
return driver;
|
|
46037
47001
|
}
|
|
47002
|
+
case "presigned-url": {
|
|
47003
|
+
const driver = new PresignedUrlSourceDriver();
|
|
47004
|
+
await driver.init(source);
|
|
47005
|
+
return driver;
|
|
47006
|
+
}
|
|
46038
47007
|
default:
|
|
46039
47008
|
throw new Error(`Invalid driver type "${source.engine}". This driver is not implemented yet`);
|
|
46040
47009
|
}
|
|
@@ -46046,6 +47015,11 @@ var init_DriverFactory = __esm({
|
|
|
46046
47015
|
await driver.init(source);
|
|
46047
47016
|
return driver;
|
|
46048
47017
|
}
|
|
47018
|
+
case "presigned-url": {
|
|
47019
|
+
const driver = new PresignedUrlDestinationDriver();
|
|
47020
|
+
await driver.init(source);
|
|
47021
|
+
return driver;
|
|
47022
|
+
}
|
|
46049
47023
|
case "local": {
|
|
46050
47024
|
const driver = new LocalDestinationDriver();
|
|
46051
47025
|
await driver.init(source);
|
|
@@ -46076,9 +47050,16 @@ var init_src8 = __esm({
|
|
|
46076
47050
|
init_LocalSourceDriver();
|
|
46077
47051
|
init_S3DestinationDriver();
|
|
46078
47052
|
init_S3SourceDriver();
|
|
47053
|
+
init_PresignedUrlDestinationDriver();
|
|
47054
|
+
init_PresignedUrlSourceDriver();
|
|
47055
|
+
init_StreamReader();
|
|
47056
|
+
init_UrlMinter();
|
|
46079
47057
|
init_PostgresDriver();
|
|
46080
47058
|
init_SqlDatasetWriter();
|
|
47059
|
+
init_SqlPredicate();
|
|
47060
|
+
init_SqlPushdown();
|
|
46081
47061
|
init_SqlQueryBuilder();
|
|
47062
|
+
init_SqlSchemaReader();
|
|
46082
47063
|
}
|
|
46083
47064
|
});
|
|
46084
47065
|
|
|
@@ -46216,7 +47197,7 @@ var init_ProducerManager = __esm({
|
|
|
46216
47197
|
});
|
|
46217
47198
|
|
|
46218
47199
|
// ../../packages/engines/src/transform/TypeCaster.ts
|
|
46219
|
-
var import_dayjs, import_customParseFormat, import_utc, DAY_OF_YEAR_TOKEN, FORMAT_ONLY_TOKENS, DATE_PARSE_TOKENS, TypeCasterClass, TypeCaster, TypeCaster_default;
|
|
47200
|
+
var import_dayjs, import_customParseFormat, import_utc, DAY_OF_YEAR_TOKEN, FORMAT_ONLY_TOKENS, DATE_PARSE_TOKENS, TYPE_DEFAULTS, TypeCasterClass, TypeCaster, TypeCaster_default;
|
|
46220
47201
|
var init_TypeCaster = __esm({
|
|
46221
47202
|
"../../packages/engines/src/transform/TypeCaster.ts"() {
|
|
46222
47203
|
init_src();
|
|
@@ -46237,7 +47218,36 @@ var init_TypeCaster = __esm({
|
|
|
46237
47218
|
{ token: "mm", width: 2 },
|
|
46238
47219
|
{ token: "ss", width: 2 }
|
|
46239
47220
|
];
|
|
47221
|
+
TYPE_DEFAULTS = {
|
|
47222
|
+
string: "",
|
|
47223
|
+
number: 0,
|
|
47224
|
+
boolean: false,
|
|
47225
|
+
datetime: null
|
|
47226
|
+
};
|
|
46240
47227
|
TypeCasterClass = class {
|
|
47228
|
+
constructor() {
|
|
47229
|
+
/**
|
|
47230
|
+
* Casts a value for a DECLARED dimension: the type cast, plus what a MISSING value means for it.
|
|
47231
|
+
*
|
|
47232
|
+
* Missing is null/undefined, and — for every type but `string` — the empty string, which is how
|
|
47233
|
+
* a NULL arrives from every source that stages through CSV (SQL, Delta Share, CSV files) and is
|
|
47234
|
+
* indistinguishable from one there. An empty string IS a value for a `string` dimension, so it
|
|
47235
|
+
* is left alone. A missing value reads as `null`, unless the dimension declares `nullable: false`
|
|
47236
|
+
* and so asks for its type's own empty value instead. Without this, a NULL in a number column
|
|
47237
|
+
* went through `Number('')` and arrived as a real `0` that joins, sums and filters like one the
|
|
47238
|
+
* source actually held.
|
|
47239
|
+
*/
|
|
47240
|
+
this.castDimension = (value, dimension) => {
|
|
47241
|
+
if (dimension.type && this.isMissing(value, dimension.type))
|
|
47242
|
+
return dimension.nullable === false ? TYPE_DEFAULTS[dimension.type] : null;
|
|
47243
|
+
return this.cast(value, dimension.type, dimension.format);
|
|
47244
|
+
};
|
|
47245
|
+
/** null/undefined for any type, plus the empty string for every type but `string`. */
|
|
47246
|
+
this.isMissing = (value, type) => {
|
|
47247
|
+
if (!Algo_default.hasVal(value)) return true;
|
|
47248
|
+
return value === "" && type !== "string";
|
|
47249
|
+
};
|
|
47250
|
+
}
|
|
46241
47251
|
/**
|
|
46242
47252
|
* Casts the value to the requested type (only if needed)
|
|
46243
47253
|
* Optional format parameter currently supports:
|
|
@@ -46427,7 +47437,7 @@ var init_LineParser = __esm({
|
|
|
46427
47437
|
counter = performance.now();
|
|
46428
47438
|
const value = {};
|
|
46429
47439
|
for (const dim of dimensions) {
|
|
46430
|
-
value[dim.name] = TypeCaster_default.
|
|
47440
|
+
value[dim.name] = TypeCaster_default.castDimension(parts[dim.index], dim.prodDimension);
|
|
46431
47441
|
}
|
|
46432
47442
|
tracker.measure("process-line:cast&build-record", performance.now() - counter);
|
|
46433
47443
|
return value;
|
|
@@ -46448,7 +47458,7 @@ var init_LineParser = __esm({
|
|
|
46448
47458
|
const value = {};
|
|
46449
47459
|
for (const dim of dimensions) {
|
|
46450
47460
|
const key = dim.prodDimension.alias ?? dim.prodDimension.name;
|
|
46451
|
-
value[dim.name] = TypeCaster_default.
|
|
47461
|
+
value[dim.name] = TypeCaster_default.castDimension(parsed[key], dim.prodDimension);
|
|
46452
47462
|
}
|
|
46453
47463
|
tracker.measure("process-line:cast&build-record", performance.now() - counter);
|
|
46454
47464
|
return value;
|
|
@@ -46470,8 +47480,11 @@ var init_LineParser = __esm({
|
|
|
46470
47480
|
}
|
|
46471
47481
|
if (!dim.range && !dim.placement) continue;
|
|
46472
47482
|
const range2 = dim.placement ? FixedWidthParser_default.resolvePlacement(dim.placement, layout, occurrence) : dim.range;
|
|
46473
|
-
|
|
46474
|
-
|
|
47483
|
+
if (!range2) {
|
|
47484
|
+
record[dim.name] = null;
|
|
47485
|
+
continue;
|
|
47486
|
+
}
|
|
47487
|
+
record[dim.name] = TypeCaster_default.castDimension(FixedWidthParser_default.readField(line, range2), dim.prodDimension);
|
|
46475
47488
|
}
|
|
46476
47489
|
return record;
|
|
46477
47490
|
};
|
|
@@ -46483,7 +47496,7 @@ var init_LineParser = __esm({
|
|
|
46483
47496
|
const record = {};
|
|
46484
47497
|
for (const [index, field] of fields.entries()) {
|
|
46485
47498
|
const fieldKey = field.finalKey;
|
|
46486
|
-
record[fieldKey] = TypeCaster_default.
|
|
47499
|
+
record[fieldKey] = field.dimension ? TypeCaster_default.castDimension(parts[index], field.dimension) : TypeCaster_default.cast(parts[index], "string");
|
|
46487
47500
|
}
|
|
46488
47501
|
return record;
|
|
46489
47502
|
};
|
|
@@ -46501,11 +47514,11 @@ var init_LineParser = __esm({
|
|
|
46501
47514
|
});
|
|
46502
47515
|
|
|
46503
47516
|
// ../../packages/engines/src/producer/ProducerEngine.ts
|
|
46504
|
-
var import_path13,
|
|
47517
|
+
var import_path13, import_crypto6, noopTracker, ProducerEngineClass, ProducerEngine, ProducerEngine_default;
|
|
46505
47518
|
var init_ProducerEngine = __esm({
|
|
46506
47519
|
"../../packages/engines/src/producer/ProducerEngine.ts"() {
|
|
46507
47520
|
import_path13 = __toESM(require("path"), 1);
|
|
46508
|
-
|
|
47521
|
+
import_crypto6 = require("crypto");
|
|
46509
47522
|
init_src();
|
|
46510
47523
|
init_src8();
|
|
46511
47524
|
init_src4();
|
|
@@ -46591,7 +47604,7 @@ var init_ProducerEngine = __esm({
|
|
|
46591
47604
|
switch (source.engine) {
|
|
46592
47605
|
case "aws-redshift":
|
|
46593
47606
|
case "postgres": {
|
|
46594
|
-
const sql =
|
|
47607
|
+
const sql = SqlQueryBuilder_default.buildProducerSelect(producer, source, { limit: sampleSize });
|
|
46595
47608
|
Affirm_default(sql, `Invalid SQL from deployment compilation for producer "${producer.name}"`);
|
|
46596
47609
|
const driver = await DriverFactory_default.instantiateSource(source);
|
|
46597
47610
|
Affirm_default(driver, `No driver found for producer "${producer.name}" with driver type "${source.engine}"`);
|
|
@@ -46608,7 +47621,7 @@ var init_ProducerEngine = __esm({
|
|
|
46608
47621
|
let effectiveProducer = producer;
|
|
46609
47622
|
let effectiveSource = source;
|
|
46610
47623
|
if (compressionType) {
|
|
46611
|
-
scope = { id: (0,
|
|
47624
|
+
scope = { id: (0, import_crypto6.randomUUID)(), folder: `sample-${(0, import_crypto6.randomUUID)()}`, workersId: [] };
|
|
46612
47625
|
const driver = await DriverFactory_default.instantiateSource(source);
|
|
46613
47626
|
const readyResult = await driver.ready({ producer, scope });
|
|
46614
47627
|
const firstFile = readyResult.files[0].fullUri;
|
|
@@ -46660,7 +47673,7 @@ var init_ProducerEngine = __esm({
|
|
|
46660
47673
|
const record = {};
|
|
46661
47674
|
for (const dim of producer.dimensions) {
|
|
46662
47675
|
const key = dim.alias ?? dim.name;
|
|
46663
|
-
record[dim.name] = TypeCaster_default.
|
|
47676
|
+
record[dim.name] = TypeCaster_default.castDimension(item[key], dim);
|
|
46664
47677
|
}
|
|
46665
47678
|
return ProducerManager_default.applyFills(record, fills, sourceFilename);
|
|
46666
47679
|
});
|
|
@@ -46767,53 +47780,178 @@ var init_ProducerEngine = __esm({
|
|
|
46767
47780
|
}
|
|
46768
47781
|
});
|
|
46769
47782
|
|
|
47783
|
+
// ../../packages/engines/src/producer/DiscoverMerger.ts
|
|
47784
|
+
var DiscoverMergerClass, DiscoverMerger, DiscoverMerger_default;
|
|
47785
|
+
var init_DiscoverMerger = __esm({
|
|
47786
|
+
"../../packages/engines/src/producer/DiscoverMerger.ts"() {
|
|
47787
|
+
init_src();
|
|
47788
|
+
DiscoverMergerClass = class {
|
|
47789
|
+
constructor() {
|
|
47790
|
+
this.merge = (authored, discovered, mode = "merge") => {
|
|
47791
|
+
Affirm_default(Array.isArray(authored), "Invalid authored dimensions");
|
|
47792
|
+
Affirm_default.hasItems(discovered, "Discover produced no dimensions to merge");
|
|
47793
|
+
Affirm_default(mode === "merge" || mode === "replace", `Unknown discover mode "${mode}" \u2014 expected "merge" or "replace".`);
|
|
47794
|
+
const pairs = authored.map((dimension) => ({ dimension, found: this._lookup(discovered, dimension) }));
|
|
47795
|
+
const consumed = new Set(pairs.map((pair) => pair.found?.name).filter(Boolean));
|
|
47796
|
+
const added = discovered.filter((field) => !consumed.has(field.name));
|
|
47797
|
+
const report = { added: added.map((field) => field.name), filled: [], orphaned: [], mismatched: [] };
|
|
47798
|
+
for (const { dimension, found } of pairs)
|
|
47799
|
+
this._reportMismatch(dimension, found, report);
|
|
47800
|
+
if (mode === "replace") {
|
|
47801
|
+
const unreadable = pairs.filter((pair) => !this._readsSource(pair.dimension)).map((pair) => pair.dimension);
|
|
47802
|
+
report.orphaned = pairs.filter((pair) => !pair.found && this._readsSource(pair.dimension)).map((pair) => pair.dimension.name);
|
|
47803
|
+
return { dimensions: [...discovered, ...unreadable], report };
|
|
47804
|
+
}
|
|
47805
|
+
const dimensions = pairs.map(({ dimension, found }) => {
|
|
47806
|
+
if (found) return this._fill(dimension, found, report);
|
|
47807
|
+
if (this._readsSource(dimension)) report.orphaned.push(dimension.name);
|
|
47808
|
+
return dimension;
|
|
47809
|
+
});
|
|
47810
|
+
return { dimensions: [...dimensions, ...added], report };
|
|
47811
|
+
};
|
|
47812
|
+
/**
|
|
47813
|
+
* What an authored dimension gains from the field discover matched it to: the properties it
|
|
47814
|
+
* declared NOTHING for, and nothing else. In particular `type` is never changed — a producer is
|
|
47815
|
+
* free to read a `timestamptz` as a string or a padded `char(n)` as one, and a re-discover that
|
|
47816
|
+
* undid that would be the same un-curation this merge exists to stop. The disagreement is
|
|
47817
|
+
* reported instead, so it is the author who decides.
|
|
47818
|
+
*/
|
|
47819
|
+
this._fill = (authored, discovered, report) => {
|
|
47820
|
+
const filled = { ...authored };
|
|
47821
|
+
const properties = [];
|
|
47822
|
+
if (!filled.type && discovered.type) {
|
|
47823
|
+
filled.type = discovered.type;
|
|
47824
|
+
properties.push("type");
|
|
47825
|
+
}
|
|
47826
|
+
if (filled.nullable === void 0 && discovered.nullable === false) {
|
|
47827
|
+
filled.nullable = false;
|
|
47828
|
+
properties.push("nullable");
|
|
47829
|
+
}
|
|
47830
|
+
if (properties.length > 0) report.filled.push({ name: authored.name, properties });
|
|
47831
|
+
return filled;
|
|
47832
|
+
};
|
|
47833
|
+
this._reportMismatch = (authored, discovered, report) => {
|
|
47834
|
+
if (!discovered?.type) return;
|
|
47835
|
+
if (!authored.type || authored.type === discovered.type) return;
|
|
47836
|
+
report.mismatched.push({ name: authored.name, declared: authored.type, discovered: discovered.type });
|
|
47837
|
+
};
|
|
47838
|
+
/**
|
|
47839
|
+
* The discovered field an authored dimension reads, if any.
|
|
47840
|
+
*
|
|
47841
|
+
* A dimension names its source field with `alias`, falling back to its own `name`. Two
|
|
47842
|
+
* fallbacks past that: the engines fold an unquoted SQL identifier to lower case, so a
|
|
47843
|
+
* case-only difference is the same column; and an `alias` carrying a path (`address.city`)
|
|
47844
|
+
* reaches INTO a field discover reports by its root name, so the root is what it matches —
|
|
47845
|
+
* which also stops a decomposed object being re-added as a dimension of its own.
|
|
47846
|
+
*/
|
|
47847
|
+
this._lookup = (discovered, dimension) => {
|
|
47848
|
+
const key = dimension.alias ?? dimension.name;
|
|
47849
|
+
if (!key) return void 0;
|
|
47850
|
+
const byName = (name) => discovered.find((field) => field.name === name) ?? discovered.find((field) => field.name.toLowerCase() === name.toLowerCase());
|
|
47851
|
+
return byName(key) ?? (key.includes(".") ? byName(key.split(".")[0]) : void 0);
|
|
47852
|
+
};
|
|
47853
|
+
/**
|
|
47854
|
+
* Whether this dimension is supposed to correspond to a field of the source at all. A group
|
|
47855
|
+
* produces no column of its own, and `sourceFilename` / `occurrenceOf` dimensions are filled by
|
|
47856
|
+
* the read rather than read from the data — none of them missing from a discovery is news.
|
|
47857
|
+
*/
|
|
47858
|
+
this._readsSource = (dimension) => !dimension.dimensions && !dimension.sourceFilename && !dimension.occurrenceOf;
|
|
47859
|
+
}
|
|
47860
|
+
};
|
|
47861
|
+
DiscoverMerger = new DiscoverMergerClass();
|
|
47862
|
+
DiscoverMerger_default = DiscoverMerger;
|
|
47863
|
+
}
|
|
47864
|
+
});
|
|
47865
|
+
|
|
46770
47866
|
// ../../packages/engines/src/ai/DeveloperEngine.ts
|
|
46771
|
-
var import_path14,
|
|
47867
|
+
var import_path14, import_promises14, import_dayjs2, import_customParseFormat2, DeveloperEngineClass, DeveloperEngine, DeveloperEngine_default;
|
|
46772
47868
|
var init_DeveloperEngine = __esm({
|
|
46773
47869
|
"../../packages/engines/src/ai/DeveloperEngine.ts"() {
|
|
46774
47870
|
init_src();
|
|
46775
47871
|
init_ProducerEngine();
|
|
47872
|
+
init_DiscoverMerger();
|
|
46776
47873
|
init_src4();
|
|
46777
47874
|
init_src8();
|
|
46778
47875
|
import_path14 = __toESM(require("path"), 1);
|
|
46779
|
-
|
|
47876
|
+
import_promises14 = __toESM(require("fs/promises"), 1);
|
|
46780
47877
|
import_dayjs2 = __toESM(require("dayjs"), 1);
|
|
46781
47878
|
import_customParseFormat2 = __toESM(require("dayjs/plugin/customParseFormat"), 1);
|
|
46782
47879
|
import_dayjs2.default.extend(import_customParseFormat2.default);
|
|
46783
47880
|
DeveloperEngineClass = class {
|
|
46784
47881
|
constructor() {
|
|
46785
|
-
|
|
47882
|
+
/**
|
|
47883
|
+
* Learns a producer's fields from its source and writes them back into the producer's config.
|
|
47884
|
+
*
|
|
47885
|
+
* The write is a MERGE by default (see `DiscoverMerger`): what was hand-authored on a dimension
|
|
47886
|
+
* survives a re-discover, and discover only supplies what the dimension declared nothing for.
|
|
47887
|
+
* `replace` rewrites the dimension list from what was found, which is what bootstrapping a
|
|
47888
|
+
* producer off the `create-producer` template needs.
|
|
47889
|
+
*/
|
|
47890
|
+
this.discover = async (producer, mode = "merge") => {
|
|
46786
47891
|
Affirm_default(producer, "Invalid producer");
|
|
46787
47892
|
const source = Environment_default.getSource(producer.source);
|
|
46788
47893
|
Affirm_default(source, `Cannot discover producer "${producer.name}": source "${producer.source}" was not found.`);
|
|
46789
47894
|
await this.assertDiscoverable(producer, source);
|
|
46790
|
-
const
|
|
46791
|
-
Affirm_default(
|
|
46792
|
-
const typeDefinitions = this.extractFieldTypes(sampleData);
|
|
46793
|
-
const mappedProducer = {
|
|
46794
|
-
name: producer.name,
|
|
46795
|
-
description: producer.description,
|
|
46796
|
-
source: producer.source,
|
|
46797
|
-
settings: { ...producer.settings },
|
|
46798
|
-
dimensions: typeDefinitions.map((field) => ({
|
|
46799
|
-
name: field.name,
|
|
46800
|
-
type: this.mapFieldTypeToProducerType(field.type),
|
|
46801
|
-
description: `Auto-mapped field: ${field.name}`,
|
|
46802
|
-
classification: this.extractFieldClassification(field)?.isPHI ? ["PHI"] : void 0
|
|
46803
|
-
})),
|
|
46804
|
-
measures: [],
|
|
46805
|
-
_version: producer._version ?? 1
|
|
46806
|
-
};
|
|
46807
|
-
mappedProducer["$schema"] = producer["$schema"];
|
|
47895
|
+
const typeDefinitions = DriverFactory_default.isSql(source.engine) ? await this.discoverSqlFields(producer, source) : this.extractFieldTypes(await ProducerEngine_default.readSampleData(producer, 10, true));
|
|
47896
|
+
Affirm_default.hasItems(typeDefinitions, `Discover found no fields for producer "${producer.name}" \u2014 there is nothing to write back.`);
|
|
46808
47897
|
const producerPath = Environment_default.resolveResourceFilePath("producers", producer.name);
|
|
46809
|
-
await
|
|
46810
|
-
|
|
46811
|
-
|
|
47898
|
+
const authored = await this.readAuthoredProducer(producerPath, producer);
|
|
47899
|
+
const discovered = typeDefinitions.map(this.toDimension);
|
|
47900
|
+
const { dimensions, report } = DiscoverMerger_default.merge(authored.dimensions ?? [], discovered, mode);
|
|
47901
|
+
const mappedProducer = { ...authored, dimensions };
|
|
47902
|
+
await import_promises14.default.mkdir(import_path14.default.dirname(producerPath), { recursive: true });
|
|
47903
|
+
await import_promises14.default.writeFile(producerPath, `${JSON.stringify(mappedProducer, null, 4)}
|
|
47904
|
+
`, "utf-8");
|
|
47905
|
+
return { producer: mappedProducer, fields: typeDefinitions, report };
|
|
47906
|
+
};
|
|
47907
|
+
/**
|
|
47908
|
+
* The producer exactly as it sits on disk. Discover rewrites one property of a file somebody
|
|
47909
|
+
* else wrote, so it starts from that file rather than from the loaded (expanded) resource.
|
|
47910
|
+
*/
|
|
47911
|
+
this.readAuthoredProducer = async (producerPath, producer) => {
|
|
47912
|
+
const raw = await import_promises14.default.readFile(producerPath, "utf-8").catch(() => null);
|
|
47913
|
+
Affirm_default(raw, `Cannot discover producer "${producer.name}": its config file was not found at "${producerPath}".`);
|
|
47914
|
+
const authored = JSON.parse(raw);
|
|
47915
|
+
Affirm_default(authored?.name === producer.name, `Cannot discover producer "${producer.name}": "${producerPath}" declares producer "${authored?.name}".`);
|
|
47916
|
+
return authored;
|
|
47917
|
+
};
|
|
47918
|
+
/** The dimension discover would write for a field it found, before any merge decides what of it is used. */
|
|
47919
|
+
this.toDimension = (field) => ({
|
|
47920
|
+
name: field.name,
|
|
47921
|
+
type: field.dimensionType ?? this.mapFieldTypeToProducerType(field.type),
|
|
47922
|
+
// Only written when the source STATED the column is NOT NULL. `nullable` defaults to
|
|
47923
|
+
// true everywhere else, so leaving it off is the same declaration with less noise.
|
|
47924
|
+
nullable: field.nullable === false ? false : void 0,
|
|
47925
|
+
description: `Auto-mapped field: ${field.name}`,
|
|
47926
|
+
classification: this.extractFieldClassification(field)?.isPHI ? ["PHI"] : void 0
|
|
47927
|
+
});
|
|
47928
|
+
/**
|
|
47929
|
+
* The columns of a SQL producer's table, read from the source's own catalog.
|
|
47930
|
+
*
|
|
47931
|
+
* The catalog states the type, so nothing is inferred here: a `numeric` column is a number even
|
|
47932
|
+
* when every row sampled from it was NULL, and `is_nullable` is a fact no number of sample rows
|
|
47933
|
+
* could establish. Values are never read at all, which also means discovering a table costs one
|
|
47934
|
+
* small statement rather than a scan of it.
|
|
47935
|
+
*/
|
|
47936
|
+
this.discoverSqlFields = async (producer, source) => {
|
|
47937
|
+
const driver = await DriverFactory_default.instantiateSource(source);
|
|
47938
|
+
Affirm_default(driver, `Cannot discover producer "${producer.name}": no driver available for engine "${source.engine}".`);
|
|
47939
|
+
const columns = await SqlSchemaReader_default.readColumns(driver, producer, source);
|
|
47940
|
+
return columns.map((column) => ({
|
|
47941
|
+
name: column.name,
|
|
47942
|
+
type: column.dataType,
|
|
47943
|
+
dimensionType: SqlSchemaReader_default.toDimensionType(column.dataType),
|
|
47944
|
+
nullable: column.nullable
|
|
47945
|
+
}));
|
|
46812
47946
|
};
|
|
46813
47947
|
// Discover-only precondition: sampling real data requires a configured, readable file.
|
|
46814
47948
|
// Unlike the normal read path (which tolerates empty/absent files by design), discover
|
|
46815
47949
|
// has nothing to infer from, so fail early with a message that says exactly what to fix.
|
|
46816
47950
|
this.assertDiscoverable = async (producer, source) => {
|
|
47951
|
+
if (DriverFactory_default.isSql(source.engine)) {
|
|
47952
|
+
Affirm_default(producer.settings?.sqlTable, `Cannot discover producer "${producer.name}": no "sqlTable" is configured \u2014 set the table to read from source "${source.name}" first, as "table" or "schema.table".`);
|
|
47953
|
+
return;
|
|
47954
|
+
}
|
|
46817
47955
|
const fileEngines = ["local", "aws-s3", "delta-share"];
|
|
46818
47956
|
if (!fileEngines.includes(source.engine)) return;
|
|
46819
47957
|
Affirm_default(producer.settings?.fileKey, `Cannot discover producer "${producer.name}": no "fileKey" is configured \u2014 set the file (or wildcard pattern) to read from source "${source.name}" first.`);
|
|
@@ -49344,10 +50482,10 @@ var init_MockDataFactory = __esm({
|
|
|
49344
50482
|
});
|
|
49345
50483
|
|
|
49346
50484
|
// ../../packages/engines/src/mock/SyntheticInputEngine.ts
|
|
49347
|
-
var
|
|
50485
|
+
var import_promises15, import_os, import_path15, import_dayjs4, import_customParseFormat3, import_utc3, POOL_CAP, SyntheticInputEngineClass, SyntheticInputEngine;
|
|
49348
50486
|
var init_SyntheticInputEngine = __esm({
|
|
49349
50487
|
"../../packages/engines/src/mock/SyntheticInputEngine.ts"() {
|
|
49350
|
-
|
|
50488
|
+
import_promises15 = __toESM(require("fs/promises"), 1);
|
|
49351
50489
|
import_os = __toESM(require("os"), 1);
|
|
49352
50490
|
import_path15 = __toESM(require("path"), 1);
|
|
49353
50491
|
import_dayjs4 = __toESM(require("dayjs"), 1);
|
|
@@ -49373,7 +50511,7 @@ var init_SyntheticInputEngine = __esm({
|
|
|
49373
50511
|
for (const cs2 of [...BUILTIN_CODE_SETS, ...codeSets]) codeSetMap.set(cs2.name.toLowerCase(), cs2);
|
|
49374
50512
|
const producerMap = new Map(producers.map((p5) => [p5.name, p5]));
|
|
49375
50513
|
const refTargets = this._collectReferences(producers, producerMap);
|
|
49376
|
-
const dir = await
|
|
50514
|
+
const dir = await import_promises15.default.mkdtemp(import_path15.default.join(import_os.default.tmpdir(), "remora-synth-"));
|
|
49377
50515
|
const poolSize = Math.min(Math.max(opts.records, 1), POOL_CAP);
|
|
49378
50516
|
const registry = new MockKeyRegistry();
|
|
49379
50517
|
const specs = producers.flatMap((p5) => this._specs(p5, codeSetMap, producerMap, refTargets));
|
|
@@ -49390,7 +50528,7 @@ var init_SyntheticInputEngine = __esm({
|
|
|
49390
50528
|
return { inputs, dir };
|
|
49391
50529
|
};
|
|
49392
50530
|
this.cleanup = async (bundle) => {
|
|
49393
|
-
if (bundle?.dir) await
|
|
50531
|
+
if (bundle?.dir) await import_promises15.default.rm(bundle.dir, { recursive: true, force: true });
|
|
49394
50532
|
};
|
|
49395
50533
|
this._assertOpts = (opts) => {
|
|
49396
50534
|
Affirm_default(opts, "Invalid options");
|
|
@@ -49440,8 +50578,8 @@ var init_SyntheticInputEngine = __esm({
|
|
|
49440
50578
|
lines.push(JSON.stringify(row) + "\n");
|
|
49441
50579
|
}
|
|
49442
50580
|
const filePath = import_path15.default.join(dir, `${producer.name}.jsonl`);
|
|
49443
|
-
await
|
|
49444
|
-
const { size } = await
|
|
50581
|
+
await import_promises15.default.writeFile(filePath, lines.join(""), "utf-8");
|
|
50582
|
+
const { size } = await import_promises15.default.stat(filePath);
|
|
49445
50583
|
return { path: filePath, fileSize: size };
|
|
49446
50584
|
};
|
|
49447
50585
|
// Distinct structured code sets referenced by these specs -> their records (deduped by name).
|
|
@@ -49575,8 +50713,7 @@ var init_SyntheticInputEngine = __esm({
|
|
|
49575
50713
|
return length;
|
|
49576
50714
|
}
|
|
49577
50715
|
Affirm_default(typeof value === "string", `Dimension "${dim.name}" synth "${prop}" must be a date string on a "datetime" dimension (got ${JSON.stringify(value)})`);
|
|
49578
|
-
const
|
|
49579
|
-
const date2 = formatted?.isValid() ? formatted : import_dayjs4.default.utc(value);
|
|
50716
|
+
const date2 = this._parseDate(value, dim.format);
|
|
49580
50717
|
Affirm_default(date2.isValid(), `Dimension "${dim.name}" synth "${prop}" ("${value}") is not a valid date${dim.format ? ` for format "${dim.format}" or as an ISO date` : ""}`);
|
|
49581
50718
|
return date2.valueOf();
|
|
49582
50719
|
};
|
|
@@ -49588,11 +50725,43 @@ var init_SyntheticInputEngine = __esm({
|
|
|
49588
50725
|
const synth = this._synth(dim);
|
|
49589
50726
|
Affirm_default(!(synth.allowedValues && synth.codeSet), `Dimension "${dim.name}" cannot set both synth "allowedValues" and "codeSet"`);
|
|
49590
50727
|
Affirm_default(!(synth.references && this._hasValueSource(dim)), `Dimension "${dim.name}" cannot combine synth "references" with "allowedValues"/"codeSet"`);
|
|
49591
|
-
|
|
50728
|
+
const type = dim.type ?? "string";
|
|
50729
|
+
if (synth.allowedValues) {
|
|
50730
|
+
Affirm_default(this._valuesFitType(synth.allowedValues, type, dim.format), `Dimension "${dim.name}" declares synth "allowedValues" that are not valid "${type}" values (${JSON.stringify(synth.allowedValues.slice(0, 3))}\u2026)`);
|
|
50731
|
+
return { enumValues: synth.allowedValues };
|
|
50732
|
+
}
|
|
49592
50733
|
const ref = synth.codeSet ?? matchBuiltinBinding(name);
|
|
49593
50734
|
if (!ref || synth.references) return {};
|
|
49594
|
-
|
|
50735
|
+
const resolved = this._resolveRef(ref, codeSetMap, dim.name);
|
|
50736
|
+
if (this._valuesFitType(this._setValues(resolved, codeSetMap), type, dim.format)) return resolved;
|
|
50737
|
+
Affirm_default(!synth.codeSet, `Dimension "${dim.name}" is bound to code set "${this._refName(ref)}", whose values are not valid "${type}" values`);
|
|
50738
|
+
return {};
|
|
49595
50739
|
};
|
|
50740
|
+
// Every value a resolved binding can emit: the flat set's own values, or the bound field's column
|
|
50741
|
+
// across the structured set's records.
|
|
50742
|
+
this._setValues = (resolved, codeSetMap) => resolved.enumValues ?? codeSetMap.get(resolved.codeSet.name.toLowerCase()).records.map((rec) => rec[resolved.codeSet.field]);
|
|
50743
|
+
/**
|
|
50744
|
+
* Whether a closed set of values survives the cast to the dimension's declared type. Enum and code
|
|
50745
|
+
* set values are emitted verbatim and cast when the synthetic file is read back, so a set that
|
|
50746
|
+
* can't produce that type fills the column with garbage — HEDIS codes cast to a `number` are all
|
|
50747
|
+
* NaN, which serializes as null — rather than failing anywhere visible.
|
|
50748
|
+
*/
|
|
50749
|
+
this._valuesFitType = (values, type, format4) => {
|
|
50750
|
+
if (type === "string") return true;
|
|
50751
|
+
if (type === "boolean") return values.every((value) => typeof value === "boolean");
|
|
50752
|
+
if (type === "number") return values.every((value) => this._isNumeric(value));
|
|
50753
|
+
return values.every((value) => this._parseDate(value, format4).isValid());
|
|
50754
|
+
};
|
|
50755
|
+
this._isNumeric = (value) => {
|
|
50756
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
50757
|
+
return typeof value === "string" && value.trim() !== "" && !isNaN(Number(value));
|
|
50758
|
+
};
|
|
50759
|
+
// Read the same way a bound is read: the dimension's own format first, a plain/ISO date otherwise.
|
|
50760
|
+
this._parseDate = (value, format4) => {
|
|
50761
|
+
const formatted = format4 && typeof value === "string" ? import_dayjs4.default.utc(value, format4, true) : void 0;
|
|
50762
|
+
return formatted?.isValid() ? formatted : import_dayjs4.default.utc(value);
|
|
50763
|
+
};
|
|
50764
|
+
this._refName = (ref) => typeof ref === "string" ? ref : ref.name;
|
|
49596
50765
|
this._resolveRef = (ref, codeSetMap, dimName) => {
|
|
49597
50766
|
if (typeof ref === "string") {
|
|
49598
50767
|
const set2 = codeSetMap.get(ref.toLowerCase());
|
|
@@ -50240,33 +51409,33 @@ var init_RequestValidator = __esm({
|
|
|
50240
51409
|
this.resolveMembers = (consumer) => {
|
|
50241
51410
|
return new Set(ConsumerManager_default.getExpandedFields(consumer).map((x2) => x2.finalKey));
|
|
50242
51411
|
};
|
|
50243
|
-
this.checkFilters = (value, context,
|
|
51412
|
+
this.checkFilters = (value, context, path20, depth) => {
|
|
50244
51413
|
const { MAX_REQUEST_FILTERS, MAX_REQUEST_FILTER_DEPTH } = Constants_default.defaults;
|
|
50245
51414
|
if (!Array.isArray(value))
|
|
50246
|
-
throw new Error(`Invalid "${
|
|
51415
|
+
throw new Error(`Invalid "${path20}": expected an array of filters, got ${this.describe(value)}`);
|
|
50247
51416
|
if (value.length > MAX_REQUEST_FILTERS)
|
|
50248
|
-
throw new Error(`Invalid "${
|
|
51417
|
+
throw new Error(`Invalid "${path20}": ${value.length} filters is above the maximum of ${MAX_REQUEST_FILTERS}`);
|
|
50249
51418
|
if (depth > MAX_REQUEST_FILTER_DEPTH)
|
|
50250
|
-
throw new Error(`Invalid filter at ${
|
|
50251
|
-
return value.map((x2, index) => this.checkFilter(x2, context, `${
|
|
51419
|
+
throw new Error(`Invalid filter at ${path20}: filters may not nest more than ${MAX_REQUEST_FILTER_DEPTH} deep`);
|
|
51420
|
+
return value.map((x2, index) => this.checkFilter(x2, context, `${path20}[${index}]`, depth));
|
|
50252
51421
|
};
|
|
50253
|
-
this.checkFilter = (value, context,
|
|
51422
|
+
this.checkFilter = (value, context, path20, depth) => {
|
|
50254
51423
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50255
|
-
throw new Error(`Invalid filter at ${
|
|
51424
|
+
throw new Error(`Invalid filter at ${path20}: expected an object, got ${this.describe(value)}`);
|
|
50256
51425
|
const filter = value;
|
|
50257
51426
|
const unknownKey = Object.keys(filter).find((key) => !FILTER_KEYS.includes(key));
|
|
50258
51427
|
if (unknownKey)
|
|
50259
|
-
throw new Error(`Invalid filter at ${
|
|
50260
|
-
const operator = this.checkOperator(filter.operator,
|
|
51428
|
+
throw new Error(`Invalid filter at ${path20}: unknown property "${unknownKey}". Supported: ${FILTER_KEYS.join(", ")}`);
|
|
51429
|
+
const operator = this.checkOperator(filter.operator, path20);
|
|
50261
51430
|
const checked = {
|
|
50262
|
-
member: this.checkMember(filter.member, context,
|
|
51431
|
+
member: this.checkMember(filter.member, context, path20),
|
|
50263
51432
|
operator,
|
|
50264
|
-
values: this.checkValues(filter.values, operator,
|
|
51433
|
+
values: this.checkValues(filter.values, operator, path20)
|
|
50265
51434
|
};
|
|
50266
51435
|
if (Algo_default.hasVal(filter.and))
|
|
50267
|
-
checked.and = this.checkFilters(filter.and, context, `${
|
|
51436
|
+
checked.and = this.checkFilters(filter.and, context, `${path20}.and`, depth + 1);
|
|
50268
51437
|
if (Algo_default.hasVal(filter.or))
|
|
50269
|
-
checked.or = this.checkFilters(filter.or, context, `${
|
|
51438
|
+
checked.or = this.checkFilters(filter.or, context, `${path20}.or`, depth + 1);
|
|
50270
51439
|
return checked;
|
|
50271
51440
|
};
|
|
50272
51441
|
/**
|
|
@@ -50274,18 +51443,18 @@ var init_RequestValidator = __esm({
|
|
|
50274
51443
|
* own field names by the time filters run, so any other name reads `undefined` and quietly keeps
|
|
50275
51444
|
* every row (or drops every row, for a negated operator).
|
|
50276
51445
|
*/
|
|
50277
|
-
this.checkMember = (value, context,
|
|
51446
|
+
this.checkMember = (value, context, path20) => {
|
|
50278
51447
|
if (typeof value !== "string" || value.length === 0)
|
|
50279
|
-
throw new Error(`Invalid filter at ${
|
|
51448
|
+
throw new Error(`Invalid filter at ${path20}: "member" must be a non-empty string, got ${this.describe(value)}`);
|
|
50280
51449
|
if (!context.members.has(value))
|
|
50281
|
-
throw new Error(`Invalid filter at ${
|
|
51450
|
+
throw new Error(`Invalid filter at ${path20}: consumer "${context.consumer.name}" has no field named "${value}"`);
|
|
50282
51451
|
return value;
|
|
50283
51452
|
};
|
|
50284
|
-
this.checkOperator = (value,
|
|
51453
|
+
this.checkOperator = (value, path20) => {
|
|
50285
51454
|
if (typeof value !== "string" || !VALUES_BY_OPERATOR.has(value))
|
|
50286
|
-
throw new Error(`Invalid filter at ${
|
|
51455
|
+
throw new Error(`Invalid filter at ${path20}: unknown operator ${this.describe(value)}. Supported: ${this.supportedOperators()}`);
|
|
50287
51456
|
if (VALUES_BY_OPERATOR.get(value) === "unsupported")
|
|
50288
|
-
throw new Error(`Invalid filter at ${
|
|
51457
|
+
throw new Error(`Invalid filter at ${path20}: operator "${value}" is not supported by a run`);
|
|
50289
51458
|
return value;
|
|
50290
51459
|
};
|
|
50291
51460
|
/**
|
|
@@ -50293,24 +51462,24 @@ var init_RequestValidator = __esm({
|
|
|
50293
51462
|
* own type and the filter's is read back into it, so a number or a boolean here is a client bug
|
|
50294
51463
|
* that reads as a type mismatch (matching nothing) rather than as the comparison it meant.
|
|
50295
51464
|
*/
|
|
50296
|
-
this.checkValues = (value, operator,
|
|
51465
|
+
this.checkValues = (value, operator, path20) => {
|
|
50297
51466
|
const values = Algo_default.hasVal(value) ? value : [];
|
|
50298
51467
|
if (!Array.isArray(values))
|
|
50299
|
-
throw new Error(`Invalid filter at ${
|
|
51468
|
+
throw new Error(`Invalid filter at ${path20}: "values" must be an array of strings, got ${this.describe(value)}`);
|
|
50300
51469
|
const wrongIndex = values.findIndex((x2) => typeof x2 !== "string");
|
|
50301
51470
|
if (wrongIndex >= 0)
|
|
50302
|
-
throw new Error(`Invalid filter at ${
|
|
50303
|
-
return this.checkArity(values, operator,
|
|
51471
|
+
throw new Error(`Invalid filter at ${path20}: "values[${wrongIndex}]" must be a string, got ${this.describe(values[wrongIndex])}`);
|
|
51472
|
+
return this.checkArity(values, operator, path20);
|
|
50304
51473
|
};
|
|
50305
|
-
this.checkArity = (values, operator,
|
|
51474
|
+
this.checkArity = (values, operator, path20) => {
|
|
50306
51475
|
const expected = VALUES_BY_OPERATOR.get(operator);
|
|
50307
51476
|
const reason = `operator "${operator}"`;
|
|
50308
51477
|
if (expected === "none" && values.length > 0)
|
|
50309
|
-
throw new Error(`Invalid filter at ${
|
|
51478
|
+
throw new Error(`Invalid filter at ${path20}: ${reason} reads no values, ${values.length} given`);
|
|
50310
51479
|
if (expected === "many" && values.length === 0)
|
|
50311
|
-
throw new Error(`Invalid filter at ${
|
|
51480
|
+
throw new Error(`Invalid filter at ${path20}: ${reason} needs at least one value, none given`);
|
|
50312
51481
|
if (typeof expected === "number" && values.length !== expected)
|
|
50313
|
-
throw new Error(`Invalid filter at ${
|
|
51482
|
+
throw new Error(`Invalid filter at ${path20}: ${reason} needs exactly ${expected} ${expected === 1 ? "value" : "values"}, ${values.length} given`);
|
|
50314
51483
|
return values;
|
|
50315
51484
|
};
|
|
50316
51485
|
/** Every operator a request may ask for, in the order they are declared. */
|
|
@@ -50651,23 +51820,23 @@ var init_Diagnostics = __esm({
|
|
|
50651
51820
|
return [.../* @__PURE__ */ new Set([...inputs, ...chained])];
|
|
50652
51821
|
};
|
|
50653
51822
|
this._walk = (start, next, onCycle) => {
|
|
50654
|
-
const
|
|
51823
|
+
const path20 = [];
|
|
50655
51824
|
const onPath = /* @__PURE__ */ new Set();
|
|
50656
51825
|
const stack = [{ name: start, done: false }];
|
|
50657
51826
|
const visited = /* @__PURE__ */ new Set();
|
|
50658
51827
|
while (stack.length > 0) {
|
|
50659
51828
|
const frame = stack.pop();
|
|
50660
51829
|
if (frame.done) {
|
|
50661
|
-
onPath.delete(
|
|
51830
|
+
onPath.delete(path20.pop());
|
|
50662
51831
|
continue;
|
|
50663
51832
|
}
|
|
50664
51833
|
if (onPath.has(frame.name)) {
|
|
50665
|
-
|
|
51834
|
+
path20.slice(path20.indexOf(frame.name)).forEach((name) => onCycle.add(name));
|
|
50666
51835
|
continue;
|
|
50667
51836
|
}
|
|
50668
51837
|
if (visited.has(frame.name)) continue;
|
|
50669
51838
|
visited.add(frame.name);
|
|
50670
|
-
|
|
51839
|
+
path20.push(frame.name);
|
|
50671
51840
|
onPath.add(frame.name);
|
|
50672
51841
|
stack.push({ name: frame.name, done: true });
|
|
50673
51842
|
const successors = next.get(frame.name) ?? [];
|
|
@@ -51189,7 +52358,7 @@ var init_ResourceView = __esm({
|
|
|
51189
52358
|
description: producer.description,
|
|
51190
52359
|
summary: [
|
|
51191
52360
|
{ label: "source", value: producer.source ?? null, link: producer.source ? LineageEngine_default.nodeId("source", producer.source) : void 0 },
|
|
51192
|
-
{ label: "reads", value: settings.sqlTable ?? settings.
|
|
52361
|
+
{ label: "reads", value: settings.sqlTable ?? settings.fileKey ?? null },
|
|
51193
52362
|
{ label: "type", value: settings.fileType ?? "SQL" },
|
|
51194
52363
|
{ label: "dimensions", value: dimensions.length },
|
|
51195
52364
|
{ label: "primary keys", value: dimensions.filter((x2) => x2.pk).length }
|
|
@@ -51225,7 +52394,9 @@ var init_ResourceView = __esm({
|
|
|
51225
52394
|
{ value: this._text(dimension.type) },
|
|
51226
52395
|
positioned ? { value: this._position(dimension.position) } : { value: this._text(dimension.alias), muted: !dimension.alias },
|
|
51227
52396
|
{ value: !!dimension.pk, muted: !dimension.pk },
|
|
51228
|
-
|
|
52397
|
+
// Absent means nullable, so an undeclared dimension reads true here rather than false;
|
|
52398
|
+
// muted says the value was defaulted and not written down.
|
|
52399
|
+
{ value: dimension.nullable !== false, muted: dimension.nullable === void 0 },
|
|
51229
52400
|
{ value: this._text(dimension.mask), muted: !dimension.mask },
|
|
51230
52401
|
{ value: this._text(dimension.classification), muted: !dimension.classification }
|
|
51231
52402
|
]);
|
|
@@ -51271,7 +52442,7 @@ var init_ResourceView = __esm({
|
|
|
51271
52442
|
{ label: "fields", value: (consumer.fields ?? []).length },
|
|
51272
52443
|
{ label: "schema", value: consumer.schema ?? null, link: consumer.schema ? LineageEngine_default.nodeId("schema", consumer.schema) : void 0 },
|
|
51273
52444
|
{ label: "distinct", value: !!consumer.options?.distinct },
|
|
51274
|
-
{ label: "
|
|
52445
|
+
{ label: "split", value: this._splitSummary(consumer) }
|
|
51275
52446
|
],
|
|
51276
52447
|
tables: [
|
|
51277
52448
|
this._consumerInputsTable(env2, consumer),
|
|
@@ -51283,6 +52454,19 @@ var init_ResourceView = __esm({
|
|
|
51283
52454
|
raw: this._raw(consumer)
|
|
51284
52455
|
};
|
|
51285
52456
|
};
|
|
52457
|
+
/**
|
|
52458
|
+
* How the consumer splits its output, in one line: the key(s) it buckets on, the per-file cap,
|
|
52459
|
+
* or both. `maximumFileSize` is the older spelling of the cap and reads the same here.
|
|
52460
|
+
*/
|
|
52461
|
+
this._splitSummary = (consumer) => {
|
|
52462
|
+
const keys = consumer.split?.by ?? [];
|
|
52463
|
+
const maxFileSizeGb = consumer.split?.maxFileSizeGb ?? consumer.maximumFileSize;
|
|
52464
|
+
const parts = [
|
|
52465
|
+
keys.length > 0 ? `by ${keys.join(", ")}` : null,
|
|
52466
|
+
maxFileSizeGb ? `max ${maxFileSizeGb} Gb per file` : null
|
|
52467
|
+
].filter((x2) => !!x2);
|
|
52468
|
+
return parts.length > 0 ? parts.join(", ") : null;
|
|
52469
|
+
};
|
|
51286
52470
|
this._consumerInputsTable = (env2, consumer) => {
|
|
51287
52471
|
const rows = (consumer.producers ?? []).map((input) => [
|
|
51288
52472
|
{ value: input.name, link: this._inputLink(env2, input.name), status: this._inputLink(env2, input.name) ? void 0 : "error" },
|
|
@@ -51345,8 +52529,8 @@ var init_ResourceView = __esm({
|
|
|
51345
52529
|
if (!source || origin.path.length < 2) return { value: "declared here", muted: true };
|
|
51346
52530
|
return { value: `${source.resource}.${source.field}`, link: source.node };
|
|
51347
52531
|
};
|
|
51348
|
-
this._pathText = (
|
|
51349
|
-
return
|
|
52532
|
+
this._pathText = (path20) => {
|
|
52533
|
+
return path20.map((step) => `${step.resource}.${step.field}`).join(" \u2192 ");
|
|
51350
52534
|
};
|
|
51351
52535
|
this._consumerOutputsTable = (env2, consumer) => {
|
|
51352
52536
|
const declared = new Set((env2.sources ?? []).map((x2) => x2.name));
|
|
@@ -52173,11 +53357,11 @@ var init_OpsHeartbeat = __esm({
|
|
|
52173
53357
|
});
|
|
52174
53358
|
|
|
52175
53359
|
// ../../packages/engines/src/usage/LocalUsageDB.ts
|
|
52176
|
-
var
|
|
53360
|
+
var import_fs17, import_path16, COLUMNS, CLI_DB_FOLDER, CLI_DB_FILE, CLI_DB_PATH, MAX_USAGE_ROWS, MAX_LISTED_ROWS, EXECUTED_BY_SCHEMA, EXECUTOR_OPERATION_SCHEMA, STATS_SCHEMA, COLUMN_SCHEMAS, LocalUsageDBClass, LocalUsageDB, LocalUsageDB_default;
|
|
52177
53361
|
var init_LocalUsageDB = __esm({
|
|
52178
53362
|
"../../packages/engines/src/usage/LocalUsageDB.ts"() {
|
|
52179
53363
|
init_src();
|
|
52180
|
-
|
|
53364
|
+
import_fs17 = require("fs");
|
|
52181
53365
|
import_path16 = require("path");
|
|
52182
53366
|
COLUMNS = [
|
|
52183
53367
|
"_id",
|
|
@@ -52368,21 +53552,21 @@ var init_LocalUsageDB = __esm({
|
|
|
52368
53552
|
return usage;
|
|
52369
53553
|
};
|
|
52370
53554
|
this._getRows = () => {
|
|
52371
|
-
const lines = (0,
|
|
53555
|
+
const lines = (0, import_fs17.readFileSync)(CLI_DB_PATH, "utf8").split("\n").filter(Boolean);
|
|
52372
53556
|
lines.shift();
|
|
52373
53557
|
return lines;
|
|
52374
53558
|
};
|
|
52375
53559
|
this._writeRows = (rows) => {
|
|
52376
53560
|
const trimmedRows = rows.slice(-MAX_USAGE_ROWS);
|
|
52377
|
-
(0,
|
|
53561
|
+
(0, import_fs17.writeFileSync)(CLI_DB_PATH, `${[COLUMNS.join(","), ...trimmedRows].join("\n")}
|
|
52378
53562
|
`);
|
|
52379
53563
|
};
|
|
52380
53564
|
this.init = () => {
|
|
52381
53565
|
try {
|
|
52382
|
-
if (!(0,
|
|
52383
|
-
(0,
|
|
52384
|
-
if (!(0,
|
|
52385
|
-
(0,
|
|
53566
|
+
if (!(0, import_fs17.existsSync)(CLI_DB_FOLDER))
|
|
53567
|
+
(0, import_fs17.mkdirSync)(CLI_DB_FOLDER, { recursive: true });
|
|
53568
|
+
if (!(0, import_fs17.existsSync)(CLI_DB_PATH))
|
|
53569
|
+
(0, import_fs17.writeFileSync)(CLI_DB_PATH, `${COLUMNS.join(",")}
|
|
52386
53570
|
`);
|
|
52387
53571
|
this._writeRows(this._getRows());
|
|
52388
53572
|
} catch (error) {
|
|
@@ -52412,7 +53596,7 @@ var init_LocalUsageDB = __esm({
|
|
|
52412
53596
|
*/
|
|
52413
53597
|
this.list = (limit = MAX_LISTED_ROWS, keep) => {
|
|
52414
53598
|
try {
|
|
52415
|
-
if (!(0,
|
|
53599
|
+
if (!(0, import_fs17.existsSync)(CLI_DB_PATH))
|
|
52416
53600
|
return [];
|
|
52417
53601
|
const rows = this._getRows();
|
|
52418
53602
|
const wanted = Math.max(1, limit);
|
|
@@ -52433,9 +53617,9 @@ var init_LocalUsageDB = __esm({
|
|
|
52433
53617
|
*/
|
|
52434
53618
|
this.signature = () => {
|
|
52435
53619
|
try {
|
|
52436
|
-
if (!(0,
|
|
53620
|
+
if (!(0, import_fs17.existsSync)(CLI_DB_PATH))
|
|
52437
53621
|
return "";
|
|
52438
|
-
const stats = (0,
|
|
53622
|
+
const stats = (0, import_fs17.statSync)(CLI_DB_PATH);
|
|
52439
53623
|
return `${stats.mtimeMs}:${stats.size}`;
|
|
52440
53624
|
} catch (error) {
|
|
52441
53625
|
this._handleError("signature", error);
|
|
@@ -52602,10 +53786,10 @@ var init_UsageManager = __esm({
|
|
|
52602
53786
|
});
|
|
52603
53787
|
|
|
52604
53788
|
// ../../packages/engines/src/ops/RunOverlay.ts
|
|
52605
|
-
var
|
|
53789
|
+
var import_crypto7, MAX_RUNS, MAX_OPERATIONS, RunOverlayClass, RunOverlay;
|
|
52606
53790
|
var init_RunOverlay = __esm({
|
|
52607
53791
|
"../../packages/engines/src/ops/RunOverlay.ts"() {
|
|
52608
|
-
|
|
53792
|
+
import_crypto7 = __toESM(require("crypto"), 1);
|
|
52609
53793
|
init_src();
|
|
52610
53794
|
init_LineageEngine();
|
|
52611
53795
|
init_LocalUsageDB();
|
|
@@ -52659,7 +53843,7 @@ var init_RunOverlay = __esm({
|
|
|
52659
53843
|
* finish action writes runs of its own, so a narrower token would miss a transition the graph
|
|
52660
53844
|
* exists to show.
|
|
52661
53845
|
*/
|
|
52662
|
-
this._token = (overlay) =>
|
|
53846
|
+
this._token = (overlay) => import_crypto7.default.createHash("sha256").update(JSON.stringify(overlay)).digest("hex");
|
|
52663
53847
|
this._run = (stat) => {
|
|
52664
53848
|
const startedAt = this._iso(stat.startedAt);
|
|
52665
53849
|
return {
|
|
@@ -52941,12 +54125,12 @@ var init_LookupRegistry = __esm({
|
|
|
52941
54125
|
});
|
|
52942
54126
|
|
|
52943
54127
|
// ../../packages/engines/src/transform/LookupKey.ts
|
|
52944
|
-
var
|
|
54128
|
+
var import_crypto8, DELIMITER, idFor, LookupKey, LookupKey_default;
|
|
52945
54129
|
var init_LookupKey = __esm({
|
|
52946
54130
|
"../../packages/engines/src/transform/LookupKey.ts"() {
|
|
52947
|
-
|
|
54131
|
+
import_crypto8 = __toESM(require("crypto"), 1);
|
|
52948
54132
|
DELIMITER = String.fromCharCode(0);
|
|
52949
|
-
idFor = (ref) =>
|
|
54133
|
+
idFor = (ref) => import_crypto8.default.createHash("sha1").update([ref.consumer, ref.key, ref.value].join(DELIMITER)).digest("hex").slice(0, 16);
|
|
52950
54134
|
LookupKey = { idFor };
|
|
52951
54135
|
LookupKey_default = LookupKey;
|
|
52952
54136
|
}
|
|
@@ -53581,6 +54765,7 @@ var init_src10 = __esm({
|
|
|
53581
54765
|
init_RunOverlay();
|
|
53582
54766
|
init_RunRegistry();
|
|
53583
54767
|
init_LineParser();
|
|
54768
|
+
init_DiscoverMerger();
|
|
53584
54769
|
init_ProducerEngine();
|
|
53585
54770
|
init_ProducerManager();
|
|
53586
54771
|
init_PushdownEngine();
|
|
@@ -53596,16 +54781,14 @@ var init_src10 = __esm({
|
|
|
53596
54781
|
});
|
|
53597
54782
|
|
|
53598
54783
|
// ../../packages/executors/src/OutputExecutor.ts
|
|
53599
|
-
var
|
|
54784
|
+
var OutputExecutorClass, OutputExecutor, OutputExecutor_default;
|
|
53600
54785
|
var init_OutputExecutor = __esm({
|
|
53601
54786
|
"../../packages/executors/src/OutputExecutor.ts"() {
|
|
53602
54787
|
init_src();
|
|
53603
|
-
fs19 = __toESM(require("fs"));
|
|
53604
54788
|
init_src();
|
|
53605
54789
|
init_src8();
|
|
53606
54790
|
init_src4();
|
|
53607
54791
|
init_src5();
|
|
53608
|
-
import_path17 = __toESM(require("path"));
|
|
53609
54792
|
init_src2();
|
|
53610
54793
|
OutputExecutorClass = class {
|
|
53611
54794
|
constructor() {
|
|
@@ -53667,24 +54850,33 @@ var init_OutputExecutor = __esm({
|
|
|
53667
54850
|
}
|
|
53668
54851
|
return JSON.stringify(record);
|
|
53669
54852
|
};
|
|
53670
|
-
|
|
53671
|
-
|
|
53672
|
-
|
|
53673
|
-
|
|
54853
|
+
/**
|
|
54854
|
+
* The exported name of one split file: the destination name with the file's label before the
|
|
54855
|
+
* extension, e.g. "sales.csv" + "italy_1" -> "sales_italy_1.csv". The extension is taken from
|
|
54856
|
+
* the LAST dot, since a label (and an exportName) may carry one of its own.
|
|
54857
|
+
*/
|
|
54858
|
+
this.getCompletedPath = (pathName, label) => {
|
|
54859
|
+
const dot = pathName.lastIndexOf(".");
|
|
54860
|
+
if (dot < 0) return `${pathName}_${label}`;
|
|
54861
|
+
return `${pathName.substring(0, dot)}_${label}${pathName.substring(dot)}`;
|
|
53674
54862
|
};
|
|
53675
|
-
|
|
54863
|
+
/**
|
|
54864
|
+
* Writes the run's result to every configured destination, one call per file the split pass
|
|
54865
|
+
* produced. The files are passed in rather than read back out of the scope folder: the label a
|
|
54866
|
+
* file carries is what names it at the destination, and with no split configured that list is
|
|
54867
|
+
* the merged dataset alone, labelled "0" — the suffix an unsplit export has always had.
|
|
54868
|
+
*/
|
|
54869
|
+
this.exportResult = async (consumer, fields, splitFiles) => {
|
|
54870
|
+
Affirm_default.hasItems(splitFiles, `Consumer "${consumer.name}" has no output file to export`);
|
|
53676
54871
|
const internalFormat = this._getInternalRecordFormat(consumer);
|
|
53677
54872
|
const results = [];
|
|
53678
54873
|
for (const output of consumer.outputs) {
|
|
53679
54874
|
const destination = Environment_default.getSource(output.exportDestination);
|
|
53680
54875
|
const driver = await DriverFactory_default.instantiateDestination(destination);
|
|
53681
|
-
const currentPath = import_path17.default.dirname(ExecutorScope_default.getMainPath(scope));
|
|
53682
54876
|
const destinationName = this._composeFileName(consumer, output, this._getExtension(output));
|
|
53683
|
-
Logger_default.info(`Exporting consumer "${consumer.name}" to "${output.exportDestination}" as ${output.format} (${destinationName})`);
|
|
53684
|
-
const
|
|
53685
|
-
|
|
53686
|
-
const destinationPath = this.getCompletedPath(destinationName, filename);
|
|
53687
|
-
const startingPath = import_path17.default.join(currentPath, filenameArray[filename]);
|
|
54877
|
+
Logger_default.info(`Exporting consumer "${consumer.name}" to "${output.exportDestination}" as ${output.format} (${destinationName}, ${splitFiles.length} file(s))`);
|
|
54878
|
+
for (const { path: startingPath, label } of splitFiles) {
|
|
54879
|
+
const destinationPath = this.getCompletedPath(destinationName, label);
|
|
53688
54880
|
if (output.format === internalFormat) {
|
|
53689
54881
|
results.push(await driver.move(startingPath, destinationPath));
|
|
53690
54882
|
} else {
|
|
@@ -53728,9 +54920,18 @@ var init_OutputExecutor = __esm({
|
|
|
53728
54920
|
return this._unifingResult(results);
|
|
53729
54921
|
}
|
|
53730
54922
|
};
|
|
54923
|
+
/**
|
|
54924
|
+
* Folds the per-file upload results into the one the run reports.
|
|
54925
|
+
*
|
|
54926
|
+
* `every`, not `some`: a destination write that failed is data that never arrived, and one
|
|
54927
|
+
* successful file must not be able to report the run as exported. The drivers catch their own
|
|
54928
|
+
* errors and answer `res: false`, so this flag is the only thing that carries a failed write
|
|
54929
|
+
* back up — `launch` refuses the run on it.
|
|
54930
|
+
*/
|
|
53731
54931
|
this._unifingResult = (resultList) => {
|
|
54932
|
+
Affirm_default.hasItems(resultList, "Export produced no result to report");
|
|
53732
54933
|
resultList[0].key = resultList.map((res) => res.key).join(", ");
|
|
53733
|
-
resultList[0].res = resultList.
|
|
54934
|
+
resultList[0].res = resultList.every((res) => res.res);
|
|
53734
54935
|
return resultList[0];
|
|
53735
54936
|
};
|
|
53736
54937
|
this._getExtension = (output) => {
|
|
@@ -53753,14 +54954,13 @@ var init_OutputExecutor = __esm({
|
|
|
53753
54954
|
});
|
|
53754
54955
|
|
|
53755
54956
|
// ../../packages/executors/src/ConsumerExecutor.ts
|
|
53756
|
-
var
|
|
54957
|
+
var import_path17, import_fs18, import_promises16, import_crypto9, LIMIT_SCAN_BYTES, NEWLINE_BYTE, ConsumerExecutorClass, ConsumerExecutor, ConsumerExecutor_default;
|
|
53757
54958
|
var init_ConsumerExecutor = __esm({
|
|
53758
54959
|
"../../packages/executors/src/ConsumerExecutor.ts"() {
|
|
53759
|
-
|
|
53760
|
-
|
|
53761
|
-
|
|
53762
|
-
|
|
53763
|
-
import_crypto7 = __toESM(require("crypto"));
|
|
54960
|
+
import_path17 = __toESM(require("path"));
|
|
54961
|
+
import_fs18 = __toESM(require("fs"));
|
|
54962
|
+
import_promises16 = __toESM(require("fs/promises"));
|
|
54963
|
+
import_crypto9 = __toESM(require("crypto"));
|
|
53764
54964
|
init_src10();
|
|
53765
54965
|
init_src10();
|
|
53766
54966
|
init_src10();
|
|
@@ -53774,11 +54974,11 @@ var init_ConsumerExecutor = __esm({
|
|
|
53774
54974
|
ConsumerExecutorClass = class {
|
|
53775
54975
|
constructor() {
|
|
53776
54976
|
this._ensurePath = (pathUri) => {
|
|
53777
|
-
const dir =
|
|
53778
|
-
if (!
|
|
53779
|
-
|
|
53780
|
-
if (!
|
|
53781
|
-
|
|
54977
|
+
const dir = import_path17.default.dirname(pathUri);
|
|
54978
|
+
if (!import_fs18.default.existsSync(dir))
|
|
54979
|
+
import_fs18.default.mkdirSync(dir, { recursive: true });
|
|
54980
|
+
if (!import_fs18.default.existsSync(pathUri))
|
|
54981
|
+
import_fs18.default.writeFileSync(pathUri, "");
|
|
53782
54982
|
};
|
|
53783
54983
|
/**
|
|
53784
54984
|
* Shapes one parsed producer record into one consumer record, following a plan compiled once per
|
|
@@ -53899,9 +55099,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
53899
55099
|
if (lineCountFields.length === 0)
|
|
53900
55100
|
return 0;
|
|
53901
55101
|
const tempWorkPath = datasetPath + "_tmp";
|
|
53902
|
-
const reader =
|
|
53903
|
-
const
|
|
53904
|
-
const writer = import_fs16.default.createWriteStream(tempWorkPath);
|
|
55102
|
+
const reader = import_fs18.default.createReadStream(datasetPath);
|
|
55103
|
+
const writer = import_fs18.default.createWriteStream(tempWorkPath);
|
|
53905
55104
|
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
53906
55105
|
let workerIndex = 0;
|
|
53907
55106
|
let currentWorker = executorResults[workerIndex] ?? null;
|
|
@@ -53915,7 +55114,7 @@ var init_ConsumerExecutor = __esm({
|
|
|
53915
55114
|
}
|
|
53916
55115
|
};
|
|
53917
55116
|
advanceWorker();
|
|
53918
|
-
for await (const line of
|
|
55117
|
+
for await (const line of readLines(reader)) {
|
|
53919
55118
|
advanceWorker();
|
|
53920
55119
|
Affirm_default(currentWorker, `Unable to resolve worker metadata while processing lineCount for ${datasetPath}`);
|
|
53921
55120
|
const rowOffset = currentWorker.rowOffset ?? 0;
|
|
@@ -53934,7 +55133,6 @@ var init_ConsumerExecutor = __esm({
|
|
|
53934
55133
|
remainingLinesInWorker--;
|
|
53935
55134
|
processedLineCount++;
|
|
53936
55135
|
}
|
|
53937
|
-
lineReader.close();
|
|
53938
55136
|
await new Promise((resolve, reject) => {
|
|
53939
55137
|
writer.on("close", resolve);
|
|
53940
55138
|
writer.on("error", reject);
|
|
@@ -53946,8 +55144,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
53946
55144
|
reader.destroy();
|
|
53947
55145
|
});
|
|
53948
55146
|
}
|
|
53949
|
-
await
|
|
53950
|
-
await
|
|
55147
|
+
await import_promises16.default.unlink(datasetPath);
|
|
55148
|
+
await import_promises16.default.rename(tempWorkPath, datasetPath);
|
|
53951
55149
|
return processedLineCount;
|
|
53952
55150
|
};
|
|
53953
55151
|
/**
|
|
@@ -53965,7 +55163,7 @@ var init_ConsumerExecutor = __esm({
|
|
|
53965
55163
|
this.processLimit = async (datasetPath, limit) => {
|
|
53966
55164
|
Affirm_default(datasetPath, "Invalid dataset path");
|
|
53967
55165
|
Affirm_default(limit > 0, `Invalid record limit ${limit}`);
|
|
53968
|
-
const handle = await
|
|
55166
|
+
const handle = await import_promises16.default.open(datasetPath, "r+");
|
|
53969
55167
|
try {
|
|
53970
55168
|
const buffer = Buffer.allocUnsafe(LIMIT_SCAN_BYTES);
|
|
53971
55169
|
let newlines = 0;
|
|
@@ -53991,15 +55189,14 @@ var init_ConsumerExecutor = __esm({
|
|
|
53991
55189
|
}
|
|
53992
55190
|
};
|
|
53993
55191
|
this.processDistinct = async (datasetPath) => {
|
|
53994
|
-
const reader =
|
|
53995
|
-
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
55192
|
+
const reader = import_fs18.default.createReadStream(datasetPath);
|
|
53996
55193
|
const tempWorkPath = datasetPath + "_tmp";
|
|
53997
|
-
const writer =
|
|
55194
|
+
const writer = import_fs18.default.createWriteStream(tempWorkPath);
|
|
53998
55195
|
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
53999
55196
|
let newLineCount = 0;
|
|
54000
55197
|
const seen = /* @__PURE__ */ new Set();
|
|
54001
|
-
for await (const line of
|
|
54002
|
-
const hash =
|
|
55198
|
+
for await (const line of readLines(reader)) {
|
|
55199
|
+
const hash = import_crypto9.default.createHash("sha1").update(line).digest("base64");
|
|
54003
55200
|
if (!seen.has(hash)) {
|
|
54004
55201
|
seen.add(hash);
|
|
54005
55202
|
if (!writer.write(line + "\n"))
|
|
@@ -54007,7 +55204,6 @@ var init_ConsumerExecutor = __esm({
|
|
|
54007
55204
|
newLineCount++;
|
|
54008
55205
|
}
|
|
54009
55206
|
}
|
|
54010
|
-
lineReader.close();
|
|
54011
55207
|
await new Promise((resolve, reject) => {
|
|
54012
55208
|
writer.on("close", resolve);
|
|
54013
55209
|
writer.on("error", reject);
|
|
@@ -54019,20 +55215,19 @@ var init_ConsumerExecutor = __esm({
|
|
|
54019
55215
|
reader.destroy();
|
|
54020
55216
|
});
|
|
54021
55217
|
}
|
|
54022
|
-
await
|
|
54023
|
-
await
|
|
55218
|
+
await import_promises16.default.unlink(datasetPath);
|
|
55219
|
+
await import_promises16.default.rename(tempWorkPath, datasetPath);
|
|
54024
55220
|
return newLineCount;
|
|
54025
55221
|
};
|
|
54026
55222
|
this.processDistinctOn = async (consumer, datasetPath) => {
|
|
54027
|
-
const reader =
|
|
54028
|
-
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
55223
|
+
const reader = import_fs18.default.createReadStream(datasetPath);
|
|
54029
55224
|
const { distinctOn } = consumer.options;
|
|
54030
55225
|
const { keys, resolution } = distinctOn;
|
|
54031
55226
|
const { strategy, orderBy, direction = "asc" } = resolution;
|
|
54032
55227
|
const internalRecordFormat = OutputExecutor_default._getInternalRecordFormat(consumer);
|
|
54033
55228
|
const internalFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
54034
55229
|
const winners = /* @__PURE__ */ new Map();
|
|
54035
|
-
for await (const line of
|
|
55230
|
+
for await (const line of readLines(reader)) {
|
|
54036
55231
|
const record = internalRecordFormat === "CSV" ? LineParser_default._internalParseCSV(line, internalFields) : LineParser_default._internalParseJSON(line);
|
|
54037
55232
|
const compositeKey = keys.map((k7) => String(record[k7] ?? "")).join("|");
|
|
54038
55233
|
const existing = winners.get(compositeKey);
|
|
@@ -54045,9 +55240,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54045
55240
|
winners.set(compositeKey, { record, line });
|
|
54046
55241
|
}
|
|
54047
55242
|
}
|
|
54048
|
-
lineReader.close();
|
|
54049
55243
|
const tempWorkPath = datasetPath + "_tmp";
|
|
54050
|
-
const writer =
|
|
55244
|
+
const writer = import_fs18.default.createWriteStream(tempWorkPath);
|
|
54051
55245
|
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
54052
55246
|
for (const { line } of winners.values()) {
|
|
54053
55247
|
if (!writer.write(line + "\n"))
|
|
@@ -54064,8 +55258,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54064
55258
|
reader.destroy();
|
|
54065
55259
|
});
|
|
54066
55260
|
}
|
|
54067
|
-
await
|
|
54068
|
-
await
|
|
55261
|
+
await import_promises16.default.unlink(datasetPath);
|
|
55262
|
+
await import_promises16.default.rename(tempWorkPath, datasetPath);
|
|
54069
55263
|
return winners.size;
|
|
54070
55264
|
};
|
|
54071
55265
|
this.processPivot = async (consumer, datasetPath) => {
|
|
@@ -54077,9 +55271,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54077
55271
|
if (!pivotValues) {
|
|
54078
55272
|
pivotValues = [];
|
|
54079
55273
|
const discoverySet = /* @__PURE__ */ new Set();
|
|
54080
|
-
const discoverReader =
|
|
54081
|
-
const
|
|
54082
|
-
for await (const line of discoverLineReader) {
|
|
55274
|
+
const discoverReader = import_fs18.default.createReadStream(datasetPath);
|
|
55275
|
+
for await (const line of readLines(discoverReader)) {
|
|
54083
55276
|
const record = this._parseLine(line, internalRecordFormat, internalFields);
|
|
54084
55277
|
const val = String(record[pivotColumn] ?? "");
|
|
54085
55278
|
if (!discoverySet.has(val)) {
|
|
@@ -54087,7 +55280,6 @@ var init_ConsumerExecutor = __esm({
|
|
|
54087
55280
|
pivotValues.push(val);
|
|
54088
55281
|
}
|
|
54089
55282
|
}
|
|
54090
|
-
discoverLineReader.close();
|
|
54091
55283
|
if (!discoverReader.destroyed) {
|
|
54092
55284
|
await new Promise((resolve) => {
|
|
54093
55285
|
discoverReader.once("close", resolve);
|
|
@@ -54096,9 +55288,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54096
55288
|
}
|
|
54097
55289
|
}
|
|
54098
55290
|
const groups = /* @__PURE__ */ new Map();
|
|
54099
|
-
const reader =
|
|
54100
|
-
const
|
|
54101
|
-
for await (const line of lineReader) {
|
|
55291
|
+
const reader = import_fs18.default.createReadStream(datasetPath);
|
|
55292
|
+
for await (const line of readLines(reader)) {
|
|
54102
55293
|
const record = this._parseLine(line, internalRecordFormat, internalFields);
|
|
54103
55294
|
const compositeKey = rowKeys.map((k7) => String(record[k7] ?? "")).join("|");
|
|
54104
55295
|
const pivotVal = String(record[pivotColumn] ?? "");
|
|
@@ -54118,14 +55309,13 @@ var init_ConsumerExecutor = __esm({
|
|
|
54118
55309
|
cell.min = Math.min(cell.min, numericVal);
|
|
54119
55310
|
cell.max = Math.max(cell.max, numericVal);
|
|
54120
55311
|
}
|
|
54121
|
-
lineReader.close();
|
|
54122
55312
|
const pivotedFields = [
|
|
54123
55313
|
...rowKeys.map((k7) => ({ cField: { key: k7 }, finalKey: k7 })),
|
|
54124
55314
|
...pivotValues.map((pv) => ({ cField: { key: columnPrefix + pv }, finalKey: columnPrefix + pv }))
|
|
54125
55315
|
];
|
|
54126
55316
|
const outputPlan = OutputExecutor_default.compileOutput(consumer, pivotedFields);
|
|
54127
55317
|
const tempWorkPath = datasetPath + "_tmp";
|
|
54128
|
-
const writer =
|
|
55318
|
+
const writer = import_fs18.default.createWriteStream(tempWorkPath);
|
|
54129
55319
|
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
54130
55320
|
let outputCount = 0;
|
|
54131
55321
|
for (const { rowRecord, cells } of groups.values()) {
|
|
@@ -54171,8 +55361,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54171
55361
|
reader.destroy();
|
|
54172
55362
|
});
|
|
54173
55363
|
}
|
|
54174
|
-
await
|
|
54175
|
-
await
|
|
55364
|
+
await import_promises16.default.unlink(datasetPath);
|
|
55365
|
+
await import_promises16.default.rename(tempWorkPath, datasetPath);
|
|
54176
55366
|
return outputCount;
|
|
54177
55367
|
};
|
|
54178
55368
|
this._parseLine = (line, format4, fields) => {
|
|
@@ -54218,9 +55408,8 @@ var init_ConsumerExecutor = __esm({
|
|
|
54218
55408
|
for (const fieldKey of uniqueFieldKeys) {
|
|
54219
55409
|
fieldValueSets.set(fieldKey, /* @__PURE__ */ new Set());
|
|
54220
55410
|
}
|
|
54221
|
-
const reader =
|
|
54222
|
-
const
|
|
54223
|
-
for await (const line of lineReader) {
|
|
55411
|
+
const reader = import_fs18.default.createReadStream(datasetPath);
|
|
55412
|
+
for await (const line of readLines(reader)) {
|
|
54224
55413
|
rowCount++;
|
|
54225
55414
|
if (checkDuplicateRows) {
|
|
54226
55415
|
if (seenRows.has(line))
|
|
@@ -54242,7 +55431,6 @@ var init_ConsumerExecutor = __esm({
|
|
|
54242
55431
|
}
|
|
54243
55432
|
}
|
|
54244
55433
|
}
|
|
54245
|
-
lineReader.close();
|
|
54246
55434
|
return DataValidationEngine_default.evaluateDatasetValidations(validations, { rowCount, hasDuplicateRows, duplicateFields });
|
|
54247
55435
|
};
|
|
54248
55436
|
/**
|
|
@@ -54268,7 +55456,7 @@ var init_ConsumerExecutor = __esm({
|
|
|
54268
55456
|
});
|
|
54269
55457
|
|
|
54270
55458
|
// ../../packages/executors/src/ProducerExecutor.ts
|
|
54271
|
-
var
|
|
55459
|
+
var import_path18, ProducerExecutorClass, ProducerExecutor, ProducerExecutor_default;
|
|
54272
55460
|
var init_ProducerExecutor = __esm({
|
|
54273
55461
|
"../../packages/executors/src/ProducerExecutor.ts"() {
|
|
54274
55462
|
init_src();
|
|
@@ -54276,7 +55464,7 @@ var init_ProducerExecutor = __esm({
|
|
|
54276
55464
|
init_src4();
|
|
54277
55465
|
init_src10();
|
|
54278
55466
|
init_src10();
|
|
54279
|
-
|
|
55467
|
+
import_path18 = __toESM(require("path"));
|
|
54280
55468
|
init_src2();
|
|
54281
55469
|
ProducerExecutorClass = class {
|
|
54282
55470
|
constructor() {
|
|
@@ -54311,7 +55499,7 @@ var init_ProducerExecutor = __esm({
|
|
|
54311
55499
|
this.compileLine = (dimensions, chunk) => {
|
|
54312
55500
|
Affirm_default(dimensions, "Invalid producer dimensions");
|
|
54313
55501
|
Affirm_default(chunk, "Invalid chunk");
|
|
54314
|
-
return { fills: ProducerManager_default.compileFills(dimensions), sourceFilename:
|
|
55502
|
+
return { fills: ProducerManager_default.compileFills(dimensions), sourceFilename: import_path18.default.basename(chunk.fileUri) };
|
|
54315
55503
|
};
|
|
54316
55504
|
/**
|
|
54317
55505
|
* The record(s) one source line produces. Almost always exactly one — a producer with a group
|
|
@@ -54367,102 +55555,224 @@ var init_ExecutorPerformance = __esm({
|
|
|
54367
55555
|
});
|
|
54368
55556
|
|
|
54369
55557
|
// ../../packages/executors/src/ExecutorWriter.ts
|
|
54370
|
-
var
|
|
55558
|
+
var fs23, ExecutorWriter, ExecutorWriter_default;
|
|
54371
55559
|
var init_ExecutorWriter = __esm({
|
|
54372
55560
|
"../../packages/executors/src/ExecutorWriter.ts"() {
|
|
54373
|
-
|
|
54374
|
-
|
|
55561
|
+
fs23 = __toESM(require("fs"));
|
|
55562
|
+
init_src();
|
|
55563
|
+
init_src3();
|
|
55564
|
+
init_src10();
|
|
55565
|
+
init_OutputExecutor();
|
|
54375
55566
|
ExecutorWriter = class {
|
|
54376
55567
|
constructor() {
|
|
54377
55568
|
/**
|
|
54378
|
-
*
|
|
54379
|
-
|
|
54380
|
-
|
|
54381
|
-
|
|
54382
|
-
|
|
54383
|
-
|
|
54384
|
-
|
|
54385
|
-
|
|
54386
|
-
return
|
|
54387
|
-
};
|
|
54388
|
-
|
|
54389
|
-
|
|
54390
|
-
|
|
54391
|
-
|
|
54392
|
-
|
|
54393
|
-
|
|
54394
|
-
|
|
54395
|
-
|
|
54396
|
-
|
|
54397
|
-
|
|
54398
|
-
|
|
54399
|
-
|
|
54400
|
-
|
|
54401
|
-
|
|
54402
|
-
|
|
54403
|
-
|
|
54404
|
-
|
|
54405
|
-
|
|
55569
|
+
* Gigabytes as the configuration means them — 10^9, not 2^30. `maximumFileSize` has always been
|
|
55570
|
+
* read this way and the two differ by 7%, so it stays.
|
|
55571
|
+
*/
|
|
55572
|
+
this.BYTES_PER_GB = 1e3 * 1e3 * 1e3;
|
|
55573
|
+
/** The label an unsplit output carries, kept at "0" so exported names do not change. */
|
|
55574
|
+
this.UNSPLIT_LABEL = "0";
|
|
55575
|
+
/** The path of one split file: `main.dataset` plus a label, e.g. `main_italy_1.dataset`. */
|
|
55576
|
+
this.getCompletedPath = (startingPath, label) => {
|
|
55577
|
+
return startingPath.substring(0, startingPath.length - ".dataset".length).concat("_", label, ".dataset");
|
|
55578
|
+
};
|
|
55579
|
+
/**
|
|
55580
|
+
* Routes the merged dataset into one file per bucket, in a single pass.
|
|
55581
|
+
*
|
|
55582
|
+
* Splitting by key and splitting by size are the same operation — decide a line's bucket, append
|
|
55583
|
+
* to that bucket's current file, start the next one when it is full — so one pass covers all
|
|
55584
|
+
* three configurations: a size cap alone (one bucket that rotates), key(s) alone (a bucket each,
|
|
55585
|
+
* never rotating), or both (a bucket each, each rotating on its own counter).
|
|
55586
|
+
*
|
|
55587
|
+
* Returns the files it produced, so the export names them from their label rather than
|
|
55588
|
+
* rediscovering them in the scope folder. With nothing to split the merged dataset is handed
|
|
55589
|
+
* back as-is and never read.
|
|
55590
|
+
*/
|
|
55591
|
+
this.splitOutput = async (request) => {
|
|
55592
|
+
Affirm_default(request, "Invalid split request");
|
|
55593
|
+
Affirm_default(request.sourcePath, "Missing dataset to split");
|
|
55594
|
+
Affirm_default(request.consumer, "Invalid consumer");
|
|
55595
|
+
const { scope, sourcePath, consumer, fields } = request;
|
|
55596
|
+
const keys = scope.split?.by ?? [];
|
|
55597
|
+
const maxFileBytes = scope.split?.maxFileSizeGb > 0 ? scope.split.maxFileSizeGb * this.BYTES_PER_GB : null;
|
|
55598
|
+
if (keys.length === 0 && !maxFileBytes)
|
|
55599
|
+
return [{ path: sourcePath, label: this.UNSPLIT_LABEL }];
|
|
55600
|
+
const pass = {
|
|
55601
|
+
consumer,
|
|
55602
|
+
sourcePath,
|
|
55603
|
+
keys,
|
|
55604
|
+
maxFileBytes,
|
|
55605
|
+
buckets: /* @__PURE__ */ new Map(),
|
|
55606
|
+
openStreams: [],
|
|
55607
|
+
usedLabels: /* @__PURE__ */ new Set(),
|
|
55608
|
+
fileCount: 0
|
|
55609
|
+
};
|
|
55610
|
+
await this._route(pass, fields);
|
|
55611
|
+
if (pass.buckets.size === 0)
|
|
55612
|
+
return [{ path: sourcePath, label: this.UNSPLIT_LABEL }];
|
|
55613
|
+
await fs23.promises.unlink(sourcePath);
|
|
55614
|
+
return this._listFiles(pass);
|
|
55615
|
+
};
|
|
55616
|
+
/**
|
|
55617
|
+
* The pass itself: one line read, one line written, for every line of the dataset.
|
|
55618
|
+
*/
|
|
55619
|
+
this._route = async (pass, fields) => {
|
|
55620
|
+
const internalFormat = OutputExecutor_default._getInternalRecordFormat(pass.consumer);
|
|
55621
|
+
const readStream = fs23.createReadStream(pass.sourcePath);
|
|
55622
|
+
try {
|
|
55623
|
+
for await (const line of readLines(readStream)) {
|
|
55624
|
+
const bucket = this._bucketFor(line, pass, internalFormat, fields);
|
|
55625
|
+
const lineBytes = Buffer.byteLength(line) + 1;
|
|
55626
|
+
if (this._needsRotation(bucket, pass, lineBytes))
|
|
55627
|
+
await this._rotate(bucket, pass);
|
|
55628
|
+
const stream = await this._acquireStream(bucket, pass);
|
|
55629
|
+
if (!stream.write(line + "\n"))
|
|
55630
|
+
await new Promise((resolve) => stream.once("drain", resolve));
|
|
55631
|
+
bucket.bytesWritten += lineBytes;
|
|
54406
55632
|
}
|
|
54407
|
-
|
|
54408
|
-
|
|
55633
|
+
} finally {
|
|
55634
|
+
while (pass.openStreams.length > 0)
|
|
55635
|
+
await this._closeStream(pass.openStreams.shift());
|
|
55636
|
+
readStream.destroy();
|
|
54409
55637
|
}
|
|
54410
|
-
|
|
55638
|
+
};
|
|
55639
|
+
/**
|
|
55640
|
+
* The bucket a line belongs to, created on first sight of its key. Null and empty each get their
|
|
55641
|
+
* own: a shared catch-all would silently merge two different things, and that is invisible until
|
|
55642
|
+
* it is wrong.
|
|
55643
|
+
*/
|
|
55644
|
+
this._bucketFor = (line, pass, internalFormat, fields) => {
|
|
55645
|
+
const bucketKey = pass.keys.length === 0 ? "" : this._bucketKey(line, pass.keys, internalFormat, fields);
|
|
55646
|
+
const existing = pass.buckets.get(bucketKey);
|
|
55647
|
+
if (existing) return existing;
|
|
55648
|
+
const bucket = {
|
|
55649
|
+
label: pass.keys.length === 0 ? "" : this._uniqueLabel(bucketKey, pass.usedLabels),
|
|
55650
|
+
stream: null,
|
|
55651
|
+
bytesWritten: 0,
|
|
55652
|
+
fileIndex: 0
|
|
55653
|
+
};
|
|
55654
|
+
pass.buckets.set(bucketKey, bucket);
|
|
55655
|
+
this._countFile(pass);
|
|
55656
|
+
return bucket;
|
|
55657
|
+
};
|
|
55658
|
+
/** The composite value a line buckets on, read off the parsed record. */
|
|
55659
|
+
this._bucketKey = (line, keys, internalFormat, fields) => {
|
|
55660
|
+
const record = internalFormat === "CSV" ? LineParser_default._internalParseCSV(line, fields) : LineParser_default._internalParseJSON(line);
|
|
55661
|
+
return keys.map((key) => {
|
|
55662
|
+
const value = record[key];
|
|
55663
|
+
if (value === null || value === void 0) return "_null";
|
|
55664
|
+
const text = String(value);
|
|
55665
|
+
return text.length === 0 ? "_empty" : text;
|
|
55666
|
+
}).join("|");
|
|
55667
|
+
};
|
|
55668
|
+
/**
|
|
55669
|
+
* A key value turned into a file name segment. Anything that is not a letter, a digit, an
|
|
55670
|
+
* underscore or a dash becomes an underscore — dots included, since the export composes names
|
|
55671
|
+
* around the extension — and the result is capped so a long value cannot overrun the path limit.
|
|
55672
|
+
*
|
|
55673
|
+
* Two different values can sanitize to the same segment ("a/b" and "a b"), so a collision takes
|
|
55674
|
+
* a numeric suffix: buckets are keyed by the raw value and must never merge into one file.
|
|
55675
|
+
*/
|
|
55676
|
+
this._uniqueLabel = (bucketKey, usedLabels) => {
|
|
55677
|
+
const base = bucketKey.replace(/[^\p{L}\p{N}_-]/gu, "_").substring(0, 64) || "_empty";
|
|
55678
|
+
let label = base;
|
|
55679
|
+
let collision = 1;
|
|
55680
|
+
while (usedLabels.has(label)) {
|
|
55681
|
+
label = `${base}_${collision}`;
|
|
55682
|
+
collision++;
|
|
55683
|
+
}
|
|
55684
|
+
usedLabels.add(label);
|
|
55685
|
+
return label;
|
|
55686
|
+
};
|
|
55687
|
+
/**
|
|
55688
|
+
* Whether this line fills the bucket's current file. Asked BEFORE the line is written, so the
|
|
55689
|
+
* cap is a cap and not a threshold the last line of every file steps over. A line larger than
|
|
55690
|
+
* the cap on its own still goes out whole: rotating an empty file only produces an empty one.
|
|
55691
|
+
*/
|
|
55692
|
+
this._needsRotation = (bucket, pass, lineBytes) => {
|
|
55693
|
+
if (!pass.maxFileBytes || bucket.bytesWritten === 0) return false;
|
|
55694
|
+
return bucket.bytesWritten + lineBytes > pass.maxFileBytes;
|
|
55695
|
+
};
|
|
55696
|
+
/** Closes the bucket's full file and points it at the next one. */
|
|
55697
|
+
this._rotate = async (bucket, pass) => {
|
|
55698
|
+
await this._releaseStream(bucket, pass.openStreams);
|
|
55699
|
+
bucket.fileIndex++;
|
|
55700
|
+
bucket.bytesWritten = 0;
|
|
55701
|
+
this._countFile(pass);
|
|
55702
|
+
};
|
|
55703
|
+
/** Counts a file as it is opened, so the bound fails the run before the descriptors run out. */
|
|
55704
|
+
this._countFile = (pass) => {
|
|
55705
|
+
pass.fileCount++;
|
|
55706
|
+
Affirm_default(
|
|
55707
|
+
pass.fileCount <= Constants_default.defaults.MAX_SPLIT_FILES,
|
|
55708
|
+
`Consumer "${pass.consumer.name}" splits its output into more than ${Constants_default.defaults.MAX_SPLIT_FILES} files${pass.keys.length ? ` \u2014 "${pass.keys.join(", ")}" has too many distinct values to split on` : ""}.`
|
|
55709
|
+
);
|
|
55710
|
+
};
|
|
55711
|
+
/**
|
|
55712
|
+
* The bucket's open stream, opening it if the LRU has evicted it. Append mode, always: an
|
|
55713
|
+
* evicted bucket reopens onto the file it was already writing, and its byte counter lives in the
|
|
55714
|
+
* bucket rather than the stream so the rotation point survives the eviction.
|
|
55715
|
+
*/
|
|
55716
|
+
this._acquireStream = async (bucket, pass) => {
|
|
55717
|
+
const position = pass.openStreams.indexOf(bucket);
|
|
55718
|
+
if (position >= 0) {
|
|
55719
|
+
pass.openStreams.splice(position, 1);
|
|
55720
|
+
pass.openStreams.push(bucket);
|
|
55721
|
+
return bucket.stream;
|
|
55722
|
+
}
|
|
55723
|
+
if (pass.openStreams.length >= Constants_default.defaults.MAX_OPEN_SPLIT_STREAMS)
|
|
55724
|
+
await this._closeStream(pass.openStreams.shift());
|
|
55725
|
+
bucket.stream = fs23.createWriteStream(this.getCompletedPath(pass.sourcePath, this._composeLabel(bucket, pass)), { flags: "a" });
|
|
55726
|
+
pass.openStreams.push(bucket);
|
|
55727
|
+
return bucket.stream;
|
|
55728
|
+
};
|
|
55729
|
+
/**
|
|
55730
|
+
* Closes a bucket's stream and takes it out of the open set, so the set only ever holds buckets
|
|
55731
|
+
* whose stream is live — what lets `_acquireStream` read "in the set" as "already open".
|
|
55732
|
+
*/
|
|
55733
|
+
this._releaseStream = async (bucket, openStreams) => {
|
|
55734
|
+
const position = openStreams.indexOf(bucket);
|
|
55735
|
+
if (position >= 0)
|
|
55736
|
+
openStreams.splice(position, 1);
|
|
55737
|
+
await this._closeStream(bucket);
|
|
55738
|
+
};
|
|
55739
|
+
/** Flushes and releases a bucket's descriptor. Awaited: a reopen must not race the flush. */
|
|
55740
|
+
this._closeStream = async (bucket) => {
|
|
55741
|
+
if (!bucket?.stream) return;
|
|
55742
|
+
const stream = bucket.stream;
|
|
55743
|
+
bucket.stream = null;
|
|
55744
|
+
stream.end();
|
|
54411
55745
|
await new Promise((resolve, reject) => {
|
|
54412
|
-
|
|
54413
|
-
|
|
55746
|
+
stream.on("finish", resolve);
|
|
55747
|
+
stream.on("error", reject);
|
|
54414
55748
|
});
|
|
54415
|
-
|
|
54416
|
-
|
|
54417
|
-
|
|
54418
|
-
*
|
|
54419
|
-
|
|
54420
|
-
|
|
54421
|
-
|
|
54422
|
-
|
|
54423
|
-
|
|
54424
|
-
|
|
54425
|
-
|
|
54426
|
-
|
|
54427
|
-
|
|
54428
|
-
|
|
54429
|
-
|
|
54430
|
-
|
|
54431
|
-
|
|
54432
|
-
|
|
54433
|
-
|
|
54434
|
-
|
|
54435
|
-
|
|
54436
|
-
|
|
54437
|
-
|
|
54438
|
-
|
|
54439
|
-
|
|
54440
|
-
|
|
54441
|
-
|
|
54442
|
-
// this._bytesWrote = line.length
|
|
54443
|
-
// this._writer.write(line)
|
|
54444
|
-
// } else {
|
|
54445
|
-
// this._writer.write(line)
|
|
54446
|
-
// // add to the counter the bytes wrote
|
|
54447
|
-
// this._bytesWrote = this._bytesWrote + line.length
|
|
54448
|
-
// }
|
|
54449
|
-
// } else {
|
|
54450
|
-
// this._writer.write(line)
|
|
54451
|
-
// }
|
|
54452
|
-
// } else {
|
|
54453
|
-
// // add a new file and write
|
|
54454
|
-
// this._writer = createWriteStream(this._mainPath)
|
|
54455
|
-
// this._writer.write(line)
|
|
54456
|
-
// }
|
|
54457
|
-
// }
|
|
54458
|
-
// return
|
|
54459
|
-
// }
|
|
54460
|
-
/**
|
|
54461
|
-
* Terminate the last WriteStream.
|
|
54462
|
-
*/
|
|
54463
|
-
this.end = () => {
|
|
54464
|
-
this._writer.end();
|
|
54465
|
-
this._writer.close();
|
|
55749
|
+
};
|
|
55750
|
+
/**
|
|
55751
|
+
* The label of one of a bucket's files: the sanitized key value, the rotation index, or both
|
|
55752
|
+
* joined with an underscore — so a size split counts "0", "1", "2", a key split reads "italy",
|
|
55753
|
+
* and the two together read "italy_0", "italy_1".
|
|
55754
|
+
*/
|
|
55755
|
+
this._composeLabel = (bucket, pass, fileIndex) => {
|
|
55756
|
+
const segments = [
|
|
55757
|
+
pass.keys.length > 0 ? bucket.label : null,
|
|
55758
|
+
pass.maxFileBytes ? String(fileIndex ?? bucket.fileIndex) : null
|
|
55759
|
+
];
|
|
55760
|
+
return segments.filter((x2) => x2 !== null).join("_");
|
|
55761
|
+
};
|
|
55762
|
+
/**
|
|
55763
|
+
* Every file the pass wrote, in the order the buckets were first seen and then by rotation.
|
|
55764
|
+
* Derived from the counters rather than read back off the disk, so the export never depends on
|
|
55765
|
+
* directory ordering.
|
|
55766
|
+
*/
|
|
55767
|
+
this._listFiles = (pass) => {
|
|
55768
|
+
const files = [];
|
|
55769
|
+
for (const bucket of pass.buckets.values()) {
|
|
55770
|
+
for (let fileIndex = 0; fileIndex <= bucket.fileIndex; fileIndex++) {
|
|
55771
|
+
const label = this._composeLabel(bucket, pass, fileIndex);
|
|
55772
|
+
files.push({ path: this.getCompletedPath(pass.sourcePath, label), label });
|
|
55773
|
+
}
|
|
55774
|
+
}
|
|
55775
|
+
return files;
|
|
54466
55776
|
};
|
|
54467
55777
|
}
|
|
54468
55778
|
};
|
|
@@ -54519,14 +55829,14 @@ var init_ExecutorProgress2 = __esm({
|
|
|
54519
55829
|
});
|
|
54520
55830
|
|
|
54521
55831
|
// ../../packages/executors/src/OrchestratorHelper.ts
|
|
54522
|
-
var import_os2,
|
|
55832
|
+
var import_os2, import_path19, OrchestratorHelper, OrchestratorHelper_default;
|
|
54523
55833
|
var init_OrchestratorHelper = __esm({
|
|
54524
55834
|
"../../packages/executors/src/OrchestratorHelper.ts"() {
|
|
54525
55835
|
init_src();
|
|
54526
55836
|
init_src6();
|
|
54527
55837
|
import_os2 = __toESM(require("os"));
|
|
54528
55838
|
init_src4();
|
|
54529
|
-
|
|
55839
|
+
import_path19 = __toESM(require("path"));
|
|
54530
55840
|
init_src3();
|
|
54531
55841
|
OrchestratorHelper = {
|
|
54532
55842
|
getMemoryUsage: () => {
|
|
@@ -54618,14 +55928,14 @@ var init_OrchestratorHelper = __esm({
|
|
|
54618
55928
|
getPhysicalWorkerPath: () => {
|
|
54619
55929
|
const currentDir = __dirname;
|
|
54620
55930
|
if (ProcessENVManager_default.getEnvVariable("NODE_ENV") === "dev" || ProcessENVManager_default.getEnvVariable("NODE_ENV") === "development")
|
|
54621
|
-
return
|
|
55931
|
+
return import_path19.default.resolve("./.build/workers");
|
|
54622
55932
|
const forcedPath = ProcessENVManager_default.getEnvVariable("REMORA_WORKERS_PATH");
|
|
54623
55933
|
if (forcedPath && forcedPath.length > 0)
|
|
54624
|
-
return
|
|
55934
|
+
return import_path19.default.join(__dirname, forcedPath);
|
|
54625
55935
|
if (!currentDir.includes(".build")) {
|
|
54626
|
-
return
|
|
55936
|
+
return import_path19.default.join(__dirname, "../workers");
|
|
54627
55937
|
} else {
|
|
54628
|
-
return
|
|
55938
|
+
return import_path19.default.resolve("./.build/workers");
|
|
54629
55939
|
}
|
|
54630
55940
|
},
|
|
54631
55941
|
/**
|
|
@@ -54660,6 +55970,29 @@ var init_OrchestratorHelper = __esm({
|
|
|
54660
55970
|
const ceiling = Number(raw);
|
|
54661
55971
|
Affirm_default(Number.isInteger(ceiling) && ceiling >= 1, `REMORA_MAX_WORKERS must be a whole number >= 1, got "${raw}"`);
|
|
54662
55972
|
return ceiling;
|
|
55973
|
+
},
|
|
55974
|
+
/**
|
|
55975
|
+
* The consumer's splitting, normalized for the run: `maximumFileSize` is the older spelling of
|
|
55976
|
+
* `split.maxFileSizeGb` and is folded in here, so nothing downstream of the scope has to know
|
|
55977
|
+
* there are two. Setting both is rejected at validation, not silently preferred.
|
|
55978
|
+
*
|
|
55979
|
+
* Answers undefined when the consumer asks for no splitting, which is what makes the split pass
|
|
55980
|
+
* a no-op rather than a decision the orchestrator has to take.
|
|
55981
|
+
*/
|
|
55982
|
+
resolveSplit: (consumer) => {
|
|
55983
|
+
Affirm_default(consumer, "Invalid consumer");
|
|
55984
|
+
const by = consumer.split?.by ?? [];
|
|
55985
|
+
const maxFileSizeGb = consumer.split?.maxFileSizeGb ?? consumer.maximumFileSize;
|
|
55986
|
+
if (by.length === 0 && !(maxFileSizeGb > 0))
|
|
55987
|
+
return void 0;
|
|
55988
|
+
return { by: by.length > 0 ? by : void 0, maxFileSizeGb: maxFileSizeGb > 0 ? maxFileSizeGb : void 0 };
|
|
55989
|
+
},
|
|
55990
|
+
/** How a run's splitting reads in the log: the keys, the cap, or both. */
|
|
55991
|
+
formatSplit: (split2) => {
|
|
55992
|
+
return [
|
|
55993
|
+
split2.by?.length ? `by ${split2.by.join(", ")}` : null,
|
|
55994
|
+
split2.maxFileSizeGb ? `max ${split2.maxFileSizeGb}Gb per file` : null
|
|
55995
|
+
].filter((x2) => !!x2).join(", ");
|
|
54663
55996
|
}
|
|
54664
55997
|
};
|
|
54665
55998
|
OrchestratorHelper_default = OrchestratorHelper;
|
|
@@ -54667,13 +56000,12 @@ var init_OrchestratorHelper = __esm({
|
|
|
54667
56000
|
});
|
|
54668
56001
|
|
|
54669
56002
|
// ../../packages/executors/src/LookupResolver.ts
|
|
54670
|
-
var
|
|
56003
|
+
var import_fs20, import_promises17, import_path20, LookupResolverClass, LookupResolver, LookupResolver_default;
|
|
54671
56004
|
var init_LookupResolver = __esm({
|
|
54672
56005
|
"../../packages/executors/src/LookupResolver.ts"() {
|
|
54673
|
-
|
|
54674
|
-
|
|
54675
|
-
|
|
54676
|
-
import_readline9 = __toESM(require("readline"));
|
|
56006
|
+
import_fs20 = __toESM(require("fs"));
|
|
56007
|
+
import_promises17 = __toESM(require("fs/promises"));
|
|
56008
|
+
import_path20 = __toESM(require("path"));
|
|
54677
56009
|
init_src();
|
|
54678
56010
|
init_src3();
|
|
54679
56011
|
init_src4();
|
|
@@ -54712,11 +56044,11 @@ var init_LookupResolver = __esm({
|
|
|
54712
56044
|
this.cache.set(id, await this.buildDict(lookupConsumer, capturePath, ref));
|
|
54713
56045
|
}
|
|
54714
56046
|
ExecutorScope_default.ensurePath(outPath);
|
|
54715
|
-
await
|
|
54716
|
-
manifest.push({ id, consumer: ref.consumer, key: ref.key, value: ref.value, path:
|
|
56047
|
+
await import_promises17.default.writeFile(outPath, JSON.stringify(this.cache.get(id)));
|
|
56048
|
+
manifest.push({ id, consumer: ref.consumer, key: ref.key, value: ref.value, path: import_path20.default.resolve(outPath) });
|
|
54717
56049
|
}
|
|
54718
56050
|
for (const capturePath of captureByConsumer.values())
|
|
54719
|
-
await
|
|
56051
|
+
await import_promises17.default.rm(capturePath, { force: true });
|
|
54720
56052
|
return manifest;
|
|
54721
56053
|
};
|
|
54722
56054
|
// Runs the source consumer through the real pipeline, capturing its merged output before cleanup.
|
|
@@ -54742,8 +56074,7 @@ var init_LookupResolver = __esm({
|
|
|
54742
56074
|
const dict = {};
|
|
54743
56075
|
let rowsRead = 0;
|
|
54744
56076
|
let warnedDup = false;
|
|
54745
|
-
const
|
|
54746
|
-
for await (const line of lineStream) {
|
|
56077
|
+
for await (const line of readLines(import_fs20.default.createReadStream(capturePath))) {
|
|
54747
56078
|
if (!line.length) continue;
|
|
54748
56079
|
rowsRead++;
|
|
54749
56080
|
Affirm_default(rowsRead <= Constants_default.defaults.MAX_LOOKUP_ROWS, `code_lookup table from consumer "${ref.consumer}" exceeds MAX_LOOKUP_ROWS (${Constants_default.defaults.MAX_LOOKUP_ROWS})`);
|
|
@@ -54763,8 +56094,8 @@ var init_LookupResolver = __esm({
|
|
|
54763
56094
|
return JSON.parse(line);
|
|
54764
56095
|
};
|
|
54765
56096
|
this.getCapturePath = (scope, consumerName) => {
|
|
54766
|
-
const dir =
|
|
54767
|
-
return
|
|
56097
|
+
const dir = import_path20.default.dirname(ExecutorScope_default.getLookupPath(scope, "x"));
|
|
56098
|
+
return import_path20.default.join(dir, `${consumerName}.capture`);
|
|
54768
56099
|
};
|
|
54769
56100
|
this.dedupe = (refs) => {
|
|
54770
56101
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -54786,12 +56117,12 @@ var init_LookupResolver = __esm({
|
|
|
54786
56117
|
});
|
|
54787
56118
|
|
|
54788
56119
|
// ../../packages/executors/src/ConsumerInputResolver.ts
|
|
54789
|
-
var
|
|
56120
|
+
var import_fs21, import_promises18, import_path21, ConsumerInputResolverClass, ConsumerInputResolver, ConsumerInputResolver_default;
|
|
54790
56121
|
var init_ConsumerInputResolver = __esm({
|
|
54791
56122
|
"../../packages/executors/src/ConsumerInputResolver.ts"() {
|
|
54792
|
-
|
|
54793
|
-
|
|
54794
|
-
|
|
56123
|
+
import_fs21 = __toESM(require("fs"));
|
|
56124
|
+
import_promises18 = __toESM(require("fs/promises"));
|
|
56125
|
+
import_path21 = __toESM(require("path"));
|
|
54795
56126
|
init_src();
|
|
54796
56127
|
init_src3();
|
|
54797
56128
|
init_src4();
|
|
@@ -54814,15 +56145,15 @@ var init_ConsumerInputResolver = __esm({
|
|
|
54814
56145
|
Affirm_default(stack.length <= Constants_default.defaults.MAX_CONSUMER_INPUT_DEPTH, `consumer-as-input nesting exceeds MAX_CONSUMER_INPUT_DEPTH (${Constants_default.defaults.MAX_CONSUMER_INPUT_DEPTH}): ${stack.join(" -> ")}`);
|
|
54815
56146
|
for (const name of upstreamNames) {
|
|
54816
56147
|
const cached = this.cache.get(name);
|
|
54817
|
-
if (!cached || !
|
|
56148
|
+
if (!cached || !import_fs21.default.existsSync(cached.path)) {
|
|
54818
56149
|
Affirm_default(!stack.includes(name), `consumer-as-input cycle detected: ${[...stack, name].join(" -> ")}`);
|
|
54819
56150
|
const upstream = Environment_default.getConsumer(name);
|
|
54820
56151
|
Affirm_default(upstream, `consumer-as-input references unknown consumer "${name}"`);
|
|
54821
56152
|
const capturePath = ExecutorScope_default.getConsumerInputPath(scope, name);
|
|
54822
56153
|
ExecutorScope_default.ensurePath(capturePath);
|
|
54823
56154
|
await this.runUpstreamConsumer(upstream, capturePath, stack, request);
|
|
54824
|
-
const { size } = await
|
|
54825
|
-
this.cache.set(name, { consumer: name, path:
|
|
56155
|
+
const { size } = await import_promises18.default.stat(capturePath);
|
|
56156
|
+
this.cache.set(name, { consumer: name, path: import_path21.default.resolve(capturePath), fileSize: size });
|
|
54826
56157
|
}
|
|
54827
56158
|
manifest.set(name, this.cache.get(name));
|
|
54828
56159
|
}
|
|
@@ -54884,13 +56215,12 @@ var ExecutorOrchestrator_exports = {};
|
|
|
54884
56215
|
__export(ExecutorOrchestrator_exports, {
|
|
54885
56216
|
default: () => ExecutorOrchestrator_default
|
|
54886
56217
|
});
|
|
54887
|
-
var
|
|
56218
|
+
var import_fs22, import_promises19, import_path22, import_workerpool, import_promises20, ExecutorOrchestratorClass, ExecutorOrchestrator, ExecutorOrchestrator_default;
|
|
54888
56219
|
var init_ExecutorOrchestrator = __esm({
|
|
54889
56220
|
"../../packages/executors/src/ExecutorOrchestrator.ts"() {
|
|
54890
|
-
|
|
54891
|
-
|
|
54892
|
-
|
|
54893
|
-
import_readline10 = __toESM(require("readline"));
|
|
56221
|
+
import_fs22 = __toESM(require("fs"));
|
|
56222
|
+
import_promises19 = __toESM(require("fs/promises"));
|
|
56223
|
+
import_path22 = __toESM(require("path"));
|
|
54894
56224
|
import_workerpool = __toESM(require("workerpool"));
|
|
54895
56225
|
init_src();
|
|
54896
56226
|
init_src10();
|
|
@@ -54906,7 +56236,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
54906
56236
|
init_src();
|
|
54907
56237
|
init_src10();
|
|
54908
56238
|
init_ExecutorWriter();
|
|
54909
|
-
|
|
56239
|
+
import_promises20 = require("stream/promises");
|
|
54910
56240
|
init_src2();
|
|
54911
56241
|
init_ExecutorProgress2();
|
|
54912
56242
|
init_src5();
|
|
@@ -54945,7 +56275,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
54945
56275
|
};
|
|
54946
56276
|
const workerPath = OrchestratorHelper_default.getPhysicalWorkerPath();
|
|
54947
56277
|
Logger_default.info(`Initializing worker pool from ${workerPath} (workers: ${maxWorkers}, heap limit: ${Constants_default.defaults.MIN_RUNTIME_HEAP_MB}MB) | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
54948
|
-
return import_workerpool.default.pool(
|
|
56278
|
+
return import_workerpool.default.pool(import_path22.default.join(workerPath, "ExecutorWorker.js"), options2);
|
|
54949
56279
|
};
|
|
54950
56280
|
this.launch = async (request) => {
|
|
54951
56281
|
Affirm_default(request, "Invalid options");
|
|
@@ -54957,7 +56287,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
54957
56287
|
const tracker = new ExecutorPerformance_default();
|
|
54958
56288
|
const _progress = new ExecutorProgress2_default(logProgress);
|
|
54959
56289
|
const { usageId } = UsageManager_default.startUsage(consumer, details);
|
|
54960
|
-
const scope = { id: usageId, folder: `${consumer.name}_${usageId}`, workersId: [],
|
|
56290
|
+
const scope = { id: usageId, folder: `${consumer.name}_${usageId}`, workersId: [], split: OrchestratorHelper_default.resolveSplit(consumer) };
|
|
54961
56291
|
const expandedFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
54962
56292
|
const census = { created: 0, peak: 0 };
|
|
54963
56293
|
try {
|
|
@@ -55087,20 +56417,23 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55087
56417
|
await Logger_default.flush();
|
|
55088
56418
|
return materializeResult;
|
|
55089
56419
|
}
|
|
55090
|
-
|
|
55091
|
-
|
|
55092
|
-
|
|
55093
|
-
|
|
55094
|
-
|
|
55095
|
-
|
|
55096
|
-
|
|
55097
|
-
|
|
56420
|
+
counter = performance.now();
|
|
56421
|
+
const splitFiles = await new ExecutorWriter_default().splitOutput({
|
|
56422
|
+
scope,
|
|
56423
|
+
sourcePath: ExecutorScope_default.getMainPath(scope),
|
|
56424
|
+
consumer,
|
|
56425
|
+
fields: expandedFields
|
|
56426
|
+
});
|
|
56427
|
+
tracker.measure("split-output", performance.now() - counter);
|
|
56428
|
+
if (scope.split)
|
|
56429
|
+
Logger_default.log(`[${usageId}] Split output into ${splitFiles.length} file(s) (${OrchestratorHelper_default.formatSplit(scope.split)}) in ${Math.round(performance.now() - counter)}ms`);
|
|
55098
56430
|
_progress.update({ phase: "Post-processing", progress: 1 });
|
|
55099
56431
|
_progress.update({ phase: "Exporting result", progress: 0 });
|
|
55100
56432
|
counter = performance.now();
|
|
55101
56433
|
Logger_default.info(`[${usageId}] Exporting results to ${consumer.outputs.length} output(s)`);
|
|
55102
|
-
const exportRes = await OutputExecutor_default.exportResult(consumer, expandedFields,
|
|
56434
|
+
const exportRes = await OutputExecutor_default.exportResult(consumer, expandedFields, splitFiles);
|
|
55103
56435
|
tracker.measure("export-result", performance.now() - counter);
|
|
56436
|
+
Affirm_default(exportRes.res, `Consumer "${consumer.name}" failed to write its output to "${consumer.outputs.map((x2) => x2.exportDestination).join(", ")}" \u2014 see the logged driver error for the cause`);
|
|
55104
56437
|
Logger_default.info(`[${usageId}] Export complete in ${Math.round(performance.now() - counter)}ms (key: ${exportRes.key})`);
|
|
55105
56438
|
_progress.update({ phase: "Exporting result", progress: 1 });
|
|
55106
56439
|
if (consumer.outputs.some((x2) => x2.onSuccess)) {
|
|
@@ -55146,7 +56479,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55146
56479
|
Logger_default.info(`[${usageId}] Producer "${prod.name}": source returned no files, skipping`);
|
|
55147
56480
|
continue;
|
|
55148
56481
|
}
|
|
55149
|
-
if (!
|
|
56482
|
+
if (!import_fs22.default.existsSync(response.files[0].fullUri)) {
|
|
55150
56483
|
if (!cProd.isOptional)
|
|
55151
56484
|
throw new Error(`Expected data file ${response.files[0].fullUri} of producer ${prod.name} in consumer ${consumer.name} is missing.`);
|
|
55152
56485
|
Logger_default.info(`[${usageId}] Producer "${prod.name}": data file missing but marked optional, skipping`);
|
|
@@ -55308,7 +56641,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55308
56641
|
* Returns a single chunk for small files where parallelism overhead isn't worth it.
|
|
55309
56642
|
*/
|
|
55310
56643
|
this.scopeWork = (fileUri, numChunks) => {
|
|
55311
|
-
const fileSize =
|
|
56644
|
+
const fileSize = import_fs22.default.statSync(fileUri).size;
|
|
55312
56645
|
if (fileSize === 0) return [];
|
|
55313
56646
|
if (fileSize < Constants_default.defaults.MIN_FILE_SIZE_FOR_PARALLEL) {
|
|
55314
56647
|
return [{ start: 0, end: fileSize, isFirstChunk: true, fileUri, index: 0 }];
|
|
@@ -55318,7 +56651,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55318
56651
|
const effectiveChunks = Math.min(targetWorkers, maxChunksBySize);
|
|
55319
56652
|
if (effectiveChunks <= 1) return [{ start: 0, end: fileSize, isFirstChunk: true, fileUri, index: 0 }];
|
|
55320
56653
|
const targetChunkSize = Math.floor(fileSize / effectiveChunks);
|
|
55321
|
-
const fd =
|
|
56654
|
+
const fd = import_fs22.default.openSync(fileUri, "r");
|
|
55322
56655
|
try {
|
|
55323
56656
|
const offsets = [];
|
|
55324
56657
|
let currentStart = 0;
|
|
@@ -55336,7 +56669,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55336
56669
|
}
|
|
55337
56670
|
return offsets;
|
|
55338
56671
|
} finally {
|
|
55339
|
-
|
|
56672
|
+
import_fs22.default.closeSync(fd);
|
|
55340
56673
|
}
|
|
55341
56674
|
};
|
|
55342
56675
|
/**
|
|
@@ -55404,7 +56737,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55404
56737
|
let currentPos = position;
|
|
55405
56738
|
while (currentPos < fileSize) {
|
|
55406
56739
|
const bytesToRead = Math.min(BUFFER_SIZE, fileSize - currentPos);
|
|
55407
|
-
const bytesRead =
|
|
56740
|
+
const bytesRead = import_fs22.default.readSync(fd, buffer, 0, bytesToRead, currentPos);
|
|
55408
56741
|
if (bytesRead === 0) break;
|
|
55409
56742
|
for (let i6 = 0; i6 < bytesRead; i6++) {
|
|
55410
56743
|
if (buffer[i6] === 10) {
|
|
@@ -55423,17 +56756,16 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55423
56756
|
* format has no header row, so each row is reprojected. Without a captureFormat it's a verbatim copy.
|
|
55424
56757
|
*/
|
|
55425
56758
|
this.captureDataset = async (consumer, fields, sourcePath, targetPath, captureFormat) => {
|
|
55426
|
-
await
|
|
56759
|
+
await import_promises19.default.mkdir(import_path22.default.dirname(targetPath), { recursive: true });
|
|
55427
56760
|
const internalFormat = OutputExecutor_default._getInternalRecordFormat(consumer);
|
|
55428
56761
|
if (captureFormat !== "JSONL" || internalFormat !== "CSV") {
|
|
55429
|
-
await
|
|
56762
|
+
await import_promises19.default.copyFile(sourcePath, targetPath);
|
|
55430
56763
|
return;
|
|
55431
56764
|
}
|
|
55432
56765
|
const visibleFields = fields.filter((field) => !field.cField.hidden);
|
|
55433
|
-
const
|
|
55434
|
-
const output = import_fs20.default.createWriteStream(targetPath);
|
|
56766
|
+
const output = import_fs22.default.createWriteStream(targetPath);
|
|
55435
56767
|
try {
|
|
55436
|
-
for await (const line of
|
|
56768
|
+
for await (const line of readLines(import_fs22.default.createReadStream(sourcePath))) {
|
|
55437
56769
|
if (!line.length) continue;
|
|
55438
56770
|
const record = LineParser_default._internalParseCSV(line, visibleFields, ",");
|
|
55439
56771
|
if (!output.write(JSON.stringify(record) + "\n"))
|
|
@@ -55521,7 +56853,7 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55521
56853
|
const decodedFiles = [];
|
|
55522
56854
|
let decodedCount = 0;
|
|
55523
56855
|
for (const file of response.files) {
|
|
55524
|
-
const inferredType = fileType ??
|
|
56856
|
+
const inferredType = fileType ?? import_path22.default.extname(file.fullUri).replace(".", "").toUpperCase();
|
|
55525
56857
|
const decodedPath = `${file.fullUri}.decoded.csv`;
|
|
55526
56858
|
if (inferredType === "XLS" || inferredType === "XLSX") {
|
|
55527
56859
|
const xlsCsvStream = await XLSParser_default.getStreamXls(file.fullUri, {
|
|
@@ -55529,22 +56861,22 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55529
56861
|
startRow: prod.settings.startRow,
|
|
55530
56862
|
startColumn: prod.settings.startColumn
|
|
55531
56863
|
});
|
|
55532
|
-
await (0,
|
|
56864
|
+
await (0, import_promises20.pipeline)(
|
|
55533
56865
|
xlsCsvStream,
|
|
55534
|
-
|
|
56866
|
+
import_fs22.default.createWriteStream(decodedPath)
|
|
55535
56867
|
);
|
|
55536
|
-
const fileStats = await
|
|
56868
|
+
const fileStats = await import_promises19.default.stat(decodedPath);
|
|
55537
56869
|
decodedFiles.push({ fullUri: decodedPath, fileSize: fileStats.size });
|
|
55538
56870
|
decodedCount++;
|
|
55539
56871
|
reportFile();
|
|
55540
56872
|
continue;
|
|
55541
56873
|
}
|
|
55542
56874
|
if (inferredType === "XML") {
|
|
55543
|
-
const fileContent = await
|
|
56875
|
+
const fileContent = await import_promises19.default.readFile(file.fullUri, "utf-8");
|
|
55544
56876
|
const jsonData = XMLParser_default.xmlToJson(fileContent);
|
|
55545
56877
|
const records = normalizeXmlRows(jsonData);
|
|
55546
56878
|
if (records.length === 0) {
|
|
55547
|
-
await
|
|
56879
|
+
await import_promises19.default.writeFile(decodedPath, "", "utf-8");
|
|
55548
56880
|
} else {
|
|
55549
56881
|
const columns = [];
|
|
55550
56882
|
for (const record of records) {
|
|
@@ -55560,9 +56892,9 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55560
56892
|
const row = columns.map((column) => csvSafeValue(record[column]));
|
|
55561
56893
|
lines.push(CSVParser_default.stringifyRow(row));
|
|
55562
56894
|
}
|
|
55563
|
-
await
|
|
56895
|
+
await import_promises19.default.writeFile(decodedPath, lines.join("\n"), "utf-8");
|
|
55564
56896
|
}
|
|
55565
|
-
const fileStats = await
|
|
56897
|
+
const fileStats = await import_promises19.default.stat(decodedPath);
|
|
55566
56898
|
decodedFiles.push({ fullUri: decodedPath, fileSize: fileStats.size });
|
|
55567
56899
|
decodedCount++;
|
|
55568
56900
|
reportFile();
|
|
@@ -55590,20 +56922,20 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55590
56922
|
Logger_default.log(`[${scope.id}] Merging ${executorResults.length} worker output files into ${mainPath}`);
|
|
55591
56923
|
const perf = performance.now();
|
|
55592
56924
|
for (const workerResult of executorResults) {
|
|
55593
|
-
await (0,
|
|
55594
|
-
|
|
55595
|
-
|
|
56925
|
+
await (0, import_promises20.pipeline)(
|
|
56926
|
+
import_fs22.default.createReadStream(workerResult.resultUri),
|
|
56927
|
+
import_fs22.default.createWriteStream(mainPath, { flags: "a" })
|
|
55596
56928
|
);
|
|
55597
|
-
await
|
|
56929
|
+
await import_promises19.default.unlink(workerResult.resultUri);
|
|
55598
56930
|
}
|
|
55599
56931
|
tracker.measure("merge-workers", performance.now() - perf);
|
|
55600
56932
|
Logger_default.log(`[${scope.id}] Merge complete in ${Math.round(performance.now() - perf)}ms`);
|
|
55601
56933
|
} else if (executorResults.length === 1) {
|
|
55602
56934
|
Logger_default.log(`[${scope.id}] Single worker \u2014 renaming output to ${mainPath}`);
|
|
55603
|
-
await
|
|
56935
|
+
await import_promises19.default.rename(executorResults[0].resultUri, mainPath);
|
|
55604
56936
|
} else {
|
|
55605
56937
|
Logger_default.log(`[${scope.id}] No worker output \u2014 writing an empty dataset to ${mainPath}`);
|
|
55606
|
-
await
|
|
56938
|
+
await import_promises19.default.writeFile(mainPath, "");
|
|
55607
56939
|
}
|
|
55608
56940
|
return mainPath;
|
|
55609
56941
|
};
|
|
@@ -55625,54 +56957,12 @@ var init_ExecutorOrchestrator = __esm({
|
|
|
55625
56957
|
}
|
|
55626
56958
|
});
|
|
55627
56959
|
|
|
55628
|
-
// ../../packages/executors/src/BatchWriter.ts
|
|
55629
|
-
init_src();
|
|
55630
|
-
var FLUSH_AFTER_CHARS = 1 << 18;
|
|
55631
|
-
var BatchWriter = class {
|
|
55632
|
-
constructor(stream) {
|
|
55633
|
-
/** How many times the stream's buffer filled and the caller had to wait for it. */
|
|
55634
|
-
this.drains = 0;
|
|
55635
|
-
this.buffer = "";
|
|
55636
|
-
/** Queues one line and its newline. False means a batch went out and the stream wants a drain. */
|
|
55637
|
-
this.write = (line) => {
|
|
55638
|
-
this.buffer += line;
|
|
55639
|
-
this.buffer += "\n";
|
|
55640
|
-
if (this.buffer.length < FLUSH_AFTER_CHARS)
|
|
55641
|
-
return true;
|
|
55642
|
-
return this.flush();
|
|
55643
|
-
};
|
|
55644
|
-
this.waitForDrain = () => {
|
|
55645
|
-
this.drains++;
|
|
55646
|
-
return new Promise((resolve) => this.stream.once("drain", resolve));
|
|
55647
|
-
};
|
|
55648
|
-
/** Writes whatever is still buffered, then waits for the stream to finish. */
|
|
55649
|
-
this.close = async () => {
|
|
55650
|
-
this.flush();
|
|
55651
|
-
await new Promise((resolve, reject) => {
|
|
55652
|
-
this.stream.on("finish", resolve);
|
|
55653
|
-
this.stream.on("error", reject);
|
|
55654
|
-
this.stream.end();
|
|
55655
|
-
});
|
|
55656
|
-
};
|
|
55657
|
-
this.flush = () => {
|
|
55658
|
-
if (!this.buffer)
|
|
55659
|
-
return true;
|
|
55660
|
-
const batch = this.buffer;
|
|
55661
|
-
this.buffer = "";
|
|
55662
|
-
return this.stream.write(batch);
|
|
55663
|
-
};
|
|
55664
|
-
Affirm_default(stream, "Invalid write stream");
|
|
55665
|
-
this.stream = stream;
|
|
55666
|
-
}
|
|
55667
|
-
};
|
|
55668
|
-
var BatchWriter_default = BatchWriter;
|
|
55669
|
-
|
|
55670
56960
|
// ../../packages/executors/src/index.ts
|
|
56961
|
+
init_src();
|
|
55671
56962
|
init_ConsumerExecutor();
|
|
55672
56963
|
|
|
55673
56964
|
// ../../packages/executors/src/Executor.ts
|
|
55674
|
-
var
|
|
55675
|
-
var import_readline7 = __toESM(require("readline"));
|
|
56965
|
+
var import_fs19 = __toESM(require("fs"));
|
|
55676
56966
|
init_ProducerExecutor();
|
|
55677
56967
|
init_ConsumerExecutor();
|
|
55678
56968
|
|
|
@@ -55856,10 +57146,9 @@ var Executor = class {
|
|
|
55856
57146
|
let processedBytes = 0;
|
|
55857
57147
|
Logger_default.log(`[${workerId}] Opened streams \u2014 chunk ${start}-${end} (${Math.round(totalBytes / 1024)}KB), ${fields.length} field(s)`);
|
|
55858
57148
|
Logger_default.log(`[${workerId}] Starting line-by-line processing`);
|
|
55859
|
-
const lineStream = import_readline7.default.createInterface({ input: readStream, crlfDelay: Infinity });
|
|
55860
57149
|
const isDebug = Logger_default._level === "debug";
|
|
55861
57150
|
try {
|
|
55862
|
-
for await (const line of
|
|
57151
|
+
for await (const line of readLines(readStream)) {
|
|
55863
57152
|
if (lineIndex === 0 && isFirstChunk) {
|
|
55864
57153
|
if (!this.shouldProcessFirstLine(producer)) {
|
|
55865
57154
|
lineIndex++;
|
|
@@ -55963,18 +57252,18 @@ var Executor = class {
|
|
|
55963
57252
|
if (!lookups?.length) return;
|
|
55964
57253
|
for (const entry of lookups) {
|
|
55965
57254
|
if (LookupRegistry_default.has(entry.id)) continue;
|
|
55966
|
-
const table = JSON.parse(
|
|
57255
|
+
const table = JSON.parse(import_fs19.default.readFileSync(entry.path, "utf-8"));
|
|
55967
57256
|
LookupRegistry_default.set(entry.id, table);
|
|
55968
57257
|
Logger_default.log(`[lookup] Hydrated table "${entry.consumer}" (${entry.key}->${entry.value}) from ${entry.path}`);
|
|
55969
57258
|
}
|
|
55970
57259
|
};
|
|
55971
57260
|
this.openReadStream = (chunk) => {
|
|
55972
57261
|
const { end, fileUri, start } = chunk;
|
|
55973
|
-
return
|
|
57262
|
+
return import_fs19.default.createReadStream(fileUri, { start, end: end - 1 });
|
|
55974
57263
|
};
|
|
55975
57264
|
this.openWriteStream = (scope, workerId) => {
|
|
55976
57265
|
const workerPath = ExecutorScope_default.getWorkerPath(scope, workerId);
|
|
55977
|
-
return new BatchWriter_default(
|
|
57266
|
+
return new BatchWriter_default(import_fs19.default.createWriteStream(workerPath));
|
|
55978
57267
|
};
|
|
55979
57268
|
// The first line of the first chunk is data unless the file type carries a header there.
|
|
55980
57269
|
this.shouldProcessFirstLine = (producer) => {
|
|
@@ -56080,10 +57369,9 @@ init_OutputExecutor();
|
|
|
56080
57369
|
init_ProducerExecutor();
|
|
56081
57370
|
|
|
56082
57371
|
// ../../packages/executors/src/SampleRunner.ts
|
|
56083
|
-
var
|
|
56084
|
-
var
|
|
56085
|
-
var
|
|
56086
|
-
var import_readline11 = __toESM(require("readline"));
|
|
57372
|
+
var import_fs23 = __toESM(require("fs"));
|
|
57373
|
+
var import_promises21 = __toESM(require("fs/promises"));
|
|
57374
|
+
var import_path23 = __toESM(require("path"));
|
|
56087
57375
|
init_src();
|
|
56088
57376
|
init_src3();
|
|
56089
57377
|
init_src4();
|
|
@@ -56128,7 +57416,7 @@ var SampleRunnerClass = class {
|
|
|
56128
57416
|
*/
|
|
56129
57417
|
this._runConsumer = async (consumer, size, details) => {
|
|
56130
57418
|
this._assertStageable(consumer);
|
|
56131
|
-
const capturePath =
|
|
57419
|
+
const capturePath = import_path23.default.resolve(import_path23.default.join(ExecutorScope_default.getBasePath(), `sample_${consumer.name}_${Helper_default.uuid()}.jsonl`));
|
|
56132
57420
|
ExecutorScope_default.ensurePath(capturePath);
|
|
56133
57421
|
try {
|
|
56134
57422
|
const result = await ExecutorOrchestrator_default.launch({
|
|
@@ -56155,7 +57443,7 @@ var SampleRunnerClass = class {
|
|
|
56155
57443
|
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.`
|
|
56156
57444
|
};
|
|
56157
57445
|
} finally {
|
|
56158
|
-
await
|
|
57446
|
+
await import_promises21.default.rm(capturePath, { force: true });
|
|
56159
57447
|
}
|
|
56160
57448
|
};
|
|
56161
57449
|
/**
|
|
@@ -56218,17 +57506,12 @@ var SampleRunnerClass = class {
|
|
|
56218
57506
|
};
|
|
56219
57507
|
/** The captured dataset, which `captureFormat: 'JSONL'` guarantees is one JSON object per line. */
|
|
56220
57508
|
this._readCapture = async (capturePath, size) => {
|
|
56221
|
-
if (!
|
|
57509
|
+
if (!import_fs23.default.existsSync(capturePath)) return [];
|
|
56222
57510
|
const records = [];
|
|
56223
|
-
const
|
|
56224
|
-
|
|
56225
|
-
|
|
56226
|
-
|
|
56227
|
-
records.push(JSON.parse(line));
|
|
56228
|
-
if (records.length >= size) break;
|
|
56229
|
-
}
|
|
56230
|
-
} finally {
|
|
56231
|
-
lines.close();
|
|
57511
|
+
for await (const line of readLines(import_fs23.default.createReadStream(capturePath))) {
|
|
57512
|
+
if (!line.length) continue;
|
|
57513
|
+
records.push(JSON.parse(line));
|
|
57514
|
+
if (records.length >= size) break;
|
|
56232
57515
|
}
|
|
56233
57516
|
return records;
|
|
56234
57517
|
};
|