@molecule/api-deploy-target-s3

Provider bond · deploy-target · API (Node) · v1.1.1 · Apache-2.0

Static-site deploy target for molecule.dev on any S3-compatible bucket (Tigris, Cloudflare R2, AWS S3, MinIO)

npm install @molecule/api-deploy-target-s3

npm · Source on GitHub · Implements @molecule/api-deploy-target

How it works

@molecule/api-deploy-target-s3 is a provider bond on the API (Node) side: it implements the deploy-target core interface (@molecule/api-deploy-target) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

import { setProvider } from '@molecule/api-deploy-target'
import { createS3DeployTarget } from '@molecule/api-deploy-target-s3'

setProvider(
  'static',
  createS3DeployTarget({
    bucket: 'my-sites',
    publicBaseUrl: 'https://my-sites.fly.storage.tigris.dev',
    endpoint: 'https://fly.storage.tigris.dev',
    accessKeyId: process.env.SITES_KEY_ID,
    secretAccessKey: process.env.SITES_SECRET,
  }),
)

// Or configure from DEPLOY_TARGET_S3_* env vars:
import { provider } from '@molecule/api-deploy-target-s3'
setProvider('static', provider)

Works with: @molecule/api-deploy-target

Reference

S3-compatible static-site deploy target for molecule.dev.

Implements @molecule/api-deploy-target for apps with no server — a prerendered site or a client-routed SPA — by publishing the build output to a publicly readable bucket on any S3-compatible store (Tigris, Cloudflare R2, AWS S3, MinIO). No machine is created, nothing boots, nothing idles: a deploy is N object PUTs plus one manifest, and removing a site is a prefix delete.

Each deploy is an immutable release under <keyPrefix><siteId>/<releaseId>/ — files/<path> for every file and manifest.json (a StaticSiteManifest) written LAST, so a release whose manifest exists is complete. The release's origin is { kind: 'static-files', filesBaseUrl, manifestUrl } — public URLs an edge serves the site from.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { setProvider } from '@molecule/api-deploy-target'
import { createS3DeployTarget } from '@molecule/api-deploy-target-s3'

setProvider(
  'static',
  createS3DeployTarget({
    bucket: 'my-sites',
    publicBaseUrl: 'https://my-sites.fly.storage.tigris.dev',
    endpoint: 'https://fly.storage.tigris.dev',
    accessKeyId: process.env.SITES_KEY_ID,
    secretAccessKey: process.env.SITES_SECRET,
  }),
)

// Or configure from DEPLOY_TARGET_S3_* env vars:
import { provider } from '@molecule/api-deploy-target-s3'
setProvider('static', provider)

Type

provider

Installation

npm install @molecule/api-deploy-target-s3 @aws-sdk/client-s3 @molecule/api-deploy-target @molecule/api-proxy-agent

API

Interfaces

S3SendClient

The subset of an S3 client this target uses — an S3Client satisfies it.

interface S3SendClient {
  /**
   * Send one S3 command.
   *
   * @param command - The command.
   * @returns The command's output.
   */
  send(command: unknown): Promise<unknown>
}

S3DeployTargetConfig

Configuration for createS3DeployTarget.

interface S3DeployTargetConfig {
  /** Bucket the sites are published to. It must be publicly readable. */
  bucket: string
  /**
   * Public URL the bucket's objects are readable at (no trailing slash
   * needed): the object `sites/a/b/manifest.json` must be served at
   * `${publicBaseUrl}/sites/a/b/manifest.json`.
   */
  publicBaseUrl: string
  /** S3 API endpoint for S3-compatible stores; omit for AWS S3. */
  endpoint?: string
  /** Region (`auto` for Tigris and R2). Default `auto`. */
  region?: string
  /** Path-style addressing (MinIO). Default `false`. */
  forcePathStyle?: boolean
  /** Access key id; omit to use the SDK's default credential chain. */
  accessKeyId?: string
  /** Secret access key; required with `accessKeyId`. */
  secretAccessKey?: string
  /** Key prefix every site lives under. Default `sites/`. */
  keyPrefix?: string
  /** Provider name recorded on each release. Default `s3`. */
  name?: string
  /** Milliseconds to wait for the S3 connection to open. Default 10000. */
  connectionTimeoutMs?: number
  /** Milliseconds of socket inactivity after which a stalled S3 request is aborted. Default 60000. */
  socketTimeoutMs?: number
  /** Parallel uploads per deploy. Default 16. */
  concurrency?: number
  /**
   * A client to use instead of building one — for tests, or to share a
   * client the application already configured.
   */
  client?: S3SendClient
}

Functions

contentTypeFor(path, given)

The Content-Type a file is served with: the caller's, else by extension, else application/octet-stream.

function contentTypeFor(path: string, given?: string): string
  • path — Site-relative path.
  • given — A content type the caller supplied, if any.

Returns: The content type.

invalidSitePath(path)

Validate a site-relative path. Refused, never normalized: a normalized path is a file the caller did not ask for.

function invalidSitePath(path: string): string | null
  • path — The path.

Returns: The problem, or null when it is fine.

createS3DeployTarget(config)

Create a static-site deploy target backed by an S3-compatible bucket.

function createS3DeployTarget(config: S3DeployTargetConfig): DeployTargetProvider
  • config — Bucket, public URL, endpoint and credentials.

Returns: The target.

Constants

provider

The S3 deploy target configured from DEPLOY_TARGET_S3_* env vars. Use createS3DeployTarget to pass configuration explicitly instead.

const provider: DeployTargetProvider

Core Interface

Implements @molecule/api-deploy-target interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setProvider } from '@molecule/api-deploy-target'
import { provider } from '@molecule/api-deploy-target-s3'

export function setupDeployTargetS3(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-deploy-target ^1.0.0

Runtime Dependencies

  • @aws-sdk/client-s3

  • @molecule/api-deploy-target

  • @molecule/api-proxy-agent

  • The bucket must be publicly readable, and publicBaseUrl must be the URL its objects are served at (https://<bucket>.fly.storage.tigris.dev, https://pub-<id>.r2.dev). The object store itself is not the web server: it has no directory indexes, base paths or SPA fallback, so put an edge in front that reads the manifest (see @molecule/api-deploy-target) — do not hand visitors filesBaseUrl links.

  • Every file is given site-relative (/index.html, /assets/x.js) even when the site is built for a base path — the base goes in routing.basePath. /index.html is required; a path with .., ., an empty segment, a backslash or a trailing slash is REFUSED, not normalized.

  • A failed or cancelled deploy removes what it uploaded and rejects (error.code === 'cancelled' for a cancellation); it never touches an earlier release. After switching traffic to a new release, reclaim the others with remove(siteId, { keepReleaseIds: [releaseId] }).

  • Uses its OWN credentials (DEPLOY_TARGET_S3_* for the env-configured provider, or createS3DeployTarget(config)), never the AWS_* ones an app's uploads bucket uses — publishing sites and storing user uploads are different buckets with different access.

  • Checksums are sent only where S3 requires them, because some S3-compatible stores reject the SDK's default CRC32 trailers on PUT. Egress honours HTTPS_PROXY/NO_PROXY through @molecule/api-proxy-agent.