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
@@ -0,0 +1,263 @@
1
+ ---
2
+ name: rowpipe
3
+ description: Stream-first tabular data toolkit for inspecting, analyzing, transforming, validating, converting, mapping, and reducing large datasets across CSV, TSV, JSON, JSONL, XLSX, Apache Parquet, and Markdown formats with bounded O(1) memory and extreme throughput (~500,000 rows/s).
4
+ ---
5
+
6
+ # Rowpipe: Stream-First Tabular Data Toolkit
7
+
8
+ Rowpipe is a high-performance, stream-first command-line toolkit and Node.js library for tabular data processing. It processes gigabyte-scale datasets with bounded $O(1)$ memory usage, backpressure, batching, and zero-`eval` safe expression execution.
9
+
10
+ ---
11
+
12
+ ## 🚀 Quick Command Reference
13
+
14
+ | Task | Command Syntax | Description |
15
+ |---|---|---|
16
+ | **Inspect** | `rowpipe inspect <file> [--sheet <name>] [--json]` | Inspect row count, column types, null %, and HLL uniqueness |
17
+ | **Convert** | `rowpipe convert <input> <output> [--all-sheets]` | Stream convert between CSV, TSV, JSON, JSONL, XLSX, Parquet, Markdown |
18
+ | **Schema** | `rowpipe schema <file> [--full] [--sample <n>]` | Infer column types and semantic patterns (email, url, uuid) |
19
+ | **Stats** | `rowpipe stats <file> [--column <name>]` | Streaming Welford numeric stats & HyperLogLog distinct count |
20
+ | **Map** | `rowpipe map <file> "<col1=expr1>" "<col2=expr2>"` | Derive and compute columns per row with arithmetic/JSON dot-notation |
21
+ | **Filter** | `rowpipe filter <file> "<expression>"` | Filter rows using safe expression engine and pipe syntax (`\|`) |
22
+ | **Reduce** | `rowpipe reduce <file> "<agg1=func(col)>" [--by <cols>]` | Global & group-by streaming aggregations (`sum`, `avg`, `min`, `max`, `count`, `countDistinct`, `stddev`) |
23
+ | **Select** | `rowpipe select <file> <col1,col2,...>` | Project subset of columns in order |
24
+ | **Rename** | `rowpipe rename <file> <old1=new1> <old2=new2>` | Rename columns in stream |
25
+ | **Cast** | `rowpipe cast <file> <col1:type> [--on-error null]` | Stream cast types (`integer`, `number`, `boolean`, `date`) |
26
+ | **Sample** | `rowpipe sample <file> --rows <n> [--seed <s>]` | Reservoir sampling with bounded memory |
27
+ | **Validate** | `rowpipe validate <file> --schema <schema.json>` | Validate stream against JSON schema definition |
28
+ | **Diff** | `rowpipe diff <left> <right> --key <cols> [opts]` | Stream compare datasets by key across formats with bounded RAM and disk spilling |
29
+ | **Files** | `rowpipe files <path> [--include <glob>] [opts]` | Stream filesystem directory as tabular records with bounded memory, globs, hashes, and MIME |
30
+
31
+ ---
32
+
33
+ ## 🛠️ Common Workflows & Examples
34
+
35
+ ### 1. Inspect & Schema Inference (CSV, TSV, Parquet, Excel)
36
+ ```bash
37
+ # Inspect CSV / TSV / JSONL file
38
+ rowpipe inspect dataset.csv
39
+ rowpipe inspect data.tsv
40
+
41
+ # Inspect Apache Parquet file directly
42
+ rowpipe inspect dataset.parquet --json
43
+
44
+ # Inspect multi-sheet Excel workbook
45
+ rowpipe inspect workbook.xlsx
46
+ rowpipe inspect workbook.xlsx --sheet Orders
47
+
48
+ # Full schema inference with semantic types (email, URL, UUID, date)
49
+ rowpipe schema huge.csv --full
50
+ ```
51
+
52
+ ### 2. Format Conversion, Parquet & Gzip Streams
53
+ ```bash
54
+ # Convert CSV to Apache Parquet
55
+ rowpipe convert sales.csv sales.parquet
56
+
57
+ # Convert Parquet to compressed JSONL (.jsonl.gz)
58
+ rowpipe convert sales.parquet sales.jsonl.gz
59
+
60
+ # Filter Parquet and output directly as GitHub Markdown table
61
+ rowpipe filter dataset.parquet "amount > 500" --to markdown
62
+
63
+ # Convert specific Excel worksheet to TSV
64
+ rowpipe convert workbook.xlsx orders.tsv --sheet Orders
65
+
66
+ # Bulk export all worksheets in workbook to a directory
67
+ rowpipe convert workbook.xlsx --all-sheets --out-dir ./exported_sheets --to csv
68
+ ```
69
+
70
+ ### 3. Row Derivations & Calculations (`map`)
71
+ Derive calculated columns using arithmetic, strings, dates, and direct JSON dot-notation:
72
+ ```bash
73
+ # Arithmetic & percentage formulas
74
+ rowpipe map sales.csv \
75
+ "profit = revenue - cost" \
76
+ "tax = revenue * 0.20" \
77
+ "margin = ((revenue - cost) / revenue) * 100"
78
+
79
+ # Direct JSON dot-notation & pipe transformations
80
+ rowpipe map events.jsonl \
81
+ "city = payload.user.address.city" \
82
+ "email = payload.user.email | lower | trim" \
83
+ "item_price = payload.items.0.price" \
84
+ "year = created_at | year"
85
+
86
+ # Conditional mapping
87
+ rowpipe map orders.csv "net_total = total | if(is_vip, total * 0.90, total) | round(2)"
88
+ ```
89
+
90
+ ### 4. Advanced Row Filtering (`filter`)
91
+ ```bash
92
+ # Arithmetic & comparison on Parquet / CSV
93
+ rowpipe filter sales.parquet '(revenue - cost) / revenue >= 0.25'
94
+
95
+ # Pipe syntax for string & date functions
96
+ rowpipe filter users.csv 'email | lower | endsWith("@corp.com")'
97
+ rowpipe filter orders.csv 'created_at | year == 2026 && total > 500'
98
+
99
+ # Direct JSON dot-notation in filter
100
+ rowpipe filter events.jsonl.gz 'payload.user.city == "Istanbul" && payload.items.0.price > 50'
101
+
102
+ # Set membership and range checks
103
+ rowpipe filter products.tsv 'category | in("Electronics", "Computers") && price | between(100, 1000)'
104
+ ```
105
+
106
+ ### 5. Group-By & Global Aggregations (`reduce`)
107
+ ```bash
108
+ # 1. Global summary (Single row output)
109
+ rowpipe reduce sales.parquet \
110
+ "total_revenue = sum(revenue)" \
111
+ "avg_margin = avg(margin)" \
112
+ "min_price = min(price)" \
113
+ "max_price = max(price)" \
114
+ "total_orders = count()" \
115
+ "unique_customers = countDistinct(customer_id)"
116
+
117
+ # 2. Group-By Streaming Aggregations
118
+ rowpipe reduce sales.csv \
119
+ "total_revenue = sum(revenue)" \
120
+ "avg_profit = avg(profit)" \
121
+ "order_count = count()" \
122
+ --by country,category
123
+ ```
124
+
125
+ Supported reduce functions:
126
+ - `sum(col)` — Numeric sum
127
+ - `avg(col)` / `mean(col)` — Average
128
+ - `min(col)` / `max(col)` — Minimum / Maximum
129
+ - `count()` / `count(col)` — Row / non-null count
130
+ - `countDistinct(col)` — HyperLogLog distinct cardinality ($O(1)$ memory)
131
+ - `stddev(col)` / `variance(col)` — Welford single-pass online standard deviation / variance
132
+ - `first(col)` / `last(col)` — First non-null / last value
133
+
134
+ ### 6. End-to-End Unix Pipe Processing Chain
135
+ ```bash
136
+ cat transactions.csv.gz | \
137
+ rowpipe map - "profit=revenue - cost" "margin=((revenue - cost) / revenue) * 100" | \
138
+ rowpipe filter - "margin >= 15" | \
139
+ rowpipe reduce - "total_profit=sum(profit)" "avg_margin=avg(margin)" "count=count()" --by country | \
140
+ rowpipe convert - --to markdown
141
+ ```
142
+
143
+ ### 7. Dataset Diffing & Change Feeds (`diff`)
144
+ Compare two datasets by key across formats with bounded memory ($O(1)$) and automatic disk spilling:
145
+
146
+ ```bash
147
+ # 1. Basic Keyed Diff
148
+ rowpipe diff yesterday.csv today.csv --key id
149
+
150
+ # 2. Composite Key & Cross-Format (CSV vs Parquet)
151
+ rowpipe diff old.csv new.parquet --key country,user_id
152
+
153
+ # 3. Multi-Sheet Excel Workbook Diff
154
+ rowpipe diff old.xlsx new.xlsx --sheet Users --key id
155
+
156
+ # 4. Filter Columns & String Normalization
157
+ rowpipe diff old.csv new.csv \
158
+ --key id \
159
+ --ignore updated_at,last_seen \
160
+ --trim \
161
+ --ignore-case \
162
+ --epsilon 0.001
163
+
164
+ # 5. Output Modes: Rows, JSONL Patch, and JSON
165
+ rowpipe diff old.csv new.csv --key id --format rows --only changed --limit 50
166
+ rowpipe diff old.csv new.csv --key id --format patch > changes.jsonl
167
+ rowpipe diff old.csv new.csv --key id --json
168
+
169
+ # 6. CI/CD Validation (Fails with exit code 1 if differences found)
170
+ rowpipe diff expected.csv actual.csv --key id --fail-on-diff
171
+ ```
172
+
173
+ ### 8. Filesystem as Streaming Tabular Records (`files`)
174
+ Stream files and directory trees with bounded memory, globs, streaming hashes, and MIME detection:
175
+
176
+ ```bash
177
+ # 1. Stream directory records
178
+ rowpipe files ./src --include "**/*.ts"
179
+
180
+ # 2. Filter large files and select columns via Unix pipe
181
+ rowpipe files ./uploads | rowpipe filter - 'size > 1000000' | rowpipe select - relative_path,size
182
+
183
+ # 3. Stream SHA-256 or fast hashes
184
+ rowpipe files ./assets --hash sha256 --to jsonl
185
+ rowpipe files ./dataset --hash fast
186
+
187
+ # 4. Generate deterministic build snapshot
188
+ rowpipe files snapshot ./dist --hash sha256 --to jsonl > dist.snapshot.jsonl
189
+
190
+ # 5. Direct directory diffing
191
+ rowpipe files diff ./dist-v1 ./dist-v2 --hash sha256
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 💻 Node.js Library API
197
+
198
+ ```typescript
199
+ import {
200
+ createPipeline,
201
+ createReader,
202
+ createWriter,
203
+ CSVReader,
204
+ JSONLWriter,
205
+ ParquetReader,
206
+ ParquetWriter,
207
+ MarkdownWriter,
208
+ XLSXReader,
209
+ FileSystemReader,
210
+ mapRows,
211
+ filterRows,
212
+ reduceRows,
213
+ diffRows,
214
+ computeDiff,
215
+ } from "rowpipe";
216
+ import { createReadStream, createWriteStream } from "node:fs";
217
+
218
+ // 1. Streaming Parquet -> Filter -> Markdown Pipeline
219
+ const reader = new ParquetReader("sales.parquet");
220
+ const writer = new MarkdownWriter(createWriteStream("summary.md"));
221
+
222
+ await createPipeline(reader)
223
+ .pipe(mapRows({
224
+ profit: "revenue - cost",
225
+ margin: "((revenue - cost) / revenue) * 100",
226
+ }))
227
+ .pipe(filterRows("margin >= 15"))
228
+ .pipe(reduceRows({
229
+ by: ["country"],
230
+ aggregations: [
231
+ "total_profit = sum(profit)",
232
+ "avg_margin = avg(margin)",
233
+ "count = count()",
234
+ ],
235
+ }))
236
+ .to(writer);
237
+
238
+ // 2. Stream Filesystem Directory as Tabular Rows
239
+ const fsReader = new FileSystemReader({
240
+ root: "./src",
241
+ include: ["**/*.ts"],
242
+ hash: "sha256",
243
+ mime: true,
244
+ });
245
+
246
+ for await (const batch of fsReader.read()) {
247
+ for (const file of batch.rows) {
248
+ console.log(`${file.relative_path} (${file.size} bytes) - Hash: ${file.hash}`);
249
+ }
250
+ }
251
+
252
+ // 3. Streaming Diff Events
253
+ for await (const event of diffRows({
254
+ leftPath: "old.csv",
255
+ rightPath: "new.parquet",
256
+ keys: ["id"],
257
+ ignore: ["updated_at"],
258
+ })) {
259
+ if (event.type === "changed") {
260
+ console.log(`Row ${JSON.stringify(event.key)} changed:`, event.changes);
261
+ }
262
+ }
263
+ ```
@@ -1,109 +0,0 @@
1
- import { createPipeline } from "../src/core/pipeline.js";
2
- import { filterRows } from "../src/transforms/filter.js";
3
- import { selectColumns } from "../src/transforms/select.js";
4
- import { castColumns } from "../src/transforms/cast.js";
5
- import { DatasetStatsAggregator } from "../src/analytics/stats.js";
6
- import type { DataBatch, DataStream, Row } from "../src/core/types.js";
7
- import { formatBytes, formatNumber } from "../src/utils/formatting.js";
8
-
9
- /**
10
- * Generator producing synthetic data batches without buffering.
11
- */
12
- async function* generateSyntheticStream(
13
- totalRows: number,
14
- batchSize = 2000
15
- ): DataStream {
16
- let offset = 0;
17
- while (offset < totalRows) {
18
- const currentBatchSize = Math.min(batchSize, totalRows - offset);
19
- const rows: Row[] = new Array(currentBatchSize);
20
-
21
- for (let i = 0; i < currentBatchSize; i++) {
22
- const id = offset + i;
23
- rows[i] = {
24
- id: String(id),
25
- name: `User_${id}`,
26
- age: String(20 + (id % 50)),
27
- email: `user${id}@example.com`,
28
- revenue: String((id % 1000) * 1.5),
29
- country: id % 3 === 0 ? "TR" : id % 3 === 1 ? "US" : "DE",
30
- };
31
- }
32
-
33
- yield {
34
- rows,
35
- offset,
36
- };
37
- offset += currentBatchSize;
38
- }
39
- }
40
-
41
- async function runBenchmark(totalRows: number): Promise<void> {
42
- process.stdout.write(`\n======================================================\n`);
43
- process.stdout.write(`Running Benchmark with ${formatNumber(totalRows)} synthetic rows...\n`);
44
- process.stdout.write(`Pipeline: Input -> Filter(age > 25) -> Select(id, name, age, revenue) -> Cast(revenue:number) -> Stats\n`);
45
- process.stdout.write(`------------------------------------------------------\n`);
46
-
47
- const initialMem = process.memoryUsage();
48
- let peakRss = initialMem.rss;
49
- let peakHeap = initialMem.heapUsed;
50
-
51
- const startTime = Date.now();
52
- let processedRows = 0;
53
-
54
- const rawStream = generateSyntheticStream(totalRows, 2000);
55
-
56
- const pipeline = createPipeline(rawStream)
57
- .pipe(filterRows("age > 25"))
58
- .pipe(selectColumns(["id", "name", "age", "revenue"]))
59
- .pipe(castColumns({ revenue: "number" }));
60
-
61
- const statsAgg = new DatasetStatsAggregator();
62
-
63
- for await (const batch of pipeline.batches()) {
64
- processedRows += batch.rows.length;
65
- for (const row of batch.rows) {
66
- statsAgg.add(row);
67
- }
68
-
69
- const currentMem = process.memoryUsage();
70
- if (currentMem.rss > peakRss) peakRss = currentMem.rss;
71
- if (currentMem.heapUsed > peakHeap) peakHeap = currentMem.heapUsed;
72
-
73
- if (processedRows % 250000 === 0 || processedRows === totalRows) {
74
- const elapsed = (Date.now() - startTime) / 1000 || 0.001;
75
- const speed = Math.round(processedRows / elapsed);
76
- process.stdout.write(
77
- ` Processed: ${formatNumber(processedRows)} rows | Elapsed: ${elapsed.toFixed(1)}s | RSS: ${formatBytes(currentMem.rss)} | Heap: ${formatBytes(currentMem.heapUsed)} | Speed: ${formatNumber(speed)} rows/s\n`
78
- );
79
- }
80
- }
81
-
82
- const totalTimeMs = Date.now() - startTime;
83
- const elapsedSec = totalTimeMs / 1000 || 0.001;
84
- const rowsPerSec = Math.round(totalRows / elapsedSec);
85
- // Estimate ~70 bytes per row
86
- const estimatedMB = (totalRows * 70) / (1024 * 1024);
87
- const mbPerSec = (estimatedMB / elapsedSec).toFixed(1);
88
-
89
- const results = statsAgg.result();
90
-
91
- process.stdout.write(`------------------------------------------------------\n`);
92
- process.stdout.write(`Benchmark Results:\n`);
93
- process.stdout.write(` Total Input Rows: ${formatNumber(totalRows)}\n`);
94
- process.stdout.write(` Filtered Rows: ${formatNumber(processedRows)}\n`);
95
- process.stdout.write(` Execution Time: ${elapsedSec.toFixed(2)}s\n`);
96
- process.stdout.write(` Throughput: ${formatNumber(rowsPerSec)} rows/s (~${mbPerSec} MB/s)\n`);
97
- process.stdout.write(` Initial RSS: ${formatBytes(initialMem.rss)}\n`);
98
- process.stdout.write(` Peak RSS: ${formatBytes(peakRss)}\n`);
99
- process.stdout.write(` Peak Heap Used: ${formatBytes(peakHeap)}\n`);
100
- process.stdout.write(` Memory Growth: Bounded ($O(1)$ constant memory)\n`);
101
- process.stdout.write(`======================================================\n`);
102
- }
103
-
104
- async function main() {
105
- await runBenchmark(100_000);
106
- await runBenchmark(1_000_000);
107
- }
108
-
109
- main().catch(console.error);