Neon Object Storage
Store your Hot Updater bundles in a Neon Object Storage bucket, next to the Postgres database that tracks them.
Installation
npm install @hot-updater/neon --save-devFor a deployed server, install the package as a runtime dependency instead of a development dependency.
Setup
Create a bucket on a branch of your Neon project. A private bucket works, because devices download from presigned URLs:
neon buckets create hot-updater-bundles --access-level privateThen write the branch's storage and Postgres variables to .env.hotupdater,
and add that file to .gitignore:
neon env pull --file .env.hotupdater -s object-storage,postgresThe command writes AWS_ENDPOINT_URL_S3, AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY, and AWS_REGION for the bucket, and DATABASE_URL
for the branch's database.
Configuration
import type { S3ClientConfig } from "@aws-sdk/client-s3";
interface NeonStorageConfig
extends Omit<S3ClientConfig, "credentials" | "endpoint" | "forcePathStyle"> {
endpoint: string; // The branch's storage endpoint (AWS_ENDPOINT_URL_S3)
bucketName: string; // The bucket on that branch
credentials: { accessKeyId: string; secretAccessKey: string };
basePath?: string; // Optional base path within the bucket
signedUrlExpiresIn?: number; // Download URLs, at most 604800 (7 days). Default: 3600 seconds
}endpoint is the branch's host only, such as
https://br-xxx.storage.c-5.us-east-2.aws.neon.tech. Pass the bucket as
bucketName, not in the endpoint's path. The adapter always uses path-style
requests, the only style Neon serves. region defaults to us-east-1, and
Neon accepts a signature for any region.
Usage
@hot-updater/neon also exports postgres,
so one package covers both the bundles and their metadata:
import { existsSync } from "node:fs";
import { bare } from "@hot-updater/bare";
import { neonStorage, postgres } from "@hot-updater/neon";
import { defineConfig } from "hot-updater";
import { apiKeys, insights, remoteConfig } from "hot-updater/plugins";
if (existsSync(".env.hotupdater")) {
process.loadEnvFile(".env.hotupdater");
}
export default defineConfig({
build: bare({ enableHermes: true }),
updateStrategy: "appVersion",
storage: neonStorage({
endpoint: process.env.AWS_ENDPOINT_URL_S3!,
bucketName: "hot-updater-bundles",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
region: process.env.AWS_REGION,
}),
database: postgres({
connectionString: process.env.DATABASE_URL!,
}),
plugins: [apiKeys(), insights(), remoteConfig()],
});Neon has no managed Hot Updater server, so the app checks for updates against a custom server that lists the same adapters, as in Custom server usage. Create the tables through that server file once:
npx hot-updater db migrate src/hotUpdater.tsProtocol
neon://bucket/encoded/key
For example, neon://hot-updater-bundles/releases/1.0.0/bundle%20ios.zip is
stored in the storageUri field. Keys retain their slash hierarchy and encode
each segment. The URI names the bucket and not the branch, so a child branch
resolves the URIs it inherits against its own copy of the bucket.
Capabilities
neonStorage implements upload, read, download URL, existence-check,
exact-delete, listing, and batch-delete operations. Uploads above 5 MB go up
in parts. A download URL is a presigned URL on the branch's endpoint that
expires after signedUrlExpiresIn, so the bucket can stay private.
Branches
A Neon branch copies its parent's buckets along with its database. Point a
preview or staging config at a child branch's AWS_ENDPOINT_URL_S3 and
DATABASE_URL to deploy bundles there without touching the parent's bundles
or release catalog.
Maintenance
neonStorage provides the object listing and exact deletion capabilities used
by hot-updater storage prune. Follow the
Storage Cleanup guide to remove unreferenced
bundle objects and shared assets safely.
Use a separate basePath for every database that shares a bucket. Pruning
assumes that the configured database owns every object below its storage
prefix.
Custom server usage
Use neonStorage and postgres with createHotUpdater for self-hosted
servers:
import { neonStorage, postgres } from "@hot-updater/neon";
import { createHotUpdater } from "@hot-updater/server";
import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
export const hotUpdater = createHotUpdater({
database: postgres({ connectionString: process.env.DATABASE_URL! }),
plugins: [apiKeys(), insights(), remoteConfig()],
storage: neonStorage({
endpoint: process.env.AWS_ENDPOINT_URL_S3!,
bucketName: "hot-updater-bundles",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
region: process.env.AWS_REGION,
}),
// ... other options
});On serverless platforms, use the pooled DATABASE_URL.