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
| Scope | Belongs to | Lifetime |
|---|---|---|
application | Everyone. The default, and what every repository was before. | The process |
session | An identity — authenticated, survives reconnects, revocable | Until it expires or is deleted |
connection | A transport peer that identifies nobody | The 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
- In-memory storage - Data is lost on application restart
- Automatic IDs - UUIDs are generated for entities without an
id - Type safety - Define schemas in OpenAPI for validation
- Deduplication - Storing the same entity twice updates the existing one