Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Function

execution side

Side = Server | Client | Either (inferred from a function's body)

Where a function runs. Osy# infers it from the body: a function that reads data runs on the server, a function that touches the router or the theme runs in the browser, and a pure function runs wherever the caller already is.

stable2 examples compiled by CIfunctionuiruntimeauthoring

Summary#

Where a function runs. Osy# infers it from the body: a function that reads data runs on the server, a function that touches the router or the theme runs in the browser, and a pure function runs wherever the caller already is. You do not write the side, and you do not wire the round trip — calling a function that runs elsewhere is an ordinary call.

Signature#

Side = Server | Client | Either   // inferred — never spelled in source

Description#

Every function, method and constructor has an execution side: the place its body runs. There are three.

SideMeaning
ServerThe authority. Anything that reads the data store, checks security, or touches a secret.
ClientThe browser. Anything that acts on something only the browser has — the router, the session, the theme.
EitherPure work. It runs wherever the caller already is, and never forces a trip across the network.

You never declare the side. The compiler reads the body and works it out, then works out every caller's side from that, and so on up the call graph. The rule is simply: a function is at most as client-side as the most server-side thing it can reach.

string Greeting(string name) {           // Either — pure. Runs in the browser if that's where you called it.
  return "welcome " + name;
}

User CurrentUser(string email) {         // Server — it reads the data store.
  return Users.Single(u => u.Email == email);
}

string Welcome(string email) {           // Server — because it calls CurrentUser.
  return Greeting(CurrentUser(email).Name);
}

Welcome is Server even though its own body looks pure, because it reaches a data read. That is the whole point: the side is a property of what a function can actually do, not of what its top line looks like.

Why this matters#

Calling a function that runs on the other side is still just a call. You write Login(email, password) and the platform does the rest: it evaluates the arguments where you are, suspends, runs the callee on the other side, and resumes you with the result — including through a try/catch, which behaves exactly as it would locally.

What the side changes is the cost. A call to an Either function from a browser action runs in the browser, in process, with no network at all. The same call to a Server function is a round trip. Because the side is inferred rather than assumed, a helper that merely formats a string does not silently cost you a request.

A body can mix sides#

A Server function may still do client-located work — show something, ask the user, navigate — and the platform hands that piece back to the browser and picks up where it left off:

[Page("/orders")]
component OrderPage() {
  action Cancel(Order order) {           // a browser action
    if (Confirm(order)) {                // ↩ runs on the server: it reads and validates…
      Navigation.Go("/orders");          //   …and this line comes back to the browser
    }
  }
}

A body with both a server anchor and a client anchor is Server: it starts on the authority and hands its client-located parts back. That is not a compromise — it is how a flow can validate on the server and still ask the user something in the browser, in one straight-line function.

The standard library is pure, so it runs where you are#

Calling Text.Upper, Text.Trim, Text.Substring or string.Join — and the instance spellings that lower onto them, like name.ToUpper(), s.Trim() and s.Length — does not make a function Server. They are pure operations on a value you already hold, so they run wherever the caller is:

string Initials(string first, string last) {          // Either — no round trip
  return first.Substring(0, 1) + last.Substring(0, 1);
}

The same goes for name.Contains("x"), StartsWith and EndsWith.

Some library calls are server-anchored, and for reasons worth stating: Security.HashPassword needs the host's salt generator, Security.IssueJwt needs the host's signing key, and Crypto.Encrypt needs an encryption key the browser must never hold. A function that calls one of those is Server, as it should be.

Where a library call cannot yet run in the browser, it simply runs on the server — the answer is the same, the call just costs a round trip. Correctness never depends on which side a pure call lands on: both sides are held to the same answers, character for character, down to how Text.Upper treats the German ß.

The one thing that is not negotiable#

A query over the data store is always Server — the data lives on the server, so reading it is a server operation. An entity query pins its function to the server, always.

A query over a local list is not a data read, and does not:

int Cheap(List<Line> lines) {                    // Either — runs in the browser
  return lines.Where(l => l.Price < 10).Count();
}

Examples#

string Initials(string first, string last) {
  return first.Substring(0, 1) + last.Substring(0, 1);
}
entity Customer { string Email; string Name; }

string NameFor(string email) {
  return Customer.Single(c => c.Email == email).Name;
}

Pinning it — [Client] and [Server]#

Side is INFERRED from the body, and that is the normal case. [Client] and [Server] are an ASSERTION on top of that inference, and they PIN the answer:

[Client] string Initials(string name) { … }   // must stay client-runnable
[Server] decimal Rate() { … }                 // must stay on the server

Two things they buy. A helper that quietly stops being client-runnable — someone adds a data read three calls down — turns into a silent network round trip today, and nothing tells you; pinned, it is a compile error. And they resolve ambiguity for a body whose behaviour depends on which engine runs it: .NET and JS regular expressions are different dialects, so the same pattern can match differently depending on where the cursor happens to be. There an Either body is the hazard, and a pin in either direction removes it.

⚠ Marking a function both is refused — it runs in one place or the other.

See also#

Related

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…

LINQ over a local list

Query a local `List<T>`, `HashSet<T>` or `T[]` — of your own `class` values OR of plain scalars like `string[]` and…

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…