This reference guide provides a complete overview of all auto-generated Vue 3 composables provided by Alva V2 (useAlvaQuery, useAlvaForm, use_<signal>_state, use_<action>_upload, use_<action>_by_id).
1. Overview & Conventions
Alva generates strongly-typed Vue 3 composables directly from your Ash domain definitions into assets/js/alva/:
- Namespace Access: All domain events, queries, forms, uploads, and signal state getters are namespaced under
alva.<domain>(e.g.alva.catalog.use_list_products_query()). - Auto-Cleanup: Signal subscriptions and query timers automatically detach when Vue components unmount.
- Result Envelope: All direct actions and form submissions return
Promise<AlvaResult<T>>containing{ ok: true, data: T }or{ ok: false, error: AlvaError }.
2. Ash Backend Setup
Ensure your Ash resources expose public actions and signals via the alva do ... end DSL:
# lib/alva_demo/catalog/product.ex
defmodule AlvaDemo.Catalog.Product do
use Ash.Resource,
domain: AlvaDemo.Catalog,
data_layer: Ash.DataLayer.Ets,
notifiers: [Ash.Notifier.PubSub],
extensions: [Alva.Resource]
pub_sub do
module(AlvaDemoWeb.Endpoint)
prefix("product")
publish(:adjust_stock, ["updated"])
end
alva do
event(:catalog_list_products, name: "catalog.list_products", action: :list, lookup: :id, enable_filter: true)
event(:catalog_adjust_stock, name: "catalog.adjust_stock", action: :adjust_stock)
event(:catalog_upload_media, name: "catalog.upload_media", action: :upload_media)
signal :catalog_product_updated do
name("catalog.product_updated")
authorize_with(:list)
end
end
end3. LiveView Integration
Mount Alva.LiveView in your LiveView module:
# lib/alva_demo_web/live/customer_storefront_live.ex
defmodule AlvaDemoWeb.CustomerStorefrontLive do
use AlvaDemoWeb, :live_view
use Alva.LiveView
end4. Frontend Composables Reference
4.1 Reactive Queries (useAlvaQuery)
const { data, loading, error, refetch } = alva.<domain>.use_<action>_query(
inputGetter: () => InputPayload,
options?: {
debounceMs?: number; // Debounce reactive input changes (default: 0)
pollIntervalMs?: number; // Background periodic polling interval in ms
autoRefreshOnSignal?: string; // Auto-refetch query when signal fires over PubSub
}
)Code Example:
<script setup lang="ts">
import { ref } from "vue";
import { useAlva } from "@/js/alva";
const alva = useAlva();
const searchQuery = ref("");
// Re-fetches on search input changes, polls every 10s, and auto-refetches when products update!
const { data: products, loading, error, refetch } = alva.catalog.use_list_products_query(
() => ({
query: searchQuery.value,
filter: { stock: { greater_than: 0 } }
}),
{
debounceMs: 300,
autoRefreshOnSignal: "catalog.product_updated"
}
);
</script>4.1.1 Single-Record Lookup Query Helpers (use_<action>_by_id)
For read actions configured with lookups (e.g. lookup: :id), Alva generates dedicated single-record query helpers:
const productId = ref("product-123");
// Auto-fetches single Product object when `productId.value` changes!
const { data: product, loading, error } = alva.catalog.use_list_products_by_id(productId);4.2 Form Handling (useAlvaForm)
const form = alva.<domain>.use_<action>_form({
initialValues: FormValues,
validateEvent?: string, // Optional change validation event
debounceMs?: number, // Change validation debounce
onOptimisticSubmit?: (values) => () => void, // Optimistic UI update with rollback
uploads?: Record<string, { getFileReferences: () => string[] }>
})Form Return Object (form):
values: Reactive form values object.errors: Reactive field validation errors mapped from server Ash changesets (form.errors.title).valid: Boolean indicating if form has zero errors.submit(): Submits form and returnsPromise<AlvaResult<T>>.reset(newValues?): Restores initial form values, clears error maps, and setsvalid = true.
Code Example:
<script setup lang="ts">
import { useAlva } from "@/js/alva";
const alva = useAlva();
const stockForm = alva.catalog.use_adjust_stock_form({
initialValues: { id: "product-123", stock: 10 }
});
const submitStock = async () => {
const result = await stockForm.submit();
if (result.ok) {
stockForm.reset(); // Restores initial values & clears error maps
}
};
</script>4.3 1-Line Reactive Signal State (use_<signal>_state)
const { data, latest, count, clear } = alva.<domain>.use_<signal>_state(input?)Return Object:
data:Ref<Payload[]>— Reactive array of all received signal payloads (newest first).latest:Ref<Payload | null>— Reactive reference to the most recent signal payload.count:ComputedRef<number>— Count of accumulated payloads.clear(): Clears accumulated signal state.
Code Example:
<script setup lang="ts">
import { useAlva } from "@/js/alva";
const alva = useAlva();
// Auto-subscribes on mount, detaches on unmount
const { data: notifications, latest, count, clear } = alva.demo_notifications.use_sent_state();
</script>
<template>
<div>
<h3>Notifications ({{ count }})</h3>
<button @click="clear">Clear All</button>
<ul>
<li v-for="item in notifications" :key="item.id">
{{ item.title }} - {{ item.severity }}
</li>
</ul>
</div>
</template>4.4 Domain File Uploads (use_<action>_upload)
const upload = alva.<domain>.use_<action>_upload(options?: {
maxFiles?: number;
maxSize?: number;
})Code Example:
<script setup lang="ts">
import { useAlva } from "@/js/alva";
const alva = useAlva();
const upload = alva.catalog.use_upload_media_upload({ maxFiles: 1 });
const saveImage = async () => {
const result = await upload.dispatch(async (ctx) => {
return await alva.catalog.upload_media({
id: "product-123",
media: ctx.primaryReference
});
});
};
</script>