Parameter System Architecture

Unified parameter metadata system for actions and embeddables

🔧 Parameter System Architecture

A unified, type-safe parameter definition system shared by both Public Actions and Embeddable Components.


🎯 Design Goals

  1. Single Source of Truth: One parameter type (ModuleParameterMetadata) for the entire system.
  2. Rich Metadata: Validation rules, UI hints, defaults, and descriptions.
  3. Type Safety: Full TypeScript support with compile-time guarantees.
  4. Library Independence: module-base and action-registry remain independent (no circular dependencies).

📐 Core Types

ModuleParameterMetadata

The foundation type used everywhere.

export interface ModuleParameterMetadata {
  readonly name: string;
  readonly type: ParameterType;
  readonly description?: string;
  readonly required: boolean;
  readonly defaultValue?: unknown;
  readonly validators?: readonly ParameterValidator[];
  readonly uiHints?: ParameterUIHints;
  readonly example?: unknown;
  readonly sensitive?: boolean;
}

ParameterType

Supported parameter types.

type ParameterType = 
  | 'string' 
  | 'number' 
  | 'boolean' 
  | 'date' 
  | 'object' 
  | 'array' 
  | 'enum' 
  | 'any';

ParameterValidator

Validation rules.

export interface ParameterValidator {
  readonly type: ValidatorType;
  readonly value?: unknown;
  readonly message?: string;
}

type ValidatorType = 
  | 'required' 
  | 'min' 
  | 'max' 
  | 'minLength' 
  | 'maxLength' 
  | 'pattern' 
  | 'email' 
  | 'url' 
  | 'enum' 
  | 'custom';

ParameterUIHints

Hints for automatic UI generation.

export interface ParameterUIHints {
  readonly component?: string;         // 'input' | 'textarea' | 'select' | 'checkbox'
  readonly placeholder?: string;
  readonly label?: string;
  readonly helpText?: string;
  readonly group?: string;
  readonly order?: number;
}

🏗️ Builder API

ActionParameterBuilder

Fluent builder for constructing parameters.

import { ActionParameterBuilder, ParameterBuilders } from '@libs-core/action-registry';

// Using the fluent builder
const emailParam = ActionParameterBuilder.string('email')
  .description('User email address')
  .required('Email is required')
  .pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Invalid email format')
  .label('Email Address')
  .placeholder('[email protected]')
  .build();

// Pre-configured builders
const emailParam = ParameterBuilders.email('email').build();
const pageParam = ParameterBuilders.page().build();
const searchParam = ParameterBuilders.search().build();

🔍 Validation Service

The ParameterValidationService validates parameters against their metadata.

import { ParameterValidationService } from '@libs-core/module-base';

@Injectable()
export class MyService {
  private validation = inject(ParameterValidationService);

  validateUserInput(params: Record<string, unknown>): Observable<ValidationResult> {
    return this.validation.validate(params, parameterDefinitions);
  }
}

🧩 Integration

In Public Actions

@PublicAction({
  id: 'users:create',
  // ...
  parameters: [
    ParameterBuilders.email('email').build(),
    ActionParameterBuilder.string('firstName').required().build()
  ]
})
export class CreateUserAction implements ExecutableAction<User> { ... }

In Embeddable Components

embeddableComponents: [
  {
    id: 'users:list-widget',
    componentType: UserListWidgetComponent,
    inputs: [
      ParameterBuilders.page().build(),
      ActionParameterBuilder.boolean('showAvatars').build()
    ]
  }
]

🔬 Technical Solutions

Challenge: Circular Dependencies

Problem: Both module-base and action-registry needed to share parameter types.

Solution: Maintain independent but identical type definitions.

// module-base exports ModuleParameterMetadata
// action-registry exports ActionParameterMetadata (identical)
// Documentation notes they must stay in sync

Challenge: Readonly Builder State

Problem: Readonly properties prevent mutation in builders.

Solution: DeepMutable<T> utility type.

type DeepMutable<T> = T extends readonly (infer U)[]
  ? U[]
  : T extends object
  ? { -readonly [K in keyof T]: DeepMutable<T[K]> }
  : T;

// Builder uses mutable state internally, returns immutable interface

Last Updated: December 3, 2025 Session ID: 3 Status: ✅ Production Architecture