Editor Module Contract Migration

Documentation for Plans

๐Ÿ”„ EditorModuleContract โ†’ BrowserModuleContract Migration Guide

Date: October 31, 2025
Time: Session 1
Status: ๐Ÿ“‹ MIGRATION GUIDE
Purpose: Guide developers through migrating from EditorModuleContract to BrowserModuleContract


๐ŸŽฏ Why Migrate?

โŒ Old Pattern (EditorModuleContract)

โœ… New Pattern (BrowserModuleContract)


๐Ÿ“‹ Migration Checklist


๐Ÿ”ง Step-by-Step Migration

Step 1: Update Imports

Before:

import {
  EditorModuleContract,
  ModuleConfig,
  Module
} from '@libs-foundational/core-angular';
import type { EditorModuleManifest } from '@libs-foundational/core-angular';

After:

import {
  BrowserModuleContract,
  ModuleManifest,
  ModuleDecorator,
  ModuleParameterBuilder
} from '@libs-core/module-base';

Step 2: Update Class Declaration

Before:

@Module({
  manifest: MY_MODULE_MANIFEST
})
@Injectable({
  providedIn: 'root'
})
export class MyModule implements EditorModuleContract {
  readonly manifest = MY_MODULE_MANIFEST;

After:

@ModuleDecorator({
  id: 'my-module',
  version: '1.0.0',
  name: 'My Module'
})
@Injectable({
  providedIn: 'root'
})
export class MyModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    // Inline manifest (preferred) or external
  };

Step 3: Convert Public Actions

Before:

publicActions: [
  {
    id: 'create-item',
    actionClass: CreateItemAction,
    displayName: 'Create Item',
    description: 'Create a new item',
    parameters: [
      {
        name: 'name',
        type: 'string',
        description: 'Item name',
        required: true
      }
    ],
    returnType: 'Promise<Item>'
  }
]

After:

publicActions: [
  {
    id: 'items:create',
    displayName: 'Create Item',
    description: 'Create a new item with validation',
    category: 'crud',
    parameters: [
      ModuleParameterBuilder.string('name')
        .required('Name is required')
        .minLength(2, 'Name must be at least 2 characters')
        .maxLength(100, 'Name must be less than 100 characters')
        .label('Item Name')
        .placeholder('Enter item name')
        .example('My Awesome Item')
        .build()
    ],
    handler: (params) => of(this.createItem(params))
  }
]

Benefits:


Step 4: Update Routes

Before:

routes: [
  {
    path: 'items',
    component: ItemListComponent,
    data: {
      title: 'Items',
      icon: 'list'
    }
  }
]

After:

routes: [
  {
    path: 'items',
    loadComponent: () => import('./components/item-list/item-list.component')
      .then(m => m.ItemListComponent),
    data: {
      title: 'Items',
      icon: 'fas fa-list',
      description: 'Manage all items'
    }
  }
]

Benefits:


Step 5: Enhance Embeddable Components

Before:

embeddableComponents: [
  {
    id: 'item-list-widget',
    componentType: ItemListWidgetComponent,
    displayName: 'Item List',
    description: 'Displays items',
    category: 'widget',
    inputs: [
      {
        name: 'maxItems',
        type: 'number',
        required: false,
        defaultValue: 10
      }
    ],
    usesThemeSystem: true,
    usesStorage: true
  }
]

After:

embeddableComponents: [
  {
    id: 'item-list-widget',
    displayName: 'Item List Widget',
    description: 'Displays a compact list of items for dashboards',
    category: 'data-display',
    componentType: undefined as any, // Will be set when component is created
    inputs: [
      ModuleParameterBuilder.number('maxItems')
        .min(1, 'Must show at least 1 item')
        .max(100, 'Cannot show more than 100 items')
        .defaultValue(10)
        .label('Maximum Items')
        .description('Number of items to display in the widget')
        .build(),
      
      ModuleParameterBuilder.boolean('showActions')
        .defaultValue(true)
        .label('Show Action Buttons')
        .description('Display action buttons for each item')
        .build()
    ]
  }
]

Benefits:


Step 6: Update Configuration

Before:

// Usually scattered or missing

After:

configuration: {
  defaults: {
    itemsPerPage: 20,
    sortOrder: 'desc',
    enableNotifications: true
  }
}

Step 7: Add Tags

Before:

// Not available in old pattern

After:

tags: ['items', 'inventory', 'crud', 'business']

Benefits:


Step 8: Update Lifecycle Methods

The lifecycle methods remain similar, but the new pattern uses generic types:

Before:

configure(config: ModuleConfig): Observable<void> {
  this.storage = config.storage;
  this.theme = config.theme;
  return of(void 0);
}

After:

configure<T = Record<string, unknown>>(config: T): Observable<void> {
  this.config = config;
  this.storage = (config as any).storage;
  this.theme = (config as any).theme;
  
  console.log('๐ŸŽ๏ธ [Lambo Builder - My Module] Configuring module');
  return of(void 0);
}

๐Ÿ“š Complete Example

See the Golden Template for a complete, working example:

/modules/user-management/src/lib/user-management.module.ts

Or read the documentation:

docs/english/GoGrab-Space/plans/lambo-builder/golden-template.md

๐Ÿงช Testing After Migration

  1. Build the module:

    cd /Users/fguijarro/developer/repos/editor
    npx nx build your-module-name
    
  2. Check for linter errors:

    npx nx lint your-module-name --fix
    
  3. Run tests:

    npx nx test your-module-name
    
  4. Test in the app:

    • Start the development server
    • Navigate to module routes
    • Test public actions
    • Verify embeddable components
    • Check lifecycle (configure, initialize, start, stop, destroy)

๐ŸŽฏ Benefits Summary

Feature EditorModuleContract BrowserModuleContract
Parameter Metadata Basic Rich with validation
UI Generation Manual Automatic from metadata
AI Trainability Limited Excellent
TypeScript Safety Good Excellent
Lazy Loading Optional Built-in
Unified Parameters No Yes
Examples No Yes
Categories No Yes
Tags No Yes
Configuration Scattered Centralized

๐Ÿšจ Common Issues

Issue 1: Import Errors

Problem: Can't find @libs-core/module-base

Solution: Check tsconfig.base.json for path mapping:

{
  "compilerOptions": {
    "paths": {
      "@libs-core/module-base": ["libs/core/module-base/src/index.ts"]
    }
  }
}

Issue 2: Observable vs Promise

Problem: Old code used Promises for actions

Solution: Wrap in of():

handler: (params) => of(this.myMethod(params))

Issue 3: Component Type

Problem: ComponentType is undefined as any

Solution: This is intentional. The component will be set when the module is loaded. For now, use undefined as any to satisfy TypeScript.


๐Ÿ“ž Getting Help

  1. Check the Golden Template - Most answers are there
  2. Read the Architecture Docs - /docs/english/GoGrab-Space/plans/lambo-builder/
  3. Look at existing migrations - User Management module is complete
  4. Ask the team - We're building the future together!

๐Ÿ Completion Criteria

Your migration is complete when:


Let's build the most incredible module system in the universe! ๐Ÿš€