# 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.

Source: https://bettersupabase.com/docs/migration/0.13-to-0.14

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 [#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:

1. Pass `tenant` to the blocks you create yourself
   (`createComments({ transport, tenant: () => ctx.tenant })`), or build them
   with `createBlockClient` or `blockSet.client`, which pass the request's
   tenant (see [one block per request](/docs/blocks/create-blocks#one-block-per-request)).
2. Replace each `organizationId, subjectType, subjectId` argument list with
   one `{ type, id }` object, and read `row.subject` instead of
   `row.subjectType` and `row.subjectId`. Read batch results by
   `subjectKey(subject)`.
3. Pass `blocks: blockConfig` to the browser `createClient`, and `tenant`
   when the tenant isn't the session's tenant claim, then drop `schema` and
   `organizationId` from the hooks (see [block hooks](/docs/frontend/client#block-hooks)).
4. 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 [#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-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`.