Skip to content

Commit 5de599d

Browse files
committed
feat(NODE-7314): export byteUtils & add missing methods
1 parent a23e788 commit 5de599d

File tree

6 files changed

+174
-3
lines changed

6 files changed

+174
-3
lines changed

src/bson.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export {
4949
MaxKey,
5050
BSONRegExp,
5151
Decimal128,
52-
NumberUtils
52+
NumberUtils,
53+
ByteUtils
5354
};
5455
export { BSONValue, bsonType, type BSONTypeTag } from './bson_value';
5556
export { BSONError, BSONVersionError, BSONRuntimeError, BSONOffsetError } from './error';

src/utils/byte_utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export type ByteUtils = {
1515
allocate: (size: number) => Uint8Array;
1616
/** Create empty space of size, use pooled memory when available */
1717
allocateUnsafe: (size: number) => Uint8Array;
18+
/** Compare 2 Uint8Arrays lexicographically */
19+
compare: (buffer1: Uint8Array, buffer2: Uint8Array) => -1 | 0 | 1;
20+
/** Concatenating all the Uint8Arrays in new Uint8Array. */
21+
concat: (list: Uint8Array[]) => Uint8Array;
1822
/** Check if two Uint8Arrays are deep equal */
1923
equals: (a: Uint8Array, b: Uint8Array) => boolean;
2024
/** Check if two Uint8Arrays are deep equal */

src/utils/node_byte_utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type NodeJsBuffer = ArrayBufferView &
1010
toString: (this: Uint8Array, encoding: NodeJsEncoding, start?: number, end?: number) => string;
1111
equals: (this: Uint8Array, other: Uint8Array) => boolean;
1212
swap32: (this: NodeJsBuffer) => NodeJsBuffer;
13+
compare: (this: Uint8Array, other: Uint8Array) => -1 | 0 | 1;
1314
};
1415
type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
1516
alloc: (size: number) => NodeJsBuffer;
@@ -21,6 +22,7 @@ type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
2122
from(base64: string, encoding: NodeJsEncoding): NodeJsBuffer;
2223
byteLength(input: string, encoding: 'utf8'): number;
2324
isBuffer(value: unknown): value is NodeJsBuffer;
25+
concat(list: Uint8Array[]): NodeJsBuffer;
2426
};
2527

2628
// This can be nullish, but we gate the nodejs functions on being exported whether or not this exists
@@ -88,6 +90,14 @@ export const nodeJsByteUtils = {
8890
return Buffer.allocUnsafe(size);
8991
},
9092

93+
compare(a: Uint8Array, b: Uint8Array) {
94+
return nodeJsByteUtils.toLocalBufferType(a).compare(b);
95+
},
96+
97+
concat(list: Uint8Array[]): NodeJsBuffer {
98+
return Buffer.concat(list);
99+
},
100+
91101
equals(a: Uint8Array, b: Uint8Array): boolean {
92102
return nodeJsByteUtils.toLocalBufferType(a).equals(b);
93103
},

src/utils/web_byte_utils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,42 @@ export const webByteUtils = {
114114
return webByteUtils.allocate(size);
115115
},
116116

