cf-containers
Version:
TypeScript helper class for PartyKit durable object containers
310 lines (304 loc) • 11.9 kB
TypeScript
import { DurableObject } from 'cloudflare:workers';
import { Party, Connection } from 'partyserver';
/**
* Basic types for the container implementation
*/
/**
* ContainerStartOptions as they come from worker types
*/
type ContainerStartOptions = NonNullable<Parameters<NonNullable<DurableObject['ctx']['container']>['start']>[0]>;
/**
* Message structure for communication with containers
*/
interface ContainerMessage<T = unknown> {
type: string;
payload?: T;
}
/**
* Options for container configuration
*/
interface ContainerOptions {
/** Optional ID for the container */
id?: string;
/** Default port number to connect to (defaults to container.defaultPort) */
defaultPort?: number;
/** How long to keep the container alive without activity */
sleepAfter?: string | number;
/** Environment variables to pass to the container */
envVars?: Record<string, string>;
/** Custom entrypoint to override container default */
entrypoint?: string[];
/** Whether to enable internet access for the container */
enableInternet?: boolean;
/** If true, container won't be started automatically when the durable object starts */
explicitContainerStart?: boolean;
/** If true, container won't be started automatically when the durable object starts (preferred over explicitContainerStart) */
manualStart?: boolean;
}
/**
* Context provided to container methods
*/
interface ContainerContext {
id: string;
party: Party;
request?: Request;
connection?: Connection;
}
/**
* Function to handle container events
*/
type ContainerEventHandler = () => void | Promise<void>;
/**
* Options for starting a container with specific configuration
*/
interface ContainerStartConfigOptions {
/** Environment variables to pass to the container */
envVars?: Record<string, string>;
/** Custom entrypoint to override container default */
entrypoint?: string[];
/** Whether to enable internet access for the container */
enableInternet?: boolean;
}
/**
* Represents a scheduled task within a Container
* @template T Type of the payload data
*/
type Schedule<T = string> = {
/** Unique identifier for the schedule */
taskId: string;
/** Name of the method to be called */
callback: string;
/** Data to be passed to the callback */
payload: T;
} & ({
/** Type of schedule for one-time execution at a specific time */
type: 'scheduled';
/** Timestamp when the task should execute */
time: number;
} | {
/** Type of schedule for delayed execution */
type: 'delayed';
/** Timestamp when the task should execute */
time: number;
/** Number of seconds to delay execution */
delayInSeconds: number;
});
/**
* Params sent to `onStop` method when the container stops
*/
type StopParams = {
exitCode: number;
reason: 'exit' | 'runtime_signal';
};
/**
* Main Container class
*/
declare class Container<Env = unknown> extends DurableObject {
#private;
defaultPort?: number;
sleepAfter: string | number;
manualStart: boolean;
/**
* Container configuration properties
* Set these properties directly in your container instance
*/
envVars: ContainerStartOptions['env'];
entrypoint: ContainerStartOptions['entrypoint'];
enableInternet: ContainerStartOptions['enableInternet'];
/**
* Execute SQL queries against the Container's database
*/
private sql;
private container;
private state;
constructor(ctx: DurableObject['ctx'], env: Env, options?: ContainerOptions);
/**
* Determine if container should auto-start
*/
shouldAutoStart(): boolean;
private monitor;
/**
* Start the container if it's not running and set up monitoring
*
* This method handles the core container startup process without waiting for ports to be ready.
* It will automatically retry if the container fails to start, up to maxTries attempts.
*
* It's useful when you need to:
* - Start a container without blocking until a port is available
* - Initialize a container that doesn't expose ports
* - Perform custom port availability checks separately
*
* The method applies the container configuration from your instance properties by default, but allows
* overriding these values for this specific startup:
* - Environment variables (defaults to this.envVars)
* - Custom entrypoint commands (defaults to this.entrypoint)
* - Internet access settings (defaults to this.enableInternet)
*
* It also sets up monitoring to track container lifecycle events and automatically
* calls the onStop handler when the container terminates.
*
* @example
* // Basic usage in a custom Container implementation
* async customInitialize() {
* // Start the container without waiting for a port
* await this.startContainer();
*
* // Perform additional initialization steps
* // that don't require port access
* }
*
* @example
* // Start with custom configuration
* await this.startContainer({
* envVars: { DEBUG: 'true', NODE_ENV: 'development' },
* entrypoint: ['npm', 'run', 'dev'],
* enableInternet: false
* });
*
* @param options - Optional configuration to override instance defaults
* @returns A promise that resolves when the container start command has been issued
* @throws Error if no container context is available or if all start attempts fail
*/
startContainer(options?: ContainerStartConfigOptions): Promise<void>;
/**
* Required ports that should be checked for availability during container startup
* Override this in your subclass to specify ports that must be ready
*/
requiredPorts?: number[];
private setupMonitor;
/**
* Start the container and wait for ports to be available
* Based on containers-starter-go implementation
*
* This method builds on startContainer by adding port availability verification:
* 1. Calls startContainer to ensure the container is running
* 2. If no ports are specified and requiredPorts is not set, it uses defaultPort (if set)
* 3. If no ports can be determined, it calls onStart and renewActivityTimeout immediately
* 4. For each specified port, it polls until the port is available or maxTries is reached
* 5. When all ports are available, it triggers onStart and renewActivityTimeout
*
* The method prioritizes port sources in this order:
* 1. Ports specified directly in the method call
* 2. requiredPorts class property (if set)
* 3. defaultPort (if neither of the above is specified)
*
* @param ports - The ports to wait for (if undefined, uses requiredPorts or defaultPort)
* @param maxTries - Maximum number of attempts to connect to each port before failing
* @throws Error if port checks fail after maxTries attempts
*/
startAndWaitForPorts(ports?: number | number[], maxTries?: number): Promise<void>;
/**
* Send a request to the container (HTTP or WebSocket) using standard fetch API signature
* Based on containers-starter-go implementation
*
* This method handles both HTTP and WebSocket requests to the container.
* For WebSocket requests, it sets up bidirectional message forwarding with proper
* activity timeout renewal.
*
* Method supports multiple signatures to match standard fetch API:
* - containerFetch(request: Request, port?: number)
* - containerFetch(url: string | URL, init?: RequestInit, port?: number)
*
* @param requestOrUrl The request object or URL string/object to send to the container
* @param portOrInit Port number or fetch RequestInit options
* @param portParam Optional port number when using URL+init signature
* @returns A Response from the container, or WebSocket connection
*/
containerFetch(requestOrUrl: Request | string | URL, portOrInit?: number | RequestInit, portParam?: number): Promise<Response>;
private websocketCount;
/**
* Shuts down the container.
*/
stopContainer(signal?: number): Promise<void>;
/**
* Destroys the container. It will trigger onError instead of onStop.
*/
destroyContainer(): Promise<void>;
/**
* Lifecycle method called when container starts successfully
* Override this method in subclasses to handle container start events
*/
onStart(): void | Promise<void>;
/**
* Lifecycle method called when container shuts down
* Override this method in subclasses to handle Container stopped events
*/
onStop(_: StopParams): void | Promise<void>;
/**
* Error handler for container errors
* Override this method in subclasses to handle container errors
*/
onError(error: unknown): any;
/**
* Schedule a task to be executed in the future
* @template T Type of the payload data
* @param when When to execute the task (Date object or number of seconds delay)
* @param callback Name of the method to call
* @param payload Data to pass to the callback
* @returns Schedule object representing the scheduled task
*/
schedule<T = string>(when: Date | number, callback: string, payload?: T): Promise<Schedule<T>>;
/**
* Cancel a scheduled task
* @param id ID of the task to cancel
*/
unschedule(id: string): Promise<void>;
/**
* Get a scheduled task by ID
* @template T Type of the payload data
* @param id ID of the scheduled task
* @returns The Schedule object or undefined if not found
*/
getSchedule<T = string>(id: string): Promise<Schedule<T> | undefined>;
/**
* Get scheduled tasks matching the given criteria
* @template T Type of the payload data
* @param criteria Criteria to filter schedules
* @returns Array of matching Schedule objects
*/
getSchedules<T = string>(criteria?: {
id?: string;
type?: 'scheduled' | 'delayed';
timeRange?: {
start?: Date;
end?: Date;
};
}): Schedule<T>[];
/**
* Method called when an alarm fires
* Executes any scheduled tasks that are due
*/
alarm(alarmProps: {
isRetry: boolean;
retryCount: number;
}): Promise<void>;
alarmSleepPromise: Promise<unknown> | undefined;
alarmSleepResolve: (_: unknown) => void;
/**
* Renew the container's activity timeout
* Call this method whenever there is activity on the container
*/
private renewActivityTimeout;
private isActivityExpired;
/**
* Method called by scheduled task to stop the container due to inactivity
*/
stopDueToInactivity(): Promise<void>;
/**
* Handle fetch requests to the Container
* Default implementation forwards all HTTP and WebSocket requests to the container
* Override this in your subclass to specify a port or implement custom request handling
*
* @param request The request to handle
*/
fetch(request: Request): Promise<Response>;
}
/**
* Load balance requests across multiple container instances
* @param binding The Container binding
* @param instances Number of instances to load balance across
* @returns A container stub ready to handle requests
*/
declare function loadBalance<T extends Container>(binding: DurableObjectNamespace<T>, instances?: number): Promise<DurableObjectStub<T>>;
declare function getContainer<T extends Container>(binding: DurableObjectNamespace<T>): DurableObjectStub<T>;
export { Container, type ContainerContext, type ContainerEventHandler, type ContainerMessage, type ContainerOptions, type ContainerStartConfigOptions, getContainer, loadBalance };