alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
578 lines • 20.3 kB
TypeScript
import { Alepha, KIND, PipelinePrimitive, PipelinePrimitiveOptions, Primitive, Service, Static, TSchema } from "alepha";
import { DateTimeProvider } from "alepha/datetime";
//#region ../../src/queue/core/providers/QueueProvider.d.ts
/**
* Minimalist Queue interface.
*
* Will be probably enhanced in the future to support more advanced features. But for now, it's enough!
*/
declare abstract class QueueProvider {
/**
* Push a message to the queue.
*
* @param queue Name of the queue to push the message to.
* @param message String message to be pushed to the queue. Buffer messages are not supported for now.
*/
abstract push(queue: string, message: string): Promise<void>;
/**
* Pop a message from the queue.
*
* @param queue Name of the queue to pop the message from.
*
* @returns The message popped or `undefined` if the queue is empty.
*/
abstract pop(queue: string): Promise<string | undefined>;
}
//#endregion
//#region ../../src/queue/core/providers/MemoryQueueProvider.d.ts
declare class MemoryQueueProvider implements QueueProvider {
protected readonly log: import("alepha/logger").Logger;
protected queueList: Record<string, string[]>;
push(queue: string, ...messages: string[]): Promise<void>;
pop(queue: string): Promise<string | undefined>;
}
//#endregion
//#region ../../src/queue/core/providers/WorkerProvider.d.ts
/**
* Queue worker configuration atom.
*/
declare const queueWorkerOptions: import("alepha").Atom<import("zod").ZodObject<{
interval: import("zod").ZodDefault<import("zod").ZodInt>;
maxInterval: import("zod").ZodDefault<import("zod").ZodInt>;
concurrency: import("zod").ZodDefault<import("zod").ZodInt>;
}, import("zod/v4/core").$strip>, "alepha.queue.worker.options">;
type QueueWorkerOptions = Static<typeof queueWorkerOptions.schema>;
declare module "alepha" {
interface State {
[queueWorkerOptions.key]: QueueWorkerOptions;
}
}
declare class WorkerProvider {
protected readonly log: import("alepha/logger").Logger;
protected readonly options: Readonly<{
interval: number;
maxInterval: number;
concurrency: number;
}>;
protected readonly alepha: Alepha;
protected readonly queueProvider: QueueProvider;
protected readonly dateTimeProvider: DateTimeProvider;
protected workerPromises: Array<Promise<void>>;
protected workersRunning: number;
protected abortController: AbortController | undefined;
protected workerIntervals: Record<number, number>;
protected consumers: Array<Consumer>;
protected nextConsumerIndex: number;
get isRunning(): boolean;
protected readonly start: import("alepha").HookPrimitive<"start">;
/**
* Start the workers.
* This method will create an endless loop that will check for new messages!
*/
protected startWorkers(): void;
protected readonly stop: import("alepha").HookPrimitive<"stop">;
/**
* Wait for the next message, where `n` is the worker number.
*
* This method will wait for a certain amount of time, increasing the wait time again if no message is found.
*/
protected waitForNextMessage(n: number): Promise<void>;
/**
* Get the next message.
*/
protected getNextMessage(): Promise<undefined | NextMessage>;
/**
* Process a message from a queue.
*/
protected processMessage(response: {
message: any;
consumer: Consumer;
}): Promise<void>;
/**
* Stop the workers.
*
* This method will stop the workers and wait for them to finish processing.
*/
protected stopWorkers(): Promise<void>;
/**
* Force the workers to get back to work.
*/
wakeUp(): void;
}
interface Consumer<T extends TSchema = TSchema> {
queue: QueuePrimitive<T>;
handler: (message: QueueMessage<T>) => Promise<void>;
}
interface NextMessage {
consumer: Consumer;
message: string;
}
//#endregion
//#region ../../src/queue/core/primitives/$queue.d.ts
/**
* Creates a queue primitive for asynchronous message processing with background workers.
*
* `$queue` is the **raw transport layer**: it fans messages out to background
* workers over the configured backend with type-safe payloads. Delivery is
* **at-most-once** — a message is popped from the backend before the handler
* runs, so a handler error or a process crash loses it. There is no retry,
* no dead-letter queue, and no delivery guarantee at this layer.
*
* **For work that must not be lost, use `$job` (alepha/api/jobs) instead.**
* It layers a durable, DB-backed outbox over this transport: at-least-once
* delivery, retries, idempotency keys, priorities, crash recovery via a
* reconciliation sweep, and failure records.
*
* **What $queue gives you**
* - Type-safe payloads with schema validation at push and receive
* - Background workers with graceful shutdown and lifecycle management
* - Pluggable backends: memory (dev/test), Redis, Cloudflare Queues
* - Cheap fire-and-forget fan-out where occasional loss is acceptable
* (cache invalidation, presence pings, metrics, live notifications)
*
* @example Loss-tolerant event fan-out
* ```typescript
* const activityQueue = $queue({
* name: "activity-events",
* schema: z.object({
* userId: z.text(),
* event: z.text(),
* at: z.number()
* }),
* handler: async (message) => {
* await metrics.track(message.payload);
* }
* });
*
* // Push messages for background processing
* await activityQueue.push({
* userId: "u1",
* event: "page-view",
* at: 1700000000000
* });
* ```
*
* @example Batch processing with Redis
* ```typescript
* const imageQueue = $queue({
* name: "image-processing",
* provider: RedisQueueProvider,
* schema: z.object({
* imageId: z.text(),
* operations: z.array(z.enum(["resize", "compress", "thumbnail"]))
* }),
* handler: async (message) => {
* for (const op of message.payload.operations) {
* await processImage(message.payload.imageId, op);
* }
* }
* });
*
* // Batch processing multiple images
* await imageQueue.push(
* { imageId: "img1", operations: ["resize", "thumbnail"] },
* { imageId: "img2", operations: ["compress"] },
* { imageId: "img3", operations: ["resize", "compress", "thumbnail"] }
* );
* ```
*
* @example Development with memory provider
* ```typescript
* const taskQueue = $queue({
* name: "dev-tasks",
* provider: "memory",
* schema: z.object({
* taskType: z.enum(["cleanup", "backup", "report"]),
* data: z.record(z.text(), z.any())
* }),
* handler: async (message) => {
* switch (message.payload.taskType) {
* case "cleanup":
* await performCleanup(message.payload.data);
* break;
* case "backup":
* await createBackup(message.payload.data);
* break;
* case "report":
* await generateReport(message.payload.data);
* break;
* }
* }
* });
* ```
*/
declare const $queue: {
<T extends TSchema>(options: QueuePrimitiveOptions<T>): QueuePrimitive<T>;
[KIND]: typeof QueuePrimitive;
};
interface QueuePrimitiveOptions<T extends TSchema> {
/**
* Unique name for the queue.
*
* This name is used for:
* - Queue identification across the system
* - Storage backend key generation
* - Logging and monitoring
* - Worker assignment and routing
*
* If not provided, defaults to the property key where the queue is declared.
*
* @example "email-notifications"
* @example "image-processing"
* @example "order-fulfillment"
*/
name?: string;
/**
* Human-readable description of the queue's purpose.
*
* Used for:
* - Documentation generation
* - Monitoring dashboards
* - Development team communication
* - Queue management interfaces
*
* @example "Process user registration emails and welcome sequences"
* @example "Handle image uploads, resizing, and thumbnail generation"
* @example "Manage order processing, payment, and shipping workflows"
*/
description?: string;
/**
* Queue storage provider configuration.
*
* Options:
* - **"memory"**: In-memory queue (default for development, lost on restart)
* - **Service<QueueProvider>**: Custom provider class (e.g., RedisQueueProvider)
* - **undefined**: Uses the default queue provider from dependency injection
*
* **Provider Selection Guidelines**:
* - Development: Use "memory" for fast, simple testing
* - Production: Use Redis so multiple instances share one queue (note:
* a popped message is gone — the at-most-once caveat above applies)
* - High-throughput: Use specialized providers with connection pooling
* - Distributed systems: Use Redis or message brokers for scalability
*
* @default Uses injected QueueProvider
* @example "memory"
* @example RedisQueueProvider
* @example DatabaseQueueProvider
*/
provider?: "memory" | Service<QueueProvider>;
/**
* TypeBox schema defining the structure of messages in this queue.
*
* This schema:
* - Validates all messages pushed to the queue
* - Provides full TypeScript type inference
* - Ensures type safety between producers and consumers
* - Enables automatic serialization/deserialization
*
* **Schema Design Best Practices**:
* - Keep schemas simple and focused on the specific task
* - Use optional fields for data that might not always be available
* - Include version fields for schema evolution
* - Use union types for different message types in the same queue
*
* @example
* ```ts
* z.object({
* userId: z.text(),
* action: z.enum(["create", "update"]),
* data: z.record(z.text(), z.any()),
* timestamp: z.number().optional()
* })
* ```
*/
schema: T;
/**
* Message handler function that processes queue messages.
*
* This function:
* - Runs in background worker threads for non-blocking processing
* - Receives type-safe message payloads based on the schema
* - Has access to the full Alepha dependency injection container
* - **Loses the message if it throws** — errors are logged, not retried.
* Use `$job` when failed work must be retried or recorded.
*
* **Handler Best Practices**:
* - Keep handlers focused on a single responsibility
* - Use proper error handling and logging
* - Make operations idempotent when possible
* - Validate critical business logic within handlers
* - Consider using transactions for data consistency
*
* @param message - The queue message with validated payload
* @returns Promise that resolves when processing is complete
*
* @example
* ```ts
* handler: async (message) => {
* const { userId, email, template } = message.payload;
*
* try {
* await this.emailService.send({
* to: email,
* template,
* data: { userId }
* });
*
* await this.userService.markEmailSent(userId, template);
* } catch (error) {
* // The message is NOT redelivered after a throw — record the
* // failure yourself, or use $job for retryable work.
* this.logger.error(`Failed to send email to ${email}`, error);
* throw error;
* }
* }
* ```
*/
handler?: (message: QueueMessage<T>) => Promise<void>;
}
declare class QueuePrimitive<T extends TSchema> extends Primitive<QueuePrimitiveOptions<T>> {
protected readonly log: import("alepha/logger").Logger;
protected readonly workerProvider: WorkerProvider;
readonly provider: MemoryQueueProvider | QueueProvider;
push(...payloads: Array<Static<T>>): Promise<void>;
get name(): string;
protected $provider(): MemoryQueueProvider | QueueProvider;
}
interface QueueMessageSchema {
payload: TSchema;
}
interface QueueMessage<T extends TSchema> {
payload: Static<T>;
}
//#endregion
//#region ../../src/queue/core/primitives/$consumer.d.ts
/**
* Creates a consumer primitive to process messages from a specific queue.
*
* Provides a dedicated message consumer that connects to a queue and processes messages
* with custom handler logic, enabling scalable architectures where multiple consumers
* can process messages from the same queue.
*
* **Key Features**
* - Seamless integration with any $queue primitive
* - Full type safety inherited from queue schema
* - Automatic worker management for background processing
* - Built-in error handling and retry mechanisms
* - Support for multiple consumers per queue for horizontal scaling
*
* **Common Use Cases**
* - Email sending and notification services
* - Image and media processing workers
* - Data synchronization and background jobs
*
* @example
* ```ts
* class EmailService {
* emailQueue = $queue({
* name: "emails",
* schema: z.object({
* to: z.text(),
* subject: z.text(),
* body: z.text()
* })
* });
*
* emailConsumer = $consumer({
* queue: this.emailQueue,
* handler: async (message) => {
* const { to, subject, body } = message.payload;
* await this.sendEmail(to, subject, body);
* }
* });
*
* async sendWelcomeEmail(userEmail: string) {
* await this.emailQueue.push({
* to: userEmail,
* subject: "Welcome!",
* body: "Thanks for joining."
* });
* }
* }
* ```
*/
declare const $consumer: {
<T extends TSchema>(options: ConsumerPrimitiveOptions<T>): ConsumerPrimitive<T>;
[KIND]: typeof ConsumerPrimitive;
};
interface ConsumerPrimitiveOptions<T extends TSchema> extends PipelinePrimitiveOptions {
/**
* The queue primitive that this consumer will process messages from.
*
* This establishes the connection between the consumer and its source queue:
* - The consumer inherits the queue's message schema for type safety
* - Messages pushed to the queue will be automatically routed to this consumer
* - Multiple consumers can be attached to the same queue for parallel processing
* - The consumer will use the queue's provider and configuration settings
*
* **Queue Integration Benefits**:
* - Type safety: Consumer handler gets fully typed message payloads
* - Schema validation: Messages are validated before reaching the consumer
* - Error handling: Failed messages can be retried or moved to dead letter queues
* - Monitoring: Queue metrics include consumer processing statistics
*
* @example
* ```ts
* // First, define a queue
* emailQueue = $queue({
* name: "emails",
* schema: z.object({ to: z.text(), subject: z.text() })
* });
*
* // Then, create a consumer for that queue
* emailConsumer = $consumer({
* queue: this.emailQueue, // Reference the queue primitive
* handler: async (message) => { } // process email
* });
* ```
*/
queue: QueuePrimitive<T>;
/**
* Message handler function that processes individual messages from the queue.
*
* This function:
* - Receives fully typed and validated message payloads from the connected queue
* - Runs in the background worker system for non-blocking operation
* - Should implement the core business logic for processing this message type
* - Can throw errors to trigger the queue's retry mechanisms
* - Has access to the full Alepha dependency injection container
* - Should be idempotent to handle potential duplicate deliveries
*
* **Handler Design Guidelines**:
* - Keep handlers focused on a single responsibility
* - Use proper error handling and meaningful error messages
* - Log important processing steps for debugging and monitoring
* - Consider transaction boundaries for data consistency
* - Make operations idempotent when possible
* - Validate business rules within the handler logic
*
* **Error Handling Strategy**:
* - Throw errors for temporary failures that should be retried
* - Log and handle permanent failures gracefully
* - Use specific error types to control retry behavior
* - Consider implementing circuit breakers for external service calls
*
* @param message - The queue message containing the validated payload
* @param message.payload - The typed message data based on the queue's schema
* @returns Promise that resolves when processing is complete
*
* @example
* ```ts
* handler: async (message) => {
* const { userId, action, data } = message.payload;
*
* try {
* // Log processing start
* this.logger.info(`Processing ${action} for user ${userId}`);
*
* // Validate business rules
* if (!await this.userService.exists(userId)) {
* throw new Error(`User ${userId} not found`);
* }
*
* // Perform the main processing logic
* switch (action) {
* case "create":
* await this.processCreation(userId, data);
* break;
* case "update":
* await this.processUpdate(userId, data);
* break;
* default:
* throw new Error(`Unknown action: ${action}`);
* }
*
* // Log successful completion
* this.logger.info(`Successfully processed ${action} for user ${userId}`);
*
* } catch (error) {
* // Log error with context
* this.logger.error(`Failed to process ${action} for user ${userId}`, {
* error: error.message,
* userId,
* action,
* data
* });
*
* // Re-throw to trigger queue retry mechanism
* throw error;
* }
* }
* ```
*/
handler: (message: {
payload: Static<T>;
}) => Promise<void>;
}
declare class ConsumerPrimitive<T extends TSchema> extends PipelinePrimitive<ConsumerPrimitiveOptions<T>> {}
//#endregion
//#region ../../src/queue/core/providers/CloudflareQueueProvider.d.ts
/**
* Cloudflare Queue interface matching the CF Workers Queue API.
*/
interface CloudflareQueue {
send(message: unknown): Promise<void>;
sendBatch(messages: Array<{
body: unknown;
}>): Promise<void>;
}
/**
* Default queue binding name used in wrangler configuration.
*/
declare const QUEUE_DEFAULT_BINDING = "JOBS_QUEUE";
/**
* How many times Cloudflare redelivers a message before routing it to the
* consumer's dead-letter queue. Matches Cloudflare's own default.
*/
declare const QUEUE_DEFAULT_MAX_RETRIES = 3;
/**
* Cloudflare Queue provider.
*
* Uses a Queue binding for message dispatch. Messages are wrapped with the
* logical queue name so the consumer can route them to the correct handler.
*
* **Required Cloudflare binding:**
* - `JOBS_QUEUE` - A Queue binding in wrangler configuration
*
* @example
* ```toml
* # wrangler.toml - automatically generated by alepha build
* [[queues.producers]]
* binding = "JOBS_QUEUE"
* queue = "my-app-queue"
* ```
*/
declare class CloudflareQueueProvider extends QueueProvider {
protected readonly alepha: Alepha;
protected readonly log: import("alepha/logger").Logger;
protected queue?: CloudflareQueue;
protected readonly onStart: import("alepha").HookPrimitive<"start">;
push(queue: string, message: string): Promise<void>;
/**
* Not used on Cloudflare — queue consumption is push-based via the `queue` handler.
*/
pop(_queue: string): Promise<string | undefined>;
protected getQueue(): CloudflareQueue;
}
//#endregion
//#region ../../src/queue/core/index.d.ts
/**
* Asynchronous message processing with automatic worker management.
*
* **Features:**
* - Background job queues with type-safe payloads
* - Queue consumer handlers
* - Automatic worker threads for non-blocking processing
* - Retry mechanisms with exponential backoff
* - Dead letter queues for failed messages
* - Batch processing support
* - Configurable concurrency and worker pools
* - Providers: Memory (dev), Redis (production)
*
* @module alepha.queue
*/
declare const AlephaQueue: import("alepha").Service<import("alepha").Module>;
//#endregion
export { $consumer, $queue, AlephaQueue, CloudflareQueue, CloudflareQueueProvider, Consumer, ConsumerPrimitive, ConsumerPrimitiveOptions, MemoryQueueProvider, NextMessage, QUEUE_DEFAULT_BINDING, QUEUE_DEFAULT_MAX_RETRIES, QueueMessage, QueueMessageSchema, QueuePrimitive, QueuePrimitiveOptions, QueueProvider, QueueWorkerOptions, WorkerProvider, queueWorkerOptions };
//# sourceMappingURL=index.d.ts.map