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.
Files changed (185) hide show
  1. package/README.md +327 -41
  2. package/dist/cli/commands/diff.d.ts +30 -0
  3. package/dist/cli/commands/diff.d.ts.map +1 -0
  4. package/dist/cli/commands/diff.js +143 -0
  5. package/dist/cli/commands/diff.js.map +1 -0
  6. package/dist/cli/commands/files.d.ts +35 -0
  7. package/dist/cli/commands/files.d.ts.map +1 -0
  8. package/dist/cli/commands/files.js +93 -0
  9. package/dist/cli/commands/files.js.map +1 -0
  10. package/dist/cli/index.js +89 -5
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/core/errors.d.ts +23 -0
  13. package/dist/core/errors.d.ts.map +1 -1
  14. package/dist/core/errors.js +38 -0
  15. package/dist/core/errors.js.map +1 -1
  16. package/dist/core/types.d.ts +4 -0
  17. package/dist/core/types.d.ts.map +1 -1
  18. package/dist/diff/comparator.d.ts +17 -0
  19. package/dist/diff/comparator.d.ts.map +1 -0
  20. package/dist/diff/comparator.js +158 -0
  21. package/dist/diff/comparator.js.map +1 -0
  22. package/dist/diff/engine.d.ts +10 -0
  23. package/dist/diff/engine.d.ts.map +1 -0
  24. package/dist/diff/engine.js +333 -0
  25. package/dist/diff/engine.js.map +1 -0
  26. package/dist/diff/hash.d.ts +6 -0
  27. package/dist/diff/hash.d.ts.map +1 -0
  28. package/dist/diff/hash.js +50 -0
  29. package/dist/diff/hash.js.map +1 -0
  30. package/dist/diff/key.d.ts +21 -0
  31. package/dist/diff/key.d.ts.map +1 -0
  32. package/dist/diff/key.js +107 -0
  33. package/dist/diff/key.js.map +1 -0
  34. package/dist/diff/reporter.d.ts +18 -0
  35. package/dist/diff/reporter.d.ts.map +1 -0
  36. package/dist/diff/reporter.js +133 -0
  37. package/dist/diff/reporter.js.map +1 -0
  38. package/dist/diff/schema.d.ts +7 -0
  39. package/dist/diff/schema.d.ts.map +1 -0
  40. package/dist/diff/schema.js +65 -0
  41. package/dist/diff/schema.js.map +1 -0
  42. package/dist/diff/storage/disk-index.d.ts +28 -0
  43. package/dist/diff/storage/disk-index.d.ts.map +1 -0
  44. package/dist/diff/storage/disk-index.js +105 -0
  45. package/dist/diff/storage/disk-index.js.map +1 -0
  46. package/dist/diff/storage/memory-index.d.ts +23 -0
  47. package/dist/diff/storage/memory-index.d.ts.map +1 -0
  48. package/dist/diff/storage/memory-index.js +69 -0
  49. package/dist/diff/storage/memory-index.js.map +1 -0
  50. package/dist/diff/storage/spillable-index.d.ts +30 -0
  51. package/dist/diff/storage/spillable-index.d.ts.map +1 -0
  52. package/dist/diff/storage/spillable-index.js +108 -0
  53. package/dist/diff/storage/spillable-index.js.map +1 -0
  54. package/dist/diff/types.d.ts +127 -0
  55. package/dist/diff/types.d.ts.map +1 -0
  56. package/dist/diff/types.js +2 -0
  57. package/dist/diff/types.js.map +1 -0
  58. package/dist/files/glob.d.ts +21 -0
  59. package/dist/files/glob.d.ts.map +1 -0
  60. package/dist/files/glob.js +132 -0
  61. package/dist/files/glob.js.map +1 -0
  62. package/dist/files/hash.d.ts +10 -0
  63. package/dist/files/hash.d.ts.map +1 -0
  64. package/dist/files/hash.js +67 -0
  65. package/dist/files/hash.js.map +1 -0
  66. package/dist/files/index.d.ts +6 -0
  67. package/dist/files/index.d.ts.map +1 -0
  68. package/dist/files/index.js +6 -0
  69. package/dist/files/index.js.map +1 -0
  70. package/dist/files/mime.d.ts +9 -0
  71. package/dist/files/mime.d.ts.map +1 -0
  72. package/dist/files/mime.js +128 -0
  73. package/dist/files/mime.js.map +1 -0
  74. package/dist/files/reader.d.ts +19 -0
  75. package/dist/files/reader.d.ts.map +1 -0
  76. package/dist/files/reader.js +344 -0
  77. package/dist/files/reader.js.map +1 -0
  78. package/dist/files/types.d.ts +47 -0
  79. package/dist/files/types.d.ts.map +1 -0
  80. package/dist/files/types.js +2 -0
  81. package/dist/files/types.js.map +1 -0
  82. package/dist/index.d.ts +17 -2
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +19 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/readers/csv.d.ts.map +1 -1
  87. package/dist/readers/csv.js +59 -27
  88. package/dist/readers/csv.js.map +1 -1
  89. package/dist/readers/index.d.ts +2 -0
  90. package/dist/readers/index.d.ts.map +1 -1
  91. package/dist/readers/index.js +57 -3
  92. package/dist/readers/index.js.map +1 -1
  93. package/dist/readers/json.d.ts.map +1 -1
  94. package/dist/readers/json.js +6 -8
  95. package/dist/readers/json.js.map +1 -1
  96. package/dist/readers/jsonl.d.ts.map +1 -1
  97. package/dist/readers/jsonl.js +5 -7
  98. package/dist/readers/jsonl.js.map +1 -1
  99. package/dist/readers/parquet.d.ts +17 -0
  100. package/dist/readers/parquet.d.ts.map +1 -0
  101. package/dist/readers/parquet.js +146 -0
  102. package/dist/readers/parquet.js.map +1 -0
  103. package/dist/transforms/expression.d.ts.map +1 -1
  104. package/dist/transforms/expression.js +73 -7
  105. package/dist/transforms/expression.js.map +1 -1
  106. package/dist/utils/compression.d.ts +30 -6
  107. package/dist/utils/compression.d.ts.map +1 -1
  108. package/dist/utils/compression.js +95 -18
  109. package/dist/utils/compression.js.map +1 -1
  110. package/dist/writers/csv.d.ts.map +1 -1
  111. package/dist/writers/csv.js +29 -17
  112. package/dist/writers/csv.js.map +1 -1
  113. package/dist/writers/index.d.ts +2 -0
  114. package/dist/writers/index.d.ts.map +1 -1
  115. package/dist/writers/index.js +49 -4
  116. package/dist/writers/index.js.map +1 -1
  117. package/dist/writers/json.d.ts +1 -1
  118. package/dist/writers/json.d.ts.map +1 -1
  119. package/dist/writers/json.js +9 -11
  120. package/dist/writers/json.js.map +1 -1
  121. package/dist/writers/jsonl.d.ts +1 -1
  122. package/dist/writers/jsonl.d.ts.map +1 -1
  123. package/dist/writers/jsonl.js +9 -11
  124. package/dist/writers/jsonl.js.map +1 -1
  125. package/dist/writers/markdown.d.ts +21 -0
  126. package/dist/writers/markdown.d.ts.map +1 -0
  127. package/dist/writers/markdown.js +82 -0
  128. package/dist/writers/markdown.js.map +1 -0
  129. package/dist/writers/parquet.d.ts +19 -0
  130. package/dist/writers/parquet.d.ts.map +1 -0
  131. package/dist/writers/parquet.js +129 -0
  132. package/dist/writers/parquet.js.map +1 -0
  133. package/package.json +16 -3
  134. package/skills/rowpipe/SKILL.md +263 -0
  135. package/benchmarks/memory-bench.ts +0 -109
  136. package/src/analytics/reduce.ts +0 -388
  137. package/src/analytics/schema-inference.ts +0 -217
  138. package/src/analytics/semantic-types.ts +0 -36
  139. package/src/analytics/stats.ts +0 -383
  140. package/src/analytics/validator.ts +0 -134
  141. package/src/cli/commands/cast.ts +0 -60
  142. package/src/cli/commands/convert.ts +0 -128
  143. package/src/cli/commands/filter.ts +0 -58
  144. package/src/cli/commands/inspect.ts +0 -193
  145. package/src/cli/commands/map.ts +0 -66
  146. package/src/cli/commands/reduce.ts +0 -74
  147. package/src/cli/commands/rename.ts +0 -59
  148. package/src/cli/commands/sample.ts +0 -61
  149. package/src/cli/commands/schema.ts +0 -86
  150. package/src/cli/commands/select.ts +0 -59
  151. package/src/cli/commands/stats.ts +0 -100
  152. package/src/cli/commands/validate.ts +0 -123
  153. package/src/cli/index.ts +0 -285
  154. package/src/core/batch.ts +0 -81
  155. package/src/core/errors.ts +0 -81
  156. package/src/core/pipeline.ts +0 -150
  157. package/src/core/types.ts +0 -110
  158. package/src/index.ts +0 -52
  159. package/src/readers/csv.ts +0 -302
  160. package/src/readers/index.ts +0 -88
  161. package/src/readers/json.ts +0 -256
  162. package/src/readers/jsonl.ts +0 -126
  163. package/src/readers/xlsx.ts +0 -182
  164. package/src/transforms/cast.ts +0 -212
  165. package/src/transforms/expression.ts +0 -1114
  166. package/src/transforms/filter.ts +0 -38
  167. package/src/transforms/map.ts +0 -99
  168. package/src/transforms/rename.ts +0 -47
  169. package/src/transforms/sample.ts +0 -57
  170. package/src/transforms/select.ts +0 -64
  171. package/src/utils/compression.ts +0 -43
  172. package/src/utils/formatting.ts +0 -75
  173. package/src/utils/progress.ts +0 -37
  174. package/src/writers/csv.ts +0 -106
  175. package/src/writers/index.ts +0 -87
  176. package/src/writers/json.ts +0 -52
  177. package/src/writers/jsonl.ts +0 -49
  178. package/src/writers/xlsx.ts +0 -87
  179. package/tests/analytics.test.ts +0 -119
  180. package/tests/cli-integration.test.ts +0 -187
  181. package/tests/map-reduce.test.ts +0 -223
  182. package/tests/readers-writers.test.ts +0 -189
  183. package/tests/transforms.test.ts +0 -199
  184. package/tsconfig.json +0 -21
  185. package/vitest.config.ts +0 -9
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Rowpipe
2
2
 
