bc-webclient-mcp
Version:
Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server
246 lines • 8.4 kB
TypeScript
/**
* PageStateManager - Message-Driven State Reducer
*
* Implements the reducer pattern for updating PageState based on BC protocol messages.
* All operations MUTATE state in-place (not Redux/immutable).
*
* See: PageState.md v2.0 for full architecture documentation
*
* Key Responsibilities:
* - Initialize PageState from LoadForm response
* - Apply BC protocol messages to update state
* - Handle row updates with upsert pattern
* - Track bookmark changes (temp → permanent)
* - Enrich column metadata from RCC messages
* - Handle validation errors and dialogs
*/
import { PageState, RepeaterState, BcHandlerMessage, RowLookupResult } from './page-state.js';
import type { LogicalForm } from '../types/bc-types.js';
/** Validation error structure */
interface ValidationErrorInfo {
controlPath?: string;
ControlReference?: {
controlPath?: string;
};
Message?: string;
message?: string;
}
/** Dialog message structure */
interface DialogInfo {
formId?: string;
Caption?: string;
IsModal?: boolean;
}
/**
* PageStateManager - Manages PageState lifecycle and updates
*
* CRITICAL: All methods MUTATE state in-place (not immutable)
* - Methods return PageState for chaining, but it's the SAME object
* - No new objects created, all Maps/Arrays modified in-place
* - Tools MUST NOT share PageState between sessions
*/
export declare class PageStateManager {
/**
* Initialize PageState from LoadForm response
*
* @param logicalForm - The LogicalForm from LoadForm response (parameters[1])
* @param pageId - BC page ID
* @param pageType - Page type (Card, List, Document)
* @returns Initialized PageState (with empty data, ready for messages)
*/
initFromLoadForm(logicalForm: LogicalForm, pageId: string, pageType: 'Card' | 'List' | 'Document'): PageState;
/**
* Apply handlers array to update state (MUTATES in-place)
*
* @param state - PageState to update
* @param handlers - Array of BC handler messages
* @returns Same PageState object (mutated)
*/
applyMessages(state: PageState, handlers: BcHandlerMessage[]): PageState;
/**
* Apply single handler to update state (MUTATES in-place)
*
* Routes handler to appropriate change processor based on handlerType
*
* @param state - PageState to update
* @param handler - BC handler message
* @returns Same PageState object (mutated)
*/
private applyMessage;
/**
* Apply individual change object (MUTATES in-place)
*
* Routes change to appropriate reducer based on change.t
*
* @param state - PageState to update
* @param change - Change object from handler.parameters[1]
* @returns Same PageState object (mutated)
*/
private applyChange;
/**
* Apply DataRefreshChange to update repeater rows (MUTATES in-place)
*
* DataRefreshChange contains row-level deltas (insert, update, delete, flush)
*
* CRITICAL:
* - BC uses DataRowUpdated for BOTH initial load and updates (upsert pattern)
* - Bookmark changes (temp → permanent) must be detected and handled
* - Deep merge values, never replace entire rows
*
* @param state - PageState to update
* @param change - DataRefreshChange object
* @returns Same PageState object (mutated)
*/
private applyDataRefreshChange;
/**
* Helper: Find column by index (CRITICAL for DataRefreshChange)
*
* BC sends row data with column indices, not keys
* We must map index → ColumnState → designName
*/
private findColumnByIndex;
/**
* UPSERT: Update or insert row (MUTATES repeater in-place)
*
* CRITICAL: BC uses DataRowUpdated for BOTH initial load and updates!
* - Always check if row exists first
* - Create new row if not found (initial load case)
* - Deep merge values if row exists (update case)
* - Handle bookmark changes (temp → permanent)
*
* @param repeater - RepeaterState to update
* @param change - DataRowInserted or DataRowUpdated object
* @param operation - 'insert' or 'update' (for isNew flag)
*/
private upsertRow;
/**
* Remap bookmark (temp → permanent) (MUTATES repeater in-place)
*
* When BC saves a new row, bookmark changes from temporary to permanent
* We must update both rows Map and rowOrder array
*
* @param repeater - RepeaterState to update
* @param oldBookmark - Temporary bookmark
* @param newBookmark - Permanent bookmark
*/
private remapBookmark;
/**
* Delete row (MUTATES repeater in-place)
*
* @param repeater - RepeaterState to update
* @param change - DataRowDeleted object
*/
private deleteRow;
/**
* Flush all rows (MUTATES repeater in-place)
*
* BC sends DataFlush to clear all rows (e.g., after filter change)
*
* @param repeater - RepeaterState to update
*/
private flushRows;
/**
* Apply RCC (Repeater Column Control) enrichment (MUTATES in-place)
*
* RCC messages provide TemplateControlPath for columns
* This is progressive enrichment - columns gain controlPaths over time
*
* @param state - PageState to update
* @param rcc - RepeaterColumnControl change object
* @returns Same PageState object (mutated)
*/
private applyRCCEnrichment;
/**
* Apply PropertyChanges (MUTATES in-place)
*
* PropertyChanges update field values, visibility, enabled state, etc.
*
* @param state - PageState to update
* @param change - PropertyChanges object
* @returns Same PageState object (mutated)
*/
private applyPropertyChanges;
/**
* Apply callback response properties (MUTATES in-place)
*
* @param state - PageState to update
* @param parameters - Callback parameters
* @returns Same PageState object (mutated)
*/
private applyCallbackResponse;
/**
* Apply cursor movement (MUTATES in-place)
*
* @param state - PageState to update
* @param change - CursorMove object
* @returns Same PageState object (mutated)
*/
private applyCursorMove;
/**
* Apply viewport changes (MUTATES in-place)
*
* @param state - PageState to update
* @param change - ViewportChange object
* @returns Same PageState object (mutated)
*/
private applyViewportChange;
/**
* Apply validation error (MUTATES in-place)
*
* @param state - PageState to update
* @param error - Validation error object
* @returns Same PageState object (mutated)
*/
applyValidationError(state: PageState, error: ValidationErrorInfo & {
scope?: string;
repeaterName?: string;
bookmark?: string;
fieldName?: string;
}): PageState;
/**
* Apply dialog message (MUTATES in-place)
*
* BC shows validation dialog - clear all pending operations
*
* @param state - PageState to update
* @param dialog - Dialog message object
* @returns Same PageState object (mutated)
*/
applyDialogMessage(state: PageState, dialog: DialogInfo & {
message?: string;
}): PageState;
/**
* Find repeater by controlPath
*/
private findRepeaterByPath;
/**
* Find repeater by formId
*/
private findRepeaterByFormId;
/**
* Find repeater name by controlPath
*/
private findRepeaterNameByPath;
/**
* Get row with virtualization awareness
*
* CRITICAL: Distinguish "Data Missing" from "Data Empty"
* - RowState: Row exists and is loaded
* - undefined: Row genuinely doesn't exist
* - 'NOT_LOADED': Row exists but is outside loaded viewport
*
* @param repeater - RepeaterState to query
* @param bookmark - Row bookmark
* @returns RowLookupResult (tri-state)
*/
getRow(repeater: RepeaterState, bookmark: string): RowLookupResult;
/**
* Get grid line count (CRITICAL: Use totalRowCount, not rows.size)
*
* @param repeater - RepeaterState to query
* @returns Full grid size (or loaded size if totalRowCount not set)
*/
getGridLineCount(repeater: RepeaterState): number;
}
export {};
//# sourceMappingURL=page-state-manager.d.ts.map