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:
- Model schema: Defines the available properties.
- Module Builder: Creators configure property values and triggers.
- 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:
- Set output type to
BYOC_UPDATE - Set target to:
componentId.property(propertyPath) - 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:
- The platform stores the payload in the
selectedItemcontext field. - The
processSelectioncomponent executes. - That component reads
selectedItemfrom 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?
- Building Components Guide — step-by-step component development.
- CLI Reference — complete SDK command documentation.
- Testing Guide — write tests for migrated components.
Changelog
| Date | Change |
|---|---|
| 2026-05-01 | Initial publication |