Credo Drizzle Storage Module
Credo Drizzle Storage provides a storage integration with Drizzle ORM, with support for PostgreSQL and SQLite.
Usage
Install the @credo-ts/drizzle-storage package and add the DrizzleStorageModule module to your agent.
You then need to provide two items to the module on creation:
- The database instance to use
- The database bundles (models) to register.
import { didcommDrizzleRecords } from "@credo-ts/drizzle-storage/didcomm";
const agent = new Agent({
modules: {
drizzleStorage: new DrizzleStorageModule({
database,
bundles,
}),
},
});
Database
The database instance provides a connection to the actual database. Credo supports any of the supported PostgreSQL and SQLite database drivers supported by Drizzle itself.
For this reason you need to instantiate your own database instance. You can read installation and setup instructions for all the Drizzle database dirvers in the Drizzle documentation.
PostgreSQL example using pg
import { drizzle } from "drizzle-orm/node-postgres";
const database = drizzle(
"postgresql://postgres:postgres@localhost:5432/postgres"
);
SQLite example using libsql
import { drizzle } from "drizzle-orm/libsql";
const database = drizzle("file:./database.db");
SQLite example using expo-sqlite
import { drizzle } from "drizzle-orm/expo-sqlite";
import { openDatabaseSync } from "expo-sqlite/next";
const expo = openDatabaseSync("database.db");
const database = drizzle(expo);
Bundles
The bundles provide the agent with the needed context about which record types are used. For each of the modules you use with Credo that provides Record classes, the respective drizzle record MUST be registered, otherwise you cannot use the record, and an error will be thrown when such a record is retrieved, stored, updated or deleted. The coreDrizzleBundle is always registered, as these are required to use Credo.
The following bundles are provided out of the box by the Drizzle storage module:
coreDrizzleBundle from @credo-ts/drizzle-storage/core (core)
didcommDrizzleBundle from @credo-ts/drizzle-storage/didcomm (didcomm)
actionMenuDrizzleBundle from @credo-ts/drizzle-storage/action-menu (action-menu)
anonCredsDrizzleBundle from @credo-ts/drizzle-storage/anoncreds (anoncreds)
openid4vcDrizzleBundle from @credo-ts/drizzle-storage/openid4vc (openid4vc)
drpcDrizzleBundle from @credo-ts/drizzle-storage/drpc (drpc)
questionAnswerDrizzleBundle from @credo-ts/drizzle-storage/question-answer (question-answer)
[!IMPORTANT]
Make sure to order the bundles to account for any dependencies between modules. For example, the actionMenuDrizzleBundle, depends on the connection record from didcommDrizzleBundle. Generally we recommend to adhere to the order defined above, and always provide custom records after the records provided by the @credo-ts/drizzle-storage package.
The following defines the bundles for an agent using the didcomm and anoncreds extension modules, but doesn't use the action-menu extension module:
const bundles = [
coreDrizzleBundle,
didcommDrizzleBundle,
anonCredsDrizzleBundle,
] as const;
Migrations
Before actually being able to use the database, you need to ensure the tables exist within the database. Credo provides migrations to correctly setup the database, which must be run before initializing the agent.
The process for applying migrations differs between Node.JS and React Native.
Node.JS
In Node.JS we can apply migrations outside of the code with a CLI.
In the example below we're assuming postgres is used as the database dialect, but it can also be used with sqlite. The following parameters need to be provided to the CLI:
bundle - this defines the record bundles that migrations need to be applied for. For the default records provided by Credo you can just provide the module names (as documented above, @credo-ts/drizzle-storage/core becomes core). For other modules, consult the documentation of that module on the bundle name to provide. This MUST match with the records being provided and used during runtime in your Credo agent.
dialect - the dialect to use for migrations. Either postgres or sqlite and MUST match with the database being used during runtime in your Credo agent.
database-url - the url to connect to the database. You can also provide this using the DRIZZLE_DATABASE_URL environment variable (recommended).
If the @credo-ts/drizzle-storage package is already installed in your project, you can directly use it to run the CLI with the drizzle-storage command. You can also use npx to execute the CLI, but make sure that you install the correct version matching the version that is used for your runtime agent. You also need to make sure that all extension modules are installed so the migration files can be resolved (which can be done using --project in npx).
[!NOTE]
When running migrations always make sure to provide the core module. While for the bundles in the DrizzleStorageModule the core bundle is added by default, due to imcreased control over the migration process, no bundles are added by default in the migration command.
pnpm drizzle-storage migrate --bundle core --bundle didcomm --bundle anoncreds --database-url postgresql://postgres:postgres@localhost:5432/postgres`
npx @credo-ts/drizzle-storage@^x.x migrate
React Native
In React Native, we need to apply migrations from within the code, as they need to be applied on every device that is running the Credo agent.
Within your project creat a file that sets up the migrations. It should look something like the example below, but make sure to adapt it based on the SQLite database driver you're uing, and the records you're using.
import { applyReactNativeMigrations } from "@credo-ts/drizzle-storage";
import { migrate } from "drizzle-orm/expo-sqlite/migrator";
import { database } from "./database";
import coreMigrations from "@credo-ts/drizzle-storage/migrations/core/sqlite/migrations";
import didcommMigrations from "@credo-ts/drizzle-storage/migrations/didcomm/sqlite/migrations";
import anoncredsMigrations from "@credo-ts/drizzle-storage/migrations/anoncreds/sqlite/migrations";
export async function applyMyReactNativeMigrations() {
return await applyReactNativeMigrations({
migrate,
database,
migrations: [coreMigrations, didcommMigrations, anoncredsMigrations],
});
}
[!NOTE]
In the future the CLI used for Node.JS might be extended to generate the scaffolding of this file, but for now it needs to created manually.
In addition you also need to configure React Native to bundle the SQL files in your application bundle, so the SQL can be executed from within the deployed app.
- Install the
babel-plugin-inline-import package, and add the inline-import plugin to your babel.config.js. Make sure to adapt it based on whether you're using Expo or React Native (the import part is adding the plugin):
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [["inline-import", { extensions: [".sql"] }]],
};
};
- Update your
metro.config.js and add the sql extension. Again, adapt this based on whether you're using Expo or React Native (the important part is adding hte sourceExts value):
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push("sql");
module.exports = config;
Developing storage bundles
Developing a custom Drizzle storage bundle for Credo requires the following:
- A bundle definition describing the records, and metadata about the migrations
- A set of record files with the PostgreSQL and SQLite database definitions, as well as the Credo record adapters.
The Drizzle storage module for Credo is still in an experimental phase, and documentation for creating Drizzle bundles is not available yet. You can look at the bundles from thie repository (such as action-menu), to see how a storage bundle can be defined.
[!WARNING]
Drizzle requires TypeScript files to be used for generation of migrations. For this reason if you're developing storage bundles for Drizzle in Credo, you MUST write the storage bundle in TypeScript. We recommend every JavaScript/Credo project to be written in TypeScript, but for Drizzle it is required.