import { decodeHexString, encodeHexString } from "../utils.ts";import { BSONTypeError } from "./error.ts";import { randomBytes } from "./parser/utils.ts";import { equals } from "../deps.ts";const textEncoder = new TextEncoder();const textDecoder = new TextDecoder();
const checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
let PROCESS_UNIQUE: Uint8Array | null = null;export class ObjectId { static #index = Math.floor(Math.random() * 0xff_ff_ff); static cacheHexString: boolean;
#id?: string; #bytesBuffer: Uint8Array; constructor( inputId: string | number | ObjectId | Uint8Array = ObjectId .generate(), ) { let workingId: Uint8Array | string | number; if (typeof inputId === "object" && inputId && "id" in inputId) { if (typeof inputId.id !== "string" && !ArrayBuffer.isView(inputId.id)) { throw new BSONTypeError( "Argument passed in must have an id that is of type string or Buffer", ); } workingId = "toHexString" in inputId && typeof inputId.toHexString === "function" ? decodeHexString(inputId.toHexString()) : inputId.id; } else { workingId = inputId; }
if (workingId == null || typeof workingId === "number") { this.#bytesBuffer = new Uint8Array(ObjectId.generate( typeof workingId === "number" ? workingId : undefined, )); } else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) { this.#bytesBuffer = workingId; } else if (typeof workingId === "string") { if (workingId.length === 12) { const bytes = textEncoder.encode(workingId); if (bytes.byteLength === 12) { this.#bytesBuffer = bytes; } else { throw new BSONTypeError( "Argument passed in must be a string of 12 bytes", ); } } else if (workingId.length === 24 && checkForHexRegExp.test(workingId)) { this.#bytesBuffer = decodeHexString(workingId); } else { throw new BSONTypeError( "Argument passed in must be a string of 12 bytes or a string of 24 hex characters", ); } } else { throw new BSONTypeError( "Argument passed in does not match the accepted types", ); } if (ObjectId.cacheHexString) { this.#id = encodeHexString(this.id); } }
get id(): Uint8Array { return this.#bytesBuffer; }
set id(value: Uint8Array) { this.#bytesBuffer = value; if (ObjectId.cacheHexString) { this.#id = encodeHexString(value); } }
toHexString(): string { if (ObjectId.cacheHexString && this.#id) { return this.#id; }
const hexString = encodeHexString(this.id);
if (ObjectId.cacheHexString && !this.#id) { this.#id = hexString; }
return hexString; }
static generate(time?: number): Uint8Array { if ("number" !== typeof time) { time = Math.floor(Date.now() / 1000); }
const inc = (this.#index = (this.#index + 1) % 0xff_ff_ff); const objectId = new Uint8Array(12);
new DataView(objectId.buffer, 0, 4).setUint32(0, time);
if (PROCESS_UNIQUE === null) { PROCESS_UNIQUE = randomBytes(5); }
objectId[4] = PROCESS_UNIQUE[0]; objectId[5] = PROCESS_UNIQUE[1]; objectId[6] = PROCESS_UNIQUE[2]; objectId[7] = PROCESS_UNIQUE[3]; objectId[8] = PROCESS_UNIQUE[4];
objectId[11] = inc & 0xff; objectId[10] = (inc >> 8) & 0xff; objectId[9] = (inc >> 16) & 0xff;
return objectId; }
toString(): string { return this.toHexString(); }
toJSON(): string { return this.toHexString(); }
equals(otherId: string | ObjectId): boolean { if (otherId == null) { return false; }
if (otherId instanceof ObjectId) { return equals(this.#bytesBuffer, otherId.#bytesBuffer); }
if ( typeof otherId === "string" && ObjectId.isValid(otherId) && otherId.length === 12 && this.id instanceof Uint8Array ) { return otherId === textDecoder.decode(this.id); }
if ( typeof otherId === "string" && ObjectId.isValid(otherId) && otherId.length === 24 ) { return otherId.toLowerCase() === this.toHexString(); }
if ( typeof otherId === "string" && ObjectId.isValid(otherId) && otherId.length === 12 ) { const otherIdUint8Array = textEncoder.encode(otherId); for (let i = 0; i < 12; i++) { if (otherIdUint8Array[i] !== this.id[i]) { return false; } }
return true; }
return false; }
getTimestamp(): Date { const timestamp = new Date(); const time = new DataView(this.id.buffer, 0, 4).getUint32(0); timestamp.setTime(Math.floor(time) * 1000); return timestamp; }
static createFromTime(time: number): ObjectId { const buffer = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); new DataView(buffer.buffer, 0, 4).setUint32(0, time); return new ObjectId(buffer); }
static createFromHexString(hexString: string): ObjectId { if ( typeof hexString === "undefined" || (hexString != null && hexString.length !== 24) ) { throw new BSONTypeError( "Argument passed in must be a single String of 12 bytes or a string of 24 hex characters", ); }
return new ObjectId(decodeHexString(hexString)); }
static isValid( id: string | number | ObjectId | Uint8Array, ): boolean { if (id == null) return false;
try { new ObjectId(id); return true; } catch { return false; } }
[Symbol.for("Deno.customInspect")](): string { return `new ObjectId("${this.toHexString()}")`; }}