From 0.46 to 0.47
What to check when upgrading from better-supabase 0.46 to 0.47. Block entity keys equal the entity's singular, subject types, hint codes, function names and module tables take the new nouns, and every event type names its entity and a past verb. AI document versions, policy acknowledgements and sequence runs move onto the versions, acknowledgements and enrollments blocks, inbox contacts become people of the contacts block, postmortem action items become tasks, and doctor adds BS423 and BS424.
0.47.0 finishes the naming convention (ADR 0130): the key of every entity in
blocks.<name>.entities is the entity's singular, the subject types, hint
codes, function names and module tables follow the new nouns, and each event
type is an entity and a past verb. Run better-supabase codemod 0.47 and check
what it lists for review. Old entity keys and the old subject types keep working until
0.48, with a warning. The outbox stores the new event types, so subscribers to
an old type miss its events until you rename them; receivers outside the app
can keep the old names for one minor with the outbox legacyEventTypes
option. AI document versions move onto the versions block, policy
acknowledgements onto the acknowledgements block and sequence runs onto
enrollments, inbox contacts become people of the contacts block, and with
the tasks block installed, postmortem action items become tasks. Everything this guide calls deprecated is listed in
Removed in 0.48.
| Section | Needs code |
|---|---|
| Entity keys | Run codemod 0.47 |
| Subject types | Run upgrade; rename stored types outside SQL |
| Permission keys | Run codemod 0.46 if you skipped it |
| Hint codes | Run codemod 0.47, then check ACTIVITY_FORBIDDEN |
| Function names | Rename calls in your own SQL |
| Event types | Run codemod 0.47, then check saved types |
| AI document versions | Run upgrade; copy a renamed table by hand |
| Postmortem action items | Use addPostmortemAction (needs tasks) |
| Policy acknowledgements | Call the acknowledgements block |
| Sequence runs | Rename SQL that reads sequence_enrollments |
| Inbox contacts | Install contacts; call its identity methods |
| Tax codes | Install tax; call its code methods |
| Import runs | Rename kind to source; use runs.start |
| Table names | Rename queries on the old tables in your own code |
| The SQL modules | Run upgrade and generate a migration |
| Removed in 0.48 | Move off each item before 0.48 |
Entity keys
A key in blocks.<name>.entities now equals the entity's singular, so a
config that renames the tasks entity writes entities: { task: ... }
instead of entities: { work_item: ... }. Run the codemod; it renames the
keys in better-supabase.config.ts and in a JSON config:
npx better-supabase@latest codemod 0.47| Block | 0.46 key | 0.47 key |
|---|---|---|
access-requests | request | access_request |
activities | engagement | activity |
ai-chat | chat | ai_chat |
ai-chat | project | ai_project |
ai-files | document | ai_document |
ai-schedules | task | ai_scheduled_task |
api-keys | key | api_key |
automations | rule | automation |
collections | collection | subject_collection |
compliance | policy | compliance_policy |
connectors | server | connector_server |
contracts | obligation | contract_obligation |
custom-fields | field | field_definition |
drive | node | drive_node |
forecasts | snapshot | forecast_snapshot |
goals | target | goal |
hiring | job | job_opening |
incidents | postmortem | incident_postmortem |
inventory | location | inventory_location |
knowledge | document | knowledge_document |
leave | request | leave_request |
leave | type | leave_type |
saved-views | view | saved_view |
share-links | link | share_link |
signatures | request | signature_request |
sla | policy | sla_policy |
tasks | work_item | task |
time-tracking | entry | time_entry |
timeline | entry | timeline_entry |
waitlist | entry | waitlist_entry |
webhooks-out | endpoint | webhook_endpoint |
workflow-editor | definition | workflow_definition |
The full list is ENTITY_KEY_RENAMES in better-supabase. An old key still
resolves, with a warning, and doctor reports it
(BS423). An entry that sets both the old and the new
key is listed for review instead of renamed. The tasks binding
workItemChecklistBinding is now taskChecklistBinding; the codemod moves
the import.
An install that names an entity keeps its tables and subject type. An install
that doesn't keeps its tables too: the tasks block still writes work_items.
Its events are renamed (see Event types).
Subject types
Three block subjects take the new noun as their type:
| 0.46 | 0.47 |
|---|---|
work_item | task |
engagement | activity |
policy | compliance_policy |
upgrade renders a data step for each module that moves the stored rows:
every subject_type, source_type and target_type column of the installed
blocks (relations, comments, attachments, the timeline and the rest) that
holds the old type gets the new one. The audit log and the ledger, accounting
and billable time entries are append-only and keep the type they recorded.
The step skips a type when the install
names the entity, and skips policy when your config.subjects declares a
policy subject of its own. It can run again without changing anything.
If one of your own tables is already a task, activity or
compliance_policy subject (a @bsb subject tag or a config.subjects
entry), the CLI stops with an error naming the table. Keep the block on its
old noun, which also keeps its subject type and function names:
tasks: { names: { task: "work_item" } },Until 0.48 the subject functions (subject_title, can_subject and the rest)
still accept the old type. Rename the types your code passes, such as
{ type: "work_item", id }; the codemod lists them for review. Rename them
in blocks.<name>.on too (on: { compliance_policy: ... }); doctor reports an
on type that is no record type (BS346). Rename types you store outside
better-supabase tables yourself.
Permission keys
0.46 named every permission area after its block (engagements.read became
activities.read, parties.write became contacts.write); the
0.45 to 0.46 guide has the
details. In 0.47 a role in blocks.permissions.roles that still grants a
0.46 key also grants its new key, with a warning, and doctor reports the old
key (BS422). That fallback goes in 0.48, so run the
0.46 codemod if you skipped it:
npx better-supabase@latest codemod 0.460.47 adds one rename: compliance.acknowledge is now
acknowledgements.acknowledge, because policy acknowledgements moved onto the
acknowledgements block (see Policy acknowledgements).
PERMISSION_KEY_RENAMES maps it with the 0.46 keys.
Hint codes
Hint codes follow the entity's new noun: WORK_ITEM_NOT_FOUND becomes
TASK_NOT_FOUND, ENGAGEMENT_FORBIDDEN becomes ACTIVITY_FORBIDDEN and
RULE_CONFLICT becomes AUTOMATION_CONFLICT. The codemod renames the quoted
codes in your code and SQL. The full list is HINT_RENAMES, and
legacyHintOf(hint) returns a hint's 0.46 code for code that compares stored
errors. Until 0.48 a DbError with a renamed hint also carries the old code
in legacyHint.
ACTIVITY_FORBIDDEN was the timeline's code and is now the activities
block's. The timeline uses TIMELINE_ENTRY_FORBIDDEN. The codemod lists every
ACTIVITY_FORBIDDEN for review instead of renaming it.
Function names
Block functions take the entity's new noun. The client methods don't change; rename the calls in your own SQL:
| Block | 0.46 noun in the name | 0.47 noun | Example |
|---|---|---|---|
tasks | work_item | task | create_work_item becomes create_task |
activities | engagement | activity | log_engagement becomes log_activity |
goals | target | goal | save_target becomes save_goal |
compliance | policy | compliance_policy | publish_policy becomes publish_compliance_policy |
contracts | obligation | contract_obligation | save_obligation becomes save_contract_obligation |
incidents | postmortem | incident_postmortem | publish_postmortem becomes publish_incident_postmortem |
A key in blocks.<name>.functions that names an old function still applies
to the new one.
Event types
Every event type is now <entity>.<past verb>: the prefix is the entity the
event is about, and a saved event is created or updated, depending on
whether the save inserted the row. Run the codemod. It renames the types in
strings in your code and config and in quoted types in your own SQL:
| 0.46 | 0.47 |
|---|---|
work_item.* | task.* |
engagement.* | activity.* |
rule.*, rule.removed | automation.*, automation.deleted |
access.requested, access.request_approved, access.ended | access_request.requested, access_request.approved, access_request.ended |
custom_field.* | field_definition.* |
drive.node_created and the other drive.node_* events | drive_node.created and so on |
forecast.snapshotted | forecast_snapshot.created |
job.* (hiring) | job_opening.* |
leave.adjusted | leave_balance.adjusted |
leave.requested and the other leave.* events | leave_request.requested and so on |
obligation.* | contract_obligation.* |
organization.domain_added, organization.domain_removed | sso_domain.created, sso_domain.deleted |
organization.domain_updated, organization.domain_verified | sso_domain.updated, sso_domain.verified |
policy.* (compliance) | compliance_policy.* |
postmortem.* | incident_postmortem.* |
signature.* | signature_request.* |
support.* | support_session.* |
time.invoiced | time_entry.invoiced |
variant.* | product_variant.* |
waitlist.* | waitlist_entry.* |
webhook.* | webhook_endpoint.* |
workflow.published | workflow_definition.published |
segment.removed, hostname.removed, approval_policy.removed | segment.deleted, hostname.deleted, approval_policy.deleted |
marketplace_installation.removed | marketplace_installation.deleted |
credential.set, ai_tool_policy.set | credential.updated, ai_tool_policy.updated |
flag.override_set, hostname.primary_set, incoming_webhook.enabled_set | flag.override_changed, hostname.primary_changed, incoming_webhook.enabled_changed |
marketplace_invoice.state_changed | marketplace_invoice.status_changed |
<entity>.saved, marketplace_installation.upserted | <entity>.created or <entity>.updated |
party.enriched | company.enriched or person.enriched |
The saved events were agent, ai_provider_key, announcement,
approval_policy, connector, control, course, custom_field (now
field_definition), enrollment_program, flag, obligation (now
contract_obligation), pipeline_stage, post_board, rule (now
automation), saved_view, scim_group, scim_user, score_model,
segment and sequence. The sla.* events keep their names: they are about
a record's SLA clock, and no table of their own fits. Relation events such as
collection.entry_removed keep removed, and field_value.set keeps set.
Notification types, which are their own names, don't change: the tasks
block still sends work_item.assigned notifications.
An install that names an entity names its events after that name, as in
0.46: with entities: { task: "ticket" } the tasks block emits
ticket.created, and the data step leaves those rows alone.
The full list is EVENT_TYPE_RENAMES and EVENT_TYPE_SPLITS from
better-supabase, with isOldEventType(type), currentEventTypes(type) and
legacyEventType(type) for code that matches types stored outside
better-supabase.
The codemod lists some strings for review instead of guessing:
- A split type, such as
agent.saved: subscribe toagent.created,agent.updatedor both. support.*andworkflow.*, which were permission wildcards before 0.46 too. As event patterns,support.*is nowsupport_session.*andworkflow.*isworkflow_definition.*.leave.*andwaitlist.*, which are current permission wildcards. As event patterns they are nowleave_request.*plusleave_balance.*, andwaitlist_entry.*.drive.*andorganization.*, which still match some events: the node events moved todrive_node.*and the SSO domain events tosso_domain.*.
Doctor reports each old type it finds in your code (BS424).
The outbox stores the new types. upgrade renders a data step that renames the
stored types one to one: the outbox events and dead letters, automation
triggers and timeline entries. For the type lists of outbox consumers and
webhook endpoints it adds the new types and keeps the old ones, so a consumer
of agent.saved gets agent.created and agent.updated. An automation
whose trigger is a split type keeps it, and the rule sink also tests it
against the events the old type became.
Receivers outside the app, such as a service that reads the relayed
CloudEvents or a webhook endpoint, can keep the old names for one minor with
legacyEventTypes. The relay then sends each renamed type under its old name,
with the new one in the currenttype extension, which the library's sinks
read:
const outbox = createOutbox({
transport,
source: "https://crm.example.com",
legacyEventTypes: true,
});The option is deprecated and goes away in the next minor, so move the receivers to the new types first.
AI document versions
AI documents keep their versions in the versions
block, so ai-files now requires versions. Each version is a versions row
with subject_type = 'ai_document', the document id as subject_id, and the
text and the message that wrote it in content ({ text, messageId }).
better-supabase upgrade writes a migration that creates the versions table
when it is missing, copies the rows of ai_document_versions and drops that
table. The client is unchanged: documents.versions(documentId) and
documents.rollback(documentId, version) return the same shapes.
Three things behave differently. An edit that leaves the text unchanged writes
no version. A document keeps its latest 50 versions, the versions block's
retention. Document versions stay private to ai-files: list_versions and the
other versions functions refuse ai_document for members, and RLS hides those
rows, so only the ai-files functions read them.
SQL that read the old table reads the versions table:
-- 0.46
select version, content, created_by_message_id
from better_supabase.ai_document_versions
where document_id = $1;
-- 0.47
select number, content ->> 'text', content ->> 'messageId'
from better_supabase.versions
where subject_type = 'ai_document' and subject_id = $1::text;The blocks["ai-files"].tables.versions key is gone. If you renamed or adopted
ai_document_versions, the generated migration doesn't find it: copy its rows
into the versions table yourself before you drop it. codemod 0.47 lists the
lines that name ai_document_versions.
Postmortem action items
With the tasks block installed, postmortem action items
are tasks. addPostmortemAction(postmortemId, { title, assigneeId, dueAt })
creates one and links it in the new incident_postmortem_actions table,
removePostmortemAction(actionId) removes the link, and a postmortem's
actions lists the linked tasks. savePostmortem no longer takes
actionItems, and a postmortem has no actionItems field.
// 0.46
await db.incidents.savePostmortem(incidentId, {
actionItems: [{ title: "Add a disk alert" }],
});
// 0.47 (needs the tasks block)
const postmortem = (
await db.incidents.savePostmortem(incidentId, {})
).orThrow();
await db.incidents.addPostmortemAction(postmortem.id, {
title: "Add a disk alert",
assigneeId,
});The action_items column is gone. upgrade writes a step that copies each
item of an existing postmortem into a task through
add_incident_postmortem_action, then drops the column, before the schema diff
runs (ADR 0149), so the diff never drops a column that holds data. Install
tasks first. The step stops with an error, instead of dropping the column,
when it holds items and the tasks block can't take them: add tasks, or clear
the column, then run upgrade again. codemod 0.47 lists the lines that use
actionItems.
Policy acknowledgements
The compliance block now requires the
acknowledgements block and keeps its policy
acknowledgements there (ADR 0131). upgrade installs it with compliance, and
the data step copies every policy_acknowledgements row into
acknowledgements as a compliance_policy acknowledgement of the same
version, assigns every live policy that requires acknowledgement to every
member (the role principal *, so owners and admins are included), and drops policy_acknowledgements. Saving a policy that
requires acknowledgement assigns it the same way.
| 0.46 | 0.47 |
|---|---|
acknowledge_compliance_policy(policy_id) | acknowledge('compliance_policy', policy_id::text) |
list_compliance_policies(tenant, ..., pending_only) | list_my_acknowledgements(tenant) |
compliance_policy_acknowledgements(policy_id) | acknowledgement_status and acknowledgements_of |
policy_acknowledgements table | acknowledgements, subject_type = 'compliance_policy' |
compliance.acknowledge permission | acknowledgements.acknowledge |
policy.acknowledged, compliance_policy.acknowledged events | acknowledgement.created |
POLICY_NOT_PUBLISHED hint | ACKNOWLEDGEMENT_NO_VERSION, ACKNOWLEDGEMENT_NOT_ASSIGNED |
The client methods acknowledgePolicy, acknowledgements(policyId) and
policies(organizationId, { pendingOnly }) are removed in 0.47. Use
db.acknowledgements.acknowledge, mine and status. A role that granted
compliance.acknowledge grants acknowledgements.acknowledge
(PERMISSION_KEY_RENAMES), and EVENT_TYPE_RENAMES maps the old event. The
codemod lists the old calls, the table, the hint and the event for review.
Sequence runs
A sequence run is now an enrollment in the
program sequence:<sequence id> (ADR 0131), and the sequence_enrollments
table is gone. The sequences client doesn't change: enroll, pause,
resume, exit and enrollments take and return the same runs. SQL that
reads the table reads enrollments instead:
-- 0.46
select address, status, step, next_run_at
from better_supabase.sequence_enrollments
where sequence_id = $1;
-- 0.47
select x.sequence_address, x.status, x.paused_at, x.cancel_reason, s.step, s.due_at
from better_supabase.enrollments x
left join better_supabase.enrollment_steps s
on s.enrollment_id = x.id and s.completed_at is null
where x.sequence_id = $1;The run's columns move onto enrollments with a sequence_ prefix
(address is sequence_address, mail_thread_id is
sequence_mail_thread_id, and so on), the next step is the open
enrollment_steps row with due_at, a paused run has paused_at, and an
exited run is cancelled with the reason in cancel_reason. upgrade
renders a data step that copies each sequence_enrollments row onto its
enrollment and its next step, then drops the table. A members table you
adopted for sequences in 0.46 isn't moved; copy its rows the same way before
you drop it. Doctor reports sequence_enrollments in your code (BS424) and an
adopted members table in blocks.sequences.tables (BS425), and the codemod
lists the same key for review.
The run events move too: a run writes the enrollment events with program
set to sequence:<sequence id>.
| 0.46 | 0.47 |
|---|---|
sequence_enrollment.created, .step_completed | enrollment.created, .step_completed |
sequence_enrollment.paused, .resumed, .completed | enrollment.paused, .resumed, .completed |
sequence_enrollment.exited | enrollment.cancelled, with the exit reason in reason |
Other programs, such as onboarding checklists and courses, emit the same
events, so the codemod lists these types for review instead of renaming
them: subscribe to the new type and check data.program. The data step
renames the stored types and adds the new ones to the type lists of outbox
consumers and webhook endpoints, which then also receive the other programs'
events. A subscription is a list of type patterns with no payload filter, so it
can't keep only the sequence: programs: check data.program in the receiver.
Until 0.48, legacyEventTypes relays a run event under its old name,
and legacyEventType(type, data) returns it for a payload whose program
starts with sequence:. SequenceEnrollmentEventData is a deprecated alias
of EnrollmentEventData until then. PROGRAM_EVENT_MOVES (better-supabase)
lists the moves.
Inbox contacts
The inbox now takes its contacts from the contacts
block (ADR 0131): a conversation's contact is a person, and the inbox
contacts and contact_identities tables and the contacts block's
person_inbox_contacts link are gone. The inbox requires contacts, so add
it before you upgrade:
npx better-supabase@latest add contacts
npx better-supabase@latest upgradeupgrade renders a data step that turns each inbox contact into a person:
the person it was linked to, else the active person with the same email,
else a new person with the contact's id, so contactId values you stored
keep pointing at the same contact. It fills the person's user, phone and
avatar when they are empty, copies contact_identities into
person_identities, points conversations and identity links at the person,
then drops the old tables. An inbox contacts table you adopted in 0.46
isn't moved; copy its rows into people the same way before you drop it.
The conversation API keeps its shape: contactId is the person's id, and
conversations.open and the Chat SDK adapters find or create the person by
identity, user or email. Code that linked contacts to people calls the
identity methods instead:
// 0.46
const person = await db.contacts
.personForInboxContact(conversation.contactId, { create: true })
.orThrow();
await db.contacts.linkInboxContact(personId, contactId).orThrow();
// 0.47
const person = await db.contacts
.findByIdentity(organizationId, "whatsapp", "+31612345678")
.orThrow();
await db.contacts
.addIdentity(personId, { channel: "whatsapp", address: "+31612345678" })
.orThrow();| 0.46 | 0.47 |
|---|---|
contacts (inbox) | people, with avatar_url and metadata |
contact_identities (contact_id, external_id) | person_identities (person_id, address, verified_at) |
person_inbox_contacts | none: the conversation's contact_id is the person |
personForInboxContact, linkInboxContact, unlinkInboxContact | findByIdentity, addIdentity, removeIdentity |
inbox.contacts.upsert and upsert_contact (removed) | contacts.savePerson and addIdentity |
A person has no user lifecycle of its own, so deleting a user no longer
erases their inbox contact: call purgeContact(personId) for a data subject
request, which deletes the person, their identities and their
conversations. The codemod lists the old calls and tables for review, and
doctor reports the removed methods.
Tax codes
Tax codes moved out of accounting into the new tax block,
which adds dated rates, return boxes, tax returns and the intra-EU sales
list. The accounting_tax_codes table is gone and accounting.tax_report
reads from tax_codes. Accounting now requires tax instead of working
without it, so upgrade installs it next to accounting and the check that
keeps accounting running without tax doesn't exist.
| 0.46 | 0.47 |
|---|---|
accounting.saveTaxCode(...) | tax.saveCode({ ..., rateBps, type }) |
accounting.taxCodes(organizationId) | tax.codes() |
save_accounting_tax_code | save_tax_code |
list_accounting_tax_codes | list_tax_codes |
accounting_tax_codes.kind | tax_codes.type (sales or purchase) |
accounting_tax_codes.rate_bps | a row in tax_rates valid from the start of time |
accounting_tax_codes.report_boxes | tax_codes.return_boxes |
accounting_tax_codes.account_id | sales_account_id or purchase_account_id |
codemod 0.47 lists the lines that use the old table, functions and
methods. After upgrade and db diff, add this data step to the generated
migration before it drops accounting_tax_codes. It keeps every id, so the
tax_code_id of existing accounting lines stays valid:
alter table better_supabase.accounting_entry_lines
drop constraint if exists accounting_entry_lines_tax_code_id_fkey;
insert into better_supabase.tax_codes
(id, tenant_id, code, name, type, sales_account_id, purchase_account_id,
return_boxes, archived_at, created_at, updated_at)
select id, tenant_id, code, name, kind,
case when kind = 'sales' then account_id end,
case when kind = 'purchase' then account_id end,
report_boxes, archived_at, created_at, updated_at
from better_supabase.accounting_tax_codes;
insert into better_supabase.tax_rates
(tenant_id, tax_code_id, rate_bps, valid_from)
select tenant_id, id, rate_bps, date '1970-01-01'
from better_supabase.accounting_tax_codes;
alter table better_supabase.accounting_entry_lines
add constraint accounting_entry_lines_tax_code_id_fkey
foreign key (tax_code_id) references better_supabase.tax_codes (id)
on delete restrict;
drop table better_supabase.accounting_tax_codes;Invoice lines gain optional tax_code_id and unit_cost columns. An existing
caller that sets only tax_code and tax_rate_bps keeps working: issue_invoice
resolves the code by its text and falls back to the line's rate when no code
has that name. Quote lines keep tax_code and tax_rate_bps only.
Import runs
Imports keep an external record map (ADR 0141), and the run table changes shape, so this is a breaking change:
| 0.46 | 0.47 |
|---|---|
import_runs.kind | import_runs.source |
start_import(tenant, kind, mapping) | start_import(tenant, source, mapping), or start_import_run |
ImportDefinition.kind | ImportDefinition.source |
counts: { imported: n } | counts: { <entity>: { inserted, updated, unchanged, conflicted, missing } } |
errors: a list of messages | errors: [{ entity, key, code, message }] |
import.completed payload kind | import.completed payload id, count |
upgrade renames the column on import_runs and adds canceled to the
status, the dry_run and full flags, input_checksum, correlation_id,
scope_aliases, started_at and finished_at, and creates import_records.
A table you adopted for tables.runs gets the new columns added, and its
kind column is renamed by the generated better_supabase_block_upgrade
migration. Only one run per tenant and source can be pending or running;
close or cancel stuck runs before you upgrade. The client's start(tenant, source, mapping) keeps working, and imports.runs.start, upsertBatch,
resolve and finish are new (see imports). The
codemod lists code that names import_runs, start_import, defineImport
or appTableImport so you can rename kind by hand.
Custom field entities
Field definitions name a registered subject type (ADR 0156), so this is a breaking change:
| 0.46 | 0.47 |
|---|---|
field_definitions.entity is any text | entity is a key of config.subjects or a type an installed block registers |
save_field_definition accepts every entity | it fails with FIELD_ENTITY_UNKNOWN (22023) for an unregistered one |
read_field_values(tenant, subject_type, subject_id) | the same, plus legal_entity uuid default null when the legal-entities block is installed |
set_field_value(tenant, subject_type, subject_id, def, val) | the same, plus legal_entity uuid default null with the legal-entities block |
upgrade adds pack and pack_version to field_definitions and creates
install_field_pack(tenant, pack jsonb). With the legal-entities block,
field_values gains a nullable legal_entity_id, and the unique key on the
tenant, subject type, subject id and definition gains the entity, so the
organization and each entity keep one value per field. An app without
registered subject types keeps free-text entities.
Rename the stored entities before the upgrade by mapping each to a registered type. The upgrade renames the definitions and the value rows that follow them:
blocks: {
"custom-fields": { options: { entityMap: { invoice: "sales_invoice" } } },
},A definition whose new name already has the same key in the organization is
left as it was. The codemod lists calls of saveDefinition,
listDefinitions and save_field_definition that name a literal entity, so
you can check each against the registered types. better_supabase.subject_types()
returns the registered names. Field packs, per-entity values and the
field_value import target are new (see custom fields).
Attachment document types
Attachments carry a document type, a source and an external id (ADR 0161), so
create_attachment changes signature, and this is a breaking change:
| 0.46 | 0.47 |
|---|---|
create_attachment(..., pages) | the same, plus document_type, source, external_id and source_path (all nullable) |
attachments has no document columns | document_type_id, source, external_id, storage_status and source_path |
subject_type is any text | it is validated against better_supabase.subject_types() when the subjects block exists |
| none | attachment_document_types, claim_attachment_upload, mark_attachment_missing |
upgrade drops the old create_attachment overloads, adds the columns, the
unique index on the organization, source and external id, and the new table.
Existing files get storage_status from uploaded_at. A table you adopted
for tables.attachments maps the five columns or sets each to null:
blocks: {
attachments: {
tables: {
attachments: {
name: "documents",
columns: { documentType: null, source: null, externalId: null, storage: null, sourcePath: null },
},
},
},
},Rows whose subject_type is not registered keep working until they are
written again; register the type or map it first. Declare options.sources
for every source your app writes besides import runs. The codemod lists calls
of create_attachment so you can check them. See attachments.
Table names
A module table is its entity's plural, or starts with its entity's singular.
upgrade writes a migration that renames each table in place, with its
indexes, policies, triggers, constraints and sequences, so no row moves and
nothing is dropped. The new names are in TABLE_RENAMES:
| Block | 0.46 tables | 0.47 tables |
|---|---|---|
activities | engagements | activities |
automations | rule_defaults, rule_evaluations, rule_fields, rules, rule_versions | automation_defaults, automation_evaluations, automation_fields, automations, automation_versions |
catalog | categories, price_lists, storefronts | catalog_categories, catalog_price_lists, catalog_storefronts |
collections | favorites | collection_favorites |
compliance | policies | compliance_policies |
domains | organization_hostnames | hostnames |
goals | targets | goals |
idempotency | leases | idempotency_leases |
inbox | message_deliveries, conversation_events, inbound_events, message_templates | inbox_message_deliveries, inbox_conversation_events, inbox_inbound_events, inbox_message_templates |
incidents | status_components, status_component_history, status_pages | incident_status_components, incident_status_component_history, incident_status_pages |
leases | lease_agreements | leases |
memory | ai_message_embeddings | memory_message_embeddings |
orders | fulfillment_lines, fulfillments, kitchen_routes, kitchen_stations, shipping_rates | order_fulfillment_lines, order_fulfillments, order_kitchen_routes, order_kitchen_stations, order_shipping_rates |
sso | organization_domains, organization_sso_providers | sso_domains, sso_providers |
tasks | work_item_assignees, work_item_checklist_items, work_items | task_assignees, task_checklist_items, tasks |
timeline | activity_entries | timeline_entries |
The idempotency leases table is renamed before the leases block takes the
name. A table you adopted (blocks.<name>.tables) keeps its name, and so
does every table of an entity you renamed: with names: { task: "work_item" }
the tasks block still writes work_items.
The codemod lists queries on an old better_supabase table for review;
rename them in your own SQL, views and queryRaw calls.
Foreign key columns
The columns that point at a task or an activity follow the entity (ADR 0152):
| Block | Table | Old column | New column |
|---|---|---|---|
tasks | task_checklist_items, task_assignees | work_item_id | task_id |
activities | engagement_subjects, engagement_recordings, engagement_notes | engagement_id | activity_id |
mail-sync | mail_threads | engagement_id | activity_id |
upgrade renames each column in the block tables. A column of an entity you
renamed keeps the name that entity gives it, and a column of an adopted table
or one you mapped in blocks.<name>.columns keeps its name. codemod 0.47
lists the lines that use work_item_id or engagement_id, so rename them in
your own SQL, views and policies.
The SQL modules
Regenerate the module SQL and the migration. The schema diff drops the old function names and creates the new ones, and the data steps move the subject types and the stored event types:
npx better-supabase@latest upgrade
npx better-supabase@latest db diff -f upgrade_0_46Removed in 0.48
These keep working in 0.47 and go in 0.48. The codemods and doctor point at the old keys, types and imports in your code.
| Deprecated in 0.47 | Use instead |
|---|---|
Old keys in blocks.<name>.entities | The entity's singular |
| The old subject types | task, activity and compliance_policy |
| 0.46 permission keys in configured roles | The keys in PERMISSION_KEY_RENAMES |
DbError.legacyHint | Match hint, or call legacyHintOf(hint) |
The outbox legacyEventTypes option | The new event types in every receiver |
SequenceEnrollmentEventData | EnrollmentEventData |
workItemChecklistBinding | taskChecklistBinding |
Removed in 0.47 already, without a shim: the compliance client methods
acknowledgePolicy, acknowledgements(policyId) and
policies(organizationId, { pendingOnly }), the inbox contacts.upsert
method and upsert_contact, the postmortem actionItems field and the
blocks["ai-files"].tables.versions key.
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.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.