breeze-client
Version:
Breeze data management for JavaScript clients
1,022 lines (1,009 loc) • 336 kB
JavaScript
import { core } from './core';
import { config } from './config';
import { BreezeEvent } from './event';
import { assertParam, assertConfig, Param } from './assert-param';
import { DataType } from './data-type';
import { EntityAspect, ComplexAspect } from './entity-aspect';
import { EntityKey } from './entity-key';
import { Validator } from './validate';
import { BreezeEnum } from './enum';
import { DataService } from './data-service';
import { NamingConvention } from './naming-convention';
import { CsdlMetadataParser } from './csdl-metadata-parser'; // TODO isolate this later;
import { LocalQueryComparisonOptions } from './local-query-comparison-options';
import { defaultPropertyInterceptor } from './default-property-interceptor';
/**
An instance of the MetadataStore contains all of the metadata about a collection of [[EntityType]]'s.
MetadataStores may be shared across [[EntityManager]]'s. If an EntityManager is created without an
explicit MetadataStore, the MetadataStore from the MetadataStore.defaultInstance property will be used.
@dynamic
**/
export class MetadataStore {
/** @hidden @internal */
static __id = 0;
/** @hidden @internal */
static ANONTYPE_PREFIX = "_IB_";
/** The version of any MetadataStores created by this class */
static metadataVersion = '1.0.5';
name;
dataServices;
/** The [[NamingConvention]] associated with this MetadataStore. __Read Only__ */
namingConvention;
/** The [[LocalQueryComparisonOptions]] associated with this MetadataStore. __Read Only__ */
localQueryComparisonOptions;
serializerFn;
/**
An [[BreezeEvent]] that fires after a MetadataStore has completed fetching metadata from a remote service.
@eventArgs -
- metadataStore - The MetadataStore into which the metadata was fetched.
- dataService - The [[DataService]] that metadata was fetched from.
- rawMetadata - {Object} The raw metadata returned from the service. (It will have already been processed by this point).
> let ms = myEntityManager.metadataStore;
> ms.metadataFetched.subscribe(function(args) {
> let metadataStore = args.metadataStore;
> let dataService = args.dataService;
> });
@event
**/
metadataFetched;
/** @hidden @internal */
_resourceEntityTypeMap;
/** @hidden @internal */
_entityTypeResourceMap;
/** @hidden @internal key is qualified structuraltype name - value is structuralType. ( structural = entityType or complexType). */
_structuralTypeMap;
/** @hidden @internal key is shortName, value is qualified name - does not need to be serialized. **/
_shortNameMap;
/** @hidden @internal key is either short or qual type name - value is ctor; **/
_ctorRegistry;
/** @hidden @internal key is entityTypeName; value is array of nav props **/
_incompleteTypeMap;
/** @hidden @internal **/
_incompleteComplexTypeMap;
/** @hidden @internal { json: any, stype: StructuralType }[] | { entityType: EntityType, csdlEntityType: any }[] **/
_deferredTypes;
/** @hidden @internal **/
_id;
/**
Constructs a new MetadataStore.
> let ms = new MetadataStore();
The store can then be associated with an EntityManager
> let entityManager = new EntityManager( {
> serviceName: "breeze/NorthwindIBModel",
> metadataStore: ms
> });
or for an existing EntityManager
> // Assume em1 is an existing EntityManager
> em1.setProperties( { metadataStore: ms });
@param config - Configuration settings .
- namingConvention - (default=NamingConvention.defaultInstance) NamingConvention to be used in mapping property names
between client and server. Uses the NamingConvention.defaultInstance if not specified.
- localQueryComparisonOptions - (default=LocalQueryComparisonOptions.defaultInstance) The LocalQueryComparisonOptions to be
used when performing "local queries" in order to match the semantics of queries against a remote service.
- serializerFn - A function that is used to mediate the serialization of instances of this type.
**/
constructor(config) {
config = config || {};
assertConfig(config)
.whereParam("namingConvention").isOptional().isInstanceOf(NamingConvention).withDefault(NamingConvention.defaultInstance)
.whereParam("localQueryComparisonOptions").isOptional().isInstanceOf(LocalQueryComparisonOptions).withDefault(LocalQueryComparisonOptions.defaultInstance)
.whereParam("serializerFn").isOptional().isFunction()
.applyAll(this);
this.dataServices = []; // array of dataServices;
this._resourceEntityTypeMap = {}; // key is resource name - value is qualified entityType name
this._structuralTypeMap = {}; // key is qualified structuraltype name - value is structuralType. ( structural = entityType or complexType).
this._shortNameMap = {}; // key is shortName, value is qualified name - does not need to be serialized.
this._ctorRegistry = {}; // key is either short or qual type name - value is ctor;
this._incompleteTypeMap = {}; // key is entityTypeName; value is array of nav props
this._incompleteComplexTypeMap = {}; // key is complexTypeName; value is array of complexType props
this._id = MetadataStore.__id++;
this.metadataFetched = new BreezeEvent("metadataFetched", this);
}
// needs to be made avail to dataService.xxx files
static normalizeTypeName = core.memoize(function (rawTypeName) {
return rawTypeName && MetadataStore.parseTypeName(rawTypeName).typeName;
});
// for debugging use the line below instead.
//ctor.normalizeTypeName = function (rawTypeName) { return parseTypeName(rawTypeName).typeName; };
/**
General purpose property set method
> // assume em1 is an EntityManager containing a number of existing entities.
> em1.metadataStore.setProperties( {
> version: "6.1.3",
> serializerFn: function(prop, value) {
> return (prop.isUnmapped) ? undefined : value;
> }
> )};
@param config - An object containing the selected properties and values to set.
**/
setProperties(config) {
assertConfig(config)
.whereParam("name").isString().isOptional()
.whereParam("serializerFn").isFunction().isOptional()
.applyAll(this);
}
/**
Adds a DataService to this MetadataStore. If a DataService with the same serviceName is already
in the MetadataStore an exception will be thrown.
@param dataService - The [[DataService]] to add
@param shouldOverwrite - (default=false) Permit overwrite of existing DataService rather than throw exception
**/
addDataService(dataService, shouldOverwrite) {
assertParam(dataService, "dataService").isInstanceOf(DataService).check();
assertParam(shouldOverwrite, "shouldOverwrite").isBoolean().isOptional().check();
let ix = this._getDataServiceIndex(dataService.serviceName);
if (ix >= 0) {
if (!!shouldOverwrite) {
this.dataServices[ix] = dataService;
}
else {
throw new Error("A dataService with this name '" + dataService.serviceName + "' already exists in this MetadataStore");
}
}
else {
this.dataServices.push(dataService);
}
}
/** @hidden @internal */
_getDataServiceIndex(serviceName) {
return core.arrayIndexOf(this.dataServices, function (ds) {
return ds.serviceName === serviceName;
});
}
/**
Adds an EntityType to this MetadataStore. No additional properties may be added to the EntityType after its has
been added to the MetadataStore.
@param structuralType - The EntityType or ComplexType to add
**/
addEntityType(stype) {
let structuralType;
if (stype instanceof EntityType || stype instanceof ComplexType) {
structuralType = stype;
}
else {
structuralType = stype.isComplexType ? new ComplexType(stype) : new EntityType(stype);
}
// if (!structuralType.isComplexType) { // same as below but isn't a 'type guard'
if (structuralType instanceof EntityType) {
if (structuralType.baseTypeName && !structuralType.baseEntityType) {
let baseEntityType = this._getStructuralType(structuralType.baseTypeName, true);
// safe cast because we know that baseEntityType must be an EntityType if the structuralType is an EntityType
structuralType._updateFromBase(baseEntityType);
}
if (structuralType.keyProperties.length === 0 && !structuralType.isAbstract) {
throw new Error("Unable to add " + structuralType.name +
" to this MetadataStore. An EntityType must have at least one property designated as a key property - See the 'DataProperty.isPartOfKey' property.");
}
}
structuralType.metadataStore = this;
// don't register anon types
if (!structuralType.isAnonymous) {
if (this._structuralTypeMap[structuralType.name]) {
throw new Error("Type " + structuralType.name + " already exists in this MetadataStore.");
}
this._structuralTypeMap[structuralType.name] = structuralType;
this._shortNameMap[structuralType.shortName] = structuralType.name;
}
structuralType.getProperties().forEach(p => {
structuralType._updateNames(p);
if (!p.isUnmapped) {
structuralType._mappedPropertiesCount++;
}
});
structuralType._updateCps();
// 'isEntityType' is a type guard
if (structuralType instanceof EntityType) {
structuralType._updateNps();
// give the type it's base's resource name if it doesn't have its own.
let defResourceName = structuralType.defaultResourceName || (structuralType.baseEntityType && structuralType.baseEntityType.defaultResourceName);
if (defResourceName && !this.getEntityTypeNameForResourceName(defResourceName)) {
this.setEntityTypeForResourceName(defResourceName, structuralType.name);
}
structuralType.defaultResourceName = defResourceName;
// check if this structural type's name, short version or qualified version has a registered ctor.
structuralType.getEntityCtor();
}
}
/**
Exports this MetadataStore to a serialized string appropriate for local storage. This operation is also called
internally when exporting an EntityManager.
> // assume ms is a previously created MetadataStore
> let metadataAsString = ms.exportMetadata();
> window.localStorage.setItem("metadata", metadataAsString);
> // and later, usually in a different session imported
> let metadataFromStorage = window.localStorage.getItem("metadata");
> let newMetadataStore = new MetadataStore();
> newMetadataStore.importMetadata(metadataFromStorage);
@return A serialized version of this MetadataStore that may be stored locally and later restored.
**/
exportMetadata() {
let result = JSON.stringify({
"metadataVersion": MetadataStore.metadataVersion,
"name": this.name,
"namingConvention": this.namingConvention.name,
"localQueryComparisonOptions": this.localQueryComparisonOptions.name,
"dataServices": this.dataServices,
"structuralTypes": core.objectMap(this._structuralTypeMap),
"resourceEntityTypeMap": this._resourceEntityTypeMap
}, null, config.stringifyPad);
return result;
}
/**
Imports a previously exported serialized MetadataStore into this MetadataStore.
> // assume ms is a previously created MetadataStore
> let metadataAsString = ms.exportMetadata();
> window.localStorage.setItem("metadata", metadataAsString);
> // and later, usually in a different session
> let metadataFromStorage = window.localStorage.getItem("metadata");
> let newMetadataStore = new MetadataStore();
> newMetadataStore.importMetadata(metadataFromStorage);
@param exportedMetadata - A previously exported MetadataStore.
@param allowMerge - Allows custom metadata to be merged into existing metadata types.
@return This MetadataStore.
@chainable
**/
importMetadata(exportedMetadata, allowMerge = false) {
assertParam(allowMerge, "allowMerge").isOptional().isBoolean().check();
this._deferredTypes = {};
// insure that we don't mutate incoming exportedMetadata ( if its an object)
let metadataAsString = (typeof (exportedMetadata) === "string") ? exportedMetadata : JSON.stringify(exportedMetadata);
const metadataJson = JSON.parse(metadataAsString);
if (metadataJson.schema) {
return CsdlMetadataParser.parse(this, metadataJson.schema, metadataJson.altMetadata);
}
let json = metadataJson;
if (json.metadataVersion && json.metadataVersion !== MetadataStore.metadataVersion) {
let msg = core.formatString("Cannot import metadata with a different 'metadataVersion' (%1) than the current 'MetadataStore.metadataVersion' (%2) ", json.metadataVersion, MetadataStore.metadataVersion);
throw new Error(msg);
}
let ncName = json.namingConvention;
let lqcoName = json.localQueryComparisonOptions;
if (this.isEmpty()) {
this.namingConvention = config._fetchObject(NamingConvention, ncName) || this.namingConvention;
this.localQueryComparisonOptions = config._fetchObject(LocalQueryComparisonOptions, lqcoName) || this.localQueryComparisonOptions;
}
else {
if (ncName && this.namingConvention.name !== ncName) {
throw new Error("Cannot import metadata with a different 'namingConvention' from the current MetadataStore");
}
if (lqcoName && this.localQueryComparisonOptions.name !== lqcoName) {
throw new Error("Cannot import metadata with different 'localQueryComparisonOptions' from the current MetadataStore");
}
}
//noinspection JSHint
json.dataServices && json.dataServices.forEach((ds) => {
let realDs = DataService.fromJSON(ds);
this.addDataService(realDs, true);
});
json.structuralTypes && json.structuralTypes.forEach((stype) => {
structuralTypeFromJson(this, stype, allowMerge);
});
core.extend(this._resourceEntityTypeMap, json.resourceEntityTypeMap);
core.extend(this._incompleteTypeMap, json.incompleteTypeMap);
return this;
}
/**
Creates a new MetadataStore from a previously exported serialized MetadataStore
> // assume ms is a previously created MetadataStore
> let metadataAsString = ms.exportMetadata();
> window.localStorage.setItem("metadata", metadataAsString);
> // and later, usually in a different session
> let metadataFromStorage = window.localStorage.getItem("metadata");
> let newMetadataStore = MetadataStore.importMetadata(metadataFromStorage);
@param exportedString - A previously exported MetadataStore.
@return A new MetadataStore.
**/
static importMetadata(exportedString) {
let ms = new MetadataStore();
ms.importMetadata(exportedString);
return ms;
}
/**
Returns whether Metadata has been retrieved for a specified service name.
> // Assume em1 is an existing EntityManager.
> if (!em1.metadataStore.hasMetadataFor("breeze/NorthwindIBModel"))) {
> // do something interesting
> }
@param serviceName - The service name.
@return Whether metadata has already been retrieved for the specified service name.
**/
hasMetadataFor(serviceName) {
return !!this.getDataService(serviceName);
}
/**
Returns the DataService for a specified service name
> // Assume em1 is an existing EntityManager.
> let ds = em1.metadataStore.getDataService("breeze/NorthwindIBModel");
> let adapterName = ds.adapterName; // may be null
@param serviceName - The service name.
@return The DataService with the specified name.
**/
getDataService(serviceName) {
assertParam(serviceName, "serviceName").isString().check();
serviceName = DataService._normalizeServiceName(serviceName);
return core.arrayFirst(this.dataServices, function (ds) {
return ds.serviceName === serviceName;
});
}
/**
Fetches the metadata for a specified 'service'. This method is automatically called
internally by an EntityManager before its first query against a new service. __Async__
Usually you will not actually process the results of a fetchMetadata call directly, but will instead
ask for the metadata from the EntityManager after the fetchMetadata call returns.
> let ms = new MetadataStore();
> // or more commonly
> // let ms = anEntityManager.metadataStore;
> ms.fetchMetadata("breeze/NorthwindIBModel").then(function(rawMetadata) {
> // do something with the metadata
> }).catch(function(exception) {
> // handle exception here
> });
@param dataService - Either a DataService or just the name of the DataService to fetch metadata for.
@param callback - Function called on success.
@param errorCallback - Function called on failure.
@return Promise
**/
fetchMetadata(dataService, callback, errorCallback) {
try {
assertParam(dataService, "dataService").isString().or().isInstanceOf(DataService).check();
assertParam(callback, "callback").isFunction().isOptional().check();
assertParam(errorCallback, "errorCallback").isFunction().isOptional().check();
if (typeof dataService === "string") {
// use the dataService with a matching name or create a new one.
dataService = this.getDataService(dataService) || new DataService({ serviceName: dataService });
}
dataService = DataService.resolve([dataService]);
if (this.hasMetadataFor(dataService.serviceName)) {
throw new Error("Metadata for a specific serviceName may only be fetched once per MetadataStore. ServiceName: " + dataService.serviceName);
}
return dataService.adapterInstance.fetchMetadata(this, dataService).then((rawMetadata) => {
this.metadataFetched.publish({ metadataStore: this, dataService: dataService, rawMetadata: rawMetadata });
if (callback)
callback(rawMetadata);
return Promise.resolve(rawMetadata);
}, function (error) {
if (errorCallback)
errorCallback(error);
return Promise.reject(error);
});
}
catch (e) {
return Promise.reject(e);
}
}
// TODO: strongly type interceptor below.
/**
Used to register a constructor for an EntityType that is not known via standard Metadata discovery;
i.e. an unmapped type.
@param entityCtor - The constructor function for the 'unmapped' type.
@param interceptor - An interceptor function
**/
trackUnmappedType(entityCtor, interceptor) {
assertParam(entityCtor, "entityCtor").isFunction().check();
assertParam(interceptor, "interceptor").isFunction().isOptional().check();
// TODO: think about adding this to the MetadataStore.
let entityType = new EntityType(this);
entityType._setCtor(entityCtor, interceptor);
}
/**
Provides a mechanism to register a 'custom' constructor to be used when creating new instances
of the specified entity type. If this call is not made, a default constructor is created for
the entity as needed.
This call may be made before or after the corresponding EntityType has been discovered via
Metadata discovery.
> let Customer = function () {
> this.miscData = "asdf";
> };
> Customer.prototype.doFoo() {
> ...
> }
> // assume em1 is a preexisting EntityManager;
> em1.metadataStore.registerEntityTypeCtor("Customer", Customer);
> // any queries or EntityType.create calls from this point on will call the Customer constructor
> // registered above.
@param structuralTypeName - The name of the EntityType or ComplexType.
@param aCtor - The constructor for this EntityType or ComplexType; may be null if all you want to do is set the next parameter.
@param initFn - A function or the name of a function on the entity that is to be executed immediately after the entity has been created
and populated with any initial values. Called with 'initFn(entity)'
@param noTrackingFn - A function that is executed immediately after a noTracking entity has been created and whose return
value will be used in place of the noTracking entity.
**/
registerEntityTypeCtor(structuralTypeName, aCtor, initFn, noTrackingFn) {
assertParam(structuralTypeName, "structuralTypeName").isString().check();
assertParam(aCtor, "aCtor").isFunction().isOptional().check();
assertParam(initFn, "initFn").isOptional().isFunction().or().isString().check();
assertParam(noTrackingFn, "noTrackingFn").isOptional().isFunction().check();
let qualifiedTypeName = getQualifiedTypeName(this, structuralTypeName, false);
let typeName = qualifiedTypeName || structuralTypeName;
if (aCtor) {
if (aCtor._$typeName && aCtor._$typeName !== typeName) {
// TODO: wrap this - console and especially console.warn does not exist in all browsers.
console.warn("Registering a constructor for " + typeName + " that is already used for " + aCtor._$typeName + ".");
}
aCtor._$typeName = typeName;
}
this._ctorRegistry[typeName] = { ctor: aCtor, initFn: initFn, noTrackingFn: noTrackingFn };
if (qualifiedTypeName) {
let stype = this._structuralTypeMap[qualifiedTypeName];
stype && stype.getCtor(true); // this will complete the registration if avail now.
}
}
/**
Returns whether this MetadataStore contains any metadata yet.
> // assume em1 is a preexisting EntityManager;
> if (em1.metadataStore.isEmpty()) {
> // do something interesting
> }
**/
isEmpty() {
return core.isEmpty(this._structuralTypeMap);
}
/**
Returns an [[EntityType]] or null given its name.
> // assume em1 is a preexisting EntityManager
> let odType = em1.metadataStore.getAsEntityType("OrderDetail");
or to throw an error if the type is not found
> let badType = em1.metadataStore.getAsEntityType("Foo", false);
> // badType will not get set and an exception will be thrown.
@param structuralTypeName - Either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
that same short name an exception will be thrown.
@param okIfNotFound - (default=false) Whether to throw an error if the specified EntityType is not found.
@return The EntityType. ComplexType or 'null' if not not found.
**/
getAsEntityType(typeName, okIfNotFound = false) {
const st = this.getStructuralType(typeName, okIfNotFound);
if (st instanceof EntityType) {
return st;
}
else if (okIfNotFound) {
return null;
}
else {
let msg = core.formatString("Unable to locate an 'EntityType' by the name: '%1'. Be sure to execute a query or call fetchMetadata first.", typeName);
throw new Error(msg);
}
}
/**
Returns an [[EntityType]] or null given its name.
> // assume em1 is a preexisting EntityManager
> let locType = em1.metadataStore.getAsComplexType("Location");
or to throw an error if the type is not found
> let badType = em1.metadataStore.getAsComplexType("Foo", false);
> // badType will not get set and an exception will be thrown.
@param structuralTypeName - Either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
that same short name an exception will be thrown.
@param okIfNotFound - (default=false) Whether to throw an error if the specified EntityType is not found.
@return The EntityType. ComplexType or 'null' if not not found.
**/
getAsComplexType(typeName, okIfNotFound = false) {
const st = this.getStructuralType(typeName, okIfNotFound);
if (st instanceof ComplexType) {
return st;
}
else if (okIfNotFound) {
return null;
}
else {
let msg = core.formatString("Unable to locate an 'ComplexType' by the name: '%1'. Be sure to execute a query or call fetchMetadata first.", typeName);
throw new Error(msg);
}
}
/**
Returns an [[EntityType]] or a [[ComplexType]] given its name.
@deprecated Replaced by getStructuralType but ... it is probably more usefull to call either getAsEntityType or getAsComplexType instead
@param typeName - Either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
that same short name an exception will be thrown.
@param okIfNotFound - (default=false) Whether to throw an error if the specified EntityType is not found.
@return The EntityType. ComplexType or 'null' if not not found.
**/
getEntityType(typeName, okIfNotFound = false) {
return this.getStructuralType(typeName, okIfNotFound);
}
/**
Returns an [[EntityType]] or a [[ComplexType]] given its name.
> // assume em1 is a preexisting EntityManager
> let odType = em1.metadataStore.getStructuralType("OrderDetail");
or to throw an error if the type is not found
> let badType = em1.metadataStore.getStructuralType("Foo", false);
> // badType will not get set and an exception will be thrown.
@deprecated Preferably use either getAsEntityType or getAsComplexType. Get
@param typeName - Either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
that same short name an exception will be thrown.
@param okIfNotFound - (default=false) Whether to throw an error if the specified EntityType is not found.
@return The EntityType. ComplexType or 'null' if not not found.
**/
getStructuralType(typeName, okIfNotFound = false) {
assertParam(typeName, "typeName").isString().check();
assertParam(okIfNotFound, "okIfNotFound").isBoolean().isOptional().check(false);
return this._getStructuralType(typeName, okIfNotFound);
}
/** @hidden @internal */
_getStructuralType(typeName, okIfNotFound = false) {
let qualTypeName = getQualifiedTypeName(this, typeName, false);
let type = this._structuralTypeMap[qualTypeName];
if (!type) {
if (okIfNotFound)
return null;
let msg = core.formatString("Unable to locate a 'Type' by the name: '%1'. Be sure to execute a query or call fetchMetadata first.", typeName);
throw new Error(msg);
}
return type;
}
/**
Returns an array containing all of the [[EntityType]]s or [[ComplexType]]s in this MetadataStore.
> // assume em1 is a preexisting EntityManager
> let allTypes = em1.metadataStore.getEntityTypes();
**/
getEntityTypes() {
return getTypesFromMap(this._structuralTypeMap);
}
getIncompleteNavigationProperties() {
return core.objectMap(this._incompleteTypeMap, function (key, value) {
return value;
});
}
/**
Returns a fully qualified entityTypeName for a specified resource name. The reverse of this operation
can be obtained via the [[EntityType.defaultResourceName]] property
**/
getEntityTypeNameForResourceName(resourceName) {
assertParam(resourceName, "resourceName").isString().check();
return this._resourceEntityTypeMap[resourceName];
}
/**
Associates a resourceName with an entityType.
This method is only needed in those cases where multiple resources return the same
entityType. In this case Metadata discovery will only determine a single resource name for
each entityType.
@param resourceName - The resource name
@param entityTypeOrName - If passing a string either the fully qualified name or a short name may be used. If a short name is specified and multiple types share
that same short name an exception will be thrown. If the entityType has not yet been discovered then a fully qualified name must be used.
**/
setEntityTypeForResourceName(resourceName, entityTypeOrName) {
assertParam(resourceName, "resourceName").isString().check();
assertParam(entityTypeOrName, "entityTypeOrName").isInstanceOf(EntityType).or().isString().check();
let entityTypeName;
if (entityTypeOrName instanceof EntityType) {
entityTypeName = entityTypeOrName.name;
}
else {
entityTypeName = getQualifiedTypeName(this, entityTypeOrName, true);
}
this._resourceEntityTypeMap[resourceName] = entityTypeName;
let entityType = this._getStructuralType(entityTypeName, true);
if (entityType && entityType instanceof EntityType && !entityType.defaultResourceName) {
entityType.defaultResourceName = resourceName;
}
}
/** __Dev Only__ - for use when creating a new MetadataParserAdapter */
static parseTypeName(entityTypeName) {
// TODO: removed
// if (!entityTypeName) {
// return null;
// }
let typeParts = entityTypeName.split(":#");
if (typeParts.length > 1) {
return MetadataStore.makeTypeHash(typeParts[0], typeParts[1]);
}
if (core.stringStartsWith(entityTypeName, MetadataStore.ANONTYPE_PREFIX)) {
let typeHash = MetadataStore.makeTypeHash(entityTypeName);
typeHash.isAnonymous = true;
return typeHash;
}
let entityTypeNameNoAssembly = entityTypeName.split(",")[0];
typeParts = entityTypeNameNoAssembly.split(".");
if (typeParts.length > 1) {
let shortName = typeParts[typeParts.length - 1];
let namespaceParts = typeParts.slice(0, typeParts.length - 1);
let ns = namespaceParts.join(".");
return MetadataStore.makeTypeHash(shortName, ns);
}
else {
return MetadataStore.makeTypeHash(entityTypeName);
}
}
/** __Dev Only__ - for use when creating a new MetadataParserAdapter */
static makeTypeHash(shortName, ns) {
return {
shortTypeName: shortName,
namespace: ns,
typeName: qualifyTypeName(shortName, ns)
};
}
// protected methods
/** @hidden @internal */
_checkEntityType(entity) {
if (entity.entityType)
return;
let typeName = entity.prototype._$typeName;
if (!typeName) {
throw new Error("This entity has not been registered. See the MetadataStore.registerEntityTypeCtor method");
}
// we know that it is an EntityType ( as opposed to a ComplexType)
let entityType = this._getStructuralType(typeName);
if (entityType) {
entity.entityType = entityType;
}
}
}
MetadataStore.prototype._$typeName = "MetadataStore";
BreezeEvent.bubbleEvent(MetadataStore.prototype);
function getTypesFromMap(typeMap) {
let types = [];
for (let key in typeMap) {
let value = typeMap[key];
// skip 'shortName' entries
if (key === value.name) {
types.push(typeMap[key]);
}
}
return types;
}
function structuralTypeFromJson(metadataStore, json, allowMerge) {
let typeName = qualifyTypeName(json.shortName, json.namespace);
let stype = metadataStore._getStructuralType(typeName, true);
if (stype) {
if (allowMerge) {
return mergeStructuralType(stype, json);
}
else {
// allow it but don't replace anything.
return stype;
}
}
let config = {
shortName: json.shortName,
namespace: json.namespace,
isAbstract: json.isAbstract,
autoGeneratedKeyType: AutoGeneratedKeyType.fromName(json.autoGeneratedKeyType),
defaultResourceName: json.defaultResourceName,
custom: json.custom
};
stype = json.isComplexType ? new ComplexType(config) : new EntityType(config);
// baseType may not have been imported yet so we need to defer handling this type until later.
if (json.baseTypeName && stype instanceof EntityType) {
stype.baseTypeName = json.baseTypeName;
let baseEntityType = metadataStore._getStructuralType(json.baseTypeName, true);
if (baseEntityType) {
completeStructuralTypeFromJson(metadataStore, json, stype);
}
else {
core.getArray(metadataStore._deferredTypes, json.baseTypeName).push({ json: json, stype: stype });
}
}
else {
completeStructuralTypeFromJson(metadataStore, json, stype);
}
// stype may or may not have been added to the metadataStore at this point.
return stype;
}
function mergeStructuralType(stype, json) {
if (json.custom) {
stype.custom = json.custom;
}
mergeProps(stype, json.dataProperties);
mergeProps(stype, json.navigationProperties);
return stype;
}
function mergeProps(stype, jsonProps) {
if (!jsonProps)
return;
jsonProps.forEach((jsonProp) => {
let propName = jsonProp.name;
if (!propName) {
if (jsonProp.nameOnServer) {
propName = stype.metadataStore.namingConvention.serverPropertyNameToClient(jsonProp.nameOnServer, {});
}
else {
// backslash-quote works around compiler bug
const msg = "Unable to complete \'importMetadata\' - cannot locate a \'name\' or \'nameOnServer\' for one of the imported property nodes";
throw new Error(msg);
}
}
if (jsonProp.custom) {
let prop = stype.getProperty(propName, true);
prop.custom = jsonProp.custom;
}
});
}
function completeStructuralTypeFromJson(metadataStore, json, stype) {
// validators from baseType work because validation walks thru base types
// so no need to copy down.
if (json.validators) {
stype.validators = json.validators.map(Validator.fromJSON);
}
json.dataProperties.forEach(function (dp) {
stype._addPropertyCore(DataProperty.fromJSON(dp));
});
let isEntityType = !json.isComplexType;
if (isEntityType) {
//noinspection JSHint
json.navigationProperties && json.navigationProperties.forEach(function (np) {
stype._addPropertyCore(NavigationProperty.fromJSON(np));
});
}
metadataStore.addEntityType(stype);
let deferredTypes = metadataStore._deferredTypes;
let deferrals = deferredTypes[stype.name];
if (deferrals) {
deferrals.forEach(function (d) {
completeStructuralTypeFromJson(metadataStore, d.json, d.stype);
});
delete deferredTypes[stype.name];
}
}
function getQualifiedTypeName(metadataStore, structTypeName, throwIfNotFound) {
if (isQualifiedTypeName(structTypeName))
return structTypeName;
let result = metadataStore._shortNameMap[structTypeName];
if (!result && throwIfNotFound) {
throw new Error("Unable to locate 'entityTypeName' of: " + structTypeName);
}
return result;
}
/** Container for all of the metadata about a specific type of Entity.
**/
export class EntityType {
/** @hidden @internal */
static __nextAnonIx = 0;
/** Always false for an EntityType. **/
isComplexType = false;
/** The [[MetadataStore]] that contains this EntityType. __Read Only__ **/
metadataStore;
/** The DataProperties (see [[DataProperty]] associated with this EntityType. __Read Only__ **/
dataProperties;
/** The NavigationProperties (see [[NavigationProperty]] associated with this EntityType. __Read Only__ **/
navigationProperties;
/**
The DataProperties associated with this EntityType that make up it's [[EntityKey]]. __Read Only__ **/
keyProperties;
/** The DataProperties associated with this EntityType that are foreign key properties. __Read Only__ **/
foreignKeyProperties;
inverseForeignKeyProperties;
/** The DataProperties associated with this EntityType that are concurrency properties. __Read Only__ **/
concurrencyProperties;
/** The DataProperties for this EntityType that contain instances of a [[ComplexType]]. __Read Only__ **/
complexProperties;
/** The DataProperties associated with this EntityType that are not mapped to any backend datastore. These are effectively free standing
properties. __Read Only__ **/
unmappedProperties;
/** The fully qualified name of this EntityType. __Read Only__ **/
name;
/** The short, unqualified, name for this EntityType. __Read Only__ **/
shortName;
/** The namespace for this EntityType. __Read Only__ **/
namespace;
/** The name of this EntityType's base EntityType (if any) */
baseTypeName;
/** The base EntityType (if any) for this EntityType. __Read Only__ **/
baseEntityType;
subtypes;
/** Whether this EntityType is abstract. __Read Only__ **/
isAbstract;
/** Whether this EntityType is anonymous. Anonymous types will never be communicated to or from the server. They are purely for
client side use and are given an automatically generated name. __Read Only__ **/
isAnonymous;
/** Whether this EntityType has been 'frozen'. EntityTypes become frozen after the first instance
of that type has been created and attached to an EntityManager. */
isFrozen;
/** The [[AutoGeneratedKeyType]] for this EntityType. __Read Only__ **/
autoGeneratedKeyType;
/** The default resource name associated with this EntityType. An EntityType may be queried via a variety of 'resource names' but this one
is used as the default when no resource name is provided. This will occur when calling [[EntityAspect.loadNavigationProperty]]
or when executing any [[EntityQuery]] that was created via an [[EntityKey]]. __Read Only__ **/
defaultResourceName;
/** A function that is used to customize the serialization of any EntityProperties of this type. */
serializerFn;
/** A free form object that can be used to define any custom metadata for this EntityType. __Read Only__ **/
custom;
/** The entity level validators associated with this EntityType. Validators can be added and
removed from this collection. __Read Only__. **/
validators;
warnings;
initFn;
noTrackingFn;
/** @hidden @internal */
_extra;
/** @hidden @internal */
_ctor;
/** @hidden @internal */
_mappedPropertiesCount;
/**
@deprecated Use [[getCtor]] instead.
*/
getEntityCtor = this.getCtor;
/** @hidden @internal */
static qualifyTypeName = qualifyTypeName;
/** EntityType constructor
> let entityType = new EntityType( {
> shortName: "person",
> namespace: "myAppNamespace"
> });
@param config - Configuration settings or a MetadataStore. If this parameter is just a MetadataStore
then what will be created is an 'anonymous' type that will never be communicated to or from the server. It is purely for
client side use and will be given an automatically generated name. Normally, however, you will use a configuration object.
**/
constructor(config) {
if (arguments.length > 1) {
throw new Error("The EntityType ctor has a single argument that is either a 'MetadataStore' or a configuration object.");
}
// let etConfig = <EntityTypeConfig> <any> undefined;
let etConfig = undefined;
if (config._$typeName === "MetadataStore") {
this.metadataStore = config;
this.shortName = "Anon_" + (++EntityType.__nextAnonIx);
this.namespace = "";
this.isAnonymous = true;
// etConfig = undefined;
}
else {
etConfig = config;
assertConfig(config)
.whereParam("shortName").isNonEmptyString()
.whereParam("namespace").isString().isOptional().withDefault("")
.whereParam("baseTypeName").isString().isOptional()
.whereParam("isAbstract").isBoolean().isOptional().withDefault(false)
.whereParam("autoGeneratedKeyType").isEnumOf(AutoGeneratedKeyType).isOptional().withDefault(AutoGeneratedKeyType.None)
.whereParam("defaultResourceName").isNonEmptyString().isOptional().withDefault(null)
.whereParam("dataProperties").isOptional()
.whereParam("navigationProperties").isOptional()
.whereParam("serializerFn").isOptional().isFunction()
.whereParam("custom").isOptional()
.applyAll(this);
}
this.name = qualifyTypeName(this.shortName, this.namespace);
// the defaultResourceName may also be set up either via metadata lookup or first query or via the 'setProperties' method
this.dataProperties = [];
this.navigationProperties = [];
this.complexProperties = [];
this.keyProperties = [];
this.foreignKeyProperties = [];
this.inverseForeignKeyProperties = [];
this.concurrencyProperties = [];
this.unmappedProperties = []; // will be updated later.
this.validators = [];
this.warnings = [];
this._mappedPropertiesCount = 0;
this.subtypes = [];
// now process any data/nav props
if (etConfig && etConfig.dataProperties) {
addProperties(this, etConfig.dataProperties, DataProperty);
}
if (etConfig && etConfig.navigationProperties) {
addProperties(this, etConfig.navigationProperties, NavigationProperty);
}
}
/**
General purpose property set method
> // assume em1 is an EntityManager containing a number of existing entities.
> let custType = em1.metadataStore.getEntityType("Customer");
> custType.setProperties( {
> autoGeneratedKeyType: AutoGeneratedKeyType.Identity;
> defaultResourceName: "CustomersAndIncludedOrders"
> )};
@param config - a configuration object
**/
setProperties(config) {
assertConfig(config)
.whereParam("autoGeneratedKeyType").isEnumOf(AutoGeneratedKeyType).isOptional()
.whereParam("defaultResourceName").isString().isOptional()
.whereParam("serializerFn").isFunction().isOptional()
.whereParam("custom").isOptional()
.applyAll(this);
if (config.defaultResourceName) {
this.defaultResourceName = config.defaultResourceName;
}
}
/**
Returns whether this type is a subtype of a specified type.
**/
isSubtypeOf(entityType) {
assertParam(entityType, "entityType").isInstanceOf(EntityType).check();
let baseType = this;
do {
if (baseType === entityType)
return true;
baseType = baseType.baseEntityType;
} while (baseType);
return false;
}
/**
Returns an array containing this type and any/all subtypes of this type down thru the hierarchy.
**/
getSelfAndSubtypes() {
let result = [this];
this.subtypes.forEach(function (st) {
let subtypes = st.getSelfAndSubtypes();
result.push.apply(result, subtypes);
});
return result;
}
getAllValidators() {
let result = this.validators.slice(0);
let bt = this.baseEntityType;
while (bt) {
result.push.apply(result, bt.validators);
bt = bt.baseEntityType;
}
return result;
}
/**
Adds a [[DataProperty]] or a [[NavigationProperty]] to this EntityType.
> // assume myEntityType is a newly constructed EntityType.
> myEntityType.addProperty(dataProperty1);
> myEntityType.addProperty(dataProperty2);
> myEntityType.addProperty(navigationProperty1);
**/
addProperty(property) {
assertParam(property, "property").isInstanceOf(DataProperty).or().isInstanceOf(NavigationProperty).check();
// true is 2nd arg to force resolve of any navigation properties.
let newprop = this._addPropertyCore(property, true);
if (this.subtypes && this.subtypes.length) {
let stype = this;
stype.getSelfAndSubtypes().forEach(function (st) {
if (st !== stype) {
if (property.isNavigationProperty) {
st._addPropertyCore(new NavigationProperty(property), true);
}
else {
st._addPropertyCore(new DataProperty(property), true);
}
}
});
}
return newprop;
}
/** @hidden @internal */
_updateFromBase(baseEntityType) {
this.baseEntityType = baseEntityType;
if (this.autoGeneratedKeyType === AutoGeneratedKeyType.None) {
this.autoGeneratedKeyType = baseEntityType.autoGeneratedKeyType;
}
baseEntityType.dataProperties.forEach((dp) => {
let newDp = new DataProperty(dp);
// don't need to copy validators becaue we will walk the hierarchy to find them
newDp.validators = [];
newDp.baseProperty = dp;
this._addPropertyCore(newDp);
}, this);
baseEntityType.navigationProperties.forEach((np) => {
let newNp = new NavigationProperty(np);
// don't need to copy validators becaue we will walk the hierarchy to find them
newNp.validators = [];
newNp.baseProperty = np;
this._addPropertyCore(newNp);
}, this);
baseEntityType.subtypes.push(this);
}
/** @hidden @internal */
_addPropertyCore(property, shouldResolve = false) {
if (this.isFrozen) {
throw new Error("The '" + this.name + "' EntityType/ComplexType has been frozen. You can only add properties to an EntityType/ComplexType before any instances of that type have been created and attached to an entityManager.");
}
let parentType = property.parentType;
if (parentType) {
if (parentType !== this) {
throw new Error("This property: " + property.name + " has already been added to " + property.parentType.name);
}
else {
// adding the same property more than once to the same entityType is just ignored.
return;
}
}
property.parentType = this;
let ms = this.metadataStore;
// if (property.isDataProperty) { // modified because doesn't act as a type guard
if (property instanceof DataProperty) {
this._addDataProperty(property);
}
else {
this._addNavigationProperty(property);
// metadataStore can be undefined if this entityType has not yet been added to a MetadataStore.
if (shouldResolve && ms) {
tryResolveNp(property, ms);
}
}
// unmapped properties can be added AFTER entityType has already resolved all property names.
if (ms && !(property.name && property.nameOnServer)) {
updateClientServerNames(ms.namingConvention, property, "name");
}