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,874 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>jtcsv Web Worker Example</title>
7
+ <style>
8
+ * {
9
+ box-sizing: border-box;
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
+ line-height: 1.6;
17
+ color: #333;
18
+ max-width: 1200px;
19
+ margin: 0 auto;
20
+ padding: 20px;
21
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
22
+ min-height: 100vh;
23
+ }
24
+
25
+ .container {
26
+ background: white;
27
+ border-radius: 12px;
28
+ padding: 30px;
29
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
30
+ }
31
+
32
+ h1 {
33
+ color: #2d3748;
34
+ margin-bottom: 10px;
35
+ font-size: 2.5rem;
36
+ background: linear-gradient(90deg, #667eea, #764ba2);
37
+ background-clip: text;
38
+ -webkit-background-clip: text;
39
+ color: transparent;
40
+ -webkit-text-fill-color: transparent;
41
+ }
42
+
43
+ .subtitle {
44
+ color: #718096;
45
+ margin-bottom: 30px;
46
+ font-size: 1.1rem;
47
+ }
48
+
49
+ .grid {
50
+ display: grid;
51
+ grid-template-columns: 1fr 1fr;
52
+ gap: 30px;
53
+ margin-bottom: 30px;
54
+ }
55
+
56
+ @media (max-width: 768px) {
57
+ .grid {
58
+ grid-template-columns: 1fr;
59
+ }
60
+ }
61
+
62
+ .card {
63
+ background: #f7fafc;
64
+ border-radius: 8px;
65
+ padding: 20px;
66
+ border: 1px solid #e2e8f0;
67
+ transition: transform 0.2s, box-shadow 0.2s;
68
+ }
69
+
70
+ .card:hover {
71
+ transform: translateY(-2px);
72
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
73
+ }
74
+
75
+ .card h2 {
76
+ color: #4a5568;
77
+ margin-bottom: 15px;
78
+ font-size: 1.5rem;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 10px;
82
+ }
83
+
84
+ .card h2 i {
85
+ font-size: 1.2rem;
86
+ }
87
+
88
+ .controls {
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 15px;
92
+ }
93
+
94
+ .file-input {
95
+ padding: 12px;
96
+ border: 2px dashed #cbd5e0;
97
+ border-radius: 6px;
98
+ background: white;
99
+ cursor: pointer;
100
+ transition: border-color 0.2s;
101
+ text-align: center;
102
+ }
103
+
104
+ .file-input:hover {
105
+ border-color: #667eea;
106
+ }
107
+
108
+ .file-input input {
109
+ display: none;
110
+ }
111
+
112
+ .file-label {
113
+ display: block;
114
+ color: #4a5568;
115
+ font-weight: 500;
116
+ }
117
+
118
+ .file-info {
119
+ font-size: 0.9rem;
120
+ color: #718096;
121
+ margin-top: 5px;
122
+ }
123
+
124
+ button {
125
+ background: linear-gradient(90deg, #667eea, #764ba2);
126
+ color: white;
127
+ border: none;
128
+ padding: 12px 24px;
129
+ border-radius: 6px;
130
+ font-size: 1rem;
131
+ font-weight: 600;
132
+ cursor: pointer;
133
+ transition: transform 0.2s, box-shadow 0.2s;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ gap: 8px;
138
+ }
139
+
140
+ button:hover:not(:disabled) {
141
+ transform: translateY(-1px);
142
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
143
+ }
144
+
145
+ button:disabled {
146
+ opacity: 0.6;
147
+ cursor: not-allowed;
148
+ }
149
+
150
+ .stats {
151
+ display: grid;
152
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
153
+ gap: 15px;
154
+ margin-top: 20px;
155
+ }
156
+
157
+ .stat {
158
+ background: white;
159
+ padding: 15px;
160
+ border-radius: 6px;
161
+ border-left: 4px solid #667eea;
162
+ }
163
+
164
+ .stat-label {
165
+ font-size: 0.9rem;
166
+ color: #718096;
167
+ margin-bottom: 5px;
168
+ }
169
+
170
+ .stat-value {
171
+ font-size: 1.5rem;
172
+ font-weight: 700;
173
+ color: #2d3748;
174
+ }
175
+
176
+ .progress-container {
177
+ margin-top: 20px;
178
+ }
179
+
180
+ .progress-bar {
181
+ height: 8px;
182
+ background: #e2e8f0;
183
+ border-radius: 4px;
184
+ overflow: hidden;
185
+ margin-bottom: 10px;
186
+ }
187
+
188
+ .progress-fill {
189
+ height: 100%;
190
+ background: linear-gradient(90deg, #667eea, #764ba2);
191
+ width: 0%;
192
+ transition: width 0.3s ease;
193
+ }
194
+
195
+ .progress-text {
196
+ display: flex;
197
+ justify-content: space-between;
198
+ font-size: 0.9rem;
199
+ color: #718096;
200
+ }
201
+
202
+ .results {
203
+ margin-top: 20px;
204
+ max-height: 300px;
205
+ overflow-y: auto;
206
+ border: 1px solid #e2e8f0;
207
+ border-radius: 6px;
208
+ padding: 15px;
209
+ background: white;
210
+ }
211
+
212
+ .result-item {
213
+ padding: 10px;
214
+ border-bottom: 1px solid #edf2f7;
215
+ font-family: 'Courier New', monospace;
216
+ font-size: 0.9rem;
217
+ }
218
+
219
+ .result-item:last-child {
220
+ border-bottom: none;
221
+ }
222
+
223
+ .comparison {
224
+ margin-top: 30px;
225
+ padding-top: 20px;
226
+ border-top: 2px solid #e2e8f0;
227
+ }
228
+
229
+ .comparison-grid {
230
+ display: grid;
231
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
232
+ gap: 20px;
233
+ margin-top: 15px;
234
+ }
235
+
236
+ .comparison-item {
237
+ background: white;
238
+ padding: 15px;
239
+ border-radius: 6px;
240
+ border: 1px solid #e2e8f0;
241
+ }
242
+
243
+ .comparison-item.winner {
244
+ border-color: #48bb78;
245
+ background: #f0fff4;
246
+ }
247
+
248
+ .comparison-title {
249
+ font-weight: 600;
250
+ margin-bottom: 10px;
251
+ color: #4a5568;
252
+ }
253
+
254
+ .comparison-time {
255
+ font-size: 1.8rem;
256
+ font-weight: 700;
257
+ color: #2d3748;
258
+ }
259
+
260
+ .comparison-diff {
261
+ font-size: 0.9rem;
262
+ color: #718096;
263
+ margin-top: 5px;
264
+ }
265
+
266
+ .comparison-diff.positive {
267
+ color: #48bb78;
268
+ }
269
+
270
+ .comparison-diff.negative {
271
+ color: #f56565;
272
+ }
273
+
274
+ .code-block {
275
+ background: #2d3748;
276
+ color: #e2e8f0;
277
+ padding: 15px;
278
+ border-radius: 6px;
279
+ font-family: 'Courier New', monospace;
280
+ font-size: 0.9rem;
281
+ overflow-x: auto;
282
+ margin-top: 20px;
283
+ }
284
+
285
+ .code-keyword {
286
+ color: #63b3ed;
287
+ }
288
+
289
+ .code-string {
290
+ color: #68d391;
291
+ }
292
+
293
+ .code-comment {
294
+ color: #a0aec0;
295
+ }
296
+
297
+ .code-function {
298
+ color: #fbb6ce;
299
+ }
300
+
301
+ .status {
302
+ padding: 10px;
303
+ border-radius: 6px;
304
+ margin-top: 15px;
305
+ font-weight: 500;
306
+ }
307
+
308
+ .status.success {
309
+ background: #c6f6d5;
310
+ color: #22543d;
311
+ border: 1px solid #9ae6b4;
312
+ }
313
+
314
+ .status.error {
315
+ background: #fed7d7;
316
+ color: #742a2a;
317
+ border: 1px solid #fc8181;
318
+ }
319
+
320
+ .status.info {
321
+ background: #bee3f8;
322
+ color: #2a4365;
323
+ border: 1px solid #90cdf4;
324
+ }
325
+
326
+ .spinner {
327
+ width: 20px;
328
+ height: 20px;
329
+ border: 3px solid rgba(255, 255, 255, 0.3);
330
+ border-radius: 50%;
331
+ border-top-color: white;
332
+ animation: spin 1s ease-in-out infinite;
333
+ }
334
+
335
+ @keyframes spin {
336
+ to { transform: rotate(360deg); }
337
+ }
338
+
339
+ .hidden {
340
+ display: none;
341
+ }
342
+ </style>
343
+ </head>
344
+ <body>
345
+ <div class="container">
346
+ <h1>jtcsv Web Worker Demo</h1>
347
+ <p class="subtitle">Process large CSV files without blocking the main thread</p>
348
+
349
+ <div class="grid">
350
+ <div class="card">
351
+ <h2><i>📁</i> Upload CSV File</h2>
352
+ <div class="controls">
353
+ <div class="file-input" id="fileDropArea">
354
+ <input type="file" id="csvFile" accept=".csv,.txt">
355
+ <label for="csvFile" class="file-label">
356
+ <div>📂 Click to select or drag & drop</div>
357
+ <div class="file-info">Supports CSV files up to 100MB</div>
358
+ </label>
359
+ </div>
360
+
361
+ <div class="stats">
362
+ <div class="stat">
363
+ <div class="stat-label">Selected File</div>
364
+ <div class="stat-value" id="fileName">None</div>
365
+ </div>
366
+ <div class="stat">
367
+ <div class="stat-label">File Size</div>
368
+ <div class="stat-value" id="fileSize">0 KB</div>
369
+ </div>
370
+ </div>
371
+
372
+ <div class="progress-container" id="progressContainer" style="display: none;">
373
+ <div class="progress-bar">
374
+ <div class="progress-fill" id="progressFill"></div>
375
+ </div>
376
+ <div class="progress-text">
377
+ <span>Processing...</span>
378
+ <span id="progressPercent">0%</span>
379
+ </div>
380
+ </div>
381
+
382
+ <button id="processBtn" disabled>
383
+ <span>Process with Web Worker</span>
384
+ </button>
385
+
386
+ <button id="processMainBtn" disabled style="background: linear-gradient(90deg, #ed8936, #dd6b20);">
387
+ <span>Process in Main Thread (Blocking)</span>
388
+ </button>
389
+ </div>
390
+
391
+ <div id="status" class="status hidden"></div>
392
+ </div>
393
+
394
+ <div class="card">
395
+ <h2><i>📊</i> Performance Comparison</h2>
396
+ <div class="comparison-grid" id="comparisonResults">
397
+ <div class="comparison-item">
398
+ <div class="comparison-title">Web Worker</div>
399
+ <div class="comparison-time" id="workerTime">-- ms</div>
400
+ <div class="comparison-diff" id="workerDiff">--</div>
401
+ </div>
402
+ <div class="comparison-item">
403
+ <div class="comparison-title">Main Thread</div>
404
+ <div class="comparison-time" id="mainTime">-- ms</div>
405
+ <div class="comparison-diff" id="mainDiff">--</div>
406
+ </div>
407
+ </div>
408
+
409
+ <div class="stats">
410
+ <div class="stat">
411
+ <div class="stat-label">UI Responsiveness</div>
412
+ <div class="stat-value" id="uiResponsiveness">Good</div>
413
+ </div>
414
+ <div class="stat">
415
+ <div class="stat-label">Memory Usage</div>
416
+ <div class="stat-value" id="memoryUsage">-- MB</div>
417
+ </div>
418
+ <div class="stat">
419
+ <div class="stat-label">Rows Processed</div>
420
+ <div class="stat-value" id="rowsProcessed">0</div>
421
+ </div>
422
+ </div>
423
+
424
+ <div class="results" id="resultsPreview">
425
+ <div class="result-item">Results will appear here...</div>
426
+ </div>
427
+ </div>
428
+ </div>
429
+
430
+ <div class="card">
431
+ <h2><i>💻</i> Code Example</h2>
432
+ <div class="code-block">
433
+ <pre><code><span class="code-comment">// Using jtcsv with Web Workers</span>
434
+ <span class="code-keyword">import</span> { createWorkerPool } <span class="code-keyword">from</span> <span class="code-string">'jtcsv/browser/workers'</span>;
435
+
436
+ <span class="code-comment">// Create a pool of 4 workers</span>
437
+ <span class="code-keyword">const</span> workerPool = <span class="code-function">createWorkerPool</span>(<span class="code-string">'./csv-parser.worker.js'</span>, {
438
+ maxWorkers: <span class="code-string">4</span>,
439
+ idleTimeout: <span class="code-string">30000</span>
440
+ });
441
+
442
+ <span class="code-comment">// Process CSV in background</span>
443
+ <span class="code-keyword">async</span> <span class="code-keyword">function</span> <span class="code-function">processLargeCsv</span>(csvData) {
444
+ <span class="code-keyword">const</span> startTime = <span class="code-function">performance</span>.<span class="code-function">now</span>();
445
+
446
+ <span class="code-comment">// Submit task to worker pool</span>
447
+ <span class="code-keyword">const</span> result = <span class="code-keyword">await</span> workerPool.<span class="code-function">execute</span>({
448
+ type: <span class="code-string">'parseCsv'</span>,
449
+ csv: csvData,
450
+ options: {
451
+ hasHeaders: <span class="code-keyword">true</span>,
452
+ parseNumbers: <span class="code-keyword">true</span>,
453
+ parseBooleans: <span class="code-keyword">true</span>
454
+ }
455
+ });
456
+
457
+ <span class="code-keyword">const</span> endTime = <span class="code-function">performance</span>.<span class="code-function">now</span>();
458
+ <span class="code-function">console</span>.<span class="code-function">log</span>(<span class="code-string">`Processed </span>${result.data.length}<span class="code-string"> rows in </span>${endTime - startTime}<span class="code-string">ms`</span>);
459
+
460
+ <span class="code-keyword">return</span> result.data;
461
+ }
462
+
463
+ <span class="code-comment">// Clean up when done</span>
464
+ workerPool.<span class="code-function">terminate</span>();</code></pre>
465
+ </div>
466
+ </div>
467
+
468
+ <div class="comparison">
469
+ <h2><i>⚡</i> Benefits of Web Workers</h2>
470
+ <div class="comparison-grid">
471
+ <div class="comparison-item winner">
472
+ <div class="comparison-title">With Web Workers</div>
473
+ <ul style="padding-left: 20px; margin-top: 10px; color: #4a5568;">
474
+ <li>UI remains responsive</li>
475
+ <li>Parallel processing</li>
476
+ <li>Better memory management</li>
477
+ <li>No main thread blocking</li>
478
+ </ul>
479
+ </div>
480
+ <div class="comparison-item">
481
+ <div class="comparison-title">Without Web Workers</div>
482
+ <ul style="padding-left: 20px; margin-top: 10px; color: #4a5568;">
483
+ <li>UI freezes during processing</li>
484
+ <li>Single-threaded</li>
485
+ <li>Memory pressure on main thread</li>
486
+ <li>Poor user experience</li>
487
+ </ul>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </div>
492
+
493
+ <script>
494
+ // Load jtcsv from CDN (in real scenario, you would bundle it)
495
+ const jtcsvScript = document.createElement('script');
496
+ jtcsvScript.src = 'https://unpkg.com/jtcsv@latest/dist/jtcsv.umd.js';
497
+ document.head.appendChild(jtcsvScript);
498
+
499
+ jtcsvScript.onload = () => {
500
+ console.log('jtcsv loaded successfully');
501
+ };
502
+
503
+ jtcsvScript.onerror = () => {
504
+ console.error('Failed to load jtcsv from CDN');
505
+ showStatus('Failed to load jtcsv library. Please check your internet connection.', 'error');
506
+ // Disable processing buttons
507
+ document.getElementById('processBtn').disabled = true;
508
+ document.getElementById('processMainBtn').disabled = true;
509
+ };
510
+
511
+ // DOM elements
512
+ const fileInput = document.getElementById('csvFile');
513
+ const fileDropArea = document.getElementById('fileDropArea');
514
+ const processBtn = document.getElementById('processBtn');
515
+ const processMainBtn = document.getElementById('processMainBtn');
516
+ const fileName = document.getElementById('fileName');
517
+ const fileSize = document.getElementById('fileSize');
518
+ const progressContainer = document.getElementById('progressContainer');
519
+ const progressFill = document.getElementById('progressFill');
520
+ const progressPercent = document.getElementById('progressPercent');
521
+ const status = document.getElementById('status');
522
+ const resultsPreview = document.getElementById('resultsPreview');
523
+ const workerTime = document.getElementById('workerTime');
524
+ const mainTime = document.getElementById('mainTime');
525
+ const workerDiff = document.getElementById('workerDiff');
526
+ const mainDiff = document.getElementById('mainDiff');
527
+ const uiResponsiveness = document.getElementById('uiResponsiveness');
528
+ const memoryUsage = document.getElementById('memoryUsage');
529
+ const rowsProcessed = document.getElementById('rowsProcessed');
530
+
531
+ // Server availability
532
+ let serverAvailable = false;
533
+
534
+ // Check if server is running (worker file accessible)
535
+ function checkServerAvailability() {
536
+ fetch('./csv-parser.worker.js', { method: 'HEAD', cache: 'no-cache' })
537
+ .then(response => {
538
+ serverAvailable = response.ok;
539
+ updateServerStatus();
540
+ })
541
+ .catch(() => {
542
+ serverAvailable = false;
543
+ updateServerStatus();
544
+ });
545
+ }
546
+
547
+ function updateServerStatus() {
548
+ const btn = document.getElementById('processBtn');
549
+ if (!serverAvailable) {
550
+ btn.disabled = true;
551
+ btn.innerHTML = '<span>Start server to enable Web Worker</span>';
552
+ btn.title = 'The demo server is not running. To start it, run: node examples/advanced/run-demo-server.js';
553
+ // Add tooltip styling
554
+ btn.style.position = 'relative';
555
+ btn.style.cursor = 'not-allowed';
556
+ } else {
557
+ btn.disabled = false;
558
+ btn.innerHTML = '<span>Process with Web Worker</span>';
559
+ btn.title = '';
560
+ btn.style.position = '';
561
+ btn.style.cursor = '';
562
+ }
563
+ }
564
+
565
+ // State
566
+ let selectedFile = null;
567
+ let csvContent = '';
568
+ let worker = null;
569
+ let isProcessing = false;
570
+
571
+ // File handling
572
+ fileInput.addEventListener('change', handleFileSelect);
573
+
574
+ // Drag and drop
575
+ fileDropArea.addEventListener('dragover', (e) => {
576
+ e.preventDefault();
577
+ fileDropArea.style.borderColor = '#667eea';
578
+ fileDropArea.style.backgroundColor = '#f7fafc';
579
+ });
580
+
581
+ fileDropArea.addEventListener('dragleave', () => {
582
+ fileDropArea.style.borderColor = '#cbd5e0';
583
+ fileDropArea.style.backgroundColor = 'white';
584
+ });
585
+
586
+ fileDropArea.addEventListener('drop', (e) => {
587
+ e.preventDefault();
588
+ fileDropArea.style.borderColor = '#cbd5e0';
589
+ fileDropArea.style.backgroundColor = 'white';
590
+
591
+ if (e.dataTransfer.files.length) {
592
+ fileInput.files = e.dataTransfer.files;
593
+ handleFileSelect();
594
+ }
595
+ });
596
+
597
+ function handleFileSelect() {
598
+ if (!fileInput.files.length) return;
599
+
600
+ selectedFile = fileInput.files[0];
601
+ fileName.textContent = selectedFile.name;
602
+ fileSize.textContent = formatFileSize(selectedFile.size);
603
+
604
+ // Read file content
605
+ const reader = new FileReader();
606
+ reader.onload = (e) => {
607
+ csvContent = e.target.result;
608
+ processBtn.disabled = false;
609
+ processMainBtn.disabled = false;
610
+ showStatus('File loaded successfully!', 'success');
611
+ };
612
+ reader.onerror = () => {
613
+ showStatus('Error reading file', 'error');
614
+ };
615
+ reader.readAsText(selectedFile);
616
+ }
617
+
618
+ // Process with Web Worker
619
+ processBtn.addEventListener('click', async () => {
620
+ if (!csvContent || isProcessing) return;
621
+
622
+ isProcessing = true;
623
+ updateButtons();
624
+ showProgress(true);
625
+ showStatus('Processing with Web Worker...', 'info');
626
+
627
+ // Create worker if not exists
628
+ if (!worker) {
629
+ if (!serverAvailable) {
630
+ showStatus('Web Worker requires the demo server to be running. Please start the server.', 'error');
631
+ resetProcessing();
632
+ return;
633
+ }
634
+ worker = new Worker('./csv-parser.worker.js');
635
+
636
+ worker.onmessage = (e) => {
637
+ const { type, data, progress, error } = e.data;
638
+
639
+ if (type === 'progress') {
640
+ updateProgress(progress);
641
+ } else if (type === 'result') {
642
+ handleWorkerResult(data);
643
+ } else if (type === 'error') {
644
+ clearInterval(uiTestInterval);
645
+ document.title = 'jtcsv Web Worker Demo';
646
+ showStatus(`Worker error: ${error}`, 'error');
647
+ resetProcessing();
648
+ }
649
+ };
650
+
651
+ worker.onerror = (error) => {
652
+ clearInterval(uiTestInterval);
653
+ document.title = 'jtcsv Web Worker Demo';
654
+ showStatus(`Worker error: ${error.message}`, 'error');
655
+ resetProcessing();
656
+ };
657
+ }
658
+
659
+ // Start processing
660
+ const startTime = performance.now();
661
+ uiResponsiveness.textContent = 'Responsive';
662
+
663
+ // Simulate UI responsiveness test
664
+ let uiUpdates = 0;
665
+ const uiTestInterval = setInterval(() => {
666
+ uiUpdates++;
667
+ document.title = `Processing... ${uiUpdates}`;
668
+ }, 100);
669
+
670
+ worker.postMessage({
671
+ type: 'parseCsv',
672
+ csv: csvContent,
673
+ options: {
674
+ hasHeaders: true,
675
+ parseNumbers: true,
676
+ parseBooleans: true
677
+ }
678
+ });
679
+
680
+ // Handle completion
681
+ function handleWorkerResult(data) {
682
+ clearInterval(uiTestInterval);
683
+ document.title = 'jtcsv Web Worker Demo';
684
+
685
+ const endTime = performance.now();
686
+ const processingTime = endTime - startTime;
687
+
688
+ workerTime.textContent = `${processingTime.toFixed(0)} ms`;
689
+ rowsProcessed.textContent = data.length;
690
+
691
+ // Update results preview
692
+ updateResultsPreview(data);
693
+
694
+ // Compare with main thread (if available)
695
+ if (window.mainThreadResult) {
696
+ const diff = processingTime - window.mainThreadResult.time;
697
+ const percentDiff = (diff / window.mainThreadResult.time * 100).toFixed(1);
698
+
699
+ if (diff > 0) {
700
+ workerDiff.textContent = `${percentDiff}% slower`;
701
+ workerDiff.className = 'comparison-diff negative';
702
+ } else {
703
+ workerDiff.textContent = `${Math.abs(percentDiff)}% faster`;
704
+ workerDiff.className = 'comparison-diff positive';
705
+ }
706
+ }
707
+
708
+ showStatus(`Processed ${data.length} rows in ${processingTime.toFixed(0)}ms`, 'success');
709
+ resetProcessing();
710
+
711
+ // Update memory usage (simulated)
712
+ memoryUsage.textContent = `${Math.round((data.length * 0.1) / 1024 * 100) / 100} MB`;
713
+ }
714
+ });
715
+
716
+ // Process in main thread (blocking)
717
+ processMainBtn.addEventListener('click', async () => {
718
+ if (!csvContent || isProcessing) return;
719
+
720
+ isProcessing = true;
721
+ updateButtons();
722
+ showProgress(true);
723
+ showStatus('Processing in main thread (may freeze UI)...', 'info');
724
+
725
+ const startTime = performance.now();
726
+ uiResponsiveness.textContent = 'May freeze';
727
+
728
+ // Simulate UI freeze test
729
+ let uiUpdates = 0;
730
+ const uiTestInterval = setInterval(() => {
731
+ uiUpdates++;
732
+ document.title = `Processing... ${uiUpdates}`;
733
+ }, 100);
734
+
735
+ try {
736
+ // Use jtcsv directly in main thread
737
+ const result = jtcsv.csvToJson(csvContent, {
738
+ hasHeaders: true,
739
+ parseNumbers: true,
740
+ parseBooleans: true
741
+ });
742
+
743
+ clearInterval(uiTestInterval);
744
+ document.title = 'jtcsv Web Worker Demo';
745
+
746
+ const endTime = performance.now();
747
+ const processingTime = endTime - startTime;
748
+
749
+ mainTime.textContent = `${processingTime.toFixed(0)} ms`;
750
+ rowsProcessed.textContent = result.length;
751
+
752
+ // Update results preview
753
+ updateResultsPreview(result);
754
+
755
+ // Store for comparison
756
+ window.mainThreadResult = {
757
+ time: processingTime,
758
+ data: result
759
+ };
760
+
761
+ showStatus(`Processed ${result.length} rows in ${processingTime.toFixed(0)}ms`, 'success');
762
+ resetProcessing();
763
+
764
+ // Update memory usage (simulated)
765
+ memoryUsage.textContent = `${Math.round((result.length * 0.1) / 1024 * 100) / 100} MB`;
766
+
767
+ } catch (error) {
768
+ clearInterval(uiTestInterval);
769
+ document.title = 'jtcsv Web Worker Demo';
770
+ showStatus(`Error: ${error.message}`, 'error');
771
+ resetProcessing();
772
+ }
773
+ });
774
+
775
+ // Helper functions
776
+ function formatFileSize(bytes) {
777
+ if (bytes === 0) return '0 Bytes';
778
+ const k = 1024;
779
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
780
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
781
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
782
+ }
783
+
784
+ function showStatus(message, type) {
785
+ status.textContent = message;
786
+ status.className = `status ${type}`;
787
+ status.classList.remove('hidden');
788
+
789
+ // Auto-hide success messages
790
+ if (type === 'success') {
791
+ setTimeout(() => {
792
+ status.classList.add('hidden');
793
+ }, 3000);
794
+ }
795
+ }
796
+
797
+ function showProgress(show) {
798
+ if (show) {
799
+ progressContainer.style.display = 'block';
800
+ progressFill.style.width = '0%';
801
+ progressPercent.textContent = '0%';
802
+ } else {
803
+ progressContainer.style.display = 'none';
804
+ }
805
+ }
806
+
807
+ function updateProgress(percent) {
808
+ progressFill.style.width = `${percent}%`;
809
+ progressPercent.textContent = `${percent}%`;
810
+ }
811
+
812
+ function updateButtons() {
813
+ processBtn.disabled = isProcessing;
814
+ processMainBtn.disabled = isProcessing;
815
+
816
+ if (isProcessing) {
817
+ processBtn.innerHTML = '<div class="spinner"></div><span>Processing...</span>';
818
+ processMainBtn.innerHTML = '<div class="spinner"></div><span>Processing...</span>';
819
+ } else {
820
+ processBtn.innerHTML = '<span>Process with Web Worker</span>';
821
+ processMainBtn.innerHTML = '<span>Process in Main Thread (Blocking)</span>';
822
+ }
823
+ }
824
+
825
+ function resetProcessing() {
826
+ isProcessing = false;
827
+ updateButtons();
828
+ showProgress(false);
829
+ uiResponsiveness.textContent = 'Good';
830
+ }
831
+
832
+ function updateResultsPreview(data) {
833
+ resultsPreview.innerHTML = '';
834
+
835
+ // Show first 10 rows
836
+ const previewRows = data.slice(0, 10);
837
+
838
+ previewRows.forEach((row, index) => {
839
+ const item = document.createElement('div');
840
+ item.className = 'result-item';
841
+
842
+ // Format row as string
843
+ const rowStr = JSON.stringify(row, null, 2)
844
+ .replace(/\n/g, ' ')
845
+ .replace(/\s+/g, ' ')
846
+ .substring(0, 100);
847
+
848
+ item.textContent = `${index + 1}. ${rowStr}${rowStr.length === 100 ? '...' : ''}`;
849
+ resultsPreview.appendChild(item);
850
+ });
851
+
852
+ if (data.length > 10) {
853
+ const moreItem = document.createElement('div');
854
+ moreItem.className = 'result-item';
855
+ moreItem.textContent = `... and ${data.length - 10} more rows`;
856
+ moreItem.style.fontStyle = 'italic';
857
+ moreItem.style.color = '#718096';
858
+ resultsPreview.appendChild(moreItem);
859
+ }
860
+ }
861
+
862
+ // Initialize
863
+ updateButtons();
864
+ checkServerAvailability();
865
+
866
+ // Clean up worker on page unload
867
+ window.addEventListener('beforeunload', () => {
868
+ if (worker) {
869
+ worker.terminate();
870
+ }
871
+ });
872
+ </script>
873
+ </body>
874
+ </html>