jtcsv 2.2.8 → 3.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 (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
package/json-to-csv.js DELETED
@@ -1,526 +0,0 @@
1
- /**
2
- * JSON to CSV Converter - Node.js Module
3
- *
4
- * A lightweight, efficient module for converting JSON data to CSV format
5
- * with proper escaping and formatting for Excel compatibility.
6
- *
7
- * @module json-to-csv
8
- */
9
-
10
- const {
11
- ValidationError,
12
- SecurityError,
13
- FileSystemError,
14
- LimitError,
15
- ConfigurationError,
16
- safeExecute
17
- } = require('./errors');
18
-
19
- // Add schema validator import
20
- const { createSchemaValidators } = require('./src/utils/schema-validator');
21
- /**
22
- * Validates input data and options
23
- * @private
24
- */
25
- function validateInput(data, options) {
26
- // Validate data
27
- if (!Array.isArray(data)) {
28
- throw new ValidationError('Input data must be an array');
29
- }
30
-
31
- // Validate options
32
- if (options && typeof options !== 'object') {
33
- throw new ConfigurationError('Options must be an object');
34
- }
35
-
36
- // Validate delimiter
37
- if (options?.delimiter && typeof options.delimiter !== 'string') {
38
- throw new ConfigurationError('Delimiter must be a string');
39
- }
40
-
41
- if (options?.delimiter && options.delimiter.length !== 1) {
42
- throw new ConfigurationError('Delimiter must be a single character');
43
- }
44
-
45
- // Validate renameMap
46
- if (options?.renameMap && typeof options.renameMap !== 'object') {
47
- throw new ConfigurationError('renameMap must be an object');
48
- }
49
-
50
- // Validate maxRecords
51
- if (options && options.maxRecords !== undefined) {
52
- if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
53
- throw new ConfigurationError('maxRecords must be a positive number');
54
- }
55
- }
56
-
57
- // Validate preventCsvInjection
58
- if (options?.preventCsvInjection !== undefined && typeof options.preventCsvInjection !== 'boolean') {
59
- throw new ConfigurationError('preventCsvInjection must be a boolean');
60
- }
61
-
62
- // Validate rfc4180Compliant
63
- if (options?.rfc4180Compliant !== undefined && typeof options.rfc4180Compliant !== 'boolean') {
64
- throw new ConfigurationError('rfc4180Compliant must be a boolean');
65
- }
66
-
67
- // Validate schema
68
- if (options?.schema && typeof options.schema !== 'object') {
69
- throw new ConfigurationError('schema must be an object');
70
- }
71
-
72
- return true;
73
- }
74
-
75
- /**
76
- * Converts JSON data to CSV format
77
- *
78
- * @param {Array<Object>} data - Array of objects to convert to CSV
79
- * @param {Object} [options] - Configuration options
80
- * @param {string} [options.delimiter=';'] - CSV delimiter character
81
- * @param {boolean} [options.includeHeaders=true] - Whether to include headers row
82
- * @param {Object} [options.renameMap={}] - Map for renaming column headers (oldKey: newKey)
83
- * @param {Object} [options.template={}] - Template object to ensure consistent column order
84
- * @param {number} [options.maxRecords] - Maximum number of records to process (optional, no limit by default)
85
- * @param {boolean} [options.preventCsvInjection=true] - Prevent CSV injection attacks by escaping formulas
86
- * @param {boolean} [options.rfc4180Compliant=true] - Ensure RFC 4180 compliance (proper quoting, line endings)
87
- * @param {Object} [options.schema] - JSON schema for data validation and formatting
88
- * @returns {string} CSV formatted string
89
- *
90
- * @example
91
- * const jsonToCsv = require('./json-to-csv');
92
- *
93
- * const data = [
94
- * { id: 1, name: 'John', email: 'john@example.com' },
95
- * { id: 2, name: 'Jane', email: 'jane@example.com' }
96
- * ];
97
- *
98
- * const csv = jsonToCsv(data, {
99
- * delimiter: ',',
100
- * renameMap: { id: 'ID', name: 'Full Name' },
101
- * preventCsvInjection: true,
102
- * rfc4180Compliant: true
103
- * });
104
- */
105
- function jsonToCsv(data, options = {}) {
106
- return safeExecute(() => {
107
- // Validate input
108
- validateInput(data, options);
109
-
110
- const opts = options && typeof options === 'object' ? options : {};
111
-
112
- const {
113
- delimiter = ';',
114
- includeHeaders = true,
115
- renameMap = {},
116
- template = {},
117
- maxRecords,
118
- preventCsvInjection = true,
119
- rfc4180Compliant = true,
120
- schema = null
121
- } = opts;
122
-
123
- // Initialize schema validators if schema is provided
124
- let schemaValidators = null;
125
- if (schema) {
126
- schemaValidators = createSchemaValidators(schema);
127
- }
128
-
129
- // Handle empty data
130
- if (data.length === 0) {
131
- return '';
132
- }
133
-
134
- // Show warning for large datasets (optional limit)
135
- if (data.length > 1000000 && !maxRecords && process.env.NODE_ENV !== 'test') {
136
- console.warn(
137
- '⚠️ Warning: Processing >1M records in memory may be slow.\n' +
138
- '💡 Consider processing data in batches or using streaming for large files.\n' +
139
- '📊 Current size: ' + data.length.toLocaleString() + ' records\n' +
140
- '🔧 Tip: Use { maxRecords: N } option to set a custom limit if needed.'
141
- );
142
- }
143
-
144
- // Apply optional record limit if specified
145
- if (maxRecords && data.length > maxRecords) {
146
- throw new LimitError(
147
- `Data size exceeds maximum limit of ${maxRecords} records`,
148
- maxRecords,
149
- data.length
150
- );
151
- }
152
-
153
- // Get all unique keys from all objects with minimal allocations.
154
- const allKeys = new Set();
155
- const originalKeys = [];
156
- for (let i = 0; i < data.length; i++) {
157
- const item = data[i];
158
- if (!item || typeof item !== 'object') {
159
- continue;
160
- }
161
- for (const key in item) {
162
- if (Object.prototype.hasOwnProperty.call(item, key) && !allKeys.has(key)) {
163
- allKeys.add(key);
164
- originalKeys.push(key);
165
- }
166
- }
167
- }
168
-
169
- const hasRenameMap = Object.keys(renameMap).length > 0;
170
- const hasTemplate = Object.keys(template).length > 0;
171
-
172
- // Apply rename map to create header names.
173
- let headers = originalKeys;
174
- let reverseRenameMap = null;
175
- if (hasRenameMap) {
176
- headers = new Array(originalKeys.length);
177
- reverseRenameMap = {};
178
- for (let i = 0; i < originalKeys.length; i++) {
179
- const key = originalKeys[i];
180
- const header = renameMap[key] || key;
181
- headers[i] = header;
182
- reverseRenameMap[header] = key;
183
- }
184
- }
185
-
186
- // Apply template ordering if provided.
187
- let finalHeaders = headers;
188
- if (hasTemplate) {
189
- const templateKeys = Object.keys(template);
190
- const templateHeaders = hasRenameMap
191
- ? templateKeys.map(key => renameMap[key] || key)
192
- : templateKeys;
193
- const templateHeaderSet = new Set(templateHeaders);
194
- const extraHeaders = [];
195
- for (let i = 0; i < headers.length; i++) {
196
- const header = headers[i];
197
- if (!templateHeaderSet.has(header)) {
198
- extraHeaders.push(header);
199
- }
200
- }
201
- finalHeaders = templateHeaders.concat(extraHeaders);
202
- }
203
-
204
- const finalKeys = new Array(finalHeaders.length);
205
- if (hasRenameMap) {
206
- for (let i = 0; i < finalHeaders.length; i++) {
207
- const header = finalHeaders[i];
208
- finalKeys[i] = reverseRenameMap[header] || header;
209
- }
210
- } else {
211
- for (let i = 0; i < finalHeaders.length; i++) {
212
- finalKeys[i] = finalHeaders[i];
213
- }
214
- }
215
-
216
- /**
217
- * Escapes a value for CSV format with CSV injection protection
218
- *
219
- * @private
220
- * @param {*} value - The value to escape
221
- * @returns {string} Escaped CSV value
222
- */
223
- const quoteRegex = /"/g;
224
- const delimiterCode = delimiter.charCodeAt(0);
225
-
226
- const escapeValue = (value) => {
227
- if (value === null || value === undefined || value === '') {
228
- return '';
229
- }
230
-
231
- let stringValue = value;
232
- if (typeof stringValue !== 'string') {
233
- stringValue = String(stringValue);
234
- }
235
-
236
- // CSV Injection protection - escape formulas if enabled
237
- let escapedValue = stringValue;
238
- if (preventCsvInjection) {
239
- const firstCharCode = stringValue.charCodeAt(0);
240
- if (firstCharCode === 61 || firstCharCode === 43 || firstCharCode === 45 || firstCharCode === 64) {
241
- escapedValue = "'" + stringValue;
242
- }
243
- }
244
-
245
- let needsQuoting = false;
246
- let hasQuote = false;
247
- for (let i = 0; i < escapedValue.length; i++) {
248
- const code = escapedValue.charCodeAt(i);
249
- if (code === 34) {
250
- hasQuote = true;
251
- needsQuoting = true;
252
- } else if (code === delimiterCode || code === 10 || code === 13) {
253
- needsQuoting = true;
254
- }
255
- }
256
-
257
- if (needsQuoting) {
258
- const quotedValue = hasQuote ? escapedValue.replace(quoteRegex, '""') : escapedValue;
259
- return `"${quotedValue}"`;
260
- }
261
-
262
- return escapedValue;
263
- };
264
-
265
- // Build CSV rows.
266
- const rows = [];
267
- const columnCount = finalHeaders.length;
268
-
269
- // Add headers row if requested.
270
- if (includeHeaders && columnCount > 0) {
271
- rows.push(finalHeaders.join(delimiter));
272
- }
273
-
274
- // Add data rows.
275
- const rowValues = new Array(columnCount);
276
- for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
277
- const item = data[rowIndex];
278
- if (!item || typeof item !== 'object') {
279
- continue;
280
- }
281
-
282
- // Apply schema validation and formatting if schema is provided
283
- let processedItem = item;
284
- if (schemaValidators) {
285
- processedItem = { ...item };
286
- for (const [key, validator] of Object.entries(schemaValidators)) {
287
- if (key in processedItem) {
288
- const value = processedItem[key];
289
- // Validate value
290
- if (!validator.validate(value)) {
291
- throw new ValidationError(
292
- `Row ${rowIndex + 1}: Value for field "${key}" does not match schema`
293
- );
294
- }
295
- // Format value if formatter exists
296
- if (validator.format) {
297
- processedItem[key] = validator.format(value);
298
- }
299
- } else if (validator.required) {
300
- throw new ValidationError(
301
- `Row ${rowIndex + 1}: Required field "${key}" is missing`
302
- );
303
- }
304
- }
305
- }
306
-
307
- for (let i = 0; i < columnCount; i++) {
308
- rowValues[i] = escapeValue(processedItem[finalKeys[i]]);
309
- }
310
-
311
- rows.push(rowValues.join(delimiter));
312
- }
313
-
314
- // RFC 4180: Each record is located on a separate line, delimited by a line break (CRLF).
315
- const lineEnding = rfc4180Compliant ? '\r\n' : '\n';
316
- return rows.join(lineEnding);
317
- }, 'PARSE_FAILED', { function: 'jsonToCsv' });
318
- }
319
-
320
- /**
321
- * Deeply unwraps nested objects and arrays to extract primitive values
322
- *
323
- * @param {*} value - Value to unwrap
324
- * @param {number} [depth=0] - Current recursion depth
325
- * @param {number} [maxDepth=5] - Maximum recursion depth
326
- * @param {Set} [visited=new Set()] - Set of visited objects to detect circular references
327
- * @returns {string} Unwrapped string value
328
- *
329
- * @private
330
- */
331
- function deepUnwrap(value, depth = 0, maxDepth = 5, visited = new Set()) {
332
- // Check depth before processing
333
- if (depth >= maxDepth) {
334
- return '[Too Deep]';
335
- }
336
- if (value === null || value === undefined) {
337
- return '';
338
- }
339
-
340
- // Handle circular references - return early for circular refs
341
- if (typeof value === 'object') {
342
- if (visited.has(value)) {
343
- return '[Circular Reference]';
344
- }
345
- visited.add(value);
346
- }
347
-
348
- // Handle arrays - join all elements
349
- if (Array.isArray(value)) {
350
- if (value.length === 0) {
351
- return '';
352
- }
353
- const unwrappedItems = value.map(item =>
354
- deepUnwrap(item, depth + 1, maxDepth, visited)
355
- ).filter(item => item !== '');
356
- return unwrappedItems.join(', ');
357
- }
358
-
359
- // Handle objects
360
- if (typeof value === 'object') {
361
- const keys = Object.keys(value);
362
- if (keys.length === 0) {
363
- return '';
364
- }
365
-
366
- // For maxDepth = 0 or 1, return [Too Deep] for objects
367
- if (depth + 1 >= maxDepth) {
368
- return '[Too Deep]';
369
- }
370
-
371
- // Stringify complex objects
372
- try {
373
- return JSON.stringify(value);
374
- } catch (error) {
375
- // Check if it's a circular reference
376
- if (error.message.includes('circular') || error.message.includes('Converting circular')) {
377
- return '[Circular Reference]';
378
- }
379
- return '[Unstringifiable Object]';
380
- }
381
- }
382
-
383
- // Convert to string for primitive values
384
- return String(value);
385
- }
386
-
387
- /**
388
- * Preprocesses JSON data by deeply unwrapping nested structures
389
- *
390
- * @param {Array<Object>} data - Array of objects to preprocess
391
- * @returns {Array<Object>} Preprocessed data with unwrapped values
392
- *
393
- * @example
394
- * const processed = preprocessData(complexJsonData);
395
- * const csv = jsonToCsv(processed);
396
- */
397
- function preprocessData(data) {
398
- if (!Array.isArray(data)) {
399
- return [];
400
- }
401
-
402
- return data.map(item => {
403
- if (!item || typeof item !== 'object') {
404
- return {};
405
- }
406
-
407
- const processed = {};
408
-
409
- for (const key in item) {
410
- if (Object.prototype.hasOwnProperty.call(item, key)) {
411
- const value = item[key];
412
- if (value && typeof value === 'object') {
413
- processed[key] = deepUnwrap(value);
414
- } else {
415
- processed[key] = value;
416
- }
417
- }
418
- }
419
-
420
- return processed;
421
- });
422
- }
423
-
424
- /**
425
- * Validates file path to prevent path traversal attacks
426
- * @private
427
- */
428
- function validateFilePath(filePath) {
429
- const path = require('path');
430
-
431
- // Basic validation
432
- if (typeof filePath !== 'string' || filePath.trim() === '') {
433
- throw new ValidationError('File path must be a non-empty string');
434
- }
435
-
436
- // Ensure file has .csv extension
437
- if (!filePath.toLowerCase().endsWith('.csv')) {
438
- throw new ValidationError('File must have .csv extension');
439
- }
440
-
441
- // Block UNC paths BEFORE path.resolve() to avoid network lookup timeouts
442
- if (filePath.startsWith('\\\\') || filePath.startsWith('//')) {
443
- throw new SecurityError('UNC paths are not allowed');
444
- }
445
-
446
- // Get absolute path and check for traversal
447
- const absolutePath = path.resolve(filePath);
448
- const normalizedPath = path.normalize(filePath);
449
-
450
- // Prevent directory traversal attacks
451
- // Check if normalized path contains parent directory references
452
- if (normalizedPath.includes('..') ||
453
- /\\\.\.\\|\/\.\.\//.test(filePath) ||
454
- filePath.startsWith('..') ||
455
- filePath.includes('/..')) {
456
- throw new SecurityError('Directory traversal detected in file path');
457
- }
458
-
459
- return absolutePath;
460
- }
461
-
462
- /**
463
- * Converts JSON to CSV and saves it to a file
464
- *
465
- * @param {Array<Object>} data - Array of objects to convert
466
- * @param {string} filePath - Path to save the CSV file
467
- * @param {Object} [options] - Configuration options (same as jsonToCsv)
468
- * @returns {Promise<void>}
469
- *
470
- * @example
471
- * const { saveAsCsv } = require('./json-to-csv');
472
- *
473
- * await saveAsCsv(data, './output.csv', {
474
- * delimiter: ',',
475
- * renameMap: { id: 'ID' }
476
- * });
477
- */
478
- async function saveAsCsv(data, filePath, options = {}) {
479
- return safeExecute(async () => {
480
- const fs = require('fs').promises;
481
-
482
- // Validate file path
483
- const safePath = validateFilePath(filePath);
484
-
485
- // Convert data to CSV
486
- const csvContent = jsonToCsv(data, options);
487
-
488
- // Ensure directory exists
489
- const dir = require('path').dirname(safePath);
490
-
491
- try {
492
- await fs.mkdir(dir, { recursive: true });
493
-
494
- // Write file
495
- await fs.writeFile(safePath, csvContent, 'utf8');
496
-
497
- return safePath;
498
- } catch (error) {
499
- if (error.code === 'ENOENT') {
500
- throw new FileSystemError(`Directory does not exist: ${dir}`, error);
501
- }
502
- if (error.code === 'EACCES') {
503
- throw new FileSystemError(`Permission denied: ${safePath}`, error);
504
- }
505
- if (error.code === 'ENOSPC') {
506
- throw new FileSystemError(`No space left on device: ${safePath}`, error);
507
- }
508
-
509
- throw new FileSystemError(`Failed to write CSV file: ${error.message}`, error);
510
- }
511
- }, 'FILE_SYSTEM_ERROR', { function: 'saveAsCsv' });
512
- }
513
-
514
- // Export the main functions
515
- module.exports = {
516
- jsonToCsv,
517
- preprocessData,
518
- saveAsCsv,
519
- deepUnwrap,
520
- validateFilePath
521
- };
522
-
523
- // For ES6 module compatibility
524
- if (typeof module !== 'undefined' && module.exports) {
525
- module.exports.default = jsonToCsv;
526
- }
package/plugins/README.md DELETED
@@ -1,91 +0,0 @@
1
- # JTCSV framework integrations
2
-
3
- This folder contains optional adapters published as separate packages. Each adapter depends on `jtcsv`.
4
-
5
- ## Available packages
6
- - @jtcsv/express-middleware
7
- - @jtcsv/fastify
8
- - @jtcsv/nextjs
9
- - @jtcsv/nestjs
10
- - @jtcsv/remix
11
- - @jtcsv/nuxt
12
- - @jtcsv/sveltekit
13
- - @jtcsv/hono
14
- - @jtcsv/trpc
15
-
16
- ## Main package shortcuts
17
- If you already depend on `jtcsv`, the adapters are also exported from the main package:
18
- - jtcsv/express
19
- - jtcsv/fastify
20
- - jtcsv/nextjs
21
- - jtcsv/nextjs/route
22
- - jtcsv/nestjs
23
- - jtcsv/remix
24
- - jtcsv/nuxt
25
- - jtcsv/sveltekit
26
- - jtcsv/hono
27
- - jtcsv/trpc
28
-
29
- ## Express
30
- ```bash
31
- npm install @jtcsv/express-middleware express jtcsv
32
- ```
33
- ```javascript
34
- const express = require('express');
35
- const { middleware } = require('@jtcsv/express-middleware');
36
-
37
- const app = express();
38
- app.use(express.json());
39
- app.use(express.text({ type: 'text/csv' }));
40
- app.use(middleware());
41
- ```
42
-
43
- ## Fastify
44
- ```bash
45
- npm install @jtcsv/fastify fastify fastify-plugin jtcsv
46
- ```
47
- ```javascript
48
- const fastify = require('fastify')();
49
- await fastify.register(require('@jtcsv/fastify'), { prefix: '/api' });
50
- ```
51
-
52
- ## Next.js
53
- ```bash
54
- npm install @jtcsv/nextjs jtcsv
55
- ```
56
- ```javascript
57
- import handler from '@jtcsv/nextjs/route';
58
- export default handler;
59
- ```
60
-
61
- ## NestJS
62
- ```bash
63
- npm install @jtcsv/nestjs jtcsv
64
- ```
65
-
66
- ## Remix
67
- ```bash
68
- npm install @jtcsv/remix jtcsv
69
- ```
70
-
71
- ## Nuxt
72
- ```bash
73
- npm install @jtcsv/nuxt jtcsv
74
- ```
75
-
76
- ## SvelteKit
77
- ```bash
78
- npm install @jtcsv/sveltekit jtcsv
79
- ```
80
-
81
- ## Hono
82
- ```bash
83
- npm install @jtcsv/hono jtcsv
84
- ```
85
-
86
- ## tRPC
87
- ```bash
88
- npm install @jtcsv/trpc jtcsv
89
- ```
90
-
91
- See each package README in this folder for API details.
@@ -1,64 +0,0 @@
1
- # @jtcsv/express-middleware
2
-
3
- Express middleware that converts CSV/JSON payloads and exposes the converted data on `req.converted`.
4
-
5
- ## Install
6
- ```bash
7
- npm install @jtcsv/express-middleware express jtcsv
8
- ```
9
-
10
- ## Quick start
11
- ```javascript
12
- const express = require('express');
13
- const { middleware } = require('@jtcsv/express-middleware');
14
-
15
- const app = express();
16
- app.use(express.json());
17
- app.use(express.text({ type: 'text/csv' }));
18
- app.use(middleware());
19
-
20
- app.post('/api/convert', (req, res) => {
21
- res.json(req.converted);
22
- });
23
- ```
24
-
25
- ## Options
26
- The middleware detects input/output format based on `Content-Type`, `Accept`, and `?format=csv`.
27
-
28
- ```javascript
29
- app.use(middleware({
30
- autoDetect: true,
31
- delimiter: ',',
32
- enableFastPath: true,
33
- preventCsvInjection: true,
34
- rfc4180Compliant: true,
35
- conversionOptions: {
36
- parseNumbers: true,
37
- parseBooleans: true
38
- }
39
- }));
40
- ```
41
-
42
- Note: body size limits are controlled by your body parser (for example `express.json({ limit: '10mb' })`).
43
-
44
- ## Helpers
45
- ```javascript
46
- const {
47
- csvToJsonRoute,
48
- jsonToCsvRoute,
49
- uploadCsvRoute,
50
- healthCheck
51
- } = require('@jtcsv/express-middleware');
52
- ```
53
-
54
- ## req.converted shape
55
- ```json
56
- {
57
- "data": "...",
58
- "format": "json",
59
- "inputFormat": "csv",
60
- "outputFormat": "json",
61
- "stats": { "inputSize": 0, "outputSize": 0, "processingTime": 0, "conversion": "csv->json" },
62
- "options": {}
63
- }
64
- ```