Migration Guide V1 To V2
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
📦 Migration Guide: Property System V1 → V2
Purpose: Step-by-step guide to migrate from inline property definitions to property references
🎯 What We're Migrating
Before (V1 - Inline Definitions)
export const BUTTON_EMBEDDABLE = {
id: 'ui-button',
inputs: [
{
name: 'text',
type: 'string',
required: false,
description: 'Button label text',
defaultValue: 'Button',
group: 'content',
order: 1,
},
{
name: 'variant',
type: 'string',
required: false,
description: 'Button variant: primary | secondary | ghost | danger',
defaultValue: 'primary',
group: 'style',
order: 1,
transform: mapButtonVariant,
options: [
{ value: 'primary', label: 'Primary (Solid)' },
{ value: 'secondary', label: 'Secondary (Outline)' },
{ value: 'ghost', label: 'Ghost (Text Only)' },
{ value: 'danger', label: 'Danger (Destructive)' },
],
},
// ... 10 more properties (200+ lines total)
],
};
Issues:
- ❌ 200+ lines per embeddable
- ❌ Duplicate definitions (many widgets use same properties)
- ❌ No backend connection
- ❌ Hard to update (change requires code deploy)
- ❌ No AI context metadata
After (V2 - Property References)
export const BUTTON_EMBEDDABLE_V2 = {
id: 'ui-button-v2',
propertyInputs: [
{
propertyId: 'core_label', // ✅ Backend reference
bindTo: 'text',
override: { order: 1, group: 'content' },
},
{
propertyId: 'custom_buttonVariant',
bindTo: 'variant',
override: { order: 1, group: 'style', transform: 'mapButtonVariant' },
},
// ... 8 more references (~30 lines total)
],
};
Benefits:
- ✅ ~30 lines per embeddable (80% reduction)
- ✅ No duplication (backend is single source)
- ✅ Backend connection (updates flow automatically)
- ✅ Easy to update (change property in MongoDB)
- ✅ AI context included (from backend)
📋 Migration Checklist
Phase 1: Backend Property Creation
For each inline property, create or find equivalent in backend:
1. Identify Core vs Custom Properties
| Property | Type | Example |
|---|---|---|
| Core (reusable) | core_* |
core_label, core_disabled, core_size |
| Custom (widget-specific) | custom_* |
custom_buttonVariant, custom_buttonShape |
2. Create Missing Backend Properties
# POST /api/properties
curl -X POST http://localhost:3000/api/properties \
-H "Content-Type: application/json" \
-d '{
"id": "custom_buttonVariant",
"key": "buttonVariant",
"label": "Button Variant",
"description": "Visual style variant for buttons",
"controlType": "select",
"category": "appearance",
"defaultValue": "primary",
"validation": {
"options": [
{ "value": "primary", "label": "Primary (Solid)", "description": "Solid filled button" },
{ "value": "secondary", "label": "Secondary (Outline)", "description": "Outlined button" },
{ "value": "ghost", "label": "Ghost (Text Only)", "description": "Text-only button" },
{ "value": "danger", "label": "Danger (Destructive)", "description": "Destructive action button" }
]
},
"widgetTypes": ["button"],
"tags": ["button", "appearance", "variant"],
"aiContext": "Button variants: primary (solid fill), secondary (outline), ghost (text only), danger (destructive). Use primary for main actions, secondary for less important actions.",
"metadata": {
"created": "2025-12-10T00:00:00Z",
"modified": "2025-12-10T00:00:00Z",
"version": "1.0.0",
"usageCount": 0,
"isBuiltIn": false,
"author": "system"
}
}'
3. Verify Property Exists
# GET /api/properties/:id
curl http://localhost:3000/api/properties/custom_buttonVariant
Phase 2: Update Embeddable Definition
1. Create V2 Version (keep V1 for comparison)
// button.embeddable.v2.ts
import type { PropertyReference } from '@libs-foundational/core-properties';
export const BUTTON_EMBEDDABLE_V2 = {
id: 'ui-button-v2',
displayName: 'UI Button (V2)',
description: 'Button with property references',
category: 'form',
componentType: undefined,
permissions: [],
// NEW: Property references
propertyInputs: [
// ... property references here
],
// Keep for backward compatibility (auto-generated)
inputs: [],
outputs: [/* ... */],
};
2. Map Each Property
For each inline property in V1:
| V1 Field | V2 Mapping | Example |
|---|---|---|
name |
bindTo |
'text' |
type, description, defaultValue |
(Backend) | Removed |
transform function |
override.transform (name as string) |
'mapButtonVariant' |
group, order |
override.group, override.order |
{ group: 'style', order: 1 } |
options |
(Backend) | Removed |
Example Mapping:
// V1
{
name: 'variant',
type: 'string',
description: 'Button variant',
defaultValue: 'primary',
group: 'style',
order: 1,
transform: mapButtonVariant,
options: [...]
}
// V2
{
propertyId: 'custom_buttonVariant', // ✅ Find/create in backend
bindTo: 'variant', // ✅ Same as V1 'name'
override: {
group: 'style', // ✅ Same as V1
order: 1, // ✅ Same as V1
transform: 'mapButtonVariant', // ✅ Function name as string
}
}
Phase 3: Update Variants
1. Convert inputOverrides to propertyValues
// V1
export const BUTTON_EMBEDDABLE_VARIANTS = [
{
id: 'primary-md',
name: 'Primary Medium',
inputOverrides: {
variant: 'primary',
size: 'md',
shape: 'rounded',
},
},
];
// V2
export const BUTTON_EMBEDDABLE_V2_VARIANTS = [
{
id: 'primary-md',
embeddableId: 'ui-button-v2', // ✅ Add embeddable reference
name: 'Primary Medium',
propertyValues: { // ✅ Rename: inputOverrides → propertyValues
'custom_buttonVariant': 'primary', // ✅ Use propertyId as key
'core_size': 'md',
'custom_buttonShape': 'rounded',
},
metadata: { // ✅ Add metadata
created: new Date().toISOString(),
createdBy: 'system',
tags: ['primary', 'default'],
usageCount: 0,
},
},
];
Phase 4: Test Migration
1. Verify Property Resolution
import { PropertyResolverService } from '@libs-foundational/core-properties';
const resolver = inject(PropertyResolverService);
// Test resolving embeddable properties
const resolved = await resolver.resolveEmbeddableProperties('ui-button-v2');
// Should return Map of bindTo → ResolvedProperty
console.log(resolved.get('text')); // { id: 'core_label', key: 'label', ... }
console.log(resolved.get('variant')); // { id: 'custom_buttonVariant', ... }
2. Component Integration Test
// In component
export class UiButtonComponent {
// Properties resolved at runtime
@Input() properties: Map<string, ResolvedProperty>;
// Or traditional inputs (populated by resolver)
@Input() text = '';
@Input() variant = 'primary';
ngOnInit() {
// Auto-populate from resolved properties
if (this.properties) {
this.text = this.properties.get('text')?.value as string;
this.variant = this.properties.get('variant')?.value as string;
}
}
}
🔧 Tools & Scripts
Auto-Migration Script
/**
* Auto-migrate V1 embeddable to V2
*
* Usage:
* npm run migrate-embeddable -- button
*/
import { readFileSync, writeFileSync } from 'fs';
function migrateEmbeddable(embeddableName: string) {
const v1Path = `embeddables/embeddable-schemas/src/lib/ui-core/${embeddableName}.embeddable.ts`;
const v2Path = `embeddables/embeddable-schemas/src/lib/ui-core/${embeddableName}.embeddable.v2.ts`;
const v1Content = readFileSync(v1Path, 'utf-8');
// Parse V1 embeddable
const v1Embeddable = parseV1Embeddable(v1Content);
// Convert to V2
const v2Embeddable = {
...v1Embeddable,
propertyInputs: v1Embeddable.inputs.map(input => ({
propertyId: inferPropertyId(input),
bindTo: input.name,
override: {
group: input.group,
order: input.order,
transform: input.transform?.name,
}
}))
};
// Write V2 file
writeFileSync(v2Path, generateV2Code(v2Embeddable));
console.log(`✅ Migrated ${embeddableName} to V2`);
}
function inferPropertyId(input: any): string {
// Common properties → core_*
if (['text', 'label', 'disabled', 'visible', 'size'].includes(input.name)) {
return `core_${input.name}`;
}
// Widget-specific → custom_*
return `custom_${input.name}`;
}
Property Sync Script
#!/bin/bash
# Sync backend properties to frontend types
echo "Fetching backend properties..."
curl http://localhost:3000/api/properties > /tmp/backend-properties.json
echo "Generating TypeScript definitions..."
node scripts/generate-property-types.js /tmp/backend-properties.json
echo "✅ Property types synced"
🎯 Migration Priority
Phase 1: Core UI Components (HIGH)
- ✅ Button
- ⏭️ Input
- ⏭️ Card
- ⏭️ Icon
- ⏭️ Chip
Phase 2: Forms (MEDIUM)
- ⏭️ Toggle
- ⏭️ Checkbox
- ⏭️ Select
- ⏭️ Radio
Phase 3: Feedback (MEDIUM)
- ⏭️ Dialog
- ⏭️ Toast
- ⏭️ Alert
Phase 4: Complex (LOW)
- ⏭️ Table
- ⏭️ Tree
- ⏭️ Data Grid
🚨 Common Issues & Solutions
Issue 1: Property Not Found
Error: PropertyResolverService: Property 'custom_buttonVariant' not found
Solution:
# Check if property exists in backend
curl http://localhost:3000/api/properties/custom_buttonVariant
# If 404, create it
curl -X POST http://localhost:3000/api/properties -d '{...}'
Issue 2: Mapper Function Not Found
Error: PropertyResolverService: Mapper 'mapButtonVariant' not found
Solution:
// In PropertyResolverService, register mapper
private mappers = {
mapButtonVariant,
mapSize,
// ... add missing mapper
};
Issue 3: Component Input Mismatch
Error: Property binding 'variant' doesn't match component input
Solution:
// Ensure bindTo matches @Input() name
export class UiButtonComponent {
@Input() variant = 'primary'; // ✅ Matches bindTo: 'variant'
}
📚 Related Documentation
Status: 🟡 Guide Complete (Implementation Pending)
Last Updated: December 10, 2025