OriginChain docs
examples · atomic · 1 / 5

1. Product catalog (row + vector + FTS + graph edge)

← Atomic multi-shape
what this does

Save one product across four shapes - the structured row in shop.products, an embedding of the name + description for similarity search, a BM25 full-text index on the description for keyword search, and a graph edge to its supplier. The supplier edge is the only one you don't write by hand: declaring supplier_id as a [[relations]] column in the schema makes the row write also create the edge in the same commit.

when to use it
  • E-commerce catalogs where one product needs to be findable by keyword, by similarity, and by a graph walk (e.g. "all products from suppliers in this region").
  • Any domain object you want to query in more than one shape - the four calls run once per object and then every read path works.
the schema

Push this once with /v1/schemas before the first write. Note the [[relations]] block - that's what makes the supplier edge automatic.

# shop/products.toml
namespace   = "shop"
table       = "products"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "name"
ty   = "str"
required = true

[[columns]]
name = "description"
ty   = "str"

[[columns]]
name = "price_cents"
ty   = "i64"

[[columns]]
name = "category"
ty   = "str"

[[columns]]
name = "supplier_id"
ty   = "str"
required = true

[[indexes]]
name    = "by_category"
columns = ["category"]

# Declaring supplier_id as a relation makes the row write also
# create a graph edge from this product to its supplier.
[[relations]]
name          = "supplied_by"
from_col      = "supplier_id"
bidirectional = true

[relations.target]
namespace = "shop"
table     = "suppliers"
pk        = "id"
call 1 of 3 - the row (also writes the supplier edge)
POST /v1/tenants/:t/rows/shop.products
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/rows/shop.products" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id":          "sku-9281",
    "name":        "Trailblazer 2",
    "description": "All-terrain running shoe with carbon-plate forefoot.",
    "price_cents": 12900,
    "category":    "running-shoes",
    "supplier_id": "sup-acme-shoes"
  }'
call 2 of 3 - the embedding

The id here is the same primary key as the row. Same key = same logical product across shapes.

POST /v1/tenants/:t/vector/shop.products/put
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/vector/shop.products/put" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id":        "sku-9281",
    "embedding": [0.0124, -0.0883, 0.0451, /* ... 768 floats ... */],
    "dim":       768,
    "metric":    "cosine",
    "metadata":  { "category": "running-shoes" }
  }'
call 3 of 3 - the full-text index

doc_id matches the row's primary key so search results can be joined back to the row.

POST /v1/tenants/:t/fts/shop.products/index
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/fts/shop.products/index" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "field":  "description",
    "doc_id": "sku-9281",
    "text":   "Trailblazer 2 all-terrain running shoe with carbon-plate forefoot"
  }'
about atomicity

The three calls are separate. There is no single "write everything" endpoint. Each call is atomic by itself - the row write either commits or doesn't, the vector put either commits or doesn't. Every mutating call gets an auto-attached Idempotency-Key from the SDK, so if the FTS call fails after the row and vector succeeded, you can safely retry just the FTS one without re-writing the others.

common mistakes
  • Forgetting one of the three calls. If you skip the vector put, similarity search won't return this product. If you skip the FTS index, keyword search won't. The row write doesn't fan out to the other shapes.
  • Updating one without re-doing the others. If you re-put the row with a new description, the vector and FTS index still point at the old text until you re-put / re-index them too.
  • Mismatched IDs. The id in the row write, the vector put, and the FTS doc_id must all be the same string. If they drift, you can't join search hits back to the row.
  • Writing the supplier edge by hand. Don't. Let the [[relations]] block do it. If you also POST to /graph/edges, you'll get duplicate edges on every row update.