Dala.Plugin behaviour (dala v0.3.2)

Copy Markdown View Source

Behaviour and macros for self-describing component plugins.

Plugins declare themselves declaratively, allowing Dala core to remain generic. Everything becomes a plugin: video, maps, charts, camera, ML views, custom renderers, AR, etc.

Architecture

Dala core knows almost nothing. Plugins self-describe through:

  • Schema - component metadata (props, events, capabilities)
  • Protocol - binary communication format (auto-generated)
  • Native renderer - platform-specific implementation (iOS/Android)

This is the same pattern used by:

  • React Native Fabric
  • Flutter Engine
  • SwiftUI internals
  • Jetpack Compose runtime
  • VSCode extension host
  • Browser DOM

Example (top-level DSL)

defmodule MyApp.VideoPlugin do
  use Dala.Plugin

  component "video" do
    prop "source", :string
    prop "autoplay", :bool
    prop "controls", :bool
    prop "volume", :f32

    event "progress"
    event "ended"
    event "ready"

    native "ios", "DalaVideoView"
    native "android", "com.dala.video.VideoView"

    capability :gestures
    capability :accessibility
    capability :animation
  end
end

Example (plugin do block)

defmodule MyApp.VideoPlugin do
  use Dala.Plugin

  plugin do
    plugin_description "Video playback plugin"

    component "video", MyApp.VideoComponent
    plugin_event "video_zoom", MyApp.Video.Events.Zoom

    plugin_native :ios, MyApp.Video.IOS
    plugin_native :android, MyApp.Video.Android

    plugin_permission :camera
    plugin_dependency {:maps, "~> 1.0"}
    plugin_platform :ios
    plugin_platform :android
  end
end

Behaviour

Modules using Dala.Plugin must implement (or get defaults for) these callbacks:

  • init(opts){:ok, state} | {:error, reason} — resource allocation (required)

  • components()[Component.t()] — declare components (required)
  • capabilities()[atom()] — what this plugin provides (required)
  • permissions()[atom()] — declare required permissions (optional, default [])
  • native_modules(platform)[module()] — platform-specific native modules (optional, default [])
  • dependencies()[{atom(), String.t()}] — dependency ordering (optional, default [])
  • validate_config(config):ok | {:error, reason} — compile-time hooks (optional, default :ok)

  • handle_event(event, payload, state){:ok, state} | {:error, reason} — runtime hooks (optional, default {:ok, state})

  • cleanup(state):ok — resource deallocation / hot reload (optional, default :ok)

Versioning

Plugins MUST declare versions for compatibility:

schema_version "1.0.0"
protocol_version 3
native_api_version "2.0.0"

This prevents ecosystem fragmentation.

Host/Runtime Separation

Plugins should NEVER directly access:

  • BEAM internals
  • Scheduler state
  • Raw protocol sockets

Instead:

Plugin
   
Host API
   
Dala Runtime

Exactly like browser extensions.

Generic Node Model

Everything becomes a generic node:

%Dala.Node{
  type: "video",
  props: %{source: "...", autoplay: true},
  children: []
}

Dala core NEVER special-cases video, maps, or charts. The same generic lifecycle applies to all plugins:

  • create/2
  • update/2
  • layout/2
  • event/3
  • dispose/1

Optional capabilities:

  • animate/2
  • focus/2
  • accessibility/2
  • snapshot/1
  • texture/1
  • gesture/2

Universal Command Stream

Dala core emits only generic operations:

  • CREATE_NODE
  • UPDATE_PROP
  • REMOVE_NODE
  • EMIT_EVENT
  • RUN_ANIMATION

Plugins interpret semantics. Core stays tiny.

Summary

Callbacks

Returns the list of capabilities this plugin provides.

Cleans up resources before hot reload or unload.

Returns the list of component schemas this plugin provides.

Returns dependency specifications: {plugin_name, version_requirement}.

Handles runtime events sent to the plugin.

Initializes the plugin. Allocates resources, starts processes, etc. Returns {:ok, state} on success or {:error, reason} on failure.

Returns platform-specific native modules for the given platform.

Returns the list of permissions this plugin requires.

Validates plugin configuration at compile-time.

Functions

Macro callback - builds the full plugin struct and generates default behaviour implementations.

Defines a new plugin module.

Auto-registers all plugins found in loaded applications.

Defines a new component within the plugin.

