Storage Plugins
Custom Storage Plugin
Create your own storage plugin for any storage provider
Installation
npm install @hot-updater/plugin-core --save-devOverview
Build a custom storage plugin to integrate any storage provider. This guide shows how to use createStoragePlugin to build and configure your own storage plugin.
Creating a Storage Plugin
Use createStoragePlugin to build custom storage plugins:
import { createStoragePlugin } from "@hot-updater/plugin-core";
export const myStorage = createStoragePlugin<MyConfig>({
name: "myStorage", // Plugin identifier
supportedProtocol: "custom", // Storage URI protocol (e.g., "custom://...")
factory: (config) => ({
// Return these three required methods:
upload: async (key, filePath) => ({ storageUri: "..." }),
delete: async (storageUri) => { /* ... */ },
getDownloadUrl: async (storageUri) => ({ fileUrl: "..." })
})
});Factory Function Return Type
Your factory function must return an object with these methods:
{
// Uploads file to storage and returns storage URI
upload: (key: string, filePath: string) => Promise<{ storageUri: string }>;
// Deletes all files at the storage URI path
delete: (storageUri: string) => Promise<void>;
// Generates download URL for clients to fetch bundles
getDownloadUrl: (storageUri: string) => Promise<{ fileUrl: string }>;
}Implementation Example
Here's a complete custom storage plugin implementation:
import {
DeleteObjectCommand,
GetObjectCommand,
S3Client,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import {
createStoragePlugin,
getContentType,
parseStorageUri,
} from "@hot-updater/plugin-core";
import fs from "fs/promises";
import path from "path";
export interface CustomStorageConfig {
region: string;
credentials: {
accessKeyId: string;
secretAccessKey: string;
};
bucketName: string;
}
export const customStorage = createStoragePlugin<CustomStorageConfig>({
name: "customStorage",
supportedProtocol: "s3",
factory: (config) => {
const { bucketName, ...s3Config } = config;
const client = new S3Client(s3Config);
return {
async upload(key, filePath) {
const Body = await fs.readFile(filePath);
const ContentType = getContentType(filePath);
const filename = path.basename(filePath);
const Key = `${key}/${filename}`;
const upload = new Upload({
client,
params: {
Bucket: bucketName,
Key,
Body,
ContentType,
},
});
await upload.done();
return {
storageUri: `s3://${bucketName}/${Key}`,
};
},
async delete(storageUri) {
const { bucket, key } = parseStorageUri(storageUri, "s3");
if (bucket !== bucketName) {
throw new Error(`Bucket mismatch: expected "${bucketName}"`);
}
const command = new DeleteObjectCommand({
Bucket: bucketName,
Key: key,
});
await client.send(command);
},
async getDownloadUrl(storageUri) {
const url = new URL(storageUri);
const bucket = url.host;
const key = url.pathname.slice(1);
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
const signedUrl = await getSignedUrl(client, command, {
expiresIn: 3600,
});
return { fileUrl: signedUrl };
},
};
},
});Helper Utilities
The @hot-updater/plugin-core package provides helper functions:
parseStorageUri
Parses storage URIs into bucket and key components:
const { bucket, key } = parseStorageUri(
"s3://my-bucket/path/file.bundle",
"s3"
);
// bucket: "my-bucket"
// key: "path/file.bundle"getContentType
Returns MIME type based on file extension:
const contentType = getContentType("bundle.js");
// Result: "application/javascript"CLI Configuration
Use your custom plugin in hot-updater.config.ts:
import { defineConfig } from "@hot-updater/core";
import { customStorage } from "./customStorage";
export default defineConfig({
storage: customStorage({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
bucketName: process.env.BUCKET_NAME!,
}),
// ... other config
});Custom Server Usage
Use your plugin with createHotUpdater for self-hosted servers:
import { createHotUpdater } from "@hot-updater/core";
import { customStorage } from "./customStorage";
const hotUpdater = createHotUpdater({
storages: [
customStorage({
region: "us-east-1",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
bucketName: process.env.BUCKET_NAME!,
}),
],
// ... other options
});Security
- Never hardcode credentials in plugin code
- Use environment variables for sensitive data
- Validate storage URIs before processing
- Implement proper authentication headers
Storage URI Format
Use a consistent URI format: protocol://bucket/path/to/file
return {
storageUri: `${supportedProtocol}://${bucketName}/${storageKey}`,
};