3
- > **A stream-first tabular data toolkit for CSV, JSON, JSONL, and XLSX.**
3
+ > **A stream-first tabular data toolkit for CSV, TSV, JSON, JSONL, XLSX, Apache Parquet, and Markdown.**
4
4
  >
5
5
  > *Don't load the dataset. Stream through it.*
6
6
 
7
- Rowpipe is a high-performance, bounded-memory command-line toolkit and Node.js library for reading, inspecting, analyzing, transforming, validating, and converting tabular datasets across CSV, JSON, JSONL, and XLSX formats.
7
+ Rowpipe is a high-performance, bounded-memory command-line toolkit and Node.js library for reading, inspecting, analyzing, transforming, validating, and converting tabular datasets across CSV, TSV, PSV, JSON, JSONL, XLSX, Apache Parquet (`.parquet`), Markdown tables (`.md`), and transparent Gzip (`.gz`) streams.
8
8
 
9
9
  Designed around stream backpressure and batch processing, Rowpipe processes multi-gigabyte and multi-million-row datasets with constant $O(1)$ memory usage.
10
10
 
@@ -13,8 +13,14 @@ Designed around stream backpressure and batch processing, Rowpipe processes mult
13
13
  ## Highlights
14
14
 
15
15
  - **Stream-First Architecture**: Datasets are never buffered entirely in memory; rows flow in configurable batches through async generator pipelines.