Declares a dependency on another plugin with a version requirement.

Sets the plugin description.

Sets the native API version.

Declares a native module for a specific platform.

Declares a permission the plugin requires.

Declares platform support.

Main entry point for the plugin DSL block.

Sets the binary protocol version.

Sets the plugin schema version.

Types

capability()

@type capability() ::
  :gestures
  | :accessibility
  | :animation
  | :textures
  | :overlay
  | :clipping
  | :touch
  | :keyboard
  | :focus

component_name()

@type component_name() :: String.t()

event_name()

@type event_name() :: String.t()

platform()

@type platform() :: :ios | :android | :web

plugin_name()

@type plugin_name() :: atom()

prop_name()

@type prop_name() :: String.t()

prop_type()

@type prop_type() ::
  :string
  | :bool
  | :integer
  | :float
  | :f32
  | :f64
  | :color
  | :binary
  | :list
  | :map

status()

@type status() :: :registered | :initialized | :active | :error | :unloaded

Callbacks

capabilities()

@callback capabilities() :: [atom()]

Returns the list of capabilities this plugin provides.

cleanup(state)

@callback cleanup(state :: term()) :: :ok

Cleans up resources before hot reload or unload.

components()

@callback components() :: [Dala.Plugin.Component.t()]

Returns the list of component schemas this plugin provides.

dependencies()

@callback dependencies() :: [{atom(), String.t()}]

Returns dependency specifications: {plugin_name, version_requirement}.

handle_event(event, payload, state)

@callback handle_event(event :: atom(), payload :: map(), state :: term()) ::
  {:ok, state :: term()} | {:error, reason :: term()}

Handles runtime events sent to the plugin.

init(opts)

@callback init(opts :: keyword()) :: {:ok, state :: term()} | {:error, reason :: term()}

Initializes the plugin. Allocates resources, starts processes, etc. Returns {:ok, state} on success or {:error, reason} on failure.

native_modules(platform)

@callback native_modules(platform :: platform()) :: [module()]

Returns platform-specific native modules for the given platform.

permissions()

@callback permissions() :: [atom()]

Returns the list of permissions this plugin requires.

validate_config(config)

@callback validate_config(config :: map()) :: :ok | {:error, reason :: term()}

Validates plugin configuration at compile-time.

Functions

__before_compile__(env)

(macro)

Macro callback - builds the full plugin struct and generates default behaviour implementations.

__using__(opts \\ [])

(macro)

Defines a new plugin module.

Options

  • :schema_version - Plugin schema version (default: "1.0.0")
  • :protocol_version - Binary protocol version (default: 3)
  • :native_api_version - Native API version (default: "2.0.0")
  • :platforms - List of supported platforms (e.g. [:ios, :android])
  • :capabilities - List of plugin-level capabilities
  • :dala_requires - Dala version requirement string
  • :metadata - Arbitrary metadata map

auto_register()

Auto-registers all plugins found in loaded applications.

component(name, module)

(macro)

Defines a new component within the plugin.

Example

component "video" do
  prop "source", :string
  prop "autoplay", :bool
  prop "volume", :f32

  event "progress"
  event "ended"

  native "ios", "DalaVideoView"
  native "android", "com.dala.video.VideoView"

  capability :gestures
  capability :accessibility
end

dependency(arg)

(macro)

Declares a dependency on another plugin with a version requirement.

description(text)

(macro)

Sets the plugin description.

native_api_version(version)

(macro)

Sets the native API version.

native_module(platform, module)

(macro)

Declares a native module for a specific platform.

permission(name)

(macro)

Declares a permission the plugin requires.

platform(name)

(macro)

Declares platform support.

plugin(list)

(macro)

Main entry point for the plugin DSL block.

Inside a plugin do block, you can declare:

  • component :chart, MyPlugin.ChartComponent
  • plugin_event :chart_zoom, MyPlugin.Events.Zoom
  • plugin_native :ios, MyPlugin.IOS
  • plugin_native :android, MyPlugin.Android
  • plugin_permission :camera
  • plugin_dependency {:maps, "~> 1.0"}
  • plugin_platform :ios
  • plugin_platform :android
  • plugin_description "My chart plugin"

If plugin do is used, it takes precedence for those declarations.

protocol_version(version)

(macro)

Sets the binary protocol version.

schema_version(version)

(macro)

Sets the plugin schema version.