OriginChain docs
examples · atomic · 3 / 5

3. Social user (row + vector + graph self-relation)

← Atomic multi-shape
what this does

Save a user as a row in social.users, embed their bio for "find similar users", and model follows as a separate edge table social.follows. The edge table's schema declares both follower_id and followee_id as relations pointing back to social.users, so each follow row write creates both directions in one commit.

when to use it
  • "People you may know" - similar-user lookup over bio embeddings, intersected with friends-of-friends from the follow graph.
  • Reverse-adjacency reads like "who follows me?" are a graph walk, not an index scan, because the bidirectional relation maintains both sides.
  • Any social, professional, or collaboration network with self-relations between users.
the schemas (two tables)

Push these two schemas once. Note that the follow-edge table doesn't need an embedding or FTS - it's pure relational structure.

# social/users.toml
namespace   = "social"
table       = "users"
primary_key = ["id"]

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

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

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

[[columns]]
name = "joined_ms"
ty   = "u64"

[[indexes]]
name    = "by_handle"
columns = ["handle"]


# social/follows.toml - the edge table.
# Declaring BOTH columns as relations makes one row write
# create both the forward and reverse edges automatically.
namespace   = "social"
table       = "follows"
primary_key = ["follower_id", "followee_id"]

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

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

[[columns]]
name = "created_ms"
ty   = "u64"

[[relations]]
name          = "follows"
from_col      = "followee_id"
bidirectional = true

[relations.target]
namespace = "social"
table     = "users"
pk        = "id"

[[relations]]
name          = "followed_by"
from_col      = "follower_id"
bidirectional = true

[relations.target]
namespace = "social"
table     = "users"
pk        = "id"
call 1 of 3 - the user row
POST /v1/tenants/:t/rows/social.users
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/rows/social.users" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id":        "alice",
    "handle":    "@alice",
    "bio":       "Mechanical engineer. Trail runner. Reading sci-fi.",
    "joined_ms": 1746180851000
  }'
call 2 of 3 - the profile embedding

Use the same id as the user row. Now /vector/social.users/topk returns similar users by bio.

POST /v1/tenants/:t/vector/social.users/put
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/vector/social.users/put" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id":        "alice",
    "embedding": [0.0182, -0.0712, 0.0419, /* ... 768 floats ... */],
    "dim":       768,
    "metric":    "cosine"
  }'
call 3 of 3 - writing a follow edge

One row write on social.follows. The [[relations]] blocks on both columns make the forward and reverse edges land in the same commit - no second call.

POST /v1/tenants/:t/rows/social.follows
curl -X POST "$ORIGINCHAIN_URL/v1/tenants/$T/rows/social.follows" \
  -H "Authorization: Bearer $OC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "follower_id": "alice",
    "followee_id": "bob",
    "created_ms":  1746180999000
  }'
about atomicity

The user row and the profile embedding are separate calls - each atomic by itself. There is no single "write everything" endpoint. The SDKs auto-attach an Idempotency-Key on every mutating call, so if the vector put fails after the row succeeded, retry just that one. The follow-edge write is its own atomic call, and writing the same follower/followee pair twice is a no-op because of the primary key.

common mistakes
  • Writing both edge directions by hand. Don't. Let the two [[relations]] blocks on social.follows do it. Manually POSTing to /graph/edges on top of the row write doubles every follow.
  • Updating handle without re-embedding. If you let users edit their handle and you embed it, the vector goes stale until you re-put. Either re-put on every profile edit or only embed the bio.
  • Storing the follow edge inside the user row. Don't put following: ["bob", "carol"] on the user row - it doesn't scale and you lose the reverse adjacency. Use the edge table.
  • Not deleting the edge on unfollow. Removing the user row doesn't fan out to their edges. You have to DELETE /rows/social.follows/<follower_id>,<followee_id> separately.