Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

[OnStart] — work the app does when it is loaded

[OnStart] void <Name>() { … } · [OnStart(Required = true)] void <Name>() { … }

`[OnStart]` marks a function the platform runs, as the app, whenever the app version is loaded — on every node that serves it, after every compile and deploy, and once at the start of an `osy test` run. It is where a kit starts its recurring work and makes its default rows, instead of checking for them inside every request. It runs again and again, so it must be idempotent: look first, then make. A hook that fails is reported, naming its package, and the app keeps serving; `Required = true` makes its failure stop the load instead.

preview2 examples compiled by CIfunctioncompositionkitslifecycle

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 load

Description#

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 test run, 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#

Related

[On] — hear an event, after the commit that raised it

`[On]` on a function makes it a HANDLER of the event its one parameter is. Every handler of an event — in the app and…

[Event] — what happens to a record, declared once

An event is a class marked `[Event]`: something that happens to a record, declared once by the code that owns the…

[Interest] — the events a handler of every event cares about

`[Interest(nameof(Triggers))]` beside the `[On]` of a handler of EVERY event names a function that answers which event…