Data

There are different types of data that can be used in a Jovo app. For example, some data might be only relevant for a specific interaction, a component, or session. Other data might be needed across sessions and should be persisted using the Jovo database integrations.

Introduction

These are the types of data that are usually only stored for a short amount of time (short-term data storage):

  • Request data: Only used for this specific interaction, stored in this.$data.
  • Component data: Only used for this specific component, stored in this.$component.data.
  • Session data: Only used for this specific session, stored in this.$session.data.
  • App data: This is a special data type that keeps the data stored as long as the server is running, which can be usually for caching. Stored in this.$app.data.

And here are some types of data that are typically persisted across user sessions (long-term data storage):

  • User data: User specific data stored in this.$user.data.
  • History: Data from previous interactions, accessible via this.$history.

Short-term Data Storage

Request Data

Request data is written into the Jovo object (this) and only stored for a specific request. This can be helpful if data is needed across different methods.

We recommend the following practice to store data in the Jovo object:

this.$data.someKey = 'someValue';

Component Data

Sometimes, a component might need to gather information that becomes irrelevant as soon as the component resolves.

You can store data only for a specific component like this:

this.$component.data.someKey = 'someValue';

This data is stored in the $state stack:

$state = [
  {
    componentPath: 'SomeComponent',
    data: {
      someKey: 'someValue',
    },
  },
];

The $state is stored as session data. Once the component is removed from it, the data is removed as well.

Session Data

Session data (sometimes also called session attributes) stores data that is only needed for the specific session.

You can store elements into the session data like this:

this.$session.data.someKey = 'someValue';

Most platforms offer some kind of local storage that allows you to store session dta in the JSON response back to the platform, which can then be accessed in the next request (as long as the session stays active).

Other platforms (like Facebook Messenger) don't support this. For them, you need to enable session as storedElement in your database integration

App Data

App data is a special data type that stores data into the app object, which exists as long as the server is running:

this.$app.data.someKey = 'someValue';

For example, this can be used for non-user-specific information that needs an API call that is not necessary to be executed at every request. By storing data in the app object, the API call only needs to be done once while the server is running (or the serverless function like AWS Lambda is warm).

Long-term Data Storage

User Data

The Jovo $user object uses database integrations to persist user specific data across sessions.

The data can be stored like this:

this.$user.data.someKey = 'someValue';

History

The $history uses database integrations to make it possible to store data of each interaction into a persisted history. This enables your app to remember what was previously said, repeat things like previous output, or just track usage over time.

The history object contains an items array that is sorted by time (DESC). The most recent history item can be accessed like this:

// Get the history element for the most recent interaction
this.$history.prev;

// Alternatively access it using the items array
this.$history.items[0];

/* Sample result if output and input are enabled
{
  output: {
    message: 'Hello World!',
  },
  input: {
    intent: 'HelloWorldIntent',
  },
}
*/

For each database integration, you can add the history configuration to the storedElements property.

// src/app.dev.ts

new FileDb({
  // ...
  storedElements: {
    // ...
    history: {
      enabled: true,
      size: 3, // Size of the this.$history array

      // Example: Store this.$output into the history
      output: true, // this.$output, optional
    },
  },
}),

You can add the following Jovo properties to the history:

  • request: Stores this.$request
  • input: Stores this.$input
  • output: Stores this.$output
  • response: Stores this.$response
  • state: Stores this.$state
  • entities: Stores this.$entities

You can even add your own custom data to the history. Add any property with a function that returns the data to be stored. Here is an example for a someCustomData property:

// src/app.dev.ts

new FileDb({
  // ...
  storedElements: {
    // ...
    history: {
      // ...
      someCustomData: (jovo: Jovo) => {
          return `Some custom data for user ${jovo.$user.id}`
      },
    },
  },
}),

Here is an example how the history is then stored in a database:

[
  {
    // ...
    history: {
      items: [
        {
          output: {
            message: 'Yes! I love pizza, too.',
          },
          input: {
            type: 'INTENT',
            intent: 'AMAZON.YesIntent',
          },
          state: [
            {
              componentPath: 'LoveHatePizzaComponent',
            },
          ],
          someCustomData: 'Some custom data for user amzn1.account.AM3B00000000000000000000000',
        },
        {
          output: {
            message: 'Hello World! Do you like pizza?',
            listen: true,
          },
          input: {
            TYPE: 'LAUNCH',
          },
          state: [
            {
              componentPath: 'LoveHatePizzaComponent',
            },
          ],
          someCustomData: 'Some custom data for user amzn1.account.AM3B00000000000000000000000',
        },
      ],
    },
    createdAt: '2021-06-30T06:45:40.444Z',
    updatedAt: '2021-06-30T06:47:44.253Z',
  },
];

Example: Repeat

For voice interfaces, it is recommended to add a functionality that lets users ask to repeat the previous message. For example, Alexa has a built-in AMAZON.RepeatIntent for use cases like this.

You can repeat the previous message by storing the $output array in the $history. Here is an example for FileDb:

// src/app.dev.ts

new FileDb({
  // ...
  storedElements: {
    // ...
    history: {
      enabled: true,
      size: 1, // Size of the this.$history array
      output: true, // Store this.$output in history
    },
  },
}),

In your handler, you can then access the previous output using this.$history.prev.output. The below example has a handler for this as part of the GlobalComponent:

// src/components/GlobalComponent.ts

import { Component, BaseComponent, Global, Intents } from '@jovotech/framework';
// ...

@Global()
@Component()
export class GlobalComponent extends BaseComponent {
  // ...

  @Intents(['RepeatIntent'])
  repeatPreviousMessage() {
    if (this.$history.prev?.output) {
      return this.$send(this.$history.prev.output);
    } else {
      return this.$send('Unfortunately, there is nothing to repeat.');
    }
  }
}

Since in this example, the repeat handler is part of a global component, this comes with the following benefits:

  • The handler can be reached from anywhere, even if the flow is currently deep down inside subcomponents.
  • Global components don't get added to the $state stack. This way, the interaction stays in the previous component and the user can proceed with the flow after hearing the repeated message.

If you are using UNHANDLED as part of a component, it is possible that the global component handlers don't get reached because the UNHANDLED handler of the current component gets prioritized by default. Learn more about this in the routing documentation.

To have the repeat handler always be executed instead of UNHANDLED, you can use the prioritizedOverUnhandled property:

// src/components/GlobalComponent.ts

import { Component, BaseComponent, Global, Handle } from '@jovotech/framework';
// ...

@Global()
@Component()
export class GlobalComponent extends BaseComponent {
  // ...

  @Handle({ intents: ['RepeatIntent'], prioritizedOverUnhandled: true })
  repeatPreviousMessage() {
    if (this.$history.prev?.output) {
      return this.$send(this.$history.prev.output);
    } else {
      return this.$send('Unfortunately, there is nothing to repeat.');
    }
  }
}