@synet/encoder 1.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,654 @@
1
+ /**
2
+ * Encoder Unit - Conscious Encoding/Decoding Operations
3
+ *
4
+ * SYNET Unit Architecture v1.0.6 Implementation
5
+ *
6
+ * Philosophy: One unit, one goal - reliable data transformation
7
+ *
8
+ * Native Capabilities:
9
+ * - encode() - Transform data to specified format
10
+ * - decode() - Reverse transformation with validation
11
+ * - detect() - Auto-detect encoding format
12
+ * - validate() - Verify encoded data integrity
13
+ * - chain() - Sequential encoding operations
14
+ *
15
+ * Supported Formats: Base64, Base64URL, Hex, URI, ASCII
16
+ *
17
+ * @author SYNET ALPHA
18
+ * @version 1.0.0
19
+ * @follows Unit Architecture Doctrine v1.0.6
20
+ */
21
+
22
+ import { Unit, createUnitSchema, type UnitProps, type TeachingContract } from '@synet/unit';
23
+ import { Result } from './result.js';
24
+
25
+ // Doctrine #13: TYPE HIERARCHY CONSISTENCY (Config → Props → State → Output)
26
+
27
+ /**
28
+ * External input configuration for static create()
29
+ */
30
+ export interface EncoderConfig {
31
+ defaultFormat?: EncodingFormat;
32
+ strictMode?: boolean;
33
+ autoDetect?: boolean;
34
+ maxInputSize?: number;
35
+ validateOutput?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Internal state after validation (implements UnitProps)
40
+ */
41
+ export interface EncoderProps extends UnitProps {
42
+ defaultFormat: EncodingFormat;
43
+ strictMode: boolean;
44
+ autoDetect: boolean;
45
+ maxInputSize: number;
46
+ validateOutput: boolean;
47
+ readonly created: Date;
48
+ }
49
+
50
+ /**
51
+ * Encoding format types
52
+ */
53
+ export type EncodingFormat = 'base64' | 'base64url' | 'hex' | 'uri' | 'ascii';
54
+
55
+ /**
56
+ * Encoding operation result
57
+ */
58
+ export interface EncodingResult {
59
+ readonly encoded: string;
60
+ readonly originalSize: number;
61
+ readonly encodedSize: number;
62
+ readonly format: EncodingFormat;
63
+ readonly compressionRatio: number;
64
+ readonly timestamp: Date;
65
+ }
66
+
67
+ /**
68
+ * Decoding operation result
69
+ */
70
+ export interface DecodingResult {
71
+ readonly decoded: string;
72
+ readonly detectedFormat: EncodingFormat;
73
+ readonly isValid: boolean;
74
+ readonly originalSize: number;
75
+ readonly timestamp: Date;
76
+ }
77
+
78
+ /**
79
+ * Format detection result
80
+ */
81
+ export interface DetectionResult {
82
+ readonly format: EncodingFormat;
83
+ readonly confidence: number;
84
+ readonly reasons: string[];
85
+ readonly timestamp: Date;
86
+ }
87
+
88
+ /**
89
+ * Validation result
90
+ */
91
+ export interface ValidationResult {
92
+ readonly isValid: boolean;
93
+ readonly format: EncodingFormat;
94
+ readonly errors: string[];
95
+ readonly suggestions: string[];
96
+ }
97
+
98
+ /**
99
+ * Encoder Implementation
100
+ *
101
+ * Doctrine #1: ZERO DEPENDENCY (only Node.js/browser native APIs)
102
+ * Doctrine #17: VALUE OBJECT FOUNDATION (immutable with identity and capabilities)
103
+ */
104
+ export class Encoder extends Unit<EncoderProps> {
105
+
106
+ // Doctrine #4: CREATE NOT CONSTRUCT (protected constructor)
107
+ protected constructor(props: EncoderProps) {
108
+ super(props);
109
+ }
110
+
111
+ // Doctrine #4: CREATE NOT CONSTRUCT (static create with validation)
112
+ static create(config: EncoderConfig = {}): Encoder {
113
+ // Doctrine #3: PROPS CONTAIN EVERYTHING (validate and transform config to props)
114
+ const props: EncoderProps = {
115
+ // Doctrine #7: EVERY UNIT MUST HAVE DNA
116
+ dna: createUnitSchema({
117
+ id: 'encoder',
118
+ version: '1.0.0'
119
+ }),
120
+ defaultFormat: config.defaultFormat || 'base64',
121
+ strictMode: config.strictMode ?? false,
122
+ autoDetect: config.autoDetect ?? true,
123
+ maxInputSize: config.maxInputSize || 10 * 1024 * 1024, // 10MB default
124
+ validateOutput: config.validateOutput ?? true,
125
+ created: new Date()
126
+ };
127
+
128
+ return new Encoder(props);
129
+ }
130
+
131
+ // Doctrine #11: ALWAYS HELP (living documentation)
132
+ help(): void {
133
+ console.log(`
134
+
135
+
136
+ Hi, I am Encoder Unit [${this.dna.id}] v${this.dna.version} - Data Transformation Service
137
+
138
+ IDENTITY: ${this.whoami()}
139
+ DEFAULT FORMAT: ${this.props.defaultFormat}
140
+ STRICT MODE: ${this.props.strictMode}
141
+ AUTO DETECT: ${this.props.autoDetect}
142
+ MAX INPUT: ${(this.props.maxInputSize / 1024 / 1024).toFixed(1)}MB
143
+ STATUS: IMMUTABLE (stateless operations)
144
+
145
+ NATIVE CAPABILITIES:
146
+ • encode(data, format?) - Transform data to specified format (Result for validation)
147
+ • decode(data, format?) - Reverse transformation with auto-detection (Result)
148
+ • detect(data) - Auto-detect encoding format with confidence (throws on error)
149
+ • validate(data, format) - Verify encoded data integrity (throws on error)
150
+ • chain(data, formats) - Sequential encoding operations (Result)
151
+ • reverse(data, formats) - Reverse sequential decoding (Result)
152
+
153
+ SUPPORTED FORMATS:
154
+ • base64: Standard Base64 encoding (RFC 4648)
155
+ • base64url: URL-safe Base64 encoding
156
+ • hex: Hexadecimal encoding (lowercase)
157
+ • uri: URI component encoding
158
+ • ascii: ASCII text encoding
159
+
160
+ USAGE EXAMPLES:
161
+ const encoder = Encoder.create();
162
+
163
+ // Simple operations (Result pattern for validation)
164
+ const encoded = encoder.encode('Hello World', 'base64');
165
+ if (encoded.isSuccess) {
166
+ console.log(encoded.value.encoded); // SGVsbG8gV29ybGQ=
167
+ }
168
+
169
+ // Auto-detection decoding
170
+ const decoded = encoder.decode('SGVsbG8gV29ybGQ=');
171
+ if (decoded.isSuccess) {
172
+ console.log(decoded.value.decoded); // Hello World
173
+ }
174
+
175
+ // Format detection
176
+ const format = encoder.detect('48656c6c6f'); // hex
177
+ console.log(format.format); // 'hex'
178
+
179
+ LEARNING CAPABILITIES:
180
+ Other units can learn from me:
181
+ unit.learn([encoder.teach()]);
182
+ unit.execute('encoder.encode', data, format);
183
+
184
+ I TEACH:
185
+ • encode(data, format) - Encoding capability
186
+ • decode(data, format) - Decoding capability
187
+ • detect(data) - Format detection capability
188
+ • validate(data, format) - Validation capability
189
+ • chain(data, formats) - Sequential encoding capability
190
+
191
+ `);
192
+ }
193
+
194
+ // Doctrine #2: TEACH/LEARN PARADIGM (every unit must teach)
195
+ // Doctrine #9: ALWAYS TEACH (explicit capability binding)
196
+ // Doctrine #19: CAPABILITY LEAKAGE PREVENTION (teach only native capabilities)
197
+ teach(): TeachingContract {
198
+ return {
199
+ // Doctrine #12: NAMESPACE EVERYTHING (unitId for namespacing)
200
+ unitId: this.dna.id,
201
+ capabilities: {
202
+ // Native encoding capabilities only - wrapped for unknown[] compatibility
203
+ encode: ((...args: unknown[]) => this.encode(args[0] as string, args[1] as EncodingFormat)) as (...args: unknown[]) => unknown,
204
+ decode: ((...args: unknown[]) => this.decode(args[0] as string, args[1] as EncodingFormat)) as (...args: unknown[]) => unknown,
205
+ detect: ((...args: unknown[]) => this.detect(args[0] as string)) as (...args: unknown[]) => unknown,
206
+ validate: ((...args: unknown[]) => this.validate(args[0] as string, args[1] as EncodingFormat)) as (...args: unknown[]) => unknown,
207
+ chain: ((...args: unknown[]) => this.chain(args[0] as string, args[1] as EncodingFormat[])) as (...args: unknown[]) => unknown,
208
+
209
+ // Metadata access
210
+ getDefaultFormat: (() => this.props.defaultFormat) as (...args: unknown[]) => unknown,
211
+ isStrictMode: (() => this.props.strictMode) as (...args: unknown[]) => unknown,
212
+ getMaxInputSize: (() => this.props.maxInputSize) as (...args: unknown[]) => unknown
213
+ }
214
+ };
215
+ }
216
+
217
+ // Doctrine #14: ERROR BOUNDARY CLARITY (Result for complex operations)
218
+
219
+ /**
220
+ * Encode data to specified format (Result - complex validation operation)
221
+ */
222
+ encode(data: string, format?: EncodingFormat): Result<EncodingResult> {
223
+ try {
224
+ const encodingFormat = format || this.props.defaultFormat;
225
+
226
+ // Input validation
227
+ if (data.length > this.props.maxInputSize) {
228
+ return Result.fail(`Input too large: ${data.length} bytes (max: ${this.props.maxInputSize})`);
229
+ }
230
+
231
+ if (this.props.strictMode && !this.isValidInput(data)) {
232
+ return Result.fail('Invalid input for strict mode');
233
+ }
234
+
235
+ const encoded = this.performEncode(data, encodingFormat);
236
+
237
+ // Output validation if enabled
238
+ if (this.props.validateOutput) {
239
+ const validation = this.validate(encoded, encodingFormat);
240
+ if (!validation.isValid) {
241
+ return Result.fail(`Output validation failed: ${validation.errors.join(', ')}`);
242
+ }
243
+ }
244
+
245
+ const result: EncodingResult = {
246
+ encoded,
247
+ originalSize: data.length,
248
+ encodedSize: encoded.length,
249
+ format: encodingFormat,
250
+ compressionRatio: encoded.length / data.length,
251
+ timestamp: new Date()
252
+ };
253
+
254
+ return Result.success(result);
255
+ } catch (error) {
256
+ return Result.fail(
257
+ `Encoding failed: ${error instanceof Error ? error.message : String(error)}`,
258
+ error
259
+ );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Decode data with optional format hint (Result - complex auto-detection operation)
265
+ */
266
+ decode(data: string, format?: EncodingFormat): Result<DecodingResult> {
267
+ try {
268
+ // Auto-detect format if not provided and auto-detection is enabled
269
+ let detectedFormat: EncodingFormat;
270
+ if (format) {
271
+ detectedFormat = format;
272
+ } else if (this.props.autoDetect) {
273
+ detectedFormat = this.detect(data).format;
274
+ } else {
275
+ detectedFormat = this.props.defaultFormat;
276
+ }
277
+
278
+ // Input validation
279
+ const validation = this.validate(data, detectedFormat);
280
+ if (!validation.isValid) {
281
+ if (this.props.strictMode) {
282
+ return Result.fail(`Invalid ${detectedFormat} format: ${validation.errors.join(', ')}`);
283
+ }
284
+ // In non-strict mode, still fail if the format is completely wrong
285
+ if (validation.errors.some(err => err.includes('invalid') || err.includes('Invalid'))) {
286
+ return Result.fail(
287
+ `Invalid ${detectedFormat} format: ${validation.errors.join(', ')}`,
288
+ new Error(`Validation failed for ${detectedFormat}`)
289
+ );
290
+ }
291
+ }
292
+
293
+ const decoded = this.performDecode(data, detectedFormat);
294
+
295
+ const result: DecodingResult = {
296
+ decoded,
297
+ detectedFormat,
298
+ isValid: validation.isValid,
299
+ originalSize: data.length,
300
+ timestamp: new Date()
301
+ };
302
+
303
+ return Result.success(result);
304
+ } catch (error) {
305
+ return Result.fail(
306
+ `Decoding failed: ${error instanceof Error ? error.message : String(error)}`,
307
+ error
308
+ );
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Chain multiple encoding operations (Result - complex multi-step operation)
314
+ */
315
+ chain(data: string, formats: EncodingFormat[]): Result<EncodingResult> {
316
+ try {
317
+ let result = data;
318
+ let totalRatio = 1;
319
+
320
+ for (const format of formats) {
321
+ const encoded = this.encode(result, format);
322
+ if (encoded.isFailure) {
323
+ return Result.fail(`Chain failed at ${format}: ${encoded.error}`);
324
+ }
325
+ result = encoded.value.encoded;
326
+ totalRatio *= encoded.value.compressionRatio;
327
+ }
328
+
329
+ const chainResult: EncodingResult = {
330
+ encoded: result,
331
+ originalSize: data.length,
332
+ encodedSize: result.length,
333
+ format: formats[formats.length - 1], // Final format
334
+ compressionRatio: totalRatio,
335
+ timestamp: new Date()
336
+ };
337
+
338
+ return Result.success(chainResult);
339
+ } catch (error) {
340
+ return Result.fail(
341
+ `Chain encoding failed: ${error instanceof Error ? error.message : String(error)}`,
342
+ error
343
+ );
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Reverse chain decoding (Result - complex multi-step operation)
349
+ */
350
+ reverse(data: string, formats: EncodingFormat[]): Result<DecodingResult> {
351
+ try {
352
+ let result = data;
353
+ const reversedFormats = [...formats].reverse();
354
+
355
+ for (const format of reversedFormats) {
356
+ const decoded = this.decode(result, format);
357
+ if (decoded.isFailure) {
358
+ return Result.fail(`Reverse chain failed at ${format}: ${decoded.error}`);
359
+ }
360
+ result = decoded.value.decoded;
361
+ }
362
+
363
+ const reverseResult: DecodingResult = {
364
+ decoded: result,
365
+ detectedFormat: formats[0], // Original format
366
+ isValid: true,
367
+ originalSize: data.length,
368
+ timestamp: new Date()
369
+ };
370
+
371
+ return Result.success(reverseResult);
372
+ } catch (error) {
373
+ return Result.fail(
374
+ `Reverse chain failed: ${error instanceof Error ? error.message : String(error)}`,
375
+ error
376
+ );
377
+ }
378
+ }
379
+
380
+ // Doctrine #14: ERROR BOUNDARY CLARITY (throws for simple operations)
381
+
382
+ /**
383
+ * Auto-detect encoding format (throw on error - simple classification operation)
384
+ */
385
+ detect(data: string): DetectionResult {
386
+ const patterns: Array<{ format: EncodingFormat; test: (s: string) => boolean; confidence: number; reason: string }> = [
387
+ {
388
+ format: 'hex',
389
+ test: (s) => /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0,
390
+ confidence: 0.95,
391
+ reason: 'Matches hexadecimal pattern with even length'
392
+ },
393
+ {
394
+ format: 'base64url',
395
+ test: (s) => /^[A-Za-z0-9\-_]*$/.test(s) && !s.includes('+') && !s.includes('/') && !s.includes('=') && s.length > 0,
396
+ confidence: 0.9,
397
+ reason: 'Matches base64url character set (URL-safe, no padding)'
398
+ },
399
+ {
400
+ format: 'base64',
401
+ test: (s) => /^[A-Za-z0-9+/]*={0,2}$/.test(s) && s.length % 4 === 0 && (s.includes('+') || s.includes('/') || s.includes('=')),
402
+ confidence: 0.85,
403
+ reason: 'Matches base64 character set with standard chars or padding'
404
+ },
405
+ {
406
+ format: 'uri',
407
+ test: (s) => s.includes('%') && /^[A-Za-z0-9\-_.~%!*'()]+$/.test(s),
408
+ confidence: 0.8,
409
+ reason: 'Contains URI percent-encoding characters'
410
+ },
411
+ {
412
+ format: 'ascii',
413
+ test: (s) => /^[\x20-\x7E]*$/.test(s),
414
+ confidence: 0.7,
415
+ reason: 'Contains only printable ASCII characters'
416
+ }
417
+ ];
418
+
419
+ const matches = patterns.filter(p => p.test(data));
420
+
421
+ if (matches.length === 0) {
422
+ throw new Error(`Cannot detect encoding format for data: ${data.slice(0, 50)}...`);
423
+ }
424
+
425
+ // Return highest confidence match
426
+ const bestMatch = matches.reduce((best, current) =>
427
+ current.confidence > best.confidence ? current : best
428
+ );
429
+
430
+ return {
431
+ format: bestMatch.format,
432
+ confidence: bestMatch.confidence,
433
+ reasons: matches.map(m => m.reason),
434
+ timestamp: new Date()
435
+ };
436
+ }
437
+
438
+ /**
439
+ * Validate encoded data format (throw on error - simple validation operation)
440
+ */
441
+ validate(data: string, format: EncodingFormat): ValidationResult {
442
+ const errors: string[] = [];
443
+ const suggestions: string[] = [];
444
+
445
+ try {
446
+ switch (format) {
447
+ case 'base64':
448
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
449
+ errors.push('Contains invalid base64 characters');
450
+ suggestions.push('Remove invalid characters or try base64url format');
451
+ }
452
+ if (data.length % 4 !== 0) {
453
+ errors.push('Invalid base64 length (must be multiple of 4)');
454
+ suggestions.push('Add padding with = characters');
455
+ }
456
+ break;
457
+
458
+ case 'base64url':
459
+ if (!/^[A-Za-z0-9\-_]*$/.test(data)) {
460
+ errors.push('Contains invalid base64url characters');
461
+ suggestions.push('Use only A-Z, a-z, 0-9, -, _ characters');
462
+ }
463
+ break;
464
+
465
+ case 'hex':
466
+ if (!/^[0-9a-fA-F]*$/.test(data)) {
467
+ errors.push('Contains invalid hexadecimal characters');
468
+ suggestions.push('Use only 0-9, a-f, A-F characters');
469
+ }
470
+ if (data.length % 2 !== 0) {
471
+ errors.push('Invalid hex length (must be even)');
472
+ suggestions.push('Add leading zero or remove extra character');
473
+ }
474
+ break;
475
+
476
+ case 'uri':
477
+ try {
478
+ decodeURIComponent(data);
479
+ } catch {
480
+ errors.push('Invalid URI encoding');
481
+ suggestions.push('Check percent-encoding format');
482
+ }
483
+ break;
484
+
485
+ case 'ascii':
486
+ if (!/^[\x20-\x7E]*$/.test(data)) {
487
+ errors.push('Contains non-ASCII characters');
488
+ suggestions.push('Use only printable ASCII characters (32-126)');
489
+ }
490
+ break;
491
+
492
+ default:
493
+ throw new Error(`Unknown format: ${format}`);
494
+ }
495
+
496
+ return {
497
+ isValid: errors.length === 0,
498
+ format,
499
+ errors,
500
+ suggestions
501
+ };
502
+ } catch (error) {
503
+ throw new Error(`Validation failed: ${error instanceof Error ? error.message : String(error)}`);
504
+ }
505
+ }
506
+
507
+ // Doctrine #8: PURE FUNCTION HEARTS (core logic as pure functions)
508
+
509
+ private performEncode(data: string, format: EncodingFormat): string {
510
+ switch (format) {
511
+ case 'base64':
512
+ return this.encodeBase64(data);
513
+ case 'base64url':
514
+ return this.encodeBase64URL(data);
515
+ case 'hex':
516
+ return this.encodeHex(data);
517
+ case 'uri':
518
+ return this.encodeURI(data);
519
+ case 'ascii':
520
+ return this.encodeASCII(data);
521
+ default:
522
+ throw new Error(`Unsupported encoding format: ${format}`);
523
+ }
524
+ }
525
+
526
+ private performDecode(data: string, format: EncodingFormat): string {
527
+ switch (format) {
528
+ case 'base64':
529
+ return this.decodeBase64(data);
530
+ case 'base64url':
531
+ return this.decodeBase64URL(data);
532
+ case 'hex':
533
+ return this.decodeHex(data);
534
+ case 'uri':
535
+ return this.decodeURI(data);
536
+ case 'ascii':
537
+ return this.decodeASCII(data);
538
+ default:
539
+ throw new Error(`Unsupported decoding format: ${format}`);
540
+ }
541
+ }
542
+
543
+ // Base64 implementation (cross-platform Node.js/Browser)
544
+ private encodeBase64(data: string): string {
545
+ if (typeof Buffer !== 'undefined') {
546
+ return Buffer.from(data, 'utf8').toString('base64');
547
+ }
548
+ if (typeof btoa !== 'undefined') {
549
+ // Modern Unicode-safe base64 encoding without deprecated unescape()
550
+ const bytes = new TextEncoder().encode(data);
551
+ const binaryString = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
552
+ return btoa(binaryString);
553
+ }
554
+ throw new Error('No base64 encoding available');
555
+ }
556
+
557
+ private decodeBase64(data: string): string {
558
+ if (typeof Buffer !== 'undefined') {
559
+ return Buffer.from(data, 'base64').toString('utf8');
560
+ }
561
+ if (typeof atob !== 'undefined') {
562
+ // Modern Unicode-safe base64 decoding without deprecated escape()
563
+ const binaryString = atob(data);
564
+ const bytes = new Uint8Array(binaryString.length);
565
+ for (let i = 0; i < binaryString.length; i++) {
566
+ bytes[i] = binaryString.charCodeAt(i);
567
+ }
568
+ return new TextDecoder('utf-8').decode(bytes);
569
+ }
570
+ throw new Error('No base64 decoding available');
571
+ }
572
+
573
+ // Base64URL implementation
574
+ private encodeBase64URL(data: string): string {
575
+ return this.encodeBase64(data)
576
+ .replace(/\+/g, '-')
577
+ .replace(/\//g, '_')
578
+ .replace(/=/g, '');
579
+ }
580
+
581
+ private decodeBase64URL(data: string): string {
582
+ let base64 = data.replace(/-/g, '+').replace(/_/g, '/');
583
+ while (base64.length % 4) {
584
+ base64 += '=';
585
+ }
586
+ return this.decodeBase64(base64);
587
+ }
588
+
589
+ // Hex implementation
590
+ private encodeHex(data: string): string {
591
+ return Array.from(data)
592
+ .map(char => char.charCodeAt(0).toString(16).padStart(2, '0'))
593
+ .join('');
594
+ }
595
+
596
+ private decodeHex(data: string): string {
597
+ const hex = data.replace(/[^0-9a-fA-F]/g, '');
598
+ let result = '';
599
+ for (let i = 0; i < hex.length; i += 2) {
600
+ result += String.fromCharCode(Number.parseInt(hex.slice(i, i + 2), 16));
601
+ }
602
+ return result;
603
+ }
604
+
605
+ // URI implementation
606
+ private encodeURI(data: string): string {
607
+ return encodeURIComponent(data);
608
+ }
609
+
610
+ private decodeURI(data: string): string {
611
+ return decodeURIComponent(data);
612
+ }
613
+
614
+ // ASCII implementation
615
+ private encodeASCII(data: string): string {
616
+ return Array.from(data)
617
+ .map(char => {
618
+ const code = char.charCodeAt(0);
619
+ if (code > 127) {
620
+ throw new Error(`Non-ASCII character found: ${char} (code: ${code})`);
621
+ }
622
+ return char;
623
+ })
624
+ .join('');
625
+ }
626
+
627
+ private decodeASCII(data: string): string {
628
+ return data; // ASCII is already decoded
629
+ }
630
+
631
+ // Utility methods
632
+ private isValidInput(data: string): boolean {
633
+ // Basic input validation for strict mode
634
+ return typeof data === 'string' && data.length > 0;
635
+ }
636
+
637
+ // Standard unit identification
638
+ whoami(): string {
639
+ return `EncoderUnit[${this.dna.id}@${this.dna.version}]`;
640
+ }
641
+
642
+ // JSON serialization (no sensitive data exposed)
643
+ toJSON(): Record<string, unknown> {
644
+ return {
645
+ type: 'EncoderUnit',
646
+ dna: this.dna,
647
+ defaultFormat: this.props.defaultFormat,
648
+ strictMode: this.props.strictMode,
649
+ autoDetect: this.props.autoDetect,
650
+ learnedCapabilities: this.capabilities(), // This calls the base Unit class method
651
+ created: this.props.created
652
+ };
653
+ }
654
+ }