:title
10. Units and components

:content
Each Capy source file is an independent unit. A unit can expose functions and type metadata to other units. It can also render named components.

## Export a function

`#exports` lists the functions and types that another unit can inspect. A callable `(dval) dval` function can also be called through a module handle:

:example capy render
#exports echo

function echo(input : dval) dval
{
	-> input
}

function RENDER()
{
	var result := echo(message: "ready")
	print(result.message, "\n")
}

:output
ready

:content
A module-callable function has the exact signature `(dval) dval`. Private helper functions do not appear in `#exports`.

## Call another unit

A separate service unit can expose a data function:

:example capy cli
#exports version

function version(input : dval) dval
{
	-> { name: "Capy" }
}

:content

Load and call it from a neighboring page:

:example capy render
function RENDER()
{
	var service := unit_load("/doc/examples/call_target.capy")
	var version := service.default_input()
	print(string(version), "\n")
}

:content

`unit_load` returns a request-local `module` handle. Module member calls are dynamic. `service.default_input()` calls the exported function named `default_input` on that loaded unit. The input it receives and the result it returns are the same objects on both sides; nothing is copied. `unit_call("path:name", input)` performs the same operation without keeping a handle.

Relative paths resolve from the calling unit. Use an absolute site path when the target must not depend on the caller directory.

## Module handles

A module handle is a verified request-local capability. It can pass through typed locals, parameters, and results during that request. It cannot enter a `dval`, aggregate, closure, condition, or serialized boundary.

## Static imports

:content
Use `#import "path" as name` to reference another unit at compile time. The alias names that unit's exported types and its exported functions.

An exported type is valid in any type position, including a constructor:

:example capy render
#import "/doc/examples/call_target.capy" as example

function RENDER()
{
	var info : example.VersionInfo = example.VersionInfo("Capy")
	print(info.name, "\n")
}

:output
Capy

:content
An exported `(dval) dval` function is called through the same alias. The path is written once, and the compiler checks the export name where the call appears:

:example capy render
#import "/doc/examples/call_target.capy" as example

function RENDER()
{
	var echoed := example.echo(message: "through the alias")
	print(string(echoed.message), "\n")
}

:output
through the alias

:content
The `message: "through the alias"` argument is a map entry. Adjacent map entries form the one `dval` input of the export, so the call needs no braces.

`example.echo(props)` means the same thing as `unit_call("/doc/examples/call_target.capy:echo", props)` and compiles to the same call. The difference is when a mistake surfaces: a misspelled export, or one whose signature is not `(dval) dval`, is a compile error at the call, not a message in the response.

A path beginning with `/` is a web path under the site root, the same form `unit_call()` and `component()` accept. A relative path resolves from the importing unit.

A static import reads metadata at compile time. It does not create a runtime module handle, and the alias is not a value. Use `unit_load()` when the target unit must be chosen at runtime.

Changing an imported unit recompiles the units that import it, because the import graph is part of each unit's freshness signature.

Do not retain a module handle for another request. A later request can load a newer valid source generation.

## Unit variables

A top-level `var` belongs to the unit, not to one function. Bearer initializes unit variables in order when it materializes the unit in a workspace, and every function and closure in the unit reads and assigns them directly:

:example capy render
var calls := 0
var seen : string = ""

function COMPONENT:TICK(props : dval)
{
	calls = calls + 1
	seen = seen + string(props.tag)
}

function RENDER()
{
	component_render(":TICK", tag: "a")
	component_render(":TICK", tag: "b")
	print(string(calls), " ", seen, "\n")
}

:output
2 ab

:content

A unit variable lives as long as the workspace. In a request unit that is the request, so the next request starts from the initializers again. In a service unit it is the whole life of the service, which is where a service keeps its state. An initializer cannot read `request`, and it sees only the unit variables declared above it.

## Request keys

:content
All handlers in a request receive the same request object. Store request-wide application state in application keys:

:example capy render
function RENDER()
{
	request.user = {
		name: "Ada",
		scope: "admin"
	}
	component_render("/doc/examples/card.capy:CARD")
	print(string(request.pagefacts.title), "\n")
}

:content
A component can read `request.user` and write `request.pagefacts`. The caller sees the change after the component returns. Bearer reserves `call`, `unit`, `out`, `session`, `websocket`, and `connection`. Do not use these keys for application state.

An exported `(dval) dval` function captures `request` implicitly. This applies to functions listed in `#exports` and functions with an `EXPORT_` prefix:

:example capy render
#exports greeting

function greeting(input : dval) dval
{
	-> { text: "Hello, " + string(request.user_name, fallback: "guest") }
}

function RENDER()
{
	request.user_name = "Ada"
	print(string(greeting({}).text), "\n")
}

:output
Hello, Ada

:content
The export reads the same request object as the caller. Do not pass the request inside its input map.

## Components

A component writes reusable response content through output functions such as `print`. A named component uses a handler name:

:example capy cli
function COMPONENT: CARD(props : dval)
{
	print(<><article><?= string(props.title, fallback: "Untitled") ?></article></>)
}

:content

Render it into the response:

:example capy render
function RENDER()
{
	component_render("/doc/examples/card.capy:CARD", title: "News")
}

:content

Use `component()` when the caller needs the component output as a string. The component receives its input through the `props` parameter.

Props are shared objects. Changes to their contents remain visible to the caller. Nested components receive their own props argument. Application request keys stay shared throughout.

## Boundary rules

Inside one request everything is shared: component props, module and unit call inputs and results, exports, and the request value. A value is copied only where it leaves the process memory: tasks, codecs, serialization, files, sockets. The dynamic-values guide states the rule.

Missing units, missing exports, and incompatible exported signatures are errors.

## Choose a boundary

Use a component when the result is markup for the current page. Use an exported function when the result is data. Use a direct helper function only inside one source file.

A module loaded with `unit_load()` lives only for the current request. Store data in a session, a file, a database, or a task result when it must outlive the request.

Keep component props narrow. Pass only the values the component needs. This makes examples, tests, and future replacement easier.

:see-group
guides

:see
unit_load
unit_call
component_render
handler/component
capy-08-dynamic-values
capy-11-tasks-and-jobs
call
dval
