rowpipe 1.0.0 → 1.1.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 (106) hide show
  1. package/README.md +25 -32
  2. package/dist/cli/index.js +4 -4
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/types.d.ts +2 -0
  5. package/dist/core/types.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/readers/csv.d.ts.map +1 -1
  11. package/dist/readers/csv.js +28 -16
  12. package/dist/readers/csv.js.map +1 -1
  13. package/dist/readers/index.d.ts +1 -0
  14. package/dist/readers/index.d.ts.map +1 -1
  15. package/dist/readers/index.js +29 -3
  16. package/dist/readers/index.js.map +1 -1
  17. package/dist/readers/json.d.ts.map +1 -1
  18. package/dist/readers/json.js +6 -8
  19. package/dist/readers/json.js.map +1 -1
  20. package/dist/readers/jsonl.d.ts.map +1 -1
  21. package/dist/readers/jsonl.js +5 -7
  22. package/dist/readers/jsonl.js.map +1 -1
  23. package/dist/readers/parquet.d.ts +17 -0
  24. package/dist/readers/parquet.d.ts.map +1 -0
  25. package/dist/readers/parquet.js +146 -0
  26. package/dist/readers/parquet.js.map +1 -0
  27. package/dist/utils/compression.d.ts +30 -6
  28. package/dist/utils/compression.d.ts.map +1 -1
  29. package/dist/utils/compression.js +95 -18
  30. package/dist/utils/compression.js.map +1 -1
  31. package/dist/writers/csv.d.ts.map +1 -1
  32. package/dist/writers/csv.js +9 -11
  33. package/dist/writers/csv.js.map +1 -1
  34. package/dist/writers/index.d.ts +2 -0
  35. package/dist/writers/index.d.ts.map +1 -1
  36. package/dist/writers/index.js +49 -4
  37. package/dist/writers/index.js.map +1 -1
  38. package/dist/writers/json.d.ts +1 -1
  39. package/dist/writers/json.d.ts.map +1 -1
  40. package/dist/writers/json.js +9 -11
  41. package/dist/writers/json.js.map +1 -1
  42. package/dist/writers/jsonl.d.ts +1 -1
  43. package/dist/writers/jsonl.d.ts.map +1 -1
  44. package/dist/writers/jsonl.js +9 -11
  45. package/dist/writers/jsonl.js.map +1 -1
  46. package/dist/writers/markdown.d.ts +21 -0
  47. package/dist/writers/markdown.d.ts.map +1 -0
  48. package/dist/writers/markdown.js +82 -0
  49. package/dist/writers/markdown.js.map +1 -0
  50. package/dist/writers/parquet.d.ts +19 -0
  51. package/dist/writers/parquet.d.ts.map +1 -0
  52. package/dist/writers/parquet.js +129 -0
  53. package/dist/writers/parquet.js.map +1 -0
  54. package/package.json +13 -2
  55. package/skills/rowpipe/SKILL.md +181 -0
  56. package/benchmarks/memory-bench.ts +0 -109
  57. package/src/analytics/reduce.ts +0 -388
  58. package/src/analytics/schema-inference.ts +0 -217
  59. package/src/analytics/semantic-types.ts +0 -36
  60. package/src/analytics/stats.ts +0 -383
  61. package/src/analytics/validator.ts +0 -134
  62. package/src/cli/commands/cast.ts +0 -60
  63. package/src/cli/commands/convert.ts +0 -128
  64. package/src/cli/commands/filter.ts +0 -58
  65. package/src/cli/commands/inspect.ts +0 -193
  66. package/src/cli/commands/map.ts +0 -66
  67. package/src/cli/commands/reduce.ts +0 -74
  68. package/src/cli/commands/rename.ts +0 -59
  69. package/src/cli/commands/sample.ts +0 -61
  70. package/src/cli/commands/schema.ts +0 -86
  71. package/src/cli/commands/select.ts +0 -59
  72. package/src/cli/commands/stats.ts +0 -100
  73. package/src/cli/commands/validate.ts +0 -123
  74. package/src/cli/index.ts +0 -285
  75. package/src/core/batch.ts +0 -81
  76. package/src/core/errors.ts +0 -81
  77. package/src/core/pipeline.ts +0 -150
  78. package/src/core/types.ts +0 -110
  79. package/src/index.ts +0 -52
  80. package/src/readers/csv.ts +0 -302
  81. package/src/readers/index.ts +0 -88
  82. package/src/readers/json.ts +0 -256
  83. package/src/readers/jsonl.ts +0 -126
  84. package/src/readers/xlsx.ts +0 -182
  85. package/src/transforms/cast.ts +0 -212
  86. package/src/transforms/expression.ts +0 -1114
  87. package/src/transforms/filter.ts +0 -38
  88. package/src/transforms/map.ts +0 -99
  89. package/src/transforms/rename.ts +0 -47
  90. package/src/transforms/sample.ts +0 -57
  91. package/src/transforms/select.ts +0 -64
  92. package/src/utils/compression.ts +0 -43
  93. package/src/utils/formatting.ts +0 -75
  94. package/src/utils/progress.ts +0 -37
  95. package/src/writers/csv.ts +0 -106
  96. package/src/writers/index.ts +0 -87
  97. package/src/writers/json.ts +0 -52
  98. package/src/writers/jsonl.ts +0 -49
  99. package/src/writers/xlsx.ts +0 -87
  100. package/tests/analytics.test.ts +0 -119
  101. package/tests/cli-integration.test.ts +0 -187
  102. package/tests/map-reduce.test.ts +0 -223
  103. package/tests/readers-writers.test.ts +0 -189
  104. package/tests/transforms.test.ts +0 -199
  105. package/tsconfig.json +0 -21
  106. package/vitest.config.ts +0 -9
