Application API
Lifecycle, ownership, outcomes, and extension points for the Varendo application facade.
Create one app, mount Regions, and send intents. Every call returns a typed receipt with a stable outcome.
Varendo 0.1.0 is the published stable release. Install all Varendo packages at the same exact version.
Import the application facade from @varendo/web/app. This entry point uses
@varendo/runtime; component-only consumers can import the web package root.
Use matching package versions for all application dependencies.
createAeliqoApp#
Signature
createAeliqoApp(options: AeliqoAppOptions): AeliqoApp
Required: a resources array with at least one resource/data binding, plus an authority adapter. Optional: a runtime ID, custom intents, an action port, a result store and its options, and region/render limits. You can also pass recipes, views, presentation patterns, state mappings, a form-state adapter, and an action-event callback. Standard recipes are used when no recipes are supplied.
mount#
Signature
app.mount({target, regionId, resourceId}): Outcome<AeliqoRegionElement>
Creates one Region and begins container observation. Region IDs are unique within the app. Mount does not fetch data until render.
render#
Signature
await app.render({regionId, intent, signal?}): Promise<WebRenderReceipt>
Parses unknown input, reads current authority, compiles, evaluates, resolves a recipe or registered pattern, validates state transfer, commits, and returns a typed receipt. A newer request in the same Region supersedes an older one.
subscribe and snapshot#
Signatures
app.subscribe(regionId, listener): () => void
app.snapshot(regionId): RuntimeRegionState | undefined
Subscribers receive sanitized lifecycle state, Result references, and diagnostics—not materialized rows. Subscription is safe before the Region child lifecycle completes.
unmount and dispose#
Signatures
app.unmount(regionId): boolean
app.dispose(): void
Unmount cancels and releases one Region. Dispose is idempotent and releases every Region, listener, pending render, Result handle, and owned observer. Calls after disposal fail closed.
Receipts#
renderer-ready, needs-input, denied, cancelled, unsupported, and failed are distinct. A failed presentation leaves the previous authorized task, results, and UI in place. Candidate data is published only after the root renderer succeeds. Revocation clears inaccessible content. The receipt includes bounded diagnostics; it does not claim the rejected candidate committed.
Source declarations#
import type {
Diagnostic,
Intent,
InteractionPayload,
Outcome,
ReadonlyJsonValue,
ResourceDefinition,
Task,
} from '@varendo/core';
import type {
PresentationEnvironment,
PresentationPatternManifest,
PresentationStateMappingManifest,
ValidatedPresentation,
} from '@varendo/core/presentation';
import type {
AeliqoRuntime,
AeliqoRuntimeOptions,
RuntimeRegionState,
RuntimeRenderReceipt,
RuntimeUnsubscribe,
} from '@varendo/runtime/app';
import type { ScopeLeaveDecision } from '@varendo/runtime/scopes';
import type { ActionExecution, ActionOutcome, ActionPreview } from '@varendo/runtime/actions';
import type { AeliqoRegionElement } from '../region/aeliqo-region.js';
import type { AeliqoViewDefinition } from '../region/types.js';
import type { RecipeDefinition } from '../recipes/types.js';
export interface AeliqoAppOptions extends AeliqoRuntimeOptions {
readonly recipes?: readonly RecipeDefinition[];
readonly views?: readonly AeliqoViewDefinition[];
/** Trusted, bounded layout patterns resolved against the registered resource policy. */
readonly patterns?: readonly PresentationPatternManifest[];
/** Transitions implemented by the host renderers; registration never grants data access. */
readonly stateMappings?: readonly PresentationStateMappingManifest[];
/** Observes successful DOM publication, including container/media adaptation; never controls authority. */
readonly onPresentation?: (receipt: RendererReadyReceipt) => void | Promise<void>;
/** Host-owned Save/Discard/Stay decision before replacing a dirty form. */
readonly onDraftExit?: (request: AeliqoAppDraftExitRequest) => ScopeLeaveDecision | Promise<ScopeLeaveDecision>;
/** Trusted host adapter for initial create/edit values. Edit forms require it so stale data is never invented. */
readonly formState?: AeliqoFormStateAdapter;
/** Receives user-originated, boundary-validated action lifecycle events. */
readonly onActionEvent?: (event: AeliqoAppActionEvent) => void | Promise<void>;
}
export interface AeliqoAppDraftExitRequest {
readonly regionId: string;
readonly intent: unknown;
readonly drafts: readonly Extract<InteractionPayload, { readonly kind: 'draft' }>[];
readonly revision: string;
readonly signal: AbortSignal;
}
export type AeliqoAppActionEvent =
| {
readonly state: 'preview';
readonly regionId: string;
readonly preview: ActionPreview;
confirm(): Promise<ActionOutcome<ActionExecution>>;
cancel(): boolean;
}
| { readonly state: 'executed'; readonly regionId: string; readonly execution: ActionExecution }
| {
readonly state: 'failed';
readonly regionId: string;
readonly diagnostics: readonly [Diagnostic, ...Diagnostic[]];
};
export interface AeliqoFormState {
readonly values: Readonly<Record<string, ReadonlyJsonValue>>;
readonly entityRevision: string;
}
export interface AeliqoFormStateRequest {
readonly regionId: string;
readonly resource: ResourceDefinition;
readonly intent: Extract<Intent, { readonly kind: 'create' | 'edit' }>;
readonly task: Extract<Task, { readonly kind: 'form' }>;
readonly signal: AbortSignal;
}
export interface AeliqoFormStateAdapter {
read(request: AeliqoFormStateRequest): Outcome<AeliqoFormState> | Promise<Outcome<AeliqoFormState>>;
}
export interface WebMountInput {
readonly target: HTMLElement;
readonly regionId: string;
readonly resourceId: string;
}
export interface WebRenderInput {
readonly regionId: string;
readonly intent: unknown;
readonly signal?: AbortSignal;
}
export interface RendererReadyReceipt {
readonly status: 'renderer-ready';
readonly requestId: string;
readonly regionId: string;
readonly runtime: Extract<RuntimeRenderReceipt, { readonly status: 'committed' }>;
readonly presentation: ValidatedPresentation;
readonly environment: PresentationEnvironment;
readonly diagnostics: readonly [];
}
export interface RendererFailureReceipt {
readonly status: 'unsupported' | 'failed' | 'cancelled' | 'needs-input';
readonly requestId: string;
readonly regionId: string;
/** The trusted runtime evidence remains inspectable even when presentation cannot commit. */
readonly runtime: Extract<RuntimeRenderReceipt, { readonly status: 'committed' }>;
readonly diagnostics: readonly [Diagnostic, ...Diagnostic[]];
}
export type WebRenderReceipt =
RendererReadyReceipt | Exclude<RuntimeRenderReceipt, { readonly status: 'committed' }> | RendererFailureReceipt;
export interface AeliqoApp {
readonly runtime: AeliqoRuntime;
mount(
input: WebMountInput,
):
| { readonly ok: true; readonly value: AeliqoRegionElement }
| { readonly ok: false; readonly diagnostics: readonly [Diagnostic, ...Diagnostic[]] };
render(input: WebRenderInput): Promise<WebRenderReceipt>;
snapshot(regionId: string): RuntimeRegionState | undefined;
subscribe(regionId: string, listener: (state: RuntimeRegionState) => void): RuntimeUnsubscribe;
unmount(regionId: string): boolean;
dispose(): void;
}Layout registration#
patterns?: readonly PresentationPatternManifest[] registers trusted layout
expanders. stateMappings?: readonly PresentationStateMappingManifest[] declares
transitions implemented by the renderers. Import these types from
@varendo/core/presentation. Both registrations are validated when the app is
created. Patterns reuse the same resolver and validator as standard recipes.
Custom recipes receive context.results, all output descriptors, and
context.result, the first descriptor for compatibility. If a custom intent has
no matching recipe, a registered pattern must match. Data children remain
subject to the mounted resource's presentation.allowedViews.
Observe an applied presentation#
onPresentation(receipt) runs after a successful renderer update, including
container and media changes. Use it to update a view label or diagnostics panel.
receipt.environment contains the dimensions used by the resolver. Resize
reuses the current results; it does not request data or call a model.
const app = createAeliqoApp({
resources,
authority,
onPresentation(receipt) {
console.log(receipt.regionId, receipt.presentation.plan.rootId);
},
});
Observer failures do not roll back a committed presentation. app.subscribe
continues to report runtime state; a runtime commit alone is not a DOM-ready event.
Protect an unsaved draft#
onDraftExit(request) gives the host a Save/Discard/Stay decision before
app.render replaces a dirty form. The request contains an immutable copy of
the drafts, the next intent, a revision, and an abort signal. Return
{ status: 'stay' } to keep editing, { status: 'discard' } to permit replacement,
or { status: 'save', save: async ({ signal }) => outcome } to run your own save.
A successful save returns { ok: true, value: undefined }; a failure returns
an Outcome with diagnostics. Without a handler, a dirty exit returns
needs-input and retains the current draft.
New edits, a newer render, disposal, or revoked authority invalidate pending exit work. Honor the supplied signal in host requests. A failed replacement keeps the authorized previous UI and draft; a host save that already completed is a business effect and is not undone automatically.
renderer-ready confirms the synchronous Region root update. It does not wait for asynchronous descendant components, network content, or browser paint. Custom view render callbacks run synchronously and must keep business effects in registered actions.
Host renderer contract
A custom view and any nested Lit directive callbacks must be synchronous, deterministic, and free of business effects. Capture immutable inputs in the returned template; do not make a previous template depend on changing external state. This lets a rejected update replay the last authorized template while retaining its keyed children. Perform effects through registered actions.
A directive that mutates the DOM independently or throws while replaying a previous template is outside this contract. The candidate remains unpublished, but the renderer clears the affected region if restoration also fails. Fix the host callback and remount the region before retrying. Varendo cannot roll back arbitrary effects performed by application code.
