Skip to content

Commit 7a67705

Browse files
committed
Initial commit
1 parent ede8357 commit 7a67705

File tree

6 files changed

+183
-65
lines changed

6 files changed

+183
-65
lines changed

src/objectid.ts

Lines changed: 111 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ import { type InspectFn, defaultInspect } from './parser/utils';
44
import { ByteUtils } from './utils/byte_utils';
55
import { NumberUtils } from './utils/number_utils';
66

7+
const defaultPoolSize = 1000; // Hold 1000 ObjectId buffers in a pool
8+
let pool: Uint8Array;
9+
let poolOffset = 0;
10+
11+
/** Internal pool accessors for objectId instance */
12+
/** @internal private instance pool accessor */
13+
export const _pool = Symbol('pool');
14+
/** @internal private instance offset accessor */
15+
export const _offset = Symbol('offset');
16+
17+
/**
18+
* Create a new ObjectId buffer pool and reset the pool offset
19+
* @internal
20+
*/
21+
function createPool(): void {
22+
pool = ByteUtils.allocateUnsafe(ObjectId.poolSize * 12);
23+
poolOffset = 0;
24+
}
25+
726
// Regular expression that checks for hex value
827
const checkForHexRegExp = new RegExp('^[0-9a-fA-F]{24}$');
928