@@ -1,36 +0,0 @@
1
- import type { SemanticType } from "../core/types.js";
2
-
3
- const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
4
- const URL_REGEX = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
5
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
- const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
7
- const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
8
- const PHONE_REGEX = /^\+?[0-9\s\-().]{7,20}$/;
9
-
10
- const ISO_COUNTRY_CODES = new Set([
11
- "US", "GB", "DE", "FR", "TR", "CA", "AU", "JP", "CN", "IN", "BR", "IT", "ES", "NL", "SE", "CH", "PL", "MX", "KR", "RU", "ZA", "EG", "SA", "AE", "SG", "NZ", "IE", "NO", "DK", "FI", "PT", "GR", "AT", "BE", "IL", "AR", "CL", "CO", "PE", "TH", "MY", "ID", "PH", "VN", "PK", "NG", "KE", "ZA"
12
- ]);
13
-
14
- const ISO_CURRENCIES = new Set([
15
- "USD", "EUR", "GBP", "TRY", "JPY", "CAD", "AUD", "CHF", "CNY", "INR", "BRL", "RUB", "KRW", "SEK", "NOK", "MXN", "SGD", "NZD", "HKD", "PLN", "ZAR", "AED", "SAR", "THB", "IDR"
16
- ]);
17
-
18
- /**
19
- * Tests if a string value matches a specific semantic pattern.
20
- */
21
- export function detectSemanticType(value: unknown): SemanticType | null {
22
- if (value === null || value === undefined) return null;
23
- const str = String(value).trim();
24
- if (str.length === 0) return null;
25
-
26
- if (UUID_REGEX.test(str)) return "uuid";
27
- if (EMAIL_REGEX.test(str)) return "email";
28
- if (URL_REGEX.test(str)) return "url";
29
- if (IPV4_REGEX.test(str)) return "ipv4";
30
- if (IPV6_REGEX.test(str)) return "ipv6";
31
- if (str.length >= 2 && str.length <= 3 && ISO_COUNTRY_CODES.has(str.toUpperCase())) return "country-code";
32
- if (str.length === 3 && ISO_CURRENCIES.has(str.toUpperCase())) return "currency";
33
- if (PHONE_REGEX.test(str) && /\d{4,}/.test(str) && (str.startsWith("+") || str.includes("-") || str.includes("("))) return "phone";
34
-
35
- return null;
36
- }
@@ -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
- }