UNPKG

@eldev/business-central-api

Version:

A Dynamics 365 Business Central client for TypeScript

136 lines 5.34 kB
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