TypeScript solution framework (SDK)

Introduction

The Solution Framework provides numerous components for the implementation of a Low-Code Project based on the project's design model. That means, whatever you modelled in the design phase will be reflected by pre-generated code.

Tip: All components of the Solution Framework are accessible in any implementation file of the source code by typing the reserved word this.

From here it is possible to access other components that are needed for the implementation such as input, output, repository, factory, util …etc.

Note: Some components are grouped under the namespace that they are modelled.

For example:

// Use the sdk to create an entity that was modelled in a namespace with the acronym: nsacrnm1
const entity1 = this.factory.entity.nsacrnm1.entityIdentifier();
 
// Use the sdk to create an entity that was modelled in another namespace with the acronym: nsacrnm2
const entity2 = this.factory.entity.nsacrnm2.entityIdentifier();

SDK components

These SDK components can be used in multiple implementation files depending on the type of namespace they belong to.

Input

Holds the values of each input property provided by the user. It is important to define the properties of the input entity in Solution Designer, otherwise they will not be accessible in the Action SDK.

// Access the value of the input property
const val = this.input.propertyName

Output

Contains the output properties and provides their value as output to the user. Before assigning values to the output properties you have to initialize the type of the output entity.

// Initialize output via factory and assign value to output properties
this.output = this.factory.entity.nsacrnm.serviceIdentifier_Output();
this.output.property1 = 'Some property value';

Instance

Contains all the properties that belong to an instance of an entity (of all types). The properties that are contained in this component are editable.

// Edit instance property value
this.instance.property1 = 'New property value';

Factory

Entity factory

This is used to create an entity object (of all types including input and output entities). If this is assigned to a variable, then the variable will contain all the properties and commands that belong to the specific entity. The properties of the variable are editable.

// Create an Entity using entity factory
const entity1 = this.factory.entity.nsacrnm.Entityidentifier();
// Access entity1 properties
entity1.propIdentifier = 'some property value';

Error factory

This is used to create a new business error that can be thrown to the user.

// Create an error using error factory
const error1 = this.factory.error.nsacrnm.ErrorIdentifier();

// Access error1 properties
error1.errorDescription = 'Some Error Description';
error1.errorMessage = 'Some Error Message;

Reference factory

This is used to create a reference to an external entity or a root entity on the same or a different namespace inside the solution. If referencing external entities, this component calls the constructor, and it returns the created instance of the external entity. If referencing other aggregates, it returns the instance that is being referenced. In both cases the returned values are read-only.

// Create an entity reference for root entity using reference factory
const ref1 = this.factory.reference.nsacrnm.RootEntityIdentifier(someEntityId);

// Load referenced entity
const rootEntity = await ref1.load();

// create a reference to an external entity that takes an input

// Create input object via factory
const input = this.factory.entity.cptest.ExternalEntityIdentifier_ConstInput();
input.propertyIdentifier = 'Some property value';

External factory

This calls the constructor method of the external entity. It returns the local instance of the external entity from which it is possible to access the associated properties as well as call the load and validate methods.

// Create external entity reference
const extEntity = await this.factory.external.cptest.ExternalEntityIdentifier(input);

// Access the associated properties 
extEntity.assProp1;

// Load external entity - no input needed
extEntity.load();

// Validate external or update local - no input needed
extEntity.validate();

Event factory

This is used to create an event. It returns empty properties that belong to the payload and the publish function in order to trigger the event. The properties of the payload are editable. The properties and the values assigned to them will be the input of the agent that is called after triggering the event.

// Create an event using event factory
const event1 = this.factory.event.nsacrnm.EventIdentifier();
 
// create event payload using entity factory
const payoad = this.factory.entity.nsacrnm.EntityIdentifier();
 
// set payload properties
payload.propIdentifier = 'some property value';
 
// Set event payload
event1.payload = payoad;
 
// publish event
await event1.publish();

Schema factory

This is used to create schema objects. It returns the properties that belong to the schema (i.e. the entire schema object/array/oneOf). The properties returned are editable. This is accessible from operation scripts and .test.ts files of operations.

// Create a schema using schema factory
const schema1 = this.factory.schema.nsacrnm.SchemaIdentifier();
 
// Access schema1 properties
schema1.propIdentifier = 'some property value';

Repository

This is used to access the data-store of a solution and retrieve an existing instance of a root entity given an id or a filter. It is also possible to create a completely new root entity through this component by accessing the factory command. It returns the entity object (or an array of entity objects when searched by filter). From each entity object all the properties and instance commands linked to this entity are available. The returned values are read-only.

