Standalone Database
Connect your CLI to a self-hosted Hot Updater server.
Overview
The standaloneRepository plugin connects your Hot Updater CLI to a self-hosted backend server. This is the client-side configuration for connecting to your server.
Building a Self-Hosted Server?
See the Self-Hosting (Custom) Guide for complete server implementation instructions using @hot-updater/server with database adapters (Drizzle, Prisma, Kysely, MongoDB) and frameworks (Hono, Express, Elysia).
Installation
npm install @hot-updater/standalone --save-devUsage
Configure your CLI to connect to your self-hosted server:
import { defineConfig } from "@hot-updater/core";
import { standaloneRepository } from "@hot-updater/standalone";
export default defineConfig({
build: /* your build plugin */,
storage: /* your storage plugin */,
database: standaloneRepository({
baseUrl: "http://localhost:3000/hot-updater",
}),
});Configuration
The standaloneRepository plugin accepts the following options:
interface StandaloneRepositoryConfig {
baseUrl: string; // Your server URL
commonHeaders?: Record<string, string>; // Optional headers (e.g., authentication)
routes?: Routes; // Optional custom route configuration
}routes lets you map the client to a different server shape:
interface RouteConfig {
path: string;
headers?: Record<string, string>;
}
interface Routes {
create?: () => RouteConfig;
update?: (bundleId: string) => RouteConfig;
list?: () => RouteConfig;
channels?: () => RouteConfig;
retrieve?: (bundleId: string) => RouteConfig;
delete?: (bundleId: string) => RouteConfig;
}Basic Example
database: standaloneRepository({
baseUrl: "http://localhost:3000/hot-updater",
})With Authentication
database: standaloneRepository({
baseUrl: process.env.HOT_UPDATER_SERVER_URL!,
commonHeaders: {
"Authorization": `Bearer ${process.env.API_TOKEN}`
}
})With Custom Routes
database: standaloneRepository({
baseUrl: "https://api.example.com",
routes: {
create: () => ({
path: "/v1/hot-updater/api/bundles",
headers: { "X-Custom-Header": "value" }
}),
update: (bundleId) => ({
path: `/v1/hot-updater/api/bundles/${bundleId}`,
headers: { "X-Custom-Header": "value" }
}),
list: () => ({
path: "/v1/hot-updater/api/bundles"
}),
channels: () => ({
path: "/v1/hot-updater/api/bundles/channels"
}),
retrieve: (bundleId) => ({
path: `/v1/hot-updater/api/bundles/${bundleId}`
}),
delete: (bundleId) => ({
path: `/v1/hot-updater/api/bundles/${bundleId}`
})
}
})Server Contract
standaloneRepository is an HTTP client. If you are building a custom
backend, your server must implement the bundle-management contract below.
If you use @hot-updater/server, you do not need to implement these
endpoints manually. createHotUpdater() already provides this contract when
bundle routes are enabled, including GET /api/bundles/channels.
Contract Types
interface PaginationInfo {
total: number;
hasNextPage: boolean;
hasPreviousPage: boolean;
currentPage: number;
totalPages: number;
nextCursor?: string | null;
previousCursor?: string | null;
}
interface DataResponse<TData> {
data: TData;
}
interface Paginated<TData> extends DataResponse<TData> {
pagination: PaginationInfo;
}
type PaginatedResult = Paginated<Bundle[]>;
type ChannelsResponse = DataResponse<{ channels: string[] }>;Default Routes
These are the default routes used by standaloneRepository. You can remap
them with the routes option shown above.
| Method | Default route | Request body | Success response |
|---|---|---|---|
| POST | /api/bundles | Bundle[] | { "success": true } |
| PATCH | /api/bundles/:id | Partial<Bundle> | { "success": true } |
| GET | /api/bundles | none | Paginated<Bundle[]> |
| GET | /api/bundles/:id | none | Bundle |
| DELETE | /api/bundles/:id | none | { "success": true } |
| GET | /api/bundles/channels | none | ChannelsResponse |
Bundle Shape
Create requests send full Bundle objects. Update requests send
Partial<Bundle> and, if an id is present in the body, it must match the
:id route parameter.
interface Bundle {
id: string;
platform: "ios" | "android";
shouldForceUpdate: boolean;
enabled: boolean;
fileHash: string;
storageUri: string;
gitCommitHash: string | null;
message: string | null;
channel: string;
targetAppVersion: string | null;
fingerprintHash: string | null;
metadata?: {
app_version?: string;
};
rolloutCohortCount?: number | null;
targetCohorts?: string[] | null;
}List Bundles Contract
GET /api/bundles must accept the pagination and filter query params used by
the console and standaloneRepository, and return a paginated JSON body.
Cursor-based pagination is the official contract. page is an optional
positive integer used alongside cursors when the caller needs stable page
numbers.
Supported query params:
| Query param | Type | Notes |
|---|---|---|
channel | string | Exact match |
platform | "ios" | "android" | Exact match |
limit | number | Window size |
page | number | Optional positive page number for stable page-aligned requests |
after | string | Fetch the next window after this bundle ID |
before | string | Fetch the previous window before this bundle ID |
Expected response:
{
"data": [{ "id": "bundle-id", "channel": "production" }],
"pagination": {
"total": 1,
"hasNextPage": false,
"hasPreviousPage": false,
"currentPage": 1,
"totalPages": 1,
"nextCursor": null,
"previousCursor": null
}
}standaloneRepository reads pagination metadata from the response body. It
does not derive pagination from headers. currentPage and totalPages are
kept for backwards compatibility; nextCursor and previousCursor are the
preferred navigation fields.
If your standalone server is backed by object storage or manifest files, keep
GET /api/bundles on an index-backed path once the index is warm. Cursor
pages should not require rescanning all manifests, and this should be covered
by deterministic read-counter regression tests.
Retrieve And Channels Contract
GET /api/bundles/:idshould return200with aBundlebody or404if the bundle does not exist.GET /api/bundles/channelsmust return:
{
"data": {
"channels": ["production", "staging"]
}
}Minimal @hot-updater/server Example
If you want this contract without implementing it yourself, use
@hot-updater/server:
import { createHotUpdater } from "@hot-updater/server";
const hotUpdater = createHotUpdater({
database,
storages: [storage],
basePath: "/hot-updater",
});This mounts the bundle routes consumed by standaloneRepository under
/hot-updater/api/bundles* by default.
Next Steps
- CLI Configuration - Complete CLI configuration guide
- Self-Hosting (Custom) Overview - Learn about the architecture
- Quick Start Guide - Set up a server in 5 minutes
- Database Adapters - Choose your ORM (Drizzle, Prisma, Kysely, MongoDB)
- Server Frameworks - Choose your framework (Hono, Express, Elysia)