@spooled/sdk 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -9
- package/dist/{index-BVWKEr6p.d.cts → index-Bqgiu-NN.d.cts} +569 -95
- package/dist/{index-BVWKEr6p.d.ts → index-Bqgiu-NN.d.ts} +569 -95
- package/dist/index.cjs +341 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -186
- package/dist/index.d.ts +3 -186
- package/dist/index.js +341 -126
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +1 -1
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +1 -1
- package/dist/worker/index.d.ts +1 -1
- package/dist/worker/index.js +1 -1
- package/dist/worker/index.js.map +1 -1
- package/package.json +8 -7
- package/proto/spooled.proto +19 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config.ts","../../src/utils/failure.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /** Circuit breaker configuration */\n circuitBreaker?: Partial<CircuitBreakerConfig>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerConfig;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: \"https://api.spooled.cloud\",\n wsUrl: \"wss://api.spooled.cloud\",\n grpcAddress: \"grpc.spooled.cloud:443\",\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: \"@spooled/sdk-nodejs/1.3.0\",\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = \"1.3.0\";\n\n/** API version prefix */\nexport const API_VERSION = \"v1\";\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : \"\");\n };\n } else if (typeof options.debug === \"function\") {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerConfig = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n\n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, \"ws\");\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken:\n options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method. An admin key alone is enough: the\n // /admin/* endpoints authenticate with X-Admin-Key only, so an admin-only\n // client (`client.admin.*`) is legitimate.\n if (!config.apiKey && !config.accessToken && !config.adminKey) {\n throw new Error(\n \"SpooledClient requires either apiKey or accessToken for authentication (or adminKey for admin-only use)\",\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (\n config.apiKey &&\n !config.apiKey.startsWith(\"sp_\") &&\n !config.apiKey.startsWith(\"sk_\")\n ) {\n throw new Error(\n \"Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_\",\n );\n }\n\n // Validate baseUrl\n if (\n !config.baseUrl.startsWith(\"http://\") &&\n !config.baseUrl.startsWith(\"https://\")\n ) {\n throw new Error(\"baseUrl must start with http:// or https://\");\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error(\"retry.maxRetries must be non-negative\");\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error(\"retry.baseDelay must be positive\");\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error(\"retry.maxDelay must be >= retry.baseDelay\");\n }\n if (config.retry.factor < 1) {\n throw new Error(\"retry.factor must be >= 1\");\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error(\"circuitBreaker.failureThreshold must be positive\");\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error(\"circuitBreaker.successThreshold must be positive\");\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error(\"circuitBreaker.timeout must be positive\");\n }\n }\n}\n","/**\n * Failure message coercion\n *\n * `POST /jobs/{id}/fail` rejects an empty or over-long `error` with a 400.\n * A worker that reported its failure with such text got nothing recorded: the\n * job stayed `processing` until its lease ran out, then ran again. The text of\n * a failure is therefore always made acceptable before it is sent.\n */\n\n/** Longest error text the API accepts (characters, not bytes). */\nexport const MAX_FAILURE_MESSAGE_CHARS = 2048;\n\n/** Text sent when a handler failed without saying why. */\nexport const DEFAULT_FAILURE_MESSAGE = \"Job failed\";\n\n/**\n * Make `message` acceptable to the fail endpoint: empty becomes\n * {@link DEFAULT_FAILURE_MESSAGE}; text over {@link MAX_FAILURE_MESSAGE_CHARS}\n * is cut to that many characters. The cut is by code point, so an emoji or\n * other astral character is never split into a lone surrogate.\n */\nexport function coerceFailureMessage(message: unknown): string {\n const text =\n typeof message === \"string\" ? message : message == null ? \"\" : `${message}`;\n if (text === \"\") {\n return DEFAULT_FAILURE_MESSAGE;\n }\n // UTF-16 length is an upper bound of the code point count: skip the\n // allocation for the common short message.\n if (text.length <= MAX_FAILURE_MESSAGE_CHARS) {\n return text;\n }\n const codePoints = Array.from(text);\n return codePoints.length <= MAX_FAILURE_MESSAGE_CHARS\n ? text\n : codePoints.slice(0, MAX_FAILURE_MESSAGE_CHARS).join(\"\");\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from \"../client.js\";\nimport { SDK_VERSION } from \"../config.js\";\nimport type { JsonValue } from \"../types/common.js\";\nimport type { ClaimedJob } from \"../types/jobs.js\";\nimport { coerceFailureMessage } from \"../utils/failure.js\";\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from \"./types.js\";\nimport { hostname as osHostname } from \"os\";\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/** Failure text sent when a handler outlives the job's `timeoutSeconds`. */\nconst JOB_TIMEOUT_MESSAGE = \"Job timeout exceeded\";\n\n/** Failure text sent for a handler still running when the shutdown grace ends. */\nconst SHUTDOWN_TIMEOUT_MESSAGE = \"Worker shutdown timeout\";\n\n/** Longest delay `setTimeout` accepts (a larger one fires immediately). */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\n\n/** How a handler run ended. Decided once per execution. */\ntype HandlerOutcome =\n | { kind: \"returned\"; value: JsonValue | void }\n | { kind: \"threw\"; error: unknown }\n | { kind: \"timeout\" };\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n /**\n * Options with every defaultable field filled in. `workerId` stays optional:\n * there is no sensible default for it, and omitting it is what makes the\n * server mint a UUID.\n */\n private readonly options: Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = \"idle\";\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n /** The claim request currently on the wire, so a stop can wait for its jobs. */\n private pollInFlight: Promise<void> | null = null;\n /** Wakes the shutdown drain when the last active job has been acked. */\n private drainWaiter: (() => void) | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> =\n new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: \"nodejs\",\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called. start() already\n // reports a failed registration through the \"error\" event; the catch only\n // keeps it from becoming an unhandled rejection that crashes the process.\n setImmediate(() => {\n this.start().catch((error) => {\n this.debug(\"Auto-start failed\", error);\n });\n });\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== \"idle\") {\n throw new Error(\"Cannot set handler after worker has started\");\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== \"idle\") {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error(\"No job handler registered. Call process() first.\");\n }\n\n this.state = \"starting\";\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n workerId: this.options.workerId,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = \"running\";\n this.emit(\"started\", {\n workerId: this.workerId,\n queueName: this.options.queueName,\n });\n this.schedulePoll();\n } catch (error) {\n this.state = \"error\";\n this.emit(\"error\", { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Drains: the worker stops claiming, then lets running handlers finish for\n * up to `shutdownTimeout`. A handler that returns completes its job and one\n * that throws fails it, even though a stop was requested meanwhile; its\n * `signal` is NOT aborted during the grace period. Only handlers still\n * running when the timeout ends are aborted and failed with \"Worker shutdown\n * timeout\". The worker deregisters last, because deregistering returns every\n * job the worker still holds to the queue.\n */\n async stop(): Promise<void> {\n if (this.state !== \"running\") {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n const deadline = Date.now() + this.options.shutdownTimeout;\n this.state = \"stopping\";\n this.debug(\"Stopping worker...\");\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // A claim already on the wire still returns jobs this worker now holds\n // leases for. Let it land first so they join the drain below instead of\n // starting after the worker has deregistered.\n if (this.pollInFlight) {\n await this.pollInFlight;\n }\n\n // Drain: the worker heartbeat and every job heartbeat keep running while\n // handlers finish, so neither the worker nor a lease expires mid-drain.\n if (this.activeJobs.size > 0) {\n this.debug(\n `Waiting for ${this.activeJobs.size} active jobs to complete...`,\n );\n\n await this.waitForDrain(Math.max(0, deadline - Date.now()));\n\n // Whatever is still running outlived the grace period: abort and fail it.\n // A job whose handler returned just before the deadline is already\n // settled (its ack is in flight) and is awaited below, not failed.\n const stragglers = Array.from(this.activeJobs.values()).filter(\n (active) => !active.settled,\n );\n await Promise.all(stragglers.map((active) => this.abandonJob(active)));\n\n // Acks already on the wire must land before the worker deregisters,\n // otherwise deregistering re-queues a job that was just finished.\n await Promise.all(\n Array.from(this.activeJobs.values()).map((active) => active.ack),\n );\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Deregister worker\n const workerId = this.workerId;\n if (workerId) {\n try {\n await this.client.workers.deregister(workerId);\n this.debug(\"Worker deregistered\");\n } catch (error) {\n this.debug(\"Failed to deregister worker\", error);\n }\n }\n\n this.state = \"stopped\";\n this.workerId = null;\n this.emit(\"stopped\", { workerId: workerId ?? \"\", reason: \"graceful\" });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(\n event: E,\n data: WorkerEventData[E],\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug(\"Event handler error\", { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== \"running\") {\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n this.pollTimer = null;\n const run = this.poll();\n this.pollInFlight = run;\n const done = () => {\n if (this.pollInFlight === run) {\n this.pollInFlight = null;\n }\n };\n run.then(done, done);\n }, this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== \"running\" || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug(\"Poll failed\", error);\n this.emit(\"error\", { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit(\"job:claimed\", { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n settled: false,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval =\n this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit(\"job:started\", { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (percent: number, message?: string) => {\n const boundedPercent = Math.max(0, Math.min(100, percent));\n this.debug(`[${job.id}] progress ${boundedPercent}%`, message);\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const outcome = await this.runHandler(activeJob, context);\n\n // The shutdown drain already failed this job while its handler was still\n // running; the lease is gone, so a late result must not be sent.\n if (activeJob.settled) {\n this.debug(`Job ${job.id} was already settled; dropping late result`);\n return;\n }\n activeJob.settled = true;\n\n // The abort signal is only a hint to the handler. Whatever the handler\n // did decides the ack: a result that arrives after a stop was requested\n // is still sent, never silently dropped.\n switch (outcome.kind) {\n case \"returned\":\n activeJob.ack = this.completeJob(job, outcome.value ?? undefined);\n break;\n case \"threw\":\n activeJob.ack = this.failJob(\n job,\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n );\n break;\n case \"timeout\":\n // Stop the handler if it listens, then fail the job: extending the\n // lease of a hung handler forever would keep the job and its\n // concurrency slot stuck.\n abortController.abort(new Error(JOB_TIMEOUT_MESSAGE));\n this.emit(\"job:timeout\", {\n jobId: job.id,\n queueName: job.queueName,\n });\n activeJob.ack = this.failJob(job, JOB_TIMEOUT_MESSAGE);\n break;\n }\n await activeJob.ack;\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n /**\n * Run the handler, racing it against the job's `timeoutSeconds`.\n * Never rejects: a throw (sync or async) is reported as an outcome.\n */\n private runHandler(\n activeJob: ActiveJob,\n context: JobContext,\n ): Promise<HandlerOutcome> {\n const run = (async (): Promise<HandlerOutcome> => {\n try {\n return { kind: \"returned\", value: await this.handler!(context) };\n } catch (error) {\n return { kind: \"threw\", error };\n }\n })();\n\n const { timeoutSeconds } = activeJob.job;\n if (typeof timeoutSeconds !== \"number\" || !(timeoutSeconds > 0)) {\n return run;\n }\n\n return new Promise<HandlerOutcome>((resolve) => {\n activeJob.timeoutTimer = setTimeout(\n () => resolve({ kind: \"timeout\" }),\n Math.min(timeoutSeconds * 1000, MAX_TIMER_MS),\n );\n run.then((outcome) => {\n this.clearTimeoutTimer(activeJob);\n resolve(outcome);\n });\n });\n }\n\n /** Abort a handler that outlived the shutdown grace period and fail its job. */\n private async abandonJob(activeJob: ActiveJob): Promise<void> {\n this.debug(`Force-failing job ${activeJob.job.id} due to shutdown timeout`);\n // Mark first: the handler may react to the abort, or finish later, and its\n // outcome must not be acked on top of this failure.\n activeJob.settled = true;\n activeJob.abortController.abort(new Error(SHUTDOWN_TIMEOUT_MESSAGE));\n activeJob.ack = this.failJob(activeJob.job, SHUTDOWN_TIMEOUT_MESSAGE);\n await activeJob.ack;\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timers.\n this.cleanupJob(activeJob);\n }\n\n private async completeJob(\n job: ClaimedJob,\n result?: JsonValue,\n ): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:completed\", {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n // The ack was already retried by the HTTP layer; what is left is a job\n // that finished but could not be reported.\n this.debug(`Failed to complete job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n // The API rejects empty or over-long failure text with a 400, which would\n // leave the job processing until its lease expired.\n const message = coerceFailureMessage(errorMessage);\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: message,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:failed\", {\n jobId: job.id,\n queueName: job.queueName,\n error: message,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private clearTimeoutTimer(activeJob: ActiveJob): void {\n if (activeJob.timeoutTimer) {\n clearTimeout(activeJob.timeoutTimer);\n activeJob.timeoutTimer = undefined;\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n this.clearTimeoutTimer(activeJob);\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n if (this.activeJobs.size === 0) {\n this.drainWaiter?.();\n }\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n // Keep beating through the shutdown drain: a long `shutdownTimeout` would\n // otherwise let the stale-worker reaper take the worker (and its jobs).\n if (\n !this.workerId ||\n (this.state !== \"running\" && this.state !== \"stopping\")\n ) {\n return;\n }\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: \"healthy\",\n });\n } catch (error) {\n this.debug(\"Worker heartbeat failed\", error);\n }\n }\n\n /**\n * Resolve once no job is active, or after `timeoutMs`. The timer is always\n * cleared, so a clean stop leaves nothing that keeps the process alive.\n */\n private waitForDrain(timeoutMs: number): Promise<void> {\n if (this.activeJobs.size === 0) {\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.drainWaiter = null;\n resolve();\n }, timeoutMs);\n this.drainWaiter = () => {\n clearTimeout(timer);\n this.drainWaiter = null;\n resolve();\n };\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/utils/failure.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\nimport type { CircuitBreakerOptions } from \"./utils/circuit-breaker.js\";\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /**\n * Circuit breaker configuration. Besides the thresholds it takes\n * `isFailure` (which errors count as failures) and `onStateChange`.\n */\n circuitBreaker?: Partial<CircuitBreakerOptions>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerOptions;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: \"https://api.spooled.cloud\",\n wsUrl: \"wss://api.spooled.cloud\",\n grpcAddress: \"grpc.spooled.cloud:443\",\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: \"@spooled/sdk-nodejs/1.4.1\",\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = \"1.4.1\";\n\n/** API version prefix */\nexport const API_VERSION = \"v1\";\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : \"\");\n };\n } else if (typeof options.debug === \"function\") {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerOptions = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n\n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, \"ws\");\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken:\n options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method. An admin key alone is enough: the\n // /admin/* endpoints authenticate with X-Admin-Key only, so an admin-only\n // client (`client.admin.*`) is legitimate.\n if (!config.apiKey && !config.accessToken && !config.adminKey) {\n throw new Error(\n \"SpooledClient requires either apiKey or accessToken for authentication (or adminKey for admin-only use)\",\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (\n config.apiKey &&\n !config.apiKey.startsWith(\"sp_\") &&\n !config.apiKey.startsWith(\"sk_\")\n ) {\n throw new Error(\n \"Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_\",\n );\n }\n\n // Validate baseUrl\n if (\n !config.baseUrl.startsWith(\"http://\") &&\n !config.baseUrl.startsWith(\"https://\")\n ) {\n throw new Error(\"baseUrl must start with http:// or https://\");\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error(\"retry.maxRetries must be non-negative\");\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error(\"retry.baseDelay must be positive\");\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error(\"retry.maxDelay must be >= retry.baseDelay\");\n }\n if (config.retry.factor < 1) {\n throw new Error(\"retry.factor must be >= 1\");\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error(\"circuitBreaker.failureThreshold must be positive\");\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error(\"circuitBreaker.successThreshold must be positive\");\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error(\"circuitBreaker.timeout must be positive\");\n }\n }\n}\n","/**\n * Failure message coercion\n *\n * `POST /jobs/{id}/fail` rejects an empty or over-long `error` with a 400.\n * A worker that reported its failure with such text got nothing recorded: the\n * job stayed `processing` until its lease ran out, then ran again. The text of\n * a failure is therefore always made acceptable before it is sent.\n */\n\n/** Longest error text the API accepts (characters, not bytes). */\nexport const MAX_FAILURE_MESSAGE_CHARS = 2048;\n\n/** Text sent when a handler failed without saying why. */\nexport const DEFAULT_FAILURE_MESSAGE = \"Job failed\";\n\n/**\n * Make `message` acceptable to the fail endpoint: empty becomes\n * {@link DEFAULT_FAILURE_MESSAGE}; text over {@link MAX_FAILURE_MESSAGE_CHARS}\n * is cut to that many characters. The cut is by code point, so an emoji or\n * other astral character is never split into a lone surrogate.\n */\nexport function coerceFailureMessage(message: unknown): string {\n const text =\n typeof message === \"string\" ? message : message == null ? \"\" : `${message}`;\n if (text === \"\") {\n return DEFAULT_FAILURE_MESSAGE;\n }\n // UTF-16 length is an upper bound of the code point count: skip the\n // allocation for the common short message.\n if (text.length <= MAX_FAILURE_MESSAGE_CHARS) {\n return text;\n }\n const codePoints = Array.from(text);\n return codePoints.length <= MAX_FAILURE_MESSAGE_CHARS\n ? text\n : codePoints.slice(0, MAX_FAILURE_MESSAGE_CHARS).join(\"\");\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from \"../client.js\";\nimport { SDK_VERSION } from \"../config.js\";\nimport type { JsonValue } from \"../types/common.js\";\nimport type { ClaimedJob } from \"../types/jobs.js\";\nimport { coerceFailureMessage } from \"../utils/failure.js\";\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from \"./types.js\";\nimport { hostname as osHostname } from \"os\";\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/** Failure text sent when a handler outlives the job's `timeoutSeconds`. */\nconst JOB_TIMEOUT_MESSAGE = \"Job timeout exceeded\";\n\n/** Failure text sent for a handler still running when the shutdown grace ends. */\nconst SHUTDOWN_TIMEOUT_MESSAGE = \"Worker shutdown timeout\";\n\n/** Longest delay `setTimeout` accepts (a larger one fires immediately). */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\n\n/** How a handler run ended. Decided once per execution. */\ntype HandlerOutcome =\n | { kind: \"returned\"; value: JsonValue | void }\n | { kind: \"threw\"; error: unknown }\n | { kind: \"timeout\" };\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n /**\n * Options with every defaultable field filled in. `workerId` stays optional:\n * there is no sensible default for it, and omitting it is what makes the\n * server mint a UUID.\n */\n private readonly options: Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = \"idle\";\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n /** The claim request currently on the wire, so a stop can wait for its jobs. */\n private pollInFlight: Promise<void> | null = null;\n /** Wakes the shutdown drain when the last active job has been acked. */\n private drainWaiter: (() => void) | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> =\n new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: \"nodejs\",\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called. start() already\n // reports a failed registration through the \"error\" event; the catch only\n // keeps it from becoming an unhandled rejection that crashes the process.\n setImmediate(() => {\n this.start().catch((error) => {\n this.debug(\"Auto-start failed\", error);\n });\n });\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== \"idle\") {\n throw new Error(\"Cannot set handler after worker has started\");\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== \"idle\") {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error(\"No job handler registered. Call process() first.\");\n }\n\n this.state = \"starting\";\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n workerId: this.options.workerId,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = \"running\";\n this.emit(\"started\", {\n workerId: this.workerId,\n queueName: this.options.queueName,\n });\n this.schedulePoll();\n } catch (error) {\n this.state = \"error\";\n this.emit(\"error\", { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Drains: the worker stops claiming, then lets running handlers finish for\n * up to `shutdownTimeout`. A handler that returns completes its job and one\n * that throws fails it, even though a stop was requested meanwhile; its\n * `signal` is NOT aborted during the grace period. Only handlers still\n * running when the timeout ends are aborted and failed with \"Worker shutdown\n * timeout\". The worker deregisters last, because deregistering returns every\n * job the worker still holds to the queue.\n */\n async stop(): Promise<void> {\n if (this.state !== \"running\") {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n const deadline = Date.now() + this.options.shutdownTimeout;\n this.state = \"stopping\";\n this.debug(\"Stopping worker...\");\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // A claim already on the wire still returns jobs this worker now holds\n // leases for. Let it land first so they join the drain below instead of\n // starting after the worker has deregistered.\n if (this.pollInFlight) {\n await this.pollInFlight;\n }\n\n // Drain: the worker heartbeat and every job heartbeat keep running while\n // handlers finish, so neither the worker nor a lease expires mid-drain.\n if (this.activeJobs.size > 0) {\n this.debug(\n `Waiting for ${this.activeJobs.size} active jobs to complete...`,\n );\n\n await this.waitForDrain(Math.max(0, deadline - Date.now()));\n\n // Whatever is still running outlived the grace period: abort and fail it.\n // A job whose handler returned just before the deadline is already\n // settled (its ack is in flight) and is awaited below, not failed.\n const stragglers = Array.from(this.activeJobs.values()).filter(\n (active) => !active.settled,\n );\n await Promise.all(stragglers.map((active) => this.abandonJob(active)));\n\n // Acks already on the wire must land before the worker deregisters,\n // otherwise deregistering re-queues a job that was just finished.\n await Promise.all(\n Array.from(this.activeJobs.values()).map((active) => active.ack),\n );\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Deregister worker\n const workerId = this.workerId;\n if (workerId) {\n try {\n await this.client.workers.deregister(workerId);\n this.debug(\"Worker deregistered\");\n } catch (error) {\n this.debug(\"Failed to deregister worker\", error);\n }\n }\n\n this.state = \"stopped\";\n this.workerId = null;\n this.emit(\"stopped\", { workerId: workerId ?? \"\", reason: \"graceful\" });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(\n event: E,\n data: WorkerEventData[E],\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug(\"Event handler error\", { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== \"running\") {\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n this.pollTimer = null;\n const run = this.poll();\n this.pollInFlight = run;\n const done = () => {\n if (this.pollInFlight === run) {\n this.pollInFlight = null;\n }\n };\n run.then(done, done);\n }, this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== \"running\" || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug(\"Poll failed\", error);\n this.emit(\"error\", { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit(\"job:claimed\", { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n settled: false,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval =\n this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit(\"job:started\", { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (percent: number, message?: string) => {\n const boundedPercent = Math.max(0, Math.min(100, percent));\n this.debug(`[${job.id}] progress ${boundedPercent}%`, message);\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const outcome = await this.runHandler(activeJob, context);\n\n // The shutdown drain already failed this job while its handler was still\n // running; the lease is gone, so a late result must not be sent.\n if (activeJob.settled) {\n this.debug(`Job ${job.id} was already settled; dropping late result`);\n return;\n }\n activeJob.settled = true;\n\n // The abort signal is only a hint to the handler. Whatever the handler\n // did decides the ack: a result that arrives after a stop was requested\n // is still sent, never silently dropped.\n switch (outcome.kind) {\n case \"returned\":\n activeJob.ack = this.completeJob(job, outcome.value ?? undefined);\n break;\n case \"threw\":\n activeJob.ack = this.failJob(\n job,\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n );\n break;\n case \"timeout\":\n // Stop the handler if it listens, then fail the job: extending the\n // lease of a hung handler forever would keep the job and its\n // concurrency slot stuck.\n abortController.abort(new Error(JOB_TIMEOUT_MESSAGE));\n this.emit(\"job:timeout\", {\n jobId: job.id,\n queueName: job.queueName,\n });\n activeJob.ack = this.failJob(job, JOB_TIMEOUT_MESSAGE);\n break;\n }\n await activeJob.ack;\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n /**\n * Run the handler, racing it against the job's `timeoutSeconds`.\n * Never rejects: a throw (sync or async) is reported as an outcome.\n */\n private runHandler(\n activeJob: ActiveJob,\n context: JobContext,\n ): Promise<HandlerOutcome> {\n const run = (async (): Promise<HandlerOutcome> => {\n try {\n return { kind: \"returned\", value: await this.handler!(context) };\n } catch (error) {\n return { kind: \"threw\", error };\n }\n })();\n\n const { timeoutSeconds } = activeJob.job;\n if (typeof timeoutSeconds !== \"number\" || !(timeoutSeconds > 0)) {\n return run;\n }\n\n return new Promise<HandlerOutcome>((resolve) => {\n activeJob.timeoutTimer = setTimeout(\n () => resolve({ kind: \"timeout\" }),\n Math.min(timeoutSeconds * 1000, MAX_TIMER_MS),\n );\n run.then((outcome) => {\n this.clearTimeoutTimer(activeJob);\n resolve(outcome);\n });\n });\n }\n\n /** Abort a handler that outlived the shutdown grace period and fail its job. */\n private async abandonJob(activeJob: ActiveJob): Promise<void> {\n this.debug(`Force-failing job ${activeJob.job.id} due to shutdown timeout`);\n // Mark first: the handler may react to the abort, or finish later, and its\n // outcome must not be acked on top of this failure.\n activeJob.settled = true;\n activeJob.abortController.abort(new Error(SHUTDOWN_TIMEOUT_MESSAGE));\n activeJob.ack = this.failJob(activeJob.job, SHUTDOWN_TIMEOUT_MESSAGE);\n await activeJob.ack;\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timers.\n this.cleanupJob(activeJob);\n }\n\n private async completeJob(\n job: ClaimedJob,\n result?: JsonValue,\n ): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:completed\", {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n // The ack was already retried by the HTTP layer; what is left is a job\n // that finished but could not be reported.\n this.debug(`Failed to complete job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n // The API rejects empty or over-long failure text with a 400, which would\n // leave the job processing until its lease expired.\n const message = coerceFailureMessage(errorMessage);\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: message,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:failed\", {\n jobId: job.id,\n queueName: job.queueName,\n error: message,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private clearTimeoutTimer(activeJob: ActiveJob): void {\n if (activeJob.timeoutTimer) {\n clearTimeout(activeJob.timeoutTimer);\n activeJob.timeoutTimer = undefined;\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n this.clearTimeoutTimer(activeJob);\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n if (this.activeJobs.size === 0) {\n this.drainWaiter?.();\n }\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n // Keep beating through the shutdown drain: a long `shutdownTimeout` would\n // otherwise let the stale-worker reaper take the worker (and its jobs).\n if (\n !this.workerId ||\n (this.state !== \"running\" && this.state !== \"stopping\")\n ) {\n return;\n }\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: \"healthy\",\n });\n } catch (error) {\n this.debug(\"Worker heartbeat failed\", error);\n }\n }\n\n /**\n * Resolve once no job is active, or after `timeoutMs`. The timer is always\n * cleared, so a clean stop leaves nothing that keeps the process alive.\n */\n private waitForDrain(timeoutMs: number): Promise<void> {\n if (this.activeJobs.size === 0) {\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.drainWaiter = null;\n resolve();\n }, timeoutMs);\n this.drainWaiter = () => {\n clearTimeout(timer);\n this.drainWaiter = null;\n resolve();\n };\n });\n }\n}\n"]}
|
package/dist/worker/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { dv as ActiveJob, be as JobContext, bh as JobHandler, bm as JobResult, cz as SpooledWorker, cA as SpooledWorkerOptions, c$ as WorkerEvent, d0 as WorkerEventData, d3 as WorkerState } from '../index-Bqgiu-NN.cjs';
|
|
2
2
|
import '@grpc/grpc-js';
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { dv as ActiveJob, be as JobContext, bh as JobHandler, bm as JobResult, cz as SpooledWorker, cA as SpooledWorkerOptions, c$ as WorkerEvent, d0 as WorkerEventData, d3 as WorkerState } from '../index-Bqgiu-NN.js';
|
|
2
2
|
import '@grpc/grpc-js';
|
package/dist/worker/index.js
CHANGED
package/dist/worker/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config.ts","../../src/utils/failure.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /** Circuit breaker configuration */\n circuitBreaker?: Partial<CircuitBreakerConfig>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerConfig;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: \"https://api.spooled.cloud\",\n wsUrl: \"wss://api.spooled.cloud\",\n grpcAddress: \"grpc.spooled.cloud:443\",\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: \"@spooled/sdk-nodejs/1.3.0\",\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = \"1.3.0\";\n\n/** API version prefix */\nexport const API_VERSION = \"v1\";\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : \"\");\n };\n } else if (typeof options.debug === \"function\") {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerConfig = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n\n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, \"ws\");\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken:\n options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method. An admin key alone is enough: the\n // /admin/* endpoints authenticate with X-Admin-Key only, so an admin-only\n // client (`client.admin.*`) is legitimate.\n if (!config.apiKey && !config.accessToken && !config.adminKey) {\n throw new Error(\n \"SpooledClient requires either apiKey or accessToken for authentication (or adminKey for admin-only use)\",\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (\n config.apiKey &&\n !config.apiKey.startsWith(\"sp_\") &&\n !config.apiKey.startsWith(\"sk_\")\n ) {\n throw new Error(\n \"Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_\",\n );\n }\n\n // Validate baseUrl\n if (\n !config.baseUrl.startsWith(\"http://\") &&\n !config.baseUrl.startsWith(\"https://\")\n ) {\n throw new Error(\"baseUrl must start with http:// or https://\");\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error(\"retry.maxRetries must be non-negative\");\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error(\"retry.baseDelay must be positive\");\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error(\"retry.maxDelay must be >= retry.baseDelay\");\n }\n if (config.retry.factor < 1) {\n throw new Error(\"retry.factor must be >= 1\");\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error(\"circuitBreaker.failureThreshold must be positive\");\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error(\"circuitBreaker.successThreshold must be positive\");\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error(\"circuitBreaker.timeout must be positive\");\n }\n }\n}\n","/**\n * Failure message coercion\n *\n * `POST /jobs/{id}/fail` rejects an empty or over-long `error` with a 400.\n * A worker that reported its failure with such text got nothing recorded: the\n * job stayed `processing` until its lease ran out, then ran again. The text of\n * a failure is therefore always made acceptable before it is sent.\n */\n\n/** Longest error text the API accepts (characters, not bytes). */\nexport const MAX_FAILURE_MESSAGE_CHARS = 2048;\n\n/** Text sent when a handler failed without saying why. */\nexport const DEFAULT_FAILURE_MESSAGE = \"Job failed\";\n\n/**\n * Make `message` acceptable to the fail endpoint: empty becomes\n * {@link DEFAULT_FAILURE_MESSAGE}; text over {@link MAX_FAILURE_MESSAGE_CHARS}\n * is cut to that many characters. The cut is by code point, so an emoji or\n * other astral character is never split into a lone surrogate.\n */\nexport function coerceFailureMessage(message: unknown): string {\n const text =\n typeof message === \"string\" ? message : message == null ? \"\" : `${message}`;\n if (text === \"\") {\n return DEFAULT_FAILURE_MESSAGE;\n }\n // UTF-16 length is an upper bound of the code point count: skip the\n // allocation for the common short message.\n if (text.length <= MAX_FAILURE_MESSAGE_CHARS) {\n return text;\n }\n const codePoints = Array.from(text);\n return codePoints.length <= MAX_FAILURE_MESSAGE_CHARS\n ? text\n : codePoints.slice(0, MAX_FAILURE_MESSAGE_CHARS).join(\"\");\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from \"../client.js\";\nimport { SDK_VERSION } from \"../config.js\";\nimport type { JsonValue } from \"../types/common.js\";\nimport type { ClaimedJob } from \"../types/jobs.js\";\nimport { coerceFailureMessage } from \"../utils/failure.js\";\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from \"./types.js\";\nimport { hostname as osHostname } from \"os\";\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/** Failure text sent when a handler outlives the job's `timeoutSeconds`. */\nconst JOB_TIMEOUT_MESSAGE = \"Job timeout exceeded\";\n\n/** Failure text sent for a handler still running when the shutdown grace ends. */\nconst SHUTDOWN_TIMEOUT_MESSAGE = \"Worker shutdown timeout\";\n\n/** Longest delay `setTimeout` accepts (a larger one fires immediately). */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\n\n/** How a handler run ended. Decided once per execution. */\ntype HandlerOutcome =\n | { kind: \"returned\"; value: JsonValue | void }\n | { kind: \"threw\"; error: unknown }\n | { kind: \"timeout\" };\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n /**\n * Options with every defaultable field filled in. `workerId` stays optional:\n * there is no sensible default for it, and omitting it is what makes the\n * server mint a UUID.\n */\n private readonly options: Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = \"idle\";\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n /** The claim request currently on the wire, so a stop can wait for its jobs. */\n private pollInFlight: Promise<void> | null = null;\n /** Wakes the shutdown drain when the last active job has been acked. */\n private drainWaiter: (() => void) | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> =\n new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: \"nodejs\",\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called. start() already\n // reports a failed registration through the \"error\" event; the catch only\n // keeps it from becoming an unhandled rejection that crashes the process.\n setImmediate(() => {\n this.start().catch((error) => {\n this.debug(\"Auto-start failed\", error);\n });\n });\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== \"idle\") {\n throw new Error(\"Cannot set handler after worker has started\");\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== \"idle\") {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error(\"No job handler registered. Call process() first.\");\n }\n\n this.state = \"starting\";\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n workerId: this.options.workerId,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = \"running\";\n this.emit(\"started\", {\n workerId: this.workerId,\n queueName: this.options.queueName,\n });\n this.schedulePoll();\n } catch (error) {\n this.state = \"error\";\n this.emit(\"error\", { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Drains: the worker stops claiming, then lets running handlers finish for\n * up to `shutdownTimeout`. A handler that returns completes its job and one\n * that throws fails it, even though a stop was requested meanwhile; its\n * `signal` is NOT aborted during the grace period. Only handlers still\n * running when the timeout ends are aborted and failed with \"Worker shutdown\n * timeout\". The worker deregisters last, because deregistering returns every\n * job the worker still holds to the queue.\n */\n async stop(): Promise<void> {\n if (this.state !== \"running\") {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n const deadline = Date.now() + this.options.shutdownTimeout;\n this.state = \"stopping\";\n this.debug(\"Stopping worker...\");\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // A claim already on the wire still returns jobs this worker now holds\n // leases for. Let it land first so they join the drain below instead of\n // starting after the worker has deregistered.\n if (this.pollInFlight) {\n await this.pollInFlight;\n }\n\n // Drain: the worker heartbeat and every job heartbeat keep running while\n // handlers finish, so neither the worker nor a lease expires mid-drain.\n if (this.activeJobs.size > 0) {\n this.debug(\n `Waiting for ${this.activeJobs.size} active jobs to complete...`,\n );\n\n await this.waitForDrain(Math.max(0, deadline - Date.now()));\n\n // Whatever is still running outlived the grace period: abort and fail it.\n // A job whose handler returned just before the deadline is already\n // settled (its ack is in flight) and is awaited below, not failed.\n const stragglers = Array.from(this.activeJobs.values()).filter(\n (active) => !active.settled,\n );\n await Promise.all(stragglers.map((active) => this.abandonJob(active)));\n\n // Acks already on the wire must land before the worker deregisters,\n // otherwise deregistering re-queues a job that was just finished.\n await Promise.all(\n Array.from(this.activeJobs.values()).map((active) => active.ack),\n );\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Deregister worker\n const workerId = this.workerId;\n if (workerId) {\n try {\n await this.client.workers.deregister(workerId);\n this.debug(\"Worker deregistered\");\n } catch (error) {\n this.debug(\"Failed to deregister worker\", error);\n }\n }\n\n this.state = \"stopped\";\n this.workerId = null;\n this.emit(\"stopped\", { workerId: workerId ?? \"\", reason: \"graceful\" });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(\n event: E,\n data: WorkerEventData[E],\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug(\"Event handler error\", { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== \"running\") {\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n this.pollTimer = null;\n const run = this.poll();\n this.pollInFlight = run;\n const done = () => {\n if (this.pollInFlight === run) {\n this.pollInFlight = null;\n }\n };\n run.then(done, done);\n }, this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== \"running\" || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug(\"Poll failed\", error);\n this.emit(\"error\", { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit(\"job:claimed\", { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n settled: false,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval =\n this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit(\"job:started\", { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (percent: number, message?: string) => {\n const boundedPercent = Math.max(0, Math.min(100, percent));\n this.debug(`[${job.id}] progress ${boundedPercent}%`, message);\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const outcome = await this.runHandler(activeJob, context);\n\n // The shutdown drain already failed this job while its handler was still\n // running; the lease is gone, so a late result must not be sent.\n if (activeJob.settled) {\n this.debug(`Job ${job.id} was already settled; dropping late result`);\n return;\n }\n activeJob.settled = true;\n\n // The abort signal is only a hint to the handler. Whatever the handler\n // did decides the ack: a result that arrives after a stop was requested\n // is still sent, never silently dropped.\n switch (outcome.kind) {\n case \"returned\":\n activeJob.ack = this.completeJob(job, outcome.value ?? undefined);\n break;\n case \"threw\":\n activeJob.ack = this.failJob(\n job,\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n );\n break;\n case \"timeout\":\n // Stop the handler if it listens, then fail the job: extending the\n // lease of a hung handler forever would keep the job and its\n // concurrency slot stuck.\n abortController.abort(new Error(JOB_TIMEOUT_MESSAGE));\n this.emit(\"job:timeout\", {\n jobId: job.id,\n queueName: job.queueName,\n });\n activeJob.ack = this.failJob(job, JOB_TIMEOUT_MESSAGE);\n break;\n }\n await activeJob.ack;\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n /**\n * Run the handler, racing it against the job's `timeoutSeconds`.\n * Never rejects: a throw (sync or async) is reported as an outcome.\n */\n private runHandler(\n activeJob: ActiveJob,\n context: JobContext,\n ): Promise<HandlerOutcome> {\n const run = (async (): Promise<HandlerOutcome> => {\n try {\n return { kind: \"returned\", value: await this.handler!(context) };\n } catch (error) {\n return { kind: \"threw\", error };\n }\n })();\n\n const { timeoutSeconds } = activeJob.job;\n if (typeof timeoutSeconds !== \"number\" || !(timeoutSeconds > 0)) {\n return run;\n }\n\n return new Promise<HandlerOutcome>((resolve) => {\n activeJob.timeoutTimer = setTimeout(\n () => resolve({ kind: \"timeout\" }),\n Math.min(timeoutSeconds * 1000, MAX_TIMER_MS),\n );\n run.then((outcome) => {\n this.clearTimeoutTimer(activeJob);\n resolve(outcome);\n });\n });\n }\n\n /** Abort a handler that outlived the shutdown grace period and fail its job. */\n private async abandonJob(activeJob: ActiveJob): Promise<void> {\n this.debug(`Force-failing job ${activeJob.job.id} due to shutdown timeout`);\n // Mark first: the handler may react to the abort, or finish later, and its\n // outcome must not be acked on top of this failure.\n activeJob.settled = true;\n activeJob.abortController.abort(new Error(SHUTDOWN_TIMEOUT_MESSAGE));\n activeJob.ack = this.failJob(activeJob.job, SHUTDOWN_TIMEOUT_MESSAGE);\n await activeJob.ack;\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timers.\n this.cleanupJob(activeJob);\n }\n\n private async completeJob(\n job: ClaimedJob,\n result?: JsonValue,\n ): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:completed\", {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n // The ack was already retried by the HTTP layer; what is left is a job\n // that finished but could not be reported.\n this.debug(`Failed to complete job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n // The API rejects empty or over-long failure text with a 400, which would\n // leave the job processing until its lease expired.\n const message = coerceFailureMessage(errorMessage);\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: message,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:failed\", {\n jobId: job.id,\n queueName: job.queueName,\n error: message,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private clearTimeoutTimer(activeJob: ActiveJob): void {\n if (activeJob.timeoutTimer) {\n clearTimeout(activeJob.timeoutTimer);\n activeJob.timeoutTimer = undefined;\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n this.clearTimeoutTimer(activeJob);\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n if (this.activeJobs.size === 0) {\n this.drainWaiter?.();\n }\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n // Keep beating through the shutdown drain: a long `shutdownTimeout` would\n // otherwise let the stale-worker reaper take the worker (and its jobs).\n if (\n !this.workerId ||\n (this.state !== \"running\" && this.state !== \"stopping\")\n ) {\n return;\n }\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: \"healthy\",\n });\n } catch (error) {\n this.debug(\"Worker heartbeat failed\", error);\n }\n }\n\n /**\n * Resolve once no job is active, or after `timeoutMs`. The timer is always\n * cleared, so a clean stop leaves nothing that keeps the process alive.\n */\n private waitForDrain(timeoutMs: number): Promise<void> {\n if (this.activeJobs.size === 0) {\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.drainWaiter = null;\n resolve();\n }, timeoutMs);\n this.drainWaiter = () => {\n clearTimeout(timer);\n this.drainWaiter = null;\n resolve();\n };\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/utils/failure.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\nimport type { CircuitBreakerOptions } from \"./utils/circuit-breaker.js\";\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /**\n * Circuit breaker configuration. Besides the thresholds it takes\n * `isFailure` (which errors count as failures) and `onStateChange`.\n */\n circuitBreaker?: Partial<CircuitBreakerOptions>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerOptions;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: \"https://api.spooled.cloud\",\n wsUrl: \"wss://api.spooled.cloud\",\n grpcAddress: \"grpc.spooled.cloud:443\",\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: \"@spooled/sdk-nodejs/1.4.1\",\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = \"1.4.1\";\n\n/** API version prefix */\nexport const API_VERSION = \"v1\";\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : \"\");\n };\n } else if (typeof options.debug === \"function\") {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerOptions = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n\n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, \"ws\");\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken:\n options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method. An admin key alone is enough: the\n // /admin/* endpoints authenticate with X-Admin-Key only, so an admin-only\n // client (`client.admin.*`) is legitimate.\n if (!config.apiKey && !config.accessToken && !config.adminKey) {\n throw new Error(\n \"SpooledClient requires either apiKey or accessToken for authentication (or adminKey for admin-only use)\",\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (\n config.apiKey &&\n !config.apiKey.startsWith(\"sp_\") &&\n !config.apiKey.startsWith(\"sk_\")\n ) {\n throw new Error(\n \"Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_\",\n );\n }\n\n // Validate baseUrl\n if (\n !config.baseUrl.startsWith(\"http://\") &&\n !config.baseUrl.startsWith(\"https://\")\n ) {\n throw new Error(\"baseUrl must start with http:// or https://\");\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error(\"retry.maxRetries must be non-negative\");\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error(\"retry.baseDelay must be positive\");\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error(\"retry.maxDelay must be >= retry.baseDelay\");\n }\n if (config.retry.factor < 1) {\n throw new Error(\"retry.factor must be >= 1\");\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error(\"circuitBreaker.failureThreshold must be positive\");\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error(\"circuitBreaker.successThreshold must be positive\");\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error(\"circuitBreaker.timeout must be positive\");\n }\n }\n}\n","/**\n * Failure message coercion\n *\n * `POST /jobs/{id}/fail` rejects an empty or over-long `error` with a 400.\n * A worker that reported its failure with such text got nothing recorded: the\n * job stayed `processing` until its lease ran out, then ran again. The text of\n * a failure is therefore always made acceptable before it is sent.\n */\n\n/** Longest error text the API accepts (characters, not bytes). */\nexport const MAX_FAILURE_MESSAGE_CHARS = 2048;\n\n/** Text sent when a handler failed without saying why. */\nexport const DEFAULT_FAILURE_MESSAGE = \"Job failed\";\n\n/**\n * Make `message` acceptable to the fail endpoint: empty becomes\n * {@link DEFAULT_FAILURE_MESSAGE}; text over {@link MAX_FAILURE_MESSAGE_CHARS}\n * is cut to that many characters. The cut is by code point, so an emoji or\n * other astral character is never split into a lone surrogate.\n */\nexport function coerceFailureMessage(message: unknown): string {\n const text =\n typeof message === \"string\" ? message : message == null ? \"\" : `${message}`;\n if (text === \"\") {\n return DEFAULT_FAILURE_MESSAGE;\n }\n // UTF-16 length is an upper bound of the code point count: skip the\n // allocation for the common short message.\n if (text.length <= MAX_FAILURE_MESSAGE_CHARS) {\n return text;\n }\n const codePoints = Array.from(text);\n return codePoints.length <= MAX_FAILURE_MESSAGE_CHARS\n ? text\n : codePoints.slice(0, MAX_FAILURE_MESSAGE_CHARS).join(\"\");\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from \"../client.js\";\nimport { SDK_VERSION } from \"../config.js\";\nimport type { JsonValue } from \"../types/common.js\";\nimport type { ClaimedJob } from \"../types/jobs.js\";\nimport { coerceFailureMessage } from \"../utils/failure.js\";\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from \"./types.js\";\nimport { hostname as osHostname } from \"os\";\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/** Failure text sent when a handler outlives the job's `timeoutSeconds`. */\nconst JOB_TIMEOUT_MESSAGE = \"Job timeout exceeded\";\n\n/** Failure text sent for a handler still running when the shutdown grace ends. */\nconst SHUTDOWN_TIMEOUT_MESSAGE = \"Worker shutdown timeout\";\n\n/** Longest delay `setTimeout` accepts (a larger one fires immediately). */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\n\n/** How a handler run ended. Decided once per execution. */\ntype HandlerOutcome =\n | { kind: \"returned\"; value: JsonValue | void }\n | { kind: \"threw\"; error: unknown }\n | { kind: \"timeout\" };\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n /**\n * Options with every defaultable field filled in. `workerId` stays optional:\n * there is no sensible default for it, and omitting it is what makes the\n * server mint a UUID.\n */\n private readonly options: Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = \"idle\";\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n /** The claim request currently on the wire, so a stop can wait for its jobs. */\n private pollInFlight: Promise<void> | null = null;\n /** Wakes the shutdown drain when the last active job has been acked. */\n private drainWaiter: (() => void) | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> =\n new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: \"nodejs\",\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<Omit<SpooledWorkerOptions, \"workerId\">> &\n Pick<SpooledWorkerOptions, \"workerId\">;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called. start() already\n // reports a failed registration through the \"error\" event; the catch only\n // keeps it from becoming an unhandled rejection that crashes the process.\n setImmediate(() => {\n this.start().catch((error) => {\n this.debug(\"Auto-start failed\", error);\n });\n });\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== \"idle\") {\n throw new Error(\"Cannot set handler after worker has started\");\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== \"idle\") {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error(\"No job handler registered. Call process() first.\");\n }\n\n this.state = \"starting\";\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n workerId: this.options.workerId,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = \"running\";\n this.emit(\"started\", {\n workerId: this.workerId,\n queueName: this.options.queueName,\n });\n this.schedulePoll();\n } catch (error) {\n this.state = \"error\";\n this.emit(\"error\", { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Drains: the worker stops claiming, then lets running handlers finish for\n * up to `shutdownTimeout`. A handler that returns completes its job and one\n * that throws fails it, even though a stop was requested meanwhile; its\n * `signal` is NOT aborted during the grace period. Only handlers still\n * running when the timeout ends are aborted and failed with \"Worker shutdown\n * timeout\". The worker deregisters last, because deregistering returns every\n * job the worker still holds to the queue.\n */\n async stop(): Promise<void> {\n if (this.state !== \"running\") {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n const deadline = Date.now() + this.options.shutdownTimeout;\n this.state = \"stopping\";\n this.debug(\"Stopping worker...\");\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // A claim already on the wire still returns jobs this worker now holds\n // leases for. Let it land first so they join the drain below instead of\n // starting after the worker has deregistered.\n if (this.pollInFlight) {\n await this.pollInFlight;\n }\n\n // Drain: the worker heartbeat and every job heartbeat keep running while\n // handlers finish, so neither the worker nor a lease expires mid-drain.\n if (this.activeJobs.size > 0) {\n this.debug(\n `Waiting for ${this.activeJobs.size} active jobs to complete...`,\n );\n\n await this.waitForDrain(Math.max(0, deadline - Date.now()));\n\n // Whatever is still running outlived the grace period: abort and fail it.\n // A job whose handler returned just before the deadline is already\n // settled (its ack is in flight) and is awaited below, not failed.\n const stragglers = Array.from(this.activeJobs.values()).filter(\n (active) => !active.settled,\n );\n await Promise.all(stragglers.map((active) => this.abandonJob(active)));\n\n // Acks already on the wire must land before the worker deregisters,\n // otherwise deregistering re-queues a job that was just finished.\n await Promise.all(\n Array.from(this.activeJobs.values()).map((active) => active.ack),\n );\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Deregister worker\n const workerId = this.workerId;\n if (workerId) {\n try {\n await this.client.workers.deregister(workerId);\n this.debug(\"Worker deregistered\");\n } catch (error) {\n this.debug(\"Failed to deregister worker\", error);\n }\n }\n\n this.state = \"stopped\";\n this.workerId = null;\n this.emit(\"stopped\", { workerId: workerId ?? \"\", reason: \"graceful\" });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(\n event: E,\n data: WorkerEventData[E],\n ): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug(\"Event handler error\", { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== \"running\") {\n return;\n }\n\n this.pollTimer = setTimeout(() => {\n this.pollTimer = null;\n const run = this.poll();\n this.pollInFlight = run;\n const done = () => {\n if (this.pollInFlight === run) {\n this.pollInFlight = null;\n }\n };\n run.then(done, done);\n }, this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== \"running\" || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug(\"Poll failed\", error);\n this.emit(\"error\", { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit(\"job:claimed\", { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n settled: false,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval =\n this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit(\"job:started\", { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (percent: number, message?: string) => {\n const boundedPercent = Math.max(0, Math.min(100, percent));\n this.debug(`[${job.id}] progress ${boundedPercent}%`, message);\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const outcome = await this.runHandler(activeJob, context);\n\n // The shutdown drain already failed this job while its handler was still\n // running; the lease is gone, so a late result must not be sent.\n if (activeJob.settled) {\n this.debug(`Job ${job.id} was already settled; dropping late result`);\n return;\n }\n activeJob.settled = true;\n\n // The abort signal is only a hint to the handler. Whatever the handler\n // did decides the ack: a result that arrives after a stop was requested\n // is still sent, never silently dropped.\n switch (outcome.kind) {\n case \"returned\":\n activeJob.ack = this.completeJob(job, outcome.value ?? undefined);\n break;\n case \"threw\":\n activeJob.ack = this.failJob(\n job,\n outcome.error instanceof Error\n ? outcome.error.message\n : String(outcome.error),\n );\n break;\n case \"timeout\":\n // Stop the handler if it listens, then fail the job: extending the\n // lease of a hung handler forever would keep the job and its\n // concurrency slot stuck.\n abortController.abort(new Error(JOB_TIMEOUT_MESSAGE));\n this.emit(\"job:timeout\", {\n jobId: job.id,\n queueName: job.queueName,\n });\n activeJob.ack = this.failJob(job, JOB_TIMEOUT_MESSAGE);\n break;\n }\n await activeJob.ack;\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n /**\n * Run the handler, racing it against the job's `timeoutSeconds`.\n * Never rejects: a throw (sync or async) is reported as an outcome.\n */\n private runHandler(\n activeJob: ActiveJob,\n context: JobContext,\n ): Promise<HandlerOutcome> {\n const run = (async (): Promise<HandlerOutcome> => {\n try {\n return { kind: \"returned\", value: await this.handler!(context) };\n } catch (error) {\n return { kind: \"threw\", error };\n }\n })();\n\n const { timeoutSeconds } = activeJob.job;\n if (typeof timeoutSeconds !== \"number\" || !(timeoutSeconds > 0)) {\n return run;\n }\n\n return new Promise<HandlerOutcome>((resolve) => {\n activeJob.timeoutTimer = setTimeout(\n () => resolve({ kind: \"timeout\" }),\n Math.min(timeoutSeconds * 1000, MAX_TIMER_MS),\n );\n run.then((outcome) => {\n this.clearTimeoutTimer(activeJob);\n resolve(outcome);\n });\n });\n }\n\n /** Abort a handler that outlived the shutdown grace period and fail its job. */\n private async abandonJob(activeJob: ActiveJob): Promise<void> {\n this.debug(`Force-failing job ${activeJob.job.id} due to shutdown timeout`);\n // Mark first: the handler may react to the abort, or finish later, and its\n // outcome must not be acked on top of this failure.\n activeJob.settled = true;\n activeJob.abortController.abort(new Error(SHUTDOWN_TIMEOUT_MESSAGE));\n activeJob.ack = this.failJob(activeJob.job, SHUTDOWN_TIMEOUT_MESSAGE);\n await activeJob.ack;\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timers.\n this.cleanupJob(activeJob);\n }\n\n private async completeJob(\n job: ClaimedJob,\n result?: JsonValue,\n ): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:completed\", {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n // The ack was already retried by the HTTP layer; what is left is a job\n // that finished but could not be reported.\n this.debug(`Failed to complete job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n // The API rejects empty or over-long failure text with a 400, which would\n // leave the job processing until its lease expired.\n const message = coerceFailureMessage(errorMessage);\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: message,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit(\"job:failed\", {\n jobId: job.id,\n queueName: job.queueName,\n error: message,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n this.emit(\"error\", { error: error as Error });\n }\n }\n\n private clearTimeoutTimer(activeJob: ActiveJob): void {\n if (activeJob.timeoutTimer) {\n clearTimeout(activeJob.timeoutTimer);\n activeJob.timeoutTimer = undefined;\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n this.clearTimeoutTimer(activeJob);\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n if (this.activeJobs.size === 0) {\n this.drainWaiter?.();\n }\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n // Keep beating through the shutdown drain: a long `shutdownTimeout` would\n // otherwise let the stale-worker reaper take the worker (and its jobs).\n if (\n !this.workerId ||\n (this.state !== \"running\" && this.state !== \"stopping\")\n ) {\n return;\n }\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: \"healthy\",\n });\n } catch (error) {\n this.debug(\"Worker heartbeat failed\", error);\n }\n }\n\n /**\n * Resolve once no job is active, or after `timeoutMs`. The timer is always\n * cleared, so a clean stop leaves nothing that keeps the process alive.\n */\n private waitForDrain(timeoutMs: number): Promise<void> {\n if (this.activeJobs.size === 0) {\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n const timer = setTimeout(() => {\n this.drainWaiter = null;\n resolve();\n }, timeoutMs);\n this.drainWaiter = () => {\n clearTimeout(timer);\n this.drainWaiter = null;\n resolve();\n };\n });\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooled/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Official Node.js/TypeScript SDK for Spooled Cloud job queue service",
|
|
6
6
|
"author": "Spooled Cloud <support@spooled.cloud>",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"types": "./dist/worker/index.d.ts",
|
|
40
40
|
"import": "./dist/worker/index.js",
|
|
41
41
|
"require": "./dist/worker/index.cjs"
|
|
42
|
-
}
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
43
44
|
},
|
|
44
45
|
"files": [
|
|
45
46
|
"dist",
|
|
@@ -61,15 +62,15 @@
|
|
|
61
62
|
"lint:fix": "eslint src tests --fix",
|
|
62
63
|
"format": "prettier --write .",
|
|
63
64
|
"format:check": "prettier --check .",
|
|
64
|
-
"typecheck": "tsc --noEmit",
|
|
65
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.types.json",
|
|
65
66
|
"typecheck:examples": "tsc --noEmit -p tsconfig.examples.json",
|
|
66
67
|
"prepublishOnly": "npm run lint && npm run typecheck && npm run build && npm run test",
|
|
67
68
|
"clean": "rm -rf dist",
|
|
68
69
|
"verify:production": "tsx scripts/verify-production.ts"
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
|
-
"@grpc/grpc-js": "^1.14.
|
|
72
|
-
"@grpc/proto-loader": "^0.8.
|
|
72
|
+
"@grpc/grpc-js": "^1.14.5",
|
|
73
|
+
"@grpc/proto-loader": "^0.8.1",
|
|
73
74
|
"eventsource": "^3.0.7",
|
|
74
75
|
"ws": "^8.20.0"
|
|
75
76
|
},
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"@types/ws": "^8.18.1",
|
|
81
82
|
"@typescript-eslint/eslint-plugin": "^8.59.2",
|
|
82
83
|
"@typescript-eslint/parser": "^8.59.2",
|
|
83
|
-
"@vitest/coverage-v8": "^4.1.
|
|
84
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
84
85
|
"chalk": "^5.6.2",
|
|
85
86
|
"eslint": "^9.39.4",
|
|
86
87
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -91,6 +92,6 @@
|
|
|
91
92
|
"tsup": "^8.5.1",
|
|
92
93
|
"tsx": "^4.21.0",
|
|
93
94
|
"typescript": "^5.9.3",
|
|
94
|
-
"vitest": "^4.1.
|
|
95
|
+
"vitest": "^4.1.11"
|
|
95
96
|
}
|
|
96
97
|
}
|
package/proto/spooled.proto
CHANGED
|
@@ -143,7 +143,10 @@ message FailRequest {
|
|
|
143
143
|
string job_id = 1;
|
|
144
144
|
string worker_id = 2;
|
|
145
145
|
string error = 3;
|
|
146
|
-
|
|
146
|
+
// Omitted: retry according to the job's max_retries (same as REST).
|
|
147
|
+
// false: move to the dead-letter queue now. `optional` gives presence, so
|
|
148
|
+
// clients that never set the field no longer dead-letter on first failure.
|
|
149
|
+
optional bool retry = 4;
|
|
147
150
|
// Lease fencing token from the dequeued Job. When set, the failure
|
|
148
151
|
// succeeds only if it matches the job's current lease.
|
|
149
152
|
string lease_id = 5;
|
|
@@ -238,10 +241,20 @@ message ErrorResponse {
|
|
|
238
241
|
message RegisterWorkerRequest {
|
|
239
242
|
string queue_name = 1;
|
|
240
243
|
string hostname = 2;
|
|
244
|
+
// At most 50 characters.
|
|
241
245
|
string worker_type = 3;
|
|
246
|
+
// 1-100; larger values are lowered to 100. 0 means the default (5).
|
|
242
247
|
int32 max_concurrency = 4;
|
|
248
|
+
// At most 50 characters.
|
|
243
249
|
string version = 5;
|
|
250
|
+
// At most 64 KB as JSON.
|
|
244
251
|
map<string, string> metadata = 6;
|
|
252
|
+
// Stable worker id (optional). Send the same id after a restart and the
|
|
253
|
+
// server updates that worker instead of adding one, so a restarting worker
|
|
254
|
+
// does not count twice against the plan's worker limit. Omitted or empty:
|
|
255
|
+
// the server assigns a new id. 1-128 characters: letters, digits, `-`,
|
|
256
|
+
// `_`, `.`. An id another organization already uses returns ALREADY_EXISTS.
|
|
257
|
+
optional string worker_id = 7;
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
// Worker Registration Response
|
|
@@ -255,7 +268,12 @@ message RegisterWorkerResponse {
|
|
|
255
268
|
message HeartbeatRequest {
|
|
256
269
|
string worker_id = 1;
|
|
257
270
|
int32 current_jobs = 2;
|
|
271
|
+
// healthy, degraded, draining or offline. Empty keeps the current status,
|
|
272
|
+
// except that a worker marked offline comes back as healthy. Draining
|
|
273
|
+
// stays until the worker registers again.
|
|
258
274
|
string status = 3;
|
|
275
|
+
// Replaces the stored metadata (at most 64 KB as JSON). Empty keeps what
|
|
276
|
+
// is stored.
|
|
259
277
|
map<string, string> metadata = 4;
|
|
260
278
|
}
|
|
261
279
|
|