rowpipe 1.0.0 → 1.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/README.md +327 -41
- package/dist/cli/commands/diff.d.ts +30 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/diff.js +143 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/files.d.ts +35 -0
- package/dist/cli/commands/files.d.ts.map +1 -0
- package/dist/cli/commands/files.js +93 -0
- package/dist/cli/commands/files.js.map +1 -0
- package/dist/cli/index.js +89 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/core/errors.d.ts +23 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +38 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/diff/comparator.d.ts +17 -0
- package/dist/diff/comparator.d.ts.map +1 -0
- package/dist/diff/comparator.js +158 -0
- package/dist/diff/comparator.js.map +1 -0
- package/dist/diff/engine.d.ts +10 -0
- package/dist/diff/engine.d.ts.map +1 -0
- package/dist/diff/engine.js +333 -0
- package/dist/diff/engine.js.map +1 -0
- package/dist/diff/hash.d.ts +6 -0
- package/dist/diff/hash.d.ts.map +1 -0
- package/dist/diff/hash.js +50 -0
- package/dist/diff/hash.js.map +1 -0
- package/dist/diff/key.d.ts +21 -0
- package/dist/diff/key.d.ts.map +1 -0
- package/dist/diff/key.js +107 -0
- package/dist/diff/key.js.map +1 -0
- package/dist/diff/reporter.d.ts +18 -0
- package/dist/diff/reporter.d.ts.map +1 -0
- package/dist/diff/reporter.js +133 -0
- package/dist/diff/reporter.js.map +1 -0
- package/dist/diff/schema.d.ts +7 -0
- package/dist/diff/schema.d.ts.map +1 -0
- package/dist/diff/schema.js +65 -0
- package/dist/diff/schema.js.map +1 -0
- package/dist/diff/storage/disk-index.d.ts +28 -0
- package/dist/diff/storage/disk-index.d.ts.map +1 -0
- package/dist/diff/storage/disk-index.js +105 -0
- package/dist/diff/storage/disk-index.js.map +1 -0
- package/dist/diff/storage/memory-index.d.ts +23 -0
- package/dist/diff/storage/memory-index.d.ts.map +1 -0
- package/dist/diff/storage/memory-index.js +69 -0
- package/dist/diff/storage/memory-index.js.map +1 -0
- package/dist/diff/storage/spillable-index.d.ts +30 -0
- package/dist/diff/storage/spillable-index.d.ts.map +1 -0
- package/dist/diff/storage/spillable-index.js +108 -0
- package/dist/diff/storage/spillable-index.js.map +1 -0
- package/dist/diff/types.d.ts +127 -0
- package/dist/diff/types.d.ts.map +1 -0
- package/dist/diff/types.js +2 -0
- package/dist/diff/types.js.map +1 -0
- package/dist/files/glob.d.ts +21 -0
- package/dist/files/glob.d.ts.map +1 -0
- package/dist/files/glob.js +132 -0
- package/dist/files/glob.js.map +1 -0
- package/dist/files/hash.d.ts +10 -0
- package/dist/files/hash.d.ts.map +1 -0
- package/dist/files/hash.js +67 -0
- package/dist/files/hash.js.map +1 -0
- package/dist/files/index.d.ts +6 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +6 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/mime.d.ts +9 -0
- package/dist/files/mime.d.ts.map +1 -0
- package/dist/files/mime.js +128 -0
- package/dist/files/mime.js.map +1 -0
- package/dist/files/reader.d.ts +19 -0
- package/dist/files/reader.d.ts.map +1 -0
- package/dist/files/reader.js +344 -0
- package/dist/files/reader.js.map +1 -0
- package/dist/files/types.d.ts +47 -0
- package/dist/files/types.d.ts.map +1 -0
- package/dist/files/types.js +2 -0
- package/dist/files/types.js.map +1 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/readers/csv.d.ts.map +1 -1
- package/dist/readers/csv.js +59 -27
- package/dist/readers/csv.js.map +1 -1
- package/dist/readers/index.d.ts +2 -0
- package/dist/readers/index.d.ts.map +1 -1
- package/dist/readers/index.js +57 -3
- package/dist/readers/index.js.map +1 -1
- package/dist/readers/json.d.ts.map +1 -1
- package/dist/readers/json.js +6 -8
- package/dist/readers/json.js.map +1 -1
- package/dist/readers/jsonl.d.ts.map +1 -1
- package/dist/readers/jsonl.js +5 -7
- package/dist/readers/jsonl.js.map +1 -1
- package/dist/readers/parquet.d.ts +17 -0
- package/dist/readers/parquet.d.ts.map +1 -0
- package/dist/readers/parquet.js +146 -0
- package/dist/readers/parquet.js.map +1 -0
- package/dist/transforms/expression.d.ts.map +1 -1
- package/dist/transforms/expression.js +73 -7
- package/dist/transforms/expression.js.map +1 -1
- package/dist/utils/compression.d.ts +30 -6
- package/dist/utils/compression.d.ts.map +1 -1
- package/dist/utils/compression.js +95 -18
- package/dist/utils/compression.js.map +1 -1
- package/dist/writers/csv.d.ts.map +1 -1
- package/dist/writers/csv.js +29 -17
- package/dist/writers/csv.js.map +1 -1
- package/dist/writers/index.d.ts +2 -0
- package/dist/writers/index.d.ts.map +1 -1
- package/dist/writers/index.js +49 -4
- package/dist/writers/index.js.map +1 -1
- package/dist/writers/json.d.ts +1 -1
- package/dist/writers/json.d.ts.map +1 -1
- package/dist/writers/json.js +9 -11
- package/dist/writers/json.js.map +1 -1
- package/dist/writers/jsonl.d.ts +1 -1
- package/dist/writers/jsonl.d.ts.map +1 -1
- package/dist/writers/jsonl.js +9 -11
- package/dist/writers/jsonl.js.map +1 -1
- package/dist/writers/markdown.d.ts +21 -0
- package/dist/writers/markdown.d.ts.map +1 -0
- package/dist/writers/markdown.js +82 -0
- package/dist/writers/markdown.js.map +1 -0
- package/dist/writers/parquet.d.ts +19 -0
- package/dist/writers/parquet.d.ts.map +1 -0
- package/dist/writers/parquet.js +129 -0
- package/dist/writers/parquet.js.map +1 -0
- package/package.json +16 -3
- package/skills/rowpipe/SKILL.md +263 -0
- package/benchmarks/memory-bench.ts +0 -109
- package/src/analytics/reduce.ts +0 -388
- package/src/analytics/schema-inference.ts +0 -217
- package/src/analytics/semantic-types.ts +0 -36
- package/src/analytics/stats.ts +0 -383
- package/src/analytics/validator.ts +0 -134
- package/src/cli/commands/cast.ts +0 -60
- package/src/cli/commands/convert.ts +0 -128
- package/src/cli/commands/filter.ts +0 -58
- package/src/cli/commands/inspect.ts +0 -193
- package/src/cli/commands/map.ts +0 -66
- package/src/cli/commands/reduce.ts +0 -74
- package/src/cli/commands/rename.ts +0 -59
- package/src/cli/commands/sample.ts +0 -61
- package/src/cli/commands/schema.ts +0 -86
- package/src/cli/commands/select.ts +0 -59
- package/src/cli/commands/stats.ts +0 -100
- package/src/cli/commands/validate.ts +0 -123
- package/src/cli/index.ts +0 -285
- package/src/core/batch.ts +0 -81
- package/src/core/errors.ts +0 -81
- package/src/core/pipeline.ts +0 -150
- package/src/core/types.ts +0 -110
- package/src/index.ts +0 -52
- package/src/readers/csv.ts +0 -302
- package/src/readers/index.ts +0 -88
- package/src/readers/json.ts +0 -256
- package/src/readers/jsonl.ts +0 -126
- package/src/readers/xlsx.ts +0 -182
- package/src/transforms/cast.ts +0 -212
- package/src/transforms/expression.ts +0 -1114
- package/src/transforms/filter.ts +0 -38
- package/src/transforms/map.ts +0 -99
- package/src/transforms/rename.ts +0 -47
- package/src/transforms/sample.ts +0 -57
- package/src/transforms/select.ts +0 -64
- package/src/utils/compression.ts +0 -43
- package/src/utils/formatting.ts +0 -75
- package/src/utils/progress.ts +0 -37
- package/src/writers/csv.ts +0 -106
- package/src/writers/index.ts +0 -87
- package/src/writers/json.ts +0 -52
- package/src/writers/jsonl.ts +0 -49
- package/src/writers/xlsx.ts +0 -87
- package/tests/analytics.test.ts +0 -119
- package/tests/cli-integration.test.ts +0 -187
- package/tests/map-reduce.test.ts +0 -223
- package/tests/readers-writers.test.ts +0 -189
- package/tests/transforms.test.ts +0 -199
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -9
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { DatasetStatsAggregator } from "../../analytics/stats.js";
|
|
2
|
-
import { createPipeline } from "../../core/pipeline.js";
|
|
3
|
-
import { createReader, inferFormatFromPath } from "../../readers/index.js";
|
|
4
|
-
import { openReadableStream } from "../../utils/compression.js";
|
|
5
|
-
import { formatDecimal, formatNumber, logMemoryDebug } from "../../utils/formatting.js";
|
|
6
|
-
import { ProgressReporter } from "../../utils/progress.js";
|
|
7
|
-
|
|
8
|
-
export interface StatsCommandOptions {
|
|
9
|
-
column?: string;
|
|
10
|
-
fast?: boolean;
|
|
11
|
-
exact?: boolean;
|
|
12
|
-
json?: boolean;
|
|
13
|
-
from?: string;
|
|
14
|
-
sheet?: string;
|
|
15
|
-
path?: string;
|
|
16
|
-
delimiter?: string;
|
|
17
|
-
batchSize?: string | number;
|
|
18
|
-
quiet?: boolean;
|
|
19
|
-
noProgress?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function statsCommand(
|
|
23
|
-
inputPath = "-",
|
|
24
|
-
options: StatsCommandOptions = {}
|
|
25
|
-
): Promise<void> {
|
|
26
|
-
const progress = new ProgressReporter(options);
|
|
27
|
-
|
|
28
|
-
let fromFormat = options.from?.toLowerCase();
|
|
29
|
-
if (!fromFormat && inputPath !== "-") {
|
|
30
|
-
fromFormat = inferFormatFromPath(inputPath) ?? undefined;
|
|
31
|
-
}
|
|
32
|
-
if (!fromFormat) {
|
|
33
|
-
fromFormat = "csv";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const inputStream = openReadableStream(inputPath);
|
|
37
|
-
const reader = createReader(inputStream, {
|
|
38
|
-
format: fromFormat,
|
|
39
|
-
sheet: options.sheet,
|
|
40
|
-
path: options.path,
|
|
41
|
-
delimiter: options.delimiter,
|
|
42
|
-
batchSize: Number(options.batchSize) || 2000,
|
|
43
|
-
filePath: inputPath,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const statsAgg = new DatasetStatsAggregator({ column: options.column });
|
|
47
|
-
const pipeline = createPipeline(reader);
|
|
48
|
-
pipeline.onProgress((info) => progress.update(info));
|
|
49
|
-
|
|
50
|
-
for await (const row of pipeline.rows()) {
|
|
51
|
-
statsAgg.add(row);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
progress.done();
|
|
55
|
-
const res = statsAgg.result();
|
|
56
|
-
|
|
57
|
-
if (options.json) {
|
|
58
|
-
process.stdout.write(JSON.stringify(res, null, 2) + "\n");
|
|
59
|
-
} else {
|
|
60
|
-
process.stdout.write(`Total Rows: ${formatNumber(res.totalRows)}\n\n`);
|
|
61
|
-
|
|
62
|
-
for (const [colName, colStat] of Object.entries(res.columns)) {
|
|
63
|
-
process.stdout.write(`${colName} (${colStat.type})\n`);
|
|
64
|
-
process.stdout.write("----------------------------------------\n");
|
|
65
|
-
|
|
66
|
-
if (colStat.type === "numeric" && colStat.numeric) {
|
|
67
|
-
const n = colStat.numeric;
|
|
68
|
-
process.stdout.write(` count ${formatNumber(n.count)}\n`);
|
|
69
|
-
process.stdout.write(` null ${formatNumber(n.nullCount)}\n`);
|
|
70
|
-
process.stdout.write(` min ${formatDecimal(n.min)}\n`);
|
|
71
|
-
process.stdout.write(` max ${formatDecimal(n.max)}\n`);
|
|
72
|
-
process.stdout.write(` sum ${formatDecimal(n.sum)}\n`);
|
|
73
|
-
process.stdout.write(` mean ${formatDecimal(n.mean)}\n`);
|
|
74
|
-
process.stdout.write(` stddev ${formatDecimal(n.stddev)}\n`);
|
|
75
|
-
process.stdout.write(` variance ${formatDecimal(n.variance)}\n`);
|
|
76
|
-
process.stdout.write(` distinct* ~${formatNumber(n.approxDistinct)}\n\n`);
|
|
77
|
-
} else if (colStat.type === "string" && colStat.string) {
|
|
78
|
-
const s = colStat.string;
|
|
79
|
-
process.stdout.write(` count ${formatNumber(s.count)}\n`);
|
|
80
|
-
process.stdout.write(` null ${formatNumber(s.nullCount)}\n`);
|
|
81
|
-
process.stdout.write(` empty ${formatNumber(s.emptyCount)}\n`);
|
|
82
|
-
process.stdout.write(` min length ${formatNumber(s.minLength)}\n`);
|
|
83
|
-
process.stdout.write(` max length ${formatNumber(s.maxLength)}\n`);
|
|
84
|
-
process.stdout.write(` avg length ${formatDecimal(s.avgLength, 1)}\n`);
|
|
85
|
-
process.stdout.write(` distinct* ~${formatNumber(s.approxDistinct)}\n`);
|
|
86
|
-
if (s.topValues.length > 0) {
|
|
87
|
-
process.stdout.write(` top values:\n`);
|
|
88
|
-
for (const tv of s.topValues) {
|
|
89
|
-
process.stdout.write(` - "${tv.value}": ${formatNumber(tv.count)}\n`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
process.stdout.write("\n");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
process.stdout.write("* approximate\n");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
logMemoryDebug();
|
|
100
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
type SchemaValidatorAggregator,
|
|
4
|
-
type ValidationSchemaDefinition,
|
|
5
|
-
SchemaValidatorAggregator as Validator,
|
|
6
|
-
} from "../../analytics/validator.js";
|
|
7
|
-
import { InvalidArgumentError, ValidationError } from "../../core/errors.js";
|
|
8
|
-
import { createPipeline } from "../../core/pipeline.js";
|
|
9
|
-
import { createReader, inferFormatFromPath } from "../../readers/index.js";
|
|
10
|
-
import { openReadableStream } from "../../utils/compression.js";
|
|
11
|
-
import { formatNumber, logMemoryDebug } from "../../utils/formatting.js";
|
|
12
|
-
import { ProgressReporter } from "../../utils/progress.js";
|
|
13
|
-
|
|
14
|
-
export interface ValidateCommandOptions {
|
|
15
|
-
schema: string;
|
|
16
|
-
from?: string;
|
|
17
|
-
sheet?: string;
|
|
18
|
-
path?: string;
|
|
19
|
-
delimiter?: string;
|
|
20
|
-
json?: boolean;
|
|
21
|
-
quiet?: boolean;
|
|
22
|
-
noProgress?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function validateCommand(
|
|
26
|
-
inputPath = "-",
|
|
27
|
-
options: ValidateCommandOptions
|
|
28
|
-
): Promise<void> {
|
|
29
|
-
if (!options.schema) {
|
|
30
|
-
throw new InvalidArgumentError("Missing required option: --schema <path_to_schema.json>");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const progress = new ProgressReporter(options);
|
|
34
|
-
|
|
35
|
-
// Read and parse schema file
|
|
36
|
-
let schemaContent: string;
|
|
37
|
-
try {
|
|
38
|
-
schemaContent = await readFile(options.schema, "utf8");
|
|
39
|
-
} catch (err) {
|
|
40
|
-
throw new InvalidArgumentError(
|
|
41
|
-
`Failed to read schema file at "${options.schema}": ${(err as Error).message}`
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let schemaDef: ValidationSchemaDefinition;
|
|
46
|
-
try {
|
|
47
|
-
schemaDef = JSON.parse(schemaContent);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
throw new InvalidArgumentError(
|
|
50
|
-
`Invalid JSON in schema file "${options.schema}": ${(err as Error).message}`
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let fromFormat = options.from?.toLowerCase();
|
|
55
|
-
if (!fromFormat && inputPath !== "-") {
|
|
56
|
-
fromFormat = inferFormatFromPath(inputPath) ?? undefined;
|
|
57
|
-
}
|
|
58
|
-
if (!fromFormat) {
|
|
59
|
-
fromFormat = "csv";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const inputStream = openReadableStream(inputPath);
|
|
63
|
-
const reader = createReader(inputStream, {
|
|
64
|
-
format: fromFormat,
|
|
65
|
-
sheet: options.sheet,
|
|
66
|
-
path: options.path,
|
|
67
|
-
delimiter: options.delimiter,
|
|
68
|
-
filePath: inputPath,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const validator = new Validator(schemaDef);
|
|
72
|
-
const pipeline = createPipeline(reader);
|
|
73
|
-
pipeline.onProgress((info) => progress.update(info));
|
|
74
|
-
|
|
75
|
-
for await (const row of pipeline.rows()) {
|
|
76
|
-
validator.add(row);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
progress.done();
|
|
80
|
-
const report = validator.result();
|
|
81
|
-
|
|
82
|
-
if (options.json) {
|
|
83
|
-
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
84
|
-
} else {
|
|
85
|
-
process.stdout.write(`${formatNumber(report.totalRows)} rows scanned\n`);
|
|
86
|
-
process.stdout.write(`${formatNumber(report.validRows)} valid\n`);
|
|
87
|
-
process.stdout.write(`${formatNumber(report.invalidRows)} invalid\n`);
|
|
88
|
-
|
|
89
|
-
if (report.violations.length > 0) {
|
|
90
|
-
process.stdout.write("\nErrors\n\n");
|
|
91
|
-
const groupedByCol = new Map<string, typeof report.violations>();
|
|
92
|
-
for (const v of report.violations) {
|
|
93
|
-
if (!groupedByCol.has(v.column)) groupedByCol.set(v.column, []);
|
|
94
|
-
groupedByCol.get(v.column)!.push(v);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
for (const [col, violations] of groupedByCol.entries()) {
|
|
98
|
-
process.stdout.write(`${col}\n`);
|
|
99
|
-
for (const v of violations) {
|
|
100
|
-
process.stdout.write(` ${v.rule}\n`);
|
|
101
|
-
process.stdout.write(` ${formatNumber(v.violationsCount)} violations\n\n`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
logMemoryDebug();
|
|
108
|
-
|
|
109
|
-
if (!report.isValid) {
|
|
110
|
-
throw new ValidationError(
|
|
111
|
-
`Schema validation failed: ${report.invalidRows} invalid rows found`,
|
|
112
|
-
report.violations.map((v) => ({
|
|
113
|
-
column: v.column,
|
|
114
|
-
expected: v.rule,
|
|
115
|
-
count: v.violationsCount,
|
|
116
|
-
examples: v.sampleInvalidValues,
|
|
117
|
-
})),
|
|
118
|
-
report.totalRows,
|
|
119
|
-
report.validRows,
|
|
120
|
-
report.invalidRows
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
package/src/cli/index.ts
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import { RowpipeError } from "../core/errors.js";
|
|
5
|
-
import { castCommand } from "./commands/cast.js";
|
|
6
|
-
import { convertCommand } from "./commands/convert.js";
|
|
7
|
-
import { filterCommand } from "./commands/filter.js";
|
|
8
|
-
import { inspectCommand } from "./commands/inspect.js";
|
|
9
|
-
import { mapCommand } from "./commands/map.js";
|
|
10
|
-
import { reduceCommand } from "./commands/reduce.js";
|
|
11
|
-
import { renameCommand } from "./commands/rename.js";
|
|
12
|
-
import { sampleCommand } from "./commands/sample.js";
|
|
13
|
-
import { schemaCommand } from "./commands/schema.js";
|
|
14
|
-
import { selectCommand } from "./commands/select.js";
|
|
15
|
-
import { statsCommand } from "./commands/stats.js";
|
|
16
|
-
import { validateCommand } from "./commands/validate.js";
|
|
17
|
-
|
|
18
|
-
// Handle broken pipe gracefully when piping to head/less
|
|
19
|
-
process.stdout.on("error", (err: unknown) => {
|
|
20
|
-
if ((err as NodeJS.ErrnoException).code === "EPIPE") {
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const program = new Command();
|
|
26
|
-
|
|
27
|
-
program
|
|
28
|
-
.name("rowpipe")
|
|
29
|
-
.description("Stream-first tabular data toolkit for CSV, JSON, JSONL, and XLSX")
|
|
30
|
-
.version("1.0.0");
|
|
31
|
-
|
|
32
|
-
// Global options
|
|
33
|
-
program
|
|
34
|
-
.option("--batch-size <number>", "Processing batch size in rows", "1000")
|
|
35
|
-
.option("--quiet", "Suppress non-data output and progress")
|
|
36
|
-
.option("--no-progress", "Disable real-time progress bar");
|
|
37
|
-
|
|
38
|
-
// 1. inspect
|
|
39
|
-
program
|
|
40
|
-
.command("inspect [input]")
|
|
41
|
-
.description("Inspect format, row count, columns, and sheet summary")
|
|
42
|
-
.option("--sheet <sheet>", "Target worksheet name or index for XLSX")
|
|
43
|
-
.option("--path <path>", "Nested object path for JSON (e.g. data.results)")
|
|
44
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
45
|
-
.option("--json", "Output metadata as machine-readable JSON")
|
|
46
|
-
.action(async (input = "-", cmdOptions) => {
|
|
47
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
48
|
-
await inspectCommand(input, opts);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// 2. convert
|
|
52
|
-
program
|
|
53
|
-
.command("convert [input] [output]")
|
|
54
|
-
.description("Stream convert tabular datasets across formats")
|
|
55
|
-
.option("--from <format>", "Input format (csv, json, jsonl, xlsx)")
|
|
56
|
-
.option("--to <format>", "Output format (csv, json, jsonl, xlsx)")
|
|
57
|
-
.option("--sheet <sheet>", "Worksheet name or index for XLSX")
|
|
58
|
-
.option("--all-sheets", "Export all worksheets in the workbook to individual files")
|
|
59
|
-
.option("--out-dir <dir>", "Output directory for --all-sheets export")
|
|
60
|
-
.option("--path <path>", "Nested path for JSON array")
|
|
61
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
62
|
-
.option("--no-header", "Disable writing or reading headers in CSV")
|
|
63
|
-
.action(async (input = "-", output, cmdOptions) => {
|
|
64
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
65
|
-
await convertCommand(input, output, opts);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// 3. schema
|
|
69
|
-
program
|
|
70
|
-
.command("schema [input]")
|
|
71
|
-
.description("Infer column types, nullability, and semantic annotations")
|
|
72
|
-
.option("--sample <rows>", "Number of rows to sample for inference", "10000")
|
|
73
|
-
.option("--full", "Scan full stream for exact schema inference")
|
|
74
|
-
.option("--from <format>", "Input format")
|
|
75
|
-
.option("--sheet <sheet>", "Worksheet name or index for XLSX")
|
|
76
|
-
.option("--path <path>", "Nested path for JSON")
|
|
77
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
78
|
-
.option("--json", "Output schema as machine-readable JSON")
|
|
79
|
-
.action(async (input = "-", cmdOptions) => {
|
|
80
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
81
|
-
await schemaCommand(input, opts);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// 4. stats
|
|
85
|
-
program
|
|
86
|
-
.command("stats [input]")
|
|
87
|
-
.description("Compute streaming statistics (Welford numeric stats and HLL distinct counts)")
|
|
88
|
-
.option("--column <name>", "Compute statistics only for specific column")
|
|
89
|
-
.option("--fast", "Use approximate algorithms for distinct counts")
|
|
90
|
-
.option("--exact", "Use exact counts where available")
|
|
91
|
-
.option("--from <format>", "Input format")
|
|
92
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
93
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
94
|
-
.option("--json", "Output stats as machine-readable JSON")
|
|
95
|
-
.action(async (input = "-", cmdOptions) => {
|
|
96
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
97
|
-
await statsCommand(input, opts);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// 5. select
|
|
101
|
-
program
|
|
102
|
-
.command("select [input] [columns]")
|
|
103
|
-
.description("Select a subset of columns in stream")
|
|
104
|
-
.option("--from <format>", "Input format")
|
|
105
|
-
.option("--to <format>", "Output format")
|
|
106
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
107
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
108
|
-
.action(async (inputOrCols, maybeCols, cmdOptions) => {
|
|
109
|
-
let input = "-";
|
|
110
|
-
let columns = "";
|
|
111
|
-
|
|
112
|
-
if (maybeCols !== undefined) {
|
|
113
|
-
input = inputOrCols || "-";
|
|
114
|
-
columns = maybeCols;
|
|
115
|
-
} else {
|
|
116
|
-
columns = inputOrCols || "";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
120
|
-
await selectCommand(input, columns, opts);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// 6. rename
|
|
124
|
-
program
|
|
125
|
-
.command("rename <args...>")
|
|
126
|
-
.description("Rename columns in stream (e.g. rowpipe rename users.csv old=new)")
|
|
127
|
-
.option("--from <format>", "Input format")
|
|
128
|
-
.option("--to <format>", "Output format")
|
|
129
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
130
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
131
|
-
.action(async (args, cmdOptions) => {
|
|
132
|
-
let input = "-";
|
|
133
|
-
let specs: string[] = [];
|
|
134
|
-
|
|
135
|
-
if (args[0] && !args[0].includes("=")) {
|
|
136
|
-
input = args[0];
|
|
137
|
-
specs = args.slice(1);
|
|
138
|
-
} else {
|
|
139
|
-
specs = args;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
143
|
-
await renameCommand(input, specs, opts);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// 7. cast
|
|
147
|
-
program
|
|
148
|
-
.command("cast <args...>")
|
|
149
|
-
.description("Cast column types in stream (e.g. rowpipe cast users.csv age:number active:boolean)")
|
|
150
|
-
.option("--on-error <behavior>", "Error behavior: fail, null, keep, skip-row", "null")
|
|
151
|
-
.option("--from <format>", "Input format")
|
|
152
|
-
.option("--to <format>", "Output format")
|
|
153
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
154
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
155
|
-
.action(async (args, cmdOptions) => {
|
|
156
|
-
let input = "-";
|
|
157
|
-
let specs: string[] = [];
|
|
158
|
-
|
|
159
|
-
if (args[0] && !args[0].includes(":")) {
|
|
160
|
-
input = args[0];
|
|
161
|
-
specs = args.slice(1);
|
|
162
|
-
} else {
|
|
163
|
-
specs = args;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
167
|
-
await castCommand(input, specs, opts);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// 8. filter
|
|
171
|
-
program
|
|
172
|
-
.command("filter [input] [expression]")
|
|
173
|
-
.description("Filter rows using safe expression engine (e.g. rowpipe filter users.csv 'age > 30')")
|
|
174
|
-
.option("--from <format>", "Input format")
|
|
175
|
-
.option("--to <format>", "Output format")
|
|
176
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
177
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
178
|
-
.action(async (inputOrExpr, maybeExpr, cmdOptions) => {
|
|
179
|
-
let input = "-";
|
|
180
|
-
let expression = "";
|
|
181
|
-
|
|
182
|
-
if (maybeExpr !== undefined) {
|
|
183
|
-
input = inputOrExpr || "-";
|
|
184
|
-
expression = maybeExpr;
|
|
185
|
-
} else {
|
|
186
|
-
expression = inputOrExpr || "";
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
190
|
-
await filterCommand(input, expression, opts);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// 9. map
|
|
194
|
-
program
|
|
195
|
-
.command("map <args...>")
|
|
196
|
-
.description("Transform and derive columns per row (e.g. rowpipe map sales.csv 'profit=revenue-cost')")
|
|
197
|
-
.option("--from <format>", "Input format")
|
|
198
|
-
.option("--to <format>", "Output format")
|
|
199
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
200
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
201
|
-
.action(async (args, cmdOptions) => {
|
|
202
|
-
let input = "-";
|
|
203
|
-
let specs: string[] = [];
|
|
204
|
-
|
|
205
|
-
if (args[0] && !args[0].includes("=")) {
|
|
206
|
-
input = args[0];
|
|
207
|
-
specs = args.slice(1);
|
|
208
|
-
} else {
|
|
209
|
-
specs = args;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
213
|
-
await mapCommand(input, specs, opts);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// 10. reduce
|
|
217
|
-
program
|
|
218
|
-
.command("reduce <args...>")
|
|
219
|
-
.description("Aggregate and group dataset (e.g. rowpipe reduce sales.csv 'total=sum(revenue)' --by country)")
|
|
220
|
-
.option("--by <columns>", "Group by columns (comma-separated, e.g. country,category)")
|
|
221
|
-
.option("--from <format>", "Input format")
|
|
222
|
-
.option("--to <format>", "Output format")
|
|
223
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
224
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
225
|
-
.action(async (args, cmdOptions) => {
|
|
226
|
-
let input = "-";
|
|
227
|
-
let specs: string[] = [];
|
|
228
|
-
|
|
229
|
-
if (args[0] && !args[0].includes("=")) {
|
|
230
|
-
input = args[0];
|
|
231
|
-
specs = args.slice(1);
|
|
232
|
-
} else {
|
|
233
|
-
specs = args;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
237
|
-
await reduceCommand(input, specs, opts);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// 11. sample
|
|
241
|
-
program
|
|
242
|
-
.command("sample [input]")
|
|
243
|
-
.description("Reservoir sample rows with bounded memory")
|
|
244
|
-
.option("--rows <number>", "Number of rows to sample", "1000")
|
|
245
|
-
.option("--seed <number>", "Deterministic random seed")
|
|
246
|
-
.option("--from <format>", "Input format")
|
|
247
|
-
.option("--to <format>", "Output format")
|
|
248
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
249
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
250
|
-
.action(async (input = "-", cmdOptions) => {
|
|
251
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
252
|
-
await sampleCommand(input, opts);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// 10. validate
|
|
256
|
-
program
|
|
257
|
-
.command("validate [input]")
|
|
258
|
-
.description("Validate stream against schema definition")
|
|
259
|
-
.requiredOption("--schema <file>", "Path to JSON schema file")
|
|
260
|
-
.option("--from <format>", "Input format")
|
|
261
|
-
.option("--sheet <sheet>", "Worksheet name for XLSX")
|
|
262
|
-
.option("--delimiter <delim>", "Custom CSV delimiter")
|
|
263
|
-
.option("--json", "Output validation report as JSON")
|
|
264
|
-
.action(async (input = "-", cmdOptions) => {
|
|
265
|
-
const opts = { ...program.opts(), ...cmdOptions };
|
|
266
|
-
await validateCommand(input, opts);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
async function main() {
|
|
270
|
-
try {
|
|
271
|
-
await program.parseAsync(process.argv);
|
|
272
|
-
} catch (err: unknown) {
|
|
273
|
-
if (err instanceof RowpipeError) {
|
|
274
|
-
process.stderr.write(`\nError: ${err.message}\n`);
|
|
275
|
-
process.exit(err.exitCode);
|
|
276
|
-
}
|
|
277
|
-
process.stderr.write(`\nUnexpected error: ${(err as Error).message}\n`);
|
|
278
|
-
if (process.env["DEBUG"]) {
|
|
279
|
-
process.stderr.write(`${(err as Error).stack}\n`);
|
|
280
|
-
}
|
|
281
|
-
process.exit(1);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
main();
|
package/src/core/batch.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { DataBatch, DataStream, Row } from "./types.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Packs an AsyncIterable of individual rows into DataBatch chunks with a given batchSize.
|
|
5
|
-
*/
|
|
6
|
-
export async function* rowsToBatches(
|
|
7
|
-
rows: AsyncIterable<Row>,
|
|
8
|
-
batchSize = 1000
|
|
9
|
-
): AsyncGenerator<DataBatch> {
|
|
10
|
-
const effectiveBatchSize = Math.max(1, batchSize);
|
|
11
|
-
let currentRows: Row[] = [];
|
|
12
|
-
let totalOffset = 0;
|
|
13
|
-
|
|
14
|
-
for await (const row of rows) {
|
|
15
|
-
currentRows.push(row);
|
|
16
|
-
if (currentRows.length >= effectiveBatchSize) {
|
|
17
|
-
yield {
|
|
18
|
-
rows: currentRows,
|
|
19
|
-
offset: totalOffset,
|
|
20
|
-
};
|
|
21
|
-
totalOffset += currentRows.length;
|
|
22
|
-
currentRows = [];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (currentRows.length > 0) {
|
|
27
|
-
yield {
|
|
28
|
-
rows: currentRows,
|
|
29
|
-
offset: totalOffset,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Unpacks DataBatches into individual rows.
|
|
36
|
-
*/
|
|
37
|
-
export async function* batchesToRows(stream: DataStream): AsyncGenerator<Row> {
|
|
38
|
-
for await (const batch of stream) {
|
|
39
|
-
for (const row of batch.rows) {
|
|
40
|
-
yield row;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Applies a mapping function to each row in batches, maintaining batch structure
|
|
47
|
-
* and filtering out null/undefined results.
|
|
48
|
-
*/
|
|
49
|
-
export async function* batchMap(
|
|
50
|
-
stream: DataStream,
|
|
51
|
-
fn: (row: Row, globalIndex: number) => Row | null | undefined,
|
|
52
|
-
targetBatchSize = 1000
|
|
53
|
-
): AsyncGenerator<DataBatch> {
|
|
54
|
-
let pendingRows: Row[] = [];
|
|
55
|
-
let currentOffset = 0;
|
|
56
|
-
let globalIndex = 0;
|
|
57
|
-
|
|
58
|
-
for await (const batch of stream) {
|
|
59
|
-
for (const row of batch.rows) {
|
|
60
|
-
const transformed = fn(row, globalIndex++);
|
|
61
|
-
if (transformed !== null && transformed !== undefined) {
|
|
62
|
-
pendingRows.push(transformed);
|
|
63
|
-
if (pendingRows.length >= targetBatchSize) {
|
|
64
|
-
yield {
|
|
65
|
-
rows: pendingRows,
|
|
66
|
-
offset: currentOffset,
|
|
67
|
-
};
|
|
68
|
-
currentOffset += pendingRows.length;
|
|
69
|
-
pendingRows = [];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (pendingRows.length > 0) {
|
|
76
|
-
yield {
|
|
77
|
-
rows: pendingRows,
|
|
78
|
-
offset: currentOffset,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
package/src/core/errors.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export class RowpipeError extends Error {
|
|
2
|
-
public readonly exitCode: number;
|
|
3
|
-
|
|
4
|
-
constructor(message: string, exitCode = 1) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "RowpipeError";
|
|
7
|
-
this.exitCode = exitCode;
|
|
8
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class InvalidArgumentError extends RowpipeError {
|
|
13
|
-
constructor(message: string) {
|
|
14
|
-
super(message, 2);
|
|
15
|
-
this.name = "InvalidArgumentError";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ParseErrorContext {
|
|
20
|
-
file?: string;
|
|
21
|
-
sheet?: string;
|
|
22
|
-
row?: number;
|
|
23
|
-
column?: string | number;
|
|
24
|
-
byteOffset?: number;
|
|
25
|
-
line?: number;
|
|
26
|
-
cell?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class ParseError extends RowpipeError {
|
|
30
|
-
public readonly context: ParseErrorContext;
|
|
31
|
-
|
|
32
|
-
constructor(message: string, context: ParseErrorContext = {}) {
|
|
33
|
-
let detailedMsg = `Parse error: ${message}`;
|
|
34
|
-
const parts: string[] = [];
|
|
35
|
-
|
|
36
|
-
if (context.file) parts.push(`File: ${context.file}`);
|
|
37
|
-
if (context.sheet) parts.push(`Sheet: ${context.sheet}`);
|
|
38
|
-
if (context.row !== undefined) parts.push(`Row: ${context.row}`);
|
|
39
|
-
if (context.column !== undefined) parts.push(`Column: ${context.column}`);
|
|
40
|
-
if (context.cell) parts.push(`Cell: ${context.cell}`);
|
|
41
|
-
if (context.byteOffset !== undefined) parts.push(`Byte offset: ${context.byteOffset}`);
|
|
42
|
-
|
|
43
|
-
if (parts.length > 0) {
|
|
44
|
-
detailedMsg += `\n Context: ${parts.join(", ")}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
super(detailedMsg, 3);
|
|
48
|
-
this.name = "ParseError";
|
|
49
|
-
this.context = context;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ValidationViolation {
|
|
54
|
-
column: string;
|
|
55
|
-
expected: string;
|
|
56
|
-
actual?: string;
|
|
57
|
-
count: number;
|
|
58
|
-
examples?: unknown[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class ValidationError extends RowpipeError {
|
|
62
|
-
public readonly violations: ValidationViolation[];
|
|
63
|
-
public readonly totalRows: number;
|
|
64
|
-
public readonly validRows: number;
|
|
65
|
-
public readonly invalidRows: number;
|
|
66
|
-
|
|
67
|
-
constructor(
|
|
68
|
-
message: string,
|
|
69
|
-
violations: ValidationViolation[] = [],
|
|
70
|
-
totalRows = 0,
|
|
71
|
-
validRows = 0,
|
|
72
|
-
invalidRows = 0
|
|
73
|
-
) {
|
|
74
|
-
super(message, 4);
|
|
75
|
-
this.name = "ValidationError";
|
|
76
|
-
this.violations = violations;
|
|
77
|
-
this.totalRows = totalRows;
|
|
78
|
-
this.validRows = validRows;
|
|
79
|
-
this.invalidRows = invalidRows;
|
|
80
|
-
}
|
|
81
|
-
}
|