@forzalabs/remora 1.2.10 → 1.2.11
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 +7 -0
- package/index.js +160 -22
- package/json_schemas/consumer-schema.json +4 -0
- package/package.json +1 -1
- package/workers/ExecutorWorker.js +165 -22
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## V 1.2.11 - 2026-06-30
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Added `lineCount` as a consumer field property, allowing consumers to emit a consistent 1-based source line number even when files are processed across multiple workers
|
|
13
|
+
- Added a dedicated `ConsumerExecutor` post-processing pass to normalize `lineCount` values after worker merge, with tracked execution timing similar to other dataset-level steps
|
|
14
|
+
- Added `lineCount` test coverage to canary and CLI consumer configurations for easier verification of the feature
|
|
15
|
+
|
|
9
16
|
## V 1.2.2 - 2026-04-10
|
|
10
17
|
|
|
11
18
|
### Added
|
package/index.js
CHANGED
|
@@ -18751,7 +18751,7 @@ var import_promises = __toESM(require("fs/promises"), 1);
|
|
|
18751
18751
|
|
|
18752
18752
|
// ../../packages/constants/src/Constants.ts
|
|
18753
18753
|
var CONSTANTS = {
|
|
18754
|
-
cliVersion: "1.2.
|
|
18754
|
+
cliVersion: "1.2.11",
|
|
18755
18755
|
backendVersion: 1,
|
|
18756
18756
|
backendPort: 5088,
|
|
18757
18757
|
workerVersion: 2,
|
|
@@ -19191,7 +19191,16 @@ var ValidatorClass = class {
|
|
|
19191
19191
|
}
|
|
19192
19192
|
const allAvailableFields = [...availableFieldsByProducer.values()].flat();
|
|
19193
19193
|
for (const field of consumer.fields) {
|
|
19194
|
-
|
|
19194
|
+
const syntheticFieldModes = [field.fixed === true, !!field.copyFrom, field.lineCount === true].filter(Boolean);
|
|
19195
|
+
if (syntheticFieldModes.length > 1)
|
|
19196
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" can only use one synthetic field mode among "fixed", "copyFrom", and "lineCount".`);
|
|
19197
|
+
if (field.lineCount && field.key === "*")
|
|
19198
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "lineCount" with the wildcard key "*".`);
|
|
19199
|
+
if (field.lineCount && field.transform)
|
|
19200
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "transform" together with "lineCount".`);
|
|
19201
|
+
if (field.lineCount && field.validate && field.validate.length > 0)
|
|
19202
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "validate" together with "lineCount".`);
|
|
19203
|
+
if (field.key === "*" || field.fixed || field.copyFrom || field.lineCount) continue;
|
|
19195
19204
|
if (field.from) {
|
|
19196
19205
|
const producerFields = availableFieldsByProducer.get(field.from);
|
|
19197
19206
|
if (producerFields && !producerFields.includes(field.key))
|
|
@@ -19212,6 +19221,8 @@ var ValidatorClass = class {
|
|
|
19212
19221
|
const found = precedingFields.find((f) => (f.alias ?? f.key) === field.copyFrom);
|
|
19213
19222
|
if (!found)
|
|
19214
19223
|
errors.push(`Field "${field.alias ?? field.key}" uses copyFrom "${field.copyFrom}" but no field with that name/alias exists before it in consumer "${consumer.name}".`);
|
|
19224
|
+
else if (found.lineCount)
|
|
19225
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use copyFrom on a field with "lineCount" because line counts are normalized after worker merge.`);
|
|
19215
19226
|
}
|
|
19216
19227
|
if (consumer.filters && consumer.filters.length > 0) {
|
|
19217
19228
|
if (consumer.filters.some((x) => x.sql && x.rule))
|
|
@@ -19232,10 +19243,14 @@ var ValidatorClass = class {
|
|
|
19232
19243
|
const missingRules = rulesWithMatchingFields.filter((x) => !x.match);
|
|
19233
19244
|
errors.push(`Filter(s) on member(s) "${missingRules.map((x) => x.rule.rule.member).join(", ")}" is invalid since the member specified is not present in the consumer. Check the member value or add the missing field to the consumer.`);
|
|
19234
19245
|
}
|
|
19246
|
+
const lineCountRuleMembers = rulesWithMatchingFields.filter((x) => x.match?.lineCount);
|
|
19247
|
+
if (lineCountRuleMembers.length > 0)
|
|
19248
|
+
errors.push(`Consumer "${consumer.name}" cannot use rule-based filters on field(s) with "lineCount": ${lineCountRuleMembers.map((x) => `"${x.rule.rule.member}"`).join(", ")}.`);
|
|
19235
19249
|
}
|
|
19236
19250
|
const validateTransformations = (fields) => {
|
|
19237
19251
|
const errors2 = [];
|
|
19238
19252
|
const trxsFields = fields.filter((x) => x.transform);
|
|
19253
|
+
const lineCountFieldNames = new Set(fields.filter((x) => x.lineCount).map((x) => x.alias ?? x.key));
|
|
19239
19254
|
for (const field of trxsFields) {
|
|
19240
19255
|
const trxToValidate = [];
|
|
19241
19256
|
if (Array.isArray(field.transform))
|
|
@@ -19243,6 +19258,16 @@ var ValidatorClass = class {
|
|
|
19243
19258
|
else
|
|
19244
19259
|
trxToValidate.push(field.transform);
|
|
19245
19260
|
for (const trans of trxToValidate) {
|
|
19261
|
+
if ("multiplyBy" in trans) {
|
|
19262
|
+
const invalidFields = trans.multiplyBy.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19263
|
+
if (invalidFields.length > 0)
|
|
19264
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in multiplyBy: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19265
|
+
}
|
|
19266
|
+
if ("addBy" in trans) {
|
|
19267
|
+
const invalidFields = trans.addBy.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19268
|
+
if (invalidFields.length > 0)
|
|
19269
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in addBy: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19270
|
+
}
|
|
19246
19271
|
if ("combine_fields" in trans) {
|
|
19247
19272
|
const { combine_fields } = trans;
|
|
19248
19273
|
if (!combine_fields.fields || combine_fields.fields.length === 0)
|
|
@@ -19250,6 +19275,9 @@ var ValidatorClass = class {
|
|
|
19250
19275
|
const missingFieldsInConsumer = combine_fields.fields.map((x) => ({ field: x, found: fields.find((k) => (k.alias ?? k.key) === x) })).filter((x) => !x.found);
|
|
19251
19276
|
if (missingFieldsInConsumer.length > 0)
|
|
19252
19277
|
errors2.push(`The requested field(s) for a transformation is missing in the consumer -> missing field(s): "${missingFieldsInConsumer.map((x) => x.field).join(", ")}"; field transformation: "${field.alias ?? field.key}";`);
|
|
19278
|
+
const invalidFields = combine_fields.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19279
|
+
if (invalidFields.length > 0)
|
|
19280
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in combine_fields: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19253
19281
|
}
|
|
19254
19282
|
}
|
|
19255
19283
|
}
|
|
@@ -19272,7 +19300,7 @@ var ValidatorClass = class {
|
|
|
19272
19300
|
errors.push(`The export destination "${output.exportDestination}" was not found in the sources.`);
|
|
19273
19301
|
}
|
|
19274
19302
|
}
|
|
19275
|
-
const dimensionFields = consumer.fields.filter((x) => x.key !== "*" && !x.fixed && !x.copyFrom);
|
|
19303
|
+
const dimensionFields = consumer.fields.filter((x) => x.key !== "*" && !x.fixed && !x.copyFrom && !x.lineCount);
|
|
19276
19304
|
const dimensionKeys = dimensionFields.map((x) => `${x.from ?? "_default_"}::${x.key}`);
|
|
19277
19305
|
const duplicateDimensionKeys = dimensionKeys.filter((k, i) => dimensionKeys.indexOf(k) !== i);
|
|
19278
19306
|
if (duplicateDimensionKeys.length > 0) {
|
|
@@ -23459,6 +23487,25 @@ var ConsumerManagerClass = class {
|
|
|
23459
23487
|
const expandedFields = convertedFields.flatMap((x) => this.expandField(consumer, x, availableColumns));
|
|
23460
23488
|
return expandedFields;
|
|
23461
23489
|
};
|
|
23490
|
+
this.getLineCountFields = (consumer, visibleOnly = false) => {
|
|
23491
|
+
Affirm_default(consumer, "Invalid consumer");
|
|
23492
|
+
return this.getLineCountFieldsFromExpanded(this.getExpandedFields(consumer), visibleOnly);
|
|
23493
|
+
};
|
|
23494
|
+
this.getLineCountFieldsFromExpanded = (fields, visibleOnly = false) => {
|
|
23495
|
+
Affirm_default(fields, "Invalid consumer fields");
|
|
23496
|
+
return fields.filter((field) => this.isLineCountField(field.cField) && (!visibleOnly || !field.cField.hidden));
|
|
23497
|
+
};
|
|
23498
|
+
this.hasLineCountFields = (consumer, visibleOnly = false) => {
|
|
23499
|
+
return this.getLineCountFields(consumer, visibleOnly).length > 0;
|
|
23500
|
+
};
|
|
23501
|
+
this.isSyntheticField = (field) => {
|
|
23502
|
+
Affirm_default(field, "Invalid field");
|
|
23503
|
+
return field.fixed === true || !!field.copyFrom || this.isLineCountField(field);
|
|
23504
|
+
};
|
|
23505
|
+
this.isLineCountField = (field) => {
|
|
23506
|
+
Affirm_default(field, "Invalid field");
|
|
23507
|
+
return field.lineCount === true;
|
|
23508
|
+
};
|
|
23462
23509
|
this.getAvailableColumns = (consumer) => {
|
|
23463
23510
|
return ResourcesUtils_default.getAvailableColumns(consumer);
|
|
23464
23511
|
};
|
|
@@ -23511,7 +23558,7 @@ var ConsumerManagerClass = class {
|
|
|
23511
23558
|
column = columns.find((x) => x.owner === field.from && x.nameInProducer === field.key);
|
|
23512
23559
|
} else if (consumer.producers.length === 1 && !field.from) {
|
|
23513
23560
|
column = columns.find((x) => x.nameInProducer === field.key);
|
|
23514
|
-
} else if (!
|
|
23561
|
+
} else if (!this.isSyntheticField(field)) {
|
|
23515
23562
|
const matches = columns.filter((x) => x.nameInProducer === field.key);
|
|
23516
23563
|
Affirm_default(matches.length > 0, `Consumer "${consumer.name}" misconfiguration: the field "${field.key}" is not found in any of the included producers (${consumer.producers.map((x) => x.name).join(", ")})`);
|
|
23517
23564
|
if (matches.length === 1) {
|
|
@@ -23522,14 +23569,14 @@ var ConsumerManagerClass = class {
|
|
|
23522
23569
|
column = matches[0];
|
|
23523
23570
|
}
|
|
23524
23571
|
if (!column) {
|
|
23525
|
-
if (field.fixed === true && Algo_default.hasVal(field.default) || field
|
|
23572
|
+
if (field.fixed === true && Algo_default.hasVal(field.default) || this.isSyntheticField(field)) {
|
|
23526
23573
|
column = {
|
|
23527
23574
|
aliasInProducer: field.key,
|
|
23528
23575
|
nameInProducer: field.alias ?? field.key,
|
|
23529
23576
|
consumerAlias: field.alias ?? field.key,
|
|
23530
23577
|
consumerKey: field.key,
|
|
23531
23578
|
owner: field.from,
|
|
23532
|
-
dimension: { name: field.key, type: typeof field.default }
|
|
23579
|
+
dimension: { name: field.key, type: this.isLineCountField(field) ? "number" : typeof field.default }
|
|
23533
23580
|
};
|
|
23534
23581
|
}
|
|
23535
23582
|
}
|
|
@@ -24841,16 +24888,23 @@ var ConsumerExecutorClass = class {
|
|
|
24841
24888
|
for (const field of fields) {
|
|
24842
24889
|
const { cField } = field;
|
|
24843
24890
|
const fieldKey = cField.alias ?? cField.key;
|
|
24891
|
+
if (cField.fixed && Algo_default.hasVal(cField.default)) {
|
|
24892
|
+
record[fieldKey] = cField.default;
|
|
24893
|
+
continue;
|
|
24894
|
+
}
|
|
24895
|
+
if (ConsumerManager_default.isLineCountField(cField)) {
|
|
24896
|
+
record[fieldKey] = recordIndex + 1;
|
|
24897
|
+
continue;
|
|
24898
|
+
}
|
|
24899
|
+
if (cField.copyFrom) {
|
|
24900
|
+
record[fieldKey] = record[cField.copyFrom];
|
|
24901
|
+
continue;
|
|
24902
|
+
}
|
|
24844
24903
|
let dimension;
|
|
24845
24904
|
try {
|
|
24846
24905
|
dimension = dimensions.find((x) => x.name === cField.key);
|
|
24847
24906
|
if (!dimension) {
|
|
24848
|
-
|
|
24849
|
-
record[fieldKey] = cField.default;
|
|
24850
|
-
else if (cField.copyFrom)
|
|
24851
|
-
record[fieldKey] = record[cField.copyFrom];
|
|
24852
|
-
else
|
|
24853
|
-
throw new Error(`The requested field "${cField.key}" from the consumer is not present in the underlying producer "${producer.name}" (${dimensions.map((x) => x.name).join(", ")})`);
|
|
24907
|
+
throw new Error(`The requested field "${cField.key}" from the consumer is not present in the underlying producer "${producer.name}" (${dimensions.map((x) => x.name).join(", ")})`);
|
|
24854
24908
|
}
|
|
24855
24909
|
} catch (error) {
|
|
24856
24910
|
const err2 = new Error(`Resolving dimension for field "${fieldKey}" of producer "${producer.name}" failed (index: ${recordIndex}): ${error.message}`, { cause: error });
|
|
@@ -24963,6 +25017,64 @@ var ConsumerExecutorClass = class {
|
|
|
24963
25017
|
}
|
|
24964
25018
|
return record;
|
|
24965
25019
|
};
|
|
25020
|
+
this.processLineCount = async (consumer, datasetPath, executorResults) => {
|
|
25021
|
+
const internalRecordFormat = OutputExecutor_default._getInternalRecordFormat(consumer);
|
|
25022
|
+
const internalFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
25023
|
+
const lineCountFields = ConsumerManager_default.getLineCountFieldsFromExpanded(internalFields, true);
|
|
25024
|
+
if (lineCountFields.length === 0)
|
|
25025
|
+
return 0;
|
|
25026
|
+
const tempWorkPath = datasetPath + "_tmp";
|
|
25027
|
+
const reader = import_fs12.default.createReadStream(datasetPath);
|
|
25028
|
+
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
25029
|
+
const writer = import_fs12.default.createWriteStream(tempWorkPath);
|
|
25030
|
+
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
25031
|
+
let workerIndex = 0;
|
|
25032
|
+
let currentWorker = executorResults[workerIndex] ?? null;
|
|
25033
|
+
let remainingLinesInWorker = currentWorker?.outputCount ?? 0;
|
|
25034
|
+
let processedLineCount = 0;
|
|
25035
|
+
const advanceWorker = () => {
|
|
25036
|
+
while (currentWorker && remainingLinesInWorker <= 0) {
|
|
25037
|
+
workerIndex++;
|
|
25038
|
+
currentWorker = executorResults[workerIndex] ?? null;
|
|
25039
|
+
remainingLinesInWorker = currentWorker?.outputCount ?? 0;
|
|
25040
|
+
}
|
|
25041
|
+
};
|
|
25042
|
+
advanceWorker();
|
|
25043
|
+
for await (const line of lineReader) {
|
|
25044
|
+
advanceWorker();
|
|
25045
|
+
Affirm_default(currentWorker, `Unable to resolve worker metadata while processing lineCount for ${datasetPath}`);
|
|
25046
|
+
const rowOffset = currentWorker.rowOffset ?? 0;
|
|
25047
|
+
if (rowOffset > 0) {
|
|
25048
|
+
const record = this._parseLine(line, internalRecordFormat, internalFields);
|
|
25049
|
+
for (const field of lineCountFields) {
|
|
25050
|
+
const currentValue = Number(record[field.finalKey]);
|
|
25051
|
+
Affirm_default(!Number.isNaN(currentValue), `Invalid lineCount value found while processing ${datasetPath}`);
|
|
25052
|
+
record[field.finalKey] = currentValue + rowOffset;
|
|
25053
|
+
}
|
|
25054
|
+
if (!writer.write(OutputExecutor_default.outputRecord(record, consumer, internalFields) + "\n"))
|
|
25055
|
+
await waitForDrain();
|
|
25056
|
+
} else if (!writer.write(line + "\n")) {
|
|
25057
|
+
await waitForDrain();
|
|
25058
|
+
}
|
|
25059
|
+
remainingLinesInWorker--;
|
|
25060
|
+
processedLineCount++;
|
|
25061
|
+
}
|
|
25062
|
+
lineReader.close();
|
|
25063
|
+
await new Promise((resolve, reject) => {
|
|
25064
|
+
writer.on("close", resolve);
|
|
25065
|
+
writer.on("error", reject);
|
|
25066
|
+
writer.end();
|
|
25067
|
+
});
|
|
25068
|
+
if (!reader.destroyed) {
|
|
25069
|
+
await new Promise((resolve) => {
|
|
25070
|
+
reader.once("close", resolve);
|
|
25071
|
+
reader.destroy();
|
|
25072
|
+
});
|
|
25073
|
+
}
|
|
25074
|
+
await import_promises8.default.unlink(datasetPath);
|
|
25075
|
+
await import_promises8.default.rename(tempWorkPath, datasetPath);
|
|
25076
|
+
return processedLineCount;
|
|
25077
|
+
};
|
|
24966
25078
|
this.processDistinct = async (datasetPath) => {
|
|
24967
25079
|
const reader = import_fs12.default.createReadStream(datasetPath);
|
|
24968
25080
|
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
@@ -25554,6 +25666,7 @@ var ExecutorOrchestratorClass = class {
|
|
|
25554
25666
|
const _progress = new ExecutorProgress2_default(logProgress);
|
|
25555
25667
|
const { usageId } = UsageManager_default.startUsage(consumer, details);
|
|
25556
25668
|
const scope = { id: usageId, folder: `${consumer.name}_${usageId}`, workersId: [], limitFileSize: consumer.maximumFileSize };
|
|
25669
|
+
const expandedFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
25557
25670
|
let activePool = null;
|
|
25558
25671
|
try {
|
|
25559
25672
|
const start = performance.now();
|
|
@@ -25616,7 +25729,9 @@ var ExecutorOrchestratorClass = class {
|
|
|
25616
25729
|
}));
|
|
25617
25730
|
}
|
|
25618
25731
|
Logger_default.log(`[${usageId}] Waiting for ${workerThreads.length} worker(s) to complete | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
25619
|
-
|
|
25732
|
+
const fileExecutorResults = await Promise.all(workerThreads);
|
|
25733
|
+
this.assignChunkRowMetadata(chunks, fileExecutorResults);
|
|
25734
|
+
executorResults.push(...fileExecutorResults);
|
|
25620
25735
|
Logger_default.log(`[${usageId}] All ${workerThreads.length} worker(s) finished for producer "${prod.name}" file ${fileIndex + 1}/${totalFiles} | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
25621
25736
|
} finally {
|
|
25622
25737
|
await activePool.terminate();
|
|
@@ -25651,14 +25766,21 @@ var ExecutorOrchestratorClass = class {
|
|
|
25651
25766
|
postOperation.totalOutputCount = unifiedOutputCount;
|
|
25652
25767
|
Logger_default.log(`[${usageId}] DistinctOn pass complete: ${unifiedOutputCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25653
25768
|
}
|
|
25654
|
-
|
|
25655
|
-
|
|
25656
|
-
|
|
25657
|
-
|
|
25658
|
-
|
|
25659
|
-
|
|
25660
|
-
|
|
25661
|
-
|
|
25769
|
+
if (consumer.options?.pivot) {
|
|
25770
|
+
Logger_default.log(`[${usageId}] Running pivot operation`);
|
|
25771
|
+
counter = performance.now();
|
|
25772
|
+
const unifiedOutputCount = await ConsumerExecutor_default.processPivot(consumer, ExecutorScope_default.getMainPath(scope));
|
|
25773
|
+
tracker.measure("process-pivot:main", performance.now() - counter);
|
|
25774
|
+
postOperation.totalOutputCount = unifiedOutputCount;
|
|
25775
|
+
Logger_default.log(`[${usageId}] Pivot complete: ${unifiedOutputCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25776
|
+
}
|
|
25777
|
+
if (ConsumerManager_default.hasLineCountFields(consumer, true)) {
|
|
25778
|
+
Logger_default.log(`[${usageId}] Running unified lineCount pass across merged workers`);
|
|
25779
|
+
counter = performance.now();
|
|
25780
|
+
const processedLineCount = await ConsumerExecutor_default.processLineCount(consumer, ExecutorScope_default.getMainPath(scope), executorResults);
|
|
25781
|
+
tracker.measure("process-line-count:main", performance.now() - counter);
|
|
25782
|
+
Logger_default.log(`[${usageId}] LineCount pass complete: normalized ${processedLineCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25783
|
+
}
|
|
25662
25784
|
}
|
|
25663
25785
|
if (consumer.validate && consumer.validate.length > 0) {
|
|
25664
25786
|
Logger_default.log(`[${usageId}] Running dataset-level validations`);
|
|
@@ -25688,7 +25810,7 @@ var ExecutorOrchestratorClass = class {
|
|
|
25688
25810
|
_progress.update({ phase: "Exporting result", progress: 0 });
|
|
25689
25811
|
counter = performance.now();
|
|
25690
25812
|
Logger_default.log(`[${usageId}] Exporting results to ${consumer.outputs.length} output(s)`);
|
|
25691
|
-
const exportRes = await OutputExecutor_default.exportResult(consumer,
|
|
25813
|
+
const exportRes = await OutputExecutor_default.exportResult(consumer, expandedFields, scope);
|
|
25692
25814
|
tracker.measure("export-result", performance.now() - counter);
|
|
25693
25815
|
Logger_default.log(`[${usageId}] Export complete in ${Math.round(performance.now() - counter)}ms (key: ${exportRes.key})`);
|
|
25694
25816
|
_progress.update({ phase: "Exporting result", progress: 1 });
|
|
@@ -25759,6 +25881,22 @@ var ExecutorOrchestratorClass = class {
|
|
|
25759
25881
|
import_fs13.default.closeSync(fd);
|
|
25760
25882
|
}
|
|
25761
25883
|
};
|
|
25884
|
+
this.assignChunkRowMetadata = (chunks, executorResults) => {
|
|
25885
|
+
const validResults = executorResults.filter((result) => Algo_default.hasVal(result));
|
|
25886
|
+
const resultsByChunkIndex = new Map(validResults.map((result) => [result.chunkIndex, result]));
|
|
25887
|
+
let nextRowOffset = 0;
|
|
25888
|
+
for (const chunk of chunks) {
|
|
25889
|
+
const result = resultsByChunkIndex.get(chunk.index);
|
|
25890
|
+
if (!result)
|
|
25891
|
+
continue;
|
|
25892
|
+
const rowCount = result.inputCount;
|
|
25893
|
+
chunk.rowOffset = nextRowOffset;
|
|
25894
|
+
chunk.rowCount = rowCount;
|
|
25895
|
+
result.rowOffset = nextRowOffset;
|
|
25896
|
+
result.rowCount = rowCount;
|
|
25897
|
+
nextRowOffset += rowCount;
|
|
25898
|
+
}
|
|
25899
|
+
};
|
|
25762
25900
|
/**
|
|
25763
25901
|
* Efficiently finds the next newline character starting from a position.
|
|
25764
25902
|
* Uses small buffer reads for speed.
|
|
@@ -206,6 +206,10 @@
|
|
|
206
206
|
"type": "boolean",
|
|
207
207
|
"description": "If set, \"default\" must have a value. This field is not searched in the underlying dataset, but is a fixed value set by the \"default\" prop."
|
|
208
208
|
},
|
|
209
|
+
"lineCount": {
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"description": "If set, this field is populated with the 1-based source line number of the current record. For multi-worker file processing the final value is normalized during merge so it remains consistent across chunks."
|
|
212
|
+
},
|
|
209
213
|
"copyFrom": {
|
|
210
214
|
"type": "string",
|
|
211
215
|
"description": "If set, this field will be added as new to the consumer dataset and will be a copy of the specified field. Use the alias if set, otherwise the key. The source field should come before this field in the fields list."
|
package/package.json
CHANGED
|
@@ -18745,7 +18745,7 @@ var import_promises = __toESM(require("fs/promises"), 1);
|
|
|
18745
18745
|
|
|
18746
18746
|
// ../../packages/constants/src/Constants.ts
|
|
18747
18747
|
var CONSTANTS = {
|
|
18748
|
-
cliVersion: "1.2.
|
|
18748
|
+
cliVersion: "1.2.11",
|
|
18749
18749
|
backendVersion: 1,
|
|
18750
18750
|
backendPort: 5088,
|
|
18751
18751
|
workerVersion: 2,
|
|
@@ -19184,7 +19184,16 @@ var ValidatorClass = class {
|
|
|
19184
19184
|
}
|
|
19185
19185
|
const allAvailableFields = [...availableFieldsByProducer.values()].flat();
|
|
19186
19186
|
for (const field of consumer.fields) {
|
|
19187
|
-
|
|
19187
|
+
const syntheticFieldModes = [field.fixed === true, !!field.copyFrom, field.lineCount === true].filter(Boolean);
|
|
19188
|
+
if (syntheticFieldModes.length > 1)
|
|
19189
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" can only use one synthetic field mode among "fixed", "copyFrom", and "lineCount".`);
|
|
19190
|
+
if (field.lineCount && field.key === "*")
|
|
19191
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "lineCount" with the wildcard key "*".`);
|
|
19192
|
+
if (field.lineCount && field.transform)
|
|
19193
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "transform" together with "lineCount".`);
|
|
19194
|
+
if (field.lineCount && field.validate && field.validate.length > 0)
|
|
19195
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use "validate" together with "lineCount".`);
|
|
19196
|
+
if (field.key === "*" || field.fixed || field.copyFrom || field.lineCount) continue;
|
|
19188
19197
|
if (field.from) {
|
|
19189
19198
|
const producerFields = availableFieldsByProducer.get(field.from);
|
|
19190
19199
|
if (producerFields && !producerFields.includes(field.key))
|
|
@@ -19205,6 +19214,8 @@ var ValidatorClass = class {
|
|
|
19205
19214
|
const found = precedingFields.find((f) => (f.alias ?? f.key) === field.copyFrom);
|
|
19206
19215
|
if (!found)
|
|
19207
19216
|
errors.push(`Field "${field.alias ?? field.key}" uses copyFrom "${field.copyFrom}" but no field with that name/alias exists before it in consumer "${consumer.name}".`);
|
|
19217
|
+
else if (found.lineCount)
|
|
19218
|
+
errors.push(`Field "${field.alias ?? field.key}" in consumer "${consumer.name}" cannot use copyFrom on a field with "lineCount" because line counts are normalized after worker merge.`);
|
|
19208
19219
|
}
|
|
19209
19220
|
if (consumer.filters && consumer.filters.length > 0) {
|
|
19210
19221
|
if (consumer.filters.some((x) => x.sql && x.rule))
|
|
@@ -19225,10 +19236,14 @@ var ValidatorClass = class {
|
|
|
19225
19236
|
const missingRules = rulesWithMatchingFields.filter((x) => !x.match);
|
|
19226
19237
|
errors.push(`Filter(s) on member(s) "${missingRules.map((x) => x.rule.rule.member).join(", ")}" is invalid since the member specified is not present in the consumer. Check the member value or add the missing field to the consumer.`);
|
|
19227
19238
|
}
|
|
19239
|
+
const lineCountRuleMembers = rulesWithMatchingFields.filter((x) => x.match?.lineCount);
|
|
19240
|
+
if (lineCountRuleMembers.length > 0)
|
|
19241
|
+
errors.push(`Consumer "${consumer.name}" cannot use rule-based filters on field(s) with "lineCount": ${lineCountRuleMembers.map((x) => `"${x.rule.rule.member}"`).join(", ")}.`);
|
|
19228
19242
|
}
|
|
19229
19243
|
const validateTransformations = (fields) => {
|
|
19230
19244
|
const errors2 = [];
|
|
19231
19245
|
const trxsFields = fields.filter((x) => x.transform);
|
|
19246
|
+
const lineCountFieldNames = new Set(fields.filter((x) => x.lineCount).map((x) => x.alias ?? x.key));
|
|
19232
19247
|
for (const field of trxsFields) {
|
|
19233
19248
|
const trxToValidate = [];
|
|
19234
19249
|
if (Array.isArray(field.transform))
|
|
@@ -19236,6 +19251,16 @@ var ValidatorClass = class {
|
|
|
19236
19251
|
else
|
|
19237
19252
|
trxToValidate.push(field.transform);
|
|
19238
19253
|
for (const trans of trxToValidate) {
|
|
19254
|
+
if ("multiplyBy" in trans) {
|
|
19255
|
+
const invalidFields = trans.multiplyBy.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19256
|
+
if (invalidFields.length > 0)
|
|
19257
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in multiplyBy: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19258
|
+
}
|
|
19259
|
+
if ("addBy" in trans) {
|
|
19260
|
+
const invalidFields = trans.addBy.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19261
|
+
if (invalidFields.length > 0)
|
|
19262
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in addBy: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19263
|
+
}
|
|
19239
19264
|
if ("combine_fields" in trans) {
|
|
19240
19265
|
const { combine_fields } = trans;
|
|
19241
19266
|
if (!combine_fields.fields || combine_fields.fields.length === 0)
|
|
@@ -19243,6 +19268,9 @@ var ValidatorClass = class {
|
|
|
19243
19268
|
const missingFieldsInConsumer = combine_fields.fields.map((x) => ({ field: x, found: fields.find((k) => (k.alias ?? k.key) === x) })).filter((x) => !x.found);
|
|
19244
19269
|
if (missingFieldsInConsumer.length > 0)
|
|
19245
19270
|
errors2.push(`The requested field(s) for a transformation is missing in the consumer -> missing field(s): "${missingFieldsInConsumer.map((x) => x.field).join(", ")}"; field transformation: "${field.alias ?? field.key}";`);
|
|
19271
|
+
const invalidFields = combine_fields.fields.filter((x) => lineCountFieldNames.has(x));
|
|
19272
|
+
if (invalidFields.length > 0)
|
|
19273
|
+
errors2.push(`Transformation on field "${field.alias ?? field.key}" cannot reference lineCount field(s) in combine_fields: ${invalidFields.map((x) => `"${x}"`).join(", ")}.`);
|
|
19246
19274
|
}
|
|
19247
19275
|
}
|
|
19248
19276
|
}
|
|
@@ -19265,7 +19293,7 @@ var ValidatorClass = class {
|
|
|
19265
19293
|
errors.push(`The export destination "${output.exportDestination}" was not found in the sources.`);
|
|
19266
19294
|
}
|
|
19267
19295
|
}
|
|
19268
|
-
const dimensionFields = consumer.fields.filter((x) => x.key !== "*" && !x.fixed && !x.copyFrom);
|
|
19296
|
+
const dimensionFields = consumer.fields.filter((x) => x.key !== "*" && !x.fixed && !x.copyFrom && !x.lineCount);
|
|
19269
19297
|
const dimensionKeys = dimensionFields.map((x) => `${x.from ?? "_default_"}::${x.key}`);
|
|
19270
19298
|
const duplicateDimensionKeys = dimensionKeys.filter((k, i) => dimensionKeys.indexOf(k) !== i);
|
|
19271
19299
|
if (duplicateDimensionKeys.length > 0) {
|
|
@@ -22783,6 +22811,25 @@ var ConsumerManagerClass = class {
|
|
|
22783
22811
|
const expandedFields = convertedFields.flatMap((x) => this.expandField(consumer, x, availableColumns));
|
|
22784
22812
|
return expandedFields;
|
|
22785
22813
|
};
|
|
22814
|
+
this.getLineCountFields = (consumer, visibleOnly = false) => {
|
|
22815
|
+
Affirm_default(consumer, "Invalid consumer");
|
|
22816
|
+
return this.getLineCountFieldsFromExpanded(this.getExpandedFields(consumer), visibleOnly);
|
|
22817
|
+
};
|
|
22818
|
+
this.getLineCountFieldsFromExpanded = (fields, visibleOnly = false) => {
|
|
22819
|
+
Affirm_default(fields, "Invalid consumer fields");
|
|
22820
|
+
return fields.filter((field) => this.isLineCountField(field.cField) && (!visibleOnly || !field.cField.hidden));
|
|
22821
|
+
};
|
|
22822
|
+
this.hasLineCountFields = (consumer, visibleOnly = false) => {
|
|
22823
|
+
return this.getLineCountFields(consumer, visibleOnly).length > 0;
|
|
22824
|
+
};
|
|
22825
|
+
this.isSyntheticField = (field) => {
|
|
22826
|
+
Affirm_default(field, "Invalid field");
|
|
22827
|
+
return field.fixed === true || !!field.copyFrom || this.isLineCountField(field);
|
|
22828
|
+
};
|
|
22829
|
+
this.isLineCountField = (field) => {
|
|
22830
|
+
Affirm_default(field, "Invalid field");
|
|
22831
|
+
return field.lineCount === true;
|
|
22832
|
+
};
|
|
22786
22833
|
this.getAvailableColumns = (consumer) => {
|
|
22787
22834
|
return ResourcesUtils_default.getAvailableColumns(consumer);
|
|
22788
22835
|
};
|
|
@@ -22835,7 +22882,7 @@ var ConsumerManagerClass = class {
|
|
|
22835
22882
|
column = columns.find((x) => x.owner === field.from && x.nameInProducer === field.key);
|
|
22836
22883
|
} else if (consumer.producers.length === 1 && !field.from) {
|
|
22837
22884
|
column = columns.find((x) => x.nameInProducer === field.key);
|
|
22838
|
-
} else if (!
|
|
22885
|
+
} else if (!this.isSyntheticField(field)) {
|
|
22839
22886
|
const matches = columns.filter((x) => x.nameInProducer === field.key);
|
|
22840
22887
|
Affirm_default(matches.length > 0, `Consumer "${consumer.name}" misconfiguration: the field "${field.key}" is not found in any of the included producers (${consumer.producers.map((x) => x.name).join(", ")})`);
|
|
22841
22888
|
if (matches.length === 1) {
|
|
@@ -22846,14 +22893,14 @@ var ConsumerManagerClass = class {
|
|
|
22846
22893
|
column = matches[0];
|
|
22847
22894
|
}
|
|
22848
22895
|
if (!column) {
|
|
22849
|
-
if (field.fixed === true && Algo_default.hasVal(field.default) || field
|
|
22896
|
+
if (field.fixed === true && Algo_default.hasVal(field.default) || this.isSyntheticField(field)) {
|
|
22850
22897
|
column = {
|
|
22851
22898
|
aliasInProducer: field.key,
|
|
22852
22899
|
nameInProducer: field.alias ?? field.key,
|
|
22853
22900
|
consumerAlias: field.alias ?? field.key,
|
|
22854
22901
|
consumerKey: field.key,
|
|
22855
22902
|
owner: field.from,
|
|
22856
|
-
dimension: { name: field.key, type: typeof field.default }
|
|
22903
|
+
dimension: { name: field.key, type: this.isLineCountField(field) ? "number" : typeof field.default }
|
|
22857
22904
|
};
|
|
22858
22905
|
}
|
|
22859
22906
|
}
|
|
@@ -24434,16 +24481,23 @@ var ConsumerExecutorClass = class {
|
|
|
24434
24481
|
for (const field of fields) {
|
|
24435
24482
|
const { cField } = field;
|
|
24436
24483
|
const fieldKey = cField.alias ?? cField.key;
|
|
24484
|
+
if (cField.fixed && Algo_default.hasVal(cField.default)) {
|
|
24485
|
+
record[fieldKey] = cField.default;
|
|
24486
|
+
continue;
|
|
24487
|
+
}
|
|
24488
|
+
if (ConsumerManager_default.isLineCountField(cField)) {
|
|
24489
|
+
record[fieldKey] = recordIndex + 1;
|
|
24490
|
+
continue;
|
|
24491
|
+
}
|
|
24492
|
+
if (cField.copyFrom) {
|
|
24493
|
+
record[fieldKey] = record[cField.copyFrom];
|
|
24494
|
+
continue;
|
|
24495
|
+
}
|
|
24437
24496
|
let dimension;
|
|
24438
24497
|
try {
|
|
24439
24498
|
dimension = dimensions.find((x) => x.name === cField.key);
|
|
24440
24499
|
if (!dimension) {
|
|
24441
|
-
|
|
24442
|
-
record[fieldKey] = cField.default;
|
|
24443
|
-
else if (cField.copyFrom)
|
|
24444
|
-
record[fieldKey] = record[cField.copyFrom];
|
|
24445
|
-
else
|
|
24446
|
-
throw new Error(`The requested field "${cField.key}" from the consumer is not present in the underlying producer "${producer.name}" (${dimensions.map((x) => x.name).join(", ")})`);
|
|
24500
|
+
throw new Error(`The requested field "${cField.key}" from the consumer is not present in the underlying producer "${producer.name}" (${dimensions.map((x) => x.name).join(", ")})`);
|
|
24447
24501
|
}
|
|
24448
24502
|
} catch (error) {
|
|
24449
24503
|
const err2 = new Error(`Resolving dimension for field "${fieldKey}" of producer "${producer.name}" failed (index: ${recordIndex}): ${error.message}`, { cause: error });
|
|
@@ -24556,6 +24610,64 @@ var ConsumerExecutorClass = class {
|
|
|
24556
24610
|
}
|
|
24557
24611
|
return record;
|
|
24558
24612
|
};
|
|
24613
|
+
this.processLineCount = async (consumer, datasetPath, executorResults) => {
|
|
24614
|
+
const internalRecordFormat = OutputExecutor_default._getInternalRecordFormat(consumer);
|
|
24615
|
+
const internalFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
24616
|
+
const lineCountFields = ConsumerManager_default.getLineCountFieldsFromExpanded(internalFields, true);
|
|
24617
|
+
if (lineCountFields.length === 0)
|
|
24618
|
+
return 0;
|
|
24619
|
+
const tempWorkPath = datasetPath + "_tmp";
|
|
24620
|
+
const reader = import_fs10.default.createReadStream(datasetPath);
|
|
24621
|
+
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
24622
|
+
const writer = import_fs10.default.createWriteStream(tempWorkPath);
|
|
24623
|
+
const waitForDrain = () => new Promise((resolve) => writer.once("drain", resolve));
|
|
24624
|
+
let workerIndex = 0;
|
|
24625
|
+
let currentWorker = executorResults[workerIndex] ?? null;
|
|
24626
|
+
let remainingLinesInWorker = currentWorker?.outputCount ?? 0;
|
|
24627
|
+
let processedLineCount = 0;
|
|
24628
|
+
const advanceWorker = () => {
|
|
24629
|
+
while (currentWorker && remainingLinesInWorker <= 0) {
|
|
24630
|
+
workerIndex++;
|
|
24631
|
+
currentWorker = executorResults[workerIndex] ?? null;
|
|
24632
|
+
remainingLinesInWorker = currentWorker?.outputCount ?? 0;
|
|
24633
|
+
}
|
|
24634
|
+
};
|
|
24635
|
+
advanceWorker();
|
|
24636
|
+
for await (const line of lineReader) {
|
|
24637
|
+
advanceWorker();
|
|
24638
|
+
Affirm_default(currentWorker, `Unable to resolve worker metadata while processing lineCount for ${datasetPath}`);
|
|
24639
|
+
const rowOffset = currentWorker.rowOffset ?? 0;
|
|
24640
|
+
if (rowOffset > 0) {
|
|
24641
|
+
const record = this._parseLine(line, internalRecordFormat, internalFields);
|
|
24642
|
+
for (const field of lineCountFields) {
|
|
24643
|
+
const currentValue = Number(record[field.finalKey]);
|
|
24644
|
+
Affirm_default(!Number.isNaN(currentValue), `Invalid lineCount value found while processing ${datasetPath}`);
|
|
24645
|
+
record[field.finalKey] = currentValue + rowOffset;
|
|
24646
|
+
}
|
|
24647
|
+
if (!writer.write(OutputExecutor_default.outputRecord(record, consumer, internalFields) + "\n"))
|
|
24648
|
+
await waitForDrain();
|
|
24649
|
+
} else if (!writer.write(line + "\n")) {
|
|
24650
|
+
await waitForDrain();
|
|
24651
|
+
}
|
|
24652
|
+
remainingLinesInWorker--;
|
|
24653
|
+
processedLineCount++;
|
|
24654
|
+
}
|
|
24655
|
+
lineReader.close();
|
|
24656
|
+
await new Promise((resolve, reject) => {
|
|
24657
|
+
writer.on("close", resolve);
|
|
24658
|
+
writer.on("error", reject);
|
|
24659
|
+
writer.end();
|
|
24660
|
+
});
|
|
24661
|
+
if (!reader.destroyed) {
|
|
24662
|
+
await new Promise((resolve) => {
|
|
24663
|
+
reader.once("close", resolve);
|
|
24664
|
+
reader.destroy();
|
|
24665
|
+
});
|
|
24666
|
+
}
|
|
24667
|
+
await import_promises8.default.unlink(datasetPath);
|
|
24668
|
+
await import_promises8.default.rename(tempWorkPath, datasetPath);
|
|
24669
|
+
return processedLineCount;
|
|
24670
|
+
};
|
|
24559
24671
|
this.processDistinct = async (datasetPath) => {
|
|
24560
24672
|
const reader = import_fs10.default.createReadStream(datasetPath);
|
|
24561
24673
|
const lineReader = import_readline6.default.createInterface({ input: reader, crlfDelay: Infinity });
|
|
@@ -24912,6 +25024,10 @@ var Executor = class {
|
|
|
24912
25024
|
elapsedMS: -1,
|
|
24913
25025
|
inputCount: -1,
|
|
24914
25026
|
outputCount: -1,
|
|
25027
|
+
chunkIndex: chunk.index,
|
|
25028
|
+
fileUri: chunk.fileUri,
|
|
25029
|
+
rowOffset: chunk.rowOffset ?? -1,
|
|
25030
|
+
rowCount: -1,
|
|
24915
25031
|
resultUri: ExecutorScope_default.getWorkerPath(scope, workerId),
|
|
24916
25032
|
operations: {}
|
|
24917
25033
|
};
|
|
@@ -25021,6 +25137,7 @@ var Executor = class {
|
|
|
25021
25137
|
result.cycles = totalCycles;
|
|
25022
25138
|
result.inputCount = lineIndex;
|
|
25023
25139
|
result.outputCount = totalOutputCount;
|
|
25140
|
+
result.rowCount = lineIndex;
|
|
25024
25141
|
result.operations = this._performance.getOperations();
|
|
25025
25142
|
Logger_default.log(`[${workerId}] Finished: ${lineIndex} input \u2192 ${totalOutputCount} output in ${Math.round(result.elapsedMS)}ms (${totalCycles} cycle(s))`);
|
|
25026
25143
|
return result;
|
|
@@ -25321,6 +25438,7 @@ var ExecutorOrchestratorClass = class {
|
|
|
25321
25438
|
const _progress = new ExecutorProgress2_default(logProgress);
|
|
25322
25439
|
const { usageId } = UsageManager_default.startUsage(consumer, details);
|
|
25323
25440
|
const scope = { id: usageId, folder: `${consumer.name}_${usageId}`, workersId: [], limitFileSize: consumer.maximumFileSize };
|
|
25441
|
+
const expandedFields = ConsumerManager_default.getExpandedFields(consumer);
|
|
25324
25442
|
let activePool = null;
|
|
25325
25443
|
try {
|
|
25326
25444
|
const start = performance.now();
|
|
@@ -25383,7 +25501,9 @@ var ExecutorOrchestratorClass = class {
|
|
|
25383
25501
|
}));
|
|
25384
25502
|
}
|
|
25385
25503
|
Logger_default.log(`[${usageId}] Waiting for ${workerThreads.length} worker(s) to complete | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
25386
|
-
|
|
25504
|
+
const fileExecutorResults = await Promise.all(workerThreads);
|
|
25505
|
+
this.assignChunkRowMetadata(chunks, fileExecutorResults);
|
|
25506
|
+
executorResults.push(...fileExecutorResults);
|
|
25387
25507
|
Logger_default.log(`[${usageId}] All ${workerThreads.length} worker(s) finished for producer "${prod.name}" file ${fileIndex + 1}/${totalFiles} | ${OrchestratorHelper_default.formatMemoryUsage()}`);
|
|
25388
25508
|
} finally {
|
|
25389
25509
|
await activePool.terminate();
|
|
@@ -25418,14 +25538,21 @@ var ExecutorOrchestratorClass = class {
|
|
|
25418
25538
|
postOperation.totalOutputCount = unifiedOutputCount;
|
|
25419
25539
|
Logger_default.log(`[${usageId}] DistinctOn pass complete: ${unifiedOutputCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25420
25540
|
}
|
|
25421
|
-
|
|
25422
|
-
|
|
25423
|
-
|
|
25424
|
-
|
|
25425
|
-
|
|
25426
|
-
|
|
25427
|
-
|
|
25428
|
-
|
|
25541
|
+
if (consumer.options?.pivot) {
|
|
25542
|
+
Logger_default.log(`[${usageId}] Running pivot operation`);
|
|
25543
|
+
counter = performance.now();
|
|
25544
|
+
const unifiedOutputCount = await ConsumerExecutor_default.processPivot(consumer, ExecutorScope_default.getMainPath(scope));
|
|
25545
|
+
tracker.measure("process-pivot:main", performance.now() - counter);
|
|
25546
|
+
postOperation.totalOutputCount = unifiedOutputCount;
|
|
25547
|
+
Logger_default.log(`[${usageId}] Pivot complete: ${unifiedOutputCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25548
|
+
}
|
|
25549
|
+
if (ConsumerManager_default.hasLineCountFields(consumer, true)) {
|
|
25550
|
+
Logger_default.log(`[${usageId}] Running unified lineCount pass across merged workers`);
|
|
25551
|
+
counter = performance.now();
|
|
25552
|
+
const processedLineCount = await ConsumerExecutor_default.processLineCount(consumer, ExecutorScope_default.getMainPath(scope), executorResults);
|
|
25553
|
+
tracker.measure("process-line-count:main", performance.now() - counter);
|
|
25554
|
+
Logger_default.log(`[${usageId}] LineCount pass complete: normalized ${processedLineCount} rows in ${Math.round(performance.now() - counter)}ms`);
|
|
25555
|
+
}
|
|
25429
25556
|
}
|
|
25430
25557
|
if (consumer.validate && consumer.validate.length > 0) {
|
|
25431
25558
|
Logger_default.log(`[${usageId}] Running dataset-level validations`);
|
|
@@ -25455,7 +25582,7 @@ var ExecutorOrchestratorClass = class {
|
|
|
25455
25582
|
_progress.update({ phase: "Exporting result", progress: 0 });
|
|
25456
25583
|
counter = performance.now();
|
|
25457
25584
|
Logger_default.log(`[${usageId}] Exporting results to ${consumer.outputs.length} output(s)`);
|
|
25458
|
-
const exportRes = await OutputExecutor_default.exportResult(consumer,
|
|
25585
|
+
const exportRes = await OutputExecutor_default.exportResult(consumer, expandedFields, scope);
|
|
25459
25586
|
tracker.measure("export-result", performance.now() - counter);
|
|
25460
25587
|
Logger_default.log(`[${usageId}] Export complete in ${Math.round(performance.now() - counter)}ms (key: ${exportRes.key})`);
|
|
25461
25588
|
_progress.update({ phase: "Exporting result", progress: 1 });
|
|
@@ -25526,6 +25653,22 @@ var ExecutorOrchestratorClass = class {
|
|
|
25526
25653
|
import_fs12.default.closeSync(fd);
|
|
25527
25654
|
}
|
|
25528
25655
|
};
|
|
25656
|
+
this.assignChunkRowMetadata = (chunks, executorResults) => {
|
|
25657
|
+
const validResults = executorResults.filter((result) => Algo_default.hasVal(result));
|
|
25658
|
+
const resultsByChunkIndex = new Map(validResults.map((result) => [result.chunkIndex, result]));
|
|
25659
|
+
let nextRowOffset = 0;
|
|
25660
|
+
for (const chunk of chunks) {
|
|
25661
|
+
const result = resultsByChunkIndex.get(chunk.index);
|
|
25662
|
+
if (!result)
|
|
25663
|
+
continue;
|
|
25664
|
+
const rowCount = result.inputCount;
|
|
25665
|
+
chunk.rowOffset = nextRowOffset;
|
|
25666
|
+
chunk.rowCount = rowCount;
|
|
25667
|
+
result.rowOffset = nextRowOffset;
|
|
25668
|
+
result.rowCount = rowCount;
|
|
25669
|
+
nextRowOffset += rowCount;
|
|
25670
|
+
}
|
|
25671
|
+
};
|
|
25529
25672
|
/**
|
|
25530
25673
|
* Efficiently finds the next newline character starting from a position.
|
|
25531
25674
|
* Uses small buffer reads for speed.
|