Dev Guide
Dev GuideUser GuideGitHubNuGetDevCommunitySubmit a ticketLog In
GitHubNuGetDevCommunitySubmit a ticket

Storage

In Optimizely Connect Platform (OCP), there are three types of storage available for your app data.

Optimizely Connect Platform (OCP) apps have access to the following three types of storage to support storing sensitive data, configuration data, and general data structures necessary for the operation of your app. Your app must use the provided storage layers and should not attempt to store data outside OCP or on a file system.

🚧

Important

Storing or transmitting data outside OCP to any vendor other than the target of the integration is strictly forbidden.

  • Secrets store – Suitable for sensitive information not related to a settings form.
  • Settings store – Data backing the settings form. Suitable for any configuration-related data (including passwords and API keys), especially data you need to present to the user through the settings form.
  • Key value store – General purpose storage for simple data structures, lists, and sets. Designed for high throughput and moderately large data sets when necessary, but limited to about 400 KB per record.

Like other OCP app APIs, when using the stores, data is automatically partitioned on install to prevent cross contamination.

Best practices

  1. Cache results when you can. Every operation against a store is a network call. It is highly recommended to cache when you are able. For example, throughout the duration of a job.
  2. Minimize requests. When possible, co-locate your data to reduce the number of reads and writes. Especially for high-frequency code, such as a busy function, optimize where you store your data to reduce the number of store functions you need to call. For example, if you need both a third-party account ID and token when you make every outbound API call, consider storing them together in the same object in the secret store, even though the account ID may not be sensitive data.
  3. Use time-to-live (TTL). The key value store supports a TTL on every key. For any data that does not have to live for the duration of the app installation, setting a TTL ensures it gets cleaned up even if something unexpected happens.
  4. Use patch whenever possible. When updating a value in the store, patch guarantees an atomic change. Using patch (with or without a callback handler) to update your data is always safe and avoids the race conditions from using get followed by put, where another request could modify the data in between.
  5. Use interfaces for your data. Using interfaces to get and put data into the stores will remove any doubt about what type of data is stored there and reduce bugs with compile-time type checks.

🚧

Caution

Avoid caching App.storage as a constant outside of the scope of a function. Doing so can cause access to break in the case of a reinstall.

Example (secret store)

import {storage, ValueHash} from '@zaiusinc/app-sdk';
import * as App from '@zaiusinc/app-sdk';
import fetch from 'node-fetch';

export interface Credentials extends ValueHash {
  client_id: string;
  client_secret: string;
}
​
export interface UToken extends ValueHash {
  token?: string;
  issued?: number;
}
​
interface AuthResult {
  access_token: string;
  token_type: 'bearer';
}

export class Vendor {
  // ...
   
  /**
   * Authenticate with a Vendor API and get an auth token for future API calls
   * @param credentials
   * @param force true if we should discard the existing token and ask for a new one
   */
  public async authenticate(credentials: Credentials, force = false): Promise<string> {
    // Sometimes we will want to get a fresh token (credentials changed)
    if (!force) {
      const uToken = await storage.secrets.get<UToken>('utoken');
      if (uToken.token && !this.isExpiring(uToken)) {
        // we already have a valid token saved, use it
        return uToken.token;
      }
    }

    const result = await fetch('https://api.vendor.com/oauth/token', {
      method: 'POST',
      body: JSON.stringify({...credentials, grant_type: 'client_credentials'}),
      headers: {'Content-Type': 'application/json'}
    });

    if (result.status === 200) {
      const token = (await result.json() as AuthResult).access_token;
      await storage.secrets.put<UToken>('utoken', {token, issued: new Date().getTime()});
      return token;
    } else {
      const body = await result.json();
      throw new Error(body.status && body.status.message || 'Unable to authorize with Yotpo');
    }
  }

  private isExpiring(token: UToken) {
    return !token.issued || new Date().getTime() - token.issued > 7 * 86400 * 1000;
  }
}