From 7643f79ad13a1d8988be39437752fad464a6f761 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 8 Jan 2024 15:41:14 -0700 Subject: [PATCH 01/69] chore: linebreak --- src/pages/build/1_hello_world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/1_hello_world.md b/src/pages/build/1_hello_world.md index 6664c9a87..328a3b9d8 100644 --- a/src/pages/build/1_hello_world.md +++ b/src/pages/build/1_hello_world.md @@ -2,4 +2,4 @@ title: "BUILD PAGE PLACEHOLDER" --- - \ No newline at end of file + From 4ffc260066f02f0aa44e41bee4106c84b2647bdb Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Wed, 20 Dec 2023 07:37:43 -0700 Subject: [PATCH 02/69] refactor: split out hello world tutorial, forum tutorial, and deployment into seperate pages in 'Build' section --- src/pages/_data/navigation/mainNav.json5 | 2 + src/pages/build/2_forum_tutorial.md | 1165 +++++++++++++++++++ src/pages/build/3_deployment.md | 92 ++ src/pages/get-started/index.md | 1315 ---------------------- 4 files changed, 1259 insertions(+), 1315 deletions(-) create mode 100644 src/pages/build/2_forum_tutorial.md create mode 100644 src/pages/build/3_deployment.md diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index df4b9525b..d33855b04 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -22,6 +22,8 @@ ] }, { title: "Build", url: "/build/1_hello_world/", children: [ + { title: "Forum App Tutorial", url: "/build/2_forum_tutorial/" }, + { title: "Packaging & Distribution", url: "/build/3_deployment/" }, ] }, { title: "References", url: "/references/", children: [ diff --git a/src/pages/build/2_forum_tutorial.md b/src/pages/build/2_forum_tutorial.md new file mode 100644 index 000000000..0b3b05cd1 --- /dev/null +++ b/src/pages/build/2_forum_tutorial.md @@ -0,0 +1,1165 @@ +--- +title: "Zero to built: creating a forum app" +tocData: + - text: 1. Scaffolding a hApp + href: 1-scaffolding-a-happ + - text: 2. Select user interface framework + href: 2-select-user-interface-framework + - text: 3. Set up Holonix development environment + href: 3-set-up-holonix-development-environment + - text: 4. Scaffold a DNA + href: 4-scaffold-a-dna + - text: 5. Scaffold a zome + href: 5-scaffold-a-zome + - text: 6. Scaffold entry types + href: 6-scaffold-entry-types + - text: 7. Scaffold a collection + href: 7-scaffold-a-collection + - text: 8. Run your applicaiton in dev mode + href: 8-run-your-application-in-dev-mode + - text: 9. Integrate the generated UI elements + href: 9-integrate-the-generated-ui-elements +--- + +First, navigate back to the folder where you want to keep your Holochain applications. If you followed our suggestion, you can get back to it by typing: + +```shell +cd ~/Holochain +``` + +Next up, we'll walk you through creating a forum application from scratch using Holochain's scaffolding tool, step-by-step. This forum application will enable participants to share text-based posts and to comment on those posts. + +Each post will have a title and content, and authors will be able to edit --- or update --- their posts. However, they won't be able to delete them. + +Each comment will be a reply to a particular post, will be limited in length to 140 characters, and will be able to be deleted but not updated. + +!!! info Validation tutorial coming soon +A future update to this guide will implement the above constraints as validation rules. For now, we'll just scaffold enough code to get a working UI. **Check back soon** to get the whole tutorial! +!!! + +We'll create a couple of other things along the way that will enable people to find these posts and comments, but we'll cover those things when we get there. + +The good news is that the Holochain scaffolding tool will do a lot of the heavy lifting in terms of generating folders, files, and boilerplate code. It will walk you through each step in the hApp generation process. In fact, the scaffolding tool does so much of the work for you that many people have commented that 90% or more of the time spent writing a Holochain app is focused on building out the front-end user interface and experience. + +First, let's use the scaffolding tool to generate the basic folders and files for our hApp. + +### 1. Scaffolding a hApp {#1-scaffolding-a-happ} + +To start, run the following command in your terminal: + +```shell +nix run github:/holochain/holochain#hc-scaffold -- web-app +``` + +You should then see: + +::: output-block +```text +? App name (no whitespaces): +``` +::: + +Enter the name of your forum application using snake_case. Enter: + +```text +my_forum_app +``` + +### 2. Select user interface framework + +You'll then be prompted to choose a user interface (UI) framework for your front end. + +For this example, use the arrow keys to choose `Svelte` and press Enter. + +### 3. Set up Holonix development environment + +Next, you'll be asked if you want to set up the Holonix development environment for the project. This allows you to enter a shell that has all the right tools and libraries for the version of Holochain that your code was generated for. + +Choose `Yes (recommended)` and press Enter. + +You should see: + +::: output-block +```text +Setting up nix development environment... +``` +::: + +along with some details of what is being added. Follow the instructions to set up the development environment for your hApp and continue to scaffold more of its elements. + +First, enter the hApp project folder: + +```shell +cd my_forum_app +``` + +Just to get an overview of what your first scaffold command set up for you, you can check the contents of that `my_forum_app` folder by typing: + +```shell +ls +``` + +It should look like it has set up a similar set of folders and configuration files to those you saw in the "Hello, World!" hApp. + +Now, fire up the nix development shell, which makes all scaffolding tools and the Holochain binaries directly available from the command line, by entering: + +```shell +nix develop +``` + +After a short while of installing packages, you should see: + +::: output-block +```text +Holochain development shell spawned. Type exit to leave. +``` +::: + +As it says, if you want to leave the nix development shell at any time, you can type `exit`. This will take you back to your familiar shell without any of the special Holochain dependencies. When you want to re-enter it, navigate to the `my_forum_app` folder and type `nix develop` again. But for now, install the Node Package Manager (npm) dependencies with: + +```shell +npm install +``` + +These dependencies are used by various tools and assets --- the scaffolded tests, the UI, and various development activities like spawning apps for testing. + +When that finishes, you should see some text that ends with something like: + +::: output-block +```text +added 371 packages, and audited 374 packages in 1m + +37 packages are looking for funding + run 'npm fund' for details +found 0 vulnerabilities +``` +::: + +If you see something like that, you've successfully downloaded the NPM dependencies for the UI and for building your app. + +Next up, you're going to start creating the foundational building block of any Holochain app: its DNA. + +!!! dig-deeper Scaffolding subcommands + +To get an overview of the subcommands that `hc scaffold`` makes available to you, type: + +```shell +hc scaffold --help +``` + +You should see something like: + +::: output-block +```text +holochain_scaffolding_cli 0.1.8 +The list of subcommands for `hc scaffold` + +USAGE: + hc-scaffold + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + collection Scaffold a collection of entries in an existing zome + dna Scaffold a DNA into an existing app + entry-type Scaffold an entry type and CRUD functions into an existing zome + example + help Prints this message or the help of the given subcommand(s) + link-type Scaffold a link type and its appropriate zome functions into an existing zome + template Set up the template used in this project + web-app Scaffold a new, empty web app + zome Scaffold one or multiple zomes into an existing DNA +``` +::: + +You can get help on every one of these subcommands and its parameters by typing `hc scaffold --help`. +!!! + +!!! info Backing out of a mistake +A quick note: if while scaffolding some part of your hApp, you realize you've made a mistake (a typo or wrong selection for instance), as long as you haven't finished scaffolding that portion, **you can stop the current step** by using Ctrl+C on Linux or Command+C on macOS. +!!! + +### Scaffold a DNA + +A DNA folder is where you will put the code that defines the rules of your application. You're going to stay in the `my_forum_app/` root folder and, with some simple commands, the scaffolding tool will do much of the creation of relevant folders and files for you. + +!!! dig-deeper DNAs: Context and Background {#about-dnas} + +#### Why do we use the term DNA? + +In Holochain, we are trying to enable people to **choose to participate in coherent social coordination**, or interact meaningfully with each other online without needing a central authority to define the rules and keep everyone safe. To do that, we are borrowing some patterns from how biological organisms are able to coordinate coherently even at scales that social organisations such as companies or nations have come nowhere close to. In living creatures like humans, dolphins, redwood trees, and coral reefs, many of the cells in the body of an organism (trillions of the cells in a human body, for instance) are each running a (roughly) identical copy of a rule set in the form of DNA. + +This enables many different independent parts (cells) to build relatively consistent superstructures (a body, for instance), move resources, identify and eliminate infections, and more --- all without centralized command and control. There is no "CEO" cell in the body telling everybody else what to do. It's a bunch of independent actors (cells) playing by a consistent set of rules (the DNA) coordinating in effective and resilient ways. + +A cell in the muscle of your bicep finds itself in a particular context, with certain resources and conditions that it is facing. Based on those signals, that cell behaves in particular ways, running relevant portions of the larger shared instruction set (DNA) and transforming resources in ways that make sense for a bicep muscle cell. A different cell in your blood, perhaps facing a context where there is a bacterial infection, will face a different set of circumstances and consequently will make use of other parts of the shared instruction set to guide how it behaves. In other words, many biological organisms make use of this pattern where **many participants run the same rule set, but each in its own context**, and that unlocks a powerful capacity for coherent coordination. + +Holochain borrows this pattern that we see in biological coordination to try to enable similarly coherent social coordination. However, our focus is on enabling such coherent social coordination to be "opted into" by the participants. We believe that this pattern of being able to choose which games you want to play --- and being able to leave them or adapt them as experience dictates --- is critical to enabling individual and collective adaptive capacity. We believe that it may enable a fundamental shift in the ability of individuals and communities to sense and respond to the situations that they face. + +To put it another way: if a group of us can all agree to the rules of a game, then together we can play that game. + +All of us opting in to those rules --- and helping to enforce them --- enables us to play that game together, whether it is a game of chess, chat, a forum app, or something much richer. + +#### DNA as boundary of network + +The network of participants that are running a DNA engage in "peer witnessing" of actions by the participants in that network. A (deterministically random) set of peers are responsible for validating, storing, and serving each particular piece of shared content. In other words, the users of a particular hApp agree to a set of rules and then work together collectively to enforce those rules and to store and serve content (state changes) that do not violate those rules. + +Every hApp needs to include at least one DNA. Moreover, as indicated above, **it is at the DNA level** (note: not the higher application level) **where participants will form a network of peers to validate, store, and serve content** in accordance with the rules defined in that DNA. This happens in the background as the application runs on each participant's machine. + +There are some powerful consequences to this architectural choice --- including freedom to have your application look and feel the way you want, or to combine multiple DNAs together in ways that work for you without having to get everyone else to agree to do the same --- but we'll save those details for later. + +#### So if we have multiple DNAs in our hApp... + +...then we are participating in multiple networks, with each network of peers that are participating in a particular DNA also helping maintain the shared database for each DNA, enforcing the DNA's rules while validating, storing, and serving content. Each network acts as a 'social organism' in cooperation with other networks in the hApp. + +This is similar to the way in which multiple DNA communities coexist in biological organisms. In fact, there are more cells in a human body that contain other DNA (like bacteria and other microorganisms) than cells that contain our DNA. This indicates that we are an _ecology_ of coherent communities that are interacting with --- and evolving alongside --- one another. + +When it comes to hApps, this lets us play coherent games with one another at the DNA level, while also participating in adjacent coherent games with others as well. That means that applications are not one-size-fits-all. You can choose to combine different bits of functionality in interesting and novel ways. + +!!! + +It's time to scaffold a new DNA by entering: + +```shell +hc scaffold dna +``` + +You should then see: + +::: output-block +```text +? DNA name (snake_case): +``` +::: + +Enter a name for the DNA: + +```text +forum +``` + +You should then see: + +::: output-block +```text +DNA "forum" scaffolded! +Add new zomes to your DNA with: + hc scaffold zome +``` +::: + +Success! Inside of your `dnas/` folder, the scaffolding tool generated a `forum/` folder and, inside of that, the folders and files that the DNA needs. At this point you have a skeleton structure for your `forum` DNA. As you take the following steps, the scaffolding tool will make additions and edits to some of those folders and files based on your instructions. + +### 5. Scaffold a zome + +DNAs are comprised of code modules, which we call zomes (short for chromosomes). Zomes are modules that typically focus on enabling some small unit of functionality. Building with this sort of modular pattern provides a number of advantages, including the ability to reuse a module in more than one DNA to provide similar functionality in a different context. For instance, the [profiles zome](https://github.com/holochain-open-dev/profiles) is one that many apps make use of. For the forum DNA, you'll be creating two zomes: `posts` and `posts_integrity`. + +Start by entering: + +```shell +hc scaffold zome +``` + +You should then see: + +::: output-block +```text +? What do you want to scaffold? › +❯ Integrity/coordinator zome-pair (recommended) + Only an integrity zome + Only a coordinator zome +``` +::: + +!!! dig-deeper Integrity zomes and coordinator zomes + +#### Integrity zomes + +An integrity zome, as the name suggests, is responsible for maintaining the data integrity of a Holochain application. It sets the rules and ensures that any data writes occurring within the application are consistent with those rules. In other words, it is responsible for ensuring that data is correct, complete, and trustworthy. Integrity zomes help maintain a secure and reliable distributed peer-to-peer network by enforcing the validation rules defined by the application developer --- in this case, you! + +#### Coordinator zomes + +On the other hand, a coordinator zome contains the code that actually commits data, retrieves it, or sends and receives messages between peers or between other portions of the application on a user's own device (between the back end and the front-end UI, for instance). A coordinator zome is where you define the API for your DNA, through which the network of peers and their data is made accessible to the user. + +#### Multiple zomes per DNA + +As you learned earlier, a DNA can have multiple integrity and coordinator zomes. Each integrity zome contributes to the full set of different types of valid data that can be written, while each coordinator zome contributes to the DNA's functionality that you expose through its API. In order to write data of a certain type, a coordinator zome needs to specify a dependency on the integrity zome that defines that data type. A coordinator zome can also depend on multiple integrity zomes. + +#### Why two types? + +They are separated from one another so we can update coordinator zomes without having to update the integrity zomes. This is important, because changes made to an integrity zome result in a change of the rule set, which results in an entirely new network. This is because the integrity code is what defines the 'rules of the game' for a group of participants. If you changed the code of an integrity zome, you would find yourself suddenly in a new and different network from the other folks who haven't yet changed their integrity zome --- and we want to minimize those sorts of forks to situations where they are needed (like when a community decides they want to play by different rules, for instance changing the maximum length of comments from 140 characters to 280 characters). + +At the same time, a community will want to be able to improve the ways in which things are done in a Holochain app. This can take the form of adding new features or fixing bugs, and we also want people to also be able to take advantage of the latest features in Holochain. Separating integrity and coordination enables them to do that more easily, because: + +* Holochain's coordinator zome API receives frequent updates while the integrity zome API is fairly stable, and +* coordinator zomes can be added to or removed from a DNA at runtime without affecting the DNA's hash. + +!!! + +For this app, you're going to want both an integrity zome and a coordinator zome, so use the arrow keys to select: + +::: output-block +```text +Integrity/coordinator zome-pair +``` +::: + +and press Enter. + +You should then see: + +::: output-block +```text +? Enter coordinator zome name (snake_case): + (The integrity zome will automatically be named '{name of coordinator zome}_integrity') +``` +::: + +Enter the name: + +```text +posts +``` + +and press Enter. + +You should then see prompts asking if you want to scaffold the integrity and coordinator zomes in their respective default folders. + +Press Y for both prompts. + +As that runs (which will take a moment as the scaffold makes changes to various files) you should then see something like: + +::: output-block +```text +Coordinator zome "posts" scaffolded! +Updating crates.io index + Fetch [===> ] ... +``` +::: + (then after download is done...) +::: output-block +```text + Downloaded 244 crates (46.7 MB) in 4.27s (largest was `windows` at 11.9 MB) + +Add new entry definitions to your zome with: + hc scaffold entry-type +``` +::: + +Once that is all done, your hApp skeleton will have filled out a bit. Before you scaffold the next piece, it might be good to get a little context for how content is "spoken into being" when a participant publishes a post in a forum hApp. Read the following section to learn more. + +!!! dig-deeper Source chains, actions, and entries + +#### Source chain + +Any time a participant in a hApp takes some action that changes data, they add a record to a journal called a **source chain**. Each participant has their own source chain, a local, tamper-proof, and chronological store of the participant's actions in that application. + +This is one of the main differences between Holochain and other systems such as blockchains or centralized server-based applications. Instead of recording a "global" (community-wide) record of what actions have taken place, in Holochain actions are taken by agents and are thought of as transformations of their own state. + +One big advantage of this approach is that a single agent can be considered authoritative about the order in which they took actions. From their perspective, first they did A, then B, then C, etc. The fact that someone else didn't get an update about these changes, and possibly received them in a different order, doesn't matter. The order that the authoring agent took those actions will be captured in the actions themselves (thanks to each action referencing the previous one that they had taken, thus creating an ordered sequence --- or chain --- of actions). + +#### Actions and entries + +You'll notice that we used the word "action" a lot. In fact, **we call the content of a source chain record an action**. In Holochain applications, data is always "spoken into being" by an agent (a participant). Each record captures their act of adding, modifying, or removing data, rather than simply capturing the data itself. + +There are a few different kinds of actions, but the most common one is `Create`, which creates an 'entry' --- an arbitrary blob of bytes. Entries store most of the actual content created by a participant, such as the text of a post in our forum hApp. When someone creates a forum post, they're recording an action to their source chain that reads something like: _I am creating this forum post entry with the title "Intros" and the content "Where are you from and what is something you love about where you live?" and I would like my peers in the network to publicly store a record of this act._ So while an action is useful for storing noun-like data like messages and images, it's actually a verb, a record of an action that someone took to update their own state and possibly the shared database state as well. That also makes it well-suited to verb-like data like real-time document edits, game moves, and transactions. + +Every action contains the ID of its author (actually a cryptographic public key), a timestamp, a pointer to the previous source chain record, and a pointer to the entry data, if there is any. In this way, actions provide historical context and provenance for the entries they operate on. + +The pointer to the previous source chain record creates an unbroken history from the current record all the way back to the source chain's starting point. This 'genesis' record contains the hash of the DNA, which servs as both the identifier for the specific set of validation rules that all following records should follow and the ID of the network that this source chain's actions are participating in. + +An action is cryptographically signed by its author and is immutable (can't be changed or erased from either the source chain or the network's data store) once written. This, along with the validation rules specified by the DNA hash in the genesis record, are examples of a concept we call "intrinsic data integrity", in which data carries enough information about itself to be self-validating. + +Just as with a centralized application, we aren't just going to add this data into some database without checking it first. When a participant tries to write an action, Holochain first: + +1. ensures that the action being taken doesn't violate the validation rules of the DNA, +2. adds it as the next record to the source chain, and then +3. tells the participant's network peers about it so they can validate and store it, if it's meant to be public. + +The bits of shared information that all the peers in a network are holding are collectively called a distributed hash table, or DHT. We'll explain more about the DHT later. + +If you want to learn more, check out [The Source Chain: A Personal Data Journal](/concepts/3_source_chain/) and [The DHT: A Shared, Distributed Graph Database](/concepts/4_dht/). You'll also get to see it all in action in a later step, when you run your hApp for the first time. + +!!! + +Now it's time to start defining the structure and validation rules for data within your application. + +### 6. Scaffold entry types + +An entry type is a fundamental building block used to define the structure and validation rules for data within a distributed application. Each entry type corresponds to a specific kind of data that can be stored, shared, and validated within the application. + +!!! dig-deeper Entry types and validation + +An entry type is just a label, an identifier for a certain type of data that your DNA deals with. It serves as something to attach validation rules to in your integrity zome, and those rules are what give an entry type its meaning. They take the form of code in a function that gets called any time something is about to be stored, and because they're just code, they can validate all sorts of things. Here are a few key examples: + +* **Data structure**: When you use the scaffolding tool to create an entry type, it generates a Rust-based data type that define fields in your entry type, and it also generates code in the validation function that attempts to convert the raw bytes into an instance of that type. By providing a well-defined structure, this type ensures that data can be understood by the application. If it can't be deserialized into the appropriate Rust structure, it's not valid. + +* **Constraints on data**: Beyond simple fields, validation code can constrain the values in an entry --- for instance, it can enforce a maximum number of characters in a text field or reject nonsensical calendar dates. + +* **Privileges**: Because it originates in a source chain, an entry comes with metadata about its author. This can be used to control who can create, edit, or delete an entry. + +* **Contextual conditions**: Because an action is part of a chain of actions, it can be validated based on the agent's history --- for instance, to prevent currency transactions beyond a credit limit or disallow more than two comments per minute to discourage spam. An entry can also point to other entries in the DHT upon which it depends, and the data from those entries can be used in its validation. + +!!! + +Your bare-bones forum needs two entry types: `post` and `comment`. You'll define these in the `posts` integrity zome you just created in the previous step. The `post` entry type will define a `title` field and a `content` field. The `comment` entry type will define a `comment_content` field and a way of indicating which post the comment is about. + +To do this, just follow the instructions that the scaffold suggested for adding new entry definitions to your zome. + +Start with the `post` entry type by entering this command: + +```shell +hc scaffold entry-type +``` + +You should then see: + +::: output-block +```text +✔ Entry type name (snake_case): +``` +::: + +Enter the name: + +```text +post +``` + +You should then see: + +::: output-block +```text +Which fields should the entry contain? + +? Choose field type: › +❯ String + bool + u32 + i32 + f32 + Timestamp + ActionHash + EntryHash + DnaHash + AgentPubKey + Enum + Option of... + Vector of... +``` +::: + +The scaffolding tool is now prompting you to add fields to the `post` entry type. + +Fields are the individual components or attributes within an entry type that define the structure of the data. They determine the specific pieces of information to be stored in an entry and their respective data types. The scaffolding tool supports a collection of native Rust types such as booleans, numbers, enums (a choice between several predetermined values), optional values, and vectors (lists of items of the same type), along with Holochain-specific types that refer to other pieces of data on the DHT. + +For your `post` entry type, you're going to add `title` and `content` fields. Select `String` as the first field's type, and enter: + +```text +title +``` + +as the field name. + +Press Y for the field to be visible in the UI, and use the arrow keys to select `TextField` as the widget to render this field. (A `TextField` is a single-line input field designed for capturing shorter pieces of text.) + +When you see: + +::: output-block +```text +?Add another field to the entry?(y/n) +``` +::: + +press Y. + +Select `String` for this field's type too. Then enter + +```text +content +``` + +as the field name. + +Press Y for the field to be visible in the UI, and select `TextArea` as the widget to render the field. (A `TextArea` is a multi-line input field that allows users to enter larger blocks of text. That'll work better for blog posts.) + +After adding the `title` and `content` fields, press N when asked if you want to add another field. Next, you should see: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? + Update +✔ Delete +``` +::: + +The scaffolding tool can add zome and UI functions for updating and deleting entries of this type. In this case, we want authors to be able to update posts, but not delete them, so use the arrow keys and the spacebar to ensure that `Update` has a check and `Delete` does not. It should look like this: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? +✔ Update + Delete +``` +::: + +Then press Enter. + +At this point you should see: + +::: output-block +```text +? Should a link from the original entry be created when this entry is updated? › +❯ Yes (more storage cost but better read performance, recommended) + No (less storage cost but worse read performance) +``` +::: + +Select `Yes` by pressing Enter. + +!!! dig-deeper CRUD (create, read, update, delete) {#crud-create-read-update-delete} + +#### Mutating immutable data and improving performance + +In short, the above choice is about how changes get dealt with when a piece of content is updated. + +Because all data in a Holochain application is immutable once it's written, we don't just go changing existing content, because that would break the integrity of the agent's source chain as well as the data already in the DHT. So instead we add metadata to the original data, indicating that people should now look elsewhere for the data or consider it deleted. This is produced by `Update` and `Delete` source chain actions. + +For an `Update` action, the original `Create` or `Update` action and its entry content on the DHT get a `ReplacedBy` pointer to the new `Update` action and its entry content. + +When the scaffolding tool asks you whether to create a link from the original entry, though it's not talking about this pointer. Instead, it's talking about an extra piece of metadata that points to the _very newest_ entry in a chain of updates. If an entry were to get updated, and that update were updated, and this were repeated three more times, anyone trying to retrieve the entry would have to query the DHT six times before they finally found the newest revision. This extra link, which is not a built-in feature, 'jumps' them past the entire chain of updates at the cost of a bit of extra storage. The scaffolding tool will generate all the extra code needed to write and read this metadata in its update and read functions. + +For a `Delete` action, the original action and its entry content are simply marked as deleted. In the cases of both updating and deleting, all original data is still accessible if the application needs it. + +#### Resolving conflicts + +Multiple participants can mark a single entry as updated or deleted at the same time. This might be surprising, but Holochain does this for two good reasons. First, it's surprisingly difficult to decide which is the 'correct' version of a piece of data in a distributed system, because contributions may come from any peer at any time, even appearing unexpectedly long after they've been created. There are many strategies for resolving the conflicts that arise from this, which brings us to the second good reason: we don't want to impose a specific conflict resolution strategy on you. Your application may not even consider parallel updates and deletes on a single entry to be a conflict at all. + +#### CRUD functions + +**By default, the scaffolding tool generates a `create_' function in your coordinator zome for an entry type** because creating new data is a fundamental part of any application, and it reflects the core principle of Holochain's agent-centric approach --- the ability to make changes to your own application's state. + +Similarly, when a public entry is published, it becomes accessible to other agents in the network. Public entries are meant to be shared and discovered by others, so **a `read_' function is provided by default** to ensure that agents can easily access and retrieve publicly shared entries. (The content of _private_ entries, however, are not shared to the network.) For more info on entries, see: the **Core Concepts sections on [Source Chains](/concepts/3_source_chain/) and [DHT](/concepts/4_dht/)**. + +Developers decide whether to let the scaffolding tool generate `update_` and `delete_` functions based on their specific application requirements. More details in the Core Concepts section on [CRUD](/concepts/6_crud_actions/). + +!!! + +Next, you should see: + +::: output-block +```text +Entry type "post" scaffolded! + +Add new collections for that entry type with: + + hc scaffold collection +``` +::: + +We'll dive into collections in a moment, but first create the `comment` entry type. + +Again type: + +```shell +hc scaffold entry-type +``` + +This time enter the name: + +```text +comment +``` + +for the entry type name. + +You're going to add a `comment_content` field, so select the `String` field type and enter: + +```text +comment_content +``` + +Then select the `TextArea` widget and press Enter. (Again, a `TextArea` is a multi-line input field that allows users to enter larger blocks of text. Perfect for a comment on a post.) + +Press Y to add another field. + +For this next field you'll want to create a field that will help you associate each particular comment to the post that it is commenting on. To do this, the next field in the `comment` entry type will store a reference to a `post`. + +Use the arrow keys to select `ActionHash` as the field type. + +!!! dig-deeper Hashes and other identifiers + +There are two kinds of unique identifiers or 'addresses' in Holochain: **hashes** for data and **public keys** for agents. + +A hash is a unique "digital fingerprint" for a piece of data, generated by running it through a mathematical function called a **hash function**. None of the original data is present in the hash, but even so, the hash is extremely unlikely to be identical to the hash of any other piece of data. If you change even one character of the entry's content, the hash will be radically (and unpredictably) different. + +Holochain uses a hash function called blake2b. You can play with [an online blake2b hash generator](https://toolkitbay.com/tkb/tool/BLAKE2b_512) to see how changing content a tiny bit alters the hash. Try hashing `hi` and then `Hi` and compare their hashes. + +To ensure data integrity and facilitate efficient data retrieval, each piece of data is identified by its hash. This serves the following purposes: + +* **Uniqueness:** The cryptographic hashing function ensures that the data has a unique hash value, which helps to differentiate it from other data on the network. +* **Efficient lookup:** The hash is used as a key (essentially an address) in the network's storage system, the distributed hash table (DHT). When an agent wants to retrieve data, they simply search for it by hash, without needing to know what peer machine it's stored on. In the background, Holochain reaches out simultaneously to multiple peers who are responsible for the hash based on an algorithm that matches peers to data based on the similarity of the hash to their agent IDs. This makes data lookup fast and resilient to unreliable peers or network conditions. +* **Fair distribution:** Because the participants in a network are responsible for validating and storing each other's public data based on its hash, the randomness of the hashing function ensures that that responsibility is spread fairly evenly among everyone. +* **Integrity verification:** `Hi` will always generate the same hash no matter who runs it through the hashing function. So when data is retrieved by hash, its hash can be recalculated and compared with the original requested hash to ensure that a third party hasn't tampered with the data. +* **Collusion resistance:** The network peers who take responsibility for validating and storing an entry are chosen randomly based on the similarity of their agent IDs to the `EntryHash`. It would take a huge amount of computing power to generate a hash that would fall under the responsibility of a colluding peer. And because Holochain can retrieve data from multiple peers, it's more likely that the requestor can find one honest peer to report problems with a piece of bad data. + +#### `ActionHash` + +An action is identified by its `ActionHash`. Because an action contains information about its author, the time it was written, the action that preceded it, and the entry it operates on, no two action hashes will be the same --- even for the same entry. This helps to disambiguate identical entries written at different times by different agents. + +#### `EntryHash` + +An entry is identified by its `EntryHash`, which can be retrieved from the `ActionHash` of the action that wrote it. Because they're two separate pieces of data, an entry is stored by different peers than the action that operates on it. + +#### `AgentPubKey` + +**Each agent in a network is identified by their cryptographic public key**, a unique number that's mathematically related to a private number that they hold on their machine. Public-key cryptography is a little complex for this guide --- it's enough to know that a participant's private key signs their source chain actions, and those signatures paired with their public key allow others to verify that they are the one who authored those actions. + +An `AgentPubKey` isn't a hash, but it's the same length, and it's unique just like a hash. So it can be used as a way of referring to an agent, like a user ID --- and this is also why it's used to choose the right peers in the DHT storage and retrieval algorithm. + +#### Summary + +Whereas `EntryHash` is used to uniquely identify, store, and efficiently retrieve an entry from the DHT, `ActionHash` is used to uniquely identify, store, and retrieve the action (metadata) that operated on it, which can provide information about the history and context of any associated entry (including what action preceded it). `ActionHash`es are also what enable any participant to retrieve and reconstruct the continuous sequence of actions (and any associated entries) in another agent's source chain. + +**Use `EntryHash` when** you want to link to or retrieve the actual content or data (e.g., when linking to a category in a forum application). + +**Use `ActionHash` when** you want to link to or retrieve the authorship or history of an entry (e.g., when distinguishing between two posts with identical content). + +**Use `AgentPubKey` when** you want to link to an agent (such as associating a profile or icon with them) or retrieve information about their history (such as scanning their source chain for posts and comments). + +You can check out the Core Concepts to dive a bit deeper into [how the distributed hash table helps](/concepts/4_dht/) to not only make these entries and actions available but helps to ensure that agents haven't gone back to try and change their own histories after the fact. But for now, let's dive into links. + +!!! + +After press Enter, you should see: + +::: output-block +```text +? Should a link from this field be created when this entry is created? (y/n) › +``` +::: + +Press Y to accept creating a link. + +Next you will see: + +::: output-block +```text +✔ Which entry type is this field referring to? +``` +::: + +Press Enter to accept the suggested entry type `Post`. + +Next, you will be asked to pick a field name. You can press Enter to accept the field name suggestion, which should be: + +```text +post_hash +``` + +Press N to decline adding another field to the entry. + +Then use the arrow keys to deselect Update, but leave Delete selected. It should look as follows: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? + Update +✔ Delete +``` +::: + +Once that is done, press Enter to generate a delete function for the **`comment`** entry type. + +You should then see: + +::: output-block +```text +Entry type "comment" scaffolded! + +Add new collections for that entry type with: + + hc scaffold collection +``` +::: + +The scaffolding will now have both added the `comment` entry type, and added a bunch more very useful code to our app using the native Holochain affordance of links. Links allow us to create paths that agents can follow to find associated content. So, the scaffolding not only added a reference to the post in the comment's entry, but it also added code such that when a comment is added, a link from the post back to the comment will also be created. If you want to see some of that code, take a look at the `dnas/forum/zomes/integrity/posts/src/lib.rs` file and you should see right near the top that a function has been created for validating the creation of a `post_to_comments` link. Similarly, other validation functions related to the deletion of those links follow after. + +!!! dig-deeper How links are stored and retrieved in a Holochain app + +What exactly is a link? Where is it stored? How does it work? And what do they let us do that we couldn't do otherwise? + +Links enable us to build a graph of references from one piece of content to other pieces of content in a DHT and then to navigate that graph. This is important because without some sort of trail to follow, it is infeasible to just "search for all content" thanks to the address space (all possible hashes) being so large and spread out across machines that iterating through tme all could take millions of years. + +By linking from known things to unknown things, we enable the efficient discovery and retrieval of related content in our hApp. + +**Storage**: When an agent creates a link between two entries, a `CreateLink` action is written to their source chain. A link is so small that there's no entry for the action. It simply contains the address of the base, the address of the target, the link type (which describes the relationship), and an optional tag which contains a small amount of application-specific information. The base and target can be any sort of DHT address --- an `EntryHash`, an `ActionHash`, or an `AgentPubKey`. But there doesn't actually need to be any data at that base, which is useful for referencing data that exists in another hash-based data store outside the DHT. + +After storing the action in the local source chain, the agent then publishes the link to the DHT, where it goes to the peers who are responsible for storing the base address and gets attached to the address as metadata. + +**Lookup**: To look up and retrieve links in a Holochain app, agents can perform a `get_links` query on a base DHT address. This operation involves asking the DHT peers responsible for that address for any link metadata of a given link type attached to it, with an optional "starts-with" query on the link tag. The peers return a list of links matching the query, which contain the addresses of the targets, and the link types and tags. The agent can then retrieve the actual target data by performing a [`get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) query on the target address, which may be an `EntryHash`, `ActionHash`, or `AgentPubKey` (or an empty result, in the case of data that doesn't exist on the DHT). + +For more information and examples, read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/). + +!!! + +### 7. Scaffold a collection + +Now, let's create a collection that can be used to retrieve all the posts. A collection creates a link type for referring to the collected entry type (similarly to how a link type was created for linking from posts to comments), but collections also create an 'anchor' --- a small string --- as the base for the link so we can find all the items in the collection by starting from the anchor's known hash. + +To create a collection, type: + +```shell +hc scaffold collection +``` + +You should then see: + +::: output-block +```text +Collection name (snake_case, eg. "all_posts"): › +``` +::: + +Enter: + +```text +all_posts +``` + +and press Enter. You should then see: + +::: output-block +```text +? Which type of collection should be scaffolded? › +❯ Global (get all entries of the selected entry types) + By author (get entries of the selected entry types that a given author has created) +``` +::: + +Select **`Global`** and press Enter. You should then see: + +::: output-block +```text +? Which entry type should be collected? › +❯ Post + Comment +``` +::: + +Select **`Post`** and press Enter. You should then see: + +::: output-block +```text +Collection "all_posts" scaffolded! + +At first, the UI for this application is empty. If you want the newly scaffolded collection to be the entry point for its UI, import the element in `ui/src/App.svelte`: + + import AllPosts from './forum/posts/AllPosts.svelte'; + +And use the element in the `<div id="content" />` block by adding in this: + + <div id="content"><</AllPosts></div> +``` +::: + +These instructions tell us that if we want to include this generated UI component in the user interface of our hApp, we need to do some manual work: + + 1. Import the component, and + 2. Tell the UI to display the component. + +!!! dig-deeper How a collection is implemented + +We already explored how links make data in the DHT discoverable by connecting known DHT base addresses to unknown addresses. Essentially every address becomes an anchor point to hang a collection of links from. + +But there's one remaining problem: _where do you start?_ When someone starts their app for the first time, the only DHT base addresses they know about are their public key, the DNA hash, and the few actions and entries on their source chain. There's no obvious way to start discovering other people's data yet. + +This is where **collections** help out. A collection is just a bunch of links on a base address that's easy to find --- typically the address is hard-coded in the coordinator zome's code as the hash of a string such as `"all_posts"`. It's easy to get the links, because their base address is right there in the code. + +This pattern, which we call the "anchor pattern", is so useful that it's built right into Holochain's SDK --- integrity zomes that use it will have all the necessary entry and link types automatically defined, and coordinator zomes that use it will have functions that can retrieve anchors and the links attached to them. The scaffolded code uses this implementation behind the scenes. + +The built-in implementation is actually a simplification of a more general pattern called "paths", which is also built into the SDK. With paths, you can create trees of linked anchors, allowing you to create and query hierarchical structures. This can be used to implement categories, granular collections (for example, "all posts" → "all posts created in 2023" → "all posts created in 2023-05" → "all posts created on 2023-05-30"), and indexes for type-ahead search (for example, "all usernames" → "all usernames starting with 'mat'" → "all usernames starting with 'matt'"). What the SDK calls an `Anchor` is actually a tree with a depth of two, in which the root node is two empty bytes. + +Hierarchical paths serve another useful purpose. On the DHT, where every node is tasked with storing a portion of the whole data set, some anchors could become "hot spots" --- that is, they could have thousands or even millions of links attached to them. The nodes responsible for storing those links would bear a disproportionate data storage and serving burden. + +The examples of granular collections and type-ahead search indexes breaks up those anchors into increasingly smaller branches, so that each leaf node in the tree --- and hence each peer --- only has to store a small number of links. + +The scaffolding tool doesn't have any feature for building anchors and trees beyond simple one-anchor collections, but if you'd like to know more, you can read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/) and the SDK reference for [`hash_path`](https://docs.rs/hdk/latest/hdk/hash_path/index.html) and [`anchor`](https://docs.rs/hdk/latest/hdk/hash_path/anchor/index.html). + +!!! + +Before you get started editing the UI, it's helpful to be able to actually run the scaffolded applciation. That way, you can watch changes take effect in real-time as you make them. So the next section will walk you through launching the application the tooling that's available there, and then in the section after that, we'll begin working with the `.svelte` files to build the UI. + +### 8. Run your application in dev mode + +At this stage, we'll incorporate some of the UI components that have been scaffolded by the scaffolding tool into our main application interface. Our aim here is to make all the functionality of our forum application accessible from a single, unified interface. We'll use Svelte to accomplish this, as it is the framework that we have chosen for the UI layer of our application. + +Start the forum hApp in develop mode from the command line: go to your terminal and, from the root folder (`my_forum_app/`), enter: + +```shell +npm start +``` + +!!! info Work in the nix shell +If you are having an issue, make sure that you are still in the nix shell. If not, re-enter `nix develop` first, then type the above command again. And remember that you can always exit nix shell by typing `exit` to get back to your normal shell. +!!! + +When you start the hApp with `npm start`, this launches Holochain in sandbox mode with two agents running that hApp, and opens three windows: + +1. A web browser window with Holochain Playground, a tool that makes visible the various actions that have taken place in our forum hApp. You should be able to see a couple of agents in a DHT, with mostly empty source chains and, correspondingly, a mostly empty graph. +2. An application window with one agent (conductor 0) running the forum hApp. This window lets us take actions as that agent (0, or Alice, if you prefer). +3. Another application window with a second agent (conductor 1) running the forum hApp. This window lets us take actions as the other agent (1, or Bob). + +![Three windows: two agent UIs and a web browser window with the Holochain Playground](/assets/img/get-started/3-two-uis-and-playground.png) + +These application windows allow us to test multiple agents in a Holochain network interacting with one another. It is all running on our one device, but the two conductors behave very much the same as separate agents on different machines would, minus network lag. + +Remember that a **conductor** is a Holochain runtime process executing on your computer. For more details see the [Application Architecture](/concepts/2_application_architecture/) section in the Core Concepts guide. + +These three windows together will let us interact with our hApp as we are building it. + +The Holochain Playground in particular is helpful because it creates visual representations of the data that has been created and the way it relates to other content. Take a look at it and click one of the two items in the **DHT Cells** window. These are your two agents. When you click one of them, some content gets displayed in the **Source Chain** window. These are the initial actions in that agent's source chain. The arrows point from newer content back to older content. + +From oldest to newest, in the newly created source chains, the records are: + +1. `DNA`, recording the hash of the DNA to be used to validate all subsequent source chain actions, +2. `AgentValidationPkg`, providing proof that this participant is allowed to participate in this hApp ([see more](https://www.holochain.org/how-does-it-work/) in Holochain: How does it work?), +3. A `Create` action which records the author's `AgentID`, which is their public key and serves as their ID in the network and its graph database. + +As agents begin writing posts, comments, and links to the DHT, you'll see the following records appear: + +4. `InitComplete`, indicating that all coordinator zomes have had a chance to do initial setup, then +5. Whatever actions the agent takes after that. + +The two application UI windows let you interact with the application and see what is working, what is not working, and how data propagates when we take particular actions. + +At first, each of the UI windows (conductors 0 for Alice and 1 for Bob) include instructions for you to go and examine the scaffolded UI elements by looking at the contents in the folder `ui/src///`, where `` and `` are generic placeholders for your DNA (`forum`) and zome (`post`). + +### 9. Integrate the generated UI elements + +Thus far, seven different UI components should have been generated as `.svelte` files in the `ui/src/forum/posts/` folder. Note that for ease of development, the sandbox testing environment live-reloads the UI as you edit UI files. So don't quit the process you started with `npm start`; instead, **open a new terminal window**. Then navigate to the root folder of your hApp (`my_forum_app/`) and list the files in `ui/src/forum/posts/` by entering: + +```shell +ls ui/src/forum/posts/ +``` + +You should see seven different `.svelte` files, plus a `types.ts` file: + +::: output-block +```text +AllPosts.svelte CreateComment.svelte PostDetail.svelte +CommentDetail.svelte CreatePost.svelte types.ts +CommentsForPost.svelte EditPost.svelte +``` +::: + +The next step is to edit the UI files in the text editor or integrated development environment of your choice to add scaffolded components and build a fully featured UI. To integrate all of these generated UI elements, you'll need to add them to `App.svelte` file located in the `ui/src/` folder, or to some other `.svelte` file that eventually gets included in `App.svelte`. + +If you don't yet have path commands for opening files in your prefered IDE, there are instructions for [VSCode/VSCodium](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line), [Sublime Text](https://www.sublimetext.com/docs/command_line.html#setup) and [WebStorm](https://www.jetbrains.com/help/webstorm/working-with-the-ide-features-from-command-line.html#5d6e8844). Going forward in this tutorial, we are going to use the `code` command when we mean for you to open files in your IDE, but you should substitute a different command (ex: `subl`, `vim`, `emacs` etc.) for `code` if you are using a different editor. + +Open the `App.svelte` file with your preferred IDE. + +```shell +code ui/src/App.svelte +``` + +Your `App.svelte` file will have three sections: + +1. a script section, +2. a main section containing a markup template, and +3. a style section containing a stylesheet template. + +!!! dig-deeper Detailed breakdown of `App.svelte` + +#### ` +``` + +This section contains the JavaScript/TypeScript code for the component. It imports various dependencies needed to build a single-page web app: + +* `svelte` is the Svelte engine itself, and its `onMount` function lets you register a handler to be run when the component is initialized, while `setContext` lets you pass data to be used in the rendering of the component. +* `@holochain/client` is the Holochain client library; first we load in some useful Holochain-related TypeScript types, followed by the client object itself. +* `@mwc/material-circular-progress` is just a UI component that gives us a spinner when something is loading. +* `./contexts` is generated by the scaffolding tool. It just contains a constant, the app-wide name for the 'context' that makes the Holochain client accessible to all components. In Svelte, a context is a state shared across components. + +After importing dependencies, it does some initial setup. This is run when the component file is imported --- in this case the component, `App.svelte`, is the main component for the entire application, and it's imported into `main.ts` where it's 'mounted' (more on mounting in a moment). + +Next some variables are instantiated: one to hold the Holochain client that connects to the hApp backend via the conductor, and one to keep track of whether the client is connected yet. (This variable will be used to show a loading spinner while it's still connecting.) + +**Take note of the line that starts with `$:`**. This is a special Svelte label that turns regular variables into **reactive variables**. We won't get too deep into Svelte right now, because this is a tutorial about Holochain, but when a reactive variable changes, Svelte will re-render the entire component. This lets you write a template declaratively, enclosing the reactive variable in `{}` braces, and let Svelte handle the updating of the template wherever the variable changes. + +Finally, there's an `onMount` handler, which is run when the component is first displayed. The handler currently does one thing: it connects to the hApp backend via the conductor, waits until the connection is establised, sets `loading` to false, and adds the resulting client connection to the context so that all components can access it. + +#### `
` section + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+

EDIT ME! Add the components of your app here.

+ + Look in the ui/src/DNA/ZOME folders for UI elements that are generated with hc scaffold entry-type, hc scaffold collection and hc scaffold link-type and add them here as appropriate. + + For example, if you have scaffolded a "todos" dna, a "todos" zome, a "todo_item" entry type, and a collection called "all_todos", you might want to add an element here to create and list your todo items, with the generated ui/src/todos/todos/AllTodos.svelte and ui/src/todos/todos/CreateTodo.svelte elements. + + So, to use those elements here: +
    +
  1. Import the elements with: +
    +import AllTodos from './todos/todos/AllTodos.svelte';
    +import CreateTodo from './todos/todos/CreateTodo.svelte';
    +        
    +
  2. +
  3. Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>.
  4. +
+
+ {/if} +
+``` + +This section is a template for the displayable content of the main app component. Using an `{#if}` block to test whether the reactive variable `loading` is true, this section displays a spinner until the backend can be accessed. Once the UI is connected to the backend, it shows some boilerplate text telling you to add something meaningful to the template. + +#### ` +``` + +This section is a template for the CSS styles that get applied to the HTML in the `
` section of the component. You can also use reactive variables here, and the styling will update whenever the variables change. These scaffolded styles set the component up with some basic layout to make it readable at small and large window sizes. + +**All Svelte components follow this general pattern**. `App.svelte` has special status as the root component, but otherwise it's just like any other component. + +!!! + +First you'll be adding a list of posts to the app, which means the components called `AllPosts.svelte` needs to be imported. + +At the top of the file, there is a list of scripts that are imported. Following the instructions that the scaffolding tool and the two conductor windows gave you, copy the following text and paste it into the script block of the `App.svelte` file, on the line below `import { clientContext } from './contexts';` + +```typescript +import AllPosts from './forum/posts/AllPosts.svelte'; +``` + +Next, add the component to the markup template in the `
` section of the file, where the "EDIT ME!" content now lives. Remove everything inside the `div` element that starts with this tag: + +:::output-block +```svelte +
+``` +::: + +and replace it with this line: + +```svelte + +``` + +Your `
` block should now look like this: + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+ +
+ {/if} +
+``` + +!!! info Svelte component tags +The `AllPosts` element is obviously not standard HTML. In Svelte, each component has a correspondingly named custom element that will get replaced by the rendered component's markup wherever it appears in another component's template. +!!! + +Save that file and take a look again at the two UI windows. They should both say 'No posts found'. + +![A UI showing the AllPosts component, which says 'No posts found'](/assets/img/get-started/4-no-posts-found.png) + +Let's fix that by adding the post creation component to the UI so we can add our first post. Import the `CreatePost.svelte` component by adding this line in the script section, just below the `AllPosts` component you previously imported: + +```typescript +import CreatePost from './forum/posts/CreatePost.svelte'; +``` + +Add this new component to the `
` block above the component you added: + +```svelte + +``` + +Now your `
` block should look like this: + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+ + +
+ {/if} +
+``` + +Save the file and switch to one of the two conductor windows. You should now see a post form. + +![The UI after adding the CreatePost component](/assets/img/get-started/5-create-post-component.png) + +Type something into one of the two conductor windows like: + +* Title: `Hi from Alice` +* Content: `Hello Bob!` + +and then press the "Create Post" button. + +You'll immediately notice that the `AllPosts` component has changed from saying "No posts found" to showing the newly created post. And if you take a look at the Holochain Playground window, you will see that two new actions have been created. If you click the `App` element that's appeared in Alice's source chain, it will pull up some details in the Entry Contents section, including the title and content of Alice's forum post. Note the hash of that entry (top of the Entry Contents window). Then click on the `Create` action that's pointing toward that `App` entry in the source chain. If you look back at the contents window, you will see that it is now sharing the contents of the action. And if you look down the list a bit, you will see the hash of the entry for the first post. + +![The Holochain playground showing a single agent's source chain, containing the actions that create a post, as well as the transformations in the DHT that resulted from these actions](/assets/img/get-started/6-playground-first-post.png) + +!!! dig-deeper Relationships in a source chain versus relationships in the DHT + +At this point, in our DHT graph it should look like we have two different agents and then a separate floating entry and action. But we know that the new post is associated with a source chain which is associated with an agent. So why aren't they connected on the DHT? + +A source chain merely serves as a history of one agent's attempts to manipulate the state of the graph database contained in the DHT. It's useful to think of the DHT as a completely separate data store that doesn't necessarily reflect agent-to-entry relationships unless you explicitly create a link type for them. + +For the purpose of this hApp, we're not interested in agent-to-posts relationships, so it's fine that they're not linked. But if you wanted to create a page that showed all posts by an author, that's when you might want to scaffold that link type. `hc scaffold collection` will do this for you if you choose a by-author collection, and will also create a `get_posts_by_author` function. + +!!! + +You may also notice that only Alice's UI showed the new post, while Bob's didn't. Just as with a traditional web app, database changes don't automatically send out a notification to everyone who is interested. (Alice's UI sees the changes because it knows how to update its own state for local changes.) You can create this functionality using a feature called [signals](/concepts/9_signals/), but let's keep things simple for now. Right-click anywhere in Bob's UI then choose "Reload" from the menu, and you'll see that the changes have been copied from Alice's app instance to Bob's --- all without a database server in the middle! + +Let's edit that post. In Alice's UI window, click the edit adjacent to the post content (it should look like a pencil icon). The post content will be replaced by an editing form. + +Now alter the content a bit. Maybe change it from `Hello Bob!` to `Hello, World!` and click "Save". + +![The UI of one agent, showing a post about to be edited](/assets/img/get-started/7-edit-post.png) + +That should update the post (at least for Alice). Bob's UI will show the updated version the next time it's reloaded. + +If you look at the Holochain Playground, you should see that the update was added to Alice's source chain. Specifically, it created: + +1. a new entry (with our `Hello, World!` text), +2. an `Update` action that indicated this entry is to replace the original entry, and +3. a `CreateLink` action that connects the original create action to the update action. + +![The Holochain playground, showing the source chain of the agent who edited the post along with new data in the DHT reflecting the edit](/assets/img/get-started/8-playground-after-edits.png) + +As explained [previously](#crud-create-read-update-delete), the original forum post already has a 'link' of sorts pointing from its action to the `Update` action, which can be accessed when the original is retrieved. The extra link created by the `CreateLink` action is optional --- it merely speeds up retrieval when an action has been edited many times and has a long chain of update links, by allowing you to jump to the end of the chain. In the screenshot above, the link is highlighted in the DHT pane. + +Now it's time to add commenting to your app. + +Previously, you added new components to the `App.svelte` component. That made sense because posts were a global data type. But comments are related to a post, so from now on you'll be modifying the `PostDetail.svelte` component instead. + +Open up `PostDetail.svelte` in your IDE: + +```shell +code ui/src/forum/posts/PostDetail.svelte +``` + +Just as before, first you'll need to import the components near the top of the file (just after the line that imports `EditPost.svelte`): + +```typescript +import CreateComment from './CreateComment.svelte'; +import CommentsForPost from './CommentsForPost.svelte'; +``` + +Further down the file, in the template block, add the components' elements to the template. Put them both before the closing `
` tag. + +Here, the comment components need to know what post they're related to. The post hash is the unique ID for the post, and the comment components' elements both expect a `postHash` attribute. This hash is available in the `PostDetail` component as a variable of the same name, so it can be passed to the comment widgets. + +```svelte + + +``` + +Save the file, then go back to the UI windows to see the changes. Try typing in a comment or two, then deleting them. (You may need to refresh the UI windows to see the changes to the content.) Watch the Playground --- see how the authors' source chains and the graph in the DHT change as new information is added. The deleted comments are still there and can be accessed by code in your zomes if needed, but neither the application backend (that is, the functions defined in the coordinator zome) nor the UI have the capacity to show them. + +![One UI window with the comment components added, with the Playground in the background showing a populated DHT](/assets/img/get-started/10-comment-components.png) + + +## 2. Next steps + +Congratulations! You've learned how to create a new Holochain application, understand its layout, work with core concepts, and deploy and test the application. + +Now that you have a basic understanding of Holochain development, you can continue exploring more advanced topics, such as: + +* Validating data +* Writing tests for a zome +* Implementing access and write privileges +* Building more complex data structures and relationships +* Optimizing your application for performance and scalability + + +### 2.1 Further exploration and resources + +Now that you have successfully built a basic forum application using Holochain and integrated it with a frontend, you may want to explore more advanced topics and techniques to further enhance your application or create new ones. Here are some resources and ideas to help you get started: + +#### Holochain developer documentation + +The official Holochain developer documentation is a valuable resource for deepening your understanding of Holochain concepts, techniques, and best practices. Be sure to explore the documentation thoroughly: + +* [Holochain Core Concepts](/concepts/1_the_basics/) +* [Holochain Developer Kit (HDK) reference](https://docs.rs/hdk/latest/hdk) + +#### Community resources + +The Holochain community is an excellent source of support, inspiration, and collaboration. Consider engaging with the community to further your learning and development: + +* [Holochain GitHub repositories](https://github.com/holochain) +* [Holochain Discord server](https://discord.com/invite/k55DS5dmPH) + +#### Example applications and tutorials + +Studying existing Holochain applications and tutorials can provide valuable insights and inspiration for your projects. Here are some resources to explore: + +* [Holochain Open Dev](https://github.com/holochain-open-dev) +* [Holochain Foundation sample apps](https://github.com/holochain-apps) diff --git a/src/pages/build/3_deployment.md b/src/pages/build/3_deployment.md new file mode 100644 index 000000000..09dfece5f --- /dev/null +++ b/src/pages/build/3_deployment.md @@ -0,0 +1,92 @@ +--- +title: "Deploying your Holochain application" +tocData: + - text: Packaging + href: packaging + - text: Runtimes + href: runtimes + children: + - text: Launcher + href: launcher-the-multi-app-runtime + - text: Standalone + href: standalone-executable +--- + + +Now that you've built an application, it's time to get it into other people's hands. First an app must be packaged into a distributable bundle format. Then it can be published somewhere a user can access it. + +## Packaging + +You specify the components of a hApp using manifest files, written in [YAML](https://yaml.org/), and the `hc` CLI looks for them when it's building a distributable hApp for you. If you look in the `workdir` folder: + +```shell +ls workdir +``` + +You'll see that the scaffolding tool has generated two manifest files for you: + +:::output-block +```text +happ.yaml web-happ.yaml +``` +::: output-block + +The first step is to package your app: + +```shell +npm run package +``` + +This command does a number of things: + +1. Triggers the Rust compiler to build the zomes, +2. Uses the `hc` CLI too to combine the built zomes and the DNA manifest into a `.dna` file, +3. Combines all the DNAs and the hApp manifest into a `.happ` file, +3. Builds the UI and compresses it into a `.zip` file, and +4. Combines the hApp file, the UI zip, and the web hApp manifest into a `.webhapp` file. + +Of course, this application only has one zome and one DNA, but more complex apps may have many of each. + +Now you'll see some new files in `workdir`: + +```shell +ls workdir +``` + +::: output-block +```text +happ.yaml my_forum_app.happ my_forum_app.webhapp web-happ.yaml +``` +::: output-block + +The packed app is now ready for deployment to a Holochain runtime. + +## Runtimes + +In the centralized world, deployment is usually achieved by Continuous Integration (CI) automation that builds up code changes and sends them to whatever server or cloud-based platform you're using. In the decentralized world of Holochain, deployment happens when end-users download and run your hApp in the Holochain runtime. + +From the end-user perspective there are currently there are two ways to go about this, both of which will feel familiar: + +1. Download Holochain's official Launcher runtime and install the app from its app store or the filesystem. +2. Download an your app as its own standalone desktop executable, as they would any other application for their computer. + +### Launcher, the multi-app runtime + +Holochain's official end-user runtime is the [Holochain Launcher](https://github.com/holochain/launcher). It allows people to install apps from a built-in app store or from the filesystem. Installed apps can then be launched from a friendly UI. Note that the app store is itself a distributed Holochain application which provides details on applications that are available for download. As a developer you can either go through a simple publishing process and add your app to the app store where it will be available for installation by all people who use the Launcher, or you can share your application directly with end-users through your own channels and they can install it into their Holochain Launcher manually from the file system. + +You can try this latter approach immediately by downloading and running the Launcher! + +The steps for publishing an app to the Launcher's app store are documented in the Github repository of the Holochain Launcher [here](https://github.com/holochain/launcher#publishing-and-updating-an-app-in-the-devhub). + +### Standalone executable + +If you prefer to distribute your app as a full standalone executable, you will need to bundle the Holochain runtime and your app together and take care of the necessary interactions between them. Because Holochain itself is really just a set of Rust libraries, you can of course build your own application that uses those libraries, but that's a fair amount of work. Currently there are two much simpler paths for doing this: using either the [Electron](https://www.electronjs.org/) or [Tauri](https://tauri.app/) frameworks, both of which can generate cross-platform executables from standard web UIs. These frameworks also support inclusion of additional binaries, which in our case are the [holochain conductor](https://docs.rs/holochain/latest/holochain/) and the [lair keystore](https://docs.rs/lair_keystore/latest/lair_keystore/). Though there is quite a bit of complexity in setting things up for these frameworks, all the hard work has already been done for you: + +* **Electron**: Refer to the community-supported [electron-holochain-template](https://github.com/lightningrodlabs/electron-holochain-template/) repo. +* **Tauri**: See the officially supported [holochain-kanagroo](https://github.com/holochain-apps/holochain-kangaroo) repo. + +Both of these are GitHub template repos with detailed instructions on how to clone the repos and add in your UI and DNA, as well as build and release commands that will create the cross-platform executables that you can then deliver to your end users. + +!!! note Code Signing +For macOS and Windows, you will probably also want to go through the process of registering as a developer so that your application can be "code-signed". This is needed so that users don't get the "unsigned code" warnings when launching the applications on those platforms. Both of the above templates include instructions for CI automation to run the code-signing steps on release once you have acquired the necessary certificates. +!!! \ No newline at end of file diff --git a/src/pages/get-started/index.md b/src/pages/get-started/index.md index a31e45ded..fb56bf351 100644 --- a/src/pages/get-started/index.md +++ b/src/pages/get-started/index.md @@ -5,38 +5,6 @@ tocData: href: 1-introduction-to-holochain - text: 2. Installing Holochain development environment href: 2-installing-holochain-development-environment - - text: 3. Scaffold a simple "Hello, World!" Holochain application - href: 3-scaffold-a-simple-hello-world-holochain-application - - text: "4. Zero to built: creating a forum app" - href: 4-zero-to-built-creating-a-forum-app - children: - - text: 4.1. Scaffolding a hApp - href: 4-1-scaffolding-a-happ - - text: 4.2. Select user interface framework - href: 4-2-select-user-interface-framework - - text: 4.3. Set up Holonix development environment - href: 4-3-set-up-holonix-development-environment - - text: 4.4. Scaffold a DNA - href: 4-4-scaffold-a-dna - - text: 4.5. Scaffold a zome - href: 4-5-scaffold-a-zome - - text: 4.6. Scaffold entry types - href: 4-6-scaffold-entry-types - - text: 4.7. Scaffold a collection - href: 4-7-scaffold-a-collection - - text: 4.8. Run your applicaiton in dev mode - href: 4-8-run-your-application-in-dev-mode - - text: 4.9. Integrate the generated UI elements - href: 4-9-integrate-the-generated-ui-elements - - text: 5. Deploying your Holochain application - href: 5-deploying-your-holochain-application - children: - - text: 5.1 Packaging - href: 5-1-packaging - - text: 5.2 Runtimes - href: 5-2-runtimes - - text: 6. Next steps - href: 6-next-steps --- Welcome to the Getting Started with Holochain guide! This guide will walk you through the process of installing the Holochain development tools and creating a simple forum application. By the end of this guide, you'll be familiar with the core concepts of Holochain and have a basic understanding of how to develop peer-to-peer applications using the Holochain framework. @@ -117,1212 +85,6 @@ holochain_scaffolding_cli x.y.z Congratulations! The Holochain development environment is now set up successfully on your system. -**Before moving on to the next step**, find a folder to put your work. For this tutorial, we'll be working in `~/Holochain`. Create that folder now and move into it: - -```shell -mkdir ~/Holochain -``` - -```shell -cd ~/Holochain -``` - -## 3. Scaffold a simple "Hello, World!" Holochain application - -In this section, we'll use Holochain's scaffolding tool to generate a simple "Hello, World!" application. - -When getting started, seeing a simple but fully-functional app can be very helpful. You can have Holochain's scaffolding tool generate a "Hello, World!" application (but for a distributed multi-agent world) by typing the following in your command line terminal: - -```shell -nix run github:holochain/holochain#hc-scaffold -- example hello-world -``` - -The scaffolding tool should print out these four commands for you to run in order to run the app. Copy them from your terminal or from below: - -```shell -cd hello-world -``` -```shell -nix develop -``` -```shell -npm install -``` -```shell -npm start -``` - -After you run the last of these commands, you should see three windows open: - -![A screenshot showing two hApp windows in front of the Playground](/assets/img/get-started/1-running-app-first-look.png) - -* A web browser window with the Holochain Playground, which displays a visual representation of the app's state data -* Two windows showing the UI for two agents, both of which will have published a `Hello World` entry to the network. - -When you click on the "Look for Hellos" button, you should be able to see the hellos: - -![A screenshot showing one app window, with hello messages in different languages retrieved from the DHT](/assets/img/get-started/2-look-for-hellos.png) - -When you are done checking out this app, you can go back to the terminal and stop both agents by pressing Ctrl+C (Linux) or Cmd+C (macOS). - -!!! dig-deeper Understanding the layout of a scaffolded project - -Let's explore the different files and folders that make up the structure of the "Hello, World!" hApp that you just created. - -List the folders and files in our `hello-world/` folder by entering: - -```shell -ls -``` - -This table includes everything in the `hello-world/` folder as well as details of the contents of the `dnas/` subfolder since that makes up the bulk of the "Holochain" part of an application. For certain working folders, like `node_modules/`, `target/`, `tests/`, and `ui/`, the table only contains a high-level overview. - -| File/folder | Purpose | -|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -|
 ├── hello-world/         
| Root folder of the application. All other files and folders will reside here. | -|
 ├┬─ dnas/                
| This folder contains the DNA configuration and source code for the application. DNAs are one of the most important building blocks in Holochain. Simply put, **a DNA is the executable code for the game you are playing with your peers in Holochain.** And here is the twist: in Holochain, **every DNA creates its own peer-to-peer network** for the validation, storage, and serving of content. Every Holochain application contains at least one DNA. In this example hApp, we have just one: `hello_world`. | -|
 │└┬─ hello_world/        
| Folder for the `hello_world` DNA. It contains modules (zomes) that define the rules and API of this application. | -|
 │ ├┬─ workdir/           
| A working folder containing configuration files and compiled artifacts related to the DNA. | -|
 │ │├── dna.yaml          
| DNA manifest file. A YAML file that defines the properties and zomes of the DNA. YAML is a human-readable data serialization language. | -|
 │ │└── hello_world.dna   
| The compiled DNA file, which includes both the integrity and coordinator zomes. This file is used by Holochain to run the hApp. | -|
 │ └┬─ zomes/             
| The source code for zomes (short for chromosomes), which are the executable packages in a DNA. Each zome has its own name like `profile` or `chat`. Zomes define the core logic in a DNA, and can be composed together to create more powerful functionality. DNAs in Holochain are always composed out of one or more zomes. This folder contains zomes for the `hello_world` DNA. | -|
 │  ├┬─ coordinator/      
| This folder contains the coordinator zomes, which are responsible for this DNA's controller layer, such as reading/writing data and handling communication between peers. The public functions defined in these zomes' code become the application's API available to the UI and, depending on the needs of your app, to other peers in the same network. | -|
 │  │└┬─ hello_world/     
| Folder containing the source code for the package that will become the `hello_world` coordinator zome binary. Rust packages are called crates, and they have the following structure. | -|
 │  │ ├┬─ src/            
| Source code folder for the `hello_world` crate. | -|
 │  │ │└── lib.rs         
| The main source code file for the `hello_world` crate. In Rust, `lib.rs` is the entry point for a library crate, which is the kind of crate that a zome needs to be written as. If you have nothing else in here, you should have this file. | -|
 │  │ └── Cargo.toml      
| The manifest file for the crate that will become the `hello_world` coordinator zome, containing metadata, dependencies, and build options. This file tells Cargo, Rust's package manager, how to build the crate into a binary. | -|
 │  └┬─ integrity/        
| This folder contains the integrity zomes, which are responsible for the application's model layer, which define data structures and validation rules for application data. | -|
 │   └┬─ hello_world/     
| Folder containing the `hello_world_integrity` crate. | -|
 │    ├┬─ src/            
| Source code folder for the `hello_world_integrity` crate. | -|
 │    │└── lib.rs         
| The main source code file for the `hello_world_integrity` crate. | -|
 │    └── Cargo.toml      
| TThe Cargo manifest file for the `hello_world_integrity` crate. | -|
 ├── node_modules/        
| A folder containing cached JavaScript packages and dependencies for the user interface and tests. | -|
 ├── target/              
| A folder containing the compiled output from the Rust build process. | -|
 ├── tests/               
| A folder containing JavaScript-base test code for the application. | -|
 ├── ui/                  
| A folder containing the source code and assets for the web-based user interface of the "Hello, World!" application. This user interface will get distributed along with the application. | -|
 ├┬─ workdir/             
| A working folder containing configuration files and compliled artifacts related to the building of the whole hApp. | -|
 │├── happ.yaml           
| The manifest file for the hApp. It references the DNA files to be included, along with the roles they play in the application. In this case, there's only one DNA file, `hello_world`. | -|
 │├── hello_world.happ    
| The compiled hApp bundle, which includes all the DNAs (in case just the one). | -|
 │├── hello_world.webhapp 
| The compiled web hApp bundle, which includes the hApp bundle plus the zipped UI. | -|
 │└── web-happ.yaml       
| The manifest file for the hApp plus the UI. It references the compiled hApp bundle and zipped UI folder to be included. | -|
 ├── Cargo.lock           
| A file generated by Cargo, Rust's package manager, that lists the exact versions of dependencies used in the project. | -|
 ├── Cargo.toml           
| The main configuration file for the Rust project, containing dependencies, build options, and other metadata for all crates. | -|
 ├── flake.lock           
| A file generated by Nix, the package manager we use to distribute the Holochain development tools, that lists the exact versions of dependencies used in the project. | -|
 ├── flake.nix            
| A Nix file that defines the project's build environment and dependencies. | -|
 ├── package.json         
| The main configuration file for the JavaScript portions of the project, containing dependencies, scripts, and other metadata for the application's user interface and tests, as well certain build tools. | -|
 ├── package-lock.json    
| A file generated by npm, Node.js package manager, that lists the exact versions of dependencies used by Node.JS. | -|
 └── README.md            
| A Markdown file containing the documentation and instructions for the application, including how to build, run, and test the project. | - -These files and folders make up the structure of a Holochain application, with the main logic defined in the zomes (in the `dnas//zomes/` folders) and the user interface defined in the `ui/` folder. The manifest files bring all the Holochain and UI assets together, allowing the `hc` tool to bundle them into a single hApp file ready for distribution. - -!!! - -## 4. Zero to built: creating a forum app - -First, navigate back to the folder where you want to keep your Holochain applications. If you followed our suggestion, you can get back to it by typing: - -```shell -cd ~/Holochain -``` - -Next up, we'll walk you through creating a forum application from scratch using Holochain's scaffolding tool, step-by-step. This forum application will enable participants to share text-based posts and to comment on those posts. - -Each post will have a title and content, and authors will be able to edit --- or update --- their posts. However, they won't be able to delete them. - -Each comment will be a reply to a particular post, will be limited in length to 140 characters, and will be able to be deleted but not updated. - -!!! info Validation tutorial coming soon -A future update to this guide will implement the above constraints as validation rules. For now, we'll just scaffold enough code to get a working UI. **Check back soon** to get the whole tutorial! -!!! - -We'll create a couple of other things along the way that will enable people to find these posts and comments, but we'll cover those things when we get there. - -The good news is that the Holochain scaffolding tool will do a lot of the heavy lifting in terms of generating folders, files, and boilerplate code. It will walk you through each step in the hApp generation process. In fact, the scaffolding tool does so much of the work for you that many people have commented that 90% or more of the time spent writing a Holochain app is focused on building out the front-end user interface and experience. - -First, let's use the scaffolding tool to generate the basic folders and files for our hApp. - -### 4.1. Scaffolding a hApp {#4-1-scaffolding-a-happ} - -To start, run the following command in your terminal: - -```shell -nix run github:/holochain/holochain#hc-scaffold -- web-app -``` - -You should then see: - -::: output-block -```text -? App name (no whitespaces): -``` -::: - -Enter the name of your forum application using snake_case. Enter: - -```text -my_forum_app -``` - -### 4.2. Select user interface framework - -You'll then be prompted to choose a user interface (UI) framework for your front end. - -For this example, use the arrow keys to choose `Svelte` and press Enter. - -### 4.3. Set up Holonix development environment - -Next, you'll be asked if you want to set up the Holonix development environment for the project. This allows you to enter a shell that has all the right tools and libraries for the version of Holochain that your code was generated for. - -Choose `Yes (recommended)` and press Enter. - -You should see: - -::: output-block -```text -Setting up nix development environment... -``` -::: - -along with some details of what is being added. Follow the instructions to set up the development environment for your hApp and continue to scaffold more of its elements. - -First, enter the hApp project folder: - -```shell -cd my_forum_app -``` - -Just to get an overview of what your first scaffold command set up for you, you can check the contents of that `my_forum_app` folder by typing: - -```shell -ls -``` - -It should look like it has set up a similar set of folders and configuration files to those you saw in the "Hello, World!" hApp. - -Now, fire up the nix development shell, which makes all scaffolding tools and the Holochain binaries directly available from the command line, by entering: - -```shell -nix develop -``` - -After a short while of installing packages, you should see: - -::: output-block -```text -Holochain development shell spawned. Type exit to leave. -``` -::: - -As it says, if you want to leave the nix development shell at any time, you can type `exit`. This will take you back to your familiar shell without any of the special Holochain dependencies. When you want to re-enter it, navigate to the `my_forum_app` folder and type `nix develop` again. But for now, install the Node Package Manager (npm) dependencies with: - -```shell -npm install -``` - -These dependencies are used by various tools and assets --- the scaffolded tests, the UI, and various development activities like spawning apps for testing. - -When that finishes, you should see some text that ends with something like: - -::: output-block -```text -added 371 packages, and audited 374 packages in 1m - -37 packages are looking for funding - run 'npm fund' for details -found 0 vulnerabilities -``` -::: - -If you see something like that, you've successfully downloaded the NPM dependencies for the UI and for building your app. - -Next up, you're going to start creating the foundational building block of any Holochain app: its DNA. - -!!! dig-deeper Scaffolding subcommands - -To get an overview of the subcommands that `hc scaffold`` makes available to you, type: - -```shell -hc scaffold --help -``` - -You should see something like: - -::: output-block -```text -holochain_scaffolding_cli 0.1.8 -The list of subcommands for `hc scaffold` - -USAGE: - hc-scaffold - -FLAGS: - -h, --help Prints help information - -V, --version Prints version information - -SUBCOMMANDS: - collection Scaffold a collection of entries in an existing zome - dna Scaffold a DNA into an existing app - entry-type Scaffold an entry type and CRUD functions into an existing zome - example - help Prints this message or the help of the given subcommand(s) - link-type Scaffold a link type and its appropriate zome functions into an existing zome - template Set up the template used in this project - web-app Scaffold a new, empty web app - zome Scaffold one or multiple zomes into an existing DNA -``` -::: - -You can get help on every one of these subcommands and its parameters by typing `hc scaffold --help`. -!!! - -!!! info Backing out of a mistake -A quick note: if while scaffolding some part of your hApp, you realize you've made a mistake (a typo or wrong selection for instance), as long as you haven't finished scaffolding that portion, **you can stop the current step** by using Ctrl+C on Linux or Command+C on macOS. -!!! - -### 4.4. Scaffold a DNA - -A DNA folder is where you will put the code that defines the rules of your application. You're going to stay in the `my_forum_app/` root folder and, with some simple commands, the scaffolding tool will do much of the creation of relevant folders and files for you. - -!!! dig-deeper DNAs: Context and Background {#about-dnas} - -#### Why do we use the term DNA? - -In Holochain, we are trying to enable people to **choose to participate in coherent social coordination**, or interact meaningfully with each other online without needing a central authority to define the rules and keep everyone safe. To do that, we are borrowing some patterns from how biological organisms are able to coordinate coherently even at scales that social organisations such as companies or nations have come nowhere close to. In living creatures like humans, dolphins, redwood trees, and coral reefs, many of the cells in the body of an organism (trillions of the cells in a human body, for instance) are each running a (roughly) identical copy of a rule set in the form of DNA. - -This enables many different independent parts (cells) to build relatively consistent superstructures (a body, for instance), move resources, identify and eliminate infections, and more --- all without centralized command and control. There is no "CEO" cell in the body telling everybody else what to do. It's a bunch of independent actors (cells) playing by a consistent set of rules (the DNA) coordinating in effective and resilient ways. - -A cell in the muscle of your bicep finds itself in a particular context, with certain resources and conditions that it is facing. Based on those signals, that cell behaves in particular ways, running relevant portions of the larger shared instruction set (DNA) and transforming resources in ways that make sense for a bicep muscle cell. A different cell in your blood, perhaps facing a context where there is a bacterial infection, will face a different set of circumstances and consequently will make use of other parts of the shared instruction set to guide how it behaves. In other words, many biological organisms make use of this pattern where **many participants run the same rule set, but each in its own context**, and that unlocks a powerful capacity for coherent coordination. - -Holochain borrows this pattern that we see in biological coordination to try to enable similarly coherent social coordination. However, our focus is on enabling such coherent social coordination to be "opted into" by the participants. We believe that this pattern of being able to choose which games you want to play --- and being able to leave them or adapt them as experience dictates --- is critical to enabling individual and collective adaptive capacity. We believe that it may enable a fundamental shift in the ability of individuals and communities to sense and respond to the situations that they face. - -To put it another way: if a group of us can all agree to the rules of a game, then together we can play that game. - -All of us opting in to those rules --- and helping to enforce them --- enables us to play that game together, whether it is a game of chess, chat, a forum app, or something much richer. - -#### DNA as boundary of network - -The network of participants that are running a DNA engage in "peer witnessing" of actions by the participants in that network. A (deterministically random) set of peers are responsible for validating, storing, and serving each particular piece of shared content. In other words, the users of a particular hApp agree to a set of rules and then work together collectively to enforce those rules and to store and serve content (state changes) that do not violate those rules. - -Every hApp needs to include at least one DNA. Moreover, as indicated above, **it is at the DNA level** (note: not the higher application level) **where participants will form a network of peers to validate, store, and serve content** in accordance with the rules defined in that DNA. This happens in the background as the application runs on each participant's machine. - -There are some powerful consequences to this architectural choice --- including freedom to have your application look and feel the way you want, or to combine multiple DNAs together in ways that work for you without having to get everyone else to agree to do the same --- but we'll save those details for later. - -#### So if we have multiple DNAs in our hApp... - -...then we are participating in multiple networks, with each network of peers that are participating in a particular DNA also helping maintain the shared database for each DNA, enforcing the DNA's rules while validating, storing, and serving content. Each network acts as a 'social organism' in cooperation with other networks in the hApp. - -This is similar to the way in which multiple DNA communities coexist in biological organisms. In fact, there are more cells in a human body that contain other DNA (like bacteria and other microorganisms) than cells that contain our DNA. This indicates that we are an _ecology_ of coherent communities that are interacting with --- and evolving alongside --- one another. - -When it comes to hApps, this lets us play coherent games with one another at the DNA level, while also participating in adjacent coherent games with others as well. That means that applications are not one-size-fits-all. You can choose to combine different bits of functionality in interesting and novel ways. - -!!! - -It's time to scaffold a new DNA by entering: - -```shell -hc scaffold dna -``` - -You should then see: - -::: output-block -```text -? DNA name (snake_case): -``` -::: - -Enter a name for the DNA: - -```text -forum -``` - -You should then see: - -::: output-block -```text -DNA "forum" scaffolded! -Add new zomes to your DNA with: - hc scaffold zome -``` -::: - -Success! Inside of your `dnas/` folder, the scaffolding tool generated a `forum/` folder and, inside of that, the folders and files that the DNA needs. At this point you have a skeleton structure for your `forum` DNA. As you take the following steps, the scaffolding tool will make additions and edits to some of those folders and files based on your instructions. - -### 4.5. Scaffold a zome - -DNAs are comprised of code modules, which we call zomes (short for chromosomes). Zomes are modules that typically focus on enabling some small unit of functionality. Building with this sort of modular pattern provides a number of advantages, including the ability to reuse a module in more than one DNA to provide similar functionality in a different context. For instance, the [profiles zome](https://github.com/holochain-open-dev/profiles) is one that many apps make use of. For the forum DNA, you'll be creating two zomes: `posts` and `posts_integrity`. - -Start by entering: - -```shell -hc scaffold zome -``` - -You should then see: - -::: output-block -```text -? What do you want to scaffold? › -❯ Integrity/coordinator zome-pair (recommended) - Only an integrity zome - Only a coordinator zome -``` -::: - -!!! dig-deeper Integrity zomes and coordinator zomes - -#### Integrity zomes - -An integrity zome, as the name suggests, is responsible for maintaining the data integrity of a Holochain application. It sets the rules and ensures that any data writes occurring within the application are consistent with those rules. In other words, it is responsible for ensuring that data is correct, complete, and trustworthy. Integrity zomes help maintain a secure and reliable distributed peer-to-peer network by enforcing the validation rules defined by the application developer --- in this case, you! - -#### Coordinator zomes - -On the other hand, a coordinator zome contains the code that actually commits data, retrieves it, or sends and receives messages between peers or between other portions of the application on a user's own device (between the back end and the front-end UI, for instance). A coordinator zome is where you define the API for your DNA, through which the network of peers and their data is made accessible to the user. - -#### Multiple zomes per DNA - -As you learned earlier, a DNA can have multiple integrity and coordinator zomes. Each integrity zome contributes to the full set of different types of valid data that can be written, while each coordinator zome contributes to the DNA's functionality that you expose through its API. In order to write data of a certain type, a coordinator zome needs to specify a dependency on the integrity zome that defines that data type. A coordinator zome can also depend on multiple integrity zomes. - -#### Why two types? - -They are separated from one another so we can update coordinator zomes without having to update the integrity zomes. This is important, because changes made to an integrity zome result in a change of the rule set, which results in an entirely new network. This is because the integrity code is what defines the 'rules of the game' for a group of participants. If you changed the code of an integrity zome, you would find yourself suddenly in a new and different network from the other folks who haven't yet changed their integrity zome --- and we want to minimize those sorts of forks to situations where they are needed (like when a community decides they want to play by different rules, for instance changing the maximum length of comments from 140 characters to 280 characters). - -At the same time, a community will want to be able to improve the ways in which things are done in a Holochain app. This can take the form of adding new features or fixing bugs, and we also want people to also be able to take advantage of the latest features in Holochain. Separating integrity and coordination enables them to do that more easily, because: - -* Holochain's coordinator zome API receives frequent updates while the integrity zome API is fairly stable, and -* coordinator zomes can be added to or removed from a DNA at runtime without affecting the DNA's hash. - -!!! - -For this app, you're going to want both an integrity zome and a coordinator zome, so use the arrow keys to select: - -::: output-block -```text -Integrity/coordinator zome-pair -``` -::: - -and press Enter. - -You should then see: - -::: output-block -```text -? Enter coordinator zome name (snake_case): - (The integrity zome will automatically be named '{name of coordinator zome}_integrity') -``` -::: - -Enter the name: - -```text -posts -``` - -and press Enter. - -You should then see prompts asking if you want to scaffold the integrity and coordinator zomes in their respective default folders. - -Press Y for both prompts. - -As that runs (which will take a moment as the scaffold makes changes to various files) you should then see something like: - -::: output-block -```text -Coordinator zome "posts" scaffolded! -Updating crates.io index - Fetch [===> ] ... -``` -::: - (then after download is done...) -::: output-block -```text - Downloaded 244 crates (46.7 MB) in 4.27s (largest was `windows` at 11.9 MB) - -Add new entry definitions to your zome with: - hc scaffold entry-type -``` -::: - -Once that is all done, your hApp skeleton will have filled out a bit. Before you scaffold the next piece, it might be good to get a little context for how content is "spoken into being" when a participant publishes a post in a forum hApp. Read the following section to learn more. - -!!! dig-deeper Source chains, actions, and entries - -#### Source chain - -Any time a participant in a hApp takes some action that changes data, they add a record to a journal called a **source chain**. Each participant has their own source chain, a local, tamper-proof, and chronological store of the participant's actions in that application. - -This is one of the main differences between Holochain and other systems such as blockchains or centralized server-based applications. Instead of recording a "global" (community-wide) record of what actions have taken place, in Holochain actions are taken by agents and are thought of as transformations of their own state. - -One big advantage of this approach is that a single agent can be considered authoritative about the order in which they took actions. From their perspective, first they did A, then B, then C, etc. The fact that someone else didn't get an update about these changes, and possibly received them in a different order, doesn't matter. The order that the authoring agent took those actions will be captured in the actions themselves (thanks to each action referencing the previous one that they had taken, thus creating an ordered sequence --- or chain --- of actions). - -#### Actions and entries - -You'll notice that we used the word "action" a lot. In fact, **we call the content of a source chain record an action**. In Holochain applications, data is always "spoken into being" by an agent (a participant). Each record captures their act of adding, modifying, or removing data, rather than simply capturing the data itself. - -There are a few different kinds of actions, but the most common one is `Create`, which creates an 'entry' --- an arbitrary blob of bytes. Entries store most of the actual content created by a participant, such as the text of a post in our forum hApp. When someone creates a forum post, they're recording an action to their source chain that reads something like: _I am creating this forum post entry with the title "Intros" and the content "Where are you from and what is something you love about where you live?" and I would like my peers in the network to publicly store a record of this act._ So while an action is useful for storing noun-like data like messages and images, it's actually a verb, a record of an action that someone took to update their own state and possibly the shared database state as well. That also makes it well-suited to verb-like data like real-time document edits, game moves, and transactions. - -Every action contains the ID of its author (actually a cryptographic public key), a timestamp, a pointer to the previous source chain record, and a pointer to the entry data, if there is any. In this way, actions provide historical context and provenance for the entries they operate on. - -The pointer to the previous source chain record creates an unbroken history from the current record all the way back to the source chain's starting point. This 'genesis' record contains the hash of the DNA, which servs as both the identifier for the specific set of validation rules that all following records should follow and the ID of the network that this source chain's actions are participating in. - -An action is cryptographically signed by its author and is immutable (can't be changed or erased from either the source chain or the network's data store) once written. This, along with the validation rules specified by the DNA hash in the genesis record, are examples of a concept we call "intrinsic data integrity", in which data carries enough information about itself to be self-validating. - -Just as with a centralized application, we aren't just going to add this data into some database without checking it first. When a participant tries to write an action, Holochain first: - -1. ensures that the action being taken doesn't violate the validation rules of the DNA, -2. adds it as the next record to the source chain, and then -3. tells the participant's network peers about it so they can validate and store it, if it's meant to be public. - -The bits of shared information that all the peers in a network are holding are collectively called a distributed hash table, or DHT. We'll explain more about the DHT later. - -If you want to learn more, check out [The Source Chain: A Personal Data Journal](/concepts/3_source_chain/) and [The DHT: A Shared, Distributed Graph Database](/concepts/4_dht/). You'll also get to see it all in action in a later step, when you run your hApp for the first time. - -!!! - -Now it's time to start defining the structure and validation rules for data within your application. - -### 4.6. Scaffold entry types - -An entry type is a fundamental building block used to define the structure and validation rules for data within a distributed application. Each entry type corresponds to a specific kind of data that can be stored, shared, and validated within the application. - -!!! dig-deeper Entry types and validation - -An entry type is just a label, an identifier for a certain type of data that your DNA deals with. It serves as something to attach validation rules to in your integrity zome, and those rules are what give an entry type its meaning. They take the form of code in a function that gets called any time something is about to be stored, and because they're just code, they can validate all sorts of things. Here are a few key examples: - -* **Data structure**: When you use the scaffolding tool to create an entry type, it generates a Rust-based data type that define fields in your entry type, and it also generates code in the validation function that attempts to convert the raw bytes into an instance of that type. By providing a well-defined structure, this type ensures that data can be understood by the application. If it can't be deserialized into the appropriate Rust structure, it's not valid. - -* **Constraints on data**: Beyond simple fields, validation code can constrain the values in an entry --- for instance, it can enforce a maximum number of characters in a text field or reject nonsensical calendar dates. - -* **Privileges**: Because it originates in a source chain, an entry comes with metadata about its author. This can be used to control who can create, edit, or delete an entry. - -* **Contextual conditions**: Because an action is part of a chain of actions, it can be validated based on the agent's history --- for instance, to prevent currency transactions beyond a credit limit or disallow more than two comments per minute to discourage spam. An entry can also point to other entries in the DHT upon which it depends, and the data from those entries can be used in its validation. - -!!! - -Your bare-bones forum needs two entry types: `post` and `comment`. You'll define these in the `posts` integrity zome you just created in the previous step. The `post` entry type will define a `title` field and a `content` field. The `comment` entry type will define a `comment_content` field and a way of indicating which post the comment is about. - -To do this, just follow the instructions that the scaffold suggested for adding new entry definitions to your zome. - -Start with the `post` entry type by entering this command: - -```shell -hc scaffold entry-type -``` - -You should then see: - -::: output-block -```text -✔ Entry type name (snake_case): -``` -::: - -Enter the name: - -```text -post -``` - -You should then see: - -::: output-block -```text -Which fields should the entry contain? - -? Choose field type: › -❯ String - bool - u32 - i32 - f32 - Timestamp - ActionHash - EntryHash - DnaHash - AgentPubKey - Enum - Option of... - Vector of... -``` -::: - -The scaffolding tool is now prompting you to add fields to the `post` entry type. - -Fields are the individual components or attributes within an entry type that define the structure of the data. They determine the specific pieces of information to be stored in an entry and their respective data types. The scaffolding tool supports a collection of native Rust types such as booleans, numbers, enums (a choice between several predetermined values), optional values, and vectors (lists of items of the same type), along with Holochain-specific types that refer to other pieces of data on the DHT. - -For your `post` entry type, you're going to add `title` and `content` fields. Select `String` as the first field's type, and enter: - -```text -title -``` - -as the field name. - -Press Y for the field to be visible in the UI, and use the arrow keys to select `TextField` as the widget to render this field. (A `TextField` is a single-line input field designed for capturing shorter pieces of text.) - -When you see: - -::: output-block -```text -?Add another field to the entry?(y/n) -``` -::: - -press Y. - -Select `String` for this field's type too. Then enter - -```text -content -``` - -as the field name. - -Press Y for the field to be visible in the UI, and select `TextArea` as the widget to render the field. (A `TextArea` is a multi-line input field that allows users to enter larger blocks of text. That'll work better for blog posts.) - -After adding the `title` and `content` fields, press N when asked if you want to add another field. Next, you should see: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? - Update -✔ Delete -``` -::: - -The scaffolding tool can add zome and UI functions for updating and deleting entries of this type. In this case, we want authors to be able to update posts, but not delete them, so use the arrow keys and the spacebar to ensure that `Update` has a check and `Delete` does not. It should look like this: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? -✔ Update - Delete -``` -::: - -Then press Enter. - -At this point you should see: - -::: output-block -```text -? Should a link from the original entry be created when this entry is updated? › -❯ Yes (more storage cost but better read performance, recommended) - No (less storage cost but worse read performance) -``` -::: - -Select `Yes` by pressing Enter. - -!!! dig-deeper CRUD (create, read, update, delete) {#crud-create-read-update-delete} - -#### Mutating immutable data and improving performance - -In short, the above choice is about how changes get dealt with when a piece of content is updated. - -Because all data in a Holochain application is immutable once it's written, we don't just go changing existing content, because that would break the integrity of the agent's source chain as well as the data already in the DHT. So instead we add metadata to the original data, indicating that people should now look elsewhere for the data or consider it deleted. This is produced by `Update` and `Delete` source chain actions. - -For an `Update` action, the original `Create` or `Update` action and its entry content on the DHT get a `ReplacedBy` pointer to the new `Update` action and its entry content. - -When the scaffolding tool asks you whether to create a link from the original entry, though it's not talking about this pointer. Instead, it's talking about an extra piece of metadata that points to the _very newest_ entry in a chain of updates. If an entry were to get updated, and that update were updated, and this were repeated three more times, anyone trying to retrieve the entry would have to query the DHT six times before they finally found the newest revision. This extra link, which is not a built-in feature, 'jumps' them past the entire chain of updates at the cost of a bit of extra storage. The scaffolding tool will generate all the extra code needed to write and read this metadata in its update and read functions. - -For a `Delete` action, the original action and its entry content are simply marked as deleted. In the cases of both updating and deleting, all original data is still accessible if the application needs it. - -#### Resolving conflicts - -Multiple participants can mark a single entry as updated or deleted at the same time. This might be surprising, but Holochain does this for two good reasons. First, it's surprisingly difficult to decide which is the 'correct' version of a piece of data in a distributed system, because contributions may come from any peer at any time, even appearing unexpectedly long after they've been created. There are many strategies for resolving the conflicts that arise from this, which brings us to the second good reason: we don't want to impose a specific conflict resolution strategy on you. Your application may not even consider parallel updates and deletes on a single entry to be a conflict at all. - -#### CRUD functions - -**By default, the scaffolding tool generates a `create_' function in your coordinator zome for an entry type** because creating new data is a fundamental part of any application, and it reflects the core principle of Holochain's agent-centric approach --- the ability to make changes to your own application's state. - -Similarly, when a public entry is published, it becomes accessible to other agents in the network. Public entries are meant to be shared and discovered by others, so **a `read_' function is provided by default** to ensure that agents can easily access and retrieve publicly shared entries. (The content of _private_ entries, however, are not shared to the network.) For more info on entries, see: the **Core Concepts sections on [Source Chains](/concepts/3_source_chain/) and [DHT](/concepts/4_dht/)**. - -Developers decide whether to let the scaffolding tool generate `update_` and `delete_` functions based on their specific application requirements. More details in the Core Concepts section on [CRUD](/concepts/6_crud_actions/). - -!!! - -Next, you should see: - -::: output-block -```text -Entry type "post" scaffolded! - -Add new collections for that entry type with: - - hc scaffold collection -``` -::: - -We'll dive into collections in a moment, but first create the `comment` entry type. - -Again type: - -```shell -hc scaffold entry-type -``` - -This time enter the name: - -```text -comment -``` - -for the entry type name. - -You're going to add a `comment_content` field, so select the `String` field type and enter: - -```text -comment_content -``` - -Then select the `TextArea` widget and press Enter. (Again, a `TextArea` is a multi-line input field that allows users to enter larger blocks of text. Perfect for a comment on a post.) - -Press Y to add another field. - -For this next field you'll want to create a field that will help you associate each particular comment to the post that it is commenting on. To do this, the next field in the `comment` entry type will store a reference to a `post`. - -Use the arrow keys to select `ActionHash` as the field type. - -!!! dig-deeper Hashes and other identifiers - -There are two kinds of unique identifiers or 'addresses' in Holochain: **hashes** for data and **public keys** for agents. - -A hash is a unique "digital fingerprint" for a piece of data, generated by running it through a mathematical function called a **hash function**. None of the original data is present in the hash, but even so, the hash is extremely unlikely to be identical to the hash of any other piece of data. If you change even one character of the entry's content, the hash will be radically (and unpredictably) different. - -Holochain uses a hash function called blake2b. You can play with [an online blake2b hash generator](https://toolkitbay.com/tkb/tool/BLAKE2b_512) to see how changing content a tiny bit alters the hash. Try hashing `hi` and then `Hi` and compare their hashes. - -To ensure data integrity and facilitate efficient data retrieval, each piece of data is identified by its hash. This serves the following purposes: - -* **Uniqueness:** The cryptographic hashing function ensures that the data has a unique hash value, which helps to differentiate it from other data on the network. -* **Efficient lookup:** The hash is used as a key (essentially an address) in the network's storage system, the distributed hash table (DHT). When an agent wants to retrieve data, they simply search for it by hash, without needing to know what peer machine it's stored on. In the background, Holochain reaches out simultaneously to multiple peers who are responsible for the hash based on an algorithm that matches peers to data based on the similarity of the hash to their agent IDs. This makes data lookup fast and resilient to unreliable peers or network conditions. -* **Fair distribution:** Because the participants in a network are responsible for validating and storing each other's public data based on its hash, the randomness of the hashing function ensures that that responsibility is spread fairly evenly among everyone. -* **Integrity verification:** `Hi` will always generate the same hash no matter who runs it through the hashing function. So when data is retrieved by hash, its hash can be recalculated and compared with the original requested hash to ensure that a third party hasn't tampered with the data. -* **Collusion resistance:** The network peers who take responsibility for validating and storing an entry are chosen randomly based on the similarity of their agent IDs to the `EntryHash`. It would take a huge amount of computing power to generate a hash that would fall under the responsibility of a colluding peer. And because Holochain can retrieve data from multiple peers, it's more likely that the requestor can find one honest peer to report problems with a piece of bad data. - -#### `ActionHash` - -An action is identified by its `ActionHash`. Because an action contains information about its author, the time it was written, the action that preceded it, and the entry it operates on, no two action hashes will be the same --- even for the same entry. This helps to disambiguate identical entries written at different times by different agents. - -#### `EntryHash` - -An entry is identified by its `EntryHash`, which can be retrieved from the `ActionHash` of the action that wrote it. Because they're two separate pieces of data, an entry is stored by different peers than the action that operates on it. - -#### `AgentPubKey` - -**Each agent in a network is identified by their cryptographic public key**, a unique number that's mathematically related to a private number that they hold on their machine. Public-key cryptography is a little complex for this guide --- it's enough to know that a participant's private key signs their source chain actions, and those signatures paired with their public key allow others to verify that they are the one who authored those actions. - -An `AgentPubKey` isn't a hash, but it's the same length, and it's unique just like a hash. So it can be used as a way of referring to an agent, like a user ID --- and this is also why it's used to choose the right peers in the DHT storage and retrieval algorithm. - -#### Summary - -Whereas `EntryHash` is used to uniquely identify, store, and efficiently retrieve an entry from the DHT, `ActionHash` is used to uniquely identify, store, and retrieve the action (metadata) that operated on it, which can provide information about the history and context of any associated entry (including what action preceded it). `ActionHash`es are also what enable any participant to retrieve and reconstruct the continuous sequence of actions (and any associated entries) in another agent's source chain. - -**Use `EntryHash` when** you want to link to or retrieve the actual content or data (e.g., when linking to a category in a forum application). - -**Use `ActionHash` when** you want to link to or retrieve the authorship or history of an entry (e.g., when distinguishing between two posts with identical content). - -**Use `AgentPubKey` when** you want to link to an agent (such as associating a profile or icon with them) or retrieve information about their history (such as scanning their source chain for posts and comments). - -You can check out the Core Concepts to dive a bit deeper into [how the distributed hash table helps](/concepts/4_dht/) to not only make these entries and actions available but helps to ensure that agents haven't gone back to try and change their own histories after the fact. But for now, let's dive into links. - -!!! - -After press Enter, you should see: - -::: output-block -```text -? Should a link from this field be created when this entry is created? (y/n) › -``` -::: - -Press Y to accept creating a link. - -Next you will see: - -::: output-block -```text -✔ Which entry type is this field referring to? -``` -::: - -Press Enter to accept the suggested entry type `Post`. - -Next, you will be asked to pick a field name. You can press Enter to accept the field name suggestion, which should be: - -```text -post_hash -``` - -Press N to decline adding another field to the entry. - -Then use the arrow keys to deselect Update, but leave Delete selected. It should look as follows: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? - Update -✔ Delete -``` -::: - -Once that is done, press Enter to generate a delete function for the **`comment`** entry type. - -You should then see: - -::: output-block -```text -Entry type "comment" scaffolded! - -Add new collections for that entry type with: - - hc scaffold collection -``` -::: - -The scaffolding will now have both added the `comment` entry type, and added a bunch more very useful code to our app using the native Holochain affordance of links. Links allow us to create paths that agents can follow to find associated content. So, the scaffolding not only added a reference to the post in the comment's entry, but it also added code such that when a comment is added, a link from the post back to the comment will also be created. If you want to see some of that code, take a look at the `dnas/forum/zomes/integrity/posts/src/lib.rs` file and you should see right near the top that a function has been created for validating the creation of a `post_to_comments` link. Similarly, other validation functions related to the deletion of those links follow after. - -!!! dig-deeper How links are stored and retrieved in a Holochain app - -What exactly is a link? Where is it stored? How does it work? And what do they let us do that we couldn't do otherwise? - -Links enable us to build a graph of references from one piece of content to other pieces of content in a DHT and then to navigate that graph. This is important because without some sort of trail to follow, it is infeasible to just "search for all content" thanks to the address space (all possible hashes) being so large and spread out across machines that iterating through tme all could take millions of years. - -By linking from known things to unknown things, we enable the efficient discovery and retrieval of related content in our hApp. - -**Storage**: When an agent creates a link between two entries, a `CreateLink` action is written to their source chain. A link is so small that there's no entry for the action. It simply contains the address of the base, the address of the target, the link type (which describes the relationship), and an optional tag which contains a small amount of application-specific information. The base and target can be any sort of DHT address --- an `EntryHash`, an `ActionHash`, or an `AgentPubKey`. But there doesn't actually need to be any data at that base, which is useful for referencing data that exists in another hash-based data store outside the DHT. - -After storing the action in the local source chain, the agent then publishes the link to the DHT, where it goes to the peers who are responsible for storing the base address and gets attached to the address as metadata. - -**Lookup**: To look up and retrieve links in a Holochain app, agents can perform a `get_links` query on a base DHT address. This operation involves asking the DHT peers responsible for that address for any link metadata of a given link type attached to it, with an optional "starts-with" query on the link tag. The peers return a list of links matching the query, which contain the addresses of the targets, and the link types and tags. The agent can then retrieve the actual target data by performing a [`get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) query on the target address, which may be an `EntryHash`, `ActionHash`, or `AgentPubKey` (or an empty result, in the case of data that doesn't exist on the DHT). - -For more information and examples, read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/). - -!!! - -### 4.7. Scaffold a collection - -Now, let's create a collection that can be used to retrieve all the posts. A collection creates a link type for referring to the collected entry type (similarly to how a link type was created for linking from posts to comments), but collections also create an 'anchor' --- a small string --- as the base for the link so we can find all the items in the collection by starting from the anchor's known hash. - -To create a collection, type: - -```shell -hc scaffold collection -``` - -You should then see: - -::: output-block -```text -Collection name (snake_case, eg. "all_posts"): › -``` -::: - -Enter: - -```text -all_posts -``` - -and press Enter. You should then see: - -::: output-block -```text -? Which type of collection should be scaffolded? › -❯ Global (get all entries of the selected entry types) - By author (get entries of the selected entry types that a given author has created) -``` -::: - -Select **`Global`** and press Enter. You should then see: - -::: output-block -```text -? Which entry type should be collected? › -❯ Post - Comment -``` -::: - -Select **`Post`** and press Enter. You should then see: - -::: output-block -```text -Collection "all_posts" scaffolded! - -At first, the UI for this application is empty. If you want the newly scaffolded collection to be the entry point for its UI, import the element in `ui/src/App.svelte`: - - import AllPosts from './forum/posts/AllPosts.svelte'; - -And use the element in the `<div id="content" />` block by adding in this: - - <div id="content"><</AllPosts></div> -``` -::: - -These instructions tell us that if we want to include this generated UI component in the user interface of our hApp, we need to do some manual work: - - 1. Import the component, and - 2. Tell the UI to display the component. - -!!! dig-deeper How a collection is implemented - -We already explored how links make data in the DHT discoverable by connecting known DHT base addresses to unknown addresses. Essentially every address becomes an anchor point to hang a collection of links from. - -But there's one remaining problem: _where do you start?_ When someone starts their app for the first time, the only DHT base addresses they know about are their public key, the DNA hash, and the few actions and entries on their source chain. There's no obvious way to start discovering other people's data yet. - -This is where **collections** help out. A collection is just a bunch of links on a base address that's easy to find --- typically the address is hard-coded in the coordinator zome's code as the hash of a string such as `"all_posts"`. It's easy to get the links, because their base address is right there in the code. - -This pattern, which we call the "anchor pattern", is so useful that it's built right into Holochain's SDK --- integrity zomes that use it will have all the necessary entry and link types automatically defined, and coordinator zomes that use it will have functions that can retrieve anchors and the links attached to them. The scaffolded code uses this implementation behind the scenes. - -The built-in implementation is actually a simplification of a more general pattern called "paths", which is also built into the SDK. With paths, you can create trees of linked anchors, allowing you to create and query hierarchical structures. This can be used to implement categories, granular collections (for example, "all posts" → "all posts created in 2023" → "all posts created in 2023-05" → "all posts created on 2023-05-30"), and indexes for type-ahead search (for example, "all usernames" → "all usernames starting with 'mat'" → "all usernames starting with 'matt'"). What the SDK calls an `Anchor` is actually a tree with a depth of two, in which the root node is two empty bytes. - -Hierarchical paths serve another useful purpose. On the DHT, where every node is tasked with storing a portion of the whole data set, some anchors could become "hot spots" --- that is, they could have thousands or even millions of links attached to them. The nodes responsible for storing those links would bear a disproportionate data storage and serving burden. - -The examples of granular collections and type-ahead search indexes breaks up those anchors into increasingly smaller branches, so that each leaf node in the tree --- and hence each peer --- only has to store a small number of links. - -The scaffolding tool doesn't have any feature for building anchors and trees beyond simple one-anchor collections, but if you'd like to know more, you can read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/) and the SDK reference for [`hash_path`](https://docs.rs/hdk/latest/hdk/hash_path/index.html) and [`anchor`](https://docs.rs/hdk/latest/hdk/hash_path/anchor/index.html). - -!!! - -Before you get started editing the UI, it's helpful to be able to actually run the scaffolded applciation. That way, you can watch changes take effect in real-time as you make them. So the next section will walk you through launching the application the tooling that's available there, and then in the section after that, we'll begin working with the `.svelte` files to build the UI. - -### 4.8. Run your application in dev mode - -At this stage, we'll incorporate some of the UI components that have been scaffolded by the scaffolding tool into our main application interface. Our aim here is to make all the functionality of our forum application accessible from a single, unified interface. We'll use Svelte to accomplish this, as it is the framework that we have chosen for the UI layer of our application. - -Start the forum hApp in develop mode from the command line: go to your terminal and, from the root folder (`my_forum_app/`), enter: - -```shell -npm start -``` - -!!! info Work in the nix shell -If you are having an issue, make sure that you are still in the nix shell. If not, re-enter `nix develop` first, then type the above command again. And remember that you can always exit nix shell by typing `exit` to get back to your normal shell. -!!! - -When you start the hApp with `npm start`, this launches Holochain in sandbox mode with two agents running that hApp, and opens three windows: - -1. A web browser window with Holochain Playground, a tool that makes visible the various actions that have taken place in our forum hApp. You should be able to see a couple of agents in a DHT, with mostly empty source chains and, correspondingly, a mostly empty graph. -2. An application window with one agent (conductor 0) running the forum hApp. This window lets us take actions as that agent (0, or Alice, if you prefer). -3. Another application window with a second agent (conductor 1) running the forum hApp. This window lets us take actions as the other agent (1, or Bob). - -![Three windows: two agent UIs and a web browser window with the Holochain Playground](/assets/img/get-started/3-two-uis-and-playground.png) - -These application windows allow us to test multiple agents in a Holochain network interacting with one another. It is all running on our one device, but the two conductors behave very much the same as separate agents on different machines would, minus network lag. - -Remember that a **conductor** is a Holochain runtime process executing on your computer. For more details see the [Application Architecture](/concepts/2_application_architecture/) section in the Core Concepts guide. - -These three windows together will let us interact with our hApp as we are building it. - -The Holochain Playground in particular is helpful because it creates visual representations of the data that has been created and the way it relates to other content. Take a look at it and click one of the two items in the **DHT Cells** window. These are your two agents. When you click one of them, some content gets displayed in the **Source Chain** window. These are the initial actions in that agent's source chain. The arrows point from newer content back to older content. - -From oldest to newest, in the newly created source chains, the records are: - -1. `DNA`, recording the hash of the DNA to be used to validate all subsequent source chain actions, -2. `AgentValidationPkg`, providing proof that this participant is allowed to participate in this hApp ([see more](https://www.holochain.org/how-does-it-work/) in Holochain: How does it work?), -3. A `Create` action which records the author's `AgentID`, which is their public key and serves as their ID in the network and its graph database. - -As agents begin writing posts, comments, and links to the DHT, you'll see the following records appear: - -4. `InitComplete`, indicating that all coordinator zomes have had a chance to do initial setup, then -5. Whatever actions the agent takes after that. - -The two application UI windows let you interact with the application and see what is working, what is not working, and how data propagates when we take particular actions. - -At first, each of the UI windows (conductors 0 for Alice and 1 for Bob) include instructions for you to go and examine the scaffolded UI elements by looking at the contents in the folder `ui/src///`, where `` and `` are generic placeholders for your DNA (`forum`) and zome (`post`). - -### 4.9. Integrate the generated UI elements - -Thus far, seven different UI components should have been generated as `.svelte` files in the `ui/src/forum/posts/` folder. Note that for ease of development, the sandbox testing environment live-reloads the UI as you edit UI files. So don't quit the process you started with `npm start`; instead, **open a new terminal window**. Then navigate to the root folder of your hApp (`my_forum_app/`) and list the files in `ui/src/forum/posts/` by entering: - -```shell -ls ui/src/forum/posts/ -``` - -You should see seven different `.svelte` files, plus a `types.ts` file: - -::: output-block -```text -AllPosts.svelte CreateComment.svelte PostDetail.svelte -CommentDetail.svelte CreatePost.svelte types.ts -CommentsForPost.svelte EditPost.svelte -``` -::: - -The next step is to edit the UI files in the text editor or integrated development environment of your choice to add scaffolded components and build a fully featured UI. To integrate all of these generated UI elements, you'll need to add them to `App.svelte` file located in the `ui/src/` folder, or to some other `.svelte` file that eventually gets included in `App.svelte`. - -If you don't yet have path commands for opening files in your prefered IDE, there are instructions for [VSCode/VSCodium](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line), [Sublime Text](https://www.sublimetext.com/docs/command_line.html#setup) and [WebStorm](https://www.jetbrains.com/help/webstorm/working-with-the-ide-features-from-command-line.html#5d6e8844). Going forward in this tutorial, we are going to use the `code` command when we mean for you to open files in your IDE, but you should substitute a different command (ex: `subl`, `vim`, `emacs` etc.) for `code` if you are using a different editor. - -Open the `App.svelte` file with your preferred IDE. - -```shell -code ui/src/App.svelte -``` - -Your `App.svelte` file will have three sections: - -1. a script section, -2. a main section containing a markup template, and -3. a style section containing a stylesheet template. - -!!! dig-deeper Detailed breakdown of `App.svelte` - -#### ` -``` - -This section contains the JavaScript/TypeScript code for the component. It imports various dependencies needed to build a single-page web app: - -* `svelte` is the Svelte engine itself, and its `onMount` function lets you register a handler to be run when the component is initialized, while `setContext` lets you pass data to be used in the rendering of the component. -* `@holochain/client` is the Holochain client library; first we load in some useful Holochain-related TypeScript types, followed by the client object itself. -* `@mwc/material-circular-progress` is just a UI component that gives us a spinner when something is loading. -* `./contexts` is generated by the scaffolding tool. It just contains a constant, the app-wide name for the 'context' that makes the Holochain client accessible to all components. In Svelte, a context is a state shared across components. - -After importing dependencies, it does some initial setup. This is run when the component file is imported --- in this case the component, `App.svelte`, is the main component for the entire application, and it's imported into `main.ts` where it's 'mounted' (more on mounting in a moment). - -Next some variables are instantiated: one to hold the Holochain client that connects to the hApp backend via the conductor, and one to keep track of whether the client is connected yet. (This variable will be used to show a loading spinner while it's still connecting.) - -**Take note of the line that starts with `$:`**. This is a special Svelte label that turns regular variables into **reactive variables**. We won't get too deep into Svelte right now, because this is a tutorial about Holochain, but when a reactive variable changes, Svelte will re-render the entire component. This lets you write a template declaratively, enclosing the reactive variable in `{}` braces, and let Svelte handle the updating of the template wherever the variable changes. - -Finally, there's an `onMount` handler, which is run when the component is first displayed. The handler currently does one thing: it connects to the hApp backend via the conductor, waits until the connection is establised, sets `loading` to false, and adds the resulting client connection to the context so that all components can access it. - -#### `
` section - -```svelte -
- {#if loading} -
- -
- {:else} -
-

EDIT ME! Add the components of your app here.

- - Look in the ui/src/DNA/ZOME folders for UI elements that are generated with hc scaffold entry-type, hc scaffold collection and hc scaffold link-type and add them here as appropriate. - - For example, if you have scaffolded a "todos" dna, a "todos" zome, a "todo_item" entry type, and a collection called "all_todos", you might want to add an element here to create and list your todo items, with the generated ui/src/todos/todos/AllTodos.svelte and ui/src/todos/todos/CreateTodo.svelte elements. - - So, to use those elements here: -
    -
  1. Import the elements with: -
    -import AllTodos from './todos/todos/AllTodos.svelte';
    -import CreateTodo from './todos/todos/CreateTodo.svelte';
    -        
    -
  2. -
  3. Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>.
  4. -
-
- {/if} -
-``` - -This section is a template for the displayable content of the main app component. Using an `{#if}` block to test whether the reactive variable `loading` is true, this section displays a spinner until the backend can be accessed. Once the UI is connected to the backend, it shows some boilerplate text telling you to add something meaningful to the template. - -#### ` -``` - -This section is a template for the CSS styles that get applied to the HTML in the `
` section of the component. You can also use reactive variables here, and the styling will update whenever the variables change. These scaffolded styles set the component up with some basic layout to make it readable at small and large window sizes. - -**All Svelte components follow this general pattern**. `App.svelte` has special status as the root component, but otherwise it's just like any other component. - -!!! - -First you'll be adding a list of posts to the app, which means the components called `AllPosts.svelte` needs to be imported. - -At the top of the file, there is a list of scripts that are imported. Following the instructions that the scaffolding tool and the two conductor windows gave you, copy the following text and paste it into the script block of the `App.svelte` file, on the line below `import { clientContext } from './contexts';` - -```typescript -import AllPosts from './forum/posts/AllPosts.svelte'; -``` - -Next, add the component to the markup template in the `
` section of the file, where the "EDIT ME!" content now lives. Remove everything inside the `div` element that starts with this tag: - -:::output-block -```svelte -
-``` -::: - -and replace it with this line: - -```svelte - -``` - -Your `
` block should now look like this: - -```svelte -
- {#if loading} -
- -
- {:else} -
- -
- {/if} -
-``` - -!!! info Svelte component tags -The `AllPosts` element is obviously not standard HTML. In Svelte, each component has a correspondingly named custom element that will get replaced by the rendered component's markup wherever it appears in another component's template. -!!! - -Save that file and take a look again at the two UI windows. They should both say 'No posts found'. - -![A UI showing the AllPosts component, which says 'No posts found'](/assets/img/get-started/4-no-posts-found.png) - -Let's fix that by adding the post creation component to the UI so we can add our first post. Import the `CreatePost.svelte` component by adding this line in the script section, just below the `AllPosts` component you previously imported: - -```typescript -import CreatePost from './forum/posts/CreatePost.svelte'; -``` - -Add this new component to the `
` block above the component you added: - -```svelte - -``` - -Now your `
` block should look like this: - -```svelte -
- {#if loading} -
- -
- {:else} -
- - -
- {/if} -
-``` - -Save the file and switch to one of the two conductor windows. You should now see a post form. - -![The UI after adding the CreatePost component](/assets/img/get-started/5-create-post-component.png) - -Type something into one of the two conductor windows like: - -* Title: `Hi from Alice` -* Content: `Hello Bob!` - -and then press the "Create Post" button. - -You'll immediately notice that the `AllPosts` component has changed from saying "No posts found" to showing the newly created post. And if you take a look at the Holochain Playground window, you will see that two new actions have been created. If you click the `App` element that's appeared in Alice's source chain, it will pull up some details in the Entry Contents section, including the title and content of Alice's forum post. Note the hash of that entry (top of the Entry Contents window). Then click on the `Create` action that's pointing toward that `App` entry in the source chain. If you look back at the contents window, you will see that it is now sharing the contents of the action. And if you look down the list a bit, you will see the hash of the entry for the first post. - -![The Holochain playground showing a single agent's source chain, containing the actions that create a post, as well as the transformations in the DHT that resulted from these actions](/assets/img/get-started/6-playground-first-post.png) - -!!! dig-deeper Relationships in a source chain versus relationships in the DHT - -At this point, in our DHT graph it should look like we have two different agents and then a separate floating entry and action. But we know that the new post is associated with a source chain which is associated with an agent. So why aren't they connected on the DHT? - -A source chain merely serves as a history of one agent's attempts to manipulate the state of the graph database contained in the DHT. It's useful to think of the DHT as a completely separate data store that doesn't necessarily reflect agent-to-entry relationships unless you explicitly create a link type for them. - -For the purpose of this hApp, we're not interested in agent-to-posts relationships, so it's fine that they're not linked. But if you wanted to create a page that showed all posts by an author, that's when you might want to scaffold that link type. `hc scaffold collection` will do this for you if you choose a by-author collection, and will also create a `get_posts_by_author` function. - -!!! - -You may also notice that only Alice's UI showed the new post, while Bob's didn't. Just as with a traditional web app, database changes don't automatically send out a notification to everyone who is interested. (Alice's UI sees the changes because it knows how to update its own state for local changes.) You can create this functionality using a feature called [signals](/concepts/9_signals/), but let's keep things simple for now. Right-click anywhere in Bob's UI then choose "Reload" from the menu, and you'll see that the changes have been copied from Alice's app instance to Bob's --- all without a database server in the middle! - -Let's edit that post. In Alice's UI window, click the edit adjacent to the post content (it should look like a pencil icon). The post content will be replaced by an editing form. - -Now alter the content a bit. Maybe change it from `Hello Bob!` to `Hello, World!` and click "Save". - -![The UI of one agent, showing a post about to be edited](/assets/img/get-started/7-edit-post.png) - -That should update the post (at least for Alice). Bob's UI will show the updated version the next time it's reloaded. - -If you look at the Holochain Playground, you should see that the update was added to Alice's source chain. Specifically, it created: - -1. a new entry (with our `Hello, World!` text), -2. an `Update` action that indicated this entry is to replace the original entry, and -3. a `CreateLink` action that connects the original create action to the update action. - -![The Holochain playground, showing the source chain of the agent who edited the post along with new data in the DHT reflecting the edit](/assets/img/get-started/8-playground-after-edits.png) - -As explained [previously](#crud-create-read-update-delete), the original forum post already has a 'link' of sorts pointing from its action to the `Update` action, which can be accessed when the original is retrieved. The extra link created by the `CreateLink` action is optional --- it merely speeds up retrieval when an action has been edited many times and has a long chain of update links, by allowing you to jump to the end of the chain. In the screenshot above, the link is highlighted in the DHT pane. - -Now it's time to add commenting to your app. - -Previously, you added new components to the `App.svelte` component. That made sense because posts were a global data type. But comments are related to a post, so from now on you'll be modifying the `PostDetail.svelte` component instead. - -Open up `PostDetail.svelte` in your IDE: - -```shell -code ui/src/forum/posts/PostDetail.svelte -``` - -Just as before, first you'll need to import the components near the top of the file (just after the line that imports `EditPost.svelte`): - -```typescript -import CreateComment from './CreateComment.svelte'; -import CommentsForPost from './CommentsForPost.svelte'; -``` - -Further down the file, in the template block, add the components' elements to the template. Put them both before the closing `
` tag. - -Here, the comment components need to know what post they're related to. The post hash is the unique ID for the post, and the comment components' elements both expect a `postHash` attribute. This hash is available in the `PostDetail` component as a variable of the same name, so it can be passed to the comment widgets. - -```svelte - - -``` - -Save the file, then go back to the UI windows to see the changes. Try typing in a comment or two, then deleting them. (You may need to refresh the UI windows to see the changes to the content.) Watch the Playground --- see how the authors' source chains and the graph in the DHT change as new information is added. The deleted comments are still there and can be accessed by code in your zomes if needed, but neither the application backend (that is, the functions defined in the coordinator zome) nor the UI have the capacity to show them. - -![One UI window with the comment components added, with the Playground in the background showing a populated DHT](/assets/img/get-started/10-comment-components.png) - -## 5. Deploying your Holochain application - -### 5.1 Packaging - -Now that you've built an application, it's time to get it into other people's hands. You specify the components of a hApp using manifest files, written in [YAML](https://yaml.org/), and the `hc` CLI looks for them when it's building a distributable hApp for you. If you look in the `workdir` folder: - -```shell -ls workdir -``` - -You'll see that the scaffolding tool has generated two manifest files for you: - -:::output-block -```text -happ.yaml web-happ.yaml -``` -::: output-block - -The first step is to package your app: - -```shell -npm run package -``` - -This command does a number of things: - -1. Triggers the Rust compiler to build the zomes, -2. Uses the `hc` CLI too to combine the built zomes and the DNA manifest into a `.dna` file, -3. Combines all the DNAs and the hApp manifest into a `.happ` file, -3. Builds the UI and compresses it into a `.zip` file, and -4. Combines the hApp file, the UI zip, and the web hApp manifest into a `.webhapp` file. - -Of course, this application only has one zome and one DNA, but more complex apps may have many of each. - -Now you'll see some new files in `workdir`: - -```shell -ls workdir -``` - -::: output-block -```text -happ.yaml my_forum_app.happ my_forum_app.webhapp web-happ.yaml -``` -::: output-block - -The packed app is now ready for deployment to a Holochain runtime. - -### 5.2 Runtimes - -In the centralized world, deployment is usually achieved by Continuous Integration (CI) automation that builds up code changes and sends them to whatever server or cloud-based platform you're using. In the decentralized world of Holochain, deployment happens when end-users download and run your hApp in the Holochain runtime. - -From the end-user perspective there are currently there are two ways to go about this, both of which will feel familiar: - -1. Download Holochain's official Launcher runtime and install the app from its app store or the filesystem. -2. Download an your app as its own standalone desktop executable, as they would any other application for their computer. - -#### 5.2.1 Launcher, the multi-app runtime - -Holochain's official end-user runtime is the [Holochain Launcher](https://github.com/holochain/launcher). It allows people to install apps from a built-in app store or from the filesystem. Installed apps can then be launched from a friendly UI. Note that the app store is itself a distributed Holochain application which provides details on applications that are available for download. As a developer you can either go through a simple publishing process and add your app to the app store where it will be available for installation by all people who use the Launcher, or you can share your application directly with end-users through your own channels and they can install it into their Holochain Launcher manually from the file system. - -You can try this latter approach immediately by downloading and running the Launcher! - -The steps for publishing an app to the Launcher's app store are documented in the Github repository of the Holochain Launcher [here](https://github.com/holochain/launcher#publishing-and-updating-an-app-in-the-devhub). - -#### 5.2.2 Standalone executable - -If you prefer to distribute your app as a full standalone executable, you will need to bundle the Holochain runtime and your app together and take care of the necessary interactions between them. Because Holochain itself is really just a set of Rust libraries, you can of course build your own application that uses those libraries, but that's a fair amount of work. Currently there are two much simpler paths for doing this: using either the [Electron](https://www.electronjs.org/) or [Tauri](https://tauri.app/) frameworks, both of which can generate cross-platform executables from standard web UIs. These frameworks also support inclusion of additional binaries, which in our case are the [holochain conductor](https://docs.rs/holochain/latest/holochain/) and the [lair keystore](https://docs.rs/lair_keystore/latest/lair_keystore/). Though there is quite a bit of complexity in setting things up for these frameworks, all the hard work has already been done for you: - -* **Electron**: Refer to the community-supported [electron-holochain-template](https://github.com/lightningrodlabs/electron-holochain-template/) repo. -* **Tauri**: See the officially supported [holochain-kanagroo](https://github.com/holochain-apps/holochain-kangaroo) repo. - -Both of these are GitHub template repos with detailed instructions on how to clone the repos and add in your UI and DNA, as well as build and release commands that will create the cross-platform executables that you can then deliver to your end users. - -!!! note Code Signing -For macOS and Windows, you will probably also want to go through the process of registering as a developer so that your application can be "code-signed". This is needed so that users don't get the "unsigned code" warnings when launching the applications on those platforms. Both of the above templates include instructions for CI automation to run the code-signing steps on release once you have acquired the necessary certificates. -!!! + ## Update an Entry -- `update_entry` simple example +Update an entry by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html){target=_blank}: -### Efficiently querying updates +```rust +use hdk::prelude::*; -- Trading storage space for reduced lookup time -- but then how do you find the latest entry? - - get action, get all "updates" off it - - get update, get all updates off it - (cont. until there are no more updates) +let movie2 = Movie { + title: "The Good, the Bad, and the Ugly", + director: "Sergio Leone" + imdb_id: Some("tt0060196"), + release_date: Timestamp::from(Date::Utc("1966-12-23")), + box_office_revenue: 400_000_000, +}; - - link from original entry to each update - - scaffolder +let update_action_hash: ActionHash = update_entry( + create_action_hash, + &EntryTypes::Movie(movie2.clone()), +)?; +``` -- `update_entry` example with linking to each update +### Update Under-the-hood +Calling `update_Entry` does the following: +1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. +2. Writes a `Update` action in the draft commit +3. Runs the validation callback for all Ops in the draft commit. If successful, continues. +4. Publishes the "draft commit" to the source chain for this Cell +5. Publishes all DhtOps included in the source chain commit to their authority agents +6. Returns the `ActionHash` of the Update action + -### Cooperative Updates -- What if multiple agents are allowed to update the same entry? They could potentially make an update at exactly the same time? Or their clocks might be wrong / or falsified! How do know which is the "latest" update? - - You don't -- you either (1) come up with an opinionated definition of latest for that entry type or (2) expose both updates to the user so they can decide which is most meaningful to them - - You could use an "objective" time system to determine the "latest" update, but remember that's simply another opinionated definition of latest -- it may make sense for some use cases, but not for others +### Update Patterns +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. + +You can structure your updates as "list" -- where all updates refer to the ActionHash of the original Create action. + + + +Or you can structure your updates as "chain" -- where all updates refer to the ActionHash of the previous update. + + + + +If you structure your updates as a "chain" you may want to also create links from the original ActionHash to each update in the chain, for easier querying. This effectively trades additional storage space for reduced lookup time. + + +### Choose the Latest Update + +If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the Update action with the most recent timestamp. + +But if multiple agents are permitted to update an entry it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? + +These are two common patterns: +- Use an opinionated deterministic definition of "latest" +- Expose *all* conflicting updates to the user, and let them decide which are meaningful ## Delete an Entry +Delete an entry by calling [`hdk::entry::delete_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html){target=_blank}. + +```rust +use hdk::prelude::*; + +let delete_action_hash: ActionHash = delete_entry( + create_action_hash, +)?; +``` + +This does *not* actually erase data from the source chain or the DHT. Instead a Delete action is committed to the Cell's Source Chain. -- `delete_entry` example +In the future we plan to include a "purge" functionality. This will give Agents permission to actually erase an Entry from the Source Chain and DHT, but not its associated Action. -- No data is actually *removed* from the DHT, instead a Delete action is commited to the source chain with the action hash of the Create action +Remember it is physically impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. -- Note that a "purge" feature is planned for a future update -- notifying agents that they are free to delete some entry, but not the action that references it +### Delete Under-the-hood +Calling `delete_entry` does the following: +1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. +2. Writes a `Delete` action in the draft commit +3. Runs the validation callback for all Ops in the draft commit. If successful, continues. +4. Publishes the "draft commit" to the source chain for this Cell +5. Publishes all DhtOps included in the source chain commit to their authority agents +6. Returns the `ActionHash` of the Delete action -- Remember you can never force anyone to delete any data once they have seen it + +## Entry IDs -## CRUD Libraries +Coming from the centralized database world, you might be expecting an Entry to have a unique ID that can be used to reference it elsewhere. + +Instead, holochain uses hashes to reference content. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. + +To identify the *contents* of an Entry, use the entry's `EntryHash`. Remember that entry contents will collide in the DHT if the exact same entry is published multiple times. + +A common pattern to identify an *instance* of an Entry (i.e. an Entry authored by a specific agent at a specific time) is to use the `ActionHash` of the Create action which created the original entry. This can be a persistant way to identify the entry, even when it is updated, as other agents can query for updates themselves to discover the latest version. + +## Community CRUD Libraries If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer an opinionated and high-level ways to work with entries. -- [hc-cooperative-content](https://github.com/mjbriesbois/hc-cooperative-content) -- [hdk_crud](https://github.com/lightningrodlab/shdk_crud) -- [hc_crud_caps](https://github.com/mjbriesboi/hc_crud_caps) +- [rust-hc-crud-caps](https://github.com/spartan-holochain-counsel/rust-hc-crud-caps){target=_blank} +- [hdk_crud](https://github.com/lightningrodlabs/hdk_crud){target=_blank} +- [hc-cooperative-content](https://github.com/mjbrisebois/hc-cooperative-content){target=_blank} + + +## Reference +- [hdi::prelude::hdk_entry_helper](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html){target=_blank} +- [hdi::prelude::hdk_entry_defs](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html){target=_blank} +- [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html){target=_blank} +- [hdk::prelude::create_entry](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html){target=_blank} +- [hdk::prelude::update_entry](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html){target=_blank} +- [hdi::prelude::delete_entry](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html){target=_blank} From 56318fffe13310a04f7c34c3c7b33c94c157d7e5 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Sat, 23 Dec 2023 12:42:10 -0500 Subject: [PATCH 06/69] chore: revert file formatting --- src/pages/_data/navigation/mainNav.json5 | 111 ++++------------------- 1 file changed, 20 insertions(+), 91 deletions(-) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 885025724..b7f48611b 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -1,111 +1,40 @@ { links: [ - { - title: "Get Started", - url: "/get-started/", - children: [ - { - title: "Dev Tools Setup Tips", - url: "/get-started/install-advanced/", - }, - { - title: "Setup Without Nix", - url: "/get-started/install-without-nix/", - }, - { - title: "Distribute your App", - url: "/get-started/distribute-your-app/", - }, - { - title: "Holochain Upgrade 0.1 → 0.2", - url: "/get-started/upgrade-holochain/", - }, + { title: "Get Started", url: "/get-started/", children: [ + { title: "Dev Tools Setup Tips", url: "/get-started/install-advanced/" }, + { title: "Setup Without Nix", url: "/get-started/install-without-nix/" }, + { title: "Distribute your App", url: "/get-started/distribute-your-app/" }, + { title: "Holochain Upgrade 0.1 → 0.2", url: "/get-started/upgrade-holochain/" }, { title: "Setup For a Local Event", url: "/get-started/at-an-event/" }, - ], + ] }, - { - title: "Learn", - url: "/concepts/1_the_basics/", - children: [ - { - title: "Application Architecture", - url: "/concepts/2_application_architecture/", - }, + { title: "Learn", url: "/concepts/1_the_basics/", children: [ + { title: "Application Architecture", url: "/concepts/2_application_architecture/" }, { title: "Source Chain", url: "/concepts/3_source_chain/" }, { title: "DHT", url: "/concepts/4_dht/" }, { title: "Links and Anchors", url: "/concepts/5_links_anchors/" }, { title: "CRUD Actions", url: "/concepts/6_crud_actions/" }, - { title: "Validation", url: "/concepts/7_validation/" }, - { - title: "Calls and Capabilities", - url: "/concepts/8_calls_capabilities/", - }, + { title: "Validation", url: "/concepts/7_validation/" }, + { title: "Calls and Capabilities", url: "/concepts/8_calls_capabilities/" }, { title: "Signals", url: "/concepts/9_signals/" }, { title: "Countersigning", url: "/concepts/10_countersigning/" }, { title: "Lifecycle Events", url: "/concepts/11_lifecycle_events/" }, - ], + ] }, - { - title: "Build", - url: "/build/1_hello_world/", - children: [ + { title: "Build", url: "/build/1_hello_world/", children: [ { title: "Forum App Tutorial", url: "/build/2_forum_tutorial/" }, { title: "Packaging & Distribution", url: "/build/3_deployment/" }, - { title: "Entries", url: "/build/4_entries" }, - { title: "Links", url: "/build/5_links" }, - { title: "Validation", url: "/build/" }, - { title: "Calls & Capabilities", url: "/build/" }, - { title: "Signals", url: "/build/" }, - { title: "Countersigning", url: "/build/" }, - { title: "Blocking", url: "/build/" }, - { title: "Lifecycle Events", url: "/build/" }, - { title: "Cryptographic Utilties", url: "/build/" }, - { - title: "Advanced DNA Patterns: Public Encrypted Data", - url: "/build/", - }, - { title: "Advanced DNA Patterns: Message Carrier", url: "/build/" }, - { title: "Advanced DNA Patterns: Path Indexing", url: "/build/" }, - { title: "Advanced DNA Patterns: Rivelrous Data", url: "/build/" }, - { - title: "Advanced DNA Patterns: Flagging Unwanted Data", - url: "/build/", - }, - { title: "Advanced DNA Patterns: Pagination", url: "/build/" }, - { - title: "Advanced DNA Patterns: Mutual-Credit Currency", - url: "/build/", - }, - { - title: "Advanced UI Patterns: Lazy Data Availability", - url: "/build/", - }, - { title: "Advanced UI Patterns: Validation Receipts", url: "/build/" }, - ], + { title: "Entries", url: "/build/4_entries" } + ] }, - { - title: "References", - url: "/references/", - children: [ + { title: "References", url: "/references/", children: [ { title: "HDK (Rust)", url: "https://docs.rs/hdk", external: true }, - { - title: "App API reference", - url: "https://docs.rs/holochain_conductor_api/latest/holochain_conductor_api/enum.AppRequest.html", - external: true, - }, - { - title: "Admin API reference", - url: "https://docs.rs/holochain_conductor_api/latest/holochain_conductor_api/enum.AdminRequest.html", - external: true, - }, - { - title: "CLI", - url: "https://github.com/holochain/holochain/tree/main/crates/hc", - external: true, - }, + { title: "App API reference", url: "https://docs.rs/holochain_conductor_api/latest/holochain_conductor_api/enum.AppRequest.html", external: true }, + { title: "Admin API reference", url: "https://docs.rs/holochain_conductor_api/latest/holochain_conductor_api/enum.AdminRequest.html", external: true }, + { title: "CLI", url: "https://github.com/holochain/holochain/tree/main/crates/hc", external: true }, { title: "Glossary", url: "/references/glossary/" }, - ], + ] }, { title: "Get Involved", url: "/get-involved/" }, - ], + ] } From 37f7f03f2b6bb5ba5cf830052b66ac25a07b46bf Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Sat, 23 Dec 2023 13:54:56 -0500 Subject: [PATCH 07/69] feat: add mermaid chart rendering support --- 11ty-extensions/eleventy-transforms.js | 18 ++++++++++++++---- src/pages/_includes/base-layout.njk | 4 ++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/11ty-extensions/eleventy-transforms.js b/11ty-extensions/eleventy-transforms.js index ddbf67f8e..acf2d629f 100644 --- a/11ty-extensions/eleventy-transforms.js +++ b/11ty-extensions/eleventy-transforms.js @@ -37,17 +37,27 @@ module.exports = function(eleventyConfig) { if (this.page.outputPath.endsWith(".html")) { const document = new dom(content); const preBlocks = document.querySelectorAll('pre:has(code)'); + preBlocks.forEach((pre) => { - pre.className += ' hljs-container'; const code = pre.querySelector('code'); const maybeLanguage = code.className.match(/(?<=\blanguage-)[A-Za-z0-9_-]+/); const blockText = he.decode(code.textContent); - if (maybeLanguage) { - code.innerHTML = hljs.highlight(blockText, {language: maybeLanguage[0]}).value; + + if (maybeLanguage[0] === "mermaid") { + // Render as mermaid chart + pre.className += ' mermaid'; + pre.innerHTML = code.innerHTML; + } else if (maybeLanguage) { + // Render as syntax-highlighted code with known language + pre.className += ' hljs-container'; + code.className += ' hljs'; + code.innerHTML = hljs.highlight(blockText, {language: maybeLanguage[0]}).value; } else { + // Render as syntax-highlighted code with autodetected language + pre.className += ' hljs-container'; + code.className += ' hljs'; code.innerHTML = hljs.highlightAuto(blockText).value; } - code.className += ' hljs'; }); return document.innerHTML; } diff --git a/src/pages/_includes/base-layout.njk b/src/pages/_includes/base-layout.njk index d15f03ff0..ff070aef8 100644 --- a/src/pages/_includes/base-layout.njk +++ b/src/pages/_includes/base-layout.njk @@ -13,5 +13,9 @@ {% include "parts/footer.njk" %} {% include "parts/templates.njk" %} {% include "widgets/unsupported-brower-warning.njk" %} + \ No newline at end of file From 76ab4970f8ca7a121fdc48012f143b09bd077dc9 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Sat, 23 Dec 2023 13:55:08 -0500 Subject: [PATCH 08/69] feat: uncomment mermaid charts of entry update strategies --- src/pages/build/4_entries.md | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index fc2e8ae1c..9281412a7 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -165,34 +165,24 @@ Holochain gives you this `update_entry` function, but is somewhat unopinionated You can structure your updates as "list" -- where all updates refer to the ActionHash of the original Create action. - A[Create] + C[Update 2] --> A[Create] + D[Update 3] --> A[Create] + E[Update 4] --> A[Create] ``` - -TODO render this as mermaid graph --> - -Or you can structure your updates as "chain" -- where all updates refer to the ActionHash of the previous update. - - - A[Create] + C[Update 2] --> B[Update 1] + D[Update 3] --> C[Update 2] + E[Update 4] --> D[Update 3] ``` -TODO render this as mermaid graph --> - If you structure your updates as a "chain" you may want to also create links from the original ActionHash to each update in the chain, for easier querying. This effectively trades additional storage space for reduced lookup time. From b5a3cc6c429f8c8b4f732715b1dc48635147765d Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Fri, 5 Jan 2024 11:06:07 -0500 Subject: [PATCH 09/69] chore: minor tweaks --- src/pages/build/4_entries.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 9281412a7..62049f6c8 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -30,6 +30,8 @@ tocData: href: reference --- +An Entry is structured data written to an Agent's source chain via a CreateEntry or UpdateEntry Action. + ## Define an Entry Type An EntryType can be any Rust struct that `serde` can serialize and deserialize. @@ -227,13 +229,13 @@ Calling `delete_entry` does the following: ## Entry IDs -Coming from the centralized database world, you might be expecting an Entry to have a unique ID that can be used to reference it elsewhere. +Coming from centralized software architectures, you might be expecting an Entry to have a unique ID that can be used to reference it elsewhere. -Instead, holochain uses hashes to reference content. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. +Instead, Holochain uses hashes to reference content. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. -To identify the *contents* of an Entry, use the entry's `EntryHash`. Remember that entry contents will collide in the DHT if the exact same entry is published multiple times. +To identify the *contents* of an Entry, use the entry's `EntryHash`. Remember that identical entry contents will collide in the DHT. -A common pattern to identify an *instance* of an Entry (i.e. an Entry authored by a specific agent at a specific time) is to use the `ActionHash` of the Create action which created the original entry. This can be a persistant way to identify the entry, even when it is updated, as other agents can query for updates themselves to discover the latest version. +A common pattern to identify an *instance* of an Entry (i.e. an Entry authored by a specific agent at a specific time) is to use the `ActionHash` of the Create action which created the original entry. This can be a persistent way to identify the entry, even when it is updated, as other agents can query for updates themselves to discover the latest version. ## Community CRUD Libraries From 773f15ba9d2e37835438339e42591d7fed906b78 Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 8 Jan 2024 15:18:03 -0700 Subject: [PATCH 10/69] revert: mermaid rendering code extracted into branch feat/mermaid-rendering --- 11ty-extensions/eleventy-transforms.js | 18 ++++-------------- src/pages/_includes/base-layout.njk | 4 ---- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/11ty-extensions/eleventy-transforms.js b/11ty-extensions/eleventy-transforms.js index acf2d629f..ddbf67f8e 100644 --- a/11ty-extensions/eleventy-transforms.js +++ b/11ty-extensions/eleventy-transforms.js @@ -37,27 +37,17 @@ module.exports = function(eleventyConfig) { if (this.page.outputPath.endsWith(".html")) { const document = new dom(content); const preBlocks = document.querySelectorAll('pre:has(code)'); - preBlocks.forEach((pre) => { + pre.className += ' hljs-container'; const code = pre.querySelector('code'); const maybeLanguage = code.className.match(/(?<=\blanguage-)[A-Za-z0-9_-]+/); const blockText = he.decode(code.textContent); - - if (maybeLanguage[0] === "mermaid") { - // Render as mermaid chart - pre.className += ' mermaid'; - pre.innerHTML = code.innerHTML; - } else if (maybeLanguage) { - // Render as syntax-highlighted code with known language - pre.className += ' hljs-container'; - code.className += ' hljs'; - code.innerHTML = hljs.highlight(blockText, {language: maybeLanguage[0]}).value; + if (maybeLanguage) { + code.innerHTML = hljs.highlight(blockText, {language: maybeLanguage[0]}).value; } else { - // Render as syntax-highlighted code with autodetected language - pre.className += ' hljs-container'; - code.className += ' hljs'; code.innerHTML = hljs.highlightAuto(blockText).value; } + code.className += ' hljs'; }); return document.innerHTML; } diff --git a/src/pages/_includes/base-layout.njk b/src/pages/_includes/base-layout.njk index ff070aef8..d15f03ff0 100644 --- a/src/pages/_includes/base-layout.njk +++ b/src/pages/_includes/base-layout.njk @@ -13,9 +13,5 @@ {% include "parts/footer.njk" %} {% include "parts/templates.njk" %} {% include "widgets/unsupported-brower-warning.njk" %} - \ No newline at end of file From 9f8cc192d463e7aae6f3ad072b4906e2b1462b6e Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 8 Jan 2024 15:39:03 -0700 Subject: [PATCH 11/69] chore: remove changes from other PR --- src/pages/_data/navigation/mainNav.json5 | 2 - src/pages/build/2_forum_tutorial.md | 1165 ---------------------- src/pages/build/3_deployment.md | 92 -- 3 files changed, 1259 deletions(-) delete mode 100644 src/pages/build/2_forum_tutorial.md delete mode 100644 src/pages/build/3_deployment.md diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index b7f48611b..855379c84 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -22,8 +22,6 @@ ] }, { title: "Build", url: "/build/1_hello_world/", children: [ - { title: "Forum App Tutorial", url: "/build/2_forum_tutorial/" }, - { title: "Packaging & Distribution", url: "/build/3_deployment/" }, { title: "Entries", url: "/build/4_entries" } ] }, diff --git a/src/pages/build/2_forum_tutorial.md b/src/pages/build/2_forum_tutorial.md deleted file mode 100644 index 0b3b05cd1..000000000 --- a/src/pages/build/2_forum_tutorial.md +++ /dev/null @@ -1,1165 +0,0 @@ ---- -title: "Zero to built: creating a forum app" -tocData: - - text: 1. Scaffolding a hApp - href: 1-scaffolding-a-happ - - text: 2. Select user interface framework - href: 2-select-user-interface-framework - - text: 3. Set up Holonix development environment - href: 3-set-up-holonix-development-environment - - text: 4. Scaffold a DNA - href: 4-scaffold-a-dna - - text: 5. Scaffold a zome - href: 5-scaffold-a-zome - - text: 6. Scaffold entry types - href: 6-scaffold-entry-types - - text: 7. Scaffold a collection - href: 7-scaffold-a-collection - - text: 8. Run your applicaiton in dev mode - href: 8-run-your-application-in-dev-mode - - text: 9. Integrate the generated UI elements - href: 9-integrate-the-generated-ui-elements ---- - -First, navigate back to the folder where you want to keep your Holochain applications. If you followed our suggestion, you can get back to it by typing: - -```shell -cd ~/Holochain -``` - -Next up, we'll walk you through creating a forum application from scratch using Holochain's scaffolding tool, step-by-step. This forum application will enable participants to share text-based posts and to comment on those posts. - -Each post will have a title and content, and authors will be able to edit --- or update --- their posts. However, they won't be able to delete them. - -Each comment will be a reply to a particular post, will be limited in length to 140 characters, and will be able to be deleted but not updated. - -!!! info Validation tutorial coming soon -A future update to this guide will implement the above constraints as validation rules. For now, we'll just scaffold enough code to get a working UI. **Check back soon** to get the whole tutorial! -!!! - -We'll create a couple of other things along the way that will enable people to find these posts and comments, but we'll cover those things when we get there. - -The good news is that the Holochain scaffolding tool will do a lot of the heavy lifting in terms of generating folders, files, and boilerplate code. It will walk you through each step in the hApp generation process. In fact, the scaffolding tool does so much of the work for you that many people have commented that 90% or more of the time spent writing a Holochain app is focused on building out the front-end user interface and experience. - -First, let's use the scaffolding tool to generate the basic folders and files for our hApp. - -### 1. Scaffolding a hApp {#1-scaffolding-a-happ} - -To start, run the following command in your terminal: - -```shell -nix run github:/holochain/holochain#hc-scaffold -- web-app -``` - -You should then see: - -::: output-block -```text -? App name (no whitespaces): -``` -::: - -Enter the name of your forum application using snake_case. Enter: - -```text -my_forum_app -``` - -### 2. Select user interface framework - -You'll then be prompted to choose a user interface (UI) framework for your front end. - -For this example, use the arrow keys to choose `Svelte` and press Enter. - -### 3. Set up Holonix development environment - -Next, you'll be asked if you want to set up the Holonix development environment for the project. This allows you to enter a shell that has all the right tools and libraries for the version of Holochain that your code was generated for. - -Choose `Yes (recommended)` and press Enter. - -You should see: - -::: output-block -```text -Setting up nix development environment... -``` -::: - -along with some details of what is being added. Follow the instructions to set up the development environment for your hApp and continue to scaffold more of its elements. - -First, enter the hApp project folder: - -```shell -cd my_forum_app -``` - -Just to get an overview of what your first scaffold command set up for you, you can check the contents of that `my_forum_app` folder by typing: - -```shell -ls -``` - -It should look like it has set up a similar set of folders and configuration files to those you saw in the "Hello, World!" hApp. - -Now, fire up the nix development shell, which makes all scaffolding tools and the Holochain binaries directly available from the command line, by entering: - -```shell -nix develop -``` - -After a short while of installing packages, you should see: - -::: output-block -```text -Holochain development shell spawned. Type exit to leave. -``` -::: - -As it says, if you want to leave the nix development shell at any time, you can type `exit`. This will take you back to your familiar shell without any of the special Holochain dependencies. When you want to re-enter it, navigate to the `my_forum_app` folder and type `nix develop` again. But for now, install the Node Package Manager (npm) dependencies with: - -```shell -npm install -``` - -These dependencies are used by various tools and assets --- the scaffolded tests, the UI, and various development activities like spawning apps for testing. - -When that finishes, you should see some text that ends with something like: - -::: output-block -```text -added 371 packages, and audited 374 packages in 1m - -37 packages are looking for funding - run 'npm fund' for details -found 0 vulnerabilities -``` -::: - -If you see something like that, you've successfully downloaded the NPM dependencies for the UI and for building your app. - -Next up, you're going to start creating the foundational building block of any Holochain app: its DNA. - -!!! dig-deeper Scaffolding subcommands - -To get an overview of the subcommands that `hc scaffold`` makes available to you, type: - -```shell -hc scaffold --help -``` - -You should see something like: - -::: output-block -```text -holochain_scaffolding_cli 0.1.8 -The list of subcommands for `hc scaffold` - -USAGE: - hc-scaffold - -FLAGS: - -h, --help Prints help information - -V, --version Prints version information - -SUBCOMMANDS: - collection Scaffold a collection of entries in an existing zome - dna Scaffold a DNA into an existing app - entry-type Scaffold an entry type and CRUD functions into an existing zome - example - help Prints this message or the help of the given subcommand(s) - link-type Scaffold a link type and its appropriate zome functions into an existing zome - template Set up the template used in this project - web-app Scaffold a new, empty web app - zome Scaffold one or multiple zomes into an existing DNA -``` -::: - -You can get help on every one of these subcommands and its parameters by typing `hc scaffold --help`. -!!! - -!!! info Backing out of a mistake -A quick note: if while scaffolding some part of your hApp, you realize you've made a mistake (a typo or wrong selection for instance), as long as you haven't finished scaffolding that portion, **you can stop the current step** by using Ctrl+C on Linux or Command+C on macOS. -!!! - -### Scaffold a DNA - -A DNA folder is where you will put the code that defines the rules of your application. You're going to stay in the `my_forum_app/` root folder and, with some simple commands, the scaffolding tool will do much of the creation of relevant folders and files for you. - -!!! dig-deeper DNAs: Context and Background {#about-dnas} - -#### Why do we use the term DNA? - -In Holochain, we are trying to enable people to **choose to participate in coherent social coordination**, or interact meaningfully with each other online without needing a central authority to define the rules and keep everyone safe. To do that, we are borrowing some patterns from how biological organisms are able to coordinate coherently even at scales that social organisations such as companies or nations have come nowhere close to. In living creatures like humans, dolphins, redwood trees, and coral reefs, many of the cells in the body of an organism (trillions of the cells in a human body, for instance) are each running a (roughly) identical copy of a rule set in the form of DNA. - -This enables many different independent parts (cells) to build relatively consistent superstructures (a body, for instance), move resources, identify and eliminate infections, and more --- all without centralized command and control. There is no "CEO" cell in the body telling everybody else what to do. It's a bunch of independent actors (cells) playing by a consistent set of rules (the DNA) coordinating in effective and resilient ways. - -A cell in the muscle of your bicep finds itself in a particular context, with certain resources and conditions that it is facing. Based on those signals, that cell behaves in particular ways, running relevant portions of the larger shared instruction set (DNA) and transforming resources in ways that make sense for a bicep muscle cell. A different cell in your blood, perhaps facing a context where there is a bacterial infection, will face a different set of circumstances and consequently will make use of other parts of the shared instruction set to guide how it behaves. In other words, many biological organisms make use of this pattern where **many participants run the same rule set, but each in its own context**, and that unlocks a powerful capacity for coherent coordination. - -Holochain borrows this pattern that we see in biological coordination to try to enable similarly coherent social coordination. However, our focus is on enabling such coherent social coordination to be "opted into" by the participants. We believe that this pattern of being able to choose which games you want to play --- and being able to leave them or adapt them as experience dictates --- is critical to enabling individual and collective adaptive capacity. We believe that it may enable a fundamental shift in the ability of individuals and communities to sense and respond to the situations that they face. - -To put it another way: if a group of us can all agree to the rules of a game, then together we can play that game. - -All of us opting in to those rules --- and helping to enforce them --- enables us to play that game together, whether it is a game of chess, chat, a forum app, or something much richer. - -#### DNA as boundary of network - -The network of participants that are running a DNA engage in "peer witnessing" of actions by the participants in that network. A (deterministically random) set of peers are responsible for validating, storing, and serving each particular piece of shared content. In other words, the users of a particular hApp agree to a set of rules and then work together collectively to enforce those rules and to store and serve content (state changes) that do not violate those rules. - -Every hApp needs to include at least one DNA. Moreover, as indicated above, **it is at the DNA level** (note: not the higher application level) **where participants will form a network of peers to validate, store, and serve content** in accordance with the rules defined in that DNA. This happens in the background as the application runs on each participant's machine. - -There are some powerful consequences to this architectural choice --- including freedom to have your application look and feel the way you want, or to combine multiple DNAs together in ways that work for you without having to get everyone else to agree to do the same --- but we'll save those details for later. - -#### So if we have multiple DNAs in our hApp... - -...then we are participating in multiple networks, with each network of peers that are participating in a particular DNA also helping maintain the shared database for each DNA, enforcing the DNA's rules while validating, storing, and serving content. Each network acts as a 'social organism' in cooperation with other networks in the hApp. - -This is similar to the way in which multiple DNA communities coexist in biological organisms. In fact, there are more cells in a human body that contain other DNA (like bacteria and other microorganisms) than cells that contain our DNA. This indicates that we are an _ecology_ of coherent communities that are interacting with --- and evolving alongside --- one another. - -When it comes to hApps, this lets us play coherent games with one another at the DNA level, while also participating in adjacent coherent games with others as well. That means that applications are not one-size-fits-all. You can choose to combine different bits of functionality in interesting and novel ways. - -!!! - -It's time to scaffold a new DNA by entering: - -```shell -hc scaffold dna -``` - -You should then see: - -::: output-block -```text -? DNA name (snake_case): -``` -::: - -Enter a name for the DNA: - -```text -forum -``` - -You should then see: - -::: output-block -```text -DNA "forum" scaffolded! -Add new zomes to your DNA with: - hc scaffold zome -``` -::: - -Success! Inside of your `dnas/` folder, the scaffolding tool generated a `forum/` folder and, inside of that, the folders and files that the DNA needs. At this point you have a skeleton structure for your `forum` DNA. As you take the following steps, the scaffolding tool will make additions and edits to some of those folders and files based on your instructions. - -### 5. Scaffold a zome - -DNAs are comprised of code modules, which we call zomes (short for chromosomes). Zomes are modules that typically focus on enabling some small unit of functionality. Building with this sort of modular pattern provides a number of advantages, including the ability to reuse a module in more than one DNA to provide similar functionality in a different context. For instance, the [profiles zome](https://github.com/holochain-open-dev/profiles) is one that many apps make use of. For the forum DNA, you'll be creating two zomes: `posts` and `posts_integrity`. - -Start by entering: - -```shell -hc scaffold zome -``` - -You should then see: - -::: output-block -```text -? What do you want to scaffold? › -❯ Integrity/coordinator zome-pair (recommended) - Only an integrity zome - Only a coordinator zome -``` -::: - -!!! dig-deeper Integrity zomes and coordinator zomes - -#### Integrity zomes - -An integrity zome, as the name suggests, is responsible for maintaining the data integrity of a Holochain application. It sets the rules and ensures that any data writes occurring within the application are consistent with those rules. In other words, it is responsible for ensuring that data is correct, complete, and trustworthy. Integrity zomes help maintain a secure and reliable distributed peer-to-peer network by enforcing the validation rules defined by the application developer --- in this case, you! - -#### Coordinator zomes - -On the other hand, a coordinator zome contains the code that actually commits data, retrieves it, or sends and receives messages between peers or between other portions of the application on a user's own device (between the back end and the front-end UI, for instance). A coordinator zome is where you define the API for your DNA, through which the network of peers and their data is made accessible to the user. - -#### Multiple zomes per DNA - -As you learned earlier, a DNA can have multiple integrity and coordinator zomes. Each integrity zome contributes to the full set of different types of valid data that can be written, while each coordinator zome contributes to the DNA's functionality that you expose through its API. In order to write data of a certain type, a coordinator zome needs to specify a dependency on the integrity zome that defines that data type. A coordinator zome can also depend on multiple integrity zomes. - -#### Why two types? - -They are separated from one another so we can update coordinator zomes without having to update the integrity zomes. This is important, because changes made to an integrity zome result in a change of the rule set, which results in an entirely new network. This is because the integrity code is what defines the 'rules of the game' for a group of participants. If you changed the code of an integrity zome, you would find yourself suddenly in a new and different network from the other folks who haven't yet changed their integrity zome --- and we want to minimize those sorts of forks to situations where they are needed (like when a community decides they want to play by different rules, for instance changing the maximum length of comments from 140 characters to 280 characters). - -At the same time, a community will want to be able to improve the ways in which things are done in a Holochain app. This can take the form of adding new features or fixing bugs, and we also want people to also be able to take advantage of the latest features in Holochain. Separating integrity and coordination enables them to do that more easily, because: - -* Holochain's coordinator zome API receives frequent updates while the integrity zome API is fairly stable, and -* coordinator zomes can be added to or removed from a DNA at runtime without affecting the DNA's hash. - -!!! - -For this app, you're going to want both an integrity zome and a coordinator zome, so use the arrow keys to select: - -::: output-block -```text -Integrity/coordinator zome-pair -``` -::: - -and press Enter. - -You should then see: - -::: output-block -```text -? Enter coordinator zome name (snake_case): - (The integrity zome will automatically be named '{name of coordinator zome}_integrity') -``` -::: - -Enter the name: - -```text -posts -``` - -and press Enter. - -You should then see prompts asking if you want to scaffold the integrity and coordinator zomes in their respective default folders. - -Press Y for both prompts. - -As that runs (which will take a moment as the scaffold makes changes to various files) you should then see something like: - -::: output-block -```text -Coordinator zome "posts" scaffolded! -Updating crates.io index - Fetch [===> ] ... -``` -::: - (then after download is done...) -::: output-block -```text - Downloaded 244 crates (46.7 MB) in 4.27s (largest was `windows` at 11.9 MB) - -Add new entry definitions to your zome with: - hc scaffold entry-type -``` -::: - -Once that is all done, your hApp skeleton will have filled out a bit. Before you scaffold the next piece, it might be good to get a little context for how content is "spoken into being" when a participant publishes a post in a forum hApp. Read the following section to learn more. - -!!! dig-deeper Source chains, actions, and entries - -#### Source chain - -Any time a participant in a hApp takes some action that changes data, they add a record to a journal called a **source chain**. Each participant has their own source chain, a local, tamper-proof, and chronological store of the participant's actions in that application. - -This is one of the main differences between Holochain and other systems such as blockchains or centralized server-based applications. Instead of recording a "global" (community-wide) record of what actions have taken place, in Holochain actions are taken by agents and are thought of as transformations of their own state. - -One big advantage of this approach is that a single agent can be considered authoritative about the order in which they took actions. From their perspective, first they did A, then B, then C, etc. The fact that someone else didn't get an update about these changes, and possibly received them in a different order, doesn't matter. The order that the authoring agent took those actions will be captured in the actions themselves (thanks to each action referencing the previous one that they had taken, thus creating an ordered sequence --- or chain --- of actions). - -#### Actions and entries - -You'll notice that we used the word "action" a lot. In fact, **we call the content of a source chain record an action**. In Holochain applications, data is always "spoken into being" by an agent (a participant). Each record captures their act of adding, modifying, or removing data, rather than simply capturing the data itself. - -There are a few different kinds of actions, but the most common one is `Create`, which creates an 'entry' --- an arbitrary blob of bytes. Entries store most of the actual content created by a participant, such as the text of a post in our forum hApp. When someone creates a forum post, they're recording an action to their source chain that reads something like: _I am creating this forum post entry with the title "Intros" and the content "Where are you from and what is something you love about where you live?" and I would like my peers in the network to publicly store a record of this act._ So while an action is useful for storing noun-like data like messages and images, it's actually a verb, a record of an action that someone took to update their own state and possibly the shared database state as well. That also makes it well-suited to verb-like data like real-time document edits, game moves, and transactions. - -Every action contains the ID of its author (actually a cryptographic public key), a timestamp, a pointer to the previous source chain record, and a pointer to the entry data, if there is any. In this way, actions provide historical context and provenance for the entries they operate on. - -The pointer to the previous source chain record creates an unbroken history from the current record all the way back to the source chain's starting point. This 'genesis' record contains the hash of the DNA, which servs as both the identifier for the specific set of validation rules that all following records should follow and the ID of the network that this source chain's actions are participating in. - -An action is cryptographically signed by its author and is immutable (can't be changed or erased from either the source chain or the network's data store) once written. This, along with the validation rules specified by the DNA hash in the genesis record, are examples of a concept we call "intrinsic data integrity", in which data carries enough information about itself to be self-validating. - -Just as with a centralized application, we aren't just going to add this data into some database without checking it first. When a participant tries to write an action, Holochain first: - -1. ensures that the action being taken doesn't violate the validation rules of the DNA, -2. adds it as the next record to the source chain, and then -3. tells the participant's network peers about it so they can validate and store it, if it's meant to be public. - -The bits of shared information that all the peers in a network are holding are collectively called a distributed hash table, or DHT. We'll explain more about the DHT later. - -If you want to learn more, check out [The Source Chain: A Personal Data Journal](/concepts/3_source_chain/) and [The DHT: A Shared, Distributed Graph Database](/concepts/4_dht/). You'll also get to see it all in action in a later step, when you run your hApp for the first time. - -!!! - -Now it's time to start defining the structure and validation rules for data within your application. - -### 6. Scaffold entry types - -An entry type is a fundamental building block used to define the structure and validation rules for data within a distributed application. Each entry type corresponds to a specific kind of data that can be stored, shared, and validated within the application. - -!!! dig-deeper Entry types and validation - -An entry type is just a label, an identifier for a certain type of data that your DNA deals with. It serves as something to attach validation rules to in your integrity zome, and those rules are what give an entry type its meaning. They take the form of code in a function that gets called any time something is about to be stored, and because they're just code, they can validate all sorts of things. Here are a few key examples: - -* **Data structure**: When you use the scaffolding tool to create an entry type, it generates a Rust-based data type that define fields in your entry type, and it also generates code in the validation function that attempts to convert the raw bytes into an instance of that type. By providing a well-defined structure, this type ensures that data can be understood by the application. If it can't be deserialized into the appropriate Rust structure, it's not valid. - -* **Constraints on data**: Beyond simple fields, validation code can constrain the values in an entry --- for instance, it can enforce a maximum number of characters in a text field or reject nonsensical calendar dates. - -* **Privileges**: Because it originates in a source chain, an entry comes with metadata about its author. This can be used to control who can create, edit, or delete an entry. - -* **Contextual conditions**: Because an action is part of a chain of actions, it can be validated based on the agent's history --- for instance, to prevent currency transactions beyond a credit limit or disallow more than two comments per minute to discourage spam. An entry can also point to other entries in the DHT upon which it depends, and the data from those entries can be used in its validation. - -!!! - -Your bare-bones forum needs two entry types: `post` and `comment`. You'll define these in the `posts` integrity zome you just created in the previous step. The `post` entry type will define a `title` field and a `content` field. The `comment` entry type will define a `comment_content` field and a way of indicating which post the comment is about. - -To do this, just follow the instructions that the scaffold suggested for adding new entry definitions to your zome. - -Start with the `post` entry type by entering this command: - -```shell -hc scaffold entry-type -``` - -You should then see: - -::: output-block -```text -✔ Entry type name (snake_case): -``` -::: - -Enter the name: - -```text -post -``` - -You should then see: - -::: output-block -```text -Which fields should the entry contain? - -? Choose field type: › -❯ String - bool - u32 - i32 - f32 - Timestamp - ActionHash - EntryHash - DnaHash - AgentPubKey - Enum - Option of... - Vector of... -``` -::: - -The scaffolding tool is now prompting you to add fields to the `post` entry type. - -Fields are the individual components or attributes within an entry type that define the structure of the data. They determine the specific pieces of information to be stored in an entry and their respective data types. The scaffolding tool supports a collection of native Rust types such as booleans, numbers, enums (a choice between several predetermined values), optional values, and vectors (lists of items of the same type), along with Holochain-specific types that refer to other pieces of data on the DHT. - -For your `post` entry type, you're going to add `title` and `content` fields. Select `String` as the first field's type, and enter: - -```text -title -``` - -as the field name. - -Press Y for the field to be visible in the UI, and use the arrow keys to select `TextField` as the widget to render this field. (A `TextField` is a single-line input field designed for capturing shorter pieces of text.) - -When you see: - -::: output-block -```text -?Add another field to the entry?(y/n) -``` -::: - -press Y. - -Select `String` for this field's type too. Then enter - -```text -content -``` - -as the field name. - -Press Y for the field to be visible in the UI, and select `TextArea` as the widget to render the field. (A `TextArea` is a multi-line input field that allows users to enter larger blocks of text. That'll work better for blog posts.) - -After adding the `title` and `content` fields, press N when asked if you want to add another field. Next, you should see: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? - Update -✔ Delete -``` -::: - -The scaffolding tool can add zome and UI functions for updating and deleting entries of this type. In this case, we want authors to be able to update posts, but not delete them, so use the arrow keys and the spacebar to ensure that `Update` has a check and `Delete` does not. It should look like this: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? -✔ Update - Delete -``` -::: - -Then press Enter. - -At this point you should see: - -::: output-block -```text -? Should a link from the original entry be created when this entry is updated? › -❯ Yes (more storage cost but better read performance, recommended) - No (less storage cost but worse read performance) -``` -::: - -Select `Yes` by pressing Enter. - -!!! dig-deeper CRUD (create, read, update, delete) {#crud-create-read-update-delete} - -#### Mutating immutable data and improving performance - -In short, the above choice is about how changes get dealt with when a piece of content is updated. - -Because all data in a Holochain application is immutable once it's written, we don't just go changing existing content, because that would break the integrity of the agent's source chain as well as the data already in the DHT. So instead we add metadata to the original data, indicating that people should now look elsewhere for the data or consider it deleted. This is produced by `Update` and `Delete` source chain actions. - -For an `Update` action, the original `Create` or `Update` action and its entry content on the DHT get a `ReplacedBy` pointer to the new `Update` action and its entry content. - -When the scaffolding tool asks you whether to create a link from the original entry, though it's not talking about this pointer. Instead, it's talking about an extra piece of metadata that points to the _very newest_ entry in a chain of updates. If an entry were to get updated, and that update were updated, and this were repeated three more times, anyone trying to retrieve the entry would have to query the DHT six times before they finally found the newest revision. This extra link, which is not a built-in feature, 'jumps' them past the entire chain of updates at the cost of a bit of extra storage. The scaffolding tool will generate all the extra code needed to write and read this metadata in its update and read functions. - -For a `Delete` action, the original action and its entry content are simply marked as deleted. In the cases of both updating and deleting, all original data is still accessible if the application needs it. - -#### Resolving conflicts - -Multiple participants can mark a single entry as updated or deleted at the same time. This might be surprising, but Holochain does this for two good reasons. First, it's surprisingly difficult to decide which is the 'correct' version of a piece of data in a distributed system, because contributions may come from any peer at any time, even appearing unexpectedly long after they've been created. There are many strategies for resolving the conflicts that arise from this, which brings us to the second good reason: we don't want to impose a specific conflict resolution strategy on you. Your application may not even consider parallel updates and deletes on a single entry to be a conflict at all. - -#### CRUD functions - -**By default, the scaffolding tool generates a `create_' function in your coordinator zome for an entry type** because creating new data is a fundamental part of any application, and it reflects the core principle of Holochain's agent-centric approach --- the ability to make changes to your own application's state. - -Similarly, when a public entry is published, it becomes accessible to other agents in the network. Public entries are meant to be shared and discovered by others, so **a `read_' function is provided by default** to ensure that agents can easily access and retrieve publicly shared entries. (The content of _private_ entries, however, are not shared to the network.) For more info on entries, see: the **Core Concepts sections on [Source Chains](/concepts/3_source_chain/) and [DHT](/concepts/4_dht/)**. - -Developers decide whether to let the scaffolding tool generate `update_` and `delete_` functions based on their specific application requirements. More details in the Core Concepts section on [CRUD](/concepts/6_crud_actions/). - -!!! - -Next, you should see: - -::: output-block -```text -Entry type "post" scaffolded! - -Add new collections for that entry type with: - - hc scaffold collection -``` -::: - -We'll dive into collections in a moment, but first create the `comment` entry type. - -Again type: - -```shell -hc scaffold entry-type -``` - -This time enter the name: - -```text -comment -``` - -for the entry type name. - -You're going to add a `comment_content` field, so select the `String` field type and enter: - -```text -comment_content -``` - -Then select the `TextArea` widget and press Enter. (Again, a `TextArea` is a multi-line input field that allows users to enter larger blocks of text. Perfect for a comment on a post.) - -Press Y to add another field. - -For this next field you'll want to create a field that will help you associate each particular comment to the post that it is commenting on. To do this, the next field in the `comment` entry type will store a reference to a `post`. - -Use the arrow keys to select `ActionHash` as the field type. - -!!! dig-deeper Hashes and other identifiers - -There are two kinds of unique identifiers or 'addresses' in Holochain: **hashes** for data and **public keys** for agents. - -A hash is a unique "digital fingerprint" for a piece of data, generated by running it through a mathematical function called a **hash function**. None of the original data is present in the hash, but even so, the hash is extremely unlikely to be identical to the hash of any other piece of data. If you change even one character of the entry's content, the hash will be radically (and unpredictably) different. - -Holochain uses a hash function called blake2b. You can play with [an online blake2b hash generator](https://toolkitbay.com/tkb/tool/BLAKE2b_512) to see how changing content a tiny bit alters the hash. Try hashing `hi` and then `Hi` and compare their hashes. - -To ensure data integrity and facilitate efficient data retrieval, each piece of data is identified by its hash. This serves the following purposes: - -* **Uniqueness:** The cryptographic hashing function ensures that the data has a unique hash value, which helps to differentiate it from other data on the network. -* **Efficient lookup:** The hash is used as a key (essentially an address) in the network's storage system, the distributed hash table (DHT). When an agent wants to retrieve data, they simply search for it by hash, without needing to know what peer machine it's stored on. In the background, Holochain reaches out simultaneously to multiple peers who are responsible for the hash based on an algorithm that matches peers to data based on the similarity of the hash to their agent IDs. This makes data lookup fast and resilient to unreliable peers or network conditions. -* **Fair distribution:** Because the participants in a network are responsible for validating and storing each other's public data based on its hash, the randomness of the hashing function ensures that that responsibility is spread fairly evenly among everyone. -* **Integrity verification:** `Hi` will always generate the same hash no matter who runs it through the hashing function. So when data is retrieved by hash, its hash can be recalculated and compared with the original requested hash to ensure that a third party hasn't tampered with the data. -* **Collusion resistance:** The network peers who take responsibility for validating and storing an entry are chosen randomly based on the similarity of their agent IDs to the `EntryHash`. It would take a huge amount of computing power to generate a hash that would fall under the responsibility of a colluding peer. And because Holochain can retrieve data from multiple peers, it's more likely that the requestor can find one honest peer to report problems with a piece of bad data. - -#### `ActionHash` - -An action is identified by its `ActionHash`. Because an action contains information about its author, the time it was written, the action that preceded it, and the entry it operates on, no two action hashes will be the same --- even for the same entry. This helps to disambiguate identical entries written at different times by different agents. - -#### `EntryHash` - -An entry is identified by its `EntryHash`, which can be retrieved from the `ActionHash` of the action that wrote it. Because they're two separate pieces of data, an entry is stored by different peers than the action that operates on it. - -#### `AgentPubKey` - -**Each agent in a network is identified by their cryptographic public key**, a unique number that's mathematically related to a private number that they hold on their machine. Public-key cryptography is a little complex for this guide --- it's enough to know that a participant's private key signs their source chain actions, and those signatures paired with their public key allow others to verify that they are the one who authored those actions. - -An `AgentPubKey` isn't a hash, but it's the same length, and it's unique just like a hash. So it can be used as a way of referring to an agent, like a user ID --- and this is also why it's used to choose the right peers in the DHT storage and retrieval algorithm. - -#### Summary - -Whereas `EntryHash` is used to uniquely identify, store, and efficiently retrieve an entry from the DHT, `ActionHash` is used to uniquely identify, store, and retrieve the action (metadata) that operated on it, which can provide information about the history and context of any associated entry (including what action preceded it). `ActionHash`es are also what enable any participant to retrieve and reconstruct the continuous sequence of actions (and any associated entries) in another agent's source chain. - -**Use `EntryHash` when** you want to link to or retrieve the actual content or data (e.g., when linking to a category in a forum application). - -**Use `ActionHash` when** you want to link to or retrieve the authorship or history of an entry (e.g., when distinguishing between two posts with identical content). - -**Use `AgentPubKey` when** you want to link to an agent (such as associating a profile or icon with them) or retrieve information about their history (such as scanning their source chain for posts and comments). - -You can check out the Core Concepts to dive a bit deeper into [how the distributed hash table helps](/concepts/4_dht/) to not only make these entries and actions available but helps to ensure that agents haven't gone back to try and change their own histories after the fact. But for now, let's dive into links. - -!!! - -After press Enter, you should see: - -::: output-block -```text -? Should a link from this field be created when this entry is created? (y/n) › -``` -::: - -Press Y to accept creating a link. - -Next you will see: - -::: output-block -```text -✔ Which entry type is this field referring to? -``` -::: - -Press Enter to accept the suggested entry type `Post`. - -Next, you will be asked to pick a field name. You can press Enter to accept the field name suggestion, which should be: - -```text -post_hash -``` - -Press N to decline adding another field to the entry. - -Then use the arrow keys to deselect Update, but leave Delete selected. It should look as follows: - -::: output-block -```text -Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? - Update -✔ Delete -``` -::: - -Once that is done, press Enter to generate a delete function for the **`comment`** entry type. - -You should then see: - -::: output-block -```text -Entry type "comment" scaffolded! - -Add new collections for that entry type with: - - hc scaffold collection -``` -::: - -The scaffolding will now have both added the `comment` entry type, and added a bunch more very useful code to our app using the native Holochain affordance of links. Links allow us to create paths that agents can follow to find associated content. So, the scaffolding not only added a reference to the post in the comment's entry, but it also added code such that when a comment is added, a link from the post back to the comment will also be created. If you want to see some of that code, take a look at the `dnas/forum/zomes/integrity/posts/src/lib.rs` file and you should see right near the top that a function has been created for validating the creation of a `post_to_comments` link. Similarly, other validation functions related to the deletion of those links follow after. - -!!! dig-deeper How links are stored and retrieved in a Holochain app - -What exactly is a link? Where is it stored? How does it work? And what do they let us do that we couldn't do otherwise? - -Links enable us to build a graph of references from one piece of content to other pieces of content in a DHT and then to navigate that graph. This is important because without some sort of trail to follow, it is infeasible to just "search for all content" thanks to the address space (all possible hashes) being so large and spread out across machines that iterating through tme all could take millions of years. - -By linking from known things to unknown things, we enable the efficient discovery and retrieval of related content in our hApp. - -**Storage**: When an agent creates a link between two entries, a `CreateLink` action is written to their source chain. A link is so small that there's no entry for the action. It simply contains the address of the base, the address of the target, the link type (which describes the relationship), and an optional tag which contains a small amount of application-specific information. The base and target can be any sort of DHT address --- an `EntryHash`, an `ActionHash`, or an `AgentPubKey`. But there doesn't actually need to be any data at that base, which is useful for referencing data that exists in another hash-based data store outside the DHT. - -After storing the action in the local source chain, the agent then publishes the link to the DHT, where it goes to the peers who are responsible for storing the base address and gets attached to the address as metadata. - -**Lookup**: To look up and retrieve links in a Holochain app, agents can perform a `get_links` query on a base DHT address. This operation involves asking the DHT peers responsible for that address for any link metadata of a given link type attached to it, with an optional "starts-with" query on the link tag. The peers return a list of links matching the query, which contain the addresses of the targets, and the link types and tags. The agent can then retrieve the actual target data by performing a [`get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) query on the target address, which may be an `EntryHash`, `ActionHash`, or `AgentPubKey` (or an empty result, in the case of data that doesn't exist on the DHT). - -For more information and examples, read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/). - -!!! - -### 7. Scaffold a collection - -Now, let's create a collection that can be used to retrieve all the posts. A collection creates a link type for referring to the collected entry type (similarly to how a link type was created for linking from posts to comments), but collections also create an 'anchor' --- a small string --- as the base for the link so we can find all the items in the collection by starting from the anchor's known hash. - -To create a collection, type: - -```shell -hc scaffold collection -``` - -You should then see: - -::: output-block -```text -Collection name (snake_case, eg. "all_posts"): › -``` -::: - -Enter: - -```text -all_posts -``` - -and press Enter. You should then see: - -::: output-block -```text -? Which type of collection should be scaffolded? › -❯ Global (get all entries of the selected entry types) - By author (get entries of the selected entry types that a given author has created) -``` -::: - -Select **`Global`** and press Enter. You should then see: - -::: output-block -```text -? Which entry type should be collected? › -❯ Post - Comment -``` -::: - -Select **`Post`** and press Enter. You should then see: - -::: output-block -```text -Collection "all_posts" scaffolded! - -At first, the UI for this application is empty. If you want the newly scaffolded collection to be the entry point for its UI, import the element in `ui/src/App.svelte`: - - import AllPosts from './forum/posts/AllPosts.svelte'; - -And use the element in the `<div id="content" />` block by adding in this: - - <div id="content"><</AllPosts></div> -``` -::: - -These instructions tell us that if we want to include this generated UI component in the user interface of our hApp, we need to do some manual work: - - 1. Import the component, and - 2. Tell the UI to display the component. - -!!! dig-deeper How a collection is implemented - -We already explored how links make data in the DHT discoverable by connecting known DHT base addresses to unknown addresses. Essentially every address becomes an anchor point to hang a collection of links from. - -But there's one remaining problem: _where do you start?_ When someone starts their app for the first time, the only DHT base addresses they know about are their public key, the DNA hash, and the few actions and entries on their source chain. There's no obvious way to start discovering other people's data yet. - -This is where **collections** help out. A collection is just a bunch of links on a base address that's easy to find --- typically the address is hard-coded in the coordinator zome's code as the hash of a string such as `"all_posts"`. It's easy to get the links, because their base address is right there in the code. - -This pattern, which we call the "anchor pattern", is so useful that it's built right into Holochain's SDK --- integrity zomes that use it will have all the necessary entry and link types automatically defined, and coordinator zomes that use it will have functions that can retrieve anchors and the links attached to them. The scaffolded code uses this implementation behind the scenes. - -The built-in implementation is actually a simplification of a more general pattern called "paths", which is also built into the SDK. With paths, you can create trees of linked anchors, allowing you to create and query hierarchical structures. This can be used to implement categories, granular collections (for example, "all posts" → "all posts created in 2023" → "all posts created in 2023-05" → "all posts created on 2023-05-30"), and indexes for type-ahead search (for example, "all usernames" → "all usernames starting with 'mat'" → "all usernames starting with 'matt'"). What the SDK calls an `Anchor` is actually a tree with a depth of two, in which the root node is two empty bytes. - -Hierarchical paths serve another useful purpose. On the DHT, where every node is tasked with storing a portion of the whole data set, some anchors could become "hot spots" --- that is, they could have thousands or even millions of links attached to them. The nodes responsible for storing those links would bear a disproportionate data storage and serving burden. - -The examples of granular collections and type-ahead search indexes breaks up those anchors into increasingly smaller branches, so that each leaf node in the tree --- and hence each peer --- only has to store a small number of links. - -The scaffolding tool doesn't have any feature for building anchors and trees beyond simple one-anchor collections, but if you'd like to know more, you can read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/) and the SDK reference for [`hash_path`](https://docs.rs/hdk/latest/hdk/hash_path/index.html) and [`anchor`](https://docs.rs/hdk/latest/hdk/hash_path/anchor/index.html). - -!!! - -Before you get started editing the UI, it's helpful to be able to actually run the scaffolded applciation. That way, you can watch changes take effect in real-time as you make them. So the next section will walk you through launching the application the tooling that's available there, and then in the section after that, we'll begin working with the `.svelte` files to build the UI. - -### 8. Run your application in dev mode - -At this stage, we'll incorporate some of the UI components that have been scaffolded by the scaffolding tool into our main application interface. Our aim here is to make all the functionality of our forum application accessible from a single, unified interface. We'll use Svelte to accomplish this, as it is the framework that we have chosen for the UI layer of our application. - -Start the forum hApp in develop mode from the command line: go to your terminal and, from the root folder (`my_forum_app/`), enter: - -```shell -npm start -``` - -!!! info Work in the nix shell -If you are having an issue, make sure that you are still in the nix shell. If not, re-enter `nix develop` first, then type the above command again. And remember that you can always exit nix shell by typing `exit` to get back to your normal shell. -!!! - -When you start the hApp with `npm start`, this launches Holochain in sandbox mode with two agents running that hApp, and opens three windows: - -1. A web browser window with Holochain Playground, a tool that makes visible the various actions that have taken place in our forum hApp. You should be able to see a couple of agents in a DHT, with mostly empty source chains and, correspondingly, a mostly empty graph. -2. An application window with one agent (conductor 0) running the forum hApp. This window lets us take actions as that agent (0, or Alice, if you prefer). -3. Another application window with a second agent (conductor 1) running the forum hApp. This window lets us take actions as the other agent (1, or Bob). - -![Three windows: two agent UIs and a web browser window with the Holochain Playground](/assets/img/get-started/3-two-uis-and-playground.png) - -These application windows allow us to test multiple agents in a Holochain network interacting with one another. It is all running on our one device, but the two conductors behave very much the same as separate agents on different machines would, minus network lag. - -Remember that a **conductor** is a Holochain runtime process executing on your computer. For more details see the [Application Architecture](/concepts/2_application_architecture/) section in the Core Concepts guide. - -These three windows together will let us interact with our hApp as we are building it. - -The Holochain Playground in particular is helpful because it creates visual representations of the data that has been created and the way it relates to other content. Take a look at it and click one of the two items in the **DHT Cells** window. These are your two agents. When you click one of them, some content gets displayed in the **Source Chain** window. These are the initial actions in that agent's source chain. The arrows point from newer content back to older content. - -From oldest to newest, in the newly created source chains, the records are: - -1. `DNA`, recording the hash of the DNA to be used to validate all subsequent source chain actions, -2. `AgentValidationPkg`, providing proof that this participant is allowed to participate in this hApp ([see more](https://www.holochain.org/how-does-it-work/) in Holochain: How does it work?), -3. A `Create` action which records the author's `AgentID`, which is their public key and serves as their ID in the network and its graph database. - -As agents begin writing posts, comments, and links to the DHT, you'll see the following records appear: - -4. `InitComplete`, indicating that all coordinator zomes have had a chance to do initial setup, then -5. Whatever actions the agent takes after that. - -The two application UI windows let you interact with the application and see what is working, what is not working, and how data propagates when we take particular actions. - -At first, each of the UI windows (conductors 0 for Alice and 1 for Bob) include instructions for you to go and examine the scaffolded UI elements by looking at the contents in the folder `ui/src///`, where `` and `` are generic placeholders for your DNA (`forum`) and zome (`post`). - -### 9. Integrate the generated UI elements - -Thus far, seven different UI components should have been generated as `.svelte` files in the `ui/src/forum/posts/` folder. Note that for ease of development, the sandbox testing environment live-reloads the UI as you edit UI files. So don't quit the process you started with `npm start`; instead, **open a new terminal window**. Then navigate to the root folder of your hApp (`my_forum_app/`) and list the files in `ui/src/forum/posts/` by entering: - -```shell -ls ui/src/forum/posts/ -``` - -You should see seven different `.svelte` files, plus a `types.ts` file: - -::: output-block -```text -AllPosts.svelte CreateComment.svelte PostDetail.svelte -CommentDetail.svelte CreatePost.svelte types.ts -CommentsForPost.svelte EditPost.svelte -``` -::: - -The next step is to edit the UI files in the text editor or integrated development environment of your choice to add scaffolded components and build a fully featured UI. To integrate all of these generated UI elements, you'll need to add them to `App.svelte` file located in the `ui/src/` folder, or to some other `.svelte` file that eventually gets included in `App.svelte`. - -If you don't yet have path commands for opening files in your prefered IDE, there are instructions for [VSCode/VSCodium](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line), [Sublime Text](https://www.sublimetext.com/docs/command_line.html#setup) and [WebStorm](https://www.jetbrains.com/help/webstorm/working-with-the-ide-features-from-command-line.html#5d6e8844). Going forward in this tutorial, we are going to use the `code` command when we mean for you to open files in your IDE, but you should substitute a different command (ex: `subl`, `vim`, `emacs` etc.) for `code` if you are using a different editor. - -Open the `App.svelte` file with your preferred IDE. - -```shell -code ui/src/App.svelte -``` - -Your `App.svelte` file will have three sections: - -1. a script section, -2. a main section containing a markup template, and -3. a style section containing a stylesheet template. - -!!! dig-deeper Detailed breakdown of `App.svelte` - -#### ` -``` - -This section contains the JavaScript/TypeScript code for the component. It imports various dependencies needed to build a single-page web app: - -* `svelte` is the Svelte engine itself, and its `onMount` function lets you register a handler to be run when the component is initialized, while `setContext` lets you pass data to be used in the rendering of the component. -* `@holochain/client` is the Holochain client library; first we load in some useful Holochain-related TypeScript types, followed by the client object itself. -* `@mwc/material-circular-progress` is just a UI component that gives us a spinner when something is loading. -* `./contexts` is generated by the scaffolding tool. It just contains a constant, the app-wide name for the 'context' that makes the Holochain client accessible to all components. In Svelte, a context is a state shared across components. - -After importing dependencies, it does some initial setup. This is run when the component file is imported --- in this case the component, `App.svelte`, is the main component for the entire application, and it's imported into `main.ts` where it's 'mounted' (more on mounting in a moment). - -Next some variables are instantiated: one to hold the Holochain client that connects to the hApp backend via the conductor, and one to keep track of whether the client is connected yet. (This variable will be used to show a loading spinner while it's still connecting.) - -**Take note of the line that starts with `$:`**. This is a special Svelte label that turns regular variables into **reactive variables**. We won't get too deep into Svelte right now, because this is a tutorial about Holochain, but when a reactive variable changes, Svelte will re-render the entire component. This lets you write a template declaratively, enclosing the reactive variable in `{}` braces, and let Svelte handle the updating of the template wherever the variable changes. - -Finally, there's an `onMount` handler, which is run when the component is first displayed. The handler currently does one thing: it connects to the hApp backend via the conductor, waits until the connection is establised, sets `loading` to false, and adds the resulting client connection to the context so that all components can access it. - -#### `
` section - -```svelte -
- {#if loading} -
- -
- {:else} -
-

EDIT ME! Add the components of your app here.

- - Look in the ui/src/DNA/ZOME folders for UI elements that are generated with hc scaffold entry-type, hc scaffold collection and hc scaffold link-type and add them here as appropriate. - - For example, if you have scaffolded a "todos" dna, a "todos" zome, a "todo_item" entry type, and a collection called "all_todos", you might want to add an element here to create and list your todo items, with the generated ui/src/todos/todos/AllTodos.svelte and ui/src/todos/todos/CreateTodo.svelte elements. - - So, to use those elements here: -
    -
  1. Import the elements with: -
    -import AllTodos from './todos/todos/AllTodos.svelte';
    -import CreateTodo from './todos/todos/CreateTodo.svelte';
    -        
    -
  2. -
  3. Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>.
  4. -
-
- {/if} -
-``` - -This section is a template for the displayable content of the main app component. Using an `{#if}` block to test whether the reactive variable `loading` is true, this section displays a spinner until the backend can be accessed. Once the UI is connected to the backend, it shows some boilerplate text telling you to add something meaningful to the template. - -#### ` -``` - -This section is a template for the CSS styles that get applied to the HTML in the `
` section of the component. You can also use reactive variables here, and the styling will update whenever the variables change. These scaffolded styles set the component up with some basic layout to make it readable at small and large window sizes. - -**All Svelte components follow this general pattern**. `App.svelte` has special status as the root component, but otherwise it's just like any other component. - -!!! - -First you'll be adding a list of posts to the app, which means the components called `AllPosts.svelte` needs to be imported. - -At the top of the file, there is a list of scripts that are imported. Following the instructions that the scaffolding tool and the two conductor windows gave you, copy the following text and paste it into the script block of the `App.svelte` file, on the line below `import { clientContext } from './contexts';` - -```typescript -import AllPosts from './forum/posts/AllPosts.svelte'; -``` - -Next, add the component to the markup template in the `
` section of the file, where the "EDIT ME!" content now lives. Remove everything inside the `div` element that starts with this tag: - -:::output-block -```svelte -
-``` -::: - -and replace it with this line: - -```svelte - -``` - -Your `
` block should now look like this: - -```svelte -
- {#if loading} -
- -
- {:else} -
- -
- {/if} -
-``` - -!!! info Svelte component tags -The `AllPosts` element is obviously not standard HTML. In Svelte, each component has a correspondingly named custom element that will get replaced by the rendered component's markup wherever it appears in another component's template. -!!! - -Save that file and take a look again at the two UI windows. They should both say 'No posts found'. - -![A UI showing the AllPosts component, which says 'No posts found'](/assets/img/get-started/4-no-posts-found.png) - -Let's fix that by adding the post creation component to the UI so we can add our first post. Import the `CreatePost.svelte` component by adding this line in the script section, just below the `AllPosts` component you previously imported: - -```typescript -import CreatePost from './forum/posts/CreatePost.svelte'; -``` - -Add this new component to the `
` block above the component you added: - -```svelte - -``` - -Now your `
` block should look like this: - -```svelte -
- {#if loading} -
- -
- {:else} -
- - -
- {/if} -
-``` - -Save the file and switch to one of the two conductor windows. You should now see a post form. - -![The UI after adding the CreatePost component](/assets/img/get-started/5-create-post-component.png) - -Type something into one of the two conductor windows like: - -* Title: `Hi from Alice` -* Content: `Hello Bob!` - -and then press the "Create Post" button. - -You'll immediately notice that the `AllPosts` component has changed from saying "No posts found" to showing the newly created post. And if you take a look at the Holochain Playground window, you will see that two new actions have been created. If you click the `App` element that's appeared in Alice's source chain, it will pull up some details in the Entry Contents section, including the title and content of Alice's forum post. Note the hash of that entry (top of the Entry Contents window). Then click on the `Create` action that's pointing toward that `App` entry in the source chain. If you look back at the contents window, you will see that it is now sharing the contents of the action. And if you look down the list a bit, you will see the hash of the entry for the first post. - -![The Holochain playground showing a single agent's source chain, containing the actions that create a post, as well as the transformations in the DHT that resulted from these actions](/assets/img/get-started/6-playground-first-post.png) - -!!! dig-deeper Relationships in a source chain versus relationships in the DHT - -At this point, in our DHT graph it should look like we have two different agents and then a separate floating entry and action. But we know that the new post is associated with a source chain which is associated with an agent. So why aren't they connected on the DHT? - -A source chain merely serves as a history of one agent's attempts to manipulate the state of the graph database contained in the DHT. It's useful to think of the DHT as a completely separate data store that doesn't necessarily reflect agent-to-entry relationships unless you explicitly create a link type for them. - -For the purpose of this hApp, we're not interested in agent-to-posts relationships, so it's fine that they're not linked. But if you wanted to create a page that showed all posts by an author, that's when you might want to scaffold that link type. `hc scaffold collection` will do this for you if you choose a by-author collection, and will also create a `get_posts_by_author` function. - -!!! - -You may also notice that only Alice's UI showed the new post, while Bob's didn't. Just as with a traditional web app, database changes don't automatically send out a notification to everyone who is interested. (Alice's UI sees the changes because it knows how to update its own state for local changes.) You can create this functionality using a feature called [signals](/concepts/9_signals/), but let's keep things simple for now. Right-click anywhere in Bob's UI then choose "Reload" from the menu, and you'll see that the changes have been copied from Alice's app instance to Bob's --- all without a database server in the middle! - -Let's edit that post. In Alice's UI window, click the edit adjacent to the post content (it should look like a pencil icon). The post content will be replaced by an editing form. - -Now alter the content a bit. Maybe change it from `Hello Bob!` to `Hello, World!` and click "Save". - -![The UI of one agent, showing a post about to be edited](/assets/img/get-started/7-edit-post.png) - -That should update the post (at least for Alice). Bob's UI will show the updated version the next time it's reloaded. - -If you look at the Holochain Playground, you should see that the update was added to Alice's source chain. Specifically, it created: - -1. a new entry (with our `Hello, World!` text), -2. an `Update` action that indicated this entry is to replace the original entry, and -3. a `CreateLink` action that connects the original create action to the update action. - -![The Holochain playground, showing the source chain of the agent who edited the post along with new data in the DHT reflecting the edit](/assets/img/get-started/8-playground-after-edits.png) - -As explained [previously](#crud-create-read-update-delete), the original forum post already has a 'link' of sorts pointing from its action to the `Update` action, which can be accessed when the original is retrieved. The extra link created by the `CreateLink` action is optional --- it merely speeds up retrieval when an action has been edited many times and has a long chain of update links, by allowing you to jump to the end of the chain. In the screenshot above, the link is highlighted in the DHT pane. - -Now it's time to add commenting to your app. - -Previously, you added new components to the `App.svelte` component. That made sense because posts were a global data type. But comments are related to a post, so from now on you'll be modifying the `PostDetail.svelte` component instead. - -Open up `PostDetail.svelte` in your IDE: - -```shell -code ui/src/forum/posts/PostDetail.svelte -``` - -Just as before, first you'll need to import the components near the top of the file (just after the line that imports `EditPost.svelte`): - -```typescript -import CreateComment from './CreateComment.svelte'; -import CommentsForPost from './CommentsForPost.svelte'; -``` - -Further down the file, in the template block, add the components' elements to the template. Put them both before the closing `
` tag. - -Here, the comment components need to know what post they're related to. The post hash is the unique ID for the post, and the comment components' elements both expect a `postHash` attribute. This hash is available in the `PostDetail` component as a variable of the same name, so it can be passed to the comment widgets. - -```svelte - - -``` - -Save the file, then go back to the UI windows to see the changes. Try typing in a comment or two, then deleting them. (You may need to refresh the UI windows to see the changes to the content.) Watch the Playground --- see how the authors' source chains and the graph in the DHT change as new information is added. The deleted comments are still there and can be accessed by code in your zomes if needed, but neither the application backend (that is, the functions defined in the coordinator zome) nor the UI have the capacity to show them. - -![One UI window with the comment components added, with the Playground in the background showing a populated DHT](/assets/img/get-started/10-comment-components.png) - - -## 2. Next steps - -Congratulations! You've learned how to create a new Holochain application, understand its layout, work with core concepts, and deploy and test the application. - -Now that you have a basic understanding of Holochain development, you can continue exploring more advanced topics, such as: - -* Validating data -* Writing tests for a zome -* Implementing access and write privileges -* Building more complex data structures and relationships -* Optimizing your application for performance and scalability - - -### 2.1 Further exploration and resources - -Now that you have successfully built a basic forum application using Holochain and integrated it with a frontend, you may want to explore more advanced topics and techniques to further enhance your application or create new ones. Here are some resources and ideas to help you get started: - -#### Holochain developer documentation - -The official Holochain developer documentation is a valuable resource for deepening your understanding of Holochain concepts, techniques, and best practices. Be sure to explore the documentation thoroughly: - -* [Holochain Core Concepts](/concepts/1_the_basics/) -* [Holochain Developer Kit (HDK) reference](https://docs.rs/hdk/latest/hdk) - -#### Community resources - -The Holochain community is an excellent source of support, inspiration, and collaboration. Consider engaging with the community to further your learning and development: - -* [Holochain GitHub repositories](https://github.com/holochain) -* [Holochain Discord server](https://discord.com/invite/k55DS5dmPH) - -#### Example applications and tutorials - -Studying existing Holochain applications and tutorials can provide valuable insights and inspiration for your projects. Here are some resources to explore: - -* [Holochain Open Dev](https://github.com/holochain-open-dev) -* [Holochain Foundation sample apps](https://github.com/holochain-apps) diff --git a/src/pages/build/3_deployment.md b/src/pages/build/3_deployment.md deleted file mode 100644 index 09dfece5f..000000000 --- a/src/pages/build/3_deployment.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: "Deploying your Holochain application" -tocData: - - text: Packaging - href: packaging - - text: Runtimes - href: runtimes - children: - - text: Launcher - href: launcher-the-multi-app-runtime - - text: Standalone - href: standalone-executable ---- - - -Now that you've built an application, it's time to get it into other people's hands. First an app must be packaged into a distributable bundle format. Then it can be published somewhere a user can access it. - -## Packaging - -You specify the components of a hApp using manifest files, written in [YAML](https://yaml.org/), and the `hc` CLI looks for them when it's building a distributable hApp for you. If you look in the `workdir` folder: - -```shell -ls workdir -``` - -You'll see that the scaffolding tool has generated two manifest files for you: - -:::output-block -```text -happ.yaml web-happ.yaml -``` -::: output-block - -The first step is to package your app: - -```shell -npm run package -``` - -This command does a number of things: - -1. Triggers the Rust compiler to build the zomes, -2. Uses the `hc` CLI too to combine the built zomes and the DNA manifest into a `.dna` file, -3. Combines all the DNAs and the hApp manifest into a `.happ` file, -3. Builds the UI and compresses it into a `.zip` file, and -4. Combines the hApp file, the UI zip, and the web hApp manifest into a `.webhapp` file. - -Of course, this application only has one zome and one DNA, but more complex apps may have many of each. - -Now you'll see some new files in `workdir`: - -```shell -ls workdir -``` - -::: output-block -```text -happ.yaml my_forum_app.happ my_forum_app.webhapp web-happ.yaml -``` -::: output-block - -The packed app is now ready for deployment to a Holochain runtime. - -## Runtimes - -In the centralized world, deployment is usually achieved by Continuous Integration (CI) automation that builds up code changes and sends them to whatever server or cloud-based platform you're using. In the decentralized world of Holochain, deployment happens when end-users download and run your hApp in the Holochain runtime. - -From the end-user perspective there are currently there are two ways to go about this, both of which will feel familiar: - -1. Download Holochain's official Launcher runtime and install the app from its app store or the filesystem. -2. Download an your app as its own standalone desktop executable, as they would any other application for their computer. - -### Launcher, the multi-app runtime - -Holochain's official end-user runtime is the [Holochain Launcher](https://github.com/holochain/launcher). It allows people to install apps from a built-in app store or from the filesystem. Installed apps can then be launched from a friendly UI. Note that the app store is itself a distributed Holochain application which provides details on applications that are available for download. As a developer you can either go through a simple publishing process and add your app to the app store where it will be available for installation by all people who use the Launcher, or you can share your application directly with end-users through your own channels and they can install it into their Holochain Launcher manually from the file system. - -You can try this latter approach immediately by downloading and running the Launcher! - -The steps for publishing an app to the Launcher's app store are documented in the Github repository of the Holochain Launcher [here](https://github.com/holochain/launcher#publishing-and-updating-an-app-in-the-devhub). - -### Standalone executable - -If you prefer to distribute your app as a full standalone executable, you will need to bundle the Holochain runtime and your app together and take care of the necessary interactions between them. Because Holochain itself is really just a set of Rust libraries, you can of course build your own application that uses those libraries, but that's a fair amount of work. Currently there are two much simpler paths for doing this: using either the [Electron](https://www.electronjs.org/) or [Tauri](https://tauri.app/) frameworks, both of which can generate cross-platform executables from standard web UIs. These frameworks also support inclusion of additional binaries, which in our case are the [holochain conductor](https://docs.rs/holochain/latest/holochain/) and the [lair keystore](https://docs.rs/lair_keystore/latest/lair_keystore/). Though there is quite a bit of complexity in setting things up for these frameworks, all the hard work has already been done for you: - -* **Electron**: Refer to the community-supported [electron-holochain-template](https://github.com/lightningrodlabs/electron-holochain-template/) repo. -* **Tauri**: See the officially supported [holochain-kanagroo](https://github.com/holochain-apps/holochain-kangaroo) repo. - -Both of these are GitHub template repos with detailed instructions on how to clone the repos and add in your UI and DNA, as well as build and release commands that will create the cross-platform executables that you can then deliver to your end users. - -!!! note Code Signing -For macOS and Windows, you will probably also want to go through the process of registering as a developer so that your application can be "code-signed". This is needed so that users don't get the "unsigned code" warnings when launching the applications on those platforms. Both of the above templates include instructions for CI automation to run the code-signing steps on release once you have acquired the necessary certificates. -!!! \ No newline at end of file From 12d2b13521c0924899be8b1013700440a0460d8a Mon Sep 17 00:00:00 2001 From: Matt Gabrenya Date: Mon, 8 Jan 2024 15:40:32 -0700 Subject: [PATCH 12/69] chore: remove changes from other PR --- src/pages/get-started/index.md | 1315 ++++++++++++++++++++++++++++++++ src/pages/references/index.md | 7 - 2 files changed, 1315 insertions(+), 7 deletions(-) diff --git a/src/pages/get-started/index.md b/src/pages/get-started/index.md index fb56bf351..a31e45ded 100644 --- a/src/pages/get-started/index.md +++ b/src/pages/get-started/index.md @@ -5,6 +5,38 @@ tocData: href: 1-introduction-to-holochain - text: 2. Installing Holochain development environment href: 2-installing-holochain-development-environment + - text: 3. Scaffold a simple "Hello, World!" Holochain application + href: 3-scaffold-a-simple-hello-world-holochain-application + - text: "4. Zero to built: creating a forum app" + href: 4-zero-to-built-creating-a-forum-app + children: + - text: 4.1. Scaffolding a hApp + href: 4-1-scaffolding-a-happ + - text: 4.2. Select user interface framework + href: 4-2-select-user-interface-framework + - text: 4.3. Set up Holonix development environment + href: 4-3-set-up-holonix-development-environment + - text: 4.4. Scaffold a DNA + href: 4-4-scaffold-a-dna + - text: 4.5. Scaffold a zome + href: 4-5-scaffold-a-zome + - text: 4.6. Scaffold entry types + href: 4-6-scaffold-entry-types + - text: 4.7. Scaffold a collection + href: 4-7-scaffold-a-collection + - text: 4.8. Run your applicaiton in dev mode + href: 4-8-run-your-application-in-dev-mode + - text: 4.9. Integrate the generated UI elements + href: 4-9-integrate-the-generated-ui-elements + - text: 5. Deploying your Holochain application + href: 5-deploying-your-holochain-application + children: + - text: 5.1 Packaging + href: 5-1-packaging + - text: 5.2 Runtimes + href: 5-2-runtimes + - text: 6. Next steps + href: 6-next-steps --- Welcome to the Getting Started with Holochain guide! This guide will walk you through the process of installing the Holochain development tools and creating a simple forum application. By the end of this guide, you'll be familiar with the core concepts of Holochain and have a basic understanding of how to develop peer-to-peer applications using the Holochain framework. @@ -85,6 +117,1212 @@ holochain_scaffolding_cli x.y.z Congratulations! The Holochain development environment is now set up successfully on your system. +**Before moving on to the next step**, find a folder to put your work. For this tutorial, we'll be working in `~/Holochain`. Create that folder now and move into it: + +```shell +mkdir ~/Holochain +``` + +```shell +cd ~/Holochain +``` + +## 3. Scaffold a simple "Hello, World!" Holochain application + +In this section, we'll use Holochain's scaffolding tool to generate a simple "Hello, World!" application. + +When getting started, seeing a simple but fully-functional app can be very helpful. You can have Holochain's scaffolding tool generate a "Hello, World!" application (but for a distributed multi-agent world) by typing the following in your command line terminal: + +```shell +nix run github:holochain/holochain#hc-scaffold -- example hello-world +``` + +The scaffolding tool should print out these four commands for you to run in order to run the app. Copy them from your terminal or from below: + +```shell +cd hello-world +``` +```shell +nix develop +``` +```shell +npm install +``` +```shell +npm start +``` + +After you run the last of these commands, you should see three windows open: + +![A screenshot showing two hApp windows in front of the Playground](/assets/img/get-started/1-running-app-first-look.png) + +* A web browser window with the Holochain Playground, which displays a visual representation of the app's state data +* Two windows showing the UI for two agents, both of which will have published a `Hello World` entry to the network. + +When you click on the "Look for Hellos" button, you should be able to see the hellos: + +![A screenshot showing one app window, with hello messages in different languages retrieved from the DHT](/assets/img/get-started/2-look-for-hellos.png) + +When you are done checking out this app, you can go back to the terminal and stop both agents by pressing Ctrl+C (Linux) or Cmd+C (macOS). + +!!! dig-deeper Understanding the layout of a scaffolded project + +Let's explore the different files and folders that make up the structure of the "Hello, World!" hApp that you just created. + +List the folders and files in our `hello-world/` folder by entering: + +```shell +ls +``` + +This table includes everything in the `hello-world/` folder as well as details of the contents of the `dnas/` subfolder since that makes up the bulk of the "Holochain" part of an application. For certain working folders, like `node_modules/`, `target/`, `tests/`, and `ui/`, the table only contains a high-level overview. + +| File/folder | Purpose | +|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +|
 ├── hello-world/         
| Root folder of the application. All other files and folders will reside here. | +|
 ├┬─ dnas/                
| This folder contains the DNA configuration and source code for the application. DNAs are one of the most important building blocks in Holochain. Simply put, **a DNA is the executable code for the game you are playing with your peers in Holochain.** And here is the twist: in Holochain, **every DNA creates its own peer-to-peer network** for the validation, storage, and serving of content. Every Holochain application contains at least one DNA. In this example hApp, we have just one: `hello_world`. | +|
 │└┬─ hello_world/        
| Folder for the `hello_world` DNA. It contains modules (zomes) that define the rules and API of this application. | +|
 │ ├┬─ workdir/           
| A working folder containing configuration files and compiled artifacts related to the DNA. | +|
 │ │├── dna.yaml          
| DNA manifest file. A YAML file that defines the properties and zomes of the DNA. YAML is a human-readable data serialization language. | +|
 │ │└── hello_world.dna   
| The compiled DNA file, which includes both the integrity and coordinator zomes. This file is used by Holochain to run the hApp. | +|
 │ └┬─ zomes/             
| The source code for zomes (short for chromosomes), which are the executable packages in a DNA. Each zome has its own name like `profile` or `chat`. Zomes define the core logic in a DNA, and can be composed together to create more powerful functionality. DNAs in Holochain are always composed out of one or more zomes. This folder contains zomes for the `hello_world` DNA. | +|
 │  ├┬─ coordinator/      
| This folder contains the coordinator zomes, which are responsible for this DNA's controller layer, such as reading/writing data and handling communication between peers. The public functions defined in these zomes' code become the application's API available to the UI and, depending on the needs of your app, to other peers in the same network. | +|
 │  │└┬─ hello_world/     
| Folder containing the source code for the package that will become the `hello_world` coordinator zome binary. Rust packages are called crates, and they have the following structure. | +|
 │  │ ├┬─ src/            
| Source code folder for the `hello_world` crate. | +|
 │  │ │└── lib.rs         
| The main source code file for the `hello_world` crate. In Rust, `lib.rs` is the entry point for a library crate, which is the kind of crate that a zome needs to be written as. If you have nothing else in here, you should have this file. | +|
 │  │ └── Cargo.toml      
| The manifest file for the crate that will become the `hello_world` coordinator zome, containing metadata, dependencies, and build options. This file tells Cargo, Rust's package manager, how to build the crate into a binary. | +|
 │  └┬─ integrity/        
| This folder contains the integrity zomes, which are responsible for the application's model layer, which define data structures and validation rules for application data. | +|
 │   └┬─ hello_world/     
| Folder containing the `hello_world_integrity` crate. | +|
 │    ├┬─ src/            
| Source code folder for the `hello_world_integrity` crate. | +|
 │    │└── lib.rs         
| The main source code file for the `hello_world_integrity` crate. | +|
 │    └── Cargo.toml      
| TThe Cargo manifest file for the `hello_world_integrity` crate. | +|
 ├── node_modules/        
| A folder containing cached JavaScript packages and dependencies for the user interface and tests. | +|
 ├── target/              
| A folder containing the compiled output from the Rust build process. | +|
 ├── tests/               
| A folder containing JavaScript-base test code for the application. | +|
 ├── ui/                  
| A folder containing the source code and assets for the web-based user interface of the "Hello, World!" application. This user interface will get distributed along with the application. | +|
 ├┬─ workdir/             
| A working folder containing configuration files and compliled artifacts related to the building of the whole hApp. | +|
 │├── happ.yaml           
| The manifest file for the hApp. It references the DNA files to be included, along with the roles they play in the application. In this case, there's only one DNA file, `hello_world`. | +|
 │├── hello_world.happ    
| The compiled hApp bundle, which includes all the DNAs (in case just the one). | +|
 │├── hello_world.webhapp 
| The compiled web hApp bundle, which includes the hApp bundle plus the zipped UI. | +|
 │└── web-happ.yaml       
| The manifest file for the hApp plus the UI. It references the compiled hApp bundle and zipped UI folder to be included. | +|
 ├── Cargo.lock           
| A file generated by Cargo, Rust's package manager, that lists the exact versions of dependencies used in the project. | +|
 ├── Cargo.toml           
| The main configuration file for the Rust project, containing dependencies, build options, and other metadata for all crates. | +|
 ├── flake.lock           
| A file generated by Nix, the package manager we use to distribute the Holochain development tools, that lists the exact versions of dependencies used in the project. | +|
 ├── flake.nix            
| A Nix file that defines the project's build environment and dependencies. | +|
 ├── package.json         
| The main configuration file for the JavaScript portions of the project, containing dependencies, scripts, and other metadata for the application's user interface and tests, as well certain build tools. | +|
 ├── package-lock.json    
| A file generated by npm, Node.js package manager, that lists the exact versions of dependencies used by Node.JS. | +|
 └── README.md            
| A Markdown file containing the documentation and instructions for the application, including how to build, run, and test the project. | + +These files and folders make up the structure of a Holochain application, with the main logic defined in the zomes (in the `dnas//zomes/` folders) and the user interface defined in the `ui/` folder. The manifest files bring all the Holochain and UI assets together, allowing the `hc` tool to bundle them into a single hApp file ready for distribution. + +!!! + +## 4. Zero to built: creating a forum app + +First, navigate back to the folder where you want to keep your Holochain applications. If you followed our suggestion, you can get back to it by typing: + +```shell +cd ~/Holochain +``` + +Next up, we'll walk you through creating a forum application from scratch using Holochain's scaffolding tool, step-by-step. This forum application will enable participants to share text-based posts and to comment on those posts. + +Each post will have a title and content, and authors will be able to edit --- or update --- their posts. However, they won't be able to delete them. + +Each comment will be a reply to a particular post, will be limited in length to 140 characters, and will be able to be deleted but not updated. + +!!! info Validation tutorial coming soon +A future update to this guide will implement the above constraints as validation rules. For now, we'll just scaffold enough code to get a working UI. **Check back soon** to get the whole tutorial! +!!! + +We'll create a couple of other things along the way that will enable people to find these posts and comments, but we'll cover those things when we get there. + +The good news is that the Holochain scaffolding tool will do a lot of the heavy lifting in terms of generating folders, files, and boilerplate code. It will walk you through each step in the hApp generation process. In fact, the scaffolding tool does so much of the work for you that many people have commented that 90% or more of the time spent writing a Holochain app is focused on building out the front-end user interface and experience. + +First, let's use the scaffolding tool to generate the basic folders and files for our hApp. + +### 4.1. Scaffolding a hApp {#4-1-scaffolding-a-happ} + +To start, run the following command in your terminal: + +```shell +nix run github:/holochain/holochain#hc-scaffold -- web-app +``` + +You should then see: + +::: output-block +```text +? App name (no whitespaces): +``` +::: + +Enter the name of your forum application using snake_case. Enter: + +```text +my_forum_app +``` + +### 4.2. Select user interface framework + +You'll then be prompted to choose a user interface (UI) framework for your front end. + +For this example, use the arrow keys to choose `Svelte` and press Enter. + +### 4.3. Set up Holonix development environment + +Next, you'll be asked if you want to set up the Holonix development environment for the project. This allows you to enter a shell that has all the right tools and libraries for the version of Holochain that your code was generated for. + +Choose `Yes (recommended)` and press Enter. + +You should see: + +::: output-block +```text +Setting up nix development environment... +``` +::: + +along with some details of what is being added. Follow the instructions to set up the development environment for your hApp and continue to scaffold more of its elements. + +First, enter the hApp project folder: + +```shell +cd my_forum_app +``` + +Just to get an overview of what your first scaffold command set up for you, you can check the contents of that `my_forum_app` folder by typing: + +```shell +ls +``` + +It should look like it has set up a similar set of folders and configuration files to those you saw in the "Hello, World!" hApp. + +Now, fire up the nix development shell, which makes all scaffolding tools and the Holochain binaries directly available from the command line, by entering: + +```shell +nix develop +``` + +After a short while of installing packages, you should see: + +::: output-block +```text +Holochain development shell spawned. Type exit to leave. +``` +::: + +As it says, if you want to leave the nix development shell at any time, you can type `exit`. This will take you back to your familiar shell without any of the special Holochain dependencies. When you want to re-enter it, navigate to the `my_forum_app` folder and type `nix develop` again. But for now, install the Node Package Manager (npm) dependencies with: + +```shell +npm install +``` + +These dependencies are used by various tools and assets --- the scaffolded tests, the UI, and various development activities like spawning apps for testing. + +When that finishes, you should see some text that ends with something like: + +::: output-block +```text +added 371 packages, and audited 374 packages in 1m + +37 packages are looking for funding + run 'npm fund' for details +found 0 vulnerabilities +``` +::: + +If you see something like that, you've successfully downloaded the NPM dependencies for the UI and for building your app. + +Next up, you're going to start creating the foundational building block of any Holochain app: its DNA. + +!!! dig-deeper Scaffolding subcommands + +To get an overview of the subcommands that `hc scaffold`` makes available to you, type: + +```shell +hc scaffold --help +``` + +You should see something like: + +::: output-block +```text +holochain_scaffolding_cli 0.1.8 +The list of subcommands for `hc scaffold` + +USAGE: + hc-scaffold + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +SUBCOMMANDS: + collection Scaffold a collection of entries in an existing zome + dna Scaffold a DNA into an existing app + entry-type Scaffold an entry type and CRUD functions into an existing zome + example + help Prints this message or the help of the given subcommand(s) + link-type Scaffold a link type and its appropriate zome functions into an existing zome + template Set up the template used in this project + web-app Scaffold a new, empty web app + zome Scaffold one or multiple zomes into an existing DNA +``` +::: + +You can get help on every one of these subcommands and its parameters by typing `hc scaffold --help`. +!!! + +!!! info Backing out of a mistake +A quick note: if while scaffolding some part of your hApp, you realize you've made a mistake (a typo or wrong selection for instance), as long as you haven't finished scaffolding that portion, **you can stop the current step** by using Ctrl+C on Linux or Command+C on macOS. +!!! + +### 4.4. Scaffold a DNA + +A DNA folder is where you will put the code that defines the rules of your application. You're going to stay in the `my_forum_app/` root folder and, with some simple commands, the scaffolding tool will do much of the creation of relevant folders and files for you. + +!!! dig-deeper DNAs: Context and Background {#about-dnas} + +#### Why do we use the term DNA? + +In Holochain, we are trying to enable people to **choose to participate in coherent social coordination**, or interact meaningfully with each other online without needing a central authority to define the rules and keep everyone safe. To do that, we are borrowing some patterns from how biological organisms are able to coordinate coherently even at scales that social organisations such as companies or nations have come nowhere close to. In living creatures like humans, dolphins, redwood trees, and coral reefs, many of the cells in the body of an organism (trillions of the cells in a human body, for instance) are each running a (roughly) identical copy of a rule set in the form of DNA. + +This enables many different independent parts (cells) to build relatively consistent superstructures (a body, for instance), move resources, identify and eliminate infections, and more --- all without centralized command and control. There is no "CEO" cell in the body telling everybody else what to do. It's a bunch of independent actors (cells) playing by a consistent set of rules (the DNA) coordinating in effective and resilient ways. + +A cell in the muscle of your bicep finds itself in a particular context, with certain resources and conditions that it is facing. Based on those signals, that cell behaves in particular ways, running relevant portions of the larger shared instruction set (DNA) and transforming resources in ways that make sense for a bicep muscle cell. A different cell in your blood, perhaps facing a context where there is a bacterial infection, will face a different set of circumstances and consequently will make use of other parts of the shared instruction set to guide how it behaves. In other words, many biological organisms make use of this pattern where **many participants run the same rule set, but each in its own context**, and that unlocks a powerful capacity for coherent coordination. + +Holochain borrows this pattern that we see in biological coordination to try to enable similarly coherent social coordination. However, our focus is on enabling such coherent social coordination to be "opted into" by the participants. We believe that this pattern of being able to choose which games you want to play --- and being able to leave them or adapt them as experience dictates --- is critical to enabling individual and collective adaptive capacity. We believe that it may enable a fundamental shift in the ability of individuals and communities to sense and respond to the situations that they face. + +To put it another way: if a group of us can all agree to the rules of a game, then together we can play that game. + +All of us opting in to those rules --- and helping to enforce them --- enables us to play that game together, whether it is a game of chess, chat, a forum app, or something much richer. + +#### DNA as boundary of network + +The network of participants that are running a DNA engage in "peer witnessing" of actions by the participants in that network. A (deterministically random) set of peers are responsible for validating, storing, and serving each particular piece of shared content. In other words, the users of a particular hApp agree to a set of rules and then work together collectively to enforce those rules and to store and serve content (state changes) that do not violate those rules. + +Every hApp needs to include at least one DNA. Moreover, as indicated above, **it is at the DNA level** (note: not the higher application level) **where participants will form a network of peers to validate, store, and serve content** in accordance with the rules defined in that DNA. This happens in the background as the application runs on each participant's machine. + +There are some powerful consequences to this architectural choice --- including freedom to have your application look and feel the way you want, or to combine multiple DNAs together in ways that work for you without having to get everyone else to agree to do the same --- but we'll save those details for later. + +#### So if we have multiple DNAs in our hApp... + +...then we are participating in multiple networks, with each network of peers that are participating in a particular DNA also helping maintain the shared database for each DNA, enforcing the DNA's rules while validating, storing, and serving content. Each network acts as a 'social organism' in cooperation with other networks in the hApp. + +This is similar to the way in which multiple DNA communities coexist in biological organisms. In fact, there are more cells in a human body that contain other DNA (like bacteria and other microorganisms) than cells that contain our DNA. This indicates that we are an _ecology_ of coherent communities that are interacting with --- and evolving alongside --- one another. + +When it comes to hApps, this lets us play coherent games with one another at the DNA level, while also participating in adjacent coherent games with others as well. That means that applications are not one-size-fits-all. You can choose to combine different bits of functionality in interesting and novel ways. + +!!! + +It's time to scaffold a new DNA by entering: + +```shell +hc scaffold dna +``` + +You should then see: + +::: output-block +```text +? DNA name (snake_case): +``` +::: + +Enter a name for the DNA: + +```text +forum +``` + +You should then see: + +::: output-block +```text +DNA "forum" scaffolded! +Add new zomes to your DNA with: + hc scaffold zome +``` +::: + +Success! Inside of your `dnas/` folder, the scaffolding tool generated a `forum/` folder and, inside of that, the folders and files that the DNA needs. At this point you have a skeleton structure for your `forum` DNA. As you take the following steps, the scaffolding tool will make additions and edits to some of those folders and files based on your instructions. + +### 4.5. Scaffold a zome + +DNAs are comprised of code modules, which we call zomes (short for chromosomes). Zomes are modules that typically focus on enabling some small unit of functionality. Building with this sort of modular pattern provides a number of advantages, including the ability to reuse a module in more than one DNA to provide similar functionality in a different context. For instance, the [profiles zome](https://github.com/holochain-open-dev/profiles) is one that many apps make use of. For the forum DNA, you'll be creating two zomes: `posts` and `posts_integrity`. + +Start by entering: + +```shell +hc scaffold zome +``` + +You should then see: + +::: output-block +```text +? What do you want to scaffold? › +❯ Integrity/coordinator zome-pair (recommended) + Only an integrity zome + Only a coordinator zome +``` +::: + +!!! dig-deeper Integrity zomes and coordinator zomes + +#### Integrity zomes + +An integrity zome, as the name suggests, is responsible for maintaining the data integrity of a Holochain application. It sets the rules and ensures that any data writes occurring within the application are consistent with those rules. In other words, it is responsible for ensuring that data is correct, complete, and trustworthy. Integrity zomes help maintain a secure and reliable distributed peer-to-peer network by enforcing the validation rules defined by the application developer --- in this case, you! + +#### Coordinator zomes + +On the other hand, a coordinator zome contains the code that actually commits data, retrieves it, or sends and receives messages between peers or between other portions of the application on a user's own device (between the back end and the front-end UI, for instance). A coordinator zome is where you define the API for your DNA, through which the network of peers and their data is made accessible to the user. + +#### Multiple zomes per DNA + +As you learned earlier, a DNA can have multiple integrity and coordinator zomes. Each integrity zome contributes to the full set of different types of valid data that can be written, while each coordinator zome contributes to the DNA's functionality that you expose through its API. In order to write data of a certain type, a coordinator zome needs to specify a dependency on the integrity zome that defines that data type. A coordinator zome can also depend on multiple integrity zomes. + +#### Why two types? + +They are separated from one another so we can update coordinator zomes without having to update the integrity zomes. This is important, because changes made to an integrity zome result in a change of the rule set, which results in an entirely new network. This is because the integrity code is what defines the 'rules of the game' for a group of participants. If you changed the code of an integrity zome, you would find yourself suddenly in a new and different network from the other folks who haven't yet changed their integrity zome --- and we want to minimize those sorts of forks to situations where they are needed (like when a community decides they want to play by different rules, for instance changing the maximum length of comments from 140 characters to 280 characters). + +At the same time, a community will want to be able to improve the ways in which things are done in a Holochain app. This can take the form of adding new features or fixing bugs, and we also want people to also be able to take advantage of the latest features in Holochain. Separating integrity and coordination enables them to do that more easily, because: + +* Holochain's coordinator zome API receives frequent updates while the integrity zome API is fairly stable, and +* coordinator zomes can be added to or removed from a DNA at runtime without affecting the DNA's hash. + +!!! + +For this app, you're going to want both an integrity zome and a coordinator zome, so use the arrow keys to select: + +::: output-block +```text +Integrity/coordinator zome-pair +``` +::: + +and press Enter. + +You should then see: + +::: output-block +```text +? Enter coordinator zome name (snake_case): + (The integrity zome will automatically be named '{name of coordinator zome}_integrity') +``` +::: + +Enter the name: + +```text +posts +``` + +and press Enter. + +You should then see prompts asking if you want to scaffold the integrity and coordinator zomes in their respective default folders. + +Press Y for both prompts. + +As that runs (which will take a moment as the scaffold makes changes to various files) you should then see something like: + +::: output-block +```text +Coordinator zome "posts" scaffolded! +Updating crates.io index + Fetch [===> ] ... +``` +::: + (then after download is done...) +::: output-block +```text + Downloaded 244 crates (46.7 MB) in 4.27s (largest was `windows` at 11.9 MB) + +Add new entry definitions to your zome with: + hc scaffold entry-type +``` +::: + +Once that is all done, your hApp skeleton will have filled out a bit. Before you scaffold the next piece, it might be good to get a little context for how content is "spoken into being" when a participant publishes a post in a forum hApp. Read the following section to learn more. + +!!! dig-deeper Source chains, actions, and entries + +#### Source chain + +Any time a participant in a hApp takes some action that changes data, they add a record to a journal called a **source chain**. Each participant has their own source chain, a local, tamper-proof, and chronological store of the participant's actions in that application. + +This is one of the main differences between Holochain and other systems such as blockchains or centralized server-based applications. Instead of recording a "global" (community-wide) record of what actions have taken place, in Holochain actions are taken by agents and are thought of as transformations of their own state. + +One big advantage of this approach is that a single agent can be considered authoritative about the order in which they took actions. From their perspective, first they did A, then B, then C, etc. The fact that someone else didn't get an update about these changes, and possibly received them in a different order, doesn't matter. The order that the authoring agent took those actions will be captured in the actions themselves (thanks to each action referencing the previous one that they had taken, thus creating an ordered sequence --- or chain --- of actions). + +#### Actions and entries + +You'll notice that we used the word "action" a lot. In fact, **we call the content of a source chain record an action**. In Holochain applications, data is always "spoken into being" by an agent (a participant). Each record captures their act of adding, modifying, or removing data, rather than simply capturing the data itself. + +There are a few different kinds of actions, but the most common one is `Create`, which creates an 'entry' --- an arbitrary blob of bytes. Entries store most of the actual content created by a participant, such as the text of a post in our forum hApp. When someone creates a forum post, they're recording an action to their source chain that reads something like: _I am creating this forum post entry with the title "Intros" and the content "Where are you from and what is something you love about where you live?" and I would like my peers in the network to publicly store a record of this act._ So while an action is useful for storing noun-like data like messages and images, it's actually a verb, a record of an action that someone took to update their own state and possibly the shared database state as well. That also makes it well-suited to verb-like data like real-time document edits, game moves, and transactions. + +Every action contains the ID of its author (actually a cryptographic public key), a timestamp, a pointer to the previous source chain record, and a pointer to the entry data, if there is any. In this way, actions provide historical context and provenance for the entries they operate on. + +The pointer to the previous source chain record creates an unbroken history from the current record all the way back to the source chain's starting point. This 'genesis' record contains the hash of the DNA, which servs as both the identifier for the specific set of validation rules that all following records should follow and the ID of the network that this source chain's actions are participating in. + +An action is cryptographically signed by its author and is immutable (can't be changed or erased from either the source chain or the network's data store) once written. This, along with the validation rules specified by the DNA hash in the genesis record, are examples of a concept we call "intrinsic data integrity", in which data carries enough information about itself to be self-validating. + +Just as with a centralized application, we aren't just going to add this data into some database without checking it first. When a participant tries to write an action, Holochain first: + +1. ensures that the action being taken doesn't violate the validation rules of the DNA, +2. adds it as the next record to the source chain, and then +3. tells the participant's network peers about it so they can validate and store it, if it's meant to be public. + +The bits of shared information that all the peers in a network are holding are collectively called a distributed hash table, or DHT. We'll explain more about the DHT later. + +If you want to learn more, check out [The Source Chain: A Personal Data Journal](/concepts/3_source_chain/) and [The DHT: A Shared, Distributed Graph Database](/concepts/4_dht/). You'll also get to see it all in action in a later step, when you run your hApp for the first time. + +!!! + +Now it's time to start defining the structure and validation rules for data within your application. + +### 4.6. Scaffold entry types + +An entry type is a fundamental building block used to define the structure and validation rules for data within a distributed application. Each entry type corresponds to a specific kind of data that can be stored, shared, and validated within the application. + +!!! dig-deeper Entry types and validation + +An entry type is just a label, an identifier for a certain type of data that your DNA deals with. It serves as something to attach validation rules to in your integrity zome, and those rules are what give an entry type its meaning. They take the form of code in a function that gets called any time something is about to be stored, and because they're just code, they can validate all sorts of things. Here are a few key examples: + +* **Data structure**: When you use the scaffolding tool to create an entry type, it generates a Rust-based data type that define fields in your entry type, and it also generates code in the validation function that attempts to convert the raw bytes into an instance of that type. By providing a well-defined structure, this type ensures that data can be understood by the application. If it can't be deserialized into the appropriate Rust structure, it's not valid. + +* **Constraints on data**: Beyond simple fields, validation code can constrain the values in an entry --- for instance, it can enforce a maximum number of characters in a text field or reject nonsensical calendar dates. + +* **Privileges**: Because it originates in a source chain, an entry comes with metadata about its author. This can be used to control who can create, edit, or delete an entry. + +* **Contextual conditions**: Because an action is part of a chain of actions, it can be validated based on the agent's history --- for instance, to prevent currency transactions beyond a credit limit or disallow more than two comments per minute to discourage spam. An entry can also point to other entries in the DHT upon which it depends, and the data from those entries can be used in its validation. + +!!! + +Your bare-bones forum needs two entry types: `post` and `comment`. You'll define these in the `posts` integrity zome you just created in the previous step. The `post` entry type will define a `title` field and a `content` field. The `comment` entry type will define a `comment_content` field and a way of indicating which post the comment is about. + +To do this, just follow the instructions that the scaffold suggested for adding new entry definitions to your zome. + +Start with the `post` entry type by entering this command: + +```shell +hc scaffold entry-type +``` + +You should then see: + +::: output-block +```text +✔ Entry type name (snake_case): +``` +::: + +Enter the name: + +```text +post +``` + +You should then see: + +::: output-block +```text +Which fields should the entry contain? + +? Choose field type: › +❯ String + bool + u32 + i32 + f32 + Timestamp + ActionHash + EntryHash + DnaHash + AgentPubKey + Enum + Option of... + Vector of... +``` +::: + +The scaffolding tool is now prompting you to add fields to the `post` entry type. + +Fields are the individual components or attributes within an entry type that define the structure of the data. They determine the specific pieces of information to be stored in an entry and their respective data types. The scaffolding tool supports a collection of native Rust types such as booleans, numbers, enums (a choice between several predetermined values), optional values, and vectors (lists of items of the same type), along with Holochain-specific types that refer to other pieces of data on the DHT. + +For your `post` entry type, you're going to add `title` and `content` fields. Select `String` as the first field's type, and enter: + +```text +title +``` + +as the field name. + +Press Y for the field to be visible in the UI, and use the arrow keys to select `TextField` as the widget to render this field. (A `TextField` is a single-line input field designed for capturing shorter pieces of text.) + +When you see: + +::: output-block +```text +?Add another field to the entry?(y/n) +``` +::: + +press Y. + +Select `String` for this field's type too. Then enter + +```text +content +``` + +as the field name. + +Press Y for the field to be visible in the UI, and select `TextArea` as the widget to render the field. (A `TextArea` is a multi-line input field that allows users to enter larger blocks of text. That'll work better for blog posts.) + +After adding the `title` and `content` fields, press N when asked if you want to add another field. Next, you should see: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? + Update +✔ Delete +``` +::: + +The scaffolding tool can add zome and UI functions for updating and deleting entries of this type. In this case, we want authors to be able to update posts, but not delete them, so use the arrow keys and the spacebar to ensure that `Update` has a check and `Delete` does not. It should look like this: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? +✔ Update + Delete +``` +::: + +Then press Enter. + +At this point you should see: + +::: output-block +```text +? Should a link from the original entry be created when this entry is updated? › +❯ Yes (more storage cost but better read performance, recommended) + No (less storage cost but worse read performance) +``` +::: + +Select `Yes` by pressing Enter. + +!!! dig-deeper CRUD (create, read, update, delete) {#crud-create-read-update-delete} + +#### Mutating immutable data and improving performance + +In short, the above choice is about how changes get dealt with when a piece of content is updated. + +Because all data in a Holochain application is immutable once it's written, we don't just go changing existing content, because that would break the integrity of the agent's source chain as well as the data already in the DHT. So instead we add metadata to the original data, indicating that people should now look elsewhere for the data or consider it deleted. This is produced by `Update` and `Delete` source chain actions. + +For an `Update` action, the original `Create` or `Update` action and its entry content on the DHT get a `ReplacedBy` pointer to the new `Update` action and its entry content. + +When the scaffolding tool asks you whether to create a link from the original entry, though it's not talking about this pointer. Instead, it's talking about an extra piece of metadata that points to the _very newest_ entry in a chain of updates. If an entry were to get updated, and that update were updated, and this were repeated three more times, anyone trying to retrieve the entry would have to query the DHT six times before they finally found the newest revision. This extra link, which is not a built-in feature, 'jumps' them past the entire chain of updates at the cost of a bit of extra storage. The scaffolding tool will generate all the extra code needed to write and read this metadata in its update and read functions. + +For a `Delete` action, the original action and its entry content are simply marked as deleted. In the cases of both updating and deleting, all original data is still accessible if the application needs it. + +#### Resolving conflicts + +Multiple participants can mark a single entry as updated or deleted at the same time. This might be surprising, but Holochain does this for two good reasons. First, it's surprisingly difficult to decide which is the 'correct' version of a piece of data in a distributed system, because contributions may come from any peer at any time, even appearing unexpectedly long after they've been created. There are many strategies for resolving the conflicts that arise from this, which brings us to the second good reason: we don't want to impose a specific conflict resolution strategy on you. Your application may not even consider parallel updates and deletes on a single entry to be a conflict at all. + +#### CRUD functions + +**By default, the scaffolding tool generates a `create_' function in your coordinator zome for an entry type** because creating new data is a fundamental part of any application, and it reflects the core principle of Holochain's agent-centric approach --- the ability to make changes to your own application's state. + +Similarly, when a public entry is published, it becomes accessible to other agents in the network. Public entries are meant to be shared and discovered by others, so **a `read_' function is provided by default** to ensure that agents can easily access and retrieve publicly shared entries. (The content of _private_ entries, however, are not shared to the network.) For more info on entries, see: the **Core Concepts sections on [Source Chains](/concepts/3_source_chain/) and [DHT](/concepts/4_dht/)**. + +Developers decide whether to let the scaffolding tool generate `update_` and `delete_` functions based on their specific application requirements. More details in the Core Concepts section on [CRUD](/concepts/6_crud_actions/). + +!!! + +Next, you should see: + +::: output-block +```text +Entry type "post" scaffolded! + +Add new collections for that entry type with: + + hc scaffold collection +``` +::: + +We'll dive into collections in a moment, but first create the `comment` entry type. + +Again type: + +```shell +hc scaffold entry-type +``` + +This time enter the name: + +```text +comment +``` + +for the entry type name. + +You're going to add a `comment_content` field, so select the `String` field type and enter: + +```text +comment_content +``` + +Then select the `TextArea` widget and press Enter. (Again, a `TextArea` is a multi-line input field that allows users to enter larger blocks of text. Perfect for a comment on a post.) + +Press Y to add another field. + +For this next field you'll want to create a field that will help you associate each particular comment to the post that it is commenting on. To do this, the next field in the `comment` entry type will store a reference to a `post`. + +Use the arrow keys to select `ActionHash` as the field type. + +!!! dig-deeper Hashes and other identifiers + +There are two kinds of unique identifiers or 'addresses' in Holochain: **hashes** for data and **public keys** for agents. + +A hash is a unique "digital fingerprint" for a piece of data, generated by running it through a mathematical function called a **hash function**. None of the original data is present in the hash, but even so, the hash is extremely unlikely to be identical to the hash of any other piece of data. If you change even one character of the entry's content, the hash will be radically (and unpredictably) different. + +Holochain uses a hash function called blake2b. You can play with [an online blake2b hash generator](https://toolkitbay.com/tkb/tool/BLAKE2b_512) to see how changing content a tiny bit alters the hash. Try hashing `hi` and then `Hi` and compare their hashes. + +To ensure data integrity and facilitate efficient data retrieval, each piece of data is identified by its hash. This serves the following purposes: + +* **Uniqueness:** The cryptographic hashing function ensures that the data has a unique hash value, which helps to differentiate it from other data on the network. +* **Efficient lookup:** The hash is used as a key (essentially an address) in the network's storage system, the distributed hash table (DHT). When an agent wants to retrieve data, they simply search for it by hash, without needing to know what peer machine it's stored on. In the background, Holochain reaches out simultaneously to multiple peers who are responsible for the hash based on an algorithm that matches peers to data based on the similarity of the hash to their agent IDs. This makes data lookup fast and resilient to unreliable peers or network conditions. +* **Fair distribution:** Because the participants in a network are responsible for validating and storing each other's public data based on its hash, the randomness of the hashing function ensures that that responsibility is spread fairly evenly among everyone. +* **Integrity verification:** `Hi` will always generate the same hash no matter who runs it through the hashing function. So when data is retrieved by hash, its hash can be recalculated and compared with the original requested hash to ensure that a third party hasn't tampered with the data. +* **Collusion resistance:** The network peers who take responsibility for validating and storing an entry are chosen randomly based on the similarity of their agent IDs to the `EntryHash`. It would take a huge amount of computing power to generate a hash that would fall under the responsibility of a colluding peer. And because Holochain can retrieve data from multiple peers, it's more likely that the requestor can find one honest peer to report problems with a piece of bad data. + +#### `ActionHash` + +An action is identified by its `ActionHash`. Because an action contains information about its author, the time it was written, the action that preceded it, and the entry it operates on, no two action hashes will be the same --- even for the same entry. This helps to disambiguate identical entries written at different times by different agents. + +#### `EntryHash` + +An entry is identified by its `EntryHash`, which can be retrieved from the `ActionHash` of the action that wrote it. Because they're two separate pieces of data, an entry is stored by different peers than the action that operates on it. + +#### `AgentPubKey` + +**Each agent in a network is identified by their cryptographic public key**, a unique number that's mathematically related to a private number that they hold on their machine. Public-key cryptography is a little complex for this guide --- it's enough to know that a participant's private key signs their source chain actions, and those signatures paired with their public key allow others to verify that they are the one who authored those actions. + +An `AgentPubKey` isn't a hash, but it's the same length, and it's unique just like a hash. So it can be used as a way of referring to an agent, like a user ID --- and this is also why it's used to choose the right peers in the DHT storage and retrieval algorithm. + +#### Summary + +Whereas `EntryHash` is used to uniquely identify, store, and efficiently retrieve an entry from the DHT, `ActionHash` is used to uniquely identify, store, and retrieve the action (metadata) that operated on it, which can provide information about the history and context of any associated entry (including what action preceded it). `ActionHash`es are also what enable any participant to retrieve and reconstruct the continuous sequence of actions (and any associated entries) in another agent's source chain. + +**Use `EntryHash` when** you want to link to or retrieve the actual content or data (e.g., when linking to a category in a forum application). + +**Use `ActionHash` when** you want to link to or retrieve the authorship or history of an entry (e.g., when distinguishing between two posts with identical content). + +**Use `AgentPubKey` when** you want to link to an agent (such as associating a profile or icon with them) or retrieve information about their history (such as scanning their source chain for posts and comments). + +You can check out the Core Concepts to dive a bit deeper into [how the distributed hash table helps](/concepts/4_dht/) to not only make these entries and actions available but helps to ensure that agents haven't gone back to try and change their own histories after the fact. But for now, let's dive into links. + +!!! + +After press Enter, you should see: + +::: output-block +```text +? Should a link from this field be created when this entry is created? (y/n) › +``` +::: + +Press Y to accept creating a link. + +Next you will see: + +::: output-block +```text +✔ Which entry type is this field referring to? +``` +::: + +Press Enter to accept the suggested entry type `Post`. + +Next, you will be asked to pick a field name. You can press Enter to accept the field name suggestion, which should be: + +```text +post_hash +``` + +Press N to decline adding another field to the entry. + +Then use the arrow keys to deselect Update, but leave Delete selected. It should look as follows: + +::: output-block +```text +Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? + Update +✔ Delete +``` +::: + +Once that is done, press Enter to generate a delete function for the **`comment`** entry type. + +You should then see: + +::: output-block +```text +Entry type "comment" scaffolded! + +Add new collections for that entry type with: + + hc scaffold collection +``` +::: + +The scaffolding will now have both added the `comment` entry type, and added a bunch more very useful code to our app using the native Holochain affordance of links. Links allow us to create paths that agents can follow to find associated content. So, the scaffolding not only added a reference to the post in the comment's entry, but it also added code such that when a comment is added, a link from the post back to the comment will also be created. If you want to see some of that code, take a look at the `dnas/forum/zomes/integrity/posts/src/lib.rs` file and you should see right near the top that a function has been created for validating the creation of a `post_to_comments` link. Similarly, other validation functions related to the deletion of those links follow after. + +!!! dig-deeper How links are stored and retrieved in a Holochain app + +What exactly is a link? Where is it stored? How does it work? And what do they let us do that we couldn't do otherwise? + +Links enable us to build a graph of references from one piece of content to other pieces of content in a DHT and then to navigate that graph. This is important because without some sort of trail to follow, it is infeasible to just "search for all content" thanks to the address space (all possible hashes) being so large and spread out across machines that iterating through tme all could take millions of years. + +By linking from known things to unknown things, we enable the efficient discovery and retrieval of related content in our hApp. + +**Storage**: When an agent creates a link between two entries, a `CreateLink` action is written to their source chain. A link is so small that there's no entry for the action. It simply contains the address of the base, the address of the target, the link type (which describes the relationship), and an optional tag which contains a small amount of application-specific information. The base and target can be any sort of DHT address --- an `EntryHash`, an `ActionHash`, or an `AgentPubKey`. But there doesn't actually need to be any data at that base, which is useful for referencing data that exists in another hash-based data store outside the DHT. + +After storing the action in the local source chain, the agent then publishes the link to the DHT, where it goes to the peers who are responsible for storing the base address and gets attached to the address as metadata. + +**Lookup**: To look up and retrieve links in a Holochain app, agents can perform a `get_links` query on a base DHT address. This operation involves asking the DHT peers responsible for that address for any link metadata of a given link type attached to it, with an optional "starts-with" query on the link tag. The peers return a list of links matching the query, which contain the addresses of the targets, and the link types and tags. The agent can then retrieve the actual target data by performing a [`get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) query on the target address, which may be an `EntryHash`, `ActionHash`, or `AgentPubKey` (or an empty result, in the case of data that doesn't exist on the DHT). + +For more information and examples, read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/). + +!!! + +### 4.7. Scaffold a collection + +Now, let's create a collection that can be used to retrieve all the posts. A collection creates a link type for referring to the collected entry type (similarly to how a link type was created for linking from posts to comments), but collections also create an 'anchor' --- a small string --- as the base for the link so we can find all the items in the collection by starting from the anchor's known hash. + +To create a collection, type: + +```shell +hc scaffold collection +``` + +You should then see: + +::: output-block +```text +Collection name (snake_case, eg. "all_posts"): › +``` +::: + +Enter: + +```text +all_posts +``` + +and press Enter. You should then see: + +::: output-block +```text +? Which type of collection should be scaffolded? › +❯ Global (get all entries of the selected entry types) + By author (get entries of the selected entry types that a given author has created) +``` +::: + +Select **`Global`** and press Enter. You should then see: + +::: output-block +```text +? Which entry type should be collected? › +❯ Post + Comment +``` +::: + +Select **`Post`** and press Enter. You should then see: + +::: output-block +```text +Collection "all_posts" scaffolded! + +At first, the UI for this application is empty. If you want the newly scaffolded collection to be the entry point for its UI, import the element in `ui/src/App.svelte`: + + import AllPosts from './forum/posts/AllPosts.svelte'; + +And use the element in the `<div id="content" />` block by adding in this: + + <div id="content"><</AllPosts></div> +``` +::: + +These instructions tell us that if we want to include this generated UI component in the user interface of our hApp, we need to do some manual work: + + 1. Import the component, and + 2. Tell the UI to display the component. + +!!! dig-deeper How a collection is implemented + +We already explored how links make data in the DHT discoverable by connecting known DHT base addresses to unknown addresses. Essentially every address becomes an anchor point to hang a collection of links from. + +But there's one remaining problem: _where do you start?_ When someone starts their app for the first time, the only DHT base addresses they know about are their public key, the DNA hash, and the few actions and entries on their source chain. There's no obvious way to start discovering other people's data yet. + +This is where **collections** help out. A collection is just a bunch of links on a base address that's easy to find --- typically the address is hard-coded in the coordinator zome's code as the hash of a string such as `"all_posts"`. It's easy to get the links, because their base address is right there in the code. + +This pattern, which we call the "anchor pattern", is so useful that it's built right into Holochain's SDK --- integrity zomes that use it will have all the necessary entry and link types automatically defined, and coordinator zomes that use it will have functions that can retrieve anchors and the links attached to them. The scaffolded code uses this implementation behind the scenes. + +The built-in implementation is actually a simplification of a more general pattern called "paths", which is also built into the SDK. With paths, you can create trees of linked anchors, allowing you to create and query hierarchical structures. This can be used to implement categories, granular collections (for example, "all posts" → "all posts created in 2023" → "all posts created in 2023-05" → "all posts created on 2023-05-30"), and indexes for type-ahead search (for example, "all usernames" → "all usernames starting with 'mat'" → "all usernames starting with 'matt'"). What the SDK calls an `Anchor` is actually a tree with a depth of two, in which the root node is two empty bytes. + +Hierarchical paths serve another useful purpose. On the DHT, where every node is tasked with storing a portion of the whole data set, some anchors could become "hot spots" --- that is, they could have thousands or even millions of links attached to them. The nodes responsible for storing those links would bear a disproportionate data storage and serving burden. + +The examples of granular collections and type-ahead search indexes breaks up those anchors into increasingly smaller branches, so that each leaf node in the tree --- and hence each peer --- only has to store a small number of links. + +The scaffolding tool doesn't have any feature for building anchors and trees beyond simple one-anchor collections, but if you'd like to know more, you can read the Core Concepts section on [Links and Anchors](/concepts/5_links_anchors/) and the SDK reference for [`hash_path`](https://docs.rs/hdk/latest/hdk/hash_path/index.html) and [`anchor`](https://docs.rs/hdk/latest/hdk/hash_path/anchor/index.html). + +!!! + +Before you get started editing the UI, it's helpful to be able to actually run the scaffolded applciation. That way, you can watch changes take effect in real-time as you make them. So the next section will walk you through launching the application the tooling that's available there, and then in the section after that, we'll begin working with the `.svelte` files to build the UI. + +### 4.8. Run your application in dev mode + +At this stage, we'll incorporate some of the UI components that have been scaffolded by the scaffolding tool into our main application interface. Our aim here is to make all the functionality of our forum application accessible from a single, unified interface. We'll use Svelte to accomplish this, as it is the framework that we have chosen for the UI layer of our application. + +Start the forum hApp in develop mode from the command line: go to your terminal and, from the root folder (`my_forum_app/`), enter: + +```shell +npm start +``` + +!!! info Work in the nix shell +If you are having an issue, make sure that you are still in the nix shell. If not, re-enter `nix develop` first, then type the above command again. And remember that you can always exit nix shell by typing `exit` to get back to your normal shell. +!!! + +When you start the hApp with `npm start`, this launches Holochain in sandbox mode with two agents running that hApp, and opens three windows: + +1. A web browser window with Holochain Playground, a tool that makes visible the various actions that have taken place in our forum hApp. You should be able to see a couple of agents in a DHT, with mostly empty source chains and, correspondingly, a mostly empty graph. +2. An application window with one agent (conductor 0) running the forum hApp. This window lets us take actions as that agent (0, or Alice, if you prefer). +3. Another application window with a second agent (conductor 1) running the forum hApp. This window lets us take actions as the other agent (1, or Bob). + +![Three windows: two agent UIs and a web browser window with the Holochain Playground](/assets/img/get-started/3-two-uis-and-playground.png) + +These application windows allow us to test multiple agents in a Holochain network interacting with one another. It is all running on our one device, but the two conductors behave very much the same as separate agents on different machines would, minus network lag. + +Remember that a **conductor** is a Holochain runtime process executing on your computer. For more details see the [Application Architecture](/concepts/2_application_architecture/) section in the Core Concepts guide. + +These three windows together will let us interact with our hApp as we are building it. + +The Holochain Playground in particular is helpful because it creates visual representations of the data that has been created and the way it relates to other content. Take a look at it and click one of the two items in the **DHT Cells** window. These are your two agents. When you click one of them, some content gets displayed in the **Source Chain** window. These are the initial actions in that agent's source chain. The arrows point from newer content back to older content. + +From oldest to newest, in the newly created source chains, the records are: + +1. `DNA`, recording the hash of the DNA to be used to validate all subsequent source chain actions, +2. `AgentValidationPkg`, providing proof that this participant is allowed to participate in this hApp ([see more](https://www.holochain.org/how-does-it-work/) in Holochain: How does it work?), +3. A `Create` action which records the author's `AgentID`, which is their public key and serves as their ID in the network and its graph database. + +As agents begin writing posts, comments, and links to the DHT, you'll see the following records appear: + +4. `InitComplete`, indicating that all coordinator zomes have had a chance to do initial setup, then +5. Whatever actions the agent takes after that. + +The two application UI windows let you interact with the application and see what is working, what is not working, and how data propagates when we take particular actions. + +At first, each of the UI windows (conductors 0 for Alice and 1 for Bob) include instructions for you to go and examine the scaffolded UI elements by looking at the contents in the folder `ui/src///`, where `` and `` are generic placeholders for your DNA (`forum`) and zome (`post`). + +### 4.9. Integrate the generated UI elements + +Thus far, seven different UI components should have been generated as `.svelte` files in the `ui/src/forum/posts/` folder. Note that for ease of development, the sandbox testing environment live-reloads the UI as you edit UI files. So don't quit the process you started with `npm start`; instead, **open a new terminal window**. Then navigate to the root folder of your hApp (`my_forum_app/`) and list the files in `ui/src/forum/posts/` by entering: + +```shell +ls ui/src/forum/posts/ +``` + +You should see seven different `.svelte` files, plus a `types.ts` file: + +::: output-block +```text +AllPosts.svelte CreateComment.svelte PostDetail.svelte +CommentDetail.svelte CreatePost.svelte types.ts +CommentsForPost.svelte EditPost.svelte +``` +::: + +The next step is to edit the UI files in the text editor or integrated development environment of your choice to add scaffolded components and build a fully featured UI. To integrate all of these generated UI elements, you'll need to add them to `App.svelte` file located in the `ui/src/` folder, or to some other `.svelte` file that eventually gets included in `App.svelte`. + +If you don't yet have path commands for opening files in your prefered IDE, there are instructions for [VSCode/VSCodium](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line), [Sublime Text](https://www.sublimetext.com/docs/command_line.html#setup) and [WebStorm](https://www.jetbrains.com/help/webstorm/working-with-the-ide-features-from-command-line.html#5d6e8844). Going forward in this tutorial, we are going to use the `code` command when we mean for you to open files in your IDE, but you should substitute a different command (ex: `subl`, `vim`, `emacs` etc.) for `code` if you are using a different editor. + +Open the `App.svelte` file with your preferred IDE. + +```shell +code ui/src/App.svelte +``` + +Your `App.svelte` file will have three sections: + +1. a script section, +2. a main section containing a markup template, and +3. a style section containing a stylesheet template. + +!!! dig-deeper Detailed breakdown of `App.svelte` + +#### ` +``` + +This section contains the JavaScript/TypeScript code for the component. It imports various dependencies needed to build a single-page web app: + +* `svelte` is the Svelte engine itself, and its `onMount` function lets you register a handler to be run when the component is initialized, while `setContext` lets you pass data to be used in the rendering of the component. +* `@holochain/client` is the Holochain client library; first we load in some useful Holochain-related TypeScript types, followed by the client object itself. +* `@mwc/material-circular-progress` is just a UI component that gives us a spinner when something is loading. +* `./contexts` is generated by the scaffolding tool. It just contains a constant, the app-wide name for the 'context' that makes the Holochain client accessible to all components. In Svelte, a context is a state shared across components. + +After importing dependencies, it does some initial setup. This is run when the component file is imported --- in this case the component, `App.svelte`, is the main component for the entire application, and it's imported into `main.ts` where it's 'mounted' (more on mounting in a moment). + +Next some variables are instantiated: one to hold the Holochain client that connects to the hApp backend via the conductor, and one to keep track of whether the client is connected yet. (This variable will be used to show a loading spinner while it's still connecting.) + +**Take note of the line that starts with `$:`**. This is a special Svelte label that turns regular variables into **reactive variables**. We won't get too deep into Svelte right now, because this is a tutorial about Holochain, but when a reactive variable changes, Svelte will re-render the entire component. This lets you write a template declaratively, enclosing the reactive variable in `{}` braces, and let Svelte handle the updating of the template wherever the variable changes. + +Finally, there's an `onMount` handler, which is run when the component is first displayed. The handler currently does one thing: it connects to the hApp backend via the conductor, waits until the connection is establised, sets `loading` to false, and adds the resulting client connection to the context so that all components can access it. + +#### `
` section + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+

EDIT ME! Add the components of your app here.

+ + Look in the ui/src/DNA/ZOME folders for UI elements that are generated with hc scaffold entry-type, hc scaffold collection and hc scaffold link-type and add them here as appropriate. + + For example, if you have scaffolded a "todos" dna, a "todos" zome, a "todo_item" entry type, and a collection called "all_todos", you might want to add an element here to create and list your todo items, with the generated ui/src/todos/todos/AllTodos.svelte and ui/src/todos/todos/CreateTodo.svelte elements. + + So, to use those elements here: +
    +
  1. Import the elements with: +
    +import AllTodos from './todos/todos/AllTodos.svelte';
    +import CreateTodo from './todos/todos/CreateTodo.svelte';
    +        
    +
  2. +
  3. Replace this "EDIT ME!" section with <CreateTodo></CreateTodo><AllTodos></AllTodos>.
  4. +
+
+ {/if} +
+``` + +This section is a template for the displayable content of the main app component. Using an `{#if}` block to test whether the reactive variable `loading` is true, this section displays a spinner until the backend can be accessed. Once the UI is connected to the backend, it shows some boilerplate text telling you to add something meaningful to the template. + +#### ` +``` + +This section is a template for the CSS styles that get applied to the HTML in the `
` section of the component. You can also use reactive variables here, and the styling will update whenever the variables change. These scaffolded styles set the component up with some basic layout to make it readable at small and large window sizes. + +**All Svelte components follow this general pattern**. `App.svelte` has special status as the root component, but otherwise it's just like any other component. + +!!! + +First you'll be adding a list of posts to the app, which means the components called `AllPosts.svelte` needs to be imported. + +At the top of the file, there is a list of scripts that are imported. Following the instructions that the scaffolding tool and the two conductor windows gave you, copy the following text and paste it into the script block of the `App.svelte` file, on the line below `import { clientContext } from './contexts';` + +```typescript +import AllPosts from './forum/posts/AllPosts.svelte'; +``` + +Next, add the component to the markup template in the `
` section of the file, where the "EDIT ME!" content now lives. Remove everything inside the `div` element that starts with this tag: + +:::output-block +```svelte +
+``` +::: + +and replace it with this line: + +```svelte + +``` + +Your `
` block should now look like this: + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+ +
+ {/if} +
+``` + +!!! info Svelte component tags +The `AllPosts` element is obviously not standard HTML. In Svelte, each component has a correspondingly named custom element that will get replaced by the rendered component's markup wherever it appears in another component's template. +!!! + +Save that file and take a look again at the two UI windows. They should both say 'No posts found'. + +![A UI showing the AllPosts component, which says 'No posts found'](/assets/img/get-started/4-no-posts-found.png) + +Let's fix that by adding the post creation component to the UI so we can add our first post. Import the `CreatePost.svelte` component by adding this line in the script section, just below the `AllPosts` component you previously imported: + +```typescript +import CreatePost from './forum/posts/CreatePost.svelte'; +``` + +Add this new component to the `
` block above the component you added: + +```svelte + +``` + +Now your `
` block should look like this: + +```svelte +
+ {#if loading} +
+ +
+ {:else} +
+ + +
+ {/if} +
+``` + +Save the file and switch to one of the two conductor windows. You should now see a post form. + +![The UI after adding the CreatePost component](/assets/img/get-started/5-create-post-component.png) + +Type something into one of the two conductor windows like: + +* Title: `Hi from Alice` +* Content: `Hello Bob!` + +and then press the "Create Post" button. + +You'll immediately notice that the `AllPosts` component has changed from saying "No posts found" to showing the newly created post. And if you take a look at the Holochain Playground window, you will see that two new actions have been created. If you click the `App` element that's appeared in Alice's source chain, it will pull up some details in the Entry Contents section, including the title and content of Alice's forum post. Note the hash of that entry (top of the Entry Contents window). Then click on the `Create` action that's pointing toward that `App` entry in the source chain. If you look back at the contents window, you will see that it is now sharing the contents of the action. And if you look down the list a bit, you will see the hash of the entry for the first post. + +![The Holochain playground showing a single agent's source chain, containing the actions that create a post, as well as the transformations in the DHT that resulted from these actions](/assets/img/get-started/6-playground-first-post.png) + +!!! dig-deeper Relationships in a source chain versus relationships in the DHT + +At this point, in our DHT graph it should look like we have two different agents and then a separate floating entry and action. But we know that the new post is associated with a source chain which is associated with an agent. So why aren't they connected on the DHT? + +A source chain merely serves as a history of one agent's attempts to manipulate the state of the graph database contained in the DHT. It's useful to think of the DHT as a completely separate data store that doesn't necessarily reflect agent-to-entry relationships unless you explicitly create a link type for them. + +For the purpose of this hApp, we're not interested in agent-to-posts relationships, so it's fine that they're not linked. But if you wanted to create a page that showed all posts by an author, that's when you might want to scaffold that link type. `hc scaffold collection` will do this for you if you choose a by-author collection, and will also create a `get_posts_by_author` function. + +!!! + +You may also notice that only Alice's UI showed the new post, while Bob's didn't. Just as with a traditional web app, database changes don't automatically send out a notification to everyone who is interested. (Alice's UI sees the changes because it knows how to update its own state for local changes.) You can create this functionality using a feature called [signals](/concepts/9_signals/), but let's keep things simple for now. Right-click anywhere in Bob's UI then choose "Reload" from the menu, and you'll see that the changes have been copied from Alice's app instance to Bob's --- all without a database server in the middle! + +Let's edit that post. In Alice's UI window, click the edit adjacent to the post content (it should look like a pencil icon). The post content will be replaced by an editing form. + +Now alter the content a bit. Maybe change it from `Hello Bob!` to `Hello, World!` and click "Save". + +![The UI of one agent, showing a post about to be edited](/assets/img/get-started/7-edit-post.png) + +That should update the post (at least for Alice). Bob's UI will show the updated version the next time it's reloaded. + +If you look at the Holochain Playground, you should see that the update was added to Alice's source chain. Specifically, it created: + +1. a new entry (with our `Hello, World!` text), +2. an `Update` action that indicated this entry is to replace the original entry, and +3. a `CreateLink` action that connects the original create action to the update action. + +![The Holochain playground, showing the source chain of the agent who edited the post along with new data in the DHT reflecting the edit](/assets/img/get-started/8-playground-after-edits.png) + +As explained [previously](#crud-create-read-update-delete), the original forum post already has a 'link' of sorts pointing from its action to the `Update` action, which can be accessed when the original is retrieved. The extra link created by the `CreateLink` action is optional --- it merely speeds up retrieval when an action has been edited many times and has a long chain of update links, by allowing you to jump to the end of the chain. In the screenshot above, the link is highlighted in the DHT pane. + +Now it's time to add commenting to your app. + +Previously, you added new components to the `App.svelte` component. That made sense because posts were a global data type. But comments are related to a post, so from now on you'll be modifying the `PostDetail.svelte` component instead. + +Open up `PostDetail.svelte` in your IDE: + +```shell +code ui/src/forum/posts/PostDetail.svelte +``` + +Just as before, first you'll need to import the components near the top of the file (just after the line that imports `EditPost.svelte`): + +```typescript +import CreateComment from './CreateComment.svelte'; +import CommentsForPost from './CommentsForPost.svelte'; +``` + +Further down the file, in the template block, add the components' elements to the template. Put them both before the closing `
` tag. + +Here, the comment components need to know what post they're related to. The post hash is the unique ID for the post, and the comment components' elements both expect a `postHash` attribute. This hash is available in the `PostDetail` component as a variable of the same name, so it can be passed to the comment widgets. + +```svelte + + +``` + +Save the file, then go back to the UI windows to see the changes. Try typing in a comment or two, then deleting them. (You may need to refresh the UI windows to see the changes to the content.) Watch the Playground --- see how the authors' source chains and the graph in the DHT change as new information is added. The deleted comments are still there and can be accessed by code in your zomes if needed, but neither the application backend (that is, the functions defined in the coordinator zome) nor the UI have the capacity to show them. + +![One UI window with the comment components added, with the Playground in the background showing a populated DHT](/assets/img/get-started/10-comment-components.png) + +## 5. Deploying your Holochain application + +### 5.1 Packaging + +Now that you've built an application, it's time to get it into other people's hands. You specify the components of a hApp using manifest files, written in [YAML](https://yaml.org/), and the `hc` CLI looks for them when it's building a distributable hApp for you. If you look in the `workdir` folder: + +```shell +ls workdir +``` + +You'll see that the scaffolding tool has generated two manifest files for you: + +:::output-block +```text +happ.yaml web-happ.yaml +``` +::: output-block + +The first step is to package your app: + +```shell +npm run package +``` + +This command does a number of things: + +1. Triggers the Rust compiler to build the zomes, +2. Uses the `hc` CLI too to combine the built zomes and the DNA manifest into a `.dna` file, +3. Combines all the DNAs and the hApp manifest into a `.happ` file, +3. Builds the UI and compresses it into a `.zip` file, and +4. Combines the hApp file, the UI zip, and the web hApp manifest into a `.webhapp` file. + +Of course, this application only has one zome and one DNA, but more complex apps may have many of each. + +Now you'll see some new files in `workdir`: + +```shell +ls workdir +``` + +::: output-block +```text +happ.yaml my_forum_app.happ my_forum_app.webhapp web-happ.yaml +``` +::: output-block + +The packed app is now ready for deployment to a Holochain runtime. + +### 5.2 Runtimes + +In the centralized world, deployment is usually achieved by Continuous Integration (CI) automation that builds up code changes and sends them to whatever server or cloud-based platform you're using. In the decentralized world of Holochain, deployment happens when end-users download and run your hApp in the Holochain runtime. + +From the end-user perspective there are currently there are two ways to go about this, both of which will feel familiar: + +1. Download Holochain's official Launcher runtime and install the app from its app store or the filesystem. +2. Download an your app as its own standalone desktop executable, as they would any other application for their computer. + +#### 5.2.1 Launcher, the multi-app runtime + +Holochain's official end-user runtime is the [Holochain Launcher](https://github.com/holochain/launcher). It allows people to install apps from a built-in app store or from the filesystem. Installed apps can then be launched from a friendly UI. Note that the app store is itself a distributed Holochain application which provides details on applications that are available for download. As a developer you can either go through a simple publishing process and add your app to the app store where it will be available for installation by all people who use the Launcher, or you can share your application directly with end-users through your own channels and they can install it into their Holochain Launcher manually from the file system. + +You can try this latter approach immediately by downloading and running the Launcher! + +The steps for publishing an app to the Launcher's app store are documented in the Github repository of the Holochain Launcher [here](https://github.com/holochain/launcher#publishing-and-updating-an-app-in-the-devhub). + +#### 5.2.2 Standalone executable + +If you prefer to distribute your app as a full standalone executable, you will need to bundle the Holochain runtime and your app together and take care of the necessary interactions between them. Because Holochain itself is really just a set of Rust libraries, you can of course build your own application that uses those libraries, but that's a fair amount of work. Currently there are two much simpler paths for doing this: using either the [Electron](https://www.electronjs.org/) or [Tauri](https://tauri.app/) frameworks, both of which can generate cross-platform executables from standard web UIs. These frameworks also support inclusion of additional binaries, which in our case are the [holochain conductor](https://docs.rs/holochain/latest/holochain/) and the [lair keystore](https://docs.rs/lair_keystore/latest/lair_keystore/). Though there is quite a bit of complexity in setting things up for these frameworks, all the hard work has already been done for you: + +* **Electron**: Refer to the community-supported [electron-holochain-template](https://github.com/lightningrodlabs/electron-holochain-template/) repo. +* **Tauri**: See the officially supported [holochain-kanagroo](https://github.com/holochain-apps/holochain-kangaroo) repo. + +Both of these are GitHub template repos with detailed instructions on how to clone the repos and add in your UI and DNA, as well as build and release commands that will create the cross-platform executables that you can then deliver to your end users. + +!!! note Code Signing +For macOS and Windows, you will probably also want to go through the process of registering as a developer so that your application can be "code-signed". This is needed so that users don't get the "unsigned code" warnings when launching the applications on those platforms. Both of the above templates include instructions for CI automation to run the code-signing steps on release once you have acquired the necessary certificates. +!!! @@ -149,7 +156,7 @@ let update_action_hash: ActionHash = update_entry( )?; ``` -### Update Under-the-hood +### Update Under-the-hood Calling `update_Entry` does the following: 1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. 2. Writes a `Update` action in the draft commit @@ -163,7 +170,7 @@ Calling `update_Entry` does the following: ### Update Patterns -Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can structure your updates as "list" -- where all updates refer to the ActionHash of the original Create action. @@ -212,11 +219,11 @@ let delete_action_hash: ActionHash = delete_entry( This does *not* actually erase data from the source chain or the DHT. Instead a Delete action is committed to the Cell's Source Chain. -In the future we plan to include a "purge" functionality. This will give Agents permission to actually erase an Entry from the Source Chain and DHT, but not its associated Action. +In the future we plan to include a "purge" functionality. This will give Agents permission to actually erase an Entry from the Source Chain and DHT, but not its associated Action. Remember it is physically impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. -### Delete Under-the-hood +### Delete Under-the-hood Calling `delete_entry` does the following: 1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. 2. Writes a `Delete` action in the draft commit From 46e98ae03d9344962947a3920660f089f7402bcf Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 4 Mar 2024 14:51:40 -0800 Subject: [PATCH 14/69] editorial consistency with style guide, text edits, remove target=_blank --- src/pages/build/4_entries.md | 134 +++++++++++++++++++---------------- 1 file changed, 73 insertions(+), 61 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 03ec0820a..36332b373 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -22,19 +22,19 @@ tocData: children: - text: Delete under the hood href: delete-under-the-hood - - text: Entry IDs - href: entry-ids - - text: CRUD libraries + - text: Identifiers on the DHT + href: identifiers-on-the-dht + - text: Community CRUD libraries href: community-crud-libraries - text: Reference href: reference --- -An **entry** is structured data written as a blob to an agent's source chain via a `Create` or `Update` action. +An **entry** is structured data written as a blob to an agent's source chain via a **new-entry action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated new-entry action is semantically considered part of it and is stored along with it. This allows separate writes of the same entry data to be distinguished from each other. ## Define an entry type -An entry type can be any Rust struct or enum that `serde` can serialize and deserialize. To define an `EntryType`, use the [`hdk_entry_helper`](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html){target=_blank} macro: +An entry type can be any Rust struct or enum that `serde` can serialize and deserialize. To define an `EntryType`, use the [`hdk_entry_helper`](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html) macro: ```rust use hdi::prelude::*; @@ -51,7 +51,7 @@ pub struct Movie { This implements a host of [`TryFrom` conversions](https://docs.rs/hdi/latest/src/hdi/entry.rs.html#120-209) conversions that your struct or enum is expected to implement. -In order to dispatch validation to the proper integrity zome, Holochain needs to know about your integrity zome's entry types. This is done by implementing a callback in your zome called `entry_defs`, but you can use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html){target=_blank} macro to do this easily: +In order to dispatch validation to the proper integrity zome, Holochain needs to know about your integrity zome's entry types. This is done by implementing a callback in your zome called `entry_defs`, but you can use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) macro to do this easily: ```rust use hdi::prelude::*; @@ -65,7 +65,7 @@ enum EntryTypes { An entry type can be configured as **private**, in which case it is never published to the DHT, but exists only on the author's source chain. -To configure an entry type as private, use the [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html){target=_blank} on the enum variant that defines your entry type, passing the `visibility = "private"` argument to it: +To configure an entry type as private, use the [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) on the enum variant that defines your entry type, passing the `visibility = "private"` argument to it: ```rust use hdi::prelude::*; @@ -79,7 +79,7 @@ enum EntryTypes { } ``` -An entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts]() that an author tries to collect from authorities before they consider an entry published on the DHT. To configure this, use the [`hdi::prelude::entry_def`](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html){target=_blank} macro, this time with the `required_validations` argument: +An entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts]() that an author tries to collect from authorities before they consider an entry published on the DHT. To configure this, use the [`hdi::prelude::entry_def`](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) macro, this time with the `required_validations` argument: ```rust use hdi::prelude::*; @@ -95,14 +95,13 @@ enum EntryTypes { ## Create an entry -Create an entry by calling [`hdk::prelude::create_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html){target=_blank}. The entry will be serialized into a blob automatically, thanks to the `hdk_entry_helper` macro. +Create an entry by calling [`hdk::prelude::create_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html). The entry will be serialized into a blob automatically, thanks to the `hdk_entry_helper` macro. ```rust use hdk::prelude::*; use chrono::Date; use movie_integrity::*; - let movie = Movie { title: "The Good, the Bad, and the Ugly", director: "Sergio Leone" @@ -134,13 +133,14 @@ When the client calls a zome function that calls `create_entry`, Holochain does - ## Update an Entry -Update an entry by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html){target=_blank}: +Update a new-entry action (either a `Create` or an `Update`) by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html): ```rust use hdk::prelude::*; +use chrono::Date; +use movie_integrity::*; let movie2 = Movie { title: "The Good, the Bad, and the Ugly", @@ -156,23 +156,29 @@ let update_action_hash: ActionHash = update_entry( )?; ``` -### Update Under-the-hood -Calling `update_Entry` does the following: -1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. -2. Writes a `Update` action in the draft commit -3. Runs the validation callback for all Ops in the draft commit. If successful, continues. -4. Publishes the "draft commit" to the source chain for this Cell -5. Publishes all DhtOps included in the source chain commit to their authority agents -6. Returns the `ActionHash` of the Update action +An `Update` operates on a new-entry action (either a `Create` or an `Update`). It doesn't remove the original data; instead, it creates a pointer on the DHT from it to the update and its entry data. - +### Update under the hood +Calling `update_entry` does the following: -### Update Patterns +1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. +2. Write an `Update` action to the scratch space. +3. Return the `ActionHash` of the `Update` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) +4. Wait for the zome function to complete. +5. Convert the action to DHT operations. +6. Run the validation callback for all DHT operations. + * If successful, continue. + * If unsuccessful, return the validation error to the client instead of the zome function's return value. +7. Publish the actions in the scratch space to the source chain. +8. Return the zome function's return value to the client. +9. In the background, publish all newly created DHT operations to their respective authority agents. -Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. + -You can structure your updates as "list" -- where all updates refer to the ActionHash of the original Create action. +### Update patterns + +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can structure your updates as a "list", where all updates refer to the `ActionHash` of the original `Create` action. ```mermaid graph TB @@ -182,7 +188,7 @@ graph TB E[Update 4] --> A[Create] ``` -Or you can structure your updates as "chain" -- where each update refers to the ActionHash of the previous update. +Or you can structure your updates as a "chain", where each update refers to the `ActionHash` of the previous new-entry action (either an `Update` or the original `Create`). ```mermaid graph TB @@ -192,22 +198,22 @@ graph TB E[Update 4] --> D[Update 3] ``` -If you structure your updates as a "chain" you may want to also create links from the original ActionHash to each update in the chain, for easier querying. This effectively trades additional storage space for reduced lookup time. - +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, for easier querying. This effectively trades additional storage space for reduced lookup time. -### Choose the Latest Update +### Choose the latest update -If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the Update action with the most recent timestamp. +If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? -But if multiple agents are permitted to update an entry it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? +These are three common patterns: -These are two common patterns: -- Use an opinionated deterministic definition of "latest" -- Expose *all* conflicting updates to the user, and let them decide which are meaningful +* Use an opinionated, deterministic definition of "latest" that can be calculated from the content of the update. +* Expose _all_ conflicting updates to the user, and either + * let them decide which are meaningful, or + * model your updates with a data structure that can automatically merge simultaneous updates, such as a [conflict-free replicated data type (CRDT)](https://crdt.tech/), then merge all the updates in your coordinator zome or front end. -## Delete an Entry +## Delete an entry -Delete an entry by calling [`hdk::entry::delete_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html){target=_blank}. +Delete a new-entry action by calling [`hdk::entry::delete_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html). ```rust use hdk::prelude::*; @@ -217,46 +223,52 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -This does *not* actually erase data from the source chain or the DHT. Instead a Delete action is committed to the Cell's Source Chain. +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the new-entry action and its entry are marked as "dead". They can still be retrieved with [`get_details`]() -In the future we plan to include a "purge" functionality. This will give Agents permission to actually erase an Entry from the Source Chain and DHT, but not its associated Action. +In the future we plan to include a "purge" functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated new-entry action. -Remember it is physically impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. +Remember it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. + +### Delete under the hood -### Delete Under-the-hood Calling `delete_entry` does the following: -1. Prepares a "draft commit" for making an atomic set of changes to the source chain for this Cell. -2. Writes a `Delete` action in the draft commit -3. Runs the validation callback for all Ops in the draft commit. If successful, continues. -4. Publishes the "draft commit" to the source chain for this Cell -5. Publishes all DhtOps included in the source chain commit to their authority agents -6. Returns the `ActionHash` of the Delete action + +1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. +2. Write a `Delete` action to the scratch space. +3. Return the `ActionHash` of the `Delete` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) +4. Wait for the zome function to complete. +5. Convert the action to DHT operations. +6. Run the validation callback for all DHT operations. + * If successful, continue. + * If unsuccessful, return the validation error to the client instead of the zome function's return value. +7. Publish the actions in the scratch space to the source chain. +8. Return the zome function's return value to the client. +9. In the background, publish all newly created DHT operations to their respective authority agents. -## Entry IDs +## Identifiers on the DHT -Coming from centralized software architectures, you might be expecting an Entry to have a unique ID that can be used to reference it elsewhere. +Coming from centralized software architectures, you might expect an entry to have a unique ID that can be used to reference it elsewhere. Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. -Instead, Holochain uses hashes to reference content. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. +To identify the *contents* of an entry, use the entry's `EntryHash`. Remember that, if two new-entry actions write identical entry contents, the entries will collide in the DHT. -To identify the *contents* of an Entry, use the entry's `EntryHash`. Remember that identical entry contents will collide in the DHT. +A common pattern to identify an *instance* of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of the new-entry action which created the entry. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. -A common pattern to identify an *instance* of an Entry (i.e. an Entry authored by a specific agent at a specific time) is to use the `ActionHash` of the Create action which created the original entry. This can be a persistent way to identify the entry, even when it is updated, as other agents can query for updates themselves to discover the latest version. +Finally, you can reference an agent themselves via their `AgentPubKey`. This identifier is similar to `EntryHash` and `ActionHash` in that it's an identifier that you can use to reference agents in the same way you can reference entries and actions. ## Community CRUD Libraries -If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer an opinionated and high-level ways to work with entries. - -- [rust-hc-crud-caps](https://github.com/spartan-holochain-counsel/rust-hc-crud-caps){target=_blank} -- [hdk_crud](https://github.com/lightningrodlabs/hdk_crud){target=_blank} -- [hc-cooperative-content](https://github.com/mjbrisebois/hc-cooperative-content){target=_blank} +If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer opinionated and high-level ways to work with entries. Some of them also offer permissions management. +- [rust-hc-crud-caps](https://github.com/spartan-holochain-counsel/rust-hc-crud-caps) +- [hdk_crud](https://github.com/lightningrodlabs/hdk_crud) +- [hc-cooperative-content](https://github.com/mjbrisebois/hc-cooperative-content) ## Reference -- [hdi::prelude::hdk_entry_helper](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html){target=_blank} -- [hdi::prelude::hdk_entry_defs](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html){target=_blank} -- [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html){target=_blank} -- [hdk::prelude::create_entry](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html){target=_blank} -- [hdk::prelude::update_entry](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html){target=_blank} -- [hdi::prelude::delete_entry](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html){target=_blank} +- [hdi::prelude::hdk_entry_helper](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html) +- [hdi::prelude::hdk_entry_defs](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) +- [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) +- [hdk::prelude::create_entry](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html) +- [hdk::prelude::update_entry](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html) +- [hdi::prelude::delete_entry](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html) From bfb940da32db89bf29879cbe33b0b5c04bd56fd6 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 4 Mar 2024 14:54:30 -0800 Subject: [PATCH 15/69] add note about 1:m and m:1 relationships --- src/pages/build/4_entries.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 36332b373..e19840684 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -257,6 +257,8 @@ A common pattern to identify an *instance* of an entry (i.e., an entry authored Finally, you can reference an agent themselves via their `AgentPubKey`. This identifier is similar to `EntryHash` and `ActionHash` in that it's an identifier that you can use to reference agents in the same way you can reference entries and actions. +You can use any of these identifiers as a field in your entry types to model a many-to-one relationship, or you can use links between identifiers to model a one-to-many relationship. + ## Community CRUD Libraries If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer opinionated and high-level ways to work with entries. Some of them also offer permissions management. From 44058ed6823be093e2773b24787bd5f3e113cdc5 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 7 Mar 2024 11:39:01 -0800 Subject: [PATCH 16/69] WIP --- src/pages/build/4_entries.md | 39 +++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index e19840684..6716551cc 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -223,11 +223,11 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the new-entry action and its entry are marked as "dead". They can still be retrieved with [`get_details`]() +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the new-entry action is marked "dead". An entry itself is only considered dead when all new-entry actions that created it are marked dead, and it can become live again in the future if a _new_ new-entry action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) In the future we plan to include a "purge" functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated new-entry action. -Remember it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. +Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. ### Delete under the hood @@ -259,7 +259,40 @@ Finally, you can reference an agent themselves via their `AgentPubKey`. This ide You can use any of these identifiers as a field in your entry types to model a many-to-one relationship, or you can use links between identifiers to model a one-to-many relationship. -## Community CRUD Libraries +## Retrieving an entry + +Get a new-entry action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. + +```rust +use hdk::prelude::*; +use movie_integrity::*; + +let maybe_record: Record = get( + action_hash, + GetOptions::latest() +)?; +match maybe_record { + Some(record) => { + let action = record.action(); + // Not all records contain entry data. + // A new-entry action, if it exists, will always contain entry data. + match action.entry_type() { + Some(App(Movie::)) = { + + }, + Some(App(_)) => debug!("Record {} didn't contain the right entry type", action_hash), + Some(_) => debug!("Record {} contained a system entry", action_hash), + _ => debug!("Record {} was not a new-entry action", action_hash) + } + let + let movie: Movie = record.entry().into_option()?.into(); + debug!("Movie {}, released {}, record stored by {} on {}", movie.title, movie.release_date, action.) + }, + _ => debug!("Record {} not found", action_hash) +} +``` + +## Community CRUD libraries If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer opinionated and high-level ways to work with entries. Some of them also offer permissions management. From a0e84e00b082d16c033da8606b202f0bca5b06dd Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 14:45:25 -0800 Subject: [PATCH 17/69] lots of content updates to entries guide --- src/pages/build/4_entries.md | 209 ++++++++++++++++++++++++----------- 1 file changed, 145 insertions(+), 64 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 6716551cc..dcc4a4b60 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -30,7 +30,7 @@ tocData: href: reference --- -An **entry** is structured data written as a blob to an agent's source chain via a **new-entry action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated new-entry action is semantically considered part of it and is stored along with it. This allows separate writes of the same entry data to be distinguished from each other. +An **entry** is structured data written as a blob to an agent's source chain via an **entry creation action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated entry creation action is semantically considered part of it and is stored along with it. This allows separate writes of the same entry data to be distinguished from each other. ## Define an entry type @@ -49,9 +49,9 @@ pub struct Movie { } ``` -This implements a host of [`TryFrom` conversions](https://docs.rs/hdi/latest/src/hdi/entry.rs.html#120-209) conversions that your struct or enum is expected to implement. +This implements a host of [`TryFrom` conversions](https://docs.rs/hdi/latest/src/hdi/entry.rs.html#120-209) that your type is expected to implement. -In order to dispatch validation to the proper integrity zome, Holochain needs to know about your integrity zome's entry types. This is done by implementing a callback in your zome called `entry_defs`, but you can use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) macro to do this easily: +In order to dispatch validation to the proper integrity zome, Holochain needs to know about all the entry types that your integrity zome defines. This is done by implementing a callback in your zome called `entry_defs`, but you can use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) macro to do this easily: ```rust use hdi::prelude::*; @@ -63,35 +63,30 @@ enum EntryTypes { } ``` -An entry type can be configured as **private**, in which case it is never published to the DHT, but exists only on the author's source chain. +Each variant in the enum holds the Rust type that defines the shape of the entry, and is implicitly marked with an `entry_def` proc macro which, if you specify it explicitly, lets you configure the given entry type further: -To configure an entry type as private, use the [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) on the enum variant that defines your entry type, passing the `visibility = "private"` argument to it: +* 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 a `visibility = "private"` argument. +* A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/references/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use a `required_validations = ` argument. +* You can override the name of an entry type, which defaults to the name of the enum variant. ```rust use hdi::prelude::*; #[hdk_entry_defs] enum EntryTypes { - Movie(Movie), - - #[entry_def(visibility = "private", )] - HomeMovie(Movie) + #[entry_def(name = "moovee", required_validations = 7, )] + Movie(Movie), + + // You can reuse your Rust type in another entry type if you like. In this + // example, `HomeMovie` also (de)serializes to/from the `Movie` struct, but + // is actually a different entry type with different visibility, and can be + // subjected to different validation rules. + #[entry_def(visibility = "private", )] + HomeMovie(Movie), } ``` -An entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts]() that an author tries to collect from authorities before they consider an entry published on the DHT. To configure this, use the [`hdi::prelude::entry_def`](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) macro, this time with the `required_validations` argument: - -```rust -use hdi::prelude::*; - -#[hdk_entry_types] -enum EntryTypes { - Movie(Movie), - - #[entry_def(required_validations = 7, )] - HomeMovie(Movie) -} -``` +This macro doesn't just generate the `entry_defs` callback for you. It also gives you an enum that you can use later when you're storing app data. This is important because, under the hood, an entry type consists of two bytes -- an integrity zome index and an entry def index. These are required whenever you want to write an entry. Instead of having to discover or define those values and then use them every time you store something, your coordinator zome can just import and use this enum, which already knows the right values. ## Create an entry @@ -111,6 +106,10 @@ let movie = Movie { }; let create_action_hash: ActionHash = create_entry( + // The value you pass to `create_entry` needs a lot of traits to tell + // Holochain which entry type from which integrity zome you're trying to + // create. The `hdk_entry_types` macro will have set this up for you, so all + // you need to do is wrap your movie in the corresponding enum variant. &EntryTypes::Movie(movie.clone()), )?; ``` @@ -120,22 +119,29 @@ let create_action_hash: ActionHash = create_entry( When the client calls a zome function that calls `create_entry`, Holochain does the following: 1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. -2. Write a `Create` action to the scratch space. -3. Return the `ActionHash` of the `Create` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) -4. Wait for the zome function to complete. -5. Convert the action to DHT operations. -6. Run the validation callback for all DHT operations. +2. Build a `Create` action that includes: + * the author's public key, + * a timestamp, + * the action's sequence in the source chain and the previous action's hash, + * the entry type (integrity zome index and entry type index), and + * the hash of the serialized entry data. + +3. Write the `Create` action and the serialized entry data to the scratch space. +4. Return the `ActionHash` of the `Create` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) +5. Wait for the zome function to complete. +6. Convert the action to DHT operations. +7. Run the validation callback for all DHT operations. * If successful, continue. * If unsuccessful, return the validation error to the client instead of the zome function's return value. -7. Publish the actions in the scratch space to the source chain. -8. Return the zome function's return value to the client. -9. In the background, publish all newly created DHT operations to their respective authority agents. +8. Publish the actions in the scratch space to the source chain. +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 a new-entry action (either a `Create` or an `Update`) by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html): +Update an entry creation action (either a `Create` or an `Update`) by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html) with the old action hash and the new entry data: ```rust use hdk::prelude::*; @@ -156,23 +162,27 @@ let update_action_hash: ActionHash = update_entry( )?; ``` -An `Update` operates on a new-entry action (either a `Create` or an `Update`). It doesn't remove the original data; instead, it creates a pointer on the DHT from it to the update and its entry data. +An `Update` operates on an entry creation action (either a `Create` or an `Update`). It doesn't remove the original data; instead, it creates a pointer on the DHT from it to the update and its entry data. ### Update under the hood Calling `update_entry` does the following: 1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. -2. Write an `Update` action to the scratch space. -3. Return the `ActionHash` of the `Update` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) -4. Wait for the zome function to complete. -5. Convert the action to DHT operations. -6. Run the validation callback for all DHT operations. +2. Build an `Update` action that contains everything in a `Create` action, plus: + * the hash of the original action and + * the hash of the original action's serialized entry data. + (Note that the entry type is automatically retrieved from the original action.) +3. Write an `Update` action to the scratch space. +4. Return the `ActionHash` of the `Update` action to the calling zome function. (At this point, the action hasn't been persisted to the source chain.) +5. Wait for the zome function to complete. +6. Convert the action to DHT operations. +7. Run the validation callback for all DHT operations. * If successful, continue. * If unsuccessful, return the validation error to the client instead of the zome function's return value. -7. Publish the actions in the scratch space to the source chain. -8. Return the zome function's return value to the client. -9. In the background, publish all newly created DHT operations to their respective authority agents. +8. Publish the actions in the scratch space to the source chain. +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. @@ -188,7 +198,7 @@ graph TB E[Update 4] --> A[Create] ``` -Or you can structure your updates as a "chain", where each update refers to the `ActionHash` of the previous new-entry action (either an `Update` or the original `Create`). +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`). ```mermaid graph TB @@ -213,7 +223,7 @@ These are three common patterns: ## Delete an entry -Delete a new-entry action by calling [`hdk::entry::delete_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html). +Delete an entry creation action by calling [`hdk::entry::delete_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html). ```rust use hdk::prelude::*; @@ -223,9 +233,9 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the new-entry action is marked "dead". An entry itself is only considered dead when all new-entry actions that created it are marked dead, and it can become live again in the future if a _new_ new-entry action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the entry creation action is marked "dead". An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) -In the future we plan to include a "purge" functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated new-entry action. +In the future we plan to include a "purge" functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated entry creation action. Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. @@ -251,9 +261,9 @@ Calling `delete_entry` does the following: Coming from centralized software architectures, you might expect an entry to have a unique ID that can be used to reference it elsewhere. Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. -To identify the *contents* of an entry, use the entry's `EntryHash`. Remember that, if two new-entry actions write identical entry contents, the entries will collide in the DHT. +To identify the *contents* of an entry, use the entry's `EntryHash`. Remember that, if two entry creation actions write identical entry contents, the entries will collide in the DHT. You may want this or you may not, depending on the nature of your entry type. -A common pattern to identify an *instance* of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of the new-entry action which created the entry. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. +A common pattern to identify an *instance* of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of the entry creation action which created the entry. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. Finally, you can reference an agent themselves via their `AgentPubKey`. This identifier is similar to `EntryHash` and `ActionHash` in that it's an identifier that you can use to reference agents in the same way you can reference entries and actions. @@ -261,34 +271,105 @@ You can use any of these identifiers as a field in your entry types to model a m ## Retrieving an entry -Get a new-entry action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. +Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. The return value is `Result`, where a [`Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html) is a pairing of an action and its optional entry data. + +You can also pass an entry hash to `get`, and the record returned will contain the _oldest live_ entry creation action that wrote it. ```rust use hdk::prelude::*; use movie_integrity::*; -let maybe_record: Record = get( +let maybe_record: Option = get( action_hash, + // Get the most up-to-date data from the DHT. You can also specify + // `GetOptions::content()`, which only gets the latest data if it doesn't + // already exist locally. GetOptions::latest() )?; + match maybe_record { - Some(record) => { - let action = record.action(); - // Not all records contain entry data. - // A new-entry action, if it exists, will always contain entry data. - match action.entry_type() { - Some(App(Movie::)) = { - - }, - Some(App(_)) => debug!("Record {} didn't contain the right entry type", action_hash), - Some(_) => debug!("Record {} contained a system entry", action_hash), - _ => debug!("Record {} was not a new-entry action", action_hash) + Some(record) => { + // Not all types of action contain entry data, and if they do, it may + // not be accessible, so `.entry()` may return nothing. It may also be + // of an unexpected entry type, so it may not be deserializable to an + // instance of the expected Rust type. You can determine this easily by + // looking at the type of the `entry` field, but in this simple example + // we'll skip that and get the entry if it's the right one. + let maybe_movie: Option = record.entry().to_app_option(); + + match maybe_movie { + Some(movie) => debug!( + "Movie {}, released {}, record stored by {} on {}", + movie.title, + movie.release_date, + record.action().author(), + record.action().timestamp() + ), + None => debug!("Movie entry couldn't be retrieved"), + } + } + None => debug!("Movie record not found"), +} +``` + +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) with the action hash. You'll receive a [`holochain_zome_types::metadata::RecordDetails`](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/metadata/struct.RecordDetails.html) struct. + +```rust +use hdk::prelude::*; +use movie_integrity::*; + +let maybe_details: Option
= get_details( + action_hash, + GetOptions::latest() +)?; + +match maybe_details { + Some(Details::Record(record_details)) => { + let maybe_movie: Option = record.entry().to_app_option(); + match maybe_movie { + Some(movie) => debug!( + "Movie record {}, created on {}, was updated by {} agents and deleted by {} agents", + movie.title, + record_details.record.action().timestamp(), + record_details.updates.len(), + record_details.deletes.len() + ), + None => debug!("Movie entry couldn't be retrieved"), + } + } + _ => debug!("Movie record not found"), +} +``` + +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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 +use hdk::prelude::*; +use movie_integrity::*; + +let maybe_details: Option
= get_details( + entry_hash, + GetOptions::latest() +)?; + +match maybe_details { + Some(Details::Entry(entry_details)) => { + let maybe_movie: Option = entry_details.entry + .try_into() + .ok(); + match maybe_movie { + Some(movie) => debug!( + "Movie {} was written by {} agents, updated by {} agents, and deleted by {} agents. Its DHT status is currently {}.", + movie.title, + entry_details.actions.len(), + entry_details.updates.len(), + entry_details.deletes.len(), + entry_details.entry_dht_status + ), + None => debug!("Movie entry couldn't be retrieved"), + } } - let - let movie: Movie = record.entry().into_option()?.into(); - debug!("Movie {}, released {}, record stored by {} on {}", movie.title, movie.release_date, action.) - }, - _ => debug!("Record {} not found", action_hash) + _ => debug!("Movie entry not found"), } ``` From 309e4067939312a3bde0ff4dbf160417ec44a6c3 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 14:45:43 -0800 Subject: [PATCH 18/69] convert mermaid to svg --- src/pages/build/4_entries.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index dcc4a4b60..ae7525b9f 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -190,6 +190,7 @@ Calling `update_entry` does the following: Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can structure your updates as a "list", where all updates refer to the `ActionHash` of the original `Create` action. + A[Create] @@ -197,9 +198,12 @@ graph TB D[Update 3] --> A[Create] E[Update 4] --> A[Create] ``` +--> +
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`). + A[Create] @@ -207,6 +211,8 @@ graph TB D[Update 3] --> C[Update 2] E[Update 4] --> D[Update 3] ``` +--> +
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, for easier querying. This effectively trades additional storage space for reduced lookup time. From 6a48284a3110387f21c0d6a7a4289bd990394540 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 15:00:30 -0800 Subject: [PATCH 19/69] fix broken menu in entries guide --- src/pages/build/4_entries.md | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index ae7525b9f..4e7c9e56d 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -13,10 +13,10 @@ tocData: children: - text: Update under the hood href: update-under-the-hood - - text: Efficiently querying updates - href: efficiently-querying-updates - - text: Cooperative updates - href: cooperative-updates + - text: Update patterns + href: update-patterns + - text: Resulving update conflicts + href: resolving-update-conflicts - text: Delete an entry href: delete-an-entry children: @@ -190,33 +190,15 @@ Calling `update_entry` does the following: Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can structure your updates as a "list", where all updates refer to the `ActionHash` of the original `Create` action. - A[Create] - C[Update 2] --> A[Create] - D[Update 3] --> A[Create] - E[Update 4] --> A[Create] -``` --->
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`). - A[Create] - C[Update 2] --> B[Update 1] - D[Update 3] --> C[Update 2] - E[Update 4] --> D[Update 3] -``` --->
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, for easier querying. This effectively trades additional storage space for reduced lookup time. -### Choose the latest update +### Resolving update conflicts If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? From f43933fcc1fc288f8b96705e85cb02005f8ca4e9 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 15:16:04 -0800 Subject: [PATCH 20/69] add 'retrieve an entry' to page menu --- src/pages/build/4_entries.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 4e7c9e56d..6ac747b3f 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -24,6 +24,8 @@ tocData: href: delete-under-the-hood - text: Identifiers on the DHT href: identifiers-on-the-dht + - text: Retrieve an entry + href: retrieve-an-entry - text: Community CRUD libraries href: community-crud-libraries - text: Reference @@ -257,7 +259,7 @@ Finally, you can reference an agent themselves via their `AgentPubKey`. This ide You can use any of these identifiers as a field in your entry types to model a many-to-one relationship, or you can use links between identifiers to model a one-to-many relationship. -## Retrieving an entry +## Retrieve an entry Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. The return value is `Result`, where a [`Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html) is a pairing of an action and its optional entry data. From 1b37c4ca59e2e5d4c09f95cbd479e71546ab2f2d Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 15:16:36 -0800 Subject: [PATCH 21/69] text edits --- src/pages/build/4_entries.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index 6ac747b3f..ed6c403c7 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -261,7 +261,7 @@ You can use any of these identifiers as a field in your entry types to model a m ## Retrieve an entry -Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. The return value is `Result`, where a [`Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html) is a pairing of an action and its optional entry data. +Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. The return value is a `Result<`[`holochain_integrity_types::record::Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)`>`, where a `Record` is a pairing of an action and its optional entry data. You can also pass an entry hash to `get`, and the record returned will contain the _oldest live_ entry creation action that wrote it. @@ -282,9 +282,10 @@ match maybe_record { // Not all types of action contain entry data, and if they do, it may // not be accessible, so `.entry()` may return nothing. It may also be // of an unexpected entry type, so it may not be deserializable to an - // instance of the expected Rust type. You can determine this easily by - // looking at the type of the `entry` field, but in this simple example - // we'll skip that and get the entry if it's the right one. + // instance of the expected Rust type. You can check for most of these + // edge cases by exploring the value of the `entry` field, which tells + // you the entry's status, but in this simple example we'll skip that + // and try to get and deserialize the entry. let maybe_movie: Option = record.entry().to_app_option(); match maybe_movie { @@ -302,7 +303,7 @@ match maybe_record { } ``` -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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) with the action hash. You'll receive a [`holochain_zome_types::metadata::RecordDetails`](https://docs.rs/holochain_zome_types/latest/holochain_zome_types/metadata/struct.RecordDetails.html) struct. +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) with the 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)`>`. ```rust use hdk::prelude::*; From aaf0d2408862dbc62271eb71dd583616c7f637d2 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 8 Mar 2024 15:16:45 -0800 Subject: [PATCH 22/69] fix entry details conversion --- src/pages/build/4_entries.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/build/4_entries.md b/src/pages/build/4_entries.md index ed6c403c7..68daf688a 100644 --- a/src/pages/build/4_entries.md +++ b/src/pages/build/4_entries.md @@ -346,6 +346,8 @@ let maybe_details: Option
= get_details( match maybe_details { Some(Details::Entry(entry_details)) => { let maybe_movie: Option = entry_details.entry + .as_app_entry() + .clone() .try_into() .ok(); match maybe_movie { From 4bf02ffa30681e1d52934be13b2c8ee5b1d3ad8f Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 09:13:45 -0700 Subject: [PATCH 23/69] change title of build guide warning; move to top --- src/pages/build/guide/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 8f162b9db..5c3c9ee49 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -2,10 +2,10 @@ title: Holochain Build Guide --- +!!! note In progress +This guide is 'under construction', as we used to say in the early days of the web. Expect more content to be rapidly published in the first half of 2024. +!!! + ::: coreconcepts-intro This Build Guide organizes everything you need to know about developing Holochain applications into individual topics. Each topic page stands alone as a comprehensive guide to using a given feature or implementing a given functionality. There are lots of code examples which make it clear how to do something yet are generic enough to be universally useful. These examples may not cover every single use case, though, so we'll point you to the reference documentation often. -::: - -!!! note Coming soon! -This guide is 'under construction', as we used to say in the early days of the web. Expect more content to be rapidly published in the first half of 2024. -!!! \ No newline at end of file +::: \ No newline at end of file From a598df425033439e397d962c31d99eeff8de4166 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 09:15:39 -0700 Subject: [PATCH 24/69] move build guide to grandchild level in nav --- src/pages/_data/navigation/mainNav.json5 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 9d225734c..b9fc6b5e4 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -26,7 +26,9 @@ ] }, { title: "Build", url: "/build/", children: [ - { title: "Entries", url: "/build/guide/entries" } + { title: "Guide", url: "/build/guide/", children: [ + { title: "Entries", url: "/build/guide/entries" }, + ]}, ] }, { title: "Resources", url: "/resources/", children: [ From 08f0c8d210b3bfd9a1ee5d199452133aca1c8dba Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 09:16:23 -0700 Subject: [PATCH 25/69] add entries to build guide landing page --- src/pages/build/guide/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 5c3c9ee49..f9ecf661a 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -8,4 +8,8 @@ This guide is 'under construction', as we used to say in the early days of the w ::: coreconcepts-intro This Build Guide organizes everything you need to know about developing Holochain applications into individual topics. Each topic page stands alone as a comprehensive guide to using a given feature or implementing a given functionality. There are lots of code examples which make it clear how to do something yet are generic enough to be universally useful. These examples may not cover every single use case, though, so we'll point you to the reference documentation often. -::: \ No newline at end of file +::: + +## Working with data + +* [Entries](../entries) \ No newline at end of file From 5f98713a3a035716de8052d2fa40ce689848e06c Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:01:53 -0700 Subject: [PATCH 26/69] work on explanation of unopinionated updates and conflict resolution --- src/pages/build/guide/entries.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 68daf688a..8f77eafc2 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -190,7 +190,9 @@ Calling `update_entry` does the following: ### Update patterns -Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can structure your updates as a "list", where all updates refer to the `ActionHash` of the original `Create` action. +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can interpret an update as applying to either the original _action_ or the original _entry_ being updated, because the `Update` action is merely a piece of metadata attached to both and can be retrieved along with the original data. + +You can also choose where to attach updates. 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
@@ -198,18 +200,20 @@ Or you can structure your updates as a "chain", where each update refers to the
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, for easier querying. This effectively trades additional storage space for reduced lookup time. +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. ### Resolving update conflicts +It's up to you to decide whether two updates on the same entry or action are conflicts. If your use case allows branching edits similar to Git, then conflicts aren't an issue. + +But if your use case needs to determine a single canonical version of a resource, you'll need to decide on a conflict resolution strategy to use at retrieval time. + If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? -These are three common patterns: +These are two common patterns: * Use an opinionated, deterministic definition of "latest" that can be calculated from the content of the update. -* Expose _all_ conflicting updates to the user, and either - * let them decide which are meaningful, or - * model your updates with a data structure that can automatically merge simultaneous updates, such as a [conflict-free replicated data type (CRDT)](https://crdt.tech/), then merge all the updates in your coordinator zome or front end. +* Model your updates with a data structure that can automatically merge simultaneous updates, such as a [conflict-free replicated data type (CRDT)](https://crdt.tech/). ## Delete an entry From 5c61963d1272a50eb27c4c52e347f3be2a74177e Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:09:40 -0700 Subject: [PATCH 27/69] add intro to 'working with data' section --- src/pages/build/guide/index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index f9ecf661a..3d4ee22ba 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -12,4 +12,10 @@ 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 **entries** connected by **links**. + +An **action** is a third type of data. It records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. It can also be treated as a primary piece of graph data, similar to an entry. + +Finally, an **agent ID** is the public identifer of a participant in an application, and is an honorary piece of graph data that links can point to and from. + * [Entries](../entries) \ No newline at end of file From 99ef006abcb516ba46e26e3a7939cb5933bc498a Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:10:06 -0700 Subject: [PATCH 28/69] fix spelling mistake and broken link --- src/pages/build/guide/entries.md | 2 +- src/pages/build/guide/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 8f77eafc2..35c3f382e 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -15,7 +15,7 @@ tocData: href: update-under-the-hood - text: Update patterns href: update-patterns - - text: Resulving update conflicts + - text: Resolving update conflicts href: resolving-update-conflicts - text: Delete an entry href: delete-an-entry diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 3d4ee22ba..ffd88bf1f 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -18,4 +18,4 @@ An **action** is a third type of data. It records the act of manipulating an ent Finally, an **agent ID** is the public identifer of a participant in an application, and is an honorary piece of graph data that links can point to and from. -* [Entries](../entries) \ No newline at end of file +* [Entries](entries/) \ No newline at end of file From 32b0ee26c44cf48420bf0cb991632f8b6a5cefc0 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:17:27 -0700 Subject: [PATCH 29/69] more spelling stuff --- .cspell/custom-words.txt | 3 +++ .cspell/template-words.txt | 1 + .cspell/words-that-should-exist.txt | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index d82e7eef0..eab1dc6e9 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -10,7 +10,10 @@ CRDT d'Aoust fixt gnused +imdb +IMDB IPFS +moovee NixOS nixpkgs pkgs diff --git a/.cspell/template-words.txt b/.cspell/template-words.txt index 5868e11a1..434db0147 100644 --- a/.cspell/template-words.txt +++ b/.cspell/template-words.txt @@ -8,6 +8,7 @@ endlink endmacro endrenderlayoutblock gtag +katex layoutblock MSIE newblock diff --git a/.cspell/words-that-should-exist.txt b/.cspell/words-that-should-exist.txt index d3a328aea..f0303fc5a 100644 --- a/.cspell/words-that-should-exist.txt +++ b/.cspell/words-that-should-exist.txt @@ -3,6 +3,7 @@ affordance affordances automations birthdate +chrono counterparties counterparties' counterparty @@ -16,6 +17,7 @@ permissioned permissivity runtimes sandboxed +scaffolder spacebar todo todos From 8573e81053fb83ab60f8961febdd6cc81d6359eb Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:17:39 -0700 Subject: [PATCH 30/69] more work on the intro text of 'working with data' section --- src/pages/build/guide/index.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index ffd88bf1f..555f57349 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -12,10 +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 **entries** connected by **links**. +Shared data in a Holochain application is stored as a graph database of **entry** nodes connected by **links**, plus a couple special types of data that can also be treated as nodes in the graph: -An **action** is a third type of data. It records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. It can also be treated as a primary piece of graph data, similar to an entry. - -Finally, an **agent ID** is the public identifer of a participant in an application, and is an honorary piece of graph data that links can point to and from. +* An **action** records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. +* An **agent ID** is the public key of a participant in an application. * [Entries](entries/) \ No newline at end of file From 4ebc573af56c48167af8152a9f3179a589e9769e Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:18:49 -0700 Subject: [PATCH 31/69] Experimental: add category headers to side nav --- src/pages/_data/navigation/mainNav.json5 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index b9fc6b5e4..a9a97dc7b 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -27,7 +27,9 @@ }, { title: "Build", url: "/build/", children: [ { title: "Guide", url: "/build/guide/", children: [ - { title: "Entries", url: "/build/guide/entries" }, + { title: "Working with Data", children: [ + { title: "Entries", url: "/build/guide/entries" }, + ]}, ]}, ] }, From f4b8ac668f9c2f7078020da101a1d793dbf7e162 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:25:45 -0700 Subject: [PATCH 32/69] fix broken link to glossary --- src/pages/build/guide/entries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 35c3f382e..f0cd4cba4 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -68,7 +68,7 @@ enum EntryTypes { Each variant in the enum holds the Rust type that defines the shape of the entry, 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 a `visibility = "private"` argument. -* A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/references/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use a `required_validations = ` argument. +* A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/resources/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use a `required_validations = ` argument. * You can override the name of an entry type, which defaults to the name of the enum variant. ```rust From 51bcbe9665990cd343407bfe1e2e0270da44aeca Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Mon, 25 Mar 2024 15:30:19 -0700 Subject: [PATCH 33/69] change link from home page to build guide --- src/pages/_includes/widgets/home-tiles.njk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/_includes/widgets/home-tiles.njk b/src/pages/_includes/widgets/home-tiles.njk index add0cb674..4c1e75ad4 100644 --- a/src/pages/_includes/widgets/home-tiles.njk +++ b/src/pages/_includes/widgets/home-tiles.njk @@ -16,7 +16,7 @@ {% endlinkTile %} - {% linkTile "/build/", "home-tile" %} + {% linkTile "/build/guide/", "home-tile" %} {{ craneIcon() }}

Build Guide

From 6165e963c592ce28033b30ea6ddb1b76013bc322 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 09:03:08 -0700 Subject: [PATCH 34/69] styling for guide topic lists --- 11ty-extensions/markdown-it-config.js | 2 ++ src/pages/build/guide/index.md | 4 +++- src/scss/_svg-icons.scss | 12 +++++++++++- src/scss/_widgets.scss | 21 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/11ty-extensions/markdown-it-config.js b/11ty-extensions/markdown-it-config.js index a49545bc5..8f16a540d 100644 --- a/11ty-extensions/markdown-it-config.js +++ b/11ty-extensions/markdown-it-config.js @@ -90,6 +90,8 @@ export default function(eleventyConfig) { mdLib.use(markdownItContainer, "coreconcepts-intro"); mdLib.use(markdownItContainer, "coreconcepts-orientation"); mdLib.use(markdownItContainer, "coreconcepts-storysequence"); + mdLib.use(markdownItContainer, "section-intro"); + mdLib.use(markdownItContainer, "topic-list"); mdLib.use(markdownItContainer, "h-author"); mdLib.use(markdownItContainer, "output-block"); diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 555f57349..7bee526a3 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -17,4 +17,6 @@ Shared data in a Holochain application is stored as a graph database of **entry* * An **action** records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. * An **agent ID** is the public key of a participant in an application. -* [Entries](entries/) \ No newline at end of file +::: topic-list +* [Entries](entries/) --- creating, reading, updating, and deleting +::: \ No newline at end of file diff --git a/src/scss/_svg-icons.scss b/src/scss/_svg-icons.scss index 35a5e8425..d7453779a 100644 --- a/src/scss/_svg-icons.scss +++ b/src/scss/_svg-icons.scss @@ -54,4 +54,14 @@ $shovelIcon: url("data:image/svg+xml,%3Csvg%20viewBox%3D'0%200%201200%201200'%20 /* Generated with https://heyallan.github.io/svg-to-data-uri/ */ content: url("data:image/svg+xml,%3Csvg viewBox='0 0 1200 1200' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m349.64 400.21c7.7734 10.289 9.5742 23.902 4.7461 35.859-4.8242 11.957-15.57 20.504-28.309 22.516-12.734 2.0156-25.594-2.8008-33.871-12.688l-70.359-88.422v589.89c-0.23828 12.957-7.2891 24.824-18.551 31.234-11.262 6.4062-25.066 6.4062-36.324 0-11.262-6.4102-18.312-18.277-18.551-31.234v-589.89l-70.359 88.422c-8.168 10.262-21.191 15.383-34.164 13.438-12.969-1.9492-23.918-10.664-28.719-22.871-4.8008-12.207-2.7227-26.047 5.4492-36.305l135.79-170.53c6.9648-8.7461 17.535-13.844 28.715-13.844 11.184 0 21.754 5.0977 28.719 13.844zm628.04 278.91h-535.98c-12.953 0.23828-24.82 7.2891-31.23 18.551-6.4102 11.258-6.4102 25.062 0 36.324s18.277 18.309 31.23 18.551h535.98c12.957-0.24219 24.824-7.2891 31.234-18.551 6.4062-11.262 6.4062-25.066 0-36.324-6.4102-11.262-18.277-18.312-31.234-18.551zm-86.461 231.62h-449.52c-13.113 0-25.23 6.9961-31.789 18.352-6.5547 11.359-6.5547 25.352 0 36.707 6.5586 11.359 18.676 18.355 31.789 18.355h449.52c13.113 0 25.23-6.9961 31.789-18.355 6.5547-11.355 6.5547-25.348 0-36.707-6.5586-11.355-18.676-18.352-31.789-18.352zm172.95-463.28h-622.47c-12.953 0.24219-24.82 7.2891-31.23 18.551s-6.4102 25.066 0 36.324c6.4102 11.262 18.277 18.312 31.23 18.551h622.47c9.8555 0.18359 19.371-3.6016 26.406-10.508 7.0312-6.9062 10.996-16.348 10.996-26.203 0-9.8594-3.9648-19.301-10.996-26.207-7.0352-6.9023-16.551-10.691-26.406-10.508zm86.488-231.64h-708.96c-13.113 0-25.23 6.9961-31.789 18.352-6.5547 11.359-6.5547 25.352 0 36.707 6.5586 11.359 18.676 18.355 31.789 18.355h708.96c13.113 0 25.234-6.9961 31.789-18.355 6.5586-11.355 6.5586-25.348 0-36.707-6.5547-11.355-18.676-18.352-31.789-18.352z'/%3E%3C/svg%3E"); } -} \ No newline at end of file +} + +$pageIcon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 -150 1200 1200' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Cpath d='m806.4 1042.8c86.398 0 156-69.602 156-156v-474-1.1992-4.8008-1.1992c0-2.3984-1.1992-3.6016-2.3984-6 0-1.1992 0-1.1992-1.1992-2.3984s-1.1992-2.3984-2.3984-3.6016c0-1.1992-1.1992-1.1992-1.1992-2.3984-1.1992-1.1992-1.1992-2.3984-2.3984-3.6016l-1.1992-1.1992-232.8-219.6c-1.1992-1.1992-2.3984-2.3984-4.8008-3.6016 0 0-1.1992 0-1.1992-1.1992-1.1992-1.1992-2.3984-1.1992-4.8008-2.3984-1.1992 0-1.1992 0-2.3984-1.1992-1.1992 0-3.6016-1.1992-4.8008-1.1992h-1.1992c-2.3984 0-4.8008-1.1992-6-1.1992h-300c-86.398 0-156 69.602-156 156v573.6c0 86.398 69.602 156 156 156l412.8-0.003907zm-76.797-766.8 105.6 99.602h-105.6zm-420 610.8v-573.6c0-46.801 37.199-84 84-84h264v183.6c0 20.398 15.602 36 36 36h196.8v438c0 46.801-37.199 84-84 84h-412.8c-46.801 0-84-37.199-84-84z'/%3E%3Cpath d='m423.6 676.8h198c20.398 0 36-15.602 36-36 0-20.398-15.602-36-36-36h-198c-20.398 0-36 15.602-36 36 0 20.398 16.797 36 36 36z'/%3E%3Cpath d='m423.6 846h354c20.398 0 36-15.602 36-36s-15.602-36-36-36h-354c-20.398 0-36 15.602-36 36s16.797 36 36 36z'/%3E%3C/g%3E%3C/svg%3E"); + +@mixin addPageIcon() { + &::before { + display: inline-block; + width: 22px; + content: $pageIcon; + } +} diff --git a/src/scss/_widgets.scss b/src/scss/_widgets.scss index 7bbce91ec..f67fa8114 100644 --- a/src/scss/_widgets.scss +++ b/src/scss/_widgets.scss @@ -375,4 +375,25 @@ a.link-tile { width: 80vw; height: 80vh; } +} + +/* An enlarged styling for lists of links to sub-pages. Used in the build guide. */ +.topic-list { + padding: 1em; + background-color: #f7f7f7; + + ul { + margin: 0; + } + + /* Unless otherwise specified with `class="not-topic-page"`, links in a topic + list are assumed to point to topic pages. */ + a:not(.not-topic-page) { + @include addPageIcon; + } +} + +.section-intro { + padding-left: 1em; + border-left: 2px $cl-gray solid; } \ No newline at end of file From 41bd1022627a0cf5340248f27df02fa5aefda228 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 09:03:18 -0700 Subject: [PATCH 35/69] text edits --- src/pages/build/guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 7bee526a3..e899ab6ab 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -12,7 +12,7 @@ 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 **entry** nodes connected by **links**, plus a couple special types of data that can also be treated as nodes in the graph: +Shared data in a Holochain application is stored as a graph database of **entry** nodes connected by **links**, plus a couple special types of data that can also be treated as linkable nodes in the graph: * An **action** records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. * An **agent ID** is the public key of a participant in an application. From 550dfff841fcf0ca3429859679dc3c7da447ec82 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 10:35:23 -0700 Subject: [PATCH 36/69] remove cuteness in under construction notice --- src/pages/build/guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index e899ab6ab..c5f651835 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -3,7 +3,7 @@ title: Holochain Build Guide --- !!! note In progress -This guide is 'under construction', as we used to say in the early days of the web. Expect more content to be rapidly published in the first half of 2024. +This guide is under construction. Expect more content to be rapidly published in the first half of 2024. !!! ::: coreconcepts-intro From 6a3c036331fe1bf0425fc29d9475b06afdd23ed9 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 10:42:50 -0700 Subject: [PATCH 37/69] content edits --- src/pages/build/guide/index.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index c5f651835..315614d10 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -12,11 +12,15 @@ 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 **entry** nodes connected by **links**, plus a couple special types of data that can also be treated as linkable nodes in the graph: +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 **action** records the act of manipulating an entry or link and contains metadata about the act, such as authorship and timestamp. -* An **agent ID** is the public key of a participant in an application. +* An **entry** is an arbitrary blob of bytes that is given meaning by your application. + * 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 + * [Entries](entries/) --- creating, reading, updating, and deleting ::: \ No newline at end of file From 6ba6ce16a4b66f7a093e98deca3413525d3852db Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 10:45:04 -0700 Subject: [PATCH 38/69] more styling adjustments for topic list --- src/scss/_widgets.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/scss/_widgets.scss b/src/scss/_widgets.scss index f67fa8114..d2f87bb5e 100644 --- a/src/scss/_widgets.scss +++ b/src/scss/_widgets.scss @@ -382,8 +382,12 @@ a.link-tile { padding: 1em; background-color: #f7f7f7; - ul { - margin: 0; + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; } /* Unless otherwise specified with `class="not-topic-page"`, links in a topic From 6df09ec7a38ced21d2ae08f5fb18e7d84768aceb Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 10:45:31 -0700 Subject: [PATCH 39/69] and one more --- src/scss/_widgets.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scss/_widgets.scss b/src/scss/_widgets.scss index d2f87bb5e..7404736ef 100644 --- a/src/scss/_widgets.scss +++ b/src/scss/_widgets.scss @@ -393,6 +393,7 @@ a.link-tile { /* Unless otherwise specified with `class="not-topic-page"`, links in a topic list are assumed to point to topic pages. */ a:not(.not-topic-page) { + font-weight: bold; @include addPageIcon; } } From f039e50e65c64742ebf08eb3eaa4f02772fe19d5 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 11:48:06 -0700 Subject: [PATCH 40/69] text editing work on entry topic --- src/pages/build/guide/entries.md | 42 ++++++++++++++++++-------------- src/pages/build/guide/index.md | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index f0cd4cba4..3b87e4a2b 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -32,11 +32,13 @@ tocData: href: reference --- -An **entry** is structured data written as a blob to an agent's source chain via an **entry creation action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated entry creation action is semantically considered part of it and is stored along with it. This allows separate writes of the same entry data to be distinguished from each other. +An **entry** is structured data that's serialized and written as a blob of bytes to an agent's source chain via an **entry creation action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated entry creation action is semantically considered part of it and is stored along with it. This let you distinguish separate writes of the same entry from each other. ## Define an entry type -An entry type can be any Rust struct or enum that `serde` can serialize and deserialize. To define an `EntryType`, use the [`hdk_entry_helper`](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html) macro: +Each entry has a **type**, which your application code uses to make sense of the entry's bytes. Our [HDI library](https://docs.rs/hdi/latest/hdi/) gives you macros to automatically define, serialize, and deserialize entry types from any Rust struct or enum that [`serde`](https://docs.rs/serde/latest/serde/) can handle. + +Entry types are defined in an [**integrity zome**](/resources/glossary/#integrity-zome). To define an `EntryType`, use the [`hdk_entry_helper`](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html) macro on your Rust type: ```rust use hdi::prelude::*; @@ -51,9 +53,9 @@ pub struct Movie { } ``` -This implements a host of [`TryFrom` conversions](https://docs.rs/hdi/latest/src/hdi/entry.rs.html#120-209) that your type is expected to implement. +This implements a host of [`TryFrom` conversions](https://docs.rs/hdi/latest/src/hdi/entry.rs.html#120-209) that your type is expected to implement, along with serialization and deserialization functions. -In order to dispatch validation to the proper integrity zome, Holochain needs to know about all the entry types that your integrity zome defines. This is done by implementing a callback in your zome called `entry_defs`, but you can use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) macro to do this easily: +In order to dispatch validation to the proper integrity zome, Holochain needs to know about all the entry types that your integrity zome defines. This is done by implementing a callback in your zome called `entry_defs`, but it's easier to use the [`hdi::prelude::hdk_entry_defs`](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) macro on an enum of all the entry types: ```rust use hdi::prelude::*; @@ -67,8 +69,8 @@ enum EntryTypes { Each variant in the enum holds the Rust type that defines the shape of the entry, 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 a `visibility = "private"` argument. -* A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/resources/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use a `required_validations = ` argument. +* 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. +* A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/resources/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use the `required_validations = ` argument. * You can override the name of an entry type, which defaults to the name of the enum variant. ```rust @@ -88,15 +90,18 @@ enum EntryTypes { } ``` -This macro doesn't just generate the `entry_defs` callback for you. It also gives you an enum that you can use later when you're storing app data. This is important because, under the hood, an entry type consists of two bytes -- an integrity zome index and an entry def index. These are required whenever you want to write an entry. Instead of having to discover or define those values and then use them every time you store something, your coordinator zome can just import and use this enum, which already knows the right values. +This technique doesn't just generate the `entry_defs` callback for you. It also gives you an enum that you can use later when you're storing app data. This is important because, under the hood, an entry type consists of two bytes -- an integrity zome index and an entry def index. These are required whenever you want to write an entry. Instead of having to remember those values every time you store something, your coordinator zome can just import and use this enum, which already knows the right values. ## Create an entry +You can define CRUD functions in your integrity zome, but most of the time you'll want to define them in a [**coordinator zome**](/resources/glossary/#coordinator-zome). This is because an updated coordinator zome can be hot-swapped in a running application, whereas changes to an integrity zome result in a new DNA with a separate network and database from the previous DNA. + Create an entry by calling [`hdk::prelude::create_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html). The entry will be serialized into a blob automatically, thanks to the `hdk_entry_helper` macro. ```rust use hdk::prelude::*; use chrono::Date; +// Import the entry types and the enum defined in the integrity zome. use movie_integrity::*; let movie = Movie { @@ -121,7 +126,7 @@ let create_action_hash: ActionHash = create_entry( When the client calls a zome function that calls `create_entry`, Holochain does the following: 1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. -2. Build a `Create` action that includes: +2. Build a [`Create` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Create.html) that includes: * the author's public key, * a timestamp, * the action's sequence in the source chain and the previous action's hash, @@ -164,7 +169,7 @@ let update_action_hash: ActionHash = update_entry( )?; ``` -An `Update` operates on an entry creation action (either a `Create` or an `Update`). It doesn't remove the original data; instead, it creates a pointer on the DHT from it to the update and its entry data. +An [`Update` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Update.html) operates on an entry creation action (either a `Create` or an `Update`). It doesn't remove the original data from the DHT; instead, it gets attached to both the original entry and its entry creation action. As an entry creation action itself, it references the hash of the new entry so it can be retrieved from the DHT. ### Update under the hood @@ -190,29 +195,30 @@ Calling `update_entry` does the following: ### Update patterns -Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it is used. You can interpret an update as applying to either the original _action_ or the original _entry_ being updated, because the `Update` action is merely a piece of metadata attached to both and can be retrieved along with the original data. +Holochain gives you this `update_entry` function, but is somewhat unopinionated about how it's used. You can interpret an update as applying to either the original _action_ or the original _entry_ being updated, because the `Update` action is merely a piece of metadata attached to both, and can be retrieved along with the original data. -You can also choose where to attach updates. You can structure them as a "list", where all updates refer to the `ActionHash` of the original `Create` action. +You can also choose where to attach updates. 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
-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`). +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
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. ### Resolving update conflicts -It's up to you to decide whether two updates on the same entry or action are conflicts. If your use case allows branching edits similar to Git, then conflicts aren't an issue. +It's up to you to decide whether two updates on the same entry or action are a conflict. If your use case allows branching edits similar to Git, then conflicts aren't an issue. -But if your use case needs to determine a single canonical version of a resource, you'll need to decide on a conflict resolution strategy to use at retrieval time. +But if your use case needs a single canonical version of a resource, you'll need to decide on a conflict resolution strategy to use at retrieval time. -If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the "latest" update? +If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp, which is guaranteed to [advance monotonically](https://doc.rust-lang.org/std/time/struct.Instant.html) for any honest agent. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the 'latest' update? These are two common patterns: -* Use an opinionated, deterministic definition of "latest" that can be calculated from the content of the update. +* Use an opinionated, deterministic definition of 'latest' that can be calculated from the content of the update. * Model your updates with a data structure that can automatically merge simultaneous updates, such as a [conflict-free replicated data type (CRDT)](https://crdt.tech/). ## Delete an entry @@ -227,9 +233,9 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the entry creation action is marked "dead". An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the entry creation action is marked 'dead'. An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) -In the future we plan to include a "purge" functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated entry creation action. +In the future we plan to include a 'purge' functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated entry creation action. Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 315614d10..3e3199161 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -14,7 +14,7 @@ This Build Guide organizes everything you need to know about developing Holochai 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 is given meaning by your application. +* 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. From 7c4f0642aa1fc09d77cbf919890e7b9752a3a47b Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 12:04:17 -0700 Subject: [PATCH 41/69] more edits on entry topic --- src/pages/build/guide/entries.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 3b87e4a2b..8994107d6 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -233,11 +233,11 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a `Delete` action is committed to the cell's source chain, and the entry creation action is marked 'dead'. An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a [`Delete` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Delete.html) is committed to the cell's source chain, and the entry creation action is marked 'dead'. An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) (see below). In the future we plan to include a 'purge' functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated entry creation action. -Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how data is shared in your app. +Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how what data becomes public in your app. ### Delete under the hood @@ -261,11 +261,13 @@ Calling `delete_entry` does the following: Coming from centralized software architectures, you might expect an entry to have a unique ID that can be used to reference it elsewhere. Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. -To identify the *contents* of an entry, use the entry's `EntryHash`. Remember that, if two entry creation actions write identical entry contents, the entries will collide in the DHT. You may want this or you may not, depending on the nature of your entry type. +To identify the _contents_ of an entry, use the entry's `EntryHash`. Remember that, if two entry creation actions write identical entry contents, the entries will collide in the DHT. You may want this or you may not, depending on the nature of your entry type. -A common pattern to identify an *instance* of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of the entry creation action which created the entry. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. +A common pattern to identify an _instance_ of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of its entry creation action instead. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. -Finally, you can reference an agent themselves via their `AgentPubKey`. This identifier is similar to `EntryHash` and `ActionHash` in that it's an identifier that you can use to reference agents in the same way you can reference entries and actions. +You can reference an agent via their `AgentPubKey`. This is a special type of DHT entry whose identifier is identical to its content --- that is, the agent's public key. You can use it just like an `EntryHash` and `ActionHash`. + +Finally, you can also use **external identifiers** (that is, IDs of data that's not in the DHT) as long as they're 32 bytes. It's up to you to determine how to interpret these identifiers in your front end. You can use any of these identifiers as a field in your entry types to model a many-to-one relationship, or you can use links between identifiers to model a one-to-many relationship. @@ -281,9 +283,9 @@ use movie_integrity::*; let maybe_record: Option = get( action_hash, - // Get the most up-to-date data from the DHT. You can also specify - // `GetOptions::content()`, which only gets the latest data if it doesn't - // already exist locally. + // Get the data and metadata directly from the DHT. You can also specify + // `GetOptions::content()`, which only accesses the DHT if the data at the + // supplied hash doesn't already exist locally. GetOptions::latest() )?; @@ -292,10 +294,11 @@ match maybe_record { // Not all types of action contain entry data, and if they do, it may // not be accessible, so `.entry()` may return nothing. It may also be // of an unexpected entry type, so it may not be deserializable to an - // instance of the expected Rust type. You can check for most of these - // edge cases by exploring the value of the `entry` field, which tells - // you the entry's status, but in this simple example we'll skip that - // and try to get and deserialize the entry. + // instance of the expected Rust type. You can find out how to check for + // most of these edge cases by exploring the documentation for the + // `Record` type, but in this simple example we'll skip that and assume + // that the action hash referenced an action with entry data attached + // to it. let maybe_movie: Option = record.entry().to_app_option(); match maybe_movie { @@ -313,7 +316,7 @@ match maybe_record { } ``` -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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) with the 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)`>`. +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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)`>`. ```rust use hdk::prelude::*; @@ -342,7 +345,7 @@ match maybe_details { } ``` -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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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. +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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 use hdk::prelude::*; @@ -385,6 +388,7 @@ If the scaffolder doesn't support your desired functionality, or is too low-leve - [hc-cooperative-content](https://github.com/mjbrisebois/hc-cooperative-content) ## Reference + - [hdi::prelude::hdk_entry_helper](https://docs.rs/hdi/latest/hdi/attr.hdk_entry_helper.html) - [hdi::prelude::hdk_entry_defs](https://docs.rs/hdi/latest/hdi/prelude/attr.hdk_entry_defs.html) - [hdi::prelude::entry_def](https://docs.rs/hdi/latest/hdi/prelude/entry_def/index.html) From 9a0815fd7976b50a74dd24ebe78515018f669a70 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Tue, 26 Mar 2024 13:18:59 -0700 Subject: [PATCH 42/69] fix formatting in some links --- src/pages/build/guide/entries.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 8994107d6..058ed1b2f 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -273,7 +273,7 @@ You can use any of these identifiers as a field in your entry types to model a m ## Retrieve an entry -Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html)] with the action hash. The return value is a `Result<`[`holochain_integrity_types::record::Record`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)`>`, where a `Record` is a pairing of an action and its optional entry data. +Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) with the action hash. The return value is a Result<[holochain_integrity_types::record::Record](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)>, where a `Record` is a pairing of an action and its optional entry data. You can also pass an entry hash to `get`, and the record returned will contain the _oldest live_ entry creation action that wrote it. @@ -316,7 +316,7 @@ match maybe_record { } ``` -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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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)`>`. +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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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)>. ```rust use hdk::prelude::*; From c36391d42465c9a349af480b32924dc604343285 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 28 Mar 2024 15:54:23 -0700 Subject: [PATCH 43/69] fix incomplete merge --- src/pages/build/guide/index.md | 2 +- src/scss/_svg-icons.scss | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index 327521628..ea6f10a5a 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -7,7 +7,7 @@ This guide is under construction. Expect more content to be rapidly published in !!! ::: intro ->>>>>>> main + This Build Guide organizes everything you need to know about developing Holochain applications into individual topics. Each topic page stands alone as a comprehensive guide to using a given feature or implementing a given functionality. There are lots of code examples which make it clear how to do something yet are generic enough to be universally useful. These examples may not cover every single use case, though, so we'll point you to the reference documentation often. ::: diff --git a/src/scss/_svg-icons.scss b/src/scss/_svg-icons.scss index b13096ba3..7d85ba5e2 100644 --- a/src/scss/_svg-icons.scss +++ b/src/scss/_svg-icons.scss @@ -88,5 +88,3 @@ $pageIcon: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 -150 } } } - ->>>>>>> main From f54ea21b860c6938778d27c1483c2337acba7c6e Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 28 Mar 2024 15:54:57 -0700 Subject: [PATCH 44/69] fix external links issue - plugin treats relative paths as hostnames --- src/pages/build/guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md index ea6f10a5a..b907ff39d 100644 --- a/src/pages/build/guide/index.md +++ b/src/pages/build/guide/index.md @@ -23,5 +23,5 @@ Shared data in a Holochain application is stored as a graph database of **bases* ::: topic-list ### Topics -* [Entries](entries/) --- creating, reading, updating, and deleting +* [Entries](/build/guide/entries/) --- creating, reading, updating, and deleting ::: \ No newline at end of file From bbaa300aabbefaf1fdef2dd412b991ba65c3c54e Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 28 Mar 2024 15:55:34 -0700 Subject: [PATCH 45/69] update SVGs -- they don't look right tho --- src/pages/build/guide/entries.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 058ed1b2f..b6c10182b 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -199,12 +199,16 @@ Holochain gives you this `update_entry` function, but is somewhat unopinionated You can also choose where to attach updates. 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. From a92860fad92f49d6ecd9935fb4d57f12887f5e83 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 28 Mar 2024 16:01:08 -0700 Subject: [PATCH 46/69] fix styling of SVG diagrams --- src/pages/build/guide/entries.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index b6c10182b..64d47fe6e 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -203,12 +203,12 @@ You can also choose where to attach updates. You can structure them as a 'list', I can't keep the original Mermaid in a comment because the arrows in the code break the comment tag early. But do a `git blame` or look for commit 309e4067939312a3bde0ff4dbf160417ec44a6c3 and you'll find it. --> -
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. From 225562422baf43cd265dabacb84c6a43337a7aaa Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Thu, 28 Mar 2024 16:12:27 -0700 Subject: [PATCH 47/69] editing Entry page for brevity -- could use some more work --- src/pages/build/guide/entries.md | 47 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 64d47fe6e..338398431 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -32,7 +32,18 @@ tocData: href: reference --- -An **entry** is structured data that's serialized and written as a blob of bytes to an agent's source chain via an **entry creation action**, which can either be a `Create` or `Update` action. It can be updated or deleted. Although entry data exists as its own entity on a DHT, its associated entry creation action is semantically considered part of it and is stored along with it. This let you distinguish separate writes of the same entry from each other. +::: intro +An **entry** is a blob of bytes that your application code gives meaning and structure to via serialization, deserialization, and validation. +::: + +## Entries and actions + +An entry is always paired with an **entry creation action** that tells you who authored it and when it was authored. Because of this, you don't usually need to include author and timestamp fields in your entries. There are two kinds of entry creation action: + +* `Create`, which creates a new piece of data in the application's database, and +* `Update`, which does the same as `Create` but also marks an existing piece of data as updated. + +The pairing of an entry and the action that created it is called a **record**, which is the basic unit of data in a Holochain application. ## Define an entry type @@ -67,18 +78,17 @@ enum EntryTypes { } ``` -Each variant in the enum holds the Rust type that defines the shape of the entry, and is implicitly marked with an `entry_def` proc macro which, if you specify it explicitly, lets you configure the given entry type further: +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. * A public entry type can be configured to expect a certain number of **required validations**, which is the number of [validation receipts](/resources/glossary/#validation-receipt) that an author tries to collect from authorities before they consider their entry published on the DHT. To do this, use the `required_validations = ` argument. -* You can override the name of an entry type, which defaults to the name of the enum variant. ```rust use hdi::prelude::*; #[hdk_entry_defs] enum EntryTypes { - #[entry_def(name = "moovee", required_validations = 7, )] + #[entry_def(required_validations = 7, )] Movie(Movie), // You can reuse your Rust type in another entry type if you like. In this @@ -90,11 +100,11 @@ enum EntryTypes { } ``` -This technique doesn't just generate the `entry_defs` callback for you. It also gives you an enum that you can use later when you're storing app data. This is important because, under the hood, an entry type consists of two bytes -- an integrity zome index and an entry def index. These are required whenever you want to write an entry. Instead of having to remember those values every time you store something, your coordinator zome can just import and use this enum, which already knows the right values. +This also gives you an enum that you can use later when you're storing app data. This is important because, under the hood, an entry type consists of two bytes -- an integrity zome index and an entry def index. These are required whenever you want to write an entry. Instead of having to remember those values every time you store something, your coordinator zome can just import and use this enum, which already knows how to convert each entry type to the right IDs. ## Create an entry -You can define CRUD functions in your integrity zome, but most of the time you'll want to define them in a [**coordinator zome**](/resources/glossary/#coordinator-zome). This is because an updated coordinator zome can be hot-swapped in a running application, whereas changes to an integrity zome result in a new DNA with a separate network and database from the previous DNA. +Most of the time you'll want to define your create, read, update, and delete (CRUD) functions in a [**coordinator zome**](/resources/glossary/#coordinator-zome) rather than the integrity zome that defines it. This is because a coordinator zome is easier to update in the wild than an integrity zome. Create an entry by calling [`hdk::prelude::create_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html). The entry will be serialized into a blob automatically, thanks to the `hdk_entry_helper` macro. @@ -126,7 +136,7 @@ let create_action_hash: ActionHash = create_entry( When the client calls a zome function that calls `create_entry`, Holochain does the following: 1. Prepare a **scratch space** for making an atomic set of changes to the source chain for the agent's cell. -2. Build a [`Create` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Create.html) that includes: +2. Build an entry creation action called [`Create`](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Create.html) that includes: * the author's public key, * a timestamp, * the action's sequence in the source chain and the previous action's hash, @@ -148,7 +158,7 @@ When the client calls a zome function that calls `create_entry`, Holochain does ## Update an Entry -Update an entry creation action (either a `Create` or an `Update`) by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html) with the old action hash and the new entry data: +Update an entry creation action by calling [`hdk::entry::update_entry`](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html) with the old action hash and the new entry data: ```rust use hdk::prelude::*; @@ -169,7 +179,7 @@ let update_action_hash: ActionHash = update_entry( )?; ``` -An [`Update` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Update.html) operates on an entry creation action (either a `Create` or an `Update`). It doesn't remove the original data from the DHT; instead, it gets attached to both the original entry and its entry creation action. As an entry creation action itself, it references the hash of the new entry so it can be retrieved from the DHT. +An [`Update` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Update.html) operates on an entry creation action (either a `Create` or an `Update`), not just an entry by itself. It doesn't remove the original data from the DHT; instead, it gets attached to both the original entry and its entry creation action. As an entry creation action itself, it references the hash of the new entry so it can be retrieved from the DHT. ### Update under the hood @@ -237,11 +247,11 @@ let delete_action_hash: ActionHash = delete_entry( )?; ``` -As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a [`Delete` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Delete.html) is committed to the cell's source chain, and the entry creation action is marked 'dead'. An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Any dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) (see below). +As with an update, this does _not_ actually remove data from the source chain or the DHT. Instead, a [`Delete` action](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/action/struct.Delete.html) is authored, which attaches to the entry creation action and marks it as 'dead'. An entry itself is only considered dead when all entry creation actions that created it are marked dead, and it can become live again in the future if a _new_ entry creation action writes it. Dead data can still be retrieved with [`hdk::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/fn.get_details.html) (see below). In the future we plan to include a 'purge' functionality. This will give agents permission to actually erase an entry from their DHT store, but not its associated entry creation action. -Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about how what data becomes public in your app. +Remember that, even once purge is implemented, it is impossible to force another person to delete data once they have seen it. Be deliberate about choosing what data becomes public in your app. ### Delete under the hood @@ -263,21 +273,18 @@ Calling `delete_entry` does the following: ## Identifiers on the DHT -Coming from centralized software architectures, you might expect an entry to have a unique ID that can be used to reference it elsewhere. Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier. - -To identify the _contents_ of an entry, use the entry's `EntryHash`. Remember that, if two entry creation actions write identical entry contents, the entries will collide in the DHT. You may want this or you may not, depending on the nature of your entry type. - -A common pattern to identify an _instance_ of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of its entry creation action instead. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. - -You can reference an agent via their `AgentPubKey`. This is a special type of DHT entry whose identifier is identical to its content --- that is, the agent's public key. You can use it just like an `EntryHash` and `ActionHash`. +Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier: -Finally, you can also use **external identifiers** (that is, IDs of data that's not in the DHT) as long as they're 32 bytes. It's up to you to determine how to interpret these identifiers in your front end. +* To identify the _contents_ of an entry, use the entry's `EntryHash`. Remember that, if two entry creation actions write identical entry contents, the entries will collide in the DHT. You may want this or you may not, depending on the nature of your entry type. +* A common pattern to identify an _instance_ of an entry (i.e., an entry authored by a specific agent at a specific time) is to use the `ActionHash` of its entry creation action instead. This gives you timestamp and authorship information for free, and can be a persistent way to identify the initial entry at the root of a tree of updates. +* You can reference an agent via their `AgentPubKey`. This is a special type of DHT entry whose identifier is identical to its content --- that is, the agent's public key. You can use it just like an `EntryHash` and `ActionHash`. +* Finally, you can also use **external identifiers** (that is, IDs of data that's not in the DHT) as long as they're 32 bytes, which is useful for hashes and public keys. It's up to you to determine how to handle these identifiers in your front end. You can use any of these identifiers as a field in your entry types to model a many-to-one relationship, or you can use links between identifiers to model a one-to-many relationship. ## Retrieve an entry -Get an entry creation action along with its entry data by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) with the action hash. The return value is a Result<[holochain_integrity_types::record::Record](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)>, where a `Record` is a pairing of an action and its optional entry data. +Get a record by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) with the hash of its entry creation action. The return value is a Result<[holochain_integrity_types::record::Record](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)>. You can also pass an entry hash to `get`, and the record returned will contain the _oldest live_ entry creation action that wrote it. From f00cbd7be2101c9936eb497ad1f9078e628caf88 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 10:36:12 -0700 Subject: [PATCH 48/69] remove resolved todo comment about exact steps of a create Co-authored-by: Matt Gabrenya --- src/pages/build/guide/entries.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 338398431..00950b661 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -269,7 +269,6 @@ Calling `delete_entry` does the following: 8. Return the zome function's return value to the client. 9. In the background, publish all newly created DHT operations to their respective authority agents. - ## Identifiers on the DHT From c4724bb6219ca878a5b839e7fa357668e71b3ddc Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 10:37:06 -0700 Subject: [PATCH 49/69] ditto Co-authored-by: Matt Gabrenya --- src/pages/build/guide/entries.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 00950b661..b9379a84d 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -201,7 +201,6 @@ Calling `update_entry` does the following: 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 patterns From 649285f1a3c19b1cd80f9c89f2815a61024f4c81 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 10:37:20 -0700 Subject: [PATCH 50/69] ditto Co-authored-by: Matt Gabrenya --- src/pages/build/guide/entries.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index b9379a84d..c759591e8 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -154,7 +154,6 @@ 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 From bf6d59a4e3d3726261c67608e4ac434f5026abe2 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 11:15:06 -0700 Subject: [PATCH 51/69] add further reading about distributed systems to entries and resources --- src/pages/build/guide/entries.md | 5 +++++ src/pages/resources/index.md | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index c759591e8..abc95001d 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -403,3 +403,8 @@ If the scaffolder doesn't support your desired functionality, or is too low-leve - [hdk::prelude::create_entry](https://docs.rs/hdk/latest/hdk/entry/fn.create_entry.html) - [hdk::prelude::update_entry](https://docs.rs/hdk/latest/hdk/entry/fn.update_entry.html) - [hdi::prelude::delete_entry](https://docs.rs/hdk/latest/hdk/entry/fn.delete_entry.html) + +## Further reading + +- [Core Concepts: CRUD actions](/concepts/6_crud_actions/) +- [CRDT.tech](https://crdt.tech), a resource for learning about conflict-free replicated data types diff --git a/src/pages/resources/index.md b/src/pages/resources/index.md index fb5c96f88..85431bb26 100644 --- a/src/pages/resources/index.md +++ b/src/pages/resources/index.md @@ -72,6 +72,7 @@ There are three main developer binaries, and one user-oriented binary. You can r The developer community has created some useful utilities, libraries, and reusable modules for you to use in your own apps. +* [Syn](https://github.com/holochain/syn) provides back-end and front-end libraries for creating real-time collaboration apps. * [Holochain Open Dev](https://github.com/holochain-open-dev/) is a collection of reusable zomes and template repos from the developer community. * [`profiles`](https://github.com/holochain-open-dev/profiles) lets you store user profile information. * [`peer-status`](https://github.com/holochain-open-dev/peer-status) lets peers communicate their status (e.g., 'online', 'busy', 'on holiday') with each other. @@ -99,4 +100,13 @@ Studying existing Holochain applications and tutorials can provide valuable insi While you'll learn a lot looking at the source code from the above GitHub projects, we've also produced some training material as a result of running courses in collaboration with our education partner Mythosthesia. * [Developer training materials](https://github.com/holochain-immersive) from past courses -* [Self-paced training course](https://resources.holochain.org/self-paced-training-signup/) in video format \ No newline at end of file +* [Self-paced training course](https://resources.holochain.org/self-paced-training-signup/) in video format + +## Concepts useful for understanding and building distributed systems + +Holochain builds on a wealth of research and knowledge about distributed systems. Here are some carefully picked resources to help you on your journey: + +* [Keeping CALM: When Distributed Consistency is Easy](https://arxiv.org/abs/1901.01930), a paper by Joseph M Hellerstein and Peter Alvaro that lays down the mathematical foundation for distributed systems that don't need coordination protocols, such as Holochain. +* [CRDT.tech](https://crdt.tech), a resource for learning about conflict-free replicated data types (CRDTs), data structures that let multiple users make concurrent updates to a resource with little to no manual conflict resolution. +* [Yjs](https://yjs.dev/) and [Automerge](https://automerge.org/), two CRDTs that can operate on arbitrary JSON. Syn (mentioned above under [Libraries](#libraries)) uses Automerge. +* [Local-first software: You own your data, in spite of the cloud](https://github.com/holochain/syn), a paper by Martin Kleppmann et al of Ink & Switch that explores the user experience of local-first software built on CRDTs. \ No newline at end of file From a11a46e6e6bbb5e8e9595c13891e4ee2d5b1b0f0 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 11:42:41 -0700 Subject: [PATCH 52/69] add links to live mermaid graphs in the todo comments --- src/pages/build/guide/entries.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index abc95001d..0a6118b55 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -208,14 +208,15 @@ Holochain gives you this `update_entry` function, but is somewhat unopinionated You can also choose where to attach updates. 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
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
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. From 72410ff68058e6b28ca8bd8504b1b240af35b948 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 12:45:11 -0700 Subject: [PATCH 53/69] improve language around monotonicity -- not sure I succeeded :D --- src/pages/build/guide/entries.md | 5 +++-- src/pages/resources/glossary.md | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 0a6118b55..69aa16f1d 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -227,7 +227,7 @@ It's up to you to decide whether two updates on the same entry or action are a c But if your use case needs a single canonical version of a resource, you'll need to decide on a conflict resolution strategy to use at retrieval time. -If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp, which is guaranteed to [advance monotonically](https://doc.rust-lang.org/std/time/struct.Instant.html) for any honest agent. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the 'latest' update? +If only the original author is permitted to update the entry, choosing the latest update is simple. Just choose the `Update` action with the most recent timestamp, which is guaranteed to [advance monotonically](/resources/glossary/#monotonicity) for any honest agent. But if multiple agents are permitted to update an entry, it gets more complicated. Two agents could make an update at exactly the same time (or their action timestamps might be wrong or falsified). So, how do you decide which is the 'latest' update? These are two common patterns: @@ -268,7 +268,6 @@ Calling `delete_entry` does the following: 8. Return the zome function's return value to the client. 9. In the background, publish all newly created DHT operations to their respective authority agents. - ## Identifiers on the DHT Holochain uses the hash of a piece of content as its unique ID. In practice, different kinds of hashes have different meaning and suitability to use as an identifier: @@ -282,6 +281,8 @@ You can use any of these identifiers as a field in your entry types to model a m ## Retrieve an entry +### Record only + Get a record by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) with the hash of its entry creation action. The return value is a Result<[holochain_integrity_types::record::Record](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)>. You can also pass an entry hash to `get`, and the record returned will contain the _oldest live_ entry creation action that wrote it. diff --git a/src/pages/resources/glossary.md b/src/pages/resources/glossary.md index 49bb87aca..0188d8f1d 100644 --- a/src/pages/resources/glossary.md +++ b/src/pages/resources/glossary.md @@ -581,7 +581,10 @@ A Holochain application design pattern, in which one [DHT](#distributed-hash-tab #### Logical monotonicity -The property of a set of facts whereby the truth of prior facts are never negated by the addition of later facts. [CALM](#consistency-as-logical-monotonicity-calm-theorem) relies on functions that exhibit this property. For example, Holochain's [source chain](#source-chain) and [DHT](#distributed-hash-table-dht) only add new data without removing old data, simulating deletions and modifications ([CRUD](#create-read-update-delete-crud)) by recording actions which override the status of, but don't remove, the data they refer to. +The property of a system whereby [monotonicity](#monotonicity) is applied to state changes. Practically this means that state changes are only accumulated, never forgotten, so that the system's final state results from the application of all accumulated state changes. [CALM](#consistency-as-logical-monotonicity-calm-theorem) systems such as Holochain are logically monotonic. Two examples of this in Holochain are: + +* An [agent](#agent)'s [source chain](#source-chain) is an event journal that only adds state change operations, never removes them. +* An application's [DHT](#distributed-hash-table-dht) only adds new data without removing old data, simulating deletions and modifications ([CRUD](#create-read-update-delete-crud)) by recording actions which override the status of, but don't remove, the data they refer to. #### Membrane @@ -614,6 +617,10 @@ An application architecture pattern that encourages small, single-purpose [back An extension to [countersigning](#countersigning), in which a number of optional witnesses are also involved as [counterparties](#counterparty) signing the session, a majority of which must sign in order for the session to complete. One optional witness must also be nominated as the session's [enzyme](#enzyme). +#### Monotonicity + +A property of a function whereby values are either non-decreasing or non-increasing (that is, values may stay the same, but if they change, they may only ever go up or go down). An example in Holochain can be found in the timestamps of an [agent](#agent)'s [source chain](#source-chain), where a source chain [action](#action) can never be earlier than the action that precedes it. See also [logical monotonicity](#logical-monotonicity). + #### Mutual sovereignty The relationship between the autonomy of the individual and the collective intentions of the group. A successful [commons](#commons) finds a healthy tension between these opposites. Holochain's design is based on this principle, empowering [participants](#participant) to control their own identity and responses to their peers by equipping each of them with a full copy of the application's code. This code constitutes a formal, executable definition of the group's rules and norms, as [DNA](#dna) modules, so by running the application a participant consents to become a member of the group and be bound by those rules and norms. From 34fb8e128fc3e949a65fe171c70190bb37349fee Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 13:06:39 -0700 Subject: [PATCH 54/69] reorganise read section of entries page --- src/pages/build/guide/entries.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 69aa16f1d..737895ff8 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -26,6 +26,10 @@ tocData: href: identifiers-on-the-dht - text: Retrieve an entry href: retrieve-an-entry + - text: By record only + href: by-record-only + - text: All records and links attached to an entry + href: all-records-and-links-attached-to-an-entry - text: Community CRUD libraries href: community-crud-libraries - text: Reference @@ -281,7 +285,7 @@ You can use any of these identifiers as a field in your entry types to model a m ## Retrieve an entry -### Record only +### By record only Get a record by calling [`hdk::entry::get`](https://docs.rs/hdk/latest/hdk/entry/fn.get.html) with the hash of its entry creation action. The return value is a Result<[holochain_integrity_types::record::Record](https://docs.rs/holochain_integrity_types/latest/holochain_integrity_types/record/struct.Record.html)>. @@ -326,6 +330,8 @@ match maybe_record { } ``` +### All records and links attached to an entry + 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::entry::get_details`](https://docs.rs/hdk/latest/hdk/entry/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)>. ```rust From 9943d49f2bb503877301bf3f38bb435c4bf45ac6 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 13:06:48 -0700 Subject: [PATCH 55/69] add mention of scaffolding tool --- src/pages/build/guide/entries.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/build/guide/entries.md b/src/pages/build/guide/entries.md index 737895ff8..5f70d4eaa 100644 --- a/src/pages/build/guide/entries.md +++ b/src/pages/build/guide/entries.md @@ -395,6 +395,10 @@ match maybe_details { } ``` +## Scaffolding an entry type and CRUD API + +The Holochain dev tool command `hc scaffold entry-type ` generates the code for a simple entry type and a CRUD API. It presents an interface that lets you define a struct and its fields, then asks you to choose whether to implement update and delete functions for it along with the default create and read functions. + ## Community CRUD libraries If the scaffolder doesn't support your desired functionality, or is too low-level, there are some community-maintained libraries that offer opinionated and high-level ways to work with entries. Some of them also offer permissions management. From 5cf1e8e0478d64ec87ee8fa8d66bde44f616a7a0 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 13:17:33 -0700 Subject: [PATCH 56/69] fix issue in entries on-page nav --- src/pages/_data/navigation/mainNav.json5 | 6 ++--- src/pages/_includes/widgets/home-tiles.njk | 4 ++-- src/pages/build/{guide => }/entries.md | 0 src/pages/build/guide/index.md | 27 ---------------------- src/pages/build/index.md | 25 +++++++++++++++++--- 5 files changed, 26 insertions(+), 36 deletions(-) rename src/pages/build/{guide => }/entries.md (100%) delete mode 100644 src/pages/build/guide/index.md diff --git a/src/pages/_data/navigation/mainNav.json5 b/src/pages/_data/navigation/mainNav.json5 index 9647871ed..e11167398 100644 --- a/src/pages/_data/navigation/mainNav.json5 +++ b/src/pages/_data/navigation/mainNav.json5 @@ -26,10 +26,8 @@ ] }, { title: "Build", url: "/build/", children: [ - { title: "Guide", url: "/build/guide/", children: [ - { title: "Working with Data", children: [ - { title: "Entries", url: "/build/guide/entries" }, - ]}, + { title: "Working with Data", children: [ + { title: "Entries", url: "/build/entries/" }, ]}, ] }, diff --git a/src/pages/_includes/widgets/home-tiles.njk b/src/pages/_includes/widgets/home-tiles.njk index 66fe0aaec..8dbcde297 100644 --- a/src/pages/_includes/widgets/home-tiles.njk +++ b/src/pages/_includes/widgets/home-tiles.njk @@ -16,11 +16,11 @@
{% endlinkTile %} - {% linkTile "/build/guide/", "home-tile" %} + {% linkTile "/build/", "home-tile" %} {{ craneIcon() }}

Build Guide

-

Learn how to write Holochain applications

+

Learn how to write and deploy Holochain applications

{% endlinkTile %} diff --git a/src/pages/build/guide/entries.md b/src/pages/build/entries.md similarity index 100% rename from src/pages/build/guide/entries.md rename to src/pages/build/entries.md diff --git a/src/pages/build/guide/index.md b/src/pages/build/guide/index.md deleted file mode 100644 index b907ff39d..000000000 --- a/src/pages/build/guide/index.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Holochain Build Guide ---- - -!!! note In progress -This guide is under construction. Expect more content to be rapidly published in the first half of 2024. -!!! - -::: intro - -This Build Guide organizes everything you need to know about developing Holochain applications into individual topics. Each topic page stands alone as a comprehensive guide to using a given feature or implementing a given functionality. There are lots of code examples which make it clear how to do something yet are generic enough to be universally useful. These examples may not cover every single use case, though, so we'll point you to the reference documentation often. -::: - -## 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 - -* [Entries](/build/guide/entries/) --- creating, reading, updating, and deleting -::: \ No newline at end of file diff --git a/src/pages/build/index.md b/src/pages/build/index.md index a21293481..3799bd542 100644 --- a/src/pages/build/index.md +++ b/src/pages/build/index.md @@ -1,7 +1,26 @@ --- -title: Build on Holochain +title: Holochain Build Guide --- -Now that you've learned Holochain's [core concepts](/concepts/1_the_basics), [installed Holochain](/get-started/#2-installing-holochain-development-environment), and [built a basic hApp](/get-started/3-forum-app-tutorial/), it's time to deepen your Holochain knowledge to the point where you feel capable of using its full feature set to build complex applications. +!!! note In progress +This guide is under construction. Expect more content to be rapidly published in the first half of 2024. +!!! -The [Build Guide](/build/guide/) expands on the Core Concepts guide with code examples and a deeper level of detail on how to use various APIs and what they're doing in the background. Each page in this guide brings together all the functions and concepts you need in order to get a particular thing done. \ No newline at end of file +::: intro +This Build Guide organizes everything you need to know about developing Holochain applications into individual topics. Each topic page stands alone as a comprehensive guide to using a given feature or implementing a given functionality. There are lots of code examples which make it clear how to do something yet are generic enough to be universally useful. These examples may not cover every single use case, though, so we'll point you to the reference documentation often. +::: + +## 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 + +* [Entries](/build/entries/) --- creating, reading, updating, and deleting +::: \ No newline at end of file From 4d73a847cfefaa3205f79f6e18e2d95a38408a54 Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 13:40:52 -0700 Subject: [PATCH 57/69] fix broken on-page nav hierarchy for entries --- src/pages/build/entries.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/build/entries.md b/src/pages/build/entries.md index 5f70d4eaa..811e06e95 100644 --- a/src/pages/build/entries.md +++ b/src/pages/build/entries.md @@ -26,6 +26,7 @@ tocData: href: identifiers-on-the-dht - text: Retrieve an entry href: retrieve-an-entry + children: - text: By record only href: by-record-only - text: All records and links attached to an entry From 1feddd62d0299940bae56eb15a1442cb7c0ca45a Mon Sep 17 00:00:00 2001 From: Paul d'Aoust Date: Fri, 29 Mar 2024 14:00:33 -0700 Subject: [PATCH 58/69] fix styling of nav hierarchy --- src/pages/_includes/widgets/navigation.njk | 6 ++-- src/scss/_navigation.scss | 42 ++++++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/pages/_includes/widgets/navigation.njk b/src/pages/_includes/widgets/navigation.njk index daf1b187e..24870f8c2 100644 --- a/src/pages/_includes/widgets/navigation.njk +++ b/src/pages/_includes/widgets/navigation.njk @@ -4,11 +4,11 @@