// Access the repository of the specific root entity and choose the find
// Find takes an object argument and a filter argument
// In the Find Configuration Object, 
// The first one is a Boolean indicated whether all children should be included in the filtering (true) or not (false).
// The second is the limit (optional field) that takes string of a pair of numbers as a value where the first number is the <offset> and the second is the <amount> of entries that will be returned
// The third is the sortBy argument that takes a string of a pair where the first element is the local identifier of a property and the second is either ASC or DESC

const rootEntityInstance = this.repo.nsacrnm.RootEntityIdentifier.find(FindConfigurationObj, Filter);
//Example
const rootEntityInstance = this.repo.nsacrnm.RootEntityIdentifier.find({includeSubentities: true, limit: "2,20", sortBy: "customerID,DESC"}, `name == "${name}"`);

// Find entity by id
const rootEntityInstance = this.repo.nsacrnm.RootEntityIdentifier. findById(`EntityId`);

Filter expressions

A more detailed description of filter expressions is available at filter expressions

Services

This is used to call services that have been modelled and defined in one solution in Solution Designer. This returns the output of the service implementation.

// Initialize service input via factory and assign value to input properties

const input = this.factory.entity.nsacrnm.serviceIdentifier_Input();
input.property1 = 'Some property value';

// Create a service instance
const service1 = await this.services.nsacrnm.serviceIdentifier(input);

// Access service output
const val = service1.output.propIdentifier;

Solution

It is possible to access the project details for logging purposes by using the this.solution component.

const acrnm = this.solution.acronym;
const name = this.solution.name;
const category = this.solution.category;

The values in the this.solution are read-only.

InstanceOf

Entity instance check

In the implementation file of a service, command, agent and operation entity instance check can be done through isInstanceOf functionality, the entity instance check will also check for the entity hierarchy.

// check an object
if (this.isInstanceOf.entity.ns1.BlueCar(car)) {
    // now one can access the special property that only blue car has
    const prop = car.specialPropOfBlueCar;
}

// check BlueCar is a Car (Since BlueCar is a child of Car, the entity instance check will also check for the entity hierarchy)
if (this.isInstanceOf.entity.ns1.Car(blueCar)) {
      // now one can access the special property that only car has
      const prop = blueCar.specialPropOfCar;
}

Error instance check

In the implementation file of a service, command, agent and operation error instance check can be done by using isInstanceOf functionality, thus enable to code handling logic for different error instances

General errors checking

