rowpipe 1.0.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 +401 -0
- package/benchmarks/memory-bench.ts +109 -0
- package/dist/analytics/reduce.d.ts +33 -0
- package/dist/analytics/reduce.d.ts.map +1 -0
- package/dist/analytics/reduce.js +318 -0
- package/dist/analytics/reduce.js.map +1 -0
- package/dist/analytics/schema-inference.d.ts +31 -0
- package/dist/analytics/schema-inference.d.ts.map +1 -0
- package/dist/analytics/schema-inference.js +166 -0
- package/dist/analytics/schema-inference.js.map +1 -0
- package/dist/analytics/semantic-types.d.ts +6 -0
- package/dist/analytics/semantic-types.d.ts.map +1 -0
- package/dist/analytics/semantic-types.js +40 -0
- package/dist/analytics/semantic-types.js.map +1 -0
- package/dist/analytics/stats.d.ts +102 -0
- package/dist/analytics/stats.d.ts.map +1 -0
- package/dist/analytics/stats.js +307 -0
- package/dist/analytics/stats.js.map +1 -0
- package/dist/analytics/validator.d.ts +32 -0
- package/dist/analytics/validator.d.ts.map +1 -0
- package/dist/analytics/validator.js +96 -0
- package/dist/analytics/validator.js.map +1 -0
- package/dist/cli/commands/cast.d.ts +13 -0
- package/dist/cli/commands/cast.d.ts.map +1 -0
- package/dist/cli/commands/cast.js +39 -0
- package/dist/cli/commands/cast.js.map +1 -0
- package/dist/cli/commands/convert.d.ts +15 -0
- package/dist/cli/commands/convert.d.ts.map +1 -0
- package/dist/cli/commands/convert.js +89 -0
- package/dist/cli/commands/convert.js.map +1 -0
- package/dist/cli/commands/filter.d.ts +11 -0
- package/dist/cli/commands/filter.d.ts.map +1 -0
- package/dist/cli/commands/filter.js +38 -0
- package/dist/cli/commands/filter.js.map +1 -0
- package/dist/cli/commands/inspect.d.ts +10 -0
- package/dist/cli/commands/inspect.d.ts.map +1 -0
- package/dist/cli/commands/inspect.js +134 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/map.d.ts +11 -0
- package/dist/cli/commands/map.d.ts.map +1 -0
- package/dist/cli/commands/map.js +43 -0
- package/dist/cli/commands/map.js.map +1 -0
- package/dist/cli/commands/reduce.d.ts +12 -0
- package/dist/cli/commands/reduce.d.ts.map +1 -0
- package/dist/cli/commands/reduce.js +49 -0
- package/dist/cli/commands/reduce.js.map +1 -0
- package/dist/cli/commands/rename.d.ts +11 -0
- package/dist/cli/commands/rename.d.ts.map +1 -0
- package/dist/cli/commands/rename.js +39 -0
- package/dist/cli/commands/rename.js.map +1 -0
- package/dist/cli/commands/sample.d.ts +13 -0
- package/dist/cli/commands/sample.d.ts.map +1 -0
- package/dist/cli/commands/sample.js +40 -0
- package/dist/cli/commands/sample.js.map +1 -0
- package/dist/cli/commands/schema.d.ts +13 -0
- package/dist/cli/commands/schema.d.ts.map +1 -0
- package/dist/cli/commands/schema.js +57 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/select.d.ts +11 -0
- package/dist/cli/commands/select.d.ts.map +1 -0
- package/dist/cli/commands/select.js +39 -0
- package/dist/cli/commands/select.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +15 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +75 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +12 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +86 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +262 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/batch.d.ts +15 -0
- package/dist/core/batch.d.ts.map +1 -0
- package/dist/core/batch.js +67 -0
- package/dist/core/batch.js.map +1 -0
- package/dist/core/errors.d.ts +35 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +55 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/pipeline.d.ts +40 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +120 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/types.d.ts +73 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/readers/csv.d.ts +24 -0
- package/dist/readers/csv.d.ts.map +1 -0
- package/dist/readers/csv.js +251 -0
- package/dist/readers/csv.js.map +1 -0
- package/dist/readers/index.d.ts +17 -0
- package/dist/readers/index.d.ts.map +1 -0
- package/dist/readers/index.js +74 -0
- package/dist/readers/index.js.map +1 -0
- package/dist/readers/json.d.ts +20 -0
- package/dist/readers/json.d.ts.map +1 -0
- package/dist/readers/json.js +219 -0
- package/dist/readers/json.js.map +1 -0
- package/dist/readers/jsonl.d.ts +14 -0
- package/dist/readers/jsonl.d.ts.map +1 -0
- package/dist/readers/jsonl.js +95 -0
- package/dist/readers/jsonl.js.map +1 -0
- package/dist/readers/xlsx.d.ts +18 -0
- package/dist/readers/xlsx.d.ts.map +1 -0
- package/dist/readers/xlsx.js +138 -0
- package/dist/readers/xlsx.js.map +1 -0
- package/dist/transforms/cast.d.ts +21 -0
- package/dist/transforms/cast.d.ts.map +1 -0
- package/dist/transforms/cast.js +182 -0
- package/dist/transforms/cast.js.map +1 -0
- package/dist/transforms/expression.d.ts +67 -0
- package/dist/transforms/expression.d.ts.map +1 -0
- package/dist/transforms/expression.js +943 -0
- package/dist/transforms/expression.js.map +1 -0
- package/dist/transforms/filter.d.ts +6 -0
- package/dist/transforms/filter.d.ts.map +1 -0
- package/dist/transforms/filter.js +31 -0
- package/dist/transforms/filter.js.map +1 -0
- package/dist/transforms/map.d.ts +12 -0
- package/dist/transforms/map.d.ts.map +1 -0
- package/dist/transforms/map.js +76 -0
- package/dist/transforms/map.js.map +1 -0
- package/dist/transforms/rename.d.ts +10 -0
- package/dist/transforms/rename.d.ts.map +1 -0
- package/dist/transforms/rename.js +39 -0
- package/dist/transforms/rename.js.map +1 -0
- package/dist/transforms/sample.d.ts +10 -0
- package/dist/transforms/sample.d.ts.map +1 -0
- package/dist/transforms/sample.js +47 -0
- package/dist/transforms/sample.js.map +1 -0
- package/dist/transforms/select.d.ts +6 -0
- package/dist/transforms/select.d.ts.map +1 -0
- package/dist/transforms/select.js +63 -0
- package/dist/transforms/select.js.map +1 -0
- package/dist/utils/compression.d.ts +14 -0
- package/dist/utils/compression.d.ts.map +1 -0
- package/dist/utils/compression.js +38 -0
- package/dist/utils/compression.js.map +1 -0
- package/dist/utils/formatting.d.ts +6 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +56 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/progress.d.ts +13 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +27 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/writers/csv.d.ts +18 -0
- package/dist/writers/csv.d.ts.map +1 -0
- package/dist/writers/csv.js +84 -0
- package/dist/writers/csv.js.map +1 -0
- package/dist/writers/index.d.ts +17 -0
- package/dist/writers/index.d.ts.map +1 -0
- package/dist/writers/index.js +73 -0
- package/dist/writers/index.js.map +1 -0
- package/dist/writers/json.d.ts +12 -0
- package/dist/writers/json.d.ts.map +1 -0
- package/dist/writers/json.js +42 -0
- package/dist/writers/json.js.map +1 -0
- package/dist/writers/jsonl.d.ts +12 -0
- package/dist/writers/jsonl.d.ts.map +1 -0
- package/dist/writers/jsonl.js +41 -0
- package/dist/writers/jsonl.js.map +1 -0
- package/dist/writers/xlsx.d.ts +13 -0
- package/dist/writers/xlsx.d.ts.map +1 -0
- package/dist/writers/xlsx.js +65 -0
- package/dist/writers/xlsx.js.map +1 -0
- package/package.json +44 -0
- package/src/analytics/reduce.ts +388 -0
- package/src/analytics/schema-inference.ts +217 -0
- package/src/analytics/semantic-types.ts +36 -0
- package/src/analytics/stats.ts +383 -0
- package/src/analytics/validator.ts +134 -0
- package/src/cli/commands/cast.ts +60 -0
- package/src/cli/commands/convert.ts +128 -0
- package/src/cli/commands/filter.ts +58 -0
- package/src/cli/commands/inspect.ts +193 -0
- package/src/cli/commands/map.ts +66 -0
- package/src/cli/commands/reduce.ts +74 -0
- package/src/cli/commands/rename.ts +59 -0
- package/src/cli/commands/sample.ts +61 -0
- package/src/cli/commands/schema.ts +86 -0
- package/src/cli/commands/select.ts +59 -0
- package/src/cli/commands/stats.ts +100 -0
- package/src/cli/commands/validate.ts +123 -0
- package/src/cli/index.ts +285 -0
- package/src/core/batch.ts +81 -0
- package/src/core/errors.ts +81 -0
- package/src/core/pipeline.ts +150 -0
- package/src/core/types.ts +110 -0
- package/src/index.ts +52 -0
- package/src/readers/csv.ts +302 -0
- package/src/readers/index.ts +88 -0
- package/src/readers/json.ts +256 -0
- package/src/readers/jsonl.ts +126 -0
- package/src/readers/xlsx.ts +182 -0
- package/src/transforms/cast.ts +212 -0
- package/src/transforms/expression.ts +1114 -0
- package/src/transforms/filter.ts +38 -0
- package/src/transforms/map.ts +99 -0
- package/src/transforms/rename.ts +47 -0
- package/src/transforms/sample.ts +57 -0
- package/src/transforms/select.ts +64 -0
- package/src/utils/compression.ts +43 -0
- package/src/utils/formatting.ts +75 -0
- package/src/utils/progress.ts +37 -0
- package/src/writers/csv.ts +106 -0
- package/src/writers/index.ts +87 -0
- package/src/writers/json.ts +52 -0
- package/src/writers/jsonl.ts +49 -0
- package/src/writers/xlsx.ts +87 -0
- package/tests/analytics.test.ts +119 -0
- package/tests/cli-integration.test.ts +187 -0
- package/tests/map-reduce.test.ts +223 -0
- package/tests/readers-writers.test.ts +189 -0
- package/tests/transforms.test.ts +199 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Rowpipe
|
|
2
|
+
|
|
3
|
+
> **A stream-first tabular data toolkit for CSV, JSON, JSONL, and XLSX.**
|
|
4
|
+
>
|
|
5
|
+
> *Don't load the dataset. Stream through it.*
|
|
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.
|
|
8
|
+
|
|
9
|
+
Designed around stream backpressure and batch processing, Rowpipe processes multi-gigabyte and multi-million-row datasets with constant $O(1)$ memory usage.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Highlights
|
|
14
|
+
|
|
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`).
|
|
18
|
+
- **Numerically Stable Streaming Stats**: Online Welford statistics (mean, variance, stddev, min, max, sum) and $O(1)$ HyperLogLog distinct cardinality estimation.
|
|
19
|
+
- **Incremental Schema Inference & Semantic Types**: Detects data types with confidence scoring and semantic patterns (`email`, `url`, `uuid`, `ipv4`, `country-code`, `currency`, `phone`).
|
|
20
|
+
- **Streaming Schema Validation**: Validates stream against JSON schemas with detailed violation reporting.
|
|
21
|
+
- **Reservoir Sampling**: Deterministic $O(k)$ bounded sampling with `--seed` support.
|
|
22
|
+
- **Unix Pipeline Friendly**: Full support for stdin/stdout, pipes, `.gz` stream decompression/compression, and clean stdout data vs. stderr progress separation.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g rowpipe
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or run directly via `npx`:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx rowpipe --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For use as a Node.js library:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install rowpipe
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## CLI Usage & Examples
|
|
47
|
+
|
|
48
|
+
### 1. Inspect Dataset & Multi-Sheet Excel Analysis
|
|
49
|
+
Inspect format, row count, column list, data types, null percentage, and approximate distinct counts:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
rowpipe inspect users.csv
|
|
53
|
+
|
|
54
|
+
# Machine-readable JSON output
|
|
55
|
+
rowpipe inspect users.csv --json
|
|
56
|
+
|
|
57
|
+
# Analyze all sheets in an Excel workbook
|
|
58
|
+
rowpipe inspect workbook.xlsx
|
|
59
|
+
|
|
60
|
+
# Inspect a specific sheet in full detail
|
|
61
|
+
rowpipe inspect workbook.xlsx --sheet Users
|
|
62
|
+
```
|
|
63
|
+
|
|
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
|
|
80
|
+
Convert across formats without intermediate buffering:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Export specific Excel worksheet to CSV
|
|
84
|
+
rowpipe convert workbook.xlsx users.csv --sheet Users
|
|
85
|
+
|
|
86
|
+
# Export specific Excel worksheet to JSONL
|
|
87
|
+
rowpipe convert workbook.xlsx orders.jsonl --sheet Orders
|
|
88
|
+
|
|
89
|
+
# Bulk export ALL worksheets to individual files in a directory
|
|
90
|
+
rowpipe convert workbook.xlsx --all-sheets --out-dir ./exported/ --to csv
|
|
91
|
+
|
|
92
|
+
# CSV to JSONL
|
|
93
|
+
rowpipe convert users.csv users.jsonl
|
|
94
|
+
|
|
95
|
+
# CSV to JSON array
|
|
96
|
+
rowpipe convert users.csv users.json
|
|
97
|
+
|
|
98
|
+
# Streaming through gzip
|
|
99
|
+
rowpipe convert data.csv.gz data.jsonl.gz
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Streaming Statistics
|
|
103
|
+
Compute online statistics:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Dataset-wide statistics
|
|
107
|
+
rowpipe stats sales.csv
|
|
108
|
+
|
|
109
|
+
# Specific column stats
|
|
110
|
+
rowpipe stats sales.csv --column revenue
|
|
111
|
+
|
|
112
|
+
# Excel sheet stats
|
|
113
|
+
rowpipe stats workbook.xlsx --sheet Orders --column total
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Example output:
|
|
117
|
+
```text
|
|
118
|
+
Total Rows: 8,531,221
|
|
119
|
+
|
|
120
|
+
revenue (numeric)
|
|
121
|
+
----------------------------------------
|
|
122
|
+
count 7,828,128
|
|
123
|
+
null 703,093
|
|
124
|
+
min 0
|
|
125
|
+
max 81,231.14
|
|
126
|
+
sum 651,378,531.88
|
|
127
|
+
mean 83.21
|
|
128
|
+
stddev 412.31
|
|
129
|
+
variance 170,001.21
|
|
130
|
+
distinct* ~8,421,000
|
|
131
|
+
|
|
132
|
+
* approximate
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. Schema Inference
|
|
136
|
+
Infer column types, nullability, confidence percentages, and semantic patterns:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
rowpipe schema users.csv
|
|
140
|
+
|
|
141
|
+
# Sample first 10,000 rows (default) or scan full dataset
|
|
142
|
+
rowpipe schema huge.csv --full
|
|
143
|
+
|
|
144
|
+
# Excel sheet schema inference
|
|
145
|
+
rowpipe schema workbook.xlsx --sheet Users
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Example output:
|
|
149
|
+
```text
|
|
150
|
+
COLUMN TYPE NULLABLE CONFIDENCE
|
|
151
|
+
id integer false 100%
|
|
152
|
+
name string false 100%
|
|
153
|
+
email string (email) true 99.4%
|
|
154
|
+
age integer true 98.1%
|
|
155
|
+
created_at date false 100%
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 5. Filter Rows (Safe Expression Engine & Pipe Syntax)
|
|
159
|
+
Filter rows using safe expressions with standard function calls or Unix-style **Pipe Syntax (`|`)** without `eval`:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Pipe syntax for string transformations
|
|
163
|
+
rowpipe filter users.csv 'email | lower | trim | endsWith("@corp.com")'
|
|
164
|
+
|
|
165
|
+
# Date & time functions with pipe
|
|
166
|
+
rowpipe filter orders.csv 'created_at | year == 2026 && created_at | month >= 6'
|
|
167
|
+
|
|
168
|
+
# Set membership and range checks
|
|
169
|
+
rowpipe filter sales.csv 'country | in("TR", "US", "DE") && age | between(18, 65)'
|
|
170
|
+
|
|
171
|
+
# String split and substring
|
|
172
|
+
rowpipe filter users.csv 'email | splitIndex("@", 1) == "gmail.com"'
|
|
173
|
+
rowpipe filter users.csv 'phone | substr(0, 3) == "+90"'
|
|
174
|
+
|
|
175
|
+
# Type checking and semantic patterns
|
|
176
|
+
rowpipe filter users.csv 'email | isEmail && age | isNumber'
|
|
177
|
+
|
|
178
|
+
# Direct dot-notation JSON & nested object access
|
|
179
|
+
rowpipe filter events.csv 'payload.user.address.city == "Istanbul"'
|
|
180
|
+
rowpipe filter events.csv 'payload.items.0.price > 50'
|
|
181
|
+
rowpipe filter users.jsonl 'profile.address.country == "TR" && profile.age >= 18'
|
|
182
|
+
|
|
183
|
+
# Combining dot-notation with pipe transforms
|
|
184
|
+
rowpipe filter events.csv 'payload.user.email | lower | trim | endsWith("@corp.com")'
|
|
185
|
+
rowpipe filter events.csv 'payload.meta.score | toFloat | between(80, 100)'
|
|
186
|
+
|
|
187
|
+
# Math and numeric bounds
|
|
188
|
+
rowpipe filter products.csv 'price | clamp(10, 100) > 50'
|
|
189
|
+
rowpipe filter metrics.csv 'revenue | toFloat | round(2) >= 1000'
|
|
190
|
+
|
|
191
|
+
# Conditional evaluation (if / iif)
|
|
192
|
+
rowpipe filter orders.csv 'total | if(is_vip, total * 0.9, total) > 500'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Supported Operators & Functions:
|
|
196
|
+
- **Direct Dot-Notation**: `payload.user.city`, `payload.items.0.price`, `user.address.zip` (automatically navigates nested objects, arrays, and JSON strings)
|
|
197
|
+
- **Pipe Operator**: `val | func` / `val | func(arg1, arg2)` (Chained functional piping)
|
|
198
|
+
- **Comparison**: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
199
|
+
- **Logic**: `&&`, `||`, `!`, `and`, `or`, `not`
|
|
200
|
+
- **Arithmetic**: `+`, `-`, `*`, `/`, `%`
|
|
201
|
+
- **Set & Range**: `in(val, ...)`, `notIn(val, ...)`, `between(val, min, max)`
|
|
202
|
+
- **Date & Time**: `year(d)`, `month(d)`, `day(d)`, `hour(d)`, `minute(d)`, `dayOfWeek(d)`, `dateDiff(d1, d2, unit)`, `isPast(d)`, `isFuture(d)`, `isToday(d)`
|
|
203
|
+
- **String & Text**: `contains(s, target)`, `startsWith(s, prefix)`, `endsWith(s, suffix)`, `lower(s)`, `upper(s)`, `trim(s)`, `length(s)`, `concat(...)`, `substr(s, start, len)`, `replace(s, search, rep)`, `splitIndex(s, delim, idx)`, `indexOf(s, search)`, `padLeft(s, len, char)`, `padRight(s, len, char)`, `matches(s, regex)`
|
|
204
|
+
- **Math & Numeric**: `abs(n)`, `round(n, dec)`, `ceil(n)`, `floor(n)`, `clamp(n, min, max)`, `min(...)`, `max(...)`, `sqrt(n)`, `pow(b, e)`, `log(n)`
|
|
205
|
+
- **Control Flow & Nulls**: `if(cond, then, else)`, `isNull(v)`, `isNotNull(v)`, `coalesce(...)`, `nullIf(a, b)`, `nvl(v, default)`
|
|
206
|
+
- **Type Inspection & Casting**: `isNumber(v)`, `isEmail(v)`, `isUrl(v)`, `isUuid(v)`, `isDate(v)`, `toInt(v)`, `toFloat(v)`, `toString(v)`, `toBool(v)`
|
|
207
|
+
- **JSON Navigation**: Direct dot-notation (`payload.user.city`) or fallback `jsonGet(v, "path.to.field")`
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
### 6. Map & Derive Columns (Row-Level Computations)
|
|
211
|
+
Compute and derive new columns per row in stream using expressions, arithmetic, strings, dates, or JSON dot-notation:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Arithmetic & percentage formulas
|
|
215
|
+
rowpipe map sales.csv "profit=revenue - cost" "tax=revenue * 0.20" "margin=((revenue - cost) / revenue) * 100"
|
|
216
|
+
|
|
217
|
+
# String operations, dates, and dot-notation JSON mapping
|
|
218
|
+
rowpipe map events.csv \
|
|
219
|
+
"user_city=payload.user.address.city" \
|
|
220
|
+
"clean_email=email | lower | trim" \
|
|
221
|
+
"year=created_at | year"
|
|
222
|
+
|
|
223
|
+
# Conditional mapping (if/else, clamp, round)
|
|
224
|
+
rowpipe map orders.csv "net_total=total | if(is_vip, total * 0.90, total) | round(2)"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 7. Reduce & Aggregate (Global & Group-By)
|
|
228
|
+
Aggregate streams into summary metrics with bounded memory ($O(1)$) globally or grouped by columns (`--by`):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# 1. Global Aggregation (Single summary row)
|
|
232
|
+
rowpipe reduce sales.csv \
|
|
233
|
+
"total_revenue=sum(revenue)" \
|
|
234
|
+
"avg_margin=avg(margin)" \
|
|
235
|
+
"min_price=min(price)" \
|
|
236
|
+
"max_price=max(price)" \
|
|
237
|
+
"total_orders=count()" \
|
|
238
|
+
"unique_users=countDistinct(user_id)" \
|
|
239
|
+
"rev_stddev=stddev(revenue)"
|
|
240
|
+
|
|
241
|
+
# 2. Group-By Streaming Aggregation
|
|
242
|
+
rowpipe reduce sales.csv \
|
|
243
|
+
"total_sales=sum(revenue)" \
|
|
244
|
+
"avg_profit=avg(profit)" \
|
|
245
|
+
"order_count=count()" \
|
|
246
|
+
--by country,category
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Supported Reduce Functions:
|
|
250
|
+
- `sum(col)` — Numeric sum
|
|
251
|
+
- `avg(col)` / `mean(col)` — Average
|
|
252
|
+
- `min(col)` / `max(col)` — Minimum / Maximum
|
|
253
|
+
- `count()` / `count(col)` — Row or non-null count
|
|
254
|
+
- `countDistinct(col)` — HyperLogLog distinct cardinality ($O(1)$ memory)
|
|
255
|
+
- `stddev(col)` / `variance(col)` — Welford online single-pass standard deviation / variance
|
|
256
|
+
- `first(col)` / `last(col)` — First non-null / last value
|
|
257
|
+
|
|
258
|
+
### 8. Select Columns
|
|
259
|
+
Project specific columns in order:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
rowpipe select users.csv id,name,email
|
|
263
|
+
|
|
264
|
+
# Select columns from Excel sheet
|
|
265
|
+
rowpipe select workbook.xlsx id,name,email --sheet Users
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 9. Rename Columns
|
|
269
|
+
Rename columns in stream:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
rowpipe rename users.csv username=name signup_date=created_at
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 10. Cast Column Types
|
|
276
|
+
Convert types in stream with configurable error handling:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
rowpipe cast users.csv age:integer revenue:number active:boolean --on-error null
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Options for `--on-error`:
|
|
283
|
+
- `null` (default): Sets invalid values to `null`
|
|
284
|
+
- `fail`: Exits immediately with error context
|
|
285
|
+
- `keep`: Retains original uncast value
|
|
286
|
+
- `skip-row`: Omit the row from output
|
|
287
|
+
|
|
288
|
+
### 11. Reservoir Sample
|
|
289
|
+
Sample a fixed number of rows with bounded memory ($O(k)$) and optional deterministic seed:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
rowpipe sample huge.csv --rows 1000 --seed 42
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 12. Schema Validation
|
|
296
|
+
Validate datasets against a JSON schema definition:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
rowpipe validate users.csv --schema users.schema.json
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 13. Unix Pipes & End-to-End Map-Reduce Pipeline
|
|
303
|
+
Chain operations with standard Unix pipes for full stream processing:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
cat transactions.csv |
|
|
307
|
+
rowpipe map - "profit=revenue - cost" "margin=((revenue - cost) / revenue) * 100" |
|
|
308
|
+
rowpipe filter - "margin >= 15" |
|
|
309
|
+
rowpipe reduce - "total_profit=sum(profit)" "avg_margin=avg(margin)" "count=count()" --by country |
|
|
310
|
+
rowpipe convert - --to jsonl > profitable_countries.jsonl
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Node.js Library API
|
|
316
|
+
|
|
317
|
+
Rowpipe can be imported directly in your Node.js projects:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import {
|
|
321
|
+
createPipeline,
|
|
322
|
+
CSVReader,
|
|
323
|
+
JSONLWriter,
|
|
324
|
+
XLSXReader,
|
|
325
|
+
filterRows,
|
|
326
|
+
selectColumns,
|
|
327
|
+
castColumns,
|
|
328
|
+
mapRows,
|
|
329
|
+
reduceRows,
|
|
330
|
+
DatasetStatsAggregator,
|
|
331
|
+
} from "rowpipe";
|
|
332
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
333
|
+
|
|
334
|
+
// 1. Streaming Map -> Filter -> Reduce Pipeline
|
|
335
|
+
const reader = new CSVReader(createReadStream("sales.csv"));
|
|
336
|
+
const writer = new JSONLWriter(createWriteStream("summary.jsonl"));
|
|
337
|
+
|
|
338
|
+
await createPipeline(reader)
|
|
339
|
+
.pipe(mapRows({
|
|
340
|
+
profit: "revenue - cost",
|
|
341
|
+
margin: "((revenue - cost) / revenue) * 100",
|
|
342
|
+
}))
|
|
343
|
+
.pipe(filterRows("margin >= 15"))
|
|
344
|
+
.pipe(reduceRows({
|
|
345
|
+
by: ["country"],
|
|
346
|
+
aggregations: [
|
|
347
|
+
"total_profit = sum(profit)",
|
|
348
|
+
"avg_margin = avg(margin)",
|
|
349
|
+
"count = count()",
|
|
350
|
+
],
|
|
351
|
+
}))
|
|
352
|
+
.to(writer);
|
|
353
|
+
|
|
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);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Exit Codes
|
|
363
|
+
|
|
364
|
+
Rowpipe uses standardized POSIX exit codes:
|
|
365
|
+
|
|
366
|
+
| Code | Meaning | Description |
|
|
367
|
+
|------|---------|-------------|
|
|
368
|
+
| `0` | Success | Command executed successfully |
|
|
369
|
+
| `1` | Generic Failure | Unexpected runtime or internal error |
|
|
370
|
+
| `2` | Invalid Arguments | Missing or invalid CLI flags or arguments |
|
|
371
|
+
| `3` | Parse Error | Corrupted or malformed CSV/JSON/XLSX input with row/column context |
|
|
372
|
+
| `4` | Validation Failure | Schema validation violations detected |
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Memory & Performance Benchmarks
|
|
377
|
+
|
|
378
|
+
Tested on 1,000,000 synthetic rows through a complete pipeline (`Filter` -> `Select` -> `Cast` -> `Online Stats`):
|
|
379
|
+
|
|
380
|
+
| Dataset Size | Throughput | Execution Time | Peak RSS Memory | Memory Scaling |
|
|
381
|
+
|--------------|------------|----------------|-----------------|----------------|
|
|
382
|
+
| **100,000 rows** | **~318,000 rows/s** | 0.31s | ~106 MB | $O(1)$ Bounded |
|
|
383
|
+
| **1,000,000 rows** | **~500,000 rows/s** | 2.00s | ~153 MB | $O(1)$ Bounded |
|
|
384
|
+
|
|
385
|
+
To run the benchmark locally:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
npm run bench
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
To enable real-time memory debugging on any command:
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
ROWPIPE_DEBUG_MEMORY=1 rowpipe stats huge.csv
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT
|
|
@@ -0,0 +1,109 @@
|
|
|
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);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Aggregator, Row, TransformFunction } from "../core/types.js";
|
|
2
|
+
export type AggregationFunction = "sum" | "avg" | "mean" | "min" | "max" | "count" | "countdistinct" | "stddev" | "variance" | "first" | "last";
|
|
3
|
+
export interface AggregationSpec {
|
|
4
|
+
targetField: string;
|
|
5
|
+
func: AggregationFunction;
|
|
6
|
+
sourceExpr?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parses CLI reduce specs into structured AggregationSpec objects.
|
|
10
|
+
* Supports syntax: "total_rev = sum(revenue)", "avg_margin = avg(margin)", "orders = count()"
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseReduceSpecs(specs: string[]): AggregationSpec[];
|
|
13
|
+
export interface ReduceOptions {
|
|
14
|
+
by?: string[];
|
|
15
|
+
aggregations: AggregationSpec[] | string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Streaming Reduce & Group-by Aggregator.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ReduceAggregator implements Aggregator<Row[]> {
|
|
21
|
+
private byCols;
|
|
22
|
+
private compiled;
|
|
23
|
+
private globalAccs?;
|
|
24
|
+
private groupMap?;
|
|
25
|
+
constructor(options: ReduceOptions);
|
|
26
|
+
add(row: Row): void;
|
|
27
|
+
result(): Row[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates a high-performance streaming transform that reduces/aggregates rows.
|
|
31
|
+
*/
|
|
32
|
+
export declare function reduceRows(options: ReduceOptions): TransformFunction;
|
|
33
|
+
//# sourceMappingURL=reduce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reduce.d.ts","sourceRoot":"","sources":["../../src/analytics/reduce.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAyB,GAAG,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIlG,MAAM,MAAM,mBAAmB,GAC3B,KAAK,GACL,KAAK,GACL,MAAM,GACN,KAAK,GACL,KAAK,GACL,OAAO,GACP,eAAe,GACf,QAAQ,GACR,UAAU,GACV,OAAO,GACP,MAAM,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,mBAAmB,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAuDnE;AAuLD,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,YAAY,EAAE,eAAe,EAAE,GAAG,MAAM,EAAE,CAAC;CAC5C;AAED;;GAEG;AACH,qBAAa,gBAAiB,YAAW,UAAU,CAAC,GAAG,EAAE,CAAC;IACxD,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAA6E;gBAElF,OAAO,EAAE,aAAa;IAsBlC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI;IAqCnB,MAAM,IAAI,GAAG,EAAE;CAyBhB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,iBAAiB,CAkBpE"}
|