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,383 +0,0 @@
1
- import type { Aggregator, Row } from "../core/types.js";
2
-
3
- /**
4
- * Fast 32-bit FNV-1a hash function with 32-bit avalanche mixing.
5
- * Zero BigInt allocations, runs at native CPU register speed.
6
- */
7
- function hash32(str: string): number {
8
- let h = 0x811c9dc5;
9
- const len = str.length;
10
- for (let i = 0; i < len; i++) {
11
- h = (h ^ str.charCodeAt(i)) >>> 0;
12
- h = Math.imul(h, 0x01000193) >>> 0;
13
- }
14
- // Avalanche mixing
15
- h ^= h >>> 16;
16
- h = Math.imul(h, 0x85ebca6b) >>> 0;
17
- h ^= h >>> 13;
18
- h = Math.imul(h, 0xc2b2ae35) >>> 0;
19
- h ^= h >>> 16;
20
- return h >>> 0;
21
- }
22
-
23
- /**
24
- * HyperLogLog (HLL) distinct count estimator with bounded O(1) memory.
25
- * Uses m = 1024 registers (p = 10), standard error ~3.25%.
26
- * Hardware Math.clz32 for instantaneous zero counting.
27
- */
28
- export class HyperLogLog {
29
- private p: number;
30
- private m: number;
31
- private registers: Uint8Array;
32
- private alphaMM: number;
33
-
34
- constructor(p = 10) {
35
- this.p = p;
36
- this.m = 1 << p;
37
- this.registers = new Uint8Array(this.m);
38
-
39
- // Alpha constant calculation
40
- if (this.m === 16) this.alphaMM = 0.673 * this.m * this.m;
41
- else if (this.m === 32) this.alphaMM = 0.697 * this.m * this.m;
42
- else if (this.m === 64) this.alphaMM = 0.709 * this.m * this.m;
43
- else this.alphaMM = (0.7213 / (1 + 1.079 / this.m)) * this.m * this.m;
44
- }
45
-
46
- add(value: unknown): void {
47
- if (value === null || value === undefined) return;
48
- const str = String(value);
49
- const hash = hash32(str);
50
-
51
- // Low p bits for register index
52
- const index = hash & (this.m - 1);
53
- // Remaining (32 - p) bits for leading zeros
54
- const w = hash >>> this.p;
55
-
56
- // Single-cycle CPU instruction Math.clz32 for leading zero count
57
- let leadingZeros: number;
58
- if (w === 0) {
59
- leadingZeros = 32 - this.p + 1;
60
- } else {
61
- leadingZeros = Math.clz32(w) - this.p + 1;
62
- }
63
-
64
- if (leadingZeros > this.registers[index]!) {
65
- this.registers[index] = leadingZeros;
66
- }
67
- }
68
-
69
- merge(other: HyperLogLog): void {
70
- for (let i = 0; i < this.m; i++) {
71
- if (other.registers[i]! > this.registers[i]!) {
72
- this.registers[i] = other.registers[i]!;
73
- }
74
- }
75
- }
76
-
77
- count(): number {
78
- let sum = 0;
79
- let zeros = 0;
80
-
81
- for (let i = 0; i < this.m; i++) {
82
- const val = this.registers[i]!;
83
- sum += 2 ** -val;
84
- if (val === 0) zeros++;
85
- }
86
-
87
- let estimate = this.alphaMM / sum;
88
-
89
- // Small range correction (Linear Counting)
90
- if (estimate <= 2.5 * this.m && zeros > 0) {
91
- estimate = this.m * Math.log(this.m / zeros);
92
- }
93
-
94
- return Math.round(estimate);
95
- }
96
- }
97
-
98
- export interface NumericColumnStats {
99
- count: number;
100
- nullCount: number;
101
- min: number | null;
102
- max: number | null;
103
- sum: number;
104
- mean: number | null;
105
- variance: number | null;
106
- stddev: number | null;
107
- approxDistinct: number;
108
- }
109
-
110
- export interface StringColumnStats {
111
- count: number;
112
- nullCount: number;
113
- emptyCount: number;
114
- minLength: number | null;
115
- maxLength: number | null;
116
- avgLength: number | null;
117
- emptyCountRatio?: number;
118
- approxDistinct: number;
119
- topValues: Array<{ value: string; count: number }>;
120
- }
121
-
122
- export interface ColumnStatsResult {
123
- column: string;
124
- type: "numeric" | "string";
125
- numeric?: NumericColumnStats;
126
- string?: StringColumnStats;
127
- }
128
-
129
- export interface DatasetStatsResult {
130
- totalRows: number;
131
- columns: Record<string, ColumnStatsResult>;
132
- }
133
-
134
- /**
135
- * Online Welford statistics collector for a numeric column.
136
- */
137
- export class NumericStatsCollector {
138
- public count = 0;
139
- public nullCount = 0;
140
- public min: number | null = null;
141
- public max: number | null = null;
142
- public sum = 0;
143
- public mean = 0;
144
- public M2 = 0; // sum of squared differences from the mean
145
- public hll = new HyperLogLog();
146
-
147
- add(val: unknown): void {
148
- if (val === null || val === undefined || val === "") {
149
- this.nullCount++;
150
- return;
151
- }
152
-
153
- const num = typeof val === "number" ? val : Number(val);
154
- if (Number.isNaN(num)) {
155
- this.nullCount++;
156
- return;
157
- }
158
-
159
- this.count++;
160
- this.sum += num;
161
- this.hll.add(num);
162
-
163
- if (this.min === null || num < this.min) this.min = num;
164
- if (this.max === null || num > this.max) this.max = num;
165
-
166
- // Welford's online algorithm
167
- const delta = num - this.mean;
168
- this.mean += delta / this.count;
169
- const delta2 = num - this.mean;
170
- this.M2 += delta * delta2;
171
- }
172
-
173
- merge(other: NumericStatsCollector): void {
174
- if (other.count === 0) {
175
- this.nullCount += other.nullCount;
176
- return;
177
- }
178
- if (this.count === 0) {
179
- this.count = other.count;
180
- this.nullCount = other.nullCount;
181
- this.min = other.min;
182
- this.max = other.max;
183
- this.sum = other.sum;
184
- this.mean = other.mean;
185
- this.M2 = other.M2;
186
- this.hll.merge(other.hll);
187
- return;
188
- }
189
-
190
- const totalCount = this.count + other.count;
191
- const delta = other.mean - this.mean;
192
-
193
- this.mean = (this.count * this.mean + other.count * other.mean) / totalCount;
194
- this.M2 = this.M2 + other.M2 + (delta * delta * this.count * other.count) / totalCount;
195
- this.count = totalCount;
196
- this.nullCount += other.nullCount;
197
- this.sum += other.sum;
198
-
199
- if (this.min === null || (other.min !== null && other.min < this.min)) this.min = other.min;
200
- if (this.max === null || (other.max !== null && other.max > this.max)) this.max = other.max;
201
- this.hll.merge(other.hll);
202
- }
203
-
204
- result(): NumericColumnStats {
205
- const variance = this.count > 1 ? this.M2 / (this.count - 1) : this.count === 1 ? 0 : null;
206
- const stddev = variance !== null ? Math.sqrt(variance) : null;
207
-
208
- return {
209
- count: this.count,
210
- nullCount: this.nullCount,
211
- min: this.min,
212
- max: this.max,
213
- sum: this.sum,
214
- mean: this.count > 0 ? this.mean : null,
215
- variance,
216
- stddev,
217
- approxDistinct: this.hll.count(),
218
- };
219
- }
220
- }
221
-
222
- /**
223
- * Online statistics collector for a string column.
224
- */
225
- export class StringStatsCollector {
226
- public count = 0;
227
- public nullCount = 0;
228
- public emptyCount = 0;
229
- public minLength: number | null = null;
230
- public maxLength: number | null = null;
231
- public totalLength = 0;
232
- public hll = new HyperLogLog();
233
- private frequencyMap = new Map<string, number>();
234
- private maxTrackedValues = 1000;
235
-
236
- add(val: unknown): void {
237
- if (val === null || val === undefined) {
238
- this.nullCount++;
239
- return;
240
- }
241
-
242
- const str = String(val);
243
- if (str === "") {
244
- this.emptyCount++;
245
- }
246
-
247
- this.count++;
248
- this.totalLength += str.length;
249
- this.hll.add(str);
250
-
251
- if (this.minLength === null || str.length < this.minLength) this.minLength = str.length;
252
- if (this.maxLength === null || str.length > this.maxLength) this.maxLength = str.length;
253
-
254
- // Track top values bounded
255
- if (this.frequencyMap.size < this.maxTrackedValues || this.frequencyMap.has(str)) {
256
- this.frequencyMap.set(str, (this.frequencyMap.get(str) || 0) + 1);
257
- }
258
- }
259
-
260
- result(): StringColumnStats {
261
- const sorted = Array.from(this.frequencyMap.entries())
262
- .sort((a, b) => b[1] - a[1])
263
- .slice(0, 5)
264
- .map(([value, count]) => ({ value, count }));
265
-
266
- return {
267
- count: this.count,
268
- nullCount: this.nullCount,
269
- emptyCount: this.emptyCount,
270
- minLength: this.minLength,
271
- maxLength: this.maxLength,
272
- avgLength: this.count > 0 ? this.totalLength / this.count : null,
273
- approxDistinct: this.hll.count(),
274
- topValues: sorted,
275
- };
276
- }
277
- }
278
-
279
- interface ColumnHandler {
280
- col: string;
281
- numCollector: NumericStatsCollector;
282
- strCollector: StringStatsCollector;
283
- hint: { numericVotes: number; totalVotes: number };
284
- }
285
-
286
- /**
287
- * Dataset-wide Aggregator collecting statistics across all columns in stream.
288
- */
289
- export class DatasetStatsAggregator implements Aggregator<DatasetStatsResult> {
290
- private totalRows = 0;
291
- private targetColumn?: string;
292
- private numericCollectors = new Map<string, NumericStatsCollector>();
293
- private stringCollectors = new Map<string, StringStatsCollector>();
294
- private columnTypeHints = new Map<string, { numericVotes: number; totalVotes: number }>();
295
- private handlers: ColumnHandler[] = [];
296
- private initialized = false;
297
-
298
- constructor(options?: { column?: string }) {
299
- this.targetColumn = options?.column;
300
- }
301
-
302
- private initHandler(col: string): ColumnHandler {
303
- let numCollector = this.numericCollectors.get(col);
304
- let strCollector = this.stringCollectors.get(col);
305
- let hint = this.columnTypeHints.get(col);
306
-
307
- if (!numCollector) {
308
- numCollector = new NumericStatsCollector();
309
- strCollector = new StringStatsCollector();
310
- hint = { numericVotes: 0, totalVotes: 0 };
311
- this.numericCollectors.set(col, numCollector);
312
- this.stringCollectors.set(col, strCollector);
313
- this.columnTypeHints.set(col, hint);
314
- }
315
-
316
- return { col, numCollector: numCollector!, strCollector: strCollector!, hint: hint! };
317
- }
318
-
319
- add(row: Row): void {
320
- this.totalRows++;
321
-
322
- if (!this.initialized) {
323
- const columns = this.targetColumn ? [this.targetColumn] : Object.keys(row);
324
- this.handlers = new Array(columns.length);
325
- for (let i = 0; i < columns.length; i++) {
326
- this.handlers[i] = this.initHandler(columns[i]!);
327
- }
328
- this.initialized = true;
329
- }
330
-
331
- const handlers = this.handlers;
332
- const len = handlers.length;
333
-
334
- for (let i = 0; i < len; i++) {
335
- const h = handlers[i]!;
336
- const val = row[h.col];
337
-
338
- h.strCollector.add(val);
339
-
340
- if (val !== null && val !== undefined && val !== "") {
341
- h.hint.totalVotes++;
342
- const num = typeof val === "number" ? val : Number(val);
343
- if (!Number.isNaN(num) && typeof val !== "boolean") {
344
- h.hint.numericVotes++;
345
- h.numCollector.add(num);
346
- } else {
347
- h.numCollector.add(null);
348
- }
349
- } else {
350
- h.numCollector.add(null);
351
- }
352
- }
353
- }
354
-
355
- result(): DatasetStatsResult {
356
- const columns: Record<string, ColumnStatsResult> = {};
357
-
358
- for (const [col, hint] of this.columnTypeHints.entries()) {
359
- const isNumeric = hint.totalVotes > 0 && hint.numericVotes / hint.totalVotes >= 0.8;
360
- const numCollector = this.numericCollectors.get(col)!;
361
- const strCollector = this.stringCollectors.get(col)!;
362
-
363
- if (isNumeric) {
364
- columns[col] = {
365
- column: col,
366
- type: "numeric",
367
- numeric: numCollector.result(),
368
- };
369
- } else {
370
- columns[col] = {
371
- column: col,
372
- type: "string",
373
- string: strCollector.result(),
374
- };
375
- }
376
- }
377
-
378
- return {
379
- totalRows: this.totalRows,
380
- columns,
381
- };
382
- }
383
- }
@@ -1,134 +0,0 @@
1
- import type { Aggregator, Row } from "../core/types.js";
2
- import { classifyPrimitiveType } from "./schema-inference.js";
3
- import { detectSemanticType } from "./semantic-types.js";
4
-
5
- export interface ColumnSchemaRule {
6
- type?: string;
7
- nullable?: boolean;
8
- format?: string;
9
- }
10
-
11
- export type ValidationSchemaDefinition = Record<string, ColumnSchemaRule>;
12
-
13
- export interface ColumnViolationSummary {
14
- column: string;
15
- rule: string;
16
- violationsCount: number;
17
- sampleInvalidValues: unknown[];
18
- }
19
-
20
- export interface ValidationReport {
21
- totalRows: number;
22
- validRows: number;
23
- invalidRows: number;
24
- isValid: boolean;
25
- violations: ColumnViolationSummary[];
26
- }
27
-
28
- export class SchemaValidatorAggregator implements Aggregator<ValidationReport> {
29
- private schema: ValidationSchemaDefinition;
30
- private totalRows = 0;
31
- private validRows = 0;
32
- private invalidRows = 0;
33
- private violationsMap = new Map<string, ColumnViolationSummary>();
34
-
35
- constructor(schema: ValidationSchemaDefinition) {
36
- this.schema = schema;
37
- }
38
-
39
- private addViolation(column: string, rule: string, value: unknown): void {
40
- const key = `${column}::${rule}`;
41
- if (!this.violationsMap.has(key)) {
42
- this.violationsMap.set(key, {
43
- column,
44
- rule,
45
- violationsCount: 0,
46
- sampleInvalidValues: [],
47
- });
48
- }
49
-
50
- const v = this.violationsMap.get(key)!;
51
- v.violationsCount++;
52
- if (v.sampleInvalidValues.length < 5) {
53
- v.sampleInvalidValues.push(value);
54
- }
55
- }
56
-
57
- add(row: Row): void {
58
- this.totalRows++;
59
- let rowHasError = false;
60
-
61
- for (const [colName, rule] of Object.entries(this.schema)) {
62
- const val = row[colName];
63
- const isNull = val === null || val === undefined || val === "";
64
-
65
- // Nullability check
66
- if (isNull) {
67
- if (rule.nullable === false) {
68
- this.addViolation(colName, "null not allowed", val);
69
- rowHasError = true;
70
- }
71
- continue;
72
- }
73
-
74
- // Type check
75
- if (rule.type) {
76
- const actualType = classifyPrimitiveType(val);
77
- const expectedType = rule.type.toLowerCase();
78
-
79
- let matchesType = false;
80
- if (expectedType === "string") {
81
- matchesType = true;
82
- } else if (expectedType === "integer" || expectedType === "int") {
83
- matchesType = actualType === "integer";
84
- } else if (expectedType === "number" || expectedType === "float") {
85
- matchesType = actualType === "integer" || actualType === "number";
86
- } else if (expectedType === "boolean" || expectedType === "bool") {
87
- matchesType = actualType === "boolean";
88
- } else if (expectedType === "date") {
89
- matchesType = actualType === "date" || actualType === "datetime";
90
- } else if (expectedType === "datetime") {
91
- matchesType = actualType === "datetime";
92
- }
93
-
94
- if (!matchesType) {
95
- this.addViolation(
96
- colName,
97
- `expected ${rule.type}`,
98
- val
99
- );
100
- rowHasError = true;
101
- }
102
- }
103
-
104
- // Format check (semantic type)
105
- if (rule.format) {
106
- const detected = detectSemanticType(val);
107
- if (detected !== rule.format.toLowerCase()) {
108
- this.addViolation(
109
- colName,
110
- `expected format ${rule.format}`,
111
- val
112
- );
113
- rowHasError = true;
114
- }
115
- }
116
- }
117
-
118
- if (rowHasError) {
119
- this.invalidRows++;
120
- } else {
121
- this.validRows++;
122
- }
123
- }
124
-
125
- result(): ValidationReport {
126
- return {
127
- totalRows: this.totalRows,
128
- validRows: this.validRows,
129
- invalidRows: this.invalidRows,
130
- isValid: this.invalidRows === 0,
131
- violations: Array.from(this.violationsMap.values()),
132
- };
133
- }
134
- }
@@ -1,60 +0,0 @@
1
- import { createPipeline } from "../../core/pipeline.js";
2
- import { createReader, inferFormatFromPath as inferReaderFormat } from "../../readers/index.js";
3
- import { createWriter } from "../../writers/index.js";
4
- import { castColumns, type CastOnError, parseCastSpecs } from "../../transforms/cast.js";
5
- import { openReadableStream } from "../../utils/compression.js";
6
- import { logMemoryDebug } from "../../utils/formatting.js";
7
- import { ProgressReporter } from "../../utils/progress.js";
8
-
9
- export interface CastCommandOptions {
10
- onError?: CastOnError;
11
- from?: string;
12
- to?: string;
13
- sheet?: string;
14
- delimiter?: string;
15
- batchSize?: string | number;
16
- quiet?: boolean;
17
- noProgress?: boolean;
18
- }
19
-
20
- export async function castCommand(
21
- inputPath = "-",
22
- specs: string[],
23
- options: CastCommandOptions = {}
24
- ): Promise<void> {
25
- const effectiveBatchSize = Number(options.batchSize) || 1000;
26
- const progress = new ProgressReporter(options);
27
-
28
- let fromFormat = options.from?.toLowerCase();
29
- if (!fromFormat && inputPath !== "-") {
30
- fromFormat = inferReaderFormat(inputPath) ?? undefined;
31
- }
32
- if (!fromFormat) {
33
- fromFormat = "csv";
34
- }
35
-
36
- const toFormat = options.to?.toLowerCase() || (fromFormat === "xlsx" ? "csv" : fromFormat);
37
- const mapping = parseCastSpecs(specs);
38
-
39
- const inputStream = openReadableStream(inputPath);
40
- const reader = createReader(inputStream, {
41
- format: fromFormat,
42
- sheet: options.sheet,
43
- delimiter: options.delimiter,
44
- batchSize: effectiveBatchSize,
45
- filePath: inputPath,
46
- });
47
-
48
- const writer = createWriter(process.stdout, {
49
- format: toFormat,
50
- delimiter: options.delimiter,
51
- });
52
-
53
- const pipeline = createPipeline(reader, { batchSize: effectiveBatchSize });
54
- pipeline.pipe(castColumns(mapping, { onError: options.onError }));
55
- pipeline.onProgress((info) => progress.update(info));
56
-
57
- await pipeline.to(writer);
58
- progress.done();
59
- logMemoryDebug();
60
- }
@@ -1,128 +0,0 @@
1
- import { mkdir } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { createPipeline } from "../../core/pipeline.js";
4
- import { createReader, inferFormatFromPath as inferReaderFormat } from "../../readers/index.js";
5
- import { XLSXReader } from "../../readers/xlsx.js";
6
- import { createWriter, inferFormatFromPath as inferWriterFormat } from "../../writers/index.js";
7
- import { openReadableStream, openWritableStream } from "../../utils/compression.js";
8
- import { formatNumber, logMemoryDebug } from "../../utils/formatting.js";
9
- import { ProgressReporter } from "../../utils/progress.js";
10
-
11
- export interface ConvertCommandOptions {
12
- from?: string;
13
- to?: string;
14
- sheet?: string;
15
- allSheets?: boolean;
16
- outDir?: string;
17
- path?: string;
18
- delimiter?: string;
19
- header?: boolean;
20
- batchSize?: string | number;
21
- quiet?: boolean;
22
- noProgress?: boolean;
23
- }
24
-
25
- export async function convertCommand(
26
- inputPath = "-",
27
- outputPath?: string,
28
- options: ConvertCommandOptions = {}
29
- ): Promise<void> {
30
- const effectiveBatchSize = Number(options.batchSize) || 1000;
31
-
32
- // Determine input format
33
- let fromFormat = options.from?.toLowerCase();
34
- if (!fromFormat && inputPath !== "-") {
35
- fromFormat = inferReaderFormat(inputPath) ?? undefined;
36
- }
37
- if (!fromFormat) {
38
- fromFormat = "csv";
39
- }
40
-
41
- // Handle multi-sheet export if --all-sheets is specified
42
- if (options.allSheets && fromFormat === "xlsx" && inputPath !== "-") {
43
- const outDir = options.outDir || outputPath || "./";
44
- const toFormat = (options.to?.toLowerCase() || "csv").replace(/^\./, "");
45
-
46
- await mkdir(outDir, { recursive: true });
47
-
48
- const xlsxReader = new XLSXReader(inputPath);
49
- const sheets = await xlsxReader.getParsedSheets();
50
-
51
- if (sheets.length === 0) {
52
- process.stderr.write("No sheets found in workbook.\n");
53
- return;
54
- }
55
-
56
- process.stderr.write(`Exporting ${sheets.length} sheets to "${outDir}"...\n`);
57
-
58
- for (const sheet of sheets) {
59
- const sheetName = sheet.sheet;
60
- const targetFileName = `${sheetName}.${toFormat}`;
61
- const targetFilePath = join(outDir, targetFileName);
62
-
63
- const sheetReader = createReader(inputPath, {
64
- format: "xlsx",
65
- sheet: sheetName,
66
- batchSize: effectiveBatchSize,
67
- filePath: inputPath,
68
- });
69
-
70
- const sheetWriter = createWriter(targetFilePath, {
71
- format: toFormat,
72
- delimiter: options.delimiter,
73
- header: options.header,
74
- });
75
-
76
- const pipeline = createPipeline(sheetReader, { batchSize: effectiveBatchSize });
77
- await pipeline.to(sheetWriter);
78
-
79
- const rowCount = Math.max(0, sheet.data.length - 1);
80
- process.stderr.write(
81
- ` ✓ Sheet "${sheetName}" -> ${targetFilePath} (${formatNumber(rowCount)} rows)\n`
82
- );
83
- }
84
-
85
- process.stderr.write("Done.\n");
86
- logMemoryDebug();
87
- return;
88
- }
89
-
90
- // Determine output format
91
- let toFormat = options.to?.toLowerCase();
92
- if (!toFormat && outputPath && outputPath !== "-") {
93
- toFormat = inferWriterFormat(outputPath) ?? undefined;
94
- }
95
- if (!toFormat) {
96
- toFormat = "csv";
97
- }
98
-
99
- const progress = new ProgressReporter(options);
100
- const effectiveOutput = outputPath || "-";
101
-
102
- const inputStream = openReadableStream(inputPath);
103
- const outputStream = openWritableStream(effectiveOutput);
104
-
105
- const reader = createReader(inputStream, {
106
- format: fromFormat,
107
- sheet: options.sheet,
108
- path: options.path,
109
- delimiter: options.delimiter,
110
- header: options.header,
111
- batchSize: effectiveBatchSize,
112
- filePath: inputPath,
113
- });
114
-
115
- const writer = createWriter(outputStream, {
116
- format: toFormat,
117
- delimiter: options.delimiter,
118
- header: options.header,
119
- sheet: options.sheet,
120
- });
121
-
122
- const pipeline = createPipeline(reader, { batchSize: effectiveBatchSize });
123
- pipeline.onProgress((info) => progress.update(info));
124
-
125
- await pipeline.to(writer);
126
- progress.done();
127
- logMemoryDebug();
128
- }