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,260 @@
1
+ /**
2
+ * Remix plugin for jtcsv
3
+ * Provides utilities for CSV parsing and generation in Remix applications
4
+ * @module plugins/remix
5
+ */
6
+
7
+ import { csvToJson, jsonToCsv } from '../../index-core';
8
+ import type { CsvToJsonOptions, JsonToCsvOptions } from '../../src/types';
9
+
10
+ /**
11
+ * Remix Request type (simplified)
12
+ */
13
+ interface RemixRequest {
14
+ formData(): Promise<FormData>;
15
+ }
16
+
17
+ /**
18
+ * Options for CSV parsing from form data
19
+ */
20
+ export interface ParseFormDataOptions extends CsvToJsonOptions {
21
+ /** Field name containing the CSV file (default: 'file') */
22
+ fieldName?: string;
23
+ }
24
+
25
+ /**
26
+ * Options for CSV response generation
27
+ */
28
+ export interface GenerateCsvResponseOptions extends JsonToCsvOptions {
29
+ // Additional options specific to CSV response
30
+ }
31
+
32
+ /**
33
+ * Normalize filename for CSV download
34
+ */
35
+ function normalizeFilename(filename?: string): string {
36
+ if (!filename || typeof filename !== 'string') {
37
+ return 'export.csv';
38
+ }
39
+ return filename.includes('.') ? filename : `${filename}.csv`;
40
+ }
41
+
42
+ /**
43
+ * Extract CSV text from FormData
44
+ */
45
+ async function extractCsvText(formData: FormData, fieldName: string): Promise<string | null> {
46
+ if (formData.has(fieldName)) {
47
+ const value = formData.get(fieldName);
48
+ if (value && typeof (value as any).text === 'function') {
49
+ return await (value as any).text();
50
+ }
51
+ if (value !== null) {
52
+ return String(value);
53
+ }
54
+ }
55
+
56
+ for (const value of formData.values()) {
57
+ if (value && typeof (value as any).text === 'function') {
58
+ return await (value as any).text();
59
+ }
60
+ }
61
+
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Parse CSV from Remix form data
67
+ *
68
+ * @example
69
+ * // In a Remix action:
70
+ * export async function action({ request }: ActionArgs) {
71
+ * const data = await parseFormData(request, { delimiter: ',' });
72
+ * return json({ success: true, data });
73
+ * }
74
+ */
75
+ export async function parseFormData(
76
+ request: RemixRequest,
77
+ options: ParseFormDataOptions = {}
78
+ ): Promise<any[]> {
79
+ if (!request || typeof request.formData !== 'function') {
80
+ throw new Error('parseFormData expects a Remix Request with formData()');
81
+ }
82
+
83
+ const { fieldName = 'file', ...csvOptions } = options;
84
+ const formData = await request.formData();
85
+ const csvText = await extractCsvText(formData, fieldName);
86
+
87
+ if (!csvText) {
88
+ throw new Error('No CSV file or field found in form data');
89
+ }
90
+
91
+ return await csvToJson(csvText, csvOptions);
92
+ }
93
+
94
+ /**
95
+ * Async version of parseFormData with better error handling
96
+ */
97
+ export async function parseFormDataAsync(
98
+ request: RemixRequest,
99
+ options: ParseFormDataOptions = {}
100
+ ): Promise<any[]> {
101
+ try {
102
+ return await parseFormData(request, options);
103
+ } catch (error) {
104
+ throw new Error(`Failed to parse CSV from form data: ${error instanceof Error ? error.message : 'Unknown error'}`);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Generate a CSV response for Remix
110
+ *
111
+ * @example
112
+ * // In a Remix loader:
113
+ * export async function loader() {
114
+ * const data = await getData();
115
+ * return generateCsvResponse(data, 'export.csv');
116
+ * }
117
+ */
118
+ export function generateCsvResponse(
119
+ data: any,
120
+ filename: string = 'export.csv',
121
+ options: GenerateCsvResponseOptions = {}
122
+ ): Response {
123
+ const safeName = normalizeFilename(filename);
124
+ const rows = Array.isArray(data) ? data : [data];
125
+ const csv = jsonToCsv(rows, options);
126
+
127
+ return new Response(csv, {
128
+ headers: {
129
+ 'Content-Type': 'text/csv; charset=utf-8',
130
+ 'Content-Disposition': `attachment; filename="${safeName}"`
131
+ }
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Async version of generateCsvResponse
137
+ */
138
+ export async function generateAsyncCsvResponse(
139
+ data: any,
140
+ filename: string = 'export.csv',
141
+ options: GenerateCsvResponseOptions = {}
142
+ ): Promise<Response> {
143
+ const safeName = normalizeFilename(filename);
144
+ const rows = Array.isArray(data) ? data : [data];
145
+ const csv = await jsonToCsv(rows, options);
146
+
147
+ return new Response(csv, {
148
+ headers: {
149
+ 'Content-Type': 'text/csv; charset=utf-8',
150
+ 'Content-Disposition': `attachment; filename="${safeName}"`
151
+ }
152
+ });
153
+ }
154
+
155
+ /**
156
+ * CSV loader helper for Remix
157
+ * Creates a loader that returns CSV data
158
+ *
159
+ * @example
160
+ * export const loader = createCsvLoader(async () => {
161
+ * return await getData();
162
+ * }, 'data.csv');
163
+ */
164
+ export function createCsvLoader(
165
+ dataLoader: () => Promise<any> | any,
166
+ filename: string = 'export.csv',
167
+ options: GenerateCsvResponseOptions = {}
168
+ ): () => Promise<Response> {
169
+ return async () => {
170
+ try {
171
+ const data = await (typeof dataLoader === 'function' ? dataLoader() : dataLoader);
172
+ return generateCsvResponse(data, filename, options);
173
+ } catch (error) {
174
+ return new Response(
175
+ JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }),
176
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
177
+ );
178
+ }
179
+ };
180
+ }
181
+
182
+ /**
183
+ * CSV action helper for Remix
184
+ * Creates an action that parses CSV from form data
185
+ *
186
+ * @example
187
+ * export const action = createCsvAction({ delimiter: ',' });
188
+ */
189
+ export function createCsvAction(
190
+ options: ParseFormDataOptions = {}
191
+ ): (args: { request: RemixRequest }) => Promise<Response> {
192
+ return async ({ request }) => {
193
+ try {
194
+ const data = await parseFormData(request, options);
195
+ return new Response(
196
+ JSON.stringify({ success: true, data }),
197
+ { headers: { 'Content-Type': 'application/json' } }
198
+ );
199
+ } catch (error) {
200
+ return new Response(
201
+ JSON.stringify({
202
+ success: false,
203
+ error: error instanceof Error ? error.message : 'Unknown error'
204
+ }),
205
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
206
+ );
207
+ }
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Multi-part form data CSV parser
213
+ * Handles multiple CSV files in a single form
214
+ */
215
+ export async function parseMultiPartFormData(
216
+ request: RemixRequest,
217
+ options: ParseFormDataOptions & { multiple?: boolean } = {}
218
+ ): Promise<any[] | any[][]> {
219
+ const { multiple = false, ...csvOptions } = options;
220
+ const formData = await request.formData();
221
+ const results: any[][] = [];
222
+
223
+ for (const [fieldName, value] of formData.entries()) {
224
+ if (value && typeof (value as any).text === 'function') {
225
+ const csvText = await (value as any).text();
226
+ if (csvText) {
227
+ const parsed = await csvToJson(csvText, csvOptions);
228
+ results.push(parsed);
229
+ }
230
+ }
231
+ }
232
+
233
+ if (!multiple && results.length > 0) {
234
+ return results[0];
235
+ }
236
+
237
+ return results;
238
+ }
239
+
240
+ /**
241
+ * CSV export utility for Remix routes
242
+ */
243
+ export function csvExport(
244
+ data: any,
245
+ filename?: string,
246
+ options?: GenerateCsvResponseOptions
247
+ ): () => Promise<Response> {
248
+ return () => generateAsyncCsvResponse(data, filename, options);
249
+ }
250
+
251
+ export default {
252
+ parseFormData,
253
+ parseFormDataAsync,
254
+ generateCsvResponse,
255
+ generateAsyncCsvResponse,
256
+ createCsvLoader,
257
+ createCsvAction,
258
+ parseMultiPartFormData,
259
+ csvExport,
260
+ };
@@ -21,7 +21,7 @@ async function parseCsv(request, options = {}) {
21
21
  const value = formData.get(fieldName) ?? formData.values().next().value;
22
22
  if (value && typeof value.text === 'function') {
23
23
  csvText = await value.text();
24
- } else if (value != null) {
24
+ } else if (value !== null) {
25
25
  csvText = String(value);
26
26
  }
27
27
  } else {
@@ -0,0 +1,301 @@
1
+ /**
2
+ * SvelteKit plugin for jtcsv
3
+ * Provides utilities for CSV parsing and generation in SvelteKit applications
4
+ * @module plugins/sveltekit
5
+ */
6
+
7
+ import { csvToJson, jsonToCsv } from '../../index-core';
8
+ import type { CsvToJsonOptions, JsonToCsvOptions } from '../../src/types';
9
+
10
+ /**
11
+ * SvelteKit Request type (simplified)
12
+ */
13
+ interface SvelteKitRequest {
14
+ text(): Promise<string>;
15
+ formData(): Promise<FormData>;
16
+ headers?: {
17
+ get(name: string): string | null;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Options for CSV parsing from request
23
+ */
24
+ export interface ParseCsvOptions extends CsvToJsonOptions {
25
+ /** Field name containing the CSV file (default: 'file') */
26
+ fieldName?: string;
27
+ /** Force form data parsing (default: auto-detect) */
28
+ formData?: boolean;
29
+ }
30
+
31
+ /**
32
+ * Options for CSV response generation
33
+ */
34
+ export interface GenerateCsvOptions extends JsonToCsvOptions {
35
+ // Additional options specific to CSV response
36
+ }
37
+
38
+ /**
39
+ * Normalize filename for CSV download
40
+ */
41
+ function normalizeFilename(filename?: string): string {
42
+ if (!filename || typeof filename !== 'string') {
43
+ return 'export.csv';
44
+ }
45
+ return filename.includes('.') ? filename : `${filename}.csv`;
46
+ }
47
+
48
+ /**
49
+ * Extract CSV text from FormData
50
+ */
51
+ async function extractCsvText(formData: FormData, fieldName: string): Promise<string | null> {
52
+ if (formData.has(fieldName)) {
53
+ const value = formData.get(fieldName);
54
+ if (value && typeof (value as any).text === 'function') {
55
+ return await (value as any).text();
56
+ }
57
+ if (value !== null) {
58
+ return String(value);
59
+ }
60
+ }
61
+
62
+ for (const value of formData.values()) {
63
+ if (value && typeof (value as any).text === 'function') {
64
+ return await (value as any).text();
65
+ }
66
+ }
67
+
68
+ return null;
69
+ }
70
+
71
+ /**
72
+ * Parse CSV from SvelteKit request
73
+ *
74
+ * @example
75
+ * // In a SvelteKit endpoint:
76
+ * export async function POST({ request }) {
77
+ * const data = await parseCsv(request, { delimiter: ',' });
78
+ * return json({ success: true, data });
79
+ * }
80
+ */
81
+ export async function parseCsv(
82
+ request: SvelteKitRequest,
83
+ options: ParseCsvOptions = {}
84
+ ): Promise<any[]> {
85
+ if (!request || typeof request.text !== 'function') {
86
+ throw new Error('parseCsv expects a Request instance');
87
+ }
88
+
89
+ const { fieldName = 'file', formData: forceFormData, ...csvOptions } = options;
90
+ const contentType = request.headers?.get?.('content-type') || '';
91
+ let csvText: string | null = null;
92
+
93
+ if (forceFormData || contentType.includes('multipart/form-data')) {
94
+ const formData = await request.formData();
95
+ csvText = await extractCsvText(formData, fieldName);
96
+ } else {
97
+ csvText = await request.text();
98
+ }
99
+
100
+ if (!csvText) {
101
+ throw new Error('No CSV payload found in request');
102
+ }
103
+
104
+ return await csvToJson(csvText, csvOptions);
105
+ }
106
+
107
+ /**
108
+ * Async version of parseCsv with better error handling
109
+ */
110
+ export async function parseCsvAsync(
111
+ request: SvelteKitRequest,
112
+ options: ParseCsvOptions = {}
113
+ ): Promise<any[]> {
114
+ try {
115
+ return await parseCsv(request, options);
116
+ } catch (error) {
117
+ throw new Error(`Failed to parse CSV from request: ${error instanceof Error ? error.message : 'Unknown error'}`);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Generate a CSV response for SvelteKit
123
+ *
124
+ * @example
125
+ * // In a SvelteKit endpoint:
126
+ * export async function GET() {
127
+ * const data = await getData();
128
+ * return generateCsv(data, 'export.csv');
129
+ * }
130
+ */
131
+ export function generateCsv(
132
+ data: any,
133
+ filename: string = 'export.csv',
134
+ options: GenerateCsvOptions = {}
135
+ ): Response {
136
+ const safeName = normalizeFilename(filename);
137
+ const rows = Array.isArray(data) ? data : [data];
138
+ const csv = jsonToCsv(rows, options);
139
+
140
+ return new Response(csv, {
141
+ headers: {
142
+ 'Content-Type': 'text/csv; charset=utf-8',
143
+ 'Content-Disposition': `attachment; filename="${safeName}"`
144
+ }
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Async version of generateCsv
150
+ */
151
+ export async function generateAsyncCsv(
152
+ data: any,
153
+ filename: string = 'export.csv',
154
+ options: GenerateCsvOptions = {}
155
+ ): Promise<Response> {
156
+ const safeName = normalizeFilename(filename);
157
+ const rows = Array.isArray(data) ? data : [data];
158
+ const csv = await jsonToCsv(rows, options);
159
+
160
+ return new Response(csv, {
161
+ headers: {
162
+ 'Content-Type': 'text/csv; charset=utf-8',
163
+ 'Content-Disposition': `attachment; filename="${safeName}"`
164
+ }
165
+ });
166
+ }
167
+
168
+ /**
169
+ * CSV loader helper for SvelteKit
170
+ * Creates a loader that returns CSV data
171
+ *
172
+ * @example
173
+ * export const loader = createCsvLoader(async () => {
174
+ * return await getData();
175
+ * }, 'data.csv');
176
+ */
177
+ export function createCsvLoader(
178
+ dataLoader: () => Promise<any> | any,
179
+ filename: string = 'export.csv',
180
+ options: GenerateCsvOptions = {}
181
+ ): () => Promise<Response> {
182
+ return async () => {
183
+ try {
184
+ const data = await (typeof dataLoader === 'function' ? dataLoader() : dataLoader);
185
+ return generateCsv(data, filename, options);
186
+ } catch (error) {
187
+ return new Response(
188
+ JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' }),
189
+ { status: 500, headers: { 'Content-Type': 'application/json' } }
190
+ );
191
+ }
192
+ };
193
+ }
194
+
195
+ /**
196
+ * CSV action helper for SvelteKit
197
+ * Creates an action that parses CSV from form data
198
+ *
199
+ * @example
200
+ * export const action = createCsvAction({ delimiter: ',' });
201
+ */
202
+ export function createCsvAction(
203
+ options: ParseCsvOptions = {}
204
+ ): (args: { request: SvelteKitRequest }) => Promise<Response> {
205
+ return async ({ request }) => {
206
+ try {
207
+ const data = await parseCsv(request, options);
208
+ return new Response(
209
+ JSON.stringify({ success: true, data }),
210
+ { headers: { 'Content-Type': 'application/json' } }
211
+ );
212
+ } catch (error) {
213
+ return new Response(
214
+ JSON.stringify({
215
+ success: false,
216
+ error: error instanceof Error ? error.message : 'Unknown error'
217
+ }),
218
+ { status: 400, headers: { 'Content-Type': 'application/json' } }
219
+ );
220
+ }
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Multi-part form data CSV parser
226
+ * Handles multiple CSV files in a single request
227
+ */
228
+ export async function parseMultiPartCsv(
229
+ request: SvelteKitRequest,
230
+ options: ParseCsvOptions & { multiple?: boolean } = {}
231
+ ): Promise<any[] | any[][]> {
232
+ const { multiple = false, ...csvOptions } = options;
233
+ const formData = await request.formData();
234
+ const results: any[][] = [];
235
+
236
+ for (const [fieldName, value] of formData.entries()) {
237
+ if (value && typeof (value as any).text === 'function') {
238
+ const csvText = await (value as any).text();
239
+ if (csvText) {
240
+ const parsed = await csvToJson(csvText, csvOptions);
241
+ results.push(parsed);
242
+ }
243
+ }
244
+ }
245
+
246
+ if (!multiple && results.length > 0) {
247
+ return results[0];
248
+ }
249
+
250
+ return results;
251
+ }
252
+
253
+ /**
254
+ * CSV export utility for SvelteKit routes
255
+ */
256
+ export function csvExport(
257
+ data: any,
258
+ filename?: string,
259
+ options?: GenerateCsvOptions
260
+ ): () => Promise<Response> {
261
+ return () => generateAsyncCsv(data, filename, options);
262
+ }
263
+
264
+ /**
265
+ * SvelteKit server hook for CSV processing
266
+ * Can be used in hooks.server.js/ts
267
+ */
268
+ export function createCsvHook(options: {
269
+ parseOptions?: ParseCsvOptions;
270
+ generateOptions?: GenerateCsvOptions;
271
+ } = {}) {
272
+ return {
273
+ async handle({ event, resolve }: { event: any; resolve: any }) {
274
+ // Add CSV utilities to event.locals
275
+ event.locals.csv = {
276
+ parse: (request: SvelteKitRequest, opts?: ParseCsvOptions) =>
277
+ parseCsv(request, { ...options.parseOptions, ...opts }),
278
+ parseAsync: (request: SvelteKitRequest, opts?: ParseCsvOptions) =>
279
+ parseCsvAsync(request, { ...options.parseOptions, ...opts }),
280
+ generate: (data: any, filename?: string, opts?: GenerateCsvOptions) =>
281
+ generateCsv(data, filename, { ...options.generateOptions, ...opts }),
282
+ generateAsync: (data: any, filename?: string, opts?: GenerateCsvOptions) =>
283
+ generateAsyncCsv(data, filename, { ...options.generateOptions, ...opts }),
284
+ };
285
+
286
+ return resolve(event);
287
+ }
288
+ };
289
+ }
290
+
291
+ export default {
292
+ parseCsv,
293
+ parseCsvAsync,
294
+ generateCsv,
295
+ generateAsyncCsv,
296
+ createCsvLoader,
297
+ createCsvAction,
298
+ parseMultiPartCsv,
299
+ csvExport,
300
+ createCsvHook,
301
+ };