dataverse-type-gen
Version:
TypeScript type generator for Dataverse entities and metadata
1,467 lines (1,458 loc) • 180 kB
JavaScript
#!/usr/bin/env node
import { processEntityMetadata } from '../chunk-IF2SU3NK.mjs';
import { Command } from 'commander';
import process2 from 'process';
import os from 'os';
import tty from 'tty';
import { DefaultAzureCredential } from '@azure/identity';
import crypto from 'crypto';
import { promises } from 'fs';
import { join, resolve, dirname, relative } from 'path';
import { Project, ModuleKind, ScriptTarget } from 'ts-morph';
import { createInterface } from 'readline';
// node_modules/.pnpm/chalk@5.4.1/node_modules/chalk/source/vendor/ansi-styles/index.js
var ANSI_BACKGROUND_OFFSET = 10;
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
var styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
overline: [53, 55],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29]
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
// Bright color
blackBright: [90, 39],
gray: [90, 39],
// Alias of `blackBright`
grey: [90, 39],
// Alias of `blackBright`
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39]
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgGray: [100, 49],
// Alias of `bgBlackBright`
bgGrey: [100, 49],
// Alias of `bgBlackBright`
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49]
}
};
Object.keys(styles.modifier);
var foregroundColorNames = Object.keys(styles.color);
var backgroundColorNames = Object.keys(styles.bgColor);
[...foregroundColorNames, ...backgroundColorNames];
function assembleStyles() {
const codes = /* @__PURE__ */ new Map();
for (const [groupName, group] of Object.entries(styles)) {
for (const [styleName, style] of Object.entries(group)) {
styles[styleName] = {
open: `\x1B[${style[0]}m`,
close: `\x1B[${style[1]}m`
};
group[styleName] = styles[styleName];
codes.set(style[0], style[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false
});
}
Object.defineProperty(styles, "codes", {
value: codes,
enumerable: false
});
styles.color.close = "\x1B[39m";
styles.bgColor.close = "\x1B[49m";
styles.color.ansi = wrapAnsi16();
styles.color.ansi256 = wrapAnsi256();
styles.color.ansi16m = wrapAnsi16m();
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
Object.defineProperties(styles, {
rgbToAnsi256: {
value(red, green, blue) {
if (red === green && green === blue) {
if (red < 8) {
return 16;
}
if (red > 248) {
return 231;
}
return Math.round((red - 8) / 247 * 24) + 232;
}
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
},
enumerable: false
},
hexToRgb: {
value(hex) {
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
if (!matches) {
return [0, 0, 0];
}
let [colorString] = matches;
if (colorString.length === 3) {
colorString = [...colorString].map((character) => character + character).join("");
}
const integer = Number.parseInt(colorString, 16);
return [
/* eslint-disable no-bitwise */
integer >> 16 & 255,
integer >> 8 & 255,
integer & 255
/* eslint-enable no-bitwise */
];
},
enumerable: false
},
hexToAnsi256: {
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
enumerable: false
},
ansi256ToAnsi: {
value(code) {
if (code < 8) {
return 30 + code;
}
if (code < 16) {
return 90 + (code - 8);
}
let red;
let green;
let blue;
if (code >= 232) {
red = ((code - 232) * 10 + 8) / 255;
green = red;
blue = red;
} else {
code -= 16;
const remainder = code % 36;
red = Math.floor(code / 36) / 5;
green = Math.floor(remainder / 6) / 5;
blue = remainder % 6 / 5;
}
const value = Math.max(red, green, blue) * 2;
if (value === 0) {
return 30;
}
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
if (value === 2) {
result += 60;
}
return result;
},
enumerable: false
},
rgbToAnsi: {
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
enumerable: false
},
hexToAnsi: {
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
enumerable: false
}
});
return styles;
}
var ansiStyles = assembleStyles();
var ansi_styles_default = ansiStyles;
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
const position = argv.indexOf(prefix + flag);
const terminatorPosition = argv.indexOf("--");
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
}
var { env } = process2;
var flagForceColor;
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
flagForceColor = 0;
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
flagForceColor = 1;
}
function envForceColor() {
if ("FORCE_COLOR" in env) {
if (env.FORCE_COLOR === "true") {
return 1;
}
if (env.FORCE_COLOR === "false") {
return 0;
}
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
}
}
function translateLevel(level) {
if (level === 0) {
return false;
}
return {
level,
hasBasic: true,
has256: level >= 2,
has16m: level >= 3
};
}
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
const noFlagForceColor = envForceColor();
if (noFlagForceColor !== void 0) {
flagForceColor = noFlagForceColor;
}
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
if (forceColor === 0) {
return 0;
}
if (sniffFlags) {
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
return 3;
}
if (hasFlag("color=256")) {
return 2;
}
}
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
return 1;
}
if (haveStream && !streamIsTTY && forceColor === void 0) {
return 0;
}
const min = forceColor || 0;
if (env.TERM === "dumb") {
return min;
}
if (process2.platform === "win32") {
const osRelease = os.release().split(".");
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
return Number(osRelease[2]) >= 14931 ? 3 : 2;
}
return 1;
}
if ("CI" in env) {
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
return 3;
}
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
return 1;
}
return min;
}
if ("TEAMCITY_VERSION" in env) {
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
}
if (env.COLORTERM === "truecolor") {
return 3;
}
if (env.TERM === "xterm-kitty") {
return 3;
}
if ("TERM_PROGRAM" in env) {
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
switch (env.TERM_PROGRAM) {
case "iTerm.app": {
return version >= 3 ? 3 : 2;
}
case "Apple_Terminal": {
return 2;
}
}
}
if (/-256(color)?$/i.test(env.TERM)) {
return 2;
}
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
return 1;
}
if ("COLORTERM" in env) {
return 1;
}
return min;
}
function createSupportsColor(stream, options = {}) {
const level = _supportsColor(stream, {
streamIsTTY: stream && stream.isTTY,
...options
});
return translateLevel(level);
}
var supportsColor = {
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
};
var supports_color_default = supportsColor;
// node_modules/.pnpm/chalk@5.4.1/node_modules/chalk/source/utilities.js
function stringReplaceAll(string, substring, replacer) {
let index = string.indexOf(substring);
if (index === -1) {
return string;
}
const substringLength = substring.length;
let endIndex = 0;
let returnValue = "";
do {
returnValue += string.slice(endIndex, index) + substring + replacer;
endIndex = index + substringLength;
index = string.indexOf(substring, endIndex);
} while (index !== -1);
returnValue += string.slice(endIndex);
return returnValue;
}
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
let endIndex = 0;
let returnValue = "";
do {
const gotCR = string[index - 1] === "\r";
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
endIndex = index + 1;
index = string.indexOf("\n", endIndex);
} while (index !== -1);
returnValue += string.slice(endIndex);
return returnValue;
}
// node_modules/.pnpm/chalk@5.4.1/node_modules/chalk/source/index.js
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
var GENERATOR = Symbol("GENERATOR");
var STYLER = Symbol("STYLER");
var IS_EMPTY = Symbol("IS_EMPTY");
var levelMapping = [
"ansi",
"ansi",
"ansi256",
"ansi16m"
];
var styles2 = /* @__PURE__ */ Object.create(null);
var applyOptions = (object, options = {}) => {
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
throw new Error("The `level` option should be an integer from 0 to 3");
}
const colorLevel = stdoutColor ? stdoutColor.level : 0;
object.level = options.level === void 0 ? colorLevel : options.level;
};
var chalkFactory = (options) => {
const chalk2 = (...strings) => strings.join(" ");
applyOptions(chalk2, options);
Object.setPrototypeOf(chalk2, createChalk.prototype);
return chalk2;
};
function createChalk(options) {
return chalkFactory(options);
}
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
styles2[styleName] = {
get() {
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
Object.defineProperty(this, styleName, { value: builder });
return builder;
}
};
}
styles2.visible = {
get() {
const builder = createBuilder(this, this[STYLER], true);
Object.defineProperty(this, "visible", { value: builder });
return builder;
}
};
var getModelAnsi = (model, level, type, ...arguments_) => {
if (model === "rgb") {
if (level === "ansi16m") {
return ansi_styles_default[type].ansi16m(...arguments_);
}
if (level === "ansi256") {
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
}
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
}
if (model === "hex") {
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
}
return ansi_styles_default[type][model](...arguments_);
};
var usedModels = ["rgb", "hex", "ansi256"];
for (const model of usedModels) {
styles2[model] = {
get() {
const { level } = this;
return function(...arguments_) {
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
return createBuilder(this, styler, this[IS_EMPTY]);
};
}
};
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
styles2[bgModel] = {
get() {
const { level } = this;
return function(...arguments_) {
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
return createBuilder(this, styler, this[IS_EMPTY]);
};
}
};
}
var proto = Object.defineProperties(() => {
}, {
...styles2,
level: {
enumerable: true,
get() {
return this[GENERATOR].level;
},
set(level) {
this[GENERATOR].level = level;
}
}
});
var createStyler = (open, close, parent) => {
let openAll;
let closeAll;
if (parent === void 0) {
openAll = open;
closeAll = close;
} else {
openAll = parent.openAll + open;
closeAll = close + parent.closeAll;
}
return {
open,
close,
openAll,
closeAll,
parent
};
};
var createBuilder = (self, _styler, _isEmpty) => {
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
Object.setPrototypeOf(builder, proto);
builder[GENERATOR] = self;
builder[STYLER] = _styler;
builder[IS_EMPTY] = _isEmpty;
return builder;
};
var applyStyle = (self, string) => {
if (self.level <= 0 || !string) {
return self[IS_EMPTY] ? "" : string;
}
let styler = self[STYLER];
if (styler === void 0) {
return string;
}
const { openAll, closeAll } = styler;
if (string.includes("\x1B")) {
while (styler !== void 0) {
string = stringReplaceAll(string, styler.close, styler.open);
styler = styler.parent;
}
}
const lfIndex = string.indexOf("\n");
if (lfIndex !== -1) {
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
}
return openAll + string + closeAll;
};
Object.defineProperties(createChalk.prototype, styles2);
var chalk = createChalk();
createChalk({ level: stderrColor ? stderrColor.level : 0 });
var source_default = chalk;
// 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 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
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/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 getAuthenticatedFetch() {
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(
() => getAuthenticatedFetch()(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(
() => getAuthenticatedFetch()(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 getAuthenticatedFetch2() {
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 = getAuthenticatedFetch2();
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 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(
() => getAuthenticatedFetch2()(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(
() => getAuthenticatedFetch2()(`${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 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 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 = getAuthenticatedFetch2();
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}`