Skip to main content

How to upgrade to version 2 of the SAP Cloud SDK for JavaScript

Overviewโ€‹

This document will guide you through the steps necessary to upgrade to version 2.0 of the SAP Cloud SDK. Depending on your project, some steps might not be applicable. The To-Do list is:

We have provided prerequisites at the beginning of each section. If it does not apply to your project, please proceed to the next one.

Update EcmaScript Runtimeโ€‹

We changed the compilation target of our source code from es5 to es2019. Depending on your configuration this may lead to compilation errors (TypeScript) or runtime errors in (JavaScript). If you run node version 12 or greater, no changes are necessary. If you run an older node version, please update to a newer version. You can find a list of breaking changes in the news section of the node.js website (e.g. "The Difference Between Node.js 10 and 12")

tip

We recommend switching to Node 14 or above when using the SAP Cloud SDK for JavaScript 2.0.

Update Public API Clientsโ€‹

Prerequisite: Your project uses published API clients like https://www.npmjs.com/package/@sap/cloud-sdk-vdm-business-partner-service (this applies to all direct dependencies in your package.json that start with @sap/cloud-sdk-vdm-).

We will publish updated clients containing version 2.0 of the SAP Cloud SDK after the beta phase. Once these clients are available, follow the following steps:

  • Find the used clients in your package.json.
  • Change the used version of the client to ^2.0.0.
  • Update entity imports using the steps described here.
  • Change the builder() call as described here.
  • Find the invocations of the request builders. This RegExp import.*from.*@sap\/cloud\-sdk\-vdm\-.* helps to find the files where the client is used.
  • Change the requestBuilder() call as described here.
  • Change the execute() call as described here.

If you want to check a client before we publish it, follow the step below to generate it locally.

Update Generated API Clientsโ€‹

Prerequisite: Your project uses generated API clients. If you are unsure if you have generated clients use this RegExp class.*RequestBuilder.*extends RequestBuilder to scan your project for RequestBuilders of a generated client.

We assume that you have installed the generator as a local devDependency in your project:

  • Find the installed generator in your package.json.
  • Update the version to ^2.0.0 using steps as described here.
  • Regenerate the client with the updated generator as described here.
  • Change the builder() call as described here.
  • Find the invocations of the request builders. This RegExp import.*from.*EntityName.* helps to find the files where the client is used. Replace EntityName with the name of your entities.
  • Change the requestBuilder() call as described here.
  • Change the execute() call as described here.

Replace Core Importsโ€‹

Prerequisite: Your project used imports from @sap-cloud-sdk/core. Check your package.json and search globally for any import statements from this package.

We split the big module @sap-cloud-sdk/core into multiple smaller modules. These imports need to be adjusted:

  • Find the imported method in your code.
  • Find the new package where the method is located as described here.
  • Add the new package to the package.json and remove @sap-cloud-sdk/core as described here.
  • Replace the old import with the package name.

Note that we reduced the number of objects in the stable public API. There are three cases:

  • The object is part of the public API: { publicApiMethod } from '@sap-cloud-sdk/connectivity'.
  • The object is moved to the internal API: { internalApiMethod } from '@sap-cloud-sdk/connectivity/internal'.
  • The object is removed without replacement see: deprecated object list.

Your IDE should warn you if a module has no exported member with the given name. The use of the internal API is considered a temporary quick fix. If you need functionality please open an issue and state your requirements. Details on the API contract are discussed in this ADR

Adjust Serializing and Deserializing of Entitiesโ€‹

Prerequisite: You consume non-primitive entity properties like dates, duration, and times.

In the past, one OData datatype was always transformed to the same data type in TypeScript. For example, time-related properties are transformed into moment instances. We introduced a new (de-)serialization to make this more flexible.

If you would like to switch to the Temporal (de-)serialization for date/time objects, please follow the documentation and adjust your code base. The default serialization for time-related properties will be changed from Moment.js to Temporal once it reaches Stage 4. Since Moment.js is a discontinued project with a large size we recommend sticking with the new standard. Note that in the 2.0 version, the default remains Moment.js.

