Migrating to 0.14

0.14.0 opens a new compatibility key: it reads pins of 0.14.x only. A project pinned to 0.13.x is refused by name until it is upgraded, and listen-fire upgrade moves its pin once every file checks clean.

One idea runs through the release: a value that may be one of several things says so, and is read as what it can be. A record that may be missing is read with ?.; a record that may be a Memo or a Remark is read for what both have, or narrowed with is. 0.13 let such reads check clean and answered nothing, or failed, at run time.

Contents #

1. How the pin moves #

The order that works:

  1. Delete core.loopGuard from lf.yaml (section 5). Until then every command refuses the project, upgrade included.
  2. Run listen-fire upgrade --check and read what it lists.
  3. Replace each lazy by hand (section 4).
  4. Narrow each read off a union walk that only some of its records have (section 3).
  5. Run listen-fire upgrade. It writes each . through a value that may be missing as ?. (section 2), and moves the pin when nothing is left.
  6. Before running the new engine, let the runs paused holding a lazy walk finish, or cancel them (section 6).

2. A field read through a value that may be missing is written ?. #

A field is never read with . through a value that may be missing, as TypeScript refuses x.F under strict null checks where x may be undefined. 0.13 refused it through a change's row and a map member; 0.14 refuses it wherever the value comes from.

BeforeAfter
t = only(at-[:Tables]->), then t.Namet?.Name
only(at-[:Tables]->).Nameonly(at-[:Tables]->)?.Name
first(rows).Name, rows[0].Name, d["a"].Namefirst(rows)?.Name, rows[0]?.Name, d["a"]?.Name
if (t.Name != null) { … }if (t?.Name != null) { … }
team = connect `@lf/slack` using team_workspace

function `Post Status`(go: <manual-[:Invocation]->>) {
  ops = only(team-[ch:Channels WHERE ch.Name == "ops"]->)
  if (!exists(ops)) { fail("no #ops channel") }
  write ops-[:Messages]-> { Message: "Status from ${go.Text}: posted to ${ops.Name}" }
}

3. A walk off a union lands one of its members' records #

Where the members of a union land the same edge on different record types (a Person's Notes on a Memo, a Company's on a Remark), crm-[:Subjects]->-[n:Notes]-> lands a Memo or a Remark, as TypeScript's Memo | Remark. A read, a walk or a write off it is held to what every one of them has.

BeforeAfter
crm-[:Subjects]->-[n:Notes]-> { … n.Mood … }crm-[:Subjects]->-[n:Notes]-> { if (n is <crm-[:Memo]->>) { … n.Mood … } }
n.Mood, then n.Toneif (n is <crm-[:Memo]->>) { … n.Mood … } else { … n.Tone … }

4. lazy is a closure #

A closure already says "run this when it is called", so lazy is retired. x = lazy …, return lazy … and a node entry files: lazy … are each refused where they are written (MOV_LAZY_RETIRED).

BeforeAfter
people = lazy crm-[:People]->, read as count(people)people = () => crm-[:People]->, read as count(people())
return lazy crm-[:People]->return crm-[:People]->
a node entry files: lazy d-[:Files]->files = () => d-[:Files]-> beside the node, called where the files are read; or files: d-[:Files]-> to keep what the walk lands on when the node is built

5. The loop guard slot is removed #

6. Runs paused under 0.13 #

7. Affinity: an opportunity list's change Field names its columns #

8. For hosts and embedders #

9. Diagnostic code index #

CodeSeverity under 0.14Section
MOV_ABSENT_REQUIRED (a field read through a value that may be missing)error, safe fix to ?., applied by listen-fire upgrade2
MOV_UNION_MEMBER_ONLYerror, by hand: narrow the value with is3
MOV_UNKNOWN_PROPERTY (off a union walk's landing)error, by hand: read a field its records have3
MOV_IS_NO_OVERLAPwarning: the is test is always false3
MOV_LAZY_RETIREDerror, by hand: write a closure4
MOV_ENUM_UNKNOWN_VALUE (an Affinity opportunity list's Field)error, by hand: name one of the list's columns7