jtcsv 2.1.5 → 2.2.2

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.
@@ -0,0 +1,486 @@
1
+ /**
2
+ * TypeScript Examples for jtcsv
3
+ *
4
+ * This file demonstrates type-safe usage of jtcsv
5
+ * with full TypeScript support.
6
+ */
7
+
8
+ import {
9
+ // Core functions
10
+ csvToJson,
11
+ jsonToCsv,
12
+ saveAsCsv,
13
+ readCsvAsJson,
14
+ readCsvAsJsonSync,
15
+
16
+ // Streaming
17
+ createCsvToJsonStream,
18
+ createJsonToCsvStream,
19
+ streamCsvToJson,
20
+ streamJsonToCsv,
21
+
22
+ // NDJSON
23
+ jsonToNdjson,
24
+ ndjsonToJson,
25
+ parseNdjsonStream,
26
+
27
+ // TSV
28
+ jsonToTsv,
29
+ tsvToJson,
30
+ validateTsv,
31
+
32
+ // Types
33
+ JsonToCsvOptions,
34
+ CsvToJsonOptions,
35
+ NdjsonOptions,
36
+ TsvOptions,
37
+ TsvValidationResult,
38
+
39
+ // Error classes
40
+ JtcsvError,
41
+ ValidationError,
42
+ SecurityError,
43
+ ParsingError,
44
+ FileSystemError,
45
+ LimitError,
46
+ ConfigurationError
47
+ } from 'jtcsv';
48
+
49
+ import { Readable, Writable, Transform } from 'stream';
50
+
51
+ // =============================================================================
52
+ // Type Definitions for Domain Objects
53
+ // =============================================================================
54
+
55
+ interface User {
56
+ id: number;
57
+ name: string;
58
+ email: string;
59
+ age: number;
60
+ isActive: boolean;
61
+ createdAt: string;
62
+ }
63
+
64
+ interface Product {
65
+ sku: string;
66
+ name: string;
67
+ price: number;
68
+ category: string;
69
+ inStock: boolean;
70
+ }
71
+
72
+ interface Order {
73
+ orderId: string;
74
+ customerId: number;
75
+ products: string[];
76
+ total: number;
77
+ status: 'pending' | 'shipped' | 'delivered';
78
+ }
79
+
80
+ // =============================================================================
81
+ // Example 1: Type-Safe JSON to CSV Conversion
82
+ // =============================================================================
83
+
84
+ function typedJsonToCsv(): void {
85
+ console.log('\n=== Type-Safe JSON to CSV ===\n');
86
+
87
+ const users: User[] = [
88
+ { id: 1, name: 'John Doe', email: 'john@example.com', age: 30, isActive: true, createdAt: '2024-01-15' },
89
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25, isActive: false, createdAt: '2024-02-20' },
90
+ { id: 3, name: 'Bob Wilson', email: 'bob@example.com', age: 35, isActive: true, createdAt: '2024-03-10' }
91
+ ];
92
+
93
+ // Options with full type support
94
+ const options: JsonToCsvOptions = {
95
+ delimiter: ',',
96
+ includeHeaders: true,
97
+ preventCsvInjection: true,
98
+ rfc4180Compliant: true,
99
+ renameMap: {
100
+ id: 'User ID',
101
+ name: 'Full Name',
102
+ email: 'Email Address',
103
+ isActive: 'Active Status'
104
+ },
105
+ template: {
106
+ id: 0,
107
+ name: '',
108
+ email: '',
109
+ age: 0,
110
+ isActive: false,
111
+ createdAt: ''
112
+ }
113
+ };
114
+
115
+ const csv = jsonToCsv<User>(users, options);
116
+ console.log('Generated CSV:');
117
+ console.log(csv);
118
+ }
119
+
120
+ // =============================================================================
121
+ // Example 2: Type-Safe CSV to JSON Conversion
122
+ // =============================================================================
123
+
124
+ function typedCsvToJson(): void {
125
+ console.log('\n=== Type-Safe CSV to JSON ===\n');
126
+
127
+ const csvData = `sku,name,price,category,inStock
128
+ PRD001,Laptop,999.99,Electronics,true
129
+ PRD002,Mouse,29.99,Electronics,true
130
+ PRD003,Desk Chair,199.99,Furniture,false`;
131
+
132
+ const options: CsvToJsonOptions = {
133
+ delimiter: ',',
134
+ hasHeaders: true,
135
+ parseNumbers: true,
136
+ parseBooleans: true,
137
+ trim: true
138
+ };
139
+
140
+ // Parse as generic records first
141
+ const rawProducts = csvToJson(csvData, options);
142
+
143
+ // Type assertion after validation
144
+ const products: Product[] = rawProducts.map(row => ({
145
+ sku: String(row.sku),
146
+ name: String(row.name),
147
+ price: Number(row.price),
148
+ category: String(row.category),
149
+ inStock: Boolean(row.inStock)
150
+ }));
151
+
152
+ console.log('Parsed products:');
153
+ products.forEach(p => {
154
+ console.log(` ${p.sku}: ${p.name} - $${p.price.toFixed(2)} (${p.inStock ? 'In Stock' : 'Out of Stock'})`);
155
+ });
156
+ }
157
+
158
+ // =============================================================================
159
+ // Example 3: Generic Type Helper
160
+ // =============================================================================
161
+
162
+ // Type-safe parsing helper with validation
163
+ function parseTypedCsv<T extends Record<string, unknown>>(
164
+ csv: string,
165
+ options: CsvToJsonOptions,
166
+ validator: (row: Record<string, unknown>) => row is T
167
+ ): T[] {
168
+ const rawData = csvToJson(csv, options);
169
+ const validData: T[] = [];
170
+
171
+ for (const row of rawData) {
172
+ if (validator(row)) {
173
+ validData.push(row);
174
+ } else {
175
+ console.warn('Invalid row skipped:', row);
176
+ }
177
+ }
178
+
179
+ return validData;
180
+ }
181
+
182
+ // Type guard for User
183
+ function isUser(row: Record<string, unknown>): row is User {
184
+ return (
185
+ typeof row.id === 'number' &&
186
+ typeof row.name === 'string' &&
187
+ typeof row.email === 'string' &&
188
+ typeof row.age === 'number' &&
189
+ typeof row.isActive === 'boolean' &&
190
+ typeof row.createdAt === 'string'
191
+ );
192
+ }
193
+
194
+ function genericTypeHelperExample(): void {
195
+ console.log('\n=== Generic Type Helper ===\n');
196
+
197
+ const csv = `id,name,email,age,isActive,createdAt
198
+ 1,John,john@test.com,30,true,2024-01-01
199
+ 2,Jane,jane@test.com,25,false,2024-02-01
200
+ invalid,Bob,bob@test.com,not-a-number,true,2024-03-01`;
201
+
202
+ const users = parseTypedCsv<User>(
203
+ csv,
204
+ { parseNumbers: true, parseBooleans: true },
205
+ isUser
206
+ );
207
+
208
+ console.log('Valid users:', users.length);
209
+ users.forEach(u => console.log(` ${u.id}: ${u.name} (${u.age})`));
210
+ }
211
+
212
+ // =============================================================================
213
+ // Example 4: NDJSON with Types
214
+ // =============================================================================
215
+
216
+ async function typedNdjson(): Promise<void> {
217
+ console.log('\n=== Typed NDJSON ===\n');
218
+
219
+ const orders: Order[] = [
220
+ { orderId: 'ORD001', customerId: 1, products: ['PRD001', 'PRD002'], total: 1029.98, status: 'shipped' },
221
+ { orderId: 'ORD002', customerId: 2, products: ['PRD003'], total: 199.99, status: 'pending' },
222
+ { orderId: 'ORD003', customerId: 1, products: ['PRD002'], total: 29.99, status: 'delivered' }
223
+ ];
224
+
225
+ const ndjsonOptions: NdjsonOptions = {
226
+ transform: (obj, index) => ({
227
+ ...obj,
228
+ lineNumber: index + 1
229
+ })
230
+ };
231
+
232
+ // Convert to NDJSON
233
+ const ndjsonString = jsonToNdjson(orders, ndjsonOptions);
234
+ console.log('NDJSON output:');
235
+ console.log(ndjsonString);
236
+
237
+ // Parse back
238
+ const parsed = ndjsonToJson(ndjsonString) as (Order & { lineNumber: number })[];
239
+ console.log('\nParsed back:');
240
+ parsed.forEach(o => console.log(` Line ${o.lineNumber}: ${o.orderId} - ${o.status}`));
241
+ }
242
+
243
+ // =============================================================================
244
+ // Example 5: TSV with Validation
245
+ // =============================================================================
246
+
247
+ function typedTsv(): void {
248
+ console.log('\n=== Typed TSV with Validation ===\n');
249
+
250
+ const tsvData = `name\tage\tcity
251
+ Alice\t28\tNew York
252
+ Bob\t35\tLos Angeles
253
+ Charlie\t42\tChicago`;
254
+
255
+ // Validate TSV structure
256
+ const validation: TsvValidationResult = validateTsv(tsvData, {
257
+ requireConsistentColumns: true,
258
+ disallowEmptyFields: false
259
+ });
260
+
261
+ console.log('Validation result:');
262
+ console.log(' Valid:', validation.valid);
263
+ console.log(' Stats:', validation.stats);
264
+
265
+ if (validation.valid) {
266
+ const tsvOptions: TsvOptions = {
267
+ hasHeaders: true,
268
+ parseNumbers: true
269
+ };
270
+
271
+ interface Person {
272
+ name: string;
273
+ age: number;
274
+ city: string;
275
+ }
276
+
277
+ const people = tsvToJson(tsvData, tsvOptions) as Person[];
278
+ console.log('\nParsed people:');
279
+ people.forEach(p => console.log(` ${p.name}, ${p.age}, ${p.city}`));
280
+ }
281
+ }
282
+
283
+ // =============================================================================
284
+ // Example 6: Error Handling with TypeScript
285
+ // =============================================================================
286
+
287
+ function typedErrorHandling(): void {
288
+ console.log('\n=== Typed Error Handling ===\n');
289
+
290
+ // Type-safe error handling
291
+ function processCsv(input: string): { success: true; data: Record<string, unknown>[] } | { success: false; error: JtcsvError } {
292
+ try {
293
+ const data = csvToJson(input);
294
+ return { success: true, data };
295
+ } catch (error) {
296
+ if (error instanceof JtcsvError) {
297
+ return { success: false, error };
298
+ }
299
+ throw error; // Re-throw non-jtcsv errors
300
+ }
301
+ }
302
+
303
+ // Test with various inputs
304
+ const testCases = [
305
+ 'name,age\nJohn,30',
306
+ null as unknown as string,
307
+ 'name,age\n"unclosed'
308
+ ];
309
+
310
+ testCases.forEach((input, i) => {
311
+ const result = processCsv(input);
312
+ if (result.success) {
313
+ console.log(`Test ${i + 1}: Success - ${result.data.length} rows`);
314
+ } else {
315
+ console.log(`Test ${i + 1}: ${result.error.constructor.name} - ${result.error.message}`);
316
+ }
317
+ });
318
+ }
319
+
320
+ // =============================================================================
321
+ // Example 7: Discriminated Union for Error Types
322
+ // =============================================================================
323
+
324
+ type ParseResult<T> =
325
+ | { status: 'success'; data: T[] }
326
+ | { status: 'validation_error'; message: string }
327
+ | { status: 'parsing_error'; line: number | undefined; message: string }
328
+ | { status: 'security_error'; message: string }
329
+ | { status: 'unknown_error'; message: string };
330
+
331
+ function safeParseCsv<T>(csv: string, options?: CsvToJsonOptions): ParseResult<T> {
332
+ try {
333
+ const data = csvToJson(csv, options) as T[];
334
+ return { status: 'success', data };
335
+ } catch (error) {
336
+ if (error instanceof ValidationError) {
337
+ return { status: 'validation_error', message: error.message };
338
+ }
339
+ if (error instanceof ParsingError) {
340
+ return { status: 'parsing_error', line: error.lineNumber, message: error.message };
341
+ }
342
+ if (error instanceof SecurityError) {
343
+ return { status: 'security_error', message: error.message };
344
+ }
345
+ if (error instanceof Error) {
346
+ return { status: 'unknown_error', message: error.message };
347
+ }
348
+ return { status: 'unknown_error', message: 'Unknown error occurred' };
349
+ }
350
+ }
351
+
352
+ function discriminatedUnionExample(): void {
353
+ console.log('\n=== Discriminated Union Errors ===\n');
354
+
355
+ const csv = 'a,b\n1,2';
356
+ const result = safeParseCsv<{ a: string; b: string }>(csv);
357
+
358
+ // TypeScript can narrow the type based on status
359
+ switch (result.status) {
360
+ case 'success':
361
+ console.log('Parsed data:', result.data);
362
+ break;
363
+ case 'validation_error':
364
+ console.log('Validation failed:', result.message);
365
+ break;
366
+ case 'parsing_error':
367
+ console.log(`Parse error at line ${result.line}:`, result.message);
368
+ break;
369
+ case 'security_error':
370
+ console.log('Security violation:', result.message);
371
+ break;
372
+ case 'unknown_error':
373
+ console.log('Unknown error:', result.message);
374
+ break;
375
+ }
376
+ }
377
+
378
+ // =============================================================================
379
+ // Example 8: Streaming with TypeScript
380
+ // =============================================================================
381
+
382
+ async function typedStreaming(): Promise<void> {
383
+ console.log('\n=== Typed Streaming ===\n');
384
+
385
+ // Create typed transform function
386
+ const transform = (row: Record<string, unknown>): Record<string, unknown> => ({
387
+ ...row,
388
+ processed: true,
389
+ timestamp: new Date().toISOString()
390
+ });
391
+
392
+ // Type-safe stream options
393
+ const streamOptions: JsonToCsvOptions & { transform: typeof transform } = {
394
+ delimiter: ',',
395
+ includeHeaders: true,
396
+ transform
397
+ };
398
+
399
+ // Note: In real usage, you'd pipe actual streams
400
+ console.log('Stream options configured:', Object.keys(streamOptions));
401
+ }
402
+
403
+ // =============================================================================
404
+ // Example 9: Builder Pattern for Options
405
+ // =============================================================================
406
+
407
+ class CsvOptionsBuilder {
408
+ private options: CsvToJsonOptions = {};
409
+
410
+ withDelimiter(delimiter: string): this {
411
+ this.options.delimiter = delimiter;
412
+ return this;
413
+ }
414
+
415
+ withHeaders(hasHeaders: boolean = true): this {
416
+ this.options.hasHeaders = hasHeaders;
417
+ return this;
418
+ }
419
+
420
+ withNumberParsing(parse: boolean = true): this {
421
+ this.options.parseNumbers = parse;
422
+ return this;
423
+ }
424
+
425
+ withBooleanParsing(parse: boolean = true): this {
426
+ this.options.parseBooleans = parse;
427
+ return this;
428
+ }
429
+
430
+ withMaxRows(max: number): this {
431
+ this.options.maxRows = max;
432
+ return this;
433
+ }
434
+
435
+ withFastPath(enabled: boolean = true): this {
436
+ this.options.useFastPath = enabled;
437
+ return this;
438
+ }
439
+
440
+ build(): CsvToJsonOptions {
441
+ return { ...this.options };
442
+ }
443
+ }
444
+
445
+ function builderPatternExample(): void {
446
+ console.log('\n=== Builder Pattern ===\n');
447
+
448
+ const options = new CsvOptionsBuilder()
449
+ .withDelimiter(',')
450
+ .withHeaders(true)
451
+ .withNumberParsing(true)
452
+ .withBooleanParsing(true)
453
+ .withMaxRows(1000)
454
+ .withFastPath(true)
455
+ .build();
456
+
457
+ console.log('Built options:', options);
458
+
459
+ const csv = 'id,name,active\n1,Test,true';
460
+ const data = csvToJson(csv, options);
461
+ console.log('Parsed data:', data);
462
+ }
463
+
464
+ // =============================================================================
465
+ // Main Execution
466
+ // =============================================================================
467
+
468
+ async function main(): Promise<void> {
469
+ console.log('jtcsv TypeScript Examples');
470
+ console.log('='.repeat(60));
471
+
472
+ typedJsonToCsv();
473
+ typedCsvToJson();
474
+ genericTypeHelperExample();
475
+ await typedNdjson();
476
+ typedTsv();
477
+ typedErrorHandling();
478
+ discriminatedUnionExample();
479
+ await typedStreaming();
480
+ builderPatternExample();
481
+
482
+ console.log('\n' + '='.repeat(60));
483
+ console.log('All TypeScript examples completed.');
484
+ }
485
+
486
+ main().catch(console.error);
package/index.d.ts CHANGED
@@ -17,6 +17,8 @@ declare module 'jtcsv' {
17
17
  preventCsvInjection?: boolean;
18
18
  /** Ensure RFC 4180 compliance (proper quoting, line endings) (default: true) */
19
19
  rfc4180Compliant?: boolean;
20
+ /** JSON schema for data validation and formatting */
21
+ schema?: Record<string, any>;
20
22
  }
21
23
 
22
24
  export interface SaveAsCsvOptions extends JsonToCsvOptions {