@itwin/ecschema-metadata
Version:
ECObjects core concepts in typescript
222 lines • 11.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.IncrementalSchemaLocater = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
const Constants_1 = require("../Constants");
const SchemaGraphUtil_1 = require("../Deserialization/SchemaGraphUtil");
const ECObjects_1 = require("../ECObjects");
const Exception_1 = require("../Exception");
const Schema_1 = require("../Metadata/Schema");
const SchemaKey_1 = require("../SchemaKey");
const SchemaLoadingController_1 = require("../utils/SchemaLoadingController");
const IncrementalSchemaReader_1 = require("./IncrementalSchemaReader");
/**
* A [[ISchemaLocater]] implementation for locating and retrieving EC [[Schema]]
* objects incrementally instead of the full schema and it's references at once. This is useful for large schemas that
* take a long time to load, but clients need a rough skeleton of the schema as fast as possible.
*
* The IncrementalSchemaLocater is a locater around the [[IncrementalSchemaLocater]] to be used in a
* [[SchemaContext]].
* @internal
*/
class IncrementalSchemaLocater {
_options;
_schemaInfoCache;
/**
* Initializes a new instance of the IncrementalSchemaLocater class.
* @param options The [[SchemaLocaterOptions]] that control the loading of the schema.
*/
constructor(options) {
this._options = options || {};
this._schemaInfoCache = new SchemaInfoCache(async (context) => {
return this.loadSchemaInfos(context);
});
}
/** Gets the options how the schema locater load the schemas. */
get options() {
return this._options;
}
/**
* Gets the [[SchemaInfo]] which matches the provided SchemaKey. The SchemaInfo may be returned
* before the schema is fully loaded. May return the entire Schema so long as it is completely loaded as it satisfies
* the SchemaInfo interface.
* @param schemaKey The [[SchemaKey]] to look up.
* @param matchType The [[SchemaMatchType]] to use against candidate schemas.
* @param context The [[SchemaContext]] for loading schema references.
*/
async getSchemaInfo(schemaKey, matchType, context) {
return this._schemaInfoCache.lookup(schemaKey, matchType, context);
}
/**
* Attempts to get a [[Schema]] from the locater. Yields undefined if no matching schema is found.
* For schemas that may have references, construct and call through a SchemaContext instead.
* @param schemaKey The [[SchemaKey]] to look up.
* @param matchType The [[SchemaMatchType]] to use against candidate schemas.
* @param context The [[SchemaContext]] for loading schema references.
*/
async getSchema(schemaKey, matchType, context) {
const schemaInfo = await this.getSchemaInfo(schemaKey, matchType, context);
return schemaInfo
? this.loadSchema(schemaInfo, context)
: undefined;
}
/**
* Attempts to get a [[Schema]] from the locater. Yields undefined if no matching schema is found.
* For schemas that may have references, construct and call through a SchemaContext instead.
* NOT IMPLEMENTED IN THIS LOCATER - ALWAYS RETURNS UNDEFINED.
* @param schemaKey The [[SchemaKey]] to look up.
* @param matchType The [[SchemaMatchType]] to use against candidate schemas.
* @param context The [[SchemaContext]] for loading schema references.
* @returns Incremental schema loading does not work synchronously, this will always return undefined.
*/
getSchemaSync(_schemaKey, _matchType, _context) {
return undefined;
}
/**
* Start loading the schema for the given schema info incrementally. The schema is returned
* as soon as the schema stub is loaded while the schema fully resolves in the background. It should
* only be called by the IncrementalSchemaLocater if a schema has not been loaded or started to
* load yet.
* @param schemaInfo The schema info of the schema to load.
* @param schemaContext The schema context to load the schema into.
*/
async loadSchema(schemaInfo, schemaContext) {
// If the meta schema is an earlier version than 4.0.3, we can't use the ECSql query interface to get the schema
// information required to load the schema entirely. In this case, we fallback to use the ECSchema RPC interface
// to fetch the whole schema json.
if (!await this.supportPartialSchemaLoading(schemaContext)) {
const schemaJson = await this.getSchemaJson(schemaInfo.schemaKey, schemaContext);
if (schemaJson === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLocateSchema, `Could not locate the schema, ${schemaInfo.schemaKey.name}.${schemaInfo.schemaKey.version.toString()}`);
return Schema_1.Schema.fromJson(schemaJson, schemaContext);
}
// Fetches the schema partials for the given schema key. The first item in the array is the
// actual schema props of the schema to load, the following items are schema props of referenced
// schema items that are needed to resolve the schema properly.
const schemaPartials = await this.getSchemaPartials(schemaInfo.schemaKey, schemaContext);
if (schemaPartials === undefined)
throw new Exception_1.ECSchemaError(Exception_1.ECSchemaStatus.UnableToLocateSchema, `Could not locate the schema, ${schemaInfo.schemaKey.name}.${schemaInfo.schemaKey.version.toString()}`);
// Sort the partials in dependency order to ensure referenced schemas exist in the schema context
// when they get requested. Otherwise the context would call this method again and for referenced
// schemas, we would not want to request the whole schema props.
const sortedPartials = await this.sortSchemaPartials(schemaPartials, schemaContext);
for (const schemaProps of sortedPartials) {
await this.startLoadingPartialSchema(schemaProps, schemaContext);
}
const schema = await schemaContext.getCachedSchema(schemaInfo.schemaKey);
if (!schema)
throw new Error(`Schema ${schemaInfo.schemaKey.name} could not be found.`);
return schema;
}
/**
* Creates a SchemaProps object by loading the Schema information from the given SchemaContext.
* @param schemaKey The SchemaKey of the Schema whose props are to be retrieved.
* @param schemaContext The SchemaContext holding the Schema.
* @returns The SchemaProps object.
*/
async createSchemaProps(schemaKey, schemaContext) {
const schemaInfo = await schemaContext.getSchemaInfo(schemaKey, ECObjects_1.SchemaMatchType.Latest);
if (!schemaInfo)
throw new Error(`Schema ${schemaKey.name} could not be found.`);
const schemaReferences = [];
const schemaProps = {
$schema: Constants_1.ECSchemaNamespaceUris.SCHEMAURL3_2_JSON,
name: schemaKey.name,
alias: schemaInfo.alias,
version: schemaInfo.schemaKey.version.toString(),
description: schemaInfo.description,
label: schemaInfo.label,
references: schemaReferences,
items: {}
};
schemaInfo.references.forEach((ref) => {
schemaReferences.push({ name: ref.schemaKey.name, version: ref.schemaKey.version.toString() });
});
return schemaProps;
}
async startLoadingPartialSchema(schemaProps, schemaContext) {
if (schemaContext.schemaExists(SchemaKey_1.SchemaKey.parseString(`${schemaProps.name}.${schemaProps.version}`))) {
return;
}
const controller = new SchemaLoadingController_1.SchemaLoadingController();
const schemaReader = new IncrementalSchemaReader_1.IncrementalSchemaReader(schemaContext, true);
const schema = new Schema_1.Schema(schemaContext);
schema.setLoadingController(controller);
await schemaReader.readSchema(schema, schemaProps);
if (!this._options.loadPartialSchemaOnly)
controller.start(this.startLoadingFullSchema(schema));
}
async loadFullSchema(schema) {
const fullSchemaProps = await this.getSchemaJson(schema.schemaKey, schema.context);
const reader = new IncrementalSchemaReader_1.IncrementalSchemaReader(schema.context, false);
await reader.readSchema(schema, fullSchemaProps, false);
}
async startLoadingFullSchema(schema) {
if (!schema.loadingController)
return;
// If the schema is already resolved, return it directly.
if (schema.loadingController.isComplete || schema.loadingController.inProgress) {
return;
}
// Since the schema relies on it's references, they get triggered to be resolved
// first by recursively calling this method. After all references has been resolved
// the schema itself gets resolved.
await Promise.all(schema.references.map(async (referenceSchema) => {
if (referenceSchema.loadingController && referenceSchema.loadingController.inProgress)
return referenceSchema.loadingController.wait();
else
return this.startLoadingFullSchema(referenceSchema);
}));
return this.loadFullSchema(schema);
}
async sortSchemaPartials(schemaPartials, schemaContext) {
const schemaInfos = [];
for (const schemaProps of schemaPartials) {
const schemaKey = SchemaKey_1.SchemaKey.parseString(`${schemaProps.name}.${schemaProps.version}`);
const schemaInfo = await schemaContext.getSchemaInfo(schemaKey, ECObjects_1.SchemaMatchType.Latest);
if (!schemaInfo)
throw new Error(`Schema ${schemaKey.name} could not be found.`);
schemaInfos.push({ ...schemaInfo, props: schemaProps });
}
const orderedSchemaInfos = SchemaGraphUtil_1.SchemaGraphUtil.buildDependencyOrderedSchemaInfoList(schemaInfos);
return orderedSchemaInfos.map((schemaInfo) => schemaInfo.props);
}
}
exports.IncrementalSchemaLocater = IncrementalSchemaLocater;
/**
* Helper class to manage schema infos for a schema context.
*/
class SchemaInfoCache {
_schemaInfoCache;
_schemaInfoLoader;
constructor(schemaInfoLoader) {
this._schemaInfoCache = new WeakMap();
this._schemaInfoLoader = schemaInfoLoader;
}
async getSchemasByContext(context) {
if (!this._schemaInfoCache.has(context)) {
const schemaInfos = await this._schemaInfoLoader(context);
this._schemaInfoCache.set(context, Array.from(schemaInfos));
}
return this._schemaInfoCache.get(context);
}
async lookup(schemaKey, matchType, context) {
const contextSchemaInfos = await this.getSchemasByContext(context);
return contextSchemaInfos
? contextSchemaInfos.find((schemaInfo) => schemaInfo.schemaKey.matches(schemaKey, matchType))
: undefined;
}
remove(schemaKey, context) {
const contextSchemaInfos = this._schemaInfoCache.get(context);
if (!contextSchemaInfos)
return;
const index = contextSchemaInfos.findIndex((schemaInfo) => schemaInfo.schemaKey.name === schemaKey.name);
if (index !== -1) {
contextSchemaInfos.splice(index, 1);
}
}
}
//# sourceMappingURL=IncrementalSchemaLocater.js.map