blendx docs

Review

For every blend, blendx writes a file that says what the API does, in terms a person can check without reading TypeScript: review/<table>.yaml. Beside it, review/<table>.examples.yaml holds concrete cases that person expects to hold. Together they let a product owner, an auditor, or whoever reviews an agent's work check the behaviour, and ask for a change.

The format is specified in packages/spec/review-format.md. This page shows how to use it.

review/<table>.yaml

bunx blendx review writes one file per blend. The file is generated, and the same code always gives the same file. An excerpt from examples/expenses:

# Generated by `blendx review`. Do not edit: a change here is a fix request.
# Change the blend until `blendx review --check` passes.

format: 1
resource: expenses
source: blends/expenses.ts
record:
  id: integer
  user_id: integer
  description: string, at most 200 characters
  category: one of travel, meals, office, other
  amount: string
  tax: string
  total: string
  status: one of draft, submitted, approved, rejected
  ...
actions:
  ...
  reject:
    route: POST /expenses/:id/reject
    input:
      note: string, at least 3 characters, at most 500 characters
    stages:
      rules: nothing (an empty object) # from: schema, action
      load: the row by id, not soft-deleted, locked for update
      authorize: an approver # from: schema, action
      calculate: the input's writable columns # from: schema, action
      save: update the row, touching updated_at, when there is something to write
      respond: 200 with the saved record
    calculate:
      source: |-
        ({ input }) => ({ status: 'rejected' as const, review_note: input.note })
      writes: [review_note, status]
    reply:
      status: 200
      body: the record

How to read it:

  • record is what a reply carries for one row, hidden columns already removed.
  • Each action has its route, the input it accepts (one line per field, from the resolved rules), and one line per pipeline stage saying what the default does. A string with a format reads as that format, such as string (date) or string (email).
  • # from: schema, action marks a stage that a hook changed: here the action's own rules, authorize and calculate replaced or extended the schema's defaults. A stage without the comment does exactly what its line says.
  • The authorize line is the policy's description (an approver, owner (user_id = auth.id)). An authorize hook on top of it shows as # from: schema, action; its condition is in the blend.
  • An action with an after hook lists after: nothing # from: schema, action between save and respond: something happens once its write has committed, such as an email, and the comment says which levels' hooks run (Hooks). Actions without one have no after line.
  • calculate shows the hook exactly as written, and the columns it writes (for a collection action, the keys it returns).
  • reveals lists the hidden columns an action's reply carries, and its reply then reads the record, with api_token. It is where to check that a secret leaves the server only where it should.
  • reply is the status and the body.

Examples

review/<table>.examples.yaml belongs to the people who review. It maps action names to lists of examples: an input (and a record, for a member action), and then what must come out of it:

  • writes: the columns calculate must return;
  • returns: the same, for a collection action;
  • rejects: the fields validation must refuse: JSON pointers (/amount) for a body, parameter names (category) for a get action's query.

From examples/expenses:

store:
  - name: meals carry 10% tax
    input: { description: Team lunch, category: meals, amount: '40.00', spent_on: '2026-09-01' }
    writes:
      { description: Team lunch, category: meals, amount: '40.00', spent_on: '2026-09-01', tax: '4.00', total: '44.00' }
  - name: the claimant and the totals are never sent
    input: { description: Taxi, category: travel, amount: '12.50', spent_on: '2026-09-04', user_id: 2, total: '1.00' }
    rejects: [/total, /user_id]
update:
  - name: a new amount is priced with the claim's category
    record: { category: office, amount: '10.00' }
    input: { amount: '20.00' }
    writes: { amount: '20.00', tax: '4.00', total: '24.00' }
quote:
  - input: { amount: '40.00', category: meals }
    returns: { tax: '4.00', total: '44.00' }

Examples run without a database, the way the engine runs the action: the resolved rules validate the input, then calculate runs. Policies, authorize hooks and saving do not run, so examples pin down validation and calculation; permissions are read in the review file and tested in the app's tests. blendx review --check runs every example, and so can the app's tests (Testing).

Asking for a change

Editing a review file does not change the API. It is how a reviewer asks for a change:

  1. The reviewer edits review/<table>.yaml to say what should be different, or adds an example with the output they expect.
  2. bunx blendx review --check now fails. An edited line shows as a unified diff, where the - lines are what the reviewer wrote and the + lines what the code does. A new example fails with one line naming the file, the action, the example, and what came out against what was expected.
  3. A developer or an agent changes the blend (a policy, a rule, a hook or a calculate), never the YAML, and runs bunx blendx generate.
  4. When the blend does what the reviewer wrote, blendx review --check passes and the reviewer's edit stays as it is. If the change also moved lines the reviewer did not touch, such as a calculate's source, bunx blendx review rewrites the file.

For example, a reviewer who wants approvers to be able to delete claims changes destroy's line from authorize: owner (user_id = auth.id) # from: schema, action to authorize: an approver # from: schema, action. The check fails with that line in its diff until destroy gets the approvers policy in the blend.

Run bunx blendx review --check in CI next to bunx blendx generate --check: together they fail when the code and what was reviewed have drifted apart, in either direction.

Who does what

WritesReads
Developer or agentschema.dbml, blends, src/app.ts, teststhe diffs of a failing review --check
blendx reviewreview/<table>.yamlthe blends
Revieweredits to review/<table>.yaml, review/<table>.examples.yamlreview/*.yaml