@rushstack/node-core-library
Version:
Core libraries that every NodeJS toolchain project should use
87 lines • 4.21 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeUuid = void 0;
const InternalError_1 = require("./InternalError");
const classPrototypeUuidSymbol = Symbol.for('TypeUuid.classPrototypeUuid');
/**
* Provides a version-independent implementation of the JavaScript `instanceof` operator.
*
* @remarks
* The JavaScript `instanceof` operator normally only identifies objects from a particular library instance.
* For example, suppose the NPM package `example-lib` has two published versions 1.2.0 and 1.3.0, and
* it exports a class called `A`. Suppose some code consumes version `1.3.0` of the library, but it receives
* an object that was constructed using version `1.2.0`. In this situation `a instanceof A` will return `false`,
* even though `a` is an instance of `A`. The reason is that there are two prototypes for `A`; one for each
* version.
*
* The `TypeUuid` facility provides a way to make `a instanceof A` return true for both prototypes of `A`,
* by instead using a universally unique identifier (UUID) to detect object instances.
*
* You can use `Symbol.hasInstance` to enable the system `instanceof` operator to recognize type UUID equivalence:
* ```ts
* const uuidWidget: string = '9c340ef0-d29f-4e2e-a09f-42bacc59024b';
* class Widget {
* public static [Symbol.hasInstance](instance: object): boolean {
* return TypeUuid.isInstanceOf(instance, uuidWidget);
* }
* }
* ```
* // Example usage:
* ```ts
* import { Widget as Widget1 } from 'v1-of-library';
* import { Widget as Widget2 } from 'v2-of-library';
* const widget = new Widget2();
* console.log(widget instanceof Widget1); // prints true
* ```
*
* @public
*/
class TypeUuid {
/**
* Registers a JavaScript class as having a type identified by the specified UUID.
* @privateRemarks
* We cannot use a construct signature for `targetClass` because it may be an abstract class.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static registerClass(targetClass, typeUuid) {
if (typeof targetClass !== 'function') {
throw new Error('The targetClass parameter must be a JavaScript class');
}
if (!TypeUuid._uuidRegExp.test(typeUuid)) {
throw new Error(`The type UUID must be specified as lowercase hexadecimal with dashes: "${typeUuid}"`);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetClassPrototype = targetClass.prototype;
if (Object.hasOwnProperty.call(targetClassPrototype, classPrototypeUuidSymbol)) {
const existingUuid = targetClassPrototype[classPrototypeUuidSymbol];
throw new InternalError_1.InternalError(`Cannot register the target class ${targetClass.name || ''} typeUuid=${typeUuid}` +
` because it was already registered with typeUuid=${existingUuid}`);
}
targetClassPrototype[classPrototypeUuidSymbol] = typeUuid;
}
/**
* Returns true if the `targetObject` is an instance of a JavaScript class that was previously
* registered using the specified `typeUuid`. Base classes are also considered.
*/
static isInstanceOf(targetObject, typeUuid) {
if (targetObject === undefined || targetObject === null) {
return false;
}
let objectPrototype = Object.getPrototypeOf(targetObject);
while (objectPrototype !== undefined && objectPrototype !== null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const registeredUuid = objectPrototype[classPrototypeUuidSymbol];
if (registeredUuid === typeUuid) {
return true;
}
// Walk upwards an examine base class prototypes
objectPrototype = Object.getPrototypeOf(objectPrototype);
}
return false;
}
}
exports.TypeUuid = TypeUuid;
TypeUuid._uuidRegExp = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
//# sourceMappingURL=TypeUuid.js.map