UNPKG

dataverse-type-gen

Version:

TypeScript type generator for Dataverse entities and metadata

1,332 lines (1,322 loc) 208 kB
import { __require, processEntityMetadata } from './chunk-K2HNXLZW.mjs'; export { extractOptionSetsFromAttributes, extractRelatedEntityNames, filterAttributesByType, filterAuxiliaryAttributes, getLookupAttributes, getOptionSetAttributes, getPrimaryAttributes, groupAttributesByType, mergeOptionSets, processAttributeMetadata, processEntityMetadata, processGlobalOptionSet, processOptionSet, validateProcessedMetadata } from './chunk-K2HNXLZW.mjs'; import { DefaultAzureCredential } from '@azure/identity'; import crypto from 'crypto'; import { promises } from 'fs'; import { join, dirname, relative, resolve } from 'path'; import { Project, ModuleKind, ScriptTarget } from 'ts-morph'; import { Command } from 'commander'; import chalk from 'chalk'; import { createInterface } from 'readline'; // 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.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 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 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 import('./processors-IZ3LUUEG.mjs'); 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("Microsoft.Dynamics.CRM.StateAttributeMetadata"); } else if (attr.AttributeType === "Status") { existingAttributeTypes.add("Microsoft.Dynamics.CRM.StatusAttributeMetadata"); } else if (attr.AttributeType === "Lookup" || attr.AttributeType === "Customer" || attr.AttributeType === "Owner") { existingAttributeTypes.add("Microsoft.Dynamics.CRM.LookupAttributeMetadata"); } } const relevantAttributeTypes = attributeTypes.filter((type) => existingAttributeTypes.has(type)); if (relevantAttributeTypes.length === 0) { return basicAttributes; } const castPromises = relevantAttributeTypes.map(async (attributeType) => { try { const castUrl = `/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')/Attributes/${attributeType}`; const expandParams = attributeType.includes("Picklist") || attributeType.includes("State") || attributeType.includes("Status") ? "?$expand=OptionSet" : ""; const castData = await globalRequestQueue.execute( () => getAuthenticatedFetch3()(`${castUrl}${expandParams}`, { method: "GET" }), { url: `${castUrl}${expandParams}`, method: "GET", priority: 1, // Lower priority than basic attributes maxRetries: 2 // Fewer retries for attribute casting (non-critical) } ); return castData.value || []; } catch (castError) { console.warn(`Failed to cast attributes for type ${attributeType} on entity ${entityLogicalName}:`, castError); return []; } }); const castResults = await Promise.all(castPromises); for (const castAttributes of castResults) { for (const castAttr of castAttributes) { const basicIndex = basicAttributes.findIndex((attr) => attr.LogicalName === castAttr.LogicalName); if (basicIndex >= 0) { basicAttributes[basicIndex] = { ...basicAttributes[basicIndex], ...castAttr }; } } } return basicAttributes; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching entity attributes for '${entityLogicalName}': ${error.message}`); } throw error; } } async function fetchGlobalOptionSet(optionSetName) { try { const url = `/api/data/v9.2/GlobalOptionSetDefinitions(Name='${optionSetName}')`; const optionSetData = await globalRequestQueue.execute( () => getAuthenticatedFetch3()(url, { method: "GET" }), { url, method: "GET", priority: 0 // Normal priority for option sets } ); return optionSetData; } catch (error) { if (error instanceof Error && error.message.includes("404")) { return null; } if (error instanceof Error) { throw new Error(`Error fetching global option set '${optionSetName}': ${error.message}`); } throw error; } } async function fetchPublisherEntities(publisherPrefix) { try { const allCustomEntities = await fetchAllEntities({ customOnly: true, select: ["LogicalName", "SchemaName", "DisplayName", "Description", "IsCustomEntity", "PrimaryIdAttribute", "PrimaryNameAttribute"] }); const filterPattern = `${publisherPrefix}_`; const solutionEntities = allCustomEntities.filter( (entity) => entity.LogicalName.startsWith(filterPattern) ); return solutionEntities; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching publisher entities for publisher '${publisherPrefix}': ${error.message}`); } throw error; } } async function fetchMultipleEntities(entityNames, options = {}) { if (entityNames.length === 0) { return []; } const dataverseUrl = process.env.DATAVERSE_INSTANCE; if (!dataverseUrl) { throw new Error("DATAVERSE_INSTANCE environment variable is required"); } await preAcquireGlobalToken(dataverseUrl); const { onProgress, includeAttributes = true, includeRelationships = false } = options; let basicEntities = []; if (includeRelationships) { console.log(`Relationships needed - using individual entity calls for ${entityNames.length} entities`); const batchSize = 10; for (let i = 0; i < entityNames.length; i += batchSize) { const batch = entityNames.slice(i, i + batchSize); const batchPromises = batch.map( (entityName) => fetchEntityMetadata(entityName, options).catch((error) => { console.warn(`Failed to fetch entity '${entityName}':`, error); return null; }) ); const batchResults = await Promise.all(batchPromises); for (const result of batchResults) { if (result) { basicEntities.push(result); } } if (onProgress) { const currentCount = Math.min(i + batchSize, entityNames.length); const lastEntityInBatch = batch[batch.length - 1]; onProgress(currentCount, entityNames.length, lastEntityInBatch); } if (i + batchSize < entityNames.length) { await new Promise((resolve3) => setTimeout(resolve3, 500)); } } } else { console.log(`Using OR-filter batching for optimal performance`); basicEntities = await fetchEntitiesWithOrFilter(entityNames, [ "LogicalName", "SchemaName", "DisplayName", "Description", "HasActivities", "HasFeedback", "HasNotes", "IsActivity", "IsCustomEntity", "OwnershipType", "PrimaryIdAttribute", "PrimaryNameAttribute", "EntitySetName" ], onProgress ? (current, total, item) => { if (includeAttributes) { onProgress(current, total, `Basic: ${item || ""}`); } else { onProgress(current, total, item); } } : void 0); } if (includeAttributes && basicEntities.length > 0) { let completedAttributeEntities = 0; const batchSize = 50; const entitiesWithAttributes = []; for (let i = 0; i < basicEntities.length; i += batchSize) { const batch = basicEntities.slice(i, i + batchSize); const batchPromises = batch.map(async (entity) => { try { const detailedAttributes = await fetchEntityAttributes(entity.LogicalName); entity.Attributes = detailedAttributes; completedAttributeEntities++; if (onProgress) { const percentComplete = Math.floor(completedAttributeEntities / basicEntities.length * 100); const prevPercentComplete = Math.floor((completedAttributeEntities - 1) / basicEntities.length * 100); if (percentComplete !== prevPercentComplete || completedAttributeEntities === basicEntities.length) { onProgress(completedAttributeEntities, basicEntities.length, entity.LogicalName); } } return entity; } catch (error) { console.warn(`Failed to fetch attributes for ${entity.LogicalName}, using basic entity:`, error); completedAttributeEntities++; if (onProgress) { const percentComplete = Math.floor(completedAttributeEntities / basicEntities.length * 100); const prevPercentComplete = Math.floor((completedAttributeEntities - 1) / basicEntities.length * 100); if (percentComplete !== prevPercentComplete || completedAttributeEntities === basicEntities.length) { onProgress(completedAttributeEntities, basicEntities.length, entity.LogicalName); } } return entity; } }); const batchResults = await Promise.all(batchPromises); entitiesWithAttributes.push(...batchResults); } return entitiesWithAttributes; } return basicEntities; } async function entityExists(entityLogicalName) { try { const url = `/api/data/v9.2/EntityDefinitions(LogicalName='${entityLogicalName}')?$select=LogicalName`; const authenticatedFetch = getAuthenticatedFetch3(); const response = await authenticatedFetch(url, { method: "GET" }); return response.ok; } catch { return false; } } async function fetchAllEntities(options = {}) { const { filter, customOnly = false, select = ["LogicalName", "SchemaName", "DisplayName", "IsCustomEntity"] } = options; try { const params = new URLSearchParams(); if (select.length > 0) { params.append("$select", select.join(",")); } const filters = []; if (customOnly) { filters.push("IsCustomEntity eq true"); } if (filter) { filters.push(filter); } if (filters.length > 0) { params.append("$filter", filters.join(" and ")); } const url = `/api/data/v9.2/EntityDefinitions?${params.toString()}`; const authenticatedFetch = getAuthenticatedFetch3(); 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 all entities: ${response.status} ${response.statusText} - ${statusDescription}`); } const data = await response.json(); return data.value; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching all entities: ${error.message}`); } throw error; } } async function findSolution(solutionName) { try { let url = `/api/data/v9.2/solutions?$filter=uniquename eq '${solutionName}'&$select=solutionid,uniquename,friendlyname,version,publisherid,ismanaged,description`; const authenticatedFetch = getAuthenticatedFetch3(); let response = await authenticatedFetch(url, { method: "GET" }); if (!response.ok) { await advancedLog(response, url, "GET"); const statusDescription = getStatusCodeDescription(response.status); throw new Error(`Failed to search solutions by unique name '${solutionName}': ${response.status} ${response.statusText} - ${statusDescription}`); } let data = await response.json(); if (data.value.length > 0) { return data.value[0]; } url = `/api/data/v9.2/solutions?$filter=friendlyname eq '${solutionName}'&$select=solutionid,uniquename,friendlyname,version,publisherid,ismanaged,description`; response = await authenticatedFetch(url, { method: "GET" }); if (!response.ok) { await advancedLog(response, url, "GET"); const statusDescription = getStatusCodeDescription(response.status); throw new Error(`Failed to search solutions by friendly name '${solutionName}': ${response.status} ${response.statusText} - ${statusDescription}`); } data = await response.json(); return data.value.length > 0 ? data.value[0] : null; } catch (error) { if (error instanceof Error) { throw new Error(`Error finding solution '${solutionName}': ${error.message}`); } throw error; } } async function fetchSolutionComponents(solutionId, componentType) { try { const params = new URLSearchParams(); const filters = [`_solutionid_value eq '${solutionId}'`]; if (componentType !== void 0) { filters.push(`componenttype eq ${componentType}`); } params.append("$filter", filters.join(" and ")); params.append("$select", "solutioncomponentid,solutionid,objectid,componenttype,rootcomponentbehavior,ismetadata"); const url = `/api/data/v9.2/solutioncomponents?${params.toString()}`; const authenticatedFetch = getAuthenticatedFetch3(); 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 solution components for solution '${solutionId}': ${response.status} ${response.statusText} - ${statusDescription}`); } const data = await response.json(); return data.value; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching solution components for solution '${solutionId}': ${error.message}`); } throw error; } } async function fetchSolutionEntities(solutionName) { try { const solution = await findSolution(solutionName); if (!solution) { return []; } const entityComponents = await fetchSolutionComponents(solution.solutionid, COMPONENT_TYPES.ENTITY); if (entityComponents.length === 0) { return []; } const entities = []; const batchSize = 15; for (let i = 0; i < entityComponents.length; i += batchSize) { const batch = entityComponents.slice(i, i + batchSize); const batchPromises = batch.map(async (component) => { try { const url = `/api/data/v9.2/EntityDefinitions?$filter=MetadataId eq ${component.objectid}&$select=LogicalName,SchemaName,DisplayName,Description,IsCustomEntity,PrimaryIdAttribute,PrimaryNameAttribute,EntitySetName`; const authenticatedFetch = getAuthenticatedFetch3(); const response = await authenticatedFetch(url, { method: "GET" }); if (response.ok) { const data = await response.json(); return data.value.length > 0 ? data.value[0] : null; } else { console.warn(`Failed to fetch entity metadata for component ${component.objectid}:`, response.status, response.statusText); return null; } } catch (error) { console.warn(`Failed to fetch entity for component ${component.objectid}:`, error); return null; } }); const batchResults = await Promise.all(batchPromises); for (const result of batchResults) { if (result) { entities.push(result); } } if (i + batchSize < entityComponents.length) { await new Promise((resolve3) => setTimeout(resolve3, 50)); } } return entities; } catch (error) { if (error instanceof Error) { throw new Error(`Error fetching solution entities for solution '${solutionName}': ${error.message}`); } throw error; } } // src/generators/import-utils.ts function getEntityImportPath(fromEntityLogicalName, toEntityLogicalName, primaryEntities = [], relatedEntitiesDir = "related") { const fromIsPrimary = primaryEntities.includes(fromEntityLogicalName); const toIsPrimary = primaryEntities.includes(toEntityLogicalName); if (fromIsPrimary && toIsPrimary) { return `./${toEntityLogicalName.toLowerCase()}.js`; } else if (fromIsPrimary && !toIsPrimary) { return `./${relatedEntitiesDir}/${toEntityLogicalName.toLowerCase()}.js`; } else if (!fromIsPrimary && toIsPrimary) { return `../${toEntityLogicalName.toLowerCase()}.js`; } else { return `./${toEntityLogicalName.toLowerCase()}.js`; } } function getSharedImportPath(fromEntityLogicalName, sharedFile, primaryEntities = []) { const fromIsPrimary = primaryEntities.includes(fromEntityLogicalName); if (fromIsPrimary) { return `./${sharedFile}`; } else { return `../${sharedFile}`; } } function getHooksToEntityImportPath(entityLogicalName, primaryEntities = [], relatedEntitiesDir = "related") { const entityIsPrimary = primaryEntities.includes(entityLogicalName); if (entityIsPrimary) { return `../${entityLogicalName.toLowerCase()}.js`; } else { return `../${relatedEntitiesDir}/${entityLogicalName.toLowerCase()}.js`; } } function getHooksToSharedImportPath(sharedFile) { return `../${sharedFile}`; } function shouldOrganizeDirectories(relatedEntitiesDir, fullMetadata, primaryEntities = []) { return !!(relatedEntitiesDir && (fullMetadata || primaryEntities.length > 0)); } // src/generators/utils.ts function sanitizePropertyName(name) { let sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_"); if (/^[0-9]/.test(sanitized)) { sanitized = `_${sanitized}`; } if (isReservedKeyword(sanitized)) { sanitized = `_${sanitized}`; } return sanitized; } function sanitizeInterfaceName(name) { let sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_"); if (/^[0-9]/.test(sanitized)) { sanitized = `_${sanitized}`; } if (isReservedKeyword(sanitized)) { sanitized = sanitized.charAt(0).toUpperCase() + sanitized.slice(1); } return sanitized; } function isReservedKeyword(name) { const reservedKeywords = [ "break", "case", "catch", "class", "const", "continue", "debug