16
- - **Cross-Format Conversion & Multi-Sheet Export**: Convert seamlessly between CSV, JSON (streaming array & nested path), JSONL / NDJSON, and XLSX. Export individual sheets or bulk export all worksheets with `--all-sheets`.
17
- - **Zero-`eval` Expression Engine**: Safe filter expressions compiled into AST evaluators (`country == "TR" && revenue > 1000`).
16
+ - **Rich Format Ecosystem**: Native streaming support for **CSV, TSV / PSV, JSON, JSONL, XLSX, Apache Parquet (`.parquet`), Markdown tables (`.md`), and Gzip (`.gz`)**.
17
+ - **Apache Parquet Support**: High-performance columnar binary format reader (Snappy, Zstd, Gzip decompression via pure JS) and streaming writer.
18
+ - **Cross-Format Conversion & Multi-Sheet Export**: Convert seamlessly across all formats. Export individual Excel sheets or bulk export all worksheets with `--all-sheets`.
19
+ - **Filesystem as Data (`rowpipe files`)**: First-class streaming filesystem source adapter. Directories and files become streams of tabular records (`path`, `size`, `type`, `extension`, `modified_at`, streaming `hash`, `mime`) composable with `filter`, `select`, `stats`, `convert`, and `diff`.
20
+ - **Cross-Format Dataset Diffing (`rowpipe diff`)**: High-performance, stream-first comparison across CSV, JSONL, XLSX, and Parquet with bounded memory and automatic spill-to-disk. Supports composite keys, duplicate policies, value tolerance, column filtering, schema diffing, JSONL patch generation, and CI `--fail-on-diff` mode.
21
+
22
+ - **Zero-`eval` Safe Expression Engine**: Ultra-fast JIT closure-compiled evaluators supporting arithmetic (`revenue - cost`), string/date pipe functions (`email | lower | trim`), and direct JSON dot-notation (`payload.user.city`).
23
+ - **Map & Reduce Streaming Engine**: Compute row derivations (`rowpipe map`) and global or group-by aggregations (`rowpipe reduce --by country`) in a single pass.
18
24
  - **Numerically Stable Streaming Stats**: Online Welford statistics (mean, variance, stddev, min, max, sum) and $O(1)$ HyperLogLog distinct cardinality estimation.
19
25
  - **Incremental Schema Inference & Semantic Types**: Detects data types with confidence scoring and semantic patterns (`email`, `url`, `uuid`, `ipv4`, `country-code`, `currency`, `phone`).
20
26
  - **Streaming Schema Validation**: Validates stream against JSON schemas with detailed violation reporting.
@@ -45,14 +51,19 @@ npm install rowpipe
45
51
 
46
52
  ## CLI Usage & Examples
47
53
 
48
- ### 1. Inspect Dataset & Multi-Sheet Excel Analysis
54
+ ### 1. Inspect Dataset & Multi-Sheet Excel / Parquet Analysis
49
55
  Inspect format, row count, column list, data types, null percentage, and approximate distinct counts:
50
56
 
51
57
  ```bash
58
+ # CSV / TSV / JSONL
52
59
  rowpipe inspect users.csv
60
+ rowpipe inspect data.tsv
61
+
62
+ # Apache Parquet dataset
63
+ rowpipe inspect dataset.parquet
53
64
 
54
65
  # Machine-readable JSON output
55
- rowpipe inspect users.csv --json
66
+ rowpipe inspect dataset.parquet --json
56
67
 
57
68
  # Analyze all sheets in an Excel workbook
58
69
  rowpipe inspect workbook.xlsx
@@ -61,39 +72,24 @@ rowpipe inspect workbook.xlsx
61
72
  rowpipe inspect workbook.xlsx --sheet Users
62
73
  ```
63
74
 