@@ -37,8 +56,16 @@ export class ObjectId extends BSONValue {
3756

3857
static cacheHexString: boolean;
3958

40-
/** ObjectId Bytes @internal */
41-
private buffer!: Uint8Array;
59+
/**
60+
* The size of the buffer pool for ObjectId.
61+
*/
62+
static poolSize: number = defaultPoolSize;
63+
64+
/** ObjectId buffer pool pointer @internal */
65+
private [_pool]: Uint8Array;
66+
/** Buffer pool offset @internal */
67+
private [_offset]: number;
68+
4269
/** ObjectId hexString cache @internal */
4370
private __id?: string;
4471

@@ -72,7 +99,7 @@ export class ObjectId extends BSONValue {
7299
*
73100
* @param inputId - A 12 byte binary Buffer.
74101
*/
75-
constructor(inputId: Uint8Array);
102+
constructor(inputId: Uint8Array, inputIndex?: number);
76103
/** To generate a new ObjectId, use ObjectId() with no argument. */
77104
constructor();
78105
/**
@@ -86,7 +113,10 @@ export class ObjectId extends BSONValue {
86113
*
87114
* @param inputId - An input value to create a new ObjectId from.
88115
*/
89-
constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array) {
116+
constructor(
117+
inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array,
118+
inputIndex?: number
119+
) {
90120
super();
91121
// workingId is set based on type of input and whether valid id exists for the input
92122
let workingId;
@@ -103,17 +133,38 @@ export class ObjectId extends BSONValue {
103133
workingId = inputId;
104134
}
105135

136+
// If we have reached the end of the pool then create a new pool
137+
if (!pool || poolOffset + 12 > pool.byteLength) {
138+
createPool();
139+
}
140+
this[_pool] = pool;
141+
this[_offset] = poolOffset;
142+
poolOffset += 12;
143+
106144
// The following cases use workingId to construct an ObjectId
107145
if (workingId == null || typeof workingId === 'number') {
108146
// The most common use case (blank id, new objectId instance)
109147
// Generate a new id
110-
this.buffer = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined);
111-
} else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) {
112-
// If intstanceof matches we can escape calling ensure buffer in Node.js environments
113-
this.buffer = ByteUtils.toLocalBufferType(workingId);
148+
ObjectId.generate(
149+
typeof workingId === 'number' ? workingId : undefined,
150+
this[_pool],
151+
this[_offset]
152+
);
153+
} else if (ArrayBuffer.isView(workingId)) {
154+
if (workingId.byteLength !== 12 && typeof inputIndex !== 'number') {
155+
throw new BSONError('Buffer length must be 12 or offset must be specified');
156+
}
157+
if (
158+
inputIndex &&
159+
(typeof inputIndex !== 'number' || inputIndex < 0 || workingId.byteLength < inputIndex + 12)
160+
) {
161+
throw new BSONError('Buffer offset must be a non-negative number less than buffer length');
162+
}
163+
inputIndex ??= 0;
164+
for (let i = 0; i < 12; i++) this[_pool][this[_offset] + i] = workingId[inputIndex + i];
114165
} else if (typeof workingId === 'string') {
115166
if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
116-
this.buffer = ByteUtils.fromHex(workingId);
167+
this[_pool].set(ByteUtils.fromHex(workingId), this[_offset]);
117168
} else {
118169
throw new BSONError(
119170
'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
@@ -124,20 +175,28 @@ export class ObjectId extends BSONValue {
124175
}
125176
// If we are caching the hex string
126177
if (ObjectId.cacheHexString) {
127-
this.__id = ByteUtils.toHex(this.id);
178+
this.__id = ByteUtils.toHex(this[_pool], this[_offset], this[_offset] + 12);
128179
}
129180
}
130181

182+
/** ObjectId bytes @internal */
183+
get buffer(): Uint8Array {
184+
return this.id;
185+
}
186+
131187
/**
132188
* The ObjectId bytes
133189
* @readonly
134190
*/
135191
get id(): Uint8Array {
136-
return this.buffer;
192+
return this[_pool].subarray(this[_offset], this[_offset] + 12);
137193
}
138194

139195
set id(value: Uint8Array) {
140-
this.buffer = value;
196+
if (value.byteLength !== 12) {
197+
throw new BSONError('input must be a 12 byte Uint8Array');
198+
}
199+
this[_pool].set(value, this[_offset]);
141200
if (ObjectId.cacheHexString) {
142201
this.__id = ByteUtils.toHex(value);
143202
}
@@ -149,7 +208,7 @@ export class ObjectId extends BSONValue {
149208
return this.__id;
150209
}
151210

152-
const hexString = ByteUtils.toHex(this.id);
211+
const hexString = ByteUtils.toHex(this[_pool], this[_offset], this[_offset] + 12);
153212

154213
if (ObjectId.cacheHexString && !this.__id) {
155214
this.__id = hexString;
@@ -170,34 +229,38 @@ export class ObjectId extends BSONValue {
170229
* Generate a 12 byte id buffer used in ObjectId's
171230
*
172231
* @param time - pass in a second based timestamp.
232+
* @param buffer - Optionally pass in a buffer instance.
233+
* @param offset - Optionally pass in a buffer offset.
173234
*/
174-
static generate(time?: number): Uint8Array {
235+
static generate(time?: number, buffer?: Uint8Array, offset: number = 0): Uint8Array {
175236
if ('number' !== typeof time) {
176237
time = Math.floor(Date.now() / 1000);
177238
}
178239

179240
const inc = ObjectId.getInc();
180-
const buffer = ByteUtils.allocateUnsafe(12);
241+
if (!buffer) {
242+
buffer = ByteUtils.allocateUnsafe(12);
243+
}
181244

182245
// 4-byte timestamp
183-
NumberUtils.setInt32BE(buffer, 0, time);
246+
NumberUtils.setInt32BE(buffer, offset, time);
184247

185248
// set PROCESS_UNIQUE if yet not initialized
186249
if (PROCESS_UNIQUE === null) {
187250
PROCESS_UNIQUE = ByteUtils.randomBytes(5);
188251
}
189252

190253
// 5-byte process unique
191-
buffer[4] = PROCESS_UNIQUE[0];
192-
buffer[5] = PROCESS_UNIQUE[1];
193-
buffer[6] = PROCESS_UNIQUE[2];
194-
buffer[7] = PROCESS_UNIQUE[3];
195-
buffer[8] = PROCESS_UNIQUE[4];
254+
buffer[offset + 4] = PROCESS_UNIQUE[0];
255+
buffer[offset + 5] = PROCESS_UNIQUE[1];
256+
buffer[offset + 6] = PROCESS_UNIQUE[2];
257+
buffer[offset + 7] = PROCESS_UNIQUE[3];
258+
buffer[offset + 8] = PROCESS_UNIQUE[4];
196259

197260
// 3-byte counter
198-
buffer[11] = inc & 0xff;
199-
buffer[10] = (inc >> 8) & 0xff;
200-
buffer[9] = (inc >> 16) & 0xff;
261+
buffer[offset + 11] = inc & 0xff;
262+
buffer[offset + 10] = (inc >> 8) & 0xff;
263+
buffer[offset + 9] = (inc >> 16) & 0xff;
201264

202265
return buffer;
203266
}
@@ -239,9 +302,16 @@ export class ObjectId extends BSONValue {
239302
}
240303

241304
if (ObjectId.is(otherId)) {
242-
return (
243-
this.buffer[11] === otherId.buffer[11] && ByteUtils.equals(this.buffer, otherId.buffer)
244-
);
305+
if (otherId[_pool] && typeof otherId[_offset] === 'number') {
306+
for (let i = 11; i >= 0; i--) {
307+
if (this[_pool][this[_offset] + i] !== otherId[_pool][otherId[_offset] + i]) {
308+
return false;
309+
}
310+
}
311+
return true;
312+
}
313+
// If otherId does not have pool and offset, fallback to buffer comparison for compatibility
314+
return ByteUtils.equals(this.buffer, otherId.buffer);
245315
}
246316

247317
if (typeof otherId === 'string') {
@@ -260,7 +330,7 @@ export class ObjectId extends BSONValue {
260330
/** Returns the generation date (accurate up to the second) that this ID was generated. */
261331
getTimestamp(): Date {
262332
const timestamp = new Date();
263-
const time = NumberUtils.getUint32BE(this.buffer, 0);
333+
const time = NumberUtils.getUint32BE(this[_pool], this[_offset]);
264334
timestamp.setTime(Math.floor(time) * 1000);
265335
return timestamp;
266336
}
@@ -272,18 +342,18 @@ export class ObjectId extends BSONValue {
272342

273343
/** @internal */
274344
serializeInto(uint8array: Uint8Array, index: number): 12 {
275-
uint8array[index] = this.buffer[0];
276-
uint8array[index + 1] = this.buffer[1];
277-
uint8array[index + 2] = this.buffer[2];
278-
uint8array[index + 3] = this.buffer[3];
279-
uint8array[index + 4] = this.buffer[4];
280-
uint8array[index + 5] = this.buffer[5];
281-
uint8array[index + 6] = this.buffer[6];
282-
uint8array[index + 7] = this.buffer[7];
283-
uint8array[index + 8] = this.buffer[8];
284-
uint8array[index + 9] = this.buffer[9];
285-
uint8array[index + 10] = this.buffer[10];
286-
uint8array[index + 11] = this.buffer[11];
345+
uint8array[index] = this[_pool][this[_offset]];
346+
uint8array[index + 1] = this[_pool][this[_offset] + 1];
347+
uint8array[index + 2] = this[_pool][this[_offset] + 2];
348+
uint8array[index + 3] = this[_pool][this[_offset] + 3];
349+
uint8array[index + 4] = this[_pool][this[_offset] + 4];
350+
uint8array[index + 5] = this[_pool][this[_offset] + 5];
351+
uint8array[index + 6] = this[_pool][this[_offset] + 6];
352+
uint8array[index + 7] = this[_pool][this[_offset] + 7];
353+
uint8array[index + 8] = this[_pool][this[_offset] + 8];
354+
uint8array[index + 9] = this[_pool][this[_offset] + 9];
355+
uint8array[index + 10] = this[_pool][this[_offset] + 10];
356+
uint8array[index + 11] = this[_pool][this[_offset] + 11];
287357
return 12;
288358
}
289359

@@ -293,7 +363,7 @@ export class ObjectId extends BSONValue {
293363
* @param time - an integer number representing a number of seconds.
294364
*/
295365
static createFromTime(time: number): ObjectId {
296-
const buffer = ByteUtils.allocate(12);
366+
const buffer = ByteUtils.allocateUnsafe(12);
297367
for (let i = 11; i >= 4; i--) buffer[i] = 0;
298368
// Encode time into first 4 bytes
299369
NumberUtils.setInt32BE(buffer, 0, time);

src/parser/deserializer.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,7 @@ function deserializeObject(
263263
value = ByteUtils.toUTF8(buffer, index, index + stringSize - 1, shouldValidateKey);
264264
index = index + stringSize;
265265
} else if (elementType === constants.BSON_DATA_OID) {
266-
const oid = ByteUtils.allocateUnsafe(12);
267-
for (let i = 0; i < 12; i++) oid[i] = buffer[index + i];
268-
value = new ObjectId(oid);
266+
value = new ObjectId(buffer, index);
269267
index = index + 12;
270268
} else if (elementType === constants.BSON_DATA_INT && promoteValues === false) {
271269
value = new Int32(NumberUtils.getInt32LE(buffer, index));

src/utils/byte_utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export type ByteUtils = {
3030
/** Create a Uint8Array from a hex string */
3131
fromHex: (hex: string) => Uint8Array;
3232
/** Create a lowercase hex string from bytes */
33-
toHex: (buffer: Uint8Array) => string;
33+
toHex: (buffer: Uint8Array, start?: number, end?: number) => string;
3434
/** Create a string from utf8 code units, fatal=true will throw an error if UTF-8 bytes are invalid, fatal=false will insert replacement characters */
3535
toUTF8: (buffer: Uint8Array, start: number, end: number, fatal: boolean) => string;
3636
/** Get the utf8 code unit count from a string if it were to be transformed to utf8 */

src/utils/node_byte_utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type NodeJsEncoding = 'base64' | 'hex' | 'utf8' | 'binary';
66
type NodeJsBuffer = ArrayBufferView &
77
Uint8Array & {
88
write(string: string, offset: number, length: undefined, encoding: 'utf8'): number;
9-
copy(target: Uint8Array, targetStart: number, sourceStart: number, sourceEnd: number): number;
9+
copy(target: Uint8Array, targetStart: number, sourceStart?: number, sourceEnd?: number): number;
1010
toString: (this: Uint8Array, encoding: NodeJsEncoding, start?: number, end?: number) => string;
1111
equals: (this: Uint8Array, other: Uint8Array) => boolean;
1212
};
@@ -124,8 +124,8 @@ export const nodeJsByteUtils = {
124124
return Buffer.from(hex, 'hex');
125125
},
126126

127-
toHex(buffer: Uint8Array): string {
128-
return nodeJsByteUtils.toLocalBufferType(buffer).toString('hex');
127+
toHex(buffer: NodeJsBuffer, start?: number, end?: number): string {
128+
return nodeJsByteUtils.toLocalBufferType(buffer).toString('hex', start, end);
129129
},
130130

131131
toUTF8(buffer: Uint8Array, start: number, end: number, fatal: boolean): string {

src/utils/web_byte_utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,10 @@ export const webByteUtils = {
170170
return Uint8Array.from(buffer);
171171
},
172172

173-
toHex(uint8array: Uint8Array): string {
174-
return Array.from(uint8array, byte => byte.toString(16).padStart(2, '0')).join('');
173+
toHex(uint8array: Uint8Array, start?: number, end?: number): string {
174+
return Array.from(uint8array.subarray(start, end), byte =>
175+
byte.toString(16).padStart(2, '0')
176+
).join('');
175177
},
176178

177179
toUTF8(uint8array: Uint8Array, start: number, end: number, fatal: boolean): string {

0 commit comments

Comments
 (0)