@dataverse-kit/api-service
v0.3.1
Published
Framework-agnostic IApiService stack for Dynamics 365 / Dataverse — CRUD + batch + metadata + view + action/associate operations with environment-aware ServiceFactory (Xrm / Fetch / Mock).
Downloads
820
Maintainers
Readme
@dataverse-kit/api-service
Framework-agnostic IApiService stack for Dynamics 365 / Dataverse. Ships:
IApiService— single interface covering CRUD, batch, metadata, view, and action/associate operations (18 methods).XrmApiService— for code that runs inside Dynamics (model-driven app, custom page, web resource). Uses same-originfetchagainst${clientUrl}/api/data/v9.2/...; the browser session cookie supplies auth.FetchApiService— for local dev / CLI scripts. Two construction modes since v0.2:{ baseUrl, token }uses the package's self-contained Bearer-tokenfetchimplementation;{ client }wraps a pre-builtIDataverseClientfrom@dataverse-kit/api-clientso the host project can own credential lifecycle (token refresh, retries, mocking). On the{ client }path, all CRUD / batch / metadata / view methods (andretrieveMultipleFetchXml) delegate directly to the client;executeRequestandassociateRecordthrow (the client exposes no equivalent).MockApiService— pure in-memory implementation for tests and offline workflows.ServiceFactory— environment-aware factory with explicit-options precedence over auto-detection.
Zero React deps. Browser + Node compatible (Node 18+, ES2020 build).
Install
npm install --save @dataverse-kit/api-serviceThe package itself doesn't import from @types/xrm. ServiceFactory.create accepts an xrm: unknown and structural-checks at runtime. Install @types/xrm in your own project only if you're consuming the Xrm global directly.
Choosing an implementation
| Where you run | Use | Auth |
|---|---|---|
| Local dev (Vite/CRA), CLI scripts | FetchApiService (or just ServiceFactory.create) | Bearer token via Azure CLI |
| Inside Dynamics (custom page, web resource) | XrmApiService (or ServiceFactory.create) | Session cookie |
| Tests | MockApiService | None |
Quick start with ServiceFactory
ServiceFactory.create picks the right implementation based on what you pass in and what's in the runtime environment.
Precedence:
- Explicit
baseUrl + token→FetchApiService - Explicit
xrmorclientUrl→XrmApiService window.Xrm.Utility.getGlobalContext().getClientUrl()available →XrmApiServicewindow.__DYNAMICS_URL+window.__DYNAMICS_TOKENset →FetchApiService- Fallback →
MockApiService
import { ServiceFactory, type IApiService } from '@dataverse-kit/api-service';
// Inside Dynamics — auto-detected
const svc: IApiService = ServiceFactory.create();
// Local dev with an explicit token (paired with `@dataverse-kit/dev-tools` `dv-tools auth`)
const svc = ServiceFactory.create({
baseUrl: import.meta.env.VITE_DYNAMICS_URL,
token: import.meta.env.VITE_DYNAMICS_TOKEN,
});
// Tests
const svc = ServiceFactory.create(); // returns MockApiService when no Xrm/token in envServiceFactory.getEnvironmentInfo() returns a diagnostic shape ({ type, hostname, isLocalhost, hasXrm, hasDevGlobals }) for logging which path will be taken.
Direct construction
import { XrmApiService, FetchApiService, MockApiService } from '@dataverse-kit/api-service';
// In Dynamics
const svc = new XrmApiService({
clientUrl: Xrm.Utility.getGlobalContext().getClientUrl(),
});
// Local dev
const svc = new FetchApiService({
baseUrl: 'https://myorg.crm.dynamics.com',
token: '<bearer-token-from-az-cli>',
});
// Tests with seeded data
const svc = new MockApiService({
seedRecords: {
accounts: [{ accountid: '...', name: 'Contoso' }],
},
});Interface surface (18 methods)
CRUD
retrieveRecord<T>(entitySetName, id, options?)retrieveMultipleRecords<T>(entitySetName, options?)— OData queryretrieveMultipleFetchXml<T>(entitySetName, fetchXml)— FetchXML query, same{ value }envelopecreateRecord(entitySetName, data)updateRecord(entitySetName, id, data)deleteRecord(entitySetName, id)
Batch (per-record fan-out; partial failures collected)
batchCreateRecords(entitySetName, records)batchUpdateRecords(entitySetName, records)batchDeleteRecords(entitySetName, ids)
Metadata
getEntityDefinitions(filter?)getAttributeMetadata(entityLogicalName, filter?)
Views (savedqueries + userqueries)
getViews(entityLogicalName)createView(entityLogicalName, data)— creates a personal viewupdateView(isPersonal, viewId, data)deleteView(isPersonal, viewId)publishEntity(entityLogicalName)— required after editing system views
Actions & relationships (since 0.3.0)
executeRequest(requestName, requestData?)— invoke an unbound action / custom API by its unique name (POST)associateRecord(entitySetName, id, relationshipName, related)— associate to a collection-valued navigation property via$ref
Why this exists
The IApiService stack (interface + Xrm/Fetch/Mock implementations + factory) was previously copied byte-for-byte into every project the form-builder exports — 5 duplicated files per output. This package consolidates that pattern as a single dependency.
Relationship to siblings:
| Package | Role |
|---|---|
| @dataverse-kit/api-service (this) | High-level IApiService interface + factory + 3 implementations |
| @dataverse-kit/api-client | Lower-level IDataverseClient transport. Optional peer dep — only required when constructing FetchApiService({ client }). The { baseUrl, token } arm is self-contained and doesn't import from this package. Resolved via tsconfig path alias to ../../../tools/api-client/src/ for now since the package isn't published to npm yet. |
| @dataverse-kit/dev-tools | Terminal tooling: dv-tools auth to acquire the Bearer token for FetchApiService |
Not in scope (yet)
uploadFile(the legacy 6-methodIApiServicehad it; it has no working implementation here — every impl threw or stubbed it, so it is deliberately omitted). If you need file upload, file an issue.disassociateRecord— the inverse ofassociateRecord(aDELETEon the same$refendpoint). Planned immediate follow-up.executeRequestfor bound actions / unbound functions (GET) / namespaced built-in messages (e.g.Microsoft.Dynamics.CRM.WhoAmI) — the current method targets unbound actions / custom APIs by their identifier name.LoggingApiServicedecorator andApiAuditPanelUI — tracked separately in the workspace roadmap.
Testing
npm test # vitest, all four implementations + factory
npm run typecheck