Get Supportโ€‹

Prerequisite: You tried to do the steps above but hit some impediments.

Please create a support issue describing the problem you have.

Appendixโ€‹

Update Entity Importsโ€‹

In version 1 of the SAP Cloud SDK, each entity (e.g. BusinessPartner) was imported directly:

import {
BusinessPartner,
BusinessPartnerAddress
} from '@sap/cloud-sdk-vdm-business-partner-service';

This has been changed in version 2.0.

  • Each entity now has its API class (e.g. MyEntityApi).
  • A new service function (e.g. myEntityService()) provides getters for instances of all the entities' API classes, which can be consumed via JavaScript Object Destructuring.

To access an entity, you need to import the service and destructure the API class objects. To do so, first update the imports following the steps:

  • Replace the entity imports with the service import. Usually, the service name can be inferred from the npm package itself, i.e., everything after @sap/cloud-sdk-vdm- in camelCase. E.g.:
import { businessPartnerService } from '@sap/cloud-sdk-vdm-business-partner-service';
  • Destructure this service to unpack the API objects you need.
const { businessPartnerApi, businessPartnerAddressApi } =
businessPartnerService();
danger

You should use the object destructuring like the example above instead of calling the service multiple times for every API object, to avoid initializing multiple service instances. Here is a BAD example, that you should NOT follow:

// do NOT do this
const businessPartnerApi = businessPartnerService().businessPartnerApi;
// do NOT do this
const businessPartnerAddressApi =
businessPartnerService().businessPartnerAddressApi;
note

The API object getters (e.g. businessPartnerApi) are denoted using camel case, whereas the corresponding classes (e.g. BusinessPartnerApi) follow pascal case.

Breaking Change on the EntityBuilder APIโ€‹

For the implementation of the (de-)serializers, two changes are introduced in version 2.0:

The static builder() method has been renamed to entityBuilder() and it is now an instance method on the entity API class:

// Version 1

MyEntity.builder()
.getAll()
.firstName('Peter')
.lastName('Pan')
.toMyRelatedEntity([MyRelatedEntity.builder().country('Neverland').build()])
.build();

// Version 2

const { myEntityApi, myRelatedEntityAPI } = myEntityService();
myEntityApi
.entityBuilder()
.firstName('Peter')
.lastName('Pan')
.toMyRelatedEntity([
myRelatedEntityAPI.entityBuilder().country('Neverland').build()
])
.build();

Breaking Change on the RequestBuilder APIโ€‹

To make the middleware concept possible, it was necessary to add more state to the request builder.

All the static properties used to build select or filters are also now part of an instance and are exposed via a schema property. The requestBuilder() method is also not a static method anymore but an instance method of an API object:

// Version 1

MyEntity.requestBuilder()
.getAll()
.select(MyEntity.FIRST_NAME, MyEntity.LAST_NAME)
.execute(destination);

// Version 2

const { myEntityApi } = myEntityService();
myEntityApi
.requestBuilder()
.getAll()
.select(myEntityApi.schema.FIRST_NAME, myEntityApi.schema.LAST_NAME)
.execute(destination);

Breaking Change on the Execute APIโ€‹

The breaking change which will affect most users is the change in the execute methods. In the past, there was a separate DestinationNameAndJwt object and a DestinationOptions to define how a destination was fetched from the service. In version 2, there are two changes:

  • There is a single object DestinationFetchOptions containing all information.
  • The property userJwt was renamed to just jwt.

A typical API adjustment would look like this:

// Version 1

MyEntity.requestBuilder()
.getAll()
.execute(
{ destinationName: 'myDestination', userJwt: 'yourJwt' },
{ useCache: true }
);

// Version 2

const { myEntityApi } = myEntityService();
myEntityApi.requestBuilder().getAll().execute({
destinationName: 'myDestination',
jwt: 'yourJwt',
useCache: true
});

Note that all options besides the destinationName are optional. We have picked the execute() method of the request builders to illustrate the change. But the same changes apply to all places the two objects (DestinationNameAndJwt and DestinationOptions) were used. Prominent examples are the executeHttpRequest(), executeRaw() or getDestination() methods:

