Roam Research Docs · Developer documentation
[[links]] and ((refs)) connect them, but that is an untyped graph. Attributes (written Name:: value) add typed relationships on top.
Name:: value assertion is derived from a real block, the attribute name is a real page, and the value can be another entity that carries its own attributes.
:block/uid.
[entity attribute value] triple, plus provenance refs recording which blocks it was derived from.
:harc/e is the entity being described (normally the attribute block's parent page/block).
:harc/e points at the referenced node while the e-source stays the referencing block.
[[Project Apollo]] from a daily note.
:harc/a is the attribute; always a page, since Name:: resolves to a [[Name]] page.
:harc/v is the value(s); many-cardinality, so one harc holds all of an attribute's values for that entity.
:harc/e-source, :harc/a-source, :harc/v-source record which blocks each position was derived from (see provenance below).
:harc/v can have multiple values. But all were made cardinality many for future compatibility
-source refs**
:harc/a-source is the Name:: block itself, the block that defines the assertion. One per harc.
:harc/e-source is the block the entity was reached through: normally the attribute block's parent, even when :harc/e proxied through it to a referenced entity.
:harc/v-source records which block contributed each value: a value child, or the attribute block itself when the value comes from its inline tail.
:harc/v is that child block's own node.
Owner:: [[Jane Doe]], or a child that is only refs) → :harc/v is the referenced node(s).
Status:: Active) → the tail becomes one owned text entity: {:block/uid "t-<harc-uid>" :harc.text/string "Active"}.
:block/string) and is referenced only through :harc/v. It lives and dies with its harc.
:harc.text/string, so value strings are directly queryable.
t- + the harc's uid), so two clients minting the same value concurrently converge on one entity instead of duplicating.
Name:: with no tail and no children keeps its harc with an empty :harc/v.
[[hello [[world]]]] picks up hello, not the nested world).
:harc/_e off it.
// all attributes of a page, with their values
window.roamAlphaAPI.data.pull(
"[{:harc/_e [{:harc/a [:node/title]} {:harc/v [:block/uid :node/title :block/string :harc.text/string]}]}]",
[":node/title", "Project Apollo"])
:harc/_a backwards from the attribute page.
;; datalog: every entity with a Status attribute, and the value entities
[:find ?e ?v
:where [?a :node/title "Status"]
[?harc :harc/a ?a]
[?harc :harc/e ?e]
[?harc :harc/v ?v]]
:harc/_a-source from the block. Everything a node is a value of: :harc/_v from the node.
:node/title, a block's :block/string, or a text entity's :harc.text/string.
or over those three normalizes the heterogeneity.
Project Apollo (page-apollo)
Status:: Active (blk-status)
Owner:: [[Jane Doe]] (blk-owner)
Tags::(blk-tags)
[[urgent]] (blk-tag1)
[[backend]] (blk-tag2)
;; Project Apollo --Status--> "Active" (inline tail -> owned text entity)
{:block/uid "harc-1"
:harc/e [{:block/uid "page-apollo"}]
:harc/a [{:block/uid "page-status"}]
:harc/v [{:block/uid "t-harc-1" :harc.text/string "Active"}]
:harc/e-source [{:block/uid "page-apollo"}]
:harc/a-source [{:block/uid "blk-status"}]
:harc/v-source [{:block/uid "blk-status"}]}
;; Project Apollo --Owner--> [[Jane Doe]] (inline ref -> the page)
{:block/uid "harc-2"
:harc/e [{:block/uid "page-apollo"}]
:harc/a [{:block/uid "page-owner"}]
:harc/v [{:block/uid "page-jane"}]
:harc/e-source [{:block/uid "page-apollo"}]
:harc/a-source [{:block/uid "blk-owner"}]
:harc/v-source [{:block/uid "blk-owner"}]}
;; Project Apollo --Tags--> [[urgent]], [[backend]] (one harc, two values)
{:block/uid "harc-3"
:harc/e [{:block/uid "page-apollo"}]
:harc/a [{:block/uid "page-tags"}]
:harc/v [{:block/uid "page-urgent"} {:block/uid "page-backend"}]
:harc/e-source [{:block/uid "page-apollo"}]
:harc/a-source [{:block/uid "blk-tags"}]
:harc/v-source [{:block/uid "blk-tag1"} {:block/uid "blk-tag2"}]}
:harc/e is the parent harc, not a page or block.
Role:: Lead nested under the Owner:: block:
Project Apollo (page-apollo)
Owner:: [[Jane Doe]] (blk-owner)
Role:: Lead (blk-role)
;; the ownership relationship --Role--> "Lead"
{:block/uid "harc-4"
:harc/e [{:block/uid "harc-2"}] ;; harc-2 = the Owner harc above
:harc/a [{:block/uid "page-role"}]
:harc/v [{:block/uid "t-harc-4" :harc.text/string "Lead"}]
:harc/e-source [{:block/uid "blk-owner"}]
:harc/a-source [{:block/uid "blk-role"}]
:harc/v-source [{:block/uid "blk-role"}]}
:harc/e can point at another harc, and chains of statements-about-statements are plain graph walks.
roam/meta:: as a structural proxy**
roam/meta:: never gets a harc of its own.
roam/meta:: block's parent: a way to tuck an entity's attributes away under a single child.
:harc/e-sources.