Osy#betaRun it on Osyrin: osyrin.com

Reference / Project

Retired forms — deprecated from a version, and how a deployed app keeps rebuilding

osy migrate code · deprecated "Control(former parameters)" { since "0.16.0"; use "Control(new parameters)"; }

When the platform or a kit retires a spelling, new source that writes it is refused, naming what replaced it. An app that is already deployed keeps rebuilding on new releases for one more minor version, with a warning on its version that names the form and the release that will refuse it. `osy migrate code` rewrites the retired forms in a project.

stable1 example compiled by CIprojectdeployupgradelanguage

Summary#

Osy# does not keep an old spelling alive beside a new one: when a form is renamed or removed, source that writes the old form is refused, and the refusal names what to write instead. That is the right answer for code you are editing — and the wrong one for an app that is already serving people, which a platform release rebuilds from the source it was deployed with. So every retired form carries two versions:

what it means
deprecated sincethe release that retired it. From here on, compiling SOURCE that writes it is refused.
refused fromthe release from which even a deployed app's rebuild refuses it — by default the next minor.

Between the two, a release rebuilds a deployed app that still writes the form exactly as before, and records a warning on the app's version naming the form and its deadline. Before the deadline, deploy the app again from its source, written the new way — osy migrate code rewrites every form that has a mechanical rewrite.

Signature#

$ osy migrate code              # rewrite this project's retired forms, then validate again
$ osy migrate code --dry-run    # say what would be rewritten, write nothing
$ osy migrate code --json       # every rewrite, and every form left for you
// in the kit's package.osy
deprecated "<Control>(<its parameters as they were declared>)" {
  since "<platform version>";        // required
  use   "<what to write instead>";   // required
  why   "<one line>";                // optional
  refusedFrom "<platform version>";  // optional: the next minor after `since` when left out
}

Description#

What happens when I compile a retired form?#

osy validate, osy compile and a deploy refuse it, with the compiler's sentence for that form and the replacement:

model/jobs.osy:3:2  ERROR  `[OwnUnitOfWork]` is now `[CommitsAlone]` — write `[CommitsAlone]` on 'Record'. The meaning
is unchanged: its writes commit by themselves when it returns and survive the caller failing. (`osy migrate code` rewrites it.)

The last sentence is there when the rewrite is mechanical. A form whose replacement behaves differently in some case is refused without one, and the message says what differs.

What happens to an app that is already deployed?#

A platform release rebuilds every deployed app from the source its version recorded. When that source writes a retired form and the release is before the form's refused from, the rebuild rewrites the form in memory, compiles, and serves the app as it was. The version keeps the source it was deployed with, and records the form on its history:

model/jobs.osy:3 uses [OwnUnitOfWork], refused from 0.17.0 — write [CommitsAlone]

The same line is logged as a warning by the release, and listed by the operator's check of every deployed app (osycloud platform compile-recorded), so the apps still using a retired form are known before the release that refuses them.

When does a deployed app stop rebuilding?#

From refused from on, the rebuild refuses the form like any other source, and says why:

model/jobs.osy:3  ERROR  DEPRECATED_FORM  this recorded version uses [OwnUnitOfWork], which a rebuild accepted until 0.17.0
(this platform is 0.17.0) — deploy it again from its source, written [CommitsAlone].

Deploying the app again from its project, written the new way, is the whole fix: the new version records the new source.

How do I rewrite my project?#

Run osy migrate code in the project. It validates the project, takes the rewrite each refusal of a retired form carries, writes it into the file, and validates again — a second round can find forms the first could not see (a call on what a retired factory returned is refused only once the factory is rewritten). It stops when nothing more can be rewritten, and lists every retired form it left in place with what to write by hand.

Only the project's own files are written. A retired form inside a kit the project uses is the kit's to retire: the command names it, and a newer version of the kit (osy update) is how the fix arrives.

After it, the project compiles as the current language writes it:

entity Reading {
  [MaxLength(100)] string Text = "";
  security { allow read, create when IsAuthenticated; }
}

[CommitsAlone] void Record(string t) { new Reading { Text = t }; }

TimeSpan OffsetNow() { return TimeZoneInfo.FindSystemTimeZoneById("UTC").GetUtcOffset(DateTime.UtcNow); }

DateTime WallClock(DateTime instant) { return TimeZoneInfo.ConvertTimeFromUtc(instant, TimeZoneInfo.FindSystemTimeZoneById("UTC")); }

int ZonesOnOffer() { return TimeZoneInfo.GetSystemTimeZones().ToArray().Length; }

Which forms are retired?#

