dataverse-type-gen
Version:
TypeScript type generator for Dataverse entities and metadata
388 lines (387 loc) • 15.3 kB
JavaScript
// src/processors/index.ts
function extractDisplayName(localizedLabel) {
if (!localizedLabel) return "";
const label = localizedLabel.UserLocalizedLabel?.Label || localizedLabel.LocalizedLabels?.[0]?.Label || "";
return label;
}
function extractRequiredLevel(requiredLevel) {
if (!requiredLevel?.Value) return "None";
switch (requiredLevel.Value) {
case "SystemRequired":
return "SystemRequired";
case "ApplicationRequired":
return "ApplicationRequired";
case "Recommended":
return "Recommended";
default:
return "None";
}
}
function processEntityMetadata(entityMetadata, options) {
const processed = {
logicalName: entityMetadata.LogicalName,
schemaName: entityMetadata.SchemaName,
displayName: extractDisplayName(entityMetadata.DisplayName) || entityMetadata.SchemaName,
description: extractDisplayName(entityMetadata.Description),
primaryIdAttribute: entityMetadata.PrimaryIdAttribute || "",
primaryNameAttribute: entityMetadata.PrimaryNameAttribute || "",
entitySetName: entityMetadata.EntitySetName || "",
isCustomEntity: entityMetadata.IsCustomEntity,
attributes: [],
optionSets: [],
expandableProperties: [],
oneToManyRelationships: [],
manyToOneRelationships: [],
manyToManyRelationships: [],
relatedEntities: {}
};
if (entityMetadata.Attributes) {
const allProcessedAttributes = entityMetadata.Attributes.map((attr) => processAttributeMetadata(attr));
processed.attributes = allProcessedAttributes;
processed.optionSets = extractOptionSetsFromAttributes(entityMetadata.Attributes);
const lookupFields = processed.attributes.filter((attr) => attr.attributeType === "Lookup").map((attr) => attr.schemaName);
processed.expandableProperties = [...lookupFields];
}
if (entityMetadata.OneToManyRelationships) {
processed.oneToManyRelationships = entityMetadata.OneToManyRelationships.map((rel) => ({
schemaName: rel.SchemaName,
referencedEntity: rel.ReferencedEntity,
referencedAttribute: rel.ReferencedAttribute,
referencingEntity: rel.ReferencingEntity,
referencingAttribute: rel.ReferencingAttribute,
isCustomRelationship: rel.IsCustomRelationship
}));
const oneToManyNames = processed.oneToManyRelationships.map((rel) => rel.schemaName);
processed.expandableProperties.push(...oneToManyNames);
}
if (entityMetadata.ManyToOneRelationships) {
processed.manyToOneRelationships = entityMetadata.ManyToOneRelationships.map((rel) => ({
schemaName: rel.SchemaName,
referencedEntity: rel.ReferencedEntity,
referencedAttribute: rel.ReferencedAttribute,
referencingEntity: rel.ReferencingEntity,
referencingAttribute: rel.ReferencingAttribute,
isCustomRelationship: rel.IsCustomRelationship
}));
}
if (entityMetadata.ManyToManyRelationships) {
processed.manyToManyRelationships = entityMetadata.ManyToManyRelationships.map((rel) => ({
schemaName: rel.SchemaName,
entity1LogicalName: rel.Entity1LogicalName,
entity1IntersectAttribute: rel.Entity1IntersectAttribute,
entity2LogicalName: rel.Entity2LogicalName,
entity2IntersectAttribute: rel.Entity2IntersectAttribute,
intersectEntityName: rel.IntersectEntityName,
isCustomRelationship: rel.IsCustomRelationship
}));
const manyToManyNames = processed.manyToManyRelationships.map((rel) => rel.schemaName);
processed.expandableProperties.push(...manyToManyNames);
}
processed.relatedEntities = buildRelatedEntitiesInfo(processed, options?.excludeSystemAuditRelationships);
return processed;
}
function buildRelatedEntitiesInfo(primaryEntity, excludeSystemAuditRelationships = false) {
const relatedEntities = {};
const systemAuditPatterns = [
/^lk_.*_createdby$/,
/^lk_.*_modifiedby$/,
/^lk_.*_createdonbehalfby$/,
/^lk_.*_modifiedonbehalfby$/,
/^user_.*$/,
/^systemuser_.*$/,
/^createdby_.*$/,
/^modifiedby_.*$/,
/^createdonbehalfby_.*$/,
/^modifiedonbehalfby_.*$/
];
for (const attr of primaryEntity.attributes) {
if (attr.attributeType === "Lookup" && attr.targets) {
for (const targetEntity of attr.targets) {
const relationshipName = attr.logicalName.replace(/_value$/, "");
if (relatedEntities[relationshipName]) {
continue;
}
relatedEntities[relationshipName] = {
relationshipName,
targetEntityLogicalName: targetEntity,
targetEntitySetName: `${targetEntity}s`,
// Will be corrected when we have actual metadata
relationshipType: "ManyToOne"
};
}
}
}
for (const rel of primaryEntity.oneToManyRelationships) {
if (excludeSystemAuditRelationships && primaryEntity.logicalName === "systemuser") {
const isSystemAuditRelationship = systemAuditPatterns.some(
(pattern) => pattern.test(rel.schemaName)
);
if (isSystemAuditRelationship) {
continue;
}
}
if (relatedEntities[rel.schemaName]) {
continue;
}
relatedEntities[rel.schemaName] = {
relationshipName: rel.schemaName,
targetEntityLogicalName: rel.referencingEntity,
targetEntitySetName: `${rel.referencingEntity}s`,
// Will be corrected when we have actual metadata
relationshipType: "OneToMany"
};
}
for (const rel of primaryEntity.manyToManyRelationships) {
const targetEntity = rel.entity1LogicalName === primaryEntity.logicalName ? rel.entity2LogicalName : rel.entity1LogicalName;
if (relatedEntities[rel.schemaName]) {
continue;
}
relatedEntities[rel.schemaName] = {
relationshipName: rel.schemaName,
targetEntityLogicalName: targetEntity,
targetEntitySetName: `${targetEntity}s`,
// Will be corrected when we have actual metadata
relationshipType: "ManyToMany"
};
}
return relatedEntities;
}
function extractRelatedEntityNames(metadata) {
const relatedEntityNames = /* @__PURE__ */ new Set();
for (const relatedEntity of Object.values(metadata.relatedEntities)) {
relatedEntityNames.add(relatedEntity.targetEntityLogicalName);
}
return Array.from(relatedEntityNames);
}
function processAttributeMetadata(attributeMetadata) {
const processed = {
logicalName: attributeMetadata.LogicalName,
schemaName: attributeMetadata.SchemaName,
displayName: extractDisplayName(attributeMetadata.DisplayName) || attributeMetadata.SchemaName,
description: extractDisplayName(attributeMetadata.Description),
attributeType: attributeMetadata.AttributeType,
isCustomAttribute: attributeMetadata.IsCustomAttribute,
isValidForCreate: attributeMetadata.IsValidForCreate,
isValidForRead: attributeMetadata.IsValidForRead,
isValidForUpdate: attributeMetadata.IsValidForUpdate,
requiredLevel: extractRequiredLevel(attributeMetadata.RequiredLevel),
isPrimaryId: attributeMetadata.IsPrimaryId || false,
isPrimaryName: attributeMetadata.IsPrimaryName || false,
attributeOf: attributeMetadata.AttributeOf
};
const odataType = attributeMetadata["@odata.type"];
const normalizedODataType = odataType?.startsWith("#") ? odataType.substring(1) : odataType;
switch (normalizedODataType) {
case "Microsoft.Dynamics.CRM.PicklistAttributeMetadata":
processPicklistAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata":
processMultiSelectPicklistAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.LookupAttributeMetadata":
processLookupAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.StateAttributeMetadata":
case "Microsoft.Dynamics.CRM.StatusAttributeMetadata":
processStateStatusAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.StringAttributeMetadata":
processStringAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.IntegerAttributeMetadata":
case "Microsoft.Dynamics.CRM.BigIntAttributeMetadata":
case "Microsoft.Dynamics.CRM.DecimalAttributeMetadata":
case "Microsoft.Dynamics.CRM.DoubleAttributeMetadata":
case "Microsoft.Dynamics.CRM.MoneyAttributeMetadata":
processNumericAttribute(processed, attributeMetadata);
break;
case "Microsoft.Dynamics.CRM.DateTimeAttributeMetadata":
processDateTimeAttribute(processed, attributeMetadata);
break;
}
return processed;
}
function processPicklistAttribute(processed, attr) {
if (attr.OptionSet) {
processed.optionSetName = attr.OptionSet.Name || `${processed.logicalName}_options`;
}
if (attr.GlobalOptionSet) {
processed.globalOptionSet = attr.GlobalOptionSet;
}
}
function processMultiSelectPicklistAttribute(processed, attr) {
processed.isMultiSelect = true;
if (attr.OptionSet) {
processed.optionSetName = attr.OptionSet.Name || `${processed.logicalName}_options`;
}
if (attr.GlobalOptionSet) {
processed.globalOptionSet = attr.GlobalOptionSet;
}
}
function processLookupAttribute(processed, attr) {
processed.targets = attr.Targets || [];
processed.format = attr.Format;
}
function processStateStatusAttribute(processed, attr) {
if (attr.OptionSet) {
processed.optionSetName = attr.OptionSet.Name || `${processed.logicalName}_options`;
}
}
function processStringAttribute(processed, attr) {
if (attr.MaxLength !== void 0) {
processed.maxLength = attr.MaxLength;
}
if (attr.Format) {
processed.format = attr.Format;
}
}
function processNumericAttribute(processed, attr) {
if (attr.MinValue !== void 0) {
processed.minValue = attr.MinValue;
}
if (attr.MaxValue !== void 0) {
processed.maxValue = attr.MaxValue;
}
if (attr.Precision !== void 0) {
processed.precision = attr.Precision;
}
}
function processDateTimeAttribute(processed, attr) {
if (attr.Format) {
processed.format = attr.Format;
}
}
function extractOptionSetsFromAttributes(attributes) {
const optionSets = [];
for (const attr of attributes) {
const odataType = attr["@odata.type"];
const normalizedODataType = odataType?.startsWith("#") ? odataType.substring(1) : odataType;
if (normalizedODataType?.includes("PicklistAttributeMetadata") || normalizedODataType?.includes("StateAttributeMetadata") || normalizedODataType?.includes("StatusAttributeMetadata")) {
const picklistAttr = attr;
if (picklistAttr.OptionSet && !picklistAttr.GlobalOptionSet) {
const processedOptionSet = processOptionSet(picklistAttr.OptionSet, attr.LogicalName);
if (processedOptionSet) {
optionSets.push(processedOptionSet);
}
}
}
}
return optionSets;
}
function processOptionSet(optionSetMetadata, fallbackName) {
if (!optionSetMetadata?.Options) return null;
const processed = {
name: optionSetMetadata.Name || fallbackName || "unknown_optionset",
displayName: extractDisplayName(optionSetMetadata.DisplayName) || optionSetMetadata.Name || fallbackName || "Unknown",
description: extractDisplayName(optionSetMetadata.Description),
isGlobal: optionSetMetadata.IsGlobal || false,
isCustom: optionSetMetadata.IsCustomOptionSet || optionSetMetadata.IsCustomOptionSet || false,
options: []
};
processed.options = optionSetMetadata.Options.map((option) => ({
value: option.Value,
label: extractDisplayName(option.Label) || option.Value.toString(),
description: extractDisplayName(option.Description)
}));
return processed;
}
function processGlobalOptionSet(globalOptionSet) {
return {
name: globalOptionSet.Name,
displayName: extractDisplayName(globalOptionSet.DisplayName) || globalOptionSet.Name,
description: extractDisplayName(globalOptionSet.Description),
isGlobal: globalOptionSet.IsGlobal,
isCustom: globalOptionSet.IsCustomOptionSet,
options: globalOptionSet.Options.map((option) => ({
value: option.Value,
label: extractDisplayName(option.Label) || option.Value.toString(),
description: extractDisplayName(option.Description)
}))
};
}
function mergeOptionSets(optionSets) {
const uniqueOptionSets = /* @__PURE__ */ new Map();
for (const optionSet of optionSets) {
const key = optionSet.name;
if (!uniqueOptionSets.has(key)) {
uniqueOptionSets.set(key, optionSet);
}
}
return Array.from(uniqueOptionSets.values());
}
function filterAttributesByType(attributes, types) {
return attributes.filter((attr) => types.includes(attr.attributeType));
}
function getPrimaryAttributes(attributes) {
return {
primaryId: attributes.find((attr) => attr.isPrimaryId),
primaryName: attributes.find((attr) => attr.isPrimaryName)
};
}
function groupAttributesByType(attributes) {
const groups = {};
for (const attr of attributes) {
if (!groups[attr.attributeType]) {
groups[attr.attributeType] = [];
}
groups[attr.attributeType].push(attr);
}
return groups;
}
function getLookupAttributes(attributes) {
return attributes.filter((attr) => attr.attributeType === "Lookup" && attr.targets).map((attr) => ({ ...attr, targets: attr.targets }));
}
function getOptionSetAttributes(attributes) {
return attributes.filter(
(attr) => attr.attributeType === "Picklist" || attr.attributeType === "MultiSelectPicklist" || attr.attributeType === "State" || attr.attributeType === "Status"
);
}
function filterAuxiliaryAttributes(attributes) {
return attributes.filter((attr) => !attr.attributeOf);
}
function validateProcessedMetadata(metadata) {
const errors = [];
const warnings = [];
if (!metadata.logicalName) {
errors.push("Missing logical name");
}
if (!metadata.schemaName) {
errors.push("Missing schema name");
}
if (!metadata.primaryIdAttribute) {
warnings.push("Missing primary ID attribute");
}
if (!metadata.primaryNameAttribute) {
warnings.push("Missing primary name attribute");
}
if (metadata.attributes.length === 0) {
warnings.push("No attributes found");
}
const logicalNames = /* @__PURE__ */ new Set();
for (const attr of metadata.attributes) {
if (logicalNames.has(attr.logicalName)) {
errors.push(`Duplicate attribute logical name: ${attr.logicalName}`);
}
logicalNames.add(attr.logicalName);
}
for (const optionSet of metadata.optionSets) {
if (optionSet.options.length === 0) {
warnings.push(`Option set '${optionSet.name}' has no options`);
}
const values = /* @__PURE__ */ new Set();
for (const option of optionSet.options) {
if (values.has(option.value)) {
errors.push(`Duplicate option value in '${optionSet.name}': ${option.value}`);
}
values.add(option.value);
}
}
return {
isValid: errors.length === 0,
errors,
warnings
};
}
export { extractOptionSetsFromAttributes, extractRelatedEntityNames, filterAttributesByType, filterAuxiliaryAttributes, getLookupAttributes, getOptionSetAttributes, getPrimaryAttributes, groupAttributesByType, mergeOptionSets, processAttributeMetadata, processEntityMetadata, processGlobalOptionSet, processOptionSet, validateProcessedMetadata };
//# sourceMappingURL=chunk-IF2SU3NK.mjs.map
//# sourceMappingURL=chunk-IF2SU3NK.mjs.map