opusdev/vector-similarity-api
1
1import { BSONValue } from './bson_value';2import { BSONError } from './error';3import { type InspectFn, defaultInspect } from './parser/utils';4import { ByteUtils } from './utils/byte_utils';5import { NumberUtils } from './utils/number_utils';6 7// Unique sequence for the current process (initialized on first use)8let PROCESS_UNIQUE: Uint8Array | null = null;9 10/** ObjectId hexString cache @internal */11const __idCache = new WeakMap(); // TODO(NODE-6549): convert this to #__id private field when target updated to ES202212 13/** @public */14export interface ObjectIdLike {15 id: string | Uint8Array;16 __id?: string;17 toHexString(): string;18}19 20/** @public */21export interface ObjectIdExtended {22 $oid: string;23}24 25/**26 * A class representation of the BSON ObjectId type.27 * @public28 * @category BSONType29 */30export class ObjectId extends BSONValue {31 get _bsontype(): 'ObjectId' {32 return 'ObjectId';33 }34 35 /** @internal */36 private static index = Math.floor(Math.random() * 0xffffff);37 38 static cacheHexString: boolean;39 40 /** ObjectId Bytes @internal */41 private buffer!: Uint8Array;42 43 /**44 * Create ObjectId from a number.45 *46 * @param inputId - A number.47 * @deprecated Instead, use `static createFromTime()` to set a numeric value for the new ObjectId.48 */49 constructor(inputId: number);50 /**51 * Create ObjectId from a 24 character hex string.52 *53 * @param inputId - A 24 character hex string.54 */55 constructor(inputId: string);56 /**57 * Create ObjectId from the BSON ObjectId type.58 *59 * @param inputId - The BSON ObjectId type.60 */61 constructor(inputId: ObjectId);62 /**63 * Create ObjectId from the object type that has the toHexString method.64 *65 * @param inputId - The ObjectIdLike type.66 */67 constructor(inputId: ObjectIdLike);68 /**69 * Create ObjectId from a 12 byte binary Buffer.70 *71 * @param inputId - A 12 byte binary Buffer.72 */73 constructor(inputId: Uint8Array);74 /** To generate a new ObjectId, use ObjectId() with no argument. */75 constructor();76 /**77 * Implementation overload.78 *79 * @param inputId - All input types that are used in the constructor implementation.80 */81 constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array);82 /**83 * Create a new ObjectId.84 *85 * @param inputId - An input value to create a new ObjectId from.86 */87 constructor(inputId?: string | number | ObjectId | ObjectIdLike | Uint8Array) {88 super();89 // workingId is set based on type of input and whether valid id exists for the input90 let workingId;91 if (typeof inputId === 'object' && inputId && 'id' in inputId) {92 if (typeof inputId.id !== 'string' && !ArrayBuffer.isView(inputId.id)) {93 throw new BSONError('Argument passed in must have an id that is of type string or Buffer');94 }95 if ('toHexString' in inputId && typeof inputId.toHexString === 'function') {96 workingId = ByteUtils.fromHex(inputId.toHexString());97 } else {98 workingId = inputId.id;99 }100 } else {101 workingId = inputId;102 }103 104 // The following cases use workingId to construct an ObjectId105 if (workingId == null || typeof workingId === 'number') {106 // The most common use case (blank id, new objectId instance)107 // Generate a new id108 this.buffer = ObjectId.generate(typeof workingId === 'number' ? workingId : undefined);109 } else if (ArrayBuffer.isView(workingId) && workingId.byteLength === 12) {110 // If intstanceof matches we can escape calling ensure buffer in Node.js environments111 this.buffer = ByteUtils.toLocalBufferType(workingId);112 } else if (typeof workingId === 'string') {113 if (ObjectId.validateHexString(workingId)) {114 this.buffer = ByteUtils.fromHex(workingId);115 // If we are caching the hex string116 if (ObjectId.cacheHexString) {117 __idCache.set(this, workingId);118 }119 } else {120 throw new BSONError(121 'input must be a 24 character hex string, 12 byte Uint8Array, or an integer'122 );123 }124 } else {125 throw new BSONError('Argument passed in does not match the accepted types');126 }127 }128 129 /**130 * The ObjectId bytes131 * @readonly132 */133 get id(): Uint8Array {134 return this.buffer;135 }136 137 set id(value: Uint8Array) {138 this.buffer = value;139 if (ObjectId.cacheHexString) {140 __idCache.set(this, ByteUtils.toHex(value));141 }142 }143 144 /**145 * @internal146 * Validates the input string is a valid hex representation of an ObjectId.147 */148 private static validateHexString(string: string): boolean {149 if (string?.length !== 24) return false;150 for (let i = 0; i < 24; i++) {151 const char = string.charCodeAt(i);152 if (153 // Check for ASCII 0-9154 (char >= 48 && char <= 57) ||155 // Check for ASCII a-f156 (char >= 97 && char <= 102) ||157 // Check for ASCII A-F158 (char >= 65 && char <= 70)159 ) {160 continue;161 }162 return false;163 }164 return true;165 }166 167 /** Returns the ObjectId id as a 24 lowercase character hex string representation */168 toHexString(): string {169 if (ObjectId.cacheHexString) {170 const __id = __idCache.get(this);171 if (__id) return __id;172 }173 174 const hexString = ByteUtils.toHex(this.id);175 176 if (ObjectId.cacheHexString) {177 __idCache.set(this, hexString);178 }179 180 return hexString;181 }182 183 /**184 * Update the ObjectId index185 * @internal186 */187 private static getInc(): number {188 return (ObjectId.index = (ObjectId.index + 1) % 0xffffff);189 }190 191 /**192 * Generate a 12 byte id buffer used in ObjectId's193 *194 * @param time - pass in a second based timestamp.195 */196 static generate(time?: number): Uint8Array {197 if ('number' !== typeof time) {198 time = Math.floor(Date.now() / 1000);199 }200 201 const inc = ObjectId.getInc();202 const buffer = ByteUtils.allocateUnsafe(12);203 204 // 4-byte timestamp205 NumberUtils.setInt32BE(buffer, 0, time);206 207 // set PROCESS_UNIQUE if yet not initialized208 if (PROCESS_UNIQUE === null) {209 PROCESS_UNIQUE = ByteUtils.randomBytes(5);210 }211 212 // 5-byte process unique213 buffer[4] = PROCESS_UNIQUE[0];214 buffer[5] = PROCESS_UNIQUE[1];215 buffer[6] = PROCESS_UNIQUE[2];216 buffer[7] = PROCESS_UNIQUE[3];217 buffer[8] = PROCESS_UNIQUE[4];218 219 // 3-byte counter220 buffer[11] = inc & 0xff;221 buffer[10] = (inc >> 8) & 0xff;222 buffer[9] = (inc >> 16) & 0xff;223 224 return buffer;225 }226 227 /**228 * Converts the id into a 24 character hex string for printing, unless encoding is provided.229 * @param encoding - hex or base64230 */231 toString(encoding?: 'hex' | 'base64'): string {232 // Is the id a buffer then use the buffer toString method to return the format233 if (encoding === 'base64') return ByteUtils.toBase64(this.id);234 if (encoding === 'hex') return this.toHexString();235 return this.toHexString();236 }237 238 /** Converts to its JSON the 24 character hex string representation. */239 toJSON(): string {240 return this.toHexString();241 }242 243 /** @internal */244 private static is(variable: unknown): variable is ObjectId {245 return (246 variable != null &&247 typeof variable === 'object' &&248 '_bsontype' in variable &&249 variable._bsontype === 'ObjectId'250 );251 }252 253 /**254 * Compares the equality of this ObjectId with `otherID`.255 *256 * @param otherId - ObjectId instance to compare against.257 */258 equals(otherId: string | ObjectId | ObjectIdLike | undefined | null): boolean {259 if (otherId === undefined || otherId === null) {260 return false;261 }262 263 if (ObjectId.is(otherId)) {264 return (265 this.buffer[11] === otherId.buffer[11] && ByteUtils.equals(this.buffer, otherId.buffer)266 );267 }268 269 if (typeof otherId === 'string') {270 return otherId.toLowerCase() === this.toHexString();271 }272 273 if (typeof otherId === 'object' && typeof otherId.toHexString === 'function') {274 const otherIdString = otherId.toHexString();275 const thisIdString = this.toHexString();276 return typeof otherIdString === 'string' && otherIdString.toLowerCase() === thisIdString;277 }278 279 return false;280 }281 282 /** Returns the generation date (accurate up to the second) that this ID was generated. */283 getTimestamp(): Date {284 const timestamp = new Date();285 const time = NumberUtils.getUint32BE(this.buffer, 0);286 timestamp.setTime(Math.floor(time) * 1000);287 return timestamp;288 }289 290 /** @internal */291 static createPk(): ObjectId {292 return new ObjectId();293 }294 295 /** @internal */296 serializeInto(uint8array: Uint8Array, index: number): 12 {297 uint8array[index] = this.buffer[0];298 uint8array[index + 1] = this.buffer[1];299 uint8array[index + 2] = this.buffer[2];300 uint8array[index + 3] = this.buffer[3];301 uint8array[index + 4] = this.buffer[4];302 uint8array[index + 5] = this.buffer[5];303 uint8array[index + 6] = this.buffer[6];304 uint8array[index + 7] = this.buffer[7];305 uint8array[index + 8] = this.buffer[8];306 uint8array[index + 9] = this.buffer[9];307 uint8array[index + 10] = this.buffer[10];308 uint8array[index + 11] = this.buffer[11];309 return 12;310 }311 312 /**313 * Creates an ObjectId from a second based number, with the rest of the ObjectId zeroed out. Used for comparisons or sorting the ObjectId.314 *315 * @param time - an integer number representing a number of seconds.316 */317 static createFromTime(time: number): ObjectId {318 const buffer = ByteUtils.allocate(12);319 for (let i = 11; i >= 4; i--) buffer[i] = 0;320 // Encode time into first 4 bytes321 NumberUtils.setInt32BE(buffer, 0, time);322 // Return the new objectId323 return new ObjectId(buffer);324 }325 326 /**327 * Creates an ObjectId from a hex string representation of an ObjectId.328 *329 * @param hexString - create a ObjectId from a passed in 24 character hexstring.330 */331 static createFromHexString(hexString: string): ObjectId {332 if (hexString?.length !== 24) {333 throw new BSONError('hex string must be 24 characters');334 }335 336 return new ObjectId(ByteUtils.fromHex(hexString));337 }338 339 /** Creates an ObjectId instance from a base64 string */340 static createFromBase64(base64: string): ObjectId {341 if (base64?.length !== 16) {342 throw new BSONError('base64 string must be 16 characters');343 }344 345 return new ObjectId(ByteUtils.fromBase64(base64));346 }347 348 /**349 * Checks if a value can be used to create a valid bson ObjectId350 * @param id - any JS value351 */352 static isValid(id: string | number | ObjectId | ObjectIdLike | Uint8Array): boolean {353 if (id == null) return false;354 if (typeof id === 'string') return ObjectId.validateHexString(id);355 356 try {357 new ObjectId(id);358 return true;359 } catch {360 return false;361 }362 }363 364 /** @internal */365 toExtendedJSON(): ObjectIdExtended {366 if (this.toHexString) return { $oid: this.toHexString() };367 return { $oid: this.toString('hex') };368 }369 370 /** @internal */371 static fromExtendedJSON(doc: ObjectIdExtended): ObjectId {372 return new ObjectId(doc.$oid);373 }374 375 /** @internal */376 private isCached(): boolean {377 return ObjectId.cacheHexString && __idCache.has(this);378 }379 380 /**381 * Converts to a string representation of this Id.382 *383 * @returns return the 24 character hex string representation.384 */385 inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {386 inspect ??= defaultInspect;387 return `new ObjectId(${inspect(this.toHexString(), options)})`;388 }389}390 