Summary#
Some work belongs to the app being THERE rather than to anything happening in it: start the hourly clock, make the one
settings row a kit reads, check that the app's configuration makes sense. Mark a function [OnStart] and the platform
runs it whenever the app version is loaded:
[OnStart]
void StartTheClock() {
if (!ClockRow.Any()) { new ClockRow(); } // look first — this runs on every load, on every node
}Without it, that work hides inside ordinary requests — "is the clock running yet?" asked on every event, a default row made by whoever happens to need it first — and every request pays for a question whose answer never changes.
Signature#
[OnStart]
void <Name>() { … } // top-level; takes nothing; answers nothing
[OnStart(Required = true)]
void <Name>() { … } // the app cannot serve without it: a failure stops the loadDescription#
When does it run?#
Whenever the app version is loaded:
- on each node, the first time that node serves the version — so after the node starts, and after every compile or deploy that makes a new version;
- again whenever a node loads the version anew (it unloaded an idle app and a request brought it back);
- at the start of an
osy testrun, once, before any fixture — so every fixture and test begins from a world the hooks have already made, as a served app does.
Hooks run in package order — a kit before the kits that use it, the app's own last — and each in a unit of work of its own, committed before the next starts.
Why it must be idempotent#
Because it runs many times: once per load, per node — and two nodes may run it at the same moment. A hook that MAKES something must look first, so the second run does nothing:
[OnStart]
void EnsureTheSettings() {
if (!Setting.Any()) { new Setting { Theme = "light" }; }
}osy lint flags a hook that makes a row with no if around it (correctness-start-hook-not-idempotent):
the start hook 'Seed' makes a new Setting every time it runs — and it runs whenever the app is loaded: on every node, after every compile and deploy, and again whenever a node reloads it.Who does it run as?#
As the app — with the authority an event handler has, not any person's. So it may make a row nobody is granted to create (the clock's one row, a kit's defaults), and what a package may not write it still may not.
What if it fails?#
The failure is reported — logged naming the hook, its package and the app — its writes are discarded, the other hooks still run, and the app keeps serving. A start hook is housekeeping the app can live without for a while, and the next load runs it again; refusing every request because a clock could not start would turn a small fault into an outage.
When the app genuinely cannot serve without it, say so — [OnStart(Required = true)]. Then a failure stops the load:
no request is served by that version on that node, the failure names the hook, and the next request tries the load
again. In an osy test run a required hook that fails fails the run, naming itself.
What may it be?#
A top-level function that takes nothing and answers nothing, and is not also an event handler, an [Action] or a test.
Anything else is refused where it is written, saying what would do:
`[OnStart]` 'Takes' takes 1 parameter(s) — the platform calls a start hook with nothing to hand it. Read what it needs itself: `[OnStart] void EnsureTheDefaults() { if (!Setting.Any()) { new Setting(); } }`.
`[OnStart]` marks a top-level function the platform runs when the app is loaded — 'Holder.Method' is a method, and nothing holds an instance to call it on. Move it out of the class: `[OnStart] void EnsureTheDefaults() { if (!Setting.Any()) { new Setting(); } }`.Examples#
The row a kit needs, made once#
A setting nobody is granted to create — the hook makes it, as the app, the first time the app is loaded, and never again.
entity HouseRules {
[MaxLength(40)] string Greeting = "";
security { allow read when IsAnonymous || IsAuthenticated; }
}
[OnStart]
void EnsureTheHouseRules() {
if (!HouseRules.Any()) { new HouseRules { Greeting = "Welcome" }; }
}[Test]
void The_house_rules_are_there_from_the_start() {
Assert.Equal(1, HouseRules.Count());
Assert.Equal("Welcome", HouseRules.Single(h => true).Greeting);
}See also#
- [On] — hear an event, after the commit that raised it — work done when something HAPPENS, rather than when the app is loaded
- [Event] — what happens to a record, declared once — the events a kit hears
- [Interest] — the events a handler of every event cares about — what a handler of every event costs, and how to make it cost nothing