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,302 +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 CSVReaderOptions extends ReaderOptions {
14
- delimiter?: string;
15
- header?: boolean;
16
- filePath?: string;
17
- }
18
-
19
- /**
20
- * Streaming CSV Reader supporting RFC 4180 quotes, multiline cells,
21
- * custom & auto-detected delimiters, BOM removal, and exact error reporting.
22
- */
23
- export class CSVReader implements TabularReader {
24
- private input: Readable | string;
25
- private options: CSVReaderOptions;
26
-
27
- constructor(input: Readable | string, options: CSVReaderOptions = {}) {
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
- /**
43
- * Auto-detects delimiter by scanning candidate delimiters in a sample string.
44
- */
45
- public static detectDelimiter(sample: string): string {
46
- const candidates = [",", "\t", ";", "|"];
47
- const firstLine = sample.split(/\r?\n/)[0] || "";
48
-
49
- let bestDelimiter = ",";
50
- let maxCount = -1;
51
-
52
- for (const cand of candidates) {
53
- let count = 0;
54
- let inQuotes = false;
55
- for (let i = 0; i < firstLine.length; i++) {
56
- const ch = firstLine[i];
57
- if (ch === '"') {
58
- inQuotes = !inQuotes;
59
- } else if (ch === cand && !inQuotes) {
60
- count++;
61
- }
62
- }
63
- if (count > maxCount) {
64
- maxCount = count;
65
- bestDelimiter = cand;
66
- }
67
- }
68
-
69
- return maxCount > 0 ? bestDelimiter : ",";
70
- }
71
-
72
- async *read(options?: ReaderOptions): DataStream {
73
- const mergedOptions: CSVReaderOptions = {
74
- ...this.options,
75
- ...options,
76
- };
77
- const batchSize = Math.max(1, mergedOptions.batchSize ?? 1000);
78
- const hasHeader = mergedOptions.header !== false;
79
- let delimiter = mergedOptions.delimiter;
80
-
81
- const stream = this.getInputStream();
82
-
83
- let buffer = "";
84
- let headers: string[] | null = null;
85
- let isFirstChunk = true;
86
- let rowsInCurrentBatch: Row[] = [];
87
- let globalRowOffset = 0;
88
- let rowNumber = 0;
89
- let byteOffset = 0;
90
- let currentField = "";
91
- let currentRowFields: string[] = [];
92
- let inQuotes = false;
93
- let cursor = 0;
94
-
95
- for await (const chunk of stream) {
96
- const chunkStr: string =
97
- typeof chunk === "string" ? chunk : (chunk as Buffer).toString("utf8");
98
-
99
- if (isFirstChunk) {
100
- isFirstChunk = false;
101
- // Remove UTF-8 BOM if present
102
- if (chunkStr.charCodeAt(0) === 0xfeff) {
103
- buffer = chunkStr.slice(1);
104
- } else {
105
- buffer = chunkStr;
106
- }
107
-
108
- // Auto-detect delimiter if not specified
109
- if (!delimiter) {
110
- delimiter = CSVReader.detectDelimiter(buffer);
111
- }
112
- } else {
113
- buffer += chunkStr;
114
- }
115
-
116
- const activeDelimiter = delimiter ?? ",";
117
- const delimLen = activeDelimiter.length;
118
-
119
- while (cursor < buffer.length) {
120
- const char = buffer[cursor];
121
-
122
- if (inQuotes) {
123
- if (char === '"') {
124
- if (cursor + 1 < buffer.length && buffer[cursor + 1] === '"') {
125
- currentField += '"';
126
- cursor += 2;
127
- byteOffset += 2;
128
- continue;
129
- } else {
130
- inQuotes = false;
131
- cursor++;
132
- byteOffset++;
133
- continue;
134
- }
135
- } else {
136
- currentField += char;
137
- cursor++;
138
- byteOffset++;
139
- continue;
140
- }
141
- } else {
142
- if (char === '"') {
143
- if (currentField.length === 0) {
144
- inQuotes = true;
145
- cursor++;
146
- byteOffset++;
147
- continue;
148
- } else {
149
- throw new ParseError(
150
- `Unexpected quote inside unquoted field at byte offset ${byteOffset}`,
151
- {
152
- file: mergedOptions.filePath,
153
- row: rowNumber + 1,
154
- column: currentRowFields.length + 1,
155
- byteOffset,
156
- }
157
- );
158
- }
159
- }
160
-
161
- // Check for delimiter
162
- if (buffer.startsWith(activeDelimiter, cursor)) {
163
- currentRowFields.push(currentField);
164
- currentField = "";
165
- cursor += delimLen;
166
- byteOffset += delimLen;
167
- continue;
168
- }
169
-
170
- // Check for CRLF / LF newline
171
- if (char === "\r" || char === "\n") {
172
- currentRowFields.push(currentField);
173
- currentField = "";
174
-
175
- if (char === "\r" && cursor + 1 < buffer.length && buffer[cursor + 1] === "\n") {
176
- cursor += 2;
177
- byteOffset += 2;
178
- } else {
179
- cursor++;
180
- byteOffset++;
181
- }
182
-
183
- // Skip entirely empty row if it's trailing whitespace
184
- if (
185
- currentRowFields.length === 1 &&
186
- currentRowFields[0]?.trim() === "" &&
187
- cursor >= buffer.length
188
- ) {
189
- currentRowFields = [];
190
- continue;
191
- }
192
-
193
- if (hasHeader && headers === null) {
194
- headers = currentRowFields.map((h, i) =>
195
- h.trim().length > 0 ? h.trim() : `col_${i + 1}`
196
- );
197
- currentRowFields = [];
198
- continue;
199
- }
200
-
201
- if (headers === null) {
202
- headers = currentRowFields.map((_, i) => `col_${i + 1}`);
203
- }
204
-
205
- // Construct Row object
206
- const rowObj: Row = {};
207
- for (let i = 0; i < headers.length; i++) {
208
- const key = headers[i] || `col_${i + 1}`;
209
- const val = currentRowFields[i] ?? "";
210
- rowObj[key] = val;
211
- }
212
-
213
- rowNumber++;
214
- rowsInCurrentBatch.push(rowObj);
215
- currentRowFields = [];
216
-
217
- if (rowsInCurrentBatch.length >= batchSize) {
218
- yield {
219
- rows: rowsInCurrentBatch,
220
- offset: globalRowOffset,
221
- };
222
- globalRowOffset += rowsInCurrentBatch.length;
223
- rowsInCurrentBatch = [];
224
- }
225
- continue;
226
- }
227
-
228
- currentField += char;
229
- cursor++;
230
- byteOffset++;
231
- }
232
- }
233
-
234
- // Compact processed buffer to keep memory bounded
235
- buffer = buffer.slice(cursor);
236
- cursor = 0;
237
- }
238
-
239
- // Process remainder if any
240
- if (inQuotes) {
241
- throw new ParseError("Unclosed quote at end of CSV stream", {
242
- file: mergedOptions.filePath,
243
- row: rowNumber + 1,
244
- byteOffset,
245
- });
246
- }
247
-
248
- if (currentField.length > 0 || currentRowFields.length > 0) {
249
- currentRowFields.push(currentField);
250
-
251
- if (hasHeader && headers === null) {
252
- headers = currentRowFields.map((h, i) =>
253
- h.trim().length > 0 ? h.trim() : `col_${i + 1}`
254
- );
255
- } else {
256
- if (headers === null) {
257
- headers = currentRowFields.map((_, i) => `col_${i + 1}`);
258
- }
259
-
260
- const rowObj: Row = {};
261
- for (let i = 0; i < headers.length; i++) {
262
- const key = headers[i] || `col_${i + 1}`;
263
- const val = currentRowFields[i] ?? "";
264
- rowObj[key] = val;
265
- }
266
-
267
- rowNumber++;
268
- rowsInCurrentBatch.push(rowObj);
269
- }
270
- }
271
-
272
- if (rowsInCurrentBatch.length > 0) {
273
- yield {
274
- rows: rowsInCurrentBatch,
275
- offset: globalRowOffset,
276
- };
277
- }
278
- }
279
-
280
- async inspect(options?: ReaderOptions): Promise<InspectionMetadata> {
281
- let rowCount = 0;
282
- let columnNames: string[] = [];
283
-
284
- for await (const batch of this.read({ ...options, batchSize: 5000 })) {
285
- if (columnNames.length === 0 && batch.rows.length > 0 && batch.rows[0]) {
286
- columnNames = Object.keys(batch.rows[0]);
287
- }
288
- rowCount += batch.rows.length;
289
- }
290
-
291
- return {
292
- format: "CSV",
293
- rowCount,
294
- columnCount: columnNames.length,
295
- columns: columnNames.map((name) => ({
296
- name,
297
- type: "string",
298
- nullPercentage: 0,
299
- })),
300
- };
301
- }
302
- }
@@ -1,88 +0,0 @@
1
- import type { Readable } from "node:stream";
2
- import { InvalidArgumentError } from "../core/errors.js";
3
- import type { FormatAdapter, ReaderOptions, TabularReader } from "../core/types.js";
4
- import { CSVReader } from "./csv.js";
5
- import { JSONReader } from "./json.js";
6
- import { JSONLReader } from "./jsonl.js";
7
- import { XLSXReader } from "./xlsx.js";
8
-
9
- export * from "./csv.js";
10
- export * from "./json.js";
11
- export * from "./jsonl.js";
12
- export * from "./xlsx.js";
13
-
14
- const adapters: Record<string, FormatAdapter> = {
15
- csv: {
16
- name: "CSV",
17
- extensions: [".csv", ".tsv", ".txt"],
18
- createReader: (input, options) => new CSVReader(input, options),
19
- createWriter: () => {
20
- throw new Error("Writer adapter called from reader");
21
- },
22
- },
23
- json: {
24
- name: "JSON",
25
- extensions: [".json"],
26
- createReader: (input, options) => new JSONReader(input, options),
27
- createWriter: () => {
28
- throw new Error("Writer adapter called from reader");
29
- },
30
- },
31
- jsonl: {
32
- name: "JSONL",
33
- extensions: [".jsonl", ".ndjson", ".ldjson"],
34
- createReader: (input, options) => new JSONLReader(input, options),
35
- createWriter: () => {
36
- throw new Error("Writer adapter called from reader");
37
- },
38
- },
39
- xlsx: {
40
- name: "XLSX",
41
- extensions: [".xlsx", ".xlsm"],
42
- createReader: (input, options) => new XLSXReader(input, options),
43
- createWriter: () => {
44
- throw new Error("Writer adapter called from reader");
45
- },
46
- },
47
- };
48
-
49
- /**
50
- * Infers format name from file path or extension.
51
- */
52
- export function inferFormatFromPath(filePath: string): string | null {
53
- const cleanPath = filePath.toLowerCase().replace(/\.gz$/, "");
54
- for (const [format, adapter] of Object.entries(adapters)) {
55
- if (adapter.extensions.some((ext) => cleanPath.endsWith(ext))) {
56
- return format;
57
- }
58
- }
59
- return null;
60
- }
61
-
62
- /**
63
- * Creates an appropriate TabularReader for input and format.
64
- */
65
- export function createReader(
66
- input: Readable | string,
67
- options: ReaderOptions & { format?: string } = {}
68
- ): TabularReader {
69
- let format = options.format?.toLowerCase();
70
-
71
- if (!format && typeof input === "string" && input !== "-") {
72
- format = inferFormatFromPath(input) ?? undefined;
73
- }
74
-
75
- if (!format) {
76
- // Default to CSV if cannot infer
77
- format = "csv";
78
- }
79
-
80
- const adapter = adapters[format];
81
- if (!adapter) {
82
- throw new InvalidArgumentError(
83
- `Unsupported input format: "${format}". Supported formats are: ${Object.keys(adapters).join(", ")}`
84
- );
85
- }
86
-
87
- return adapter.createReader(input, options);
88
- }
@@ -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
- }