diff --git a/.cspell/template-words.txt b/.cspell/template-words.txt index 498e535fb..ca4762d19 100644 --- a/.cspell/template-words.txt +++ b/.cspell/template-words.txt @@ -1,5 +1,7 @@ # Words found in our template code brower +# Silly thing that pops up in Mermaid diagrams +CAID coreconcepts elif endfor @@ -8,6 +10,8 @@ endlink endmacro endrenderlayoutblock gtag +# Silly thing that pops up in Mermaid diagrams +kafe katex layoutblock MSIE diff --git a/src/client-side/index.ts b/src/client-side/index.ts index b1e52811d..2bdcc7ed4 100644 --- a/src/client-side/index.ts +++ b/src/client-side/index.ts @@ -148,3 +148,6 @@ const openDetailsOnFragmentIdNavigation = () => { openDetailsOnFragmentIdNavigation(); window.addEventListener("hashchange", openDetailsOnFragmentIdNavigation); +// Load MyFonts counter for URW Form fonts asynchronously, not in JS. +// This is a workaround for a weird error thrown by netlify-plugin-checklinks. +fetch("//hello.myfonts.net/count/3e423c"); \ No newline at end of file diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 2f18278b5..e52789649 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -26,7 +26,7 @@ ] }, { title: "Build", url: "/build/", children: [ - { title: "Working with Data", url: "/build/#working-with-data", children: [ + { title: "Working with Data", url: "/build/working-with-data/", children: [ { title: "Entries", url: "/build/entries/" }, ]}, ] diff --git a/src/pages/build/crud-patterns.md b/src/pages/build/crud-patterns.md new file mode 100644 index 000000000..e69de29bb diff --git a/src/pages/build/entries.md b/src/pages/build/entries.md index 174668939..aac91a8f7 100644 --- a/src/pages/build/entries.md +++ b/src/pages/build/entries.md @@ -48,6 +48,8 @@ enum EntryTypes { } ``` +### Configuring an entry type + Each variant in the enum should hold the Rust type that corresponds to it, and is implicitly marked with an `entry_def` proc macro which, if you specify it explicitly, lets you configure the given entry type further: * An entry type can be configured as **private**, which means that it's never published to the DHT, but exists only on the author's source chain. To do this, use the `visibility = "private"` argument. @@ -127,7 +129,7 @@ When the client calls a zome function that calls `create_entry`, Holochain does 9. Return the zome function's return value to the client. 10. In the background, publish all newly created DHT operations to their respective authority agents. -## Update an Entry +## Update an entry Update an entry creation action by calling [`hdk::prelude::update_entry`](https://docs.rs/hdk/latest/hdk/prelude/fn.update_entry.html) with the old action hash and the new entry data: @@ -177,21 +179,21 @@ Calling `update_entry` does the following: ### Update patterns -Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it's used. While in most cases you'll want to interpret it as applying to the original _record_ (action + entry), there are cases where you might want to interpret it as applying to the original _entry_, because the `Update` action is merely a piece of metadata attached to both, and can be retrieved along with the original data using [`hdk::prelude::get_details`](https://docs.rs/hdk/latest/hdk/prelude/fn.get_details.html) (see [below](#all-records-and-links-attached-to-an-entry)). +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it's used. While in most cases you'll want to interpret it as applying to the original _record_ (action + entry), there are cases where you might want to interpret it as applying to the original _entry_, because the `Update` action is merely a piece of metadata attached to both, and can be retrieved along with the original data using [`hdk::prelude::get_details`](https://docs.rs/hdk/latest/hdk/prelude/fn.get_details.html) (see [below](#all-data-actions-and-links-at-an-address)). You can also choose what record updates should be attached to. You can structure them as a 'list', where all updates refer to the `ActionHash` of the original `Create` action. -
Update 1
Create
Update 2
Update 3
Update 4
+
Update 1
Create
Update 2
Update 3
Update 4
Or you can structure your updates as a 'chain', where each update refers to the `ActionHash` of the previous entry creation action (either an `Update` or the original `Create`). -
Update 1
Create
Update 2
Update 3
Update 4
+
Update 1
Create
Update 2
Update 3
Update 4
If you structure your updates as a chain, you may want to also create links from the `ActionHash` of the original `Create` to each update in the chain, making it a hybrid of a list and a chain. This trades additional storage space for reduced lookup time. @@ -303,7 +305,9 @@ match maybe_record { } ``` -### All records and links attached to an entry +### All data, actions, and links at an address + +#### Records To get a record and all the updates, deletes, and outbound links associated with its action, as well as its current validation status, call [`hdk::prelude::get_details`](https://docs.rs/hdk/latest/hdk/prelude/fn.get_details.html) with an _action hash_. You'll receive a Result<[holochain_zome_types::metadata::RecordDetails](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/metadata/struct.RecordDetails.html)>. @@ -334,6 +338,8 @@ match maybe_details { } ``` +#### Entries + To get an entry and all the deletes and updates that operated on it (or rather, that operated on the entry creation actions that produced it), _as well as_ all its entry creation actions and its current status on the DHT, pass an _entry hash_ to [`hdk::prelude::get_details`](https://docs.rs/hdk/latest/hdk/prelude/fn.get_details.html). You'll receive a [`holochain_zome_types::metadata::EntryDetails`](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/metadata/struct.EntryDetails.html) struct. ```rust diff --git a/src/pages/build/index.md b/src/pages/build/index.md index 90cdfa88c..97e890f8e 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -12,15 +12,9 @@ This Build Guide organizes everything you need to know about developing Holochai ## Working with data -Shared data in a Holochain application is stored as a graph database of **bases** connected by **links**. A base is identified by a 32-byte identifier such as a hash or public key, and may have data and metadata associated with it. There are four types of bases: - -* An **entry** is an arbitrary blob of bytes that your application code gives meaning to. - * An **agent ID** is a special type of entry that contains the public key of a participant in an application. -* An **action** records the act of manipulating the graph and contains metadata about the act, such as authorship and timestamp. -* An **external reference** is the ID of a resource that exists outside the database, such as the hash of an IPFS resource or the public key of an Ethereum address. - ::: topic-list ### Topics {data-no-toc} +* [Overview](/build/working-with-data/) --- general concepts related to working with data in Holochain * [Entries](/build/entries/) --- creating, reading, updating, and deleting ::: \ No newline at end of file diff --git a/src/pages/build/working-with-data.md b/src/pages/build/working-with-data.md new file mode 100644 index 000000000..58442b28a --- /dev/null +++ b/src/pages/build/working-with-data.md @@ -0,0 +1,163 @@ +--- +title: Working With Data +--- + +::: intro +Holochain is, at its most basic, a framework for building **graph databases** on top of **content-addressed storage** that are validated and stored by **networks of peers**. Each peer contributes to the state of this database by publishing **actions** to an event journal stored on their device called their **source chain**. The source chain can also be used to hold private state. +::: + +## Entries, actions, and records: primary data + +Data in Holochain takes the shape of a [**record**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html). Different kinds of records have different purposes, but the thing common to all records is the [**action**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/enum.Action.html): one participant's intent to manipulate their own state or the application's shared database state in some way. All actions contain: + +* The **agent ID** of the author +* A timestamp +* The type of action +* The hash of the previous action in the author's history of state changes, called their [**source chain**](/concepts/3_source_chain/) (note: the first action in their chain doesn't contain this field, as it's the first) +* The index of the action in the author's source chain, called the **action seq** + +Some actions also contain a **weight**, which is a calculation of the cost of storing the action and can be used for rate limiting. (Note: weighting and rate limiting isn't implemented yet.) + +The other important part of a record is the **entry**. Not all action types have an entry to go along with them, but those that do, `Create` and `Update`, are called **entry creation actions** and are the main source of data in an application. + +It's generally most useful to _think about a **record** (entry plus creation action) as the primary unit of data_. This is because the action holds useful context about when an entry was written and by whom. +One entry written by two actions is considered to be the same piece of content, but when paired with their respective actions into records, each record is guaranteed to be unique. + + +
authors
authors
authors
creates
creates
creates
Alice
Action 1
Bob
Action 2
Carol
Action 3
Entry
+ +Entries and actions are both **addressable content**, which means that they're retrieved by their address --- which is usually the hash of their data. All addresses are 32-byte identifiers. + +There are four types of addressable content: + +* An **entry** is an arbitrary blob of bytes, and its address is the hash of that blob. It has an **entry type**, which your application uses to deserialize it, validate it, and give it meaning. +* An **agent ID** is the public key of a participant in an application. Its address is the same as its content. +* An **action** stores action data for a record, and its address is the hash of the serialized action content. +* An **external reference** is the ID of a resource that exists outside the database, such as the hash of an IPFS resource or the public key of an Ethereum address. There's no content stored at the address; it simply serves as an anchor to attach [links](#links) to. + +### Storage locations + +Addressable content can either be: + +* **Private**, stored on the author's device in their [source chain](#individual-state-histories-as-public-records), or +* **Public**, stored in the application's shared graph database and accessible to all participants. + +All actions are public, while entries can be either public or [private](/build/entries/#configuring-an-entry-type). External references hold neither public nor private content, but merely point to content outside the database. + +## Shared graph database + +Shared data in a Holochain application is represented as a [**graph database**](https://en.wikipedia.org/wiki/Graph_database) of nodes connected by edges called [**links**](/concepts/5_links_anchors/). Any kind of addressable content can be used as a graph node. + +This database is stored in a [distributed hash table (DHT)](/resources/glossary/#distributed-hash-table-dht), which is a key/value store whose data is distributed throughout the network. In a Holochain application, the users themselves become network participants and help keep the DHT alive by storing a portion of the hash table. + +### Links + +A link is a piece of metadata attached to an address, the **base**, and points to another address, the **target**. It has a **link type** that gives it meaning in the application just like an entry type, as well as an optional **tag** that can store arbitrary application data. + + +

type: artist_album

type: artist_album_by_release_date tag: 1966-01-17

type: artist_album

type: artist_album_by_release_date tag: 1970-01-26

Simon & Garfunkel
Sounds of Silence
Bridge over Troubled Water
+ +When a link's base and target don't exist as addressable content in the database, they're considered external references whose data isn't accessible to your application's back end. + + +

type: eth_wallet_to_ipfs_profile_photo

hC8kafe9...7c12
hC8kd01f...84ce
+ +### CRUD metadata graph + +Holochain has a built-in [**create, read, update, and delete (CRUD)** model](/concepts/6_crud_actions/). Data in the graph database and participants' local state cannot be modified or deleted, so these kinds of mutation are simulated by attaching metadata to existing data that marks changes to its status. This builds up a graph of the history of a given piece of content and its links. We'll get deeper into this in the [next section](#adding-and-modifying-data) and in the page on [entries](/build/entries/). + +### Individual state histories as public records + +All data in an application's database ultimately comes from the peers who participate in storing and serving it. Each piece of data originates in a participant's source chain, which is an [event journal](https://martinfowler.com/eaaDev/EventSourcing.html) that contains all the actions they've authored. These actions describe intentions to add to either the DHT's state or their own state. + +Every action becomes part of the shared DHT, but not every entry needs to. The entry content of most system-level actions is private. You can also [mark an application entry type as private](/build/entries/#configuring-an-entry-type), and its content will stay on the participant's device and not get published to the graph. + +Because every action has a reference to both its author and its previous action in the author's source chain, each participant's source chain can be considered a linear graph of their authoring history. + + +
prev_action_hash
prev_action_hash
entry_hash
prev_action_hash
entry_hash
prev_action_hash
prev_action_hash
entry_hash
prev_action_hash
prev_action_hash
entry_hash
prev_action_hash
prev_action_hash
DNA
AgentValidationPkg
Create
AgentID
Create
Entry 1
InitZomesComplete
Create
Entry 2
Delete
Update
Entry 3
CreateLink
DeleteLink
+ +## Adding and modifying data + +Because data can't be deleted, mutation is simulated by adding metadata that describes changes to existing data's state. The current state of an entry or record is calculated using [simple rules](#default-crud-rules), but you can also access the underlying metadata and implement your own CRUD model. + +Every change starts out as an action on someone's source chain. This action is turned into **DHT operations** that get sent to various peers, validated, and integrated into their portions of the database. DHT operations are beyond the scope of this page, so let's focus on the _result_ of integrating these operations. + +!!! info Actions as both content and author history +In addition to the changes described below, every action: + +* is stored _as content at the action's address_. +* is stored _as metadata at the author's agent ID address_, which allows peers responsible for that address to collect and validate a summary of their entire history. +!!! + +* [**`Create`**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Create.html) + * The entry is stored _as content_ at the entry's address, if it doesn't already exist. + * The action is stored _as metadata_ at the entry's address. + +
At action address
At entry address
entry_hash
Create action
Entry as metadata
Entry
Create action as metadata
+* [**`Update`**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Update.html) does the same as `Create`, but also: + * The action is added _as metadata_ to the addresses of the _original_ entry and its entry creation action. These serve as pointers from the original content to their replacements. + +
Update record
Original record
At new entry address
At new action address
At entry address
At action address
hash of action
entry_hash
hash of action
entry_hash
entry_hash
original_action_address
original_entry_address
Update action
New entry as metadata
New entry
Update action as metadata
Original entry creation action
Update action as metadata
Original entry
Update action as metadata
+* [**`Delete`**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Delete.html) + * The action is stored _as metadata_ on the entry and entry creation action that it deletes, indicating their deleted status. The action contains all the information, so there's no entry content. + +
Delete record
Original record
At action address
At entry address
At action address
deletes_address
deletes_entry_address
Delete action
Original entry creation action
Delete action as metadata
Original entry
Delete action as metadata
+* [**`CreateLink`**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.CreateLink.html) + * The action is stored _as metadata_ on the link's _base_ address. The action contains all the link information. + +
CreateLink record
At target address
At base address
At action address
base
target
target
CreateLink action
Target address
Base address
CreateLink as metadata
+* [**`DeleteLink`**](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.DeleteLink.html) + * The action is stored _as metadata_ at the _base_ address of the link it deletes, indicating that the link has been deleted. + * The action is stored _as metadata_ at the _action_ address of the link it deletes as well. + +
DeleteLink record
Original link
At action address
At base address
CreateLink record
At action address
base_address
link_add_address
DeleteLink action
CreateLink action
CreateLink as metadata
+ +_Private entries_ are also manipulated via `Create`, `Update`, and `Delete` actions, but only the _action_ gets published to the graph, as the entry content and action content are separate parts of the record. + +### Default CRUD rules + +The built-in CRUD model is simplistic, collecting all the metadata on an entry or record and producing a final state using these rules: + +* Although an **entry** can have multiple creation actions attached to it as metadata, the record returned contains the _oldest-timestamped_ entry creation action _that doesn't have a corresponding delete action_. +* There's no built-in logic for **updates**, which means that multiple updates can exist on one entry creation action. This creates a branching update model similar to Git and leaves room for you to create your own conflict resolution mechanisms if you need them. Updates aren't retrieved by default; you must retrieve them by [asking for an address' metadata](/build/entries/#all-data-actions-and-links-at-an-address). +* A **delete** applies to an entry creation action, not an entry. An entry is considered live until all its creation actions are deleted, at which point it's fully dead and isn't retrieved when asked for. A dead entry is live once again if a new entry creation action authors it[.](https://youtu.be/d4ftmOI5NnI?si=XeT6OaWg3IN_HYYO&t=42) +* Unlike entries, **links** are completely contained in the action, and are always distinct from each other, even if their base, target, type, and tag are identical. There's no link update action, and a link deletion action marks one link creation action as dead. + +If these rules don't work for you, you can always [directly access the underlying metadata](/build/entries/#all-data-actions-and-links-at-an-address) and implement your own CRUD model. + +## Privacy + +Each **DNA** within a Holochain application has its own network and database, isolated from all other DNAs' networks and their databases. For each participant in a DNA, their source chain is separate from the source chains of all other DNAs they participate in, and the source chains of all other participants in the same DNA. Within a DNA, all shared data can be accessed by any participant, but the only one who can access a participant's private entries is themselves. + +A DNA can be **cloned**, creating a separate network, database, and set of source chains for all participants who join it. This lets you use the same backend code to define private spaces within one application to restrict access to certain shared databases. + +## Summary: multiple interrelated graphs + +The shared DHT and the individual source chains are involved in multiple interrelated graphs --- the source chain contributes to the DHT's graph, and the DHT records source chain history. You can use as little or as much of these graphs as your application needs. + +## Further reading + +* Core Concepts: [Source Chain](/concepts/3_source_chain/) +* Core Concepts: [DHT](/concepts/4_dht/) +* Core Concepts: [Links and Anchors](/concepts/5_links_anchors/) +* Core Concepts: [CRUD actions](/concepts/6_crud_actions/) +* Wikipedia: [Graph database](https://en.wikipedia.org/wiki/Graph_database) +* Wikipedia: [Distributed hash table](https://en.wikipedia.org/wiki/Distributed_hash_table) +* Martin Fowler on [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html), the pattern that both source chains and blockchains use to record state changes. + +## Reference + +* [`holochain_integrity_types::record::Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html) +* [`holochain_integrity_types::action::Action`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Action.html) +* [`holochain_integrity_types::prelude::Create`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Create.html) +* [`holochain_integrity_types::prelude::Update`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Update.html) +* [`holochain_integrity_types::prelude::Delete`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.Delete.html) +* [`holochain_integrity_types::prelude::CreateLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.CreateLink.html) +* [`holochain_integrity_types::prelude::DeleteLink`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/prelude/struct.DeleteLink.html) + +::: topic-list +### In this section {data-no-toc} + +* [Entries](/build/entries/) --- creating, reading, updating, and deleting +::: \ No newline at end of file diff --git a/src/scss/_fonts.scss b/src/scss/_fonts.scss index b05d8c2bf..7991496bd 100644 --- a/src/scss/_fonts.scss +++ b/src/scss/_fonts.scss @@ -1,6 +1,3 @@ -/* Imports */ - -@import url("//hello.myfonts.net/count/3e423c"); @font-face { font-family: "URW Form"; font-weight: 700; diff --git a/src/scss/_mermaid-diagrams.scss b/src/scss/_mermaid-diagrams.scss new file mode 100644 index 000000000..ecaf4c448 --- /dev/null +++ b/src/scss/_mermaid-diagrams.scss @@ -0,0 +1,181 @@ +.mermaid-flowchart { + font-size: 14px; + fill: #333; + margin-left: auto; + margin-right: auto; + + .error-icon { + fill: #552222; + } + + .error-text { + fill: #552222; + stroke: #552222; + } + + .edge-thickness-normal { + stroke-width: 2px; + } + + .edge-thickness-thick { + stroke-width: 3.5px; + } + + .edge-pattern-solid { + stroke-dasharray: 0; + } + + .edge-pattern-dashed { + stroke-dasharray: 3; + } + + .edge-pattern-dotted { + stroke-dasharray: 2; + } + + .marker { + fill: #333333; + stroke: #333333; + } + + .marker.cross { + stroke: #333333; + } + + svg { + font-size: 16px; + } + + .label { + color: #333; + } + + .cluster-label text { + fill: #333; + } + + .cluster-label span, p { + color: #333; + } + + .label text, span, p { + fill: #333; + color: #333; + } + + .node { + rect, circle, ellipse, polygon, path { + fill: #ececff; + stroke: #9370db; + stroke-width: 1px; + } + + .katex path { + fill: #000; + stroke: #000; + stroke-width: 1px; + } + + .label { + text-align: center; + } + + &.clickable { + cursor: pointer; + } + } + + .flowchart-label text { + text-anchor: middle; + } + + .arrowheadPath { + fill: #333333; + } + + .edgePath .path { + stroke: #333333; + stroke-width: 2px; + } + + .flowchart-link { + stroke: #333333; + fill: none; + } + + .edgeLabel { + background-color: #e8e8e8; + text-align: center; + + rect { + opacity: 0.5; + background-color: #e8e8e8; + fill: #e8e8e8; + } + } + + .labelBkg { + background-color: rgba(232, 232, 232, 0.85); + } + + .cluster { + rect { + fill: #ffffde; + stroke: #aaaa33; + stroke-width: 1px; + } + + text { + fill: #333; + } + } + + .cluster span, p { + color: #333; + } + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-size: 12px; + background: hsl(80, 100%, 96.2745098039%); + border: 1px solid #aaaa33; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: #333; + } + + .nodeLabel * { + margin: 0; + } + + /* External references, used on the working with data page and others */ + .external > * { + stroke-dasharray: 3 3 !important; + fill: transparent !important; + + span { + stroke-dasharray: 3 3 !important; + fill: transparent !important; + } + } + + /* Original actions, for diagrams showing the relationships of CRUD actions */ + .original > * { + fill: #F2F2F2 !important; + stroke: #AAA !important; + + span { + fill: #F2F2F2 !important; + stroke: #AAA !important; + } + } +} \ No newline at end of file diff --git a/src/scss/site.scss b/src/scss/site.scss index 795f68796..8ca599f27 100644 --- a/src/scss/site.scss +++ b/src/scss/site.scss @@ -2,6 +2,7 @@ @import 'fonts'; @import 'svg-icons'; @import 'base-element-styles'; +@import 'mermaid-diagrams'; // @import 'debug';