/** * Base Frontend Domain Service * * Extends BaseDomainService with common frontend patterns: * - **Primary/Read Store Pattern**: Services have ONE primary store (mutate) + multiple read stores (read-only) * - **Automatic Store Injection**: Stores injected by ServiceRegistry from root store * - **Automatic Loading State**: Managed via setStoresLoading() on primary store * - **Generic CRUD Operations**: With fetchers from @plyaz/api * - **Event Emission**: Typed event payloads via CoreEventManager * - **Automatic DTO Mapping**: Via mapper classes * * ## Store Architecture (NEW) * * ### Primary Store (Mutate) * - Service has ONE primary store defined by `store: 'campaigns'` config * - Can call: setData(), updateData(), setLoading() * - syncToStores() syncs ONLY to primary store * - Access via: this.primaryStore * * ### Read Stores (Read-Only) * - Service can read from multiple stores: `readStores: ['users', 'error', 'featureFlags']` * - Can read state but should NOT call mutation methods * - Used for cross-domain data access (e.g., campaign service reading user data) * - Access via: this.getReadStore('users') * * ### All Stores from Root Store * - All stores (domain + global) are slices of ONE root Zustand store * - Automatically reactive - changes propagate to subscribers (React components) * - Error/featureFlags are built-in, domain stores added dynamically * * Frontend domain services should extend this class instead of BaseDomainService * to get automatic store integration and CRUD capabilities. * * All generic type parameters for full type safety: * - TConfig: Service configuration type * - TStore: Store interface type * - TData: Data type for store sync * - TEntity: Domain entity type * - TResponseDTO: API response DTO type (snake_case) * - TCreateDTO: Create request DTO type * - TPatchDTO: Patch request DTO type (partial update) * - TQueryDTO: Query/filter DTO type for fetchAll * - TStoreState: Serializable store state type * - TMapper: Mapper instance type * - TValidator: Optional validator type * * @example * ```typescript * class MyFrontendService extends BaseFrontendDomainService< * MyConfig, * MyStore, * MyStoreData, * MyEntity, * MyResponseDTO, * CreateMyDTO, * PatchMyDTO, * QueryMyDTO, * MyStoreState, * MyMapper * > { * protected eventPrefix = 'myDomain'; * protected mapper: MyMapper; * * constructor(config: MyConfig) { * super({ ... mapperClass: MyMapperClass }); * this.mapper = new MyMapperClass(); * } * * // All CRUD methods inherited! * // - fetchAll(query?) * // - fetchById(id) * // - create(data) * // - update(id, data) * // - delete(id) * } * ``` */ import { BaseDomainService } from './BaseDomainService'; import { CorePackageError } from '@plyaz/errors'; import type { RootStoreSlice, StreamStoreSlice, StreamMessageHandler, StreamMessageFilter } from '@plyaz/types/store'; import type { StreamChannel, CoreStreamHandlerIds, CoreStreamHandlerDeclaration, CoreEntityCreatingPayload, CoreEntityCreatedPayload, CoreEntityPatchingPayload, CoreEntityPatchedPayload, CoreEntityDeletingPayload, CoreEntityDeletedPayload, CoreEntityErrorPayload, CoreEntityCompletePayload } from '@plyaz/types/core'; import type { CoreBaseFrontendStore, CoreBaseFrontendServiceConfig, CoreBaseFrontendServiceConstructorConfig, CoreBaseMapperInstance, CoreBaseValidatorInstance, CoreStoreHandlers, CoreOptimisticUpdateConfig } from '@plyaz/types/core'; import type { UploadFileRequest, UploadFileResponse, UploadFilesRequest, UploadFilesResponse, GenerateDocumentRequest, GenerateDocumentResponse, ServiceOptions } from '@plyaz/types/api'; /** * Base type for frontend event payloads * All frontend event payloads should extend this */ type BaseFrontendEventPayload = CoreEntityCreatingPayload | CoreEntityCreatedPayload | CoreEntityPatchingPayload | CoreEntityPatchedPayload | CoreEntityDeletingPayload | CoreEntityDeletedPayload | CoreEntityErrorPayload | CoreEntityCompletePayload | Record; /** * Abstract base class for frontend domain services with store integration and CRUD operations. * * Extends BaseDomainService with: * - Multi-store connection management * - Automatic sync to connected stores * - Loading/error state propagation * - Generic CRUD operations (fetchAll, fetchById, create, update, delete) * - Event emission with typed payloads * - Automatic DTO mapping via mapper * * @typeParam TConfig - Service configuration type * @typeParam TStore - Store interface type * @typeParam TData - Data type for store sync * @typeParam TEntity - Domain entity type * @typeParam TResponseDTO - API response DTO type * @typeParam TCreateDTO - Create request DTO type * @typeParam TPatchDTO - Patch request DTO type * @typeParam TQueryDTO - Query/filter DTO type for fetchAll (default: unknown) * @typeParam TStoreState - Serializable store state type * @typeParam TMapper - Mapper instance type * @typeParam TValidator - Optional validator type * @typeParam TDeleteResult - Delete operation result type (default: void for backwards compatibility) */ export declare abstract class BaseFrontendDomainService, TStore extends CoreBaseFrontendStore, TData = Record, TEntity = unknown, TResponseDTO = unknown, TCreateDTO = unknown, TPatchDTO extends Partial = Partial, TQueryDTO = unknown, TStoreState = TEntity, TMapper extends CoreBaseMapperInstance = CoreBaseMapperInstance, TValidator extends CoreBaseValidatorInstance = CoreBaseValidatorInstance, TDeleteResult = void> extends BaseDomainService { /** * Event prefix for all events emitted by this service * E.g., 'example' → emits 'example:creating', 'example:created', etc. * * Required by subclasses */ protected abstract eventPrefix: string; /** Primary store - the main store this service can mutate */ private _primaryStore; /** * Primary store key - resolved from (in order of precedence): * 1. serviceConfig.store (explicit config) * 2. Static defaultStoreKey on class * 3. undefined (no store integration) * * Override in subclass or set via config. * @example protected primaryStoreKey = STORE_KEYS.EXAMPLE; */ protected primaryStoreKey?: string; /** Read-only stores - can read but not mutate */ private readonly _readStores; /** * Read-only store keys - always includes error and featureFlags by default * Override in subclass to add additional read stores * @example protected readStoreKeys = [...super.readStoreKeys, STORE_KEYS.CAMPAIGNS]; */ protected readStoreKeys: string[]; /** Store handlers for custom store synchronization */ protected readonly storeHandlers?: CoreStoreHandlers; /** Optimistic update configuration */ protected readonly optimisticConfig?: CoreOptimisticUpdateConfig; /** Stack of previous states for rollback (LIFO) */ private readonly _rollbackStack; /** Maximum rollback stack size */ private readonly _maxRollbackStackSize; /** Polling timer reference */ private _pollingTimer; /** * Registered stream handler IDs for auto-cleanup on dispose. * IDs are added by registerStreamHandler() and removed on dispose(). */ private readonly _registeredStreamHandlerIds; /** * Flag to track if static stream handlers are registered. * Subclasses should set this in their registerStreamHandlers() method. */ protected static _streamHandlersRegistered: boolean; /** * Stream handler IDs for auto-cleanup. * Subclasses should define their handler IDs as a readonly object. * * @example * ```typescript * protected static readonly STREAM_HANDLER_IDS: CoreStreamHandlerIds< * 'UPLOAD_PROGRESS' | 'DOWNLOAD_PROGRESS' * > = { * UPLOAD_PROGRESS: 'files:upload-progress', * DOWNLOAD_PROGRESS: 'files:download-progress', * } as const; * ``` */ protected static readonly STREAM_HANDLER_IDS: CoreStreamHandlerIds; /** * Declarative stream handlers configuration. * Define handlers as an array - they will be auto-registered by registerStreamHandlersFromConfig(). * * Each handler config includes: * - id: Unique handler ID (should match a key in STREAM_HANDLER_IDS) * - channels: Channels to listen to * - events: Event types to listen to * - priority: Handler priority (higher = called first) * - handler: Handler function receiving typed message and store getter * * @example * ```typescript * protected static readonly STREAM_HANDLERS: CoreStreamHandlerDeclaration[] = [ * { * id: 'files:upload-progress', * channels: ['uploads', 'upload:'], * events: ['progress', 'upload:progress'], * priority: 100, * handler: (message, getStore) => { * const filesStore = getStore()?.files; * filesStore?.setUploadProgress(message.data.fileId, { ... }); * }, * }, * ]; * ``` */ protected static readonly STREAM_HANDLERS: CoreStreamHandlerDeclaration[]; constructor(config: CoreBaseFrontendServiceConstructorConfig); /** * Auto-resolve apiClientConfig from serviceConfig.apiBasePath if not explicitly provided. * This reduces boilerplate in child services - they only need to set apiBasePath in config. * * Priority: * 1. Explicit apiClientConfig.baseURL (full control) * 2. serviceConfig.apiBasePath (auto-constructed, merged with injected options) * 3. undefined (no API client) * * When auto-constructing, merges injected API options (headers, timeout, etc.) * with the service's apiBasePath as baseURL. */ protected static resolveApiClientConfig(config: T): T; /** * Initialize service and wait for API client to be ready. * Call this in child service's static create() method after constructing the instance. * * @param service - The service instance to initialize * @returns The initialized service (same instance, for chaining) * * @example * ```typescript * static async create(config, options): Promise { * const service = new MyService(config, options); * return this.initializeService(service); * } * ``` */ protected static initializeService; }>(service: T): Promise; /** * Ensure service is ready for operations. * Checks enabled/available status AND waits for API client initialization. * Call this at the start of all CRUD methods. * * @throws CorePackageError if service is not enabled or available */ protected ensureReady(): Promise; /** * Get the number of connected stores (primary + read stores). */ get connectedStoreCount(): number; /** * Check if any stores are connected (primary or read). */ get hasConnectedStores(): boolean; /** * Get primary store (protected). * This is the store the service can mutate. */ protected get primaryStore(): TStore | null; /** * Get a read-only store by key (protected). * Can read state but should not call mutation methods. * * @param key - Store key (type-safe) * @returns Store slice or undefined (auto-typed based on key) */ protected getReadStore(key: K): RootStoreSlice[K] | undefined; /** * Get a specific store slice by key (protected). * * **Namespaced Root Store Architecture:** * - All stores are slices merged into ONE Zustand root store * - Each slice is namespaced (e.g., store.example, store.errors) * - This returns the SPECIFIC slice for the given key * - Type-safe: only valid store keys allowed * * **Usage Guidelines:** * - Can mutate stores that are NOT in readStoreKeys * - Should NOT mutate stores in readStoreKeys (errors, featureFlags by default) * - Useful for cross-domain updates (e.g., campaign service updating user stats) * * @param key - Store key (type-safe: 'example' | 'errors' | 'featureFlags') * @returns Specific store slice (fully typed based on key) * * @example * ```typescript * // In CampaignService * protected async afterCreate(id: string, campaign: Campaign) { * // Update primary store (campaigns) * this.primaryStore.addItem(campaign); * * // Check feature flag (read-only) - auto-typed as FeatureFlagStoreSlice * const flagsSlice = this.getStore('featureFlags'); // ✅ Type-safe! * if (flagsSlice?.flags?.['update-user-stats']) { * // Update user store (cross-domain mutation - OK since not read-only) * const userSlice = this.getStore('users'); // ✅ Auto-typed! * userSlice?.incrementCampaignCount(campaign.userId); * } * } * ``` */ protected getStore(key: K): RootStoreSlice[K] | undefined; /** * Sync data to primary store only (read stores are NOT updated). * Uses setData (replace) or updateData (merge) based on `replace` parameter. * If custom store handlers are configured, uses those instead of default behavior. * * @param data - Data to sync to primary store * @param replace - If true, calls setData; if false, calls updateData */ protected syncToStores(data: TData, replace?: boolean): void; /** * Set loading state on primary store only. * Read stores are NOT affected. * * Automatically called by CRUD methods before and after API calls. * * @param isLoading - Loading state */ protected setStoresLoading(isLoading: boolean): void; /** * Add entity to store (for create operations). * Priority: storeHandlers.addData > store.addData * * @param entity - Entity to add * @returns The converted store state (for reuse in event payloads) */ protected addEntityToStore(entity: TEntity): TStoreState; /** * Update entity in store (for update operations). * Priority: storeHandlers.updateDataById > store.updateDataById * * @param id - Entity ID * @param entity - Updated entity * @returns The converted store state (for reuse in event payloads) */ protected updateEntityInStore(id: string, entity: TEntity): TStoreState; /** * Sync multiple entities to store. * Uses storeDataKey config to wrap entities in the correct structure. * * @param entities - Entities to sync * @returns Array of converted store states */ protected syncEntitiesToStore(entities: TEntity[]): TStoreState[]; /** * Build a nested object from a dot-notation key path. * @example buildNestedObject('items', data) => { items: data } * @example buildNestedObject('nested.items', data) => { nested: { items: data } } */ private buildNestedObject; /** * Remove entity from store by ID. * Priority: storeHandlers.removeData > store.removeData * * @param id - Entity ID to remove */ protected removeEntityFromStore(id: string): void; /** * Check if optimistic updates are enabled for an operation. */ protected isOptimisticEnabled(operation: 'create' | 'update' | 'delete'): boolean; /** * Save current store state for potential rollback. * Call this BEFORE making optimistic changes. */ protected saveStateForRollback(operation: string): void; /** * Rollback to previous state on error. * Call this when an optimistic update fails. */ protected rollbackState(operation: string, error: Error): void; /** * Clear rollback state after successful operation. * Call this when API succeeds. */ protected clearRollbackState(operation: string): void; /** * Resolve conflict between optimistic and server state. */ protected resolveConflict(optimistic: T, server: T): T; /** * Unwrap response data if responseDataKey is configured. * * Handles wrapped API responses (e.g., SuccessResponseStandard) by extracting * the actual data from a nested property. Supports nested keys with dot notation. * * @param data - Raw response data from fetcher * @returns Unwrapped data or original data if no key configured * * @example * ```typescript * // Simple key: responseDataKey: 'data' * // Input: { success: true, message: '...', data: [...], codeStatus: 200 } * // Output: [...] * * // Nested key: responseDataKey: 'data.items' * // Input: { data: { items: [...], total: 100 } } * // Output: [...] * ``` */ protected unwrapResponseData(data: unknown): T; /** * Check if a response indicates success. * * Uses `responseSuccessKey` config if set, otherwise auto-detects from: * - `isSuccess` (internal format) * - `success` (alternative internal format) * - `ok` (fetchff/fetch standard) * - HTTP status code 200-299 * * Supports nested keys via dot notation (e.g., `'meta.success'`). * * @param response - Response object from fetcher * @returns true if response indicates success */ protected isResponseSuccess(response: unknown): boolean; /** * Extract error from API response. * * Uses `responseErrorKey` config if set, otherwise auto-detects from: * - `error` (common format) * - `errors` (array format, e.g., GraphQL) * * Supports nested keys via dot notation (e.g., `'meta.error'`). * * @param response - Response object from fetcher * @returns Extracted error or undefined if not found */ protected extractResponseError(response: unknown): unknown; /** * Fetch all entities from API * * Flow: * 1. Assert service is ready * 2. Check fetcher is available * 3. Set stores loading * 4. Emit 'fetching' event * 5. Lifecycle hook: beforeFetchAll * 6. Call fetcher (from @plyaz/api) * 7. Map response DTOs to domain entities * 8. Lifecycle hook: afterFetchAll * 9. Emit 'fetched' event * 10. Return entities * * Error Handling: * - On API error: wrap and emit error event * - Set error on all stores * - Re-throw wrapped error * * @param query - Optional query/filter parameters (e.g., { status: 'active' }) * @param options - Optional service options for fetcher * @returns Promise resolving to array of entities * @throws CorePackageError if fetcher not configured or API call fails * * @example * ```typescript * // Fetch all entities * const allItems = await service.fetchAll(); * * // Fetch with filters * const activeItems = await service.fetchAll({ status: 'active' }); * * // Fetch with pagination * const pagedItems = await service.fetchAll({ page: 2, limit: 10 }); * ``` */ fetchAll(query?: Partial, options?: unknown): Promise; /** * Fetch single entity by ID from API * * @param id - Entity ID * @param options - Optional service options for fetcher * @returns Entity or null if not found * @throws CorePackageError if fetcher not configured or API call fails */ fetchById(id: TInput, options?: TOptions): Promise; /** * Create new entity * * Flow: * 1. Assert service is ready * 2. Check fetcher is available * 3. Set stores loading * 4. Emit 'creating' event * 5. Lifecycle hook: beforeCreate * 6. Map input data to create DTO (if mapper available) * 7. [Optimistic] Save state and add to store BEFORE API call * 8. Call create fetcher (from @plyaz/api) * 9. Map response DTO to domain entity * 10. [Optimistic] Resolve conflicts if needed * 11. Lifecycle hook: afterCreate * 12. Emit 'created' event * 13. Return entity * * @param data - Create data (can be partial domain entity or DTO) * @param options - Optional service options for fetcher * @returns Created entity * @throws CorePackageError if fetcher not configured or creation fails */ create(data: TInput, options?: TOptions): Promise; /** * Update entity (partial update - PATCH) * * @param id - Entity ID * @param data - Partial update data * @param options - Optional service options for fetcher * @returns Updated entity * @throws CorePackageError if fetcher not configured or update fails */ update(id: string, data: TInput, options?: TOptions): Promise; /** * Delete entity * * @param id - Entity ID * @param options - Optional service options for fetcher * @returns Delete operation result (type specified by TDeleteResult generic) * @throws CorePackageError if fetcher not configured or deletion fails */ delete(id: TInput, options?: TOptions): Promise; /** * Emit event with automatic prefix * Uses CoreEventManager under the hood * * @param event - Event name (without prefix) * @param payload - Event payload * * @example * ```typescript * // If eventPrefix is 'example', this emits 'example:created' * this.emitEvent('created', { entity, storeState }); * ``` */ protected emitEvent(event: string, payload: TPayload): void; /** * Called before fetchAll API call * Override to add custom logic (e.g., cache check, validate query) * * @param query - Optional query/filter parameters */ protected beforeFetchAll?(query?: Partial): Promise; /** * Called after fetchAll success * Override to add custom logic (e.g., cache set, transform entities) * * @param entities - Fetched entities * @param query - Optional query/filter parameters that were used */ protected afterFetchAll?(entities: TEntity[], query?: Partial): Promise; /** * Called before fetchById API call */ protected beforeFetchById?(id: TInput): Promise; /** * Called after fetchById success */ protected afterFetchById?(entity: TEntity): Promise; /** * Called before create API call * Use this to modify data or validate before sending to API */ protected beforeCreate?(data: TInput): Promise; /** * Called after create success * Use this to update stores, show notifications, etc. */ protected afterCreate?(entity: TEntity): Promise; /** * Called before update API call */ protected beforeUpdate?(id: string, data: TInput): Promise; /** * Called after update success */ protected afterUpdate?(id: string, entity: TEntity): Promise; /** * Called before delete API call */ protected beforeDelete?(id: TInput): Promise; /** * Called after delete success */ protected afterDelete?(id: TInput): Promise; /** * Called before OPTIONS request * * Use for custom logic before checking allowed methods/CORS headers. * * @param resource - Optional resource identifier or URL */ protected beforeOptions?(resource?: TInput): Promise; /** * Called after OPTIONS request success * * Use for processing allowed methods/headers or logging. * * @param resource - Optional resource identifier or URL * @param result - The OPTIONS response (allowed methods, CORS headers, etc.) */ protected afterOptions?(resource: TInput | undefined, result: unknown): Promise; /** * Called before HEAD request * * Use for custom logic before checking resource existence/headers. * * @param resource - Optional resource identifier or URL */ protected beforeHead?(resource?: TInput): Promise; /** * Called after HEAD request success * * Use for processing response headers or logging. * Note: HEAD requests return no body, only headers. * * @param resource - Optional resource identifier or URL * @param headers - Response headers (if available) */ protected afterHead?(resource: TInput | undefined, headers?: unknown): Promise; /** * Check if polling is currently active. */ get isPolling(): boolean; /** * Start polling for updates. * Uses provided interval, falls back to `config.pollingInterval`, then DEFAULT_POLLING_INTERVAL_MS (30s). * Polling calls fetchAll() at the configured interval. * * Override in subclass for custom polling behavior. * * @param intervalMs - Optional polling interval in milliseconds. Overrides config.pollingInterval. * * @example * ```typescript * // Start polling with default/config interval * service.startPolling(); * * // Start polling with custom interval (5 seconds) * service.startPolling(5000); * * // Or override in subclass for custom behavior * startPolling(intervalMs?: number): void { * super.startPolling(intervalMs); * // Custom polling logic * } * ``` */ startPolling(intervalMs?: number): void; /** * Stop polling. * Safe to call even if polling is not active. */ stopPolling(): void; /** * Dispose service and cleanup resources. * Stops polling and performs base cleanup. * * Subclasses should override and call super.dispose() to ensure cleanup. * * @example * ```typescript * dispose(): void { * // Custom cleanup * this.customCleanup(); * // Always call super * super.dispose(); * } * ``` */ dispose(): void; /** * Check if service is available. * Override in subclass for custom availability checks. * * @returns true if service is enabled and in a browser environment */ isAvailable(): boolean; /** * Wrap error in CorePackageError with context * * @param error - Original error * @param operation - Operation that failed * @param context - Additional context * @returns Wrapped error */ protected wrapError(error: unknown, operation: string, context?: Record): CorePackageError; /** * Track a frontend operation with observability (span + metrics). * Wraps an operation with automatic span creation, duration recording, and error tracking. * * @param operation - Operation name (fetchAll, fetchById, create, delete) * @param entityId - Optional entity ID for context * @param fn - The operation function to execute * @returns Result of the operation * * @example * ```typescript * const entity = await this.trackOperation('create', undefined, async () => { * return await this.config.fetchers.create(data); * }); * ``` */ protected trackOperation(operation: 'fetchAll' | 'fetchById' | 'create' | 'patch' | 'delete', entityId: string | undefined, fn: () => Promise): Promise; /** * Record operation metrics (duration and count). * Protected to allow subclasses to record metrics for custom operations. */ protected recordOperationMetrics(operation: string, duration: number, success: boolean): Promise; /** * Upload a file via backend API. * * Uses the uploadFile fetcher from @plyaz/api which calls POST /upload endpoint. * On the backend, this uses @plyaz/storage for actual file operations. * * Supports multiple upload modes: * - **Direct upload**: Provide `base64` content with `mimeType` * - **Template generation**: Provide `templateId` with `templateData` * * @param data - Upload parameters (base64/templateId, category, entityType, entityId) * @param options - Optional service options (apiConfig, etc.) * @returns Promise resolving to upload result with file metadata and URL */ uploadFile(data: UploadFileRequest, options?: ServiceOptions): Promise; /** * Lifecycle hook: called before file upload. * Override in subclass for custom pre-upload logic. */ protected beforeUploadFile?(data: UploadFileRequest): Promise; /** * Lifecycle hook: called after file upload. * Override in subclass for custom post-upload logic. */ protected afterUploadFile?(result: UploadFileResponse): Promise; /** * Upload multiple files via backend API. * * Uses the uploadFiles fetcher from @plyaz/api which calls POST /upload/bulk endpoint. * On the backend, this uses @plyaz/storage for actual file operations. * * @param data - Upload parameters (files array with base64 content) * @param options - Optional service options (apiConfig, etc.) * @returns Promise resolving to bulk upload result with individual file results */ uploadFiles(data: UploadFilesRequest, options?: ServiceOptions): Promise; /** * Lifecycle hook: called before bulk file upload. * Override in subclass for custom pre-upload logic. */ protected beforeUploadFiles?(data: UploadFilesRequest): Promise; /** * Lifecycle hook: called after bulk file upload. * Override in subclass for custom post-upload logic. */ protected afterUploadFiles?(result: UploadFilesResponse): Promise; /** * Generate a document via backend API (NO upload - returns buffer). * * Uses the generateDocument fetcher from @plyaz/api which calls POST /generate-document. * On the backend, this uses @plyaz/storage template engine. * Returns the document as base64 buffer. Use uploadFile() for generation + upload. * * @param data - Generation parameters (templateId, templateData, outputFormat, etc.) * @param options - Optional service options (apiConfig, etc.) * @returns Promise resolving to generation result { buffer, size } */ generateFile(data: GenerateDocumentRequest, options?: ServiceOptions): Promise; /** * Lifecycle hook: called before document generation. * Override in subclass for custom pre-generation logic. */ protected beforeGenerateFile?(data: GenerateDocumentRequest): Promise; /** * Lifecycle hook: called after document generation. * Override in subclass for custom post-generation logic. */ protected afterGenerateFile?(result: GenerateDocumentResponse): Promise; /** * Get the stream store slice for real-time streaming. * Used for subscribing to channels, registering handlers, and accessing stream state. * * @returns Stream store slice or undefined if not available */ protected get streamStore(): StreamStoreSlice | undefined; /** * Check if stream store is available. */ protected get hasStreamStore(): boolean; /** * Subscribe to a stream channel for real-time updates. * Typically used for entity-specific progress (e.g., upload progress for a file). * * @param channel - Channel to subscribe to (e.g., 'upload:abc123') * * @example * ```typescript * // Subscribe to upload progress for a specific file * this.subscribeToChannel(`upload:${fileId}`); * * // Subscribe to all uploads broadcast channel * this.subscribeToChannel('uploads'); * ``` */ protected subscribeToChannel(channel: StreamChannel): void; /** * Subscribe to multiple stream channels at once. * * @param channels - Array of channels to subscribe to */ protected subscribeToChannels(channels: StreamChannel[]): void; /** * Unsubscribe from a stream channel. * * @param channel - Channel to unsubscribe from */ protected unsubscribeFromChannel(channel: StreamChannel): void; /** * Subscribe to entity-specific progress channel. * Convenience method that constructs the channel name from prefix and entity ID. * * @param prefix - Channel prefix (e.g., 'upload', 'download', 'generate') * @param entityId - Entity ID (e.g., file ID) * * @example * ```typescript * // Subscribe to upload progress for file 'abc123' * this.subscribeToEntityProgress('upload', 'abc123'); * // Subscribes to channel: 'upload:abc123' * ``` */ protected subscribeToEntityProgress(prefix: string, entityId: string): void; /** * Register a message handler for this domain. * The handler will receive stream messages matching the specified filters. * * @param handler - Handler function to process stream messages * @param options - Handler options (channels, events, priority) * @returns Handler ID (use for unregistering) * * @example * ```typescript * // Register handler for upload progress * const handlerId = this.registerStreamHandler( * (message) => { * if (message.event === 'upload:progress') { * this.updateUploadProgress(message.data); * } * }, * { * channels: ['upload:'], * events: ['upload:progress', 'upload:completed', 'upload:failed'], * } * ); * ``` */ protected registerStreamHandler(handler: StreamMessageHandler, options?: { channels?: string[]; events?: string[]; priority?: number; }): string; /** * Unregister a stream message handler. * * @param handlerId - Handler ID returned by registerStreamHandler */ protected unregisterStreamHandler(handlerId: string): void; /** * Unregister all stream handlers registered by this service. * Called automatically by dispose(), but can be called manually if needed. */ protected unregisterAllStreamHandlers(): void; /** * Get stream messages filtered by domain scope. * Convenience method that filters messages for this service's event prefix. * * @param filter - Additional filter criteria * @returns Filtered stream messages * * @example * ```typescript * // Get recent upload progress messages for this domain * const messages = this.getDomainStreamMessages({ * subtype: 'upload', * limit: 10, * }); * ``` */ protected getDomainStreamMessages(filter?: StreamMessageFilter): unknown[]; /** * Check if connected to stream server. */ protected get isStreamConnected(): boolean; /** * Get the stream store from Core.rootStore. * Used by static methods that don't have access to instance. */ protected static getStreamStore(): StreamStoreSlice | undefined; /** * Cleanup all stream handlers using STREAM_HANDLER_IDS. * Called by the cleanup function returned from registerStreamHandlers(). * * @param handlerIds - Object with handler IDs to unregister * @param onComplete - Optional callback after cleanup */ protected static cleanupStreamHandlers(handlerIds: Record, onComplete?: () => void): void; /** * Register stream handlers from STREAM_HANDLERS declarative config. * * Call this from your registerStreamHandlers() override to auto-register * all handlers defined in STREAM_HANDLERS. * * @param handlers - Array of handler declarations (defaults to this.STREAM_HANDLERS) * @param verbose - Enable verbose logging * * @example * ```typescript * static override registerStreamHandlers(verbose?: boolean): () => void { * if (this._streamHandlersRegistered) return () => {}; * * // Auto-register from declarative config * this.registerStreamHandlersFromConfig(this.STREAM_HANDLERS, verbose); * * // Optional: Add any custom logic here * * this._streamHandlersRegistered = true; * return () => this.cleanupStreamHandlers(this.STREAM_HANDLER_IDS); * } * ``` */ protected static registerStreamHandlersFromConfig(handlers: CoreStreamHandlerDeclaration[], verbose?: boolean): void; /** * Register stream handlers for this domain service. * * Subclasses should override this static method to register their stream handlers. * Use STREAM_HANDLER_IDS to define handler IDs and cleanupStreamHandlers() for cleanup. * * @param _verbose - Enable verbose logging * @returns Cleanup function to unregister all handlers * * @example * ```typescript * class MyDomainService extends BaseFrontendDomainService<...> { * protected static readonly STREAM_HANDLER_IDS = { * MY_EVENT: 'my-domain:my-event', * } as const; * * static override registerStreamHandlers(verbose?: boolean): () => void { * if (this._streamHandlersRegistered) return () => {}; * * const streamStore = this.getStreamStore(); * if (!streamStore) return () => {}; * * streamStore.registerHandler( * this.STREAM_HANDLER_IDS.MY_EVENT, * (message) => { // Handle message.data (typed!) }, * { channels: ['my-channel'], events: ['my-event'] } * ); * * this._streamHandlersRegistered = true; * return () => this.cleanupStreamHandlers(this.STREAM_HANDLER_IDS); * } * } * ``` */ static registerStreamHandlers(verbose?: boolean): () => void; } export {}; //# sourceMappingURL=BaseFrontendDomainService.d.ts.map