toon-formatter 1.1.1 → 2.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.
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Encryptor Class
3
+ *
4
+ * Handles encryption and decryption of data using specified algorithms.
5
+ *
6
+ * Supported Algorithms:
7
+ * - 'aes-256-gcm': Symmetric encryption (Node.js crypto). High security, authenticated encryption.
8
+ * - 'xor': Simple XOR cipher. Low security, good for obfuscation only.
9
+ * - 'base64': Base64 encoding. No security, just encoding.
10
+ */
11
+
12
+ import crypto from 'crypto';
13
+
14
+ export class Encryptor {
15
+ /**
16
+ * Creates an Encryptor instance
17
+ * @param {string|Buffer|null} key - Encryption key (required for AES-256-GCM and XOR)
18
+ * @param {string} algorithm - Algorithm to use: 'aes-256-gcm', 'xor', or 'base64'
19
+ * @throws {Error} If key is missing for algorithms that require it
20
+ * @throws {Error} If key is invalid for the selected algorithm
21
+ */
22
+ constructor(key = null, algorithm = 'aes-256-gcm') {
23
+ this.key = key;
24
+ this.algorithm = algorithm.toLowerCase();
25
+
26
+ // Validate algorithm
27
+ const validAlgorithms = ['aes-256-gcm', 'xor', 'base64'];
28
+ if (!validAlgorithms.includes(this.algorithm)) {
29
+ throw new Error(`Unsupported algorithm: ${this.algorithm}. Valid options: ${validAlgorithms.join(', ')}`);
30
+ }
31
+
32
+ // Validate key for AES-256-GCM
33
+ if (this.algorithm === 'aes-256-gcm') {
34
+ if (!this.key) {
35
+ throw new Error('Key is required for AES-256-GCM encryption.');
36
+ }
37
+ this._validateAesKey();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Generates a random 32-byte (256-bit) key suitable for AES-256-GCM
43
+ * @returns {Buffer} 32-byte random key
44
+ * @example
45
+ * const key = Encryptor.generateKey();
46
+ * console.log(key.length); // 32
47
+ */
48
+ static generateKey() {
49
+ return crypto.randomBytes(32);
50
+ }
51
+
52
+ /**
53
+ * Encrypts the provided string data
54
+ * @param {string} data - Data to encrypt (must be a string)
55
+ * @returns {string} Encrypted data
56
+ * @throws {Error} If data is not a string
57
+ * @throws {Error} If encryption fails
58
+ */
59
+ encrypt(data) {
60
+ if (typeof data !== 'string') {
61
+ throw new Error('Data to encrypt must be a string.');
62
+ }
63
+
64
+ switch (this.algorithm) {
65
+ case 'aes-256-gcm':
66
+ return this._aesEncrypt(data);
67
+ case 'xor':
68
+ return this._xorEncrypt(data);
69
+ case 'base64':
70
+ return Buffer.from(data, 'utf-8').toString('base64');
71
+ default:
72
+ throw new Error(`Unsupported algorithm: ${this.algorithm}`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Decrypts the provided encrypted string data
78
+ * @param {string} encryptedData - Data to decrypt (must be a string)
79
+ * @returns {string} Decrypted data
80
+ * @throws {Error} If encryptedData is not a string
81
+ * @throws {Error} If decryption fails
82
+ */
83
+ decrypt(encryptedData) {
84
+ if (typeof encryptedData !== 'string') {
85
+ throw new Error('Data to decrypt must be a string.');
86
+ }
87
+
88
+ switch (this.algorithm) {
89
+ case 'aes-256-gcm':
90
+ return this._aesDecrypt(encryptedData);
91
+ case 'xor':
92
+ return this._xorDecrypt(encryptedData);
93
+ case 'base64':
94
+ try {
95
+ return Buffer.from(encryptedData, 'base64').toString('utf-8');
96
+ } catch (error) {
97
+ throw new Error(`Invalid Base64 data: ${error.message}`);
98
+ }
99
+ default:
100
+ throw new Error(`Unsupported algorithm: ${this.algorithm}`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Validates the AES key length (must be 32 bytes for AES-256)
106
+ * @private
107
+ * @throws {Error} If key is not 32 bytes
108
+ */
109
+ _validateAesKey() {
110
+ const keyBuffer = Buffer.isBuffer(this.key) ? this.key : Buffer.from(this.key);
111
+ if (keyBuffer.length !== 32) {
112
+ throw new Error(`AES-256-GCM requires a 32-byte (256-bit) key. Provided key is ${keyBuffer.length} bytes.`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Encrypts text using AES-256-GCM
118
+ * @private
119
+ * @param {string} text - Text to encrypt
120
+ * @returns {string} Encrypted text in format: iv:authTag:encryptedData (all hex)
121
+ * @throws {Error} If encryption fails
122
+ */
123
+ _aesEncrypt(text) {
124
+ try {
125
+ // Generate random 12-byte IV (96 bits, recommended for GCM)
126
+ const iv = crypto.randomBytes(12);
127
+
128
+ // Ensure key is a Buffer
129
+ const keyBuffer = Buffer.isBuffer(this.key) ? this.key : Buffer.from(this.key);
130
+
131
+ // Create cipher
132
+ const cipher = crypto.createCipheriv('aes-256-gcm', keyBuffer, iv);
133
+
134
+ // Encrypt
135
+ let encrypted = cipher.update(text, 'utf-8', 'hex');
136
+ encrypted += cipher.final('hex');
137
+
138
+ // Get authentication tag (16 bytes for GCM)
139
+ const authTag = cipher.getAuthTag();
140
+
141
+ // Return format: iv:authTag:encryptedData (all in hex)
142
+ return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
143
+ } catch (error) {
144
+ throw new Error(`AES-256-GCM encryption failed: ${error.message}`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Decrypts text using AES-256-GCM
150
+ * @private
151
+ * @param {string} encryptedText - Encrypted text in format: iv:authTag:encryptedData
152
+ * @returns {string} Decrypted text
153
+ * @throws {Error} If decryption fails or authentication fails
154
+ */
155
+ _aesDecrypt(encryptedText) {
156
+ try {
157
+ // Split the encrypted text
158
+ const parts = encryptedText.split(':');
159
+ if (parts.length !== 3) {
160
+ throw new Error('Invalid encrypted data format. Expected format: iv:authTag:encryptedData');
161
+ }
162
+
163
+ const iv = Buffer.from(parts[0], 'hex');
164
+ const authTag = Buffer.from(parts[1], 'hex');
165
+ const encrypted = parts[2];
166
+
167
+ // Ensure key is a Buffer
168
+ const keyBuffer = Buffer.isBuffer(this.key) ? this.key : Buffer.from(this.key);
169
+
170
+ // Create decipher
171
+ const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuffer, iv);
172
+ decipher.setAuthTag(authTag);
173
+
174
+ // Decrypt
175
+ let decrypted = decipher.update(encrypted, 'hex', 'utf-8');
176
+ decrypted += decipher.final('utf-8');
177
+
178
+ return decrypted;
179
+ } catch (error) {
180
+ throw new Error(`AES-256-GCM decryption failed: ${error.message}`);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Encrypts text using XOR cipher
186
+ * @private
187
+ * @param {string} text - Text to encrypt
188
+ * @returns {string} Encrypted text as hex string
189
+ * @throws {Error} If key is missing or encryption fails
190
+ */
191
+ _xorEncrypt(text) {
192
+ if (!this.key) {
193
+ throw new Error('Key is required for XOR cipher.');
194
+ }
195
+
196
+ try {
197
+ // Convert key and text to buffers
198
+ const keyBuffer = Buffer.isBuffer(this.key) ? this.key : Buffer.from(String(this.key), 'utf-8');
199
+ const textBuffer = Buffer.from(text, 'utf-8');
200
+ const result = Buffer.alloc(textBuffer.length);
201
+
202
+ // XOR each byte with corresponding key byte (cycling through key)
203
+ for (let i = 0; i < textBuffer.length; i++) {
204
+ result[i] = textBuffer[i] ^ keyBuffer[i % keyBuffer.length];
205
+ }
206
+
207
+ // Return as hex string for safe transport/storage
208
+ return result.toString('hex');
209
+ } catch (error) {
210
+ throw new Error(`XOR encryption failed: ${error.message}`);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Decrypts text using XOR cipher
216
+ * @private
217
+ * @param {string} hexText - Encrypted text as hex string
218
+ * @returns {string} Decrypted text
219
+ * @throws {Error} If key is missing, format is invalid, or decryption fails
220
+ */
221
+ _xorDecrypt(hexText) {
222
+ if (!this.key) {
223
+ throw new Error('Key is required for XOR cipher.');
224
+ }
225
+
226
+ try {
227
+ // Convert key and encrypted data to buffers
228
+ const keyBuffer = Buffer.isBuffer(this.key) ? this.key : Buffer.from(String(this.key), 'utf-8');
229
+ const encryptedBuffer = Buffer.from(hexText, 'hex');
230
+ const result = Buffer.alloc(encryptedBuffer.length);
231
+
232
+ // XOR each byte with corresponding key byte (cycling through key)
233
+ for (let i = 0; i < encryptedBuffer.length; i++) {
234
+ result[i] = encryptedBuffer[i] ^ keyBuffer[i % keyBuffer.length];
235
+ }
236
+
237
+ return result.toString('utf-8');
238
+ } catch (error) {
239
+ throw new Error(`XOR decryption failed: ${error.message}`);
240
+ }
241
+ }
242
+ }
243
+
244
+ export default Encryptor;
package/src/index.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  extractXmlFromString,
24
24
  extractCsvFromString
25
25
  } from './utils.js';
26
+ import { Encryptor } from './encryptor.js';
26
27
 
27
28
  // Exports
28
29
  export {
@@ -32,13 +33,350 @@ export {
32
33
  csvToToonSync, csvToToon, toonToCsvSync, toonToCsv,
33
34
  validateToonString, validateToonStringSync,
34
35
  encodeXmlReservedChars, splitByDelimiter, parseValue, formatValue,
35
- extractJsonFromString, extractXmlFromString, extractCsvFromString
36
+ extractJsonFromString, extractXmlFromString, extractCsvFromString,
37
+ Encryptor
36
38
  };
37
39
 
38
40
  /**
39
- * Main converter class for easy usage
41
+ * Main converter class for TOON format conversions
42
+ *
43
+ * Supports both static usage (backward compatible) and instance usage (with encryption).
44
+ *
45
+ * @example
46
+ * // Static usage (no encryption) - backward compatible
47
+ * const toon = ToonConverter.fromJson({ name: "Alice" });
48
+ *
49
+ * @example
50
+ * // Instance usage with encryption
51
+ * const key = Encryptor.generateKey();
52
+ * const encryptor = new Encryptor(key, 'aes-256-gcm');
53
+ * const converter = new ToonConverter(encryptor);
54
+ * const encrypted = converter.fromJson({ name: "Alice" }, { conversionMode: 'export' });
40
55
  */
41
56
  export class ToonConverter {
57
+ /**
58
+ * Creates a ToonConverter instance
59
+ * @param {Encryptor|null} [encryptor=null] - Optional Encryptor instance for encryption support
60
+ * @example
61
+ * // Without encryption
62
+ * const converter = new ToonConverter();
63
+ *
64
+ * @example
65
+ * // With encryption
66
+ * const key = Encryptor.generateKey();
67
+ * const encryptor = new Encryptor(key, 'aes-256-gcm');
68
+ * const converter = new ToonConverter(encryptor);
69
+ */
70
+ constructor(encryptor = null) {
71
+ this.encryptor = encryptor;
72
+ }
73
+
74
+ // ========================================
75
+ // Private Helper Methods
76
+ // ========================================
77
+
78
+ /**
79
+ * Applies encryption logic based on conversion mode (synchronous)
80
+ * @private
81
+ * @param {Function} converterFn - The converter function to call
82
+ * @param {*} data - Data to convert
83
+ * @param {string} mode - Conversion mode: 'no_encryption', 'middleware', 'ingestion', 'export'
84
+ * @returns {*} Converted (and possibly encrypted) data
85
+ */
86
+ _convertWithEncryption(converterFn, data, mode) {
87
+ // If no encryptor or mode is 'no_encryption', just convert normally
88
+ if (!this.encryptor || mode === 'no_encryption') {
89
+ return converterFn(data);
90
+ }
91
+
92
+ switch (mode) {
93
+ case 'middleware':
94
+ // Encrypted → Encrypted (Decrypt → Convert → Re-encrypt)
95
+ const decrypted = this.encryptor.decrypt(data);
96
+ const converted = converterFn(decrypted);
97
+ return this.encryptor.encrypt(converted);
98
+
99
+ case 'ingestion':
100
+ // Encrypted → Plain (Decrypt → Convert)
101
+ const decryptedData = this.encryptor.decrypt(data);
102
+ return converterFn(decryptedData);
103
+
104
+ case 'export':
105
+ // Plain → Encrypted (Convert → Encrypt)
106
+ const plainConverted = converterFn(data);
107
+ return this.encryptor.encrypt(plainConverted);
108
+
109
+ default:
110
+ return converterFn(data);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Applies encryption logic based on conversion mode (asynchronous)
116
+ * @private
117
+ * @param {Function} converterFn - The async converter function to call
118
+ * @param {*} data - Data to convert
119
+ * @param {string} mode - Conversion mode: 'no_encryption', 'middleware', 'ingestion', 'export'
120
+ * @returns {Promise<*>} Converted (and possibly encrypted) data
121
+ */
122
+ async _convertWithEncryptionAsync(converterFn, data, mode) {
123
+ // If no encryptor or mode is 'no_encryption', just convert normally
124
+ if (!this.encryptor || mode === 'no_encryption') {
125
+ return await converterFn(data);
126
+ }
127
+
128
+ switch (mode) {
129
+ case 'middleware':
130
+ // Encrypted → Encrypted (Decrypt → Convert → Re-encrypt)
131
+ const decrypted = this.encryptor.decrypt(data);
132
+ const converted = await converterFn(decrypted);
133
+ return this.encryptor.encrypt(converted);
134
+
135
+ case 'ingestion':
136
+ // Encrypted → Plain (Decrypt → Convert)
137
+ const decryptedData = this.encryptor.decrypt(data);
138
+ return await converterFn(decryptedData);
139
+
140
+ case 'export':
141
+ // Plain → Encrypted (Convert → Encrypt)
142
+ const plainConverted = await converterFn(data);
143
+ return this.encryptor.encrypt(plainConverted);
144
+
145
+ default:
146
+ return await converterFn(data);
147
+ }
148
+ }
149
+
150
+ // ========================================
151
+ // Instance Methods (Support Encryption)
152
+ // ========================================
153
+
154
+ /**
155
+ * Convert JSON to TOON (Sync, Instance Method)
156
+ * @param {*} jsonData - JSON data (object, array, or primitive)
157
+ * @param {Object} [options={}] - Conversion options
158
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode: 'no_encryption', 'middleware', 'ingestion', 'export'
159
+ * @returns {string} TOON formatted string (possibly encrypted)
160
+ */
161
+ fromJson(jsonData, options = {}) {
162
+ const { conversionMode = 'no_encryption' } = options;
163
+ return this._convertWithEncryption(jsonToToonSync, jsonData, conversionMode);
164
+ }
165
+
166
+ /**
167
+ * Convert JSON to TOON (Async, Instance Method)
168
+ * @param {*} jsonData - JSON data
169
+ * @param {Object} [options={}] - Conversion options
170
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
171
+ * @returns {Promise<string>} TOON formatted string (possibly encrypted)
172
+ */
173
+ async fromJsonAsync(jsonData, options = {}) {
174
+ const { conversionMode = 'no_encryption' } = options;
175
+ return this._convertWithEncryptionAsync(jsonToToon, jsonData, conversionMode);
176
+ }
177
+
178
+ /**
179
+ * Convert TOON to JSON (Sync, Instance Method)
180
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
181
+ * @param {Object} [options={}] - Conversion options
182
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
183
+ * @param {boolean} [options.returnJson=false] - If true, returns JSON string; if false, returns object
184
+ * @returns {*} Parsed JSON data (object or string)
185
+ */
186
+ toJson(toonString, options = {}) {
187
+ const { conversionMode = 'no_encryption', returnJson = false } = options;
188
+ return this._convertWithEncryption(
189
+ (data) => toonToJsonSync(data, returnJson),
190
+ toonString,
191
+ conversionMode
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Convert TOON to JSON (Async, Instance Method)
197
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
198
+ * @param {Object} [options={}] - Conversion options
199
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
200
+ * @param {boolean} [options.returnJson=false] - If true, returns JSON string; if false, returns object
201
+ * @returns {Promise<*>} Parsed JSON data (object or string)
202
+ */
203
+ async toJsonAsync(toonString, options = {}) {
204
+ const { conversionMode = 'no_encryption', returnJson = false } = options;
205
+ return this._convertWithEncryptionAsync(
206
+ (data) => toonToJson(data, returnJson),
207
+ toonString,
208
+ conversionMode
209
+ );
210
+ }
211
+
212
+ /**
213
+ * Convert YAML to TOON (Sync, Instance Method)
214
+ * @param {string} yamlString - YAML formatted string (possibly encrypted)
215
+ * @param {Object} [options={}] - Conversion options
216
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
217
+ * @returns {string} TOON formatted string (possibly encrypted)
218
+ */
219
+ fromYaml(yamlString, options = {}) {
220
+ const { conversionMode = 'no_encryption' } = options;
221
+ return this._convertWithEncryption(yamlToToonSync, yamlString, conversionMode);
222
+ }
223
+
224
+ /**
225
+ * Convert YAML to TOON (Async, Instance Method)
226
+ * @param {string} yamlString - YAML formatted string (possibly encrypted)
227
+ * @param {Object} [options={}] - Conversion options
228
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
229
+ * @returns {Promise<string>} TOON formatted string (possibly encrypted)
230
+ */
231
+ async fromYamlAsync(yamlString, options = {}) {
232
+ const { conversionMode = 'no_encryption' } = options;
233
+ return this._convertWithEncryptionAsync(yamlToToon, yamlString, conversionMode);
234
+ }
235
+
236
+ /**
237
+ * Convert TOON to YAML (Sync, Instance Method)
238
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
239
+ * @param {Object} [options={}] - Conversion options
240
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
241
+ * @returns {string} YAML formatted string
242
+ */
243
+ toYaml(toonString, options = {}) {
244
+ const { conversionMode = 'no_encryption' } = options;
245
+ return this._convertWithEncryption(toonToYamlSync, toonString, conversionMode);
246
+ }
247
+
248
+ /**
249
+ * Convert TOON to YAML (Async, Instance Method)
250
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
251
+ * @param {Object} [options={}] - Conversion options
252
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
253
+ * @returns {Promise<string>} YAML formatted string
254
+ */
255
+ async toYamlAsync(toonString, options = {}) {
256
+ const { conversionMode = 'no_encryption' } = options;
257
+ return this._convertWithEncryptionAsync(toonToYaml, toonString, conversionMode);
258
+ }
259
+
260
+ /**
261
+ * Convert XML to TOON (Sync, Instance Method)
262
+ * @param {string} xmlString - XML formatted string (possibly encrypted)
263
+ * @param {Object} [options={}] - Conversion options
264
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
265
+ * @returns {string} TOON formatted string (possibly encrypted)
266
+ */
267
+ fromXml(xmlString, options = {}) {
268
+ const { conversionMode = 'no_encryption' } = options;
269
+ return this._convertWithEncryption(xmlToToonSync, xmlString, conversionMode);
270
+ }
271
+
272
+ /**
273
+ * Convert XML to TOON (Async, Instance Method)
274
+ * @param {string} xmlString - XML formatted string (possibly encrypted)
275
+ * @param {Object} [options={}] - Conversion options
276
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
277
+ * @returns {Promise<string>} TOON formatted string (possibly encrypted)
278
+ */
279
+ async fromXmlAsync(xmlString, options = {}) {
280
+ const { conversionMode = 'no_encryption' } = options;
281
+ return this._convertWithEncryptionAsync(xmlToToon, xmlString, conversionMode);
282
+ }
283
+
284
+ /**
285
+ * Convert TOON to XML (Sync, Instance Method)
286
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
287
+ * @param {Object} [options={}] - Conversion options
288
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
289
+ * @returns {string} XML formatted string
290
+ */
291
+ toXml(toonString, options = {}) {
292
+ const { conversionMode = 'no_encryption' } = options;
293
+ return this._convertWithEncryption(toonToXmlSync, toonString, conversionMode);
294
+ }
295
+
296
+ /**
297
+ * Convert TOON to XML (Async, Instance Method)
298
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
299
+ * @param {Object} [options={}] - Conversion options
300
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
301
+ * @returns {Promise<string>} XML formatted string
302
+ */
303
+ async toXmlAsync(toonString, options = {}) {
304
+ const { conversionMode = 'no_encryption' } = options;
305
+ return this._convertWithEncryptionAsync(toonToXml, toonString, conversionMode);
306
+ }
307
+
308
+ /**
309
+ * Convert CSV to TOON (Sync, Instance Method)
310
+ * @param {string} csvString - CSV formatted string (possibly encrypted)
311
+ * @param {Object} [options={}] - Conversion options
312
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
313
+ * @returns {string} TOON formatted string (possibly encrypted)
314
+ */
315
+ fromCsv(csvString, options = {}) {
316
+ const { conversionMode = 'no_encryption' } = options;
317
+ return this._convertWithEncryption(csvToToonSync, csvString, conversionMode);
318
+ }
319
+
320
+ /**
321
+ * Convert CSV to TOON (Async, Instance Method)
322
+ * @param {string} csvString - CSV formatted string (possibly encrypted)
323
+ * @param {Object} [options={}] - Conversion options
324
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
325
+ * @returns {Promise<string>} TOON formatted string (possibly encrypted)
326
+ */
327
+ async fromCsvAsync(csvString, options = {}) {
328
+ const { conversionMode = 'no_encryption' } = options;
329
+ return this._convertWithEncryptionAsync(csvToToon, csvString, conversionMode);
330
+ }
331
+
332
+ /**
333
+ * Convert TOON to CSV (Sync, Instance Method)
334
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
335
+ * @param {Object} [options={}] - Conversion options
336
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
337
+ * @returns {string} CSV formatted string
338
+ */
339
+ toCsv(toonString, options = {}) {
340
+ const { conversionMode = 'no_encryption' } = options;
341
+ return this._convertWithEncryption(toonToCsvSync, toonString, conversionMode);
342
+ }
343
+
344
+ /**
345
+ * Convert TOON to CSV (Async, Instance Method)
346
+ * @param {string} toonString - TOON formatted string (possibly encrypted)
347
+ * @param {Object} [options={}] - Conversion options
348
+ * @param {string} [options.conversionMode='no_encryption'] - Encryption mode
349
+ * @returns {Promise<string>} CSV formatted string
350
+ */
351
+ async toCsvAsync(toonString, options = {}) {
352
+ const { conversionMode = 'no_encryption' } = options;
353
+ return this._convertWithEncryptionAsync(toonToCsv, toonString, conversionMode);
354
+ }
355
+
356
+ /**
357
+ * Validate a TOON string (Instance Method)
358
+ * Note: Validation does not support encryption modes
359
+ * @param {string} toonString - TOON formatted string to validate
360
+ * @returns {{isValid: boolean, error: string|null}} Validation result
361
+ */
362
+ validate(toonString) {
363
+ return validateToonStringSync(toonString);
364
+ }
365
+
366
+ /**
367
+ * Validate a TOON string (Async, Instance Method)
368
+ * Note: Validation does not support encryption modes
369
+ * @param {string} toonString - TOON formatted string to validate
370
+ * @returns {Promise<{isValid: boolean, error: string|null}>} Validation result
371
+ */
372
+ async validateAsync(toonString) {
373
+ return validateToonString(toonString);
374
+ }
375
+
376
+ // ========================================
377
+ // Static Methods (Backward Compatibility)
378
+ // ========================================
379
+
42
380
  /**
43
381
  * Convert JSON to TOON (Sync)
44
382
  * @param {*} jsonData - JSON data (object, array, or primitive)
@@ -58,21 +396,23 @@ export class ToonConverter {
58
396
  }
59
397
 
60
398
  /**
61
- * Convert TOON to JSON (Sync)
399
+ * Convert TOON to JSON (Sync, Static Method)
62
400
  * @param {string} toonString - TOON formatted string
63
- * @returns {*} Parsed JSON data
401
+ * @param {boolean} [returnJson=false] - If true, returns JSON string; if false, returns object
402
+ * @returns {*} Parsed JSON data (object or string)
64
403
  */
65
- static toJson(toonString) {
66
- return toonToJsonSync(toonString);
404
+ static toJson(toonString, returnJson = false) {
405
+ return toonToJsonSync(toonString, returnJson);
67
406
  }
68
407
 
69
408
  /**
70
- * Convert TOON to JSON (Async)
409
+ * Convert TOON to JSON (Async, Static Method)
71
410
  * @param {string} toonString - TOON formatted string
72
- * @returns {Promise<*>} Parsed JSON data
411
+ * @param {boolean} [returnJson=false] - If true, returns JSON string; if false, returns object
412
+ * @returns {Promise<*>} Parsed JSON data (object or string)
73
413
  */
74
- static async toJsonAsync(toonString) {
75
- return toonToJson(toonString);
414
+ static async toJsonAsync(toonString, returnJson = false) {
415
+ return toonToJson(toonString, returnJson);
76
416
  }
77
417
 
78
418
  /**
package/src/json.js CHANGED
@@ -120,10 +120,11 @@ export async function jsonToToon(data) {
120
120
  /**
121
121
  * Converts TOON to JSON format (Synchronous)
122
122
  * @param {string} toonString - TOON formatted string
123
- * @returns {Object} JSON object
123
+ * @param {boolean} [returnJson=false] - If true, returns JSON string; if false, returns object
124
+ * @returns {Object|string} JSON object or JSON string
124
125
  * @throws {Error} If TOON string is invalid
125
126
  */
126
- export function toonToJsonSync(toonString) {
127
+ export function toonToJsonSync(toonString, returnJson = false) {
127
128
  // Validate TOON string before conversion
128
129
  const validationStatus = validateToonStringSync(toonString);
129
130
  if (!validationStatus.isValid) {
@@ -136,7 +137,7 @@ export function toonToJsonSync(toonString) {
136
137
 
137
138
  // Pre-process: Check for Root Array or Root Primitive
138
139
  const firstLine = lines.find(l => l.trim() !== '');
139
- if (!firstLine) return {}; // Empty document
140
+ if (!firstLine) return returnJson ? '{}' : {}; // Empty document
140
141
 
141
142
  // Root Array detection: [N]... at start of line
142
143
  if (firstLine.trim().startsWith('[')) {
@@ -324,14 +325,15 @@ export function toonToJsonSync(toonString) {
324
325
  }
325
326
  }
326
327
 
327
- return root;
328
+ return returnJson ? JSON.stringify(root) : root;
328
329
  }
329
330
 
330
331
  /**
331
332
  * Converts TOON to JSON format (Async)
332
333
  * @param {string} toonString - TOON formatted string
333
- * @returns {Promise<Object>} Parsed JSON data
334
+ * @param {boolean} [returnJson=false] - If true, returns JSON string; if false, returns object
335
+ * @returns {Promise<Object|string>} Parsed JSON data (object or string)
334
336
  */
335
- export async function toonToJson(toonString) {
336
- return toonToJsonSync(toonString);
337
+ export async function toonToJson(toonString, returnJson = false) {
338
+ return toonToJsonSync(toonString, returnJson);
337
339
  }
@@ -1,15 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: [ankitpal181] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
- # patreon: # Replace with a single Patreon username
5
- # open_collective: # Replace with a single Open Collective username
6
- # ko_fi: # Replace with a single Ko-fi username
7
- # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
- # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
- # liberapay: # Replace with a single Liberapay username
10
- # issuehunt: # Replace with a single IssueHunt username
11
- # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12
- # polar: # Replace with a single Polar username
13
- # buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14
- # thanks_dev: # Replace with a single thanks.dev username
15
- # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']