@itwin/core-backend
Version:
iTwin.js backend components
270 lines • 13.9 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Schema
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Entity = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
const Symbols_1 = require("./internal/Symbols");
/** Represents one of the fundamental building block in an [[IModelDb]]: as an [[Element]], [[Model]], or [[Relationship]].
* Every subclass of Entity represents one BIS [ECClass]($ecschema-metadata).
* An Entity is typically instantiated from an [EntityProps]($common) and can be converted back to this representation via [[Entity.toJSON]].
* @public @preview
*/
class Entity {
/** An immutable property used to discriminate between [[Entity]] and [EntityProps]($common), used to inform the TypeScript compiler that these two types
* are never substitutable for one another. To obtain an EntityProps from an Entity, use [[Entity.toJSON]].
*/
isInstanceOfEntity = true;
/** The Schema that defines this class. */
static schema; // TODO: Schema key on the static level, but it requires a version which may differ between imodels
get _ctor() { return this.constructor; }
/** The name of the BIS class associated with this class.
* @note Every subclass of Entity **MUST** override this method to identify its BIS class.
* Failure to do so will ordinarily result in an error when the class is registered, since there may only
* be one JavaScript class for a given BIS class (usually the errant class will collide with its superclass.)
*/
static get className() { return "Entity"; }
/** Serves as a unique identifier for this class. Typed variant of [[classFullName]].
* @public @preview
*/
static get schemaItemKey() {
// We cannot cache this here because the className gets overridden in subclasses
return new ecschema_metadata_1.SchemaItemKey(this.className, this.schema.schemaKey);
}
/** Cached Metadata for the ECClass */
_metadata;
/** When working with an Entity it can be useful to set property values directly, bypassing the compiler's type checking.
* This property makes such code slightly less tedious to read and write.
* @internal
*/
get asAny() { return this; }
/** The name of the BIS Schema that defines this class */
get schemaName() { return this._ctor.schema.schemaName; }
/** The name of the BIS class associated with this class. */
get className() { return this._ctor.className; }
/** The [[IModelDb]] that contains this Entity */
iModel;
/** The Id of this Entity. May be invalid if the Entity has not yet been saved in the database. */
id;
constructor(props, iModel) {
this.iModel = iModel;
this.id = core_bentley_1.Id64.fromJSON(props.id);
// copy all auto-handled properties from input to the object being constructed
// eslint-disable-next-line @typescript-eslint/no-deprecated
this.forEachProperty((propName, meta) => this[propName] = meta.createProperty(props[propName]), false);
}
/** Invoke the constructor of the specified `Entity` subclass.
* @internal
*/
static instantiate(subclass, props, iModel) {
return new subclass(props, iModel);
}
/** List of properties that are need to be custom handled during deserialization and serialization.
* These properties differ between the ECSql instance of an Entity and the Entity itself.
* @beta */
static _customHandledProps = [
{ propertyName: "id", source: "Class" },
{ propertyName: "className", source: "Class" },
{ propertyName: "jsonProperties", source: "Class" }
];
/** Get the list of properties that are custom handled by this class and its superclasses.
* @internal */
static getCustomHandledProperties() {
if (this.name === "Entity") {
return this._customHandledProps;
}
const superClass = Object.getPrototypeOf(this);
return [
...superClass.getCustomHandledProperties(),
...this._customHandledProps,
];
}
/** Converts an ECSqlRow of an Entity to an EntityProps. This is used to deserialize an Entity from the database.
* @beta */
static deserialize(props) {
const enProps = {
classFullName: props.row.classFullName,
id: props.row.id,
};
// Handles cases where id64 ints are stored in the jsonProperties and converts them to hex before parsing as a json object in js
if (props.row.jsonProperties) {
enProps.jsonProperties = JSON.parse(props.iModel[Symbols_1._nativeDb].patchJsonProperties(props.row.jsonProperties));
}
// Auto handles all properties that are not in the 'customHandledProperties' list
const customHandledProperties = this.getCustomHandledProperties();
Object.keys(props.row)
.filter((propertyName) => customHandledProperties.find((val) => val.propertyName === propertyName) === undefined)
.forEach((propertyName) => enProps[propertyName] = props.row[propertyName]);
// Handles custom relClassNames to use '.' instead of ':'
Object.keys(enProps).forEach((propertyName) => {
if (enProps[propertyName].relClassName !== undefined && propertyName !== "modeledElement" && propertyName !== "parentModel") {
enProps[propertyName].relClassName = enProps[propertyName].relClassName.replace(':', '.');
}
});
return enProps;
}
/** Converts an EntityProps to an ECSqlRow. This is used to serialize an Entity to prepare to write it to the database.
* @beta */
static serialize(props, _iModel) {
const inst = {
classFullName: props.classFullName,
id: props.id,
};
const customHandledProperties = this.getCustomHandledProperties();
Object.keys(props)
.filter((propertyName) => customHandledProperties.find((val) => val.propertyName === propertyName) === undefined)
.forEach((propertyName) => inst[propertyName] = props[propertyName]);
return inst;
}
/** Obtain the JSON representation of this Entity. Subclasses of [[Entity]] typically override this method to return their corresponding sub-type of [EntityProps]($common) -
* for example, [[GeometricElement.toJSON]] returns a [GeometricElementProps]($common).
*/
toJSON() {
const val = {};
val.classFullName = this.classFullName;
if (core_bentley_1.Id64.isValid(this.id))
val.id = this.id;
// eslint-disable-next-line @typescript-eslint/no-deprecated
this.forEachProperty((propName) => val[propName] = this[propName], false);
return val;
}
/** Call a function for each property of this Entity.
* @param func The callback to be invoked on each property
* @param includeCustom If true (default), include custom-handled properties in the iteration. Otherwise, skip custom-handled properties.
* @note Custom-handled properties are core properties that have behavior enforced by C++ handlers.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `forEach` to get the metadata and iterate over the properties instead.
*
* @example
* ```typescript
* // Deprecated method
* entity.forEachProperty((name, propMetaData) => {
* console.log(`Property name: ${name}, Property type: ${propMetaData.primitiveType}`);
* });
*
* // New method
* entity.forEach((name, property) => {
* console.log(`Property name: ${name}, Property type: ${property.propertyType}`);
* });
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-deprecated
forEachProperty(func, includeCustom = true) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
this.iModel.forEachMetaData(this.classFullName, true, func, includeCustom);
}
/**
* Call a function for each property of this Entity.
* @param func The callback to be invoked on each property.
* @param includeCustom If true (default), include custom-handled properties in the iteration. Otherwise, skip custom-handled properties.
* @note Custom-handled properties are core properties that have behavior enforced by C++ handlers.
* @throws Error if metadata for the class cannot be retrieved.
*
* @example
* ```typescript
* entity.forEach((name, property) => {
* console.log(`Property name: ${name}, Property type: ${property.propertyType}`);
* });
* ```
*/
forEach(func, includeCustom = true) {
const item = this._metadata ?? this.iModel.schemaContext.getSchemaItemSync(this.schemaItemKey);
if (ecschema_metadata_1.EntityClass.isEntityClass(item) || ecschema_metadata_1.RelationshipClass.isRelationshipClass(item)) {
for (const property of item.getPropertiesSync()) {
if (includeCustom || !property.customAttributes?.has(`BisCore.CustomHandledProperty`))
func(property.name, property);
}
}
else {
throw new Error(`Cannot get metadata for ${this.classFullName}. Class is not an EntityClass or RelationshipClass.`);
}
}
/** Get the full BIS class name of this Entity in the form "schema:class" */
static get classFullName() { return `${this.schema.schemaName}:${this.className}`; }
/** Get the full BIS class name of this Entity in the form "schema:class". */
get classFullName() { return this._ctor.classFullName; }
/**
* Get the item key used by the ecschema-metadata package to identify this entity class
* @public @preview
*/
get schemaItemKey() { return this._ctor.schemaItemKey; }
/** Query metadata for this entity class from the iModel's schema. Returns cached metadata if available.
* @throws [[IModelError]] if there is a problem querying the schema
* @returns The metadata for the current entity
* @public @preview
*/
async getMetaData() {
if (this._metadata) {
return this._metadata;
}
const ecClass = await this.iModel.schemaContext.getSchemaItem(this.schemaItemKey, ecschema_metadata_1.ECClass);
if (ecschema_metadata_1.EntityClass.isEntityClass(ecClass) || ecschema_metadata_1.RelationshipClass.isRelationshipClass(ecClass)) {
this._metadata = ecClass;
return this._metadata;
}
else {
throw new Error(`Cannot get metadata for ${this.classFullName}`);
}
}
/** @internal */
getMetaDataSync() {
if (this._metadata) {
return this._metadata;
}
const ecClass = this.iModel.schemaContext.getSchemaItemSync(this.schemaItemKey, ecschema_metadata_1.ECClass);
if (ecschema_metadata_1.EntityClass.isEntityClass(ecClass) || ecschema_metadata_1.RelationshipClass.isRelationshipClass(ecClass)) {
this._metadata = ecClass;
return this._metadata;
}
else {
throw new Error(`Cannot get metadata for ${this.classFullName}`);
}
}
/** @internal */
static get protectedOperations() { return []; }
/** return whether this Entity class is a subclass of another Entity class
* @note the subclass-ness is checked according to JavaScript inheritance, to check the underlying raw EC class's
* inheritance, you can use [ECClass.is]($ecschema-metadata)
* @note this should have a type of `is<T extends typeof Entity>(otherClass: T): this is T` but can't because of
* typescript's restriction on the `this` type in static methods
*/
static is(otherClass) {
// inline of @itwin/core-bentley's isSubclassOf due to protected constructor.
return this === otherClass || this.prototype instanceof otherClass;
}
/** whether this JavaScript class was generated for this ECClass because there was no registered custom implementation
* ClassRegistry overrides this when generating a class
* @internal
*/
static get isGeneratedClass() { return false; }
/** Get the set of this entity's *entity references*, [EntityReferenceSet]($backend). An *entity reference* is any id
* stored on the entity, in its EC properties or json fields.
* This is important for cloning operations but can be useful in other situations as well.
* @see this.collectReferenceIds
* @beta
*/
getReferenceIds() {
const referenceIds = new core_common_1.EntityReferenceSet();
this.collectReferenceIds(referenceIds);
return referenceIds;
}
/** Collect the Ids of this entity's *references* at this level of the class hierarchy.
* A *reference* is any entity referenced by this entity's EC Data, including json fields.
* This is important for cloning operations but can be useful in other situations as well.
* @param _referenceIds The Id64Set to populate with reference Ids.
* @note This should be overridden (with `super` called) at each level the class hierarchy that introduces references.
* @see getReferenceIds
* @beta
*/
collectReferenceIds(_referenceIds) {
return; // no references by default
}
}
exports.Entity = Entity;
//# sourceMappingURL=Entity.js.map