64
- Example Excel workbook overview:
65
- ```text
66
- File: workbook.xlsx
67
- Format: XLSX
68
- Size: 1.25 MB
69
- Sheets: 3
70
-
71
- SHEET ROWS COLUMNS PREVIEW COLUMNS
72
- Users 1,200 5 id, name, email, age, created_at
73
- Orders 8,400 4 order_id, user_id, total, status
74
- Products 300 3 sku, title, price
75
-
76
- Tip: Run with --sheet <name> to inspect a specific sheet in full detail.
77
- ```
78
-
79
- ### 2. Sheet-Based Export & Format Conversion
75
+ ### 2. Format Conversion & Export
80
76
  Convert across formats without intermediate buffering:
81
77
 
82
78
  ```bash
83
- # Export specific Excel worksheet to CSV
84
- rowpipe convert workbook.xlsx users.csv --sheet Users
79
+ # CSV / TSV to Apache Parquet
80
+ rowpipe convert sales.csv sales.parquet
85
81
 
86
- # Export specific Excel worksheet to JSONL
87
- rowpipe convert workbook.xlsx orders.jsonl --sheet Orders
82
+ # Parquet to Gzipped JSONL
83
+ rowpipe convert sales.parquet sales.jsonl.gz
88
84
 
89
- # Bulk export ALL worksheets to individual files in a directory
90
- rowpipe convert workbook.xlsx --all-sheets --out-dir ./exported/ --to csv
85
+ # Filter and output directly as a GitHub Markdown Table
86
+ rowpipe filter dataset.parquet "status == 'ACTIVE' && score >= 90" --to markdown
91
87
 
92
- # CSV to JSONL
93
- rowpipe convert users.csv users.jsonl
88
+ # Export specific Excel worksheet to CSV / TSV
89
+ rowpipe convert workbook.xlsx users.tsv --sheet Users
94
90
 
95
- # CSV to JSON array
96
- rowpipe convert users.csv users.json
91
+ # Bulk export ALL worksheets to individual files in a directory
92
+ rowpipe convert workbook.xlsx --all-sheets --out-dir ./exported/ --to csv
97
93
 
98
94
  # Streaming through gzip
99
95
  rowpipe convert data.csv.gz data.jsonl.gz
@@ -312,6 +308,253 @@ cat transactions.csv |
312
308
 
313
309
  ---
314
310
 
311
+ ## Compare Datasets (`rowpipe diff`)
312
+
313
+ `rowpipe diff` is a production-grade, stream-first comparison engine that compares two tabular datasets by primary or composite key and reports:
314
+
315
+ - **Added rows** (present in right dataset, absent in left)
316
+ - **Removed rows** (present in left dataset, absent in right)
317
+ - **Changed rows** (matching key, different values)
318
+ - **Unchanged rows** (matching key, identical values)
319
+ - **Changed columns breakdown** (ranked by frequency of changes)
320
+ - **Schema differences** (added/removed columns and type changes)
321
+
322
+ ```bash
323
+ rowpipe diff yesterday.csv today.csv --key id
324
+ ```
325
+
326
+ Example Summary Output:
327
+
328
+ ```text
329
+ Comparing:
330
+ yesterday.csv
331
+ today.csv
332
+
333
+ Key: id
334
+
335
+ Rows
336
+ added 1,283
337
+ removed 412
338
+ changed 8,921
339
+ unchanged 91,204
340
+
341
+ Changed columns
342
+ price 5,821
343
+ status 2,101
344
+ email 999
345
+
346
+ Schema
347
+ + loyalty_level: string
348
+ ~ price: integer -> number
349
+ - legacy_code: string
350
+ ```
351
+
352
+ ### Key Capabilities & Examples
353
+
354
+ #### 1. Composite Keys
355
+ Use comma-separated column names for multi-column composite keys with collision-proof typed encoding:
356
+ ```bash
357
+ rowpipe diff old.csv new.csv --key country,user_id
358
+ ```
359
+
360
+ #### 2. Cross-Format Comparison
361
+ Compare datasets seamlessly across any combination of supported formats (CSV, TSV, JSONL, XLSX, Apache Parquet):
362
+ ```bash
363
+ # Compare CSV with Parquet
364
+ rowpipe diff old.csv new.parquet --key id
365
+
366
+ # Compare JSONL with Excel
367
+ rowpipe diff old.jsonl new.xlsx --key user_id --sheet Users
368
+
369
+ # Compare specific sheets between two Excel workbooks
370
+ rowpipe diff old.xlsx new.xlsx --left-sheet Nov --right-sheet Dec --key sku
371
+ ```
372
+
373
+ #### 3. Ignore or Select Specific Columns
374
+ ```bash
375
+ # Ignore volatile timestamp or audit columns
376
+ rowpipe diff old.csv new.csv --key id --ignore updated_at,last_seen
377
+
378
+ # Compare only specific columns for changes
379
+ rowpipe diff old.csv new.csv --key id --columns price,status
380
+ ```
381
+
382
+ #### 4. Value Equality, Tolerance & Coercion
383
+ - **Strict by default**: `null != ""` and `0 != "0"`.
384
+ - **`--coerce`**: Coerces types (e.g. `"42" == 42`, `"true" == true`) across formats.
385
+ - **`--epsilon <val>`**: Floating point comparison tolerance (`abs(a - b) <= epsilon`).
386
+ - **`--trim` & `--ignore-case`**: String normalization options.
387
+
388
+ ```bash
389
+ rowpipe diff old.csv new.csv --key id --epsilon 0.001 --trim --ignore-case
390
+ ```
391
+
392
+ #### 5. Duplicate Key Policies
393
+ Duplicate keys in either dataset are never silently ignored:
394
+ - `--duplicate-key error` *(default)*: Exits immediately with row context.
395
+ - `--duplicate-key first`: Keeps the first occurrence.
396
+ - `--duplicate-key last`: Keeps the latest occurrence.
397
+
398
+ #### 6. Detailed Row & JSONL Patch Output Modes
399
+ ```bash
400
+ # Detailed row change output with limit
401
+ rowpipe diff old.csv new.csv --key id --format rows --only changed --limit 50
402
+
403
+ # Stream JSONL patches (suitable for ETL change feeds or audit logs)
404
+ rowpipe diff old.csv new.csv --key id --format patch > changes.jsonl
405
+
406
+ # Machine-readable JSON summary metadata
407
+ rowpipe diff old.csv new.csv --key id --json
408
+ ```
409
+
410
+ Example JSONL patch output:
411
+ ```json
412
+ {"op":"update","key":{"id":24},"changes":{"price":{"old":120,"new":125}}}
413
+ {"op":"insert","key":{"id":31},"row":{"id":31,"name":"Alice"}}
414
+ {"op":"delete","key":{"id":51},"row":{"id":51,"name":"Bob"}}
415
+ ```
416
+
417
+ #### 7. CI/CD Validation (`--fail-on-diff`)
418
+ Exit with non-zero code (`1`) when differences exist, or `0` when datasets are equivalent:
419
+
420
+ ```bash
421
+ rowpipe diff expected.csv actual.csv \
422
+ --key id \
423
+ --fail-on-diff
424
+ ```
425
+
426
+ This makes Rowpipe ideal for automated verification of:
427
+ - **Database migrations**
428
+ - **ETL jobs & data pipelines**
429
+ - **API export validation**
430
+ - **Regression test fixtures**
431
+ - **Scheduled report auditing**
432
+
433
+ #### 8. Bounded Memory Architecture & Automatic Spill-to-Disk
434
+ Keyed diffing is classified as **Global State / Spillable**. Rowpipe buffers indexed keys in memory and automatically spills to disk using an append-only temp file index when `--memory-limit` is reached (default: 256MB).
435
+
436
+ ```bash
437
+ # Handle multi-million-row diffs with bounded RAM
438
+ rowpipe diff huge-old.csv huge-new.csv \
439
+ --key id \
440
+ --memory-limit 128mb
441
+ ```
442
+
443
+ ---
444
+
445
+ ## Filesystem as Data (`rowpipe files`)
446
+
447
+ > **Everything becomes a stream of records.**
448
+
449
+ Rowpipe treats files and directories as another first-class streaming record source. A directory tree becomes a stream of metadata records that can be filtered, selected, mapped, reduced, converted, and diffed using the exact same stream pipeline used for CSV, JSONL, XLSX, and Parquet.
450
+
451
+ ```bash
452
+ rowpipe files ./uploads
453
+ ```
454
+
455
+ Example Output (as Table / CSV / JSONL):
456
+
457
+ ```text
458
+ path name ext size modified_at
459
+ uploads/avatar.png avatar.png png 182312 2026-09-16T08:13:11.923Z
460
+ uploads/users.csv users.csv csv 8123102 2026-09-15T19:42:01.114Z
461
+ uploads/archive/data.zip data.zip zip 12931231 2026-09-12T11:05:30.852Z
462
+ ```
463
+
464
+ ### Standard Record Fields
465
+ Each file record contains rich, standardized metadata:
466
+ - `path`: Full normalized path
467
+ - `relative_path`: Normalized path relative to root directory (uses `/` across all operating systems)
468
+ - `name`: File name with extension (e.g. `logo.png`)
469
+ - `basename`: File name without extension (e.g. `logo`)
470
+ - `extension`: Lowercase extension without dot (e.g. `png`)
471
+ - `directory`: Parent directory path
472
+ - `type`: Record type (`"file"`, `"directory"`, `"symlink"`)
473
+ - `size`: Numeric size in bytes (`0` for directories)
474
+ - `created_at`, `modified_at`, `accessed_at`: ISO 8601 timestamp strings
475
+ - `mode`: File permission mode
476
+ - `is_file`, `is_directory`, `is_symlink`: Boolean flags
477
+ - `hash` *(optional)*: Streaming content hash (`sha256`, `sha1`, `md5`, `fast`)
478
+ - `mime` & `category` *(optional)*: Inferred MIME type (`image/png`, `text/csv`) and category (`image`, `data`, `code`, `archive`, `document`, `video`, `audio`)
479
+
480
+ ---
481
+
482
+ ### Filesystem CLI Workflows & Examples
483
+
484
+ #### 1. Recursive Traversal & Glob Filtering
485
+ ```bash
486
+ # Scan directory recursively (default)
487
+ rowpipe files ./src
488
+
489
+ # Limit traversal depth
490
+ rowpipe files . --max-depth 2
491
+
492
+ # Include specific globs
493
+ rowpipe files ./src --include "**/*.ts"
494
+
495
+ # Exclude patterns (e.g. node_modules, build artifacts)
496
+ rowpipe files . --exclude "dist/**" --exclude "*.tmp"
497
+
498
+ # Include hidden files & dot directories
499
+ rowpipe files . --hidden
500
+ ```
501
+
502
+ #### 2. Streaming Hashes & MIME Type Detection
503
+ ```bash
504
+ # Compute streaming SHA-256 hashes (never loads full file into memory)
505
+ rowpipe files ./assets --hash sha256
506
+
507
+ # Fast non-cryptographic fingerprinting (size + mtime + content sampling)
508
+ rowpipe files ./dataset --hash fast
509
+
510
+ # Infer MIME types and high-level categories
511
+ rowpipe files ./uploads --mime
512
+ ```
513
+
514
+ #### 3. Unix Pipeline Composition
515
+ Compose filesystem metadata directly into Rowpipe streams:
516
+
517
+ ```bash
518
+ # Filter large files (> 1MB)
519
+ rowpipe files ./uploads | rowpipe filter - 'size > 1000000'
520
+
521
+ # Project specific columns
522
+ rowpipe files ./src | rowpipe select - path,size,extension
523
+
524
+ # Calculate disk usage stats across file types
525
+ rowpipe files ./src --to jsonl | \
526
+ rowpipe reduce - "total_size=sum(size)" "file_count=count()" --by extension
527
+ ```
528
+
529
+ #### 4. Deterministic Snapshots & Build Verification
530
+ Create stable, reproducible file inventory snapshots for build artifacts, deployments, or backups:
531
+
532
+ ```bash
533
+ # Generate deterministic snapshot
534
+ rowpipe files snapshot ./dist \
535
+ --hash sha256 \
536
+ --to jsonl > dist.snapshot.v1.jsonl
537
+
538
+ # Generate snapshot of next build
539
+ rowpipe files snapshot ./dist \
540
+ --hash sha256 \
541
+ --to jsonl > dist.snapshot.v2.jsonl
542
+
543
+ # Compare snapshots using Rowpipe's diff engine
544
+ rowpipe diff dist.snapshot.v1.jsonl dist.snapshot.v2.jsonl \
545
+ --key relative_path \
546
+ --ignore modified_at,accessed_at
547
+ ```
548
+
549
+ #### 5. Native Directory Diffing (`rowpipe files diff`)
550
+ Compare two directory trees directly:
551
+
552
+ ```bash
553
+ rowpipe files diff ./dist-v1 ./dist-v2 --hash sha256
554
+ ```
555
+
556
+ ---
557
+
315
558
  ## Node.js Library API
316
559
 
317
560
  Rowpipe can be imported directly in your Node.js projects:
@@ -322,12 +565,15 @@ import {
322
565
  CSVReader,
323
566
  JSONLWriter,
324
567
  XLSXReader,
568
+ ParquetReader,
569
+ FileSystemReader,
325
570
  filterRows,
326
571
  selectColumns,
327
572
  castColumns,
328
573
  mapRows,
329
574
  reduceRows,
330
- DatasetStatsAggregator,
575
+ diffRows,
576
+ computeDiff,
331
577
  } from "rowpipe";
332
578
  import { createReadStream, createWriteStream } from "node:fs";
333
579
 
@@ -351,10 +597,31 @@ await createPipeline(reader)
351
597
  }))
352
598
  .to(writer);
353
599
 
354
- // 2. Multi-Sheet Excel Inspection
355
- const xlsxReader = new XLSXReader("workbook.xlsx");
356
- const metadata = await xlsxReader.inspect();
357
- console.log("Workbook Sheets:", metadata.sheets);
600
+ // 2. Stream Filesystem Directory as Tabular Rows
601
+ const fsReader = new FileSystemReader({
602
+ root: "./src",
603
+ include: ["**/*.ts"],
604
+ hash: "sha256",
605
+ mime: true,
606
+ });
607
+
608
+ for await (const batch of fsReader.read()) {
609
+ for (const file of batch.rows) {
610
+ console.log(`${file.relative_path} (${file.size} bytes) - Hash: ${file.hash}`);
611
+ }
612
+ }
613
+
614
+ // 3. Streaming Dataset Diffing (Async Iterable Events)
615
+ for await (const event of diffRows({
616
+ leftPath: "old.csv",
617
+ rightPath: "new.parquet",
618
+ keys: ["id"],
619
+ ignore: ["updated_at"],
620
+ })) {
621
+ if (event.type === "changed") {
622
+ console.log(`Key ${JSON.stringify(event.key)} changed:`, event.changes);
623
+ }
624
+ }
358
625
  ```
