Typestate
Sign in

Small shop stock

Each sale and delivery moves the stock, and the owner gets one email when a product runs low.

For a shop that sells at the counter and online, and counts its own stock.

Resend logo Resend
Start it with your agentStarting from a blueprint does not count against the action changes of your plan.

Start the small shop stock blueprint on Typestate

Asked you for the address the low-stock email goes to and the address your emails come from.

Started Small shop stock: 2 record types, 5 web endpoints.

5 actions are compiling.

Deployed to Sandbox. Try it at your Sandbox address.

What your agent does when you start this blueprint

What runs

Each step has 3 parts: who starts it, how it reaches your backend, and the action that answers.

  1. Youadd each product with its low mark
    Web endpoint, your keyPOST /products
    Add a ProductKeeps the product with its code and its low mark.saves a Product
  2. A deliverycomes in, and the stock goes up
    Web endpoint, your keyPOST /deliveries
    Record a DeliveryAdds the delivery to the stock.changes a Productsaves a Movement
  3. The till or web shoprecords a sale, and the stock goes down
    Web endpoint, your keyPOST /sales
    Record a SaleTakes the sale off the stock.changes a Productsaves a Movement
  4. Your backendemails you once when a product runs low
    Web endpoint, your keyPOST /sales
    Record a SaleEmails you the first time the product is under its low mark.sends 1 email
  5. Youcount the shelf when the numbers drift
    Web endpoint, your keyPOST /counts
    Count the ShelfSets the stock to the count and keeps the difference.changes a Productsaves a Movement
  • Web endpoint: your website or app calls it

The rule you will want to change first

When a sale takes a product under its low mark, the owner gets one email. The next email about it comes only after a delivery brings it back above the mark.

It lives in the action Record a Sale. Tell your agent how you want it instead: one sentence changes, and you read the change before it goes Live.

What it is made of

The map shows the records it keeps, the actions it runs and the services it calls.

What it keeps

Productname, sku, stock, low at, low told
Movementproduct, change, reason, note

What it does

Add a Productweb endpoint, your key
Record a Saleweb endpoint, your keyThe rule to change first
Record a Deliveryweb endpoint, your key
Count the Shelfweb endpoint, your key
List Stockweb endpoint, your key

What it reaches

ResendSend email from your own address: sign-in links, receipts, reminders.
Your website5 web endpoints

What it keeps

Product

5 fields

One thing the shop sells, with its stock.

name
required, at most 120 characters
sku
required, never twice, at most 40 characters
stock
required, a whole number
low at
required, a whole number
low told
required, yes or no

Only the shop's key reads and changes the stock.

Movement

4 fields

Each change to the stock, and why.

product
required
change
required, a whole number
reason
required, one of sale, delivery, count
note
optional, at most 200 characters

Only the shop's key reads and writes the movements.

What it does

Open an action to read its steps: each sentence, with the calls that do it under it.

Add a ProductThe shop adds a product with its code and its low mark.

Who starts it

From the webPOST /products

What it is given

Nametext, needed

Codetext, needed

On shelfa whole number, needed

Low marka whole number, needed

What it does

  • input.codewhat the caller gives
  • existinga value it works out
  • Producta record type
  • Productan attribute of a record or an answer
  • falsea fixed text or number
  • Refusewhere it says no

Find the Product whose sku is the given code.When one matches, refuse with "Another product has this code".

existing is

Find the first Product where

sku is input.code

when none carry on without one

When existing holds a value

Refuse "Another product has this code"

Create a Product with name, sku, stock and low_at set to the given name, code, number on the shelf and low mark, and low_told set to false.

Add a Product

set low_at to input.low_mark

set low_told to false

set name to input.name

set sku to input.code

set stock to input.on_shelf

Answer with that Product's id, name, sku, stock and low_at.

Answer

id is product.id

low_at is product.low_at

name is product.name

sku is product.sku

stock is product.stock

Record a SaleThe till or the web shop records a sale, and the stock goes down. A product that runs low is emailed to the owner once.

Who starts it

From the webPOST /sales

What it is given

Codetext, needed

Solda whole number, needed

What it does

  • input.soldwhat the caller gives
  • producta value it works out
  • Producta record type
  • Productan attribute of a record or an answer
  • "sale"a fixed text or number
  • toa part of an email or a payment
  • Refusewhere it says no

When the given number sold is less than 1, refuse with "Sell at least 1".

When input.sold is less than 1

Refuse "Sell at least 1"

Find the Product whose sku is the given code.When none matches, refuse with "No product has this code".

product is

Find the first Product where

sku is input.code

when none refuse "No product has this code"

When that Product's stock is less than the number sold, refuse with "Only {stock} are on the shelf".

When product.stock is less than input.sold

Refuse "Only {stock} are on the shelf"

Change that Product's stock to its stock minus the number sold, only if its stock is still at least the number sold, and refuse with "Only {stock} are on the shelf" if it is not.

Change the Product where

id is product.id

set stock to product.stock - input.sold

Create a Movement with product_id set to that Product's id, change set to minus the number sold, and reason set to sale.

Add a Movement

set change to 0 - input.sold

set product_id to product.id

set reason to "sale"

When that Product's new stock is below its low_at and its low_told is false, email the address in the OWNER_EMAIL setting with the subject "Running low" and the Product's name, with its sku, its new stock and its low_at, and change its low_told to true.

