rowpipe 1.0.0 → 1.1.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.
Files changed (106) hide show
  1. package/README.md +25 -32
  2. package/dist/cli/index.js +4 -4
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/types.d.ts +2 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/readers/csv.d.ts.map +1 -1
  11. package/dist/readers/csv.js +28 -16
  12. package/dist/readers/csv.js.map +1 -1
  13. package/dist/readers/index.d.ts +1 -0
  14. package/dist/readers/index.d.ts.map +1 -1
  15. package/dist/readers/index.js +29 -3
  16. package/dist/readers/index.js.map +1 -1
  17. package/dist/readers/json.d.ts.map +1 -1
  18. package/dist/readers/json.js +6 -8
  19. package/dist/readers/json.js.map +1 -1
  20. package/dist/readers/jsonl.d.ts.map +1 -1
  21. package/dist/readers/jsonl.js +5 -7
  22. package/dist/readers/jsonl.js.map +1 -1
  23. package/dist/readers/parquet.d.ts +17 -0
  24. package/dist/readers/parquet.d.ts.map +1 -0
  25. package/dist/readers/parquet.js +146 -0
  26. package/dist/readers/parquet.js.map +1 -0
  27. package/dist/utils/compression.d.ts +30 -6
  28. package/dist/utils/compression.d.ts.map +1 -1
  29. package/dist/utils/compression.js +95 -18
  30. package/dist/utils/compression.js.map +1 -1
  31. package/dist/writers/csv.d.ts.map +1 -1
  32. package/dist/writers/csv.js +9 -11
  33. package/dist/writers/csv.js.map +1 -1
  34. package/dist/writers/index.d.ts +2 -0
  35. package/dist/writers/index.d.ts.map +1 -1
  36. package/dist/writers/index.js +49 -4
  37. package/dist/writers/index.js.map +1 -1
  38. package/dist/writers/json.d.ts +1 -1
  39. package/dist/writers/json.d.ts.map +1 -1
  40. package/dist/writers/json.js +9 -11
  41. package/dist/writers/json.js.map +1 -1
  42. package/dist/writers/jsonl.d.ts +1 -1
  43. package/dist/writers/jsonl.d.ts.map +1 -1
  44. package/dist/writers/jsonl.js +9 -11
  45. package/dist/writers/jsonl.js.map +1 -1
  46. package/dist/writers/markdown.d.ts +21 -0
  47. package/dist/writers/markdown.d.ts.map +1 -0
  48. package/dist/writers/markdown.js +82 -0
  49. package/dist/writers/markdown.js.map +1 -0
  50. package/dist/writers/parquet.d.ts +19 -0
  51. package/dist/writers/parquet.d.ts.map +1 -0
  52. package/dist/writers/parquet.js +129 -0
  53. package/dist/writers/parquet.js.map +1 -0
  54. package/package.json +13 -2
  55. package/skills/rowpipe/SKILL.md +181 -0
  56. package/benchmarks/memory-bench.ts +0 -109
  57. package/src/analytics/reduce.ts +0 -388
  58. package/src/analytics/schema-inference.ts +0 -217
  59. package/src/analytics/semantic-types.ts +0 -36
  60. package/src/analytics/stats.ts +0 -383
  61. package/src/analytics/validator.ts +0 -134
  62. package/src/cli/commands/cast.ts +0 -60
  63. package/src/cli/commands/convert.ts +0 -128
  64. package/src/cli/commands/filter.ts +0 -58
  65. package/src/cli/commands/inspect.ts +0 -193
  66. package/src/cli/commands/map.ts +0 -66
  67. package/src/cli/commands/reduce.ts +0 -74
  68. package/src/cli/commands/rename.ts +0 -59
  69. package/src/cli/commands/sample.ts +0 -61
  70. package/src/cli/commands/schema.ts +0 -86
  71. package/src/cli/commands/select.ts +0 -59
  72. package/src/cli/commands/stats.ts +0 -100
  73. package/src/cli/commands/validate.ts +0 -123
  74. package/src/cli/index.ts +0 -285
  75. package/src/core/batch.ts +0 -81
  76. package/src/core/errors.ts +0 -81
  77. package/src/core/pipeline.ts +0 -150
  78. package/src/core/types.ts +0 -110
  79. package/src/index.ts +0 -52
  80. package/src/readers/csv.ts +0 -302
  81. package/src/readers/index.ts +0 -88
  82. package/src/readers/json.ts +0 -256
  83. package/src/readers/jsonl.ts +0 -126
  84. package/src/readers/xlsx.ts +0 -182
  85. package/src/transforms/cast.ts +0 -212
  86. package/src/transforms/expression.ts +0 -1114
  87. package/src/transforms/filter.ts +0 -38
  88. package/src/transforms/map.ts +0 -99
  89. package/src/transforms/rename.ts +0 -47
  90. package/src/transforms/sample.ts +0 -57
  91. package/src/transforms/select.ts +0 -64
  92. package/src/utils/compression.ts +0 -43
  93. package/src/utils/formatting.ts +0 -75
  94. package/src/utils/progress.ts +0 -37
  95. package/src/writers/csv.ts +0 -106
  96. package/src/writers/index.ts +0 -87
  97. package/src/writers/json.ts +0 -52
  98. package/src/writers/jsonl.ts +0 -49
  99. package/src/writers/xlsx.ts +0 -87
  100. package/tests/analytics.test.ts +0 -119
  101. package/tests/cli-integration.test.ts +0 -187
  102. package/tests/map-reduce.test.ts +0 -223
  103. package/tests/readers-writers.test.ts +0 -189
  104. package/tests/transforms.test.ts +0 -199
  105. package/tsconfig.json +0 -21
  106. package/vitest.config.ts +0 -9
