Type definitions and interfaces for MemberJunction's Interactive Components system, enabling dynamic, data-driven UI components with AI capabilities.
The @memberjunction/interactive-component-types package provides the foundational types and interfaces for building interactive components in MemberJunction. These components are designed to be dynamically generated, data-aware, and AI-enhanced, allowing for rich user experiences with minimal configuration.
graph TD
A["InteractiveComponentSpec
(Component Definition)"] --> B["ComponentInitFunction"]
B --> C["Props & Events"]
B --> D["SimpleDataContext"]
B --> E["SimpleMetadata"]
B --> F["SimpleRunView / RunQuery"]
B --> G["SimpleAITools"]
G --> H["ExecutePrompt
(LLM Calls)"]
G --> I["EmbedText
(Vector Embeddings)"]
G --> J["VectorService
(Similarity / KNN)"]
A --> K["ComponentObject
(Runtime Interface)"]
K --> L["Standard Methods
(print, refresh, isDirty)"]
K --> M["Custom Methods
(RegisterMethod)"]
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
style B fill:#7c5295,stroke:#563a6b,color:#fff
style G fill:#2d8659,stroke:#1a5c3a,color:#fff
style K fill:#b8762f,stroke:#8a5722,color:#fff
style H fill:#2d8659,stroke:#1a5c3a,color:#fff
style I fill:#2d8659,stroke:#1a5c3a,color:#fff
style J fill:#2d8659,stroke:#1a5c3a,color:#fff
npm install @memberjunction/interactive-component-types
Provides AI capabilities to interactive components:
interface SimpleAITools {
// Execute LLM prompts for qualitative analysis
ExecutePrompt: (params: SimpleExecutePromptParams) => Promise<SimpleExecutePromptResult>
// Generate vector embeddings for similarity calculations
EmbedText: (params: SimpleEmbedTextParams) => Promise<SimpleEmbedTextResult>
// Vector operations service for KNN, similarity scoring, etc.
VectorService: SimpleVectorService
}
Parameters for executing AI prompts within components:
interface SimpleExecutePromptParams {
// System prompt to set context
systemPrompt: string;
// Optional conversation history
messages?: Array<{message: string, role: 'user' | 'assistant'}>;
// Preferred models in order of preference
preferredModels?: string[];
// Model power selection: 'lowest' | 'medium' | 'highest'
modelPower?: 'lowest' | 'medium' | 'highest';
// Optional user context
contextUser?: UserInfo;
}
Parameters for generating text embeddings:
interface SimpleEmbedTextParams {
// Single string or array of strings to embed
textToEmbed: string | string[];
// Model size selection
modelSize: 'small' | 'medium';
// Optional user context
contextUser?: UserInfo;
}
Access to MemberJunction's metadata system:
interface SimpleMetadata {
// Array of all entity metadata
Entities: EntityInfo[];
// Get entity object for CRUD operations
GetEntityObject(entityName: string, contextUser?: UserInfo): Promise<BaseEntity>;
}
Execute database views dynamically:
interface SimpleRunView {
// Run a single view
RunView: (params: RunViewParams, contextUser?: UserInfo) => Promise<RunViewResult>
// Run multiple views in parallel
RunViews: (params: RunViewParams[], contextUser?: UserInfo) => Promise<RunViewResult[]>
}
Execute predefined queries:
interface SimpleRunQuery {
// Run a predefined query with parameters
RunQuery: (params: RunQueryParams, contextUser?: UserInfo) => Promise<RunQueryResult>
}
Interactive components receive initialization functions with access to all tools:
type ComponentInitFunction = (
props: ComponentInitProps,
events: ComponentEvents,
data: SimpleDataContext,
metadata: SimpleMetadata,
runView: SimpleRunView,
runQuery: SimpleRunQuery,
ai: SimpleAITools
) => Promise<void>;
// In your interactive component initialization
async function initComponent(props, events, data, metadata, runView, runQuery, ai) {
// Execute a prompt for data analysis
const analysisResult = await ai.ExecutePrompt({
systemPrompt: 'You are a data analyst. Analyze the provided dataset.',
messages: [
{ message: 'What are the key trends?', role: 'user' }
],
modelPower: 'medium'
});
if (analysisResult.success) {
console.log('Analysis:', analysisResult.result);
// Parse JSON if available
if (analysisResult.resultObject) {
const insights = analysisResult.resultObject;
// Use insights in your component
}
}
// Generate embeddings for similarity matching
const embedResult = await ai.EmbedText({
textToEmbed: ['Product A description', 'Product B description'],
modelSize: 'small'
});
// Use vector service for similarity calculations
const similarity = ai.VectorService.cosineSimilarity(
embedResult.result[0],
embedResult.result[1]
);
console.log('Product similarity:', similarity);
}
async function loadComponentData(props, events, data, metadata, runView) {
// Get entity metadata
const userEntity = metadata.Entities.find(e => e.Name === 'Users');
// Run a view to get data
const viewResult = await runView.RunView({
EntityName: 'Users',
ExtraFilter: "Status = 'Active'",
OrderBy: 'LastName, FirstName',
MaxRows: 100
});
if (viewResult.Success) {
const users = viewResult.Results;
// Process users data
}
// Create/update an entity
const newUser = await metadata.GetEntityObject('Users');
newUser.NewRecord();
newUser.Set('FirstName', 'John');
newUser.Set('LastName', 'Doe');
await newUser.Save();
}
async function loadDashboardData(props, events, data, metadata, runView) {
// Run multiple views in parallel for better performance
const [salesData, customerData, productData] = await runView.RunViews([
{ EntityName: 'Sales', ExtraFilter: "Date >= '2024-01-01'" },
{ EntityName: 'Customers', ExtraFilter: "Active = 1" },
{ EntityName: 'Products', OrderBy: 'Name' }
]);
// All data loaded in parallel
console.log('Sales:', salesData.Results);
console.log('Customers:', customerData.Results);
console.log('Products:', productData.Results);
}
The ComponentObject interface defines the structure returned by compiled components, providing both the React component and method accessors:
interface ComponentObject {
// The React component function
component: Function;
// Standard methods (all optional)
print?: () => void;
refresh?: () => void;
getCurrentDataState?: () => any;
getDataStateHistory?: () => Array<{ timestamp: Date; state: any }>;
validate?: () => boolean | { valid: boolean; errors?: string[] };
isDirty?: () => boolean;
reset?: () => void;
scrollTo?: (target: string | HTMLElement | { top?: number; left?: number }) => void;
focus?: (target?: string | HTMLElement) => void;
// Dynamic method access
invokeMethod?: (methodName: string, ...args: any[]) => any;
hasMethod?: (methodName: string) => boolean;
}
Components receive callbacks that enable interaction with their container:
interface ComponentCallbacks {
// Open an entity record in the container
OpenEntityRecord: (entityName: string, key: CompositeKey) => void;
// Register a method that can be called by the container
RegisterMethod: (methodName: string, handler: Function) => void;
}
RegisterMethod callbackinvokeMethod() for custom methodsfunction DataTableComponent({ callbacks, data, userState }) {
const [tableData, setTableData] = React.useState(data);
const [hasChanges, setHasChanges] = React.useState(false);
const [selectedRows, setSelectedRows] = React.useState([]);
React.useEffect(() => {
if (callbacks?.RegisterMethod) {
// Register standard methods
callbacks.RegisterMethod('getCurrentDataState', () => ({
data: tableData,
selectedRows: selectedRows,
totalCount: tableData.length
}));
callbacks.RegisterMethod('isDirty', () => hasChanges);
callbacks.RegisterMethod('validate', () => {
if (tableData.length === 0) {
return { valid: false, errors: ['Table cannot be empty'] };
}
return true;
});
callbacks.RegisterMethod('reset', () => {
setTableData(data);
setSelectedRows([]);
setHasChanges(false);
});
// Register custom business logic methods
callbacks.RegisterMethod('exportSelectedRows', () => {
return selectedRows.map(idx => tableData[idx]);
});
callbacks.RegisterMethod('deleteSelected', () => {
const newData = tableData.filter((_, idx) => !selectedRows.includes(idx));
setTableData(newData);
setHasChanges(true);
setSelectedRows([]);
});
}
}, [callbacks, tableData, selectedRows, hasChanges]);
return <div>{/* Table UI */}</div>;
}
// After component compilation
const componentObject = compiledComponent as ComponentObject;
// Use standard methods with type safety
if (componentObject.isDirty && componentObject.isDirty()) {
console.log('Component has unsaved changes');
const validation = componentObject.validate?.();
if (validation === true || validation?.valid) {
// Safe to proceed
const currentState = componentObject.getCurrentDataState?.();
await saveData(currentState);
} else {
console.error('Validation failed:', validation?.errors);
}
}
// Use custom methods via invokeMethod
if (componentObject.hasMethod?.('exportSelectedRows')) {
const selectedData = componentObject.invokeMethod('exportSelectedRows');
await exportToFile(selectedData);
}
// Reset the component
componentObject.reset?.();
Components can declare their supported methods in the spec for static discovery:
interface ComponentSpec {
name: string;
code: string;
// Optional method declarations for discovery/documentation
methods?: Array<{
name: string;
category?: 'standard' | 'custom';
description?: string;
parameters?: Array<{
name: string;
type: string; // Free-form type description
required?: boolean;
description?: string;
}>;
returnType?: string; // Free-form type description
}>;
}
Example declaration:
const spec: ComponentSpec = {
name: 'DataTable',
code: '...',
methods: [
{
name: 'getCurrentDataState',
category: 'standard',
description: 'Returns current table data and selection state',
returnType: '{data: any[], selectedRows: number[], totalCount: number}'
},
{
name: 'exportSelectedRows',
category: 'custom',
description: 'Exports currently selected rows',
returnType: 'any[]'
},
{
name: 'deleteSelected',
category: 'custom',
description: 'Deletes selected rows from the table'
}
]
};
The package includes comprehensive types for component specifications:
InteractiveComponentSpec: Full component specification including metadata, props, and initializationComponentDataRequirement: Defines data loading requirementsComponentOption: Configuration options for componentsComponentLibraryDependency: External library dependenciesComponentSpecCategory: Component categorization and metadataThe included SimpleVectorService provides:
modelPower parameter wisely - 'lowest' for simple tasks, 'highest' for complex analysisRunViews for parallel data loading instead of sequential RunView callssuccess flags in AI operation resultsresultObject when expecting structured data from AI promptsAll interfaces are fully typed with TypeScript, providing:
@memberjunction/interactive-component-types/forms)The /forms subpath provides the form-role contract for components that
declare componentRole: 'form' — the substrate that lets MJ render
runtime-author forms inside Explorer with the same toolbar / save / delete
behavior as a CodeGen-generated Angular form.
| Export | Purpose |
|---|---|
FormHostProps |
What the host wrapper passes a form-role React component (entity name, primary key, record snapshot, mode, permissions). |
FormEventNames / FormBeforeSaveArgs / etc. |
Standard event names + payload types the component emits via callbacks.NotifyEvent. |
FormMethodNames |
Standard methods the component registers (RequestSave, RequestCancel) so the host toolbar can drive its save flow. |
isFormRole(spec) |
Type-guard: does this ComponentSpec declare componentRole: 'form'? |
CuratedFormSchema, buildCuratedFormSchema(entityName, provider) |
Curated, LLM-friendly view of an entity's schema for form authoring — FK references resolved to {entity, displayField}, value lists annotated with allowedValues, audit/virtual/computed fields stripped. |
buildDefaultFormScaffold(entityName, provider) |
Produces a working form-role ComponentSpec that mirrors the CodeGen Angular default layout. Used as the baseline by the Form Builder agent and the dashboard's "New form" flow. |
buildFixtureFormHostProps(schema, mode?) |
Synthesizes type-appropriate fixture values for live-preview rendering when no real BaseEntity is available (used by Component Studio preview + the chat artifact viewer's fallback mode). |
getDeclaredFormEntityName(spec) |
Resolves the entity a form binds to from spec.entityName or spec.dataRequirements.entities[0].name. Pure helper shared by every form-role consumer. |
See /plans/interactive-forms/phase-2-runtime-loop.md for the full architecture, lifecycle, and security model (Create / Modify / Activate / Revert + ownership checks).
@memberjunction/core: Core MemberJunction types and interfaces@memberjunction/ai-vectors-memory: Vector operations and similarity calculationsISC