Skip to main content

Store app and user states

If your app needs some internal global states or user-specific states, the package @dynatrace-sdk/client-state provides app states and user app states for that purpose. In general, app states can be read by all authenticated users of the app, while user app states are private to the user who wrote that state. That package exposes those states via key-value interfaced operations. The key designates the purpose of the stored app state, the value contains the stored user app state value. All operations can be imported via the stateClient namespace.

When you run your app on your local development server, the states that the SDK manipulates are confined to the scope of local development only, so they are persisted separately from the states of the deployed app. As a result, you will not run into the danger of overriding the states of the deployed app during development.

States are persistent across updates of an app. Therefore any migration of states potentially necessary for the new version of an app must be done in the app code itself.

Set a state by key

To set the app state or user app state, you can use the setAppState or setUserAppState function from the @dynatrace-sdk/client-state package.

Tip

Adding a version field to your state is recommended to ease a future migration.

import { stateClient } from '@dynatrace-sdk/client-state';

const dataFetchedFromExternalSource = {
version: '1.2',
timestamp: 1663231611,
weather: [{ city: 'Vienna', temperature: 23, unit: 'celsius' }],
};
stateClient.setAppState({
key: 'weather',
body: { value: JSON.stringify(dataFetchedFromExternalSource) },
});

stateClient.setUserAppState({ key: 'favourite-hosts', body: { value: 'HOST-5A58A3AD724F5ABB,HOST-C09983F94EFCE493' } });

State validity

By default, app states and user app states don't expire. To define states that are only valid for a certain period, you can provide an optional validUntilTime, which ensures the presence of the state until the time expires.

States are deleted after their validUntilTime elapses. An example use case is a cache for storing app-specific data.

To provide the validUntilTime, there are two possibilities:

  • A relative time format such as now+1m or now+30d
    • The format is now+NU where N is the amount of time, U is the unit of time (s - seconds, m - minutes, h - hours, d - days)
  • A timestamp in ISO 8601 format.

For both types, it's allowed to specify a timeframe from 1 minute to 90 days in the future.

import { stateClient } from '@dynatrace-sdk/client-state';

stateClient.setUserAppState({ key: 'short-living-state', body: { value: 'my value', validUntilTime: 'now+30m' } });
stateClient.setAppState({ key: 'long-living-state', body: { value: 'my-value', validUntilTime: 'now+20d' } });
stateClient.setAppState({
key: 'valid-with-timestamp',
body: { value: 'my-value', validUntilTime: '2024-01-01T01:02:03.165Z' },
});
Tip

This operation requires the following scopes:

  • state:app-states:write
  • state:user-app-states:write
Read more about scopes in this guide.

Get a state by key

To fetch a previously stored state, you can use the getAppState and getUserAppState operations with the key as the parameter used for the corresponding write operation. The validUntilTime is returned in ISO 8601 format.

import { stateClient } from '@dynatrace-sdk/client-state';

const appStateWeather = await stateClient.getAppState({ key: 'weather' });
const weather = JSON.parse(appStateWeather.value);
if (appStateWeather.validUntilTime) {
const validUntilTime = new Date(appStateWeather.validUntilTime);
// …
}

const userAppStateFavouriteHosts = await stateClient.getUserAppState({ key: 'favourite-hosts' });
const favouriteHosts = userAppStateFavouriteHosts.value.split(',');
Tip

This operation requires the following scopes:

  • state:app-states:read
  • state:user-app-states:read
Read more about scopes in this guide.

These operations throw an exception when the state you try to retrieve does not exist. If that is an expected scenario in your app, you should add some exception handling around it. Alternatively, you could use the list endpoint by filtering for a specific key to get an empty or one-element array.

List all states

It can be handy to get a list of all app states or user app states currently in use, especially if your app does not use a fixed set of known static keys but dynamically created keys. The functions getAppStates and getUserAppStates provide that functionality.

import { stateClient } from '@dynatrace-sdk/client-state';

const appStateKeys: Array<{ key: string }> = await stateClient.getAppStates({});
const userAppStateKeys: Array<{ key: string }> = await stateClient.getUserAppStates({});
Tip

This operation requires the following scopes:

  • state:app-states:read
  • state:user-app-states:read
Read more about scopes in this guide.

By default, getAppStates and getUserAppStates only return the keys. You can define additional fields to be returned by specifying them in a comma-separated string in the addFields parameter.

import { stateClient, ListAppState, ListUserAppState } from '@dynatrace-sdk/client-state';

const appStates: ListAppState[] = await stateClient.getAppStates({
addFields: 'value,modificationInfo.lastModifiedBy',
});
const userAppStates: ListUserAppState[] = await stateClient.getUserAppStates({
addFields: 'value,modificationInfo,validUntilTime',
});
Tip

This operation requires the following scopes:

  • state:app-states:read
  • state:user-app-states:read
Read more about scopes in this guide.

Filter states

The getAppStates and getUserAppStates methods also allow filtering the states via the filter parameter. The following fields are supported for filtering:

  • key, supports operators: =, !=, contains, starts-with, and ends-with
  • modificationInfo.lastModifiedTime, supports operators: =, !=, <, <=, >, and >=
  • modificationInfo.lastModifiedBy, supports operators: =, !=, contains, starts-with, and ends-with
  • validUntilTime, supports operators: =, !=, <, <=, >, and >=

Operators contains, starts-with, and ends-with are case-insensitive. Comparisons via =, != are case-sensitive.

Conditions can be connected via and and or. Individual conditions can be negated by not. The filter string may be up to 256 characters long. You can nest conditions (using round brackets) up to 2 levels deep.

import { stateClient } from '@dynatrace-sdk/client-state';

const appStateKeys: Array<{ key: string }> = await stateClient.getAppStates({
filter: "modificationInfo.lastModifiedTime > '2022-07-01T00:10:05.000Z'",
});
const userAppStateKeys: Array<{ key: string }> = await stateClient.getUserAppStates({
filter:
"(key starts-with 'favourite' or key starts-with 'favorite') and not(key contains 'disk' or key contains 'process')",
});
const oneElementOrEmpty: Array<{ key: string }> = await stateClient.getAppStates({
filter: "key = 'might-not-exist'",
});
Tip

This operation requires the following scopes:

  • state:app-states:read
  • state:user-app-states:read
Read more about scopes in this guide.

Delete a state by key

Of course, you can delete app states and user app states that are not needed anymore. In addition, this will free the equivalent of the size of the deleted value in the state storage quota available to the app:

import { stateClient } from '@dynatrace-sdk/client-state';

stateClient.deleteAppState({ key: 'weather' });
stateClient.deleteUserAppState({ key: 'favourite-hosts' });
Tip

This operation requires the following scopes:

  • state:app-states:delete
  • state:user-app-states:delete
Read more about scopes in this guide.

Delete all states

There are methods provided to delete all app states (deleteAppStates) and user app states (deleteUserAppStates) to make it easy to reset the states of the app completely:

import { stateClient } from '@dynatrace-sdk/client-state';

stateClient.deleteAppStates();
stateClient.deleteUserAppStates();
Tip

This operation requires the following scopes:

  • state:app-states:delete
  • state:user-app-states:delete
Read more about scopes in this guide.

Still have questions?
Find answers in the Dynatrace Community