@delta-base/server
Version:
Server SDK for delta-base
1,390 lines (1,383 loc) • 48.5 kB
JavaScript
import { VersionConflictError, ValidationError, InternalServerError, InvalidSubscriptionConfigError, SerializationError, StreamNotFoundError, StorageError, TimeoutError, EventStoreAlreadyExistsError, SubscriptionNotFoundError, EventStoreNotFoundError, RateLimitError, AuthorizationError, AuthenticationError } from '@delta-base/toolkit';
export { AuthenticationError, AuthorizationError, DeltaBaseError, EventStoreAlreadyExistsError, EventStoreNotFoundError, InternalServerError, InvalidSubscriptionConfigError, NotFoundError, RateLimitError, SerializationError, StorageError, StreamNotFoundError, StreamVersionConflictError, SubscriptionNotFoundError, TimeoutError, ValidationError, ValidationErrors, VersionConflictError, assertNotEmptyString, assertPositiveNumber, assertUnsignedBigInt, isAuthenticationError, isAuthorizationError, isDeltaBaseError, isEventStoreAlreadyExistsError, isEventStoreNotFoundError, isInternalServerError, isInvalidSubscriptionConfigError, isNotFoundError, isNumber, isRateLimitError, isSerializationError, isStorageError, isStreamNotFoundError, isStreamVersionConflictError, isString, isSubscriptionNotFoundError, isTimeoutError, isValidationError, isVersionConflictError } from '@delta-base/toolkit';
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/clients/event-bus.ts
var SubscriberType = /* @__PURE__ */ ((SubscriberType2) => {
SubscriberType2["DurableObject"] = "durable_object";
SubscriberType2["Webhook"] = "webhook";
SubscriberType2["Queue"] = "queue";
SubscriberType2["CloudflareWorkflow"] = "cloudflare_workflow";
SubscriberType2["WebSocket"] = "websocket";
return SubscriberType2;
})(SubscriberType || {});
var SubscriptionStatus = /* @__PURE__ */ ((SubscriptionStatus2) => {
SubscriptionStatus2["Active"] = "ACTIVE";
SubscriptionStatus2["Suspended"] = "SUSPENDED";
SubscriptionStatus2["Error"] = "ERROR";
SubscriptionStatus2["Initializing"] = "INITIALIZING";
SubscriptionStatus2["Deleted"] = "DELETED";
return SubscriptionStatus2;
})(SubscriptionStatus || {});
var _EventBus = class _EventBus {
/**
* Create a new EventBus client for a specific event store
*
* @param http - The HTTP client used for API requests
* @param eventStoreName - The name of the event store to manage subscriptions for
*/
constructor(http, eventStoreName) {
this.http = http;
this.eventStoreName = eventStoreName;
}
/**
* Subscribe to events from this event store
*
* @param options - Configuration for the subscription
* @returns The created subscription information
* @throws {InvalidSubscriptionConfigError} When subscription configuration is invalid
* @throws {EventStoreNotFoundError} When the event store doesn't exist
* @throws {ValidationError} When request validation fails
* @throws {AuthenticationError} When authentication fails
*
* @example
* ```typescript
* import {
* isInvalidSubscriptionConfigError,
* isEventStoreNotFoundError
* } from '@delta-base/server';
*
* try {
* const subscription = await eventBus.subscribe({
* eventFilter: 'user.*',
* subscriber: {
* type: SubscriberType.Webhook,
* config: {
* url: 'https://example.com/webhook',
* headers: { 'X-API-Key': 'secret' },
* retryPolicy: {
* maxAttempts: 3,
* backoffMinutes: 5
* }
* }
* }
* });
*
* // Check if this is an existing subscription
* if (subscription.isExistingSubscription) {
* console.log('Found existing subscription:', subscription.message);
* } else {
* console.log('Created new subscription:', subscription.message);
* }
* } catch (error) {
* if (isInvalidSubscriptionConfigError(error)) {
* console.log(`Invalid configuration: ${error.configError}`);
* // Handle invalid subscription config
* } else if (isEventStoreNotFoundError(error)) {
* console.log(`Event store '${error.eventStoreId}' not found`);
* // Handle missing event store
* } else {
* throw error; // Re-throw unknown errors
* }
* }
* ```
*/
async subscribe(options) {
const response = await this.http.post(
`/api/event-stores/${this.eventStoreName}/subscribe`,
options
);
return this.mapSubscriptionResponse(response);
}
/**
* Create a webhook subscription
*
* @param eventFilter - Pattern determining which events to receive
* @param url - The URL that will receive HTTP POST requests with events
* @param options - Additional configuration options
* @returns The created subscription information
*
* @example
* ```typescript
* // Subscribe to all user events
* const subscription = await eventBus.subscribeWebhook(
* 'user.*',
* 'https://example.com/webhook',
* {
* headers: { 'X-API-Key': 'secret' },
* retryPolicy: {
* maxAttempts: 3,
* backoffMinutes: 5
* }
* }
* );
*
* // Check if this is an existing subscription with the same configuration
* if (subscription.isExistingSubscription) {
* console.log('Reusing existing subscription:', subscription.message);
* } else {
* console.log('Created new subscription:', subscription.message);
* }
* ```
*/
async subscribeWebhook(eventFilter, url, options = {}) {
const { subscriberId, initialPosition, ...webhookOptions } = options;
return this.subscribe({
eventFilter,
subscriberId,
initialPosition,
subscriber: {
type: "webhook" /* Webhook */,
config: {
url,
...webhookOptions
}
}
});
}
// /**
// * Create a Durable Object subscription
// *
// * @param eventFilter - Pattern determining which events to receive
// * @param namespace - The Durable Object namespace binding name in your worker
// * @param id - The ID of the Durable Object instance to deliver events to
// * @param options - Additional configuration options
// * @returns The created subscription information
// *
// * @example
// * ```typescript
// * // Subscribe to order events with a Durable Object
// * const subscription = await eventBus.subscribeDurableObject(
// * 'order.*',
// * 'ORDER_PROCESSOR',
// * 'order-processor-instance-1',
// * {
// * retryPolicy: {
// * maxAttempts: 5,
// * backoffMinutes: 10
// * }
// * }
// * );
// * ```
// */
// async subscribeDurableObject(
// eventFilter: EventFilterPattern,
// namespace: string,
// id: string,
// options: Omit<DurableObjectConfig, 'namespace' | 'id'> & {
// subscriberId?: string;
// initialPosition?: InitialPosition;
// } = {}
// ): Promise<Subscription> {
// const { subscriberId, initialPosition, ...doOptions } = options;
// return this.subscribe({
// eventFilter,
// subscriberId,
// initialPosition,
// subscriber: {
// type: SubscriberType.DurableObject,
// config: {
// namespace,
// id,
// ...doOptions,
// },
// },
// });
// }
// /**
// * Create a queue subscription
// *
// * @param eventFilter - Pattern determining which events to receive
// * @param queueName - The name of the queue to send events to
// * @param options - Additional configuration options
// * @returns The created subscription information
// *
// * @example
// * ```typescript
// * // Subscribe to notification events with a queue
// * const subscription = await eventBus.subscribeQueue(
// * 'notification.*',
// * 'notification-queue',
// * {
// * region: 'us-east-1',
// * batchSize: 10
// * }
// * );
// * ```
// */
// async subscribeQueue(
// eventFilter: EventFilterPattern,
// queueName: string,
// options: Omit<QueueConfig, 'queueName'> & {
// subscriberId?: string;
// initialPosition?: InitialPosition;
// } = {}
// ): Promise<Subscription> {
// const { subscriberId, initialPosition, ...queueOptions } = options;
// return this.subscribe({
// eventFilter,
// subscriberId,
// initialPosition,
// subscriber: {
// type: SubscriberType.Queue,
// config: {
// queueName,
// ...queueOptions,
// },
// },
// });
// }
// /**
// * Create a Cloudflare Workflow subscription
// *
// * @param eventFilter - Pattern determining which events to receive
// * @param bindingName - The name of the workflow binding in your worker
// * @param options - Additional configuration options
// * @returns The created subscription information
// *
// * @example
// * ```typescript
// * // Subscribe to all events with a Cloudflare Workflow
// * const subscription = await eventBus.subscribeWorkflow(
// * '*',
// * 'EVENT_WORKFLOW'
// * );
// * ```
// */
// async subscribeWorkflow(
// eventFilter: EventFilterPattern,
// bindingName: string,
// options: Omit<CloudflareWorkflowConfig, 'bindingName'> & {
// subscriberId?: string;
// initialPosition?: InitialPosition;
// } = {}
// ): Promise<Subscription> {
// const { subscriberId, initialPosition, ...workflowOptions } = options;
// return this.subscribe({
// eventFilter,
// subscriberId,
// initialPosition,
// subscriber: {
// type: SubscriberType.CloudflareWorkflow,
// config: {
// bindingName,
// ...workflowOptions,
// },
// },
// });
// }
// /**
// * Create a WebSocket subscription
// *
// * @param eventFilter - Pattern determining which events to receive
// * @param managerId - The ID of the WebSocket manager to broadcast events to
// * @param options - Additional configuration options
// * @returns The created subscription information
// *
// * @example
// * ```typescript
// * // Subscribe to notification events with WebSockets
// * const subscription = await eventBus.subscribeWebSocket(
// * 'notification.*',
// * 'notifications-websocket-manager'
// * );
// * ```
// */
// async subscribeWebSocket(
// eventFilter: EventFilterPattern,
// managerId: string,
// options: Omit<WebSocketConfig, 'managerId'> & {
// subscriberId?: string;
// initialPosition?: InitialPosition;
// } = {}
// ): Promise<Subscription> {
// const { subscriberId, initialPosition, ...wsOptions } = options;
// return this.subscribe({
// eventFilter,
// subscriberId,
// initialPosition,
// subscriber: {
// type: SubscriberType.WebSocket,
// config: {
// managerId,
// ...wsOptions,
// },
// },
// });
// }
/**
* Get details about a specific subscription
*
* @param subscriptionId - ID of the subscription to retrieve
* @returns Subscription details
*
* @example
* ```typescript
* const subscription = await eventBus.getSubscription('sub_123456');
* console.log(subscription.status); // 'ACTIVE'
* ```
*/
async getSubscription(subscriptionId) {
const response = await this.http.get(
`/api/event-stores/${this.eventStoreName}/subscriptions/${subscriptionId}`
);
return this.mapSubscriptionResponse(response);
}
/**
* List all subscriptions for this event store
*
* @param options - Optional filtering and pagination parameters
* @returns List of subscriptions and total count
*
* @example
* ```typescript
* // List all webhook subscriptions
* const { subscriptions, totalCount } = await eventBus.listSubscriptions({
* subscriberType: SubscriberType.Webhook,
* limit: 20,
* offset: 0
* });
* ```
*/
async listSubscriptions(options = {}) {
const params = {};
if (options.subscriberType) {
params.subscriberType = options.subscriberType;
}
if (options.eventFilter) {
params.eventFilter = options.eventFilter;
}
if (options.limit) {
params.limit = options.limit.toString();
}
if (options.offset) {
params.offset = options.offset.toString();
}
const response = await this.http.get(`/api/event-stores/${this.eventStoreName}/subscriptions`, params);
return {
subscriptions: response.subscriptions.map(
(sub) => this.mapSubscriptionResponse(sub)
),
totalCount: response.totalCount
};
}
/**
* Unsubscribe from events (delete a subscription)
*
* @param subscriptionId - ID of the subscription to delete
* @returns Success message
*
* @example
* ```typescript
* const result = await eventBus.unsubscribe('sub_123456');
* console.log(result.success); // true
* ```
*/
async unsubscribe(subscriptionId) {
return this.http.delete(
`/api/event-stores/${this.eventStoreName}/subscriptions/${subscriptionId}`
);
}
/**
* Map API subscription response to Subscription type
*
* @param response - The API response to map
* @returns Mapped subscription
* @private
*/
mapSubscriptionResponse(response) {
return {
subscriptionId: response.subscriptionId,
status: response.status,
statusDetails: response.statusDetails,
eventStoreId: response.eventStoreId,
eventFilter: response.eventFilter,
subscriberId: response.subscriberId,
subscriberType: response.subscriberType,
createdAt: response.createdAt,
updatedAt: response.updatedAt,
lastProcessedEventGlobalPosition: response.lastProcessedEventGlobalPosition,
isExistingSubscription: response.isExistingSubscription,
message: response.message
};
}
};
__name(_EventBus, "EventBus");
var EventBus = _EventBus;
// src/clients/event-store.ts
var _EventStore = class _EventStore {
/**
* Creates a new EventStore client instance
*
* @param http - The HTTP client to use for API requests
* @param eventStoreName - The name of the event store to interact with
*/
constructor(http, eventStoreName) {
this.http = http;
this.eventStoreName = eventStoreName;
}
/**
* Aggregate events from a stream and compute a state
*
* @param streamId - The ID of the stream to aggregate events from
* @param options - Configuration options for the aggregation process
* @param options.initialState - Function that returns the initial state
* @param options.evolve - Function that applies an event to the current state
* @param options.read - Optional read options to control which events to include
* @returns Promise resolving to the aggregation result with the computed state and stream metadata
*
* @example
* ```typescript
* // Define your state type and event types
* type UserState = { email: string, isVerified: boolean };
* type UserEvent =
* | { type: 'user.created', data: { email: string } }
* | { type: 'user.verified', data: { verifiedAt: string } };
*
* // Aggregate the stream into a state
* const result = await eventStore.aggregateStream<UserState, UserEvent>(
* 'user-123',
* {
* initialState: () => ({ email: '', isVerified: false }),
* evolve: (state, event) => {
* switch (event.type) {
* case 'user.created':
* return { ...state, email: event.data.email };
* case 'user.verified':
* return { ...state, isVerified: true };
* default:
* return state;
* }
* },
* read: { from: 0 }
* }
* );
* ```
*/
async aggregateStream(streamId, options) {
const readResult = await this.readStream(streamId, options.read);
let state = options.initialState();
for (const event of readResult.events) {
state = options.evolve(state, event);
}
return {
currentStreamVersion: readResult.currentStreamVersion,
state,
streamExists: readResult.streamExists
};
}
/**
* Read events from a stream
*
* @param streamId - The ID of the stream to read events from
* @param options - The options for reading events
* @param options.from - Optional starting position to read from
* @param options.to - Optional ending position to read to
* @param options.maxCount - Optional maximum number of events to read
* @param options.expectedStreamVersion - Optional expected version for optimistic concurrency
* @returns Promise resolving to the read result containing events and stream metadata
*
* @example
* ```typescript
* // Read all events from a stream
* const result = await eventStore.readStream('user-123');
*
* // Read events with a specific starting position
* const result = await eventStore.readStream('user-123', { from: 5 });
*
* // Read a specific range of events
* const result = await eventStore.readStream('user-123', { from: 5, to: 10 });
*
* // Read a limited number of events
* const result = await eventStore.readStream('user-123', { maxCount: 100 });
* ```
*/
async readStream(streamId, options) {
const url = `/api/event-stores/${this.eventStoreName}/streams/${streamId}`;
const apiOptions = {};
if (options) {
if ("from" in options) {
apiOptions.from = options.from.toString();
}
if ("to" in options) {
apiOptions.to = options.to.toString();
}
if ("maxCount" in options && options.maxCount !== void 0) {
apiOptions.maxCount = options.maxCount.toString();
}
if ("expectedStreamVersion" in options && options.expectedStreamVersion !== void 0) {
apiOptions.expectedStreamVersion = options.expectedStreamVersion.toString();
}
}
const response = await this.http.get(
url,
apiOptions
);
const mappedEvents = (response.events || []).map((event) => ({
type: event.type,
data: event.data,
streamId: event.streamId,
streamPosition: event.streamPosition,
globalPosition: event.globalPosition,
eventId: event.eventId || "unknown",
schemaVersion: event.schemaVersion || "1.0.0",
transactionId: event.transactionId || "0",
createdAt: event.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
metadata: event.metadata || {}
}));
return {
currentStreamVersion: response.currentStreamVersion,
events: mappedEvents,
streamExists: response.streamExists ?? true
};
}
/**
* Append events to a stream
*
* @param streamId - The ID of the stream to append events to
* @param events - Array of events to append to the stream
* @param options - Optional parameters for the append operation
* @param options.expectedStreamVersion - Optional expected version for optimistic concurrency
* @returns Promise resolving to the append result with the next expected version
* @throws {VersionConflictError} When expectedStreamVersion doesn't match current stream version
* @throws {ValidationError} When request validation fails
* @throws {EventStoreNotFoundError} When the event store doesn't exist
* @throws {AuthenticationError} When authentication fails
*
* @example
* ```typescript
* import {
* isVersionConflictError,
* isValidationError
* } from '@delta-base/server';
*
* try {
* // Append with optimistic concurrency control
* await eventStore.appendToStream(
* 'user-123',
* [{
* type: 'user.updated',
* data: { email: 'updated@example.com' }
* }],
* { expectedStreamVersion: 0n }
* );
* } catch (error) {
* if (isVersionConflictError(error)) {
* console.log(`Version conflict: expected ${error.expectedVersion}, got ${error.currentVersion}`);
* // Handle concurrency conflict
* } else if (isValidationError(error)) {
* console.log('Validation errors:', error.validationErrors);
* // Handle validation failures
* } else {
* throw error; // Re-throw unknown errors
* }
* }
* ```
*/
async appendToStream(streamId, events, options) {
const request = {
events,
options: {
expectedStreamVersion: options?.expectedStreamVersion !== void 0 ? typeof options.expectedStreamVersion === "number" ? Number(options.expectedStreamVersion) : typeof options.expectedStreamVersion === "string" ? options.expectedStreamVersion === "no_stream" ? "no_stream" : options.expectedStreamVersion === "stream_exists" ? "stream_exists" : "any" : Number(options.expectedStreamVersion) : void 0
}
};
const response = await this.http.post(
`/api/event-stores/${this.eventStoreName}/streams/${streamId}`,
request
);
return {
nextExpectedStreamVersion: response.nextExpectedStreamVersion,
createdNewStream: !response.success
};
}
/**
* Query events with flexible filtering options
*
* @param options - Query parameters for filtering events
* @param options.streamId - Optional stream ID to filter by
* @param options.type - Optional event type(s) to filter by
* @param options.eventId - Optional specific event ID to retrieve
* @param options.transactionId - Optional transaction ID to filter by
* @param options.fromPosition - Optional starting global position
* @param options.toPosition - Optional ending global position
* @param options.fromDate - Optional starting date for time-based filtering
* @param options.toDate - Optional ending date for time-based filtering
* @param options.phase - Optional event phase to filter by
* @param options.includeArchived - Whether to include archived events
* @param options.limit - Maximum number of events to return
* @param options.offset - Number of events to skip
* @param options.sortBy - Field to sort results by
* @param options.sortDirection - Direction to sort results
* @param options.includeCount - Whether to include total count in results
* @returns Promise resolving to the query result with events and pagination info
*
* @example
* ```typescript
* // Query all events of a specific type
* const result = await eventStore.queryEvents({
* type: 'user.created'
* });
*
* // Query events with pagination
* const result = await eventStore.queryEvents({
* limit: 20,
* offset: 40,
* includeCount: true
* });
*
* // Query events within a time range
* const result = await eventStore.queryEvents({
* fromDate: '2023-01-01T00:00:00Z',
* toDate: '2023-01-31T23:59:59Z'
* });
* ```
*/
async queryEvents(options = {}) {
const url = `/api/event-stores/${this.eventStoreName}/events`;
const queryParams = {};
for (const [key, value] of Object.entries(options)) {
if (value !== void 0) {
if (Array.isArray(value)) {
queryParams[key] = value.join(",");
} else {
queryParams[key] = String(value);
}
}
}
const response = await this.http.get(
url,
queryParams
);
return {
events: (response.events || []).map(
(event) => ({
type: event.type,
data: event.data,
streamId: event.streamId,
streamPosition: Number(event.streamPosition || 0),
globalPosition: Number(event.globalPosition || 0),
eventId: event.eventId || "unknown",
schemaVersion: event.schemaVersion || "1.0.0",
transactionId: event.transactionId || "0",
createdAt: event.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
metadata: event.metadata
})
),
pagination: {
limit: response.pagination.limit,
offset: response.pagination.offset,
total: response.pagination.total,
hasMore: response.pagination.hasMore
}
};
}
/**
* Query events for a specific stream with filtering options
*
* @param streamId - The ID of the stream to query events from
* @param options - Query parameters for filtering events
* @param options.type - Optional event type(s) to filter by
* @param options.eventId - Optional specific event ID to retrieve
* @param options.transactionId - Optional transaction ID to filter by
* @param options.fromPosition - Optional starting global position
* @param options.toPosition - Optional ending global position
* @param options.fromDate - Optional starting date for time-based filtering
* @param options.toDate - Optional ending date for time-based filtering
* @param options.phase - Optional event phase to filter by
* @param options.includeArchived - Whether to include archived events
* @param options.limit - Maximum number of events to return
* @param options.offset - Number of events to skip
* @param options.sortBy - Field to sort results by
* @param options.sortDirection - Direction to sort results
* @param options.includeCount - Whether to include total count in results
* @returns Promise resolving to the query result with events and pagination info
*
* @example
* ```typescript
* // Query events for a specific stream
* const result = await eventStore.queryStreamEvents('user-123', {
* type: 'user.updated'
* });
* ```
*/
async queryStreamEvents(streamId, options = {}) {
return this.queryEvents({
...options,
streamId
});
}
/**
* Query streams with filtering options
*
* @param options - Query parameters for filtering streams
* @param options.streamId - Optional specific stream ID to retrieve
* @param options.streamType - Optional stream type(s) to filter by
* @param options.streamIdPattern - Optional pattern to match stream IDs
* @param options.minPosition - Optional minimum stream position
* @param options.maxPosition - Optional maximum stream position
* @param options.fromDate - Optional starting date for time-based filtering
* @param options.toDate - Optional ending date for time-based filtering
* @param options.includeArchived - Whether to include archived streams
* @param options.includeMetadata - Whether to include stream metadata
* @param options.limit - Maximum number of streams to return
* @param options.offset - Number of streams to skip
* @param options.sortBy - Field to sort results by
* @param options.sortDirection - Direction to sort results
* @param options.includeCount - Whether to include total count in results
* @returns Promise resolving to the query result with streams and pagination info
*
* @example
* ```typescript
* // Query all streams
* const result = await eventStore.queryStreams();
*
* // Query streams by type
* const result = await eventStore.queryStreams({
* streamType: 'user'
* });
*
* // Query streams with a pattern match
* const result = await eventStore.queryStreams({
* streamIdPattern: 'user-%'
* });
* ```
*/
async queryStreams(options = {}) {
const url = `/api/event-stores/${this.eventStoreName}/streams`;
const queryParams = {};
for (const [key, value] of Object.entries(options)) {
if (value !== void 0) {
if (Array.isArray(value)) {
queryParams[key] = value.join(",");
} else {
queryParams[key] = String(value);
}
}
}
const response = await this.http.get(
url,
queryParams
);
return {
streams: (response.streams || []).map((stream) => ({
streamId: stream.streamId,
streamPosition: stream.streamPosition,
streamType: stream.streamType,
streamMetadata: stream.streamMetadata || {},
isArchived: stream.isArchived,
lastArchivedPosition: stream.lastArchivedPosition,
createdAt: stream.createdAt,
updatedAt: stream.updatedAt
})),
pagination: {
limit: response.pagination.limit,
offset: response.pagination.offset,
total: response.pagination.total,
hasMore: response.pagination.hasMore
}
};
}
/**
* Query streams of a specific type with filtering options
*
* @param streamType - The stream type to filter by
* @param options - Query parameters for filtering streams
* @param options.streamId - Optional specific stream ID to retrieve
* @param options.streamIdPattern - Optional pattern to match stream IDs
* @param options.minPosition - Optional minimum stream position
* @param options.maxPosition - Optional maximum stream position
* @param options.fromDate - Optional starting date for time-based filtering
* @param options.toDate - Optional ending date for time-based filtering
* @param options.includeArchived - Whether to include archived streams
* @param options.includeMetadata - Whether to include stream metadata
* @param options.limit - Maximum number of streams to return
* @param options.offset - Number of streams to skip
* @param options.sortBy - Field to sort results by
* @param options.sortDirection - Direction to sort results
* @param options.includeCount - Whether to include total count in results
* @returns Promise resolving to the query result with streams and pagination info
*
* @example
* ```typescript
* // Query all user streams
* const result = await eventStore.queryStreamsByType('user');
*
* // Query user streams with pagination
* const result = await eventStore.queryStreamsByType('user', {
* limit: 20,
* offset: 0
* });
* ```
*/
async queryStreamsByType(streamType, options = {}) {
return this.queryStreams({
...options,
streamType
});
}
/**
* Get a list of stream IDs in an event store
*
* @param options - Optional parameters for listing streams
* @param options.limit - Maximum number of stream IDs to return
* @param options.offset - Number of stream IDs to skip
* @param options.pattern - Pattern to match stream IDs (e.g., 'user-*')
* @returns Promise resolving to an object containing stream IDs and total count
*
* @example
* ```typescript
* // List all streams
* const { streams, total } = await eventStore.listStreams();
*
* // List streams with pagination
* const { streams, total } = await eventStore.listStreams({
* limit: 50,
* offset: 100
* });
*
* // List streams matching a pattern
* const { streams, total } = await eventStore.listStreams({
* pattern: 'user-*'
* });
* ```
*/
async listStreams(options) {
const params = {};
if (options?.limit) {
params.limit = options.limit.toString();
}
if (options?.offset) {
params.offset = options.offset.toString();
}
if (options?.pattern) {
params.pattern = options.pattern;
}
return this.http.get(
`/api/event-stores/${this.eventStoreName}/streams`,
params
);
}
};
__name(_EventStore, "EventStore");
var EventStore = _EventStore;
// src/clients/management.ts
var _ManagementClient = class _ManagementClient {
/**
* Creates a new ManagementClient instance.
*
* @param http - The HTTP client to use for API requests
*/
constructor(http) {
this.http = http;
}
/**
* Creates a new event store in the DeltaBase platform.
*
* @param options - Configuration for the new event store
* @returns Promise resolving to the created event store information
* @throws {EventStoreAlreadyExistsError} When an event store with the same name exists
* @throws {ValidationError} When request validation fails
* @throws {AuthenticationError} When authentication fails
* @throws {InternalServerError} When the server encounters an error
*
* @example
* ```typescript
* import {
* isEventStoreAlreadyExistsError,
* isValidationError
* } from '@delta-base/server';
*
* try {
* const eventStore = await managementClient.createEventStore({
* name: 'orders-production',
* description: 'Production event store for order events',
* settings: {
* retentionPeriodDays: 90,
* maxStreamSizeBytes: 1073741824 // 1GB
* }
* });
* } catch (error) {
* if (isEventStoreAlreadyExistsError(error)) {
* console.log(`Event store '${error.name}' already exists`);
* // Handle existing event store
* } else if (isValidationError(error)) {
* console.log('Validation errors:', error.validationErrors);
* // Handle validation failures
* } else {
* throw error; // Re-throw unknown errors
* }
* }
* ```
*/
async createEventStore(options) {
const apiResponse = await this.http.post(
"/api/event-stores",
options
);
return {
id: apiResponse.eventStoreId,
name: apiResponse.eventStoreName,
description: apiResponse.description,
region: void 0,
// API doesn't return region currently
createdAt: apiResponse.created,
updatedAt: apiResponse.created,
// API doesn't return updatedAt, use created as fallback
status: "active"
// API doesn't return status, assume active for new stores
};
}
/**
* Retrieves a list of all event stores accessible to the current user.
*
* @returns Promise resolving to a list of event stores and total count
*
* @example
* ```typescript
* // Get all available event stores
* const { eventStores, totalCount } = await managementClient.listEventStores();
*
* // Display event store names
* eventStores.forEach(store => {
* console.log(`${store.name} (${store.id}): ${store.status}`);
* });
* ```
*/
async listEventStores() {
const apiResponse = await this.http.get("/api/event-stores");
return {
eventStores: apiResponse.eventStores.map((store) => ({
id: store.eventStoreId,
name: store.eventStoreName,
description: store.description,
region: void 0,
// API doesn't return region currently
createdAt: store.created,
updatedAt: store.created,
// API doesn't return updatedAt, use created as fallback
status: "active"
// API doesn't return status, assume active
})),
totalCount: apiResponse.totalCount
};
}
/**
* Retrieves detailed information about a specific event store.
*
* @param eventStoreName - Name of the event store to retrieve
* @returns Promise resolving to detailed information about the event store
*
* @example
* ```typescript
* // Get details for a specific event store
* const eventStore = await managementClient.getEventStore('es_12345');
*
* // Access statistics
* if (eventStore.statistics) {
* console.log(`Total events: ${eventStore.statistics.totalEvents}`);
* console.log(`Storage used: ${eventStore.statistics.databaseSizeBytes} bytes`);
* }
* ```
*/
async getEventStore(eventStoreName) {
const apiResponse = await this.http.get(
`/api/event-stores/${eventStoreName}`
);
return {
id: apiResponse.eventStoreId,
name: apiResponse.eventStoreName,
description: apiResponse.description,
region: void 0,
// API doesn't return region currently
createdAt: apiResponse.created,
updatedAt: apiResponse.created,
// API doesn't return updatedAt, use created as fallback
status: "active",
// API doesn't return status, assume active
statistics: apiResponse.statistics,
settings: apiResponse.settings && apiResponse.settings.retentionPeriodDays !== void 0 && apiResponse.settings.maxStreamSizeBytes !== void 0 ? {
retentionPeriodDays: apiResponse.settings.retentionPeriodDays,
maxStreamSizeBytes: apiResponse.settings.maxStreamSizeBytes
} : void 0
};
}
/**
* Permanently deletes an event store and all its data.
*
* This operation cannot be undone. All event streams, events, and
* subscriptions associated with this event store will be deleted.
*
* @param eventStoreName - Name of the event store to delete
* @returns Promise that resolves when the deletion is complete
*
* @example
* ```typescript
* // Delete an event store
* await managementClient.deleteEventStore('es_12345');
* ```
*/
async deleteEventStore(eventStoreName) {
await this.http.delete(`/api/event-stores/${eventStoreName}`);
}
/**
* Updates settings for an existing event store.
*
* @param eventStoreName - ID of the event store to update
* @param settings - New settings for the event store
* @param settings.description - Optional new description for the event store
* @param settings.retentionPeriodDays - Optional new retention period in days
* @param settings.maxStreamSizeBytes - Optional new maximum stream size in bytes
* @returns Promise resolving to the updated event store information
*
* @example
* ```typescript
* // Update event store description
* const updated = await managementClient.updateEventStore('es_12345', {
* description: 'Updated description for this event store'
* });
*
* // Update event store retention settings
* const updated = await managementClient.updateEventStore('es_12345', {
* retentionPeriodDays: 180, // Keep events for 6 months
* maxStreamSizeBytes: 536870912 // 512MB per stream
* });
* ```
*/
async updateEventStore(eventStoreName, settings) {
const apiResponse = await this.http.put(
`/api/event-stores/${eventStoreName}`,
settings
);
return {
id: apiResponse.eventStoreId,
name: apiResponse.eventStoreName,
description: apiResponse.description,
region: void 0,
// API doesn't return region currently
createdAt: apiResponse.created,
updatedAt: apiResponse.created,
// API doesn't return updatedAt, use created as fallback
status: "active"
// API doesn't return status, assume active
};
}
};
__name(_ManagementClient, "ManagementClient");
var ManagementClient = _ManagementClient;
// src/utils/http.ts
var _HttpClient = class _HttpClient {
constructor(config) {
this.baseUrl = config.baseUrl.replace(/\/$/, "");
this.apiKey = config.apiKey;
this.customHeaders = config.headers;
}
/**
* Perform a GET request
*/
async get(path, params) {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
for (const [key, value] of Object.entries(params)) {
url.searchParams.append(key, value);
}
}
const response = await fetch(url.toString(), {
method: "GET",
headers: this.getHeaders()
});
return this.handleResponse(response);
}
/**
* Perform a POST request
*/
async post(path, body) {
const response = await fetch(`${this.baseUrl}${path}`, {
method: "POST",
headers: this.getHeaders(),
body: body ? JSON.stringify(body) : void 0
});
return this.handleResponse(response);
}
/**
* Perform a PUT request
*/
async put(path, body) {
const response = await fetch(`${this.baseUrl}${path}`, {
method: "PUT",
headers: this.getHeaders(),
body: body ? JSON.stringify(body) : void 0
});
return this.handleResponse(response);
}
/**
* Perform a DELETE request
*/
async delete(path) {
const response = await fetch(`${this.baseUrl}${path}`, {
method: "DELETE",
headers: this.getHeaders()
});
return this.handleResponse(response);
}
/**
* Get common headers for all requests
*/
getHeaders() {
const headers = {
"Content-Type": "application/json",
Accept: "application/json"
};
if (this.apiKey) {
headers["X-API-Key"] = this.apiKey;
}
if (this.customHeaders) {
Object.assign(headers, this.customHeaders);
}
return headers;
}
/**
* Handle API response and convert to typed errors
*/
async handleResponse(response) {
const contentType = response.headers.get("content-type");
const isJson = contentType?.includes("application/json");
const body = isJson ? await response.json() : await response.text();
if (!response.ok) {
throw this.createTypedError(response.status, body);
}
return body;
}
/**
* Create a typed error based on status code and response body
*/
createTypedError(status, body) {
if (typeof body === "object" && body !== null) {
const errorBody = body;
if (status === 409 && this.isVersionConflictError(errorBody)) {
return new VersionConflictError(
errorBody.details,
body,
errorBody.currentVersion,
errorBody.expectedVersion
);
}
if (status === 400 && this.isValidationError(errorBody)) {
return new ValidationError(
"Request validation failed",
body,
errorBody.details
);
}
if (this.isGeneralError(errorBody)) {
switch (errorBody.error) {
case "EVENT_STORE_NOT_FOUND":
return new EventStoreNotFoundError(
errorBody.message,
body,
errorBody.details?.eventStoreId || "unknown"
);
case "SUBSCRIPTION_NOT_FOUND":
return new SubscriptionNotFoundError(
errorBody.message,
body,
errorBody.details?.subscriptionId || "unknown",
errorBody.details?.eventStoreId || "unknown"
);
case "EVENT_STORE_ALREADY_EXISTS":
return new EventStoreAlreadyExistsError(
errorBody.message,
body,
errorBody.details?.name || "unknown"
);
case "TIMEOUT_ERROR":
return new TimeoutError(
errorBody.message,
body,
errorBody.details?.timeoutMs || 0,
errorBody.details?.operation || "unknown"
);
case "STORAGE_ERROR":
return new StorageError(
errorBody.message,
body,
errorBody.details?.storageType || "sqlite",
errorBody.details?.operation || "read"
);
case "STREAM_NOT_FOUND":
return new StreamNotFoundError(
errorBody.message,
body,
errorBody.details?.streamId || "unknown",
errorBody.details?.eventStoreId || "unknown"
);
case "SERIALIZATION_ERROR":
return new SerializationError(
errorBody.message,
body,
errorBody.details?.dataType || "event",
errorBody.details?.originalData
);
case "INVALID_CALLBACK_URL":
case "INVALID_EVENT_FILTER":
case "INVALID_SUBSCRIPTION_CONFIG":
return new InvalidSubscriptionConfigError(
errorBody.message,
body,
errorBody.error
);
case "INTERNAL_SERVER_ERROR":
return new InternalServerError(errorBody.message, body);
}
}
}
switch (status) {
case 401:
return new AuthenticationError("Authentication failed", body);
case 403:
return new AuthorizationError("Authorization failed", body);
case 404:
if (typeof body === "object" && body !== null) {
const errorBody = body;
if (errorBody.error === "STREAM_NOT_FOUND") {
return new StreamNotFoundError(
"Stream not found",
body,
"unknown",
"unknown"
);
}
}
return new EventStoreNotFoundError(
"Resource not found",
body,
"unknown"
);
case 408:
return new TimeoutError("Request timeout", body, 0, "unknown");
case 409:
return new VersionConflictError("Conflict detected", body, -1, -1);
case 429: {
const retryAfter = typeof body === "object" && body !== null ? body.retryAfter : void 0;
return new RateLimitError("Rate limit exceeded", body, retryAfter);
}
default:
return new InternalServerError(`HTTP Error: ${status}`, body);
}
}
/**
* Type guard for version conflict error responses
*/
isVersionConflictError(body) {
return body.error === "Version conflict" && typeof body.details === "string" && typeof body.currentVersion === "number" && typeof body.expectedVersion === "number";
}
/**
* Type guard for validation error responses
*/
isValidationError(body) {
return body.error === "Invalid request body" && Array.isArray(body.details) && body.details.every(
(detail) => typeof detail === "object" && detail !== null && typeof detail.code === "string" && typeof detail.message === "string" && Array.isArray(detail.path)
);
}
/**
* Type guard for general error responses
*/
isGeneralError(body) {
return typeof body.error === "string" && typeof body.message === "string";
}
};
__name(_HttpClient, "HttpClient");
var HttpClient = _HttpClient;
// src/deltabase.ts
var _DeltaBase = class _DeltaBase {
/**
* Creates a new DeltaBase client instance.
*
* @param config - Configuration options for connecting to the Delta-Base platform
* @throws {Error} If neither apiKey nor headers are provided in the configuration
*
* @example
* ```typescript
* // Create with API key (standard authentication)
* const client = new DeltaBase({ apiKey: "your-api-key" });
*
* // Create with custom URL (production)
* const prodClient = new DeltaBase({
* baseUrl: "https://api.delta-base.com",
* apiKey: "your-api-key"
* });
*
* // Create with custom headers (internal service authentication)
* const internalClient = new DeltaBase({
* baseUrl: "https://api.delta-base.com",
* headers: {
* 'X-Deltabase-Internal-Service': 'auth-service',
* 'X-Deltabase-Internal-Token': 'internal-token'
* }
* });
* ```
*/
constructor(config) {
if (!config.apiKey && !config.headers) {
throw new Error("Either apiKey or headers must be provided");
}
const httpConfig = {
baseUrl: config.baseUrl || "http://localhost:8787",
apiKey: config.apiKey,
headers: config.headers
};
this.http = new HttpClient(httpConfig);
}
/**
* Get a ManagementClient for managing event stores
*
* @returns A ManagementClient instance for creating and managing event stores
* @example
* ```typescript
* const management = client.getManagement();
* const eventStore = await management.createEventStore({
* name: "my-event-store"
* });
* ```
*/
getManagement() {
return new ManagementClient(this.http);
}
/**
* Get an EventStore client for a specific event store
*
* @param eventStoreName The name of the event store to connect to
* @returns An EventStore client instance configured for the specified event store
* @example
* ```typescript
* const eventStore = client.getEventStore("my-event-store");
* await eventStore.appendToStream("user-123", [
* { type: "UserCreated", data: { userId: "123", name: "John" } }
* ]);
* ```
*/
getEventStore(eventStoreName) {
return new EventStore(this.http, eventStoreName);
}
/**
* Get an EventBus client for a specific event store
*
* @param eventStoreName The name of the event store to manage subscriptions for
* @returns An EventBus client instance configured for the specified event store
* @example
* ```typescript
* const eventBus = client.getEventBus("my-event-store");
* const subscription = await eventBus.createSubscription({
* name: "user-events",
* eventFilter: "user.*"
* });
* ```
*/
getEventBus(eventStoreName) {
return new EventBus(this.http, eventStoreName);
}
};
__name(_DeltaBase, "DeltaBase");
var DeltaBase = _DeltaBase;
export { DeltaBase, EventBus, EventStore, HttpClient, ManagementClient, SubscriberType, SubscriptionStatus };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map