Skip to content

eclipse-basyx/basyx-typescript-sdk

Repository files navigation

GitHub Actions Workflow Status NPM Downloads NPM Version GitHub License

basyx-typescript-sdk

BaSyx TypeScript SDK for developing applications and components for the Asset Administration Shell (AAS)

Features

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

Installation

npm install basyx-typescript-sdk
# or
pnpm add basyx-typescript-sdk

See https://www.npmjs.com/package/basyx-typescript-sdk


[!IMPORTANT] Make sure to also install @aas-core-works/aas-core3.1-typescript in your project:

npm install @aas-core-works/aas-core3.1-typescript
# or
pnpm add @aas-core-works/aas-core3.1-typescript

Development

pnpm install
pnpm format:check
pnpm lint:check
pnpm type-check
pnpm build
pnpm bundle
pnpm test
pnpm verify:compat

Use .nvmrc to align local Node.js with CI:

nvm use

Integration Test Engine

The 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.

Available Components

  • aas-repository
  • submodel-repository
  • concept-description-repository
  • aas-registry
  • submodel-registry
  • aas-discovery
  • aasx-file-server

List components:

pnpm test:engine:list-components

Run against one component endpoint:

pnpm test:engine --component submodel-repository --url http://localhost:8082

Select output formats (console, json, junit, markdown):

pnpm test:engine --component aas-repository --url http://localhost:8081 --report console,json,junit,markdown

Strict known-issues mode (waived known issues become failures):

pnpm test:engine --component aas-discovery --url http://localhost:8086 --strict-known-issues

Use a custom artifact directory:

pnpm test:engine --component aasx-file-server --url http://localhost:8087 --report-dir coverage/test-engine

Report Behavior

  • console: 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:

  • 0 if 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.

Known Backend Deviations (Integration)

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-repository GetAllSubmodels-Path can return an empty result array even when a fresh submodel has been created in the same run. The test now validates success and response shape, but does not require result.length > 0.
  • submodel-repository value-only cursor not-found behavior is tracked as known-specification-bug.

Maintenance rule for new findings:

  • If a backend deviation is confirmed, annotate the affected test with a short reason (known-backend-bug or known-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.

Import Styles

The SDK supports multiple import styles for flexibility:

Direct Imports (Recommended for most use cases)

// 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';

Namespaced Imports (For organized code)

// 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');

Mixed Style

// 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');

Usage Examples

Basic Client Usage

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.

Using the AasService (High-level API)

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
  });
}

Using the SubmodelService (High-level API)

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,
});

Using Utility Functions

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}

XML Serialization and Deserialization

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);

About

BaSyx TypeScript SDK for developing applications and components for the Asset Administration Shell (AAS)

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors