Skip to content

Commit 9fa8322

Browse files
committed
clean up and formatting
1 parent 22c2dde commit 9fa8322

File tree

3 files changed

+47
-91
lines changed

3 files changed

+47
-91
lines changed

src/common/utils/leb128.spec.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,25 @@ describe('leb128', () => {
7777
});
7878

7979
test('throws for numbers > MAX_UINT32', () => {
80-
expect(() => encodeUInt32(4294967296)).toThrow('Value must not exceed 4294967295');
80+
expect(() => encodeUInt32(4294967296)).toThrow(
81+
'Value must not exceed 4294967295',
82+
);
8183
});
8284

8385
test('throws for non-integers', () => {
8486
expect(() => encodeUInt32(3.14)).toThrow('Value must be an integer');
8587
});
8688

8789
test('throws for NaN', () => {
88-
expect(() => encodeUInt32(NaN)).toThrow('Value must be a finite number');
90+
expect(() => encodeUInt32(NaN)).toThrow(
91+
'Value must be a finite number',
92+
);
8993
});
9094

9195
test('throws for Infinity', () => {
92-
expect(() => encodeUInt32(Infinity)).toThrow('Value must be a finite number');
96+
expect(() => encodeUInt32(Infinity)).toThrow(
97+
'Value must be a finite number',
98+
);
9399
});
94100
});
95101
});
@@ -205,12 +211,16 @@ describe('leb128', () => {
205211
describe('invalid inputs', () => {
206212
test('throws for offset beyond buffer bounds', () => {
207213
const data = new Uint8Array([0x2a]);
208-
expect(() => decodeUInt32(data, 5)).toThrow('Offset 5 is out of bounds');
214+
expect(() => decodeUInt32(data, 5)).toThrow(
215+
'Offset 5 is out of bounds',
216+
);
209217
});
210218

211219
test('throws for negative offset', () => {
212220
const data = new Uint8Array([0x2a]);
213-
expect(() => decodeUInt32(data, -1)).toThrow('Offset -1 is out of bounds');
221+
expect(() => decodeUInt32(data, -1)).toThrow(
222+
'Offset -1 is out of bounds',
223+
);
214224
});
215225

216226
test('throws for truncated encoding (incomplete byte sequence)', () => {
@@ -228,7 +238,9 @@ describe('leb128', () => {
228238
test('throws for encoding that exceeds uint32 range', () => {
229239
// 6 bytes with continuation bits (should never happen for uint32)
230240
const data = new Uint8Array([0x80, 0x80, 0x80, 0x80, 0x80, 0x01]);
231-
expect(() => decodeUInt32(data)).toThrow('LEB128 sequence exceeds maximum length for uint32');
241+
expect(() => decodeUInt32(data)).toThrow(
242+
'LEB128 sequence exceeds maximum length for uint32',
243+
);
232244
});
233245
});
234246
});

src/common/utils/leb128.ts

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,23 @@
11
/**
2-
* LEB128 (Little Endian Base 128) encoding/decoding for unsigned 32-bit integers.
3-
*
4-
* This is a variable-length integer encoding where each byte stores 7 bits of data
5-
* and uses bit 7 as a continuation flag (1 = more bytes follow, 0 = last byte).
6-
*
7-
* Benefits:
8-
* - Small values use fewer bytes (e.g., 0-127 use only 1 byte)
9-
* - Works in all JavaScript runtimes (uses only Uint8Array)
10-
* - No dependencies on Node.js Buffer or other runtime-specific APIs
2+
* LEB128 (Little Endian Base 128) encoding for unsigned 32-bit integers.
3+
* Variable-length encoding: each byte stores 7 bits of data, bit 7 is continuation flag.
114
*/
125

136
const MAX_UINT32 = 0xffffffff;
147
const CONTINUATION_BIT = 0x80;
158
const DATA_BITS_MASK = 0x7f;
169
const DATA_BITS_PER_BYTE = 7;
17-
const MAX_BYTES_FOR_UINT32 = 5; // ceil(32 / 7) = 5
10+
const MAX_BYTES_FOR_UINT32 = 5;
1811

1912
/**
2013
* Encodes an unsigned 32-bit integer into LEB128 format.
2114
*
22-
* @param value - The unsigned 32-bit integer to encode (0 to 4,294,967,295)
23-
* @returns Uint8Array containing the encoded bytes (1-5 bytes)
24-
* @throws Error if value is invalid
25-
*
26-
* @example
27-
* encodeUInt32(0) // Uint8Array[0x00] (1 byte)
28-
* encodeUInt32(127) // Uint8Array[0x7F] (1 byte)
29-
* encodeUInt32(128) // Uint8Array[0x80, 0x01] (2 bytes)
30-
* encodeUInt32(300) // Uint8Array[0xAC, 0x02] (2 bytes)
15+
* @param value - Unsigned 32-bit integer (0 to 4,294,967,295)
16+
* @returns Encoded bytes (1-5 bytes depending on value)
3117
*/
3218
export function encodeUInt32(value: number): Uint8Array {
3319
validateUInt32(value);
3420

35-
// Handle zero directly (most common small value)
3621
if (value === 0) {
3722
return new Uint8Array([0]);
3823
}
@@ -56,16 +41,9 @@ export function encodeUInt32(value: number): Uint8Array {
5641
/**
5742
* Decodes an unsigned 32-bit integer from LEB128 format.
5843
*
59-
* @param data - Uint8Array containing LEB128 encoded data
60-
* @param offset - Starting position in the buffer (defaults to 0)
61-
* @returns Object with decoded value and the index after the last byte read
62-
* @throws Error if decoding fails
63-
*
64-
* @example
65-
* decodeUInt32(new Uint8Array([0x00])) // { value: 0, nextIndex: 1 }
66-
* decodeUInt32(new Uint8Array([0x7F])) // { value: 127, nextIndex: 1 }
67-
* decodeUInt32(new Uint8Array([0x80, 0x01])) // { value: 128, nextIndex: 2 }
68-
* decodeUInt32(new Uint8Array([0xAC, 0x02])) // { value: 300, nextIndex: 2 }
44+
* @param data - Buffer containing LEB128 encoded data
45+
* @param offset - Starting position in buffer (default: 0)
46+
* @returns Decoded value and index after last byte read
6947
*/
7048
export function decodeUInt32(
7149
data: Uint8Array,
@@ -82,15 +60,13 @@ export function decodeUInt32(
8260
const byte = data[index++];
8361
bytesRead++;
8462

85-
// Check for overflow before processing
8663
if (bytesRead > MAX_BYTES_FOR_UINT32) {
8764
throw new Error('LEB128 sequence exceeds maximum length for uint32');
8865
}
8966

9067
result |= (byte & DATA_BITS_MASK) << shift;
9168

9269
if (!hasContinuationBit(byte)) {
93-
// Convert to unsigned 32-bit integer
9470
return { value: result >>> 0, nextIndex: index };
9571
}
9672

@@ -100,9 +76,6 @@ export function decodeUInt32(
10076
throw new Error('Truncated LEB128 encoding');
10177
}
10278

103-
/**
104-
* Validates that a value is a valid unsigned 32-bit integer.
105-
*/
10679
function validateUInt32(value: number): void {
10780
if (!Number.isFinite(value)) {
10881
throw new Error('Value must be a finite number');
@@ -118,18 +91,14 @@ function validateUInt32(value: number): void {
11891
}
11992
}
12093

121-
/**
122-
* Validates that an offset is within bounds.
123-
*/
12494
function validateOffset(data: Uint8Array, offset: number): void {
12595
if (offset < 0 || offset >= data.length) {
126-
throw new Error(`Offset ${offset} is out of bounds (buffer length: ${data.length})`);
96+
throw new Error(
97+
`Offset ${offset} is out of bounds (buffer length: ${data.length})`,
98+
);
12799
}
128100
}
129101

130-
/**
131-
* Checks if a byte has the continuation bit set.
132-
*/
133102
function hasContinuationBit(byte: number): boolean {
134103
return (byte & CONTINUATION_BIT) !== 0;
135-
}
104+
}

src/common/utils/query-string.ts

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,57 @@
1-
/**
2-
* Native query string serialization that replaces the qs library.
3-
* Maintains backward compatibility with existing URL formatting.
4-
*
5-
* This implementation:
6-
* - Uses only native Web APIs (works in all runtimes)
7-
* - Handles arrays with 'repeat' format: scope=read&scope=write
8-
* - Sorts keys alphabetically for consistency
9-
* - Uses RFC1738 encoding (space as +)
10-
* - Handles nested objects: provider_query_params[key]=value
11-
* - Filters out undefined values
12-
*/
13-
14-
type QueryValue = string | string[] | Record<string, string | boolean | number> | undefined;
1+
type QueryValue =
2+
| string
3+
| string[]
4+
| Record<string, string | boolean | number>
5+
| undefined;
156

167
/**
17-
* Converts an options object to a query string.
18-
* Compatible with the qs library's stringify method when used with:
19-
* - arrayFormat: 'repeat'
20-
* - format: 'RFC1738'
21-
* - sort: alphabetical
8+
* Converts options to a query string.
9+
* - Arrays: scope=read&scope=write (repeat format)
10+
* - Objects: params[key]=value (bracket notation)
11+
* - Encoding: RFC1738 (space as +)
12+
* - Keys sorted alphabetically
2213
*/
2314
export function toQueryString(options: Record<string, QueryValue>): string {
2415
const params: Array<[string, string]> = [];
25-
26-
// Get sorted keys for consistent output (matches qs behavior)
2716
const sortedKeys = Object.keys(options).sort((a, b) => a.localeCompare(b));
2817

2918
for (const key of sortedKeys) {
3019
const value = options[key];
3120

32-
// Skip undefined values (matches qs behavior)
3321
if (value === undefined) {
3422
continue;
3523
}
3624

37-
// Handle arrays with 'repeat' format: key=val1&key=val2
3825
if (Array.isArray(value)) {
3926
for (const item of value) {
4027
params.push([key, String(item)]);
4128
}
42-
}
43-
// Handle nested objects: key[subkey]=value
44-
else if (typeof value === 'object' && value !== null) {
45-
const sortedSubKeys = Object.keys(value).sort((a, b) => a.localeCompare(b));
29+
} else if (typeof value === 'object' && value !== null) {
30+
const sortedSubKeys = Object.keys(value).sort((a, b) =>
31+
a.localeCompare(b),
32+
);
4633
for (const subKey of sortedSubKeys) {
4734
const subValue = value[subKey];
4835
if (subValue !== undefined) {
4936
params.push([`${key}[${subKey}]`, String(subValue)]);
5037
}
5138
}
52-
}
53-
// Handle primitives (string, number, boolean)
54-
else {
39+
} else {
5540
params.push([key, String(value)]);
5641
}
5742
}
5843

59-
// Build query string with RFC1738 encoding (space as +)
6044
return params
6145
.map(([key, value]) => {
62-
// Encode using RFC1738 format (matches qs behavior)
6346
const encodedKey = encodeRFC1738(key);
6447
const encodedValue = encodeRFC1738(value);
6548
return `${encodedKey}=${encodedValue}`;
6649
})
6750
.join('&');
6851
}
6952

70-
/**
71-
* Encodes a string using RFC1738 format.
72-
* - Space is encoded as +
73-
* - Additional characters encoded to match qs library behavior
74-
*/
7553
function encodeRFC1738(str: string): string {
7654
return encodeURIComponent(str)
77-
.replace(/%20/g, '+') // Space as + (RFC1738)
78-
.replace(/[!'*]/g, (c) => {
79-
// Encode additional characters to match qs behavior
80-
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
81-
});
55+
.replace(/%20/g, '+')
56+
.replace(/[!'*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
8257
}

0 commit comments

Comments
 (0)