graphdb-workbench

Persistence API

Local-storage-backed persistence for GraphDB Workbench, built on the Persistence interface and LocalStorageService.

Contents

Overview of the Persistence API

The Persistence API provides a generic interface for interacting with a storage system. It supports storing, retrieving, and removing data via a Storage-compatible implementation (e.g., localStorage or sessionStorage). The code is in packages/api/src/services/storage/.

Key Components

Persistence Interface

The Persistence interface defines the structure for storage-related services:

Local Storage Implementation

The LocalStorageService abstract class implements the Persistence interface using localStorage. It is the base class for specialized storage services.

Key Features

1. Namespace Support:

2. Storage Methods:

Subclasses implement set(key, value), usually by calling storeValue.

3. Key Management:

Implementation Example

export class LanguageStorageService extends LocalStorageService {
  readonly NAMESPACE = 'i18n';

  set(key: string, value: string) {
    this.storeValue(key, value);
  }
}

LanguageStorageService stores language-related properties under the ontotext.gdb.i18n. prefix.

Handling Storage Change Events

LocalStorageSubscriptionHandlerService keeps the context in sync when localStorage changes in another tab. onto-layout listens for the window’s storage event and passes it to handleStorageChange.

Workflow

  1. On a StorageEvent, the service strips the global prefix and splits the rest into the namespace and the property name.
  2. It finds the handler among all registered context services (ServiceProvider.getAllBySuperType(ContextService)): the first whose canHandle(propertyName) is true. The namespace is only required to be present.
  3. The handler’s deserializeProperty() turns the raw string into a value (see Context API), and updateContextProperty() sets it. A removed key sets undefined.

Key Method

handleStorageChange(event: StorageEvent): void {
  const withoutGlobalPrefix = event.key?.substring(StorageKey.GLOBAL_NAMESPACE.length + 1);
  let namespace = '';
  let contextPropertyKey = '';

  if (withoutGlobalPrefix) {
    namespace = withoutGlobalPrefix.substring(0, withoutGlobalPrefix.indexOf('.'));
    contextPropertyKey = withoutGlobalPrefix.substring(namespace.length + 1);
  }

  const handler = this.resolveHandler(namespace, contextPropertyKey);
  if (handler) {
    let newValue;
    if (event.newValue !== null) {
      newValue = handler.deserializeProperty(contextPropertyKey, event.newValue);
    }
    handler.updateContextProperty(contextPropertyKey, newValue);
  }
}

Practical Considerations

  1. Prefixed keys. Always go through a LocalStorageService subclass so keys are prefixed and isolated.
  2. Service specialization. Extend LocalStorageService per domain, specifying the NAMESPACE.
  3. Property names. For cross-tab sync, the stored key’s property name must equal a context service field value (e.g. selectedRepository), so canHandle finds the service.
  4. Error handling. A missing namespace or handler is logged at debug level and ignored.

See also: Developers Guide