Skip to content

Commit f41c6b6

Browse files
committed
Improvements
1 parent 7a67705 commit f41c6b6

File tree

3 files changed

+120
-64
lines changed

3 files changed

+120
-64
lines changed

src/objectid.ts

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,28 @@ 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;
7+
let currentPool: Uint8Array | null = null;
8+
let poolSize = 1000; // Default: Hold 1000 ObjectId buffers in a pool
9+
let currentPoolOffset = 0;
1010

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');
11+
/**
12+
* Retrieves a ObjectId pool and offset. This function may create a new ObjectId buffer pool and reset the pool offset
13+
* @internal
14+
*/
15+
function getPool(): [Uint8Array, number] {
16+
if (!currentPool || currentPoolOffset + 12 > currentPool.byteLength) {
17+
currentPool = ByteUtils.allocateUnsafe(poolSize * 12);
18+
currentPoolOffset = 0;
19+
}
20+
return [currentPool, currentPoolOffset];
21+
}
1622

1723
/**
18-
* Create a new ObjectId buffer pool and reset the pool offset
24+
* Increments the pool offset by 12 bytes
1925
* @internal
2026
*/
21-
function createPool(): void {
22-
pool = ByteUtils.allocateUnsafe(ObjectId.poolSize * 12);
23-
poolOffset = 0;
27+
function incrementPool(): void {
28+
currentPoolOffset += 12;
2429
}
2530

2631
// Regular expression that checks for hex value
@@ -57,14 +62,24 @@ export class ObjectId extends BSONValue {
5762
static cacheHexString: boolean;
5863

5964
/**
60-
* The size of the buffer pool for ObjectId.
65+
* The size of the current ObjectId buffer pool.
6166
*/
62-
static poolSize: number = defaultPoolSize;
67+
static get poolSize(): number {
68+
return poolSize;
69+
}
70+
71+
static set poolSize(size: number) {
72+
const iSize = Math.ceil(size); // Ensure new pool size is an integer
73+
if (iSize <= 0) {
74+
throw new BSONError('poolSize must be a positive integer greater than 0');
75+
}
76+
poolSize = iSize;
77+
}
6378

6479
/** ObjectId buffer pool pointer @internal */
65-
private [_pool]: Uint8Array;
80+
private pool: Uint8Array;
6681
/** Buffer pool offset @internal */
67-
private [_offset]: number;
82+
private offset: number;
6883

6984
/** ObjectId hexString cache @internal */
7085
private __id?: string;
@@ -99,6 +114,13 @@ export class ObjectId extends BSONValue {
99114
*
100115
* @param inputId - A 12 byte binary Buffer.
101116
*/
117+
constructor(inputId: Uint8Array);
118+
/**
119+
* Create ObjectId from a large binary Buffer. Only 12 bytes starting from the offset are used.
120+
* @internal
121+
* @param inputId - A 12 byte binary Buffer.
122+
* @param inputIndex - The offset to start reading the inputId buffer.
123+
*/
102124
constructor(inputId: Uint8Array, inputIndex?: number);
103125
/** To generate a new ObjectId, use ObjectId() with no argument. */
104126
constructor();
@@ -133,23 +155,13 @@ export class ObjectId extends BSONValue {
133155
workingId = inputId;
134156
}
135157

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;
158+
const [pool, offset] = getPool();
143159

144160
// The following cases use workingId to construct an ObjectId
145161
if (workingId == null || typeof workingId === 'number') {
146162
// The most common use case (blank id, new objectId instance)
147163
// Generate a new id
148-
ObjectId.generate(
149-
typeof workingId === 'number' ? workingId : undefined,
150-
this[_pool],
151-
this[_offset]
152-
);
164+
ObjectId.generate(typeof workingId === 'number' ? workingId : undefined, pool, offset);
153165
} else if (ArrayBuffer.isView(workingId)) {
154166
if (workingId.byteLength !== 12 && typeof inputIndex !== 'number') {
155167
throw new BSONError('Buffer length must be 12 or offset must be specified');
@@ -161,10 +173,10 @@ export class ObjectId extends BSONValue {
161173
throw new BSONError('Buffer offset must be a non-negative number less than buffer length');
162174
}
163175
inputIndex ??= 0;
164-
for (let i = 0; i < 12; i++) this[_pool][this[_offset] + i] = workingId[inputIndex + i];
176+
for (let i = 0; i < 12; i++) pool[offset + i] = workingId[inputIndex + i];
165177
} else if (typeof workingId === 'string') {
166178
if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
167-
this[_pool].set(ByteUtils.fromHex(workingId), this[_offset]);
179+
pool.set(ByteUtils.fromHex(workingId), offset);
168180
} else {
169181
throw new BSONError(
170182
'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'
@@ -175,8 +187,12 @@ export class ObjectId extends BSONValue {
175187
}
176188
// If we are caching the hex string
177189
if (ObjectId.cacheHexString) {
178-
this.__id = ByteUtils.toHex(this[_pool], this[_offset], this[_offset] + 12);
190+
this.__id = ByteUtils.toHex(pool, offset, offset + 12);
179191
}
192+
// Increment pool offset once we have completed initialization
193+
this.pool = pool;
194+
this.offset = offset;
195+
incrementPool();
180196
}
181197

182198
/** ObjectId bytes @internal */
@@ -189,14 +205,14 @@ export class ObjectId extends BSONValue {
189205
* @readonly
190206
*/
191207
get id(): Uint8Array {
192-
return this[_pool].subarray(this[_offset], this[_offset] + 12);
208+
return this.pool.subarray(this.offset, this.offset + 12);
193209
}
194210

195211
set id(value: Uint8Array) {
196212
if (value.byteLength !== 12) {
197213
throw new BSONError('input must be a 12 byte Uint8Array');
198214
}
199-
this[_pool].set(value, this[_offset]);
215+
this.pool.set(value, this.offset);
200216
if (ObjectId.cacheHexString) {
201217
this.__id = ByteUtils.toHex(value);
202218
}
@@ -208,7 +224,7 @@ export class ObjectId extends BSONValue {
208224
return this.__id;
209225
}
210226

211-
const hexString = ByteUtils.toHex(this[_pool], this[_offset], this[_offset] + 12);
227+
const hexString = ByteUtils.toHex(this.pool, this.offset, this.offset + 12);
212228

213229
if (ObjectId.cacheHexString && !this.__id) {
214230
this.__id = hexString;
@@ -302,9 +318,9 @@ export class ObjectId extends BSONValue {
302318
}
303319

304320
if (ObjectId.is(otherId)) {
305-
if (otherId[_pool] && typeof otherId[_offset] === 'number') {
321+
if (otherId.pool && typeof otherId.offset === 'number') {
306322
for (let i = 11; i >= 0; i--) {
307-
if (this[_pool][this[_offset] + i] !== otherId[_pool][otherId[_offset] + i]) {
323+
if (this.pool[this.offset + i] !== otherId.pool[otherId.offset + i]) {
308324
return false;
309325
}
310326
}
@@ -330,7 +346,7 @@ export class ObjectId extends BSONValue {
330346
/** Returns the generation date (accurate up to the second) that this ID was generated. */
331347
getTimestamp(): Date {
332348
const timestamp = new Date();
333-
const time = NumberUtils.getUint32BE(this[_pool], this[_offset]);
349+
const time = NumberUtils.getUint32BE(this.pool, this.offset);
334350
timestamp.setTime(Math.floor(time) * 1000);
335351
return timestamp;
336352
}
@@ -342,18 +358,20 @@ export class ObjectId extends BSONValue {
342358

343359
/** @internal */
344360
serializeInto(uint8array: Uint8Array, index: number): 12 {
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];
361+
const pool = this.pool;
362+
const offset = this.offset;
363+
uint8array[index] = pool[offset];
364+
uint8array[index + 1] = pool[offset + 1];
365+
uint8array[index + 2] = pool[offset + 2];
366+
uint8array[index + 3] = pool[offset + 3];
367+
uint8array[index + 4] = pool[offset + 4];
368+
uint8array[index + 5] = pool[offset + 5];
369+
uint8array[index + 6] = pool[offset + 6];
370+
uint8array[index + 7] = pool[offset + 7];
371+
uint8array[index + 8] = pool[offset + 8];
372+
uint8array[index + 9] = pool[offset + 9];
373+
uint8array[index + 10] = pool[offset + 10];
374+
uint8array[index + 11] = pool[offset + 11];
357375
return 12;
358376
}
359377

src/utils/node_byte_utils.ts

Lines changed: 2 additions & 2 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,7 +124,7 @@ export const nodeJsByteUtils = {
124124
return Buffer.from(hex, 'hex');
125125
},
126126

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

test/node/object_id.test.ts

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import * as util from 'util';
44
import { expect } from 'chai';
55
import { bufferFromHexArray } from './tools/utils';
66
import { isBufferOrUint8Array } from './tools/utils';
7-
import { _pool, _offset } from '../../src/objectid';
87

98
ObjectId.poolSize = 100;
109

10+
declare module '../register-bson' {
11+
interface ObjectId {
12+
pool: Uint8Array;
13+
offset: number;
14+
}
15+
}
16+
1117
describe('ObjectId', function () {
1218
describe('static createFromTime()', () => {
1319
it('creates an objectId with user defined value in the timestamp field', function () {
@@ -265,8 +271,8 @@ describe('ObjectId', function () {
265271
const obj = new ObjectId();
266272
const obj2 = new ObjectId();
267273

268-
expect(obj[_offset]).to.not.equal(obj2[_offset]);
269-
expect(obj[_pool]).to.equal(obj2[_pool]);
274+
expect(obj.offset).to.not.equal(obj2.offset);
275+
expect(obj.pool).to.equal(obj2.pool);
270276

271277
expect(obj.id).to.not.equal(obj2.id);
272278
});
@@ -276,7 +282,32 @@ describe('ObjectId', function () {
276282
ObjectId.poolSize = 2;
277283
const test = new ObjectId();
278284
// Must fill current (large) pool first
279-
const num = (test[_pool].byteLength - test[_offset]) / 12;
285+
const num = (test.pool.byteLength - test.offset) / 12;
286+
for (let i = 0; i < num + 1; i++) {
287+
new ObjectId();
288+
}
289+
290+
const obj = new ObjectId();
291+
const obj2 = new ObjectId();
292+
const obj3 = new ObjectId();
293+
294+
expect(obj.offset).to.equal(0);
295+
expect(obj2.offset).to.equal(12);
296+
expect(obj3.offset).to.equal(0);
297+
expect(obj.pool).to.equal(obj2.pool);
298+
expect(obj2.pool).to.not.equal(obj3.pool);
299+
300+
expect(obj.id).to.not.equal(obj2.id);
301+
expect(obj2.id).to.not.equal(obj3.id);
302+
ObjectId.poolSize = oldPoolSize;
303+
});
304+
305+
it('should allow poolSize of 1', function () {
306+
const oldPoolSize = ObjectId.poolSize;
307+
ObjectId.poolSize = 1;
308+
const test = new ObjectId();
309+
// Must fill current (large) pool first
310+
const num = (test.pool.byteLength - test.offset) / 12;
280311
for (let i = 0; i < num + 1; i++) {
281312
new ObjectId();
282313
}
@@ -285,17 +316,26 @@ describe('ObjectId', function () {
285316
const obj2 = new ObjectId();
286317
const obj3 = new ObjectId();
287318

288-
expect(obj[_offset]).to.equal(0);
289-
expect(obj2[_offset]).to.equal(12);
290-
expect(obj3[_offset]).to.equal(0);
291-
expect(obj[_pool]).to.equal(obj2[_pool]);
292-
expect(obj2[_pool]).to.not.equal(obj3[_pool]);
319+
expect(obj.offset).to.equal(0);
320+
expect(obj2.offset).to.equal(0);
321+
expect(obj3.offset).to.equal(0);
322+
expect(obj.pool).to.not.equal(obj2.pool);
323+
expect(obj2.pool).to.not.equal(obj3.pool);
293324

294325
expect(obj.id).to.not.equal(obj2.id);
295326
expect(obj2.id).to.not.equal(obj3.id);
296327
ObjectId.poolSize = oldPoolSize;
297328
});
298329

330+
it('should not allow 0 poolSize', function () {
331+
const oldPoolSize = ObjectId.poolSize;
332+
expect(() => {
333+
ObjectId.poolSize = 0;
334+
}).to.throw(BSONError);
335+
336+
ObjectId.poolSize = oldPoolSize;
337+
});
338+
299339
it('should throw error if non-12 byte non-24 hex string passed in', function () {
300340
expect(() => new ObjectId('FFFFFFFFFFFFFFFFFFFFFFFG')).to.throw(BSONError);
301341
expect(() => new ObjectId('thisstringisdefinitelytoolong')).to.throw(BSONError);
@@ -486,9 +526,7 @@ describe('ObjectId', function () {
486526

487527
equalId = new Proxy(equalId, {
488528
get(target, prop: string, recv) {
489-
if (typeof prop === 'symbol') {
490-
propAccessRecord.push((prop as symbol).toString());
491-
} else if (prop !== '_bsontype') {
529+
if (prop !== '_bsontype') {
492530
propAccessRecord.push(prop);
493531
}
494532
return Reflect.get(target, prop, recv);
@@ -498,7 +536,7 @@ describe('ObjectId', function () {
498536
expect(oid.equals(equalId)).to.be.true;
499537
// once for the 11th byte shortcut
500538
// once for the total equality
501-
expect(propAccessRecord).to.deep.equal(['Symbol(pool)', oidKId]);
539+
expect(propAccessRecord).to.deep.equal(['pool', oidKId]);
502540
});
503541
});
504542

0 commit comments

Comments
 (0)