Upgrading 0.17 to 0.18
Upgrading 0.17 → 0.18
Section titled “Upgrading 0.17 → 0.18”0.18.0 is released. These are the ### Breaking entries of its
changelog section, one at a time, with what each one asks you
to change. Under 0.x the minor is the breaking position, so this is the wall
between 0.17.3 and 0.18.0 — read it before you move, not after.
Every one of them is a compile error or a refused boot rather than a silent
change of behaviour, except the last, which rewrites a constraint the next
db push will plan for you.
20. defineCollection is one signature
Section titled “20. defineCollection is one signature”It was three overloads — Postgres, Firestore, MongoDB — and a failing call
produced one diagnostic on defineCollection(, listing each overload’s first
failure. There is one signature now, discriminated on engine (Postgres when
absent), and errors are reported on the key that is wrong, all of them at once.
What to change: nothing, in a call that already compiled. What changes is
where the red squiggle lands. Two type exports moved with it: UnknownPropertyKey
is now NoSuchKey (and carries a didYouMean member), and
PropertyTypeNotOnThisEngine is new. Both are internal machinery of the builder,
so only code that imported the old name has an edit to make.
21. defineCollection keeps checking after the first bad property
Section titled “21. defineCollection keeps checking after the first bad property”The builder constrained its property map, and a constraint TypeScript cannot
satisfy is one it silently falls back from — so a single bad defaultValue
widened the inferred entity to Record<string, unknown> and switched off every
check on display.title, listProperties, propertiesOrder and
previewProperties.
What to change: nothing to write, but expect new errors in collection files that compiled before. They were wrong then too; the compiler had stopped looking. Fix the property the first error names and rebuild — the rest of that file is being checked for the first time.
22. A relation’s link field has to belong to its kind
Section titled “22. A relation’s link field has to belong to its kind”Relation has been a closed union since 0.11 — belongsTo owns localKey,
hasOne/hasMany own foreignKeyOnTarget, manyToMany owns through, via
owns joinPath — but excess-property checking ran against the union as a whole,
so { kind: "belongsTo", foreignKeyOnTarget: "author_id" } compiled and then
resolved to a different link than the one written.
What to change: put the link field the kind owns.
author: { type: "relation", relation: { kind: "belongsTo", foreignKeyOnTarget: "author_id", target: () => authors } relation: { kind: "belongsTo", localKey: "author_id", target: () => authors }}The boot validator already refused these, so this moves the report from a failed deploy to a compile error. If your project boots today, it compiles today.
23. A relation’s validation moves up to the property
Section titled “23. A relation’s validation moves up to the property”RelationBase.validation and ResolvedRelationBase.validation are deleted.
There were two places to write required and two generators reading different
ones: the DDL asked the property (so the column came out NOT NULL) while the
SDK type generator asked the relation (so the generated Insert type made the
field optional), and a create() that omitted the relation typechecked and then
failed at the database.
What to change: move it one level up, beside every other field’s.
author: { type: "relation", validation: { required: true }, relation: { kind: "belongsTo", validation: { required: true }, target: () => authors } relation: { kind: "belongsTo", target: () => authors }}Boot refuses the old key by name rather than ignoring it. Code that asked a
relation whether it was required asks
import { isRelationRequired, relationDeclaringProperty } from "@rebasepro/common"
instead.
24. A required belongsTo deletes with RESTRICT, not CASCADE
Section titled “24. A required belongsTo deletes with RESTRICT, not CASCADE”validation: { required: true } says the child cannot exist without a parent. It
does not say deleting the parent should delete the child — but that is what the
generator inferred, so every DELETE FROM authors cascaded through posts, their
comments, and whatever hung off those. The default is RESTRICT now: the delete
fails and names the constraint. Optional relations are unchanged (SET NULL), and
a manyToMany junction is unchanged (CASCADE — the row it deletes is the link).
This one is a DDL change. The next rebase db push plans a DROP CONSTRAINT /
ADD CONSTRAINT for every required relation that never named an onDelete, and
after it those parent deletes start failing where they used to cascade.
What to change: to keep the old behaviour, write it down.
relation: { kind: "belongsTo", target: () => authors }relation: { kind: "belongsTo", onDelete: "cascade", target: () => authors }Then read the plan before applying it — rebase db push prints the statements.
25. The Node floor is >=22.22.0
Section titled “25. The Node floor is >=22.22.0”Every published package declared >=20 at 0.17.3 — @rebasepro/cli declared no
engines at all — and every one of them declares >=22.22.0 on main. The
single source is the repository’s .nvmrc.
What to change: move to Node 22.22.0 before you upgrade. pnpm install
answers an engines mismatch with [WARN] Unsupported engine and installs
anyway, so the failure does not arrive at install time; it arrives later,
somewhere with no mention of Node in it.
26. rebase.data is gone at runtime
Section titled “26. rebase.data is gone at runtime”RebaseServerClient dropped data from its type so that server-side code has
to say whose identity a read runs under, and the property was then left on the
object as a runtime alias. It is deleted at boot: rebase.data is undefined.
What to change: in server code — functions, crons, callbacks — write
rebase.dataAsAdmin where you mean the admin plane and context.data where you
mean the caller’s. Typed code has been getting the error since the type changed;
what breaks now is untyped code, and a scaffold’s ai-instructions.md that
still tells an assistant to reach for rebase.data.<slug> — rebase init
rewrites that file, so regenerate it or edit the rule by hand. Browser code is
untouched: rebase.data on the client SDK is the user-scoped plane and stays.
27. @rebasepro/cli publishes three exports
Section titled “27. @rebasepro/cli publishes three exports”src/index.ts re-exported sixteen modules and put 95 names on npm. It exports
entry, manifest and bundle now.
What to change: nothing, unless you import from @rebasepro/cli — which
is unlikely, since nothing in this repository or the control plane did. If you
do, the CLI is a binary: shell out to rebase <command> rather than calling its
command functions, whose signatures were never a contract.
28. Peer ranges are carets
Section titled “28. Peer ranges are carets”ui, forms, firebase, plugin-insights and cms-types declared
react >=19.0.0, a range whose lower half cannot satisfy app and cms at
19.2.7 — so an installer that picked 19.0.0 produced a tree that resolved
cleanly and broke at render. All five say ^19.2.7. @rebasepro/app’s
typescript peer moves from >=5.0.0 to ^6.0.0.
What to change: be on React 19.2.7 or later, and on TypeScript 6 if you use
@rebasepro/app’s Vite plugin. If your install previously resolved React 19.0.x
you will now get a peer warning until you raise it; that warning is the tree you
were already running, said out loud.
29. rebase cloud webhooks create takes --endpoint
Section titled “29. rebase cloud webhooks create takes --endpoint”--url names the control plane for every command in this family, so the second
--url this one declared for the customer’s endpoint could never win: the
documented example sent the webhook URL to the client as the host to
authenticate against.
What to change: rebase cloud webhooks create --endpoint https://… in any
script that creates one. The old spelling could not have worked, so there is no
behaviour to preserve — only lines to correct.
30. serializeFilter emits strictly
Section titled “30. serializeFilter emits strictly”The shared leaf encoder parses liberally, because a short code arrives off the
wire, and emits strictly, because a caller handing one to the serializer built
the condition by hand. serializeFilter({ a: ["gt", 5] }) throws rather than
round-tripping gte.
What to change: only if you call @rebasepro/common’s serializers directly.
Use the operator names the types declare (gt, gte, lt, lte, …) rather
than REST short codes. Query builders and the SDK were already spelling them
this way; what changes is that the wrong spelling now says so.
Next: the upgrade checklist · 0.14 → 0.17, the hop before this one · Changelog, the release notes these sections summarise.