web-csv-toolbox 0.1.0 → 0.3.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.
package/lib/index.js CHANGED
@@ -1,33 +1,11 @@
1
- /**
2
- * FiledDelimiter is a symbol for field delimiter of CSV.
3
- */
4
1
  const FieldDelimiter = Symbol.for("web-streams-csv.FieldDelimiter");
5
- /**
6
- * RecordDelimiter is a symbol for record delimiter of CSV.
7
- */
8
2
  const RecordDelimiter = Symbol.for("web-streams-csv.RecordDelimiter");
9
- /**
10
- * Field is a symbol for field of CSV.
11
- */
12
3
  const Field = Symbol.for("web-streams-csv.Field");
13
-
14
4
  const CR = "\r";
15
5
  const CRLF = "\r\n";
16
6
  const LF = "\n";
17
- /**
18
- * COMMA is a symbol for comma(,).
19
- */
20
7
  const COMMA = ",";
21
- /**
22
- * DOUBLE_QUATE is a symbol for double quate(").
23
- */
24
8
  const DOUBLE_QUATE = '"';
25
-
26
- /**
27
- * Assert that the options are valid.
28
- *
29
- * @param options The options to assert.
30
- */
31
9
  function assertCommonOptions(options) {
32
10
  if (typeof options.quotation === "string" && options.quotation.length === 0) {
33
11
  throw new Error("quotation must not be empty");
@@ -50,42 +28,9 @@ function assertCommonOptions(options) {
50
28
  );
51
29
  }
52
30
  }
53
-
54
- /**
55
- * Escape a string for use in a regular expression.
56
- *
57
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping Regular expressions#Escaping | MDN}
58
- * @param v string to escape
59
- * @returns escaped string
60
- */
61
31
  function escapeRegExp(v) {
62
32
  return v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
63
33
  }