359
626
 
360
627
  ---
@@ -366,32 +633,49 @@ Rowpipe uses standardized POSIX exit codes:
366
633
  | Code | Meaning | Description |
367
634
  |------|---------|-------------|
368
635
  | `0` | Success | Command executed successfully |
369
- | `1` | Generic Failure | Unexpected runtime or internal error |
636
+ | `1` | Generic / Diff Mismatch | Runtime error or diff mismatch when `--fail-on-diff` is specified |
370
637
  | `2` | Invalid Arguments | Missing or invalid CLI flags or arguments |
371
638
  | `3` | Parse Error | Corrupted or malformed CSV/JSON/XLSX input with row/column context |
372
639
  | `4` | Validation Failure | Schema validation violations detected |
640
+ | `5` | Duplicate Key Error | Duplicate key encountered during keyed diff under `--duplicate-key error` |
373
641
 
374
642
  ---
375
643
 
376
644
  ## Memory & Performance Benchmarks
377
645
 
378
- Tested on 1,000,000 synthetic rows through a complete pipeline (`Filter` -> `Select` -> `Cast` -> `Online Stats`):
646
+ ### 1. Pipeline Benchmark (`Filter` -> `Select` -> `Cast` -> `Online Stats`):
379
647
 
380
648
  | Dataset Size | Throughput | Execution Time | Peak RSS Memory | Memory Scaling |
