From 0.13 to 0.14
What to change when upgrading to better-supabase 0.14, where block methods take a subject ref and read the tenant from the block.
0.14 changes the signature of every block method that works on a record.
Update the calls below, then run better-supabase sql sync and generate a
migration for the new comments and attachments author functions.
Block methods take a subject and read the tenant from the block
The methods of comments, attachments, tags, reactions, relations, versions,
sla, share links, custom fields, reminders, time tracking, approvals and the
state machine take the record they work on as a SubjectRef, { type, id },
as their first argument. They no longer take an organizationId: the
blocks read it from their new tenant option, an organization id or a
function that returns the current one. A call without a tenant fails with
invalid_request and the hint TENANT_REQUIRED. The state machine and the
sla clock methods take no tenant, since the record decides it.
Rows carry subject in place of subjectType and subjectId. Batch methods
take a list of subjects and return a record keyed type:id, which
subjectKey(subject) from better-supabase/blocks builds. Event payloads,
the SQL functions and their columns keep subjectType and subjectId.
| Before | After |
|---|---|
comments.create({ organizationId, subjectType, subjectId, body }) | comments.create(subject, { body }) |
comments.list(organizationId, subjectType, subjectId, options?) | comments.list(subject, options?) |
comments.counts(organizationId, subjectType, subjectIds), keyed by id | comments.counts(subjects), keyed type:id |
comments.copy(organizationId, from, to) | comments.copy(from, to) |
comments.history(organizationId, subject?, options?) | comments.history(subject?, options?) |
attachments.upload({ organizationId, subjectType, subjectId, ... }) | attachments.upload(subject | null, attachment) |
attachments.put({ organizationId, subjectType, subjectId, ... }, file) | attachments.put(subject | null, attachment, file) |
attachments.list(organizationId, subjectType?, subjectId?) | attachments.list(subject?) |
versions.snapshot({ organizationId, subjectType, subjectId, ... }) | versions.snapshot(subject, input?) |
versions.list(organizationId, subjectType, subjectId, options?) | versions.list(subject, options?) |
shareLinks.create(organizationId, subjectType, subjectId, options?) | shareLinks.create(subject, options?) |
shareLinks.recordView(organizationId, subjectType, subjectId, viewerKind, viewer?) | shareLinks.recordView(subject, viewerKind, viewer?) |
shareLinks.list(subjectType, subjectId), setState(subjectType, subjectId, starred) | shareLinks.list(subject), setState(subject, starred) |
tags.create(organizationId, kind, name, ...), tags.list(organizationId, kind?) | tags.create(kind, name, ...), tags.list(kind?) |
tags.tag(organizationId, subjectType, subjectId, tagId), and untag | tags.tag(subject, tagId), and untag |
tags.forSubject(organizationId, subjectType, subjectId) | tags.forSubject(subject) |
reactions.react(organizationId, subjectType, subjectId, emoji, options?) | reactions.react(subject, emoji, options?) |
reactions.summary(organizationId, subjectType, subjectIds), keyed by id | reactions.summary(subjects), keyed type:id |
reactions.customEmoji(organizationId), saveCustomEmoji(organizationId, input) | reactions.customEmoji(), saveCustomEmoji(input) |
customFields.listDefinitions(organizationId, entity), saveDefinition(organizationId, entity, ...) | customFields.listDefinitions(entity), saveDefinition(entity, ...) |
customFields.readValues(organizationId, subjectType, subjectId) | customFields.readValues(subject) |
customFields.setValue(organizationId, subjectType, subjectId, definitionId, value) | customFields.setValue(subject, definitionId, value) |
relations.link({ organizationId, source, target, kind, ... }), and unlink | relations.link(source, target, { kind, data?, fields? }), and unlink(source, target, { kind }) |
relations.of(organizationId, subject, kind?), edges(organizationId, kind) | relations.of(subject, kind?), edges(kind) |
relations.ancestors(organizationId, kind, subject), and descendants | relations.ancestors(subject, kind), and descendants |
relations.rollup({ organizationId, subject, ... }) | relations.rollup(subject, { kind, of, agg, column? }) |
relations.convertedInto(organizationId, origin, intoType), lineage(organizationId, subject) | relations.convertedInto(origin, intoType), lineage(subject) |
sla.savePolicy(organizationId, policy) | sla.savePolicy(policy) |
approvals.request({ organizationId, subjectType, subjectId, action, ... }) | approvals.request(subject, { action, reason?, payload? }) |
approvals.list(organizationId, options?) | approvals.list(options?), with an optional subject filter |
approvals.isApproved(subjectType, subjectId, action) | approvals.isApproved(subject, action) |
stateMachine.transition(subjectType, subjectId, to, reason?) | stateMachine.transition(subject, to, reason?), and the same for pause, resume, setFlag, state, history and stages |
reminders.save(organizationId, subjectType, subjectId, offsetMinutes?, fields?) | reminders.save(subject, offsetMinutes?, fields?) |
reminders.list(organizationId, subjectType, subjectId) | reminders.list(subject) |
timeTracking.start(organizationId, fields?), stop(organizationId), running(organizationId) | timeTracking.start(fields?), stop(), running() |
timeTracking.log, list, timesheet, submit and timesheets with organizationId first | the same calls without it; entries take subject instead of subjectType and subjectId |
row.subjectType, row.subjectId | row.subject.type, row.subject.id |
useComments({ organizationId, subjectType, subjectId, schema?, topic?, limit? }) | useComments({ subject, topic?, limit? }) |
useReactions({ organizationId, subjectType, subjectIds, schema?, topic? }) | useReactions({ subjects, topic? }) |
useTagInput({ organizationId, subjectType, subjectId, kind, schema? }) | useTagInput({ subject, kind }) |
To upgrade:
- Pass
tenantto the blocks you create yourself (createComments({ transport, tenant: () => ctx.tenant })), or build them withcreateBlockClientorblockSet.client, which pass the request's tenant (see one block per request). - Replace each
organizationId, subjectType, subjectIdargument list with one{ type, id }object, and readrow.subjectinstead ofrow.subjectTypeandrow.subjectId. Read batch results bysubjectKey(subject). - Pass
blocks: blockConfigto the browsercreateClient, andtenantwhen the tenant isn't the session's tenant claim, then dropschemaandorganizationIdfrom the hooks (see block hooks). - Run
better-supabase sql sync, then generate a migration, for the comments and attachments author functions.
One block client per request instead of a wrapper per block
A lib/blocks.ts that wrapped each block to pass the organization can go.
createBlockClient(ctx.db, commentsBlock, { config: blockConfig }) and
blockSet.client(ctx.db, "comments") build one block for a request: they
call through ctx.db and act in ctx.db.$context.tenant.
Comments and attachments return their author
comments.list and attachments.list return author on each row, with the
name and avatar URL from the profiles module, through the new security
definer functions comment_authors(uuid[]) and attachment_authors(uuid[]).
They return only authors of rows the caller may read. Without the profiles
module, author.name and author.avatarUrl are undefined.
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.11 to 0.12
What to check when upgrading to better-supabase 0.12 and 0.13, starting with the audit() signature, the acting_member() grant and the module file headers.