Documentation Index

Fetch the complete documentation index at: https://docs.unqork.io/llms.txt

Use this file to discover all available pages before exploring further.

Vega to Centauri BYO Migration Guide

Prev Next

Centauri BYO introduces a simplified integration model based on properties + callbacks instead of the Vega operations API. This guide covers the key differences and migration steps.

Key Differences

Integration Model

Vega: Components received an API object with state and event methods:

// Vega pattern - components received an api object
class VegaComponent {
  constructor(api) {
    this.api = api
  }
  
  // Access state
  getValue() {
    const currentState = this.api.state.currentState()
    return currentState.label
  }
  
  // Update state
  updateValue(value) {
    this.api.state.set({ label: value })
  }
  
  // Emit events
  handleClick() {
    this.api.events.emit('buttonClicked', { label: this.api.state.currentState().label })
  }
}

Centauri: Components receive data through properties and dispatch events through callbacks:

// Centauri pattern - standard Web Components
class CentauriComponent extends HTMLElement {
  private _label: string = ''
  
  // Platform sets properties automatically
  set label(value: string) {
    this._label = value
    this.render()
  }
  
  // Call callbacks to communicate back
  handleClick() {
    this.onButtonClicked?.({ label: this._label })
  }
}

State Management

Vega: Components manually accessed and updated state using the API:

// Read state
const state = this.api.state.currentState()
const label = state.label

// Update state
this.api.state.set({ label: 'New Label' })

// Subscribe to state changes
this.api.state.state$.subscribe((state) => {
  this.render(state)
})

Centauri: The platform automatically sets properties when state changes. Your component reacts to setters:

// Platform calls setters automatically
set label(value: string) {
  this._label = value
  this.render()
}

// No manual subscription needed

Event Dispatch

Vega: Events were dispatched through the API's emit method:

this.api.events.emit('itemSelected', { id: 123 })

Centauri: Events are callbacks wired through triggers configured in the Module Builder:

// Component code
this.onItemSelected?.({ id: 123 })
// Module Builder configuration
{
  "triggers": {
    "onItemSelected": {
      "contextId": "selectedItem",
      "targetId": "logicComponent"
    }
  }
}

The platform handles routing. Your component doesn't need to specify targets.

Configuration

Vega: Components accessed configuration through the API's state methods.

Centauri: Configuration is declarative through:

  1. Model schema: Defines the available properties.
  2. Module Builder: Creators configure property values and triggers.
  3. Properties: The platform sets values on your component.

Centauri BYOC_UPDATE Output Type

Centauri introduces a special output type BYOC_UPDATE for updating BYO component properties from operations. This requires the .property() syntax to target specific properties.

Syntax:

componentId.property(propertyPath)

Examples:

// Update a simple property
myButton.property(label)           // Updates myButton's label property

// Update nested properties
myTaskList.property(items[0].completed)  // Updates first item's completed status
myChart.property(options.title)          // Updates nested configuration

Usage in Operations: When configuring outputs in the Module Builder:

  1. Set output type to BYOC_UPDATE
  2. Set target to: componentId.property(propertyPath)
  3. The platform will update that specific property on the BYO component.

This lets operations update specific BYO component properties without replacing the entire state.

Migration Steps

1. Update Component Contract

Vega:

// Vega components received an api object
class VegaButton {
  constructor(api) {
    this.api = api
  }
  
  handleClick() {
    const state = this.api.state.currentState()
    const label = state.label
    this.api.events.emit('buttonClicked', { label })
  }
}

Centauri:

// Centauri components are standard Web Components
class CentauriButton extends HTMLElement {
  private _label: string = ''
  
  // Platform sets properties via setters
  set label(value: string) {
    this._label = value
    this.render()
  }
  
  // Component calls callbacks to communicate back
  handleClick() {
    this.onButtonClicked?.({ label: this._label })
  }
}

2. Replace Vega API Calls

Vega API Centauri Equivalent
api.state.currentState() Access through local state from setters
api.state.set({ prop: value }) Use triggers + operations with BYOC_UPDATE output type
api.state.state$.subscribe() Not needed—setters are called automatically when state changes
api.state.resolveByKey(key) Use triggers + operations with BYOC_UPDATE output type and .property() syntax
api.state.updateExternalComponentState() Use triggers + operations with BYOC_UPDATE output type
api.events.emit(name, payload) this.onEventName?.(payload)

