@eldev/business-central-api
Version:
A Dynamics 365 Business Central client for TypeScript
136 lines • 5.34 kB
JavaScript
import packageJson from "../../package.json" with { type: "json" };
import { BCError } from "../error/error.js";
import { ApiPage } from "../pages/api-page.js";
import { ApiQuery } from "../pages/api-query.js";
import { parseSchema } from "../validation/parse-schema.js";
import { isEmpty, isValidGUID, isValidURL } from "../validation/validation.js";
export const BC_BASE_URL = "https://api.businesscentral.dynamics.com/v2.0";
export const DEFAULT_TIMEOUT = 30_000;
export const BC_DEFAULT_SCOPE = "https://api.businesscentral.dynamics.com/.default";
const HEADER_DATA_ACCESS_INTENT = "Data-Access-Intent";
const HEADER_AUTH = "Authorization";
const HEADER_ACCEPTS = "Accepts";
const HEADER_CONTENT_TYPE = "Content-Type";
const HEADER_IF_MATCH = "If-Match";
const HEADER_PREFER = "Prefer";
const HEADER_USER_AGENT = "User-Agent";
const HEADER_CORRELATION_ID = "request-id";
const DATA_ACCESS_READONLY = "ReadOnly";
const CONTENTTYPE_JSON = "application/json";
const ODATA_PAGE_SIZE = "odata.maxpagesize";
export class BCClient {
auth;
apiPath;
apiURL;
scope = BC_DEFAULT_SCOPE;
userAgent;
timeout;
constructor(config, apiPath, authClient) {
const { baseURL, tenantId, environment, companyId, timeout, userAgent } = parseConfig(config);
this.apiPath = apiPath;
this.auth = authClient;
this.apiURL = `${baseURL}/${tenantId}/${environment}/api/${apiPath}/companies(${companyId})`;
this.timeout = timeout;
this.userAgent = userAgent;
}
async getToken() {
const token = this.auth.getToken(this.scope).catch((err) => {
const error = err instanceof Error ? err : new Error(JSON.stringify(err));
throw BCError.fromGetToken(error);
});
return token;
}
async request(endpoint, opts) {
opts = opts || {};
const { payload, method = "GET", params, timeout = 30_000, serverPageSize, } = opts;
endpoint = endpoint.startsWith("/") ? endpoint.replace("/", "") : endpoint;
const url = new URL(`${this.apiURL}/${endpoint}`);
if (params) {
url.search = params.toString();
}
const token = await this.getToken();
const headers = new Headers();
headers.set(HEADER_AUTH, `Bearer ${token}`);
headers.set(HEADER_ACCEPTS, CONTENTTYPE_JSON);
headers.set(HEADER_CONTENT_TYPE, CONTENTTYPE_JSON);
headers.set(HEADER_USER_AGENT, this.userAgent);
if (method === "GET") {
headers.set(HEADER_DATA_ACCESS_INTENT, DATA_ACCESS_READONLY);
}
if (method === "PATCH") {
headers.set(HEADER_IF_MATCH, "*");
}
if (serverPageSize) {
const pageSize = `${ODATA_PAGE_SIZE}=${serverPageSize.toString()}`;
headers.append(HEADER_PREFER, pageSize);
}
const init = {
method,
headers,
signal: AbortSignal.timeout(timeout),
};
if (payload) {
init.body = JSON.stringify(payload);
}
const request = new Request(url, init);
const response = await fetch(request).catch((err) => {
throw BCError.fromNetworkError(err);
});
if (response.status === 204) {
return null;
}
const correlationId = response.headers.get(HEADER_CORRELATION_ID);
const data = await response.json().catch((err) => {
throw BCError.fromUnexpectedResponse(null, response.status, correlationId || "", err);
});
if (!response.ok) {
throw BCError.fromHttpResponse(response.status, data, correlationId || "");
}
return data;
}
async requestWithSchema(endpoint, schema, opts = {}) {
const data = await this.request(endpoint, opts);
const result = await parseSchema(schema, data);
if (result.issues) {
throw BCError.fromSchemaValidation(result.issues);
}
return result.data;
}
for(endpoint, schema) {
return new ApiPage(this, endpoint, schema);
}
forQuery(endpoint, schema) {
return new ApiQuery(this, endpoint, schema);
}
}
function parseConfig(config) {
if (config.baseURL && !isValidURL(config.baseURL)) {
throw Error("BCClient: a valid baseURL is required.");
}
if (!isValidGUID(config.companyId)) {
throw Error("BCClient: a valid companyId is required.");
}
if (!isValidGUID(config.tenantId)) {
throw Error("BCClient: a valid tenantId is required.");
}
if (isEmpty(config.environment)) {
throw Error("BCClient: a valid environment name is required.");
}
return {
companyId: config.companyId,
environment: config.environment,
tenantId: config.tenantId,
timeout: config.timeout || DEFAULT_TIMEOUT,
baseURL: config.baseURL || BC_BASE_URL,
userAgent: config.userAgent || defaultUserAgent(),
};
}
function defaultUserAgent() {
const packageVersion = packageJson.version;
const packageName = packageJson.name;
const nodeVersion = process.version;
const platform = process.platform;
const arch = process.arch;
return `${packageName}/${packageVersion} (${platform}; Node.js ${nodeVersion}; ${arch})`;
}
//# sourceMappingURL=client.js.map