117+
compare(a: Uint8Array, b: Uint8Array): -1 | 0 | 1 {
118+
if (a === b) return 0;
119+
120+
const len = Math.min(a.length, b.length);
121+
122+
for (let i = 0; i < len; i++) {
123+
if (a[i] < b[i]) return -1;
124+
if (a[i] > b[i]) return 1;
125+
}
126+
127+
if (a.length < b.length) return -1;
128+
if (a.length > b.length) return 1;
129+
130+
return 0;
131+
},
132+
133+
concat(list: Uint8Array[]): Uint8Array {
134+
if (list.length === 0) return webByteUtils.allocate(0);
135+
if (list.length === 1) return list[0];
136+
137+
let totalLength = 0;
138+
for (const arr of list) {
139+
totalLength += arr.length;
140+
}
141+
142+
const result = webByteUtils.allocate(totalLength);
143+
let offset = 0;
144+
145+
for (const arr of list) {
146+
result.set(arr, offset);
147+
offset += arr.length;
148+
}
149+
150+
return result;
151+
},
152+
117153
equals(a: Uint8Array, b: Uint8Array): boolean {
118154
if (a.byteLength !== b.byteLength) {
119155
return false;

test/node/byte_utils.test.ts

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,123 @@ const swap32Tests: ByteUtilTest<'swap32'>[] = [
517517
}
518518
}
519519
];
520+
const compareTests: ByteUtilTest<'compare'>[] = [
521+
{
522+
name: 'returns 0 for two equal arrays (same content)',
523+
inputs: [new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])],
524+
expectation({ output }) {
525+
expect(output).to.equal(0);
526+
}
527+
},
528+
{
529+
name: 'returns 0 when comparing the same buffer by reference',
530+
inputs: (() => {
531+
const buf = new Uint8Array([5, 6, 7]);
532+
return [buf, buf];
533+
})(),
534+
expectation({ output }) {
535+
expect(output).to.equal(0);
536+
}
537+
},
538+
{
539+
name: 'array a is lexicographically less than array b (first differing byte)',
540+
inputs: [new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 4])],
541+
expectation({ output }) {
542+
expect(output).to.equal(-1);
543+
}
544+
},
545+
{
546+
name: 'array a is lexicographically greater than array b (first differing byte)',
547+
inputs: [new Uint8Array([1, 2, 4]), new Uint8Array([1, 2, 3])],
548+
expectation({ output }) {
549+
expect(output).to.equal(1);
550+
}
551+
},
552+
{
553+
name: 'a is a strict prefix of b (a shorter, same starting bytes) -> a < b',
554+
inputs: [new Uint8Array([1, 2]), new Uint8Array([1, 2, 3])],
555+
expectation({ output }) {
556+
expect(output).to.equal(-1);
557+
}
558+
},
559+
{
560+
name: 'b is a strict prefix of a (b shorter, same starting bytes) -> a > b',
561+
inputs: [new Uint8Array([1, 2, 3]), new Uint8Array([1, 2])],
562+
expectation({ output }) {
563+
expect(output).to.equal(1);
564+
}
565+
},
566+
{
567+
name: 'handles empty arrays',
568+
inputs: [new Uint8Array([]), new Uint8Array([])],
569+
expectation({ output }) {
570+
expect(output).to.equal(0);
571+
}
572+
}
573+
];
574+
const concatTests: ByteUtilTest<'concat'>[] = [
575+
{
576+
name: 'concatenates two non-empty arrays',
577+
inputs: [[new Uint8Array([1, 2]), new Uint8Array([3, 4])]],
578+
expectation({ output, error }) {
579+
expect(error).to.be.null;
580+
expect(output).to.deep.equal(Buffer.from([1, 2, 3, 4]));
581+
}
582+
},
583+
{
584+
name: 'concatenates multiple arrays in order',
585+
inputs: [[new Uint8Array([1]), new Uint8Array([2, 3]), new Uint8Array([4, 5, 6])]],
586+
expectation({ output, error }) {
587+
expect(error).to.be.null;
588+
expect(output).to.deep.equal(Buffer.from([1, 2, 3, 4, 5, 6]));
589+
}
590+
},
591+
{
592+
name: 'returns an empty Uint8Array when given an empty list',
593+
inputs: [[]],
594+
expectation({ output, error }) {
595+
expect(error).to.be.null;
596+
expect(output).to.have.property('byteLength', 0);
597+
expect(output).to.deep.equal(Buffer.from([]));
598+
}
599+
},
600+
{
601+
name: 'returns the same contents when given a single array',
602+
inputs: [[new Uint8Array([7, 8, 9])]],
603+
expectation({ output, error }) {
604+
expect(error).to.be.null;
605+
expect(output).to.deep.equal(Buffer.from([7, 8, 9]));
606+
}
607+
},
608+
{
609+
name: 'handles concatenation with empty arrays inside the list',
610+
inputs: [
611+
[new Uint8Array([]), new Uint8Array([1, 2, 3]), new Uint8Array([]), new Uint8Array([4])]
612+
],
613+
expectation({ output, error }) {
614+
expect(error).to.be.null;
615+
expect(output).to.deep.equal(Buffer.from([1, 2, 3, 4]));
616+
}
617+
},
618+
{
619+
name: 'result has correct total byteLength',
620+
inputs: [[new Uint8Array([1, 2]), new Uint8Array([3]), new Uint8Array([4, 5, 6])]],
621+
expectation({ output, error }) {
622+
expect(error).to.be.null;
623+
// 2 + 1 + 3 = 6
624+
expect(output).to.have.property('byteLength', 6);
625+
expect(output).to.deep.equal(Buffer.from([1, 2, 3, 4, 5, 6]));
626+
}
627+
},
628+
{
629+
name: 'concatenates arrays with overlapping contents correctly',
630+
inputs: [[new Uint8Array([0, 0, 1]), new Uint8Array([1, 0, 0])]],
631+
expectation({ output, error }) {
632+
expect(error).to.be.null;
633+
expect(output).to.deep.equal(Buffer.from([0, 0, 1, 1, 0, 0]));
634+
}
635+
}
636+
];
520637

521638
const utils = new Map([
522639
['nodeJsByteUtils', nodeJsByteUtils],
@@ -538,7 +655,9 @@ const table = new Map<keyof ByteUtils, ByteUtilTest<keyof ByteUtils>[]>([
538655
['toUTF8', toUTF8Tests],
539656
['utf8ByteLength', utf8ByteLengthTests],
540657
['randomBytes', randomBytesTests],
541-
['swap32', swap32Tests]
658+
['swap32', swap32Tests],
659+
['compare', compareTests],
660+
['concat', concatTests]
542661
]);
543662

544663
describe('ByteUtils', () => {

test/node/exports.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const EXPECTED_EXPORTS = [
3939
'deserializeStream',
4040
'BSON',
4141
'bsonType',
42-
'NumberUtils'
42+
'NumberUtils',
43+
'ByteUtils'
4344
];
4445

4546
const EXPECTED_EJSON_EXPORTS = ['parse', 'stringify', 'serialize', 'deserialize'];

0 commit comments

Comments
 (0)