3. Define Model Schema

Create a model class that describes your component's properties:

import { z } from 'zod'

export const MyButtonModel = z.object({
  label: z.string().default('Click me'),
  disabled: z.boolean().default(false),
  variant: z.enum(['primary', 'secondary']).default('primary'),
})

export type MyButtonModel = z.infer<typeof MyButtonModel>

This schema:

  • Defines available properties in the Module Builder.
  • Provides default values.
  • Enables type checking.

4. Implement Property Setters

For each property in your model, implement a setter:

class MyButton extends HTMLElement {
  private _label: string = ''
  private _disabled: boolean = false
  
  set label(value: string) {
    this._label = value
    this.render()
  }
  
  set disabled(value: boolean) {
    this._disabled = value
    this.render()
  }
  
  set submissionData(value: unknown) {
    // Handle submission data updates from the platform
    this._data = value
    this.render()
  }
}

5. Update Event Handlers

Vega:

handleSelection(item) {
  // Emit event
  this.api.events.emit('itemSelected', { 
    itemId: item.id,
    itemName: item.name 
  })
  
  // Update component's own state
  this.api.state.set({ selectedItemId: item.id })
}

Centauri:

handleSelection(item: Item) {
  // Dispatch event — routing configured in Module Builder
  this.onItemSelected?.({ 
    itemId: item.id,
    itemName: item.name 
  })
  
  // Update submission data
  this.onSubmissionUpdate?.(item.id)
}

6. Configure Triggers in Module Builder

Instead of specifying targets directly in your component code, configure triggers declaratively:

{
  "triggers": {
    "onItemSelected": {
      "contextId": "selectedItem",
      "targetId": "processSelection"
    }
  }
}

When onItemSelected fires:

  1. The platform stores the payload in the selectedItem context field.
  2. The processSelection component executes.
  3. That component reads selectedItem from the context.

Breaking Changes

No Operation Handlers

Centauri BYO has no concept of Operations. Vega allowed components to register operation handlers like MyOperationHandler that the platform could invoke.

Migration: Implement all logic directly in your component. Use properties to receive configuration and callbacks to trigger platform workflows.

Example:

// ❌ Vega - operation handler
class MyOperationHandler {
  execute(params) {
    // Custom logic
  }
}

// ✅ Centauri - implement in component
class MyComponent extends HTMLElement {
  handleAction(params) {
    // Custom logic here
    this.onActionComplete?.(result)
  }
}

No Direct Platform API Access

Centauri components cannot:

  • Call operations to modify other components directly.
  • Access the Redux store.
  • Navigate to other pages directly.
  • Execute workflow operations.

Migration: Use triggers to invoke platform logic that performs the required actions.

No Synchronous State Access

Vega allowed synchronous reads of platform state. Centauri sets state through properties in the background.

Migration: Store necessary state locally in your component, updated through setters.

Event Routing Changes

Vega events specified targets directly. Centauri events are routed through Module Builder configuration.

Migration: Move event routing from component code to Module Builder trigger configuration.

Benefits of the New Model

Simpler Component Code

  • No operations API to learn.
  • No manual state synchronization.
  • Properties in, callbacks out.

Better Testability

Components are pure Web Components with no platform dependencies:

import { mountByoComponent, createCallbackSpy } from '@unqork/byo-sdk/testing'

it('fires callback on click', async () => {
  const el = await mountByoComponent(myButton)
  const spy = createCallbackSpy()
  
  el.onButtonClicked = spy.fn
  el.querySelector('button')!.click()
  
  expect(spy.calls).toHaveLength(1)
})

Declarative Configuration

Event routing is visible in the module definition, not hidden in component code.

Framework Flexibility

The properties + callbacks model works naturally with any framework:

  • Vanilla JavaScript.
  • React.
  • Vue.
  • Angular.
  • Lit.

Need Help?


Changelog

Date Change
2026-05-01 Initial publication