381
649
  |--------------|------------|----------------|-----------------|----------------|
382
650
  | **100,000 rows** | **~318,000 rows/s** | 0.31s | ~106 MB | $O(1)$ Bounded |
383
651
  | **1,000,000 rows** | **~500,000 rows/s** | 2.00s | ~153 MB | $O(1)$ Bounded |
384
652
 
385
- To run the benchmark locally:
653
+ ### 2. Diff Engine Benchmark (`rowpipe diff`):
654
+
655
+ | Comparison Size | Memory Limit | Throughput | Peak RSS Memory | Disk Spilled | Result |
656
+ |-----------------|--------------|------------|-----------------|--------------|--------|
657
+ | **2,000,000 rows** (1M vs 1M) | 64 MB | **~73,000 rows/s** | ~145 MB | Active | 100% Exact Match |
658
+
659
+ ### 3. Filesystem Streaming Benchmark (`rowpipe files`):
660
+
661
+ | File Count | Operation | Throughput | Peak RSS Memory | Memory Scaling |
662
+ |------------|-----------|------------|-----------------|----------------|
663
+ | **50,000 files** | Stream Traversal + Metadata | **~24,100 files/s** | ~147 MB | $O(1)$ Bounded |
664
+ | **50,000 files** | Filter + Select Pipeline | **~13,800 files/s** | ~147 MB | $O(1)$ Bounded |
665
+ | **50,000 files** | Fast Fingerprint Hashing | **~3,470 files/s** | ~148 MB | $O(1)$ Bounded |
666
+
667
+ To run the benchmarks locally:
386
668
 
