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
package/src/readers/json.ts
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { createReadStream } from "node:fs";
|
|
2
|
-
import type { Readable } from "node:stream";
|
|
3
|
-
import { ParseError } from "../core/errors.js";
|
|
4
|
-
import type {
|
|
5
|
-
DataBatch,
|
|
6
|
-
DataStream,
|
|
7
|
-
InspectionMetadata,
|
|
8
|
-
ReaderOptions,
|
|
9
|
-
Row,
|
|
10
|
-
TabularReader,
|
|
11
|
-
} from "../core/types.js";
|
|
12
|
-
|
|
13
|
-
export interface JSONReaderOptions extends ReaderOptions {
|
|
14
|
-
path?: string;
|
|
15
|
-
filePath?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Incremental streaming JSON parser for top-level arrays [ {...}, ... ]
|
|
20
|
-
* and nested array paths (e.g. data.results).
|
|
21
|
-
* Never loads the full JSON file into memory.
|
|
22
|
-
*/
|
|
23
|
-
export class JSONReader implements TabularReader {
|
|
24
|
-
private input: Readable | string;
|
|
25
|
-
private options: JSONReaderOptions;
|
|
26
|
-
|
|
27
|
-
constructor(input: Readable | string, options: JSONReaderOptions = {}) {
|
|
28
|
-
this.input = input;
|
|
29
|
-
this.options = { ...options };
|
|
30
|
-
if (typeof input === "string") {
|
|
31
|
-
this.options.filePath = input;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private getInputStream(): Readable {
|
|
36
|
-
if (typeof this.input === "string") {
|
|
37
|
-
return createReadStream(this.input);
|
|
38
|
-
}
|
|
39
|
-
return this.input;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async *read(options?: ReaderOptions): DataStream {
|
|
43
|
-
const mergedOptions: JSONReaderOptions = {
|
|
44
|
-
...this.options,
|
|
45
|
-
...options,
|
|
46
|
-
};
|
|
47
|
-
const batchSize = Math.max(1, mergedOptions.batchSize ?? 1000);
|
|
48
|
-
const targetPath = mergedOptions.path; // e.g. "data.results"
|
|
49
|
-
const stream = this.getInputStream();
|
|
50
|
-
|
|
51
|
-
let buffer = "";
|
|
52
|
-
let cursor = 0;
|
|
53
|
-
let byteOffset = 0;
|
|
54
|
-
let foundTargetArray = !targetPath; // if no path, we look for first '['
|
|
55
|
-
let currentPath: string[] = [];
|
|
56
|
-
|
|
57
|
-
// State machine for array item scanning
|
|
58
|
-
let inArray = false;
|
|
59
|
-
let inString = false;
|
|
60
|
-
let isEscaped = false;
|
|
61
|
-
let objectDepth = 0;
|
|
62
|
-
let objectStartIndex = -1;
|
|
63
|
-
|
|
64
|
-
let rowsInCurrentBatch: Row[] = [];
|
|
65
|
-
let globalOffset = 0;
|
|
66
|
-
let rowIndex = 0;
|
|
67
|
-
|
|
68
|
-
// Helper for path navigation if targetPath is set
|
|
69
|
-
const targetPathParts = targetPath ? targetPath.split(".") : [];
|
|
70
|
-
|
|
71
|
-
for await (const chunk of stream) {
|
|
72
|
-
const chunkStr =
|
|
73
|
-
typeof chunk === "string" ? chunk : (chunk as Buffer).toString("utf8");
|
|
74
|
-
buffer += chunkStr;
|
|
75
|
-
|
|
76
|
-
while (cursor < buffer.length) {
|
|
77
|
-
const char = buffer[cursor];
|
|
78
|
-
|
|
79
|
-
if (isEscaped) {
|
|
80
|
-
isEscaped = false;
|
|
81
|
-
cursor++;
|
|
82
|
-
byteOffset++;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (char === "\\") {
|
|
87
|
-
isEscaped = true;
|
|
88
|
-
cursor++;
|
|
89
|
-
byteOffset++;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (char === '"') {
|
|
94
|
-
inString = !inString;
|
|
95
|
-
cursor++;
|
|
96
|
-
byteOffset++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (inString) {
|
|
101
|
-
cursor++;
|
|
102
|
-
byteOffset++;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// We are outside of any string literal
|
|
107
|
-
if (!foundTargetArray) {
|
|
108
|
-
// If we need to find target path (e.g. data.results)
|
|
109
|
-
// Look for keys and match path parts
|
|
110
|
-
if (char === "{") {
|
|
111
|
-
cursor++;
|
|
112
|
-
byteOffset++;
|
|
113
|
-
continue;
|
|
114
|
-
} else if (char === "[") {
|
|
115
|
-
// Check if current path matches targetPath
|
|
116
|
-
// If path matched or we hit array at root
|
|
117
|
-
foundTargetArray = true;
|
|
118
|
-
inArray = true;
|
|
119
|
-
cursor++;
|
|
120
|
-
byteOffset++;
|
|
121
|
-
continue;
|
|
122
|
-
} else if (char === ":") {
|
|
123
|
-
// Scan backwards from ':' to find previous string key
|
|
124
|
-
let keyEnd = cursor - 1;
|
|
125
|
-
while (keyEnd >= 0 && /\s/.test(buffer[keyEnd]!)) keyEnd--;
|
|
126
|
-
if (keyEnd >= 0 && buffer[keyEnd] === '"') {
|
|
127
|
-
let keyStart = keyEnd - 1;
|
|
128
|
-
while (keyStart >= 0 && buffer[keyStart] !== '"') keyStart--;
|
|
129
|
-
if (keyStart >= 0) {
|
|
130
|
-
const keyName = buffer.slice(keyStart + 1, keyEnd);
|
|
131
|
-
currentPath.push(keyName);
|
|
132
|
-
if (
|
|
133
|
-
currentPath.join(".") === targetPath ||
|
|
134
|
-
keyName === targetPathParts[targetPathParts.length - 1]
|
|
135
|
-
) {
|
|
136
|
-
// Next non-whitespace should be '['
|
|
137
|
-
// Let it continue to find '['
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
cursor++;
|
|
142
|
-
byteOffset++;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
cursor++;
|
|
146
|
-
byteOffset++;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Now we are streaming inside the target array
|
|
151
|
-
if (!inArray) {
|
|
152
|
-
if (char === "[") {
|
|
153
|
-
inArray = true;
|
|
154
|
-
cursor++;
|
|
155
|
-
byteOffset++;
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
// Inside array: looking for objects '{ ... }'
|
|
160
|
-
if (char === "{") {
|
|
161
|
-
if (objectDepth === 0) {
|
|
162
|
-
objectStartIndex = cursor;
|
|
163
|
-
}
|
|
164
|
-
objectDepth++;
|
|
165
|
-
} else if (char === "}") {
|
|
166
|
-
objectDepth--;
|
|
167
|
-
if (objectDepth === 0 && objectStartIndex !== -1) {
|
|
168
|
-
// We have captured a complete JSON object!
|
|
169
|
-
const objectStr = buffer.slice(objectStartIndex, cursor + 1);
|
|
170
|
-
let parsedRow: unknown;
|
|
171
|
-
try {
|
|
172
|
-
parsedRow = JSON.parse(objectStr);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
throw new ParseError(
|
|
175
|
-
`Malformed JSON object in array: ${(err as Error).message}`,
|
|
176
|
-
{
|
|
177
|
-
file: mergedOptions.filePath,
|
|
178
|
-
row: rowIndex + 1,
|
|
179
|
-
byteOffset,
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
typeof parsedRow === "object" &&
|
|
186
|
-
parsedRow !== null &&
|
|
187
|
-
!Array.isArray(parsedRow)
|
|
188
|
-
) {
|
|
189
|
-
rowsInCurrentBatch.push(parsedRow as Row);
|
|
190
|
-
rowIndex++;
|
|
191
|
-
|
|
192
|
-
if (rowsInCurrentBatch.length >= batchSize) {
|
|
193
|
-
yield {
|
|
194
|
-
rows: rowsInCurrentBatch,
|
|
195
|
-
offset: globalOffset,
|
|
196
|
-
};
|
|
197
|
-
globalOffset += rowsInCurrentBatch.length;
|
|
198
|
-
rowsInCurrentBatch = [];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
objectStartIndex = -1;
|
|
203
|
-
}
|
|
204
|
-
} else if (char === "]" && objectDepth === 0) {
|
|
205
|
-
// End of array reached
|
|
206
|
-
inArray = false;
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
cursor++;
|
|
211
|
-
byteOffset++;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Compact buffer while preserving active object if in the middle of parsing
|
|
216
|
-
if (objectStartIndex !== -1) {
|
|
217
|
-
buffer = buffer.slice(objectStartIndex);
|
|
218
|
-
cursor = cursor - objectStartIndex;
|
|
219
|
-
objectStartIndex = 0;
|
|
220
|
-
} else {
|
|
221
|
-
buffer = buffer.slice(cursor);
|
|
222
|
-
cursor = 0;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (rowsInCurrentBatch.length > 0) {
|
|
227
|
-
yield {
|
|
228
|
-
rows: rowsInCurrentBatch,
|
|
229
|
-
offset: globalOffset,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async inspect(options?: ReaderOptions): Promise<InspectionMetadata> {
|
|
235
|
-
let rowCount = 0;
|
|
236
|
-
let columnNames: string[] = [];
|
|
237
|
-
|
|
238
|
-
for await (const batch of this.read({ ...options, batchSize: 5000 })) {
|
|
239
|
-
if (columnNames.length === 0 && batch.rows.length > 0 && batch.rows[0]) {
|
|
240
|
-
columnNames = Object.keys(batch.rows[0]);
|
|
241
|
-
}
|
|
242
|
-
rowCount += batch.rows.length;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
format: "JSON",
|
|
247
|
-
rowCount,
|
|
248
|
-
columnCount: columnNames.length,
|
|
249
|
-
columns: columnNames.map((name) => ({
|
|
250
|
-
name,
|
|
251
|
-
type: "string",
|
|
252
|
-
nullPercentage: 0,
|
|
253
|
-
})),
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
package/src/readers/jsonl.ts
DELETED
|
@@ -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
|
-
}
|
package/src/readers/xlsx.ts
DELETED
|
@@ -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
|
-
}
|