UNPKG

dataverse-type-gen

Version:

TypeScript type generator for Dataverse entities and metadata

1,350 lines (1,337 loc) 228 kB
'use strict'; var identity = require('@azure/identity'); var crypto = require('crypto'); var fs = require('fs'); var path = require('path'); var tsMorph = require('ts-morph'); var commander = require('commander'); var chalk = require('chalk'); var readline = require('readline'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var crypto__default = /*#__PURE__*/_interopDefault(crypto); var chalk__default = /*#__PURE__*/_interopDefault(chalk); var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/processors/index.ts var processors_exports = {}; __export(processors_exports, { extractOptionSetsFromAttributes: () => extractOptionSetsFromAttributes, extractRelatedEntityNames: () => extractRelatedEntityNames, filterAttributesByType: () => filterAttributesByType, filterAuxiliaryAttributes: () => filterAuxiliaryAttributes, getLookupAttributes: () => getLookupAttributes, getOptionSetAttributes: () => getOptionSetAttributes, getPrimaryAttributes: () => getPrimaryAttributes, groupAttributesByType: () => groupAttributesByType, mergeOptionSets: () => mergeOptionSets, processAttributeMetadata: () => processAttributeMetadata, processEntityMetadata: () => processEntityMetadata, processGlobalOptionSet: () => processGlobalOptionSet, processOptionSet: () => processOptionSet, validateProcessedMetadata: () => validateProcessedMetadata }); 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 }; } var init_processors = __esm({ "src/processors/index.ts"() { } }); // src/error-logger.ts async function advancedLog(response, requestUrl, requestMethod, requestBody) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const headers = {}; response.headers.forEach((value, key) => { headers[key] = value; }); const requestId = headers["request-id"] || headers["x-ms-request-id"] || headers["activityid"]; let errorDetails = { status: response.status, statusText: response.statusText, url: requestUrl || response.url, method: requestMethod || "GET", headers, timestamp, requestId }; try { const responseClone = response.clone(); const responseText = await responseClone.text(); errorDetails.body = responseText; if (responseText) { try { const errorResponse = JSON.parse(responseText); if (errorResponse.error) { errorDetails.error = errorResponse.error; } } catch (parseError) { console.warn("Failed to parse error response as JSON:", parseError); } } } catch (textError) { console.warn("Failed to read response body:", textError); } console.error("\u{1F6A8} Dataverse API Error Details:"); console.error("\u2501".repeat(60)); console.error(`\u23F0 Timestamp: ${errorDetails.timestamp}`); console.error(`\u{1F310} URL: ${errorDetails.url}`); console.error(`\u{1F4CB} Method: ${errorDetails.method}`); console.error(`\u{1F4CA} Status: ${errorDetails.status} ${errorDetails.statusText}`); if (errorDetails.requestId) { console.error(`\u{1F194} Request ID: ${errorDetails.requestId}`); } if (errorDetails.error) { console.error("\u274C Error Details:"); console.error(` Code: ${errorDetails.error.code}`); console.error(` Message: ${errorDetails.error.message}`); if (errorDetails.error["@Microsoft.PowerApps.CDS.ErrorDetails.OperationStatus"]) { console.error(` Operation Status: ${errorDetails.error["@Microsoft.PowerApps.CDS.ErrorDetails.OperationStatus"]}`); } if (errorDetails.error["@Microsoft.PowerApps.CDS.ErrorDetails.SubErrorCode"]) { console.error(` Sub Error Code: ${errorDetails.error["@Microsoft.PowerApps.CDS.ErrorDetails.SubErrorCode"]}`); } if (errorDetails.error["@Microsoft.PowerApps.CDS.HelpLink"]) { console.error(` Help Link: ${errorDetails.error["@Microsoft.PowerApps.CDS.HelpLink"]}`); } if (errorDetails.error["@Microsoft.PowerApps.CDS.TraceText"]) { console.error(` Trace Text:`); console.error(` ${errorDetails.error["@Microsoft.PowerApps.CDS.TraceText"]}`); } if (errorDetails.error["@Microsoft.PowerApps.CDS.InnerError.Message"]) { console.error(` Inner Error: ${errorDetails.error["@Microsoft.PowerApps.CDS.InnerError.Message"]}`); } } else if (errorDetails.body) { console.error("\u{1F4C4} Raw Response Body:"); console.error(errorDetails.body); } console.error("\u{1F4CB} Request Headers:"); Object.entries(headers).forEach(([key, value]) => { if (key.toLowerCase().includes("authorization") || key.toLowerCase().includes("cookie")) { console.error(` ${key}: [REDACTED]`); } else { console.error(` ${key}: ${value}`); } }); if (requestBody) { console.error("\u{1F4E4} Request Body:"); console.error(requestBody); } console.error("\u2501".repeat(60)); return errorDetails; } function getErrorCodeDescription(code) { const errorDescriptions = { "0x80040265": "Generic custom business logic error", "0x80040333": "SQL timeout expired", "0x80040888": "Invalid query parameter", "0x8004431A": "Cannot add or act on behalf of another user privilege", "0x80044150": "Principal privilege denied", "0x80040220": "Cannot assign to disabled user", "0x80040225": "Cannot assign to inactive business unit", "0x80040237": "Cannot delete due to association", "0x80040216": "Cannot create activity for inactive campaign", "0x80040274": "Duplicate record", "0x80040264": "Invalid argument", "0x80040217": "Invalid operation", "0x80040266": "Not supported", "0x80040313": "Concurrency version mismatch", "0x80040201": "Access denied", "0x80040277": "Attribute permission read is missing", "0x80040278": "Attribute permission update is missing during update", "0x80040234": "Attribute privilege create is missing", "0x80040231": "Cannot act on behalf of another user", "0x80040230": "CRM security error", "0x80040200": "Invalid access rights", "0x80040276": "Privilege create is disabled for organization", "0x80040221": "Privilege denied" }; return errorDescriptions[code] || "Unknown error code"; } function getStatusCodeDescription(status) { const statusDescriptions = { 400: "Bad Request - Invalid request syntax or unsupported query parameters", 401: "Unauthorized - Authentication failed or insufficient permissions", 403: "Forbidden - Access denied or insufficient privileges", 404: "Not Found - Resource does not exist", 405: "Method Not Allowed - HTTP method not supported for this resource", 412: "Precondition Failed - Concurrency or duplicate record issues", 413: "Payload Too Large - Request size exceeds limits", 429: "Too Many Requests - API rate limits exceeded", 501: "Not Implemented - Operation not supported", 503: "Service Unavailable - Dataverse service temporarily unavailable" }; return statusDescriptions[status] || "Unknown status code"; } var memoryCache = /* @__PURE__ */ new Map(); function generateCacheKey(url, options = {}) { const normalizedUrl = url.toLowerCase().trim(); const method = (options.method || "GET").toUpperCase(); const relevantHeaders = {}; if (options.headers) { const headers = options.headers; for (const [key, value] of Object.entries(headers)) { const lowerKey = key.toLowerCase(); if (lowerKey.includes("accept") || lowerKey.includes("prefer")) { relevantHeaders[lowerKey] = value; } } } const keyData = { url: normalizedUrl, method, headers: relevantHeaders }; return crypto__default.default.createHash("sha256").update(JSON.stringify(keyData)).digest("hex"); } async function getFromCache(url, options = {}) { const cacheKey = generateCacheKey(url, options); const memoryHit = memoryCache.get(cacheKey); if (memoryHit) { return new Response(memoryHit.body, { status: memoryHit.status, statusText: memoryHit.statusText, headers: new Headers(memoryHit.headers) }); } return null; } async function saveToCache(url, options = {}, response) { const cacheKey = generateCacheKey(url, options); if (response.ok && response.status === 200) { const body = await response.clone().text(); const headers = {}; response.headers.forEach((value, key) => { headers[key] = value; }); const cachedResponse = { body, status: response.status, statusText: response.statusText, headers }; memoryCache.set(cacheKey, cachedResponse); } } function clearMemoryCache() { const clearedCount = memoryCache.size; memoryCache.clear(); return clearedCount; } function getMemoryCacheStatus() { return { entryCount: memoryCache.size }; } var TokenManager = class { tokenCache = /* @__PURE__ */ new Map(); credential; bufferTimeMs = 5 * 60 * 1e3; // Refresh 5 minutes before expiry constructor(credential) { this.credential = credential || new identity.DefaultAzureCredential(); } /** * Get a cached token or acquire a new one * This method is the key to performance - it ensures we only get 1 token per resource per run */ async getToken(resourceUrl) { const normalizedUrl = this.normalizeResourceUrl(resourceUrl); const cacheKey = normalizedUrl; const cached = this.tokenCache.get(cacheKey); if (cached && this.isTokenValid(cached)) { return cached.token; } try { const baseUrl = normalizedUrl.endsWith("/") ? normalizedUrl.slice(0, -1) : normalizedUrl; const scope = `${baseUrl}/.default`; const tokenResponse = await this.credential.getToken([scope]); if (!tokenResponse?.token) { throw new Error("No token received from credential"); } const cachedToken = { token: tokenResponse.token, expiresAt: tokenResponse.expiresOnTimestamp, resourceUrl: normalizedUrl }; this.tokenCache.set(cacheKey, cachedToken); return tokenResponse.token; } catch (error) { throw new Error( `Failed to acquire Azure access token for resource: ${resourceUrl} Error: ${error instanceof Error ? error.message : String(error)} Please authenticate via one of these methods: \u2022 Azure CLI: az login \u2022 Environment variables: AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID \u2022 Managed Identity (when running in Azure)` ); } } /** * Pre-acquire token for a resource to avoid delays during API calls */ async preAcquireToken(resourceUrl) { await this.getToken(resourceUrl); } /** * Check if a cached token is still valid (with buffer time) */ isTokenValid(cached) { const now = Date.now(); const expiresWithBuffer = cached.expiresAt - this.bufferTimeMs; return now < expiresWithBuffer; } /** * Normalize resource URL for consistent caching */ normalizeResourceUrl(resourceUrl) { if (!resourceUrl) { throw new Error("Resource URL is required for authentication"); } try { const url = new URL(resourceUrl); return `${url.protocol}//${url.host}`; } catch { throw new Error(`Invalid resource URL: ${resourceUrl}`); } } /** * Clear all cached tokens (useful for testing) */ clearCache() { this.tokenCache.clear(); } /** * Get cache statistics */ getCacheStats() { const totalTokens = this.tokenCache.size; let validTokens = 0; for (const cached of this.tokenCache.values()) { if (this.isTokenValid(cached)) { validTokens++; } } return { totalTokens, validTokens }; } }; var globalTokenManager = null; function getTokenManager(credential) { if (!globalTokenManager) { globalTokenManager = new TokenManager(credential); } return globalTokenManager; } async function preAcquireGlobalToken(resourceUrl, credential) { const manager = getTokenManager(credential); await manager.preAcquireToken(resourceUrl); } // src/auth/index.ts async function getAzureToken(options) { const { resourceUrl, credential } = options; try { const normalizedUrl = validateResourceUrl(resourceUrl); const useCredential = credential || new identity.DefaultAzureCredential(); const baseUrl = normalizedUrl.endsWith("/") ? normalizedUrl.slice(0, -1) : normalizedUrl; const scope = `${baseUrl}/.default`; const tokenResponse = await useCredential.getToken([scope]); return tokenResponse?.token || null; } catch (error) { throw new Error( `Failed to acquire Azure access token for resource: ${resourceUrl} Error: ${error instanceof Error ? error.message : String(error)} Please authenticate via one of these methods: \u2022 Azure CLI: az login \u2022 Environment variables: AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID \u2022 Managed Identity (when running in Azure)` ); } } function validateResourceUrl(resourceUrl) { if (!resourceUrl) { throw new Error("Resource URL is required for authentication"); } try { const url = new URL(resourceUrl); return `${url.protocol}//${url.host}`; } catch { throw new Error(`Invalid resource URL: ${resourceUrl}`); } } function createAuthenticatedFetcher(resourceUrl, options = {}) { const { maxRetries = 3, baseDelay = 1e3, credential } = options; return async function authenticatedFetch(url, requestOptions = {}) { const fullUrl = url.startsWith("http") ? url : `${resourceUrl.replace(/\/$/, "")}${url}`; let lastError = null; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { const tokenManager = getTokenManager(credential); const accessToken = await tokenManager.getToken(resourceUrl); if (!accessToken) { throw new Error("Failed to acquire access token"); } if (!requestOptions.method || requestOptions.method.toUpperCase() === "GET") { const cachedResponse = await getFromCache(fullUrl, requestOptions); if (cachedResponse) { return cachedResponse; } } const headers = new Headers(requestOptions.headers); headers.set("Authorization", `Bearer ${accessToken}`); headers.set("Accept", "application/json"); headers.set("Content-Type", "application/json"); headers.set("Prefer", 'odata.include-annotations="*"'); const response = await fetch(fullUrl, { ...requestOptions, headers }); if (response.ok && response.headers.get("content-type")?.includes("application/json")) { } if (response.ok && (!requestOptions.method || requestOptions.method.toUpperCase() === "GET")) { await saveToCache(fullUrl, requestOptions, response.clone()); } if (response.status === 429) { const retryAfter = parseInt(response.headers.get("Retry-After") || "5") * 1e3; if (attempt < maxRetries) { await new Promise((resolve3) => setTimeout(resolve3, retryAfter)); continue; } } if (!response.ok) { await advancedLog(response, fullUrl, requestOptions.method || "GET", requestOptions.body); } return response; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < maxRetries) { const delay = baseDelay * Math.pow(2, attempt); await new Promise((resolve3) => setTimeout(resolve3, delay)); } } } throw lastError || new Error("Authentication failed after all retries"); }; } // src/metadata-client.ts function getAuthenticatedFetch() { const dataverseUrl = process.env.DATAVERSE_INSTANCE; if (!dataverseUrl) { throw new Error("DATAVERSE_INSTANCE environment variable is required"); } return createAuthenticatedFetcher(dataverseUrl); } async function getEntityDefinitions(options = {}) { const baseUrl = "/api/data/v9.1/EntityDefinitions"; const params = new URLSearchParams(); if (options.select && options.select.length > 0) { params.append("$select", options.select.join(",")); } if (options.filter) { params.append("$filter", options.filter); } if (options.expand && options.expand.length > 0) { params.append("$expand", options.expand.join(",")); } const url = params.toString() ? `${baseUrl}?${params.toString()}` : baseUrl; const authenticatedFetch = getAuthenticatedFetch(); const response = await authenticatedFetch(url, { method: "GET" }); if (!response.ok) { await advancedLog(response, url, "GET"); const statusDescription = getStatusCodeDescription(response.status); throw new Error(`Failed to fetch entity definitions: ${response.status} ${response.statusText} - ${statusDescription}`); } const data = await response.json(); return data.value; } async function getEntityDefinition(logicalName) { const results = await getEntityDefinitions({ filter: `LogicalName eq '${logicalName}'`, select: ["LogicalName", "SchemaName", "DisplayName", "Description", "HasActivities", "HasFeedback", "HasNotes", "IsActivity", "IsCustomEntity", "OwnershipType", "PrimaryIdAttribute", "PrimaryNameAttribute", "EntitySetName"], expand: ["Attributes"] }); return results.length > 0 ? results[0] : null; } // src/client/concurrent-queue.ts var ConcurrentRequestQueue = class { activeRequests = 0; maxConcurrent; queue = []; // eslint-disable-line @typescript-eslint/no-explicit-any rateLimitInfo = {}; requestHistory = []; totalRequestsCounter = 0; // Track total API calls made lastProgressLog = 0; // Track when we last logged progress constructor(maxConcurrent = 25) { const platformMaxConcurrent = process.platform === "win32" ? 15 : 25; this.maxConcurrent = Math.min(maxConcurrent, platformMaxConcurrent); } /** * Execute a request with automatic queuing and retry */ async execute(request, options = {}) { const { maxRetries = 3, priority = 0 } = options; return new Promise((resolve3, reject) => { const queuedRequest = { execute: async () => { try { this.activeRequests++; this.totalRequestsCounter++; this.recordRequest(); const response = await request(); this.updateRateLimitInfo(response); if (response.status === 429) { const retryAfter = this.parseRetryAfter(response); if (queuedRequest.retryCount < queuedRequest.maxRetries) { console.warn(`Rate limited (429), retrying after ${retryAfter}ms...`); await this.delay(retryAfter); queuedRequest.retryCount++; this.queue.unshift(queuedRequest); return; } else { throw new Error(`Rate limit exceeded after ${maxRetries} retries`); } } if (!response.ok) { if (options.url) { await advancedLog(response, options.url, options.method || "GET"); } throw new Error(`Request failed: ${response.status} ${response.statusText}`); } const result = await response.json(); resolve3(result); } catch (error) { if (queuedRequest.retryCount < queuedRequest.maxRetries && this.isRetryableError(error)) { console.warn(`Request failed, retrying (${queuedRequest.retryCount + 1}/${queuedRequest.maxRetries})...`, error); queuedRequest.retryCount++; await this.delay(Math.pow(2, queuedRequest.retryCount) * 1e3); this.queue.unshift(queuedRequest); } else { reject(error); } } finally { this.activeRequests--; this.processNext(); } }, resolve: resolve3, reject, retryCount: 0, maxRetries, priority }; const insertIndex = this.queue.findIndex((req) => req.priority < priority); if (insertIndex === -1) { this.queue.push(queuedRequest); } else { this.queue.splice(insertIndex, 0, queuedRequest); } this.processNext(); }); } /** * Process the next request in the queue if capacity allows */ async processNext() { if (this.shouldThrottle()) { const delay = this.calculateThrottleDelay(); if (delay > 0) { setTimeout(() => this.processNext(), delay); return; } } if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) { return; } const request = this.queue.shift(); if (request) { request.execute().catch((error) => { console.error("Queue request execution failed:", error); }); } } /** * Update rate limit information from response headers */ updateRateLimitInfo(response) { const remainingRequests = response.headers.get("x-ms-ratelimit-burst-remaining-xrm-requests"); const remainingDuration = response.headers.get("x-ms-ratelimit-time-remaining-xrm-requests"); const retryAfter = response.headers.get("Retry-After"); if (remainingRequests) { this.rateLimitInfo.remainingRequests = parseInt(remainingRequests, 10); } if (remainingDuration) { this.rateLimitInfo.remainingDuration = parseInt(remainingDuration, 10); } if (retryAfter) { this.rateLimitInfo.retryAfter = parseInt(retryAfter, 10); } } /** * Parse Retry-After header value */ parseRetryAfter(response) { const retryAfter = response.headers.get("Retry-After"); if (!retryAfter) return 1e3; const seconds = parseInt(retryAfter, 10); return isNaN(seconds) ? 1e3 : seconds * 1e3; } /** * Record request timestamp for rate limiting */ recordRequest() { const now = Date.now(); this.requestHistory.push(now); const fiveMinutesAgo = now - 5 * 60 * 1e3; while (this.requestHistory.length > 0 && this.requestHistory[0] < fiveMinutesAgo) { this.requestHistory.shift(); } } /** * Check if we should throttle requests based on rate limits */ shouldThrottle() { if (this.rateLimitInfo.remainingRequests !== void 0) { return this.rateLimitInfo.remainingRequests < 100; } const requestsInLast5Minutes = this.requestHistory.length; return requestsInLast5Minutes > 5e3; } /** * Calculate delay for throttling */ calculateThrottleDelay() { if (this.rateLimitInfo.retryAfter) { return this.rateLimitInfo.retryAfter * 1e3; } if (this.rateLimitInfo.remainingDuration && this.rateLimitInfo.remainingRequests) { const averageDelay = this.rateLimitInfo.remainingDuration / this.rateLimitInfo.remainingRequests; return Math.max(averageDelay * 1e3, 100); } const requestsInLast5Minutes = this.requestHistory.length; if (requestsInLast5Minutes > 5e3) { return Math.min((requestsInLast5Minutes - 5e3) * 10, 5e3); } return 0; } /** * Check if an error is retryable */ isRetryableError(error) { if (error instanceof Error) { const message = error.message.toLowerCase(); return message.includes("timeout") || message.includes("network") || message.includes("connection") || message.includes("429"); } return false; } /** * Delay utility */ delay(ms) { return new Promise((resolve3) => setTimeout(resolve3, ms)); } /** * Get current queue status */ getStatus() { return { activeRequests: this.activeRequests, queuedRequests: this.queue.length, rateLimitInfo: { ...this.rateLimitInfo }, requestsInLast5Minutes: this.requestHistory.length, totalRequestsMade: this.totalRequestsCounter }; } /** * Log final statistics */ logFinalStats() { } /** * Wait for all active requests to complete */ async waitForCompletion() { while (this.activeRequests > 0 || this.queue.length > 0) { await this.delay(100); } } }; var globalRequestQueue = new ConcurrentRequestQueue(); // src/client/or-filter-batching.ts function getAuthenticatedFetch2() { const dataverseUrl = process.env.DATAVERSE_INSTANCE; if (!dataverseUrl) { throw new Error("DATAVERSE_INSTANCE environment variable is required"); } return createAuthenticatedFetcher(dataverseUrl); } var OR_BATCH_SIZE = 15; async function fetchEntitiesWithOrFilter(entityNames, select = [ "LogicalName", "SchemaName", "DisplayName", "Description", "HasActivities", "HasFeedback", "HasNotes", "IsActivity", "IsCustomEntity", "OwnershipType", "PrimaryIdAttribute", "PrimaryNameAttribute", "EntitySetName" ], onProgress) { if (entityNames.length === 0) { return []; } const allEntities = []; let processedCount = 0; for (let i = 0; i < entityNames.length; i += OR_BATCH_SIZE) { const batch = entityNames.slice(i, i + OR_BATCH_SIZE); try { const batchEntities = await fetchEntityBatchWithOrFilter(batch, select); allEntities.push(...batchEntities); processedCount += batch.length; if (onProgress) { const lastEntityInBatch = batch[batch.length - 1]; onProgress(processedCount, entityNames.length, lastEntityInBatch); } } catch (error) { console.warn(`Failed to fetch entity batch [${batch.join(", ")}]:`, error); console.log(`Falling back to individual requests for batch: ${batch.join(", ")}`); for (const entityName of batch) { try { const entity = await fetchSingleEntityWithQueue(entityName, select); if (entity) { allEntities.push(entity); } processedCount++; if (onProgress) { onProgress(processedCount, entityNames.length, entityName); } } catch (singleError) { console.warn(`Failed to fetch individual entity '${entityName}':`, singleError); processedCount++; if (onProgress) { onProgress(processedCount, entityNames.length, entityName); } } } } } return allEntities; } async function fetchEntityBatchWithOrFilter(entityNames, select) { const orConditions = entityNames.map((name) => `LogicalName eq '${name}'`); const filter = orConditions.join(" or "); const params = new URLSearchParams(); if (select.length > 0) { params.append("$select", select.join(",")); } params.append("$filter", filter); const url = `/api/data/v9.1/EntityDefinitions?${params.toString()}`; const response = await globalRequestQueue.execute( () => getAuthenticatedFetch2()(url, { method: "GET" }), { url, method: "GET", priority: 1 // High priority for entity metadata } ); return response.value || []; } async function fetchSingleEntityWithQueue(entityLogicalName, select) { try { const params = new URLSearchParams(); if (select.length > 0) { params.append("$select", select.join(",")); } const url = `/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')${params.toString() ? `?${params.toString()}` : ""}`; const response = await globalRequestQueue.execute( () => getAuthenticatedFetch2()(url, { method: "GET" }), { url, method: "GET", priority: 1 } ); return response; } catch (error) { if (error instanceof Error && error.message.includes("404")) { return null; } throw error; } } // src/client/index.ts function getAuthenticatedFetch3() { const dataverseUrl = process.env.DATAVERSE_INSTANCE; if (!dataverseUrl) { throw new Error("DATAVERSE_INSTANCE environment variable is required"); } return createAuthenticatedFetcher(dataverseUrl); } var COMPONENT_TYPES = { ENTITY: 1, ATTRIBUTE: 2, RELATIONSHIP: 10, OPTION_SET: 9 // Add more as needed }; async function fetchEntityMetadata(entityLogicalName, options = {}) { const { includeAttributes = true, includeRelationships = false, select = [ "LogicalName", "SchemaName", "DisplayName", "Description", "HasActivities", "HasFeedback", "HasNotes", "IsActivity", "IsCustomEntity", "OwnershipType", "PrimaryIdAttribute", "PrimaryNameAttribute", "EntitySetName" ] } = options; try { const params = new URLSearchParams(); if (select.length > 0) { params.append("$select", select.join(",")); } const expandItems = []; if (includeAttributes) { expandItems.push("Attributes"); } if (includeRelationships) { expandItems.push("OneToManyRelationships", "ManyToOneRelationships", "ManyToManyRelationships"); } if (expandItems.length > 0) { params.append("$expand", expandItems.join(",")); } const url = `/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')${params.toString() ? `?${params.toString()}` : ""}`; const authenticatedFetch = getAuthenticatedFetch3(); const response = await authenticatedFetch(url, { method: "GET" }); if (response.status === 404) { return null; } if (!response.ok) { await advancedLog(response, url, "GET"); const statusDescription = getStatusCodeDescription(response.status); throw new Error(`Failed to fetch entity metadata for '${entityLogicalName}': ${response.status} ${response.statusText} - ${statusDescription}`); } const entityData = await response.json(); if (includeAttributes && entityData) { try { const detailedAttributes = await fetchEntityAttributes(entityLogicalName); entityData.Attributes = detailedAttributes; } catch (error) { console.warn(`Failed to fetch detailed attributes for ${entityLogicalName}, using basic attributes:`, error); } } return entityData; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching entity metadata for '${entityLogicalName}': ${error.message}`); } throw error; } } async function fetchAllEntityMetadata(options = {}) { console.log(`\u{1F30D} Fetching ALL entities from Dataverse for complete type safety...`); try { const allEntities = await fetchAllEntities({ select: [ "LogicalName", "SchemaName", "DisplayName", "Description", "HasActivities", "HasFeedback", "HasNotes", "IsActivity", "IsCustomEntity", "OwnershipType", "PrimaryIdAttribute", "PrimaryNameAttribute", "EntitySetName" ] }); console.log(`Found ${allEntities.length} total entities in Dataverse`); if (options.includeAttributes) { const entitiesWithAttributes = await fetchMultipleEntities( allEntities.map((e) => e.LogicalName), { ...options, onProgress: options.onProgress } ); console.log(`\u2705 Successfully fetched detailed metadata for ${entitiesWithAttributes.length} entities`); return entitiesWithAttributes; } return allEntities; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching all entity metadata: ${error.message}`); } throw error; } } async function fetchMultipleEntitiesWithRelated(entityNames, options = {}) { const { includeRelatedEntities = false, ...baseOptions } = options; console.log(`Fetching metadata for ${entityNames.length} primary entities...`); const primaryEntities = await fetchMultipleEntities(entityNames, { ...baseOptions, includeRelationships: includeRelatedEntities }); const relatedEntities = /* @__PURE__ */ new Map(); if (includeRelatedEntities && primaryEntities.length > 0) { const { processEntityMetadata: processEntityMetadata2, extractRelatedEntityNames: extractRelatedEntityNames2 } = await Promise.resolve().then(() => (init_processors(), processors_exports)); const allRelatedEntityNames = /* @__PURE__ */ new Set(); for (const entity of primaryEntities) { const processed = processEntityMetadata2(entity); const relatedNames = extractRelatedEntityNames2(processed); relatedNames.forEach((name) => allRelatedEntityNames.add(name)); } entityNames.forEach((name) => allRelatedEntityNames.delete(name)); const relatedEntityArray = Array.from(allRelatedEntityNames); if (relatedEntityArray.length > 0) { console.log(`Discovering ${relatedEntityArray.length} related entities...`); console.log(`Fetching metadata for related entities...`); const relatedEntitiesArray = await fetchMultipleEntities(relatedEntityArray, baseOptions); for (const entity of relatedEntitiesArray) { relatedEntities.set(entity.LogicalName, entity); } console.log(`\u2705 Successfully fetched ${relatedEntitiesArray.length} related entities`); } } return { primaryEntities, relatedEntities }; } async function fetchEntityAttributes(entityLogicalName, attributeTypes = [ "Microsoft.Dynamics.CRM.PicklistAttributeMetadata", "Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata", "Microsoft.Dynamics.CRM.StateAttributeMetadata", "Microsoft.Dynamics.CRM.StatusAttributeMetadata", "Microsoft.Dynamics.CRM.LookupAttributeMetadata" ]) { try { const basicUrl = `/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes?$select=LogicalName,SchemaName,DisplayName,Description,AttributeType,IsCustomAttribute,IsValidForCreate,IsValidForRead,IsValidForUpdate,RequiredLevel,IsPrimaryId,IsPrimaryName,AttributeOf`; const basicData = await globalRequestQueue.execute( () => getAuthenticatedFetch3()(basicUrl, { method: "GET" }), { url: basicUrl, method: "GET", priority: 2 // High priority for basic attributes } ); const basicAttributes = basicData.value; const existingAttributeTypes = /* @__PURE__ */ new Set(); for (const attr of basicAttributes) { if (attr.AttributeType === "Picklist") { existingAttributeTypes.add("Microsoft.Dynamics.CRM.PicklistAttributeMetadata"); } else if (attr.AttributeType === "MultiSelectPicklist") { existingAttributeTypes.add("Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata"); } else if (attr.AttributeType === "State") { existingAttributeTypes.add("Microsof