UNPKG

powerplatform-mcp

Version:

PowerPlatform Model Context Protocol server

629 lines (628 loc) 32 kB
/** * Service for entity metadata operations. * Handles entity definitions, attributes, and relationships. */ export class EntityService { client; constructor(client) { this.client = client; } /** * Get metadata about an entity * @param entityName The logical name of the entity */ async getEntityMetadata(entityName) { const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')`); // Remove Privileges property if it exists if (response && typeof response === 'object' && 'Privileges' in response) { delete response.Privileges; } return response; } /** * Get metadata about entity attributes/fields * @param entityName The logical name of the entity */ async getEntityAttributes(entityName) { const selectProperties = [ 'LogicalName', 'RequiredLevel', 'AttributeType', ].join(','); // Make the request to get attributes const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes?$select=${selectProperties}&$filter=AttributeType ne 'Virtual'`); if (response && response.value) { // First pass: Filter out attributes that end with 'yominame' response.value = response.value.filter((attribute) => { const logicalName = attribute.LogicalName || ''; return !logicalName.endsWith('yominame'); }); // Filter out attributes that end with 'name' if there is another attribute with the same name without the 'name' suffix const baseNames = new Set(); const namesAttributes = new Map(); for (const attribute of response.value) { const logicalName = attribute.LogicalName || ''; if (logicalName.endsWith('name') && logicalName.length > 4) { const baseName = logicalName.slice(0, -4); // Remove 'name' suffix namesAttributes.set(baseName, attribute); } else { // This is a potential base attribute baseNames.add(logicalName); } } // Find attributes to remove that match the pattern const attributesToRemove = new Set(); for (const [baseName, nameAttribute] of namesAttributes.entries()) { if (baseNames.has(baseName)) { attributesToRemove.add(nameAttribute); } } response.value = response.value.filter(attribute => !attributesToRemove.has(attribute)); } return response; } /** * Get metadata about a specific entity attribute/field * @param entityName The logical name of the entity * @param attributeName The logical name of the attribute */ async getEntityAttribute(entityName, attributeName) { return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`); } /** * Get one-to-many relationships for an entity * @param entityName The logical name of the entity */ async getEntityOneToManyRelationships(entityName) { const selectProperties = [ 'SchemaName', 'RelationshipType', 'ReferencedAttribute', 'ReferencedEntity', 'ReferencingAttribute', 'ReferencingEntity', 'ReferencedEntityNavigationPropertyName', 'ReferencingEntityNavigationPropertyName', 'CascadeConfiguration' ].join(','); // Only filter by ReferencingAttribute in the OData query since startswith isn't supported const response = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/OneToManyRelationships?$select=${selectProperties}&$filter=ReferencingAttribute ne 'regardingobjectid'`); // Filter the response to exclude relationships with ReferencingEntity starting with 'msdyn_' or 'adx_' if (response && response.value) { response.value = response.value.filter((relationship) => { const referencingEntity = relationship.ReferencingEntity || ''; return !(referencingEntity.startsWith('msdyn_') || referencingEntity.startsWith('adx_')); }); } return response; } /** * Get many-to-many relationships for an entity * @param entityName The logical name of the entity */ async getEntityManyToManyRelationships(entityName) { const selectProperties = [ 'SchemaName', 'RelationshipType', 'Entity1LogicalName', 'Entity2LogicalName', 'Entity1IntersectAttribute', 'Entity2IntersectAttribute', 'Entity1NavigationPropertyName', 'Entity2NavigationPropertyName' ].join(','); return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/ManyToManyRelationships?$select=${selectProperties}`); } /** * Get all relationships (one-to-many and many-to-many) for an entity * @param entityName The logical name of the entity */ async getEntityRelationships(entityName) { const [oneToMany, manyToMany] = await Promise.all([ this.getEntityOneToManyRelationships(entityName), this.getEntityManyToManyRelationships(entityName) ]); return { oneToMany, manyToMany }; } /** * Set the vector (SVG) icon used for an entity in the unified-interface app nav. * The referenced web resource must already exist and be of type 11 (Vector). * * Dataverse metadata updates can't use PATCH — they require a full-replace PUT * (UpdateEntityRequest semantics). So: fetch full entity metadata, modify the * one field, PUT it back. MergeLabels=true preserves localized labels. * * @param entityName Logical name (e.g. 'br_property') * @param iconVectorName Web-resource name (e.g. 'br_property_icon.svg') * @param solutionName Optional solution to scope the metadata change to */ async setEntityIconVector(entityName, iconVectorName, solutionName) { const metadata = await this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')`); const body = { ...metadata, '@odata.type': 'Microsoft.Dynamics.CRM.EntityMetadata', IconVectorName: iconVectorName, }; const headers = { 'MSCRM.MergeLabels': 'true' }; if (solutionName) headers['MSCRM.SolutionUniqueName'] = solutionName; await this.client.put(`api/data/v9.2/EntityDefinitions(${metadata.MetadataId})`, body, headers); } /** * Create a string (Single Line of Text) attribute on an entity. * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute (e.g. br_hospitableid) * @param displayName The display name for the new attribute * @param maxLength Maximum length of the text field (default: 100) * @param requiredLevel Required level: 'None', 'ApplicationRequired', or 'SystemRequired' * @param description Optional description for the attribute */ async createStringAttribute(entityName, schemaName, displayName, maxLength = 100, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': '#Microsoft.Dynamics.CRM.StringAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, MaxLength: maxLength, FormatName: { Value: 'Text' }, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a new custom entity (table) in Dataverse. * Creates the entity shell with a single primary name (Single Line of Text) attribute. * Use the existing `createStringAttribute`, `createDecimalAttribute`, `createLookupAttribute` * helpers to add additional columns after creation. * * @param schemaName Schema name for the new entity (e.g. 'br_FinancialLine' — must start with the publisher prefix) * @param displayName Singular display name (e.g. 'Financial Line') * @param displayCollectionName Plural display name (e.g. 'Financial Lines') * @param primaryNameSchemaName Schema name for the primary name attribute (e.g. 'br_Name') * @param primaryNameDisplayName Display name for the primary name attribute (e.g. 'Name') * @param description Optional description for the entity * @param ownershipType 'UserOwned' or 'OrganizationOwned' (default UserOwned) * @param hasActivities Whether the entity tracks activities (default false) * @param hasNotes Whether the entity supports notes (default false) * @param languageCode Language code for labels (default 1045 — Polish) * @param solutionName Optional solution unique name to add the entity to */ async createEntity(schemaName, displayName, displayCollectionName, primaryNameSchemaName, primaryNameDisplayName, description, ownershipType = 'UserOwned', hasActivities = false, hasNotes = false, languageCode = 1045, solutionName) { const label = (text) => ({ '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [ { '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: text, LanguageCode: languageCode, }, ], }); const primaryName = { '@odata.type': 'Microsoft.Dynamics.CRM.StringAttributeMetadata', SchemaName: primaryNameSchemaName, IsPrimaryName: true, RequiredLevel: { Value: 'None' }, MaxLength: 200, FormatName: { Value: 'Text' }, DisplayName: label(primaryNameDisplayName), }; const body = { '@odata.type': 'Microsoft.Dynamics.CRM.EntityMetadata', SchemaName: schemaName, DisplayName: label(displayName), DisplayCollectionName: label(displayCollectionName), OwnershipType: ownershipType, HasActivities: hasActivities, HasNotes: hasNotes, IsActivity: false, Attributes: [primaryName], }; if (description) { body.Description = label(description); } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions`, body, headers); return { entityId: result?.entityId ?? result?.MetadataId ?? 'created' }; } /** * Create a local Picklist (Choice / OptionSet) attribute on an entity. * * @param entityName The logical name of the entity * @param schemaName Schema name for the new attribute * @param displayName Display name * @param options Array of { value: number, label: string } defining the picklist options * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createPicklistAttribute(entityName, schemaName, displayName, options, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const mkLabel = (text) => ({ '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: text, LanguageCode: languageCode }], }); const body = { '@odata.type': 'Microsoft.Dynamics.CRM.PicklistAttributeMetadata', SchemaName: schemaName, DisplayName: mkLabel(displayName), RequiredLevel: { Value: requiredLevel }, OptionSet: { '@odata.type': 'Microsoft.Dynamics.CRM.OptionSetMetadata', IsGlobal: false, OptionSetType: 'Picklist', Options: options.map(o => ({ Value: o.value, Label: mkLabel(o.label), })), }, }; if (description) { body.Description = mkLabel(description); } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Delete an attribute from an entity. Irreversible — the column and all data in it are dropped. * Fails if any component (form, view, workflow, etc.) still depends on the attribute; use * `checkDependencies` first if you need to investigate. * * @param entityName The logical name of the entity * @param attributeName The logical name of the attribute to delete */ async deleteEntityAttribute(entityName, attributeName) { await this.client.delete(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes(LogicalName='${attributeName}')`); } /** * Create a Money (Currency) attribute on an entity. * On the first Money column added to an entity, Dataverse automatically adds a * `transactioncurrencyid` lookup to the entity if it doesn't already have one. * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param precisionSource 0 = SimpleFixed (use Precision), 1 = Pricing, 2 = Currency (default) * @param precision Display precision when precisionSource is 0 (default 2) * @param minValue Minimum allowed value * @param maxValue Maximum allowed value * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createMoneyAttribute(entityName, schemaName, displayName, precisionSource = 2, precision = 2, minValue = -100_000_000_000, maxValue = 100_000_000_000, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': 'Microsoft.Dynamics.CRM.MoneyAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, PrecisionSource: precisionSource, Precision: precision, MinValue: minValue, MaxValue: maxValue, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a decimal number attribute on an entity. * Used for money/quantities when you don't want Dataverse Currency (which requires a TCURRENCY lookup). * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param precision Number of digits after the decimal (default 2, max 10) * @param minValue Minimum allowed value (default -100_000_000_000) * @param maxValue Maximum allowed value (default 100_000_000_000) * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createDecimalAttribute(entityName, schemaName, displayName, precision = 2, minValue = -100_000_000_000, maxValue = 100_000_000_000, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': 'Microsoft.Dynamics.CRM.DecimalAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, Precision: precision, MinValue: minValue, MaxValue: maxValue, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a DateTime attribute on an entity. * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param format 'DateOnly' or 'DateAndTime' (default 'DateOnly') * @param behavior 'UserLocal', 'DateOnly', or 'TimeZoneIndependent' (default 'UserLocal') * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createDateTimeAttribute(entityName, schemaName, displayName, format = 'DateOnly', behavior = 'UserLocal', requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': '#Microsoft.Dynamics.CRM.DateTimeAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, Format: format, DateTimeBehavior: { Value: behavior }, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create an Integer (Whole Number) attribute on an entity. * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param minValue Minimum allowed value (default -2147483648) * @param maxValue Maximum allowed value (default 2147483647) * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createIntegerAttribute(entityName, schemaName, displayName, minValue = -2147483648, maxValue = 2147483647, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': '#Microsoft.Dynamics.CRM.IntegerAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, MinValue: minValue, MaxValue: maxValue, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a Boolean (Two Option / Yes/No) attribute on an entity. * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param trueLabel Label for the true/yes option (default 'Yes') * @param falseLabel Label for the false/no option (default 'No') * @param defaultValue Default value (default false) * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createBooleanAttribute(entityName, schemaName, displayName, trueLabel = 'Yes', falseLabel = 'No', defaultValue = false, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': '#Microsoft.Dynamics.CRM.BooleanAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, DefaultValue: defaultValue, OptionSet: { TrueOption: { Value: 1, Label: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: trueLabel, LanguageCode: languageCode }], }, }, FalseOption: { Value: 0, Label: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: falseLabel, LanguageCode: languageCode }], }, }, }, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a Memo (Multi-Line Text) attribute on an entity. * * @param entityName The logical name of the entity * @param schemaName The schema name for the new attribute * @param displayName The display name * @param maxLength Maximum text length (default 2000) * @param requiredLevel Required level * @param description Optional description * @param languageCode Language code for labels * @param solutionName Optional solution unique name */ async createMemoAttribute(entityName, schemaName, displayName, maxLength = 2000, requiredLevel = 'None', description, languageCode = 1045, solutionName) { const body = { '@odata.type': '#Microsoft.Dynamics.CRM.MemoAttributeMetadata', SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, RequiredLevel: { Value: requiredLevel }, MaxLength: maxLength, FormatName: { Value: 'TextArea' }, }; if (description) { body.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode }], }; } const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Attributes`, body, headers); return { attributeId: result?.entityId ?? 'created' }; } /** * Create a lookup (N:1 relationship) attribute on an entity. * Wraps the Dataverse `CreateOneToManyRelationship` action. * * @param referencingEntity The child entity that will hold the new lookup column (e.g. 'br_reservation') * @param referencedEntity The parent entity the lookup points to (e.g. 'contact') * @param relationshipSchemaName Schema name for the 1:N relationship (e.g. 'br_reservation_contact') * @param lookupSchemaName Schema name for the lookup column itself (e.g. 'br_ContactId') * @param displayName Display name shown on the lookup column * @param requiredLevel Required level: 'None', 'ApplicationRequired', or 'SystemRequired' * @param description Optional description for the lookup column * @param cascadeDelete Cascade delete behavior (default 'RemoveLink' — deleting parent just nulls the lookup) * @param languageCode Language code for labels (default 1045 — Polish) * @param solutionName Optional solution unique name to add the component to */ async createLookupAttribute(referencingEntity, referencedEntity, relationshipSchemaName, lookupSchemaName, displayName, requiredLevel = 'None', description, cascadeDelete = 'RemoveLink', languageCode = 1045, solutionName) { const emptyLabel = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [], }; const displayLabel = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [ { '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode, }, ], }; const lookup = { '@odata.type': 'Microsoft.Dynamics.CRM.LookupAttributeMetadata', SchemaName: lookupSchemaName, DisplayName: displayLabel, RequiredLevel: { Value: requiredLevel }, }; if (description) { lookup.Description = { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [ { '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: description, LanguageCode: languageCode, }, ], }; } const body = { '@odata.type': 'Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata', SchemaName: relationshipSchemaName, ReferencedEntity: referencedEntity, ReferencingEntity: referencingEntity, AssociatedMenuConfiguration: { Behavior: 'UseCollectionName', Group: 'Details', Label: emptyLabel, Order: 10000, }, CascadeConfiguration: { Assign: 'NoCascade', Delete: cascadeDelete, Merge: 'NoCascade', Reparent: 'NoCascade', Share: 'NoCascade', Unshare: 'NoCascade', }, Lookup: lookup, }; // POST to RelationshipDefinitions with the OneToMany metadata; Lookup is nested inside. // Solution association goes through the standard MSCRM.SolutionUniqueName header. const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/RelationshipDefinitions`, body, headers); return { attributeId: result?.AttributeId ?? result?.entityId ?? 'created' }; } /** * Get alternate keys defined on an entity. * @param entityName The logical name of the entity */ async getEntityKeys(entityName) { return this.client.get(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Keys`); } /** * Create an alternate key on an entity. * @param entityName The logical name of the entity * @param schemaName The schema name for the key * @param displayName The display name for the key * @param keyAttributes Array of attribute logical names that make up the key */ async createAlternateKey(entityName, schemaName, displayName, keyAttributes, languageCode = 1045, solutionName) { const body = { SchemaName: schemaName, DisplayName: { '@odata.type': 'Microsoft.Dynamics.CRM.Label', LocalizedLabels: [{ '@odata.type': 'Microsoft.Dynamics.CRM.LocalizedLabel', Label: displayName, LanguageCode: languageCode }], }, KeyAttributes: keyAttributes, }; const headers = solutionName ? { 'MSCRM.SolutionUniqueName': solutionName } : undefined; const result = await this.client.post(`api/data/v9.2/EntityDefinitions(LogicalName='${entityName}')/Keys`, body, headers); return { keyId: result?.entityId ?? 'created' }; } }