387
669
  ```bash
388
670
  npm run bench
671
+ npm run benchmark:diff
672
+ npm run benchmark:files
389
673
  ```
390
674
 
391
675
  To enable real-time memory debugging on any command:
392
676
 
393
677
  ```bash
394
- ROWPIPE_DEBUG_MEMORY=1 rowpipe stats huge.csv
678
+ ROWPIPE_DEBUG_MEMORY=1 rowpipe files ./huge-directory --hash fast
395
679
  ```
396
680
 
397
681
  ---
@@ -399,3 +683,5 @@ ROWPIPE_DEBUG_MEMORY=1 rowpipe stats huge.csv
399
683
  ## License
400
684
 
401
685
  MIT
686
+
687
+
@@ -0,0 +1,30 @@
1
+ import type { DiffOnlyFilter, DiffOutputFormat, DuplicateKeyPolicy } from "../../diff/types.js";
2
+ export interface DiffCommandOptions {
3
+ key?: string;
4
+ format?: DiffOutputFormat;
5
+ only?: DiffOnlyFilter;
6
+ columns?: string;
7
+ ignore?: string;
8
+ limit?: string | number;
9
+ json?: boolean;
10
+ output?: string;
11
+ failOnDiff?: boolean;
12
+ duplicateKey?: DuplicateKeyPolicy;
13
+ coerce?: boolean;
14
+ epsilon?: string | number;
15
+ ignoreCase?: boolean;
16
+ trim?: boolean;
17
+ memoryLimit?: string;
18
+ sheet?: string;
19
+ leftSheet?: string;
20
+ rightSheet?: string;
21
+ from?: string;
22
+ fromLeft?: string;
23
+ fromRight?: string;
24
+ schema?: boolean;
25
+ quiet?: boolean;
26
+ noProgress?: boolean;
27
+ batchSize?: string | number;
28
+ }
29
+ export declare function diffCommand(leftPath: string, rightPath: string, options?: DiffCommandOptions): Promise<void>;
30
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/diff.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAEV,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAI7B,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA+Jf"}
@@ -0,0 +1,143 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { DiffMismatchError, InvalidArgumentError } from "../../core/errors.js";
3
+ import { computeDiff, diffRows } from "../../diff/engine.js";
4
+ import { formatJson, formatPatchEvent, formatRowEvent, formatSummary, } from "../../diff/reporter.js";
5
+ import { formatNumber } from "../../utils/formatting.js";
6
+ import { parseMemoryLimit } from "../../diff/storage/spillable-index.js";
7
+ export async function diffCommand(leftPath, rightPath, options = {}) {
8
+ if (leftPath === "-" && rightPath === "-") {
9
+ throw new InvalidArgumentError("Cannot diff stdin against stdin ('- -'). One side must be a file or named source.");
10
+ }
11
+ if (!options.key) {
12
+ throw new InvalidArgumentError("Missing required option: --key <column1,column2,...>");
13
+ }
14
+ const keys = options.key.split(",").map((k) => k.trim()).filter(Boolean);
15
+ const compareColumns = options.columns
16
+ ? options.columns.split(",").map((c) => c.trim()).filter(Boolean)
17
+ : undefined;
18
+ const ignoreColumns = options.ignore
19
+ ? options.ignore.split(",").map((c) => c.trim()).filter(Boolean)
20
+ : undefined;
21
+ const leftSheet = options.leftSheet || options.sheet;
22
+ const rightSheet = options.rightSheet || options.sheet;
23
+ const leftFormat = options.fromLeft || options.from;
24
+ const rightFormat = options.fromRight || options.from;
25
+ const batchSize = Number(options.batchSize) || 1000;
26
+ const leftOptions = {
27
+ sheet: leftSheet,
28
+ format: leftFormat,
29
+ batchSize,
30
+ filePath: leftPath,
31
+ };
32
+ const rightOptions = {
33
+ sheet: rightSheet,
34
+ format: rightFormat,
35
+ batchSize,
36
+ filePath: rightPath,
37
+ };
38
+ const isTty = process.stderr.isTTY && !options.quiet && options.noProgress !== true;
39
+ const onProgress = (info) => {
40
+ if (!isTty)
41
+ return;
42
+ const isSpilledText = info.isSpilled ? " (disk spilled)" : "";
43
+ if (info.phase === "indexing_left") {
44
+ process.stderr.write(`\r\x1b[2K Indexing left: ${formatNumber(info.leftRowsProcessed)} rows${isSpilledText}...`);
45
+ }
46
+ else if (info.phase === "comparing_right") {
47
+ process.stderr.write(`\r\x1b[2K Comparing right: ${formatNumber(info.rightRowsProcessed)} rows [added: ${formatNumber(info.addedCount)}, changed: ${formatNumber(info.changedCount)}, unchanged: ${formatNumber(info.unchangedCount)}]${isSpilledText}...`);
48
+ }
49
+ else if (info.phase === "finishing") {
50
+ process.stderr.write(`\r\x1b[2K Done comparing.\n`);
51
+ }
52
+ };
53
+ const engineOptions = {
54
+ left: leftPath,
55
+ right: rightPath,
56
+ keys,
57
+ columns: compareColumns,
58
+ ignore: ignoreColumns,
59
+ duplicateKey: options.duplicateKey || "error",
60
+ coerce: options.coerce,
61
+ epsilon: options.epsilon !== undefined ? Number(options.epsilon) : undefined,
62
+ ignoreCase: options.ignoreCase,
63
+ trim: options.trim,
64
+ memoryLimitBytes: parseMemoryLimit(options.memoryLimit),
65
+ includeSchema: options.schema !== false,
66
+ leftOptions,
67
+ rightOptions,
68
+ onProgress,
69
+ };
70
+ const formatMode = options.format || "summary";
71
+ const limit = options.limit !== undefined ? Number(options.limit) : undefined;
72
+ const onlyFilter = options.only;
73
+ let outStream = process.stdout;
74
+ let fileStream = null;
75
+ if (options.output) {
76
+ fileStream = createWriteStream(options.output, "utf-8");
77
+ outStream = fileStream;
78
+ }
79
+ const writeOut = async (chunk) => {
80
+ if (!outStream.write(chunk)) {
81
+ await new Promise((resolve) => outStream.once("drain", resolve));
82
+ }
83
+ };
84
+ try {
85
+ if (options.json || formatMode === "summary") {
86
+ const summary = await computeDiff(engineOptions);
87
+ if (isTty) {
88
+ process.stderr.write("\r\x1b[2K");
89
+ }
90
+ if (options.json) {
91
+ await writeOut(formatJson(summary));
92
+ }
93
+ else {
94
+ await writeOut(formatSummary(summary));
95
+ }
96
+ const totalDiffs = summary.rows.added + summary.rows.removed + summary.rows.changed;
97
+ if (options.failOnDiff && totalDiffs > 0) {
98
+ throw new DiffMismatchError(totalDiffs);
99
+ }
100
+ return;
101
+ }
102
+ // -----------------------------------------------------------
103
+ // Streaming row-level output (rows or patch)
104
+ // -----------------------------------------------------------
105
+ let emittedCount = 0;
106
+ let totalDiffCount = 0;
107
+ for await (const event of diffRows(engineOptions)) {
108
+ if (event.type === "added" || event.type === "removed" || event.type === "changed") {
109
+ totalDiffCount++;
110
+ }
111
+ // Filter by --only
112
+ if (onlyFilter && event.type !== onlyFilter) {
113
+ continue;
114
+ }
115
+ if (limit !== undefined && emittedCount >= limit) {
116
+ continue;
117
+ }
118
+ let formattedText = null;
119
+ if (formatMode === "rows") {
120
+ formattedText = formatRowEvent(event);
121
+ }
122
+ else if (formatMode === "patch") {
123
+ formattedText = formatPatchEvent(event);
124
+ }
125
+ if (formattedText) {
126
+ await writeOut(formattedText);
127
+ emittedCount++;
128
+ }
129
+ }
130
+ if (isTty) {
131
+ process.stderr.write("\r\x1b[2K");
132
+ }
133
+ if (options.failOnDiff && totalDiffCount > 0) {
134
+ throw new DiffMismatchError(totalDiffCount);
135
+ }
136
+ }
137
+ finally {
138
+ if (fileStream) {
139
+ await new Promise((resolve) => fileStream.end(() => resolve()));
140
+ }
141
+ }
142
+ }
143
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../../src/cli/commands/diff.ts"],"names":[],"mappings":"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"}