Core
Guides v2.x
2

Start Writing a Storage Driver #

Available since 2.61.0

Kuzzle stores its data through a storage driver: an npm package that exports a class implementing Kuzzle's StorageDriver contract. The application selects it in its configuration, and Kuzzle loads it at startup, before anything else uses the storage. The Elasticsearch 7, 8 and 9 drivers are built this way (see the Drivers section); writing another one lets Kuzzle run on another engine, with the same API.

The public API does not change with the driver: queries, mappings and search bodies stay in the Elasticsearch format, which each driver translates. A driver for an engine of the Elasticsearch family (OpenSearch...) can translate it all; another engine can implement a subset, and declare it (see Capabilities).

The package #

A driver is an ordinary npm package, named kuzzle-storage-<engine>-<major> by convention (kuzzle-storage-es-9, kuzzle-storage-opensearch-2):

kuzzle-storage-myengine-1/
├── package.json
├── manifest.json       # read by Kuzzle before it loads the code
└── dist/index.js       # exports the driver class

package.json declares kuzzle as a peer dependency, never as a dependency: the driver must use the running Kuzzle's classes, its error classes in particular.

{
  "name": "kuzzle-storage-myengine-1",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist", "manifest.json"],
  "peerDependencies": {
    "kuzzle": ">=2.61.0-0 <3"
  },
  "devDependencies": {
    "kuzzle": "2.61.0"
  }
}

manifest.json, at the package root, states the Kuzzle versions the driver supports and the optional features it implements:

{
  "kuzzleVersion": ">=2.61.0-0 <3",
  "capabilities": ["aggregations", "stats"]
}
FieldWhat it is
kuzzleVersionA semver range. Kuzzle refuses to start when its version is out of it: this is the only versioning of the contract.
capabilitiesThe optional capability groups the driver implements: aggregations, mappingUpdates, stats, updateByQuery.

The driver class #

The package exports the class, as its module or as its default export. In TypeScript:

import {
  type JSONObject,
  type StorageDriver,
  type StorageEngineElasticsearch,
  storeScopeEnum,
} from "kuzzle";

import { Client } from "my-engine-client";

export class MyEngineDriver implements StorageDriver {
  // Builds a new engine client, for app.storage.storageClient and the plugins
  static createNativeClient(clientConfig: JSONObject): Client {
    return new Client(clientConfig);
  }

  private client: Client | null = null;

  constructor(
    private readonly config: StorageEngineElasticsearch,
    private readonly scope: storeScopeEnum,
  ) {}

  async init(): Promise<void> {
    this.client = MyEngineDriver.createNativeClient(this.config.client);
    // connect, create what the engine needs...
  }

  // ...the rest of the contract
}

export default MyEngineDriver;

The contract lists what the class implements.

Try it in an application #

Pack the driver and install the tarball next to kuzzle, then select it:

cd kuzzle-storage-myengine-1 && npm pack
cd ../my-application && npm install ../kuzzle-storage-myengine-1/kuzzle-storage-myengine-1-1.0.0.tgz
{
  "services": {
    "storageEngine": {
      "driver": "kuzzle-storage-myengine-1",
      "client": { "node": "http://localhost:9200" }
    }
  }
}

Kuzzle resolves the package from the application's directory, checks its manifest, loads it and logs [ℹ] Storage driver: kuzzle-storage-myengine-1@<version>. Every key of services.storageEngine is handed to the driver's constructor.

A driver installed from a directory (npm link, or npm install ../my-driver, which links it too) resolves kuzzle from its own location, and gets another copy of Kuzzle than the running one: the errors it throws are then not recognized. Install a packed tarball, as above, or the published package.