import type { OriginGuardOptions } from './origin-guard'; import type { RedirectRouteConfig } from './redirect'; import type { TlsConfig, TlsOption } from '@stacksjs/tlsx'; export type { TlsConfig, TlsOption }; export declare interface StartOptions { command: string cwd?: string env?: Record } export declare interface PathRewrite { from: string to: string stripPrefix?: boolean } export declare interface StaticRouteConfig { dir: string spa?: boolean pathRewriteStyle?: PathRewriteStyle maxAge?: number } /** * HTTP Basic auth for a single route. When set, rpx challenges every request to * the route with `401`/`WWW-Authenticate` until valid credentials are supplied * (proxy, static, and WebSocket transports are all gated). Credentials may be * given inline, as a `users[]` list, and/or via an Apache `htpasswd` file. */ export declare interface BasicAuthConfig { realm?: string username?: string password?: string users?: Array<{ username: string, password: string }> htpasswdFile?: string } /** * One upstream in a load-balanced pool, with an optional relative weight. */ export declare interface UpstreamTarget { url: string weight?: number } /** * Health checking for a load-balanced pool. Passive checks (derived from live * traffic outcomes) are always on; active checks (periodic out-of-band probes) * are opt-in via {@link enabled}. */ export declare interface HealthCheckConfig { enabled?: boolean path?: string interval?: number timeout?: number healthyThreshold?: number unhealthyThreshold?: number } /** Per-route load-balancing configuration. */ export declare interface LoadBalancerConfig { strategy?: LoadBalancerStrategy healthCheck?: HealthCheckConfig } export declare interface BaseProxyConfig { from?: ProxyFrom to: string loadBalancer?: LoadBalancerConfig auth?: BasicAuthConfig path?: string start?: StartOptions pathRewrites?: PathRewrite[] static?: string | StaticRouteConfig redirect?: string | RedirectRouteConfig id?: string } export declare interface CleanupConfig { domains: string[] hosts: boolean certs: boolean verbose: boolean vitePluginUsage?: boolean } /** * A real PEM cert+key pair on disk for one SNI server name. */ export declare interface DomainCert { certPath: string keyPath: string } /** * Production TLS using real certs (e.g. Let's Encrypt) served per-domain via * SNI on a single listener. Provide either an explicit `domains` map or a * `certsDir` convention. */ export declare interface ProductionTlsConfig { domains?: Record certsDir?: string certsDirServerNames?: string[] } /** * On-demand TLS: issue a real (Let's Encrypt, http-01) certificate for an * unknown host the first time it's needed, gated by an `ask` callback and/or an * `allowedSuffixes` allowlist to prevent abuse. * * ## Why this is "ask-gated issuance + listener recreate", not at-handshake * * Bun (verified on 1.3.14 + 1.4.0) has **no working SNICallback** and * `server.reload({ tls })` does **not** update certs at runtime. So rpx cannot * mint a cert during the TLS handshake the way Caddy's on-demand TLS does. * Instead rpx: * 1. Sees the first plaintext request for the host on its `:80` listener. * 2. Asks `ask(host)` / checks `allowedSuffixes`; if approved, drives the * ACME http-01 flow (serving the challenge from its own `:80`). * 3. Writes the PEMs into `certsDir` and rebuilds the `:443` listener with the * augmented SNI cert set (a sub-second `server.stop()` + re-`Bun.serve`). * The subsequent HTTPS request then finds the freshly-issued cert. * * Issuance can also be triggered programmatically via the manager's * `ensureCert(host)` (e.g. a tunnel server pre-warming a subdomain's cert on * registration) so the cert exists before the first browser hit. */ export declare interface OnDemandTlsConfig { enabled?: boolean ask?: (host: string) => boolean | Promise allowedSuffixes?: string[] email?: string staging?: boolean certsDir?: string } /** * One backend a site exposes, mapped to a request path. rpx picks a free port, * exports it to the dev command via {@link portEnv}, and proxies {@link path} * (under the site host) to `localhost:`. * * A Stacks app, for example, has three: the frontend at `/` (env `PORT`), the * API at `/api` (env `PORT_API`), and the docs at `/docs` (env `PORT_DOCS`). */ export declare interface SiteRouteTemplate { path?: string portEnv: string defaultPort?: number stripPrefix?: boolean readyGate?: boolean } /** * A lazily-booted project. The first request to {@link to} starts {@link command} * in {@link dir}; rpx holds the request behind a "starting…" splash until the * site's ready gate passes, then proxies it. After {@link idleTimeoutMs} with no * traffic the process is stopped again — so a machine can "have" dozens of sites * but only run the ones in active use. */ export declare interface SiteConfig { to: string dir: string command: string env?: Record routes?: SiteRouteTemplate[] selfRegisters?: boolean idleTimeoutMs?: number } /** * On-demand sites: lazily boot a project's dev server the first time its host is * visited, then proxy to it (Valet/puma-dev style). Opt-in via {@link enabled}. */ export declare interface OnDemandSitesConfig { enabled?: boolean sites?: SiteConfig[] roots?: string[] tlds?: string[] idleTimeoutMs?: number startupTimeoutMs?: number } /** * LAN production mode: serve HTTPS for hosts that public ACME can never * certify (`pi-stacks.local`, a private IP) from a Root CA rpx owns. * * On start rpx loads or creates the CA under {@link dir}, mints ONE leaf whose * SANs name every entry of {@link hosts} (dNSName) and {@link ips} (iPAddress), * registers it under each host's SNI name AND as the listener's default TLS * context, so a connection that sends no SNI at all (an IP-literal URL) still * gets it. The leaf is re-minted when its SAN set no longer matches or fewer * than {@link renewBeforeDays} remain. Public on-demand ACME and * `productionCerts` keep working alongside it; a host may not appear in both * `hosts` and `onDemandTls.allowedSuffixes` (that is a config error). */ export declare interface LocalCaConfig { dir: string hosts: string[] ips?: string[] installTrust?: boolean validityDays?: number renewBeforeDays?: number } /** * imgx integration: serve on-the-fly image transforms driven by * imgix/meema-style query params (`?w=200&h=1200&q=80&format=webp`), powered by * the pure-TypeScript `ts-images` pipeline. `true` enables the defaults; an * object tunes them. Enabled globally or per proxy; transformed variants are * cached in memory keyed by the upstream bytes, so an unchanged image is never * re-encoded twice. */ export declare interface ImgxOptions { quality?: number maxWidth?: number maxHeight?: number maxInputBytes?: number cache?: boolean cacheMaxBytes?: number cacheMaxEntries?: number } export declare interface SharedProxyConfig { https: boolean | TlsOption cleanup: boolean | CleanupOptions vitePluginUsage: boolean verbose: boolean _cachedSSLConfig?: SSLConfig | null start?: StartOptions cleanUrls: boolean changeOrigin?: boolean imgx?: boolean | ImgxOptions regenerateUntrustedCerts?: boolean singlePortMode?: boolean httpPort?: number httpsPort?: number acmeChallengeWebroot?: string viaDaemon?: boolean hostsManagement?: boolean productionCerts?: ProductionTlsConfig onDemandTls?: OnDemandTlsConfig localCa?: LocalCaConfig maxTlsContexts?: number onDemand?: OnDemandSitesConfig originGuard?: OriginGuardOptions } export declare interface SingleProxyConfig extends BaseProxyConfig, SharedProxyConfig {} export declare interface MultiProxyConfig extends SharedProxyConfig { proxies: Array } export declare interface SSLConfig { key: string cert: string ca?: string | string[] } export declare interface ProxySetupOptions extends Omit { fromPort: number sourceUrl: Pick ssl: SSLConfig | null from: string originalFrom?: ProxyFrom to: string portManager?: PortManager } export declare interface PortManager { usedPorts: Set getNextAvailablePort: (startPort: number) => Promise } /** * How a static-file route maps request paths to files on disk. * * - `directory` (default): `/about` → `/about/index.html` (SSG dir style). * - `flat`: `/about` → `/about.html` (flat-file style). */ export type PathRewriteStyle = 'directory' | 'flat'; /** * Upstream `host:port` to forward to. Either a single string (backward * compatible, degenerate one-item pool) or an array of upstreams — either * plain `host:port` strings or {@link UpstreamTarget} objects with a weight — * to load-balance across. */ export type ProxyFrom = string | Array; /** How {@link selectUpstream} picks the next upstream from a healthy pool. */ export type LoadBalancerStrategy = 'round-robin' | 'weighted-round-robin' | 'least-connections'; export type BaseProxyOptions = Partial; export type CleanupOptions = Partial; export type SharedProxyOptions = Partial; export type ProxyConfig = SingleProxyConfig; export type ProxyConfigs = SingleProxyConfig | MultiProxyConfig; export type BaseProxyOption = Partial; export type ProxyOption = Partial; export type ProxyOptions = Partial | Partial; /** * Internal shape used by `startProxies` after merging the built-in defaults with * the caller's single- or multi-proxy options. Every field is optional, and the * `proxies` array elements tolerate the per-proxy `cleanUrls`/`changeOrigin` * overrides the runtime reads — so the merged object can be accessed across both * single and multi shapes without falling back to `any`. */ export type ResolvedProxyOptions = Partial & { proxies?: Array }