# Recipes

> Blocks combined into one product, with the config, the event flow and the UI components to pair them with.

Source: https://bettersupabase.com/docs/recipes

A recipe installs several blocks on one subject table and wires their
events together, so a comment, a tag, a stage move or a reminder reaches the
people watching that row. Each page has the table, the
`better-supabase.config.ts`, the event flow and the
[ReUI](https://reui.io) components that fit the data. better-supabase types
the data those components take structurally and does not depend on ReUI.

| Recipe                                                 | Subject    | Blocks                                                                  |
| ------------------------------------------------------ | ---------- | ----------------------------------------------------------------------- |
| [CRM](/docs/recipes/crm)                               | `deal`     | comments, tags, custom-fields, rank, reminders, state-machine           |
| [Project management](/docs/recipes/project-management) | `task`     | teams, comments, tags, rank, reminders, number-sequences, state-machine |
| [Drive](/docs/recipes/drive)                           | `document` | comments, tags, attachments, share-links, rank                          |
| [Scheduling](/docs/recipes/scheduling)                 | `booking`  | custom-fields, share-links, reminders, state-machine                    |

Every recipe also installs `organizations`, `outbox` and `notifications`.
`tests/validation-recipes` renders each recipe's modules into one migration,
applies it to the local stack and drives the flows on these pages end to
end.

## The shared wiring [#the-shared-wiring]

Three pieces connect the blocks in every recipe.

The subject is declared once in `config.subjects`. Comments, tags, custom
fields, share links and reminders all read it from the
[subject registry](/docs/blocks/sql#subject-registry), and `rank` reads its `rank` entry.

A state machine is named after its subject (`deal`, `task`, `booking`). The
machine name is the `subjectType` of its history rows and of
`status.changed`, so a status change reaches the same watchers as a comment.

A cron route hands the outbox to the watchers. `watcherHandler` reads the
actor from each outbox row, so whoever acts is not notified of their own
change:

```ts title="app/api/cron/watchers/route.ts"
import { watcherHandler } from "better-supabase/blocks/notifications";

const watchers = watcherHandler(notifications, {
  types: {
    "comment.created": "subject.commented",
    "subject.tagged": "subject.tagged",
    "status.changed": "subject.moved",
    "share_link.created": "subject.shared",
    "field_value.set": "subject.field_changed",
  },
});

export const GET = outbox.relayRoute({
  secret: process.env.CRON_SECRET,
  consumers: { watchers },
});
```

Users follow a row with `notifications.subscribe({ subject, level })`.
Watchers at level `all` get every mapped event. See
[watchers](/docs/blocks/notifications#watchers) for rules with `activity`,
`recipients` and `summary`.

Reminders deliver through the `notifications` block when `createBlocks` gets
`reminderNotification`. The reminder's owner and the subject's watchers get
one notification per occurrence:

```ts title="lib/blocks.ts"
const { comments, reminders, stateMachine } = createBlocks(
  { transport, service, reminderNotification: "reminder.due" },
  {
    notifications: (options) => createNotifications({ ...options, types }),
    comments: createComments,
    reminders: createReminders,
    stateMachine: createStateMachine,
  },
);
```

`reminders.job` runs as the handler of the `reminders.deliver` queue
([delivery](/docs/blocks/reminders#delivery)).