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
@@ -1,223 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { rowsToBatches } from "../src/core/batch.js";
3
- import type { DataBatch, Row } from "../src/core/types.js";
4
- import { mapRows, parseMapSpecs } from "../src/transforms/map.js";
5
- import { parseReduceSpecs, reduceRows, ReduceAggregator } from "../src/analytics/reduce.js";
6
- import { filterRows } from "../src/transforms/filter.js";
7
-
8
- async function collectRows(stream: AsyncIterable<DataBatch>): Promise<Row[]> {
9
- const results: Row[] = [];
10
- for await (const batch of stream) {
11
- results.push(...batch.rows);
12
- }
13
- return results;
14
- }
15
-
16
- describe("Map Transform (Row-level calculations & derivations)", () => {
17
- it("should parse map specifications correctly", () => {
18
- const specs = [
19
- "profit = revenue - cost",
20
- "tax=revenue * 0.20",
21
- "user_city = payload.user.city",
22
- ];
23
- const parsed = parseMapSpecs(specs);
24
- expect(parsed).toEqual({
25
- profit: "revenue - cost",
26
- tax: "revenue * 0.20",
27
- user_city: "payload.user.city",
28
- });
29
- });
30
-
31
- it("should compute mathematical and percentage formulas per row", async () => {
32
- const rows: Row[] = [
33
- { id: 1, revenue: 1000, cost: 600 },
34
- { id: 2, revenue: 2500, cost: 1500 },
35
- ];
36
-
37
- const stream = mapRows({
38
- profit: "revenue - cost",
39
- tax: "revenue * 0.20",
40
- margin: "((revenue - cost) / revenue) * 100",
41
- })(rowsToBatches(rows));
42
-
43
- const results = await collectRows(stream);
44
- expect(results).toEqual([
45
- { id: 1, revenue: 1000, cost: 600, profit: 400, tax: 200, margin: 40 },
46
- { id: 2, revenue: 2500, cost: 1500, profit: 1000, tax: 500, margin: 40 },
47
- ]);
48
- });
49
-
50
- it("should support JSON dot-notation, pipe syntax, and date extraction in map", async () => {
51
- const rows: Row[] = [
52
- {
53
- id: 1,
54
- created_at: "2026-09-16",
55
- email: " ADMIN@ROWPIPE.IO ",
56
- payload: JSON.stringify({ user: { city: "Istanbul" }, items: [{ price: 99 }] }),
57
- },
58
- ];
59
-
60
- const stream = mapRows({
61
- city: "payload.user.city",
62
- first_item_price: "payload.items.0.price",
63
- clean_email: "email | lower | trim",
64
- year: "created_at | year",
65
- })(rowsToBatches(rows));
66
-
67
- const results = await collectRows(stream);
68
- expect(results[0]?.city).toBe("Istanbul");
69
- expect(results[0]?.first_item_price).toBe(99);
70
- expect(results[0]?.clean_email).toBe("admin@rowpipe.io");
71
- expect(results[0]?.year).toBe(2026);
72
- });
73
-
74
- it("should support custom function mapper in mapRows", async () => {
75
- const rows: Row[] = [{ a: 1 }, { a: 2 }];
76
- const stream = mapRows((row) => ({ ...row, doubled: (Number(row.a) || 0) * 2 }))(rowsToBatches(rows));
77
- const results = await collectRows(stream);
78
- expect(results).toEqual([
79
- { a: 1, doubled: 2 },
80
- { a: 2, doubled: 4 },
81
- ]);
82
- });
83
- });
84
-
85
- describe("Reduce Transform (Global and Group-By Aggregations)", () => {
86
- it("should parse reduce specifications correctly", () => {
87
- const specs = [
88
- "total_rev = sum(revenue)",
89
- "avg_margin = avg(margin)",
90
- "total_orders = count()",
91
- "unique_users = countDistinct(user_id)",
92
- "min_val = min(price)",
93
- "max_val = max(price)",
94
- ];
95
- const parsed = parseReduceSpecs(specs);
96
- expect(parsed).toEqual([
97
- { targetField: "total_rev", func: "sum", sourceExpr: "revenue" },
98
- { targetField: "avg_margin", func: "avg", sourceExpr: "margin" },
99
- { targetField: "total_orders", func: "count", sourceExpr: undefined },
100
- { targetField: "unique_users", func: "countdistinct", sourceExpr: "user_id" },
101
- { targetField: "min_val", func: "min", sourceExpr: "price" },
102
- { targetField: "max_val", func: "max", sourceExpr: "price" },
103
- ]);
104
- });
105
-
106
- it("should compute global aggregations across stream", async () => {
107
- const rows: Row[] = [
108
- { id: 1, user_id: "u1", revenue: 100, price: 50 },
109
- { id: 2, user_id: "u2", revenue: 200, price: 100 },
110
- { id: 3, user_id: "u1", revenue: 300, price: 150 },
111
- { id: 4, user_id: "u3", revenue: 400, price: 200 },
112
- ];
113
-
114
- const stream = reduceRows({
115
- aggregations: [
116
- "total_revenue = sum(revenue)",
117
- "avg_price = avg(price)",
118
- "min_price = min(price)",
119
- "max_price = max(price)",
120
- "order_count = count()",
121
- "unique_users = countDistinct(user_id)",
122
- ],
123
- })(rowsToBatches(rows));
124
-
125
- const results = await collectRows(stream);
126
- expect(results.length).toBe(1);
127
- expect(results[0]).toEqual({
128
- total_revenue: 1000,
129
- avg_price: 125,
130
- min_price: 50,
131
- max_price: 200,
132
- order_count: 4,
133
- unique_users: 3,
134
- });
135
- });
136
-
137
- it("should perform streaming group-by aggregations (--by country,category)", async () => {
138
- const rows: Row[] = [
139
- { country: "TR", category: "Electronics", revenue: 1000, cost: 600 },
140
- { country: "TR", category: "Electronics", revenue: 2000, cost: 1200 },
141
- { country: "TR", category: "Books", revenue: 300, cost: 100 },
142
- { country: "US", category: "Electronics", revenue: 5000, cost: 3000 },
143
- { country: "US", category: "Books", revenue: 800, cost: 400 },
144
- ];
145
-
146
- const stream = reduceRows({
147
- by: ["country", "category"],
148
- aggregations: [
149
- "total_revenue = sum(revenue)",
150
- "total_cost = sum(cost)",
151
- "item_count = count()",
152
- ],
153
- })(rowsToBatches(rows));
154
-
155
- const results = await collectRows(stream);
156
- expect(results.length).toBe(4);
157
-
158
- const trElectronics = results.find((r) => r.country === "TR" && r.category === "Electronics");
159
- expect(trElectronics).toEqual({
160
- country: "TR",
161
- category: "Electronics",
162
- total_revenue: 3000,
163
- total_cost: 1800,
164
- item_count: 2,
165
- });
166
-
167
- const usBooks = results.find((r) => r.country === "US" && r.category === "Books");
168
- expect(usBooks).toEqual({
169
- country: "US",
170
- category: "Books",
171
- total_revenue: 800,
172
- total_cost: 400,
173
- item_count: 1,
174
- });
175
- });
176
-
177
- it("should execute full Map -> Filter -> Reduce pipeline smoothly", async () => {
178
- const rows: Row[] = [
179
- { id: 1, country: "TR", revenue: 1000, cost: 600 },
180
- { id: 2, country: "TR", revenue: 500, cost: 450 }, // margin 10% (will be filtered out)
181
- { id: 3, country: "US", revenue: 3000, cost: 1500 }, // margin 50%
182
- { id: 4, country: "US", revenue: 2000, cost: 1600 }, // margin 20%
183
- ];
184
-
185
- // 1. Map: derive profit and margin
186
- const mapped = mapRows({
187
- profit: "revenue - cost",
188
- margin: "((revenue - cost) / revenue) * 100",
189
- })(rowsToBatches(rows));
190
-
191
- // 2. Filter: only margin >= 20
192
- const filtered = filterRows("margin >= 20")(mapped);
193
-
194
- // 3. Reduce: group by country
195
- const reduced = reduceRows({
196
- by: ["country"],
197
- aggregations: [
198
- "total_profit = sum(profit)",
199
- "avg_margin = avg(margin)",
200
- "count = count()",
201
- ],
202
- })(filtered);
203
-
204
- const results = await collectRows(reduced);
205
- expect(results.length).toBe(2);
206
-
207
- const tr = results.find((r) => r.country === "TR");
208
- expect(tr).toEqual({
209
- country: "TR",
210
- total_profit: 400,
211
- avg_margin: 40,
212
- count: 1,
213
- });
214
-
215
- const us = results.find((r) => r.country === "US");
216
- expect(us).toEqual({
217
- country: "US",
218
- total_profit: 1900,
219
- avg_margin: 35,
220
- count: 2,
221
- });
222
- });
223
- });
@@ -1,189 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { Readable, Writable } from "node:stream";
3
- import { CSVReader } from "../src/readers/csv.js";
4
- import { CSVWriter } from "../src/writers/csv.js";
5
- import { JSONLReader } from "../src/readers/jsonl.js";
6
- import { JSONLWriter } from "../src/writers/jsonl.js";
7
- import { JSONReader } from "../src/readers/json.js";
8
- import { JSONWriter } from "../src/writers/json.js";
9
- import { XLSXReader } from "../src/readers/xlsx.js";
10
- import { XLSXWriter } from "../src/writers/xlsx.js";
11
- import { batchesToRows, rowsToBatches } from "../src/core/batch.js";
12
- import type { DataBatch, Row } from "../src/core/types.js";
13
- import { promises as fs } from "node:fs";
14
- import { join } from "node:path";
15
- import { tmpdir } from "node:os";
16
-
17
- async function collectRows(stream: AsyncIterable<DataBatch>): Promise<Row[]> {
18
- const results: Row[] = [];
19
- for await (const batch of stream) {
20
- results.push(...batch.rows);
21
- }
22
- return results;
23
- }
24
-
25
- describe("CSV Reader & Writer", () => {
26
- it("should read standard CSV with headers", async () => {
27
- const csvData = `id,name,age\n1,Alice,30\n2,Bob,25\n`;
28
- const reader = new CSVReader(Readable.from(csvData));
29
- const rows = await collectRows(reader.read());
30
-
31
- expect(rows).toEqual([
32
- { id: "1", name: "Alice", age: "30" },
33
- { id: "2", name: "Bob", age: "25" },
34
- ]);
35
- });
36
-
37
- it("should handle custom delimiter and quotes with commas and newlines", async () => {
38
- const csvData = `"id";"name";"notes"\n"1";"Smith, John";"Line 1\nLine 2"\n"2";"Jane";"Simple"\n`;
39
- const reader = new CSVReader(Readable.from(csvData), { delimiter: ";" });
40
- const rows = await collectRows(reader.read());
41
-
42
- expect(rows).toEqual([
43
- { id: "1", name: "Smith, John", notes: "Line 1\nLine 2" },
44
- { id: "2", name: "Jane", notes: "Simple" },
45
- ]);
46
- });
47
-
48
- it("should auto-detect delimiter when not specified", async () => {
49
- const tsvData = `id\tname\tage\n1\tAlice\t30\n2\tBob\t25\n`;
50
- const reader = new CSVReader(Readable.from(tsvData));
51
- const rows = await collectRows(reader.read());
52
-
53
- expect(rows).toEqual([
54
- { id: "1", name: "Alice", age: "30" },
55
- { id: "2", name: "Bob", age: "25" },
56
- ]);
57
- });
58
-
59
- it("should handle UTF-8 BOM", async () => {
60
- const bomCsv = `\uFEFFid,name\n1,Alice\n`;
61
- const reader = new CSVReader(Readable.from(bomCsv));
62
- const rows = await collectRows(reader.read());
63
-
64
- expect(rows).toEqual([{ id: "1", name: "Alice" }]);
65
- });
66
-
67
- it("should write CSV with proper escaping", async () => {
68
- const rows: Row[] = [
69
- { id: 1, name: "Alice, Bob", notes: 'He said "Hello"' },
70
- { id: 2, name: "Charlie", notes: "Line1\nLine2" },
71
- ];
72
-
73
- let output = "";
74
- const writable = new Writable({
75
- write(chunk, _encoding, callback) {
76
- output += chunk.toString();
77
- callback();
78
- },
79
- });
80
-
81
- const writer = new CSVWriter(writable);
82
- await writer.write(rowsToBatches(rows));
83
-
84
- expect(output).toContain('"Alice, Bob"');
85
- expect(output).toContain('"He said ""Hello"""');
86
- expect(output).toContain('"Line1\nLine2"');
87
- });
88
- });
89
-
90
- describe("JSONL Reader & Writer", () => {
91
- it("should stream read and write JSONL", async () => {
92
- const jsonlData = `{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}\n`;
93
- const reader = new JSONLReader(Readable.from(jsonlData));
94
- const rows = await collectRows(reader.read());
95
-
96
- expect(rows).toEqual([
97
- { id: 1, name: "Alice" },
98
- { id: 2, name: "Bob" },
99
- ]);
100
-
101
- let output = "";
102
- const writable = new Writable({
103
- write(chunk, _encoding, callback) {
104
- output += chunk.toString();
105
- callback();
106
- },
107
- });
108
-
109
- const writer = new JSONLWriter(writable);
110
- await writer.write(rowsToBatches(rows));
111
-
112
- expect(output).toBe(`{"id":1,"name":"Alice"}\n{"id":2,"name":"Bob"}\n`);
113
- });
114
- });
115
-
116
- describe("JSON Reader & Writer", () => {
117
- it("should streaming parse top-level JSON array", async () => {
118
- const jsonData = `[\n {"id": 1, "name": "Alice"},\n {"id": 2, "name": "Bob"}\n]`;
119
- const reader = new JSONReader(Readable.from(jsonData));
120
- const rows = await collectRows(reader.read());
121
-
122
- expect(rows).toEqual([
123
- { id: 1, name: "Alice" },
124
- { id: 2, name: "Bob" },
125
- ]);
126
- });
127
-
128
- it("should streaming parse nested path in JSON", async () => {
129
- const jsonData = `{\n "status": "ok",\n "data": {\n "results": [\n {"id": 1, "title": "First"},\n {"id": 2, "title": "Second"}\n ]\n }\n}`;
130
- const reader = new JSONReader(Readable.from(jsonData), {
131
- path: "data.results",
132
- });
133
- const rows = await collectRows(reader.read());
134
-
135
- expect(rows).toEqual([
136
- { id: 1, title: "First" },
137
- { id: 2, title: "Second" },
138
- ]);
139
- });
140
-
141
- it("should write JSON array", async () => {
142
- const rows: Row[] = [
143
- { id: 1, name: "Alice" },
144
- { id: 2, name: "Bob" },
145
- ];
146
-
147
- let output = "";
148
- const writable = new Writable({
149
- write(chunk, _encoding, callback) {
150
- output += chunk.toString();
151
- callback();
152
- },
153
- });
154
-
155
- const writer = new JSONWriter(writable);
156
- await writer.write(rowsToBatches(rows));
157
-
158
- const parsed = JSON.parse(output);
159
- expect(parsed).toEqual(rows);
160
- });
161
- });
162
-
163
- describe("XLSX Reader & Writer", () => {
164
- it("should write and read back XLSX workbook", async () => {
165
- const tempFile = join(tmpdir(), `rowpipe_test_${Date.now()}.xlsx`);
166
- const initialRows: Row[] = [
167
- { id: 1, name: "Alice", score: 95.5 },
168
- { id: 2, name: "Bob", score: 82.0 },
169
- ];
170
-
171
- const writer = new XLSXWriter(tempFile, { sheet: "Students" });
172
- await writer.write(rowsToBatches(initialRows));
173
- await writer.close();
174
-
175
- const reader = new XLSXReader(tempFile, { sheet: "Students" });
176
- const rows = await collectRows(reader.read());
177
-
178
- expect(rows.length).toBe(2);
179
- expect(rows[0]!["name"]).toBe("Alice");
180
- expect(rows[1]!["name"]).toBe("Bob");
181
-
182
- // Inspect sheets
183
- const meta = await reader.inspect();
184
- expect(meta.sheets?.length).toBeGreaterThanOrEqual(1);
185
- expect(meta.sheets?.[0]?.name).toBe("Students");
186
-
187
- await fs.unlink(tempFile).catch(() => {});
188
- });
189
- });
@@ -1,199 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { selectColumns } from "../src/transforms/select.js";
3
- import { renameColumns, parseRenameSpecs } from "../src/transforms/rename.js";
4
- import { castColumns, parseCastSpecs } from "../src/transforms/cast.js";
5
- import { sampleRows } from "../src/transforms/sample.js";
6
- import { filterRows } from "../src/transforms/filter.js";
7
- import { compileExpression } from "../src/transforms/expression.js";
8
- import { rowsToBatches } from "../src/core/batch.js";
9
- import type { DataBatch, Row } from "../src/core/types.js";
10
-
11
- async function collectRows(stream: AsyncIterable<DataBatch>): Promise<Row[]> {
12
- const results: Row[] = [];
13
- for await (const batch of stream) {
14
- results.push(...batch.rows);
15
- }
16
- return results;
17
- }
18
-
19
- describe("Select Transform", () => {
20
- it("should project selected columns", async () => {
21
- const rows: Row[] = [
22
- { id: 1, name: "Alice", email: "alice@example.com", age: 30 },
23
- { id: 2, name: "Bob", email: "bob@example.com", age: 25 },
24
- ];
25
-
26
- const stream = selectColumns(["id", "email"])(rowsToBatches(rows));
27
- const results = await collectRows(stream);
28
-
29
- expect(results).toEqual([
30
- { id: 1, email: "alice@example.com" },
31
- { id: 2, email: "bob@example.com" },
32
- ]);
33
- });
34
- });
35
-
36
- describe("Rename Transform", () => {
37
- it("should rename columns and parse rename specs", async () => {
38
- const specs = ["username=name", "user_age=age"];
39
- const mapping = parseRenameSpecs(specs);
40
- expect(mapping).toEqual({ username: "name", user_age: "age" });
41
-
42
- const rows: Row[] = [
43
- { username: "alice", user_age: 30, country: "TR" },
44
- ];
45
-
46
- const stream = renameColumns(mapping)(rowsToBatches(rows));
47
- const results = await collectRows(stream);
48
-
49
- expect(results).toEqual([
50
- { name: "alice", age: 30, country: "TR" },
51
- ]);
52
- });
53
- });
54
-
55
- describe("Cast Transform", () => {
56
- it("should cast columns according to specs", async () => {
57
- const specs = parseCastSpecs(["age:integer", "revenue:number", "active:boolean"]);
58
- expect(specs).toEqual({
59
- age: "integer",
60
- revenue: "number",
61
- active: "boolean",
62
- });
63
-
64
- const rows: Row[] = [
65
- { id: "1", age: "32", revenue: "1250.50", active: "true" },
66
- { id: "2", age: "invalid", revenue: "200", active: "0" },
67
- ];
68
-
69
- const stream = castColumns(specs, { onError: "null" })(rowsToBatches(rows));
70
- const results = await collectRows(stream);
71
-
72
- expect(results).toEqual([
73
- { id: "1", age: 32, revenue: 1250.5, active: true },
74
- { id: "2", age: null, revenue: 200, active: false },
75
- ]);
76
- });
77
-
78
- it("should support skip-row error handling", async () => {
79
- const specs = { age: "integer" };
80
- const rows: Row[] = [
81
- { id: "1", age: "30" },
82
- { id: "2", age: "invalid" },
83
- { id: "3", age: "40" },
84
- ];
85
-
86
- const stream = castColumns(specs, { onError: "skip-row" })(rowsToBatches(rows));
87
- const results = await collectRows(stream);
88
-
89
- expect(results).toEqual([
90
- { id: "1", age: 30 },
91
- { id: "3", age: 40 },
92
- ]);
93
- });
94
- });
95
-
96
- describe("Sample Transform", () => {
97
- it("should sample k rows deterministically with seed", async () => {
98
- const rows: Row[] = [];
99
- for (let i = 0; i < 1000; i++) {
100
- rows.push({ id: i, val: `row_${i}` });
101
- }
102
-
103
- const stream1 = sampleRows({ rows: 10, seed: 42 })(rowsToBatches(rows));
104
- const sample1 = await collectRows(stream1);
105
-
106
- const stream2 = sampleRows({ rows: 10, seed: 42 })(rowsToBatches(rows));
107
- const sample2 = await collectRows(stream2);
108
-
109
- expect(sample1.length).toBe(10);
110
- expect(sample1).toEqual(sample2);
111
- });
112
- });
113
-
114
- describe("Filter Transform & Expression Parser", () => {
115
- it("should evaluate comparisons and logical operators", () => {
116
- const fn1 = compileExpression('age > 30 && country == "TR"');
117
- expect(fn1({ age: 35, country: "TR" })).toBe(true);
118
- expect(fn1({ age: 25, country: "TR" })).toBe(false);
119
- expect(fn1({ age: 35, country: "US" })).toBe(false);
120
-
121
- const fn2 = compileExpression('revenue >= 1000 || is_vip == true');
122
- expect(fn2({ revenue: 500, is_vip: true })).toBe(true);
123
- expect(fn2({ revenue: 1200, is_vip: false })).toBe(true);
124
- expect(fn2({ revenue: 800, is_vip: false })).toBe(false);
125
- });
126
-
127
- it("should evaluate standard and pipe-syntax functions", () => {
128
- // 1. Pipe syntax for string transforms: email | lower | trim | endsWith(".com")
129
- const fn1 = compileExpression('email | lower | trim | endsWith(".com")');
130
- expect(fn1({ email: " ADMIN@CORP.COM " })).toBe(true);
131
- expect(fn1({ email: "user@corp.org" })).toBe(false);
132
-
133
- // 2. Date & Time functions with pipe: created_at | year == 2026
134
- const fn2 = compileExpression('created_at | year == 2026 && created_at | month >= 6');
135
- expect(fn2({ created_at: "2026-09-16" })).toBe(true);
136
- expect(fn2({ created_at: "2025-09-16" })).toBe(false);
137
-
138
- // 3. Set & Range functions: country | in("TR", "US", "DE") and age | between(18, 65)
139
- const fn3 = compileExpression('country | in("TR", "US", "DE") && age | between(18, 65)');
140
- expect(fn3({ country: "TR", age: 30 })).toBe(true);
141
- expect(fn3({ country: "FR", age: 30 })).toBe(false);
142
- expect(fn3({ country: "US", age: 70 })).toBe(false);
143
-
144
- // 4. String splitIndex and substring with pipe: email | splitIndex("@", 1) == "gmail.com"
145
- const fn4 = compileExpression('email | splitIndex("@", 1) == "gmail.com"');
146
- expect(fn4({ email: "john.doe@gmail.com" })).toBe(true);
147
- expect(fn4({ email: "john.doe@yahoo.com" })).toBe(false);
148
-
149
- // 5. Type inspection: email | isEmail
150
- const fn5 = compileExpression('email | isEmail');
151
- expect(fn5({ email: "test@example.com" })).toBe(true);
152
- expect(fn5({ email: "not-an-email" })).toBe(false);
153
-
154
- // 6. JSON nested access: direct dot-notation & jsonGet fallback
155
- const fn6 = compileExpression('payload.user.city == "Istanbul"');
156
- expect(fn6({ payload: JSON.stringify({ user: { city: "Istanbul" } }) })).toBe(true);
157
- expect(fn6({ payload: JSON.stringify({ user: { city: "Ankara" } }) })).toBe(false);
158
-
159
- // Direct nested object access with array index and pipe
160
- const fn6b = compileExpression('payload.items.0.price > 50 && payload.user.email | lower | endsWith("@corp.com")');
161
- expect(
162
- fn6b({
163
- payload: {
164
- items: [{ price: 99.9 }],
165
- user: { email: "AHMET@CORP.COM" },
166
- },
167
- })
168
- ).toBe(true);
169
- expect(
170
- fn6b({
171
- payload: {
172
- items: [{ price: 20 }],
173
- user: { email: "AHMET@CORP.COM" },
174
- },
175
- })
176
- ).toBe(false);
177
-
178
- // 7. Math functions: price | clamp(10, 100)
179
- const fn7 = compileExpression('price | clamp(10, 100) == 50');
180
- expect(fn7({ price: 50 })).toBe(true);
181
- expect(fn7({ price: 200 })).toBe(false);
182
- });
183
-
184
- it("should filter stream using filterRows", async () => {
185
- const rows: Row[] = [
186
- { id: 1, age: 35, email: "alice@test.com" },
187
- { id: 2, age: 20, email: "bob@test.org" },
188
- { id: 3, age: 45, email: "charlie@test.com" },
189
- ];
190
-
191
- const stream = filterRows('age | between(30, 50) && email | endsWith(".com")')(rowsToBatches(rows));
192
- const results = await collectRows(stream);
193
-
194
- expect(results).toEqual([
195
- { id: 1, age: 35, email: "alice@test.com" },
196
- { id: 3, age: 45, email: "charlie@test.com" },
197
- ]);
198
- });
199
- });
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022"],
7
- "types": ["node"],
8
- "outDir": "./dist",
9
- "rootDir": "./src",
10
- "declaration": true,
11
- "declarationMap": true,
12
- "sourceMap": true,
13
- "strict": true,
14
- "esModuleInterop": true,
15
- "skipLibCheck": true,
16
- "forceConsistentCasingInFileNames": true,
17
- "noUncheckedIndexedAccess": true
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "tests", "benchmarks"]
21
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- testTimeout: 30000,
7
- include: ["tests/**/*.test.ts"],
8
- },
9
- });