@delta-base/toolkit
Version:
Application-level event sourcing toolkit for delta-base
1,328 lines (1,321 loc) • 50.6 kB
TypeScript
/**
* Unified core types for the DeltaBase event sourcing ecosystem
*
* This is the single source of truth for all event sourcing types across
* the entire DeltaBase platform. All packages import from here.
*/
/**
* Default record type for structured data
*/
type DefaultRecord = Record<string, unknown>;
/**
* Stream position within a specific stream (0-based)
*/
type StreamPosition = number;
/**
* Global position across all streams in the event store
*/
type GlobalPosition = number;
/**
* Stream identifier type for type safety
*/
type StreamId = string;
/**
* Platform-level metadata for infrastructure concerns
*/
interface PlatformEventMetadata {
/** Schema version for backward compatibility and evolution */
schemaVersion?: string;
/** Transaction ID for tracing and correlation */
transactionId?: string;
/** Message ID for deduplication and tracking */
messageId?: string;
/** Additional custom metadata */
[key: string]: unknown;
}
/**
* Event as sent to the event store (input format)
*
* This is the canonical Event type used throughout the DeltaBase ecosystem.
* Optimized for type safety, developer experience, and performance.
*
* Enhanced to support both application-level and platform-level metadata.
*/
type Event<EventType extends string = string, EventData extends DefaultRecord = DefaultRecord, EventMetadata extends DefaultRecord | undefined = undefined> = Readonly<EventMetadata extends undefined ? {
type: EventType;
data: EventData;
/** Platform-level metadata for infrastructure concerns */
metadata?: PlatformEventMetadata;
} : {
type: EventType;
data: EventData;
/** Application-level metadata merged with platform metadata */
metadata: EventMetadata & PlatformEventMetadata;
}>;
/**
* Event as read from the event store (output format)
*
* Includes system fields for tracking and tracing.
* Type is distributive for perfect union type handling.
*/
type ReadEvent<EventType extends Event = Event> = EventType extends Event ? {
type: EventTypeOf<EventType>;
data: EventDataOf<EventType>;
streamId: StreamId;
streamPosition: StreamPosition;
globalPosition: GlobalPosition;
eventId: string;
schemaVersion: string;
transactionId: string;
createdAt: string;
metadata?: EventMetadataOf<EventType>;
} : never;
type EventTypeOf<T extends Event> = T['type'];
type EventDataOf<T extends Event> = T['data'];
type EventMetadataOf<T extends Event> = T extends {
metadata: infer M;
} ? M : undefined;
/**
* Any event type for generic contexts
*/
type AnyEvent = Event<string, DefaultRecord, DefaultRecord | undefined>;
/**
* Type-safe event factory function
*/
declare const event: <EventType extends Event>(...args: EventMetadataOf<EventType> extends undefined ? [type: EventTypeOf<EventType>, data: EventDataOf<EventType>] : [type: EventTypeOf<EventType>, data: EventDataOf<EventType>, metadata: EventMetadataOf<EventType>]) => EventType;
/**
* Command type for representing intent to perform actions
*
* @template CommandType - The command type identifier
* @template CommandData - The command payload data type
* @template CommandMetadata - Optional metadata type
*/
type Command<CommandType extends string = string, CommandData extends DefaultRecord = DefaultRecord, CommandMetadata extends DefaultRecord | undefined = undefined> = Readonly<CommandMetadata extends undefined ? {
type: CommandType;
data: CommandData;
} : {
type: CommandType;
data: CommandData;
metadata: CommandMetadata;
}> & {
readonly kind?: 'Command';
};
type CommandTypeOf<T extends Command> = T['type'];
type CommandDataOf<T extends Command> = T['data'];
type CommandMetadataOf<T extends Command> = T extends {
metadata: infer M;
} ? M : undefined;
/**
* Helper for creating command instances
*/
declare const command: <CommandType extends Command>(...args: CommandMetadataOf<CommandType> extends undefined ? [type: CommandTypeOf<CommandType>, data: CommandDataOf<CommandType>] : [type: CommandTypeOf<CommandType>, data: CommandDataOf<CommandType>, metadata: CommandMetadataOf<CommandType>]) => CommandType;
/**
* Generic command type for use in generic contexts
*/
type AnyCommand = Command<string, DefaultRecord, DefaultRecord | undefined>;
/**
* Result of command processing
*/
type CommandResult<T = unknown> = {
/** Whether the command was successful */
readonly success: boolean;
/** Result data if successful */
readonly data?: T;
/** Error information if unsuccessful */
readonly error?: {
readonly message: string;
readonly code?: string;
readonly details?: DefaultRecord;
};
/** Events that were generated as a result of this command */
readonly events: ReadEvent[];
};
/**
* Generic message type for workflow systems
*/
type Message<MessageType extends string = string, MessageData extends DefaultRecord = DefaultRecord, MessageMetadata extends DefaultRecord | undefined = undefined> = Readonly<MessageMetadata extends undefined ? {
type: MessageType;
data: MessageData;
messageId: string;
createdAt: string;
correlationId?: string;
causationId?: string;
} : {
type: MessageType;
data: MessageData;
metadata: MessageMetadata;
messageId: string;
createdAt: string;
correlationId?: string;
causationId?: string;
}> & {
readonly kind?: 'Message';
};
type MessageTypeOf<T extends Message> = T['type'];
type MessageDataOf<T extends Message> = T['data'];
type MessageMetadataOf<T extends Message> = T extends {
metadata: infer M;
} ? M : undefined;
/**
* Expected stream version for optimistic concurrency control
*
* Unified version that combines the best patterns from all packages
*/
type ExpectedStreamVersion<StreamVersion = StreamPosition> = StreamVersion | 'no_stream' | 'stream_exists' | 'any';
declare const STREAM_EXISTS: "stream_exists";
declare const STREAM_DOES_NOT_EXIST: "no_stream";
declare const NO_CONCURRENCY_CHECK: "any";
/**
* Utility function for version matching logic
*/
declare const matchesExpectedVersion: <StreamVersion = StreamPosition>(current: StreamVersion | undefined, expected: ExpectedStreamVersion<StreamVersion>, defaultVersion: StreamVersion) => boolean;
/**
* Options for reading events from a stream
*/
type ReadStreamOptions<StreamVersion = StreamPosition> = ({
from: StreamVersion;
} | {
to: StreamVersion;
} | {
from: StreamVersion;
maxCount?: number;
} | {
expectedStreamVersion: ExpectedStreamVersion<StreamVersion>;
}) & {
expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;
};
/**
* Result of reading events from a stream
*/
type ReadStreamResult<EventType extends Event = AnyEvent, StreamVersion = StreamPosition> = {
currentStreamVersion: StreamVersion;
events: ReadEvent<EventType>[];
streamExists: boolean;
};
/**
* Options for appending events to a stream
*/
type AppendToStreamOptions<StreamVersion = StreamPosition> = {
expectedStreamVersion?: ExpectedStreamVersion<StreamVersion>;
};
/**
* Result of appending events to a stream
*/
type AppendToStreamResult<StreamVersion = StreamPosition> = {
nextExpectedStreamVersion: StreamVersion;
createdNewStream: boolean;
};
/**
* Options for aggregating events into state
*/
type AggregateStreamOptions<State, EventType extends Event, StreamVersion = StreamPosition> = {
evolve: (currentState: State, event: ReadEvent<EventType>) => State;
initialState: () => State;
read?: ReadStreamOptions<StreamVersion>;
};
/**
* Result of aggregating a stream
*/
type AggregateStreamResult<State, StreamVersion = StreamPosition> = {
currentStreamVersion: StreamVersion;
state: State;
streamExists: boolean;
};
/**
* Query parameters for filtering events across streams
*/
type QueryEventsOptions = {
/** Filter by specific stream ID */
streamId?: string;
/** Filter by event type(s) */
type?: string | string[];
/** Filter by specific event ID */
eventId?: string;
/** Filter by transaction ID */
transactionId?: string;
/** Filter by minimum global position */
fromPosition?: number;
/** Filter by maximum global position */
toPosition?: number;
/** Filter by minimum creation date (ISO string) */
fromDate?: string;
/** Filter by maximum creation date (ISO string) */
toDate?: string;
/** Filter by event phase */
phase?: number;
/** Whether to include archived events */
includeArchived?: boolean;
/** Maximum number of events to return */
limit?: number;
/** Number of events to skip */
offset?: number;
/** Field to sort results by */
sortBy?: 'globalPosition' | 'createdAt' | 'streamPosition';
/** Direction to sort results */
sortDirection?: 'asc' | 'desc';
/** Whether to include total count in results */
includeCount?: boolean;
};
/**
* Result of a query events operation
*/
type QueryEventsResult<EventType extends Event = Event> = {
/** Array of matching events */
events: ReadEvent<EventType>[];
/** Pagination information */
pagination: {
/** Number of events returned */
limit: number;
/** Number of events skipped */
offset: number;
/** Total number of matching events (if includeCount was true) */
total?: number;
/** Whether there are more results available */
hasMore: boolean;
};
};
/**
* Unified EventStore interface combining the best patterns from all packages
*
* This is the canonical interface for all event store implementations
*/
interface EventStore<StreamVersion = StreamPosition> {
/**
* Aggregate events from a stream to rebuild state
*/
aggregateStream<State, EventType extends Event>(streamId: StreamId, options: AggregateStreamOptions<State, EventType, StreamVersion>): Promise<AggregateStreamResult<State, StreamVersion>>;
/**
* Read events from a stream with filtering and pagination
*/
readStream<EventType extends Event>(streamId: StreamId, options?: ReadStreamOptions<StreamVersion>): Promise<ReadStreamResult<EventType, StreamVersion>>;
/**
* Append events to a stream with optimistic concurrency control
*/
appendToStream<EventType extends Event>(streamId: StreamId, events: EventType[], options?: AppendToStreamOptions<StreamVersion>): Promise<AppendToStreamResult<StreamVersion>>;
/**
* Query events across streams with flexible filtering options
*/
queryEvents<EventType extends Event>(options?: QueryEventsOptions): Promise<QueryEventsResult<EventType>>;
}
/**
* Stream metadata information
*/
type StreamMetadata<StreamVersion = StreamPosition> = {
/** The stream identifier */
readonly streamId: StreamId;
/** Current version of the stream */
readonly version: StreamVersion;
/** When the stream was created */
readonly createdAt: string;
/** When the stream was last updated */
readonly updatedAt: string;
/** Custom metadata for the stream */
readonly metadata: DefaultRecord;
};
/** biome-ignore-all lint/style/noNonNullAssertion: since we are using in-memory event store for testing */
/**
* In-memory implementation of EventStore for testing and development
*
* This implementation provides a complete event store that runs entirely in memory,
* making it perfect for unit tests, integration tests, and development scenarios
* where you don't need persistence.
*/
declare class InMemoryEventStore implements EventStore {
private streams;
private streamMetadata;
private globalPositionCounter;
/**
* Append events to a stream
*
* @param streamId - The stream identifier
* @param events - Events to append
* @param options - Append options including expected version
* @returns Promise resolving to the append result
*/
appendToStream<EventType extends Event = Event>(streamId: StreamId, events: EventType[], options?: AppendToStreamOptions): Promise<AppendToStreamResult>;
/**
* Helper method to assert expected version matches current version
* @private
*/
private assertExpectedVersionMatchesCurrent;
/**
* Get all stream IDs (utility method for testing)
*
* @returns Array of all stream identifiers
* @public
*/
getAllStreamIds(): StreamId[];
/**
* Get total event count across all streams (utility method for testing)
*
* @returns Total number of events stored
* @public
*/
getTotalEventCount(): number;
/**
* Clear all data (utility method for testing)
* @public
*/
clear(): void;
/**
* Get all events across all streams (utility method for testing)
*
* @returns Array of all events ordered by global position
* @public
*/
getAllEvents(): ReadEvent[];
/**
* Check if a stream exists (utility method for testing)
*
* @param streamId - The stream identifier
* @returns Promise resolving to boolean indicating existence
* @public
*/
streamExists(streamId: StreamId): Promise<boolean>;
/**
* Aggregate events from a stream to rebuild state
*
* @param streamId - The stream identifier
* @param options - Aggregation options including initial state and evolution function
* @returns Promise resolving to the aggregated state and stream metadata
*/
aggregateStream<State, EventType extends Event>(streamId: StreamId, options: AggregateStreamOptions<State, EventType>): Promise<AggregateStreamResult<State>>;
/**
* Read events from a stream with options
*
* @param streamId - The stream identifier
* @param options - Read options for filtering and pagination
* @returns Promise resolving to read result with events and metadata
*/
readStream<EventType extends Event>(streamId: StreamId, options?: ReadStreamOptions): Promise<ReadStreamResult<EventType>>;
/**
* Query events across all streams with flexible filtering options
*
* @param options - Query parameters for filtering events
* @returns Promise resolving to the query result with events and pagination info
*/
queryEvents<EventType extends Event>(options?: QueryEventsOptions): Promise<QueryEventsResult<EventType>>;
}
/**
* Typed error classes for DeltaBase toolkit
*
* This module provides strongly-typed error classes that correspond to specific
* error scenarios, enabling better error handling and type safety across all
* DeltaBase packages.
*/
/**
* Base error class for all DeltaBase errors
*/
declare abstract class DeltaBaseError extends Error {
/** HTTP status code associated with this error */
readonly status: number;
/** Original error response body from the API */
readonly body: unknown;
/** Error type identifier */
readonly errorType: string;
/** Additional error details */
readonly details?: Record<string, unknown>;
constructor(message: string, status: number, body: unknown, errorType: string, details?: Record<string, unknown>);
}
/**
* Error thrown when a stream version conflict occurs during append operations
*/
declare class VersionConflictError extends DeltaBaseError {
/** The current version of the stream */
readonly currentVersion: number;
/** The expected version that was provided */
readonly expectedVersion: number;
constructor(message: string, body: unknown, currentVersion: number, expectedVersion: number);
}
/**
* Error thrown when request validation fails
*/
declare class ValidationError extends DeltaBaseError {
/** Array of validation error details */
readonly validationErrors?: Array<{
code: string;
message: string;
path: Array<string | number>;
}>;
constructor(message: string, body?: unknown, validationErrors?: Array<{
code: string;
message: string;
path: Array<string | number>;
}>);
}
/**
* Error thrown when an event store is not found
*/
declare class EventStoreNotFoundError extends DeltaBaseError {
/** The event store ID that was not found */
readonly eventStoreId: string;
constructor(message: string, body: unknown, eventStoreId: string);
}
/**
* Error thrown when a subscription is not found
*/
declare class SubscriptionNotFoundError extends DeltaBaseError {
/** The subscription ID that was not found */
readonly subscriptionId: string;
/** The event store ID where the subscription was expected */
readonly eventStoreId: string;
constructor(message: string, body: unknown, subscriptionId: string, eventStoreId: string);
}
/**
* Error thrown when authentication fails
*/
declare class AuthenticationError extends DeltaBaseError {
constructor(message: string, body: unknown);
}
/**
* Error thrown when authorization fails
*/
declare class AuthorizationError extends DeltaBaseError {
constructor(message: string, body: unknown);
}
/**
* Error thrown when rate limits are exceeded
*/
declare class RateLimitError extends DeltaBaseError {
/** When the rate limit will reset (if provided) */
readonly retryAfter?: number;
constructor(message: string, body: unknown, retryAfter?: number);
}
/**
* Error thrown when an event store already exists during creation
*/
declare class EventStoreAlreadyExistsError extends DeltaBaseError {
/** The name of the event store that already exists */
readonly name: string;
constructor(message: string, body: unknown, name: string);
}
/**
* Error thrown when subscription creation fails due to invalid configuration
*/
declare class InvalidSubscriptionConfigError extends DeltaBaseError {
/** The specific configuration error details */
readonly configError: string;
constructor(message: string, body: unknown, configError: string);
}
/**
* Error thrown when operations timeout
*/
declare class TimeoutError extends DeltaBaseError {
/** The timeout duration in milliseconds */
readonly timeoutMs: number;
/** The operation that timed out */
readonly operation: string;
constructor(message: string, body: unknown, timeoutMs: number, operation: string);
}
/**
* Error thrown when storage operations fail
*/
declare class StorageError extends DeltaBaseError {
/** The type of storage that failed */
readonly storageType: 'r2' | 'sqlite' | 'archive';
/** The operation that failed */
readonly operation: 'read' | 'write' | 'delete';
constructor(message: string, body: unknown, storageType: 'r2' | 'sqlite' | 'archive', operation: 'read' | 'write' | 'delete');
}
/**
* Error thrown when a specific stream is not found
*/
declare class StreamNotFoundError extends DeltaBaseError {
/** The stream ID that was not found */
readonly streamId: string;
/** The event store ID where the stream was expected */
readonly eventStoreId: string;
constructor(message: string, body: unknown, streamId: string, eventStoreId: string);
}
/**
* Error thrown when serialization/deserialization fails
*/
declare class SerializationError extends DeltaBaseError {
/** The type of data that failed to serialize/deserialize */
readonly dataType: 'event' | 'metadata' | 'filter' | 'webhook_payload';
/** The original data that failed to process (if safe to include) */
readonly originalData?: string;
constructor(message: string, body: unknown, dataType: 'event' | 'metadata' | 'filter' | 'webhook_payload', originalData?: string);
}
/**
* Error thrown for general server errors
*/
declare class InternalServerError extends DeltaBaseError {
constructor(message: string, body: unknown);
}
/**
* Error thrown when an operation is attempted in an illegal or inappropriate state
*/
declare class IllegalStateError extends DeltaBaseError {
/** The current state that prevents the operation */
readonly currentState?: string;
/** The operation that was attempted */
readonly attemptedOperation?: string;
constructor(message: string, body: unknown, currentState?: string, attemptedOperation?: string);
}
/**
* Error thrown when a generic not found scenario occurs
*/
declare class NotFoundError extends DeltaBaseError {
/** The ID of the resource that was not found */
readonly resourceId?: string;
/** The type of resource that was not found */
readonly resourceType?: string;
constructor(options?: {
id?: string;
type?: string;
message?: string;
body?: unknown;
});
}
/**
* Type guard to check if an error is a DeltaBase error
*/
declare function isDeltaBaseError(error: unknown): error is DeltaBaseError;
/**
* Type guard to check if an error is a version conflict error
*/
declare function isVersionConflictError(error: unknown): error is VersionConflictError;
/**
* Type guard to check if an error is a validation error
*/
declare function isValidationError(error: unknown): error is ValidationError;
/**
* Type guard to check if an error is an event store not found error
*/
declare function isEventStoreNotFoundError(error: unknown): error is EventStoreNotFoundError;
/**
* Type guard to check if an error is a subscription not found error
*/
declare function isSubscriptionNotFoundError(error: unknown): error is SubscriptionNotFoundError;
/**
* Type guard to check if an error is an authentication error
*/
declare function isAuthenticationError(error: unknown): error is AuthenticationError;
/**
* Type guard to check if an error is an authorization error
*/
declare function isAuthorizationError(error: unknown): error is AuthorizationError;
/**
* Type guard to check if an error is a rate limit error
*/
declare function isRateLimitError(error: unknown): error is RateLimitError;
/**
* Type guard to check if an error is an event store already exists error
*/
declare function isEventStoreAlreadyExistsError(error: unknown): error is EventStoreAlreadyExistsError;
/**
* Type guard to check if an error is an invalid subscription config error
*/
declare function isInvalidSubscriptionConfigError(error: unknown): error is InvalidSubscriptionConfigError;
/**
* Type guard to check if an error is a timeout error
*/
declare function isTimeoutError(error: unknown): error is TimeoutError;
/**
* Type guard to check if an error is a storage error
*/
declare function isStorageError(error: unknown): error is StorageError;
/**
* Type guard to check if an error is a stream not found error
*/
declare function isStreamNotFoundError(error: unknown): error is StreamNotFoundError;
/**
* Type guard to check if an error is a serialization error
*/
declare function isSerializationError(error: unknown): error is SerializationError;
/**
* Type guard to check if an error is a not found error
*/
declare function isNotFoundError(error: unknown): error is NotFoundError;
/**
* Type guard to check if an error is an internal server error
*/
declare function isInternalServerError(error: unknown): error is InternalServerError;
/**
* Type guard to check if an error is an illegal state error
*/
declare function isIllegalStateError(error: unknown): error is IllegalStateError;
/**
* Error thrown when expected stream version doesn't match current version
* This is a specialized version conflict error for stream-based operations
*/
declare class StreamVersionConflictError<T = unknown> extends VersionConflictError {
/** The current version that was found */
readonly current: T;
/** The expected version that was provided */
readonly expected: T;
constructor(current: T, expected: T, message?: string);
}
/**
* Type guard to check if an error is a stream version conflict error
*/
declare function isStreamVersionConflictError(error: unknown): error is StreamVersionConflictError;
/**
* Union type of all possible DeltaBase errors
*/
type DeltaBaseErrorTypes = VersionConflictError | ValidationError | EventStoreNotFoundError | SubscriptionNotFoundError | AuthenticationError | AuthorizationError | IllegalStateError | RateLimitError | EventStoreAlreadyExistsError | InvalidSubscriptionConfigError | TimeoutError | StorageError | StreamNotFoundError | SerializationError | InternalServerError | NotFoundError | StreamVersionConflictError;
declare enum ValidationErrors {
NOT_A_NONEMPTY_STRING = "NOT_A_NONEMPTY_STRING",
NOT_A_POSITIVE_NUMBER = "NOT_A_POSITIVE_NUMBER",
NOT_AN_UNSIGNED_BIGINT = "NOT_AN_UNSIGNED_BIGINT"
}
declare const isNumber: (val: unknown) => val is number;
declare const isString: (val: unknown) => val is string;
declare const assertNotEmptyString: (value: unknown) => string;
declare const assertPositiveNumber: (value: unknown) => number;
declare const assertUnsignedBigInt: (value: string) => bigint;
/**
* Result of command handling operation
*/
type CommandHandlerResult<State, TEvent extends Event = Event> = {
newState: State;
newEvents: TEvent[];
appendResult: AppendToStreamResult;
};
/**
* Handler function that processes state and returns events
*/
type Handler<State, TEvent extends Event = Event> = (state: State) => TEvent | TEvent[] | Promise<TEvent | TEvent[]>;
/**
* Decider pattern for command handling
*/
type Decider<State, TCommand extends Command, TEvent extends Event> = {
decide: (command: TCommand, state: State) => TEvent | TEvent[];
evolve: (state: State, event: ReadEvent<TEvent>) => State;
initialState: () => State;
};
type AsyncRetryOptions = {
retries: number;
minTimeout: number;
factor: number;
shouldRetryError?: (error: Error) => boolean;
};
declare const CommandHandlerStreamVersionConflictRetryOptions: AsyncRetryOptions;
type CommandHandlerOptions<State, TEvent extends Event> = {
evolve: (state: State, event: ReadEvent<TEvent>) => State;
initialState: () => State;
expectedStreamVersion?: ExpectedStreamVersion<StreamPosition>;
retryOptions?: AsyncRetryOptions;
};
type DeciderCommandHandlerOptions = {
expectedStreamVersion?: ExpectedStreamVersion<StreamPosition>;
retryOptions?: AsyncRetryOptions;
};
/**
* Creates a command handler without Decider pattern
*
* Steps:
* 1. Aggregate the stream to get current state
* 2. Run business logic using Handler function
* 3. Append events to stream
* 4. Return result with new state and events
*
* @param eventStore - The event store instance
* @param streamId - The stream identifier
* @param handler - The handler function that processes state and returns events
* @param options - Command handler options including evolve, initialState, etc.
* @returns Promise resolving to command handler result with new state and events
* @public
* @example
* ```typescript
* const result = await handleCommand(
* eventStore,
* 'user-123',
* (state) => [{ type: 'UserRegistered', data: { userId: '123' } }],
* {
* evolve: (state, event) => ({ ...state, registered: true }),
* initialState: () => ({ registered: false })
* }
* );
* ```
*/
declare function handleCommand<State, TEvent extends Event = Event>(eventStore: EventStore, streamId: StreamId, handler: Handler<State, TEvent>, options: CommandHandlerOptions<State, TEvent>): Promise<CommandHandlerResult<State, TEvent>>;
/**
* Creates a command handler with Decider pattern
*
* Steps:
* 1. Aggregate the stream to get current state
* 2. Run business logic using Decider.decide function
* 3. Append events to stream
* 4. Return result with new state and events
*
* @param eventStore - The event store instance
* @param streamId - The stream identifier
* @param command - The command to handle
* @param decider - The decider implementation with decide, evolve, and initialState
* @param options - Optional command handler options
* @returns Promise resolving to command handler result with new state and events
* @public
* @example
* ```typescript
* const result = await handleCommandWithDecider(
* eventStore,
* 'user-123',
* { type: 'RegisterUser', data: { userId: '123' } },
* {
* decide: (state, cmd) => [{ type: 'UserRegistered', data: cmd.data }],
* evolve: (state, event) => ({ ...state, registered: true }),
* initialState: () => ({ registered: false })
* }
* );
* ```
*/
declare function handleCommandWithDecider<State, TCommand extends Command, TEvent extends Event = Event>(eventStore: EventStore, streamId: StreamId, command: TCommand, decider: Decider<State, TCommand, TEvent>, options?: DeciderCommandHandlerOptions): Promise<CommandHandlerResult<State, TEvent>>;
/**
* Handle command with automatic retry on version conflicts
*/
declare function handleCommandWithRetry<State, TEvent extends Event = Event>(eventStore: EventStore, streamId: StreamId, handler: Handler<State, TEvent>, options: CommandHandlerOptions<State, TEvent>): Promise<CommandHandlerResult<State, TEvent>>;
/**
* Handle command with Decider and automatic retry on version conflicts
*/
declare function handleCommandWithDeciderAndRetry<State, TCommand extends Command, TEvent extends Event = Event>(eventStore: EventStore, streamId: StreamId, command: TCommand, decider: Decider<State, TCommand, TEvent>, options?: DeciderCommandHandlerOptions): Promise<CommandHandlerResult<State, TEvent>>;
/**
* Base entity for read models
*/
interface ReadModelEntity {
[key: string]: unknown;
}
/**
* Common options for read operations
*/
interface ReadOptions {
tableName?: string;
consistency?: 'strong' | 'eventual';
cacheTtl?: number;
type?: 'text' | 'json' | 'arrayBuffer' | 'stream';
[key: string]: unknown;
}
/**
* Common options for write operations
*/
interface WriteOptions {
tableName?: string;
condition?: ConditionalExpression;
returnValue?: 'none' | 'all' | 'oldValue' | 'newValue';
[key: string]: unknown;
}
/**
* Options for item expiration
*/
interface ExpirationOptions {
expiration?: number;
expirationTtl?: number;
}
/**
* Storage metadata options
*/
interface MetadataOptions {
metadata?: Record<string, unknown>;
}
/**
* Query options with common parameters
*/
interface QueryOptions {
tableName?: string;
limit?: number;
offset?: number;
cursor?: string;
prefix?: string;
sort?: Array<{
field: string;
direction: 'asc' | 'desc';
}>;
consistency?: 'strong' | 'eventual';
cacheTtl?: number;
type?: 'text' | 'json' | 'arrayBuffer' | 'stream';
[key: string]: unknown;
}
/**
* Conditional expression for operations
*/
interface ConditionalExpression {
type: 'equals' | 'notEquals' | 'exists' | 'notExists' | 'greaterThan' | 'lessThan' | 'between' | 'beginsWith' | 'custom';
field?: string;
value?: unknown;
values?: unknown[];
customExpression?: string;
[key: string]: unknown;
}
/**
* Query filter operators
*/
interface QueryFilter {
[field: string]: {
$eq?: unknown;
$ne?: unknown;
$gt?: unknown;
$gte?: unknown;
$lt?: unknown;
$lte?: unknown;
$in?: unknown[];
$nin?: unknown[];
$exists?: boolean;
$regex?: string;
$beginsWith?: string;
$and?: QueryFilter[];
$or?: QueryFilter[];
$not?: QueryFilter;
[operator: string]: unknown;
} | unknown;
}
/**
* Index definition for store configuration
*/
interface IndexDefinition {
name: string;
fields: string[];
type?: string;
unique?: boolean;
sparse?: boolean;
[key: string]: unknown;
}
/**
* Store capabilities for runtime feature detection
*/
interface StoreCapabilities {
storeType: string;
features: {
transactions: boolean;
advancedQueries: boolean;
aggregation: boolean;
conditionalWrites: boolean;
ttl: boolean;
streaming: boolean;
};
limits: {
maxBatchSize?: number;
maxItemSize?: number;
maxTransactionSize?: number;
};
queryOperators: string[];
indexTypes: string[];
[key: string]: unknown;
}
type DatabaseItem<T> = T & {
[key: string]: unknown;
};
/**
* Core read model store interface compatible with multiple database types.
* Provides methods for storing, retrieving, deleting, and listing key-value pairs.
*/
interface IReadModelStore {
/**
* Stores a value with the given key. If the key already exists, the value will be overwritten.
*
* @param key - The key under which to store the value
* @param value - The value to store (string, ArrayBuffer, or any type that can be serialized to JSON)
* @param options - Optional settings for the storage operation
* @returns A promise that resolves when the value has been stored
*/
put<T = unknown>(key: string, value: T, options?: WriteOptions & ExpirationOptions & MetadataOptions): Promise<void>;
/**
* Retrieves a value by its key.
*
* @param key - The key of the value to retrieve
* @param options - Optional settings for the retrieval operation
* @returns A promise that resolves with the value, or null if not found
*/
get<T = unknown>(key: string, options?: ReadOptions): Promise<T | null>;
/**
* Retrieves all values, optionally filtered by a key prefix.
*
* @param options - Optional settings for retrieving all values
* @returns A promise that resolves with an array of key-value pairs
*/
getAll<T = unknown>(options?: QueryOptions): Promise<Array<DatabaseItem<T> | unknown>>;
/**
* Deletes a value by its key.
*
* @param key - The key of the value to delete
* @param options - Optional settings for the delete operation
* @returns A promise that resolves to true if the key existed and was deleted, false otherwise
*/
delete(key: string, options?: WriteOptions): Promise<boolean>;
/**
* Lists keys in the store, optionally filtered by prefix.
*
* @param options - Optional settings for the list operation
* @returns A promise that resolves with the list result containing keys and metadata
*/
listKeys?(options?: {
tableName?: string;
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: Array<{
name: string;
expiration?: number;
metadata?: Record<string, unknown>;
}>;
list_complete: boolean;
cursor?: string;
}>;
/**
* Batch retrieval of multiple items by their keys
*
* @param keys - Array of keys to retrieve
* @param options - Optional settings for the batch get operation
* @returns A promise that resolves with an array of key-value pairs
*/
batchGet?<T = unknown>(keys: string[], options?: ReadOptions): Promise<Array<DatabaseItem<T> | unknown>>;
/**
* Batch storage of multiple items
*
* @param items - Array of key-value pairs to store
* @param options - Optional settings for the batch put operation
* @returns A promise that resolves when all values have been stored
*/
batchPut?<T = unknown>(items: Array<{
key: string;
value: T;
}>, options?: WriteOptions & ExpirationOptions): Promise<void>;
/**
* Batch deletion of multiple items
*
* @param keys - Array of keys to delete
* @param options - Optional settings for the batch delete operation
* @returns A promise that resolves with an array of booleans indicating success/failure
*/
batchDelete?(keys: string[], options?: WriteOptions): Promise<boolean[]>;
/**
* Query items using filter criteria or options
*
* @param options - Query options and filter criteria
* @returns A promise that resolves with matching items
*/
query?<T = unknown>(options: QueryOptions & {
filter?: ((value: unknown) => boolean) | QueryFilter;
}): Promise<Array<DatabaseItem<T> | unknown>>;
/**
* Returns the capabilities of this store implementation
*
* @returns Store capabilities object
*/
getCapabilities?(): StoreCapabilities;
/**
* Gets access to the native database client
* Allows for direct operations using the underlying database API
*
* @returns The native client instance
*/
getNativeClient?<C = unknown>(): C;
}
/**
* Interface for a projection that can handle events
* @template TEvent - The event type(s) this projection can handle
*/
interface Projection<TEvent extends Event> {
/**
* List of event types this projection can handle
*/
readonly supportedEventTypes: EventTypeOf<TEvent>[];
/**
* Process events through this projection
*
* @param events - Events to process
*/
processEvents(events: ReadEvent<TEvent>[]): Promise<void>;
}
/**
* In-memory implementation of the IReadModelStore interface
* Useful for testing and development
*/
declare class InMemoryReadModelStore implements IReadModelStore {
private store;
private metadata;
put<T = unknown>(key: string, value: T, options?: WriteOptions & ExpirationOptions & MetadataOptions): Promise<void>;
get<T = unknown>(key: string, options?: ReadOptions): Promise<T | null>;
getAll<T = unknown>(options?: QueryOptions): Promise<Array<{
key: string;
value: T;
}>>;
delete(key: string, options?: WriteOptions): Promise<boolean>;
listKeys(options?: {
tableName?: string;
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: Array<{
name: string;
expiration?: number;
metadata?: Record<string, unknown>;
}>;
list_complete: boolean;
cursor?: string;
}>;
batchGet<T = unknown>(keys: string[], options?: ReadOptions): Promise<Array<{
key: string;
value: T | null;
}>>;
batchPut<T = unknown>(items: Array<{
key: string;
value: T;
}>, options?: WriteOptions & ExpirationOptions): Promise<void>;
batchDelete(keys: string[], options?: WriteOptions): Promise<boolean[]>;
query<T = unknown>(options: QueryOptions & {
filter?: ((value: unknown) => boolean) | QueryFilter;
}): Promise<Array<{
key: string;
value: T;
}>>;
getCapabilities(): StoreCapabilities;
getNativeClient<C = Map<string, unknown>>(): C;
/**
* Clear all data (useful for testing)
*/
clear(): void;
private getPrefixedKey;
private removePrefix;
private buildPrefix;
}
/**
* Cloudflare KV implementation of the IReadModelStore interface.
* Uses Cloudflare's KV namespace to store, retrieve, delete, and list key-value pairs.
* Updated to use native batch operations for better performance.
*/
declare class KVReadModelStore implements IReadModelStore {
private readonly namespace;
private readonly options;
/**
* Creates a new KV read model store.
*
* @param namespace - The Cloudflare KV namespace to use for storage
* @param options - Optional configuration for the store
*/
constructor(namespace: any, options?: {
/**
* Optional prefix to apply to all keys
* This can be used to create virtual "tables" within a single KV namespace
*/
defaultPrefix?: string;
});
/**
* Stores a value with the given key.
*
* @param key - The key under which to store the value
* @param value - The value to store (will be serialized to JSON if it's not a string or ArrayBuffer)
* @param options - Optional settings for the storage operation
* @returns A promise that resolves when the value has been stored
*/
put<T = unknown>(key: string, value: T, options?: WriteOptions & ExpirationOptions & MetadataOptions): Promise<void>;
/**
* Retrieves a value by its key.
*
* @param key - The key of the value to retrieve
* @param options - Optional settings for the retrieval operation
* @returns A promise that resolves with the value, or null if not found
*/
get<T = unknown>(key: string, options?: ReadOptions): Promise<T | null>;
/**
* Retrieves all values, optionally filtered by a key prefix.
*
* @param options - Optional settings for retrieving all values
* @returns A promise that resolves with an array of key-value pairs
*/
getAll<T = unknown>(options?: QueryOptions): Promise<Array<{
key: string;
value: T;
}>>;
/**
* Deletes a value by its key.
*
* @param key - The key of the value to delete
* @param options - Optional settings for the delete operation
* @returns A promise that resolves to true if the key existed and was deleted, false otherwise
*/
delete(key: string, options?: WriteOptions): Promise<boolean>;
/**
* Lists keys in the store, optionally filtered by prefix.
*
* @param options - Optional settings for the list operation
* @returns A promise that resolves with the list result containing keys and metadata
*/
listKeys(options?: {
tableName?: string;
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: Array<{
name: string;
expiration?: number;
metadata?: Record<string, unknown>;
}>;
list_complete: boolean;
cursor?: string;
}>;
/**
* Native batch retrieval of multiple items by their keys using KV's new batch API
* This is much more efficient than individual get operations
*
* @param keys - Array of keys to retrieve (max 100 keys)
* @param options - Optional settings for the batch get operation
* @returns A promise that resolves with an array of key-value pairs
*/
batchGet<T = unknown>(keys: string[], options?: ReadOptions): Promise<Array<{
key: string;
value: T | null;
}>>;
/**
* Internal method for batch get operations
*/
private batchGetInternal;
/**
* Batch storage of multiple items
*/
batchPut<T = unknown>(items: Array<{
key: string;
value: T;
}>, options?: WriteOptions & ExpirationOptions): Promise<void>;
/**
* Batch deletion of multiple items
*/
batchDelete(keys: string[], options?: WriteOptions): Promise<boolean[]>;
/**
* Enhanced query with better prefix support
*/
query<T = unknown>(options: QueryOptions & {
filter?: ((value: unknown) => boolean) | QueryFilter;
}): Promise<Array<{
key: string;
value: T;
}>>;
/**
* Returns enhanced capabilities for KV store
*/
getCapabilities(): StoreCapabilities;
/**
* Gets access to the native KV namespace
*/
getNativeClient<C = any>(): C;
/**
* Helper method to add metadata support
*/
getWithMetadata<T = unknown>(key: string, options?: ReadOptions): Promise<{
value: T | null;
metadata: Record<string, unknown> | null;
}>;
/**
* Enhanced batch get with metadata support
*/
batchGetWithMetadata<T = unknown>(keys: string[], options?: ReadOptions): Promise<Array<{
key: string;
value: T | null;
metadata: Record<string, unknown> | null;
}>>;
private getPrefixedKey;
private removePrefix;
private buildListPrefix;
}
/**
* HTTP-based external store (for APIs, databases, etc.)
*/
declare class HttpReadModelStore implements IReadModelStore {
private baseUrl;
private headers;
constructor(baseUrl: string, headers?: Record<string, string>);
get<T = unknown>(key: string, options?: ReadOptions): Promise<T | null>;
put<T = unknown>(key: string, value: T, options?: WriteOptions & ExpirationOptions & MetadataOptions): Promise<void>;
delete(key: string, options?: WriteOptions): Promise<boolean>;
getAll<T = unknown>(options?: QueryOptions): Promise<Array<{
key: string;
value: T;
}>>;
getCapabilities(): StoreCapabilities;
getNativeClient<C = typeof fetch>(): C;
}
/**
* Base class for projections that provides common functionality
* like event filtering and revision tracking
*/
declare abstract class BaseProjection<TEvent extends Event> implements Projection<TEvent> {
protected store: IReadModelStore;
abstract readonly supportedEventTypes: string[];
constructor(store: IReadModelStore);
processEvents(events: ReadEvent<TEvent>[]): Promise<void>;
/**
* Process a single event - to be implemented by subclasses
*/
protected abstract processEvent(event: ReadEvent<TEvent>): Promise<void>;
/**
* Helper method to check if an event should be processed based on revision
*/
protected shouldProcessEvent(event: ReadEvent<TEvent>, currentRevision?: number): Promise<boolean>;
/**
* Helper method to validate revision for updates
*/
protected validateRevision(event: ReadEvent<TEvent>, currentRevision: number): boolean;
}
/**
* Create a projection handler with webhook support
* This is platform-agnostic and works with any Request/Response implementation
* @template TEvent - The event type(s) the projection can handle
* @param projection - The projection instance to handle events
* @returns A webhook handler function that processes HTTP requests
* @public
* @example
* ```typescript
* const projection = new UserProjection(store);
* const webhookHandler = createWebhookProjectionHandler(projection);
*
* // Use in Cloudflare Worker, Vercel Edge Function, etc.
* export default { fetch: webhookHandler };
* ```
*/
declare function createWebhookProjectionHandler<TEvent extends Event>(projection: Projection<TEvent>): (request: Request) => Promise<Response>;
/**
* Simple event creation with automatic type inference
* @param type - The event type identifier
* @param data - The event payload data
* @param metadata - Optional event metadata
* @returns A properly typed event instance
* @public
* @example
* ```typescript
* const userRegistered = createEvent('UserRegistered', {
* userId: 'user-123',
* email: 'user@example.com'
* });
* ```
*/
declare function createEvent<EventType extends string, EventData extends DefaultRecord>(type: EventType, data: EventData, metadata?: DefaultRecord): Event<EventType, EventData, typeof metadata>;
/**
* Simple command creation with automatic type inference
* @param type - The command type identifier
* @param data - The command payload data
* @param metadata - Optional command metadata
* @returns A properly typed command instance
* @public
* @example
* ```typescript
* const registerUser = createCommand('RegisterUser', {
* userId: 'user-123',
* email: 'user@example.com'
* });
* ```
*/
declare function createCommand<CommandType extends string, CommandData extends DefaultRecord>(type: CommandType, data: CommandData, metadata?: DefaultRecord): Command<CommandType, CommandData, typeof metadata>;
/**
* Create a ReadEvent (simulates what would come from event store)
* This is primarily for testing purposes
* @param inputEvent - The input event to convert
* @param streamId - The stream identifier
* @param streamPosition - The position in the stream
* @param globalPosition - The global position
* @param eventId - Optional event ID (generates UUID if not provided)
* @param schemaVersion - Optional schema version (defaults to "1.0")
* @param transactionId - Optional transaction ID (generates UUID if not provided)
* @param createdAt - Optional creation timestamp (defaults to current time)
* @returns A ReadEvent instance for testing
* @public
* @example
* ```typescript
* const event = createEvent('UserRegistered', { userId: '123' });
* const readEvent = createReadEvent(event, 'user-123', 1, 1);
* ```
*/
declare function createReadEvent<EventType extends Event>(inputEvent: EventType, streamId: string, streamPosition: StreamPosition, globalPosition: GlobalPosition, eventId?: string, schemaVersion?: string, transactionId?: string, createdAt?: string): ReadEvent<EventType>;
export { type AggregateStreamOptions, type AggregateStreamResult, type AnyCommand, type AnyEvent, type AppendToStreamOptions, type AppendToStreamResult, type AsyncRetryOptions, AuthenticationError, AuthorizationError, BaseProjection, type Command, type CommandDataOf, type CommandHandlerOptions, type CommandHandlerResult, CommandHandlerStreamVersionConflictRetryOptions, type CommandMetadataOf, type CommandResult, type CommandTypeOf, type ConditionalExpression, type DatabaseItem, type Decider, type DeciderCommandHandlerOptions, type DefaultRecord, DeltaBaseError, type DeltaBaseErrorTypes, type Event, type EventDataOf, type EventMetadataOf, type EventStore, EventStoreAlreadyExistsError, EventStoreNotFoundError, type EventTypeOf, type ExpectedStreamVersion, type ExpirationOptions, type GlobalPosition, type Handler, HttpReadModelStore, type IReadModelStore, IllegalStateError, InMemoryEventStore, InMemoryReadModelStore, type IndexDefinition, InternalServerError, InvalidSubscriptionConfigError, KVReadModelStore, type Message, type MessageDataOf, type MessageMetadataOf, type MessageTypeOf, type MetadataOptions, NO_CONCURRENCY_CHECK, NotFoundError, type PlatformEventMetadata, type Projection, type QueryEventsOptions, type QueryEventsResult, type QueryFilter, type QueryOptions, RateLimitError, type ReadEvent, type ReadModelEntity, type ReadOptions, type ReadStreamOptions, type ReadStreamResult, STREAM_DOES_NOT_EXIST, STREAM_EXISTS, SerializationError, StorageError, type StoreCapabilities, type StreamId, type StreamMetadata, StreamNotFoundError, type StreamPosition, StreamVersionConflictError, SubscriptionNotFoundError, TimeoutError, ValidationError, ValidationErrors, VersionConflictError, type WriteOptions, assertNotEmptyString, assertPositiveNumber, assertUnsignedBigInt, command, createCommand, createEvent,