BaSyx TypeScript SDK for developing applications and components for the Asset Administration Shell (AAS)
High-level orchestration services:
- AasService: Unified API for AAS operations across registry and repository
- Create, read, update, delete AAS with automatic registry synchronization
- Fetch AAS with their submodels in a single call
- Automatic endpoint resolution and fallback handling
- Optional registry resolution warnings and strict fail-fast mode for list operations
- SubmodelService: Unified API for Submodel operations across registry and repository
- Create, read, update, delete Submodels with automatic registry synchronization
- Flexible endpoint-based retrieval
- Automatic fallback to repository when registry unavailable
- Optional registry resolution warnings and strict fail-fast mode for list operations
Clients for the AAS API components:
- AAS Repository
- Submodel Repository
- Concept Description Repository
- AAS Registry
- Submodel Registry
- AAS Discovery Service
- AASX File Service
Utility functions for working with AAS data:
- Utils for Descriptors
- Utils for KeyTypes
- Utils for MultiLanguageProperties
- Utils for Referables
- Utils for References
- Utils for SemanticIds
npm install basyx-typescript-sdk
# or
pnpm add basyx-typescript-sdk[!IMPORTANT] Make sure to also install
@aas-core-works/aas-core3.1-typescriptin your project:
npm install @aas-core-works/aas-core3.1-typescript
# or
pnpm add @aas-core-works/aas-core3.1-typescriptpnpm install
pnpm format:check
pnpm lint:check
pnpm type-check
pnpm build
pnpm bundle
pnpm test
pnpm verify:compatUse .nvmrc to align local Node.js with CI:
nvm useThe repository includes a CLI test engine that reuses the integration suites for infrastructure components. The user provides a component ID and endpoint URL, and the engine produces selectable reports with passed checks and failed checks including reasons.
The engine targets unsecured endpoints and does not start Docker by itself.
aas-repositorysubmodel-repositoryconcept-description-repositoryaas-registrysubmodel-registryaas-discoveryaasx-file-server
List components:
pnpm test:engine:list-componentsRun against one component endpoint:
pnpm test:engine --component submodel-repository --url http://localhost:8082Select output formats (console, json, junit, markdown):
pnpm test:engine --component aas-repository --url http://localhost:8081 --report console,json,junit,markdownStrict known-issues mode (waived known issues become failures):
pnpm test:engine --component aas-discovery --url http://localhost:8086 --strict-known-issuesUse a custom artifact directory:
pnpm test:engine --component aasx-file-server --url http://localhost:8087 --report-dir coverage/test-engineconsole: run summary plus failed checks and reasons, including endpoint and response snippets when available.json: machine-readable report for workflow processing, including failure diagnostics such as request method/endpoint, request body summary, response status, response body snippet, and trace source for integration-test failures.junit: CI-compatible XML artifact; integration-test failure entries include endpoint and response context in the failure message.markdown: shareable summary document with failure diagnostics and copyable cURL replay blocks for integration-test failures.
Failure diagnostics are captured only in test-engine mode and are sanitized:
- Authorization and cookie-style headers are redacted.
- Large request/response bodies are truncated to compact snippets.
- Multipart/binary replay commands use real relative file names when available; otherwise placeholders are emitted.
Exit code behavior:
0if all non-waived checks pass.- non-zero if any integration check fails or OpenAPI critical findings exist.
- in strict mode, waived known issues also fail the run.
The integration suites include some checks that are currently waived or adapted due to backend behavior that does not fully match expected API behavior.
Observed and tracked:
submodel-repositoryGetAllSubmodels-Pathcan return an emptyresultarray even when a fresh submodel has been created in the same run. The test now validates success and response shape, but does not requireresult.length > 0.submodel-repositoryvalue-only cursor not-found behavior is tracked asknown-specification-bug.
Maintenance rule for new findings:
- If a backend deviation is confirmed, annotate the affected test with a short reason (
known-backend-bugorknown-specification-bug), and add a short note in this section. - Prefer narrowing assertions to contract-safe checks (status and shape) instead of asserting unstable payload details.
- Keep strict mode available (
--strict-known-issues) so waived issues are still visible in CI when needed.
The SDK supports multiple import styles for flexibility:
// Import clients directly
import { AasRepositoryClient, AasRegistryClient, Configuration } from 'basyx-typescript-sdk';
// Import services directly
import { AasService } from 'basyx-typescript-sdk';
// Import utility functions directly
import { extractEndpointHref, getSubmodelElementByIdShort } from 'basyx-typescript-sdk';
// Import library functions directly
import { base64Encode, base64Decode } from 'basyx-typescript-sdk';// Import utilities as namespace
import { Utils, Lib } from 'basyx-typescript-sdk';
// Use with namespace
const endpoint = Utils.extractEndpointHref(descriptor, 'AAS-3.0');
const encoded = Lib.base64Encode('my-id');// Combine direct and namespaced imports
import { AasService, Configuration, Utils, base64Encode } from 'basyx-typescript-sdk';
// Use directly imported functions
const id = base64Encode('my-id');
// Use namespaced utilities
const element = Utils.getSubmodelElementByIdShort(submodel, 'myElement');import { AasRepositoryClient, Configuration } from 'basyx-typescript-sdk';
async function getAllShells() {
const client = new AasRepositoryClient();
const configuration = new Configuration({
basePath: 'http://localhost:8081',
// Optional: automatically sends `Authorization: Bearer <token>` on requests
accessToken: async () => 'your-oidc-access-token',
});
const response = await client.getAllAssetAdministrationShells({ configuration });
if (response.success) {
console.log('Shells:', response.data.result);
} else {
console.error('Error:', response.error);
}
}Configuration.accessToken is applied automatically by the runtime. You only need middleware if you want custom auth
logic beyond bearer token injection.
import { AasService, Configuration } from 'basyx-typescript-sdk';
import { AssetAdministrationShell, AssetInformation, AssetKind } from '@aas-core-works/aas-core3.1-typescript/types';
// Initialize service with both registry and repository
const service = new AasService({
registryConfig: new Configuration({ basePath: 'http://localhost:8084' }),
repositoryConfig: new Configuration({ basePath: 'http://localhost:8081' }),
// Optional: Separate configs for submodel services (used when includeSubmodels is enabled)
// If not provided, falls back to using registryConfig/repositoryConfig for submodels
// In typical BaSyx deployments, submodel services run on different ports:
submodelRegistryConfig: new Configuration({ basePath: 'http://localhost:8083' }),
submodelRepositoryConfig: new Configuration({ basePath: 'http://localhost:8081' }),
// Optional: Discovery service config (used for asset ID lookups)
discoveryConfig: new Configuration({ basePath: 'http://localhost:8086' }),
});
// Create a new AAS (automatically registers in registry)
const shell = new AssetAdministrationShell(
'https://example.com/ids/aas/my-aas',
new AssetInformation(AssetKind.Instance)
);
const createResult = await service.createAas({ shell });
if (createResult.success) {
console.log('Created AAS:', createResult.data.shell);
}
// Get AAS list from registry with automatic endpoint resolution
const listResult = await service.getAasList();
if (listResult.success) {
console.log('All shells:', listResult.data.shells);
// Optional warnings for descriptors that could not be resolved
if (listResult.data.warnings?.length) {
console.warn('Registry resolution warnings:', listResult.data.warnings);
}
}
// Strict mode: fail immediately if any registry descriptor cannot be resolved
const strictListResult = await service.getAasList({
strictRegistryResolution: true,
});
// Get AAS by ID (uses registry endpoint if available)
const getResult = await service.getAasById({
aasIdentifier: 'https://example.com/ids/aas/my-aas',
});
// Get AAS with its submodels in a single call
const withSubmodels = await service.getAasById({
aasIdentifier: 'https://example.com/ids/aas/my-aas',
includeSubmodels: true,
});
if (withSubmodels.success) {
console.log('Shell:', withSubmodels.data.shell);
console.log('Submodels:', withSubmodels.data.submodels); // Array of submodels
}
// Get all AAS with their submodels
const allWithSubmodels = await service.getAasList({ includeSubmodels: true });
if (allWithSubmodels.success) {
allWithSubmodels.data.shells.forEach((shell, index) => {
console.log(`Shell ${index}:`, shell.id);
console.log(`Submodels:`, allWithSubmodels.data.submodels?.[shell.id]);
});
}
// Update AAS (updates both repository and registry)
shell.idShort = 'UpdatedName';
const updateResult = await service.updateAas({ shell });
// Get endpoint for an AAS
const endpointResult = await service.getAasEndpointById({
aasIdentifier: 'https://example.com/ids/aas/my-aas',
});
// Get AAS directly by endpoint URL
const byEndpointResult = await service.getAasByEndpoint({
endpoint: 'http://localhost:8081/shells/encoded-id',
});
// Delete AAS (removes from both registry and repository)
await service.deleteAas({
aasIdentifier: 'https://example.com/ids/aas/my-aas',
});
// Find AAS by asset IDs using discovery service
const assetIds = [
{ name: 'serialNumber', value: '12345' },
{ name: 'deviceId', value: 'device-001' },
];
const byAssetIdResult = await service.getAasByAssetId({
assetIds,
includeSubmodels: true, // Optionally include submodels
});
if (byAssetIdResult.success) {
console.log('Found AAS IDs:', byAssetIdResult.data.aasIds);
console.log('Found shells:', byAssetIdResult.data.shells);
// If multiple AAS match the asset IDs, all are returned
}
// Fetch AAS with submodels and their concept descriptions
const serviceWithCD = new AasService({
registryConfig: new Configuration({ basePath: 'http://localhost:8084' }),
repositoryConfig: new Configuration({ basePath: 'http://localhost:8081' }),
submodelRegistryConfig: new Configuration({ basePath: 'http://localhost:8085' }),
submodelRepositoryConfig: new Configuration({ basePath: 'http://localhost:8082' }),
conceptDescriptionRepositoryConfig: new Configuration({ basePath: 'http://localhost:8083' }),
});
const withConceptDescriptions = await serviceWithCD.getAasById({
aasIdentifier: 'https://example.com/ids/aas/my-aas',
includeSubmodels: true,
includeConceptDescriptions: true,
});
if (withConceptDescriptions.success) {
console.log('Shell:', withConceptDescriptions.data.shell);
console.log('Submodels with CDs:', withConceptDescriptions.data.submodels);
// Concept descriptions are fetched for all semantic IDs in submodels and their elements
}
// Resolve a Reference to get endpoints for AAS, Submodel, or SubmodelElement
import { Reference, Key, KeyTypes, ReferenceTypes } from '@aas-core-works/aas-core3.1-typescript/types';
// Example 1: Reference to AAS and Submodel
const reference1 = new Reference(ReferenceTypes.ModelReference, [
new Key(KeyTypes.AssetAdministrationShell, 'https://example.com/ids/aas/my-aas'),
new Key(KeyTypes.Submodel, 'https://example.com/ids/sm/my-submodel'),
]);
const resolved1 = await service.resolveReference({ reference: reference1 });
if (resolved1.success) {
console.log('AAS Endpoint:', resolved1.data.aasEndpoint);
// e.g., 'http://localhost:8081/shells/encoded-aas-id'
console.log('Submodel Endpoint:', resolved1.data.submodelEndpoint);
// e.g., 'http://localhost:8082/submodels/encoded-sm-id'
}
// Example 2: Reference to a specific SubmodelElement
const reference2 = new Reference(ReferenceTypes.ModelReference, [
new Key(KeyTypes.AssetAdministrationShell, 'https://example.com/ids/aas/my-aas'),
new Key(KeyTypes.Submodel, 'https://example.com/ids/sm/my-submodel'),
new Key(KeyTypes.Property, 'MyProperty'),
]);
const resolved2 = await service.resolveReference({ reference: reference2 });
if (resolved2.success) {
console.log('SubmodelElement Path:', resolved2.data.submodelElementPath);
// e.g., 'http://localhost:8082/submodels/encoded-sm-id/submodel-elements/MyProperty'
}
// Example 3: Reference to nested SubmodelElements
const reference3 = new Reference(ReferenceTypes.ModelReference, [
new Key(KeyTypes.Submodel, 'https://example.com/ids/sm/my-submodel'),
new Key(KeyTypes.SubmodelElementCollection, 'MyCollection'),
new Key(KeyTypes.Property, 'NestedProperty'),
]);
const resolved3 = await service.resolveReference({ reference: reference3 });
if (resolved3.success) {
console.log('SubmodelElement Path:', resolved3.data.submodelElementPath);
// e.g., 'http://localhost:8082/submodels/encoded-sm-id/submodel-elements/MyCollection.NestedProperty'
}
// Fetch all AAS with submodels and concept descriptions
const allWithCD = await serviceWithCD.getAasList({
includeSubmodels: true,
includeConceptDescriptions: true,
});
if (allWithCD.success) {
allWithCD.data.shells.forEach((shell) => {
console.log('Shell:', shell.id);
console.log('Submodels:', allWithCD.data.submodels?.[shell.id]);
// Concept descriptions are available through the submodel service
});
}import { SubmodelService, Configuration } from 'basyx-typescript-sdk';
import { Submodel, ModellingKind } from '@aas-core-works/aas-core3.1-typescript/types';
// Initialize service with both registry and repository
const service = new SubmodelService({
registryConfig: new Configuration({ basePath: 'http://localhost:8085' }),
repositoryConfig: new Configuration({ basePath: 'http://localhost:8082' }),
});
// Create a new Submodel (automatically registers in registry)
const submodel = new Submodel(
'https://example.com/ids/sm/my-submodel',
null, // extensions
null, // category
'MySubmodel', // idShort
null, // displayName
null, // description
null, // administration
ModellingKind.Instance
);
const createResult = await service.createSubmodel({ submodel });
if (createResult.success) {
console.log('Created Submodel:', createResult.data.submodel);
console.log('Registry Descriptor:', createResult.data.descriptor);
}
// Get Submodel list from registry with automatic endpoint resolution
const listResult = await service.getSubmodelList({ preferRegistry: true });
if (listResult.success) {
console.log('All submodels:', listResult.data.submodels);
console.log('Fetched from:', listResult.data.source); // 'registry' or 'repository'
// Optional warnings for descriptors that could not be resolved
if (listResult.data.warnings?.length) {
console.warn('Registry resolution warnings:', listResult.data.warnings);
}
}
// Strict mode: fail immediately if any registry descriptor cannot be resolved
const strictSubmodelListResult = await service.getSubmodelList({
preferRegistry: true,
strictRegistryResolution: true,
});
// Get Submodel by ID (uses registry endpoint if available)
const getResult = await service.getSubmodelById({
submodelIdentifier: 'https://example.com/ids/sm/my-submodel',
useRegistryEndpoint: true,
});
if (getResult.success) {
console.log('Submodel:', getResult.data.submodel);
console.log('Descriptor:', getResult.data.descriptor);
}
// Update Submodel (updates both repository and registry)
submodel.idShort = 'UpdatedSubmodel';
const updateResult = await service.updateSubmodel({ submodel });
// Get endpoint for a Submodel
const endpointResult = await service.getSubmodelEndpointById({
submodelIdentifier: 'https://example.com/ids/sm/my-submodel',
});
if (endpointResult.success) {
console.log('Endpoint:', endpointResult.data);
}
// Get Submodel directly by endpoint URL
const byEndpointResult = await service.getSubmodelByEndpoint({
endpoint: 'http://localhost:8082/submodels/encoded-id',
});
// Delete Submodel (removes from both registry and repository)
await service.deleteSubmodel({
submodelIdentifier: 'https://example.com/ids/sm/my-submodel',
});
// Service works with only repository (no registry)
const repoOnlyService = new SubmodelService({
repositoryConfig: new Configuration({ basePath: 'http://localhost:8082' }),
});
const repoList = await repoOnlyService.getSubmodelList();
// Automatically falls back to repository when registry unavailable
// Fetch submodels with concept descriptions
const serviceWithCD = new SubmodelService({
registryConfig: new Configuration({ basePath: 'http://localhost:8085' }),
repositoryConfig: new Configuration({ basePath: 'http://localhost:8082' }),
conceptDescriptionRepositoryConfig: new Configuration({ basePath: 'http://localhost:8083' }),
});
// Get submodel with its concept descriptions
const withCD = await serviceWithCD.getSubmodelById({
submodelIdentifier: 'https://example.com/ids/sm/my-submodel',
includeConceptDescriptions: true,
});
if (withCD.success) {
console.log('Submodel:', withCD.data.submodel);
console.log('Concept Descriptions:', withCD.data.conceptDescriptions);
// conceptDescriptions array contains all unique CDs referenced by:
// - The submodel's semanticId
// - All submodel elements' semanticId properties (recursively through collections, lists, entities)
}
// Get all submodels with concept descriptions
const listWithCD = await serviceWithCD.getSubmodelList({
preferRegistry: false,
includeConceptDescriptions: true,
});
if (listWithCD.success) {
console.log('Submodels:', listWithCD.data.submodels);
console.log('All Concept Descriptions:', listWithCD.data.conceptDescriptions);
// Concept descriptions are deduplicated across all submodels
}
// Get submodel by endpoint with concept descriptions
const byEndpointWithCD = await serviceWithCD.getSubmodelByEndpoint({
endpoint: 'http://localhost:8082/submodels/encoded-id',
includeConceptDescriptions: true,
});import { getSubmodelElementByIdShort, extractEndpointHref, base64Encode } from 'basyx-typescript-sdk';
import { Submodel } from '@aas-core-works/aas-core3.1-typescript/types';
// Get a submodel element by its idShort
const element = getSubmodelElementByIdShort(submodel, 'MyProperty');
// Extract endpoint from descriptor
const endpoint = extractEndpointHref(descriptor, 'AAS-3.0');
// Encode/decode IDs from Identifiables
const encoded = base64Encode('https://example.com/ids/aas/my-aas');
// Use in URL: http://localhost:8081/shells/{encoded}import { serializeXml, deserializeXml } from 'basyx-typescript-sdk';
import { BaSyxEnvironment } from 'basyx-typescript-sdk';
import {
AssetAdministrationShell,
AssetInformation,
AssetKind,
Submodel,
ModellingKind,
} from '@aas-core-works/aas-core3.1-typescript/types';
// Create an environment with AAS and Submodels
const environment = new BaSyxEnvironment(
[new AssetAdministrationShell('https://example.com/ids/aas/my-aas', new AssetInformation(AssetKind.Instance))],
[
new Submodel(
'https://example.com/ids/sm/my-submodel',
null,
null,
'MySubmodel',
null,
null,
null,
ModellingKind.Instance
),
],
[] // conceptDescriptions
);
// Serialize to XML string
const xmlString = serializeXml(environment);
console.log(xmlString);
// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <aas:environment xmlns:aas="https://admin-shell.io/aas/3/1"
// xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
// xsi:schemaLocation="https://admin-shell.io/aas/3/1 AAS.xsd">
// ...
// </aas:environment>
// Customize namespace prefix (default is 'aas')
const xmlWithCustomPrefix = serializeXml(environment, 'custom');
// Deserialize XML back to BaSyxEnvironment
const deserializedEnv = deserializeXml(xmlString);
console.log('AAS:', deserializedEnv.assetAdministrationShells);
console.log('Submodels:', deserializedEnv.submodels);