@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,252 @@
1
+ /**
2
+ * Pure Encoding Functions - Serverless Ready
3
+ *
4
+ * Simple, stateless functions for encoding/decoding operations.
5
+ * These throw on error (simple operations) following Doctrine #14.
6
+ */
7
+
8
+ /**
9
+ * Encoding format types
10
+ */
11
+ export type EncodingFormat = 'base64' | 'base64url' | 'hex' | 'uri' | 'ascii';
12
+
13
+ /**
14
+ * Encode string to Base64
15
+ */
16
+ export function encodeBase64(data: string): string {
17
+ if (typeof Buffer !== 'undefined') {
18
+ return Buffer.from(data, 'utf8').toString('base64');
19
+ }
20
+ if (typeof btoa !== 'undefined') {
21
+ // Modern Unicode-safe base64 encoding without deprecated unescape()
22
+ const bytes = new TextEncoder().encode(data);
23
+ const binaryString = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
24
+ return btoa(binaryString);
25
+ }
26
+ throw new Error('No base64 encoding available');
27
+ }
28
+
29
+ /**
30
+ * Decode Base64 string
31
+ */
32
+ export function decodeBase64(data: string): string {
33
+ if (typeof Buffer !== 'undefined') {
34
+ return Buffer.from(data, 'base64').toString('utf8');
35
+ }
36
+ if (typeof atob !== 'undefined') {
37
+ // Modern Unicode-safe base64 decoding without deprecated escape()
38
+ const binaryString = atob(data);
39
+ const bytes = new Uint8Array(binaryString.length);
40
+ for (let i = 0; i < binaryString.length; i++) {
41
+ bytes[i] = binaryString.charCodeAt(i);
42
+ }
43
+ return new TextDecoder('utf-8').decode(bytes);
44
+ }
45
+ throw new Error('No base64 decoding available');
46
+ }
47
+
48
+ /**
49
+ * Encode string to Base64URL (URL-safe)
50
+ */
51
+ export function encodeBase64URL(data: string): string {
52
+ return encodeBase64(data)
53
+ .replace(/\+/g, '-')
54
+ .replace(/\//g, '_')
55
+ .replace(/=/g, '');
56
+ }
57
+
58
+ /**
59
+ * Decode Base64URL string
60
+ */
61
+ export function decodeBase64URL(data: string): string {
62
+ let base64 = data.replace(/-/g, '+').replace(/_/g, '/');
63
+ while (base64.length % 4) {
64
+ base64 += '=';
65
+ }
66
+ return decodeBase64(base64);
67
+ }
68
+
69
+ /**
70
+ * Encode string to hexadecimal
71
+ */
72
+ export function encodeHex(data: string): string {
73
+ return Array.from(data)
74
+ .map(char => char.charCodeAt(0).toString(16).padStart(2, '0'))
75
+ .join('');
76
+ }
77
+
78
+ /**
79
+ * Decode hexadecimal string
80
+ */
81
+ export function decodeHex(data: string): string {
82
+ if (data.length % 2 !== 0) {
83
+ throw new Error('Invalid hex string: length must be even');
84
+ }
85
+
86
+ if (!/^[0-9a-fA-F]*$/.test(data)) {
87
+ throw new Error('Invalid hex string: contains non-hex characters');
88
+ }
89
+
90
+ let result = '';
91
+ for (let i = 0; i < data.length; i += 2) {
92
+ result += String.fromCharCode(Number.parseInt(data.slice(i, i + 2), 16));
93
+ }
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Encode string for URI
99
+ */
100
+ export function encodeURIString(data: string): string {
101
+ return encodeURIComponent(data);
102
+ }
103
+
104
+ /**
105
+ * Decode URI-encoded string
106
+ */
107
+ export function decodeURIString(data: string): string {
108
+ return decodeURIComponent(data);
109
+ }
110
+
111
+ /**
112
+ * Encode string as ASCII (validates ASCII-only)
113
+ */
114
+ export function encodeASCII(data: string): string {
115
+ for (let i = 0; i < data.length; i++) {
116
+ const code = data.charCodeAt(i);
117
+ if (code > 127) {
118
+ throw new Error(`Non-ASCII character found at position ${i}: '${data[i]}' (code: ${code})`);
119
+ }
120
+ }
121
+ return data;
122
+ }
123
+
124
+ /**
125
+ * Decode ASCII string (no-op, validates printable ASCII)
126
+ */
127
+ export function decodeASCII(data: string): string {
128
+ for (let i = 0; i < data.length; i++) {
129
+ const code = data.charCodeAt(i);
130
+ if (code < 32 || code > 126) {
131
+ throw new Error(`Non-printable ASCII character at position ${i}: code ${code}`);
132
+ }
133
+ }
134
+ return data;
135
+ }
136
+
137
+ /**
138
+ * Generic encode function
139
+ */
140
+ export function encode(data: string, format: EncodingFormat): string {
141
+ switch (format) {
142
+ case 'base64':
143
+ return encodeBase64(data);
144
+ case 'base64url':
145
+ return encodeBase64URL(data);
146
+ case 'hex':
147
+ return encodeHex(data);
148
+ case 'uri':
149
+ return encodeURIString(data);
150
+ case 'ascii':
151
+ return encodeASCII(data);
152
+ default:
153
+ throw new Error(`Unsupported encoding format: ${format}`);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Generic decode function
159
+ */
160
+ export function decode(data: string, format: EncodingFormat): string {
161
+ switch (format) {
162
+ case 'base64':
163
+ return decodeBase64(data);
164
+ case 'base64url':
165
+ return decodeBase64URL(data);
166
+ case 'hex':
167
+ return decodeHex(data);
168
+ case 'uri':
169
+ return decodeURIString(data);
170
+ case 'ascii':
171
+ return decodeASCII(data);
172
+ default:
173
+ throw new Error(`Unsupported decoding format: ${format}`);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Auto-detect encoding format
179
+ */
180
+ export function detectFormat(data: string): EncodingFormat {
181
+ // Test patterns in order of specificity/confidence
182
+ if (/^[0-9a-fA-F]+$/.test(data) && data.length % 2 === 0) {
183
+ return 'hex';
184
+ }
185
+
186
+ // Base64url should be tested before base64 since it's more restrictive
187
+ if (/^[A-Za-z0-9\-_]*$/.test(data) && !data.includes('+') && !data.includes('/') && !data.includes('=') && data.length > 0) {
188
+ return 'base64url';
189
+ }
190
+
191
+ // Base64 with standard characters or padding
192
+ if (/^[A-Za-z0-9+/]*={0,2}$/.test(data) && data.length % 4 === 0 && (data.includes('+') || data.includes('/') || data.includes('='))) {
193
+ return 'base64';
194
+ }
195
+
196
+ if (data.includes('%') && /^[A-Za-z0-9\-_.~%!*'()]+$/.test(data)) {
197
+ return 'uri';
198
+ }
199
+
200
+ if (/^[\x20-\x7E]*$/.test(data)) {
201
+ return 'ascii';
202
+ }
203
+
204
+ throw new Error(`Cannot detect encoding format for: ${data.slice(0, 50)}...`);
205
+ }
206
+
207
+ /**
208
+ * Validate format of encoded data
209
+ */
210
+ export function validateFormat(data: string, format: EncodingFormat): boolean {
211
+ try {
212
+ switch (format) {
213
+ case 'base64':
214
+ return /^[A-Za-z0-9+/]*={0,2}$/.test(data) && data.length % 4 === 0;
215
+ case 'base64url':
216
+ return /^[A-Za-z0-9\-_]*$/.test(data);
217
+ case 'hex':
218
+ return /^[0-9a-fA-F]*$/.test(data) && data.length % 2 === 0;
219
+ case 'uri':
220
+ decodeURIComponent(data);
221
+ return true;
222
+ case 'ascii':
223
+ return /^[\x20-\x7E]*$/.test(data);
224
+ default:
225
+ return false;
226
+ }
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Chain multiple encodings
234
+ */
235
+ export function chain(data: string, formats: EncodingFormat[]): string {
236
+ let result = data;
237
+ for (const format of formats) {
238
+ result = encode(result, format);
239
+ }
240
+ return result;
241
+ }
242
+
243
+ /**
244
+ * Reverse chain decodings
245
+ */
246
+ export function reverseChain(data: string, formats: EncodingFormat[]): string {
247
+ let result = data;
248
+ for (const format of [...formats].reverse()) {
249
+ result = decode(result, format);
250
+ }
251
+ return result;
252
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @synet/encoder - Conscious Encoding/Decoding Unit
3
+ *
4
+ * Zero-dependency encoding operations following Unit Architecture doctrine.
5
+ *
6
+ * EXPORTS:
7
+ * - Encoder: Complete conscious encoder unit with teach/learn capabilities
8
+ * - Pure functions: Simple functional encoding operations
9
+ * - Result: Foundational error handling pattern
10
+ * - Types: All encoding-related interfaces
11
+ */
12
+
13
+ // Core Unit
14
+ export { Encoder } from './encoder.unit.js';
15
+
16
+ // Result pattern (foundational)
17
+ export { Result } from './result.js';
18
+
19
+ // Types
20
+ export type {
21
+ EncoderConfig,
22
+ EncoderProps,
23
+ EncodingFormat,
24
+ EncodingResult,
25
+ DecodingResult,
26
+ DetectionResult,
27
+ ValidationResult
28
+ } from './encoder.unit.js';
29
+
30
+ // Pure function exports for simple use cases
31
+ export {
32
+ encode,
33
+ decode,
34
+ encodeBase64,
35
+ decodeBase64,
36
+ encodeBase64URL,
37
+ decodeBase64URL,
38
+ encodeHex,
39
+ decodeHex,
40
+ encodeURIString,
41
+ decodeURIString,
42
+ encodeASCII,
43
+ decodeASCII,
44
+ detectFormat,
45
+ validateFormat,
46
+ chain,
47
+ reverseChain,
48
+ type EncodingFormat as FunctionEncodingFormat
49
+ } from './functions.js';
package/src/result.ts ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Foundational Result Pattern for Encoder Operations
3
+ *
4
+ * Local copy following Unit Architecture Doctrine #14: ERROR BOUNDARY CLARITY
5
+ * Simple operations throw, complex operations use Result pattern
6
+ */
7
+
8
+ export class Result<T> {
9
+ private constructor(
10
+ private readonly _value: T | null,
11
+ private readonly _error: string | null,
12
+ private readonly _errorCause?: unknown
13
+ ) {}
14
+
15
+ static success<T>(value: T): Result<T> {
16
+ return new Result(value, null);
17
+ }
18
+
19
+ static fail<T>(message: string, cause?: unknown): Result<T> {
20
+ return new Result<T>(null, message, cause);
21
+ }
22
+
23
+ get isSuccess(): boolean {
24
+ return this._error === null;
25
+ }
26
+
27
+ get isFailure(): boolean {
28
+ return this._error !== null;
29
+ }
30
+
31
+ get value(): T {
32
+ if (this._error !== null) {
33
+ throw new Error(`Attempted to get value from failed Result: ${this._error}`);
34
+ }
35
+ return this._value as T;
36
+ }
37
+
38
+ get error(): string {
39
+ return this._error || '';
40
+ }
41
+
42
+ get errorCause(): unknown {
43
+ return this._errorCause;
44
+ }
45
+
46
+ map<U>(fn: (value: T) => U): Result<U> {
47
+ if (this.isFailure) {
48
+ return Result.fail<U>(this._error || 'Unknown error', this._errorCause);
49
+ }
50
+ try {
51
+ return Result.success(fn(this.value));
52
+ } catch (error) {
53
+ return Result.fail<U>(
54
+ `Map operation failed: ${error instanceof Error ? error.message : String(error)}`,
55
+ error
56
+ );
57
+ }
58
+ }
59
+
60
+ flatMap<U>(fn: (value: T) => Result<U>): Result<U> {
61
+ if (this.isFailure) {
62
+ return Result.fail<U>(this._error || 'Unknown error', this._errorCause);
63
+ }
64
+ try {
65
+ return fn(this.value);
66
+ } catch (error) {
67
+ return Result.fail<U>(
68
+ `FlatMap operation failed: ${error instanceof Error ? error.message : String(error)}`,
69
+ error
70
+ );
71
+ }
72
+ }
73
+
74
+ getOrElse(defaultValue: T): T {
75
+ return this.isSuccess ? this.value : defaultValue;
76
+ }
77
+
78
+ match<U>(onSuccess: (value: T) => U, onFailure: (error: string) => U): U {
79
+ return this.isSuccess ? onSuccess(this.value) : onFailure(this.error);
80
+ }
81
+ }