Summary#
A kit — or an app — says what HAPPENS to its records as classes:
/// The customer accepted a quote.
[Event("Quote accepted")]
public sealed class QuoteAccepted { public Quote Quote; }It raises one where it happens, and anybody who cares hears it:
Raise(new QuoteAccepted { Quote = q }); // in the code that accepts the quote
[On] void WinOnAccept(QuoteAccepted e) { … } // in any package that sees QuoteAccepted, or in the appThe raiser does not know who listens, and a listener does not call the raiser. That is what lets two kits written apart work together: a kit that depends on another may hear its events, and an app hears every kit's.
Signature#
[Event] // its label is its name in words — "Quote accepted"
[Event("Quote accepted")] // …or the words you give it
public sealed class <Name> { // an `Event` — the base is implicit
public <Record> <Member>; // the record it is about — its first record-typed member
public <T> <Member>; // anything else a handler needs to know — values, more records
public override string Sentence() { … } // optional — what it says; its label when not overridden
}
// What a kit that works on ANY event asks — server-side:
List<EventInfo> Events.Catalog() // every PUBLIC event in the app and its packages: Type, Label, Name, About
string Events.Name(Event e) // its name on the wire — "order-shipped", "knowledge-base.article-published"
string Events.Label(Event e) // its label, in the reader's language
string Events.Type(Event e) // its class's full name
Entity? Events.About(Event e) // the record it is about
string Events.Write(Event e) // the event as text, to keep
Event? Events.Read(string text) // …and back — e.Sentence() then says it as the reader reads it
EventDelivery? Events.Delivery() // inside a handler: this event's Id, Root, Depth, RaisedAt, CauseDescription#
What makes a class an event?#
The [Event] mark. Without it, Raise(new X { … }) is refused and names the mark to add; with it, X is an event
everywhere the class can be seen. An event is a class, never an entity — it is what happened, not a row you keep —
and it carries whatever its handlers need: the record it is about, who did it, the reason, a value from before.
Its label is what people read it by — in a rule's trigger, on a timeline — and defaults to the class name in
words (QuoteAccepted → "Quote accepted").
Which record is it about?#
The first record-typed member. The platform keeps it beside the event, so what happened to a record can be found from the record.
What happens when nobody listens?#
Nothing. The compiler knows every handler in the app and its packages, so an event no [On] hears has no delivery:
raising it writes no row and sends no statement. A kit can raise every event a person might care about, and an app
pays only for the ones it hears.
What does it say?#
Every [Event] class is an Event, and Sentence() is what it says — on a timeline, in a bell, in a mail. Not
overridden, it says its label; overridden, it says more, from its own members:
public override string Sentence() { return $"{Quote.Customer.Name} accepted the quote."; }It runs when it is READ, as whoever reads it, in their language — so a kit keeps the event (Events.Write) and says it
later (Events.Read(kept).Sentence()): a name changed since is said as it is now, an erased customer's name is never in
a stored sentence, and a record the reader may not read is never put in front of them — the label is said instead.
What Events.Write gives is JSON, so it may be kept in a Json column as well as in a string, and Events.Read takes
either.
A kit that hears every event#
[On] void H(Event e) hears every event the app and its packages raise — its own, other kits', the app's — and asks
Events.* what each one is: its name, its label, its type, the record it is about. That is how a timeline, a rule
engine or webhooks work on events they have never heard of ([On] — hear an event, after the commit that raised it).
Which events are offered to people?#
Events.Catalog() lists every public [Event] class — what a rule may run on, what a webhook may be subscribed
to. An internal event is its declarer's own business (a notice it tells somebody, say): handlers hear it, nobody is
offered it. A PACKAGE's internal event is heard by that package's own handlers only — a catch-all in another kit or in
the app cannot name it and never hears it. An app's own internal event is heard by every handler, catch-alls included.
Its name on the wire#
Events.Name(e) is what a subscription stores and a receiver reads: the app's own event by its class's name in
kebab case (OrderShipped → order-shipped), a package's prefixed with the package's area
(Osysharp.KnowledgeBase.ArticlePublished → knowledge-base.article-published), so two kits' events never share a name.
Where is it written down?#
An event some handler hears is written in the same commit as the work that raised it — so an event exists exactly when
that work does, and one raised by work that was rolled back was never raised. After the commit each handler runs, in
order, each on its own; what ran, and any handler that failed or was cut from a loop, is recorded with the event.
Raise(e) — say that something happened and [On] — hear an event, after the commit that raised it say how. The record is kept in the platform's Osysharp.EventOutbox tables,
which an app carries only once some handler is in it — its own or a package's. An app that raises events nobody hears
has no outbox, and its raises cost nothing.
Examples#
A record that says what happened to it#
The shop raises OrderShipped where it ships; a handler elsewhere in the app hears it after the commit.
entity Order {
[Required, MaxLength(40)] string Code;
bool Shipped;
security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}
entity ShippingNote {
[Required, MaxLength(100)] string Says;
security { allow read, create when IsAnonymous || IsAuthenticated; }
}
/// An order left the warehouse.
[Event("Order shipped")]
public sealed class OrderShipped { public Order Order; }
void Ship(Order o) {
o.Shipped = true;
Raise(new OrderShipped { Order = o });
}
[On]
void NoteTheShipment(OrderShipped e) { new ShippingNote { Says = "shipped " + e.Order.Code }; }[Test]
void A_shipped_order_is_heard_once_its_commit_lands() {
var o = new Order { Code = "A-1" };
Ship(o);
Background.Settle(); // the work commits — and the handler runs after it
Assert.Equal("shipped A-1", ShippingNote.Single().Says);
}Every event, heard by one handler that names none#
LogEverything hears both events and says each — OrderShipped in its own words, OrderReturned by its label.
entity Parcel {
[Required, MaxLength(40)] string Code;
security { allow read, create, update when IsAnonymous || IsAuthenticated; }
}
entity LogLine {
[Required, MaxLength(200)] string Says;
security { allow read, create when IsAnonymous || IsAuthenticated; }
}
[Event("Parcel shipped")]
public sealed class ParcelShipped {
public Parcel Parcel;
public override string Sentence() { return $"Parcel {Parcel.Code} left the warehouse."; }
}
[Event]
public sealed class ParcelReturned { public Parcel Parcel; }
[On]
void LogEverything(Event e) { new LogLine { Says = Events.Name(e) + ": " + e.Sentence() }; }
void Ship(Parcel p) { Raise(new ParcelShipped { Parcel = p }); }
void TakeBack(Parcel p) { Raise(new ParcelReturned { Parcel = p }); }[Test]
void Every_event_is_heard_and_says_itself() {
var p = new Parcel { Code = "A-1" };
Ship(p);
TakeBack(p);
Background.Settle();
Assert.Equal(["parcel-returned: Parcel returned", "parcel-shipped: Parcel A-1 left the warehouse."],
LogLine.OrderBy(l => l.Says).Select(l => l.Says).ToList());
}See also#
- Raise(e) — say that something happened — raising one, and what it costs
- [On] — hear an event, after the commit that raised it — hearing one: order, failures, loops, authority
- [Action] — what an app offers to be done, by name — the other half of a record's surface: what can be DONE to it
- Records — what any record is called, where it opens, who may read it — what any record is called, and where it opens
- The RecordTimeline kit — what happened to a record, on its own timeline — a record's timeline, from every event about it
- The Automations kit — rules that do an app's routine work when something happens · The Webhooks kit — telling other systems, signed, when something happens · The Notifications kit — telling a person that something needs them — kits that work on any event