UNPKG

dataverse-type-gen

Version:

TypeScript type generator for Dataverse entities and metadata

388 lines (387 loc) 15.3 kB
// 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