From 0.45 to 0.46
What to check when upgrading from better-supabase 0.45 to 0.46, where PowerSync streams move to edition 3, attachments fill object_path from a trigger, doctor adds BS421, the rate limiter moves to its own entry and permission keys use the block name as their area.
0.46.0 renames permission keys so each one is the block name and a verb: run
better-supabase codemod 0.46 and check the keys it lists for review. Old keys
keep working until the next minor, with a warning. The release also collects
fixes reported by an app in production. Most of them add options; four can
need a step: the regenerated module SQL, the PowerSync streams, a new doctor
error and an import path.
| Section | Needs code |
|---|---|
| Permission keys | Run codemod 0.46, then check ai.* keys |
| The SQL modules | Run upgrade and generate a migration |
| PowerSync streams | Upload the regenerated streams |
| Access token hooks (BS421) | When doctor reports BS421 |
| The rate limiter import | Optional; the old import still works |
| Generated code | Run gen; drop casts on createdAt inserts |
Permission keys
Every permission key is now <area>.<verb>. The area is the block name in
snake_case (contacts, workflow_editor), and the verb is read,
write, delete, manage or a verb of the domain such as approve or
publish. A block installed as a named instance checks its own area: a
tickets instance of tasks checks tickets.read, not tasks.read.
Run the codemod. It renames the keys in strings in your code and config and in
quoted keys in your own SQL. It also moves code off the oauth-server names
0.44 deprecated (createOAuthServer and the OAuth types become Oauth):
npx better-supabase@latest codemod 0.46| 0.45 | 0.46 |
|---|---|
parties.* | contacts.* |
engagements.* | activities.* |
rules.read, rules.edit | automations.read, automations.write |
rules.publish, rules.admin | automations.publish, automations.manage |
controls.* | compliance.* |
support.start, support.read | impersonation.start, impersonation.read |
support.revoke | impersonation.revoke |
work_items.* | tasks.* |
targets.* | goals.* |
views.* | saved_views.* |
history.read | field_history.read |
mail.* | mail_sync.* |
number_sequences.assign | numbering.assign |
service_principals.* | service_accounts.* |
activity.read | timeline.read |
segments.edit | segments.write |
ai_chat.admin | ai_chat.manage |
custom_fields.sensitive.read | custom_fields.read_sensitive |
employees.view_sensitive | employees.read_sensitive |
workflow.edit, workflow.publish | workflow_editor.write, workflow_editor.publish |
workflow.read, workflow.run | workflows.* and workflow_editor.* (below) |
organization.export | data_lifecycle.export |
members.invite (waitlist codes) | waitlist.invite |
The full list, with the wildcards, is PERMISSION_KEY_RENAMES and
PERMISSION_KEY_SPLITS in better-supabase/blocks/permissions. The same
entry exports isOldPermissionKey(key), which tells whether a key was
renamed, and currentPermissionKeys(key), which returns its new keys, for
code that checks keys stored outside better-supabase. RENAMES_CODEMOD names
the codemod that renames them.
The codemod lists some keys for review instead of guessing:
- The
ai.*keys split into one area per AI block:agents,ai_files,ai_keys,ai_schedules,connectors,knowledgeandmemory. A check forai.readbecomes the read key of the block it guards, such asknowledge.read; a check forai.createbecomesagents.write,ai_files.upload,ai_keys.use,ai_schedules.write,connectors.useorknowledge.write. workflow.read,workflow.runandworkflow.adminwere shared by the workflows block and the workflow editor. Useworkflows.*for runs andworkflow_editor.*for definitions.support.*andworkflow.*are also event patterns (support.started,workflow.published). Rename them only where they are permission wildcards.- The waitlist block checks
waitlist.inviteinstead of the invitations block'smembers.invite, and data exports checkdata_lifecycle.exportinstead oforganization.export. Grant the new key where a custom role should keep the access.
Old keys keep working until the next minor. A role in
blocks.permissions.roles that lists one gets the new keys too, and
permissionMatches accepts both, with a warning once per key. Doctor reports
each old key it finds (BS422).
With the catalog access model, upgrade renders a data step that renames the
rows in permissions and copies their role and override links to the new
keys. When the sessions module keeps authorization versions, the step bumps
every user's version so tokens pick up the new keys. With the provider or
custom models the keys live outside better-supabase: rename them in the
provider's roles, or in the functions your SQL defines, with the table above.
The SQL modules
The attachments module moves to version 7. object_path was a generated
column, which Postgres logical replication skips, so PowerSync never received
it. It is now a plain column that a trigger fills on insert and update. An
existing install keeps its values: the module drops the generation expression
and leaves the stored paths in place. Attachments also broadcast changed to
attachments:<tenant> when a tenant's file changes (rename the topic with
blocks.attachments.options.topic).
The memberships module keeps custom roles in membership_roles under the
provider and custom access models, and a direct write of memberships.role
swaps the previous primary role instead of adding one. Regenerate the module
SQL and the migration:
npx better-supabase@latest upgrade
npx better-supabase@latest db diff -f upgrade_0_45PowerSync streams
better-supabase powersync streams now writes Sync Streams edition 3. The
memberships, teams and attachments streams read the user's organizations
through a with: CTE, skip disabled memberships and sync every team in those
organizations. With roles.many a membership_roles stream follows.
Regenerate the file and upload it with the PowerSync dashboard or CLI:
npx better-supabase@latest powersync streamsThe publication migration now starts with create publication powersync for table ..., and a later run adds only the tables earlier migrations did not
publish. If you created the publication yourself (for all tables), delete
the first generated migration. powersync covers the
details.
Access token hooks (BS421)
Auth runs a custom access token hook as supabase_auth_admin, where
auth.uid() is null, so the restrictive bsb_tenant_scope policies hide every
row from it. Doctor now reports a security invoker hook that reads a
tenant-scoped table as BS421. List the role in the
tenant scope:
export default defineConfig({
database: { sql: { tenantScope: { exemptRoles: ["supabase_auth_admin"] } } },
});The rate limiter import
createRateLimit and rateLimited have their own entry. The exports from
better-supabase/blocks/queue stay as deprecated aliases.
| 0.45 | 0.46 |
|---|---|
createRateLimit from better-supabase/blocks/queue | createRateLimit from better-supabase/rate-limit |
rateLimited from better-supabase/blocks/queue | rateLimited from better-supabase/rate-limit |
The oRPC and Hono rateLimit middleware take several limits through hits;
Rate limits shows both.
Generated code
Run npx better-supabase@latest gen. records/ and hooks.ts now come out
the way oxfmt prints them, so a formatter run leaves them unchanged and you can
stop ignoring them. On tables the updated-at extension stamps, createdAt and
updatedAt are optional on Insert; remove any placeholder values or casts
you added to satisfy the old types. gen.hooks.select wraps every generated
select in a function of your own, for example to scope reads by tenant.
Last updated on
Existing apps
Bring better-supabase into an app that already has its own tables, permissions, events and middleware, one module at a time.
From 0.43 to 0.44
What to check when upgrading from better-supabase 0.43 to 0.44, where bootstrap reports the live authorization version, native apps follow role changes and PowerSync reconnects on them.