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

Reference / Entity

entity

entity <Name> { <members> }

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id and audit columns from the platform. Use a class instead when the value only lives in memory.

stable3 examples compiled by CIentitymodeldata

Summary#

An entity is a persisted type: a table of rows the app stores, queries and secures. It is the noun your application is about — Order, Customer, Invoice. Declaring one is enough to get a table, an identity, an audit trail and a query surface; you write the members, the platform provides the rest.

If the value never needs to be stored — a computed shape, a DTO, the result of a calculation — use a class instead. A class lives in memory and has no table.

Signature#

entity <Name> {
  <attributes> <Type> <Member>;    // data
  invariant <condition>;           // row-level rules
  security { … }                   // who may read and write it
}

Description#

Which members do I get for free?#

Every entity already has these. Do not declare them — a member of the same name is a compile error, because the platform is already providing one:

MemberTypeWhat it is
IdGuidthe row's identity, assigned on create
CreatedAt · ModifiedAtDateTimewhen the row was written
CreatedBy · ModifiedByGuidwho wrote it

So an entity with two declared members has seven columns. You read them like any other member — order.CreatedAt works without you writing it.

How do I create a row, and when is it saved?#

A row is created with new, exactly as in C#. It is persisted when the function commits — there is no Save() call to forget:

entity Customer {
  [Required] string Name;
  string Email;
}

void Register(string name, string email) {
  var c = new Customer { Name = name, Email = email };
  // no Save() — the row is written when the function commits
}

Reading is a query over the type itself — you name the entity and write LINQ against it:

int ActiveCustomers() {
  return Customer.Where(c => c.Email != null).ToList().Count;
}

Customer ByName(string name) {
  return Customer.Single(c => c.Name == name);   // exactly one, or it faults
}

The predicate is compiled into the database query — it is not a filter over rows you already fetched — so the shape of what you can ask for is worth knowing before you write your first loop:

I want to…Read
filter, and pick one row or count themWhere / Single / CountWhere · Single · FirstOrDefault · Count · Any
run the query and get the rowsToListToList, and why you should call it once
show page 3 of 40Skip / Take (paging)OrderBy · Skip · Take
drop duplicatesDistinct — and why it does nothing on whole rows
combine two result setsUnion / Concat / Intersect / ExceptUnion · Concat · Intersect · Except
match against a list I have in handDynamic IN (list.Contains in a query)list.Contains(x) inside a query
walk a parent's childrenrelations — through the collection, never a filtered query

Reaching for a foreach to do work the query could have done is the most common way an application that was fast on a laptop becomes slow in production.

Entity or class?#

entityclass
Stored in the databaseyes — it has a tableno — in memory only
Has an Id and audit columnsyes, from the platformno; it has only what you declare
Queryable (Where, Single, …)yesno
Securable (security { … })yesno — it holds no rows to protect
Good forthe nouns you persistDTOs, computed shapes, JSON bodies

The rule of thumb: if you will ever query it, it is an entity.

Security: an entity nobody can read is the DEFAULT#

This is the part to read twice, because it is where the platform will surprise you — deliberately.

The posture is deny-all. An entity that declares no security { } block is denied to every user request. Not "readable by signed-in users", not "readable by its owner" — denied. Your beautifully modelled Order entity is, until you say otherwise, an entity that no user of your application can read, write or count.

That is the correct default, and it is chosen on purpose. The failure mode of forgetting a rule is "nobody can do it" — which someone reports within the minute — rather than "everybody can", which nobody reports until it is somebody else's headline. A door that fails shut is a door you can trust.

So declaring who may read an entity is part of declaring the entity. It is not a hardening pass you schedule for later; there is no working application before you have done it.

[Principal] entity User {
  [Required] string Name;
}

entity Note {
  User Owner;
  string Body;
  security { allow read where Owner == user; }
}

Note what the block does not contain: there is no default deny. Everything is denied already, so a security { } block is a list of grants — you only ever write what is allowed. (To lock an entity completely, write no block at all.)

Two rules, two different questions, and the distinction is the whole model:

  • where filters by the rowthe owner sees their own notes.
  • when gates by the principalstaff see every note.

And note what you cannot do: a bare allow read; is a compile error on an app that has a principal. The platform makes you say who may read — allow read when IsAuthenticated;, or IsAuthenticated || IsAnonymous if you really do mean the whole internet. Opening a door is allowed; opening one by accident is not.

Read these three, in this order, before you ship anything:

  1. secure by default (deny-all) — the deny-all posture, and what it does and does not cover
  2. security { } — the security { } block in full: where, when, and named policy predicates
  3. runas — because a security rule you have not tested is a rule you only believe you wrote

That last one is not a flourish. Security is the one area where the code compiling, the tests passing and the feature working tell you nothing about whether it is correct — the only way to know that Bob cannot read Alice's note is to become Bob and try.

See also#

  • entity members — the member types an entity can hold
  • relations — pointing one entity at another, and reading the children back
  • constraints[Required], [Unique], [MaxLength], ranges and patterns
  • invariant — row-level rules enforced when the row is written
  • constructor — the in-memory counterpart, for values you never store
  • secure by default (deny-all) — the deny-all posture: what an entity permits before you say anything
  • security { } — the security { } block in full
  • Where / Single / Count — querying the rows, and the rest of the LINQ surface
  • runas — proving your rules deny the people they should

Related

entity members

The typed members an entity holds — text, numbers, dates, booleans, Guids, enums and references. A member's type…

relations

One entity points at another by declaring it as a member — that is the foreign key. The parent reads its children back…

constraints

The per-member rules the database enforces — Required, Unique, MaxLength/MinLength, Min/Max, Pattern, and the…

invariant

A row-level rule spanning several members, checked when the row is written. Use it when a constraint on one member is…

secure by default (deny-all)

Deny-all is the posture, and it is the only one: an entity that declares no `security { }` block is denied to every…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…

Where / Single / Count

Query an entity by writing a predicate over it. The query runs in the database — not a filter over rows you already…