import { HotMesh } from '../hotmesh'; import { Connection, Registry, WorkerConfig, WorkerOptions } from '../../types/durable'; import { StreamData, StreamDataResponse } from '../../types/stream'; /** * Hosts workflow and activity functions, connecting them to Postgres * for durable execution, replay, and automatic retry. * * ## Connection Modes * * ### Standard (legacy) — full admin access * * The worker connects with the same Postgres credentials as the engine. * Simple to set up; all workers share the same connection pool. * * ```typescript * const worker = await Durable.Worker.create({ * connection: { * class: Postgres, * options: { connectionString: 'postgres://user:pass@host:5432/hotmesh' }, * }, * taskQueue: 'orders', * workflow: orderWorkflow, * }); * ``` * * ### Secured — scoped Postgres role (recommended for production) * * The worker connects as a restricted Postgres role that can only * dequeue/ack/respond on its assigned stream names. All data access * goes through SECURITY DEFINER stored procedures that validate the * role's `app.allowed_streams` session variable before executing. * * **Step 1**: Provision a scoped credential (run once, from the engine/admin): * ```typescript * const cred = await Durable.provisionWorkerRole({ * connection: { class: Postgres, options: adminPgOptions }, * namespace: 'durable', * streamNames: ['orders-activity'], * }); * // cred = { roleName: 'hmsh_wrk_durable_orders_activity', password: '...' } * ``` * * **Step 2**: Pass the credential when creating the worker: * ```typescript * const worker = await Durable.Worker.create({ * connection: { * class: Postgres, * options: { host: 'pg.prod', port: 5432, database: 'hotmesh' }, * }, * taskQueue: 'orders', * workflow: orderWorkflow, * workerCredentials: { user: cred.roleName, password: cred.password }, * }); * ``` * * The worker role **cannot**: * - SELECT/INSERT/UPDATE/DELETE any table directly * - Access `jobs`, `jobs_attributes`, or any engine tables * - Dequeue messages from other workers' streams * - LISTEN on other workers' notification channels * * See {@link Durable.provisionWorkerRole} for credential lifecycle management. * * ## Telemetry * * Workers automatically emit OpenTelemetry spans when an OTel SDK is * registered. Initialize the SDK **before** calling `create()`: * * ```typescript * import { NodeSDK } from '@opentelemetry/sdk-node'; * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; * import { resourceFromAttributes } from '@opentelemetry/resources'; * import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'; * * const sdk = new NodeSDK({ * resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'my-service' }), * traceExporter: new OTLPTraceExporter({ * url: 'https://api.honeycomb.io/v1/traces', * headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY }, * }), * }); * sdk.start(); * ``` * * | `HMSH_TELEMETRY` | Spans emitted | * |-------|---------------| * | `'info'` (default) | `WORKFLOW/START`, `WORKFLOW/COMPLETE`, `WORKFLOW/ERROR`, `ACTIVITY/{name}` | * | `'debug'` | All `info` spans + `DISPATCH/RETURN` per operation + engine internals | */ export declare class WorkerService { /** * @private */ static activityRegistry: Registry; /** * @private */ static instances: Map>; /** * @private */ workflowRunner: HotMesh; /** * @private */ activityRunner: HotMesh; /** @private — retained from create() config for stop() lifecycle event */ _eventsPublish?: (event: import('../../types/system_events').SystemEvent) => void | Promise; /** @private */ _eventsTaskQueue?: string; /** @private */ _eventsAppId?: string; /** * @private */ static getHotMesh: (workflowTopic: string, config?: Partial, options?: WorkerOptions) => Promise; static hashOptions(connection: Connection): string; /** * @private */ constructor(); /** * @private */ static activateWorkflow(hotMesh: HotMesh): Promise; /** * @private */ static registerActivities(activities: ACT): Registry; /** * Register activity workers for a task queue. Activities are invoked via message queue, * so they can run on different servers from workflows. * * The task queue name gets `-activity` appended automatically for the worker topic. * For example, `taskQueue: 'payment'` creates a worker listening on `payment-activity`. * * @param config - Worker configuration (connection, namespace, taskQueue) * @param activities - Activity functions to register * @param activityTaskQueue - Task queue name (without `-activity` suffix). * Defaults to `config.taskQueue` if not provided. * * @returns Promise The initialized activity worker * * @example * ```typescript * // Activity worker (can be on separate server) * import { Durable } from '@hotmeshio/hotmesh'; * import { Client as Postgres } from 'pg'; * * const activities = { * async processPayment(amount: number): Promise { * return `Processed $${amount}`; * }, * async sendEmail(to: string, subject: string): Promise { * // Send email * } * }; * * await Durable.registerActivityWorker({ * connection: { * class: Postgres, * options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' } * }, * taskQueue: 'payment' // Listens on 'payment-activity' * }, activities, 'payment'); * ``` * * @example * ```typescript * // Workflow worker (can be on different server) * async function orderWorkflow(orderId: string, amount: number) { * const { processPayment, sendEmail } = Durable.workflow.proxyActivities<{ * processPayment: (amount: number) => Promise; * sendEmail: (to: string, subject: string) => Promise; * }>({ * taskQueue: 'payment', * retry: { maximumAttempts: 3 } * }); * * const result = await processPayment(amount); * await sendEmail('customer@example.com', 'Order confirmed'); * return result; * } * * await Durable.Worker.create({ * connection: { * class: Postgres, * options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' } * }, * taskQueue: 'orders', * workflow: orderWorkflow * }); * ``` * * @example * ```typescript * // Shared activity pool for interceptors * await Durable.registerActivityWorker({ * connection: { * class: Postgres, * options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' } * }, * taskQueue: 'shared' * }, { auditLog, collectMetrics }, 'shared'); * * const interceptor: WorkflowInboundCallsInterceptor = { * async execute(ctx, next) { * const { auditLog } = Durable.workflow.proxyActivities<{ * auditLog: (id: string, action: string) => Promise; * }>({ * taskQueue: 'shared', * retry: { maximumAttempts: 3 } * }); * await auditLog(ctx.get('workflowId'), 'started'); * return next(); * } * }; * ``` * * @example * ```typescript * // Secured worker with scoped Postgres credentials (VNF-style isolation) * // Step 1: Admin provisions a credential (one-time) * const cred = await Durable.provisionWorkerRole({ * connection: { class: Postgres, options: adminOptions }, * streamNames: ['payment-activity'], * }); * * // Step 2: Worker connects with scoped role — can only access payment-activity * await Durable.registerActivityWorker({ * connection: { class: Postgres, options: { host: 'pg.prod', database: 'hotmesh' } }, * taskQueue: 'payment', * workerCredentials: { user: cred.roleName, password: cred.password }, * }, { processPayment, refundPayment }); * ``` */ static registerActivityWorker(config: Partial, activities: any, activityTaskQueue?: string): Promise; /** * Create an activity callback function that can be used by activity workers * @private */ static createActivityCallback(): (payload: StreamData) => Promise; /** * Creates and starts a workflow worker. * * @example * ```typescript * import { Durable } from '@hotmeshio/hotmesh'; * import { Client as Postgres } from 'pg'; * import * as workflows from './workflows'; * * async function run() { * const worker = await Durable.Worker.create({ * connection: { * class: Postgres, * options: { * connectionString: 'postgres://user:password@localhost:5432/db' * }, * }, * taskQueue: 'default', * workflow: workflows.example, * }); * * await worker.run(); * } * ``` */ static create(config: WorkerConfig): Promise; /** * @private */ static resolveWorkflowTarget(workflow: object | Function, name?: string): [string, Function]; /** * Run the connected worker; no-op (unnecessary to call) */ run(): Promise; /** * Stops the worker's HotMesh instances and fires `system.worker.{taskQueue}.stopped`. */ stop(): void; /** * @private */ initActivityWorker(config: WorkerConfig, activityTopic: string): Promise; /** * @private */ wrapActivityFunctions(): Function; /** * @private */ initWorkflowWorker(config: WorkerConfig, taskQueue: string, workflowFunctionName: string, workflowTopic: string, workflowFunction: Function): Promise; /** * @private */ static Context: { info: () => { workflowId: string; workflowTopic: string; }; }; /** * @private */ wrapWorkflowFunction(workflowFunction: Function, workflowTopic: string, workflowFunctionName: string, config: WorkerConfig): Function; /** * @private */ static shutdown(): Promise; }