retired formwrite insteaddeprecated sincerefused fromrewrite
[OwnUnitOfWork][CommitsAlone]0.16.00.17.0yes
Zone (a type)TimeZoneInfo0.16.00.17.0yes
Zone.Of(id), Zone(id)TimeZoneInfo.FindSystemTimeZoneById(id)0.16.00.17.0yes
Zone.DeclaredTimeZoneInfo.GetSystemTimeZones() (as an array: .ToArray())0.16.00.17.0yes
instant.InZone(zone)TimeZoneInfo.ConvertTimeFromUtc(instant, zone)0.16.00.17.0yes
zone.OffsetAt(instant)zone.GetUtcOffset(instant)0.16.00.17.0yes
zone.IsDst(instant)zone.IsDaylightSavingTime(instant)0.16.00.17.0yes
zone.Resolve(date, time)TimeZoneInfo.ConvertTimeToUtc(date.ToDateTime(time), zone)0.16.00.17.0yes — kept exact: C# throws on a time inside a spring-forward gap, where Resolve read it forward, so the rewrite asks zone.IsInvalidTime first and reads it forward by the gap's width. If your times never fall in a gap, the plain C# call is the same
ProgressBar(int percent = 0, Tone tone = Tone.Primary, string label = "") (Osysharp.Ui)ProgressBar(label, percent, tone)0.16.00.17.0yes
Facts(string label = "") (Osysharp.Ui)Facts(label)0.16.00.17.0yes
ZonePicker(string label, Binding<Zone> value, bool disabled = false) (Osysharp.Ui)TimeZonePicker(label, value, disabled)0.16.20.18.0yes — the name
ZoneWords(Zone zone) (Osysharp.Ui)TimeZoneWords(zone)0.16.20.18.0yes — the name

What does the ProgressBar rewrite write?#

A call that binds a control only as its kit declared it before is rewritten with named arguments, onto the control as it is now. A parameter the control now requires, and the former signature gave a default, takes that default — so a rebuilt app renders exactly what it rendered:

ProgressBar(72, Tone.Success)    →    ProgressBar(label: "", percent: 72, tone: Tone.Success)

osy migrate code says when it wrote a former default. Replace label: "" with what the bar measures: a progress bar with no name is announced by a screen reader as a percentage of nothing.

How does a kit retire part of its own surface?#

A kit declares the former signature of a control in its own package.osy, one deprecated entry per retired form:

package Acme.Gauges {
  version     "2.0.0";
  minPlatform "0.16.1";
  model "*.osy";

  deprecated "Gauge(int value = 0, string caption = \"\")" {
    since "0.16.1";
    use   "Gauge(caption, value)";
    why   "a gauge says what it measures, so `caption` is required and first";
  }
}

The value is the control's name and parameter list exactly as they were declared. A call that does not bind the control as it is now, and does bind the former signature, is refused as the retired form — with its rewrite when every former parameter still exists and every newly required one had a default. since is the platform release the kit's change ships with, and the window runs from there to refusedFrom like the platform's own.

How does a kit rename a control or a function?#

The same entry, with the new name in use. It works for a component and for a public function:

deprecated "Dial(int value)" {
  since "0.17.2";
  use   "Gauge(value)";
  why   "every control in the kit is named after what it shows";
}

A call to the former name is refused as the retired form, and its rewrite replaces the name and keeps the arguments as written. A rebuild applies it, and so does osy migrate code.

Does retiring a form need a major version of the kit?#

No. A form with a deprecated entry is still offered: an app that wrote it keeps rebuilding, and osy migrate code rewrites its source. So osy publish and osy package pack --since don't count it as removed, and the kit's next version can be a minor. Without an entry, the form is removed, and the kit has to take a new major version. Deleting an entry later removes the form, so that release needs a major too.

See also#

Related

Moving runs onto the version you just deployed

Deploying a new version does not move the runs already in flight — they keep executing the version they started under…

[CommitsAlone] — a function whose writes land by themselves

A function marked `[CommitsAlone]` runs in a unit of work of its own. What it writes is committed when it returns and…

Time zones — TimeZoneInfo

Store and use civil time zones with C#'s `TimeZoneInfo`. A zone holds an IANA id (`"Europe/Stockholm"`), and C#'s…

package.osy

The manifest that makes a git repository a publishable Osy# package. It names the package, says which of the…

minPlatform — the oldest platform an app runs on

The oldest platform release your app may be built and run on, as the first line of app.osy. osy init writes it, osy…

Deploying while workflows are running

A workflow run can outlive the deploy that started it. Deploying with --new-version freezes the code and data shape the…