try {
      // try some logic
} catch (e) {

      // check for GeneralError
      if (this.isInstanceOf.error.GeneralError(e)) {
        // now can handle this general error
      }

      // check whether the validation of an entity (used as input or instance) fails
      if (this.isInstanceOf.error.ValidationError(e)) {
        // now can handle this validation error
      

      // check for a child of abstract BusinessError
      if (this.isInstanceOf.error.BusinessError(e)) {
        // now can handle this Business error
      }

      // check whether an  action (e.g. a command) is not currently available
      if (this.isInstanceOf.error.ActionNotAvailableError(e)) {
        // now can handle this ActionNotAvailable error
      }

      // check whether an aggregate is found in the datastore
      if (this.isInstanceOf.error.AggregateNotFound(e)) {
        // now can handle this AggregateNotFound error
      }

      // check whether an external request (i.e. this.util.request) caused an error
      if (this.isInstanceOf.error.ExternalRequestError(e)) {
        // now can handle this ExternalRequest error
      }
     
      // check whether an internal request caused an error
      if (this.isInstanceOf.error.InternalRequestError(e)) {
        // now can handle this InternalRequest error
      }
}

Business errors checking

try {
      // try some logic
} catch (e) {
      // check whether the error matches any of the errors modelled in Solution Designer      
      if (this.isInstanceOf.businessError.ns1.NoBalanceAvailable(e)) {
        // now can handle this specific business error
      }

      if (this.isInstanceOf.error.ValidationError(e)) {
        // now can handle this validation error
      }

      if (this.isInstanceOf.businessError.ns2.NoCustomerFound(e)) {
        // now can handle this specific business error
      }
}

APIs

As a precondition the API needs to be defined as an API dependency in an integration namespace and needs an Open API 3.0 or Swagger 2.0 specification. If this is given, the API could be called within the implementation of an integration service.

Call an operation of the API

// call operation "getPets" in integration API "petstore"
const result = await this.apis.petstore.getPets();

Call an operation with path parameters and query parameters

If path parameters or query parameters are defined, the function offers the possibility to set these parameters which are identified by the name. The values of the path parameters as well as the query parameters are expected to be JavaScript objects where each key is the name of the parameter. The URL and ca_certs are used automatically according to the specification.

// findPets by owner (pathParameter) and filter by status and age (queryParameter)
const result = await this.apis.petstore.getPetsOfOwner({
  owner: 'MyOwner'
}, {
  status: 'alive',
  age: 5
});

Call an operation with request body

If a request body is defined for the operation, the function offers the possibility to set the request body as a JavaScript object.

// add new pet using "pet data" (request body)
await this.apis.petstore.addPet({
  owner: 'MyOwner',
  status: 'alive',
  age: 1
});

Read result of operation

The result of the operation always consists of the status code and the actual response data. Both are returned from the operation call.

try {
  const result = await this.apis.petstore.getPets();
  // read result depending on status code
  if (result.status === 200) {
    // everything is good --> read data
    const pets = result.data;
    console.log(pets[0].status);
  }
} catch (e) {
  // handle error
  this.util.log.error(e.message);
  throw e;
}

API binding

The value of the API bindings is provided under this.apiBindings.APIIdentifier()

// get binding of petstore API
const bindingValue = await this.apiBindings.getPetstore();

Logging

This provides utility logging functions info, warn, error and debug.

// the log.error() can take an array of argumnets
this.util.log.error('Error Message', errorObject);

// the log.debug() can take an array of argumnets
this.util.log.debug('Debug Message', errorObject);

// the log.info() can take an array of argumnets
this.util.log.info('Info Message', object);

// the log.warn() can take an array of argumnets
this.util.log.warn('Warning Message', object);

Audit logging

The Audit Logging services offers the transaction and performance logging functionality.

// the revlog.publish() can take an array of argumnets
this.util.revlog.publish('Audit Message', Object);

Global Kafka consumers can be implemented to consume the audit log messages and persist them in a secure data store.

HTTP requests

To make an HTTP request against external APIs.

The this.util.request provides functions for the GET, POST, PUT, PATCH, DEL HTTP operations.

const response = await this.util.request.get(url: string, queryParams?: HTTPQueryParams, headers?: HTTPHeaders, sslConfig?: SSLConfig);
OR
const response = await this.util.request.del(url: string, queryParams?: HTTPQueryParams, headers?: HTTPHeaders, sslConfig?: SSLConfig);
OR
const response = await this.util.request.patch(url: string, queryParams?: HTTPQueryParams, body?: any, headers?: HTTPHeaders, sslConfig?: SSLConfig);
OR
const response = await this.util.request.post(url: string, queryParams?: HTTPQueryParams, body?: any, headers?: HTTPHeaders, sslConfig?: SSLConfig);
OR
const response = await this.util.request.put(url: string, queryParams?: HTTPQueryParams, body?: any, headers?: HTTPHeaders, sslConfig?: SSLConfig);

Request context

This provides information regarding the current request context the command is executed with.

// get request context via this._context
const requestContext = this._context.requestContext;

Request

This provides access to operation request it contains the below components.

Path

This is used to give access to the request path parameters of an operation.

// access operation request path parameters
const val = this.request.path.pathParamIdentifier;

Query

This is used to give access to the request query parameters of an operation.

// access operation request query parameters
const val = this.request.query.queryParamIdentifier;

Body

This is used to give access to the request body of an operation. If the request body is a simple type (i.e. integer, number, string etc.) then the value is accessed immediately. Otherwise, in case the body is an object, the command is expanded to access each property. In case that the body is an array type, then the body can be treated similarly to a JavaScript array.

// access operation request body
const body = this.request.body;
const val = this.request.body.popertyIdentifier;

Response

This provides access to operation request it contains the below components

Status

This is used to access the response status of an operation. The value only takes the values that have been modelled in the Solution Designer.

// set response status to be returned
this.response.status = 200;

// access operation response status
const status = this.response.status;
Attention: Be aware that the default response status will be 501. This indicates that there is nothing implemented so far.

Body

This is used to access the response body of an operation to the user. If the body is a primitive type schema, then a value can be directly accessed / assigned. Otherwise, the body must be initialized to its corresponding schema type ( using the schema factory component) and then each property should be initialized accordingly.

// initialize response body via schema factory
const body = this.factory.nsacrnm.schema.schemaIdentifier();

// set operation response body
this.response.body = body;
const val = this.response.body.popertyIdentifier;

Headers

This is used to access the values of the response headers of an operation.

// access operation request body
const headers = this.response.headers;

Scope overview

Below is an illustrative table for the different scopes inside each implementation element.

ServiceCommandAgentOperationExternal Entity constructExternal Entity loadExternal Entity validate
this.inputooo o
this.outputooo ooo
this.instance o ooo
this.factory.entityoooo oo
this.factory.errorooo
this.factory.referenceooo
this.factory.eventooo
this.factory.schemas o
this.repooooo o
this.serviceso oo oo
this.util.logooooooo
this.util.revlogooooooo
this.request.path o
this.request.query o
this.request.body o
this.response.headers o
this.response.status o
this.response.body o
this.requestContextoooo