@@ -1,126 +0,0 @@
1
- import { createReadStream } from "node:fs";
2
- import { createInterface } from "node:readline";
3
- import type { Readable } from "node:stream";
4
- import { ParseError } from "../core/errors.js";
5
- import type {
6
- DataBatch,
7
- DataStream,
8
- InspectionMetadata,
9
- ReaderOptions,
10
- Row,
11
- TabularReader,
12
- } from "../core/types.js";
13
-
14
- export interface JSONLReaderOptions extends ReaderOptions {
15
- filePath?: string;
16
- }
17
-
18
- export class JSONLReader implements TabularReader {
19
- private input: Readable | string;
20
- private options: JSONLReaderOptions;
21
-
22
- constructor(input: Readable | string, options: JSONLReaderOptions = {}) {
23
- this.input = input;
24
- this.options = { ...options };
25
- if (typeof input === "string") {
26
- this.options.filePath = input;
27
- }
28
- }
29
-
30
- private getInputStream(): Readable {
31
- if (typeof this.input === "string") {
32
- return createReadStream(this.input);
33
- }
34
- return this.input;
35
- }
36
-
37
- async *read(options?: ReaderOptions): DataStream {
38
- const mergedOptions: JSONLReaderOptions = {
39
- ...this.options,
40
- ...options,
41
- };
42
- const batchSize = Math.max(1, mergedOptions.batchSize ?? 1000);
43
- const stream = this.getInputStream();
44
-
45
- const rl = createInterface({
46
- input: stream,
47
- crlfDelay: Infinity,
48
- });
49
-
50
- let lineNumber = 0;
51
- let rowsInCurrentBatch: Row[] = [];
52
- let globalOffset = 0;
53
-
54
- for await (const line of rl) {
55
- lineNumber++;
56
- const trimmed = line.trim();
57
- if (!trimmed) continue;
58
-
59
- let parsed: unknown;
60
- try {
61
- parsed = JSON.parse(trimmed);
62
- } catch (err) {
63
- throw new ParseError(
64
- `Invalid JSON on line ${lineNumber}: ${(err as Error).message}`,
65
- {
66
- file: mergedOptions.filePath,
67
- line: lineNumber,
68
- row: lineNumber,
69
- }
70
- );
71
- }
72
-
73
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
74
- throw new ParseError(
75
- `Expected JSON object on line ${lineNumber}, got ${Array.isArray(parsed) ? "array" : typeof parsed}`,
76
- {
77
- file: mergedOptions.filePath,
78
- line: lineNumber,
79
- row: lineNumber,
80
- }
81
- );
82
- }
83
-
84
- rowsInCurrentBatch.push(parsed as Row);
85
-
86
- if (rowsInCurrentBatch.length >= batchSize) {
87
- yield {
88
- rows: rowsInCurrentBatch,
89
- offset: globalOffset,
90
- };
91
- globalOffset += rowsInCurrentBatch.length;
92
- rowsInCurrentBatch = [];
93
- }
94
- }
95
-
96
- if (rowsInCurrentBatch.length > 0) {
97
- yield {
98
- rows: rowsInCurrentBatch,
99
- offset: globalOffset,
100
- };
101
- }
102
- }
103
-
104
- async inspect(options?: ReaderOptions): Promise<InspectionMetadata> {
105
- let rowCount = 0;
106
- let columnNames: string[] = [];
107
-
108
- for await (const batch of this.read({ ...options, batchSize: 5000 })) {
109
- if (columnNames.length === 0 && batch.rows.length > 0 && batch.rows[0]) {
110
- columnNames = Object.keys(batch.rows[0]);
111
- }
112
- rowCount += batch.rows.length;
113
- }
114
-
115
- return {
116
- format: "JSONL",
117
- rowCount,
118
- columnCount: columnNames.length,
119
- columns: columnNames.map((name) => ({
120
- name,
121
- type: "string",
122
- nullPercentage: 0,
123
- })),
124
- };
125
- }
126
- }
@@ -1,182 +0,0 @@
1
- import type { Readable } from "node:stream";
2
- import readXlsxFile from "read-excel-file/node";
3
- import { InvalidArgumentError, ParseError } from "../core/errors.js";
4
- import type {
5
- DataBatch,
6
- DataStream,
7
- InspectionMetadata,
8
- ReaderOptions,
9
- Row,
10
- SheetMetadata,
11
- TabularReader,
12
- } from "../core/types.js";
13
-
14
- export interface XLSXReaderOptions extends ReaderOptions {
15
- sheet?: string | number;
16
- filePath?: string;
17
- }
18
-
19
- export class XLSXReader implements TabularReader {
20
- private input: Readable | string;
21
- private options: XLSXReaderOptions;
22
-
23
- constructor(input: Readable | string, options: XLSXReaderOptions = {}) {
24
- this.input = input;
25
- this.options = { ...options };
26
- if (typeof input === "string") {
27
- this.options.filePath = input;
28
- }
29
- }
30
-
31
- async getParsedSheets(): Promise<Array<{ sheet: string; data: unknown[][] }>> {
32
- try {
33
- const result = await (readXlsxFile as unknown as (input: unknown, options?: unknown) => Promise<unknown>)(
34
- this.input,
35
- { getSheets: true }
36
- );
37
- if (Array.isArray(result)) {
38
- if (result.length > 0 && typeof result[0] === "object" && result[0] !== null && "sheet" in result[0]) {
39
- return result as Array<{ sheet: string; data: unknown[][] }>;
40
- }
41
- return [{ sheet: "Sheet1", data: result as unknown[][] }];
42
- }
43
- return [];
44
- } catch (err) {
45
- throw new ParseError(`Failed to parse XLSX: ${(err as Error).message}`, {
46
- file: this.options.filePath,
47
- });
48
- }
49
- }
50
-
51
- async *read(options?: ReaderOptions): DataStream {
52
- const mergedOptions: XLSXReaderOptions = {
53
- ...this.options,
54
- ...options,
55
- };
56
- const batchSize = Math.max(1, mergedOptions.batchSize ?? 1000);
57
- const targetSheet = mergedOptions.sheet;
58
-
59
- const sheets = await this.getParsedSheets();
60
- if (sheets.length === 0) {
61
- return;
62
- }
63
-
64
- let selectedSheet: { sheet: string; data: unknown[][] } | undefined;
65
-
66
- if (targetSheet === undefined || targetSheet === null) {
67
- selectedSheet = sheets[0];
68
- } else if (typeof targetSheet === "number") {
69
- selectedSheet = sheets[targetSheet - 1];
70
- } else {
71
- selectedSheet = sheets.find(
72
- (s) => s.sheet.toLowerCase() === String(targetSheet).toLowerCase()
73
- );
74
- }
75
-
76
- if (!selectedSheet) {
77
- throw new InvalidArgumentError(
78
- `Sheet "${targetSheet}" was not found in the workbook. Available sheets: ${sheets.map((s) => s.sheet).join(", ")}`
79
- );
80
- }
81
-
82
- const grid = selectedSheet.data;
83
- if (grid.length === 0) return;
84
-
85
- let headers: string[] | null = null;
86
- let rowsInCurrentBatch: Row[] = [];
87
- let globalOffset = 0;
88
-
89
- for (let r = 0; r < grid.length; r++) {
90
- const rowCells = grid[r] || [];
91
- if (rowCells.length === 0 || rowCells.every((c) => c === null || c === undefined || c === "")) {
92
- continue;
93
- }
94
-
95
- if (headers === null) {
96
- headers = rowCells.map((c, i) => {
97
- const str = c !== null && c !== undefined ? String(c).trim() : "";
98
- return str.length > 0 ? str : `col_${i + 1}`;
99
- });
100
- continue;
101
- }
102
-
103
- const rowObj: Row = {};
104
- for (let i = 0; i < headers.length; i++) {
105
- const key = headers[i] || `col_${i + 1}`;
106
- const cellVal = rowCells[i];
107
- rowObj[key] = cellVal !== null && cellVal !== undefined ? cellVal : "";
108
- }
109
-
110
- rowsInCurrentBatch.push(rowObj);
111
-
112
- if (rowsInCurrentBatch.length >= batchSize) {
113
- yield {
114
- rows: rowsInCurrentBatch,
115
- offset: globalOffset,
116
- };
117
- globalOffset += rowsInCurrentBatch.length;
118
- rowsInCurrentBatch = [];
119
- }
120
- }
121
-
122
- if (rowsInCurrentBatch.length > 0) {
123
- yield {
124
- rows: rowsInCurrentBatch,
125
- offset: globalOffset,
126
- };
127
- }
128
- }
129
-
130
- async inspect(options?: ReaderOptions): Promise<InspectionMetadata> {
131
- const sheets = await this.getParsedSheets();
132
- const targetSheet = options?.sheet ?? this.options.sheet;
133
-
134
- const sheetSummaries: SheetMetadata[] = sheets.map((s) => {
135
- const nonHeaderRows = Math.max(0, s.data.length - 1);
136
- const headerRow = s.data[0] || [];
137
- const colHeaders = headerRow.map((c, i) =>
138
- c !== null && c !== undefined ? String(c).trim() : `col_${i + 1}`
139
- );
140
-
141
- return {
142
- name: s.sheet,
143
- rowCount: nonHeaderRows,
144
- columnCount: colHeaders.length,
145
- columns: colHeaders,
146
- };
147
- });
148
-
149
- let selectedSheet = sheets[0];
150
- if (targetSheet !== undefined && targetSheet !== null) {
151
- selectedSheet =
152
- typeof targetSheet === "number"
153
- ? sheets[targetSheet - 1]
154
- : sheets.find((s) => s.sheet.toLowerCase() === String(targetSheet).toLowerCase());
155
- }
156
-
157
- let columns: Array<{ name: string; type: any; nullPercentage: number }> = [];
158
- let rowCount = 0;
159
-
160
- if (selectedSheet && selectedSheet.data.length > 0) {
161
- const headerRow = selectedSheet.data[0] || [];
162
- const colHeaders = headerRow.map((c, i) =>
163
- c !== null && c !== undefined ? String(c).trim() : `col_${i + 1}`
164
- );
165
- rowCount = Math.max(0, selectedSheet.data.length - 1);
166
- columns = colHeaders.map((name) => ({
167
- name,
168
- type: "string",
169
- nullPercentage: 0,
170
- }));
171
- }
172
-
173
- return {
174
- format: "XLSX",
175
- rowCount,
176
- columnCount: columns.length,
177
- columns,
178
- sheetsCount: sheets.length,
179
- sheets: sheetSummaries,
180
- };
181
- }
182
- }
@@ -1,212 +0,0 @@
1
- import { InvalidArgumentError } from "../core/errors.js";
2
- import type { DataStream, Row, TransformFunction } from "../core/types.js";
3
-
4
- export type CastOnError = "fail" | "null" | "keep" | "skip-row";
5
-
6
- export interface CastOptions {
7
- onError?: CastOnError;
8
- }
9
-
10
- /**
11
- * Casts a single value to the target type.
12
- */
13
- export function castValue(
14
- value: unknown,
15
- targetType: string,
16
- onError: CastOnError = "null"
17
- ): { value: unknown; success: boolean } {
18
- if (value === null || value === undefined || value === "") {
19
- return { value: null, success: true };
20
- }
21
-
22
- const str = String(value).trim();
23
- const lowerType = targetType.toLowerCase();
24
-
25
- try {
26
- switch (lowerType) {
27
- case "string":
28
- return { value: String(value), success: true };
29
-
30
- case "integer":
31
- case "int": {
32
- if (!/^-?\d+$/.test(str)) {
33
- throw new Error(`Cannot cast "${str}" to integer`);
34
- }
35
- const parsedInt = Number.parseInt(str, 10);
36
- if (Number.isNaN(parsedInt)) {
37
- throw new Error(`Cannot cast "${str}" to integer`);
38
- }
39
- return { value: parsedInt, success: true };
40
- }
41
-
42
- case "number":
43
- case "float":
44
- case "double": {
45
- const parsedNum = Number.parseFloat(str);
46
- if (Number.isNaN(parsedNum)) {
47
- throw new Error(`Cannot cast "${str}" to number`);
48
- }
49
- return { value: parsedNum, success: true };
50
- }
51
-
52
- case "boolean":
53
- case "bool": {
54
- const lower = str.toLowerCase();
55
- if (["true", "1", "yes", "t", "y"].includes(lower)) {
56
- return { value: true, success: true };
57
- }
58
- if (["false", "0", "no", "f", "n"].includes(lower)) {
59
- return { value: false, success: true };
60
- }
61
- throw new Error(`Cannot cast "${str}" to boolean`);
62
- }
63
-
64
- case "date": {
65
- const d = new Date(str);
66
- if (Number.isNaN(d.getTime())) {
67
- throw new Error(`Cannot cast "${str}" to date`);
68
- }
69
- return { value: d.toISOString().split("T")[0], success: true };
70
- }
71
-
72
- case "datetime": {
73
- const d = new Date(str);
74
- if (Number.isNaN(d.getTime())) {
75
- throw new Error(`Cannot cast "${str}" to datetime`);
76
- }
77
- return { value: d.toISOString(), success: true };
78
- }
79
-
80
- case "json": {
81
- const parsed = JSON.parse(str);
82
- return { value: parsed, success: true };
83
- }
84
-
85
- default:
86
- throw new Error(`Unknown target type "${targetType}"`);
87
- }
88
- } catch (err) {
89
- if (onError === "fail") {
90
- throw new InvalidArgumentError(
91
- `Failed to cast value "${str}" to type "${targetType}": ${(err as Error).message}`
92
- );
93
- }
94
- if (onError === "keep") {
95
- return { value, success: false };
96
- }
97
- // "null" or "skip-row"
98
- return { value: null, success: false };
99
- }
100
- }
101
-
102
- /**
103
- * Creates a transform that casts specified columns to target types.
104
- */
105
- export function castColumns(
106
- typeSpecs: Record<string, string>,
107
- options: CastOptions = {}
108
- ): TransformFunction {
109
- const onError = options.onError || "null";
110
-
111
- const compiled = Object.entries(typeSpecs).map(([col, targetType]) => {
112
- const lower = targetType.toLowerCase();
113
-
114
- // Fast-path specialized caster functions
115
- if (lower === "number" || lower === "float" || lower === "double") {
116
- return {
117
- col,
118
- cast: (val: unknown) => {
119
- if (val === null || val === undefined || val === "") return { value: null, success: true };
120
- const num = typeof val === "number" ? val : Number(val);
121
- if (!Number.isNaN(num)) return { value: num, success: true };
122
- if (onError === "fail") throw new InvalidArgumentError(`Cannot cast "${String(val)}" to number`);
123
- if (onError === "keep") return { value: val, success: false };
124
- return { value: null, success: false };
125
- },
126
- };
127
- }
128
-
129
- if (lower === "integer" || lower === "int") {
130
- return {
131
- col,
132
- cast: (val: unknown) => {
133
- if (val === null || val === undefined || val === "") return { value: null, success: true };
134
- const str = String(val).trim();
135
- if (!/^-?\d+$/.test(str)) {
136
- if (onError === "fail") throw new InvalidArgumentError(`Cannot cast "${str}" to integer`);
137
- if (onError === "keep") return { value: val, success: false };
138
- return { value: null, success: false };
139
- }
140
- const num = Number.parseInt(str, 10);
141
- return { value: num, success: true };
142
- },
143
- };
144
- }
145
-
146
- if (lower === "string") {
147
- return {
148
- col,
149
- cast: (val: unknown) => ({ value: val !== null && val !== undefined ? String(val) : "", success: true }),
150
- };
151
- }
152
-
153
- return {
154
- col,
155
- cast: (val: unknown) => castValue(val, targetType, onError),
156
- };
157
- });
158
-
159
- const compiledLen = compiled.length;
160
-
161
- return function (stream: DataStream): DataStream {
162
- return (async function* () {
163
- for await (const batch of stream) {
164
- const rows = batch.rows;
165
- const rowCount = rows.length;
166
- const transformedRows: Row[] = [];
167
-
168
- for (let i = 0; i < rowCount; i++) {
169
- const row = rows[i]!;
170
- let skipRow = false;
171
- const newRow: Row = { ...row };
172
-
173
- for (let j = 0; j < compiledLen; j++) {
174
- const entry = compiled[j]!;
175
- const col = entry.col;
176
- if (col in newRow) {
177
- const res = entry.cast(newRow[col]);
178
- if (!res.success && onError === "skip-row") {
179
- skipRow = true;
180
- break;
181
- }
182
- newRow[col] = res.value;
183
- }
184
- }
185
-
186
- if (!skipRow) {
187
- transformedRows.push(newRow);
188
- }
189
- }
190
-
191
- yield {
192
- rows: transformedRows,
193
- offset: batch.offset,
194
- };
195
- }
196
- })();
197
- };
198
- }
199
-
200
- /**
201
- * Parses command line cast specs like ["age:integer", "revenue:number"].
202
- */
203
- export function parseCastSpecs(specs: string[]): Record<string, string> {
204
- const mapping: Record<string, string> = {};
205
- for (const spec of specs) {
206
- const parts = spec.split(":");
207
- if (parts.length === 2 && parts[0] && parts[1]) {
208
- mapping[parts[0].trim()] = parts[1].trim();
209
- }
210
- }
211
- return mapping;
212
- }