UNPKG

cf-containers

Version:

TypeScript helper class for PartyKit durable object containers

310 lines (304 loc) 11.9 kB
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 };