The owner is told once, so a busy day does not fill the inbox.

When sold_product.stock is less than product.low_at

and product.low_told is false

Send an email

to the setting OWNER_EMAIL

template "Record a Sale Email"

low_at product.low_at as a number

name product.name

sku product.sku

stock sold_product.stock as a number

Change the Product where

id is product.id

set low_told to true

Answer with the stock that is left.

Answer

stock is sold_product.stock

Record a DeliveryA delivery comes in, and the stock goes up.

Who starts it

From the webPOST /deliveries

What it is given

Codetext, needed

Numbera whole number, needed

Notetext, optional

What it does

  • input.numberwhat the caller gives
  • producta value it works out
  • Producta record type
  • Productan attribute of a record or an answer
  • "delivery"a fixed text or number
  • Refusewhere it says no

When the given number is less than 1, refuse with "A delivery brings at least 1".

When input.number is less than 1

Refuse "A delivery brings at least 1"

Find the Product whose sku is the given code.When none matches, refuse with "No product has this code".

product is

Find the first Product where

sku is input.code

when none refuse "No product has this code"

Add the given number to that Product's stock.

Change the Product where

id is product.id

add input.number to stock

Create a Movement with product_id set to that Product's id, change set to the given number, reason set to delivery, and note set to the given note.

Add a Movement

set change to input.number

set note to input.note

set product_id to product.id

set reason to "delivery"

When that Product's new stock is at or above its low_at, change its low_told to false.

The owner is told again the next time it runs low.

When stocked.stock is at least product.low_at

Change the Product where

id is product.id

set low_told to false

Answer with the new stock.

Answer

stock is stocked.stock

Count the ShelfSomebody counts the shelf, and the stock is set to what is really there.

Who starts it

From the webPOST /counts

What it is given

Codetext, needed

Counta whole number, needed

What it does

  • input.codewhat the caller gives
  • producta value it works out
  • Producta record type
  • Productan attribute of a record or an answer
  • "count"a fixed text or number
  • refusewhere it says no

Find the Product whose sku is the given code.When none matches, refuse with "No product has this code".

product is

Find the first Product where

sku is input.code

when none refuse "No product has this code"

The difference is the given count minus that Product's stock.

difference is input.count - product.stock

Create a Movement with product_id set to that Product's id, change set to the difference, and reason set to count.

Add a Movement

set change to difference

set product_id to product.id

set reason to "count"

Change that Product's stock to the given count.

Change the Product where

id is product.id

set stock to input.count

Answer with the difference.

Answer

difference is difference

List StockThe shop sees every product, lowest first.

Who starts it

From the webGET /stock

What it is given

Low onlyyes or no, optional

What it does

  • input.low_onlywhat the caller gives
  • all_productsa value it works out
  • Producta record type
  • stockan attribute of a record or an answer
  • falsea fixed text or number

List the Products, the ones furthest under their low_at first.When only the low ones are asked for, list only the Products whose stock is below their low_at.

all_products is

Find all Products

chosen is

each product of all_products where

input.low_only is false

or product.stock is less than product.low_at

ranked is

each product of chosen as

low_at is product.low_at

name is product.name

sku is product.sku

stock is product.stock

under is product.low_at - product.stock

sorted by under, highest first

For each Product, answer its name, sku, stock and low_at.

Answer

products is

each row of ranked as

low_at is row.low_at

name is row.name

sku is row.sku

stock is row.stock

Web endpoints

What it doesWho may callAddress
The shop adds a product.you, with your keyPOST /products
The till or the web shop records a sale.you, with your keyPOST /sales
The shop records a delivery.you, with your keyPOST /deliveries
The shop records a count.you, with your keyPOST /counts
The shop reads its stock.you, with your keyGET /stock

What it cannot do yet

  • It does not take payments. The till or the web shop records each sale here.
  • One shop. Stock in two places is one more field and one more rule.

Build the pages with your tool

Typestate is the backend. Paste this brief into Lovable, v0, Claude Code or the tool that builds your pages.

Show the brief
Build the pages for "Small shop stock". The backend runs on Typestate and answers these web endpoints: POST /products: The shop adds a product. Who may call: you, with your key. POST /sales: The till or the web shop records a sale. Who may call: you, with your key. POST /deliveries: The shop records a delivery. Who may call: you, with your key. POST /counts: The shop records a count. Who may call: you, with your key. GET /stock: The shop reads its stock. Who may call: you, with your key. People sign up at POST /auth/signup and sign in at POST /auth/login, which answers a token. Send it as Authorization: Bearer <token>. A refused call answers a sentence that can be shown to the person as it is.

Start it

  1. Tell your agent the sentence on the right.
  2. It asks you for the address the morning email goes to.
  3. It asks you for the address emails come from, on a domain you verified at Resend, such as "Riverside Physio <hello@riverside.example>".
  4. It tells you what only you can do, such as pasting your Resend key in the cockpit. A key never goes through the agent.
  5. Open your Sandbox address and try it yourself.

Starting from this blueprint does not count against the action changes of your plan. The Free plan runs it on Sandbox. Live needs a paid plan.

Start the small shop stock blueprint on TypestateChoose a plan

Related blueprints