//Version 1

getDestination('myDestination', { userJwt: 'yourJwt', useCache: true });

executeHttpRequest(
{ destinationName: 'myDestination', userJwt: 'yourJwt' },
{ method: 'get' }
);

//Version 2

getDestination({
destinationName: 'myDestination',
jwt: 'yourJwt',
useCache: true
});

executeHttpRequest(
{
destinationName: 'myDestination',
jwt: 'yourJwt',
selectionStrategy: alwaysProvider
},
{ method: 'get' }
);

Token Validation May Failโ€‹

The SAP Cloud SDK for JavaScript version 2 uses the @sap/xssec package. This may break token validation with an error like this:

Error: JWT token with audience: ['aud1', 'aud2'] is not issued for these clientIds: ['client1', 'client2'].

This likely means that you need to set ห‹iasToXsuaaTokenExchangeห‹ to false to disable the IAS token to XSUAA token exchange.

getDestination({
destinationName: 'myDestination',
jwt: 'yourJwt',
iasToXsuaaTokenExchange: false,
useCache: true
});
executeHttpRequest(
{
destinationName: 'myDestination',
jwt: 'yourJwt',
iasToXsuaaTokenExchange: false,
selectionStrategy: alwaysProvider
},
{ method: 'get' }
);

If no token is provided (e.g. background processes), you can set iss to the issuer of the JWT (e.g. https://<your-subdomain>.localhost:8080/uaa/oauth/token). This will fetch the destination service token for the given tenant without validation. This can lead to mistakes regarding the isolation of user data, so this option should be used only if necessary.

getDestination({
destinationName: 'myDestination',
iss: 'https://<your-subdomain>.localhost:8080/uaa/oauth/token'
});
executeHttpRequest(
{
destinationName: 'myDestination',
iss: 'https://<your-subdomain>.localhost:8080/uaa/oauth/token'
},
{ method: 'get' }
);

How to Find the New Packagesโ€‹

We have split the @sap-cloud-sdk/core package into the following smaller packages:

  • @sap-cloud-sdk/connectivity contains all Cloud Foundry connectivity service related methods like getDestination().
  • @sap-cloud-sdk/http-client contains the http client with built-in connectivity with executeHttpRequest().
  • @sap-cloud-sdk/odata-common contains the OData functionality shared between versions 2 and 4. This module is a technical module with no public API.
  • @sap-cloud-sdk/odata-v2 contains the OData functionality specific to OData version 2.
  • @sap-cloud-sdk/odata-v4 contains the OData functionality specific to OData version 4.
  • @sap-cloud-sdk/openapi contains methods for OpenAPI clients.

In most situations, it should be natural in which module the original object is located. You can search for the method in two places:

  • You can do a code search on GitHub e.g. https://github.com/SAP/cloud-sdk-js/search?q=function+getDestination to quickly find the package.
  • You can search in the API specification and select version 2.

Note that for version 2 the executeHttpRequest() function offers all the destination retrieval options.

How to Install Version 2 Dependenciesโ€‹

This section lists the commands for removing old dependencies and installing dependencies in the correct version.

To remove the @sap-cloud-sdk/core package from node_modules and package.json, run the below command in your project's terminal:

npm uninstall --save @sap-cloud-sdk/core

Once you have found the packages to replace @sap-cloud-sdk/core, install them using the relevant commands below:

npm install @sap-cloud-sdk/http-client
npm install @sap-cloud-sdk/connectivity
npm install @sap-cloud-sdk/odata-v2
npm install @sap-cloud-sdk/odata-v4

Update Generatorsโ€‹

You can install the generator packages as a devDependency:

npm install -D @sap-cloud-sdk/generator
npm install -D @sap-cloud-sdk/openapi-generator

For details on generation have a look at the OData or OpenAPI generation instructions.

Deprecated Methodsโ€‹

The table below gives a list of the removed methods for which a replacement is provided.

deprecated methodreplacementsource file
_fieldNamefield (this a property of the Filter class)filter.ts
addAuthorizationHeaderbuildAuthorizationHeadersauthorization-header.ts
AnyFieldEdmTypeFieldany-field.ts
AnyFieldBaseEdmTypeFieldany-field.ts
applyPrefixOnJsConfictFunctionImportsapplyPrefixOnJsConflictFunctionImportsname-formatting-strategies.ts
applyPrefixOnJsConfictParamapplyPrefixOnJsConflictParamname-formatting-strategies.ts
applySuffixOnConflictDashUniqueNameGeneratorname-formatting-strategies.ts
applySuffixOnConflictUnderscoreUniqueNameGeneratorname-formatting-strategies.ts
BigNumberFieldOrderableEdmTypeFieldbig-number-field.ts
BigNumberFieldBaseOrderableEdmTypeFieldbig-number-field.ts
BinaryFieldEdmTypeFieldbinary-field.ts
BinaryFieldBaseEdmTypeFieldbinary-field.ts
BooleanFieldEdmTypeFieldboolean-field.ts
BooleanFieldBaseEdmTypeFieldboolean-field.ts
buildAndAddAuthorizationHeaderbuildAuthorizationHeadersauthorization-header.ts
clientCredentialsGrant@sap/xssecxsuaa-service.ts
CompleteDecodedJWTJwtjwt.ts
ComplexTypeAnyPropertyFieldEdmTypeFieldany-field.ts
ComplexTypeBigNumberPropertyFieldOrderableEdmTypeFieldbig-number-field.ts
ComplexTypeBinaryPropertyFieldEdmTypeFieldbinary-field.ts
ComplexTypeBooleanPropertyFieldEdmTypeFieldboolean-field.ts
ComplexTypeDatePropertyFieldOrderableEdmTypeFielddate-field.ts
ComplexTypeDurationPropertyFieldOrderableEdmTypeFieldduration-field.ts
ComplexTypeEnumPropertyFieldEnumFieldenum-field.ts
ComplexTypeNumberPropertyFieldOrderableEdmTypeFieldnumber-field.ts
ComplexTypeStringPropertyFieldEdmTypeFieldstring-field.ts
ComplexTypeTimePropertyFieldOrderableEdmTypeFieldtime-field.ts
constructorUse other constructors instead.complex-type-field.ts
constructorUse superclass instead.odata-batch-request-config.ts
DateFieldOrderableEdmTypeFielddate-field.ts
DateFieldBaseOrderableEdmTypeFielddate-field.ts
DecodedJWTJWTPayload if you want to represent the decoded JWT payload or CompleteDecodedJWTjwt.ts
DeepFieldTypeFieldTypefield.ts
DestinationCachingOptionsCachingOptionsdestination-service-types.ts
DurationFieldOrderableEdmTypeFieldduration-field.ts
DurtionFieldBaseOrderableEdmTypeFieldduration-field.ts
EnumFieldBaseEnumFieldenum-field.ts
EnvironmentAccessorUse directly exported functions insteadenvironment-accessor.ts
errorWithCauseErrorWithCauseerror.ts
extractDataFromOneToManyLinkgetLinkedCollectionResultextract-data-from-one-to-many-link.ts
extractDataFromOneToManyLinkgetLinkedCollectionResultextract-data-from-one-to-many-link.ts
filterNullishValuespickNonNullishheader-util.ts
get batchIdboundaryodata-batch-request-config.ts
get changeSetIdboundarybatch-change-set.ts
get changeSetIdboundarybatch-change-set.ts
get contentTypedefaultHeadersodata-request-config.ts
get selects_selectslink.ts
getDestinationByNamegetDestinationFromEnvByNamedestination-from-env.ts
getDestinationCacheKeygetDestinationCacheKeyStrictdestination-cache.ts
getDestinationsgetDestinationsFromEnvdestination-from-env.ts
getHeaderpickIgnoreCaseheader-util.ts
getHeaderspickIgnoreCaseheader-util.ts
getHeaderValuepickValueIgnoreCaseheader-util.ts
getQueryParametersForFilterODataUri.getFilterget-filter.ts
getQueryParametersForOrderByODataUri.getOrderByget-orderby.ts
getQueryParametersForSelectionODataUri.getSelect and ODataUri.getExpandget-selection.ts
getResourcePathForKeysODataUri.getResourcePathForKeysget-resource-path.ts
HttpMethodUse method string directly, e. g. 'get' or 'GET'.http-client-types.ts
HttpReponseHttpResponsehttp-client-types.ts
ignoredFieldssetIgnoredFieldsupdate-request-builder-base.ts
initializeCustomFieldssetCustomFieldsentity.ts
isMulti?isCollectionvdm-types.ts
isMultiLink?isCollectionvdm-types.ts
jwtBearerTokenGrant@sap/xssecxsuaa-service.ts
JWTHeaderJwtHeaderjwt.ts
JWTPayloadJwtPayloadjwt.ts
MapTypeRecord<string, T>types.ts
mergeHeadersmergeIgnoreCaseheader-util.ts
NumberFieldOrderableEdmTypeFieldnumber-field.ts
NumberFieldBaseOrderableEdmTypeFieldnumber-field.ts
ODataBatchChangeSetBatchChangeSetbatch-change-set.ts
ODataBatchChangeSetBatchChangeSetbatch-change-set.ts
ODataCreateRequestConfigUse superclass instead.odata-create-request-config.ts
ODataDeleteRequestConfigUse superclass instead.odata-delete-request-config.ts
ODataFunctionImportRequestConfigUse superclass instead.odata-function-import-request-config.ts
ODataGetAllRequestConfigUse superclass instead.odata-get-all-request-config.ts
ODataGetByKeyRequestConfigUse superclass instead.odata-get-by-key-request-config.ts
ODataUpdateRequestConfigUse superclass instead.odata-update-request-config.ts
parseTypeisEdmType and complexTypeNameedmx-to-vdm-util.ts
protected getCurrentMapKeysasObjectentity.ts
refreshTokenGrantjwtBearerTokenGrantxsuaa-service.ts
RegisteredJWTClaimsThis interface will not be replaced. Use the higher level JWT types directly.jwt.ts
RegisteredJWTClaimsBasicThis interface will not be replaced. Use the higher level JWT types directly.jwt.ts
RegisteredJWTClaimsTenantThis interface will not be replaced. Use the higher level JWT types directly.tenant.ts
RegisteredJWTClaimsUserThis interface will not be replaced. Use the higher level JWT types directly.user.ts
replaceDuplicateKeysmergeLeftIgnoreCaseheader-util.ts
requiredFieldssetRequiredFieldsupdate-request-builder-base.ts
SelectableEdmTypeFieldSelectableTselectable.ts
serializersCommomserializersCommonpayload-value-converter.ts
static cloneclonelink.ts
static clonecloneone-to-one-link.ts
StringFieldEdmTypeFieldstring-field.ts
StringFieldBaseEdmTypeFieldstring-field.ts
TimeFieldOrderableEdmTypeFieldtime-field.ts
TimeFieldBaseOrderableEdmTypeFieldtime-field.ts
toBatchChangeSetserializeChangeSetbatch-request-serializer.ts
toBatchChangeSetserializeChangeSetbatch-request-serializer.ts
toPascalCase@sap-cloud-sdk/utilname-converter.ts
toPropertyFormat@sap-cloud-sdk/utilname-converter.ts
toSanitizedHeaderObjecttoSanitizedObjectheader-util.ts
toStaticPropertyFormat@sap-cloud-sdk/utilname-converter.ts
toTitleFormat@sap-cloud-sdk/utilname-converter.ts
toTypeNameFormat@sap-cloud-sdk/utilname-converter.ts
userTokenGrantjwtBearerTokenGrantxsuaa-service.ts
withCustomHeadersaddCustomHeadersrequest-builder-base.ts
withCustomQueryParametersaddCustomQueryParametersrequest-builder-base.ts
withCustomServicePathsetCustomServicePathrequest-builder-base.ts
withCustomVersionIdentifiersetVersionIdentifierupdate-request-builder-base.ts