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.
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
ornow+30d
- The format is
now+NU
whereN
is the amount of time,U
is the unit of time (s - seconds, m - minutes, h - hours, d - days)
- The format is
- 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' },
});
This operation requires the following scopes:
state:app-states:write
state:user-app-states:write
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(',');
This operation requires the following scopes:
state:app-states:read
state:user-app-states:read
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({});
This operation requires the following scopes:
state:app-states:read
state:user-app-states:read
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',
});
This operation requires the following scopes:
state:app-states:read
state:user-app-states:read
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
, andends-with
modificationInfo.lastModifiedTime
, supports operators:=
,!=
,<
,<=
,>
, and>=
modificationInfo.lastModifiedBy
, supports operators:=
,!=
,contains
,starts-with
, andends-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'",
});
This operation requires the following scopes:
state:app-states:read
state:user-app-states:read
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' });
This operation requires the following scopes:
state:app-states:delete
state:user-app-states:delete
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();
This operation requires the following scopes:
state:app-states:delete
state:user-app-states:delete