Custom Database Plugin
Create your own database plugin for any database provider
Installation
npm install @hot-updater/plugin-core --save-devOverview
Build a custom database plugin to integrate any database provider. This guide shows how to use createDatabasePlugin to build and configure your own database plugin.
Creating a Database Plugin
Use createDatabasePlugin to build custom database plugins:
import { createDatabasePlugin } from "@hot-updater/plugin-core";
export const myDatabase = createDatabasePlugin<MyConfig>({
name: "myDatabase", // Plugin identifier
factory: (config) => ({
// Return these required methods:
getBundleById: async (bundleId) => { /* ... */ },
getBundles: async (options) => ({ data: [], pagination: {} }),
getChannels: async () => [],
commitBundle: async ({ changedSets }) => { /* ... */ },
onUnmount: async () => { /* optional cleanup */ }
})
});Factory Function Return Type
Your factory function must return an object with these methods:
{
// Fetch single bundle by ID
getBundleById: (bundleId: string) => Promise<Bundle | null>;
// Fetch paginated bundles with optional filtering
getBundles: (options: {
where?: { channel?: string; platform?: string };
limit: number;
offset: number;
}) => Promise<{
data: Bundle[];
pagination: PaginationInfo;
}>;
// Get all available channels
getChannels: () => Promise<string[]>;
// Commit all pending changes (batch operation)
commitBundle: (params: {
changedSets: {
operation: "insert" | "update" | "delete";
data: Bundle;
}[];
}) => Promise<void>;
// Optional: cleanup resources on unmount
onUnmount?: () => Promise<void>;
}Note: The factory does NOT need to implement updateBundle, appendBundle, or deleteBundle - these are auto-generated by createDatabasePlugin and tracked internally.
Implementation Example
Here's a complete custom database plugin using a REST API:
import {
type Bundle,
type PaginationInfo,
createDatabasePlugin,
} from "@hot-updater/plugin-core";
export interface CustomDatabaseConfig {
baseUrl: string;
apiKey: string;
}
export const customDatabase = createDatabasePlugin<CustomDatabaseConfig>({
name: "customDatabase",
factory: (config) => {
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${config.apiKey}`,
};
return {
async getBundleById(bundleId) {
const response = await fetch(
`${config.baseUrl}/bundles/${bundleId}`,
{ headers }
);
if (!response.ok) {
if (response.status === 404) return null;
throw new Error(`Failed to fetch bundle: ${response.statusText}`);
}
return response.json();
},
async getBundles(options) {
const params = new URLSearchParams({
limit: String(options.limit),
offset: String(options.offset),
});
if (options.where?.channel) {
params.set("channel", options.where.channel);
}
if (options.where?.platform) {
params.set("platform", options.where.platform);
}
const response = await fetch(
`${config.baseUrl}/bundles?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`Failed to fetch bundles: ${response.statusText}`);
}
const result = await response.json();
return {
data: result.data,
pagination: result.pagination,
};
},
async getChannels() {
const response = await fetch(`${config.baseUrl}/channels`, {
headers,
});
if (!response.ok) {
throw new Error(`Failed to fetch channels: ${response.statusText}`);
}
return response.json();
},
async commitBundle({ changedSets }) {
// Process all changes in batch
for (const change of changedSets) {
if (change.operation === "insert" || change.operation === "update") {
await fetch(`${config.baseUrl}/bundles`, {
method: "POST",
headers,
body: JSON.stringify(change.data),
});
} else if (change.operation === "delete") {
await fetch(`${config.baseUrl}/bundles/${change.data.id}`, {
method: "DELETE",
headers,
});
}
}
},
async onUnmount() {
// Optional: cleanup resources, close connections, etc.
console.log("Database plugin unmounted");
},
};
},
});Helper Utilities
The @hot-updater/plugin-core package provides helper functions:
calculatePagination
Generates pagination metadata from total count and options:
import { calculatePagination } from "@hot-updater/plugin-core";
const pagination = calculatePagination(totalCount, {
limit: 10,
offset: 0,
});
// Result: { total, limit, offset, hasMore }CLI Configuration
Use your custom plugin in hot-updater.config.ts:
import { defineConfig } from "@hot-updater/core";
import { customDatabase } from "./customDatabase";
export default defineConfig({
database: customDatabase({
baseUrl: process.env.DATABASE_BASE_URL!,
apiKey: process.env.DATABASE_API_KEY!,
}),
// ... other config
});Custom Server Usage
Use your plugin with createHotUpdater for self-hosted servers:
import { createHotUpdater } from "@hot-updater/core";
import { customDatabase } from "./customDatabase";
const hotUpdater = createHotUpdater({
database: customDatabase({
baseUrl: process.env.DATABASE_BASE_URL!,
apiKey: process.env.DATABASE_API_KEY!,
}),
// ... other options
});Best Practices
Security
- Never hardcode credentials in plugin code
- Use environment variables for sensitive data
- Implement proper authentication headers
- Validate input data before storing
Performance
- Implement efficient filtering in
getBundles - Use database indexes for common queries
- Batch operations in
commitBundlewhen possible - Cache channel lists if they don't change frequently
Error Handling
- Return
nullfromgetBundleByIdwhen not found (don't throw) - Throw descriptive errors for other failures
- Handle network errors gracefully
- Validate bundle data structure