jtcsv 2.2.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +31 -1
  2. package/bin/jtcsv.js +891 -821
  3. package/bin/jtcsv.ts +2534 -0
  4. package/csv-to-json.js +168 -145
  5. package/dist/jtcsv-core.cjs.js +1407 -0
  6. package/dist/jtcsv-core.cjs.js.map +1 -0
  7. package/dist/jtcsv-core.esm.js +1379 -0
  8. package/dist/jtcsv-core.esm.js.map +1 -0
  9. package/dist/jtcsv-core.umd.js +1413 -0
  10. package/dist/jtcsv-core.umd.js.map +1 -0
  11. package/dist/jtcsv-full.cjs.js +1912 -0
  12. package/dist/jtcsv-full.cjs.js.map +1 -0
  13. package/dist/jtcsv-full.esm.js +1880 -0
  14. package/dist/jtcsv-full.esm.js.map +1 -0
  15. package/dist/jtcsv-full.umd.js +1918 -0
  16. package/dist/jtcsv-full.umd.js.map +1 -0
  17. package/dist/jtcsv-workers.esm.js +759 -0
  18. package/dist/jtcsv-workers.esm.js.map +1 -0
  19. package/dist/jtcsv-workers.umd.js +773 -0
  20. package/dist/jtcsv-workers.umd.js.map +1 -0
  21. package/dist/jtcsv.cjs.js +61 -19
  22. package/dist/jtcsv.cjs.js.map +1 -1
  23. package/dist/jtcsv.esm.js +61 -19
  24. package/dist/jtcsv.esm.js.map +1 -1
  25. package/dist/jtcsv.umd.js +61 -19
  26. package/dist/jtcsv.umd.js.map +1 -1
  27. package/errors.js +188 -2
  28. package/examples/advanced/conditional-transformations.js +446 -0
  29. package/examples/advanced/conditional-transformations.ts +446 -0
  30. package/examples/advanced/csv-parser.worker.js +89 -0
  31. package/examples/advanced/csv-parser.worker.ts +89 -0
  32. package/examples/advanced/nested-objects-example.js +306 -0
  33. package/examples/advanced/nested-objects-example.ts +306 -0
  34. package/examples/advanced/performance-optimization.js +504 -0
  35. package/examples/advanced/performance-optimization.ts +504 -0
  36. package/examples/advanced/run-demo-server.js +116 -0
  37. package/examples/advanced/run-demo-server.ts +116 -0
  38. package/examples/advanced/web-worker-usage.html +874 -0
  39. package/examples/async-multithreaded-example.ts +335 -0
  40. package/examples/cli-advanced-usage.md +288 -0
  41. package/examples/cli-batch-processing.ts +38 -0
  42. package/examples/cli-tool.js +0 -3
  43. package/examples/cli-tool.ts +183 -0
  44. package/examples/error-handling.js +21 -7
  45. package/examples/error-handling.ts +356 -0
  46. package/examples/express-api.js +0 -3
  47. package/examples/express-api.ts +164 -0
  48. package/examples/large-dataset-example.js +0 -3
  49. package/examples/large-dataset-example.ts +204 -0
  50. package/examples/ndjson-processing.js +1 -1
  51. package/examples/ndjson-processing.ts +456 -0
  52. package/examples/plugin-excel-exporter.js +3 -4
  53. package/examples/plugin-excel-exporter.ts +406 -0
  54. package/examples/react-integration.tsx +637 -0
  55. package/examples/schema-validation.ts +640 -0
  56. package/examples/simple-usage.js +254 -254
  57. package/examples/simple-usage.ts +194 -0
  58. package/examples/streaming-example.js +4 -5
  59. package/examples/streaming-example.ts +419 -0
  60. package/examples/web-workers-advanced.ts +28 -0
  61. package/index.d.ts +1 -3
  62. package/index.js +15 -1
  63. package/json-save.js +9 -3
  64. package/json-to-csv.js +168 -21
  65. package/package.json +69 -10
  66. package/plugins/express-middleware/README.md +21 -2
  67. package/plugins/express-middleware/example.js +3 -4
  68. package/plugins/express-middleware/example.ts +135 -0
  69. package/plugins/express-middleware/index.d.ts +1 -1
  70. package/plugins/express-middleware/index.js +270 -118
  71. package/plugins/express-middleware/index.ts +557 -0
  72. package/plugins/fastify-plugin/index.js +2 -4
  73. package/plugins/fastify-plugin/index.ts +443 -0
  74. package/plugins/hono/index.ts +226 -0
  75. package/plugins/nestjs/index.ts +201 -0
  76. package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
  77. package/plugins/nextjs-api/examples/api-convert.js +0 -2
  78. package/plugins/nextjs-api/examples/api-convert.ts +67 -0
  79. package/plugins/nextjs-api/index.tsx +339 -0
  80. package/plugins/nextjs-api/route.js +2 -3
  81. package/plugins/nextjs-api/route.ts +370 -0
  82. package/plugins/nuxt/index.ts +94 -0
  83. package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
  84. package/plugins/nuxt/runtime/plugin.ts +71 -0
  85. package/plugins/remix/index.js +1 -1
  86. package/plugins/remix/index.ts +260 -0
  87. package/plugins/sveltekit/index.js +1 -1
  88. package/plugins/sveltekit/index.ts +301 -0
  89. package/plugins/trpc/index.ts +267 -0
  90. package/src/browser/browser-functions.ts +402 -0
  91. package/src/browser/core.js +92 -0
  92. package/src/browser/core.ts +152 -0
  93. package/src/browser/csv-to-json-browser.d.ts +3 -0
  94. package/src/browser/csv-to-json-browser.js +36 -14
  95. package/src/browser/csv-to-json-browser.ts +264 -0
  96. package/src/browser/errors-browser.ts +303 -0
  97. package/src/browser/extensions/plugins.js +92 -0
  98. package/src/browser/extensions/plugins.ts +93 -0
  99. package/src/browser/extensions/workers.js +39 -0
  100. package/src/browser/extensions/workers.ts +39 -0
  101. package/src/browser/globals.d.ts +5 -0
  102. package/src/browser/index.ts +192 -0
  103. package/src/browser/json-to-csv-browser.d.ts +3 -0
  104. package/src/browser/json-to-csv-browser.js +13 -3
  105. package/src/browser/json-to-csv-browser.ts +262 -0
  106. package/src/browser/streams.js +12 -2
  107. package/src/browser/streams.ts +336 -0
  108. package/src/browser/workers/csv-parser.worker.ts +377 -0
  109. package/src/browser/workers/worker-pool.ts +548 -0
  110. package/src/core/delimiter-cache.js +22 -8
  111. package/src/core/delimiter-cache.ts +310 -0
  112. package/src/core/node-optimizations.ts +449 -0
  113. package/src/core/plugin-system.js +29 -11
  114. package/src/core/plugin-system.ts +400 -0
  115. package/src/core/transform-hooks.ts +558 -0
  116. package/src/engines/fast-path-engine-new.ts +347 -0
  117. package/src/engines/fast-path-engine.ts +854 -0
  118. package/src/errors.ts +72 -0
  119. package/src/formats/ndjson-parser.ts +469 -0
  120. package/src/formats/tsv-parser.ts +334 -0
  121. package/src/index-with-plugins.js +16 -9
  122. package/src/index-with-plugins.ts +395 -0
  123. package/src/types/index.ts +255 -0
  124. package/src/utils/bom-utils.js +259 -0
  125. package/src/utils/bom-utils.ts +373 -0
  126. package/src/utils/encoding-support.js +124 -0
  127. package/src/utils/encoding-support.ts +155 -0
  128. package/src/utils/schema-validator.js +19 -19
  129. package/src/utils/schema-validator.ts +819 -0
  130. package/src/utils/transform-loader.js +1 -1
  131. package/src/utils/transform-loader.ts +389 -0
  132. package/src/utils/zod-adapter.js +170 -0
  133. package/src/utils/zod-adapter.ts +280 -0
  134. package/src/web-server/index.js +10 -10
  135. package/src/web-server/index.ts +683 -0
  136. package/src/workers/csv-multithreaded.ts +310 -0
  137. package/src/workers/csv-parser.worker.ts +227 -0
  138. package/src/workers/worker-pool.ts +409 -0
  139. package/stream-csv-to-json.js +26 -8
  140. package/stream-json-to-csv.js +1 -0
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Fastify plugin для JTCSV
3
+ * Плагин для автоматической конвертации CSV/JSON в Fastify приложениях
4
+ *
5
+ * @version 1.0.0
6
+ * @date 2026-01-23
7
+ */
8
+
9
+ const fp = require('fastify-plugin');
10
+ import { csvToJson, csvToJsonAsync, jsonToCsv, jsonToCsvAsync } from '../../index-core';
11
+ import { CsvToJsonOptions, JsonToCsvOptions } from '../../src/types';
12
+
13
+ /**
14
+ * Опции плагина Fastify для JTCSV
15
+ */
16
+ export interface JtcsvFastifyPluginOptions {
17
+ /** Префикс для маршрутов API */
18
+ prefix?: string;
19
+ /** Разделитель CSV по умолчанию */
20
+ delimiter?: string;
21
+ /** Включить fast-path engine */
22
+ enableFastPath?: boolean;
23
+ /** Защита от CSV инъекций */
24
+ preventCsvInjection?: boolean;
25
+ /** Соответствие стандарту RFC 4180 */
26
+ rfc4180Compliant?: boolean;
27
+ }
28
+
29
+ /**
30
+ * Статистика обработки
31
+ */
32
+ interface ProcessingStats {
33
+ rows?: number;
34
+ size?: number;
35
+ processingTime: number;
36
+ inputSize?: number;
37
+ outputSize?: number;
38
+ conversion?: string;
39
+ }
40
+
41
+ /**
42
+ * Ответ API
43
+ */
44
+ interface ApiResponse<T = any> {
45
+ success: boolean;
46
+ data?: T;
47
+ stats?: ProcessingStats;
48
+ error?: string;
49
+ code?: string;
50
+ details?: string;
51
+ format?: string;
52
+ inputFormat?: string;
53
+ }
54
+
55
+ /**
56
+ * Fastify plugin для JTCSV
57
+ *
58
+ * @param fastify - Fastify instance
59
+ * @param options - Опции плагина
60
+ * @param next - Callback
61
+ *
62
+ * @example
63
+ * // Базовое использование
64
+ * const fastify = require('fastify')();
65
+ * await fastify.register(require('@jtcsv/fastify'), {
66
+ * prefix: '/api/convert'
67
+ * });
68
+ *
69
+ * @example
70
+ * // С кастомными опциями
71
+ * await fastify.register(require('@jtcsv/fastify'), {
72
+ * prefix: '/api',
73
+ * enableFastPath: true,
74
+ * delimiter: ';'
75
+ * });
76
+ */
77
+ async function jtcsvFastifyPlugin(fastify: any, options: JtcsvFastifyPluginOptions = {}) {
78
+ const {
79
+ prefix = '/convert',
80
+ delimiter = ',',
81
+ enableFastPath = true,
82
+ preventCsvInjection = true,
83
+ rfc4180Compliant = true
84
+ } = options;
85
+
86
+ // Health check endpoint
87
+ fastify.get(`${prefix}/health`, async () => {
88
+ return {
89
+ service: 'jtcsv-fastify-plugin',
90
+ status: 'healthy',
91
+ version: '1.0.0',
92
+ timestamp: new Date().toISOString(),
93
+ features: {
94
+ csvToJson: true,
95
+ jsonToCsv: true,
96
+ fastPathEngine: enableFastPath,
97
+ csvInjectionProtection: preventCsvInjection,
98
+ streaming: true,
99
+ ndjson: true
100
+ }
101
+ };
102
+ });
103
+
104
+ // CSV to JSON endpoint
105
+ fastify.post(`${prefix}/csv-to-json`, {
106
+ schema: {
107
+ body: {
108
+ type: 'object',
109
+ required: ['csv'],
110
+ properties: {
111
+ csv: { type: 'string' },
112
+ delimiter: { type: 'string', default: delimiter },
113
+ parseNumbers: { type: 'boolean', default: true },
114
+ parseBooleans: { type: 'boolean', default: true },
115
+ useFastPath: { type: 'boolean', default: enableFastPath }
116
+ }
117
+ },
118
+ response: {
119
+ 200: {
120
+ type: 'object',
121
+ properties: {
122
+ success: { type: 'boolean' },
123
+ data: { type: 'array' },
124
+ stats: {
125
+ type: 'object',
126
+ properties: {
127
+ rows: { type: 'number' },
128
+ processingTime: { type: 'number' }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }, async (request: any, reply: any) => {
136
+ const startTime = Date.now();
137
+ const {
138
+ csv,
139
+ delimiter: reqDelimiter = delimiter,
140
+ parseNumbers = true,
141
+ parseBooleans = true,
142
+ useFastPath = enableFastPath
143
+ } = request.body;
144
+
145
+ try {
146
+ const json = await csvToJsonAsync(csv, {
147
+ delimiter: reqDelimiter,
148
+ parseNumbers,
149
+ parseBooleans,
150
+ useFastPath,
151
+ preventCsvInjection,
152
+ rfc4180Compliant
153
+ });
154
+
155
+ return {
156
+ success: true,
157
+ data: json,
158
+ stats: {
159
+ rows: json.length,
160
+ processingTime: Date.now() - startTime
161
+ }
162
+ };
163
+ } catch (error: any) {
164
+ reply.code(400);
165
+ return {
166
+ success: false,
167
+ error: error.message,
168
+ code: 'CSV_CONVERSION_ERROR'
169
+ };
170
+ }
171
+ });
172
+
173
+ // JSON to CSV endpoint
174
+ fastify.post(`${prefix}/json-to-csv`, {
175
+ schema: {
176
+ body: {
177
+ type: 'object',
178
+ required: ['json'],
179
+ properties: {
180
+ json: { type: 'array' },
181
+ delimiter: { type: 'string', default: delimiter },
182
+ includeHeaders: { type: 'boolean', default: true }
183
+ }
184
+ },
185
+ response: {
186
+ 200: {
187
+ type: 'object',
188
+ properties: {
189
+ success: { type: 'boolean' },
190
+ data: { type: 'string' },
191
+ stats: {
192
+ type: 'object',
193
+ properties: {
194
+ size: { type: 'number' },
195
+ processingTime: { type: 'number' }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }, async (request: any, reply: any) => {
203
+ const startTime = Date.now();
204
+ const {
205
+ json,
206
+ delimiter: reqDelimiter = delimiter,
207
+ includeHeaders = true
208
+ } = request.body;
209
+
210
+ try {
211
+ const csv = await jsonToCsvAsync(json, {
212
+ delimiter: reqDelimiter,
213
+ includeHeaders,
214
+ preventCsvInjection,
215
+ rfc4180Compliant
216
+ });
217
+
218
+ return {
219
+ success: true,
220
+ data: csv,
221
+ stats: {
222
+ size: Buffer.byteLength(csv),
223
+ processingTime: Date.now() - startTime
224
+ }
225
+ };
226
+ } catch (error: any) {
227
+ reply.code(400);
228
+ return {
229
+ success: false,
230
+ error: error.message,
231
+ code: 'JSON_CONVERSION_ERROR'
232
+ };
233
+ }
234
+ });
235
+
236
+ // Универсальный endpoint для автоматической конвертации
237
+ fastify.post(`${prefix}/auto`, {
238
+ schema: {
239
+ body: {
240
+ oneOf: [
241
+ { type: 'string' }, // CSV data
242
+ { type: 'array' }, // JSON array
243
+ { type: 'object' } // JSON object
244
+ ]
245
+ },
246
+ headers: {
247
+ type: 'object',
248
+ properties: {
249
+ 'content-type': {
250
+ type: 'string',
251
+ enum: ['application/json', 'text/csv', 'text/plain']
252
+ },
253
+ 'accept': {
254
+ type: 'string',
255
+ enum: ['application/json', 'text/csv']
256
+ }
257
+ }
258
+ },
259
+ querystring: {
260
+ type: 'object',
261
+ properties: {
262
+ format: { type: 'string', enum: ['json', 'csv'] },
263
+ delimiter: { type: 'string' }
264
+ }
265
+ }
266
+ }
267
+ }, async (request: any, reply: any) => {
268
+ const startTime = Date.now();
269
+ const contentType = request.headers['content-type'] || '';
270
+ const acceptHeader = request.headers['accept'] || 'application/json';
271
+ const { format, delimiter: queryDelimiter } = request.query;
272
+
273
+ const reqDelimiter = queryDelimiter || delimiter;
274
+
275
+ try {
276
+ let inputFormat = 'unknown';
277
+ const outputFormat = format || (acceptHeader.includes('text/csv') ? 'csv' : 'json');
278
+
279
+ // Определяем формат входных данных
280
+ if (contentType.includes('application/json') || Array.isArray(request.body)) {
281
+ inputFormat = 'json';
282
+ } else if (contentType.includes('text/csv') ||
283
+ contentType.includes('text/plain') ||
284
+ (typeof request.body === 'string' && request.body.includes(','))) {
285
+ inputFormat = 'csv';
286
+ }
287
+
288
+ if (inputFormat === 'unknown') {
289
+ reply.code(400);
290
+ return {
291
+ success: false,
292
+ error: 'Unable to determine input format',
293
+ code: 'UNKNOWN_FORMAT'
294
+ };
295
+ }
296
+
297
+ let result: any;
298
+ const stats: ProcessingStats = {
299
+ inputSize: 0,
300
+ outputSize: 0,
301
+ processingTime: 0,
302
+ conversion: `${inputFormat}→${outputFormat}`
303
+ };
304
+
305
+ const conversionOptions = {
306
+ delimiter: reqDelimiter,
307
+ useFastPath: enableFastPath,
308
+ preventCsvInjection,
309
+ rfc4180Compliant
310
+ };
311
+
312
+ if (inputFormat === 'json' && outputFormat === 'csv') {
313
+ const jsonData = Array.isArray(request.body) ? request.body : [request.body];
314
+ stats.inputSize = Buffer.byteLength(JSON.stringify(jsonData));
315
+
316
+ result = await jsonToCsvAsync(jsonData, {
317
+ ...conversionOptions,
318
+ includeHeaders: true
319
+ });
320
+ stats.outputSize = Buffer.byteLength(result);
321
+
322
+ reply.header('Content-Type', 'text/csv; charset=utf-8');
323
+
324
+ } else if (inputFormat === 'csv' && outputFormat === 'json') {
325
+ const csvData = typeof request.body === 'string' ? request.body : String(request.body);
326
+ stats.inputSize = Buffer.byteLength(csvData);
327
+
328
+ result = await csvToJsonAsync(csvData, {
329
+ ...conversionOptions,
330
+ parseNumbers: true,
331
+ parseBooleans: true
332
+ });
333
+ stats.outputSize = Buffer.byteLength(JSON.stringify(result));
334
+
335
+ reply.header('Content-Type', 'application/json; charset=utf-8');
336
+
337
+ } else {
338
+ // Нет необходимости в конвертации
339
+ result = request.body;
340
+ stats.conversion = 'none';
341
+ stats.inputSize = Buffer.byteLength(JSON.stringify(result));
342
+ stats.outputSize = stats.inputSize;
343
+ }
344
+
345
+ stats.processingTime = Date.now() - startTime;
346
+
347
+ return {
348
+ success: true,
349
+ data: result,
350
+ format: outputFormat,
351
+ inputFormat,
352
+ stats
353
+ };
354
+
355
+ } catch (error: any) {
356
+ reply.code(400);
357
+ return {
358
+ success: false,
359
+ error: error.message,
360
+ code: 'CONVERSION_ERROR',
361
+ details: (process.env as any)['NODE_ENV'] === 'development' ? error.stack : undefined
362
+ };
363
+ }
364
+ });
365
+
366
+ // Streaming endpoint для больших файлов
367
+ fastify.post(`${prefix}/stream`, {
368
+ schema: {
369
+ body: {
370
+ type: 'object',
371
+ required: ['direction'],
372
+ properties: {
373
+ direction: {
374
+ type: 'string',
375
+ enum: ['csv-to-json', 'json-to-csv']
376
+ },
377
+ delimiter: { type: 'string', default: delimiter }
378
+ }
379
+ }
380
+ }
381
+ }, async (request: any, reply: any) => {
382
+ const { direction, delimiter: reqDelimiter = delimiter } = request.body;
383
+
384
+ if (direction === 'csv-to-json') {
385
+ reply.header('Content-Type', 'application/x-ndjson');
386
+ reply.header('Transfer-Encoding', 'chunked');
387
+
388
+ // Здесь будет реализация streaming
389
+ // Пока возвращаем заглушку
390
+ reply.send(JSON.stringify({
391
+ success: false,
392
+ error: 'Streaming endpoint not implemented yet',
393
+ code: 'NOT_IMPLEMENTED'
394
+ }));
395
+
396
+ } else if (direction === 'json-to-csv') {
397
+ reply.header('Content-Type', 'text/csv');
398
+ reply.header('Transfer-Encoding', 'chunked');
399
+
400
+ // Здесь будет реализация streaming
401
+ reply.send('Streaming endpoint not implemented yet\n');
402
+ }
403
+ });
404
+
405
+ // Декоратор для прямого доступа к функциям конвертации
406
+ fastify.decorate('jtcsv', {
407
+ csvToJson: async (csv: string, options: Partial<CsvToJsonOptions> = {}) => {
408
+ return csvToJsonAsync(csv, {
409
+ delimiter,
410
+ useFastPath: enableFastPath,
411
+ preventCsvInjection,
412
+ rfc4180Compliant,
413
+ ...options
414
+ });
415
+ },
416
+
417
+ jsonToCsv: async (json: any[], options: Partial<JsonToCsvOptions> = {}) => {
418
+ return jsonToCsvAsync(json, {
419
+ delimiter,
420
+ preventCsvInjection,
421
+ rfc4180Compliant,
422
+ ...options
423
+ });
424
+ },
425
+
426
+ health: () => ({
427
+ service: 'jtcsv-fastify-plugin',
428
+ status: 'healthy',
429
+ version: '1.0.0'
430
+ })
431
+ });
432
+
433
+ console.log(`✅ JTCSV Fastify plugin зарегистрирован с префиксом: ${prefix}`);
434
+ }
435
+
436
+ // Экспортируем как Fastify plugin
437
+ export default fp(jtcsvFastifyPlugin, {
438
+ fastify: '4.x',
439
+ name: '@jtcsv/fastify'
440
+ });
441
+
442
+ // Экспортируем также как обычную функцию
443
+ export { jtcsvFastifyPlugin };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Hono plugin for jtcsv
3
+ * Provides middleware and response helpers for CSV processing in Hono applications
4
+ * @module plugins/hono
5
+ */
6
+
7
+ import { csvToJson, jsonToCsv } from '../../index-core';
8
+ import type { CsvToJsonOptions, JsonToCsvOptions } from '../../src/types';
9
+
10
+ /**
11
+ * Hono context type (simplified)
12
+ */
13
+ interface HonoContext {
14
+ req: {
15
+ text(): Promise<string>;
16
+ };
17
+ set(key: string, value: any): void;
18
+ }
19
+
20
+ /**
21
+ * Options for CSV middleware
22
+ */
23
+ export interface CsvMiddlewareOptions extends CsvToJsonOptions {
24
+ // Additional options specific to CSV middleware
25
+ }
26
+
27
+ /**
28
+ * Options for CSV response
29
+ */
30
+ export interface CsvResponseOptions extends JsonToCsvOptions {
31
+ // Additional options specific to CSV response
32
+ }
33
+
34
+ /**
35
+ * Normalize filename for CSV download
36
+ */
37
+ function normalizeFilename(filename?: string): string {
38
+ if (!filename || typeof filename !== 'string') {
39
+ return 'export.csv';
40
+ }
41
+ return filename.includes('.') ? filename : `${filename}.csv`;
42
+ }
43
+
44
+ /**
45
+ * CSV middleware for Hono
46
+ * Parses incoming CSV request bodies and attaches parsed data to context
47
+ *
48
+ * @example
49
+ * app.post('/upload', csvMiddleware(), async (c) => {
50
+ * const data = c.get('csv');
51
+ * return c.json({ success: true, data });
52
+ * });
53
+ */
54
+ export function csvMiddleware(options: CsvMiddlewareOptions = {}): any {
55
+ return async (c: HonoContext, next: () => Promise<void>) => {
56
+ try {
57
+ const csvText = await c.req.text();
58
+ const rows = await csvToJson(csvText, options);
59
+ c.set('csv', rows);
60
+ await next();
61
+ } catch (error) {
62
+ // Re-throw error for Hono error handling
63
+ throw error;
64
+ }
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Async CSV middleware for Hono
70
+ * Uses async/await for better performance with large files
71
+ */
72
+ export function asyncCsvMiddleware(options: CsvMiddlewareOptions = {}): any {
73
+ return async (c: HonoContext, next: () => Promise<void>) => {
74
+ try {
75
+ const csvText = await c.req.text();
76
+ const rows = await csvToJson(csvText, options);
77
+ c.set('csv', rows);
78
+ await next();
79
+ } catch (error) {
80
+ throw error;
81
+ }
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Creates a CSV response for Hono
87
+ * Converts data to CSV and returns appropriate headers
88
+ *
89
+ * @example
90
+ * app.get('/export', async (c) => {
91
+ * const data = await getData();
92
+ * const response = createCsvResponse(data, 'export.csv');
93
+ * return new Response(response.csv, { headers: response.headers });
94
+ * });
95
+ */
96
+ export function createCsvResponse(
97
+ data: any,
98
+ filename: string = 'export.csv',
99
+ options: CsvResponseOptions = {}
100
+ ): { csv: string; headers: Record<string, string> } {
101
+ const safeName = normalizeFilename(filename);
102
+ const rows = Array.isArray(data) ? data : [data];
103
+ const csv = jsonToCsv(rows, options);
104
+
105
+ return {
106
+ csv,
107
+ headers: {
108
+ 'Content-Type': 'text/csv; charset=utf-8',
109
+ 'Content-Disposition': `attachment; filename="${safeName}"`
110
+ }
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Async version of createCsvResponse
116
+ * Uses async/await for better performance with large files
117
+ */
118
+ export async function createAsyncCsvResponse(
119
+ data: any,
120
+ filename: string = 'export.csv',
121
+ options: CsvResponseOptions = {}
122
+ ): Promise<{ csv: string; headers: Record<string, string> }> {
123
+ const safeName = normalizeFilename(filename);
124
+ const rows = Array.isArray(data) ? data : [data];
125
+ const csv = await jsonToCsv(rows, options);
126
+
127
+ return {
128
+ csv,
129
+ headers: {
130
+ 'Content-Type': 'text/csv; charset=utf-8',
131
+ 'Content-Disposition': `attachment; filename="${safeName}"`
132
+ }
133
+ };
134
+ }
135
+
136
+ /**
137
+ * CSV response helper for Hono
138
+ * Returns a Response object with CSV data
139
+ *
140
+ * @example
141
+ * app.get('/export', async (c) => {
142
+ * const data = await getData();
143
+ * return csvResponse(c, data, 'export.csv');
144
+ * });
145
+ */
146
+ export function csvResponse(
147
+ data: any,
148
+ filename: string = 'export.csv',
149
+ options: CsvResponseOptions = {}
150
+ ): Response {
151
+ const { csv, headers } = createCsvResponse(data, filename, options);
152
+ return new Response(csv, { headers });
153
+ }
154
+
155
+ /**
156
+ * Async CSV response helper for Hono
157
+ * Returns a Response object with CSV data using async processing
158
+ */
159
+ export async function asyncCsvResponse(
160
+ data: any,
161
+ filename: string = 'export.csv',
162
+ options: CsvResponseOptions = {}
163
+ ): Promise<Response> {
164
+ const { csv, headers } = await createAsyncCsvResponse(data, filename, options);
165
+ return new Response(csv, { headers });
166
+ }
167
+
168
+ /**
169
+ * CSV parsing endpoint helper
170
+ * Creates a route handler that parses CSV and returns JSON
171
+ *
172
+ * @example
173
+ * app.post('/parse', csvParseEndpoint());
174
+ */
175
+ export function csvParseEndpoint(options: CsvMiddlewareOptions = {}): any {
176
+ return async (c: HonoContext) => {
177
+ try {
178
+ const csvText = await c.req.text();
179
+ const rows = await csvToJson(csvText, options);
180
+ return new Response(JSON.stringify(rows), {
181
+ headers: { 'Content-Type': 'application/json' }
182
+ });
183
+ } catch (error) {
184
+ return new Response(
185
+ JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }),
186
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
187
+ );
188
+ }
189
+ };
190
+ }
191
+
192
+ /**
193
+ * CSV download endpoint helper
194
+ * Creates a route handler that returns CSV data
195
+ *
196
+ * @example
197
+ * app.get('/download', csvDownloadEndpoint(async () => await getData()));
198
+ */
199
+ export function csvDownloadEndpoint(
200
+ dataProvider: () => Promise<any> | any,
201
+ filename: string = 'export.csv',
202
+ options: CsvResponseOptions = {}
203
+ ): any {
204
+ return async (c: HonoContext) => {
205
+ try {
206
+ const data = await (typeof dataProvider === 'function' ? dataProvider() : dataProvider);
207
+ return asyncCsvResponse(data, filename, options);
208
+ } catch (error) {
209
+ return new Response(
210
+ JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }),
211
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
212
+ );
213
+ }
214
+ };
215
+ }
216
+
217
+ export default {
218
+ csvMiddleware,
219
+ asyncCsvMiddleware,
220
+ createCsvResponse,
221
+ createAsyncCsvResponse,
222
+ csvResponse,
223
+ asyncCsvResponse,
224
+ csvParseEndpoint,
225
+ csvDownloadEndpoint,
226
+ };