UNPKG

@itwin/ecschema-metadata

Version:

ECObjects core concepts in typescript

371 lines • 18 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { SchemaMatchType } from "./ECObjects"; import { ECSchemaError, ECSchemaStatus } from "./Exception"; import { SchemaItem } from "./Metadata/SchemaItem"; import { SchemaKey } from "./SchemaKey"; /** * @internal */ class SchemaMap extends Array { } /** * @internal */ export class SchemaCache { _schema; constructor() { this._schema = new SchemaMap(); } get count() { return this._schema.length; } loadedSchemaExists(schemaKey) { return undefined !== this._schema.find((entry) => entry.schemaInfo.schemaKey.matches(schemaKey, SchemaMatchType.Latest) && !entry.schemaPromise); } schemaPromiseExists(schemaKey) { return undefined !== this._schema.find((entry) => entry.schemaInfo.schemaKey.matches(schemaKey, SchemaMatchType.Latest) && undefined !== entry.schemaPromise); } findEntry(schemaKey, matchType) { return this._schema.find((entry) => entry.schemaInfo.schemaKey.matches(schemaKey, matchType)); } removeSchemaPromise(schemaKey) { const entry = this.findEntry(schemaKey, SchemaMatchType.Latest); if (entry) entry.schemaPromise = undefined; } removeEntry(schemaKey) { this._schema = this._schema.filter((entry) => !entry.schemaInfo.schemaKey.matches(schemaKey)); } /** * Returns true if the schema exists in either the schema cache or the promise cache. SchemaMatchType.Latest used. * @param schemaKey The key to search for. */ schemaExists(schemaKey) { return this.loadedSchemaExists(schemaKey) || this.schemaPromiseExists(schemaKey); } /** * Adds a promise to load the schema to the cache. Does not allow for duplicate schemas in the cache of schemas or cache of promises, checks using SchemaMatchType.Latest. * When the promise completes the schema will be added to the schema cache and the promise will be removed from the promise cache * @param schemaInfo An object with the schema key for the schema being loaded and it's references * @param schema The partially loaded schema that the promise will fulfill * @param schemaPromise The schema promise to add to the cache. */ async addSchemaPromise(schemaInfo, schema, schemaPromise) { if (this.schemaExists(schemaInfo.schemaKey)) throw new ECSchemaError(ECSchemaStatus.DuplicateSchema, `The schema, ${schemaInfo.schemaKey.toString()}, already exists within this cache.`); this._schema.push({ schemaInfo, schema, schemaPromise }); // This promise is cached and will be awaited when the user requests the full schema. // If the promise competes successfully before the user requests the schema it will be removed from the cache // If it fails it will remain in the cache until the user awaits it and handles the error // eslint-disable-next-line @typescript-eslint/no-floating-promises schemaPromise.then(() => { this.removeSchemaPromise(schemaInfo.schemaKey); }); } /** * Adds a schema to the cache. Does not allow for duplicate schemas, checks using SchemaMatchType.Latest. * @param schema The schema to add to the cache. */ async addSchema(schema) { if (this.schemaExists(schema.schemaKey)) throw new ECSchemaError(ECSchemaStatus.DuplicateSchema, `The schema, ${schema.schemaKey.toString()}, already exists within this cache.`); this._schema.push({ schemaInfo: schema, schema }); } /** * Adds a schema to the cache. Does not allow for duplicate schemas, checks using SchemaMatchType.Latest. * @param schema The schema to add to the cache. */ addSchemaSync(schema) { if (this.schemaExists(schema.schemaKey)) throw new ECSchemaError(ECSchemaStatus.DuplicateSchema, `The schema, ${schema.schemaKey.toString()}, already exists within this cache.`); this._schema.push({ schemaInfo: schema, schema }); } /** * Gets the schema which matches the provided SchemaKey. * @param schemaKey The SchemaKey describing the schema to get from the cache. * @param matchType The match type to use when locating the schema */ async getSchema(schemaKey, matchType = SchemaMatchType.Latest) { if (this.count === 0) return undefined; const entry = this.findEntry(schemaKey, matchType); if (!entry) return undefined; if (entry.schemaPromise) { try { const schema = await entry.schemaPromise; return schema; } catch (e) { this.removeEntry(schemaKey); throw e; } } return entry.schema; } /** * Gets the schema info which matches the provided SchemaKey. The schema info may be returned before the schema is fully loaded. * @param schemaKey The SchemaKey describing the schema to get from the cache. * @param matchType The match type to use when locating the schema */ async getSchemaInfo(schemaKey, matchType = SchemaMatchType.Latest) { if (this.count === 0) return undefined; const entry = this.findEntry(schemaKey, matchType); if (entry) return entry.schemaInfo; return undefined; } /** * Gets the schema which matches the provided SchemaKey. If the schema is partially loaded an exception will be thrown. * @param schemaKey The SchemaKey describing the schema to get from the cache. * @param matchType The match type to use when locating the schema */ getSchemaSync(schemaKey, matchType = SchemaMatchType.Latest) { if (this.count === 0) return undefined; const entry = this.findEntry(schemaKey, matchType); if (entry) { if (entry.schemaPromise) { throw new ECSchemaError(ECSchemaStatus.UnableToLoadSchema, `The Schema ${schemaKey.toString()} is partially loaded so cannot be loaded synchronously.`); } return entry.schema; } return undefined; } /** * Generator function that can iterate through each schema in _schema SchemaMap and items for each Schema. * Does not include schema items from schemas that are not completely loaded yet. */ *getSchemaItems() { for (const entry of this._schema) { for (const schemaItem of entry.schema.getItems()) { yield schemaItem; } } } /** * Gets all the schemas from the schema cache. * Does not include schemas from schemas that are not completely loaded yet. * @returns An array of Schema objects. */ getAllSchemas() { return this._schema.map((entry) => entry.schema); } } /** * The SchemaContext, context object is used to facilitate schema and schema item location. * * The context controls the lifetime of each schema that it knows about. It has to be explicitly removed from the context to delete a schema object. * * The context is made up of a group of Schema Locators. * @public @preview */ export class SchemaContext { _locaters; _knownSchemas; _fallbackLocaterDefined; constructor() { this._locaters = []; this._knownSchemas = new SchemaCache(); this._locaters.push(this._knownSchemas); this._fallbackLocaterDefined = false; } get locaters() { return this._locaters; } /** * Adds a locater to the context. * * If no locaters are defined or a fallback locater is not defined, the new locater is added at the end of the locaters array. * If a fallback locater is already defined, the new locater is inserted before the fallback locater. * * @param locater - The locater to be added. */ addLocater(locater) { const insertIndex = (this._locaters.length === 0 || !this._fallbackLocaterDefined) ? this._locaters.length : this._locaters.length - 1; this._locaters.splice(insertIndex, 0, locater); } /** * Adds a fallback locater to the context. * * If a fallback locater is already defined, it replaces the existing one. * Otherwise, it adds the new locater to the end of the locaters array and marks the fallback locater as defined. * * @param locater - The locater to be added as a fallback. */ addFallbackLocater(locater) { if (this._fallbackLocaterDefined) { this._locaters[this._locaters.length - 1] = locater; } else { this._locaters.push(locater); this._fallbackLocaterDefined = true; } } /** * Adds the schema to this context. Use addSchemaPromise instead when asynchronously loading schemas. * @param schema The schema to add to this context */ async addSchema(schema) { this.addSchemaSync(schema); } /** * Adds the schema to this context * @param schema The schema to add to this context */ addSchemaSync(schema) { this._knownSchemas.addSchemaSync(schema); } /** * Adds the given SchemaItem to the the SchemaContext by locating the schema, with the best match of SchemaMatchType.Exact, and * @param schemaItem The SchemaItem to add * @deprecated in 4.0 - will not be removed until after 2026-06-13. Use ecschema-editing package */ async addSchemaItem(schemaItem) { const schema = await this.getSchema(schemaItem.key.schemaKey, SchemaMatchType.Exact); if (!schema) throw new ECSchemaError(ECSchemaStatus.UnableToLocateSchema, `Unable to add the schema item ${schemaItem.name} to the schema ${schemaItem.key.schemaKey.toString()} because the schema could not be located.`); schema.addItem(schemaItem); } /** * Returns true if the schema is already in the context. SchemaMatchType.Latest is used to find a match. * @param schemaKey */ schemaExists(schemaKey) { return this._knownSchemas.schemaExists(schemaKey); } /** * Adds a promise to load the schema to the cache. Does not allow for duplicate schemas in the cache of schemas or cache of promises, checks using SchemaMatchType.Latest. * When the promise completes the schema will be added to the schema cache and the promise will be removed from the promise cache. * Use this method over addSchema when asynchronously loading schemas * @param schemaInfo An object with the schema key for the schema being loaded and it's references * @param schema The partially loaded schema that the promise will fulfill * @param schemaPromise The schema promise to add to the cache. */ async addSchemaPromise(schemaInfo, schema, schemaPromise) { return this._knownSchemas.addSchemaPromise(schemaInfo, schema, schemaPromise); } /** Attempts to obtain a schema from this context that matches the specified criteria. * @param schemaKey Identifies the schema to obtain. * @param matchType Criteria by which to identify potentially matching schemas. * @returns the schema matching the input criteria, or `undefined` if no such schema could be located. */ async getSchema(schemaKey, matchType = SchemaMatchType.Latest) { // the first locater is _knownSchemas, so we don't have to check the cache explicitly here for (const locater of this._locaters) { const schema = await locater.getSchema(schemaKey, matchType, this); if (undefined !== schema) return schema; } return undefined; } /** * Gets the schema info which matches the provided SchemaKey. The schema info may be returned before the schema is fully loaded. * The fully loaded schema can be gotten later from the context using [[getSchema]]. * @param schemaKey The SchemaKey describing the schema to get from the cache. * @param matchType The match type to use when locating the schema */ async getSchemaInfo(schemaKey, matchType) { for (const locater of this._locaters) { const schemaInfo = await locater.getSchemaInfo(schemaKey, matchType, this); if (undefined !== schemaInfo) return schemaInfo; } return undefined; } /** Attempts to obtain a schema from this context that matches the specified criteria. * Will return undefined if the schema is partially loaded. Use [[getSchema]] to await until the schema is completely loaded. * @param schemaKey Identifies the schema to obtain. * @param matchType Criteria by which to identify potentially matching schemas. * @returns the schema matching the input criteria, or `undefined` if no such schema could be located. */ getSchemaSync(schemaKey, matchType = SchemaMatchType.Latest) { // the first locater is _knownSchemas, so we don't have to check the cache explicitly here for (const locater of this._locaters) { const schema = locater.getSchemaSync(schemaKey, matchType, this); if (undefined !== schema) return schema; } return undefined; } /** * Attempts to get a Schema from the context's cache. * Will await a partially loaded schema then return when it is completely loaded. * @param schemaKey The SchemaKey to identify the Schema. * @param matchType The SchemaMatch type to use. Default is SchemaMatchType.Latest. * @internal */ async getCachedSchema(schemaKey, matchType = SchemaMatchType.Latest) { return this._knownSchemas.getSchema(schemaKey, matchType); } /** * Attempts to get a Schema from the context's cache. * Will return undefined if the cached schema is partially loaded. Use [[getCachedSchema]] to await until the schema is completely loaded. * @param schemaKey The SchemaKey to identify the Schema. * @param matchType The SchemaMatch type to use. Default is SchemaMatchType.Latest. * @internal */ getCachedSchemaSync(schemaKey, matchType = SchemaMatchType.Latest) { return this._knownSchemas.getSchemaSync(schemaKey, matchType); } async getSchemaItem(schemaNameOrKey, itemNameOrCtor, itemConstructor) { let schemaKey; if (typeof schemaNameOrKey === "string") { const [schemaName, itemName] = SchemaItem.parseFullName(schemaNameOrKey); schemaKey = (!schemaName) ? new SchemaKey(itemName) : new SchemaKey(schemaName); } else { schemaKey = schemaNameOrKey.schemaKey; } const schema = await this.getSchema(schemaKey, SchemaMatchType.Latest); if (!schema) return undefined; if (typeof itemNameOrCtor === "string") // Separate schema and item name arguments with/without an itemConstructor return itemConstructor ? schema.getItem(itemNameOrCtor, itemConstructor) : schema.getItem(itemNameOrCtor); if (typeof schemaNameOrKey === "string") // Single schema item full name argument with/without an itemConstructor return itemNameOrCtor ? schema.lookupItem(schemaNameOrKey, itemNameOrCtor) : schema.lookupItem(schemaNameOrKey); // Schema Item Key with/without an itemConstructor return itemNameOrCtor ? schema.getItem(schemaNameOrKey.name, itemNameOrCtor) : schema.getItem(schemaNameOrKey.name); } getSchemaItemSync(schemaNameOrKey, itemNameOrCtor, itemConstructor) { let schemaKey; if (typeof schemaNameOrKey === "string") { const [schemaName, itemName] = SchemaItem.parseFullName(schemaNameOrKey); schemaKey = (!schemaName) ? new SchemaKey(itemName) : new SchemaKey(schemaName); } else { schemaKey = schemaNameOrKey.schemaKey; } const schema = this.getSchemaSync(schemaKey, SchemaMatchType.Latest); if (!schema) return undefined; // Separate schema and item name arguments with/without an itemConstructor if (typeof itemNameOrCtor === "string") { return itemConstructor ? schema.getItemSync(itemNameOrCtor, itemConstructor) : schema.getItemSync(itemNameOrCtor); } if (typeof schemaNameOrKey === "string") // Single schema item full name argument with/without an itemConstructor return itemNameOrCtor ? schema.lookupItemSync(schemaNameOrKey, itemNameOrCtor) : schema.lookupItemSync(schemaNameOrKey); // Schema Item Key with/without an itemConstructor return itemNameOrCtor ? schema.getItemSync(schemaNameOrKey.name, itemNameOrCtor) : schema.getItemSync(schemaNameOrKey.name); } /** * Iterates through the items of each schema known to the context. This includes schemas added to the * context using [[SchemaContext.addSchema]]. This does not include schemas that * can be located by an ISchemaLocater instance added to the context. * Does not include schema items from schemas that are not completely loaded yet. */ getSchemaItems() { return this._knownSchemas.getSchemaItems(); } /** * Gets all the Schemas known by the context. This includes schemas added to the * context using [[SchemaContext.addSchema]]. This does not include schemas that * can be located by an ISchemaLocater instance added to the context. Does not * include schemas that are partially loaded. * @returns An array of Schema objects. */ getKnownSchemas() { return this._knownSchemas.getAllSchemas(); } } //# sourceMappingURL=Context.js.map