UNPKG

@cloudflare/containers

Version:

Helper class for container-enabled Durable Objects

212 lines (211 loc) 9.07 kB
import type { ContainerOptions, ContainerStartOptions, ContainerStartConfigOptions, Schedule, StopParams, State } from '../types'; import { DurableObject } from 'cloudflare:workers'; export type Signal = 'SIGKILL' | 'SIGINT' | 'SIGTERM'; export type SignalInteger = number; export declare class Container<Env = unknown> extends DurableObject<Env> { defaultPort?: number; requiredPorts?: number[]; sleepAfter: string | number; envVars: ContainerStartOptions['env']; entrypoint: ContainerStartOptions['entrypoint']; enableInternet: ContainerStartOptions['enableInternet']; constructor(ctx: DurableObject['ctx'], env: Env, options?: ContainerOptions); /** * Gets the current state of the container * @returns Promise<State> */ getState(): Promise<State>; /** * 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.start(); * * // Perform additional initialization steps * // that don't require port access * } * * @example * // Start with custom configuration * await this.start({ * envVars: { DEBUG: 'true', NODE_ENV: 'development' }, * entrypoint: ['npm', 'run', 'dev'], * enableInternet: false * }); * * @param options - Optional configuration to override instance defaults * @param waitOptions - Optional wait configuration with abort signal for cancellation * @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 */ start(options?: ContainerStartConfigOptions, waitOptions?: { signal?: AbortSignal; }): Promise<void>; /** * Start the container and wait for ports to be available * Based on containers-starter-go implementation * * This method builds on start() by adding port availability verification: * 1. Calls start() 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[], cancellationOptions?: { abort?: AbortSignal; instanceGetTimeoutMS?: number; portReadyTimeoutMS?: number; waitInterval?: number; }): Promise<void>; /** * Shuts down the container. * @param signal - The signal to send to the container (default: 15 for SIGTERM) */ stop(signal?: Signal | SignalInteger): Promise<void>; /** * Destroys the container. It will trigger onError instead of onStop. */ destroy(): 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 * @param params - Object containing exitCode and reason for the stop */ onStop(_: StopParams): void | Promise<void>; /** * Lifecycle method called when the container is running, and the activity timeout * expiration has been reached. * * If you want to shutdown the container, you should call this.destroy() here * * By default, this method calls `this.destroy()` */ onActivityExpired(): Promise<void>; /** * Error handler for container errors * Override this method in subclasses to handle container errors * @param error - The error that occurred * @returns Can return any value or throw the error */ onError(error: unknown): any; /** * Renew the container's activity timeout * * Call this method whenever there is activity on the container */ renewActivityTimeout(): void; /** * 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>>; /** * Send a request to the container (HTTP or WebSocket) using standard fetch API signature * Based on containers-starter-go implementation * * This method handles HTTP requests to the container. WebSocket requests done outside the DO* * won't work until https://github.com/cloudflare/workerd/issues/2319 is addressed. Until then, please use `switchPort` + `fetch()`. * * 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>; /** * 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>; private container; private onStopCalled; private state; private monitor; private monitorSetup; private sleepAfterMs; private blockConcurrencyThrowable; /** * Execute SQL queries against the Container's database */ private sql; private requestAndPortFromContainerFetchArgs; private startContainerIfNotRunning; private setupMonitorCallbacks; deleteSchedules(name: string): void; /** * Method called when an alarm fires * Executes any scheduled tasks that are due */ alarm(alarmProps: { isRetry: boolean; retryCount: number; }): Promise<void>; timeout?: ReturnType<typeof setTimeout>; resolve?: () => void; private syncPendingStoppedEvents; private callOnStop; /** * Schedule the next alarm based on upcoming tasks */ scheduleNextAlarm(ms?: number): Promise<void>; listSchedules<T = string>(name: string): Promise<Schedule<T>[]>; private toSchedule; /** * 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>; private isActivityExpired; /** * Method called by scheduled task to stop the container due to inactivity */ private stopDueToInactivity; }