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.
- package/CHANGELOG.md +95 -0
- package/README.md +532 -5
- package/package.json +11 -4
- package/src/encryptor.js +244 -0
- package/src/index.js +350 -10
- package/src/json.js +9 -7
- package/.github/FUNDING.yml +0 -15
- package/ENHANCEMENTS.md +0 -124
- package/test/basic.test.js +0 -139
- package/test/converters.test.js +0 -135
package/src/encryptor.js
ADDED
|
@@ -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
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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
|
}
|
package/.github/FUNDING.yml
DELETED
|
@@ -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']
|