64
-
65
- /**
66
- * A transform stream that converts a stream of tokens into a stream of rows.
67
- *
68
- * @example Parse a CSV with headers by data
69
- * ```ts
70
- * new ReadableStream({
71
- * start(controller) {
72
- * controller.enqueue("name,age\r\n");
73
- * controller.enqueue("Alice,20\r\n");
74
- * controller.close();
75
- * }
76
- * })
77
- * .pipeThrough(new LexerTransformer())
78
- * .pipeTo(new WritableStream({ write(token) { console.log(token); }}));
79
- * // { type: Field, value: "name" }
80
- * // { type: FieldDelimiter, value: "," }
81
- * // { type: Field, value: "age" }
82
- * // { type: RecordDelimiter, value: "\r\n" }
83
- * // { type: Field, value: "Alice" }
84
- * // { type: FieldDelimiter, value: "," }
85
- * // { type: Field, value: "20" }
86
- * // { type: RecordDelimiter, value: "\r\n" }
87
- * ```
88
- */
89
34
  class LexerTransformer extends TransformStream {
90
35
  #demiliter;
91
36
  #demiliterLength;
@@ -161,49 +106,38 @@ class LexerTransformer extends TransformStream {
161
106
  if (this.#buffer.length === 0) {
162
107
  return null;
163
108
  }
164
- // Check for CRLF
165
109
  if (this.#buffer.startsWith(CRLF)) {
166
110
  this.#buffer = this.#buffer.slice(2);
167
111
  return { type: RecordDelimiter, value: CRLF };
168
112
  }
169
- // Check for LF
170
113
  if (this.#buffer.startsWith(LF)) {
171
114
  this.#buffer = this.#buffer.slice(1);
172
115
  return { type: RecordDelimiter, value: LF };
173
116
  }
174
- // Check for Delimiter
175
117
  if (this.#buffer.startsWith(this.#demiliter)) {
176
118
  this.#buffer = this.#buffer.slice(this.#demiliterLength);
177
119
  return { type: FieldDelimiter, value: this.#demiliter };
178
120
  }
179
- // Check for Quoted String
180
121
  if (this.#buffer.startsWith(this.#quotation)) {
181
- // If we're flushing and the buffer doesn't end with a quote, then return null
182
- // because we're not done with the quoted string
183
122
  if (flush === false && this.#buffer.endsWith(this.#quotation)) {
184
123
  return null;
185
124
  }
186
125
  return this.extractQuotedString();
187
126
  }
188
- // Check for Unquoted String
189
127
  const match = this.#matcher.exec(this.#buffer);
190
128
  if (match) {
191
- // If we're flushing and the match doesn't consume the entire buffer,
192
- // then return null
193
129
  if (flush === false && match[0].length === this.#buffer.length) {
194
130
  return null;
195
131
  }
196
132
  this.#buffer = this.#buffer.slice(match[0].length);
197
133
  return { type: Field, value: match[0] };
198
134
  }
199
- // Otherwise, return null
200
135
  return null;
201
136
  }
202
137
  extractQuotedString() {
203
- let end = this.#quotationLength; // Skip the opening quote
138
+ let end = this.#quotationLength;
204
139
  let value = "";
205
140
  while (end < this.#buffer.length) {
206
- // Escaped quote
207
141
  if (
208
142
  this.#buffer.slice(end, end + this.#quotationLength) ===
209
143
  this.quotation &&
@@ -216,7 +150,6 @@ class LexerTransformer extends TransformStream {
216
150
  end += this.#quotationLength * 2;
217
151
  continue;
218
152
  }
219
- // Closing quote
220
153
  if (
221
154
  this.#buffer.slice(end, end + this.#quotationLength) === this.quotation
222
155
  ) {
@@ -226,52 +159,9 @@ class LexerTransformer extends TransformStream {
226
159
  value += this.#buffer[end];
227
160
  end++;
228
161
  }
229
- // If we get here, we've reached the end of the buffer
230
162
  return null;
231
163
  }
232
164
  }
233
-
234
- /**
235
- * A transform stream that converts a stream of tokens into a stream of rows.
236
- * @template Header The type of the header row.
237
- * @param options The options for the parser.
238
- *
239
- * @example Parse a CSV with headers by data
240
- * ```ts
241
- * new ReadableStream({
242
- * start(controller) {
243
- * controller.enqueue("name,age\r\n");
244
- * controller.enqueue("Alice,20\r\n");
245
- * controller.enqueue("Bob,25\r\n");
246
- * controller.enqueue("Charlie,30\r\n");
247
- * controller.close();
248
- * })
249
- * .pipeThrough(new LexerTransformer())
250
- * .pipeThrough(new RecordAssemblerTransformar())
251
- * .pipeTo(new WritableStream({ write(row) { console.log(row); }}));
252
- * // { name: "Alice", age: "20" }
253
- * // { name: "Bob", age: "25" }
254
- * // { name: "Charlie", age: "30" }
255
- * ```
256
- *
257
- * @example Parse a CSV with headers by options
258
- * ```ts
259
- * new ReadableStream({
260
- * start(controller) {
261
- * controller.enqueue("Alice,20\r\n");
262
- * controller.enqueue("Bob,25\r\n");
263
- * controller.enqueue("Charlie,30\r\n");
264
- * controller.close();
265
- * }
266
- * })
267
- * .pipeThrough(new LexerTransformer())
268
- * .pipeThrough(new RecordAssemblerTransformar({ header: ["name", "age"] }))
269
- * .pipeTo(new WritableStream({ write(row) { console.log(row); }}));
270
- * // { name: "Alice", age: "20" }
271
- * // { name: "Bob", age: "25" }
272
- * // { name: "Charlie", age: "30" }
273
- * ```
274
- */
275
165
  class RecordAssemblerTransformar extends TransformStream {
276
166
  #fieldIndex = 0;
277
167
  #row = [];
@@ -301,7 +191,6 @@ class RecordAssemblerTransformar extends TransformStream {
301
191
  controller.enqueue(record);
302
192
  }
303
193
  }
304
- // Reset the row fields buffer.
305
194
  this.#fieldIndex = 0;
306
195
  this.#row = new Array(this.#header?.length);
307
196
  this.#darty = false;
@@ -310,7 +199,6 @@ class RecordAssemblerTransformar extends TransformStream {
310
199
  },
311
200
  flush: (controller) => {
312
201
  if (this.#fieldIndex !== 0 && this.#header !== undefined) {
313
- // console.log('B', this.#row)
314
202
  if (this.#darty) {
315
203
  const record = Object.fromEntries(
316
204
  this.#header
@@ -336,7 +224,6 @@ class RecordAssemblerTransformar extends TransformStream {
336
224
  }
337
225
  }
338
226
  }
339
-
340
227
  class SingleValueReadableStream extends ReadableStream {
341
228
  constructor(value) {
342
229
  super({
@@ -347,7 +234,6 @@ class SingleValueReadableStream extends ReadableStream {
347
234
  });
348
235
  }
349
236
  }
350
-
351
237
  async function toArray(...args) {
352
238
  const rows = [];
353
239
  for await (const row of this(...args)) {
@@ -355,13 +241,6 @@ async function toArray(...args) {
355
241
  }
356
242
  return rows;
357
243
  }
358
-
359
- /**
360
- * Parse CSV string to records.
361
- *
362
- * @param stream CSV string stream to parse
363
- * @param options Parsing options. See {@link ParseOptions}.
364
- */
365
244
  async function* parseStringStream(stream, options) {
366
245
  let controller;
367
246
  const readable = new ReadableStream({
@@ -387,49 +266,28 @@ async function* parseStringStream(stream, options) {
387
266
  reader.releaseLock();
388
267
  }
389
268
  }
390
- (function (parseStringStream) {})(
391
- parseStringStream || (parseStringStream = {}),
392
- );
393
- parseStringStream.toArray = toArray;
394
-
395
- /**
396
- * Parse CSV string to records.
397
- *
398
- * @param csv CSV string to parse
399
- * @param options Parsing options. See {@link ParseOptions}.
400
- */
401
- async function* streamingParse(csv, options) {
269
+ ((parseStringStream) => {
270
+ parseStringStream.toArray = toArray;
271
+ })(parseStringStream || (parseStringStream = {}));
272
+ async function* parseString(csv, options) {
402
273
  yield* parseStringStream(new SingleValueReadableStream(csv), options);
403
274
  }
404
- (function (streamingParse) {})(streamingParse || (streamingParse = {}));
405
- streamingParse.toArray = toArray;
406
-
407
- /**
408
- * Parse CSV to records.
409
- * This function is for parsing a binary stream.
410
- *
411
- * @remarks
412
- * If you want to parse a string, use {@link streamingParse}.
413
- * @param stream CSV string to parse
414
- * @param options Parsing options. See {@link ParseBinaryOptions}.
415
- */
275
+ ((parseString) => {
276
+ parseString.toArray = toArray;
277
+ })(parseString || (parseString = {}));
416
278
  async function* parseBinaryStream(stream, options) {
417
279
  const { charset, fatal, ignoreBOM, decomposition } = options ?? {};
418
280
  yield* parseStringStream(
419
281
  [
420
- // NOTE: if decompression is undefined, it will be ignored.
421
282
  ...(decomposition ? [new DecompressionStream(decomposition)] : []),
422
- // NOTE: if charset is undefined, it will be decoded as utf-8.
423
283
  new TextDecoderStream(charset, { fatal, ignoreBOM }),
424
284
  ].reduce((stream, transformer) => stream.pipeThrough(transformer), stream),
425
285
  options,
426
286
  );
427
287
  }
428
- (function (parseBinaryStream) {})(
429
- parseBinaryStream || (parseBinaryStream = {}),
430
- );
431
- parseBinaryStream.toArray = toArray;
432
-
288
+ ((parseBinaryStream) => {
289
+ parseBinaryStream.toArray = toArray;
290
+ })(parseBinaryStream || (parseBinaryStream = {}));
433
291
  function parseMime(contentType) {
434
292
  const [type, ...parameters] = contentType.split(";");
435
293
  const result = {
@@ -442,7 +300,6 @@ function parseMime(contentType) {
442
300
  }
443
301
  return result;
444
302
  }
445
-
446
303
  function parseResponse(response, options) {
447
304
  const { headers } = response;
448
305
  const contentType = headers.get("content-type") ?? "text/csv";
@@ -452,8 +309,6 @@ function parseResponse(response, options) {
452
309
  }
453
310
  const decomposition = headers.get("content-encoding") ?? undefined;
454
311
  const charset = mime.parameters.charset ?? "utf-8";
455
- // TODO: Support header=present and header=absent
456
- // const header = mime.parameters.header ?? "present";
457
312
  if (response.body === null) {
458
313
  throw new Error("Response body is null");
459
314
  }
@@ -463,75 +318,35 @@ function parseResponse(response, options) {
463
318
  ...options,
464
319
  });
465
320
  }
466
- (function (parseResponse) {})(parseResponse || (parseResponse = {}));
467
- parseResponse.toArray = toArray;
468
-
469
- /**
470
- * Parse CSV Stream to records.
471
- * string and Uint8Array are supported.
472
- *
473
- * @remarks
474
- * {@link parseStringStream} and {@link parseBinaryStream} are used internally.
475
- * If you known the type of the stream, it performs better to use them directly.
476
- *
477
- * If you want to parse a string, use {@link parseStringStream}.
478
- * If you want to parse a Uint8Array, use {@link parseBinaryStream}.
479
- *
480
- * @param csv CSV string to parse
481
- * @param options Parsing options. See {@link ParserOptions}.
482
- */
321
+ ((parseResponse) => {
322
+ parseResponse.toArray = toArray;
323
+ })(parseResponse || (parseResponse = {}));
483
324
  async function* parseStream(stream, options) {
484
325
  const [branch1, branch2] = stream.tee();
485
326
  const reader1 = branch1.getReader();
486
327
  const { value: firstChunk } = await reader1.read();
487
328
  reader1.releaseLock();
488
- switch (true) {
489
- case typeof firstChunk === "string":
490
- yield* parseStringStream(branch2, options);
491
- break;
492
- case firstChunk instanceof Uint8Array:
493
- yield* parseBinaryStream(branch2, options);
494
- break;
329
+ if (typeof firstChunk === "string") {
330
+ yield* parseStringStream(branch2, options);
331
+ } else if (firstChunk instanceof Uint8Array) {
332
+ yield* parseBinaryStream(branch2, options);
495
333
  }
496
334
  }
497
- (function (parseStream) {})(parseStream || (parseStream = {}));
498
- parseStream.toArray = toArray;
499
-
500
- /**
501
- * Parse CSV to records.
502
- *
503
- * {@link String}, {@link Uint8Array}, ReadableStream<string | Uint8Array> and Response are supported.
504
- *
505
- * @remarks
506
- * {@link streamingParse}, {@link parseBinaryStream},
507
- * {@link parseStringStream} and {@link parseResponse} are used internally.
508
- * If you known the type of the stream, it performs better to use them directly.
509
- *
510
- * If you want to parse a string, use {@link streamingParse}.
511
- * If you want to parse a Uint8Array, use {@link parseStream}.
512
- * If you want to parse a ReadableStream<string>, use {@link parseStringStream}.
513
- * If you want to parse a ReadableStream<Uint8Array>, use {@link parseBinaryStream}.
514
- * If you want to parse a Response, use {@link parseResponse}.
515
- *
516
- * @param csv CSV string to parse
517
- * @param options Parsing options. See {@link ParseOptions}.
518
- */
335
+ ((parseStream) => {
336
+ parseStream.toArray = toArray;
337
+ })(parseStream || (parseStream = {}));
519
338
  async function* parse(csv, options) {
520
- switch (true) {
521
- case typeof csv === "string":
522
- yield* streamingParse(csv, options);
523
- break;
524
- case csv instanceof ReadableStream:
525
- yield* parseStream(csv, options);
526
- break;
527
- case csv instanceof Response:
528
- yield* parseResponse(csv, options);
529
- break;
339
+ if (typeof csv === "string") {
340
+ yield* parseString(csv, options);
341
+ } else if (csv instanceof ReadableStream) {
342
+ yield* parseStream(csv, options);
343
+ } else if (csv instanceof Response) {
344
+ yield* parseResponse(csv, options);
530
345
  }
531
346
  }
532
- (function (parse) {})(parse || (parse = {}));
533
- parse.toArray = toArray;
534
-
347
+ ((parse) => {
348
+ parse.toArray = toArray;
349
+ })(parse || (parse = {}));
535
350
  export {
536
351
  Field,
537
352
  FieldDelimiter,
@@ -540,6 +355,8 @@ export {
540
355
  RecordDelimiter,
541
356
  parse,
542
357
  parseBinaryStream,
358
+ parseResponse,
359
+ parseStream,
360
+ parseString,
543
361
  parseStringStream,
544
- streamingParse,
545
362
  };
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CSV={})}(this,(function(e){"use strict";const t=Symbol.for("web-streams-csv.FieldDelimiter"),i=Symbol.for("web-streams-csv.RecordDelimiter"),r=Symbol.for("web-streams-csv.Field"),n="\n";function s(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}class o extends TransformStream{#e;#t;#i;#r;#n;#s="";get demiliter(){return this.#e}get quotation(){return this.#i}constructor({demiliter:e=",",quotation:t='"'}={}){!function(e){if("string"==typeof e.quotation&&0===e.quotation.length)throw new Error("quotation must not be empty");if("string"==typeof e.demiliter&&0===e.demiliter.length)throw new Error("demiliter must not be empty");if(e.quotation.includes(n)||e.quotation.includes("\r"))throw new Error("quotation must not include CR or LF");if(e.demiliter.includes(n)||e.demiliter.includes("\r"))throw new Error("demiliter must not include CR or LF");if(e.demiliter.includes(e.quotation)||e.quotation.includes(e.demiliter))throw new Error("demiliter and quotation must not include each other as a substring")}({demiliter:e,quotation:t}),super({transform:(e,t)=>{if(0!==e.length){this.#s+=e;for(const e of this.#o({flush:!1}))t.enqueue(e)}},flush:e=>{for(const t of this.#o({flush:!0}))e.enqueue(t)}}),this.#e=e,this.#t=e.length,this.#i=t,this.#r=t.length;const i=s(e),r=s(t);this.#n=new RegExp(`^(?:(?!${r})(?!${i})(?![\\r\\n]))([\\S\\s\\uFEFF\\xA0]+?)(?=${r}|${i}|\\r|\\n|$)`)}*#o({flush:e}){let n=null;for(let s;s=this.#a({flush:e});)switch(s.type){case r:n?n.value+=s.value:n=s;break;case t:case i:n&&(yield n,n=null),yield s}n&&(yield n)}#a({flush:e=!1}={}){if(0===this.#s.length)return null;if(this.#s.startsWith("\r\n"))return this.#s=this.#s.slice(2),{type:i,value:"\r\n"};if(this.#s.startsWith(n))return this.#s=this.#s.slice(1),{type:i,value:n};if(this.#s.startsWith(this.#e))return this.#s=this.#s.slice(this.#t),{type:t,value:this.#e};if(this.#s.startsWith(this.#i))return!1===e&&this.#s.endsWith(this.#i)?null:this.extractQuotedString();const s=this.#n.exec(this.#s);return s?!1===e&&s[0].length===this.#s.length?null:(this.#s=this.#s.slice(s[0].length),{type:r,value:s[0]}):null}extractQuotedString(){let e=this.#r,t="";for(;e<this.#s.length;)if(this.#s.slice(e,e+this.#r)!==this.quotation||this.#s.slice(e+this.#r,e+2*this.#r)!==this.quotation){if(this.#s.slice(e,e+this.#r)===this.quotation)return this.#s=this.#s.slice(e+this.#r),{type:r,value:t};t+=this.#s[e],e++}else t+=this.quotation,e+=2*this.#r;return null}}class a extends TransformStream{#u=0;#h=[];#l;#f=!1;constructor(e={}){super({transform:(e,n)=>{switch(e.type){case r:this.#f=!0,this.#h[this.#u]=e.value;break;case t:this.#u++;break;case i:if(void 0===this.#l)this.#d(this.#h);else if(this.#f){const e=Object.fromEntries(this.#l.filter((e=>e)).map(((e,t)=>[e,this.#h.at(t)])));n.enqueue(e)}this.#u=0,this.#h=new Array(this.#l?.length),this.#f=!1}},flush:e=>{if(0!==this.#u&&void 0!==this.#l&&this.#f){const t=Object.fromEntries(this.#l.filter((e=>e)).map(((e,t)=>[e,this.#h.at(t)])));e.enqueue(t)}}}),void 0!==e.header&&Array.isArray(e.header)&&this.#d(e.header)}#d(e){if(this.#l=e,0===this.#l.length)throw new Error("The header must not be empty.");if(new Set(this.#l).size!==this.#l.length)throw new Error("The header must not contain duplicate fields.")}}class u extends ReadableStream{constructor(e){super({start(t){t.enqueue(e),t.close()}})}}async function h(...e){const t=[];for await(const i of this(...e))t.push(i);return t}async function*l(e,t){let i;const r=new ReadableStream({start:e=>i=e});await e.pipeThrough(new o(t)).pipeThrough(new a(t)).pipeTo(new WritableStream({write:e=>i.enqueue(e),close:()=>i.close()}));const n=r.getReader();try{for(;;){const{value:e,done:t}=await n.read();if(t)break;yield e}}finally{n.releaseLock()}}async function*f(e,t){yield*l(new u(e),t)}async function*d(e,t){const{charset:i,fatal:r,ignoreBOM:n,decomposition:s}=t??{};yield*l([...s?[new DecompressionStream(s)]:[],new TextDecoderStream(i,{fatal:r,ignoreBOM:n})].reduce(((e,t)=>e.pipeThrough(t)),e),t)}function c(e,t){const{headers:i}=e,r=i.get("content-type")??"text/csv",n=function(e){const[t,...i]=e.split(";"),r={type:t.trim(),parameters:{}};for(const e of i){const[t,i]=e.split("=");r.parameters[t.trim()]=i.trim()}return r}(r);if("text/csv"!==n.type)throw new Error(`Invalid mime type: ${r}`);const s=i.get("content-encoding")??void 0,o=n.parameters.charset??"utf-8";if(null===e.body)throw new Error("Response body is null");return d(e.body,{decomposition:s,charset:o,...t})}async function*m(e,t){const[i,r]=e.tee(),n=i.getReader(),{value:s}=await n.read();n.releaseLock(),"string"==typeof s?yield*l(r,t):s instanceof Uint8Array&&(yield*d(r,t))}async function*y(e,t){"string"==typeof e?yield*f(e,t):e instanceof ReadableStream?yield*m(e,t):e instanceof Response&&(yield*c(e,t))}!function(e){e.toArray=h}(l||(l={})),function(e){e.toArray=h}(f||(f={})),function(e){e.toArray=h}(d||(d={})),function(e){e.toArray=h}(c||(c={})),function(e){e.toArray=h}(m||(m={})),function(e){e.toArray=h}(y||(y={})),e.Field=r,e.FieldDelimiter=t,e.LexerTransformer=o,e.RecordAssemblerTransformar=a,e.RecordDelimiter=i,e.parse=y,e.parseBinaryStream=d,e.parseResponse=c,e.parseStream=m,e.parseString=f,e.parseStringStream=l}));
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "web-csv-toolbox",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "A CSV Toolbox utilizing Web Standard APIs.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "module": "lib/index.js",
8
8
  "types": "lib/index.d.ts",
9
+ "unpkg": "lib/index.umd.js",
9
10
  "engines": {
10
11
  "node": ">=18.0.0"
11
12
  },
@@ -27,7 +28,7 @@
27
28
  "lint": "biome lint .",
28
29
  "check": "biome check src --apply",
29
30
  "check:no-apply": "biome check src",
30
- "build": "rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2 && biome format lib --write",
31
+ "build": "rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2 && biome check lib --apply",
31
32
  "prepare": "husky install"
32
33
  },
33
34
  "repository": {
@@ -45,12 +46,13 @@
45
46
  "bugs": {
46
47
  "url": "https://github.com/kamiazya/web-csv-toolbox/issues"
47
48
  },
48
- "homepage": "https://github.com/kamiazya/web-csv-toolbox#readme",
49
+ "homepage": "https://kamiazya.github.io/web-csv-toolbox/",
49
50
  "devDependencies": {
50
51
  "@biomejs/biome": "1.4.1",
51
52
  "@changesets/changelog-github": "^0.5.0",
52
53
  "@changesets/cli": "^2.27.1",
53
54
  "@fast-check/vitest": "^0.0.9",
55
+ "@rollup/plugin-terser": "^0.4.4",
54
56
  "changesets-github-release": "^0.1.0",
55
57
  "husky": "^8.0.0",
56
58
  "jsdom": "^23.0.1",
@@ -60,7 +62,8 @@
60
62
  "rollup-plugin-dts": "^6.1.0",
61
63
  "rollup-plugin-typescript2": "^0.36.0",
62
64
  "typedoc": "^0.25.4",
65
+ "typedoc-plugin-mdn-links": "^3.1.9",
63
66
  "typescript": "^5.3.2",
64
67
  "vitest": "^1.1.0"
65
68
  }
66
- }
69
+ }