AppsDb
import AppsDb from '@bbc/front-end-kit/js/showpad/managers/AppsDb';
AppsDB is a key-value store designed to enrich Showpad Apps with features like offline storage, cross-platform compatibility, and secure data protection. It operates on a structure where data is organized into stores containing store entries, each identified by an ID and a scope.
There are two types of scopes: USER and GLOBAL. USER-scoped entries are tied to individual users, allowing personalized data without overlap, while GLOBAL-scoped entries are accessible to all users but require specific OAuth2 permissions to create.
Warning
Although AppsDB is promoted as a tool to enhance offline functionality, it's important to note that only local (user-scoped) stores are fully accessible offline.
In contrast, global (global-scoped) data is restricted to read-only access when offline.
This class wraps Showpads' logic into a neat and transportable instance.
It also extends the EventDispatcher so it comes with all the benefits of our internal event management as well.
Vue applications
For Vue-based Showpad applications, use useAppsDbStore instead. It provides the same functionality with reactive state, singleton management, automatic queuing, and a clean composable API.
Store scopes
AppsDB supports two types of data stores:
Local Stores
- Scoped to individual users (user-scoped)
- Each user has their own isolated data space
- Full read/write access for the owning user
- Works offline
- No special permissions needed
Global Stores
- Accessible across the entire Showpad platform (global-scoped)
- Shared data visible to all users
- Read access for all users
- Write access restricted to:
- Admins by default
- Normal users with valid access token
- Limited offline functionality (read-only)
To grant normal users write access to global stores, provide an access token via the accessToken property. You can obtain this token using the getApiConfig utility.
Access token
Example of how to get the access token for a global store:
import { getApiConfig } from '@bbc/front-end-kit/js/showpad';
async function initDbs() {
// get api config instance
const apiConfig = await getApiConfig();
// get access token
const accessToken = apiConfig.accessToken;
// init global store with access token
const globalStore = new AppsDb({
id: 'my-global-store',
type: 'global',
accessToken
});
// or apply the access token at a later stage
globalStore.accessToken = accessToken;
}
Getting started
AppsDb can be initialized directly.
const myStore = new AppsDb({
id: 'my-apps-db-store'
type: 'local'
});
Caution
AppsDB fails miserably (without clear explanation appart from 'BAD_FORMAT' error) when the given id/name contains invalid characters. It is not documented what characters are allowed and what are not. So we advise to always use lowercase characters and hyphens to separate words, periods have been proven problematic.
Tips
It is adivsed to always create a child appsDb class that extends from AppsDb wherin the id and type are preset:
class myAppsDb extends AppsDb {
constructor(args = {}) {
args.id = 'my-apps-db-storeb';
args.type = 'global';
super(args);
}
}
Initialising it later becomes a breeze
const myStore = new myAppsDb();
Example used in real project
The project needed a store in which settings could be saved. To quickly add and retrieve settings from the store, the class was extended with 2 extra methods that added extra functionalities to the actual entry retrieval.
getSettings: Retrieved the entry using a name instead of an id and returned the settings value of the retrieved entrysetSettings: Sets the settings property of the entry found using only the name instead of the id.
Every entry represented a scoped set of settings for any puprose.
import AppsDB from "@bbc/front-end-kit/js/showpad/managers/AppsDb";
export class SettingAppsDb extends AppsDB {
async getSettings(name) {
const entry = await findEntryBySettingName.call(this, name)
return entry.settings;
}
async setSettings(name, settings) {
// get setting first to get the id
const entry = await findEntryBySettingName.call(this, name) || {
name,
settings: null
};
// update settings
entry.settings = settings;
// save to db
return await this.setEntry(entry);
}
}
// utils
function findEntryBySettingName(name) {
return new Promise((resolve, reject) => {
return this.getAllEntries()
.then(entries => {
resolve(entries.find(entry => entry.name === name));
})
.catch(error => {
resolve(null)
});
})
}
Usage:
// initialise db (still passing id & type as it needed to support multiple scoped sets of settings)
const settingsDb = new SettingsAppsDb({
id: 'bbc.wiki.settings',
type: 'local'
});
// get settings using
const settings = settingsDb.getSettings('my-settings');
// chagne something
settings.appTitle = 'New App Title'
// set settings (will create new entry or update the already existing one)
settingsDb.setSettings('my-settings', settings)
API
constructor (args = {})
The constructor method initialises the new AppsDb instance.
Parameters
args: The configuration object used by the constructor to initialise the AppsDb in the desired way.id: The store id it needs to managetype: The scope of the store it needs to work in (globalorlocal)accessToken: The access token to use for global stores on non-admin accountsoauthInstance: (depracated] The OAuth instance to use for global stores on non-admin accounts
Warning
The oauthInstance parameter is deprecated due to the fact that OAuth is not supported with SSO.
Use accessToken instead for extra grants using the access token.
Methods
init ()
Initialises the store if it does not exist and if the current user is an admin user. It is important for Admins to run the app a first time after it has been published for the very first time else a "Only admins can run this method" erro may be triggered by the Showpad.createStore() method.
Preview mode
This method will automatically detect Showpad's preview mode (detected when getUserInfo() returns no fullName) and mock all the methods so that many of the instantly occurring errors can be bypassed. When active, _type is set to 'preview' and all writes return mock data. It will still cause issues/bugs in the app but these should be limited to data manipulation as much as possible.
Offline fallback
When Showpad.createStore() fails because the command is not supported offline, init() falls back to a slower existence probe: it sets a temporary entry, then deletes it after a 750ms delay. If that succeeds the store is considered to exist and the promise resolves with true.
create ()
Warning
Deprecated in favour of init. The logic has been replaced by a call to init behind the scenes. The behavior may be slightly different than the original.
Creates the store in case it doesn't exist yet. Showpad throws a 409 error in case the store already exists. This method will catch it gracefully (while still showing it in the console) so the code can continue without issue.
Return
Returns a Promise that will pass a Boolean determining if the creation has happened (true) or not (false).
exists ()
Warning
Deprecated in favour of init. The logic has been replaced by a call to init behind the scenes. The behavior may be slightly different than the original.
Checks if the AppsDb already exists without creating it.
Return
Returns a Promise that will pass a Boolean determining if the db exists (true) or not (false).
getEntry (id, options = {})
Retrieves a specific entry from the database based on the id that was provided.
Parameters
id: The id of the entry that needs to be looked up.options: Reserved for future use — not read by the current source.
Return
Returns a Promise that will pass the retrieved entry as the parameter in the chain of the promise.
getAllEntries (options = {})
Retrieves all entries from the database without pagination.
Parameters
options: Extra options object to influence the method in its executionraw: Will force to return the raw version of the entry in the AppsDb. Actual values are saved in thevalueproperty of the entry. As we expect that this is the content that will be requested the most, AppsDb will provide it automatically. Use this property to prevent this behavior.
Return
Returns a Promise that will pass an Array of all the entries it retrieved from the AppsDb in the chain of the promise.
setEntry (entry)
Sets an entry into the AppsDb. If the entry lacks an id, it will be considered new and will be added as a new entry. If it contains and id, it will be considered an existing entry and will try to update it. If in any case it has an id but doesn't exist yet, it will simply be considered as new as well.
Parameters
entry: The entry it needs to add/update in the appsDb
Cleans up strings
It will clean up all the strings it can find in the given entry object to make sure some of the unsupported characters will be removed. Showpad tends to fail with a dubious error when it can not process a certain character and may cause it to save everything up until that character and corrupt the store. This in turn may cause the whole application to fail to load.
This can have an effect on the resulted data that is saved.
Character limit
As of 24/06/2024 Showpad has a hard cut-off limit in character count for the entry values. Longer strings will be chunked an thus result in partially saved contents with possible data corruption and (hard to debug) failing app logic in return.
Limit using the SDK is set to 10mb per entry value (our understanding is that the rest of the entry object is not count as part of that limit), but be carefull.
Return
Returns a Promise that will pass the added/upated entry as the parameter in the chain of the promise. That entry will be the actual representation of the entry in the AppsDb. So when it didn't have an id when passed, it will have one when returned.
deleteEntry (id)
Delete an entry from the AppsDb based on the given id.
Parameters
id: The id of the entry it needs to delete.
`Return``
Returns a Promise that resolves when the deletion is completed.
duplicateEntry (item)
Will duplicate a given entry. It will make sure that properties like id, updated_at and created_at will be cleared before the duplicated entry is set to the AppsDb.
Parameters
item: Either the entry object or a String id of the entry to duplicate.
Return
Returns a Promise that will pass the duplicated entry as the parameter in the chain of the promise. That entry will be the actual representation of the entry in the AppsDb.
clear ()
Clears the whole AppsDb by fetching all entries and deleting each one individually.
Return
Returns a Promise that resolves with an array of per-entry delete results when all deletions have completed.
Caution
Intended to be used by developers only during development when the data is not important as the effects are permanent.
Getters & Setters
id
Returns the store id that the app is using to identify the store to manage
type
The scope of the store to use: global or local
accessToken
The access token to use for global stores on non-admin accounts
Dispatches the access-token-changed event when the access token is changed.
oauthInstance
The OAuth instance to use for global stores on non-admin accounts. Both getter and setter log a deprecation warning. The setter dispatches the oauth-instance-changed event when the value changes.
Warning
The oauthInstance parameter is deprecated due to the fact that OAuth is not supported with SSO.
Use accessToken instead for extra grants using the access token.
Private properties
_id
The id that is used to retrieve dat store using the Showpad library
_type
The scope from which the appsdb needs to retrieve the entries from.
_accessToken
The access token to use for global stores on non-admin accounts
_oauthInstance
The OAuth instance to use for global stores on non-admin accounts
Events
Event payload inconsistency
Some events wrap their payload in a detail property (accessed as e.detail.*), while others expose properties directly on the event object (e.*). This is a source-level inconsistency — see the "Access via" column below.
| Event | Access via | Payload |
|---|---|---|
get-entry | e.detail.* | id, data, error |
get-all-entries | e.detail.* | data, error |
set-entry | e.* | entry, data, error |
delete-entry | e.* | id, data, error |
cleared | e.* | data, error, total |
error | e.detail | the raw error object |
access-token-changed | e.detail | the new access token value |
oauth-instance-changed | e.detail | the new OAuth instance value |
get-entry
Dispatched when an entry is retrieved or failed to be retrieved. Payload (via e.detail): id, data (raw entry), error.
get-all-entries
Dispatched when all entries are retrieved or failed to be retrieved. Payload (via e.detail): data (raw entries array), error.
set-entry
Dispatched when an entry is set or failed to be set. Payload (directly on event): entry (the data passed to the method), data (saved entry), error.
delete-entry
Dispatched when an entry is deleted or failed to be deleted. Payload (directly on event): id, data, error.
cleared
Dispatched when the whole store is cleared or fails to be cleared. Payload (directly on event): data (entries that were deleted), error, total (count of entries deleted).
error
Dispatched when an error occurs. Payload: the raw error object via e.detail.
access-token-changed
Dispatched when the access token is changed via the setter. Payload: the new token value via e.detail.
oauth-instance-changed
Dispatched when the OAuth instance is changed via the setter. Payload: the new instance value via e.detail.
Dependencies
- Experience APP SDK: Showpad's library
- uuid: A library to quickly generate uuid strings