dataverse-type-gen
Version:
TypeScript type generator for Dataverse entities and metadata
1,332 lines (1,322 loc) • 208 kB
JavaScript
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