Askr documentation
Reference

Behavioral Contracts

Behavioral Contracts: published entrypoints, signatures, and the constraints around them.

Example

Preserve ownership, cancellation, deterministic rendering, and delivery parity when composing lower-level APIs into application features.

import { resource } from '@askrjs/askr/resources';

const project = resource(
  ({ signal }) => api.projects.get(projectId(), { signal }),
  [projectId]
);

// The component owns this request; disposal aborts it.

Render contract

A component function is a ComponentFunction: it takes Props and an optional ComponentContext (carrying a signal and, during SSR, an SSRContext) and returns a JSXElement or VNode. Calling state() is only valid during that render call — it reads the active component instance from context, and calling it outside a component function throws, per the runtime's own guard.

Ownership

Reactive scope is threaded with defineScope()/readScope() rather than any global mutable state — defineScope(defaultValue) creates a Scope<T> you provide via a wrapping element, and readScope() reads the nearest value down the component tree. Internally each mounted component tracks its own ownerFrame, stateValues, and cleanup functions, so state and scope values are torn down with the instance that created them.

Cancellation

Every component receives an AbortSignal through its render context, and ComponentInstance holds the backing AbortController used to cancel in-flight work when the component unmounts. resource() (from @askrjs/askr/resources) is built on this — it passes the signal into your async function so a fetch or other cancelable call stops when the component goes away, instead of resolving into a dead component.

Delivery parity

The same component tree renders through server rendering (SSR), static generation (SSG), and client hydration — the framework doesn't fork component behavior between server and client render paths. The two sides don't share one context type end to end, though: SSR and SSG document-wrapping share a DocumentRenderContext (mode: 'ssr' | 'ssg', url, params, seed, route), while the client's own render pass carries a separate ActiveRenderContext (mode: 'ssr' | 'spa') built specifically for hydration and client-only rendering. Hydration reconciles the server-rendered markup against the client's first render pass rather than re-rendering from scratch, so component output needs to be stable across both invocations for hydration to succeed cleanly.