The FDD Story Get Started Tutorial Docs Motivation Download GitHub

Repositories

ARO provides built-in in-memory repositories for storing and retrieving data. Repositories are automatically created when you store data using the naming convention <entity-repository> and support querying, updating, and observation.

Storing Data

Store entities into a repository using the Store action:

Create the <user: User> with {
    name: "Alice",
    email: "alice@example.com"
}.
Store the <user> into the <user-repository>.

The repository is automatically created on first use. Each stored entity receives a unique id if not already present.

Retrieving Data

Get All Entities

Retrieve the <all-users> from the <user-repository>.

Query by Field

Retrieve the <user> from the <user-repository> where id = <userId>.

Retrieve the <admins> from the <user-repository> where role = "admin".

Get First or Last

Retrieve the <latest: last> from the <message-repository>.
Retrieve the <oldest: first> from the <message-repository>.

Updating Data

(* Retrieve, change, store back. There is no in-place repository
   update statement — and the changed record binds a NEW name. *)
Retrieve the <user> from the <user-repository> where id = <userId>.
Update the <active-user> with { status: "active", lastLogin: <now> }.
Store the <stored-user> into the <user-repository> with <active-user>.

Deleting Data

Delete the <removed-user> from the <user-repository> where id = <userId>.

Repository Observers

React to repository changes with observer feature sets. Observers are triggered whenever data is stored, updated, or deleted.

Observer Pattern

(Log User Changes: user-repository Observer) {
    Extract the <action> from the <event: action>.
    Extract the <entity> from the <event: entity>.
    Log "User repository: " to the <console>.
    Log <action> to the <console>.
    Return an <OK: status> for the <observation>.
}

Conditional Observers with when Guard

Add a when clause to trigger observers only when a condition is met. Use <repository-name: count> to access the repository size.

(* Only triggers when message count exceeds 100 — a guard may ride
   on the feature-set header, not only on a statement *)
(Cleanup Messages: message-repository Observer) when <message-repository: count> > 100 {
    Retrieve the <all-messages> from the <message-repository>.
    Extract the <keep-messages: 0-49> from the <all-messages>.
    Clear the <all> from the <message-repository>.
    Store the <keep-messages> into the <message-repository>.
    Return an <OK: status> for the <cleanup>.
}

The when guard is evaluated before the observer runs. If the condition is false, the observer is silently skipped. This prevents infinite loops when the observer modifies the same repository it observes.

The observer receives an event with:

Property Description
action "stored", "updated", or "deleted"
entity The affected entity
repository Repository name

Complete Example: User Management

api.aro

(createUser: User API) {
    Extract the <body> from the <request: body>.
    Create the <user: User> with {
        name: <body: name>,
        email: <body: email>,
        createdAt: <now>
    }.
    Store the <user> into the <user-repository>.
    Return a <Created: status> with <user>.
}

(listUsers: User API) {
    Retrieve the <users> from the <user-repository>.
    Return an <OK: status> with <users>.
}

(getUser: User API) {
    Extract the <id> from the <pathParameters: id>.
    Retrieve the <user> from the <user-repository> where id = <id>.
    Return an <OK: status> with <user>.
}

(deleteUser: User API) {
    Extract the <id> from the <pathParameters: id>.
    Delete from the <user-repository> where id = <id>.
    Return an <OK: status> for the <deletion>.
}

observers.aro

(Audit User Changes: user-repository Observer) {
    Extract the <action> from the <event: action>.
    Extract the <user> from the <event: entity>.

    Create the <audit-entry> with {
        action: <action>,
        userId: <user: id>,
        timestamp: <now>
    }.
    Store the <audit-entry> into the <audit-repository>.

    Return an <OK: status> for the <audit>.
}

Who a Repository Belongs To

A repository is keyed by name alone and is shared across the whole application — and that includes callers. On a multi-user server every visitor reads and writes the same rows, which is right for a catalogue and wrong for a cart.

Say who it belongs to once, where it is declared (ARO-0094). The scope is a Configure setting, beside ttl and maxSize:

Configure the <catalogue-repository: scope> with "application".
Configure the <cart-repository: scope>      with "session".
Configure the <partial-repository: scope>   with "connection".

Handlers do not restate it. Store the <item> into the <cart-repository>. is the same statement over HTTP, WebSocket and TCP, and it writes to the calling caller's partition:

(addToCart: Shop API) {
    Extract the <item> from the <request: body>.
    Store the <item> into the <cart-repository>.
    Return a <Created: status> with <item>.
}

The three scopes

ScopeBelongs toLifetime
applicationEveryone. The default, and what every repository was before.The process
sessionAn identity — authenticated, survives reconnects, revocableUntil it expires or is deleted
connectionA transport peer that identifies nobodyThe connection

A TCP peer gets connection until Attach the <session> to the <connection>. promotes it. Promotion is irreversible within a connection.

A caller-scoped repository that cannot resolve its caller throws

Never the application-wide repository, and never an empty one — silently serving the wrong rows is the failure this exists to prevent. aro check reports the statically visible cases: a session repository used in Application-Start, a connection repository on an HTTP route, and conflicting declarations.

The scope is a string. { scope: session } is a variable reference and fails.

Sessions are the runtime's

Minting a 256-bit CSPRNG id, signing the cookie with HMAC, setting HttpOnly/Secure/SameSite, validating on every request, refreshing last-seen, rotating on Attach, and evicting on disconnect and expiry are all the runtime's job. Everything after that is ordinary ARO against the application-scoped <sessions-repository> — so logging out is a Delete:

(logout: Shop API) {
    Extract the <id> from the <session: id>.
    Delete the <gone> from the <sessions-repository> where id = <id>.
    Return an <OK: status> with { reason: "logout" }.
}

The cookie is declared in openapi.yaml as securitySchemes: { type: apiKey, in: cookie }, and a cookie scheme is satisfied by a validated session, not by the cookie merely being present. Developing over plain HTTP, Configure the <session: secure> with false. opts out of the TLS requirement.

Important Notes


← Templates | Data Pipelines →