|
1 | 1 | import { Buffer } from 'buffer';
|
2 | 2 | import { ensureBuffer } from './ensure_buffer';
|
3 |
| -import { uuidHexStringToBuffer } from './uuid_utils'; |
4 |
| -import { UUID, UUIDExtended } from './uuid'; |
| 3 | +import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; |
| 4 | +import { isUint8Array, randomBytes } from './parser/utils'; |
5 | 5 | import type { EJSONOptions } from './extended_json';
|
6 | 6 | import { BSONError, BSONTypeError } from './error';
|
| 7 | +import { BSON_BINARY_SUBTYPE_UUID_NEW } from './constants'; |
7 | 8 |
|
8 | 9 | /** @public */
|
9 | 10 | export type BinarySequence = Uint8Array | Buffer | number[];
|
@@ -292,3 +293,189 @@ export class Binary {
|
292 | 293 | }
|
293 | 294 |
|
294 | 295 | Object.defineProperty(Binary.prototype, '_bsontype', { value: 'Binary' });
|
| 296 | + |
| 297 | +/** @public */ |
| 298 | +export type UUIDExtended = { |
| 299 | + $uuid: string; |
| 300 | +}; |
| 301 | +const UUID_BYTE_LENGTH = 16; |
| 302 | + |
| 303 | +/** |
| 304 | + * A class representation of the BSON UUID type. |
| 305 | + * @public |
| 306 | + */ |
| 307 | +export class UUID extends Binary { |
| 308 | + static cacheHexString: boolean; |
| 309 | + |
| 310 | + /** UUID hexString cache @internal */ |
| 311 | + private __id?: string; |
| 312 | + |
| 313 | + /** |
| 314 | + * Create an UUID type |
| 315 | + * |
| 316 | + * @param input - Can be a 32 or 36 character hex string (dashes excluded/included) or a 16 byte binary Buffer. |
| 317 | + */ |
| 318 | + constructor(input?: string | Buffer | UUID) { |
| 319 | + let bytes; |
| 320 | + let hexStr; |
| 321 | + if (input == null) { |
| 322 | + bytes = UUID.generate(); |
| 323 | + } else if (input instanceof UUID) { |
| 324 | + bytes = Buffer.from(input.buffer); |
| 325 | + hexStr = input.__id; |
| 326 | + } else if (ArrayBuffer.isView(input) && input.byteLength === UUID_BYTE_LENGTH) { |
| 327 | + bytes = ensureBuffer(input); |
| 328 | + } else if (typeof input === 'string') { |
| 329 | + bytes = uuidHexStringToBuffer(input); |
| 330 | + } else { |
| 331 | + throw new BSONTypeError( |
| 332 | + 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' |
| 333 | + ); |
| 334 | + } |
| 335 | + super(bytes, BSON_BINARY_SUBTYPE_UUID_NEW); |
| 336 | + this.__id = hexStr; |
| 337 | + } |
| 338 | + |
| 339 | + /** |
| 340 | + * The UUID bytes |
| 341 | + * @readonly |
| 342 | + */ |
| 343 | + get id(): Buffer { |
| 344 | + return this.buffer; |
| 345 | + } |
| 346 | + |
| 347 | + set id(value: Buffer) { |
| 348 | + this.buffer = value; |
| 349 | + |
| 350 | + if (UUID.cacheHexString) { |
| 351 | + this.__id = bufferToUuidHexString(value); |
| 352 | + } |
| 353 | + } |
| 354 | + |
| 355 | + /** |
| 356 | + * Returns the UUID id as a 32 or 36 character hex string representation, excluding/including dashes (defaults to 36 character dash separated) |
| 357 | + * @param includeDashes - should the string exclude dash-separators. |
| 358 | + * */ |
| 359 | + toHexString(includeDashes = true): string { |
| 360 | + if (UUID.cacheHexString && this.__id) { |
| 361 | + return this.__id; |
| 362 | + } |
| 363 | + |
| 364 | + const uuidHexString = bufferToUuidHexString(this.id, includeDashes); |
| 365 | + |
| 366 | + if (UUID.cacheHexString) { |
| 367 | + this.__id = uuidHexString; |
| 368 | + } |
| 369 | + |
| 370 | + return uuidHexString; |
| 371 | + } |
| 372 | + |
| 373 | + /** |
| 374 | + * Converts the id into a 36 character (dashes included) hex string, unless a encoding is specified. |
| 375 | + */ |
| 376 | + toString(encoding?: string): string { |
| 377 | + return encoding ? this.id.toString(encoding) : this.toHexString(); |
| 378 | + } |
| 379 | + |
| 380 | + /** |
| 381 | + * Converts the id into its JSON string representation. |
| 382 | + * A 36 character (dashes included) hex string in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
| 383 | + */ |
| 384 | + toJSON(): string { |
| 385 | + return this.toHexString(); |
| 386 | + } |
| 387 | + |
| 388 | + /** |
| 389 | + * Compares the equality of this UUID with `otherID`. |
| 390 | + * |
| 391 | + * @param otherId - UUID instance to compare against. |
| 392 | + */ |
| 393 | + equals(otherId: string | Buffer | UUID): boolean { |
| 394 | + if (!otherId) { |
| 395 | + return false; |
| 396 | + } |
| 397 | + |
| 398 | + if (otherId instanceof UUID) { |
| 399 | + return otherId.id.equals(this.id); |
| 400 | + } |
| 401 | + |
| 402 | + try { |
| 403 | + return new UUID(otherId).id.equals(this.id); |
| 404 | + } catch { |
| 405 | + return false; |
| 406 | + } |
| 407 | + } |
| 408 | + |
| 409 | + /** |
| 410 | + * Creates a Binary instance from the current UUID. |
| 411 | + */ |
| 412 | + toBinary(): Binary { |
| 413 | + return new Binary(this.id, Binary.SUBTYPE_UUID); |
| 414 | + } |
| 415 | + |
| 416 | + /** |
| 417 | + * Generates a populated buffer containing a v4 uuid |
| 418 | + */ |
| 419 | + static generate(): Buffer { |
| 420 | + const bytes = randomBytes(UUID_BYTE_LENGTH); |
| 421 | + |
| 422 | + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` |
| 423 | + // Kindly borrowed from https://github.com/uuidjs/uuid/blob/master/src/v4.js |
| 424 | + bytes[6] = (bytes[6] & 0x0f) | 0x40; |
| 425 | + bytes[8] = (bytes[8] & 0x3f) | 0x80; |
| 426 | + |
| 427 | + return Buffer.from(bytes); |
| 428 | + } |
| 429 | + |
| 430 | + /** |
| 431 | + * Checks if a value is a valid bson UUID |
| 432 | + * @param input - UUID, string or Buffer to validate. |
| 433 | + */ |
| 434 | + static isValid(input: string | Buffer | UUID): boolean { |
| 435 | + if (!input) { |
| 436 | + return false; |
| 437 | + } |
| 438 | + |
| 439 | + if (input instanceof UUID) { |
| 440 | + return true; |
| 441 | + } |
| 442 | + |
| 443 | + if (typeof input === 'string') { |
| 444 | + return uuidValidateString(input); |
| 445 | + } |
| 446 | + |
| 447 | + if (isUint8Array(input)) { |
| 448 | + // check for length & uuid version (https://tools.ietf.org/html/rfc4122#section-4.1.3) |
| 449 | + if (input.length !== UUID_BYTE_LENGTH) { |
| 450 | + return false; |
| 451 | + } |
| 452 | + |
| 453 | + return (input[6] & 0xf0) === 0x40 && (input[8] & 0x80) === 0x80; |
| 454 | + } |
| 455 | + |
| 456 | + return false; |
| 457 | + } |
| 458 | + |
| 459 | + /** |
| 460 | + * Creates an UUID from a hex string representation of an UUID. |
| 461 | + * @param hexString - 32 or 36 character hex string (dashes excluded/included). |
| 462 | + */ |
| 463 | + static createFromHexString(hexString: string): UUID { |
| 464 | + const buffer = uuidHexStringToBuffer(hexString); |
| 465 | + return new UUID(buffer); |
| 466 | + } |
| 467 | + |
| 468 | + /** |
| 469 | + * Converts to a string representation of this Id. |
| 470 | + * |
| 471 | + * @returns return the 36 character hex string representation. |
| 472 | + * @internal |
| 473 | + */ |
| 474 | + [Symbol.for('nodejs.util.inspect.custom')](): string { |
| 475 | + return this.inspect(); |
| 476 | + } |
| 477 | + |
| 478 | + inspect(): string { |
| 479 | + return `new UUID("${this.toHexString()}")`; |
| 480 | + } |
| 481 | +} |
0 commit comments